Bounding Box of a stl object in three.js - javascript

Good day everyone,
I have come across several posts about this topic but no solution would work. I have an object that I load with the help of the STLLoader in three.js for which I would like to get a bounding box.
// Add stl objects and a name
function addSTLObject(url, name) {
var loader = new THREE.STLLoader();
loader.load(url, function (geometry) {
var material = new THREE.MeshPhongMaterial({ color: 0xff5533 });
var mesh = new THREE.Mesh(geometry, material);
// To scale element, use:
// mesh.scale.set(0.01, 0.01, 0.01);
// Add a name to the object to find it if it needs to be removed
mesh.name = name;
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = 0;
scene.add(mesh);
});
}
and I am loading this object as follows:
addSTLObject('model/cases/iphone5.stl', 'phone-model');
var phoneModelAdded = scene.getObjectByName('phone-model', true);
Now I have tried the solutions provided here: https://github.com/mrdoob/three.js/issues/3471 and here Any way to get a bounding box from a three.js Object3D?
var bbox = new THREE.Box3().setFromObject(phoneModelAdded);
var geometry = phoneModel.children[0].children[0].geometry;
geometry.computeBoundingBox();
While the first solution gives me an error saying "Cannot read property 'updateMatrixWorld' of undefined", the second gives me no error at all but does nothing and if I try accessing the "geometry" property it says that it doesn't exist.
Does someone have a working solution?
Any help is appreciated.
Have a good day!
EDIT:
// Add stl objects and a name
function addSTLObject(url, name) {
var bbox;
var loader = new THREE.STLLoader();
loader.load(url, function (geometry) {
var material = new THREE.MeshPhongMaterial({ color: 0xff5533 });
var mesh = new THREE.Mesh(geometry, material);
// To scale element, use:
// mesh.scale.set(0.01, 0.01, 0.01);
// Add a name to the object to find it if it needs to be removed
mesh.name = name;
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = 0;
bbox = new THREE.Box3().setFromObject(mesh);
scene.add(mesh);
return bbox;
});
}
and aftwards
var bbox = addSTLObject('model/cases/iphone5.stl', 'phone-model');
scene.add(bbox);
Error: "THREE.Object3D.add: object not an instance of THREE.Object3D"
EDIT 2:
var bbox, bboxComputed = false;
function addSTLObject(url, name) {
var bbox;
var loader = new THREE.STLLoader();
loader.load(url, function (geometry) {
var material = new THREE.MeshPhongMaterial({ color: 0xff5533 });
var mesh = new THREE.Mesh(geometry, material);
// To scale element, use:
// mesh.scale.set(0.01, 0.01, 0.01);
// Add a name to the object to find it if it needs to be removed
mesh.name = name;
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = 0;
bbox = new THREE.BoundingBoxHelper(mesh);
bbox.update();
bboxComputed = true;
scene.add(mesh);
});
}
addSTLObject('model/cases/iphone5.stl', 'phone-model');
var myInterval = setInterval( function(){
if( bboxComputed ) {
alert( bbox.box.min, bbox.box.max, bbox.box.size() );
scene.add(bbox);
clearInterval( myInterval );
bboxComputed = false;
}
}, 100 );
This won't work.
EDIT 3:
I was trying to set up a function that would have everything I need and give back an object with all of the calculated information:
function calculateSTLProperties(url, name) {
var STLObject, STLbbox, STLComputed = false, STLGeometry;
var loader = new THREE.STLLoader();
loader.load(url, function (geometry) {
var material = new THREE.MeshPhongMaterial({ color: 0xff5533 });
var mesh = new THREE.Mesh(geometry, material);
// To scale element, use:
// mesh.scale.set(0.01, 0.01, 0.01);
// Add a name to the object to find it if it needs to be removed
mesh.name = name;
mesh.position.x = 0;
mesh.position.y = 0;
mesh.position.z = 0;
// Compute a bounding box for the element
STLbbox = new THREE.BoundingBoxHelper(mesh);
STLbbox.update();
// Get the geometry of the case
STLGeometry = geometry;
STLComputed = true;
});
// Set an interval to wait for the corresponding bounding box and geometry to be computed
var myInterval = setInterval( function(){
if( STLComputed ) {
STLObject = {
"geometry" : STLGeometry,
"bbox" : STLbbox,
"x" : STLbbox.box.size().x,
"y" : STLbbox.box.size().y,
"z" : STLbbox.box.size().z
};
clearInterval( myInterval );
bboxComputed = false;
}
}, 100 );
return STLObject;
}
Unfortunately, somehow the object doesn't get passed through and I end up with an "undefined" in the end when trying to save it:
var STLObjectLoaded = calculateSTLProperties('model/cases/iphone5.stl', 'phone-model');
console.log(STLObjectLoaded);
What am I missing?

