Mike
Mike

Reputation: 419

Rotate at specific pivot point

I'm trying to pivot a piece of a headphone set. It's capable of doing such in my model (in Maya).. but I can't seem to figure it out in threejs.

headphone pivot

I know I can rotate my objects X Y and Z by doing something like this:

object.rotateX(THREE.Math.degToRad(degreeX));
object.rotateY(THREE.Math.degToRad(degreeY));
object.rotateZ(THREE.Math.degToRad(degreeZ));

But how do I keep the pivot point stationary while the rests rotates/moves? So in my example, I'd want the ear piece to be able to move left and right based off of the black-ish screw you see in my picture.

Upvotes: 2

Views: 462

Answers (2)

user128511
user128511

Reputation:

Parent your model to another THREE.Object3D but to make it easy use the SceneUtils.attach function.

Example:

Click then drag, each time you click the pivot object will be moved to that location and then the model (the cube) will be attached to the pivot by calling THREE.SceneUtils.attach(model, scene, pivot). When you let off the mouse the model is detached using THREE.SceneUtils.detach(model, pivot, scene).

'use strict';

/* global THREE */

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas: canvas});

  const fov = 45;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 100;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  // make the camera look down
  camera.position.set(0, 10, 0);
  camera.up.set(0, 0, -1);
  camera.lookAt(0, 0, 0);
  
  const scene = new THREE.Scene();
  scene.background = new THREE.Color('black');

  scene.add(new THREE.GridHelper(40, 40));
     
  let model;
  {
    const cubeSize = 3;
    const cubeGeo = new THREE.BoxBufferGeometry(cubeSize, cubeSize, cubeSize);
    const cubeMat = new THREE.MeshBasicMaterial({color: 'red'});
    model = new THREE.Mesh(cubeGeo, cubeMat);
    model.position.set(.5, .5, .5);
    scene.add(model);
  }

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render() {

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }
    
    renderer.render(scene, camera);
  }
  render();

  let rotate = false;
  const startPos = {x:0, y:0};
  const raycaster = new THREE.Raycaster();
  const pivot = new THREE.Object3D();  
  scene.add(pivot);
  pivot.add(new THREE.AxesHelper(.5));
  
  function setPivotPoint(e) {
    startPos.x = e.clientX;
    startPos.y = e.clientY;
    const normalizedPosition = {
      x: e.clientX / canvas.clientWidth  *  2 - 1,
      y: e.clientY / canvas.clientHeight * -2 + 1,
    };
    
    // this part is NOT important to the answer. The question
    // is how to rotate from some point. This code is picking
    // a point. Which point to pick was not part of the question
    // but to demo the solution it's important to pick a point
    
    // put the pivot where the mouse was clicked
    raycaster.setFromCamera(normalizedPosition, camera);
     
    const intersection = raycaster.intersectObjects(scene.children)[0];
    if (intersection) {
      if (rotate) {
        removeFromPivot();
      }
      pivot.position.copy(intersection.point);
      pivot.rotation.set(0,0,0);
      pivot.updateMatrixWorld();
      rotate = true;
      
      // this the important part. We're making the cube
      // a child of 'pivot' without it moving in world space
      THREE.SceneUtils.attach(model, scene, pivot);
      render();
    }
  }
  
  function rotatePivot(e) {
    e.preventDefault();
    if (rotate) {
      const dx = e.clientX - startPos.x;
      const dy = e.clientY - startPos.y;
      const maxDelta = Math.abs(dx) > Math.abs(dy) ? dx : dy;
      pivot.rotation.y = maxDelta * 0.01;
      render();
    }
  }
  
  function removeFromPivot() {
    if (rotate) {
      rotate = false;
      THREE.SceneUtils.detach(model, pivot, scene);
      window.removeEventListener('mousemove', rotatePivot);
      window.removeEventListener('mouseup', removeFromPivot);
    }
  }
  
  canvas.addEventListener('mousedown', (e) => {
    e.preventDefault();
    setPivotPoint(e);
    if (rotate) {
      window.addEventListener('mousemove', rotatePivot);
      window.addEventListener('mouseup', removeFromPivot);
    }
  });
}

main();
html, body {
  margin: 0;
  height: 100%;
}
#c {
  width: 100%;
  height: 100%;
  display: block;
}
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/js/utils/SceneUtils.js"></script>

Upvotes: 2

M -
M -

Reputation: 28472

You could nest your headphones Mesh inside another THREE.Group, reposition the headphones inside this group so the pivot is in the desired position, then rotate the parent.

// You take your headphones and nest them inside a Group
var headphones = new THREE.Mesh(geom, material);
var parent = new THREE.Group();
parent.add(headphones);

// Then you move your headphones to the desired pivot position
headphones.position.set(-5, 0.1, 0);

// Parent is going to rotate around it origin
parent.rotateX(THREE.Math.degToRad(degreeX));

Note that if you want the pivot to be at (5, -0.1, 0), you should move headphones in the opposite direction: (-5, 0.1, 0).

Upvotes: 2

Related Questions