Three.js: Raycast Has an Empty Array of Intersects - javascript

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 );
}
}

Related

Set 3d cube rotation origin

I have a simple 3d cube (BoxGeometry of 100, 100, 100) and I am trying to rotate it. If we call all 100x100x100 a tile - when I rotate it I can see it's overlapping the below tile.
(by changing color, now I totally understand the behaviour).
tl.to(this.cube4.rotation, 0.5, {z: -45* Math.PI/180});
[
What if I want to rotate it based on an anchor point of right bottom? So instead of overflowing inside the below tile, it will overflow that portion to above tile.
So it will look like the green example and not the red example:
The red example here is achieved by
tl.to(this.cube4.rotation, 0.5, {z: -45* Math.PI/180});
tl.to(this.cube4.position, 0.5, {x: 50 }, 0.5);
I am very new to three.js so if any terminology is wrong, please warn me
Add the ("red") cube to a THREE.Group, in that way that the rotation axis (the edge) is in the origin of the group. This means the cube has to be shifted by the half side length.
If you rotate the group object, then the cube (which is inside the group) will rotate around the edge and not around its center.
e.g.
var bbox = new THREE.Box3().setFromObject(cube);
cube.position.set(bbox.min.x, bbox.max.y, 0);
var pivot = new THREE.Group();
pivot.add(cube);
scene.add(pivot);
See also the answer to How to center a group of objects?, which uses this solution to rotate a group of objects.
(function onLoad() {
var camera, scene, renderer, orbitControls, pivot;
var rot = 0.02;
init();
animate();
function init() {
container = document.getElementById('container');
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 100);
camera.position.set(4, 1, 2);
//camera.lookAt( -1, 0, 0 );
loader = new THREE.TextureLoader();
loader.setCrossOrigin("");
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
scene.add(camera);
window.onresize = function() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
orbitControls = new THREE.OrbitControls(camera, container);
var ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
directionalLight.position.set(1,2,-1.5);
scene.add( directionalLight );
addGridHelper();
createModel();
}
function createModel() {
var material = new THREE.MeshPhongMaterial({color:'#80f080'});
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var cube1 = new THREE.Mesh(geometry, material);
cube1.position.set(0,-0.5,-0.5);
var cube2 = new THREE.Mesh(geometry, material);
cube2.position.set(0,0.5,-0.5);
var cube3 = new THREE.Mesh(geometry, material);
cube3.position.set(0,-0.5,0.5);
var material2 = new THREE.MeshPhongMaterial({color:'#f08080'});
var cube4 = new THREE.Mesh(geometry, material2);
var bbox = new THREE.Box3().setFromObject(cube4);
cube4.position.set(bbox.min.x, bbox.max.y, 0);
pivot = new THREE.Group();
pivot.add(cube4);
pivot.position.set(-bbox.min.x, 0.5-bbox.max.y, 0.5);
scene.add(cube1);
scene.add(cube2);
scene.add(cube3);
scene.add(pivot);
}
function addGridHelper() {
var helper = new THREE.GridHelper(100, 100);
helper.material.opacity = 0.25;
helper.material.transparent = true;
scene.add(helper);
var axis = new THREE.AxesHelper(1000);
scene.add(axis);
}
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
pivot.rotation.z += rot;
if (pivot.rotation.z > 0.0 || pivot.rotation.z < -Math.PI/2) rot *= -1;
render();
}
function render() {
renderer.render(scene, camera);
}
})();
<!--script src="https://threejs.org/build/three.js"></!--script-->
<script src="https://rawcdn.githack.com/mrdoob/three.js/r124/build/three.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/r124/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.js"></script>
<div id="container"></div>
From the first image, it appears that the pivot of your red tile is at its center.
For the rotation you want, you would ideally change the pivot to the lower right of the cube. This is impossible without modifying the geometry of the cube.
BUT a simple trick is to create an empty node at that pivot point, parent your cube to that empty, and apply your rotation to the empty. (Don't forget to remove your translation, you don't need it anymore)
Here is some pseudo code, assuming your red box is centered at (0,0,0) and has a width and height of 100:
// create an empty node at desired rotation pivot
var empty = new Object3D or group
empty.position = (50, -50, 0)
// parent your cube to the empty
var cube = your box
empty.add(cube)
// you may need to change the local position of your cube to bring it back to its global position of (0,0,0)
cube.position = (-50, 50, 0)
rotate empty by 45°
I think you can get the bounds of the rotated object like this:
bounds = new THREE.Box3().setFromObject( theRedObject )
Then reposition the object.y based on its bounds.min.y
let scene, camera, controls, ambient, point, loader, renderer, container, stats;
const targetRotation = 0;
const targetRotationOnMouseDown = 0;
const mouseX = 0;
const mouseXOnMouseDown = 0;
const windowHalfX = window.innerWidth / 2;
const windowHalfY = window.innerHeight / 2;
init();
animate();
var box, b1, b2, b3;
function init() {
// Create a scene which will hold all our meshes to be rendered
scene = new THREE.Scene();
// Create and position a camera
camera = new THREE.PerspectiveCamera(
60, // Field of view
window.innerWidth / window.innerHeight, // Aspect ratio
/*window.innerWidth / -8,
window.innerWidth / 8,
window.innerHeight / 8,
window.innerHeight / -8,
*/
0.1, // Near clipping pane
1000 // Far clipping pane
);
scene.add(camera)
// Reposition the camera
camera.position.set(0, 5, 10);
// Point the camera at a given coordinate
camera.lookAt(new THREE.Vector3(0, 0, 0));
// Add orbit control
controls = new THREE.OrbitControls(camera);
controls.target.set(0, -0.5, 0);
controls.update();
// Add an ambient lights
ambient = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(ambient);
// Add a point light that will cast shadows
point = new THREE.PointLight(0xffffff, 1);
point.position.set(25, 50, 25);
point.castShadow = true;
point.shadow.mapSize.width = 1024;
point.shadow.mapSize.height = 1024;
scene.add(point);
group = new THREE.Group();
group.position.y = 0;
scene.add(group);
rotationAnchor = new THREE.Object3D()
group.add(rotationAnchor);
box = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshStandardMaterial({
color: 'grey'
}))
b1 = box.clone();
b2 = box.clone();
b3 = box.clone();
b3.material = b3.material.clone()
b3.material.color.set('red')
group.add(box);
group.add(b1);
b1.position.y += 1
group.add(b2);
b2.position.z += 1
rotationAnchor.add(b3);
rotationAnchor.position.set(0.5, 0.5, 1.5)
b3.position.set(-.5, -.5, -.5)
// Create a renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
// Set size
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// Set color
renderer.setClearColor(0xf8a5c2);
renderer.gammaOutput = true;
// Enable shadow mapping
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Append to the document
container = document.createElement("div");
document.body.appendChild(container);
document.body.appendChild(renderer.domElement);
// Add resize listener
window.addEventListener("resize", onWindowResize, false);
// Enable FPS stats
stats = new Stats();
container.appendChild(stats.dom);
var gui = new dat.GUI({
height: 5 * 32 - 1
});
let params = {
'test': 4,
'bevelThickness': 1,
'bevelSize': 1.5,
'bevelSegments': 3
}
gui.add(params, 'test', 0, 10).onChange(val => {
test = val
})
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
rotationAnchor.rotation.z = (Math.cos(performance.now() * 0.001) * Math.PI * 0.25) + (Math.PI * 1.25)
requestAnimationFrame(animate);
// Re-render scene
renderer.render(scene, camera);
// Update stats
stats.update();
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/libs/stats.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script>

