Code-MonKy Code-MonKy - 9 months ago 54
CoffeeScript Question

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

And it does rotate, using:

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()


With my ship + 'route' defined like so:

# 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 )


There is the issue of the rotation not going very well though:

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

getTangent does provide me with valuable data of what looks like to be the vector of direction on the curve (tangent). Now I of course could use trigonometry to get the y-axis rotation angle out of the x- and z-values, but I was hoping there would be a more easy - Three.js - way.

Also I would like the center of rotation to be in the middle of the ship. Looks like it's somewhere at the rear right now.

ANSWER

From prisoner849. Plus a tidy up of my own code.

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()

Answer Source

You can use .lookAt() method of your ship. Take a look at the updateT3 function.

I reworked your jsfiddle (a bit): simplified the creation of the ship; used Tween.js for animation; added THREE.OrbitControls() to have better view.

See the code snippet.

// 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>

As a reference, take a look at the source code of this example.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download