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

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?

Related

How to apply texture on an object loaded by OBJLoader?

I'm currently working on my little project using three.js, but I am having hard time mapping texture on objects loaded by THREE.OBJLoader. There is no such problem for three.js built in geometry. I'm really confused now...
// Load the model from obj file [teapot, 74KB]
var onProgress = function ( xhr ) {
};
var onError = function ( xhr ) {
};
//var oCubeMap = THREE.ImageUtils.loadTexture(imageNames);
var objTexture = new THREE.ImageUtils.loadTexture(textureImage);
var oMaterial = new THREE.MeshLambertMaterial( { map: objTexture } );
var ooMaterial = new THREE.MeshPhongMaterial( { color: 0x99CCFF } );
var thisTexture = THREE.ImageUtils.loadTexture( textureImage, {}, function() {
renderer.render(scene, camera);
} );
var loader = new THREE.OBJLoader();
loader.load(teapot, function (object) {
object.position.set(8, -5, -25);
object.scale.set(0.04, 0.04, 0.04);
object.rotation.set(0, 180 * Math.PI / 180, 0);
// object.color = '0x99CCFF';
object.material = ooMaterial;
// object.texture = thisTexture;
scene.add(object);
}, onProgress, onError);
As you can see I've tried several things like color, material and texture but nothing worked so far. Can anyone tell me how to apply a texture onto this object?
OBJLoader returns an object with children. Add the following to your loader callback function:
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material.map = texture;
}
} );
Make sure your model geometry has UVs, otherwise textures are not supported.
three.js r.73

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.

Bounding Box of a stl object in three.js

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...
}

Rotating .obj file OBJMTLLoader three.js

I used the OBJMTLLoader class for one obj file and rotation worked well around a fixed point on the object by using object.rotation.y -= 0.5. Using the same code (minus changing the camera position), I replaced the .obj file with another and the rotation is now going in a circular motion, like around the camera instead of staying in place. Any idea why when I used the same code?
Thanks
EDIT:
var OBJLoaded;
function init()
{
container = document.getElementById('player');
camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 1, 2000);
camera.position.x = 110;
camera.position.z = -160;
camera.position.y = 15;
// camera.position.z = 40;
// camera.position.y = 2;
//scene
scene = new THREE.Scene();
var ambient = new THREE.AmbientLight( 0x444444 );
scene.add( ambient );
var directionalLight = new THREE.DirectionalLight( 0xffffff );
directionalLight.position.set( 100, 90, 200 );
scene.add( directionalLight );
//model
var loader = new THREE.OBJMTLLoader();
//loader.load('./assets/Stereo_LowPoly.obj', './assets/Stereo_LowPoly.mtl', function(object)
loader.load('./assets/studio_beats.obj', './assets/studio_beats.mtl', function(object)
{
OBJLoaded = object;
console.log(object);
scene.add( object );
});
renderer = new THREE.WebGLRenderer({alpha: true});
renderer.setClearColor(0x000000, 0);
renderer.setSize($('#player').width(), $('#player').height());
container.appendChild(renderer.domElement);
scene.add(camera);
}
function animateBoombox()
{
requestAnimationFrame(animateBoombox);
render();
}
function render()
{
var rotSpeed = 0.004;
if (OBJLoaded)
{
OBJLoaded.rotation.y -= rotSpeed;
}
renderer.render(scene, camera);
}
The parts commented (camera and object load) is for the previous object that was loaded. That works fine, but the uncommented partdoes not work the same.
The object you loaded has a pivot point which came from the model creater software... You need to change the pivot point of the object before you load it with three.js.
If you cannot, you should do it like i had in loader callback:
var loader = new THREE.OBJMTLLoader();
loader.load('your_file.obj', 'your_file.mtl', function (object) {
object.traverse(function (child) {
child.centroid = new THREE.Vector3();
for (var i = 0, l = child.geometry.vertices.length; i < l; i++) {
child.centroid.add(child.geometry.vertices[i].clone());
}
child.centroid.divideScalar(child.geometry.vertices.length);
var offset = child.centroid.clone();
child.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-offset.x, -offset.y, -offset.z));
child.position.copy(child.centroid);
child.geometry.computeBoundingBox();
});
});
Then rotate your object...

three.js load object/model and then manipulate sub-parts/children

I have a 3D model of a robot arm, that I want displayed and manipulated in the browser.
My question is: how do I load the model into three.js, so that I can manipulate all sub-parts of the robot arm.
As an example I have a rotary motor and a shaft attached as an assembly in Inventor.
Image: http://i.stack.imgur.com/custz.png
This is exported as an stl file and imported in Three.js using STLLoader.js.
Image: http://i.stack.imgur.com/nLmBe.png
I want to know how I can manipulate the shaft to turn to a specified angle.
I have loaded the model using the following code:
<div id="container"></div>
<script src="three.js\build\three.min.js"></script>
<script src="js\STLLoader.js"></script>
<script>
// Set size variables
var SIZE_x = 400, SIZE_y = 400;
// Set three main THREE variables
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, SIZE_x/SIZE_y, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
// Set renderer options
renderer.setSize(SIZE_x, SIZE_y);
renderer.setClearColor(0xEEEEEE, 1.0);
renderer.clear();
// Append to HTML Dom
//document.body.appendChild(renderer.domElement);
$('#container').append(renderer.domElement);
// Create light
var pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
scene.add(pointLight);
// Move camera
camera.position.x = 0;
camera.position.y = 20;
camera.position.z = 20;
var loader = new THREE.STLLoader();
loader.addEventListener( 'load', function ( event ) {
var geometry = event.content;
var material = new THREE.MeshLambertMaterial( { ambient: 0xff5533, color: 0xff5533 } );
var mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
mesh.traverse(function ( child ) {
//if ( child instanceof THREE.Mesh ) {
console.log("Hej: " + child);
//}
});
//scene.add( new THREE.Mesh( geometry ) );
console.log('Loaded');
} );
loader.load( 'models/AssemblySimple1.stl' );
// Render loop
var render = function () {
requestAnimationFrame(render);
camera.lookAt(scene.position);
renderer.render(scene, camera);
};
render();
</script>
Any points and hints are welcome. Also if there is a preferred export file format. I have both SolidWorks and Inventor at my disposal. Or if I've taken a completely wrong approach to the problem, please let me know of other ways.
Thanks
Three js has function called THREE.STLLoader() .This one can be used to load stl file.
Here is the way how it is loaded
var loader = new THREE.STLLoader();
var group = new THREE.Object3D();
loader.load("../lib/SolidFz.stl", function (geometry) {
console.log(geometry);
var mat = new THREE.MeshLambertMaterial({color: 0x7777ff});
group = new THREE.Mesh(geometry, mat);
group.rotation.x = -0.5 * Math.PI;
group.scale.set(0.6, 0.6, 0.6);
scene.add(group);
});
Here scene
var scene new THREE.Scene();
After that you have a created 3d object and loaded one added into that 3d object.Then you can control that 3d object as you wish. According to this way you can load several parts and do the what you want to do with that

Categories

Resources