【问题标题】:Cannon.js + Three.js: Minimal car + physicsCannon.js + Three.js:最小的汽车 + 物理
【发布时间】:2020-04-04 02:36:49
【问题描述】:

我正在尝试弄清楚如何使用 Three.js 和 Cannon.js 创建具有物理特性的最小汽车。我已经为我的汽车和车轮创建了基于视觉和物理的元素,并且我的汽车响应向上箭头和左/右箭头命令以加速和转弯:

var container = document.querySelector('body'),
    w = container.clientWidth,
    h = container.clientHeight,
    scene = new THREE.Scene(),
    camera = new THREE.PerspectiveCamera(75, w/h, 0.001, 100),
    renderConfig = {antialias: true, alpha: true},
    renderer = new THREE.WebGLRenderer(renderConfig);
camera.position.set(0, 1, -10);
camera.lookAt(0,0,0);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(w, h);
container.appendChild(renderer.domElement);

window.addEventListener('resize', function() {
  w = container.clientWidth;
  h = container.clientHeight;
  camera.aspect = w/h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h);
})

var geometry = new THREE.PlaneGeometry(10, 10, 10);
var material = new THREE.MeshBasicMaterial({color: 0xff0000, side: THREE.DoubleSide});
var plane = new THREE.Mesh(geometry, material);
plane.rotation.x = Math.PI/2;
scene.add(plane);

var sunlight = new THREE.DirectionalLight(0xffffff, 1.0);
sunlight.position.set(-10, 10, 0);
scene.add(sunlight)

/**
* Physics
**/

var world = new CANNON.World();
world.broadphase = new CANNON.SAPBroadphase(world);
world.gravity.set(0, -10, 0);
world.defaultContactMaterial.friction = 0;

var groundMaterial = new CANNON.Material('groundMaterial');
var wheelMaterial = new CANNON.Material('wheelMaterial');
var wheelGroundContactMaterial = new CANNON.ContactMaterial(wheelMaterial, groundMaterial, {
    friction: 0.3,
    restitution: 0,
    contactEquationStiffness: 1000,
});

world.addContactMaterial(wheelGroundContactMaterial);

// car physics body
var chassisShape = new CANNON.Box(new CANNON.Vec3(1, 0.3, 2));
var chassisBody = new CANNON.Body({mass: 150});
chassisBody.addShape(chassisShape);
chassisBody.position.set(0, 0.2, 0);
chassisBody.angularVelocity.set(0, 0, 0); // initial velocity

// car visual body
var geometry = new THREE.BoxGeometry(2, 0.6, 4); // double chasis shape
var material = new THREE.MeshBasicMaterial({color: 0xffff00, side: THREE.DoubleSide});
var box = new THREE.Mesh(geometry, material);
scene.add(box);

// parent vehicle object
vehicle = new CANNON.RaycastVehicle({
  chassisBody: chassisBody,
  indexRightAxis: 0, // x
  indexUpAxis: 1, // y
  indexForwardAxis: 2, // z
});

// wheel options
var options = {
  radius: 0.3,
  directionLocal: new CANNON.Vec3(0, -1, 0),
  suspensionStiffness: 45,
  suspensionRestLength: 0.4,
  frictionSlip: 5,
  dampingRelaxation: 2.3,
  dampingCompression: 4.5,
  maxSuspensionForce: 200000,
  rollInfluence:  0.01,
  axleLocal: new CANNON.Vec3(-1, 0, 0),
  chassisConnectionPointLocal: new CANNON.Vec3(1, 1, 0),
  maxSuspensionTravel: 0.25,
  customSlidingRotationalSpeed: -30,
  useCustomSlidingRotationalSpeed: true,
};

var axlewidth = 0.7;
options.chassisConnectionPointLocal.set(axlewidth, 0, -1);
vehicle.addWheel(options);

options.chassisConnectionPointLocal.set(-axlewidth, 0, -1);
vehicle.addWheel(options);

options.chassisConnectionPointLocal.set(axlewidth, 0, 1);
vehicle.addWheel(options);

options.chassisConnectionPointLocal.set(-axlewidth, 0, 1);
vehicle.addWheel(options);

vehicle.addToWorld(world);

