Shoot a ray out of camera in ThreeJS - javascript

I'm trying to create a simple first person shooter in ThreeJS and am having some trouble with shooting a ray out of the camera. I am using the code for the PointerLock example code on Mr. Doob's github and am just trying to change the color of a box when I look at it and click the mouse but my code seems to just change boxes colors at random.
Here is the code for looking for collisions:
function onDocumentMouseDown( event ) {
event.preventDefault();
var vector = new THREE.Vector3(0,0,1);
projector.unprojectVector(vector, camera);
var rayCam = new THREE.Ray(camera.position, vector.sub(camera.position).normalize() );
var rayCaster = new THREE.Raycaster(rayCam.origin, rayCam.direction);
//rayCaster.ray = rayCam;
var intersects = rayCaster.intersectObjects(scene.children);
if (intersects.length) {
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
}
}
My code works when you stand in one place but when you start moving the code acts as if the camera is still at the origin, where it started out. How can I modify this code to take into account the fact that I will be moving around?

Related

Attaching position to camera but rotating in world axis

Quick abstract: I don't know how to give to an object, attached to the camera, a local rotation so that it is rotated as if it is attached to the scene.
Background: I am the creator of this website which offers to players of the related game the ability to build levels on a 3D Builder, in a web browser. Everything work fine with it but some parts are not well optimized, specially the toolbar (or dashboard), where objects can be selected to be added to the main scene.
Those toolbar objects need to be sticked at the bottom of the screen and always above all layers but their rotation has to be relative to the main axis of the scene. That's where I face some weird behaviors on browsers other than Firefox and Chrome.
If first thought this other thread was saving my life on this matter by implementing the Object3D.onBeforeRender function, but I faced other issues.
Currently those objects are attached to the scene and their position is updated relatively to the camera with the onBeforeRender function of each object. On such way I noticed some flickering results with Edge and iPhone browser, despite everything ends up fine in the rendering. Not nice on the scene rotation...
So I tried something else by attaching each object to the camera and giving them their supposed world rotation. Now when testing on Edge there is no flickering and the results is much better but I can't get how to give them the same rotation than when they are attached to the scene.
Here is a visual:
and an animated gif (you can see a little how the red background and cube at the right part are moving/flickering, but it is awfull if done quicklier):
For the #1 (at the right) there's a good rendering but a bad experience on some browsers and for the #2 (at the left) the rotation, based on the camera, is not good but the experience is fine.
Here are some code abstracts I've implemented:
For #1:
rightCube.relPos = new THREE.Vector3(500, 0, 0); // a new property to be used
rightCube.onBeforeRender = function( renderer, scene, camera, geometry, material, group ) {
camReference.position.copy(this.relPos);
var pos = camReference.getWorldPosition();
this.position.set( pos.x, pos.y, pos.z );
};
scene.add(rightCube);
And camReference is a simple Object3D representing the camera origin
For the #2:
leftCube.onBeforeRender = function( renderer, scene, camera, geometry, material, group ) {
this.rotation.copy(camera.getWorldRotation());
};
camera.add(leftCube);
I admit on this last part, that getting the camera world rotation is not sufficient but that's where I'm stuck and need some help: I don't know how to give to an object, attached to the camera, a local rotation so that it is rotated as if it is attached to the scene.
This post is pretty long but I hope my problem can be well understood... and that someone will give me the key to make it work.
Three.js r82
I've found out how to deal with that problem. I had to go into some calculus and I am surprised that it is not yet implemented in Three.js. If I have missed something #WestLangley please let me know!
Basically my request is to have a rotateToWorld functionnality from the camera coordinate. Getting the rotation of the camera is not enought because of the orbit control position offset which could be different than the Scene Axis. From the camera coordinate, rotating to the World can be seen as a rotation around x then y (Euler rotations), z being directed to the viewer. Here is a figure I tried to draw as a better explanation:
Here is the function I have coded to do that:
var rotateToWorld = function (camera, controls) {
var p = camera.position.clone();
var o = controls.target.clone();
// position relative to the control
p.sub(o);
var Dxyz = Math.sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
var Dxz = Math.sqrt(p.x*p.x + p.z*p.z);
return {
x: Math.acos(Dxz/Dxyz) * (p.y>0?1:-1), // Angle Rotation on x axis camera
y: Math.acos(p.z/Dxz) * (p.x>0?-1:1), // Angle Rotation on y axis camera
z: 0 // No rotation around z axis
};
};
Then from a cube object attached to the camera (not to the scene) it can be rendered like that:
cube.onBeforeRender = function( renderer, scene, camera, geometry, material, group ) {
var R = rotateToWorld(camera, controls); // controls defined elsewhere in the code
this.rotation.set(R.x, R.y, 0);
};
As a result this is what I got with a perspective camera:
And now it works with Microsoft Edge, Safari and other browsers despite when attaching to the scene and updating the position of the object (to keep it in front of the camera) it was only workable with Firefox and Chrome (as for today).
I wonder now if this is a functionnality that could be added to Three.js...
EDIT:
#WestLangley pointed out in comments that the clone() function of Vector3 instantiate a new object each time it is called, so better is to use a closure function to initialize once p and o as Vector3 objects then update them with the copy() function.
So here is a better version:
var rotateToWorld = function() {
var p = new THREE.Vector3();
var o = new THREE.Vector3();
return function rotateToWorld(camera, controls) {
p.copy(camera.position);
o.copy(controls.target);
// position relative to the control
p.sub(o);
var Dxyz = Math.sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
var Dxz = Math.sqrt(p.x*p.x + p.z*p.z);
return {
x: Math.acos(Dxz/Dxyz) * (p.y>0?1:-1), // Angle Rotation on X axis camera
y: Math.acos(p.z/Dxz) * (p.x>0?-1:1), // Angle Rotation on Y axis camera
z: 0 // No rotation around z axis
};
}
}();
Again, thanks #WestLangley! :)

