Three.js 曲线上的 getTangent 应该在曲线上移动时正确旋转我的对象

Three.js getTangent on curve should rotate my object correctly while moving on the curve

它确实会旋转,使用:

t = 0
animate = ->
  requestAnimationFrame animate

  t += 0.001
  # ...

  position3 = curveSeaRoute.getPoint(t+.3)
  tangent3 = curveSeaRoute.getTangent(t)
  ship.position.copy(position3)
  ship.lookAt(tangent3)
  # console.log tangent3

  renderer.render scene, camera
animate()

我的船 + 'route' 定义如下:

# Ship
centerLength = 2
bodyLength = 3
bodyWidth = 2
deckHeight = 0.1
geometry = new THREE.Geometry() 
v1 = new THREE.Vector3(1, deckHeight,0)
v2 = new THREE.Vector3(-2, deckHeight,2)
v3 = new THREE.Vector3(1, deckHeight,2)
v4 = new THREE.Vector3(-2, deckHeight,0)
v5 = new THREE.Vector3(-3, deckHeight, 1)
geometry.vertices.push(v1)
geometry.vertices.push(v2)
geometry.vertices.push(v3)
geometry.vertices.push(v4)
geometry.vertices.push(v5)
geometry.faces.push( new THREE.Face3( 0, 1, 2 ) )
geometry.faces.push( new THREE.Face3( 3, 1, 0 ) )
geometry.faces.push( new THREE.Face3( 3, 4, 1 ) )
material = new THREE.MeshBasicMaterial( { color: 0x005b1e } )
ship = new THREE.Mesh( geometry, material )
ship.translateZ 20 
ship.translateX 14.5
scene.add( ship )

# Sea route
curveSeaRoute = new THREE.CatmullRomCurve3([
  new THREE.Vector3( 18, roadHeight, 31 ),
  new THREE.Vector3( 24, roadHeight, 23 ),
  new THREE.Vector3( 22, roadHeight, 20 ),
  new THREE.Vector3( 14, roadHeight, 19 ),
  new THREE.Vector3( 10, roadHeight, 20 ),
  new THREE.Vector3( 8, roadHeight, 23 ),
  new THREE.Vector3( 10, roadHeight, 31 ),
] )

geometry = new THREE.Geometry()
geometry.vertices = curveSeaRoute.getPoints( 200 )
material = new THREE.LineBasicMaterial( { color : 0x45607c } )
curveObject = new THREE.Line( geometry, material )
scene.add( curveObject )

但是轮换的问题不是很顺利:

http://jsfiddle.net/CoderX99/66b3j9wa/6/

getTangent 确实为我提供了关于曲线方向矢量(切线)的宝贵数据。现在我当然可以使用三角学从 x 和 z 值中获取 y 轴旋转角度,但我希望有更简单的方法 - Three.js - 。

另外我希望旋转中心在船的中间。看起来它现在在后面的某个地方。

回答

来自 prisoner849。加上我自己的代码整理。

setOutPath = (path, color, segments ) ->
    geometry = new THREE.Geometry()
    geometry.vertices = path.getPoints( segments )
    material = new THREE.LineBasicMaterial( { color : color } )
    curveObject = new THREE.Line( geometry, material )
    scene.add( curveObject )

setOutPath(curveRoadNo1, 0xa9c41e, 500)
setOutPath(curveRoadNo2, 0xa9c41e, 200)
setOutPath(curveSeaRoute, 0x45607c, 200)

# Moving objects
# Vehicle
geometry = new THREE.SphereBufferGeometry( 0.1, 32, 32 )
material = new THREE.MeshBasicMaterial( {color: 0xffff00} )
vehicle1 = new THREE.Mesh( geometry, material )
scene.add( vehicle1 )
vehicle2 = vehicle1.clone()
scene.add( vehicle2 )
vehicle3 = vehicle1.clone()
scene.add( vehicle3 )
# The ship
centerLength = 2
bodyLength = 3
bodyWidth = 2
deckHeight = 0.1
geometry = new THREE.PlaneGeometry(2,2,2)
geometry.vertices[4].y = -3
geometry.rotateX(-Math.PI * 0.5)
geometry.translate(0, deckHeight, 0)
material = new THREE.MeshBasicMaterial({
    color: 0x005b1e
  })