// car wheels
var wheelBodies = [],
    wheelVisuals = [];
vehicle.wheelInfos.forEach(function(wheel) {
  var shape = new CANNON.Cylinder(wheel.radius, wheel.radius, wheel.radius / 2, 20);
  var body = new CANNON.Body({mass: 1, material: wheelMaterial});
  var q = new CANNON.Quaternion();
  q.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2);
  body.addShape(shape, new CANNON.Vec3(), q);
  wheelBodies.push(body);
  // wheel visual body
  var geometry = new THREE.CylinderGeometry( wheel.radius, wheel.radius, 0.4, 32 );
  var material = new THREE.MeshPhongMaterial({
    color: 0xd0901d,
    emissive: 0xaa0000,
    side: THREE.DoubleSide,
    flatShading: true,
  });
  var cylinder = new THREE.Mesh(geometry, material);
  wheelVisuals.push(cylinder);
  scene.add(cylinder);
});

// update the wheels to match the physics
world.addEventListener('postStep', function() {
  for (var i=0; i<vehicle.wheelInfos.length; i++) {
    vehicle.updateWheelTransform(i);
    var t = vehicle.wheelInfos[i].worldTransform;
    // update wheel physics
    wheelBodies[i].position.copy(t.position);
    wheelBodies[i].quaternion.copy(t.quaternion);
    // update wheel visuals
    wheelVisuals[i].position.copy(t.position);
    wheelVisuals[i].quaternion.copy(t.quaternion);
  }
});

var q = plane.quaternion;
var planeBody = new CANNON.Body({
  mass: 0, // mass = 0 makes the body static
  material: groundMaterial,
  shape: new CANNON.Plane(),
  quaternion: new CANNON.Quaternion(-q._x, q._y, q._z, q._w)
});
world.add(planeBody)

/**
* Main
**/

function updatePhysics() {
  world.step(1/60);
  // update the chassis position
  box.position.copy(chassisBody.position);
  box.quaternion.copy(chassisBody.quaternion);
}

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
  updatePhysics();
}

function navigate(e) {
  if (e.type != 'keydown' && e.type != 'keyup') return;
  var keyup = e.type == 'keyup';
  vehicle.setBrake(0, 0);
  vehicle.setBrake(0, 1);
  vehicle.setBrake(0, 2);
  vehicle.setBrake(0, 3);

  var engineForce = 800,
      maxSteerVal = 0.3;
  switch(e.keyCode) {

    case 38: // forward
      vehicle.applyEngineForce(keyup ? 0 : -engineForce, 2);
      vehicle.applyEngineForce(keyup ? 0 : -engineForce, 3);
      break;

    case 40: // backward
      vehicle.applyEngineForce(keyup ? 0 : engineForce, 2);
      vehicle.applyEngineForce(keyup ? 0 : engineForce, 3);
      break;

    case 39: // right
      vehicle.setSteeringValue(keyup ? 0 : -maxSteerVal, 2);
      vehicle.setSteeringValue(keyup ? 0 : -maxSteerVal, 3);
      break;

    case 37: // left
      vehicle.setSteeringValue(keyup ? 0 : maxSteerVal, 2);
      vehicle.setSteeringValue(keyup ? 0 : maxSteerVal, 3);
      break;
  }
}

window.addEventListener('keydown', navigate)
window.addEventListener('keyup', navigate)

