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 );
Related
Think of my question as a more complicated version of this question here:
Three.js, sharing ShaderMaterial between meshes but with different uniform sets
tl;dr: I'm relying on a skin shader from THREE.js r100 to enable subsurface scattering on the meshes to make them appear more realistic but only one of them actually has the proper postprocessing to enable the effects.
The postprocessing effect is desired on all three meshes but only exists on one, the center. You can tell because the lighting looks right, and you can see where light travels "through" the mesh in thinner areas, like on the bottom part of the neck area and the ears (and if you get reaaaallly close it's in the nose too :D ).
Where the code is hosted has some wild things going on to generate the mesh, but that's beside the point, the main problem that I'm having is what you can see, only the center mesh (the one that was first added to the scene) actually has the proper effects applied to its material, the others have cloned versions of that shader but they don't render with the proper post-processing, I have a feeling it's because they're sharing uniforms or something related to that, but I'm not sure how to duplicate it properly. I was wondering how I can fix it? Do I add more render passes?
Is the way to adjust the render passes for the shader materials by adding even more passes relevant to the materials or just merely editing the uniforms (as stated in the linked question)?
I'm a bit lost and I've tried a lot to get this to work (though I'm definitely new to THREE.js post-processing passes), but you're my last hope. To someone experienced with this I feel like the solution will be very straightforward, I have a feeling I'm missing something very basic.
to view the output: https://abrasive-likeable-gateway.glitch.me/index-shader.html
to view the code: https://glitch.com/edit/#!/abrasive-likeable-gateway?path=index-shader.html%3A655%3A0
the filesystem is visible on the left side, you can remix the project on glitch (w/ a free account) to edit the code. I would've used codepen but I didn't want to deal with linking all of the three.js dependencies.
at index-shader.html on line 655 is where the setup begins for postprocessing
and at SS-xfer-placement.js on line 2838 is where rendering happens
in the same document, between lines 1900 - 2048 is the following code, which I suspect is where things are wrong, it looks like this, and imports the mesh, adds a material to it (that was set up in the html file after line 655) and adds it to the scene
the code looks like this:
setTimeout(()=>{
updateFaceParams()
setTimeout(()=>{
console.log(scene)
//send the model from ONE to THREE, init THREE
// document.querySelector("#ONEJS").style.height = "10vh!important;"
// document.querySelector("#ONEJS").style.width = "10vw!important;"
document.querySelector("#ONEJS").style.position = `absolute`
document.querySelector("#ONEJS").style.top = `0px`
document.querySelector("#ONEJS").style.right = `0px`
initTHREE();
let materialClone = window.THREEmaterial.clone()
var facePortion = scene.getObjectByName("face").geometry
console.log("FACE", scene.getObjectByName("face"))
var geometryConvert = convertGeometryToFiveBufferGeometry(facePortion)
var transferMesh = new THREE.Mesh( geometryConvert, window.THREEmaterial );
transferMesh.position.y = - 50;
transferMesh.rotation.y=3*Math.PI
// transferMesh.scale.set( scale, scale, scale );
transferMesh.scale.set(200,200,200)
transferMesh.doubleSided = true;
// console.log(transferMesh)
transferMesh.name = "face"
transferMesh.rotateY(Math.PI/180 * 180);
transferMesh.material.flatShading = false
transferMesh.material.shading = THREE.SmoothShading
THREEscene.add( transferMesh );
// console.log("test",transferMesh)
// console.log(THREEscene)
//
}, 1000)
setTimeout(()=>{
updateFaceParams()
setTimeout(()=>{
var facePortion = scene.getObjectByName("face").geometry
console.log("FACE", scene.getObjectByName("face"))
var geometryConvert = convertGeometryToFiveBufferGeometry(facePortion)
var transferMesh = new THREE.Mesh( geometryConvert, window.THREEmaterial.clone() );
transferMesh.position.y = - 50;
transferMesh.rotation.y=3*Math.PI
// transferMesh.scale.set( scale, scale, scale );
transferMesh.scale.set(200,200,200)
transferMesh.doubleSided = true;
// console.log(transferMesh)
transferMesh.name = "face"
transferMesh.rotateY(Math.PI/180 * 180);
transferMesh.material.flatShading = false
transferMesh.material.shading = THREE.SmoothShading
transferMesh.position.set(transferMesh.position.x+200, transferMesh.position.y, transferMesh.position.z)
THREEscene.add( transferMesh );
var THREErenderModelUV = new THREE.RenderPass( THREEscene,THREEcamera, window.THREEmaterialUV.clone(), new THREE.Color( 0x575757 ) );
THREEcomposer.addPass( THREErenderModelUV );
//TODO: write a stack overflow question about copying shaders!!!
setTimeout(()=>{
updateFaceParams()
setTimeout(()=>{
var facePortion = scene.getObjectByName("face").geometry
console.log("FACE", scene.getObjectByName("face"))
var geometryConvert = convertGeometryToFiveBufferGeometry(facePortion)
var transferMesh = new THREE.Mesh( geometryConvert, window.THREEmaterial.clone() );
// \var transferMesh = new THREE.Mesh( geometryConvert, new THREE.MeshPhongMaterial({color:0xffffff}) );
transferMesh.position.y = - 50;
transferMesh.rotation.y=3*Math.PI
// transferMesh.scale.set( scale, scale, scale );
transferMesh.scale.set(200,200,200)
transferMesh.doubleSided = true;
// console.log(transferMesh)
transferMesh.name = "face"
transferMesh.rotateY(Math.PI/180 * 180);
transferMesh.material.flatShading = false
transferMesh.material.shading = THREE.SmoothShading
transferMesh.position.set(transferMesh.position.x-200, transferMesh.position.y, transferMesh.position.z)
THREEscene.add( transferMesh );
// var THREErenderModelUV = new THREE.RenderPass( THREEscene,THREEcamera, window.THREEmaterialUV.clone(), new THREE.Color( 0x575757 ) );
// THREEcomposer.addPass( THREErenderModelUV );
var THREErenderModelUV = new THREE.RenderPass( THREEscene,THREEcamera, THREEmaterialUV.clone(), new THREE.Color( 0x575757 ) );
// var THREEeffectCopy = new THREE.ShaderPass( THREE.CopyShader );
// var THREEeffectBloom1 = new THREE.BloomPass( 1, 15, 2, 512 );
// var THREEeffectBloom2 = new THREE.BloomPass( 1, 25, 3, 512 );
// var THREEeffectBloom3 = new THREE.BloomPass( 1, 25, 4, 512 );
// THREEeffectBloom1.clear = true;
// THREEeffectBloom2.clear = true;
// THREEeffectBloom3.clear = true;
// THREEeffectCopy.renderToScreen = true;
// //
// var THREEpars = {
// generateMipmaps: true,
// minFilter: THREE.LinearMipmapLinearFilter,
// magFilter: THREE.LinearFilter,
// format: THREE.RGBFormat,
// stencilBuffer: false
// };
// var THREErtwidth = 512;
// var THREErtheight = 512;
//
// THREEcomposer = new THREE.EffectComposer( THREErenderer, new THREE.WebGLRenderTarget( THREErtwidth, THREErtheight, THREEpars ) );
THREEcomposer.addPass( THREErenderModelUV );
console.log(THREEcomposer)
}, 2000)
}, 2000)
}, 2000)
}, 2000)
},1000)
in other areas of the project, I wouldn't recommend looking at since it's really not relevant to this issue, the points that I highlighted are the only areas that deal with rendering, adding the mesh, and applying postprocessing.
the uniforms for the material are set up in the html file "index-shader.html" between lines 655 and 700 which may also be where I'd need to duplicate the uniforms and apply them properly, but I can't seem to figure out how to do that.
Please let me know if you have any help, thank you for reading!
I’m creating a cube using BoxGeometry and trying to apply the texture that the user has selected.
I created a raycaster and use that raycaster inside mouse down event.
If the object is present on cursor position then a user-selected texture gets applied.
So far everything works fine. Then I added if-else condition if the user selected single image for texture then that single image gets applied whole body of cube means all side of cube gets that image as material.
But if the user selected more than one image suppose three images then only three sides of cube get material. This also works very well.
Now a real problem when user select only three images remaining side of cube remain black and that is okay only material applied side shows up.
Now if user again select a single image and click on cube to apply single image texture to whole body again ray caster gives problem that
“–can’t access property “side”, the material is undefined–”
so this callback function which i added to select image files
function onUserMultiImageSelect(event) {
materialArray.length = 0;
length = multiFilesId.files.length;
let multiImage = [];
let userMultiImageUrl = [];
for (let i = 0; i < length; i++) {
multiImage[i] = multiFilesId.files.item(i);
userMultiImageUrl[i] = URL.createObjectURL(multiImage[i]);
materialArray.push(new THREE.MeshBasicMaterial({ map: new THREE.TextureLoader().load(userMultiImageUrl[i]),side:THREE.DoubleSide }));
}
isTextureSelect = true;
}
this is callback funtion which gets called when i click on object to apply texture
function onMouseButtonDown(event) {
var rect = event.target.getBoundingClientRect();
var mouseX = event.clientX - rect.left;
var mouseY = event.clientY - rect.top;
let raycaster = new THREE.Raycaster();
let pointer = new THREE.Vector2();
pointer.set((mouseX / window.innerWidth) * 2 - 1, -(mouseY / window.innerHeight) * 2 + 1);
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
var curentIntersectedObject = intersects[0];
if (isTextureSelect) {
if(length == 1){
var singleImage = multiFilesId.files.item(0);
var singleImageUrl = URL.createObjectURL(singleImage);
var texture = new THREE.TextureLoader().load(singleImageUrl);
curentIntersectedObject.object.material = new THREE.MeshBasicMaterial({ map: texture });
}else{
curentIntersectedObject.object.material = materialArray;
}
}
}
}
I'm using Cannon.js with Three.js. I have set a scene which has 5 columns of 4 blocks stacked on top of each other.
I want these to be interactable with other objects I'm planning on adding to the scene. However, the blocks in the columns seem to be causing lots of micro-collisions and over time, jitter out of position. I want them to stay exactly in line until they're interacted with.
If you view the codepen and wait for about 20/30 seconds you'll see the blocks start to move. Is there something specific I need to set on these blocks to prevent this from happening?
Here is an example I've put together - https://codepen.io/danlong/pen/XxZROj
As an aside, there's also quite a big performance drop when there are these blocks in the scene which I wasn't expecting. I plan to add more objects to the scene and not sure why the performance drops?
Is it something to do with the below in my animate() loop?
this.world.step(1 / 30);
Code specifically to set up my 'Cannon world' and 'columns' is below:
Cannon World:
this.world = new CANNON.World();
this.world.defaultContactMaterial.contactEquationStiffness = 1e6;
this.world.defaultContactMaterial.contactEquationRegularizationTime = 3;
this.world.solver.iterations = 20;
this.world.gravity.set(0,-25,0);
this.world.allowSleep = true;
this.world.broadphase = new CANNON.SAPBroadphase(this.world);
Columns:
var geometry = new THREE.BoxBufferGeometry(5,5,5);
var material = new THREE.MeshNormalMaterial();
var shape = new CANNON.Box(new CANNON.Vec3(5/2, 5/2, 5/2));
for (var rows = 0, yPos = 2.5; rows < 4; rows++, yPos+=5) {
for (var i = -20; i <= 20; i+=10) {
// physics
var body = new CANNON.Body({
mass: 0.5,
position: new CANNON.Vec3(i, yPos, 0),
friction: 0.1,
restitution: 0.3
});
body.allowSleep = true;
body.sleepSpeedLimit = 0.01;
body.sleepTimeLimit = 1.0;
body.addShape(shape);
this.world.addBody(body);
this.bodies.push(body);
// material
var mesh = new THREE.Mesh(geometry, material);
this.scene.add(mesh);
this.meshes.push(mesh);
}
}
Try this?
body.sleepSpeedLimit = 1.0;
I have searched on this and found several examples on stackoverflow, but the answers have not solved my problem.
What I have tried:
First I create the geometry bucket to be used for the group and an array to store my materials.
var totalGeom = new THREE.Geometry();
var materials = [];
I then run through my data (strData) with a for loop and call addMapMarker3d.
for(var i=0; i<strData.length;i++){
addMapMarker3d([strData[i].lat,strData[i].lon], strData[i].time, measureColors[i]);
}
The addMapMarker3d function is as follows:
addMapMarker3d = function(latLng, height, color){
var max = 600;
var dist = 25;
var opacity = (height/max);
var geometry = new THREE.BoxGeometry( Math.floor(dist), height, Math.floor(dist));
//create the material and add it to the materials array
var material = new THREE.MeshBasicMaterial({ transparent:true, color: Number(color), opacity:opacity});
this.materials.push(material);
//create a mesh from the geometry and material.
var cube = new THREE.Mesh( geometry, material);
//leaf is a simple lat/lng to pixel position converter
var actualMarkerPos = leaf.getPoint(latLng);
//apply the position on a 5000x5000 cartesian plane
var extent = 5000;
cube.position.setZ((actualMarkerPos[1] * extent) - extent/2);
cube.position.setX(-((actualMarkerPos[0] * extent) - extent/2));
cube.position.setY(height/2);
//update the matrix and add the cube to the totalGeom bucket
cube.updateMatrix();
totalGeom.merge( cube.geometry, cube.matrix);
}
After the for loop runs and all the cubes are created:
var mats = new THREE.MeshFaceMaterial(materials)
var total = new THREE.Mesh(totalGeom, mats);
world.scene.add(total);
The question
While the geometry merge functions, and my view port is running at a much improved FPS, all the cubes have exactly the same color and opacity. It appears the merge is using a single material of the 10k I supplied. Is there some way to ensure that the geometry uses the material supplied in the array? Am I doing something incorrect?
If I try this in addMapMarker3d:
totalGeom.merge( cube.geometry, cube.matrix, materials.length-1);
I get "Uncaught TypeError: Cannot read property 'transparent' of undefined" and nothing renders, which I don't understand, because by the examples, each geometry should index to a material in the materials array.
three.js r.70
The following technique uses just one material, but allows you to retain the individual color of each merged object. I don't know if it's possible to retain the individual alpha of each merged object.
http://jsfiddle.net/looshi/nsknn53p/61/
For each mesh, set each of its geometry.faces color :
function makeCube(size, color) {
var geom = new THREE.BoxGeometry(size, size, size);
for (var i = 0; i < geom.faces.length; i++) {
face = geom.faces[i];
face.color.setHex(color);
}
var cube = new THREE.Mesh(geom);
return cube;
}
Then, in the parent geometry you are going to mesh into, set its material vertexColors property.
var parentGeometry = new THREE.Geometry();
var parentMatrial = new THREE.MeshLambertMaterial({
color: 0xffffff,
shading: THREE.SmoothShading,
vertexColors: THREE.VertexColors
});
// in a loop you could create many objects and merge them
for (var i = 0; i < 1000; i++) {
cube = makeCube(size, color);
cube.position.set(x, y, z);
cube.rotation.set(rotation,rotation,rotation);
cube.updateMatrix()
parentGeometry.merge(cube.geometry, cube.matrix);
}
// after you're done creating objects and merging them, add the parent to the scene
parentMesh = new THREE.Mesh(parentGeometry, parentMatrial);
scene.add(parentMesh);
My original question was not answered: Is there some way to ensure that the geometry uses the material supplied in the array?
The answer is yes. Multiple materials can be applied to a single mesh during merge. After pushing the material to the materials array, the merge will utilize the geometry face material index. The merge will apply multiple materials when an array is supplied to new THREE.MeshFaceMaterial([materialsArray]) as the total mesh is created. This solves the mystery of the syntax. Just because you supply an array of materials does not mean that the merge will use each material in an iterative fashion as the objects are merged as of r71. The faces must inform the merge which material in the material array to use.
I am using this for a non rendered scene, and the final obj is exported. If you need render performance, see one of the other answers for some options.
A simple for loop on the face array on the geometry informs the merge which material to apply:
addMapMarker3d = function(latLng, height, color){
var max = 600;
var dist = 25;
var opacity = (height/max);
var geometry = new THREE.BoxGeometry( Math.floor(dist), height, Math.floor(dist));
//create the material and add it to the materials array
var material = new THREE.MeshBasicMaterial({ transparent:true, color: Number(color), opacity:opacity});
this.materials.push(material);
//set the material index of each face so a merge knows which material to apply
for ( var i = 0; i < geometry.faces.length; i ++ ) {
geometry.faces[i].materialIndex = this.materials.length-1;
}
//create a mesh from the geometry and material.
var cube = new THREE.Mesh( geometry, material);
//leaf is a simple lat/lng to pixel position converter
var actualMarkerPos = leaf.getPoint(latLng);
//apply the position on a 5000x5000 cartesian plane
var extent = 5000;
cube.position.setZ((actualMarkerPos[1] * extent) - extent/2);
cube.position.setX(-((actualMarkerPos[0] * extent) - extent/2));
cube.position.setY(height/2);
//update the matrix and add the cube to the totalGeom bucket
cube.updateMatrix();
totalGeom.merge( cube.geometry, cube.matrix);
}
The only reason you are seeing improved performance is because, after the merge, there is only one material. If you want your scene to have multiple materials you should not merge.
Adding to the same group:
var group = new THREE.Object3D();
group.add (object1);
group.add (object2);
group.add (object3);
I need to apply a texture on a ExtrudeGeometry object.
The shape is a circle and the extrude path is composed of 2 vectors :
One for the top.
One for the bottom.
I didn't choose cylinderGeometry because I need to place top/bottom sections of my geometry at precise positions and because the geometry created will not be always purely vertical (like a oblique cylinder for example).
Here is a picture of a section (one top vector, one bottom vector and a shape extruded between these 2 vectors).
and a picture of the texture I'm trying to apply.
All I want to do is to wrap this picture on the vertical sides of my object just one time.
Here is my code :
var biVectors = [ new THREE.Vector3( this.startVector.x, this.startVector.y, this.startVector.z ) , new THREE.Vector3( this.endVector.x, this.endVector.y, this.endVector.z ) ];
var wellSpline = new THREE.SplineCurve3(biVectors);
var extrudeSettings = {
steps : 1,
material: 0,
extrudeMaterial: 1,
extrudePath : wellSpline
};
var pts = [];
for (var i = 0; i <= this.segments; i++) {
var theta = (i / this.segments) * Math.PI * 2;
pts.push( new THREE.Vector3(Math.cos(theta) * this.diameter , Math.sin(theta) * this.diameter, 0) );
}
var shape = new THREE.Shape( pts );
var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings );
var texture = THREE.ImageUtils.loadTexture( 'textures/sampleTexture2.jpg' );
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.flipY = false;
var material = new THREE.MeshBasicMaterial( { map: texture } );
var slice = new THREE.Mesh( geometry, material );
var faceNormals = new THREE.FaceNormalsHelper( slice );
console.log("face normals: ", faceNormals);
myCanvas.scene.add( faceNormals );
slice.parentObject = this;
myCanvas.scene.add( slice );
this.object3D = slice;
}
Now, as you can see, the mapping is not correct at all.
I've read a lot of information about this problem the last 3 days. But I'm running out of options as I'm new to THREE.JS.
I think I have to redefine the UV coordinates but I have no clue how to do this.
It seems that wrapping a texture on a cylinder like object is anything but easy in THREE.JS.
Can someone please help me on this issue ?