Prevent model from rolling when applying quaternion - javascript

How do I rotate a model and keep the model from rolling sideways? Using quaternions work fine when applied directly on an axis. As soon as I do more than one axis, the model starts twisting.
Original orientation is 0,0,1
Rotated to 1,0,0 works fine.
Rotated to 1,1,0 does not work. It starts rolling/twisting around its original z axis.
Does anyone know how to fix this? It essentially needs to lock the z-axis when applying the quaternion.
Here is the basic code:
//Box natural direction
let vB = new THREE.Vector3(0, 0, 1);
//Cable direction
let vC = new THREE.Vector3(1, 1, 0).normalize();
//Quatonion
let q = new THREE.Quaternion();
q.setFromUnitVectors(vB, vC);
//Box with connection
const boxG = new THREE.BoxGeometry(4, 8, 10);
const boxM = new THREE.Mesh(boxG, matSS);
const conG = new THREE.CylinderGeometry(0.5, 0.5, 2, 16);
conG.rotateX(Math.PI / 2);
const conM = new THREE.Mesh(conG, matSS);
conM.translateZ(5);
boxM.applyQuaternion(q);
boxM.add(conM);
scene.add(boxM);
//Cable to connect
const cabG = new THREE.CylinderGeometry(0.25, 0.25, 100, 16);
cabG.rotateX(Math.PI / 2);
const cabM = new THREE.Mesh(cabG, matBL);
const cabP = vC.clone().setLength(50);
cabM.applyQuaternion(q);
cabM.position.set(cabP.x,cabP.y,cabP.z);
scene.add(cabM);
Tried a number of things like rotating the Z-axis back, but that did not work.

Depending on how z-axis to be locked (respect to which plane), solution varies.
Solution #1: Polar angle rotation respect to Z-axis + azimuthal angle rotation in X-Y plane
In case box natural direction coincides pole,
it's possible to control the box direction by simply specifying another box-axis (e.g. rotation axis) to adjust roll angle.
//Box natural direction
let vB = new THREE.Vector3(0, 0, 1);
//Cable direction
let vC = new THREE.Vector3(1, 1, 0).normalize();
//Quatonion
let q = new THREE.Quaternion();
q.setFromUnitVectors(vB, vC);
console.log("q before", q);
// Box's rotation axis
let vAxisFrom = new THREE.Vector3(0, 1, 0); // or (1, 0, 0)
// Axis of quaternion rotation (from vB to vC)
let vAxisTo = vB.clone().cross(vC).normalize();
let q_roll = new THREE.Quaternion();
q_roll.setFromUnitVectors(vAxisFrom, vAxisTo);
q.multiply(q_roll);
console.log("q after", q);
Solution #2: Polar angle rotation respect to Y-axis + azimuthal angle rotation in Z-X plane
In this case, composing quaternion as a combination of yaw angle rotation around Y-axis and pitch angle rotation around X axis would be a simple way.
//Box natural direction
let vB = new THREE.Vector3(0, 0, 1);
//Cable direction
let vC = new THREE.Vector3(1, 1, 0).normalize();
//Quatonion
let q = new THREE.Quaternion();
let vAxisInPlane = vC.clone().setY(0.0).normalize();
if (vAxisInPlane.lengthSq() < 1.0) {
q.setFromUnitVectors(vB, vC);
}
else {
q.setFromUnitVectors(vB, vAxisInPlane);
console.log("q before", q);
let q_pitch = new THREE.Quaternion();
q_pitch.setFromUnitVectors(vAxisInPlane, vC);
q.premultiply(q_pitch);
console.log("q after", q);
}

Related

How to rotate camera to point without using lookAt method