3d position to screen coordinates not working properly three js [r100]

I have this really simple test scene to place a bunch of html labels on the position of a 3d object in my scene. But when ever i try converting the 3d coordinates to the screen location i get weird values that do not at all corrosponded with the position of my 3d object. See below for the code with which i generate a scene and place a label:
var CanvasContainer = document.getElementById("KettingContainer");
function CameraSetup(container){
var camera = new THREE.PerspectiveCamera( 50, container.offsetWidth / container.offsetHeight, 1, 300 );
camera.position.set( 0, 0, 100 );
camera.lookAt( 0, 0, 0 );
return camera;
}
function SceneSetup(){
var scene = new THREE.Scene();
return scene;
}
function RenderSetup(container) {
var renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.autoClear = false;
renderer.setClearColor( 0x000000, 0 );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( container.offsetWidth, container.offsetHeight );
container.appendChild( renderer.domElement );
return renderer;
}
function OnWindowResize(container){
camera.aspect = container.offsetWidth / container.offsetHeight;
camera.updateProjectionMatrix();
renderer.setSize( container.offsetWidth, container.offsetHeight );
}
var kraalLabels = document.getElementById('kraalLinkLabels').getElementsByTagName('div');
function Animate(){
requestAnimationFrame(Animate);
Render();
}
function Render(){
renderer.render( scene, camera );
}
function createScreenVector(pos, camera, width, height) {
const hw = width/2, hh = height/2;
var pos2d = pos.clone();
pos2d.project(camera);
console.log(pos2d);
pos2d.x = ( pos2d.x * hw ) + hw;
pos2d.y = - ( pos2d.y * hh ) + hh;
return pos2d;
}
function positionLabels(positions, camera, width, height){
for( var i = 0; i < kraalLabels.length; i++ )
{
const d = createScreenVector(positions, camera, width, height);
kraalLabels[i].style.left = d.x + "px";
kraalLabels[i].style.top = d.y + "px";
}
}
var camera = CameraSetup(CanvasContainer);
var scene = SceneSetup();
var renderer = RenderSetup(CanvasContainer);
console.log("fc: ", camera.position);
const geometry = new THREE.SphereGeometry( 10, 32, 32 );
const material = new THREE.MeshBasicMaterial( {color: 0xff0000} );
let sphere = new THREE.Mesh( geometry, material );
sphere.position.set(20, 20, 0.1);
scene.add( sphere );
positionLabels(new THREE.Vector3(20, 20, 0.1), camera, CanvasContainer.offsetWidth, CanvasContainer.offsetHeight);
window.addEventListener("resize", function(){ OnWindowResize(CanvasContainer); }, false);
Animate();
See below a screenshot of the camera position, the canvas container size, and the screen location for each of the labels:
As you can see the positions of the labels are huge way bigger then the canvas itself. And the positions are all in the negative...
Even doh my object is just a bit to the top and left of the center of the screen..
I am completely stuck at this time i have no clue what i am doing wrong :(
If extra information is needed i am happy to clarify!
After searching for a solution for houres upon hours i finally found the problem before you do the vector3.project(camera); you have to update the camera matrix like so: camera.updateMatrixWorld(); this will fix the problem with weird values.
Hopes this helps someone in the future!

How to change the circle radius when I click on them (with three.js)?

I have the following JavaScript code, taken from an example, which draws some circles, and when you click on one of them, it changes color. But I also want to change the radius/size of a circle when you click on that circle (and leave the other circles unchanged). The documentation does not help at all, and I have tried several variations in the code like
intersects[ 0 ].object.geometry.parameters.radius = 50;
intersects[ 0 ].object.geometry.radius = 50;
intersects[ 0 ].object.geometry.setRadius(50);
Anyway, here is the complete code:
var container, camera, scene, geometry, mesh, renderer, controls, particles, colors;
var objects = [];
// DOM element...
container = document.createElement('div');
document.body.appendChild(container);
// Camera...
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 0, 75);
// Scene...
scene = new THREE.Scene();
scene.add(camera);
// Renderer...
renderer = new THREE.WebGLRenderer({
clearAlpha: 1
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xffffff, 1);
document.body.appendChild(renderer.domElement);
// Scatter plot...
scatterPlot = new THREE.Object3D();
scene.add(scatterPlot);
// Make grid...
xzColor = 'black';
xyColor = 'black';
yzColor = 'black';
// Plot some random points...
circle = new THREE.CircleGeometry(5);
colors = [];
var max = 50;
var min = -50;
for (var i = 0; i < 10; i++) {
var object = new THREE.Mesh( circle, new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 } ) );
object.position.x = Math.random() * (max - min) + min;
object.position.y = Math.random() * (max - min) + min;
object.position.z = 0;
scene.add( object );
objects.push( object );
}
animate();
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
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.material.color.setHex( Math.random() * 0xffffff );
intersects[ 0 ].object.geometry.setRadius(50);
var particle = new THREE.Sprite( particleMaterial );
particle.position.copy( intersects[ 0 ].point );
particle.scale.x = particle.scale.y = 16;
scene.add( particle );
}
}
Any idea how I can solve this? And where can I find the proper documentation?
Addendum:
I have used the following line of code:
intersects[ 0 ].object.geometry.scale(1.1,1.1,1.1);
In the code, and now the circles change their size. But ALL of them! I click on one circle, and every circle changes size. Does not make sense to me...
Don't scale your geometry. You're using the same geometry reference for all of your circles, so scaling one scales them all.
Instead, scale your Mesh, which is a unique object in the scene (even if it references the same geometry as other meshes). Much like how you're setting position for each Mesh, you also have access to scale:
intersects[0].object.scale.set(1.1, 1.1, 1.1);
That will scale the intersected Mesh object, and only that Mesh.
Cloning and creating a new Mesh is literally introducing a memory leak. The original Mesh won't go away until you remove it from the scene, and you keep making more and more Geometry objects as you clone the circle.