Model loading is asynchronous so all you computations should happen after the model has loaded. Add a callback, which is called when the model has loaded and add there the call to the bbox. The way you have it the scene.getObjectByName() call is being made with an empty object which you also verify ("if I try accessing the "geometry" property it says that it doesn't exist")
Update:
Use:
var bbox = new THREE.BoundingBoxHelper( mesh ); bbox.update();
scene.add( bbox );
from inside the loader.load() function.
The setFromObject() call only create the bounding box but not the geometry for the bbox.
Update II
If you want the bbox to be available outside your loader.load() function you would need to use a global variable
var bboxComputed = false;
which you set to true right after you compute the bbox inside your loader.load() function:
scene.add( bbox );
bboxComputed = true;
Then in your main instead of using:
console.log( bbox.box.min, bbox.box.max, bbox.box.size() );
you would use something like:
var myInterval = setInterval( function(){
if( bboxComputed ) {
console.log( bbox.box.min, bbox.box.max, bbox.box.size() );
clearInterval( myInterval );
}
}, 100 );
I am setting a delay of 0.1 second and when the bbox has finally been computed I clear the interval so that it does not run forever.

As of three.js R125, the recommended way to do this is with the loadAsync method, which is now native to three.js:
https://threejs.org/docs/#api/en/loaders/Loader.loadAsync
That method returns a promise. You could then use a 'then' to get the geometry of the STL and create the mesh. You could also use a traditional callback as in earlier answers, or an async/await structure, but I think the example below using the native three.js method is the simplest way. The example shows how you can get geometry to a global variable once the promise is resolved and the STL file is loaded:
// Global variables for bounding boxes
let bbox;
const loader = new STLLoader();
const promise = loader.loadAsync('model1.stl');
promise.then(function ( geometry ) {
const material = new THREE.MeshPhongMaterial();
const mesh = new THREE.Mesh( geometry, material );
mesh.geometry.computeBoundingBox();
bbox = mesh.geometry.boundingBox;
scene.add( mesh );
buildScene();
console.log('STL file loaded!');
}).catch(failureCallback);
function failureCallback(){
console.log('Could not load STL file!');
}
function buildScene() {
console.log('STL file is loaded, so now build the scene');
// !VA bounding box of the STL mesh accessible now
console.log(bbox);
// Build the rest of your scene...
}

Related

How can I get the geometry from a gltf object

I have used three.js to get a gltf by gltfloader, and I want to create a particle system. I need to get the geometry object, how can I get it
function initModel() {
var planeGeometry = new THREE.PlaneGeometry(100, 100);
var planeMaterial = new THREE.MeshLambertMaterial({color: 0xaaaaaa,
side: THREE.DoubleSide});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.y = -.1;
plane.receiveShadow = true;
scene.add(plane);
var loader = new THREE.GLTFLoader();
loader.load('./../model/scene.gltf', function (gltf) {
gltf.scene.scale.set(10,10,10);
//how to get the geometry?
});
}
You can traverse the model to find the mesh, or use getObjectByName(MeshName) when you know the name, and pick the geometry from the mesh.
Something like
var geometry = getObjectByName('Plane001').geometry;
if the name of the mesh is Plane001
I have a simple helper method to find all children of a type from an object
findType(object, type) {
object.children.forEach((child) => {
if (child.type === type) {
console.log(child);
}
this.findType(child, type);
});
}
from the loader I would then call findType(gltf.scene, 'Mesh') to print out all meshes in the model

