Skip to content

Instantly share code, notes, and snippets.

@Matt54
Created September 14, 2024 00:41
Show Gist options
  • Save Matt54/5faee4bbbb935c334ebf8df7b44db28d to your computer and use it in GitHub Desktop.
Save Matt54/5faee4bbbb935c334ebf8df7b44db28d to your computer and use it in GitHub Desktop.
RealityView with raycasting from the index finger to a sphere, showing a color change on hit
import SwiftUI
import RealityKit
import ARKit
struct HandTrackingRaycastView: View {
@State var jointPositions: [HandSkeleton.JointName: SIMD3<Float>] = [:]
@State var sphereEntity: ModelEntity?
@State var debugCylinderEntity: ModelEntity?
let sphereRadius: Float = 0.1
var body: some View {
RealityView { content in
let sphere = createSphere()
content.add(sphere)
let cylinder = createCylinder()
sphereEntity = sphere
debugCylinderEntity = cylinder
content.add(cylinder)
}
.task {
await setupHandTracking()
}
}
func createSphere() -> ModelEntity {
let sphere = ModelEntity(mesh: .generateSphere(radius: sphereRadius),
materials: [SimpleMaterial(color: .blue, isMetallic: false)])
sphere.collision = CollisionComponent(shapes: [.generateSphere(radius: sphereRadius)])
sphere.position = SIMD3<Float>(0.0, 1.3, -2.0)
sphere.position.z = -2.0
return sphere
}
func createCylinder() -> ModelEntity {
let cylinderMesh = MeshResource.generateCylinder(height: 1, radius: 0.002)
let material = SimpleMaterial(color: .red, isMetallic: false)
let entity = ModelEntity(mesh: cylinderMesh, materials: [material])
return entity
}
private func setupHandTracking() async {
let session = ARKitSession()
let handTracking = HandTrackingProvider()
do {
try await session.run([handTracking])
for await update in handTracking.anchorUpdates {
let handAnchor = update.anchor
if handAnchor.chirality == .right {
updateJointPositions(for: handAnchor)
updateForHandPosition()
}
}
} catch {
print("Error setting up hand tracking: \(error)")
}
}
func updateJointPositions(for handAnchor: HandAnchor) {
jointPositions = Dictionary(uniqueKeysWithValues:
HandSkeleton.JointName.allCases.compactMap { jointName in
guard let joint = handAnchor.handSkeleton?.joint(jointName) else { return nil }
let worldPosition = handAnchor.originFromAnchorTransform * joint.anchorFromJointTransform.columns.3
return (jointName, SIMD3<Float>(worldPosition.x, worldPosition.y, worldPosition.z))
}
)
}
func updateForHandPosition() {
guard let indexTipPosition = jointPositions[.indexFingerTip],
let indexPIPPosition = jointPositions[.indexFingerIntermediateBase] else {
return
}
let rayDirection = simd_normalize(indexTipPosition - indexPIPPosition)
let rayLength: Float = 10 // 10 meters long ray
let rayEnd = indexPIPPosition + rayDirection * rayLength
updateSphere(rayStart: indexPIPPosition, rayDirection: rayDirection)
updateCylinder(rayStart: indexPIPPosition, rayEnd: rayEnd)
}
func updateSphere(rayStart: SIMD3<Float>, rayDirection: SIMD3<Float>) {
guard let sphere = sphereEntity else { return }
let intersection = rayIntersectsSphere(rayStart: rayStart,
rayDirection: rayDirection,
sphereCenter: sphere.position,
sphereRadius: sphereRadius)
if intersection {
sphere.model?.materials = [SimpleMaterial(color: .green, isMetallic: false)]
} else {
sphere.model?.materials = [SimpleMaterial(color: .blue, isMetallic: false)]
}
}
func updateCylinder(rayStart: SIMD3<Float>, rayEnd: SIMD3<Float>) {
guard let debugCylinder = debugCylinderEntity else { return }
positionAndOrientCylinder(debugCylinder, from: rayStart, to: rayEnd)
}
private func rayIntersectsSphere(rayStart: SIMD3<Float>, rayDirection: SIMD3<Float>, sphereCenter: SIMD3<Float>, sphereRadius: Float) -> Bool {
let originToCenter = rayStart - sphereCenter
let directionMagnitudeSquared = simd_dot(rayDirection, rayDirection)
let originToCenterProjection = 2.0 * simd_dot(originToCenter, rayDirection)
let perpDistanceSquared = simd_dot(originToCenter, originToCenter) - sphereRadius * sphereRadius
let discriminant = originToCenterProjection * originToCenterProjection - 4 * directionMagnitudeSquared * perpDistanceSquared
return discriminant > 0
}
private func positionAndOrientCylinder(_ cylinder: ModelEntity, from start: SIMD3<Float>, to end: SIMD3<Float>) {
let direction = end - start
let distance = simd_length(direction)
let midpoint = (start + end) / 2
cylinder.position = midpoint
cylinder.scale = [1, distance, 1]
let yAxis = simd_normalize(direction)
let xAxis = simd_normalize(simd_cross([0, 1, 0], yAxis))
let zAxis = simd_cross(xAxis, yAxis)
let rotationMatrix = simd_float3x3(columns: (xAxis, yAxis, zAxis))
cylinder.transform.rotation = simd_quaternion(rotationMatrix)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment