I'm having an issue with the mousemove function in a three.js scene. The goal is for the text to change color when the mouse is over the object. I've followed many examples from the web and the raycaster doesn't register the object. I tried adding update() in the function animate() like in the link above, but the console indicates an error, "update is not defined." I can't put the mousemove function after the init function because it doesn't recognize the object variable.
var mouse = { x: 0, y: 0 }, INTERSECTED;
var projector = new THREE.Projector();
var scene, camera,renderer;
function init(font){...
//loaded the font, camera, etc.
var option="object";
var geometry_option= new THREE.TextGeometry( option{font:font, size:200,height:20, curveSegments:2});
var material_option = new THREE.MeshBasicMaterial( { color: 0x0000000, side: THREE.BackSide } );
var option1= new THREE.Mesh(geometry_option, material_option); //added the position and added it to the scene.
//added other functions under function init such as mousedown,mousemove, etc.
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
function update()
{
// find intersections
// create a Ray with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
// create an array containing all objects in the scene with which the ray intersects
var intersects_option= ray.intersectObjects([option1] );
if ( intersects_option.length > 0 )
{
if ( intersects_option[ 0 ].object != INTERSECTED )
{
if ( INTERSECTED )
INTERSECTED.material.color.setHex( INTERSECTED.currentHex );
INTERSECTED = intersects_option[ 0 ].object;
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
INTERSECTED.material.color.setHex( 0xffff00 );
}
}
else
{
if ( INTERSECTED )
INTERSECTED.material.color.setHex( INTERSECTED.currentHex );
INTERSECTED = null;
}
}
} // close init function
Related
Like their`s official example, we can use Raycaster to get current matched objects https://threejs.org/docs/?q=Raycaster#api/en/core/Raycaster.
The official example is:
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
function onPointerMove( event ) {
// calculate pointer position in normalized device coordinates
// (-1 to +1) for both components
pointer.x = ( event.clientX / window.innerWidth ) * 2 - 1;
pointer.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
function render() {
// update the picking ray with the camera and pointer position
raycaster.setFromCamera( pointer, camera );
// calculate objects intersecting the picking ray
const intersects = raycaster.intersectObjects( scene.children );
for ( let i = 0; i < intersects.length; i ++ ) {
intersects[ i ].object.material.color.set( 0xff0000 );
}
renderer.render( scene, camera );
}
window.addEventListener( 'pointermove', onPointerMove );
window.requestAnimationFrame(render);
Accrodding to this example, in my understanding, I can just get intersects[i].object, which is a threejs Mesh class`s instance.
I want to bind onClick function to object3d like this way:
function createObject(id, position) {
// ...
const mesh = new Mesh()
// ...
mesh.onClick = () => handleClickFn(id, position);
scene.add(mesh);
}
Then I can call intersects[i].object.onClick(); to trigger it.
Every examples I found seems like they do some operation to intersects[i].object directly. Just like intersects[ i ].object.material.color.set( 0xff0000 );.
So, is there any way I can bind functions to each intersects[i].object like this?
I am trying to make draggable objects, as seen in this example: https://www.script-tutorials.com/demos/467/index.html
The objects which should be draggable are in the array objectMoverLines.
I have added a plane to my scene with the following code:
plane = new THREE.Mesh(new THREE.PlaneBufferGeometry(500, 500, 8, 8), new THREE.MeshBasicMaterial({color: 0x248f24, alphaTest: 0}));
plane.visible = false;
scene.add(plane);
The problem occurs under the onDocumentMouseDown function. For some reason, if the planes visibility is set to false (plane.visible = false), then at a certain point, intersectsobjmovers will not be populated. If the plane's visibility is set to true, however, it will work fine (but obviously, that causes a huge plane to be in the way of everything):
function onDocumentMouseDown(event) {
// Object position movers
var vector = new THREE.Vector3(mouse.x, mouse.y, 1);
vector.unproject(camera);
raycaster.set( camera.position, vector.sub( camera.position ).normalize() );
var intersectsobjmovers = raycaster.intersectObjects(objectMoverLines);
if (intersectsobjmovers.length > 0) {
console.log('clicking an object mover');
// Disable the controls
controls.enabled = false;
// Set the selection - first intersected object
objmoverselection = intersectsobjmovers[0].object;
// Calculate the offset
var intersectsobjmovers = raycaster.intersectObject(plane);
// At this point, intersectsobjmovers does not include any items, even though
// it should (but it does work when plane.visible is set to true...)
offset.copy(intersectsobjmovers[0].point).sub(plane.position);
} else {
controls.enabled = true;
}
}
Also, this is what I currently have under the onDocumentMouseMove function:
function onDocumentMouseMove(event) {
event.preventDefault();
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
// Get 3D vector from 3D mouse position using 'unproject' function
var vector = new THREE.Vector3(mouse.x, mouse.y, 1);
vector.unproject(camera);
// Set the raycaster position
raycaster.set( camera.position, vector.sub( camera.position ).normalize() );
if (objmoverselection) {
// Check the position where the plane is intersected
var intersectsobjmovers = raycaster.intersectObject(plane);
// Reposition the object based on the intersection point with the plane
objmoverselection.position.copy(intersectsobjmovers[0].point.sub(offset));
} else {
// Update position of the plane if need
var intersectsobjmovers = raycaster.intersectObjects(objectMoverLines);
if (intersectsobjmovers.length > 0) {
// var lookAtVector = new THREE.Vector3(0,0, -1);
// lookAtVector.applyQuaternion(camera.quaternion);
plane.position.copy(intersectsobjmovers[0].object.position);
plane.lookAt(camera.position);
}
}
requestAnimationFrame( render );
}
try this:
plane = new THREE.Mesh(new THREE.PlaneBufferGeometry(500, 500, 8, 8),
new THREE.MeshBasicMaterial( {
color: 0x248f24, alphaTest: 0, visible: false
}));
scene.add(plane);
I had loaded 3 external model with the name into my scene using json loader and now i want to get the name of the model/object by clicking it.
Below is the that i had used to load the model
var object_material = new THREE.MeshBasicMaterial({
color: 0xd6d6d6,
side: THREE.DoubleSide
});
var loader = new THREE.JSONLoader();
loader.load("models/"+file,
function(geometry, object_material)
{
var object = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(object_material));
model = new THREE.Object3D();
model.id=file;
model.name='sample'+file;
model.userData.id='sampledata'+file;
model.add(object);
model.position.set(obj_x,obj_y,obj_z);
model.mirroredLoop = true;
model.castShadow = true;
model.receiveShadow = true;
scene.add(model);
}
);
Below is my mouse down function
function onMouseDown(event) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
projector.unprojectVector( vector, camera );
var pLocal = new THREE.Vector3(0, 0, -1);
var pWorld = pLocal.applyMatrix4(camera.matrixWorld);
var ray = new THREE.Raycaster(pWorld, vector.sub(pWorld).normalize());
// Get meshes from all objects
var getMeshes = function(children) {
var meshes = [];
for (var i = 0; i < children.length; i++)
{
if (children[i].children.length > 0) {
meshes = meshes.concat(getMeshes(children[i].children));
} else if (children[i] instanceof THREE.Mesh) {
meshes.push(children[i]);
}
}
return meshes;
};
function attributeValues(o)
{
var out = [];
for (var key in o) {
if (!o.hasOwnProperty(key))
continue;
out.push(o[key]);
}
return out;
}
var objects = attributeValues(this.o3dByEntityId);
var meshes = getMeshes(objects);
var intersects = ray.intersectObjects(meshes);
raycaster.set( camera.position, vector.sub( camera.position ).normalize() );
var intersects = raycaster.intersectObjects( scene.children );
console.log(scene);
// this console displays all the objects under children - THREE.Object3D - name as name: "sample513.js"
if ( intersects.length > 0 )
{
// but for the clickedObject - the length is > 0 and name is empty
var clickedObject = intersects[0].object;
console.log(clickedObject.parent.userData.id); // return as undefined
if ( INTERSECTED != intersects[ 0 ].object )
{
INTERSECTED= intersects[ 0 ].object;
name = INTERSECTED.name;
}
} else {
console.log('intersects.length is 0');
}
}
Even-though i had provided the model name in the userData , i am not able to retrieve it . can any one guide me how to retrieve the name of the object when it is clicked
Try to make through this example. Look at messages in the console.
<script src="js/controls/EventsControls.js"></script>
EventsControls = new EventsControls( camera, renderer.domElement );
EventsControls.attachEvent( 'onclick', function() {
console.log( 'this.focused.name: ' + this.focused.name );
});
// if use drag and drop
EventsControls.attachEvent( 'dragAndDrop', function () {
this.container.style.cursor = 'move';
this.focused.position.y = this.previous.y;
});
EventsControls.attachEvent( 'mouseOut', function () {
this.container.style.cursor = 'auto';
});
var jsonLoader = new THREE.JSONLoader();
jsonLoader.load( "models/Tux.js", addModelToScene );
function addModelToScene( geometry, materials ) {
var material = new THREE.MeshFaceMaterial( materials );
model = new THREE.Mesh( geometry, material );
model.scale.set( 10, 10, 10 ); model.name = 'Tux';
model.rotation.x = -Math.PI/2;
model.position.set( 175, 45, 125 );
scene.add( model );
EventsControls.attach( model );
}
The parent of the clickedObject is probably undefined. Perhaps you can console log the clickedObject and see which path you need to access the id.
I set a canvas renderer which contain two meshs (cubes). What i need to do is to catch the click event on each cube to call the convenient method for it.
So far, i could catch the click event on all the renderer, means when i click on cube1 and cube2, the click belong the same 'cause it's bound to the renderer :)
My question is, how to bind the click event on each cube?
My relevant code is the following:
//dom
var containerPopUp=document.getElementById('popup');
//renderer
var rendererPopUp = new THREE.CanvasRenderer();
rendererPopUp.setSize(420,200);
containerPopUp.appendChild(rendererPopUp.domElement);
//Scene
var scenePopUp = new THREE.Scene();
//Camera
var cameraPopUp = new THREE.PerspectiveCamera(50,60/60,1,1000);
cameraPopUp.position.z = 220;
cameraPopUp.position.y = 20;
//
scenePopUp.add(cameraPopUp);
//Add texture for the cube
//Use image as texture
var img2D = new THREE.MeshBasicMaterial({ //CHANGED to MeshBasicMaterial
map:THREE.ImageUtils.loadTexture('img/2d.png')
});
img2D.map.needsUpdate = true; //ADDED
//Add Cube
var cubeFor2D = new THREE.Mesh(new THREE.CubeGeometry(40,80,40),img2D);
cubeFor2D.position.x =- 60;
cubeFor2D.position.y = 20;
scenePopUp.add(cubeFor2D);
//
var img3D = new THREE.MeshBasicMaterial({ //CHANGED to MeshBasicMaterial
map:THREE.ImageUtils.loadTexture('img/3d.png')
});
img3D.map.needsUpdate = true;
var cubeFor3D = new THREE.Mesh(new THREE.CubeGeometry(40,80,40),img3D);
cubeFor3D.position.x = 60;
cubeFor3D.position.y=20;
scenePopUp.add(cubeFor3D);
//
rendererPopUp.render(scenePopUp,cameraPopUp);
//
animate();
rendererPopUp.domElement.addEventListener('click',testCall,false);//Here the click event is bound on the whole renderer, means what ever object in the renderer is clicked, the testCall method is called.
As you can see, cubeFor2D and cubeFor3D are contained in the renderer. I need to bind the click event on each mesh. I tried this with the threex.domevent.js:
var meshes = {};
meshes['mesh1'] = cubeFor2D;
meshes['mesh1'].on('mouseover', function(event){
//response to click...
console.log('you have clicked on cube 2D');
});
But it doesn't work, in the console, i got this error:
TypeError: meshes.mesh1.on is not a function
Of course, i included the API source code file:
<script src="threex.domevent.js"></script>
You can generate a callback like this. First define your callback function for each object:
mesh.callback = function() { console.log( this.name ); }
Then follow the standard picking pattern:
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onDocumentMouseDown( event ) {
event.preventDefault();
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 );
if ( intersects.length > 0 ) {
intersects[0].object.callback();
}
}
EDIT: updated to three.js r.70
Create a click handler
window.addEventListener('click', onDocumentMouseDown, false);
Define the function onDocumentMouseDown, note that raycaster the difference in above answer is the index position of the object clicked!
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onDocumentMouseDown( event ) {
event.preventDefault();
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
console.log(scene.children);
var intersects = raycaster.intersectObjects( scene.children );
console.log(intersects[1]);
if ( intersects.length > 0 ) {
intersects[1].object.callback();
}}
Define the Mesh object
var mesh_menu_title = new THREE.Mesh(geometry_menu, materials_menu);
mesh_menu_title.name = 'select_lang';
mesh_menu_title.callback = function() { select_language();}
scene.add(mesh_menu_title);
define the callback function
function select_language(){
var selectedObject = scene.getObjectByName("select_lang");
scene.remove( selectedObject );
var selectedObject = scene.getObjectByName("start");
scene.remove( selectedObject );
var selectedObject = scene.getObjectByName("menu");
scene.remove( selectedObject );
}
So this code above will handle specific object clicked inside my canvas, then callback a function, the "mesh.callback" and it will remove some scene childs from the canvas.
It doesn't work if you use intersects[0].object.callback(); because at the index 0 the stored object are the vertices.
I'm on the final stretch of a this project I've been working on and I'm having issues with the ability to click and drag objects. I've added them all into an array called items, and it sort of works right now. Here is the link to the page in action. If you add any of the items from the menu in the upper right, it'll show up but you can only drag it around piece by piece. From what I can tell, the issue is that it is treating each item as a series of items instead of as one item. This makes sense as each model is several models pieced together, but I'm not sure how to work around that. Any ideas?
Here are the three functions I have controlling mouse interaction:
function onMouseMove( event ){
event.preventDefault();
mouse.x = ( event.clientX / width ) * 2 - 1;
mouse.y = - ( event.clientY / height ) * 2 + 1;
var vector = new THREE.Vector3( mouse.x, mouse.y, 0 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
if ( SELECTED ) {
var intersects = ray.intersectObject( plane );
SELECTED.position.copy( intersects[ 0 ].point.subSelf( offset ) );
return;
}
var intersects = ray.intersectObjects( items );
if ( intersects.length > 0 ) {
if ( INTERSECTED != intersects[ 0 ] ) {
INTERSECTED = intersects[ 0 ].object;
plane.position.copy( INTERSECTED.position );
}
container.style.cursor = 'pointer';
}
else {
INTERSECTED = null;
container.style.cursor = 'auto';
}
}
function onMouseDown( event ) {
event.preventDefault();
var vector = new THREE.Vector3( mouse.x, mouse.y, 0 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
var intersects = ray.intersectObjects( items );
if ( intersects.length > 0 ) {
SELECTED = intersects[ 0 ].object;
var intersects = ray.intersectObject( plane );
offset.copy( intersects[ 0 ].point ).subSelf( plane.position );
container.style.cursor = 'move';
}
}
function onMouseUp( event ) {
event.preventDefault();
if ( INTERSECTED ) {
plane.position.copy( INTERSECTED.position );
SELECTED = null;
}
container.style.cursor = 'auto';
}
It's heavily based on this example, but without the color bits.
By changing the code in onMouseDown like so
// OLD
SELECTED = intersects[0].object;
// NEW
SELECTED = intersects[0].object.parent;
I can now move the full object. This only works if the object only has one parent though, and so some items are not able to move with this code. Anyone have a suggestion on determining if it has parent objects and moving up if it does?
If somebody is still interested in this question subSelf method is now called sub.
Resolved by adding the following to onMouseDown
SELECTED = intersects[0].object;
while(SELECTED.parent != scene){
SELECTED = SELECTED.parent;
}
This ensures that the object grabbed will be the highest level that isn't the scene and makes all the models drag-able.