Three.js PLYLoader to save geometry to object (async issue)

I have an object in which I want to store the geometry contained on a Ply file, for which I have the following:
function MyObject(filename) {
var container = this.createObject(filename);
this.mesh = container.children[0];
this.geometry = this.mesh.geometry;
};
MyObject.prototype.createObject = function(filename) {
var loader = THREE.PLYLoader();
var container = new THREE.Object3D();
loader.load(filename, function ( geometry ) {
var material = new THREE.MeshStandardMaterial( { color: 0x0055ff } );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.y = -0.25;
mesh.rotation.x = -Math.PI / 2;
mesh.scale.multiplyScalar( 0.04 );
container.add( mesh );
});
return container;
};
But the loader is run asynchronously and therefore when creating this.mesh it becomes "undefined" making the program fail at this.geometry.
I thought the return function would fix this but it does not and after reading about asynchronicity in javascript I haven't found the solution. Does anyone know how to implement three.js loaders with objects?

Three.js transparency model texture bug

I have searched over all internet and tried a lot of ways, but no results..
How to remove texture transparency bug? Check on image
So, on 3dsmax model looks okey. I have converted to .js format from .obj with python script in order .png files were transparent (.obj does not make transparence).
How to solve my problem? Thank you
var loader = new THREE.JSONLoader();
loader.load('tree_model.js', function(geometry, materials) {
var material = new THREE.MeshFaceMaterial(materials);
var object = new THREE.Mesh(geometry, material);
object.traverse( function ( child ) {
if ( child.material instanceof THREE.MeshPhongMaterial ) {
// this code is unattainable, but anyway without if (..) it does not work
child.material.alphaTest = 0.5;
child.material.depthWrite = false;
child.material.depthTest = false;
child.material.side = THREE.BackSide;
child.material.transparent = true;
}
});
scene.add(object);
});
});
And renderer:
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, premultipliedAlpha: true });
You have to set alphaTest on your materials. Additionally setting the leafes and branches to THREE.DoubleSide ensures that they are not disappearing when viewed from the other side.
The code you have posted contains various errors, so replace it with this:
var loader = new THREE.JSONLoader();
loader.load('model/Elka.js', function(geometry, materials) {
for( var i = 0; i < materials.length; i ++ ) {
var material = materials[ i ];
material.alphaTest = 0.5;
material.side = THREE.DoubleSide;
// not-so-good practice
if ( material.name === "NorwaySpruceBark" ) {
material.transparent = false;
}
}
var material = new THREE.MeshFaceMaterial(materials);
var object = new THREE.Mesh(geometry, material);
scene.add(object);
});
To further reduce transparency artefacts, set the trunk to non-transparent. Your model should contain the correct material settings, so this is kind of a bad practice.
Edit: Setting alpha and premultipliedAlpha in the renderer is not necessary for this problem.
Result:

Threejs TextureLoader CubeGeometry

