The skeletal model moves

I want to make a model with added bones move by dragging it with gestures,This is a model exported using Blender, What I understand is using IKComponent, but I don't know how to use it specifically

Answered by Vision Pro Engineer in 834762022

Hi @LazyCoder

Here's a snippet to get you started. The snippet enables you to manipulate a humanoid robot by dragging sphere's attached to its hands ands feet. The snippet uses the model available for download here. For more information on the model format, read Rigging a Model for Motion Capture.

To run the snippet, download the model and include it in your project. The snippet looks for a model named "biped_robot".

Please reach out if you have followup questions. If this answers your question please accept the answer.

Here's the source for ImmersiveView. I'll share the code for the SampleModel in my next response.

struct ImmersiveView: View {
    @State fileprivate var model = SampleModel()
    @State var subscription: EventSubscription?
    @State var humanEntity = Entity()
    
    @State var leftHandTarget:ModelEntity
    @State var rightHandTarget:ModelEntity
    @State var leftFootTarget:ModelEntity
    @State var rightFootTarget:ModelEntity
    let root = Entity()
    
    init() {
        let alpha = 0.65
        leftHandTarget = Self.createTargetEntity(color: .blue.withAlphaComponent(alpha))
        rightHandTarget = Self.createTargetEntity(color: .red.withAlphaComponent(alpha))
        leftFootTarget = Self.createTargetEntity(color: .orange.withAlphaComponent(alpha))
        rightFootTarget = Self.createTargetEntity(color: .green.withAlphaComponent(alpha))
    }
    
    var body: some View {
        RealityView { content in
            guard let character = try? await ModelEntity(named: "biped_robot") else {
                fatalError("Failed to load character model.")
            }
            
            character.transform.translation = [0, 0, -1]
            character.transform.scale = SIMD3(repeating: 1.0)
            humanEntity = character
            
            content.add(character)
            
            try? model.setUpIK(character)
            
            subscription = content.subscribe(to: SceneEvents.Update.self) { event in
                model.updateIK()
            }
            
            root.addChild(character)
            
            addTargetEntities()
            
            root.position = [0, 1.5, -1]
            content.add(root)
            
            initializePositions(for: character)
        }.gesture(DragGesture().targetedToAnyEntity()
            .onChanged { event in
                let entity = event.entity
                guard let parent = entity.parent else {return}
                
                entity.position = event.convert(event.location3D, from: .local, to: parent)
                syncPositions()
            }
        )
    }
    
    private func addTargetEntities() {
        setInitialModelPose()
        
        root.addChild(leftHandTarget)
        root.addChild(rightHandTarget)
        root.addChild(leftFootTarget)
        root.addChild(rightFootTarget)
    }
    
    private func setInitialModelPose() {
        leftHandTarget.position = [1.014, 0.543, -1.109]
        rightHandTarget.position = [-1.014, 0.543, -1.109]
        leftFootTarget.position = [0.31, -1.063, -1.07]
        rightFootTarget.position = [-0.31, -1.063, -1.07]
    }
    
    private func initializePositions(for character: ModelEntity) {
        Task {
            // wait for model to load
            var attempts = 0
            let maxAttempts = 10
            let retryDelayInMilliseconds = 100
            
            while model.setUpTargets(character) == false {
                attempts += 1
                
                if attempts >= maxAttempts {
                    fatalError("Model failed to load in time.")
                }
                
                try? await Task.sleep(for: .milliseconds(retryDelayInMilliseconds))
            }
            
            syncPositions()
        }
    }
    
    private func syncPositions() {
        model.setTargetPosition(limb: .leftHand, targetPosition: leftHandTarget.position(relativeTo: humanEntity))
        model.setTargetPosition(limb: .rightHand, targetPosition: rightHandTarget.position(relativeTo: humanEntity))
        model.setTargetPosition(limb: .leftFoot, targetPosition: leftFootTarget.position(relativeTo: humanEntity))
        model.setTargetPosition(limb: .rightFoot, targetPosition: rightFootTarget.position(relativeTo: humanEntity))
    }
    