ship = new THREE.Mesh(geometry, material)
ship.translateZ(15)
ship.translateX(15)
scene.add(ship)
# vehicle1, vehicle2, vehicle3, ship

t1 = { value: 0 } # for RoadNo1
t2 = { value: 0 } # for RoadNo2
t3 = { value: 0 } # for SeaRoute

# Tween updates
updateT = -> 
    vehicle1.position.copy(curveRoadNo1.getPointAt(t1.value))
    vehicle2.position.copy(curveRoadNo1.getPointAt(1 - t1.value)) 
updateT2 = ->
    vehicle3.position.copy(curveRoadNo2.getPointAt(t2.value))
updateT3 = ->
    lookAt = (t3.value + 0.0001) % 1
    ship.position.copy(curveSeaRoute.getPointAt(t3.value))
    ship.lookAt(curveSeaRoute.getPointAt(lookAt))

# Tweens - for Inbetweens
tween11 = new TWEEN.Tween(t1).to({ value: .3 }, 7000).delay(500).onUpdate(updateT)
tween12 = new TWEEN.Tween(t1).to({ value: 1 }, 3000).delay(1000).onUpdate(updateT).onComplete( () ->
        t1.value = 0
    )
tween11.chain(tween12)
tween12.chain(tween11)
tween11.start()

tween21 = new TWEEN.Tween(t2).to({ value: 1 }, 10000).onUpdate(updateT2)
tween22 = new TWEEN.Tween(t2).to({ value: 0 }, 10000).onUpdate(updateT2)
tween21.chain(tween22)
tween22.chain(tween21)
tween21.start()

tween3 = new TWEEN.Tween(t3).to({ value: 1 }, 17000).delay(1500).onUpdate(updateT3).onComplete( () ->
        t3.value = 0
    )
tween3.chain(tween3)
tween3.start()

controls = new THREE.OrbitControls(camera, renderer.domElement)
camera.position.x = colms/2
camera.position.y = 13
camera.position.z = 25
camera.rotation.x = -40 * Math.PI / 180
controls.target = new THREE.Box3().setFromObject(scene).getCenter()
controls.update()

t = 0
animate = ->
    requestAnimationFrame animate
    TWEEN.update()
    renderer.render scene, camera
animate()

您可以使用 ship.lookAt() 方法。看看 updateT3 函数。

我修改了你的 jsfiddle(稍微):简化了飞船的创建;使用 Tween.js 制作动画;添加了 THREE.OrbitControls() 以获得更好的视图。

查看代码片段。

