Three.js How to get position of a mesh? - javascript

With the below code, position of a mesh is returned as (0, 0, 0) but it is not. So is the positioın vector calculated after render process?
me.scene.add(objMesh); //me is a project class
objMesh.updateMatrixWorld(true);
alert(objMesh.position.x + ',' + objMesh.position.y + ',' + objMesh.position.z);
objMesh is created from objfile, it is added to the scene correctly and centroid is approx (-8, 3, 0)
but position vector of objMesh is (0, 0, 0) do we have to auto calculate something first or should i calculate it manually from geometry vertices of the mesh ?
http://81.214.75.32:8181/admin is the url
the site is in Turkish so i will translate the UI items
in the site there is "Dosya" menu item
oppen the menu item and select "Proje Aç"
a dialog appears
in that dialog select MUTFAK_1
scene will appear
in that scene, every meshes position is (0, 0, 0)
is that possible :)

object.position is always local to the object. If you want to get the position in world space you need to get it from object.matrixWorld.
Try with this:
scene.add(objMesh);
scene.updateMatrixWorld(true);
var position = new THREE.Vector3();
position.getPositionFromMatrix( objMesh.matrixWorld );
alert(position.x + ',' + position.y + ',' + position.z);
r58
Update:
The function getPositionFromMatrix() has been renamed to setFromMatrixPosition().

For finding where in world space is the geometry centroid, try this:
objMesh.geometry.computeBoundingBox();
var boundingBox = objMesh.geometry.boundingBox;
var position = new THREE.Vector3();
position.subVectors( boundingBox.max, boundingBox.min );
position.multiplyScalar( 0.5 );
position.add( boundingBox.min );
position.applyMatrix4( objMesh.matrixWorld );
alert(position.x + ',' + position.y + ',' + position.z);
r58

Yeah. after some talk with mrdoob, i realized that .position of objects are local to theirselves. My situation was to find the center point of my mesh considering the vertices. Below is the code to get the centroid which came from an answer #447 ( https://github.com/mrdoob/three.js/issues/447 )
geom.centroid = new THREE.Vector3();
for (var i = 0, l = geom.vertices.length; i < l; i++) {
geom.centroid.addSelf(geom.vertices[i]);
}
geom.centroid.divideScalar(geom.vertices.length);
Now we have centroid of geometry...
Update
according to https://github.com/mrdoob/three.js/wiki/Migration, the .addSelf had been renamed to .add after r55

alert(objMesh.matrixWorld.getPosition().x + ',' + objMesh.matrixWorld.getPosition().y + ',' + objMesh.matrixWorld.getPosition().z);

You can use .getWorldPosition() method of a mesh, to get its absolute position.
Docs here: https://threejs.org/docs/#api/en/core/Object3D.getWorldPosition

According to this post the center of gravity C for a mesh can be found by
C = [sum of all (A*R)] / [sum of all A]
A = (area of a face * 2)
R = face centroid = average of vertices making the face
and here is the code in three.js
function calculateCenterOfMass( mesh ){
var centroid = new THREE.Vector3();
// centroid = centroidNominator / centroidDenominator;
var centroidNominator = new THREE.Vector3();
var centroidDenominator = 0;
for(var i = 0; i < mesh.geometry.faces.length; i++){
var Pi = mesh.geometry.faces[i].a;
var Qi = mesh.geometry.faces[i].b;
var Ri = mesh.geometry.faces[i].c;
var a = new THREE.Vector3(mesh.geometry.vertices[Pi].x, mesh.geometry.vertices[Pi].y, mesh.geometry.vertices[Pi].z);
var b = new THREE.Vector3(mesh.geometry.vertices[Qi].x, mesh.geometry.vertices[Qi].y, mesh.geometry.vertices[Qi].z);
var c = new THREE.Vector3(mesh.geometry.vertices[Ri].x, mesh.geometry.vertices[Ri].y, mesh.geometry.vertices[Ri].z);
var ab = b.clone().sub(a);
var ac = c.clone().sub(a);
var cross = new THREE.Vector3();
cross.crossVectors( ab, ac );
var faceArea = cross.lengthSq() / 2;
var faceCentroid = new THREE.Vector3( (a.x + b.x + c.x)/3, (a.y + b.y + c.y)/3, (a.z + b.z + c.z)/3 );
if (!isNaN(faceArea)){
centroidNominator.add(faceCentroid.multiplyScalar(faceArea));
centroidDenominator += faceArea;
}
}
centroid = centroidNominator.divideScalar(centroidDenominator);
return centroid;
}

Tip. If you are getting the position of a mesh within an imported model (e.g. GLTF) make sure that the mesh has it's origin set to it's own centre before exporting. i.e. don't apply transforms to it. Otherwise it's world centre will be the centre of the gltf and not the mesh itself.

Related

Click to place camera near an object in Three js

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.

Three.js - Get the position of a single face of a tessellated object