    private static func createTargetEntity(color: UIColor) -> ModelEntity {
        let radius:Float = 0.05
        let entity = ModelEntity(
            mesh: .generateSphere(radius: radius),
            materials: [SimpleMaterial(color: color, roughness: 0.9, isMetallic: false)]
        )
        
        entity.components.set(CollisionComponent(shapes: [.generateSphere(radius: radius)]))
        entity.components.set(InputTargetComponent())
        entity.components.set(HoverEffectComponent())
        
        return entity
    }
}
Accepted Answer

Hi @LazyCoder

Here's a snippet to get you started. The snippet enables you to manipulate a humanoid robot by dragging sphere's attached to its hands ands feet. The snippet uses the model available for download here. For more information on the model format, read Rigging a Model for Motion Capture.

To run the snippet, download the model and include it in your project. The snippet looks for a model named "biped_robot".

Please reach out if you have followup questions. If this answers your question please accept the answer.

Here's the source for ImmersiveView. I'll share the code for the SampleModel in my next response.

struct ImmersiveView: View {
    @State fileprivate var model = SampleModel()
    @State var subscription: EventSubscription?
    @State var humanEntity = Entity()
    
    @State var leftHandTarget:ModelEntity
    @State var rightHandTarget:ModelEntity
    @State var leftFootTarget:ModelEntity
    @State var rightFootTarget:ModelEntity
    let root = Entity()
    
    init() {
        let alpha = 0.65
        leftHandTarget = Self.createTargetEntity(color: .blue.withAlphaComponent(alpha))
        rightHandTarget = Self.createTargetEntity(color: .red.withAlphaComponent(alpha))
        leftFootTarget = Self.createTargetEntity(color: .orange.withAlphaComponent(alpha))
        rightFootTarget = Self.createTargetEntity(color: .green.withAlphaComponent(alpha))
    }
    
    var body: some View {
        RealityView { content in
            guard let character = try? await ModelEntity(named: "biped_robot") else {
                fatalError("Failed to load character model.")
            }
            
            character.transform.translation = [0, 0, -1]
            character.transform.scale = SIMD3(repeating: 1.0)
            humanEntity = character
            
            content.add(character)
            
            try? model.setUpIK(character)
            
            subscription = content.subscribe(to: SceneEvents.Update.self) { event in
                model.updateIK()
            }
            
            root.addChild(character)
            
            addTargetEntities()
            
            root.position = [0, 1.5, -1]
            content.add(root)
            
            initializePositions(for: character)
        }.gesture(DragGesture().targetedToAnyEntity()
            .onChanged { event in
                let entity = event.entity
                guard let parent = entity.parent else {return}
                
                entity.position = event.convert(event.location3D, from: .local, to: parent)
                syncPositions()
            }
        )
    }
    
    private func addTargetEntities() {
        setInitialModelPose()
        
        root.addChild(leftHandTarget)
        root.addChild(rightHandTarget)
        root.addChild(leftFootTarget)
        root.addChild(rightFootTarget)
    }
    
    private func setInitialModelPose() {
        leftHandTarget.position = [1.014, 0.543, -1.109]
        rightHandTarget.position = [-1.014, 0.543, -1.109]
        leftFootTarget.position = [0.31, -1.063, -1.07]
        rightFootTarget.position = [-0.31, -1.063, -1.07]
    }
    
    private func initializePositions(for character: ModelEntity) {
        Task {
            // wait for model to load
            var attempts = 0
            let maxAttempts = 10
            let retryDelayInMilliseconds = 100
            
            while model.setUpTargets(character) == false {
                attempts += 1
                
                if attempts >= maxAttempts {
                    fatalError("Model failed to load in time.")
                }
                
                try? await Task.sleep(for: .milliseconds(retryDelayInMilliseconds))
            }
            
            syncPositions()
        }
    }
    
