Detecting object collision by dragging. Trying to use the raycaster intersectObjects method - three.js

I am simply trying to detect when the lower sphere (the draggable one) is intersecting with the upper ones. I'm sure there's something I do not understand, unfortunately, nothing is crossing my mind on what.
<script src='https://threejs.org/build/three.min.js'></script>
<script src='https://threejs.org/examples/js/controls/DragControls.js'></script>
<script>
window.onload = init;
// Global variables
var renderer, raycaster, mouse,
scene, camera, sphere1, sphere2,
sphere3, sphere4;
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
function init(){
// Get WebGL ready
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer = this.renderer;
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
camera.position.set(0, 0, 100);
camera.lookAt(0, 0, 0);
scene = new THREE.Scene();
// Get set
drawSpheres();
// Go
eventful();
animate();
};
function animate(){
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
function eventful(){ // Where all events happen
new THREE.DragControls([sphere1], camera, renderer.domElement);
window.addEventListener( 'mousemove', onMouseMove, false);
};
function drawSphere(){ // Sphere geometry
var geometry, material, sphere;
geometry = new THREE.SphereBufferGeometry(3, 50, 50, 0, Math.PI * 2, 0, Math.PI * 2);
material = new THREE.MeshNormalMaterial();
sphere = new THREE.Mesh(geometry, material);
return sphere;
};
function drawSpheres(){ // Draw four corners for the quadrant
sphere1 = drawSphere(); sphere1.position.set(20, 0, 0);
sphere2 = drawSphere(); sphere2.position.set(15, 23, 0);
sphere3 = drawSphere(); sphere3.position.set(0, 22, 0);
sphere4 = drawSphere(); sphere4.position.set(-20, 20, 0);
scene.add(sphere1, sphere2, sphere3, sphere4);
};
function onMouseMove(event){ // Calculate mouse movements
// Pixel coordinates
mouse.x = event.clientX;
mouse.y = event.clientY;
raycasting(renderer, scene, camera);
};
function raycasting(renderer, scene, camera){
raycaster.setFromCamera(sphere1, camera); // Update the picking ray with the camera and mouse movements
intersects = raycaster.intersectObjects([sphere2, sphere3, sphere4]);
for(var i = 0; i < intersects.length; i++){
intersects[i].object.material.color.set(0xff0000);
console.log('Hit on: ', intersects[i]);
}
};
</script>
The only thing I can think of is my usage of the intersectObjects() method or the setFromCamera(), but I am not sure. I think this would make sense, since it is updated on mouse move. How would I say: "I want the draggable sphere to be the raycaster, as I move it, and detect collision"? Or something simpler to detect when things collide.
For instance, consider the following:
window.onload = init;
// Global variables
var renderer, raycaster, mouse,
scene, camera, sphere1, sphere2,
sphere3, sphere4;
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
console.log(raycaster);
function init(){
// Get WebGL ready
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer = this.renderer;
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
camera.position.set(0, 0, 100);
camera.lookAt(0, 0, 0);
scene = new THREE.Scene();
// Get set
drawSpheres();
// Go
eventful();
animate();
};
function animate(){
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
function eventful(){ // Where all events happen
new THREE.DragControls([sphere1], camera, renderer.domElement);
window.addEventListener( 'mousemove', onMouseMove, false);
};
function drawSphere(){ // Sphere geometry
var geometry, material, sphere;
geometry = new THREE.SphereBufferGeometry(3, 50, 50, 0, Math.PI * 2, 0, Math.PI * 2);
material = new THREE.MeshNormalMaterial();
sphere = new THREE.Mesh(geometry, material);
return sphere;
};
function drawSpheres(){ // Draw four corners for the quadrant
sphere1 = drawSphere(); sphere1.position.set(20, 0, 0);
sphere2 = sphere1.clone(); sphere2.position.set(15, 23, 0);
sphere3 = sphere1.clone(); sphere3.position.set(0, 22, 0);
sphere4 = sphere1.clone(); sphere4.position.set(-20, 20, 0);
console.log(sphere1, sphere2, sphere3, sphere4);
scene.add(sphere1, sphere2, sphere3, sphere4);
};
function onMouseMove(event){ // Calculate mouse movements
// Normalized Coordinate System
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
raycasting(renderer, scene, camera);
};
function raycasting(renderer, scene, camera){
raycaster.setFromCamera(mouse, camera); // Update the picking ray with the camera and mouse movements
intersects = raycaster.intersectObjects([sphere2, sphere3, sphere4]);
for(var i = 0; i < intersects.length; i++){
console.log('Hit on: ', intersects[i].object.uuid);
}
};
In this example, the raycaster is the mouse. You'll see the 'hit' message on the console, every time there is a mouse hover the spheres I've specified in the intersectObjects() method.

if you aren't casting a ray from the mouse cursor, you don't need .setFromCamera().. you would just set up the ray manually.
You can use raycasting to check if one sphere hits another, or you can do a sphere->sphere intersection test like this..
var tmp = new THREE.Vector3();
function spheresCollide(centerA,radiusA,centerB,radiusB){
var sqdist = radiusA+radiusB;
sqdist*=sqdist;
tmp.copy(centerB).sub(centerA)
if(tmp.lengthSq()<sqdist)return true;
return false;
}
//centerA and centerB are the vector3 positions of your spheres.. radiusA and B are the sphere radii
To do a raycast, you'll need to do something like the following, for each sphere:
rayCaster.ray.origin.copy(sphereA.position);
rayCaster.ray.direction.copy(sphereB.position).sub(sphereA.position).normalize()
intersects = raycaster.intersectObjects([sphereB]);
for(var i = 0; i < intersects.length; i++){
tmp.copy(intersects[i].position).sub(sphereA.position);
if(tmp.length()<(radiusA+radiusB)){
intersects[i].object.material.color.set(0xff0000);
console.log('Hit on: ', intersects[i]);
}
}

It took me a while to get through this. There's something different about raycasting to a moving object. The idea behind ray casting is that a ray is being casted. For this example, the setFromCamera() method won't do, because the 'sphere' is supposed to be the object the ray(s) is(are) coming from.
<script src='https://threejs.org/build/three.min.js'></script>
<script src='https://threejs.org/examples/js/controls/DragControls.js'></script>
<script>
window.onload = init;
// Global variables
var renderer, raycaster,
scene, camera, sphere1, sphere2,
sphere3, sphere4, dragControls;
function init(){
// Get WebGL ready
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer = this.renderer;
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500);
camera.position.set(0, 0, 100);
camera.lookAt(0, 0, 0);
scene = new THREE.Scene();
// Get set
drawSpheres();
// Go
eventful();
animate();
};
function animate(){
requestAnimationFrame(animate);
renderer.render(scene, camera);
raycasting();
};
function eventful(){ // Where all events happen
dragControls = new THREE.DragControls([sphere1], camera, renderer.domElement);
dragControls.addEventListener('dragstart', onDragStart, false);
dragControls.addEventListener('dragend', onDragEnd, false);
};
function drawSphere(){ // Sphere geometry
var geometry, material, sphere;
geometry = new THREE.CubeGeometry(3,3,3,1,1,1);
material = new THREE.MeshBasicMaterial({
wireframe: true
});
sphere = new THREE.Mesh(geometry, material);
return sphere;
};
function drawSpheres(){ // Draw four corners for the quadrant
sphere1 = drawSphere(); sphere1.position.set(20, 0, 0);
sphere2 = sphere1.clone(); sphere2.position.set(15, 23, 0);
sphere3 = sphere1.clone(); sphere3.position.set(0, 22, 0);
sphere4 = sphere1.clone(); sphere4.position.set(-20, 20, 0);
console.log(sphere1, sphere2, sphere3, sphere4);
scene.add(sphere1, sphere2, sphere3, sphere4);
};
function onDragStart(event){
console.log('on drag start');
};
function onDragEnd(event){
console.log('on drag end');
};
function onMouseMove(event){ // Calculate mouse movements
// Normalized Coordinate System
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
};
//////////////////////////////
//////// RAYCASTING //////////
//////////////////////////////
function raycasting(){ // Blast rays like Cyclops or Superman, but only to measure proximity
var sphere1Origin, // The 3D position of sphere1 when the page loads
vIndex, // Vertex index
sphere1VLength, // The amount of vertices
dVector, // The directions the ray should be pointing to while it is moving
raycaster, // The ray casting from a given point
sphere1Origin = getUpdatedPosition();
sphere1VLength = sphere1.geometry.vertices.length;
for(vIndex = 0; vIndex < sphere1VLength; vIndex++){
dVector = bindRaysToVertices(sphere1, vIndex);
raycaster = raycast(sphere1Origin, dVector);
collided = detectCollision(raycaster, dVector).hasCollided;
if(collided){
console.log('Hit!');
}
}
};
function detectCollision(raycaster, dVector){ // Determines whether there is/are (a) collision(s)
var collisions, // Results of each collisions
collided; // True/False
collisions = raycaster.intersectObjects([sphere2, sphere3, sphere4]);
collided = collisions.length > 0 && collisions[0].distance < dVector.length();
return {
hasCollided: collided,
collisionsList: collisions
};
};
function bindRaysToVertices(sphere1, vIndex){ // Make the geometry blast rays in all directions, while moving
var lVertex, // The re-calculated (updated) vertices for the moving object
gVertex, // The complete representation of the re-calculated (updated) vertices
dVector; // The directions the ray should be pointing to while it is moving
lVertex = sphere1.geometry.vertices[vIndex].clone();
gVertex = lVertex.applyMatrix4(sphere1.matrix);
dVector = gVertex.sub(sphere1.position);
return dVector;
};
function getUpdatedPosition(){
var sphere1Origin, // The 3D position of sphere1 when the page loads
sphere1Origin = sphere1.position.clone();
return sphere1Origin;
};
function raycast(sphere1Origin, dVector){
// Make the sphere cast the ray, through its vertices,
// while moving, using a Normalized Coordinate System
return new THREE.Raycaster(sphere1Origin, toNCS(dVector));
};
function toNCS(dVector){ // To Normalize Coordinate System
return dVector.clone().normalize();
};
</script>
Following Stemkoski example, I've decided to use cubes as wireframes, and should there be a need to have a sphere, the cube should be within it. Otherwise it will be computationally expensive to have a sphere blasting rays like the Sun for proximity detection purposes.

Related

Three.js - How to slowly change the camera's orientation untill it reaches a specific vector

I'm using Three.js to develop a player for 360° pictures, and I need some advice.
I have created a few cliquable meshs inside the scene. Currently, when the user clicks on a mesh, the camera's orientation is brutally changed to the mesh's direction. (this done by calling THREE.Camera.lookat()).
What I want is that when the users clicks, the camera transitions smoothly from it's target vector to the mesh's direction. I would like that the camera takes about 1 second to go from its current vector to the mesh's direction.
I have seen that tween is a library with which we can animate the scene, but I didn't really understand how it works.
Do you know what I could use to implement this animation ?
If tween can help me, can you explain how tween comes into play with three.js, or can you link some githubs or else ?
Thank you for feedbacks.
Just an extension of the manthrax's idea with Tween.js
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 startRotation = new THREE.Quaternion();
var targetRotation = new THREE.Quaternion();
window.addEventListener("mousedown", onMouseDown, false);
function onMouseDown(event) {
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);
// manthrax's idea + Tween.js
startRotation.copy(camera.quaternion);
camera.lookAt(newPosition);
camera.updateMatrixWorld();
targetRotation = camera.quaternion.clone();
camera.quaternion.copy(startRotation);
new TWEEN.Tween(camera.quaternion).to(targetRotation, 1000).easing(TWEEN.Easing.Bounce.Out).delay(250).start();
// one of benefits of using Tween.js is easings
// you can find many of them here
// https://sole.github.io/tween.js/examples/03_graphs.html
}
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/91/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>
Something like:
var targetRotation,startTime,transitionDuration;
var startRotation = camera.quaternion.clone();
function smoothTransition(newTarget){
startRotation.copy(camera.quaternion);
camera.lookAt(newTarget);
camera.updateMatrixWorld();
targetRotation = camera.rotation.clone();
startTime = performance.now();
transitionDuration = 1000;
}
In animate:
if(startRotation){
var playTime = (performance.now()-startTime)/transitionDuration;
if(playTime>1)playTime = 1;
Quaternion.slerp(startRotation,targetRotation,camera.rotation,playTime);
camera.updateMatrixWorld();
}

