i have a scene contains some objects, i select one of them then rotate it then test collision by thes code
var firstObject = SELECTED;
for(var i=0;i<objects.length;i++){
if(objects[i]!=SELECTED){
var secondObject = objects[i];
firstBB = new THREE.Box3().setFromObject(firstObject);
secondBB = new THREE.Box3().setFromObject(secondObject);
var collision = firstBB.isIntersectionBox(secondBB);
if(collision){
return;
}
}
}
the code is works fine before rotation but after rotation the bounded bos is not computed correctly.
I was too facing the same issue. This is beacuse the bounding box is created without the factor of translation and rotation so the quick fix for this is try to apply the matrix operation on the Mesh before computing the setFromObject like:
firstObject.updateMatrix();
firstObject.geometry.applyMatrix( firstObject.matrix );
secondObject.updateMatrix();
secondObject.geometry.applyMatrix( secondObject.matrix );
firstBB = new THREE.Box3().setFromObject(firstObject);
secondBB = new THREE.Box3().setFromObject(secondObject);
var collision = firstBB.isIntersectionBox(secondBB);
Related
I’m developing a VueJs application and added 1000 box model to the scene. Although I’ve set the every geometry’s name attribute when I clicked the box model I couldn’t get the box geometry’s name. I think this is caused by applying merged buffer geometry. Is it possible to get name attribute by clicking in some way ?
Here is my code;
addCubeToScene() {
this.oDracoLoader = new DRACOLoader();
this.oDracoLoader.setDecoderPath("./draco/");
this.oGltfLoader = new GLTFLoader();
this.oGltfLoader.setDRACOLoader(this.oDracoLoader);
this.oDracoLoader.preload();
this.oGltfLoader.load(BoxModel, function(oObject) {
oObject.scene.traverse(function(oObject) {
if (oObject.isMesh) {
var oObjectGeometry = oObject.geometry;
for (var i = 0; i < 1000; i++) {
var oGeometry = oObjectGeometry.clone();
oGeometry.applyMatrix4(
new THREE.Matrix4().makeTranslation(
Math.random() * 3000,
Math.random() * 5000,
Math.random() * 1000
)
);
oGeometry.name = "Box-" + i;
oCubes.push(oGeometry);
}
var oGeometriesCubes = BufferGeometryUtils.mergeBufferGeometries(
oCubes
);
oGeometriesCubes.computeBoundingSphere();
const oTexture = new THREE.TextureLoader().load(BoxTexture);
oTexture.magFilter = THREE.NearestFilter;
var oMesh = new THREE.Mesh(
oGeometriesCubes,
new THREE.MeshLambertMaterial({
map: oTexture,
side: THREE.DoubleSide,
})
// new THREE.MeshNormalMaterial()
);
oMesh.scale.set(0.5, 0.5, 0.5);
oThis.scene.add(oMesh);
oThis.renderScene();
}
});
});
}
onMouseClick(oEvent) {
oEvent.preventDefault();
oEvent.stopPropagation();
oMouse.x =
(oEvent.offsetX / this.renderer.domElement.clientWidth) * 2 - 1;
oMouse.y =
-(oEvent.offsetY / this.renderer.domElement.clientHeight) * 2 + 1;
oRaycaster.setFromCamera(oMouse, this.camera);
var oIntersects = oRaycaster.intersectObjects(this.scene.children, true);
if (oIntersects[0] && oIntersects[0].object) {
var oObject = oIntersects[0].object;
console.log(oObject); // I would like to get clicked geometry's name from this object
}
}
Here is the image representing box model’s view;
Here is the result of box geometry datas
When I merged all geometries I can't see any name attribute
I assume you're trying to merge the geometry to increase render performance. While you did manage to reduce the number of draw calls the GPU had to make, you also removed all uniqueness of the different BufferGeometry objects. Your geometry is now represented by one big buffer (debug oGeometriesCubes.attributes.position.array to see this).
In the interest of performance, we can not only save you GPU draw calls, but also memory, and we can get your box picking to work as well. Let's start from scratch. It will seem like we've taken a step backward, but then it will all fall together...
Your geometry definitions
First, you do not need to uniquely identify each BufferGeometry. Mesh objects can share geometry! AND materials! This equates to memory savings.
Having unique Mesh objects also means you can put the name and translation information at the Mesh level. Translating at the geometry level 1000 times is expensive, even if you do it up-front like you do. Trust that the GPU can do these translations very quickly from the Mesh level. It's literally the GPU's job.
Your mesh definitions
Now, we're still looking at 1000 meshes, which means 1000 draw calls, right? That's where InstancedMesh comes into play.
Instancing--in very simple terms--is a way for the GPU to not only re-use the geometry buffer information, but also to perform the drawing of all 1000 meshes in a single swipe, rather than one at a time.
To sum it up:
Using only one BufferGeometry object...
And one material...
We'll draw 1000 instances...
In one draw call.
Ready? Let's do it.
The code
let geometry = yourGeometryFromGLTF;
// load your texture here...
let material = new new THREE.MeshLambertMaterial({
map: oTexture,
side: THREE.DoubleSide,
})
let iMesh = new THREE.InstancedMesh( geometry, material, 1000 );
let translateMatrix = new THREE.Matrix4();
let scaleMatrix = new THREE.Matrix4().makeScale( 0.5, 0.5, 0.5 );
let finalMatrix = new THREE.Matrix4();
for( let i = 0; i < 1000; ++i ){
translateMatrix.makeTranslation(
Math.random() * 1500,
Math.random() * 2500,
Math.random() * 500
);
finalMatrix.multiplyMatrices( translateMatrix, scaleMatrix );
iMesh.setMatrixAt( i, finalMatrix );
}
imesh.instanceMatrix.needsUpdate = true; // IMPORTANT
scene.add( iMesh );
Now your Raycaster should return the instances, but it will take one more step to get the "ID" of which box you selected.
let intersects = raycaster.intersectObjects(scene);
if(intersects.length > 0){
let boxName = `Box-${ intersects[0].instanceId }`;
}
The instanceId will be the index of the box instance in your InstancedMesh. You can even use it to get more information about that instance, like its transformation matrix:
let iMatrix = new THREE.Matrix4();
iMesh.getMatrixAt( intersects.instanceId, iMatrix );
I have a light that is a child to a pivot object:
var pivotpoint = new THREE.Object3D();
pivotpoint.name="pivot";
scene.add(pivotpoint);
var light = new THREE.PointLight( 0xffffff, 1, 100 );
light.name = "light";
light.castShadow = true;
pivotpoint.add( light );
light.position.set(10,25,0);
Now, in my update() method I rotate the pivot object:
var o = scene.getObjectByName("pivot");
if(GLOBAL_KEYS['a'])
{
o.rotation.y += 0.05;
}
if(GLOBAL_KEYS['d'])
{
o.rotation.y -= 0.05;
}
This works perfectly well. I can see my light rotating around the pivot point, casting shadows and all.
However, if I do...
console.log(light.position);
...the position attribute always stays at (10,25,0).
What in god's name do I need to do in order to get the actual light position??
Thanks in advance!
object.position is a local position, relative to the object's parent in the scene graph. To compute position in global space, use getWorldPosition:
const worldPos = new THREE.Vector3();
light.getWorldPosition(worldPos);
console.log(worldPos);
I am using Three.js (r86) to render a group of spheres. I'm doing this by calling a createSphereGroup function from an external library:
// External library code.
function createSphereGroup(sphereCount) [
var group = new THREE.Group();
for (var i = 0; i < sphereCount; i++) {
var geometry = new THREE.SphereGeometry(50);
var material = new THREE.MeshPhongMaterial({ color: 0xFF0000 });
var sphere = new THREE.Mesh(geometry, material);
sphere.position.x = i * 150;
group.add(sphere);
}
return group;
}
(Assume that the external library is opaque and I can't modify any code in it.)
// My code.
var group = createSphereGroup(5);
scene.add(group);
...which looks like this:
As you can see, the group of spheres is off-center. I would like the group to be centered, with the 3rd sphere to be at the point where the two lines intersect (x=0).
So is there some way that I can center the group? Maybe by computing the width of the group and then positioning it at x=-width/2? I know you can call computeBoundingBox on a geometry to determine its width, but a THREE.Group doesn't have a geometry, so I'm not sure how to do this...
If you want to center an object (or group) and its children, you can do so by setting the object's position like so:
new THREE.Box3().setFromObject( object ).getCenter( object.position ).multiplyScalar( - 1 );
scene.add( object );
three.js r.92
If all the objects in the group are rather the same size, as in your example, you can just take the average of the children's position :
function computeGroupCenter(count) {
var center = new THREE.Vector3();
var children = group.children;
var count = children.length;
for (var i = 0; i < count; i++) {
center.add(children[i].position);
}
center.divideScalar(count);
return center;
}
Another (more robust) way to do this is to create a bounding box containing the bounding boxes of every child of the group.
There are some important things to consider :
A THREE.Group can contain others THREE.Group, therefore, the algorithm should be recursive
Bounding boxes are computed in local space, however, the object may be translated with respect to its parent so we need to work in world space.
The group itself might be translated to! So we have to jump back from world space to the group's local space to have the center defined in the group's space.
You can achieve this easily with THREE.Object3D.traverse, THREE.BufferGeometry.computeBoundingBox and THREE.Box3.ApplyMatrix4 :
/**
* Compute the center of a THREE.Group by creating a bounding box
* containing every children's bounding box.
* #param {THREE.Group} group - the input group
* #param {THREE.Vector3=} optionalTarget - an optional output point
* #return {THREE.Vector3} the center of the group
*/
var computeGroupCenter = (function () {
var childBox = new THREE.Box3();
var groupBox = new THREE.Box3();
var invMatrixWorld = new THREE.Matrix4();
return function (group, optionalTarget) {
if (!optionalTarget) optionalTarget = new THREE.Vector3();
group.traverse(function (child) {
if (child instanceof THREE.Mesh) {
if (!child.geometry.boundingBox) {
child.geometry.computeBoundingBox();
childBox.copy(child.geometry.boundingBox);
child.updateMatrixWorld(true);
childBox.applyMatrix4(child.matrixWorld);
groupBox.min.min(childBox.min);
groupBox.max.max(childBox.max);
}
}
});
// All computations are in world space
// But the group might not be in world space
group.matrixWorld.getInverse(invMatrixWorld);
groupBox.applyMatrix4(invMatrixWorld);
groupBox.getCenter(optionalTarget);
return optionalTarget;
}
})();
Here's a fiddle.
I'm new at three.js and i need to load a particle object
it seems to work if add particles on vertices
if i load my Json once and inside the load() i build 2 different THREE.Points when i mov the first the second also move if i use 2 different load calls and inside each i build a THREE.Points object each object can be move separately
this i the function that i call inside animate
function animateParticles( particleSystem, particleSystemOriginal, deltaTime ) {
var vertsOriginal = particleSystemOriginal.geometry.vertices;
var verts = particleSystem.geometry.vertices;
for(var i = 0; i < verts.length; i++) {
var vertOriginal = vertsOriginal[i]; // original position
var vert = verts[i]; // cloud position
var vertOriginalY = vertOriginal.y;
var vertY = vert.y;
if (i==1) console.log("vertOriginalY " + vertOriginalY + " vertY " + vertY);
vert.y = vertY - (10 * deltaTime); // move
}
particleSystemOriginal.geometry.verticesNeedUpdate = true;
particleSystem.geometry.verticesNeedUpdate = true;
}
it seems strange that i need to load twice the same object to move one of them
in my function i'just testing and moving dove the cloud but what i would like is to shake particles, so i need to know particles original position and set particles new position
EDIT 1
i load my model like this
var loader = new THREE.JSONLoader();
loader.load('../3d-models/creati/mymodel-001.json', function(geometry, materials) {
var material = new THREE.MeshNormalMaterial();
var particleModelOriginal = new THREE.Mesh( geometry, material );
var particleModel = particleModelOriginal.clone();
/* build particle THREE.Points */
particleSystemOriginal = new THREE.Points(particlesOriginal, particleMaterial);
particleSystem = new THREE.Points(particles, particleMaterial);
//particleSystem = particleSystemOriginal.clone();
});
If I understand the question correctly, you probably need to use the .clone() object on mesh/geometry that you are loading if you want to create multiple objects. Otherwise they are referencing the same object, and will both be modified if one of them are modified
I loaded some objects via OBJLoader , loaded object contain one parent and multiple childs; then I apply Raycaster and find clicked child.
Then I want to update position of child object, but initial position comes zero for all childs.
var intersect = intersects[0].object;
intersect.position.y = intersect.position.y + 5; // 0 + 5
But in my scene all looks fine. Also, If i remove clicked object, actually it is removed from scene. I think I missed some point their positions cant be (0,0,0). How can I reach their relative position ?
Here is code for set Transform Control into center of object:
let objLoader = new THREE.OBJLoader();
let mtlLoader = new THREE.MTLLoader();
mtlLoader.load("path/to/mtlFile.mtl", (materials) => {
materials.preload();
objLoader.setMaterials(materials).load("path/to/objFile.obj", (object3d) => {
object3d.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.geometry.computeBoundingBox();
let matrix = new THREE.Vector3();
let offset = child.geometry.boundingBox.getCenter(matrix);
child.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-offset.x, -offset.y, -offset.z));
child.position.copy(offset);
objects.push(child);
}
});
scene.add(object3d);
});
});
Try this, then read position(). I had the same issue, got an answer here. (geom is your Meshes geometry.)
objMesh.centroid = new THREE.Vector3();
for (var i = 0, l = geom.vertices.length; i < l; i++) {
objMesh.centroid.add(geom.vertices[i].clone());
}
objMesh.centroid.divideScalar(geom.vertices.length);
var offset = objMesh.centroid.clone();
objMesh.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-offset.x, -offset.y, -offset.z));
objMesh.position.copy(objMesh.centroid);
The position is relative to the parent
Multiply the position by the transform of the parent to get the world-space coordinates, if that's what you're seeking
That is because all objects in a obj file have their pivot at World 0,0,0
no matter where their local pivot was before exporting.