EdgesGeometry: raycasting not accurate

I'm using EdgesGeometry on PlaneGeometry and it seems it creates a larger hitbox in mouse events. This however, isn't evident when using CircleGeometry. I have the following:
createPanel = function(width, height, widthSegments) {
var geometry = new THREE.PlaneBufferGeometry(width, height, widthSegments);
var edges = new THREE.EdgesGeometry( geometry );
var panel = new THREE.LineSegments( edges, new THREE.LineBasicMaterial({
color: 0xffffff }));
return panel;
}
var tile = createPanel(1.45, .6, 1);
Now I'm using a library called RayInput which does all the raycasting for me but imagine I'm just using a normal raycaster for mouse events. Without the edges and using just the plane, the boundaries of collision is accurate.
After adding EdgesGeometry, the vertical hitbox seems to has increased dramatically thus, the object is detected being clicked when I'm not even clicking on it. The horizontal hitbox seems to have increased only slightly. I've never used EdgesGeometry before so anyone have a clue what is going on?
Thanks in advance.
If you are raycasting against THREE.Line or THREE.LineSegments, you should set the Line.threshold parameter to a value appropriate to the scale of your scene:
raycaster.params.Line.threshold = 0.1; // default is 1
three.js r.114

three.js rotate facing object

I did vector stuff for already 13 years now, but am still struggling with the way three.js handles things. So what I like to do is best described with a fan that always faces the camera and, of course, rotates.
This is how I achieved this in another language:
// Create a vector facing in camera direction.
temp.x = 0;
temp.y = 1;
temp.z = 0;
vec_rotate(temp, camera.pan);
// Apply vector direction to scene object.
vec_to_angle(entity.pan, temp);
// Rotate scene object's angle around another angle / imaginary line from camera to object.
ang_rotate(
entity.pan,
vector(random(360), 0, 0)
);
Thus, after applying entity.lookAt(camera.position) I am missing an angle rotation based on the current angle (last function call of the example).
One way to model a fan is like so:
var fan = new THREE.Mesh( ... );
var pivot = new THREE.Object3D();
scene.add( pivot );
pivot.lookAt( camera.position );
pivot.add( fan );
Then in the animation loop (assuming the fan mesh by default faces the positive-z axis),
fan.rotation.z += 0.01;
three.js r.68

Three.js Rotate camera around object (which may move)