// Generated by CoffeeScript 2.0.1
(function() {
  var animate, bodyLength, bodyWidth, build_city, camera, centerLength, city_map, colms, curveObject, curveRoadNo1, curveRoadNo2, deckHeight, flora_density_map, geometry, height, heightBuilding, height_map, i, j, k, len, len1, material, plant_tree, raise_land, ref, renderer, roadHeight, rows, scene, ship, t, v1, v2, v3, v4, v5, vehicle;

  scene = new THREE.Scene;

  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);

  renderer = new THREE.WebGLRenderer({
    antialias: true
  });

  renderer.setSize(window.innerWidth, window.innerHeight);

  renderer.setPixelRatio((ref = window.devicePixelRatio) != null ? ref : window.devicePixelRatio || 1);

  document.body.appendChild(renderer.domElement);

  var controls = new THREE.OrbitControls(camera, renderer.domElement);

  //##########################################################################################
  colms = 30;

  rows = 14;

  height_map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 4, 5, 5, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 1, 2, 1, 3, 4, 7, 5, 5, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 2, 2, 3, 3, 4, 5, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 5, 4, 5, 6, 6];

  flora_density_map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0];

  city_map = [9, 9, 9, 9, 9, 0, 0, 9, 9, 0, 0, 9];

  plant_tree = function(x, y, z) {
    var cone, geometry, material;
    geometry = new THREE.ConeGeometry(0.2, 0.5);
    material = new THREE.MeshBasicMaterial({
      color: 0x20281e
    });
    cone = new THREE.Mesh(geometry, material);
    cone.translateX(x);
    cone.translateY(y + .25);
    cone.translateZ(z);
    return scene.add(cone);
  };

  build_city = function(start_c, start_r, colms, rows, i, heightBuilding) {
    var geometry, height, land, material, row, width;
    if (heightBuilding !== 0) {
      height = heightBuilding * 0.035;
      width = 0.5;
      geometry = new THREE.BoxGeometry(width, height, width);
      material = new THREE.MeshBasicMaterial({
        color: 0x8b0000
      });
      land = new THREE.Mesh(geometry, material);
      // Set box base equal to overall baselevel.
      land.translateY(height / 2);
      // Normal animation translation.
      row = (Math.floor(i / colms)) + 1;
      land.translateZ(row + start_r - 1);
      land.translateX(i - (colms * (row - 1)) + start_c);
      return scene.add(land);
    }
  };

  raise_land = function(colms, rows, i, height) {
    var color, geometry, land, material, row;
    color = new THREE.Color(`rgb(${0 + height * 12}, 153, ${51 + height * 30})`);
    height = height * 0.22;
    geometry = new THREE.BoxGeometry(1, height, 1);
    material = new THREE.MeshBasicMaterial({
      color: color
    });
    land = new THREE.Mesh(geometry, material);
    // Set box base equal to overall baselevel.
    land.translateY(height / 2);
    // Normal animation translation.
    row = (Math.floor(i / colms)) + 1;
    land.translateZ(row);
    land.translateX(i - (colms * (row - 1)));
    scene.add(land);
    if (flora_density_map[i] !== 0) {
      return plant_tree(i - (colms * (row - 1)), height, row);
    }
  };

  centerLength = 2;

  bodyLength = 3;

  bodyWidth = 2;

  deckHeight = 0.1;

  geometry = new THREE.PlaneGeometry(2, 2, 2);
  geometry.vertices[4].y = -3;
  geometry.rotateX(-Math.PI * .5);
  geometry.translate(0, .1, 0);
  material = new THREE.MeshBasicMaterial({
    color: 0x005b1e
  });

  ship = new THREE.Mesh(geometry, material);

  ship.translateZ(15);

  ship.translateX(15);

  scene.add(ship);

  // Roads around the main settlement.
  roadHeight = 0.1;

  curveRoadNo1 = new THREE.CatmullRomCurve3([
    new THREE.Vector3(13,
      roadHeight,
      13),
    new THREE.Vector3(12,
      roadHeight,
      14),
    new THREE.Vector3(10,
      roadHeight,
      14),
    new THREE.Vector3(10,
      roadHeight,
      13),
    new THREE.Vector3(11,
      roadHeight,
      12),
    new THREE.Vector3(11,
      roadHeight,
      11),
    // new THREE.Vector3( 9, roadHeight, 10),
    // new THREE.Vector3( 7, roadHeight, 9),
    new THREE.Vector3(4,
      roadHeight,
      10),
    new THREE.Vector3(4,
      roadHeight,
      7),
    new THREE.Vector3(1,
      roadHeight,
      3),
    new THREE.Vector3(5,
      roadHeight,
      2),
    new THREE.Vector3(10,
      roadHeight,
      4),
    new THREE.Vector3(15,
      roadHeight,
      3),
    new THREE.Vector3(18,
      roadHeight,
      6),
    new THREE.Vector3(22,
      roadHeight,
      2),
    new THREE.Vector3(25,
      roadHeight,
      5),
    new THREE.Vector3(25,
      roadHeight,
      7),
    new THREE.Vector3(21,
      roadHeight,
      11),
    new THREE.Vector3(19,
      roadHeight,
      9),
    new THREE.Vector3(17,
      roadHeight,
      11),
    new THREE.Vector3(19,
      roadHeight,
      12),
    new THREE.Vector3(18,
      roadHeight,
      13)
  ]);

  curveRoadNo2 = new THREE.CatmullRomCurve3([new THREE.Vector3(15, roadHeight, 11), new THREE.Vector3(12, roadHeight, 12), new THREE.Vector3(11, roadHeight, 11), new THREE.Vector3(11, roadHeight, 9), new THREE.Vector3(12, roadHeight, 8), new THREE.Vector3(12, roadHeight, 5), new THREE.Vector3(15, roadHeight, 3), new THREE.Vector3(17, roadHeight, 7), new THREE.Vector3(16, roadHeight, 8), new THREE.Vector3(16, roadHeight, 9), new THREE.Vector3(17, roadHeight, 11)]);

  curveRoadNo2.closed = true;

  geometry = new THREE.Geometry();

  geometry.vertices = curveRoadNo1.getPoints(300);

  material = new THREE.LineBasicMaterial({
    color: 0xa9c41e
  });

  curveObject = new THREE.Line(geometry, material);

  scene.add(curveObject);

  geometry = new THREE.Geometry();

  geometry.vertices = curveRoadNo2.getPoints(200);

  material = new THREE.LineBasicMaterial({
    color: 0xa9c41e
  });

  curveObject = new THREE.Line(geometry, material);

  scene.add(curveObject);

  geometry = new THREE.SphereBufferGeometry(0.2, 32, 32);

  material = new THREE.MeshBasicMaterial({
    color: 0xffff00
  });

  vehicle = new THREE.Mesh(geometry, material);

  scene.add(vehicle);

  for (i = j = 0, len = height_map.length; j < len; i = ++j) {
    height = height_map[i];
    // vehicle.position.x = 15
    // vehicle.position.z = 3

    // Fundaments of scene.
    raise_land(30, 14, i, height);
  }

  for (i = k = 0, len1 = city_map.length; k < len1; i = ++k) {
    heightBuilding = city_map[i];
    build_city(14, 12, 4, 3, i, heightBuilding);
  }

  camera.position.x = colms / 2;

  camera.position.y = 15;

  camera.position.z = 30;

  controls.target = new THREE.Box3().setFromObject(scene).getCenter();
  controls.update();

  t = {
    value: 0
  };

  vehicle2 = new THREE.Mesh(vehicle.geometry, vehicle.material);
  scene.add(vehicle2);

  vehicle3 = new THREE.Mesh(vehicle.geometry, vehicle.material);
  scene.add(vehicle3);

  var tween11 = new TWEEN.Tween(t).to({
    value: .3
  }, 7000).delay(500).onUpdate(updateT);
  var tween12 = new TWEEN.Tween(t).to({
    value: 1
  }, 3000).delay(1000).onUpdate(updateT).onComplete(function() {
    t.value = 0
  });
  tween11.chain(tween12);
  tween12.chain(tween11);
  tween11.start();

  function updateT() {
    vehicle.position.copy(curveRoadNo2.getPointAt(t.value));
    vehicle2.position.copy(curveRoadNo2.getPointAt(1 - t.value));
  }

  let t2 = {
    value: 0
  };
  var tween21 = new TWEEN.Tween(t2).to({
    value: 1
  }, 10000).onUpdate(updateT2);
  var tween22 = new TWEEN.Tween(t2).to({
    value: 0
  }, 10000).onUpdate(updateT2);
  tween21.chain(tween22);
  tween22.chain(tween21);
  tween21.start();

  function updateT2() {
    vehicle3.position.copy(curveRoadNo1.getPointAt(t2.value));
  }

  var shipCurve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(15, 0, 15),
    new THREE.Vector3(0, 0, 25),
    new THREE.Vector3(30, 0, 25)
  ]);
  shipCurve.closed = true;

  var shipLineGeometry = new THREE.Geometry();
  shipLineGeometry.vertices = shipCurve.getPoints(200);
  var shipLine = new THREE.Line(shipLineGeometry, new THREE.LineBasicMaterial({
    color: "aqua"
  }));
  scene.add(shipLine);

  let t3 = {
    value: 0
  };
  var tween3 = new TWEEN.Tween(t3).to({
    value: 1
  }, 17000).delay(1500).onUpdate(updateT3).onComplete(function() {
    t3.value = 0;
  });
  tween3.chain(tween3);
  tween3.start();

  function updateT3() {
    let lookAt = (t3.value + 0.0001) % 1;
    ship.position.copy(shipCurve.getPointAt(t3.value));
    ship.lookAt(shipCurve.getPointAt(lookAt));
  }

  animate = function() {
    requestAnimationFrame(animate);
    TWEEN.update();
    renderer.render(scene, camera);
  };

  animate();

}).call(this);
body {
  overflow: hidden;
  margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/libs/tween.min.js"></script>

作为参考,看看这个example的源代码。