I am trying to implement 360 degree rotation camera with offset to another 3d object and lookAt to point. Camera offset and lookAt points are relative to 3d object quaternion.
I have 3d object which locates at center of the world coordinates. Then i want to offset camera like third-person camera implementation. Position isn't a problem, i tried like this:
const cameraOffset = new Vector3(0, 2, 3)
cameraOffset.applyQuaternion(this.ship.quaternion)
cameraOffset.add(this.ship.position)
this.position.lerp(cameraOffset, 0.1)
this.tjsObject.position.copy(this.position)
this.tjsObejct: Three.js Object3D type.
It works and the offset vector follows object when it is rotating.
But i want make focus camera at the different direction with the object like so:
const cameraLookAt = new Vector3(0, 1, -5)
cameraLookAt.applyQuaternion(this.ship.quaternion)
cameraLookAt.add(this.ship.position)
this.lookAt.lerp(cameraLookAt, 0.1)
this.tjsObject.lookAt(this.lookAt)
lookAt works fine, but when 3d object rotation higher 180 or lower -180 degrees, lookAt method uses camera.up vector, so camera always oriented to it.
I tried find euler angles between cameraLookAt vector and camera world direction, but have no luck with it:
const f = new Vector3()
this.tjsObject.getWorldDirection(f)
f.sub(this.tjsObject.position)
f.normalize()
cameraLookAt.sub(this.tjsObject.position)
cameraLookAt.normalize()
const x = new Vector3(1, 0, 0)
const y = new Vector3(0, 1, 0)
const z = new Vector3(0, 0, 1)
x.normalize()
y.normalize()
z.normalize()
const fProjectXY = f.clone().projectOnPlane(z)
const fProjectXZ = f.clone().projectOnPlane(y)
const fProjectZY = f.clone().projectOnPlane(x)
const cameraLookAtProjectXY = cameraLookAt.clone().projectOnPlane(z)
const cameraLookAtProjectXZ = cameraLookAt.clone().projectOnPlane(y)
const cameraLookAtProjectZY = cameraLookAt.clone().projectOnPlane(x)
console.log(fProjectXY, fProjectXZ, fProjectZY)
const zAngleFCamera = fProjectXY.angleTo(cameraLookAtProjectXY)
const yAngleFCamera = fProjectXZ.angleTo(cameraLookAtProjectXZ)
const xAngleFCamera = fProjectZY.angleTo(cameraLookAtProjectZY)
const eulerRotation = new Euler()
eulerRotation.set(xAngleFCamera, yAngleFCamera, zAngleFCamera)
const q = new Quaternion().setFromEuler(eulerRotation)
this.tjsObject.quaternion.slerp(q, 0.1)
I tried change camera.up vector when 3d object rotation higher 180 or lower -180 degrees too, but it was noticeable that she was switching

Rotate object to face the mouse cursor

I can't figure out how to properly rotate an object in ThreeJS. The object is a simple box geometry that is rendered from above somewhere on the screen.
Codepen with the full code.
The object is supposed to rotate around it's own Y axis (the vertical axis) to always face the mouse cursor. I can get it to rotate as the cursor moves around the global axis in the middle of the screen, but not when the cursor moves around the object's own local axis.
UPDATE: I got it to work using ray casting. See code further down or in the codepen.
The orthographic camera is set up like this:
camera = new THREE.OrthographicCamera(
window.innerWidth / - 2, // left
window.innerWidth / 2, // right
window.innerHeight / 2, // top
window.innerHeight / - 2, // bottom
0, // near
1000 ); // far
camera.position.set(0, 0, 500)
camera.updateProjectionMatrix()
The object is set up like this:
const geometry = new THREE.BoxGeometry( 10, 10, 10 );
const material = new THREE.MeshBasicMaterial( { color: "grey" } );
const mesh = new THREE.Mesh( geometry, material );
Code for handling rotation at mousemove:
function onMouseMove(event) {
// Get mouse position
let mousePos = new THREE.Vector2();
mousePos.set(
(event.clientX / window.innerWidth) * 2 - 1, // x
-(event.clientY / window.innerHeight) * 2 + 1); // y
// Calculate angle
let angle = Math.atan2(mousePos.y, mousePos.x);
// Add rotation to object
mesh.rotation.set(
0, // x
angle, // y
0) // z
}
I have also tried
mesh.rotateY(angle)
but this only makes the object spinn like a helicopter.
It's obvious the rotation needs to be based on the relationship between the cursor and the local axis rather than the global axis. I just can't figure out how to achieve that.
UPDATE
I have added a codepen at the top of the question.
UPDATE
I got it to work using the following method with ray casting.
let plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
let pointOfIntersection = new THREE.Vector3();
let raycaster = new THREE.Raycaster();
let mousePos = new THREE.Vector2();
mousePos.set(
(event.clientX / window.innerWidth) * 2 - 1, // x
-(event.clientY / window.innerHeight) * 2 + 1))
raycaster.setFromCamera(mousePos, camera);
raycaster.ray.intersectPlane(plane, pointOfIntersection);
mesh.lookAt(pointOfIntersection)

How can I rotate a vector 90 degrees into a perpendicular plane, and then 15 degrees free from that plane?