I have a camera that moves in a few different ways in the scene. The camera should rotate around a target position. In my case, this is a point on a mesh that the user has targeted. Because the camera usually doesn't require moving relative to this point, I was not able to use the pivot idea here: https://github.com/mrdoob/three.js/issues/1830. My current solution uses the following code:
var rotationY = new THREE.Matrix4();
var rotationX = new THREE.Matrix4();
var translation = new THREE.Matrix4();
var translationInverse = new THREE.Matrix4();
var matrix = new THREE.Matrix4();
function rotateCameraAroundObject(dx, dy, target) {
// collect up and right vectors from camera perspective
camComponents.up = rotateVectorForObject(new THREE.Vector3(0,1,0), camera.matrixWorld);
camComponents.right = rotateVectorForObject(new THREE.Vector3(1,0,0), camera.matrixWorld);
matrix.identity();
rotationX.makeRotationAxis(camComponents.right, -dx);
rotationY.makeRotationAxis(camComponents.up, -dy);
translation.makeTranslation(
target.position.x - camera.position.x,
target.position.y - camera.position.y,
target.position.z - camera.position.z);
translationInverse.getInverse(translation);
matrix.multiply(translation).multiply(rotationY).multiply(rotationX).multiply(translationInverse);
camera.applyMatrix(matrix);
camera.lookAt(target.position);
}
The issue is that we do not want to use lookAt, because of the reorientation. We want to be able to remove that line.
If we use the code above without lookAt, we rotate around the point but we do not look at the point. My understanding is that my method should rotate the camera's view as much as the camera itself is rotate, but instead the camera is rotated only a small amount. Could anyone help me understand what's wrong?
EDIT: Cleaned up the original post and code to hopefully clarify my question.
My thinking is that I can translate to the origin (my target position), rotate my desired amount, and then translate back to the beginning position. Because of the rotation, I expect to be in a new position looking at the origin.
In fact, I'm testing it now without the translation matrices being used, so the matrix multiplication line is:
matrix.multiply(rotationY).multiply(rotationX);
and it seems to be behaving the same. Thanks for all the help so far!
ONE MORE THING! A part of the problem is that when the camera behaves badly close to the north or south poles. I am looking for a 'free roaming' sort of feel.
Put the following in your render loop:
camera.position.x = target.position.x + radius * Math.cos( constant * elapsedTime );
camera.position.z = target.position.z + radius * Math.sin( constant * elapsedTime );
camera.lookAt( target.position );
renderer.render( scene, camera );
Alternatively, you can use THREE.OrbitControls or THREE.TrackballControls. See the three.js examples.
The Gimbal lock that you are referring to (reorientation) is because of the use of Euler angles in the default implementation of the camera lookat. If you set
camera.useQuaternion = true;
before your call to lookat, then euler angles will not be used. Would this solve your problem ?

How to get Orientation of Camera in THREE.js

I am creating a 3d game using THREE.JS and the Web Audio API. One of the problems I am having is that I want to use the web audio Listener orientation, and define the listener to be the camera, whose position and direction are constantly being updated
My question, is there anyway to easily get the vector direction of a THREE camera?
I was trying to calculate it by using the old camera position, and using the velocity vectors to calculate which way it is facing, but this won't work when the camera is standing still...
Would it be feasible to create a unit vector by getting using camera.rotation.x, camera.rotation.y, camera.rotation.z ?
or is there an even easier way?
Thanks so much for your time!
You want to know the direction in world space in which the camera is looking.
In camera space, the camera is located at the origin and is looking down it's negative z-axis.
Pick a point in front of the camera in camera space:
var pLocal = new THREE.Vector3( 0, 0, -1 );
Now transform that point into world space:
var pWorld = pLocal.applyMatrix4( camera.matrixWorld );
You can now construct the desired direction vector:
var dir = pWorld.sub( camera.position ).normalize();
EDIT: Updated for three.js r.57
EDIT: Also see: three.js set and read camera look vector
In revision 69 (not sure in what revision it was introduced), you can call
camera.getWorldDirection()
to get the camera orientation vector.
if your camera is a child of the player object .e.g. in FPS game. Do the same calculation on the player (parent of camera) , and use this (the Bullit gets the right direction, in this case from obj)
Bullit.prototype.getDirection=function(obj){
this.position.add(obj.position);
var pLocal = new THREE.Vector3( 0, 0, -1 );
var pWorld = pLocal.applyMatrix4( obj.matrixWorld );
var dir = pWorld.sub( obj.position ).normalize();
this.direction=dir;
}

Categories

Resources