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...
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.
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:
BufferGeometry
object...Ready? Let's do it.
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 );