Bilal
Bilal

Reputation: 3854

Better arrow cone rotation using ThreeJs

I'm drawing an arrow between pick and place points using QuadraticBezierCurve3 and ConeGeometry, but the rotation of the cone is not perfect as you can see in the snippet down!

Can you please tell me how can I improve the visualization (cone rotation) of my arrow function? thanks in advance.

var camera, scene, renderer;

init();
animate();

 /**
 draw arrow
*/
function drawArrow(pick_pos, place_pos, scene) {
    /**
    * Curve
    */
    const start = new THREE.Vector3();
    start.add(pick_pos)
    
    const finish = new THREE.Vector3();
    finish.add(place_pos)

    let mid = new THREE.Vector3();
    mid.add(start);
    let dist = finish.x + mid.x;
    mid.x = dist/2;
    mid.y += 3;

const b_curve = new THREE.QuadraticBezierCurve3(
    start,
    mid,
    finish
);
const points = b_curve.getPoints( 100 );
const line_geometry = new THREE.BufferGeometry().setFromPoints( points );
const material = new THREE.LineBasicMaterial( { color: 0x0ffffb, linewidth:5 } );
const curve = new THREE.Line( line_geometry, material );
/**
 * Cone
 */
const cone_geometry = new THREE.ConeGeometry( 0.2, 1, 8 );
const cone_material = new THREE.MeshBasicMaterial({ color: 0xff00fb });

const cone = new THREE.Mesh( cone_geometry, cone_material );
cone.position.set(points[98].x, points[98].y, points[98].z);
cone.rotateX(Math.PI * points[100].x); 
cone.rotateZ(Math.PI * points[100].z);

/**
 * Arrow
 */

const arrow = new THREE.Group();
arrow.add(curve);
arrow.add(cone);
arrow.name = 'arrow';
scene.add(arrow);
}

/**
 Create the scene, camera, renderer
*/
function init() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x21252d);
  renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
  camera.position.x = 0;
  camera.position.y = 8;
  camera.position.z = 10;
  scene.add(camera);
  
  controls = new THREE.OrbitControls(camera, renderer.domElement);
  
  const pick = new THREE.Vector3(0, 0, 0);
  const place = new THREE.Vector3(5, 0, 5);
  drawArrow(pick, place, scene);
  window.addEventListener('resize', onWindowResize);

}

function onWindowResize() {
 camera.aspect = window.innerWidth / window.innerHeight;
 camera.updateProjectionMatrix();
 renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
  requestAnimationFrame(animate);
  render();
}

function render() {
  renderer.render(scene, camera);
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.min.js"></script>

Upvotes: 0

Views: 350

Answers (1)

sungryeol
sungryeol

Reputation: 4015

Solution

  1. The cone's default position points the sky(z); it invalidates the rotation.
  2. It seems the arc's direction is inverted.
  3. .lookAt() is a great shortcut for getting rotation from two different 3D vectors.
// ANSWER
const VEnd = points[15].clone();
// fix cone's default position which points skyward.
cone.geometry.rotateX( -Math.PI / 2 );
cone.lookAt(VEnd);

try run the code below:

var camera, scene, renderer;

init();
animate();

 /**
 draw arrow
*/
function drawArrow(pick_pos, place_pos, scene) {
    /**
    * Curve
    */
    const start = new THREE.Vector3();
    start.add(pick_pos)
    
    const finish = new THREE.Vector3();
    finish.add(place_pos)

    let mid = new THREE.Vector3();
    mid.add(start);
    let dist = finish.x + mid.x;
    mid.x = dist/2;
    mid.y += 3;

const b_curve = new THREE.QuadraticBezierCurve3(
    start,
    mid,
    finish
);
const points = b_curve.getPoints( 100 );
const line_geometry = new THREE.BufferGeometry().setFromPoints( points );
const material = new THREE.LineBasicMaterial( { color: 0x0ffffb, linewidth:5 } );
const curve = new THREE.Line( line_geometry, material );
/**
 * Cone
 */
const cone_geometry = new THREE.ConeGeometry( 0.2, 1, 8 );
const cone_material = new THREE.MeshBasicMaterial({ color: 0xff00fb });

const cone = new THREE.Mesh( cone_geometry, cone_material );
// ANSWER
const VEnd = points[15].clone();
// fix cone's default position which points skyward.
cone.geometry.rotateX( -Math.PI / 2 );
cone.lookAt(VEnd);

/**
 * Arrow
 */

const arrow = new THREE.Group();
arrow.add(curve);
arrow.add(cone);
arrow.name = 'arrow';
scene.add(arrow);
}

/**
 Create the scene, camera, renderer
*/
function init() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x21252d);
  renderer = new THREE.WebGLRenderer({antialias: true});
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
  camera.position.x = 0;
  camera.position.y = 8;
  camera.position.z = 10;
  scene.add(camera);
  
  controls = new THREE.OrbitControls(camera, renderer.domElement);
  
  const pick = new THREE.Vector3(0, 0, 0);
  const place = new THREE.Vector3(5, 0, 5);
  drawArrow(pick, place, scene);
  window.addEventListener('resize', onWindowResize);

}

function onWindowResize() {
 camera.aspect = window.innerWidth / window.innerHeight;
 camera.updateProjectionMatrix();
 renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
  requestAnimationFrame(animate);
  render();
}

function render() {
  renderer.render(scene, camera);
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.min.js"></script>

Upvotes: 1

Related Questions