    private func syncPositions() {
        model.setTargetPosition(limb: .leftHand, targetPosition: leftHandTarget.position(relativeTo: humanEntity))
        model.setTargetPosition(limb: .rightHand, targetPosition: rightHandTarget.position(relativeTo: humanEntity))
        model.setTargetPosition(limb: .leftFoot, targetPosition: leftFootTarget.position(relativeTo: humanEntity))
        model.setTargetPosition(limb: .rightFoot, targetPosition: rightFootTarget.position(relativeTo: humanEntity))
    }
    
    private static func createTargetEntity(color: UIColor) -> ModelEntity {
        let radius:Float = 0.05
        let entity = ModelEntity(
            mesh: .generateSphere(radius: radius),
            materials: [SimpleMaterial(color: color, roughness: 0.9, isMetallic: false)]
        )
        
        entity.components.set(CollisionComponent(shapes: [.generateSphere(radius: radius)]))
        entity.components.set(InputTargetComponent())
        entity.components.set(HoverEffectComponent())
        
        return entity
    }
}

Here's the source for SampleModel.

private class SampleModel {
    var humanEntity: ModelEntity?
    
    // Define entities to manipulate the model.
    var leftHandTargetPosition = SIMD3<Float>()
    var rightHandTargetPosition = SIMD3<Float>()
    var leftFootTargetPosition = SIMD3<Float>()
    var rightFootTargetPosition = SIMD3<Float>()
    
    var modelInitialized = false
    var targetsInitialized = false
    
    enum Joints {
        case hips
        case chest
        case leftHand
        case rightHand
        case leftFoot
        case rightFoot
        
        var name: String {
            switch self {
                
            case .hips:
                "root/hips_joint"
            case .chest:
                "root/hips_joint/spine_1_joint/spine_2_joint/spine_3_joint/spine_4_joint/spine_5_joint/spine_6_joint/spine_7_joint"
            case .leftHand:
                "root/hips_joint/spine_1_joint/spine_2_joint/spine_3_joint/spine_4_joint/spine_5_joint/spine_6_joint/spine_7_joint/left_shoulder_1_joint/left_arm_joint/left_forearm_joint/left_hand_joint"
            case .rightHand:
                "root/hips_joint/spine_1_joint/spine_2_joint/spine_3_joint/spine_4_joint/spine_5_joint/spine_6_joint/spine_7_joint/right_shoulder_1_joint/right_arm_joint/right_forearm_joint/right_hand_joint"
            case .leftFoot:
                "root/hips_joint/left_upLeg_joint/left_leg_joint/left_foot_joint"
            case .rightFoot:
                "root/hips_joint/right_upLeg_joint/right_leg_joint/right_foot_joint"
            }
        }
        
        var constraintName: String {
            switch self {
                
            case .hips:
                "hips_constraint"
            case .chest:
                "chest_constraint"
            case .leftHand:
                "left_hand_constraint"
            case .rightHand:
                "right_hand_constraint"
            case .leftFoot:
                "left_foot_constraint"
            case .rightFoot:
                "right_foot_constraint"
            }
        }
    }
    
    enum Limb {
        case leftHand
        case rightHand
        case leftFoot
        case rightFoot
    }
    
    func setTargetPosition(limb: Limb, targetPosition: SIMD3<Float>) {
        switch limb {
        case .leftHand:
            leftHandTargetPosition = targetPosition
        case .rightHand:
            rightHandTargetPosition = targetPosition
        case .leftFoot:
            leftFootTargetPosition = targetPosition
        case .rightFoot:
            rightFootTargetPosition = targetPosition
        }
    }
    
