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 );
}
}
}
}());
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 try to use trackballcontrols.js in my project but need some modification,and i cant understand these code ,can you give me some explanation?
var getMouseOnCircle = ( function () {
var vector = new THREE.Vector2();
return function getMouseOnCircle( pageX, pageY ) {
vector.set(
( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ),
( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional
);
return vector;
};
}() );
This code converts the coordinate of the mouse position into the normalized 2d vector (to use it for rotation calculation).
The method expects to be passed the mouse's XY coordinate on the global screen. It returns the X component linearly interpolated between -1 and 1 and the Y component linearly interpolated between -a and a, where a is the "aspect ratio" given by the viewport height divided by the viewport width.
I understand there is no THREE.projector in version 71 (see the deprecated list), but I don't understand how to replace it, particularly in this code that detects which object has been clicked on:
var vector = new THREE.Vector3(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1,
0.5
);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(
camera.position,
vector.sub(camera.position).normalize()
);
var intersects = raycaster.intersectObjects(objects);
if (intersects.length > 0) {
clicked = intersects[0];
console.log("my clicked object:", clicked);
}
There is now an easier pattern that works with both orthographic and perspective camera types:
var raycaster = new THREE.Raycaster(); // create once
var mouse = new THREE.Vector2(); // create once
...
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( objects, recursiveFlag );
three.js r.84
The THREE.JS raycaster documentation actually gives all the relevant information that is laid out in these answers as well as all the missing points that may be difficult to get your head around.
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseMove( event ) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function render() {
// update the picking ray with the camera and mouse position
raycaster.setFromCamera( mouse, camera );
// calculate objects intersecting the picking ray var intersects =
raycaster.intersectObjects( scene.children );
for ( var i = 0; i < intersects.length; i++ ) {
intersects[ i ].object.material.color.set( 0xff0000 );
}
renderer.render( scene, camera );
}
window.addEventListener( 'mousemove', onMouseMove, false );
window.requestAnimationFrame(render);`
Hope it helps.
You can use the latest recommended version as stated above.
To get your specific code working, replace:
projector.unprojectVector( vector, camera );
with
vector.unproject(camera);
I noted that all this that was said before is fine in a full window I think, but if you have other things besides a canvas on the page you need to get the click events target's offset and remove it.
e = event and mVec is a THREE.Vector2
let et = e.target, de = renderer.domElement;
let trueX = (e.pageX - et.offsetLeft);
let trueY = (e.pageY - et.offsetTop);
mVec.x = (((trueX / de.width) * 2) - 1);
mVec.y = (((trueY / de.height) * -2) + 1);
wup.raycaster.setFromCamera( mVec, camera );
[Then check for intersections, etc.]
Looks like you need to watch for dragging (mouse movements) too or releasing from a drag will trigger an unwanted click when using window.addEventListener( 'click', function(e) {<code>});
[You'll notice I put the negative sign where it's more logical as well.]
https://github.com/mrdoob/three.js/issues/5587
var vector = new THREE.Vector3();
var raycaster = new THREE.Raycaster();
var dir = new THREE.Vector3();
...
if ( camera instanceof THREE.OrthographicCamera ) {
vector.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, - 1 ); // z = - 1 important!
vector.unproject( camera );
dir.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
raycaster.set( vector, dir );
} else if ( camera instanceof THREE.PerspectiveCamera ) {
vector.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 ); // z = 0.5 important!
vector.unproject( camera );
raycaster.set( camera.position, vector.sub( camera.position ).normalize() );
}
var intersects = raycaster.intersectObjects( objects, recursiveFlag );
Objects = array of objects of type Object3D to check for intersection with the ray. Can be everything in your scene, but might be inefficient if you have a lot of stuff there.
recursiveFlag = If true, it also checks all descendants. Otherwise it only checks intersection with the object. Default is true.
docs
I wanted to share my experiences with more details doing this since I feel the answers above do not really explain the details to get this going.
In my case I was using STL_Viewer, you can locate it here (https://www.viewstl.com/plugin/), it helps create a threeJS scene and supplies you with the camera and scene that you will need for this to work.
function mouse_click_normalize(clientX,clientY,offset_left,offset_top){
var mouse = new THREE.Vector2(); // create once
var c = document.getElementById("stl_contEye").childNodes[0];//get canvas element created in here
mouse.x = ( (event.clientX-offset_left) / (c.clientWidth) * 2) - 1;
mouse.y = - ( (event.clientY-offset_top) / (c.clientHeight) ) * 2 + 1;
console.log(`clientX=${clientX},clientY=${clientY},mouse.x=${mouse.x},mouse.y=${mouse.y}\nwidth=${c.clientWidth},height=${c.clientHeight},offset_left=${offset_left},offset_top=${offset_top}`);
return mouse;
}
Above is my function that will automatically normalize the x,y coordinates in the space that was clicked (between -1 and 1), as this is what the raycaster requires for it's setFromCamera function.
Because you may be clicking in a section of the screen with offsets I programmed it this way so it will handle no matter how it's positioned in the DOM. Just replace "stl_contEye" with the name of your div that will contain the rendered ThreeJS or STLViewer in my case.
function model_clicked(model_id, e, distance, click_type){
console.log(e);
var pos = new THREE.Vector3(); // create once and reuse
// var vec = new THREE.Vector3(); // create once and reuse
var camera = stl_viewer1.camera;
var scene = stl_viewer1.scene;
// vec.unproject( camera );
// vec.sub( camera.position ).normalize();
// //var distance2 = (distance - camera.position.z) / vec.z;
// pos.copy( camera.position ).add( vec.multiplyScalar( distance ) );
var mouse_coords_normalized = mouse_click_normalize(e.clientX,e.clientY,e.srcElement.offsetLeft,e.srcElement.offsetTop);
var raycaster = new THREE.Raycaster(); // create once
raycaster.setFromCamera( mouse_coords_normalized, camera );
//console.log(raycaster);
//console.log(scene);
var intersects = raycaster.intersectObjects( scene.children, true );
if(intersects.length > 0){
console.log(intersects);
console.log(intersects[0].point);
pos = intersects[0].point
}
}
By doing it this way, you will get the exact point in 3D space from where you clicked. The function model_clicked simply returns an event that holds the clientX or clientY, you just have to get this yourself somehow if you are not using STLViewers event for detecting a click. There are many examples above for this from the other answers.
I hope this helps someone trying to figure this out with or without stl_viewer
camera.shooting = Date.now()
document.getElementById("div5").addEventListener("mousedown", mousedown);
document.getElementById("div5").addEventListener("mouseup", mouseup);
document.getElementById("div5").addEventListener( 'mousemove', renderq);
function mousedown(event) {
camera.calculator = 1;
}
function mouseup(event) {
camera.calculator = 0;
}
function renderq(event){
if( ai2bcz.length > 0 && ai_key3.length > 0 ){
if( camera.calculator > 0 ){
camera.mouse = new THREE.Vector2();
var fleece = document.getElementById("div5").scrollHeight;
var fleeceb = document.getElementById("div5").scrollWidth;
var fees = (event.clientX / fleeceb) - 1;
var feesa = - (event.clientY / fleece) + 1;
camera.mouse.x = fees ; camera.mouse.y = feesa;
var sphereMaterialc = new THREE.MeshBasicMaterial({color: 0x0099FF});
var sphereGeoc = new THREE.SphereGeometry(5, 5, 5);
var spherec = new THREE.Mesh(sphereGeoc, sphereMaterialc);
spherec.position.set(camera.position.x, camera.position.y, camera.position.z);
spherec.raycaster = new THREE.Raycaster();
spherec.raycaster.setFromCamera( camera.mouse, camera);
spherec.owner = 'player';
spherec.health = 100;
bulletsc.push(spherec);
scene.add(spherec);
camera.lastshot = Date.now();
camera.shooting = Date.now();
}
}
}
function render() { const controlscamera = new FirstPersonControls(camera);
controlscamera.update(100);
if( ai2bcz.length > 0 && ai_key3.length > 0 ){
if( camera.calculator > 0 && camera.shooting + 25 < Date.now() ){
renderq(event)
camera.shooting = Date.now();
}}
if(bulletsc.length > 1){
for (var i = 0; i < bulletsc.length - 1; i++) {
var bu = bulletsc[i], pu = bu.position, du = bu.raycaster.ray.direction;
if(bu.owner == "player"){
var enemybulletspeeda = window.document.getElementById("bulletsplayerspeed").value;
bu.translateX(enemybulletspeeda * du.x);
bu.translateY(enemybulletspeeda * du.y);
bu.translateZ(enemybulletspeeda * du.z);
}
}}
renderer.render( scene, camera ); }
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