Getting the coordinates on the scene (three js)

I'm really struggling with this and have gone through several discussions that are not really related to my problem. If anybody can help, I would greatly appreciate.
I'm writing a html document using the three.js library. There is a scene called scaledScene as below (scaledMap and scaledScene are already defined):
scaledMap = new THREE.Mesh(
new THREE.PlaneGeometry( 1600, 1200 ),
new THREE.MeshBasicMaterial( {
map: new THREE.ImageUtils.loadTexture( 'texture/test1.png' ),
//wireframe: true,
side: THREE.DoubleSide
} )
);
scaledScene.add( scaledMap );
The scene is created by a high solution picture that's zoomed in to the highest level. Zooming is not allowed but only panning.
controls.noZoom = true;
controls.noPan = false;
I would like to get the absolute coordinates on the scene when panning. For example, if I pan to the top left corner, I'd like to get the information that the cursor or the view is located at top left corner. Is this possible? If so, how do I do it?
You want a picking ray.
A ray can be sent from the center of the viewport to the map in 3d space. this will tell you the pixel of the map that's currently at the center of the viewport.
I have created a fiddle.
The code is as follows. It disables rotation of the camera so that only panning is available. The pixel of the image that is at the center of the view port is listed below the viewport. Remember that threejs uses cartesian space. So an image that is 150px / 150px will report pixels from -75 to 75. So if both of your x is negative and your y is positive, then you know you're viewing the top left portion of the image.
The fiddle: http://jsfiddle.net/v1g64zkb/
var width = 400;
var height = 300;
var aspect = width/height;
var near = 1;
var far = 1000;
var angle = 45;
var createRenderer = function(){
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width,height);
return renderer;
}
var camera;
var createCamera = function(){
camera = new THREE.PerspectiveCamera(
angle, aspect, near, far);
camera.position.set( 0, 0, 100 );
camera.lookAt(new THREE.Vector3( 0, 0, 0 ));
return camera;
}
var createScene = function(){
var scene = new THREE.Scene();
return scene;
}
var createControls = function(camera){
var controls = new
THREE.OrbitControls(camera);
controls.rotateSpeed = 0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = true;
controls.noPan = false;
return controls;
}
var createLight = function(){
var light = new THREE.PointLight(0xFFFFFF);
light.position.x=0;
light.position.y=0;
light.position.z=100;
return light;
}
var scene = createScene();
var camera = createCamera();
var controls = createControls(camera);
var light = createLight();
var renderer = createRenderer();
scene.add(light);
scaledMap = new THREE.Mesh(
new THREE.PlaneGeometry( 150, 150 ),
new THREE.MeshBasicMaterial( {
map: new THREE.ImageUtils.loadTexture( '../img/logo.png' ),
color:0xFFFFFF,
side: THREE.DoubleSide
} )
);
scene.add( scaledMap );
var raycaster = new THREE.Raycaster();
var cameraPosition = new THREE.Vector2();
cameraPosition.x = 0;
cameraPosition.y = 0;
console.log(cameraPosition)
var render = function(){
renderer.render(scene,camera);
raycaster.setFromCamera( cameraPosition, camera );
var intersects = raycaster.intersectObjects( scene.children );
if(intersects[0] != undefined){
$("#output").html(intersects[0].point.x.toFixed(2)+" "+intersects[0].point.y.toFixed(2));
}
renderer.render( scene, camera );
}
controls.addEventListener('change',render);
var animate = function(){
requestAnimationFrame(animate);
render();
controls.update();
}
animate();
$("#container").append(renderer.domElement);
//edit: I would not run a picking ray inside a render call. This is just a simplified example. Use the controls change event or whatever to fire the picking ray.