Using three.js raycaster as the developers recommend?

My code works for picking objects and turning them a different color, but what is the technique to get them back to the original color after my mouse crosses the cube? Here is my code. As it stands, when I bring my mouse over the cube, it turns red.
(document).ready(function () {
window.addEventListener('mousemove', onMouseMove, false);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
//start a scene
var scene = new THREE.Scene();
//add a cube
var cube = new THREE.Mesh(new THREE.CubeGeometry(3, 3, 3), new THREE.MeshBasicMaterial({ color:0x454545 }));
cube.position.set(0, 0, 0);
scene.add(cube);
//get a renderer
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xffffff);
renderer.setSize(window.innerWidth, window.innerHeight);
$('#GameArea').append(renderer.domElement);
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(10, 10, 10);
camera.lookAt(0,0,0);
var controls = new THREE.TrackballControls(camera);
var light = new THREE.SpotLight(0xeeeeee);
light.position.set(-10, 5, 40);
scene.add(light);
render();
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;
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);
}
}
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);
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
});

EdgesHelper loses its mesh when scene is rotated

I've drawn a mesh with a edgeshelper.
When I rotate the scene using the mouse, the edges seems to react twice as fast the mesh.
The result is that the edges don't "fit" around the mesh anymore when the scene is rotated.
What am I doing wrong ?
<html>
<body onmousemove="bmousemove(event);">
<script src="three.min.js"></script>
<script>
var prevX = 0, prevY = 0;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight, 1,10000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
geometry = new THREE.BoxGeometry( 10, 20, 40, 1, 1, 1 );
material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
object = new THREE.Mesh( geometry, material );
edges = new THREE.EdgesHelper( object, 0xffffff );
camera.position.z = 100;
scene.add( object );
scene.add( edges );
render();
function render()
{
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function bmousemove(e)
{
if (prevX == 0)
{
prevX = e.clientX;
prevY = e.clientY;
}
scene.rotation.y += (e.clientX - prevX) / 100;
scene.rotation.x += (e.clientY - prevY) / 100;
prevX = e.clientX;
prevY = e.clientY;
}
</script>
</body>
</html>
I'm using version r71 under Windows 7
Yes, this is a consequence of how EdgesHelper(and some of the other helpers) have been implemented.
If you want to use EdgesHelper, you have to adhere to two rules:
EdgesHelper must be added to the scene, directly.
The scene can't have a transform applied (for example, be rotated).
Consequently, you will have to rotate the mesh, instead.
three.js r.71

camera inside a sphere

I want to create a skydome and made the shpere, texture loading also fine but I cannot move the camera inside the sphere.
The sphere disappears. I know it is an amateur issue but cannot see the inside of the sphere.
Is it some kind of cutting or Z-buffer issue?
How can I fix it?
My code:
<html>
<head>
<script src="js/jquery-1.8.3.min.js"></script>
<script src="js/three.min.js"></script>
</head>
<body>
<div id="container">
</div>
<script>
function addSpaceSphere( ){
// set up the sphere vars
var radius = 200,
segments = 16,
rings = 16;
var material = new THREE.MeshPhongMaterial({
color:0xFFFFFF,
map: THREE.ImageUtils.loadTexture( 'textures/SPACE014SX.png' )
});
var sphere = new THREE.Mesh(
new THREE.SphereGeometry(
radius,
segments,
rings
),
material
);
// add the sphere to the scene
scene.add(sphere);
}
function addLights(){
// create a point light
var ambient = new THREE.AmbientLight( 0xFFFFFF );
scene.add( ambient );
}
function render() {
camera.lookAt( focus );
camera.updateProjectionMatrix();
renderer.render( scene, camera );
}
function animate() {
requestAnimationFrame( animate );
render();
}
function createScene(){
// add the camera to the scene
scene.add(camera);
// the camera starts at 0,0,0
// so pull it back
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 300;
// start the renderer
renderer.setSize(WIDTH, HEIGHT);
$container.append(renderer.domElement);
addSpaceSphere( );
addLights();
animate();
}
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.01,
FAR = 10000;
var focus = new THREE.Vector3( 0, 0, 0 );
var isUserInteracting = false,
onPointerDownPointerX = 0, onPointerDownPointerY = 0,
lon = 0, onPointerDownLon = 0,
lat = 0, onPointerDownLat = 0,
phi = 0, theta = 0;
var $container = $('#container');
// create a WebGL renderer, camera
// and a scene
//var renderer = new THREE.CanvasRenderer();
var renderer = new THREE.WebGLRenderer();
var camera = new THREE.PerspectiveCamera(
VIEW_ANGLE, ASPECT, NEAR, FAR
);
var scene = new THREE.Scene();
createScene();
</script>
</body>
make the skydome material double-sided -- it's being culled. Set the 'side' attribute to THREE.DoubleSide
(or THREE.BackSide should work too, if the camera is ONLY inside the sphere)

Animating canvas billboard in Three.js

I'm trying to animate a Canvas-based texture that is mapped onto a plane, like a billboard. I've made a point of including material.needsUpdate & texture.needsUpdate, but I'm still unable to get the texture to come to life. I've also included a rotating cube just so I know the animation routine is functioning on some level.
Here is the code:
<body>
<script src="http://mrdoob.github.com/three.js/build/three.min.js"></script>
<script>
if (window.innerWidth === 0) {
window.innerWidth = parent.innerWidth;
window.innerHeight = parent.innerHeight;
}
var camera, scene, renderer;
var mesh, geometry, material;
var light, sign, animTex;
var canvas, context;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1200);
camera.position.z = 700;
scene = new THREE.Scene();
material = new THREE.MeshLambertMaterial(
{
color: 0x885522,
wireframe: false,
overdraw: false
});
geometry = new THREE.CubeGeometry(80, 120, 100, 1, 1, 1);
mesh = new THREE.Mesh(geometry, material);
sign = createSign();
light = new THREE.DirectionalLight(0xFFFFFF, 3.0);
light.position = new THREE.Vector3(5, 10, 7);
light.target = new THREE.Vector3(0, 0, 0);
scene.add(mesh);
scene.add(sign);
scene.add(light);
renderer = new THREE.CanvasRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function createSign() {
canvas = document.createElement("canvas");
context = canvas.getContext("2d");
canvas.width = 200;
canvas.height = 200;
context = canvas.getContext("2d");
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
var material = new THREE.MeshBasicMaterial({ map : texture, overdraw: true });
material.needsUpdate = true;
var mesh = new THREE.Mesh(new THREE.PlaneGeometry(200, 200), material);
mesh.doubleSided = true;
return mesh;
}
function animate() {
var time = Date.now()*0.01;
var sinTime = Math.sin(time * 0.05) * 100;
var cosTime = Math.cos(time * 0.05) * 100;
mesh.rotation.y = sinTime*0.01;
requestAnimationFrame(animate);
context.fillStyle = "black";
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "white";
context.fillRect((canvas.width/2) + sinTime, (canvas.height/2) + cosTime, 20, 20)
renderer.render(scene, camera);
}
This runs, but I can't seem to get the Canvas texture material to update. What have I overlooked?
Place this right before your render() call:
sign.material.map.needsUpdate = true;
The needsUpdate flag is reset (to false) every time the texture is used (every render loop), so it needs to be set to true in the render loop (before the render call, or it'll be a frame off). So in your example, put sign.material.map.needsUpdate = true before renderer.render( scene, camera ). texture.needsUpdate = true and material.needsUpdate = true are not needed.
Also, you only need to set the needsUpdate flag on the texture, as the material properties are not changing.

Resources