I've been struggling for a while with the concepts of quaternions and I think that it may have something to do with this particular challenge.
If you know three.js, you may be familiar with the equirectangular panorama video example. What I've been trying to do is to simply extract the camera's rotation at any given moment in a format that I understand (radians, degrees...) for each axis. In theory, shouldn't I be able to simply tap into the camera's rotation.x/y/z parameters to find those out? I'm getting strange values, though.
Check out this example:
http://www.spotted-triforce.com/other_files/three/test.html
I'm outputting the camera's xyz rotation values at the upper left corner and instead of expected values, the numbers bounce around between positive and negative values.
Now, this example doesn't use one of the control scripts that are available. Rather, it creates a vector to to calculate a camera target to look at. Here's what the camera code looks like:
function init() {
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1100 );
camera.target = new THREE.Vector3( 0, 0, 0 );
}
function onDocumentMouseDown( event ) {
event.preventDefault();
isUserInteracting = true;
onPointerDownPointerX = event.clientX;
onPointerDownPointerY = event.clientY;
onPointerDownLon = lon;
onPointerDownLat = lat;
}
function onDocumentMouseMove( event ) {
if ( isUserInteracting === true ) {
lon = ( onPointerDownPointerX - event.clientX ) * 0.1 + onPointerDownLon;
lat = ( event.clientY - onPointerDownPointerY ) * 0.1 + onPointerDownLat;
}
}
function onDocumentMouseUp( event ) {
isUserInteracting = false;
}
function update() {
lat = Math.max( - 85, Math.min( 85, lat ) );
phi = THREE.Math.degToRad( 90 - lat );
theta = THREE.Math.degToRad( lon );
camera.target.x = 500 * Math.sin( phi ) * Math.cos( theta );
camera.target.y = 500 * Math.cos( phi );
camera.target.z = 500 * Math.sin( phi ) * Math.sin( theta );
camera.lookAt( camera.target );
}
Does anybody know what these strange values are that I'm getting and how I can extract proper rotation values that I can then use on another object to mirror the motion?
If you set
camera.rotation.order = "YXZ"
( the default is "XYZ" ) the Euler angles will make a lot more sense to you:
rotation.y will be the camera heading in radians
rotation.x will be the camera pitch in radians
rotation.z will be the camera roll in radians
The rotations will be applied in that order.
three.js r.70
Related
I’m building a basketball game with three.js and ammo.js. (Enable3d)
The hoop/rim and ball’s positions are constantly changing (AR) so the shots will have to be dynamic and relative.
I need to calculate the force vector to apply to the ball, for a successful shot. In the game play the user will swipe to shoot and this will effect the “perfect shot” force.
I’ve seen many examples of code and equations for calculations of trajectories, ballistics, etc, and I’ve been converting them to JavaScript from C# as a lot of the scripts I’m finding are on the Unity forums.
I need a function that calculates the initial force vector3 To apply to the ball using the position of the ball, position of the hoop, the ball’s mass, and gravity. The initial angle or max height (terminal velocity y) will also have to be passed to this function, as I’ve noticed in all the equations and calculators I’ve seen.
EDIT:
So I converted a script I found on the Unity forums (Below), to Javascript/Three.js:
function getBallVelocity( ballPos, rimPos, angleDegrees, gravity ) {
const Vector3 = {
forward: new THREE.Vector3( 0, 0, 1 ),
up: new THREE.Vector3( 0, 1, 0 )
};
// Get angle in radians, from angleDegrees argument
const angle = THREE.Math.degToRad( angleDegrees );
gravity = gravity || 9.81;
// Positions of this object and the target on the same plane
const planarRimPos = new THREE.Vector3( rimPos.x, 0, rimPos.z ),
planarBallPos = new THREE.Vector3( ballPos.x, 0, ballPos.z );
// Planar distance between objects
const distance = planarRimPos.distanceTo( planarBallPos );
// Distance along the y axis between objects
const yOffset = rimPos.y - ballPos.y;
// Calculate velocity
const initialVelocity = ( 1 / Math.cos( angle ) ) * Math.sqrt( ( 0.5 * gravity * Math.pow( distance, 2 ) ) / ( distance * Math.tan( angle ) + yOffset ) ),
velocity = new THREE.Quaternion( 0, initialVelocity * Math.sin( angle ), initialVelocity * Math.cos( angle ) );
// Rotate our velocity to match the direction between the two objects
const planarPosDifferenceBetweenObjects = planarRimPos.sub( planarBallPos ),
angleBetweenObjects = Vector3.forward.angleTo( planarPosDifferenceBetweenObjects ),
angleUpRotated = new THREE.Quaternion().setFromAxisAngle( Vector3.up, angleBetweenObjects ),
finalVelocity = angleUpRotated.multiply( velocity );
return finalVelocity;
}
I'm calling the shot like this:
const velocity = getBallVelocity( ball.position, rim.position, 45 );
ball.body.applyForce( velocity.x, velocity.y, velocity.z )
It's shoots the wrong direction and very weak. I assume I'm not doing the rotation correctly at the end of the function, and the weakness could be due to not having mass multiplied. The ball's mass it 2, so I assume I should be multiplying some Y values by 2?? Have no idea :(
Here is the C# script I attempted a conversion of:
using UnityEngine;
using System.Collections;
public class ProjectileFire : MonoBehaviour {
[SerializeField]
Transform target;
[SerializeField]
float initialAngle;
void Start () {
var rigid = GetComponent<Rigidbody>();
Vector3 p = target.position;
float gravity = Physics.gravity.magnitude;
// Selected angle in radians
float angle = initialAngle * Mathf.Deg2Rad;
// Positions of this object and the target on the same plane
Vector3 planarTarget = new Vector3(p.x, 0, p.z);
Vector3 planarPostion = new Vector3(transform.position.x, 0, transform.position.z);
// Planar distance between objects
float distance = Vector3.Distance(planarTarget, planarPostion);
// Distance along the y axis between objects
float yOffset = transform.position.y - p.y;
float initialVelocity = (1 / Mathf.Cos(angle)) * Mathf.Sqrt((0.5f * gravity * Mathf.Pow(distance, 2)) / (distance * Mathf.Tan(angle) + yOffset));
Vector3 velocity = new Vector3(0, initialVelocity * Mathf.Sin(angle), initialVelocity * Mathf.Cos(angle));
// Rotate our velocity to match the direction between the two objects
float angleBetweenObjects = Vector3.Angle(Vector3.forward, planarTarget - planarPostion);
Vector3 finalVelocity = Quaternion.AngleAxis(angleBetweenObjects, Vector3.up) * velocity;
// Fire!
rigid.velocity = finalVelocity;
// Alternative way:
// rigid.AddForce(finalVelocity * rigid.mass, ForceMode.Impulse);
}
}
Thanks!
OK, three things:
The rotation of the velocity wasn't working. Used atan2 instead.
I needed to switch the subtracting Y axes points getting the yOffset
I needed to use .setVelocity(x, y, z) instead of .applyForce(x, y, z)
Here is the final script, hope it helps someone!
static getBallVelocity( ballPos, rimPos, angleDegrees, gravity ){
// Get angle in radians, from angleDegrees argument
const angle = THREE.Math.degToRad( angleDegrees );
gravity = gravity || 9.81;
// Positions of this object and the target on the same plane
const planarRimPos = new THREE.Vector3( rimPos.x, 0, rimPos.z ),
planarBallPos = new THREE.Vector3( ballPos.x, 0, ballPos.z );
// Planar distance between objects
const distance = planarRimPos.distanceTo( planarBallPos );
// Distance along the y axis between objects
const yOffset = ballPos.y - rimPos.y;
// Calculate velocity
const initialVelocity = ( 1 / Math.cos( angle ) ) * Math.sqrt( ( 0.5 * gravity * Math.pow( distance, 2 ) ) / ( distance * Math.tan( angle ) + yOffset ) ),
velocity = new THREE.Vector3( 0, initialVelocity * Math.sin( angle ), initialVelocity * Math.cos( angle ) );
// Rotate our velocity to match the direction between the two objects
const dy = planarRimPos.x - planarBallPos.x,
dx = planarRimPos.z - planarBallPos.z,
theta = Math.atan2( dy, dx ),
finalVelocity = velocity.applyAxisAngle( PopAShotAR.Vector3.up, theta )
return finalVelocity;
}
I have a panoramic image loaded in threejs but it starts camera rotation from the logic below which is default in threejs
if ( isUserInteracting === false ) {
lon += 0.1;
}
lat = Math.max( - 85, Math.min( 85, lat ) );
phi = THREE.Math.degToRad( 90 - lat );
theta = THREE.Math.degToRad( lon );
camera.target.x = 100 * Math.sin( phi ) * Math.cos( theta );
camera.target.y = 100 * Math.cos( phi );
camera.target.z = 100 * Math.sin( phi ) * Math.sin( theta );
What I want to do is place the camera at a specific point which I am able to place using
camera.lookAt( -56.86954186163314, 0, -71.49481268065709 );
Now i want to start normal camera rotation from the above lookAt point. What I am currently doing is
camera.lookAt( -56.86954186163314 + camera.target.x, 0, -71.49481268065709 + camera.target.z);
Which is wrong I think.. PS (I am very weak in geometry, sin, cos).. Can any 1 please help me with this?? PS(I dont want to change camera.target.y It should be 0).. Thanks in advance..
This is best looked at from the perspective of vectors.
Take your lookAt position: that's a vector. You can make that vector spin around an axis using Vector3.applyAxisAngle. As you update the vector, make your camera look at it.
For your example, you want the camera to look at -56.86954186163314, 0, -71.49481268065709, and then spin 360° about the Y-axis (the camera position doesn't change, and the lookAt target doesn't change its Y value).
var lookVector = new THREE.Vector3();
// later...
lookVector.set(x, y, z); // -56.86954186163314, 0, -71.49481268065709
// down with your render function...
var axis = new THREE.Vector3(0, 1, 0);
function render(){
lookVector.applyAxisAngle(axis, 0.001); // or some other theta
camera.lookAt(lookVector);
renderer.render(scene, camera);
}
You'll need to track when your thetas add up to 360°, and perform logic to stop the rotation, but I'll leave that as an exercise for you.
I've setup a scene with a camera which remains in a fixed point in the center of the scene. The user can pan the camera around by clicking (and holding) and dragging the left mouse button. When the user releases the left mouse button, the motion of the camera stops. My scene is based on one of the Three.js demos as see here
There are a bunch of event handlers which are used to create a target position for the camera to lookAt:
function onDocumentMouseDown( event ) {
event.preventDefault();
isUserInteracting = true;
onPointerDownPointerX = event.clientX;
onPointerDownPointerY = event.clientY;
onPointerDownLon = lon;
onPointerDownLat = lat;
}
function onDocumentMouseMove( event ) {
if ( isUserInteracting === true ) {
lon = ( onPointerDownPointerX - event.clientX ) * 0.1 + onPointerDownLon;
lat = ( event.clientY - onPointerDownPointerY ) * 0.1 + onPointerDownLat;
}
}
and in my update loop I have:
// lat and long have been calculated in the event handlers from the cursor click location
lat = Math.max( - 85, Math.min( 85, lat ) );
phi = THREE.Math.degToRad( 90 - lat );
theta = THREE.Math.degToRad( lon );
camera.target.x = (500 * Math.sin( phi ) * Math.cos( theta ));
camera.target.y = (500 * Math.cos( phi ));
camera.target.z = (500 * Math.sin( phi ) * Math.sin( theta ));
camera.lookAt( camera.target );
Which works as expected. When the user clicks and drags the mouse, the camera rotates to follow. Now I am attempting to add some inertia to the movement so when the user releases the left mouse button the camera continues to rotate in the direction of rotation for a small amount of time.
I have tried tracking the change in position of the mouse position in my drag start and drag end events and try to calculate the direction of movement based on that, but it seems like a convoluted way of doing it
Any suggestions on how I could add inertia to the camera controls?
I am trying to rotate earth about it's tilted axis in three js. I found this solution, and I am trying to use it on my code, but it doesn't do anything.
When I execute this code the planet just sits there and doesn't rotate at all. I don't really have a firm grasp of what a quaternion is or how it works, so I am not sure what is going wrong.
function rotateAroundAxis(object, axis, radians) {
var vector = axis.normalize();
var quaternion = new THREE.Quaternion().setFromAxisAngle(vector, radians);
object.rotation = new THREE.Euler().setFromQuaternion( quaternion );
}
earthAxis = new THREE.Vector3(Math.cos(23.4*Math.PI/180), Math.sin(23.4*Math.PI/180), 0);
function render() {
stats.update();
step += 0.02;
rotateAroundAxis(earth, earthAxis, step);
}
First, you need to tilt your sphere's geometry by 23.4 degrees by applying a transformation to it.
var radians = 23.4 * Math.PI / 180; // tilt in radians
mesh.geometry.applyMatrix( new THREE.Matrix4().makeRotationZ( - radians ) );
Then, to rotate your earth on its axis in object space, first normalize the axis you are rotating around.
earthAxis = new THREE.Vector3( Math.sin( radians ), Math.cos( radians ), 0 ).normalize();
Then in your render function, do this:
earth.rotateOnAxis( earthAxis, 0.01 ); // axis must be normalized
three.js r.69
I'd like to modify the TrackballControls so that the camera only rotates on the Z-axis. However, I'm having a devil of a time understanding how the mouse/touch coordinates are mapped. The function below receives the mouse/touch x/y coordinates and a "projection" vector, which I assume is a projection of where the camera is expected to rotate, but that's not particularly clear to me either.
Basically I need to know what to do in the if statement below where it says "what goes here?" I need to essentially clip the vector so that rotation only happens on the Z axis. I know I could do this with a simple rotate, but I'd like to implement it in the TrackballControls so I can switch to "roll only" mode at will (camera only "rolls" on the Z axis). Can someone who is better at vector math give me a hand?
this.getMouseProjectionOnBall = (function(){
var objectUp = new THREE.Vector3(),
mouseOnBall = new THREE.Vector3();
return function ( pageX, pageY, projection ) {
mouseOnBall.set(
( pageX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width*.5),
( _this.screen.height * 0.5 + _this.screen.top - pageY ) / (_this.screen.height*.5),
0.0
);
var length = mouseOnBall.length();
if ( _this.noRoll ) {
if ( length < Math.SQRT1_2 ) {
mouseOnBall.z = Math.sqrt( 1.0 - length*length );
} else {
mouseOnBall.z = .5 / length;
}
} else if (_this.rollOnly === true) {
// What goes here?
} else if ( length > 1.0 ) {
mouseOnBall.normalize();
} else {
mouseOnBall.z = Math.sqrt( 1.0 - length * length );
}
_eye.copy( _this.object.position ).sub( _this.target );
projection.copy( _this.object.up ).setLength( mouseOnBall.y )
projection.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) );
projection.add( _eye.setLength( mouseOnBall.z ) );
return projection;
}
}());
The actual camera rotation is done in the function below every frame. This function is much more understandable to me. Just basic angles and quaternion stuff. It's the above function that boggles my mind.
this.rotateCamera = (function(){
var axis = new THREE.Vector3(),
quaternion = new THREE.Quaternion();
return function () {
var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
if ( angle ) {
axis.crossVectors( _rotateStart, _rotateEnd ).normalize();
angle *= _this.rotateSpeed;
quaternion.setFromAxisAngle( axis, -angle );
_eye.applyQuaternion( quaternion );
_this.object.up.applyQuaternion( quaternion );
_rotateEnd.applyQuaternion( quaternion );
if ( _this.staticMoving ) {
_rotateStart.copy( _rotateEnd );
} else {
quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
_rotateStart.applyQuaternion( quaternion );
}
}
}
}());