英文:
Collada Kinematics Tweening to desired position with three.js and tween.js
问题
我想要复制现有的示例加载 Collada 骨骼动画并使用它们 ,并使用我的自己的模型,为此我创建了以下类:
import { Object3D, MathUtils } from "three";
import { ColladaLoader } from "three/addons/loaders/ColladaLoader.js";
import yumi_path from "../assets/dae/yumi.dae";
import { Tween, Easing, update } from "@tweenjs/tween.js";
export class YuMiMotion {
constructor(scene, joint_values) {
this.scene = scene;
this.target_joint_values = joint_values;
this.loader = new ColladaLoader();
this.yumi_model = new Object3D();
this.kinematics;
this.kinematicsTween;
this.tweenParameters = {};
this.loadYuMi();
this.startMotion();
}
startMotion = () => {
if (this.kinematics == undefined) {
console.log("运动学尚未加载!");
this.anim_frame_id1 = requestAnimationFrame(this.startMotion);
} else {
console.log(this.kinematics);
this.setupTween();
cancelAnimationFrame(this.anim_frame_id1);
this.animate();
}
}
animate = () => {
update();
this.anim_frame_id2 = requestAnimationFrame(this.animate);
}
loadYuMi = async() => {
const onLoad = (result, yumi) => {
const model = result.scene.children[0];
yumi.add(model.clone(true));
yumi.traverse(function (child) {
if (child.isMesh) {
child.material.flatShading = true;
}
});
this.kinematics = result.kinematics;
};
await this.loader.load(yumi_path, (collada) =>
onLoad(collada, this.yumi_model, this.kinematics)
),
undefined,
function (error) {
console.log("发生错误", error);
};
this.yumi_model.position.set(0.0, 0.0, -6.0);
this.yumi_model.rotation.set(-Math.PI / 2, 0.0, -Math.PI / 2);
this.yumi_model.scale.set(20, 20, 20);
this.scene.add(this.yumi_model);
}
setupTween = () => {
const duration = MathUtils.randInt(1000, 5000);
const target = {};
for (const prop in this.target_joint_values) {
const joint = this.kinematics.joints[prop];
const old = this.tweenParameters[prop];
const position = old ? old : joint.zeroPosition;
this.tweenParameters[prop] = position;
target[prop] = this.target_joint_values[prop];
}
this.kinematicsTween = new Tween(this.tweenParameters).to(target, duration).easing(Easing.Quadratic.Out);
this.kinematicsTween.onUpdate((object) => {
for (const prop in this.kinematics.joints) {
if (prop in this.kinematics.joints) {
if (!this.kinematics.joints[prop].static) {
this.kinematics.setJointValue(prop, object[prop]);
}
}
}
});
this.kinematicsTween.start();
setTimeout(this.setupTween, duration);
}
}
然后我如下调用这个类:
let target_position = {
gripper_l_joint: 0.01,
gripper_l_joint_m: 0.01,
gripper_r_joint: 0.01,
gripper_r_joint_m: 0.01,
yumi_joint_1_l: 10.0,
yumi_joint_1_r: 10.0,
yumi_joint_2_l: 20.0,
yumi_joint_2_r: 20.0,
yumi_joint_3_l: 30.0,
yumi_joint_3_r: 30.0,
yumi_joint_4_l: 40.0,
yumi_joint_4_r: 40.0,
yumi_joint_5_l: 50.0,
yumi_joint_5_r: 50.0,
yumi_joint_6_l: 60.0,
yumi_joint_6_r: 60.0,
yumi_joint_7_l: 70.0,
yumi_joint_7_r: 70.0,
};
new YuMiMotion(this.scene, target_position);
我已经创建了一个示例库以重现我的示例。
请问如何使用Three.js
和Tween.js
来正确移动我的模型到所需的关节位置?提前感谢。
英文:
I want to replicate the existent example of loading Collada kinematics and using them with my own model, to do so I have created a class as follows:
import { Object3D, MathUtils } from "three";
import { ColladaLoader } from "three/addons/loaders/ColladaLoader.js";
import yumi_path from "../assets/dae/yumi.dae";
import { Tween, Easing, update } from "@tweenjs/tween.js";
export class YuMiMotion {
constructor(scene, joint_vlaues) {
this.scene = scene;
this.tgt_jnt_vals = joint_vlaues;
this.loader = new ColladaLoader();
this.yumi_model = new Object3D();
this.kinematics;
this.kinematicsTween;
this.tweenParameters = {};
this.loadYuMi();
this.startMotion();
}
startMotion = () => {
if (this.kinematics == undefined) {
console.log("Kinematics Still not loaded!");
this.anim_frame_id1 = requestAnimationFrame(this.startMotion);
}
else {
console.log(this.kinematics);
this.setupTween();
cancelAnimationFrame(this.anim_frame_id1);
this.animate();
}
}
animate = () => {
update();
this.anim_frame_id2 = requestAnimationFrame(this.animate);
}
loadYuMi = async() => {
const onLoad = (result, yumi) => {
const model = result.scene.children[0];
yumi.add(model.clone(true));
yumi.traverse(function (child) {
if (child.isMesh) {
child.material.flatShading = true;
}
});
this.kinematics = result.kinematics;
};
await this.loader.load(yumi_path, (collada) =>
onLoad(collada, this.yumi_model, this.kinematics)
),
undefined,
function (error) {
console.log("An error happened", error);
};
this.yumi_model.position.set(0.0, 0.0, -6.0);
this.yumi_model.rotation.set(-Math.PI / 2, 0.0, -Math.PI / 2);
this.yumi_model.scale.set(20, 20, 20);
this.scene.add(this.yumi_model);
}
setupTween =() =>{
const duration = MathUtils.randInt( 1000, 5000 );
const target = {};
for (const prop in this.tgt_jnt_vals) {
const joint = this.kinematics.joints[ prop ];
const old = this.tweenParameters[ prop ];
const position = old ? old : joint.zeroPosition;
this.tweenParameters[ prop ] = position;
target[prop] = this.tgt_jnt_vals[prop]; //MathUtils.randInt( joint.limits.min, joint.limits.max );
// console.log('target:', prop, this.tgt_jnt_vals[prop]);
}
this.kinematicsTween = new Tween( this.tweenParameters ).to( target, duration ).easing( Easing.Quadratic.Out );
this.kinematicsTween.onUpdate( ( object )=> {
for ( const prop in this.kinematics.joints ) {
if ( prop in this.kinematics.joints) {
if ( ! this.kinematics.joints[ prop ].static ) {
this.kinematics.setJointValue( prop, object[ prop ] );
}
}
}
});
// console.log("Tween Parameters", this.tweenParameters);
// console.log("kinematics tween", this.kinematicsTween);
this.kinematicsTween.start();
setTimeout( this.setupTween, duration );
}
}
And I'm calling this class as follows:
let target_position = {
gripper_l_joint: 0.01,
gripper_l_joint_m: 0.01,
gripper_r_joint: 0.01,
gripper_r_joint_m: 0.01,
yumi_joint_1_l: 10.0,
yumi_joint_1_r: 10.0,
yumi_joint_2_l: 20.0,
yumi_joint_2_r: 20.0,
yumi_joint_3_l: 30.0,
yumi_joint_3_r: 30.0,
yumi_joint_4_l: 40.0,
yumi_joint_4_r: 40.0,
yumi_joint_5_l: 50.0,
yumi_joint_5_r: 50.0,
yumi_joint_6_l: 60.0,
yumi_joint_6_r: 60.0,
yumi_joint_7_l: 70.0,
yumi_joint_7_r: 70.0,
};
new YuMiMotion(this.scene, target_position);
I have created a repo to reproduce my example here.
Can you please tell me how can I Move my model properly to the desired joints position using Three.js
and Tween.js
? thanks in advance.
答案1
得分: 0
以下是代码的翻译部分:
Motion Class
import { Tween, Easing } from "@tweenjs/tween.js";
import { ColladaLoader } from "three/examples/jsm/loaders/ColladaLoader.js";
import yumi_path from "../assets/dae/yumi.dae";
export class LoadMoveRobot {
constructor(scene) {
this.scene = scene;
this.yumi;
this.kinematics;
this.duration = 1000;
this.target = null;
this.tweenParameters = {};
this.joints = {
left: [
"yumi_joint_1_l",
"yumi_joint_2_l",
"yumi_joint_3_l",
"yumi_joint_4_l",
"yumi_joint_5_l",
"yumi_joint_6_l",
"yumi_joint_7_l",
],
right: [
"yumi_joint_1_r",
"yumi_joint_2_r",
"yumi_joint_3_r",
"yumi_joint_4_r",
"yumi_joint_5_r",
"yumi_joint_6_r",
"yumi_joint_7_r",
],
both: [
"yumi_joint_1_l",
"yumi_joint_2_l",
"yumi_joint_3_l",
"yumi_joint_4_l",
"yumi_joint_5_l",
"yumi_joint_6_l",
"yumi_joint_7_l",
"yumi_joint_1_r",
"yumi_joint_2_r",
"yumi_joint_3_r",
"yumi_joint_4_r",
"yumi_joint_5_r",
"yumi_joint_6_r",
"yumi_joint_7_r",
],
};
this.end_effector = {
left: ["gripper_l_joint", "gripper_l_joint_m"],
right: ["gripper_r_joint", "gripper_r_joint_m"],
both: [
"gripper_l_joint",
"gripper_l_joint_m",
"gripper_r_joint",
"gripper_r_joint_m",
],
};
this.loadRobot();
}
loadRobot = () => {
const loader = new ColladaLoader();
loader.load(yumi_path, (collada) => {
this.yumi = collada.scene;
this.yumi.traverse((child) => {
if (child.isMesh) {
// model does not have normals
child.material.flatShading = true;
}
});
this.yumi.position set(0.0, 0.0, -6.0);
this.yumi.rotation.set(-Math.PI / 2, 0.0, -Math.PI / 2);
this.yumi.scale.set(20, 20, 20);
this.yumi.updateMatrix();
this.kinematics = collada.kinematics;
// Add the COLLADA
this.scene.add(this.yumi);
this.moveJointValues("left");
// this.moveXYZValues();
this.controlGripper("both", 2);
});
};
moveJointValues = (arm, target) => {
if (target !== undefined) {
this.target = target;
}
if (this.kinematics === undefined) {
/**
* TODO: target object is undefined in the recursive calls!
* It will be nice to replace setTimeout by a Promise.
*/
setTimeout(this.moveJointValues.bind(null, arm, target), 3000);
} else {
this.joints[arm].forEach((joint) => {
if (!this.kinematics.joints[joint].static) {
const old = this.tweenParameters[joint];
const position = old ? old : this.kinematics.getJointValue(joint);
this.tweenParameters[joint] = position;
}
});
this.kinematicsTween = new Tween(this.tweenParameters)
.to(this.target, this.duration)
.easing(Easing.Quadratic.Out)
.onUpdate((object) => {
this.joints[arm].forEach((joint) => {
if (!this.kinematics.joints[joint].static) {
this.kinematics.setJointValue(joint, object[joint]);
}
});
})
.start();
}
};
controlGripper = (arm, act_idx, val = null) => {
if (act_idx === 1) {
this.end_effector[arm].forEach((finger) =>
this.kinematics.setJointValue(finger, 0.0)
);
} else if (act_idx === 2) {
this.end_effector[arm].forEach((finger) =>
this.kinematics.setJointValue(finger, 0.025)
);
} else if (act_idx === 3) {
if (val !== null) {
if (val >= 0.0 && val <= 0.25) {
this.end_effector[arm].forEach((finger) =>
this.kinematics.setJointValue(finger, val)
);
} else {
console.warn("Gripper finger values has to be between 0.0 and 0.025!");
}
} else {
console.warn("Wrong Gripper Move_To value!");
}
} else {
console.warn("Gripper Parameters has to be 1, 2, or 3!");
}
};
}
Animating the motion
import { update } from "@tweenjs/tween.js";
animate(){
requestAnimationFrame( this.animate );
this.renderer.render(this.scene, this.camera);
update();
}
let target_position = {
gripper_l_joint: 0.0125, // 0.0 --> 0.025 , Prismatic
gripper_l_joint_m: 0.0125, // 0.0 --> 0.025 , Prismatic
gripper_r_joint: 0.0125, // 0.0 --> 0.025 , Prismatic
gripper_r_joint_m: 0.0125, // 0.0 --> 0.025 , Prismatic
yumi_joint_1_l: 0.0, // -168.5 --> 168.5 , Revolute
yumi_joint_1_r: 0.0, // -168.5 --> 168.5 , Revolute
yumi_joint_2_l: -130.0, // -143.5 --> 43.5 , Revolute
yumi_joint_2_r: -130.0, // -143.5 --> 43.5 , Revolute
yumi_joint_3_l: 30, // -123.5 --> 80.0 , Revolute
yumi_joint_3_r: 30, // -123.5 --> 80.0 , Revolute
yumi_joint_4_l: 0.0, // -
<details>
<summary>英文:</summary>
The problem was animating the motion, this problem was solved by calling the animation loop outside the motion class:
#### Motion Class
```js
import { Tween, Easing } from "@tweenjs/tween.js";
import { ColladaLoader } from "three/examples/jsm/loaders/ColladaLoader.js";
import yumi_path from "../assets/dae/yumi.dae";
export class LoadMoveRobot {
constructor(scene) {
this.scene = scene;
this.yumi;
this.kinematics;
this.duration = 1000;
this.target = null;
this.tweenParameters = {};
this.joints = {
left: [
"yumi_joint_1_l",
"yumi_joint_2_l",
"yumi_joint_3_l",
"yumi_joint_4_l",
"yumi_joint_5_l",
"yumi_joint_6_l",
"yumi_joint_7_l",
],
right: [
"yumi_joint_1_r",
"yumi_joint_2_r",
"yumi_joint_3_r",
"yumi_joint_4_r",
"yumi_joint_5_r",
"yumi_joint_6_r",
"yumi_joint_7_r",
],
both: [
"yumi_joint_1_l",
"yumi_joint_2_l",
"yumi_joint_3_l",
"yumi_joint_4_l",
"yumi_joint_5_l",
"yumi_joint_6_l",
"yumi_joint_7_l",
"yumi_joint_1_r",
"yumi_joint_2_r",
"yumi_joint_3_r",
"yumi_joint_4_r",
"yumi_joint_5_r",
"yumi_joint_6_r",
"yumi_joint_7_r",
],
};
this.end_effector = {
left: ["gripper_l_joint", "gripper_l_joint_m"],
right: ["gripper_r_joint", "gripper_r_joint_m"],
both: [
"gripper_l_joint",
"gripper_l_joint_m",
"gripper_r_joint",
"gripper_r_joint_m",
],
};
this.loadRobot();
}
loadRobot = () => {
const loader = new ColladaLoader();
loader.load(yumi_path, (collada) => {
this.yumi = collada.scene;
this.yumi.traverse((child) => {
if (child.isMesh) {
// model does not have normals
child.material.flatShading = true;
}
});
this.yumi.position.set(0.0, 0.0, -6.0);
this.yumi.rotation.set(-Math.PI / 2, 0.0, -Math.PI / 2);
this.yumi.scale.set(20, 20, 20);
this.yumi.updateMatrix();
this.kinematics = collada.kinematics;
// Add the COLLADA
this.scene.add(this.yumi);
this.moveJointValues("left");
// this.moveXYZValues();
this.controlGripper("both", 2);
});
};
moveJointValues = (arm, target) => {
if (target !== undefined) {
this.target = target;
}
if (this.kinematics === undefined) {
/**
* TODO: target object is undefined in the recursive calls!
* It will be nice to replace setTimeout by a Promise.
*/
setTimeout(this.moveJointValues.bind(null, arm, target), 3000);
} else {
this.joints[arm].forEach((joint) => {
if (!this.kinematics.joints[joint].static) {
const old = this.tweenParameters[joint];
const position = old ? old : this.kinematics.getJointValue(joint);
this.tweenParameters[joint] = position;
}
});
this.kinematicsTween = new Tween(this.tweenParameters)
.to(this.target, this.duration)
.easing(Easing.Quadratic.Out)
.onUpdate((object) => {
this.joints[arm].forEach((joint) => {
if (!this.kinematics.joints[joint].static) {
this.kinematics.setJointValue(joint, object[joint]);
}
});
})
.start();
}
};
controlGripper = (arm, act_idx, val = null) => {
if (act_idx === 1) {
this.end_effector[arm].forEach((finger) =>
this.kinematics.setJointValue(finger, 0.0)
);
} else if (act_idx === 2) {
this.end_effector[arm].forEach((finger) =>
this.kinematics.setJointValue(finger, 0.025)
);
} else if (act_idx === 3) {
if (val !== null) {
if (val >= 0.0 && val <= 0.25) {
this.end_effector[arm].forEach((finger) =>
this.kinematics.setJointValue(finger, val)
);
} else {
console.warn(
"Gripper finger values has to be between 0.0 and 0.025!"
);
}
} else {
console.warn("Wrong Gripper Move_To value!");
}
} else {
console.warn("Gripper Parameters has to be 1, 2, or 3!");
}
};
}
Animating the motion
import { update } from "@tweenjs/tween.js";
animate(){
requestAnimationFrame( this.animate );
this.renderer.render(this.scene, this.camera);
update();
}
let target_position = {
gripper_l_joint: 0.0125, // 0.0 --> 0.025 , Prismatic
gripper_l_joint_m: 0.0125, // 0.0 --> 0.025 , Prismatic
gripper_r_joint: 0.0125, // 0.0 --> 0.025 , Prismatic
gripper_r_joint_m: 0.0125, // 0.0 --> 0.025 , Prismatic
yumi_joint_1_l: 0.0, // -168.5 --> 168.5 , Revolute
yumi_joint_1_r: 0.0, // -168.5 --> 168.5 , Revolute
yumi_joint_2_l: -130.0, // -143.5 --> 43.5 , Revolute
yumi_joint_2_r: -130.0, // -143.5 --> 43.5 , Revolute
yumi_joint_3_l: 30, // -123.5 --> 80.0 , Revolute
yumi_joint_3_r: 30, // -123.5 --> 80.0 , Revolute
yumi_joint_4_l: 0.0, // -290.0 --> 290.0 , Revolute
yumi_joint_4_r: 0.0, // -290.0 --> 290.0 , Revolute
yumi_joint_5_l: 40.0, // -88.0 --> 130.0 , Revolute
yumi_joint_5_r: 40.0, // -88.0 --> 130.0 , Revolute
yumi_joint_6_l: 0.0, // -229.0 --> 229.0 , Revolute
yumi_joint_6_r: 0.0, // -229.0 --> 229.0 , Revolute
yumi_joint_7_l: 135.0, // -168.0 --> 168.0 , Revolute
yumi_joint_7_r: -135.0 // -168.0 --> 168.0 , Revolute
};
const LMR_ = new LoadMoveRobot(this.scene);
LMR_.moveJointValues('left', target_position);
this.animate();
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论