    @MainActor
    func setUpTargets(_ entity: ModelEntity) -> Bool {
        // Get the limb positions in model space.
        guard let leftHandModelSpace = entity.pins.set(named: "leftHand", skeletalJointName: Joints.leftHand.name).position,
              let rightHandModelSpace = entity.pins.set(named: "rightHand", skeletalJointName: Joints.rightHand.name).position,
              let leftFootModelSpace = entity.pins.set(named: "leftFoot", skeletalJointName: Joints.leftFoot.name).position,
              let rightFootModelSpace = entity.pins.set(named: "rightFoot", skeletalJointName: Joints.rightFoot.name).position else {return false}
        
        leftHandTargetPosition  = leftHandModelSpace
        rightHandTargetPosition = rightHandModelSpace
        leftFootTargetPosition  = leftFootModelSpace
        rightFootTargetPosition = rightFootModelSpace
        
        targetsInitialized = true
        
        return true
    }
    
    func setUpIK(_ entity: ModelEntity) throws {
        // Fetch skeleton for rig
        var skeletonIterator = entity.model?.mesh.contents.skeletons.makeIterator()
        guard let modelSkeleton = skeletonIterator?.next() else {
            fatalError("Skeleton not found in model")
        }
        
        // Start with empty rig for the given model skeleton.
        var rig = try IKRig(for: modelSkeleton)
        
        /// Update global rig settings.
        rig.maxIterations = 30
        rig.globalFkWeight = 0.02
        
        // Define constraints for the rig.
        rig.constraints = [
            .parent(named: Joints.hips.constraintName, on: Joints.hips.name, positionWeight: SIMD3(repeating: 90.0), orientationWeight: SIMD3(repeating: 90.0)),
            .parent(named: Joints.chest.constraintName, on: Joints.chest.name, positionWeight: SIMD3(repeating: 120.0), orientationWeight: SIMD3(repeating: 120.0)),
            .point(named: Joints.leftHand.constraintName,  on: Joints.leftHand.name,  positionWeight: SIMD3(repeating: 10.0)),
            .point(named: Joints.rightHand.constraintName, on: Joints.rightHand.name, positionWeight: SIMD3(repeating: 10.0)),
            .point(named: Joints.leftFoot.constraintName,  on: Joints.leftFoot.name,  positionWeight: SIMD3(repeating: 10.0)),
            .point(named: Joints.rightFoot.constraintName, on: Joints.rightFoot.name, positionWeight: SIMD3(repeating: 10.0)),
        ]
        
        let resource = try IKResource(rig: rig)
        
        entity.components.set(IKComponent(resource: resource))
        humanEntity = entity
        
        modelInitialized = true
    }
    
    @MainActor
    func updateIK() {
        guard modelInitialized,
              targetsInitialized,
              let humanEntity,
              let ikComponent = humanEntity.components[IKComponent.self] else { return }
        
        ikComponent.solvers[0].constraints[Joints.leftHand.constraintName]?.target.translation = leftHandTargetPosition
        ikComponent.solvers[0].constraints[Joints.leftHand.constraintName]?.animationOverrideWeight.position = 1.0
        
        ikComponent.solvers[0].constraints[Joints.rightHand.constraintName]?.target.translation = rightHandTargetPosition
        ikComponent.solvers[0].constraints[Joints.rightHand.constraintName]?.animationOverrideWeight.position = 1.0
        
        ikComponent.solvers[0].constraints[Joints.leftFoot.constraintName]?.target.translation = leftFootTargetPosition
        ikComponent.solvers[0].constraints[Joints.leftFoot.constraintName]?.animationOverrideWeight.position = 1.0
        
        ikComponent.solvers[0].constraints[Joints.rightFoot.constraintName]?.target.translation = rightFootTargetPosition
        ikComponent.solvers[0].constraints[Joints.rightFoot.constraintName]?.animationOverrideWeight.position = 1.0
        
        humanEntity.components.set(ikComponent)
    }
}
The skeletal model moves
 
 
Q