Orthographic camera and selecting objects with raycast

I am running into a bit of difficulty selecting objects with the orthographic camera using the raycaster. Though, I have no problem with it when I use a perspective camera. The only thing I am changing when switching between the two is the type camera.
I am able to select faces on the orthographic view, but it is only loosely related to where I am clicking on the screen. When I can click far away from the object and it will still come back as if it has hit the object near its center.
Any ideas on what I am missing here?
I am basing much of my code on this example, and am hoping to achieve a very similar result from my code. (this example I'm referencing uses the perspective camera)
Any help is much appreciated
<html>
<head>
<style>
canvas {
left: 0;
top: 0;
width: 100%;
height: 100%;
position: fixed;
background-color: #111115;
}
</style>
</head>
<body id='c'>
<script src="js/three.js"></script>
<script>
var obj = [];
var mouse ={};
var zoom = 2;
var scene = new THREE.Scene();
//switch between these two and see the difference:
//var camera = new THREE.OrthographicCamera(window.innerWidth / -zoom, window.innerWidth / zoom, window.innerHeight / zoom, window.innerHeight / -zoom, -1000, 1000);
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position = new THREE.Vector3(100,100,100);
camera.lookAt(new THREE.Vector3(0,0,0));
// this material causes a mesh to use colors assigned to faces
var material = new THREE.MeshBasicMaterial(
{ color: 0xffffff, vertexColors: THREE.FaceColors } );
var sphereGeometry = new THREE.SphereGeometry( 80, 32, 16 );
for ( var i = 0; i < sphereGeometry.faces.length; i++ )
{
face = sphereGeometry.faces[ i ];
face.color.setRGB( 0, 0, 0.8 * Math.random() + 0.2 );
}
obj['box'] = {};
obj['box'] = new THREE.Mesh( sphereGeometry, material );
obj['box'].castShadow = true;
obj['box'].receiveShadow = true;
scene.add(obj['box']);
var ambientLight = new THREE.AmbientLight(0xbbbbbb);
scene.add(ambientLight);
var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(-100, 40, 100);
directionalLight.castShadow = true;
directionalLight.shadowOnly = true;
directionalLight.shadowDarkness = .5;
scene.add(directionalLight);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
document.body.appendChild(renderer.domElement);
projector = new THREE.Projector();
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
function onDocumentMouseDown( event ) {
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
// event.preventDefault();
console.log("Click.");
// update the mouse variable
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// 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 = ray.intersectObjects( [obj['box']] );
// if there is one (or more) intersections
if ( intersects.length > 0 )
{
console.log("Hit # " + toString( intersects[0].point ) );
console.log(intersects);
// change the color of the closest face.
intersects[ 0 ].face.color.setRGB( 0.8 * Math.random() + 0.2, 0, 0 );
intersects[ 0 ].object.geometry.colorsNeedUpdate = true;
}
}
function toString(v) { return "[ " + v.x + ", " + v.y + ", " + v.z + " ]"; }
var render = function() {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
console.log(camera);
console.log(obj['box'])
render();
</script>
</body>
I am hoping it is something simple that I just don't know about yet.
three.js r60
Here is the pattern to use when raycasting with either an orthographic camera or perspective camera:
var raycaster = new THREE.Raycaster(); // create once
var mouse = new THREE.Vector2(); // create once
...
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, recursiveFlag );
three.js r.84
One more note that might save you some trouble. If you have a camera like this:
var camera = new THREE.OrthographicCamera(0, window.innerWidth, -window.innerHeight, 0, -100, 100);
Then during raycasting, be sure to move the ray origin.z to camera.far for it to hit anything in the entire visible range:
this.ray.origin.set(0, 0, 0);
this.camera.localToWorld(this.ray.origin);
this.ray.setFromCamera(this.mouseCoord, this.camera);
this.ray.origin.z = this.camera.far;

Categories