I have a flat surface which I have exploded and tessellated into triangles. I want to get the position of every single face to apply an animation, but apparently I am not able to do that.
if (intersects.length > 0) {
// if the closest object intersected is not the currently stored intersection object
if (intersects[0].object != INTERSECTED) {
if (INTERSECTED) {
INTERSECTED.face.color.setHex(INTERSECTED.currentHex);
}
INTERSECTED = intersects[0];
INTERSECTED.currentHex = INTERSECTED.face.color.getHex();
INTERSECTED.face.color.setHex(0xc0392b);
INTERSECTED.object.geometry.colorsNeedUpdate = true;
var position = new THREE.Vector3();
position.setFromMatrixPosition( INTERSECTED.matrixWorld );
alert(position.x + ',' + position.y + ',' + position.z);
}
}
I tried to use INTERSECTED.face.matrixWorld.getPosition(), as specified in this example, but it's throwing an error. Here you can find a JSFiddle with the full code. Do you know what's the issue with my code?
Thank in advance for your replies!
A THREE.Face3 has no such thing as a position or a matrix. You need to use the face indexes a, b and c to find the corresponding vertices and then you can calculate your face centroid (also the face gravity point) using those points:
var geometry = INTERSECTED.object.geometry;
var face = INTERSECTED.face;
var vertices = geometry.vertices;
var v1 = vertices[ face.a ];
var v2 = vertices[ face.b ];
var v3 = vertices[ face.c ];
// calculate the centroid
var position = new THREE.Vector3();
position.x = ( v1.x + v2.x + v3.x ) / 3;
position.y = ( v1.y + v2.y + v3.y ) / 3;
position.z = ( v1.z + v2.z + v3.z ) / 3;
Here an updated fiddle.

Drawing a line with three.js dynamically

This is what I'd like to achieve (a modifiable polygon where the red circles are vertices) and I'd like to build the polygon dynamically.
When initiating the geometry as
var geometry = new THREE.Geometry();
geometry.vertices.push(point);
geometry.vertices.push(point);
var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({}));
it works well until the second click, it builds a straight line between 1 and 2 but does not add a third line when it's pushed to the array. WebGL seems to require buffered points.
When I predefine vertices like this I can draw two lines (third click)
var geometry = new THREE.Geometry();
for (var i = 0; i < 4; i++) {
geometry.vertices.push(point);
}
var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({}));
but this is not a good solution as I don't know how many vertices does the user want to add and it's pointless to assign it a big number as I have to loop it multiple times.
Is there any way around it?
You can animate a line -- or increase the number of points rendered -- very easily using BufferGeometry and the setDrawRange() method. You do need to set a maximum number of points, however.
const MAX_POINTS = 500;
// geometry
const geometry = new THREE.BufferGeometry();
// attributes
const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point
geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
// drawcalls
drawCount = 2; // draw the first 2 points, only
geometry.setDrawRange( 0, drawCount );
// material
const material = new THREE.LineBasicMaterial( { color: 0xff0000 } );
// line
line = new THREE.Line( geometry, material );
scene.add( line );
If you want to change the number of points rendered after the first render, do this:
line.geometry.setDrawRange( 0, newValue );
If you want to change the position data values after the first render, you set the needsUpdate flag like so:
line.geometry.attributes.position.needsUpdate = true; // required after the first render
Here is a fiddle showing an animated line which you can adapt to your use case.
three.js r.147
Draw a line in real time
Here an updated fiddle where I optimized the code from user3325025 his example; In this case there is absolutely no need to update all the points of the line on render. Update is only needed onMouseMove (updating end of line) and onMouseDown (drawing new point):
// update line
function updateLine() {
positions[count * 3 - 3] = mouse.x;
positions[count * 3 - 2] = mouse.y;
positions[count * 3 - 1] = mouse.z;
line.geometry.attributes.position.needsUpdate = true;
}
// mouse move handler
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
mouse.z = 0;
mouse.unproject(camera);
if( count !== 0 ){
updateLine();
}
}
// add point
function addPoint(event){
positions[count * 3 + 0] = mouse.x;
positions[count * 3 + 1] = mouse.y;
positions[count * 3 + 2] = mouse.z;
count++;
line.geometry.setDrawRange(0, count);
updateLine();
}
I updated the fiddle with mouse events and a vector array if you want to scribble freehand.
https://jsfiddle.net/w67tzfhx/40/
function onMouseDown(evt) {
if(evt.which == 3) return;
var x = ( event.clientX / window.innerWidth ) * 2 - 1;
var y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// do not register if right mouse button is pressed.
var vNow = new THREE.Vector3(x, y, 0);
vNow.unproject(camera);
console.log(vNow.x + " " + vNow.y+ " " + vNow.z);
splineArray.push(vNow);
document.addEventListener("mousemove",onMouseMove,false);
document.addEventListener("mouseup",onMouseUp,false);
}