I am learning threejs and I want that my cube has 6 different textures on each side. I did make that with loadTexture
var material3 = new THREE.MeshPhongMaterial( {map: THREE.ImageUtils.loadTexture('textures/ps.png')} );
I did save 6 of this materials in array and then use THREE.MeshFaceMaterial. But there is problem with THREE.ImageUtils.loadTexture because it is deprecated and I should use THREE.TextureLoader and I do not know how to load 6 textures in this way.
This is what I have:
function texture()
{
var loader = new THREE.TextureLoader();
loader.load( 'textures/ps.png', function ( texture )
{
var geometry = new THREE.CubeGeometry( 10, 10, 10 );
var material = new THREE.MeshBasicMaterial( { map: texture, overdraw: 0.5 } );
mesh = new THREE.Mesh( geometry, material );
mesh.position.z = -50;
scene.add( mesh );
} );
}
I think this is close to what you are looking for:
function MultiLoader(TexturesToLoad, LastCall, ReturnObjects) {
if (TexturesToLoad.length == 0) return;
if (!ReturnObjects) ReturnObjects = [];
var loader = new THREE.TextureLoader()
//Pop off the latest in the ,
//you could use shift instead if you want to read the array from
var texture = TexturesToLoad.shift()
loader.load(texture,
function (texture) {
ReturnObjects.push(texture);
if (TexturesToLoad.length > 0) {
MultiLoader(TexturesToLoad, LastCall, ReturnObjects)
} else {
LastCall(ReturnObjects)
}
},
LoadProgress,
LoadError);
}
function LoadProgress(xhr) {
console.log(('Lodaing ' + xhr.loaded / xhr.total * 100) + '% loaded ');
}
function LoadError(xhr) {
console.log('An error happened ');
}
call it with this
var TexturesToLoad = []
TexturesToLoad.push("../surfacemap.jpg")
TexturesToLoad.push("../normalmap.jpg");
TexturesToLoad.push("../spekularmap.jpg");
var ReturnedMaterials=[];
var ReturnMaterials=[];
var LastCall=function(ReturnedMaterials)
{
var surfaceMap = ReturnedMaterials[0];
var normalMap = ReturnedMaterials[1];
var specularMap = ReturnedMaterials[2];
var decalMaterial = new THREE.MeshPhongMaterial(
{
map: surfaceMap,
normalMap: normalMap,
normalScale: new THREE.Vector2( 1, 1 ),
specularMap: specularMap,
transparent:false,
wireframe: false
} );
var globeGeometry = new THREE.SphereGeometry(100.0, SPHERE_SIDES, SPHERE_SIDES);
mesh = new THREE.Mesh( globeGeometry, decalMaterial );
mesh.rotation.x=Math.PI/2;
};
MultiLoader(TexturesToLoad,LastCall,ReturnMaterials)
Explanation:
The new THREE.TextureLoader, uses a callback function. This ensures that the ressource you are using is really loaded when you need to add it.
Callbacks are difficult however if you want to use a lot of material.
above MultiLoader allows you to call recursivly and then call back to the function you want to use all your materials at. The materials are collected into an array (ReturnObjects) .
There are many ways to achieve it. I'll show you 2;
1)
Make your object (cube) using own vertices and faces
.vertices.push( new THREE.Vector3( x, y, z ) );
.faces.push( new THREE.Face3( x, y, z ) );
I've prepared an example in jsfiddle.
2)
Using UV map. So first you need to prepare object with UV map in 3D software like Blender and export it as a JSON file.
I've also prepared an example in jsfiddle.
If you are not familiar with UV map or Blender, check toturials.

Issue accessing object on mouseover in THREE.js

