I've got an isometric level going and I'm trying to select objects within the level.
I've looked at a few Stackoverflow answers including this one Orthographic camera and selecting objects with raycast but at the moment, nothing seems to be working. Something is off, perhaps it has something to do with my camera set up, so here is the relevant code I am using.
// set up camera
scope.camera = new THREE.OrthographicCamera( - scope.cameraDistance * aspect, scope.cameraDistance * aspect, scope.cameraDistance, - scope.cameraDistance, - height, 1000 );
scope.camera.position.set(0 , 0 , 0);
scope.camera.rotation.order = scope.rotationOrder; // = "YXZ"
scope.camera.rotation.y = - Math.PI / 4;
scope.camera.rotation.x = Math.atan(- 1 / Math.sqrt(2));
When I loop through my matrix and add the tiles, I add them all to a new THREE.Object3D() and then add that object to the scene. My onMouseMove event looks like this
onMouseMove:function(event) {
event.preventDefault();
var scope = Game.GSThree,
$container = $(scope.container.element),
width = $container.width(),
height = $container.height(),
vector,
ray,
intersects;
scope.mouse.x = (event.clientX / width) * 2 - 1;
scope.mouse.y = - (event.clientY / height) * 2 + 1;
vector = new THREE.Vector3(scope.mouse.x , scope.mouse.y , 0.5);
ray = scope.projector.pickingRay(vector , scope.camera);
intersects = ray.intersectObjects(scope.tiles.children);
if(intersects.length) {
console.log(intersects[0]);
}
}
Now the problem is that something is very off. The ray intersects with things when it is no where near them and also seems to intersect with multiple children of tiles at a time. If I log intersects.length it sometimes returns 3, 2 or 1 object(s). Just in case it is relevant; my material for each object mesh is a new new THREE.MeshFaceMaterial() with an array containing 6 new THREE.MeshBasicMaterial() passed in.
Any ideas?
It's always the stupidest thing; my container element had padding-left:250px; on it. Removed that and it works. Always correct for your offsets!
Related
My I am trying to do a click to zoom feature with Three.js, I have a canvas and an object loaded in the canvas.On click I am trying to place the camera near the point of intersection(Actually like zooming that point).
Here is what I have done, but doesn't work as I wanted, on click camera positions changes but kind of works partially sometimes camera is placed near the point of intersection, some times not.
onmousedown = function (event) {
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
event.preventDefault();
mouse.x = (event.clientX / self.renderer.domElement.clientWidth) * 2 - 1;
mouse.y = -(event.clientY / self.renderer.domElement.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, self.camera);
var objects = [];
for (var i = 0; i < self.scene.children.length; i++) {
if (self.scene.children[i] instanceof THREE.Group) {
objects.push(self.scene.children[i]);
}
}
console.log(objects);
var intersects = raycaster.intersectObjects( objects,true );
console.log(intersects.length);
if (intersects.length > 0) {
self.camera.up = new THREE.Vector3(0, 0, 1);
self.camera.lookAt(new THREE.Vector3(0, 0, 0));
self.camera.position.z = intersects[0].point.z * .9;
self.camera.position.x = intersects[0].point.x * .9;
self.camera.position.y = intersects[0].point.y * .9;
}
};
Here self is a global viewer object which holds camera, canvas, different objects etc.
0.9 is just a number used to place camera just near the point of intersection.
camera used is PerspectiveCamera and controls is TrackballControls
new THREE.PerspectiveCamera(90, this.width / this.height, 1, 1000);
The objects loaded are from .obj or .dae files ,I expect this to work like click on any point on the object and place the camera near that point. But camera is moving but sometimes not near the point I clicked.
Does intersects[0] gives the nearest intersection point? or nearest in the direction of camera ?
What is my mistake here ?
I am new to three js , just started learning it.If something or some logic is wrong help me with that.
The position is a bit complicated to calculate; you have to find the segment between camera and intersection and than place the camera at specific distance from intersection along the segment looking to the intersection point.
try this:
var length=[the desiderated distance camera-intersection]
var dir = camera.position.clone().sub(intersects[0].point).normalize().multiplyScalar(length);
camera.position = intersects[0].point.clone().add(dir);
camera.lookAt(intersects[0].point);
I have created a fiddle: http://jsfiddle.net/h5my29aL/
It's not so difficult. Think of your object as a planet, and your camera as a satellite. You need to position the camera somewhere in an orbit near your object. Three contains a distanceTo function that makes it simple. The example uses a sphere, but it will work with an arbitrary mesh. It measures the distance from the center point to the desired vector3. In your case the vector3 is likely the face position returned by a picker ray. But anyhow, the lookAt is set to the mesh, and then a distance from the vertex is calculated so that the camera is always the same altitude regardless of a vertex's or face's distance from the object center.
var point = THREE.GeometryUtils.randomPointsInGeometry( geometry, 1 );
var altitude = 100;
var rad = mesh.position.distanceTo( point[0] );
var coeff = 1+ altitude/rad;
camera.position.x = point[0].x * coeff;
camera.position.y = point[0].y * coeff;
camera.position.z = point[0].z * coeff;
camera.lookAt(mesh.position);
I've came somewhat close to what I want with an example from Three js.
Three JS webgl_decals
this is what I have done.
function zoomCam(event) {
var point_mouse = new THREE.Vector2(),
var point_x = null;
var point_y = null;
if (event.changedTouches) {
point_x = event.changedTouches[ 0 ].pageX;
point_y = event.changedTouches[ 0 ].pageY;
} else {
point_x = event.clientX;
point_y = event.clientY;
}
point_mouse.x = (point_x / window.innerWidth) * 2 - 1;
point_mouse.y = -(point_y / window.innerHeight) * 2 + 1;
if (sceneObjects.length > 0) {
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera(point_mouse, camera);
var intersects = raycaster.intersectObjects(sceneObjects, true);
if (intersects.length > 0) {
var p = intersects[ 0 ].point;
var n = intersects[ 0 ].face.normal.clone();
n.multiplyScalar(10);
n.add(intersects[ 0 ].point);
camera.position.copy(n);
camera.lookAt(p);
}
}
There might be some minor issues as I formatted/changed the code for answering here. Check the code before implementing.
The Problem
Ok, so I'm relatively new with three.js and I'm trying to do this: on click of a avatar in 3D space, it grabs the selected avatars relative this value (so I can do returned_object.position.x and returned_object.data.name, etc...) but it's not working. Right now I can click an object, and it brings up the three.js SCENE values for the object, but not mine.
-- edit additions
I'd like to be able to grab the value of this.data set by the avatar function - on click of an avatar.
But when I console.log(intersects); as shown below in the code, it comes up with the three.js object, stuff. See this link for a screenshot
I want to just do something like console.log(intersects.data); and have it return what is set in the avatar function
Sorry, this is surprisingly hard to put into words.
The Code
Here's all the code that's needed to make sense of it all:
var avatars = [],
avatar;
avatar = function(id, row) {
this.data = row;
this.avatar = new THREE.Object3D();
this.avatar.add(head);
this.avatar.position.x = 20 * id;
//extra
SCENE.add(this.avatar);
avatars[id] = this;
}
$(document).click(function(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;
mouse.fx = -mouse.y / 6;
mouse.fy = (mouse.x / 6) + 0.3;
CAMERA.rotation.x = ((-mouse.y) / 30) + -0.4;
CAMERA.position.z = (mouse.y * -20) + 80;
CAMERA.position.x = (mouse.x * 20);
raycaster.setFromCamera( mouse, CAMERA );
var intersects = raycaster.intersectObjects( SCENE.children, true ); // It grabs all the objects in the scene, but I only need it to grab objects in the avatars array.
console.log(intersects);
//I want to just do - console.log(intersects.data); - and have it return what is set in the avatar function.
console.log(intersects);
});
Now I got it: Your avatar consists of a head or several objects and of course these objects are returned by the raycaster, but the data of interest is within your avatars array.
You could do it either way: Every Object3D has an userData object, where you can put your data (this.avatar.userData = row;). Since the intersect object is (in this case) the head which is a child object of your avatar, you access the data via intersects[0].object.parent.userData.
Maybe a better way would be to store just the id within that userData object. Because with the id you have an "reference" to the avatars array.
// save id to userData
this.avatar.userData.id = id;
// accessing avatar
var clickedAvatar = avatars[intersects[0].object.parent.userData.id];
console.log(clickedAvatar.data.name);
I'm working on someone else's project to finish it, and it's pretty cool. It is supposed to be a diagram that will dynamically generate nodes(spheres) and be able to click on them and then a new diagram with related nodes will appear.
I am working on the clic kable part now, it seems harder than i thought. I have read into raycasting and vectors. I have even found examples that get pretty close to what i want: http://mrdoob.github.io/three.js/examples/canvas_interactive_cubes.html
BUT, for some reason when i hover next to the nodes (the hover spots seem very arbitrary, as highlighted in the image) they change color. For some most of them i cant even find the "hotspots".
I think i lack some understanding of the unprojecting/converting/transformation from 3d to 2d part. And maybe that's why my mouse doesn't intersect properly with the nodes on screen i guess.
My onmousemove event:
function onDocumentMouseMove2(event){
event.preventDefault();
var canvasSvg= d3.select("#canvas-svg")[0][0];
mouse.x = ( event.clientX / canvasSvg.clientWidth) * 2 - 1;
mouse.y = - ( event.clientY / canvasSvg.clientHeight ) * 2 + 1;
var vector = new THREE.Vector3(mouse.x, mouse.y, 1).unproject(camera);
//raycaster.setFromCamera( mouse, camera );
raycaster.set(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects( scene.children );
//console logs
console.log("current 'canvas' div");
console.log(canvasSvg)
console.log("mouse");
console.log(mouse);
console.log("vector");
console.log(vector);
if ( intersects.length > 0 ) {
intersects[0].object.material.color.setHex( Math.random() * 0xffffff ); //gives another color to the node i hover over
intersects[0].object.callback(); //this calls the funcion "callback" i attached to the nodes.
}
for ( var i in intersects ) {
intersects[ i ].object.material.color.setHex( Math.random() * 0xffffff | 0x80000000 );
}
}
My camera
// Set camera attributes and create camera
var VIEW_ANGLE = 7, //field of view
ASPECT = WIDTH / HEIGHT,
//ASPECT = $container[0].clientWidth / $container[0].clientHeight,
NEAR = 0.1,
FAR = 10000;
var camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
http://i.imgur.com/wWlEYQT.png?1
Good day! I managed to fix it with this example:
http://jsfiddle.net/fek9ddg5/62/
mouse.x = ( ( event.clientX - renderer.domElement.offsetLeft ) / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( ( event.clientY - renderer.domElement.offsetTop ) / renderer.domElement.height ) * 2 + 1;
The ray was indeed confused on where it had to be projected. I had to take note of the offSet from the top of thescreen to my canvas.
I have some code that tweens an object towards the camera and fits to half the screen
var vFOV = camera.fov * Math.PI / 180;
var ratio = 2 * Math.tan( vFOV / 2 );
var screen = ratio * (window.innerWidth * 0.6 / window.innerHeight * 0.6) ;
var size = getCompoundBoundingBox( object ).max.y;
var width = getCompoundBoundingBox( object ).max.x;
var dist = (size/screen) * (object.scale.x * 2);
//get final position in front of camera
var pLocal = new THREE.Vector3( 0, 0, -dist );
//apply the direction the camera is facing
var target = pLocal.applyMatrix4( camera.matrixWorld );
//tween the object towards the camera
var tweenMove = new TWEEN.Tween(object.position).to(target, 1500).easing(TWEEN.Easing.Cubic.InOut);
The next thing I need to do is able able to move it either to the left or up, for other UI on the screen and the responsive element.
i.e. to move it to the left 3rd, or top third. It also needs to still be square onto the camera.
I've tried changing the pLocal value to something like (1, 0, - dist) but this just rotates the object when it tweens up.
Any ideas how I can add that functionality?
Solved by adding a new look position to left of camera
I'm trying to scale height(y) of a cube as well as raising its y position simultaneously so that its base remains on the same x-z plane even after scaling. The rendering works fine but it is not working as expected. The object position increases rapidly in Y and then it starts disappearing. Can anyone help me out?
I've added an eventListener: mouseMove and its listener function to do the above transformations:
var fHeight = 5, yShift;
function onDocumentMouseMove(event) {
var mouseVector = new THREE.Vector3(2*(event.clientX/window.innerWidth) - 1, 1 - 2*(event.clientY/window.innerHeight));
var projector = new THREE.Projector();
var raycaster = projector.pickingRay(mouseVector.clone(), camera);
var intersects = raycaster.intersectObjects( Object.children );
if(intersects.length > 0) {
intersects[0].object.scale.y += 0.1;
fHeight = fHeight*intersects[0].object.scale.y;
yShift = fHeight/2 - intersects[0].object.position.y - 2.5;
intersects[0].object.position.y = intersects[0].object.position.y + yShift;
}
}
You can place the vertices of your cube so its base sticks to its y=0 plane.
Then object.scale.y will directly modify the cube height only, without displacing the base, and you will not have to reposition the object.
Hope it helps