How to interact with shapes in Pixijs?

I am trying to get the id of the shape my mouse is currently hovering over.
my shapes are in a container
// creating the layers
gridLayer = new PIXI.DisplayObjectContainer ();
gridLayer.setInteractive(true);
stage.addChild(gridLayer);
and i am creating each shape like this;
function drawHexagon(x,y, size, gap,scale, color, iterI, iterJ, type) {
var shape = new PIXI.Graphics();
// set a fill and line style
shape.beginFill(color);
shape.lineStyle(1, 0xa0a0a0, 1);
size = size-gap;
for (i = 0; i < 7; i++) {
angle = 2 * Math.PI / 6 * (i + 0.5);
var x_i = x + size * Math.cos(angle);
var y_i = y + size * Math.sin(angle);
if (i === 0) {
shape.moveTo(x_i, scale *y_i)
}
else {
shape.lineTo(x_i, scale * y_i)
}
};
shape.endFill();
// calculate and save the axial coordinates
var cX = iterJ - (iterI - (iterI&1)) / 2;
var cZ = iterI;
var cY = -1*(cX+cZ);
shape.hexId = cX + "x" + cY + "y" + cZ + "z";
shape.hexPosX = x;
shape.hexPosY = y;
shape.setInteractive(true);
shape.mouseover = function(mouseData){
console.log("MOUSE OVER " + shape.hexId);
}
shape.click = function(mouseData){
console.log("MOUSE CLICK " + shape.hexId);
}
gridLayer.addChild(shape);
}
However, clicking on any shape or hovering over it is not showing me anything in the console. what am i doing wrong?
i have tried both
shape.setInteractive(true)
and
shape.interactive = true
but neither seems to work for me.
EDIT: i have added a jsfiddle. it doesnt works (i dont know how to link things in jsfiddle) but you can see my entire code in there.
http://jsfiddle.net/9aqHz/1/
For a PIXI.Graphics object to be interactive you need to set a hitArea shape (it can be a Rectangle, Circle or a Polygon):
shape.hitArea = new PIXI.Polygon([
new PIXI.Point(/* first point */),
new PIXI.Point(/* second point */),
new PIXI.Point(/* third point */),
new PIXI.Point(/* fourth point */),
new PIXI.Point(/* fifth point */)
]);
Another approach would be to generate a texture from the shape and use a Sprite, but the hit area would be the entire rectangular bounds of the hexagon:
var texture = shape.generateTexture();
var sprite = new PIXI.Sprite(texture);
sprite.setInteractive(true);
sprite.anchor.set(0.5, 0.5);
Fiddle with this applied to your example
#imcg I updated your code so it workes with Pixi 3.0.8
- sprite.setInteractive(true);
+ shape.interactive = true;
+ shape.buttonMode = true;
- sprite.setInteractive(true)
+ sprite.interactive = true;
+ sprite.buttonMode = true;
http://jsfiddle.net/LP2j8/56/
I will add a bit of info for anyone who is in the same boat i was in;
When you define a shape as a geom, you have to explicitly state a hitarea.
So adding the following code makes it work;
shape.hitArea = new PIXI.Polygon(vertices);
shape.interactive = true;
shape.click = function(mouseData){
console.log("MOUSE CLICK " + shape.hexId);
}
But, when you define a shape as a sprite/texture, you dont need to do this.
in cases of sprites, just setting shape.interactive = true for the sprite is sufficient. You dont need to set the interactive property for the parent object or the stage.

Can I have the ray collision position (X,Y) in targeted face relative coordinate?

I'm trying to find the ray collision coordinate relative to the face targeted...
code:
var fMouseX = (iX / oCanvas.width) * 2 - 1;
var fMouseY = -(iY / oCanvas.height) * 2 + 1;
//I Use OrthographicCamera
var vecOrigin = new THREE.Vector3( fMouseX, fMouseY, - 1 );
var vecTarget = new THREE.Vector3( fMouseX, fMouseY, 1 );
oProjector.unprojectVector( vecOrigin, this.__oCamera );
oProjector.unprojectVector( vecTarget, this.__oCamera );
vecTarget.subSelf( vecOrigin ).normalize();
var oRay = new THREE.Ray(vecOrigin, vecTarget);
intersects = oRay.intersectObjects([ oCylinderMesh ]);
With intersects[ 0 ].point, I can have the mouse position in 'screen coordinate', but how can I have it in Cylinder coordinate ?
PS: mesh are not rotate, but camera can change position...
Really nice framework ;)
Here is my solution, just get the Cylinder absolute coordinate (position relative to screen), then, itersects[0].point sub the Cylinder absolute coordinate.
The following code may help:
var relativeTo = function(element, ancestor) {
var offset = element.position.clone();
if (element.parent == ancestor) {
return offset;
}
return offset.addSelf(relativeTo(element.parent, ancestor));
}

Categories