Hi @AVPDeveloper
Here's how you can use RealityKit's Entity Component System to update the target position of an IK constraint in response to a drag gesture.
First, create a component and a system responsible for updating the target position of a given IK constraint every frame.
/// Stores the name of an IK constraint and its target position, along with an optional helper entity for visualizing the target position.
struct IKTargetPositionerComponent: Component {
let targetConstraintName: String
var targetPosition: SIMD3<Float>
let targetVisualizerEntity: Entity?
}
/// Updates the target position of an IK constraint every frame.
struct IKTargetPositionerSystem: System {
let query: EntityQuery = EntityQuery(where: .has(IKTargetPositionerComponent.self))
init(scene: RealityKit.Scene) {}
func update(context: SceneUpdateContext) {
// Get all entities with an `IKTargetPositionerComponent`.
let entities = context.entities(matching: self.query, updatingSystemWhen: .rendering)
for entity in entities {
// Get the necessary IK components attached to the entity.
guard let ikComponent = entity.components[IKComponent.self],
let ikTargetPositionerComponent = entity.components[IKTargetPositionerComponent.self] else {
assertionFailure("Entity is missing required IK components.")
return
}
// Set the target position of the target constraint.
ikComponent.solvers[0].constraints[ikTargetPositionerComponent.targetConstraintName]!.target.translation = ikTargetPositionerComponent.targetPosition
// Fully override the rest pose, allowing IK to fully move the joint to the target position.
ikComponent.solvers[0].constraints[ikTargetPositionerComponent.targetConstraintName]!.animationOverrideWeight.position = 1.0
// Position the target visualizer entity at the target position.
ikTargetPositionerComponent.targetVisualizerEntity?.position = ikTargetPositionerComponent.targetPosition
// Apply component changes.
entity.components.set(ikComponent)
}
}
}
Be sure to register the IKTargetPositionerSystem
. You can do this in the initializer of your view.
init() {
IKTargetPositionerSystem.registerSystem()
}
Next, add an InputTargetComponent
and a collision shape to the hand joint entity so that it can be targeted by gestures.
I modified the constraint initialization code to achieve this like so:
// Get the index of the hand joint.
// May be different for your custom skeleton.
let handJointIndex = customSkeleton.joints.count - 1
// Define the joint constraints for the rig.
let rootConstraintName = "root_constraint"
let handConstraintName = "end_constraint"
customIKRig.constraints = [
// Constrain the root joint's position.
.point(named: rootConstraintName, on: customSkeleton.joints.first!.name, positionWeight: [5.0, 5.0, 5.0]),
// Add a point demand to the hand joint.
// This will be used to set a target position for the hand.
.point(named: handConstraintName, on: customSkeleton.joints[handJointIndex].name)
]
// Add an input target and collision shape to the hand joint entity
// so that it can be the target of a drag gesture.
handJointEntity = jointEntities[handJointIndex]
handJointEntity.components.set(InputTargetComponent())
handJointEntity.generateCollisionShapes(recursive: false)
Where, for the sake of this example, handJointEntity
is defined as a state variable so that it can be accessed in the drag gesture in the final step.
@State var handJointEntity: Entity = Entity()
Then, add the IKTargetPositionerComponent
to the skeletonContainerEntity
.
// Create a helper entity to visualize the target position.
let targetVisualizerEntity = ModelEntity(mesh: .generateSphere(radius: 0.015), materials: [SimpleMaterial(color: .magenta, isMetallic: false)])
targetVisualizerEntity.components.set(OpacityComponent(opacity: 0.5))
targetVisualizerEntity.setParent(skeletonContainerEntity)
// Create the IK target positioner component and add it to the skeleton container entity.
skeletonContainerEntity.components.set(IKTargetPositionerComponent(targetConstraintName: handConstraintName,
targetPosition: handJointEntity.position(relativeTo: skeletonContainerEntity),
targetVisualizerEntity: targetVisualizerEntity))
The targetVisualizerEntity
is useful for visualizing the target position and making sure everything is working correctly, but feel free to leave it out in your final implementation.
Finally, modify your drag gesture to update the targetPosition
of the skeletonContainerEntity
's IKTargetPositionerComponent
.
.gesture(
DragGesture()
.targetedToEntity(handJointEntity)
.onChanged { value in
let currentHandPosition = value.convert(value.location3D, from: .local, to: skeletonContainerEntity)
skeletonContainerEntity.components[IKTargetPositionerComponent.self]?.targetPosition = currentHandPosition
}
)
Let me know if you run into any issues getting this to work!