render();
* {
  margin: 0;
  padding: 0;
  overflow: hidden;
}
html,
body,
canvas {
  width: 100%;
  height: 100%;
  background: #aaa;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.js'></script>

我现在正试图弄清楚如何旋转车轮,使它们像正确的轮胎一样滚动。以前,在world.addEventListener('postStep') 回调中,我在设置轮子的四元数后手动运行wheelVisuals[i].rotation.z = Math.PI/2。这一直很好,直到我转动汽车,此时轮胎被甩出对齐......

如果有人能指点一下如何使用 Cannon.js 配置轮子,我将不胜感激!

【问题讨论】:

    标签: javascript three.js cannon.js


    【解决方案1】:

    啊哈,当我应该将其应用于几何体时,我正在将 hacky 旋转应用于网格!

    下面的答案只是在vehicle.wheelInfos 循环中添加了一行:

    cylinder.geometry.rotateZ(Math.PI/2);
    

    var container = document.querySelector('body'),
        w = container.clientWidth,
        h = container.clientHeight,
        scene = new THREE.Scene(),
        camera = new THREE.PerspectiveCamera(75, w/h, 0.001, 100),
        renderConfig = {antialias: true, alpha: true},
        renderer = new THREE.WebGLRenderer(renderConfig);
    camera.position.set(0, 1, -10);
    camera.lookAt(0,0,0);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(w, h);
    container.appendChild(renderer.domElement);
    
    window.addEventListener('resize', function() {
      w = container.clientWidth;
      h = container.clientHeight;
      camera.aspect = w/h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    })
    
    var geometry = new THREE.PlaneGeometry(10, 10, 10);
    var material = new THREE.MeshBasicMaterial({color: 0xff0000, side: THREE.DoubleSide});
    var plane = new THREE.Mesh(geometry, material);
    plane.rotation.x = Math.PI/2;
    scene.add(plane);
    
    var sunlight = new THREE.DirectionalLight(0xffffff, 1.0);
    sunlight.position.set(-10, 10, 0);
    scene.add(sunlight)
    
    /**
    * Physics
    **/
    
    var world = new CANNON.World();
    world.broadphase = new CANNON.SAPBroadphase(world);
    world.gravity.set(0, -10, 0);
    world.defaultContactMaterial.friction = 0;
    
    var groundMaterial = new CANNON.Material('groundMaterial');
    var wheelMaterial = new CANNON.Material('wheelMaterial');
    var wheelGroundContactMaterial = new CANNON.ContactMaterial(wheelMaterial, groundMaterial, {
        friction: 0.3,
        restitution: 0,
        contactEquationStiffness: 1000,
    });
    
    world.addContactMaterial(wheelGroundContactMaterial);
    
    // car physics body
    var chassisShape = new CANNON.Box(new CANNON.Vec3(1, 0.3, 2));
    var chassisBody = new CANNON.Body({mass: 150});
    chassisBody.addShape(chassisShape);
    chassisBody.position.set(0, 0.2, 0);
    chassisBody.angularVelocity.set(0, 0, 0); // initial velocity
    
    // car visual body
    var geometry = new THREE.BoxGeometry(2, 0.6, 4); // double chasis shape
    var material = new THREE.MeshBasicMaterial({color: 0xffff00, side: THREE.DoubleSide});
    var box = new THREE.Mesh(geometry, material);
    scene.add(box);
    
    // parent vehicle object
    vehicle = new CANNON.RaycastVehicle({
      chassisBody: chassisBody,
      indexRightAxis: 0, // x
      indexUpAxis: 1, // y
      indexForwardAxis: 2, // z
    });
    
    // wheel options
    var options = {
      radius: 0.3,
      directionLocal: new CANNON.Vec3(0, -1, 0),
      suspensionStiffness: 45,
      suspensionRestLength: 0.4,
      frictionSlip: 5,
      dampingRelaxation: 2.3,
      dampingCompression: 4.5,
      maxSuspensionForce: 200000,
      rollInfluence:  0.01,
      axleLocal: new CANNON.Vec3(-1, 0, 0),
      chassisConnectionPointLocal: new CANNON.Vec3(1, 1, 0),
      maxSuspensionTravel: 0.25,
      customSlidingRotationalSpeed: -30,
      useCustomSlidingRotationalSpeed: true,
    };
    
    var axlewidth = 0.7;
    options.chassisConnectionPointLocal.set(axlewidth, 0, -1);
    vehicle.addWheel(options);
    
    options.chassisConnectionPointLocal.set(-axlewidth, 0, -1);
    vehicle.addWheel(options);
    
    options.chassisConnectionPointLocal.set(axlewidth, 0, 1);
    vehicle.addWheel(options);
    
    options.chassisConnectionPointLocal.set(-axlewidth, 0, 1);
    vehicle.addWheel(options);
    
    vehicle.addToWorld(world);
    
    // car wheels
    var wheelBodies = [],
        wheelVisuals = [];
    vehicle.wheelInfos.forEach(function(wheel) {
      var shape = new CANNON.Cylinder(wheel.radius, wheel.radius, wheel.radius / 2, 20);
      var body = new CANNON.Body({mass: 1, material: wheelMaterial});
      var q = new CANNON.Quaternion();
      q.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2);
      body.addShape(shape, new CANNON.Vec3(), q);
      wheelBodies.push(body);
      // wheel visual body
      var geometry = new THREE.CylinderGeometry( wheel.radius, wheel.radius, 0.4, 32 );
      var material = new THREE.MeshPhongMaterial({
        color: 0xd0901d,
        emissive: 0xaa0000,
        side: THREE.DoubleSide,
        flatShading: true,
      });
      var cylinder = new THREE.Mesh(geometry, material);
      cylinder.geometry.rotateZ(Math.PI/2);
      wheelVisuals.push(cylinder);
      scene.add(cylinder);
    });
    
    // update the wheels to match the physics
    world.addEventListener('postStep', function() {
      for (var i=0; i<vehicle.wheelInfos.length; i++) {
        vehicle.updateWheelTransform(i);
        var t = vehicle.wheelInfos[i].worldTransform;
        // update wheel physics
        wheelBodies[i].position.copy(t.position);
        wheelBodies[i].quaternion.copy(t.quaternion);
        // update wheel visuals
        wheelVisuals[i].position.copy(t.position);
        wheelVisuals[i].quaternion.copy(t.quaternion);
      }
    });
    
    var q = plane.quaternion;
    var planeBody = new CANNON.Body({
      mass: 0, // mass = 0 makes the body static
      material: groundMaterial,
      shape: new CANNON.Plane(),
      quaternion: new CANNON.Quaternion(-q._x, q._y, q._z, q._w)
    });
    world.add(planeBody)
    
    /**
    * Main
    **/
    
    function updatePhysics() {
      world.step(1/60);
      // update the chassis position
      box.position.copy(chassisBody.position);
      box.quaternion.copy(chassisBody.quaternion);
    }
    
    function render() {
      requestAnimationFrame(render);
      renderer.render(scene, camera);
      updatePhysics();
    }
    
    function navigate(e) {
      if (e.type != 'keydown' && e.type != 'keyup') return;
      var keyup = e.type == 'keyup';
      vehicle.setBrake(0, 0);
      vehicle.setBrake(0, 1);
      vehicle.setBrake(0, 2);
      vehicle.setBrake(0, 3);
    
      var engineForce = 800,
          maxSteerVal = 0.3;
      switch(e.keyCode) {
    
        case 38: // forward
          vehicle.applyEngineForce(keyup ? 0 : -engineForce, 2);
          vehicle.applyEngineForce(keyup ? 0 : -engineForce, 3);
          break;
    
        case 40: // backward
          vehicle.applyEngineForce(keyup ? 0 : engineForce, 2);
          vehicle.applyEngineForce(keyup ? 0 : engineForce, 3);
          break;
    
        case 39: // right
          vehicle.setSteeringValue(keyup ? 0 : -maxSteerVal, 2);
          vehicle.setSteeringValue(keyup ? 0 : -maxSteerVal, 3);
          break;
    
        case 37: // left
          vehicle.setSteeringValue(keyup ? 0 : maxSteerVal, 2);
          vehicle.setSteeringValue(keyup ? 0 : maxSteerVal, 3);
          break;
      }
    }
    
    window.addEventListener('keydown', navigate)
    window.addEventListener('keyup', navigate)
    
    render();
    * {
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
    html,
    body,
    canvas {
      width: 100%;
      height: 100%;
      background: #aaa;
    }
    <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js'></script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.js'></script>

    【讨论】:

    • 为这个确切的问题浪费了 2 天!非常感谢。同样在我的情况下,我正在循环之外创建几何图形(以避免创建几何图形 4 次)。但这也导致了同样的问题。一旦我为每个车轮使用了新的几何形状,它就可以很好地配合您的修复。
    猜你喜欢
    • 2013-03-29
    • 1970-01-01
    • 2013-04-29
    • 1970-01-01
    • 2013-05-26
    • 2012-02-12
    • 2012-12-28
    • 1970-01-01
    • 2018-10-13
    相关资源
    最近更新 更多