feat: Implement ArmBot simulation with IK animation and event handling
- Added ArmBot component to manage ArmBot instances in the simulation. - Created ArmBotInstances component to render individual ArmBot models. - Developed IKAnimationController for handling inverse kinematics during animations. - Introduced IkInstances component to load and manage IK-enabled arm models. - Defined simulation types for ArmBot events and connections in TypeScript. - Enhanced type definitions for better clarity and maintainability.
This commit is contained in:
128
app/src/modules/simulation/armbot/IkInstances.tsx
Normal file
128
app/src/modules/simulation/armbot/IkInstances.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import * as THREE from "three";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useLoader } from "@react-three/fiber";
|
||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { clone } from "three/examples/jsm/utils/SkeletonUtils";
|
||||
import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver";
|
||||
import IKAnimationController from "./IKAnimationController";
|
||||
|
||||
const IkInstances = ({ modelUrl, position, rotation }: { modelUrl: string; position: [number, number, number]; rotation: [number, number, number]; }) => {
|
||||
const [ikSolver, setIkSolver] = useState<any>(null);
|
||||
const [selectedTrigger, setSelectedTrigger] = useState<string>("idle");
|
||||
const gltf = useLoader(GLTFLoader, modelUrl, (loader) => {
|
||||
const draco = new DRACOLoader();
|
||||
draco.setDecoderPath(
|
||||
"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/"
|
||||
);
|
||||
loader.setDRACOLoader(draco);
|
||||
});
|
||||
const cloned = useMemo(() => clone(gltf.scene), [gltf]);
|
||||
const groupRef = useRef<any>(null);
|
||||
const [selectedArm, setSelectedArm] = useState<THREE.Group>();
|
||||
const targetBoneName = "Target";
|
||||
const skinnedMeshName = "link_0";
|
||||
|
||||
const process = useMemo(() => [
|
||||
{
|
||||
trigger: "Trigger1",
|
||||
start: new THREE.Vector3(-0.75, 1.5, -2.2),
|
||||
end: new THREE.Vector3(0, 1.2, 2.2),
|
||||
speed: 0.25,
|
||||
},
|
||||
{
|
||||
trigger: "Trigger2",
|
||||
start: new THREE.Vector3(0, 1.2, 2.2),
|
||||
end: new THREE.Vector3(0.75, 1.5, -2.2),
|
||||
speed: 0.22,
|
||||
}
|
||||
], []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!gltf) return;
|
||||
const OOI: any = {};
|
||||
cloned.traverse((n: any) => {
|
||||
if (n.name === targetBoneName) OOI.Target_Bone = n;
|
||||
if (n.name === skinnedMeshName) OOI.Skinned_Mesh = n;
|
||||
});
|
||||
|
||||
if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return;
|
||||
|
||||
const iks = [
|
||||
{
|
||||
target: 7,
|
||||
effector: 6,
|
||||
links: [
|
||||
{
|
||||
index: 5,
|
||||
enabled: true,
|
||||
rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0),
|
||||
rotationMax: new THREE.Vector3(Math.PI / 2, 0, 0),
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
enabled: true,
|
||||
rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0),
|
||||
rotationMax: new THREE.Vector3(0, 0, 0),
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
enabled: true,
|
||||
rotationMin: new THREE.Vector3(0, 0, 0),
|
||||
rotationMax: new THREE.Vector3(2, 0, 0),
|
||||
},
|
||||
{ index: 1, enabled: true, limitation: new THREE.Vector3(0, 1, 0) },
|
||||
{ index: 0, enabled: false, limitation: new THREE.Vector3(0, 0, 0) },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks);
|
||||
setIkSolver(solver);
|
||||
|
||||
const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05);
|
||||
// groupRef.current.add(helper);
|
||||
|
||||
}, [gltf]);
|
||||
|
||||
useEffect(() => {
|
||||
const triggers = ['Trigger1', 'Trigger2'];
|
||||
let index = 0;
|
||||
|
||||
const cycleTriggers = setInterval(() => {
|
||||
setSelectedTrigger(triggers[index]);
|
||||
index = (index + 1) % triggers.length;
|
||||
}, 10000);
|
||||
|
||||
return () => clearInterval(cycleTriggers);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<group
|
||||
ref={groupRef}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedArm(groupRef.current?.getObjectByName(targetBoneName))
|
||||
}}
|
||||
>
|
||||
<primitive
|
||||
object={cloned}
|
||||
position={position}
|
||||
rotation={rotation}
|
||||
scale={[1, 1, 1]}
|
||||
name={`arm-bot`}
|
||||
/>
|
||||
</group>
|
||||
<IKAnimationController
|
||||
ikSolver={ikSolver}
|
||||
process={process}
|
||||
selectedTrigger={selectedTrigger}
|
||||
targetBoneName={targetBoneName}
|
||||
/>
|
||||
{/* {selectedArm && <TransformControls object={selectedArm} />} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default IkInstances;
|
||||
Reference in New Issue
Block a user