英文:
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();
				通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论