I am working with the threex domevents by Jeromeetienne: https://github.com/jeromeetienne/threex.domevents to handle my hover over of rendered in objects in my canvas
I am having some issue when updating the perspective view when the user changes the type of object, my dom sets the event listener but it seems to never get called. Here is the overview:
When user first loads the page, we set our camera:
// setup local vars
var camera = new THREE.PerspectiveCamera(40,window.innerWidth / window.innerHeight,1,10000);
camera.position.set(500, 800, 1300);// 1st = swing (left or right) 2nd = up or down 3rd = zoom
camera.lookAt( new THREE.Vector3());
camera.positionID = 1;// default
camera.positionType = 'sideView';// default
this.cam = camera;
Than we set our renderer:
renderer = new THREE.CanvasRenderer();
renderer.setClearColor( 0xf0f0f0 );
renderer.setSize( window.innerWidth, window.innerHeight );
Now when a user clicks the canvas, an object is added. We first get our positioning and call a intersectofobjects to see if we are actually on the canvas (mousedown):
event.preventDefault();
this.mouse.x = ( event.clientX / this.renderer.domElement.width ) * 2 - 1;
this.mouse.y = - ( event.clientY / this.renderer.domElement.height ) * 2 + 1;
this.raycaster.setFromCamera( this.mouse, this.camera.cam );
var intersects = this.raycaster.intersectObjects( this.blocks );
After we actually figure out that we are on the plane, we setup our object:
var voxel = new THREE.Mesh( this.room.cubeGeometry.clone(), this.room.cubeMaterial.clone() );
voxel.geometry.computeBoundingBox();
voxel.position.copy( intersect.point ).add( intersect.face.normal );
voxel.position.divideScalar( this.room.divideScalar ).floor().multiplyScalar( this.room.multiplyScalar ).addScalar( this.room.addScalar );
After we setup our object we setup the event listeners on the object
// get our dom event to attach to object
var domEvents = new THREEx.DomEvents(this.camera.cam, this.renderer.domElement);
// DOM events for inside 3d rendering
domEvents.addEventListener(voxel, 'mouseover', this.onDocumentMouseOverCube.bind(this),false);
domEvents.addEventListener(voxel, 'mouseout', this.onDocumentMouseOutCube.bind(this),false);
this.scene.add( voxel );
this.blocks.push( voxel );
this.rooms.push(voxel);
this.render();
This works perfect on first use. It only seems to stop working when the user changes the object that they want to place on the screen, here is how.
We have a gui object that handles our connection with the UI and the core, when someone chooses to add a different object, we call:
jQuery(".changeRoomType.active").removeClass('active');
jQuery(event.srcElement).toggleClass('active');
this.canvas.room.updateRoomType(event.srcElement.id);
Now when room.updateRoomType...() is called it initiates an object like this:
var cubeGeometry = new THREE.BoxGeometry( 100, 50, 100,2,1,2 );// moving to roomObj
var cubeMaterial = new THREE.MeshLambertMaterial( { color: 0x00ff80,overdraw: 1 } ); // moving to roomObj
var cubeTmpHoverMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff80, transparent: true, overdraw: 0, opacity:0.5});// used to give a transparency to the cube tmp view // moving to roomObj
var divideScalar = 50;
var multiplyScalar = 50;
var addScalar = 25;
var additionalZ = 25;
var additionalX = 25;
this.divideScalar = divideScalar;
this.multiplyScalar = multiplyScalar;
this.addScalar = addScalar;
var colorNormal = 0x00ff80;
var colorHover = 0xE4E4E4;
var colorTmpHover = 0xEAEAEA;
var colorRemove = 0xE23434;
this.colorNormal = colorNormal;
this.colorHover = colorHover;
this.colorTmpHover = colorTmpHover;
this.colorRemove = colorRemove;
this.additionalZ = additionalZ;
this.additionalX = additionalX;
this.cubeGeometry = cubeGeometry;
this.cubeMaterial = cubeMaterial;
this.cubeTmpHoverMaterial = cubeTmpHoverMaterial;
Now after this point the mouseover and mouseout on the object seem to never work anymore. My question is why is this? Am I not updating my camera perspective after changing the object type to be displayed?
EDIT:
Please note that our initiated object that is first selected on page load is the following:
var cubeGeometry = new THREE.BoxGeometry( 50, 50, 50,1,1,1 );// moving to roomObj
var cubeMaterial = new THREE.MeshLambertMaterial( { color: 0x00ff80, overdraw: 1 } ); // moving to roomObj
var cubeTmpHoverMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff80, transparent: true, overdraw: 0,opacity:0.5});// used to give a transparency to the cube tmp view // moving to roomObj
var divideScalar = 50;
var multiplyScalar = 50;
var addScalar = 25;
var additionalZ = 0;
var additionalX = 0;
this.additionalZ = additionalZ;
this.additionalX = additionalX;
var colorNormal = 0x00ff80;
var colorHover = 0xE4E4E4;
var colorTmpHover = 0xEAEAEA;
var colorRemove = 0xE23434;
this.colorNormal = colorNormal;
this.colorHover = colorHover;
this.colorTmpHover = colorTmpHover;
this.colorRemove = colorRemove;
this.cubeGeometry = cubeGeometry;
this.cubeMaterial = cubeMaterial;
this.cubeTmpHoverMaterial = cubeTmpHoverMaterial;
The other object that was called above shown in the original question demonstrates our other object size. After this is called it seems to stop recognizing the location of our mouseover and mouseout on the object and or its being placed on someplace else it should not be.

Categories

Resources