What I ultimately want is a vector, giving the direction of the green line in the image below, knowing only the position of the yellow and green dots.
To be more specific, it's angle can be random as long as it's endpoint ends up somewhere on the green-blue surface of the cylinder. So, 360° free around cylinder, and about 15° limited to the edges of the cylinder.
The cylinder is perpendicular to the line from the yellow and green dot.
Length is not important, only direction.
My main problem is I don't know how to go from vector Yellow to green dot, to any vector perpendicular to it.
PS None of these things are aligned on a x y z axis. That grid is not xyz, just to help visualize.
here is the code: given an angle theta and two points it will give you a vector starting from pointStart perpendicular to the vector from pointStart to pointEnd:
function perpendicularVector(pointStart,pointEnd,theta){
let vDiff = new THREE.Vector3(0, 0, 0)
.subVectors(pointEnd, pointStart)
.normalize()
let V = new THREE.Vector3(
vDiff.y + vDiff.x * vDiff.z,
vDiff.y * vDiff.z -vDiff.x,
-(vDiff.x * vDiff.x) - vDiff.y * vDiff.y
)
return
V .applyAxisAngle(vDiff, theta)
.applyAxisAngle( new THREE.Vector3().multiplyVectors(V, vDiff).normalize(), 15*Math.PI/180 )
}
here is a small showoff of what the above code do: (the snippet is intentionally bad because its there just to show the functionality of the above code)
(you can zoom rotate and pan using the mouse on the render that appears after you click run snippet)
body {
font-family: sans-serif;
margin: 0;
background-color: #e2cba9;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
<div id="app"></div>
<script type="module">
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three#0.121.1/examples/jsm/controls/OrbitControls.js";
import * as THREE from "https://cdn.jsdelivr.net/npm/three#0.121.1/build/three.module.js";
var scene = new THREE.Scene, theta = 0;
let point1 = new THREE.Vector3(4, 2, 1),
point2 = new THREE.Vector3(0, 3, 3);
function perpendicularVector(e, n, t) {
let r = new THREE.Vector3(0, 0, 0).subVectors(n, e).normalize(),
o = new THREE.Vector3(r.y, -r.x, 0),
i = new THREE.Vector3(r.x * r.z, r.y * r.z, -r.x * r.x - r.y * r.y);
var a = o.multiplyScalar(Math.cos(t)).add(i.multiplyScalar(Math.sin(t)));
return a.add(e), a
}
function pointAtCoords(e, n) {
let t = new THREE.MeshBasicMaterial({ color: n }),
r = new THREE.SphereGeometry(.1, 8, 8),
o = new
THREE.Mesh(r, t);
return o.position.add(e), o
}
function lineFromAtoB(e, n, t) {
let r = new THREE.LineBasicMaterial({ color: t }),
o = [];
o.push(e), o.push(n);
let i = (new THREE.BufferGeometry).setFromPoints(o);
return new THREE.Line(i, r)
}
var renderer = new THREE.WebGLRenderer({ antialias: !0 });
renderer.setSize(window.innerWidth, window.innerHeight), document.getElementById("app").appendChild(renderer.domElement);
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight,
.1, 1e3);
camera.position.set(7, 7, 8), camera.lookAt(new THREE.Vector3), camera.position.add(new THREE.Vector3(3, 0, 3));
var controls = new OrbitControls(camera, renderer.domElement);
function drawEverything(e) {
const n = new THREE.AxesHelper(30);
scene.add(n);
const t = new THREE.GridHelper(30, 30);
t.position.add(new THREE.Vector3(15, 0, 15)), scene.add(t);
const r = new THREE.GridHelper(30, 30);
r.rotateX(Math.PI / 2), r.position.add(new THREE.Vector3(15, 15, 0)), scene.add(r);
const o = new THREE.GridHelper(30, 30);
o.rotateZ(Math.PI / 2), o.position.add(new THREE.Vector3(0, 15, 15)), scene.add(o);
let i = new THREE.Vector3(0, 0, 0),
a = perpendicularVector(point1, point2, e);
scene.add(pointAtCoords(point1, 16776960)), scene.add(pointAtCoords(point2, 65280));
var d = pointAtCoords(a, 255);
scene.add(d), scene.add(lineFromAtoB(point1, point2, 16711935)), scene.add(lineFromAtoB(i, point1, 16711680)), scene.add(lineFromAtoB(i, point2, 16711680)), scene.add(lineFromAtoB(point1, a, 65280))
}
function animate() {
scene = new THREE.Scene, drawEverything(theta += .1),
setTimeout((() => {
requestAnimationFrame(animate)
}), 1e3 / 30), renderer.render(scene, camera)
}
animate();
</script>
This is totally achievable with some math calculations. The term you're looking for is "Orthogonal vectors", which means vectors that are perpendicular to each other. The cylinder radius is orthogonal to the line between blue to yellow points.
However, since you're already using Three.js, you can just let it do all the hard work for you with the help of an Object3D.
// Declare vectorA (center, green)
const vecA = new THREE.Vector3(xA, yA, zA);
// Declare vectorB (destination, yellow)
const vecB = new THREE.Vector3(xB, yB, zB);
// Create helper object
const helper = new THREE.Object3D();
// Center helper at vecA
helper.position.copy(vecA);
// Rotate helper towards vecB
helper.lookAt(vecB);
// Move helper perpendicularly along its own y-axis
const cylinderRadius = 27;
helper.translateY(cylinderRadius);
// Now you have your final position!
console.log(helper.position);
In the diagram below, the helper Object3D is shown as a red line only to give you a sense of its rotation and position, but in reality it is invisible unless you add a Mesh to it.
If you want to add/subtract 15 degrees from the perpendicular, you could just rotate the helper along its own x-axis before translateY()
const xAngle = THREE.MathUtils.degToRad(15);
helper.rotateX(xAngle);
const cylinderRadius = 27;
helper.translateY(cylinderRadius);

