Im currently working on an small web-application which is using threejs. I ran into the following issue:
I build a prototype which contains my threejs content and everything works well here (The canvas is in the prototype window.innerWidth and window.innerHeight => so has the same size as my Browser window. Selecting works well but I want to use the canvas on my web page application and picking of 3d surfaces needs to work as well there.
I discovered as soon as I change the margin or top via CSS of the canvas it doesn't work anymore. The web-application is based on a scroll page and the threejs canvas is inside a div container which can only be seen by scrolling through the page.
For picking I use the following logic/code -> this one works well in the "fullscreen prototype" but not in the web application page
self.renderer.domElement.addEventListener( 'click', function(event){
event.preventDefault();
//CONVERT MOUSE POSITION TO CORRECT VECTOR
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
//TRANSLATES A 2D POINT FROM Normalized Device Coordinates TO RAYCASTER THAT CAN BE USED FOR PICKING
self.projector.unprojectVector( vector, self.camera );
//RAYCASTER IS NEEDED TO DETECT INTERACTION WITH CUBE SURFACE
var raycaster = new THREE.Raycaster( self.camera.position, vector.sub( self.camera.position ).normalize() );
var intersects = raycaster.intersectObjects( self.scene.children );
//CHANGE COLOR BASED ON INTERSECTION WITH ELEMENT
if ( intersects.length > 0 ) {
//SELECTED OBJECT
}
}, false );
I think that the calculation is wrong for the var vector but I just can't figure it out how to do it correctly.
Any help would be appreciated
Thank you
best reards
200% way
var x = event.offsetX == undefined ? event.layerX : event.offsetX;
var y = event.offsetY == undefined ? event.layerY : event.offsetY;
var vector = new THREE.Vector3();
vector.set( ( x / renderer.domElement.width ) * 2 - 1, - ( y / renderer.domElement.height ) * 2 + 1, 0.5 );
projector.unprojectVector( vector, camera );
Or see this example. Look at messages in the console.
<script src="js/controls/EventsControls.js"></script>
EventsControls = new EventsControls( camera, renderer.domElement );
EventsControls.draggable = false;
EventsControls.onclick = function() {
console.log( this.focused.name );
}
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
EventsControls.attach( mesh );
//
function render() {
EventsControls.update();
controls.update();
renderer.render(scene, camera);
}
If you want to use it in your webpage, you probably need to calculate the vector with the width and height from your canvas element instead of the window which is your whole browser window.
Related
I'm trying to use THREE.js and been looking at some examples, Voxel Painter exmaple
I'm trying to get it so that every time you click to create a new cube the roll over mesh will always move on top of the cube just pasted rather than being at the point of intersecting of the current mouse position...
All of the source code can be viewed from the link but I believe what I'm trying to do has something to do with this...
You click the mouse to add a Voxel, when onMouseDown() function is active it will check if current mouse position is intersecting with the plane and if CTRL button has been pressed for either a new cube or delete a cube.
function onDocumentMouseDown( event ) {
event.preventDefault();
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
intersector = getRealIntersector( intersects );
// delete cube
if ( isCtrlDown ) {
if ( intersector.object != plane ) {
scene.remove( intersector.object );
}
}
// create cube
else {
intersector = getRealIntersector( intersects);
setVoxelPosition( intersector );
var voxel = new THREE.Mesh( cubeGeo, cubeMaterial );
voxel.position.copy( voxelPosition );
voxel.matrixAutoUpdate = false;
voxel.updateMatrix();
scene.add( voxel );
}
}
}
When creating a new cube I believe THREE.js grabs the current point where the mouse intersects intersector = getRealIntersector( intersects); and then sets the new Voxel position with the function setVoxelPosition( intersector ); with the intersect point being passed in.
This is the setVoxelPosition function
function setVoxelPosition( intersector ) {
normalMatrix.getNormalMatrix( intersector.object.matrixWorld );
tmpVec.copy( intersector.face.normal );
tmpVec.applyMatrix3( normalMatrix ).normalize();
voxelPosition.addVectors( intersector.point, tmpVec );
voxelPosition.x = Math.floor( voxelPosition.x / 50 ) * 50 + 25;
voxelPosition.y = Math.floor( voxelPosition.y / 50 ) * 50 + 25;
voxelPosition.z = Math.floor( voxelPosition.z / 50 ) * 50 + 25;
}
and the render loop
function render() {
if ( isShiftDown )
theta += mouse2D.x * 1.5;
raycaster = projector.pickingRay( mouse2D.clone(), camera )
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
intersector = getRealIntersector( intersects );
if ( intersector ) {
setVoxelPosition( intersector );
rollOverMesh.position = voxelPosition;
}
}
camera.position.x = 1400 * Math.sin( THREE.Math.degToRad( theta ) );
camera.position.z = 1400 * Math.cos( THREE.Math.degToRad( theta ) );
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
I have tried to pass in different values into setVoxelPosition( intersector ) but I can't seem to get it right..
Could someone please point me in the right direction?
Thanks.
There are several ways of doing this. I'm not going to sugar coat this answer because frankly, reverse engineering this code will do you some good. I will say, having worked with this voxel code myself, it's important you understand what's happening when you click the mouse button and create a new voxel box.
You're correct in understanding that, this function is in fact taking the current mouse position and creating the box. When the click is complete and the box has been made, the process starts over so the program again looks to where the mouse is and places the Ghost box. In this case the Ghost box is not on top of the previously made box, so you'd have to move the mouse up manually a few pixels to get it there.
Rather than fool around with the setVoxelPosition function directly, I'd recommend you 'temporarly change the x,y,z position of the ghost in relation to the matrix intersect mouse position of your computer. Upon a successful click, increase the matrixIntersect.x .y .z properties of this matrixIntersects object, increasing these values only a little so you get the 'box-on-top' effect you want directly after a click. Remember to change them back when the users mouse moves off the object otherwise the Ghost box will no longer be directly under the mouse and things can get messy fast if these properties grow after every click.
I'm actually working on a website using three.js. You can see a demo here: https://c9.io/frescogusto/demi/workspace/demi_0.3.html
It's on canvas to be viewable on ios and android.
Question is: how do I move the camera from its position to the position of the object that has been clicked? Do I have to use translate method on every axis or there is a faster way?
thanks in advance
pp
renderer.domElement.addEventListener('mousedown', function(event) {
event.preventDefault();
var vector = new THREE.Vector3(
renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
- renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
0.5
);
projector.unprojectVector(vector, camera);
var raycaster = new THREE.Raycaster(
camera.position,
vector.sub( camera.position ).normalize()
);
var intersects = raycaster.intersectObjects(YOUR_CLICKABLE_OBJECTS);
if (intersects.length) {
camera.position = intersects[0].point;
// Alternatively, camera.position = intersects[0].object.position.clone();
}
}, false);
This code:
Registers a listener on the mousedown event
Projects the clicked location in 2D screen space into a location in 3D scene space
Casts an imaginary line (a ray) from the camera towards the click
Checks whether any objects intersect with that line; the first object to intersect is the one that was clicked
Snaps the camera to the clicked position
If you'd rather transition the camera slowly to the clicked location instead of snapping it there immediately, you may want to look into a tweening library like TweenJS to help you with the timing of the transition.
(P.S.: I think renderer.devicePixelRatio is only present for the WebGL renderer. You can just remove it for other renderers.)
I'm trying to place a cube relative to the camera, rather than relative to the scene. The thing is, to place it in the scene (which I have to do make it show), I have to know the scene coordinates that correspond to the cubes camera space coordinates. I found this function "projectionMatrixInverse" in THREE.Camera. It has a nice function called "multiplyVector3" which I hoped would enable me to transform a vector (1,1,1) back to scene space like this:
var camera, myvec, multvec; // (and others)
camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, - 2000, 1000 );
camera.position.x = 200;
camera.position.y = 100;
camera.position.z = 200;
myvec = new THREE.Vector3(1,1,1);
console.log("myvec: ", myvec);
multvec = camera.projectionMatrixInverse.multiplyVector3(THREE.Vector3(1,1,1));
console.log("multvec: ", multvec);
the thing is, on the console i get:
myvec: Object { x=1, y=1, z=1}
TypeError: v is undefined
var vx = v.x, vy = v.y, vz = v.z;
multiplyVector3 simply doesn't accept my myvec, or says it's undefined, even though the console says it's an object. I don't get it.
The camera is located at the origin of it's coordinate system, and looks down it's negative-Z axis. A point directly in front of the camera has camera coordinates of the form ( 0, 0, z ), where z is a negative number.
You convert a point p
p = new THREE.Vector3(); // create once and reuse if you can
p.set( x, y, z );
from camera coordinates to world coordinates like so:
p.applyMatrix4( camera.matrixWorld );
camera.matrixWorld is by default updated every frame, but if need be, you can update it yourself by calling camera.updateMatrixWorld();
three.js r.95
This may also be what you're after:
scene.add( camera );
brick.position.set( 0, 0, -1 );
camera.add( brick );
Is it possible to use Projector and Ray with OrthographicCamera?
I searched for it but I didn't find any example or documents.
Also my Camera isn't set in center of screen.
camera = new THREE.OrthographicCamera(0, width, 0, height, orthonear, orthofar);
That mean top left will be (0 ,0).
So I don't think below code works correctly.
mouse.x = ( event.clientX / width ) * 2 - 1;
mouse.y = -( event.clientY / height) * 2 + 1;
So how can I use Projector and Ray with OrthographicCamera or any other methods to interact with objects?
Original example:
http://mrdoob.github.com/three.js/examples/webgl_interactive_voxelpainter.html
Use the DAT.GUI controls on the right to change the camera to Orthographic...
A little snippet from the code:
ray = projector.pickingRay( mouse2D.clone(), camera );
var intersects = ray.intersectObjects( scene.children );
I have building project with three.js... canvas where you can drag object
and play with the camera view as well... there is a famous example- "Draggable Cubes",
well my project is pretty similar.
On my project there are 3 main events: mouseup /mousedown/ mousemove...
Well everything was ok....but now I'm trying to run this code on iPhone,changing my events
with touchstart / touchmove / touchend...
The moving object function seems to work fine, but when I'm trying to select the object by clicking him,
it's always the same object that been selected... and not the one I'm pointing on....
I guess the problem is with this function:
function onDocumentMouseDown( event ) {
event.preventDefault();
var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
var intersects = ray.intersectObjects( objects );
if ( intersects.length > 0 ) {
SELECTED = intersects[ 0 ].object;
var intersects = ray.intersectObject( plane );
offset.copy( intersects[ 0 ].point ).subSelf( plane.position );
}
}
Is anybody have an idea what is the problem?
in this line :
var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
You use the mouse Vector2 object but you don't initialize it.
Something like this should work :
mouse.x = +(event.targetTouches[0].pageX / window.innerwidth) * 2 +-1;
mouse.y = -(event.targetTouches[0].pageY / window.innerHeight) * 2 + 1;