I'm adding sprites in a 3d scene using three.js and I want to know distance between the camera and sprite when I click on screen. So I use a Raycater.
But if I click on the sprite, the distance property of intersection object is always "wrong" (someting like 0.3), or maybe I don't know how to read and understand the result. I thought "distance" value of intersection is the distance from camera to sprite (so, in my case something like 5).
Here is a shortened version of my code :
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var sprite = new THREE.Sprite(new THREE.SpriteMaterial({color: 0x00ff00}));
scene.add(sprite);
camera.position.z = 5;
var render = function () {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
render();
window.addEventListener('mousedown', function (e) {
if (e.target == renderer.domElement) {
var vector = new THREE.Vector3((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1, 0.5);
var projector = new THREE.Projector();
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects([sprite]);
console.log(intersects[0]);
}
}, false);
You can see it in action here : http://jsfiddle.net/pWr57/
So how can I have the distance form camera to a sprite ?
three.js r66
Do this, instead
console.log( raycaster.ray.origin.distanceTo( intersects[0].point ) );
Tip: Read the source code, Raycaster.js, so you know what it is doing. It is currently returning the perpendicular distance from the sprite center to the ray.
In this case, I agree that it would be better to return the distance from the camera.
three.js r.66
Related
I have a rotating box made with three.js and I want to stop (and restart) animation to show the object face frontally.
I would like to show a smooth animation until the cube is in the correct position. I 've seen several examples here on StackOverflow but I wasn't able to replicate it in my example.
Example: JSFiddle example
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.z = 500;
scene.add(camera);
geometry = new THREE.CubeGeometry(200, 200, 200);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
Here the animation
function render() {
mesh.rotation.x += 0.01;
mesh.rotation.z += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
}
How is the best approach to keep it simple and smooth?
One approach to solve this problem is the usage of Quaternion.rotateTowards() (a method similar to Unity's Quaternion.RotateTowards()).
First, you have to define a target rotation for your box. This can be done like so:
var targetQuaternion = new THREE.Quaternion();
targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI, 0 ) );
In the next step, you use Quaternion.rotateTowards() in your animation loop like so:
if ( ! mesh.quaternion.equals( targetQuaternion ) ) {
var step = speed * delta;
mesh.quaternion.rotateTowards( targetQuaternion, step );
}
step represents the angular displacement in radians per seconds. In the following live example, the value is set to Math.PI. Meaning the box can rotate 180 degrees per second.
So essentially, you can define a target rotation for all sides of the cube and then use Quaternion.rotateTowards() to rotate towards it.
https://jsfiddle.net/0eutrjqs/
three.js R113
I am trying to build a rocketship model by add different shapes to a large group and position them at specific axis.
When I try to use
rocketConeMesh.position.y = 15;
The shape does not move at all, I am trying to put the rocketCone (The nose of the rocket ship) on top of the rocketBody and then have them under the same group.
I get the followig error message
"THREE.Object3D.add: object not an instance of THREE.Object3D. "
for the coneGeometry object.
my code is as follows:
<script type="text/javascript">
// create a scene, that will hold all our elements such as objects, cameras and lights.
var scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render and set the size
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', function()
{
var width = window.innerWidth;
var height = window.innerHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
} );
controls = new THREE.OrbitControls(camera, renderer.domElement);
// create the object group that contains all the sub-shapes
var rocketGroup = new THREE.Object3D();
scene.add(rocketGroup);
//The object below is the top cone of the rocket
var rocketCone = new THREE.ConeGeometry(6, 10, 6);
var material = new THREE.MeshBasicMaterial({color: 0xcccccc, wireframe: true});
var cone = new THREE.Mesh(rocketCone, material);
rocketConeMesh = new THREE.Mesh(rocketCone,new THREE.MeshPhongMaterial())
scene.add(cone);
//Specify the position of the rocket cone
rocketConeMesh.position.y = 15;
//Add the rocketCone to the lowpolyRocket group
rocketGroup.add(rocketCone);
/******************************************************************************************************************/
//var rocketBody = new THREE.CylinderGeometry( 5, 5, 20, 32 );
//var material = new THREE.MeshBasicMaterial({color: 0xcccccc, wireframe:false});
//var cylinder = new THREE.Mesh(rocketBody, material);
//scene.add(cylinder);
// position and point the camera to the center of the scene
camera.position.x = -30;
camera.position.y = 20;
camera.position.z = 30;
camera.lookAt(scene.position);
// game logic
var update = function ( )
{
//cone.rotation.x += 0.01;
//cone.rotation.y += 0.005;
};
// draw scene
var render = function ( )
{
renderer.render(scene, camera);
};
// run game loop (update, render, repeat)
var GameLoop = function ( )
{
requestAnimationFrame(GameLoop);
update( );
render( );
};
GameLoop( );
</script>
rocketGroup.add(rocketCone);
This code is not valid since it's not possible to add an instance of ConeGeometry to an Object3D. Try to change your code to the following:
rocketGroup.add(rocketConeMesh);
Now you add the mesh (which is derived from THREE.Object3D) to rocketGroup. It's now part of the scene graph and changing its transformation should work.
BTW: I don't understand why you create two meshes with your rocketCone geometry. I guess you can remove the cone variable.
I'm using Three.js to develop a player for 360° pictures.
My scene contains a sphere (BasicMeshMaterial) that has a 360° picture as texture. The camera is in the middle of that sphere, so that the user sees the picture, that is applied to the sphere's "walls".
I recently added the possibility to click on some spots of the sphere, to rotate the camera towards that spot. The camera smoothly moves to this spot, untill it has this spot in the center of the screen.
My problem is that sometimes the camera easing (tween animation) that is supposed to focus the spot clicked uses a very strange path and it's all very ugly.
This seems to happen when the initial position of the camera and the target position cross a certain point of the sphere. I have almost no clue what happens. Do you ? Could it have a link with the sphere, or the camera is only playing with quaternions and so it's not affected by others objects ? Then what could make it chose wrong path, and how come it happens only with a certain angle of the sphere ? anything would be help !
The player is testable at http://isiko.io/, after scrolling a bit.
I can show you the code but don't really know what to pick. Tell me if you need the camera's rotation,or the sphere's initiation..
Thank you for your feedbacks.
There is another approach with tweening an angle around an axis:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 0);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var sphere = new THREE.Mesh(new THREE.SphereGeometry(10, 32, 24), new THREE.MeshBasicMaterial({
color: "yellow",
wireframe: true
}));
scene.add(sphere);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var center = new THREE.Vector3();
var vectorStart = new THREE.Vector3(0, 0, -10); // in front of the camera
var vectorEnd = new THREE.Vector3();
var angle = {
value: 0
};
var angleEnd = {
value: 0
};
var normal = new THREE.Vector3();
var lookAt = new THREE.Vector3();
var isMoving = false;
window.addEventListener("mousedown", onMouseDown, false);
function onMouseDown(event) {
if (isMoving) return;
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
let newPosition = raycaster.ray.at(10);
setPoint(newPosition);
// prepare the values we need for tweening
vectorEnd.copy(newPosition);
angle.value = 0; // we will tween this value
angleEnd.value = vectorStart.angleTo(vectorEnd); // to this one
normal.copy(vectorStart).cross(vectorEnd).normalize(); // around this axis
// tween the angle
var tween = new TWEEN.Tween(angle).to(angleEnd, 1000).delay(250).easing(TWEEN.Easing.Cubic.InOut).onStart(function(){
isMoving = true;
}).onUpdate(
function() {
camera.lookAt(lookAt.copy(vectorStart).applyAxisAngle(normal, angle.value));
}
).onComplete(function() {
isMoving = false;
vectorStart.copy(vectorEnd);
}).start();
}
function setPoint(position) {
let point = new THREE.Mesh(new THREE.SphereGeometry(0.125, 4, 2), new THREE.MeshBasicMaterial({
color: "red",
wireframe: true
}));
point.position.copy(position);
scene.add(point);
}
render()
function render() {
requestAnimationFrame(render);
TWEEN.update(); // don't forget to put this line into the animation loop, when you use Tween.js
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>
I"m not exactly sure how I can do this. I read a lot about raycasting and that seems to be good for finding points that intersect with something, but in this case I just want it to interpolate the 2d mouse coordinates to the 3d point exactly where the mouse clicks, regardless of scale, rotation, whether or not there's an object there, etc.
One method I've thought of but not approached would be making an invisible plane that is parallel to the camera, always oriented upright and always intersecting the y axis. Then use a raycaster to hit the plane, draw as needed, then delete the plane. Seems like a silly way to do this though.
At the moment I have a method that works pretty well but it has some issues when the line gets further away from the origin, or the camera gets zoomed
In this photo I drew two lines from two different perspectives. The vertical line what it looks like when the camera is level with the x and z axis, and I draw a straight line down the y axis, while the horizontal line is what happens when i scribble with the camera facing down.
https://i.imgur.com/f8qw5xV.png
As you can see, it seems to use the distance to the camera to make this calculation, so the further the distance from the camera, the more distortion is in the calculation. How can get rid of this distortion?
source: https://github.com/AskAlice/mandala-3d-threejs
live demo: https://askalice.me/mandala/
Here is the relevant code:
js/content.js#112
function get3dPointZAxis(event)
{
camPos = camera.position;
var mv = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY/window.innerHeight) * 2 + 1, 1).unproject(camera);
var m2 = new THREE.Vector3(0,0,0);
var pos = camPos.clone();
pos.add(mv.sub(camPos).normalize().multiplyScalar(m2.distanceTo(camPos)));
return pos;
}
I used information from two stackoverflow posts to come up with this, and it has the issues I've described.
firstly, this post shows how to draw and convert it to the z axis. It is flat. But I had a lot of trouble trying to make that work in three dimensions.
How to draw a line segment at run time using three.js
and then i used information in the below post to at least get it parallel to the camera on the x-z axis like such: https://i.imgur.com/E9AQNpH.png
Moving objects parallel to projection plane in three.js
That option with THREE.Plane() and THREE.Raycaster().ray.intersectPlane():
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var point = new THREE.Vector3();
function getPoint(event){
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
planeNormal.copy(camera.position).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, scene.position);
raycaster.setFromCamera(mouse, camera);
raycaster.ray.intersectPlane(plane, point);
}
Run the code snippet, click the "draw" checkbox to set it as checked, move your mouse randomly (without mouse down), click the checkbox again, rotate the scene with mousedown. All points are on the same plane.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var point = new THREE.Vector3();
document.addEventListener("mousedown", onMouseDown, false);
document.addEventListener("mousemove", onMouseMove, false);
function getPoint(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
planeNormal.copy(camera.position).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, scene.position);
raycaster.setFromCamera(mouse, camera);
raycaster.ray.intersectPlane(plane, point);
}
function setPoint() {
var sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(.125, 4, 2), new THREE.MeshBasicMaterial({
color: "yellow",
wireframe: true
}));
sphere.position.copy(point);
scene.add(sphere);
}
function onMouseDown(event) {
getPoint(event);
if (draw.checked) setPoint();
}
function onMouseMove(event) {
getPoint(event);
if (draw.checked) setPoint();
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div style="position:absolute;">
<input id="draw" type="checkbox">
<label for="draw" style="color: white;">draw</label>
</div>
I am using this example for my WebGL panorama cube: https://threejs.org/examples/?q=pano#webgl_panorama_equirectangular
I want to know what cube user clicks on and I discovered I can use Raycaster for this. According to docs I added the following function:
function onMouseDown( event ) {
event.preventDefault();
var mouseVector = new THREE.Vector3(
( event.clientX / window.innerWidth ) * 2 - 1,
- ( event.clientY / window.innerHeight ) * 2 + 1,
1 );
//projector.unprojectVector( mouseVector, camera );
mouseVector.unproject( camera );
var raycaster = new THREE.Raycaster( camera.position, mouseVector.sub( camera.position ).normalize() );
// create an array containing all objects in the scene with which the ray intersects
var intersects = raycaster.intersectObjects( scene.children );
console.log(intersects);
if (intersects.length>0){
console.log("Intersected object:", intersects.length);
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
}
// ...
But intersects is always empty. My scene is defined as
scene = new THREE.Scene();
and has skyBox added:
var skyBox = new THREE.Mesh( new THREE.CubeGeometry( 1, 1, 1 ), materials );
skyBox.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, - 1 ) );
scene.add( skyBox );
I've seen similar posts related to this issue but could not figure out how to apply to this example. Any directions are appreciated.
Try adding this to your material definition:
var materials = new THREE.SomeMaterial({
/* other settings */,
side: THREE.DoubleSide
});
Raycaster won't intersect back-faces unless the side property is set to THREE.BackSide or THREE.DoubleSide. Even though your scaling technically inverts the face direction, the vertex order stays the same, which is what's important to Raycaster.
Some further explanation
The snippet below is showing how a ray projected from a camera at the center of a skybox inverted by a -Z scale might look.
The box itself looks weird because it has been -Z scaled, and the normals no longer match the material. But that's here nor there.
The green arrow represents the original ray. The red arrow represents what will happen to that ray inside the Mesh.raycast function, which will apply the inverse of the object's world matrix to the ray, but not to the object's geometry. This is a whole different problem.
The point I'm making is that within Mesh.raycast, it does not affect the vertex/index order, so when it checks the triangles of the mesh, they are still in their original order. For a standard BoxGeometry/BoxBufferGeometry, this means the faces all face outward from the geometric origin.
This means the rays (regardless of how the transformation matrix affects them) are still trying to intersect the back-face of those triangles, which will not work unless the material is set to THREE.DoubleSide. (It can also be set to THREE.BackSide, but the -Z scale will ruin that.)
Clicking either of the raycast buttons will produce 0 intersects if the -Z scaled box is not set to THREE.DoubleSide (default). Click the "Set THREE.DoubleSide" button and try it again--it will now intersect.
var renderer, scene, camera, controls, stats;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000,
ray1, ray2, mesh;
function populateScene(){
var cubeGeo = new THREE.BoxBufferGeometry(10, 10, 10),
cubeMat = new THREE.MeshPhongMaterial({ color: "red", transparent: true, opacity: 0.5 });
mesh = new THREE.Mesh(cubeGeo, cubeMat);
mesh.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, -1 ) );
mesh.updateMatrixWorld(true);
scene.add(mesh);
var dir = new THREE.Vector3(0.5, 0.5, 1);
dir.normalize();
ray1 = new THREE.Ray(new THREE.Vector3(), dir);
var arrow1 = new THREE.ArrowHelper(ray1.direction, ray1.origin, 20, 0x00ff00);
scene.add(arrow1);
var inverseMatrix = new THREE.Matrix4();
inverseMatrix.getInverse(mesh.matrixWorld);
ray2 = ray1.clone();
ray2.applyMatrix4(inverseMatrix);
var arrow2 = new THREE.ArrowHelper(ray2.direction, ray2.origin, 20, 0xff0000);
scene.add(arrow2);
}
function init() {
document.body.style.backgroundColor = "slateGray";
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
document.body.appendChild(renderer.domElement);
document.body.style.overflow = "hidden";
document.body.style.margin = "0";
document.body.style.padding = "0";
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 50;
scene.add(camera);
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.dynamicDampingFactor = 0.5;
controls.rotateSpeed = 3;
var light = new THREE.PointLight(0xffffff, 1, Infinity);
camera.add(light);
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);
resize();
window.onresize = resize;
populateScene();
animate();
var rayCaster = new THREE.Raycaster();
document.getElementById("greenCast").addEventListener("click", function(){
rayCaster.ray.copy(ray1);
alert(rayCaster.intersectObject(mesh).length + " intersections!");
});
document.getElementById("redCast").addEventListener("click", function(){
rayCaster.ray.copy(ray2);
alert(rayCaster.intersectObject(mesh).length + " intersections!");
});
document.getElementById("setSide").addEventListener("click", function(){
mesh.material.side = THREE.DoubleSide;
mesh.material.needsUpdate = true;
});
}
function resize() {
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
if (renderer && camera && controls) {
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
controls.handleResize();
}
}
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
render();
controls.update();
stats.update();
}
function threeReady() {
init();
}
(function () {
function addScript(url, callback) {
callback = callback || function () { };
var script = document.createElement("script");
script.addEventListener("load", callback);
script.setAttribute("src", url);
document.head.appendChild(script);
}
addScript("https://threejs.org/build/three.js", function () {
addScript("https://threejs.org/examples/js/controls/TrackballControls.js", function () {
addScript("https://threejs.org/examples/js/libs/stats.min.js", function () {
threeReady();
})
})
})
})();
body{
text-align: center;
}
<input id="greenCast" type="button" value="Cast Green">
<input id="redCast" type="button" value="Cast Red">
<input id="setSide" type="button" value="Set THREE.DoubleSide">
You might actually want to use an easier process to determine your ray from the camera:
THREE.Raycaster.prototype.setFromCamera( Vector2, Camera );
Simply define your mouse coordinates as you do in a Vector2, then pass the elements to the Raycaster and let it do its thing. It hides the complexity of intersecting the frustrum with the ray from the camera, and should solve your problem.
(Also, the raycaster does indeed only intersect faces that face the ray directly, but since your SkyBox has been inverted its geometries faces are pointing to the inside of the box, so they should intersect if the camera is inside the box. Another possibility is that your box is further away than the raycasters default far value.)
function onMouseDown( event ) {
event.preventDefault();
var mouseVector = new THREE.Vector2(
event.clientX / window.innerWidth * 2 - 1,
-event.clientY / window.innerHeight * 2 + 1
);
var raycaster = new THREE.Raycaster;
raycaster.setFromCamera( mouseVector, camera );
var intersects = raycaster.intersectObjects( scene.children );
console.log(intersects);
if( intersects.length > 0 ){
console.log( "Intersected object:", intersects[ 0 ] );
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
}
}