Calculating matrix for bone to look at object in Three.js

I am having problems doing the calculation for a bone to "look at" an object. First off, the lookAt function is not working for me. I can kind of understand this because the bone's matrix is an identity matrix, in local space so it wont work out of the box. (doing lookAt produces strange results).
Here is what I have managed to far. It rotates the head left to right, but up and down I haven't calculated yet. I should add, it doesn't work very well at the moment :(
//local looking forawrd vector
var v1 = new THREE.Vector3(0, 0, 1);
//vector represting camera in local space?
var v2 = new THREE.Vector3(
camera.position.x - headBone.matrixWorld.elements[12],
0,
camera.position.z - headBone.matrixWorld.elements[14]
);
v2.normalize();
//seem to need this to determine whether to rotate left or right
var mult = 1.0;
if(camera.position.x - headBone.matrixWorld.elements[12] < 0.0000) {
mult = -1.0;
}
var fAng = v1.angleTo(v2) * mult;
//var headUD = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), 0.0);
headBone.quaternion.setFromAxisAngle(new THREE.Vector3(0,0,-1), fAng);
//headBone.quaternion.multiply(headUD);
Suggestions on how to do this properly would be GREATLY appeciated as I can't figure out the math at the moment. In the mean time I will continue to do what feels like hacking away...
If it helps, below is the worldMatrix of the headBone at rest:
0.9950730800628662, -0.02474924363195896, 0.09600517153739929, 0
-0.09914379566907883, -0.2472541183233261, 0.9638656973838806, 0
-0.00011727288801921532, -0.9686352014541626, -0.24848966300487518, 0
0.5000047087669373, 1.5461946725845337, -0.01913299970328808, 1
UPDATE
I have updated my code. It does work when I rotate on 1 axis (x axis to move head up and down towards camera or z axis to move it left or right towards camera) but when I attempt to combine those 2 rotations things go haywire when I am at the sides of the character the head starts to spin.
function lookAt() {
var v1 = new THREE.Vector3(0, 0, 1);
var v2 = new THREE.Vector3(
camera.position.x - headBone.matrixWorld.elements[12],
0,
camera.position.z - headBone.matrixWorld.elements[14]
).normalize();
var mult = 1.0;
if(camera.position.x - headBone.matrixWorld.elements[12] < 0.0000) {
mult = -1.0;
}
var fAng = v2.angleTo(v1) * mult;
v2 = new THREE.Vector3(
0,
camera.position.y - headBone.matrixWorld.elements[13],
camera.position.z - headBone.matrixWorld.elements[14]
);
mult = 1.0;
if(camera.position.y - headBone.matrixWorld.elements[13] > 0.0000) {
mult = -1.0;
}
fAng2 = v2.angleTo(v1) * mult;
headBone.rotation.set(
Math.max(-0.5, Math.min(fAng2, 0.1)),
0,
-Math.max(-1.0, Math.min(fAng, 1.0))
);
}
I can separate out the quaternions like so:
//head left and right to match camera
var Q1 = new THREE.Quaternion().setFromEuler( new THREE.Euler(0,0,-fAng), false );
//head up and down to match camera
var Q2 = new THREE.Quaternion().setFromEuler( new THREE.Euler(fAng2,0,0), false );
Q2.multiply(Q1);
headBone.quaternion.copy(Q2);
But this still does not track the camera properly. Note that applying one or the other does work.
I have reached an acceptable answer. I found a way to make the bone look at an arbitrary point. Perhaps this could be expanded on and included in THREE.js.
This still suffers from problems and does not seem 100% accurate. For example I placed this on my head bone, and it goes upside down when I am pointing behind the character. Does anyone know how it can be improved? Please comment!
Also note that I am pointing the bone's Z axis THREE.Vector3(0,0,1);. It could be moved to a parameter.
function boneLookAt(bone, position) {
var target = new THREE.Vector3(
position.x - bone.matrixWorld.elements[12],
position.y - bone.matrixWorld.elements[13],
position.z - bone.matrixWorld.elements[14]
).normalize();
var v = new THREE.Vector3(0,0,1);
var q = new THREE.Quaternion().setFromUnitVectors( v, target );
var tmp = q.z;
q.z = -q.y;
q.y = tmp;
bone.quaternion.copy(q);
}

Calculate near/far plane vertices using THREE.Frustum

I need some help to deal with THREE.Frustum object.
My problem:
I need to calculate near/far plane vertices; I've taken a look at these tutorials
http://www.lighthouse3d.com/tutorials/view-frustum-culling/view-frustums-shape/
http://www.lighthouse3d.com/tutorials/view-frustum-culling/geometric-approach-extracting-the-planes/
and I've sketched this function implementing exactly (I hope so) the procedure explained (just to get top-left/right vertices, assuming the camera can only look left and right):
// Near Plane dimensions
hNear = 2 * Math.tan(camera.fov / 2) * camera.near; // height
wNear = hNear * camera.aspect; // width
// Far Plane dimensions
hFar = 2 * Math.tan(camera.fov / 2) * camera.far; // height
wFar = hFar * camera.aspect; // width
getVertices : function() {
var p = camera.position.clone();
var l = getCurrentTarget(); // see below
var u = new THREE.Vector3(0, 1, 0);
var d = new THREE.Vector3();
d.sub(l, p);
d.normalize();
var r = new THREE.Vector3();
r.cross(u, d);
r.normalize();
// Near Plane center
var dTmp = d.clone();
var nc = new THREE.Vector3();
nc.add(p, dTmp.multiplyScalar(camera.near));
// Near Plane top-right and top-left vertices
var uTmp = u.clone();
var rTmp = r.clone();
var ntr = new THREE.Vector3();
ntr.add(nc, uTmp.multiplyScalar(hNear / 2));
ntr.subSelf(rTmp.multiplyScalar(wNear / 2));
uTmp.copy(u);
rTmp.copy(r);
var ntl = new THREE.Vector3();
ntl.add(nc, uTmp.multiplyScalar(hNear / 2));
ntl.addSelf(rTmp.multiplyScalar(wNear / 2));
// Far Plane center
dTmp.copy(d);
var fc = new THREE.Vector3();
fc.add(p, dTmp.multiplyScalar(camera.far));
// Far Plane top-right and top-left vertices
uTmp.copy(u);
rTmp.copy(r);
var ftr = new THREE.Vector3();
ftr.add(fc, uTmp.multiplyScalar(hFar / 2));
ftr.subSelf(rTmp.multiplyScalar(wFar / 2));
uTmp.copy(u);
rTmp.copy(r);
var ftl = new THREE.Vector3();
ftl.add(fc, uTmp.multiplyScalar(hFar / 2));
ftl.addSelf(rTmp.multiplyScalar(wFar / 2));
getCurrentTarget : function() {
var l = new THREE.Vector3(0, 0, -100);
this.camera.updateMatrixWorld();
this.camera.matrixWorld.multiplyVector3(l);
return l;
}
This seems to work but...
My Question:
Can I obtain the same result in a more elegant (maybe more correct) way, using a THREE.Frustum object?
Three.Frustum is not really going to help you -- it is a set of planes. The good news is your solution appears correct, but there is an easier way to think about this.
The upper right corner of the near plane is a point in camera space with these coordinates:
var ntr = new THREE.Vector3( wNear / 2, hNear / 2, -camera.near );
using your definition of wNear and hNear, which are correct.
Now, making sure that camera.matrixWorld is updated, you convert that point to world coordinates like so:
camera.updateMatrixWorld();
ntr.applyMatrix4( camera.matrixWorld );
Now, flip the signs to get the other three corners, and then repeat the calculation for the far plane.
See, you had it right; you just took a more complicated route. :-)
EDIT: updated to three.js r.66

Categories

Resources