Hello, I have one doubt:
I have implemented a raycaster and I have been testing it manually, however I do not know why on most of the clicks which I made on the 3D model, it did not get the intersection point.
First I will show you the points I clicked on, then the points which were logged in the web console then the code I have implemented and finally the web structure:
I have clicked on those eight points:
And the results are:
[]length: 0__proto__: Array(0)
[]length: 0__proto__: Array(0)
[]length: 0__proto__: Array(0)
[]length: 0__proto__: Array(0)
point: Vector3 x:--99.34871894866089 y:67 z:0
point: Vector3 x: -126.50880038786315 y: 73.48094335146214 z: -5.684341886080802
[]length: 0__proto__: Array(0)
[]length: 0__proto__: Array(0)
Here we have the implemented code, the important part is the onDocumentMouseDown function:
if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
// global variables for this scripts
let OriginalImg,
SegmentImg;
var mouse = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var mousePressed = false;
init();
animate();
// initilize the page
function init ()
{
let filename = "models/nrrd/columna01.nrrd"; // change your nrrd file
let idDiv = 'original';
OriginalImg = new InitCanvas(idDiv, filename );
OriginalImg.init();
console.log(OriginalImg);
filename = "models/nrrd/columnasegmentado01.nrrd"; // change your nrrd file
idDiv = 'segment';
SegmentImg = new InitCanvas(idDiv, filename );
SegmentImg.init();
}
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
function onDocumentMouseDown( event ) {
mousePressed = true;
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera( mouse.clone(), OriginalImg.camera );
var objects = raycaster.intersectObjects(OriginalImg.scene.children);
console.log(objects);
}
function onDocumentMouseUp( event ) { mousePressed = false}
function animate() {
requestAnimationFrame( animate );
OriginalImg.animate();
SegmentImg.animate();
}
Here we see the web structure:
I suspect that because of the canvas have an offset into the window, the points are not being effectively gotten from the areas we clicked on.
I have also read:
Debug threejs raycaster mouse coordinates
How do I get the coordinates of a mouse click on a canvas element?
https://threejs.org/docs/#api/core/Raycaster
threejs raycasting does not work
Any help would be appreciated both link to further reading, theoretical suggestions and code examples.
EDIT:
1) I have opened a new thread with the same topic and more detailed images, graphs, and logs. Here is the link: ThreeJS, raycaster gets strange coordinates when we log the intersection object
2) I followed the answer provided by #TheJim01 here is the current code:
logic.js
if (!Detector.webgl) Detector.addGetWebGLMessage();
// global variables for this scripts
let OriginalImg,
SegmentImg;
var mouse = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var mousePressed = false;
var clickCount = 0;
init();
animate();
// initilize the page
function init() {
let filename = "models/nrrd/columna01.nrrd"; // change your nrrd file
let idDiv = 'original';
OriginalImg = new InitCanvas(idDiv, filename);
OriginalImg.init();
console.log(OriginalImg);
filename = "models/nrrd/columnasegmentado01.nrrd"; // change your nrrd file
idDiv = 'segment';
SegmentImg = new InitCanvas(idDiv, filename);
SegmentImg.init();
}
let originalCanvas = document.getElementById('original');
originalCanvas.addEventListener('mousedown', onDocumentMouseDown, false);
originalCanvas.addEventListener('mouseup', onDocumentMouseUp, false);
function onDocumentMouseDown(event) {
mousePressed = true;
clickCount++;
mouse.x = ( event.offsetX / window.innerWidth ) * 2 - 1;
console.log('Mouse x position is: ', mouse.x, 'the click number was: ', clickCount);
mouse.y = -( event.offsetY / window.innerHeight ) * 2 + 1;
console.log('Mouse Y position is: ', mouse.y);
raycaster.setFromCamera(mouse.clone(), OriginalImg.camera);
var objects = raycaster.intersectObjects(OriginalImg.scene.children);
console.log(objects);
}
function onDocumentMouseUp(event) {
mousePressed = false
}
function animate() {
requestAnimationFrame(animate);
OriginalImg.animate();
SegmentImg.animate();
}
InitCanvas.js
// this class handles the load and the canva for a nrrd
// Using programming based on prototype: https://javascript.info/class
// This class should be improved:
// - Canvas Width and height
InitCanvas = function ( IdDiv, Filename ) {
this.IdDiv = IdDiv;
this.Filename = Filename
}
InitCanvas.prototype = {
constructor: InitCanvas,
init: function() {
this.container = document.getElementById( this.IdDiv );
// this should be changed.
debugger;
this.container.innerHeight = 600;
this.container.innerWidth = 800;
//These statenments should be changed to improve the image position
this.camera = new THREE.PerspectiveCamera( 60, this.container.innerWidth / this.container.innerHeight, 0.01, 1e10 );
this.camera.position.z = 300;
let scene = new THREE.Scene();
scene.add( this.camera );
// light
let dirLight = new THREE.DirectionalLight( 0xffffff );
dirLight.position.set( 200, 200, 1000 ).normalize();
this.camera.add( dirLight );
this.camera.add( dirLight.target );
// read file
let loader = new THREE.NRRDLoader();
loader.load( this.Filename , function ( volume ) {
//z plane
let sliceZ = volume.extractSlice('z',Math.floor(volume.RASDimensions[2]/4));
debugger;
this.container.innerWidth = sliceZ.iLength;
this.container.innerHeight = sliceZ.jLength;
scene.add( sliceZ.mesh );
}.bind(this) );
this.scene = scene;
// renderer
this.renderer = new THREE.WebGLRenderer( { alpha: true } );
this.renderer.setPixelRatio( this.container.devicePixelRatio );
debugger;
this.renderer.setSize( this.container.innerWidth, this.container.innerHeight );
// add canvas in container
this.container.appendChild( this.renderer.domElement );
},
animate: function () {
this.renderer.render( this.scene, this.camera );
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Prototype: three.js without react.js</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="css/styles.css">
<!-- load the libraries and js -->
<script src="js/libs/three.js"></script>
<script src="js/Volume.js"></script>
<script src="js/VolumeSlice.js"></script>
<script src="js/loaders/NRRDLoader.js"></script>
<script src="js/Detector.js"></script>
<script src="js/libs/stats.min.js"></script>
<script src="js/libs/gunzip.min.js"></script>
<script src="js/libs/dat.gui.min.js"></script>
<script src="js/InitCanvas.js"></script>
</head>
<body>
<div id="info">
<h1>Prototype: three.js without react.js</h1>
</div>
<!-- two canvas -->
<div class="row">
<div class="column" id="original">
</div>
<div class="column" id="segment">
</div>
</div>
<script src="js/logic.js"></script>
</body>
</html>
I have sseen that it indeed works differently. Until now I know that the origin of canvas coordinates, would be the red one in fullscreen mode, and the green one when we open the Chrome dev tools on the right:
In addition the zone where raycaster interepts ThreeJS model, is the red area in fullscreen and the green one when we open the Mozilla dev tools below:
3) Currently the canvas is created from a parent div named column which is:
And the canvas being created in it is 800x600
How could we achieve that the raycaster's intercept zone become canvas' model's size?
5) To be able to solve the difficulty for myself I have studied this good SO post:
THREE.js Ray Intersect fails by adding div
However I see that in the post I linked, #WestLangley uses clientX, Y and here in the answers section #TheJim01 advices to use offsetX, Y.
Also as I am beginner with ThreeJS, and I have been learning JS some time I have some difficulties:
How is the origin of coordinates handled in the browser?
What is the origin of coordinates in three.js?
How are both related?
Why most of the articles use this expression?:
mouse.x = ( event.offsetX / window.innerWidth ) * 2 - 1;
Why do we need to divide by window.innerWidth? By we do * 2 - 1?
6) I ask all of that because I would like to do a web application where we could gather the point where the user clicked on the left canvas, and then we change the color of the same part on the right canvas, and we display some information about it as the name and description.
So then gathering the mouse click position with ThreeJS is important to be able to use that to change the color on the right canvas, and on the clicked part.
7) In addition I have also read:
Update Three.js Raycaster After CSS Tranformation
EDIT2: 22/03/18
I have followed the answer provided by #WestLangley here: THREE.js Ray Intersect fails by adding div
And it allows us to have the raycaster's intersection zone on the canvas' image.
So it solves the question in practice.
However I still not understanding something, for example the relation between browser's and Threejs' coordinates.
Here we see that in the browser and ThreeJS' raycaster's intercepted object, x coordinate is the same, however Y coordinate is different, why?
Also I suspect that browser's origin of coordinates on canvas is on the center:
Is this correct?
I will show the pieces of code I needed to add to make the raycaster's detection area be the same as canvas' image.
First I added in the CSS:
canvas {
width: 200px;
height: 200px;
margin: 100px;
padding: 0px;
position: static; /* fixed or static */
top: 100px;
left: 100px;
}
Then I have added in the logic.js
function onDocumentMouseDown(event) {
mousePressed = true;
clickCount++;
mouse.x = ( ( event.clientX - OriginalImg.renderer.domElement.offsetLeft ) / OriginalImg.renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( ( event.clientY - OriginalImg.renderer.domElement.offsetTop ) / OriginalImg.renderer.domElement.clientHeight ) * 2 + 1
console.log('Mouse x position is: ', mouse.x, 'the click number was: ', clickCount);
console.log('Mouse Y position is: ', mouse.y);
raycaster.setFromCamera(mouse.clone(), OriginalImg.camera);
var objects = raycaster.intersectObjects(OriginalImg.scene.children);
console.log(objects);
}
As you can see above, I have added on mouse x and y the offset Left and Top, divided by the renderer Width / Height.
In addition I have also studied how is the mouse click done in OpenAnatomy:
function onSceneMouseMove(event) {
//check if we are not doing a drag (trackball controls)
if (event.buttons === 0) {
//compute offset due to container position
mouse.x = ( (event.clientX-containerOffset.left) / container.clientWidth ) * 2 - 1;
mouse.y = - ( (event.clientY-containerOffset.top) / container.clientHeight ) * 2 + 1;
needPickupUpdate = true;
}
else {
needPickupUpdate = false;
}
}
Link: https://github.com/mhalle/oabrowser/blob/gh-pages/src/app.js
So we see they use the offset, left and top too, and divided by the width and height, but this time the ones from the container not the renderer.
Also I have studied how they do it in AMI:
function onDoubleClick(event) {
const canvas = event.target.parentElement;
const id = event.target.id;
const mouse = {
x: ((event.clientX - canvas.offsetLeft) / canvas.clientWidth) * 2 - 1,
y: - ((event.clientY - canvas.offsetTop) / canvas.clientHeight) * 2 + 1,
};
Link: https://github.com/FNNDSC/ami/blob/dev/examples/viewers_quadview/viewers_quadview.js
So here we see that instead of the container or even the renderer they use the offset of the canvas itself.
In addition I have studied some official ThreeJS examples, and they look like there is only a fullscreen renderer/scene so then they do not show how to handle various canvas and raycasters in a same web page.
function onDocumentMouseMove( event ) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
Link: https://github.com/mrdoob/three.js/blob/master/examples/webgl_interactive_cubes.html
function onMouseMove( event ) {
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
// See if the ray from the camera into the world hits one of our meshes
var intersects = raycaster.intersectObject( mesh );
// Toggle rotation bool for meshes that we clicked
if ( intersects.length > 0 ) {
helper.position.set( 0, 0, 0 );
helper.lookAt( intersects[ 0 ].face.normal );
helper.position.copy( intersects[ 0 ].point );
}
}
Link: https://github.com/mrdoob/three.js/blob/master/examples/webgl_geometry_terrain_raycast.html
Could you help me please?
Thank you.
Raycarster.setFromCamera(NDC, camera) takes as first parameter the normalized device coordinates. That is a value between -1 and 1. But you are giving the actual coordinates. That is why it doesn't intersect. Try this:
const screenPosition = {
x: event.clientX - canvas.offsetLeft,
y: event.clientY - canvas.offsetHeight
};
const widthHalf = canvas.clientWidth * 0.5;
const heightHalf = canvas.clientHeight * 0.5;
const mouse = {
x: (screenPosition.x - widthHalf) / widthHalf ,
y: - (screenPosition.y - heightHalf) / heightHalf,
};
Also try to set recursive to true in intersectObject().
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’ve asked in another forum but I thought I would like to be more clear on my problem.
Whats my intention?
Currently I am using three.js within WebView on my android device and created a scene which contains a simple box (which should be used as a bounding box) and a camera. My camera needs to be an interactive one with my android device, which means that I set the position by moving the device dynamically. These vectors are coming from a SLAM-algorithmn named Direct Sparse Odometry which recreates the camera position, I can also call these values with javascript by using the provided WebViewInterface from Android. My goal is to “walk around” the box dynamically without using the camera.lookAt()-Method every time I change the values, because if I move away from the box, the view should not be centered anymore (like an AR-Application), so the point of view should be created dynamically such as the position and rotation of the camera towards the object. My goal is to place an object over a real world object with three.js to scan it later with DSO by walking around the box to detect feature points. The whole visualisation should be created with three.js.
What is DSO?
DSO is a library to track the real environment by detecting points from a camera frames, which are provided by Android’s camera 2 API. This send me a 4x4 transformation Matrix with the current pose, which I try to apply on three.js’s camera position. Due to the complexity of this algorithm, lets pretend this gives me proper values (in meters, but I also tried to multiplicate the values by 10 or 100 to receive larger results than 0.XX).
Whats my Problem?
The box does not seem to have an absolute position, even if the values seems to be fixed. Every time when placing the Box, it seem to move in an opposite direction. After many adjustments on the dso values, I am crystal clear that this the problem is happening with three.js.
I’ve also tried to apply matrixes of the scene/camera and/or using the box as a child (because of the object-heredity), but the box seems not to have an absolute position inside the scene. Also I am not able to rotate the object that seem to be realistic.
Enclosed, you’ll find my code but p
lease note that I am using dynamically dummy values as a replacement for the dso values.
<body>
<canvas id="mCanvas">
</canvas>
</body>
<script>
// Var Init
var renderer, scene, camera, box, transformControl, orbitControl, geometry, material, poseMatrix;
var mPoints = [];
//Box coordinate
var xBCordinate, yBCordinate, zBCordinate, isScaled, posVec, startPosVec, lookPos, helper;
var process = false;
var scanActive = false;
var pointArr = [];
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer({canvas: document.getElementById("mCanvas"),
alpha: true});
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer.setClearColor(0xffffff, 0);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.up.set(0, 0, 1); // Definition of coordinationsystem
// set initial scale position of camera
camera.position.x = 0;
camera.position.y = -0.5;
camera.position.z = 0.15;
scene.add(camera);
// set position to look at
camera.lookAt(0,2.5,-0.2);
// apply values
camera.updateMatrix();
// light
var light = new THREE.HemisphereLight( 0xeeeeee, 0x888888, 1 );
light.position.set( 0, -0.75, 2.5 );
scene.add(light);
placeBox();
}
function placeBox()
{
geometry = new THREE.BoxGeometry(0.5, 1, 0.5); //3,5,3
material = new THREE.MeshLambertMaterial({color: 0xfece46});
box = new THREE.Mesh(geometry, material);
box.position.set(0, 2.5, -0.2);
box.updateMatrix();
scene.add(box);
}
function animate() {
requestAnimationFrame(animate);
if(process == false){
setCurrentPose();
}
renderer.render(scene, camera);
}
function setCurrentPose(){
process = true;
// this is where I receive the position data via Android
// but lets try using random numbers between 0.01 - 0.99 (which are the results interval of dso)
moveRotateCamera();
}
function moveRotateCamera(){
// Create Vector to work with
posVec = new THREE.Vector3();
posVec.x = getRandomFloat(0.01, 0.99);
posVec.y = pgetRandomFloat(0.01, 0.99);
posVec.z = getRandomFloat(0.01, 0.99);
camera.position.x = posVec.x;
camera.position.y = (posVec.y) - 0.50; // minus initial scale position
camera.position.z = (posVec.z) + 0.15;
// camera.updateMatrix(); <- seem to change nothing such as UpdateWorldMatrix() etc.
// camera rotation tried to calculate with quaternions (result NaN) and/or euler by using former and current point.
process = false;
}
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
// My attempts in trying to calculate the rotation
/*
function setQuaternionRotation(poseMatrix){
// TODO: delete if not needed!
// adapted from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm, 2.12.2019, 2.34pm
mQuaternion = new THREE.Quaternion();
// Calculate Angle w
mQuaternion.w = ((Math.sqrt(Math.max(0, (1.0 + poseMatrix.elements[0] + poseMatrix.elements[5] + poseMatrix.elements[10])))/2.0));
//Sign x,y,z values of quaternion
mQuaternion.x = ((Math.sqrt(Math.max(0, (1.0 + poseMatrix.elements[0] - poseMatrix.elements[5] - poseMatrix.elements[10])))/2.0));
mQuaternion.y = ((Math.sqrt(Math.max(0, (1.0 - poseMatrix.elements[0] + poseMatrix.elements[5] - poseMatrix.elements[10])))/2.0));
mQuaternion.y = ((Math.sqrt(Math.max(0, (1.0 - poseMatrix.elements[0] - poseMatrix.elements[5] + poseMatrix.elements[10])))/2.0));
//Sign element values
mQuaternion.x = (Math.sign(mQuaternion.x * (poseMatrix.elements[6] - poseMatrix.elements[9])));
mQuaternion.y = (Math.sign(mQuaternion.y * (poseMatrix.elements[8] - poseMatrix.elements[2])));
mQuaternion.z = (Math.sign(mQuaternion.z * (poseMatrix.elements[1] - poseMatrix.elements[4])));
// debug
console.log("QuaternionVal: "+mQuaternion.x+ ", " +mQuaternion.y+", "+mQuaternion.z+", "+mQuaternion.w);
camera.applyQuaternion(mQuaternion);
camera.quaternion.normalize();
// debug
console.log("newCamRotation: "+camera.rotation.x +", "+camera.rotation.y+", "+ camera.rotation.z);
// camera.updateMatrix(true);
}
*/
</script>
Link to my Fiddle
Do you have any suggestions?
Thank you very much in advance!
Best regards,
FWIW. I think part of the issue is that the box is not centered about the camera rotation. I tweaked your fiddle by centering the box at the origin, in addition to using spherical coordinates to move the camera about. This keeps the camera at a uniform distance from the box, and with the box being centered about the rotation, it does not appear to be moving about the viewport...
<body>
<canvas id="mCanvas">
</canvas>
</body>
<script src="https://threejs.org/build/three.js"></script>
<script>
// Var Init
var renderer, scene, camera, box, transformControl, orbitControl, geometry, material, poseMatrix;
var mPoints = [];
//Box coordinate
var xBCordinate, yBCordinate, zBCordinate, isScaled, posVec, startPosVec, lookPos, helper;
var process = false;
var scanActive = false;
var pointArr = [];
var cameraSpherical;
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer({canvas: document.getElementById("mCanvas"),
alpha: true});
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer.setClearColor(0xffffff, 0);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.up.set(0, 0, 1); // Definition of coordinationsystem
// set initial scale position of camera
camera.position.x = 0;
camera.position.y = -0.5;
camera.position.z = 0.15;
scene.add(camera);
cameraSpherical = new THREE.Spherical( camera.position );
// set position to look at
camera.lookAt(0,2.5,-0.2);
// apply values
camera.updateMatrix();
// light
var light = new THREE.HemisphereLight( 0xeeeeee, 0x888888, 1 );
light.position.set( 0, -0.75, 2.5 );
scene.add(light);
placeBox();
}
function placeBox()
{
geometry = new THREE.BoxGeometry(0.5, 1, 0.5); //3,5,3
material = new THREE.MeshLambertMaterial({color: 0xfece46});
box = new THREE.Mesh(geometry, material);
box.position.set(0, 0, 0);
box.updateMatrix();
scene.add(box);
}
function animate() {
requestAnimationFrame(animate);
if(process == false){
setCurrentPose();
}
renderer.render(scene, camera);
}
function setCurrentPose(){
process = true;
// this is where I receive the position data via Android
// but lets try using random numbers between 0.01 - 0.99 (which are the results interval of dso)
moveRotateCamera();
}
function moveRotateCamera(){
// Create Vector to work with
/* posVec = new THREE.Vector3();
posVec.x = getRandomFloat(0.01, 0.05);
posVec.y = getRandomFloat(0.01, 0.05);
posVec.z = getRandomFloat(0.01, 0.02);
camera.position.x += posVec.x;
camera.position.y += posVec.y; // minus initial scale position
camera.position.z += posVec.z;
*/
cameraSpherical.radius = 5;
cameraSpherical.phi += getRandomFloat(0.001, 0.015);
cameraSpherical.theta += getRandomFloat(0.001, 0.015);
let xyz = new THREE.Vector3().setFromSpherical( cameraSpherical );
camera.position.x = xyz.x;
camera.position.y = xyz.y;
camera.position.z = xyz.z;
camera.lookAt(0,0,0);
camera.updateMatrix();
// camera.updateMatrix(); <- seem to change nothing such as UpdateWorldMatrix() etc.
// camera rotation tried to calculate with quaternions (result NaN) and/or euler by using former and current point.
process = false;
}
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
// My attempts in trying to calculate the rotation
/*
function setQuaternionRotation(poseMatrix){
// TODO: delete if not needed!
// adapted from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm, 2.12.2019, 2.34pm
mQuaternion = new THREE.Quaternion();
// Calculate Angle w
mQuaternion.w = ((Math.sqrt(Math.max(0, (1.0 + poseMatrix.elements[0] + poseMatrix.elements[5] + poseMatrix.elements[10])))/2.0));
//Sign x,y,z values of quaternion
mQuaternion.x = ((Math.sqrt(Math.max(0, (1.0 + poseMatrix.elements[0] - poseMatrix.elements[5] - poseMatrix.elements[10])))/2.0));
mQuaternion.y = ((Math.sqrt(Math.max(0, (1.0 - poseMatrix.elements[0] + poseMatrix.elements[5] - poseMatrix.elements[10])))/2.0));
mQuaternion.y = ((Math.sqrt(Math.max(0, (1.0 - poseMatrix.elements[0] - poseMatrix.elements[5] + poseMatrix.elements[10])))/2.0));
//Sign element values
mQuaternion.x = (Math.sign(mQuaternion.x * (poseMatrix.elements[6] - poseMatrix.elements[9])));
mQuaternion.y = (Math.sign(mQuaternion.y * (poseMatrix.elements[8] - poseMatrix.elements[2])));
mQuaternion.z = (Math.sign(mQuaternion.z * (poseMatrix.elements[1] - poseMatrix.elements[4])));
// debug
console.log("QuaternionVal: "+mQuaternion.x+ ", " +mQuaternion.y+", "+mQuaternion.z+", "+mQuaternion.w);
camera.applyQuaternion(mQuaternion);
camera.quaternion.normalize();
// debug
console.log("newCamRotation: "+camera.rotation.x +", "+camera.rotation.y+", "+ camera.rotation.z);
// camera.updateMatrix(true);
}
*/
</script>
Not sure whether this helps you in the direction you're going, but hope it sheds some light.
I'm working on a Three.js chart that basically represents a bunch of images in a 2D plane.
Right now the individual images are each 32px by 32px segments of larger 2048px by 2048px image atlas files. I want to increase the size of those individual images when users zoom in to particular regions of the scene. For example, if users start to zoom in on the images in the far right region of the space, I plan to update the 32px by 32px individual images in that region with 64px by 64px images with the same content (to show more detail).
My question is: what's the Three.js way to accomplish this goal?
My flat-footed plan is to load the higher-resolution assets, map them to the proper geometry coordinates, then simply delete the old mesh with 32px subimages and add the new mesh with 64px subimages. I originally thought I could just update the texture/material for an extant geometry, but I've read that one shouldn't use textures larger than 2048px by 2048px, and a geometry with n points won't allow me to continually increase the fidelity of the images in that geometry without surpassing that maximum texture size.
I would be very grateful for any insight Three.js veterans can offer on how they would approach this task!
Full code:
/**
* Globals
**/
// Identify data endpoint
var dataUrl = 'https://s3.amazonaws.com/duhaime/blog/tsne-webgl/data/';
// Create global stores for image and atlas sizes
var image, atlas;
// Create a store for image position information
var imagePositions = null;
// Create a store for the load progress. Data structure:
// {atlas0: percentLoaded, atlas1: percentLoaded}
var loadProgress = {};
// Create a store for the image atlas materials. Data structure:
// {subImageSize: {atlas0: material, atlas1: material}}
var materials = {32: {}, 64: {}};
// Create a store for meshes
var meshes = [];
/**
* Create Scene
**/
// Create the scene and a camera to view it
var scene = new THREE.Scene();
/**
* Camera
**/
// Specify the portion of the scene visiable at any time (in degrees)
var fieldOfView = 75;
// Specify the camera's aspect ratio
var aspectRatio = window.innerWidth / window.innerHeight;
/*
Specify the near and far clipping planes. Only objects
between those planes will be rendered in the scene
(these values help control the number of items rendered
at any given time)
*/
var nearPlane = 100;
var farPlane = 50000;
// Use the values specified above to create a camera
var camera = new THREE.PerspectiveCamera(
fieldOfView, aspectRatio, nearPlane, farPlane
);
// Finally, set the camera's position
camera.position.z = 12000;
camera.position.y = -2000;
/**
* Lights
**/
// Add a point light with #fff color, .7 intensity, and 0 distance
var light = new THREE.PointLight( 0xffffff, 1, 0 );
// Specify the light's position
light.position.set( 1, 1, 100 );
// Add the light to the scene
scene.add(light)
/**
* Renderer
**/
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({ antialias: true });
// Add support for retina displays
renderer.setPixelRatio( window.devicePixelRatio );
// Specify the size of the canvas
renderer.setSize( window.innerWidth, window.innerHeight );
// Add the canvas to the DOM
document.body.appendChild( renderer.domElement );
/**
* Load External Data
**/
// Load the image position JSON file
var fileLoader = new THREE.FileLoader();
var url = dataUrl + 'image_tsne_projections.json';
fileLoader.load(url, function(data) {
imagePositions = JSON.parse(data);
conditionallyBuildGeometries(32)
})
/**
* Load Atlas Textures
**/
// List of all textures to be loaded, the size of subimages
// in each, and the total count of atlas files for each size
var textureSets = {
32: { size: 32, count: 5 },
64: { size: 64, count: 20 }
}
// Create a texture loader so we can load our image files
var textureLoader = new AjaxTextureLoader();
function loadTextures(size, onProgress) {
setImageAndAtlasSize(size)
for (var i=0; i<textureSets[size].count; i++) {
var url = dataUrl + 'atlas_files/' + size + 'px/atlas-' + i + '.jpg';
if (onProgress) {
textureLoader.load(url,
handleTexture.bind(null, size, i),
onProgress.bind(null, size, i));
} else {
textureLoader.load(url, handleTexture.bind(null, size, i));
}
}
}
function handleProgress(size, idx, xhr) {
loadProgress[idx] = xhr.loaded / xhr.total;
var sum = 0;
Object.keys(loadProgress).forEach(function(k) { sum += loadProgress[k]; })
var progress = sum/textureSets[size].count;
var loader = document.querySelector('#loader');
progress < 1
? loader.innerHTML = parseInt(progress * 100) + '%'
: loader.style.display = 'none';
}
// Create a material from the new texture and call
// the geometry builder if all textures have loaded
function handleTexture(size, idx, texture) {
var material = new THREE.MeshBasicMaterial({ map: texture });
materials[size][idx] = material;
conditionallyBuildGeometries(size, idx)
}
// If the textures and the mapping from image idx to positional information
// are all loaded, create the geometries
function conditionallyBuildGeometries(size, idx) {
if (size === 32) {
var nLoaded = Object.keys(materials[size]).length;
var nRequired = textureSets[size].count;
if (nLoaded === nRequired && imagePositions) {
// Add the low-res textures and load the high-res textures
buildGeometry(size);
loadTextures(64)
}
} else {
// Add the new high-res texture to the scene
updateMesh(size, idx)
}
}
loadTextures(32, handleProgress)
/**
* Build Image Geometry
**/
// Iterate over the textures in the current texture set
// and for each, add a new mesh to the scene
function buildGeometry(size) {
for (var i=0; i<textureSets[size].count; i++) {
// Create one new geometry per set of 1024 images
var geometry = new THREE.Geometry();
geometry.faceVertexUvs[0] = [];
for (var j=0; j<atlas.cols*atlas.rows; j++) {
var coords = getCoords(i, j);
geometry = updateVertices(geometry, coords);
geometry = updateFaces(geometry);
geometry = updateFaceVertexUvs(geometry, j);
if ((j+1)%1024 === 0) {
var idx = (i*textureSets[size].count) + j;
buildMesh(geometry, materials[size][i], idx);
var geometry = new THREE.Geometry();
}
}
}
}
// Get the x, y, z coords for the subimage at index position j
// of atlas in index position i
function getCoords(i, j) {
var idx = (i * atlas.rows * atlas.cols) + j;
var coords = imagePositions[idx];
coords.x *= 2200;
coords.y *= 1200;
coords.z = (-200 + j/10);
return coords;
}
// Add one vertex for each corner of the image, using the
// following order: lower left, lower right, upper right, upper left
function updateVertices(geometry, coords) {
// Retrieve the x, y, z coords for this subimage
geometry.vertices.push(
new THREE.Vector3(
coords.x,
coords.y,
coords.z
),
new THREE.Vector3(
coords.x + image.shownWidth,
coords.y,
coords.z
),
new THREE.Vector3(
coords.x + image.shownWidth,
coords.y + image.shownHeight,
coords.z
),
new THREE.Vector3(
coords.x,
coords.y + image.shownHeight,
coords.z
)
);
return geometry;
}
// Create two new faces for a given subimage, then add those
// faces to the geometry
function updateFaces(geometry) {
// Add the first face (the lower-right triangle)
var faceOne = new THREE.Face3(
geometry.vertices.length-4,
geometry.vertices.length-3,
geometry.vertices.length-2
)
// Add the second face (the upper-left triangle)
var faceTwo = new THREE.Face3(
geometry.vertices.length-4,
geometry.vertices.length-2,
geometry.vertices.length-1
)
// Add those faces to the geometry
geometry.faces.push(faceOne, faceTwo);
return geometry;
}
function updateFaceVertexUvs(geometry, j) {
// Identify the relative width and height of the subimages
// within the image atlas
var relativeW = image.width / atlas.width;
var relativeH = image.height / atlas.height;
// Identify this subimage's offset in the x dimension
// An xOffset of 0 means the subimage starts flush with
// the left-hand edge of the atlas
var xOffset = (j % atlas.cols) * relativeW;
// Identify this subimage's offset in the y dimension
// A yOffset of 0 means the subimage starts flush with
// the bottom edge of the atlas
var yOffset = 1 - (Math.floor(j/atlas.cols) * relativeH) - relativeH;
// Determine the faceVertexUvs index position
var faceIdx = 2 * (j%1024);
// Use the xOffset and yOffset (and the knowledge that
// each row and column contains only 32 images) to specify
// the regions of the current image. Use .set() if the given
// faceVertex is already defined, due to a bug in updateVertexUvs:
// https://github.com/mrdoob/three.js/issues/7179
if (geometry.faceVertexUvs[0][faceIdx]) {
geometry.faceVertexUvs[0][faceIdx][0].set(xOffset, yOffset)
geometry.faceVertexUvs[0][faceIdx][1].set(xOffset + relativeW, yOffset)
geometry.faceVertexUvs[0][faceIdx][2].set(xOffset + relativeW, yOffset + relativeH)
} else {
geometry.faceVertexUvs[0][faceIdx] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset + relativeH)
]
}
// Map the region of the image described by the lower-left,
// upper-right, and upper-left vertices to `faceTwo`
if (geometry.faceVertexUvs[0][faceIdx+1]) {
geometry.faceVertexUvs[0][faceIdx+1][0].set(xOffset, yOffset)
geometry.faceVertexUvs[0][faceIdx+1][1].set(xOffset + relativeW, yOffset + relativeH)
geometry.faceVertexUvs[0][faceIdx+1][2].set(xOffset, yOffset + relativeH)
} else {
geometry.faceVertexUvs[0][faceIdx+1] = [
new THREE.Vector2(xOffset, yOffset),
new THREE.Vector2(xOffset + relativeW, yOffset + relativeH),
new THREE.Vector2(xOffset, yOffset + relativeH)
]
}
return geometry;
}
function buildMesh(geometry, material, idx) {
// Convert the geometry to a BuferGeometry for additional performance
//var geometry = new THREE.BufferGeometry().fromGeometry(geometry);
// Combine the image geometry and material into a mesh
var mesh = new THREE.Mesh(geometry, material);
// Store this image's index position in the mesh
mesh.userData.idx = idx;
// Set the position of the image mesh in the x,y,z dimensions
mesh.position.set(0,0,0)
// Add the image to the scene
scene.add(mesh);
// Save this mesh
meshes.push(mesh);
return mesh;
}
/**
* Update Geometries with new VertexUvs and materials
**/
function updateMesh(size, idx) {
// Update the appropriate material
meshes[idx].material = materials[size][idx];
meshes[idx].material.needsUpdate = true;
// Update the facevertexuvs
for (var j=0; j<atlas.cols*atlas.rows; j++) {
meshes[idx].geometry = updateFaceVertexUvs(meshes[idx].geometry, j);
}
meshes[idx].geometry.uvsNeedUpdate = true;
meshes[idx].geometry.verticesNeedUpdate = true;
}
/**
* Helpers
**/
function setImageAndAtlasSize(size) {
// Identify the subimage size in px (width/height) and the
// size of the image as it will be displayed in the map
image = { width: size, height: size, shownWidth: 64, shownHeight: 64 };
// Identify the total number of cols & rows in the image atlas
atlas = { width: 2048, height: 2048, cols: 2048/size, rows: 2048/size };
}
/**
* Add Controls
**/
var controls = new THREE.TrackballControls(camera, renderer.domElement);
/**
* Add Raycaster
**/
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
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;
}
function onClick( event ) {
// Determine which image is selected (if any)
var selected = raycaster.intersectObjects( scene.children );
// Intersecting elements are ordered by their distance (increasing)
if (!selected) return;
if (selected.length) {
selected = selected[0];
console.log('clicked', selected.object.userData.idx)
}
}
window.addEventListener('mousemove', onMouseMove)
window.addEventListener('click', onClick)
/**
* Handle window resizes
**/
window.addEventListener('resize', function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
controls.handleResize();
});
/**
* Render!
**/
// The main animation function that re-renders the scene each animation frame
function animate() {
requestAnimationFrame( animate );
raycaster.setFromCamera( mouse, camera );
renderer.render( scene, camera );
controls.update();
}
animate();
* {
margin: 0;
padding: 0;
background: #000;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>
<script src="https://s3.amazonaws.com/duhaime/blog/tsne-webgl/assets/js/texture-loader.js"></script>
<script src="https://s3.amazonaws.com/duhaime/blog/tsne-webgl/assets/js/trackball-controls.js"></script>
<div id='loader'>0%</div>
You can potentially use mutli-materials and geometry groups (or in your case, material indices).
This depends on your texture dimensions scaling 1::1. In other words, if your first resolution as dimensions 32x64, then double that resolution should have dimensions of 64x128. UVs are percentage-based, so moving from an image at one resolution to the same image at another resolution "just works".
At this point, you really only need to change the texture image source. But it sounds like you don't want to do that. So instead, we need to assign ALL of your textures to the same Mesh at once. Three.js makes this really easy...
var myMesh = new THREE.Mesh(myGeometry, [ material1, material2, material3 ]);
Notice that the material parameters is defined as an array. Each material has a different texture, which in your case are the different resolution images.
Now, debug into your Mesh. Under the goemetry property, you'll see an a property called faces, which is an array of Face3 objects. Each face has a property named materialIndex. This is the face's reference to the array of materials.
When you reach a point where you want to trigger a change (such as your camera being a certain distance from a mesh), you can change the material index, then trigger the mesh to change its material:
var distance = camera.position.distanceTo(myMesh.position);
if(distance < 50){
myMesh.faces.forEach(function(face){
face.materialIndex = 2;
});
}
else if(distance => 50 && distance < 100){
myMesh.faces.forEach(function(face){
face.materialIndex = 1;
});
}
else{
myMesh.faces.forEach(function(face){
face.materialIndex = 0;
});
}
myMesh.groupsNeedUpdate = true;
The last line (myMesh.groupsNeedUpdate = true;) tells the renderer that the material indices changed, so it will need to update the materials for the render.
Perhaps you could use THREE.LOD. It basically allows you to define different meshes for a range of distances. The meshes would be the same Quads, but you could change their materials to use different textures...
Here is the LOD example in the THREE.js web.
Hope it helps!!
I am trying to detect a click on a bounding box for an object (rather than just on the object itself - more clickable area). When I load the object like this:
var loader2 = new THREE.ObjectLoader();
loader2.load( "models/Platform/Platform.json", function(object, materials){
object.rotation.x = - (Math.PI / 2);
object.rotation.y = Math.PI;
object.scale.set(.025, .025, .025);
object.position.set(0, 1, .4);
var bbox = new THREE.BoundingBoxHelper(object, 0xffffff);
bbox.update();
scene.add(object);
scene.add(bbox);
objects.push(bbox);
});
And detect the click like this:
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
document.addEventListener( 'touchstart', onDocumentTouchStart, false );
window.addEventListener( 'resize', onWindowResize, false );
function onDocumentTouchStart( event ) {
event.preventDefault();
event.clientX = event.touches[0].clientX;
event.clientY = event.touches[0].clientY;
onDocumentMouseDown( event );
}
function onDocumentMouseDown( event ) {
console.log("here");
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(mouse.x);
console.log(mouse.y);
var intersects = raycaster.intersectObjects( objects, true );
if ( intersects.length > 0 ) {
console.log("click");
}
The bounding box shows up correctly, and I can click on it!!!!! However, the bounding box is visible on the screen:
I want the bounding box to be transparent/invisible/hidden. Is there any way I can have a bounding box attached to the object which is clickable but not visible?
I read that to make the bounding box invisible I should remove the scene.add(bbox); (not add it to the scene), but if I do that, then it is not in the scene for the ray to intersect, and thus the click is not registered.
Solutions?
Thanks so much!!!
You can try to set the material to invisible:
bbox.material.visible = false;
So, there seem to be (at least) two solutions.
As suggested by #prisoner849:
bbox.material.opacity = 0;
bbox.material.transparent = true;
As suggested by #tomacco and refined by #WestLangley:
bbox.material.visible = false;
Both of these solutions worked for me!
I have a .obj file on the web page using Three js.
My aim is, when I drag the mouse left/right, the OBJ model should rotate which I am able to do so USING THREE.TrackballControls().
Next thing is, I want to touch on the specific points on that OBJ model and if the mouse is down on those points something would happen(like a counter increase which will be shown on the web page).
I have seen DOMevents for three js but it looks like it allows us to click on the whole object not on specific points on the objects.
How can I achieve that?
You have to create a raycaster. (r69)
mouse_vector = new THREE.Vector3(),
mouse = { x: 0, y: 0, z: 1 };
var vector = new THREE.Vector3();
var raycaster = new THREE.Raycaster();
var dir = new THREE.Vector3();
function onMouseDown( event_info )
{
event_info.preventDefault();
mouse.x = ( event_info.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event_info.clientY / window.innerHeight ) * 2 + 1;
mouse_vector.set( mouse.x, mouse.y, mouse.z );
mouse_vector.unproject(camera);
var direction = mouse_vector.sub( camera.position ).normalize();
ray = new THREE.Raycaster( camera.position, direction );
ray.set( camera.position, direction );
intersects = ray.intersectObjects(scene.children, true);
if( intersects.length )
{
intersects.forEach(function(clicked)
{
// Your stuff like
if (clicked.object.typ == 'yourObject')
{
//Event on click..
}
});
}
}