Reputation: 30158
I have a threejs model in a scene whose 0,0,0 point is at its end. I've had to rotate it so that its resting position is at the angle I want, and animate it into the scene, so neither the position nor the rotation is at 0,0,0. Furthermore, the camera has been positioned and set to look at the object, so its position and rotation have been changed.
I want to get the user's mouse coordinates, and just have the model rotate around its center point on a world Y axis a certain number of max and min degrees. I know how to map the mouse coordinates to the degree / radian angle I want, but how do I tell the model to rotate?
I've tried model.rotation.y = [radians]
, and it rotates, I'm assuming, based on whatever rotation matrix it currently has (or at the very least, not the way I expect it to). And if I do rotateOnWorldAxis
it rotates from its 0,0,0 point at which it was imported, which not where I want it to be, and furthermore isn't an absolute number of degrees (i.e. it rotates 30 degrees from where it's at currently each mouse move, rather than to 30 degrees on the world origin.
How do I get it to rotate on its center point to a certain degree angle on the world Y axis?
Model position is at {_x: 0, _y: -0.3, _z: -2}
and its rotation is at {x: -2, y: 2.4, z: 0}
Camera position is {x: 0, y: 0, z: 5}
and rotation {_x: 0.3926990816987242, _y: 0, _z: -0}
.
Current code is:
const target = this.mouseTarget;
const mouseX = clientX - this.halfWidth; //halfWidth is half the screen width, client X is the mouse e.clientX
const mouseY = clientY - this.halfHeight;
target.x += (mouseX - target.x) * 0.02;
target.y += (-mouseY - target.y) * 0.02;
target.z = this.camera.position.z; // assuming the camera is located at ( 0, 0, z );
const maxRotDeg = 30;
const maxRot = this.degToRad(maxRotDeg);
const rotVal = this.mapValue(
target.x,
-this.halfWidth,
this.halfWidth,
-maxRot,
maxRot,
); //works to give me the radians that I want for rotation
//this.modelGroup.rotateOnWorldAxis(this.rotationAxisY, rotVal); //doesn't work
//this.modelGroup.lookAt(target); //doesn't work
Upvotes: 0
Views: 2792
Reputation:
The simplest generic way to rotate any object anywhere in the scene hierarchy around some arbitrary rotation center is to make an Object3D
where you want that rotation center to be. Let's call it the "pivotPoint". Then take the thing you want to rotate, attach
it to the pivotPoint. rotate the pivotPoint, then put the object back where it was (re-attach it to it's previous parent). In other words
const pivotPoint = new THREE.Object3D();
pivotPoint.position.set(...); // optional
pivotPoint.rotation.set(...); // optional
someParent.add(pivotPoint)
Then to rotate some other thing around that point
const parent = thingToRotate.parent;
pivotPoint.attach(thingToRotate);
pivotPoint.rotation.set( ... ); // do the rotation
parent.attach(thingToRotate);
That is the most generic way to rotate around any point in any orientation regardless of how deep in the scene hierarchy the thingToRotate is.
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 50;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 3, 3);
camera.lookAt(0, 1, 0);
const scene = new THREE.Scene();
scene.background = new THREE.Color('white');
[[-1, 2, 4], [1, 2, -3]].forEach(pos => {
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(...pos);
scene.add(light);
});
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function makeInstance(parent, pos, rot, scale) {
const material = new THREE.MeshPhongMaterial({color: Math.random() * 0xFFFFFF | 0});
const mesh = new THREE.Mesh(geometry, material);
parent.add(mesh);
mesh.position.set(...pos);
mesh.rotation.set(...rot);
return mesh;
}
const m1 = makeInstance(scene, [0, 0, 0], [0, 0.7, 0]);
const m2 = makeInstance(m1, [1, 1, 0], [0.3, 0.5, 0]);
const m3 = makeInstance(m1, [-1, 1, 0], [-0.9, 0.5, 0]);
const m4 = makeInstance(m2, [1, 1, 0], [-0.4, 1.3, 0.8]);
let thingToRotate = m3;
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;
}
const pivotPoint = new THREE.Object3D();
scene.add(pivotPoint);
let then = 0;
function render(now) {
now *= 0.001;
delta = now - then;
then = now;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
{
// put pivotPoint at origin of thingToRotate in world space
// note: an object's origin might not be its center. If oyu
// want its center you need to compute its center
thingToRotate.getWorldPosition(pivotPoint.position);
// reset rotation for pivotPoint
pivotPoint.rotation.set(0, 0, 0);
// rotate thingToRotate around pivotPoint's yAxis
const parent = thingToRotate.parent;
pivotPoint.attach(thingToRotate);
pivotPoint.rotation.y = delta;
parent.attach(thingToRotate);
}
m1.rotation.y -= delta * 0.1;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function setClick(selector, thing) {
document.querySelector(selector).addEventListener('input', () => {
thingToRotate = thing;
});
}
setClick('#m1', m1);
setClick('#m2', m2);
setClick('#m3', m3);
setClick('#m4', m4);
requestAnimationFrame(render);
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
#ui {
position: absolute;
left: 0;
top: 0;
}
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.min.js"></script>
<div id="ui">
<div><input type="radio" name="thing" id="m1"><label for="m1">m1</label></div>
<div><input type="radio" name="thing" id="m2"><label for="m2">m2</label></div>
<div><input type="radio" name="thing" id="m3" checked><label for="m3">m3</label></div>
<div><input type="radio" name="thing" id="m4"><label for="m4">m4</label></div>
</div>
There are lots other shortcuts but each one requires knowing exactly how you have your scene setup.
For example if you the only thing you ever want to do is rotate things around the world Y axis but those things themselves have rotation then parent them to some other Object3D
, set the position on the Object3D
and the rotation on the thing.
Example: Imagine you had some thing oriented and rotated like this
scene.add(thing);
thing.position.set(100, 200, 300); // some arbitrary position
thing.rotation.set(1, 2, 3); // some arbitrary rotation
Change it to this
const helper = new THREE.Object3D();
scene.add(helper);
helper.position.set(100, 200, 300); // some arbitrary position on helper
thing.rotation.set(1, 2, 3); // some arbitrary rotation on thing
Now you can rotate helper.rotation.y
and thing will rotate around its origin at the world Y axis.
If you want to rotate around the center of some object then you need to compute its center (center != origin though they are often the same) and add other helpers. See this article toward the bottom where it makes 3d text rotate around its center because its origin is on the left off the text.
This article also covers moving rotation points by adding helpers like above.
Upvotes: 2