How to smooth mesh triangles in STL loaded BufferGeometry - stl

I´m trying to load some STL files using Three.js. The models are loaded correctly, but there are too many triangles that I would like to merge/smooth.
I had successfully applied smooth loading terrains in other 3D formats, but I can´t do it with the BufferGeometry that results from loading an STL file with the STLLoader.
_
var material = new THREE.MeshLambertMaterial( { ... } );
var path = "./models/budah.stl";
var loader = new THREE.STLLoader();
loader.load( path, function ( object ) {
object.computeBoundingBox();
object.computeBoundingSphere();
object.computeFaceNormals();
object.computeVertexNormals();
object.normalizeNormals();
object.center();
// Apply smooth
var modifier = new THREE.SubdivisionModifier( 1);
var smooth = smooth = object.clone();
smooth.mergeVertices();
smooth.computeFaceNormals();
smooth.computeVertexNormals();
modifier.modify( smooth );
scene.add( smooth );
});
This is what I tried, it throws an error: Uncaught TypeError: smooth.mergeVertices is not a function
If I comment the "mergeVertices()" line, what I get is a different error: Uncaught TypeError: Cannot read property 'length' of undefined in SubdivisionsModifier, line 156.
It seems that the sample codes I´m trying are outdated (this is happenning a lot recently due to the massive changes in the Three.JS library). Or maybe I´m forgetting something. The fact is that the vertices seems to be null..?
Thanks in advance!

It seems I was looking in the wrong direction: smoothing the triangles has nothing to do with the SubdivisionsModifier... What I needed was easier than that, just compute the vertex BEFORE applying the material, so it can use SmoothShading instead of FlatShading (did I got it right?).
The problem here was that the BufferGeometry returned by the STLLoader has not calculated vertices/vertex, so I had to do it manually. After that, apply mergeVertices() just before computeVertexNormals() and voilà! The triangles dissappear and everything is smooth:
var material = new THREE.MeshLambertMaterial( { ... } );
var path = "./models/budah.stl";
var loader = new THREE.STLLoader();
loader.load( path, function ( object ) {
object.computeBoundingBox();
object.computeVertexNormals();
object.center();
///////////////////////////////////////////////////////////////
var attrib = object.getAttribute('position');
if(attrib === undefined) {
throw new Error('a given BufferGeometry object must have a position attribute.');
}
var positions = attrib.array;
var vertices = [];
for(var i = 0, n = positions.length; i < n; i += 3) {
var x = positions[i];
var y = positions[i + 1];
var z = positions[i + 2];
vertices.push(new THREE.Vector3(x, y, z));
}
var faces = [];
for(var i = 0, n = vertices.length; i < n; i += 3) {
faces.push(new THREE.Face3(i, i + 1, i + 2));
}
var geometry = new THREE.Geometry();
geometry.vertices = vertices;
geometry.faces = faces;
geometry.computeFaceNormals();
geometry.mergeVertices()
geometry.computeVertexNormals();
///////////////////////////////////////////////////////////////
var mesh = new THREE.Mesh(geometry, material);
scene.add( mesh );
});

Than, you can convert it back to BufferGeometry, because it's more GPU/CPU efficient for more complex models:
var geometry = new THREE.Geometry();
geometry.vertices = vertices;
geometry.faces = faces;
geometry.computeFaceNormals();
geometry.mergeVertices();
geometry.computeVertexNormals();
var buffer_g = new THREE.BufferGeometry();
buffer_g.fromGeometry(geometry);
var mesh = new THREE.Mesh(buffer_g, material);
scene.add( mesh )

Happened this issue for me while loading an obj file. If you have a 3d software like 3dsmax:
Open the obj file,
Go to polygons selection mode and select all polygons.
Under the Surface properties panel, click 'Auto Smooth' button.
Export the model back to obj format
Now you won't have to call the functions geometry.mergeVertices() and geometry.computeVertexNormals();. Just load the obj and add to the scene, mesh will be smooth.
EDIT:
My obj files had meshphongmaterial by default and on changing the shading property to value 2 the mesh became smooth.
child.material.shading = 2

STL does not support vertex index.
That is reason it has duplicated vertex of all triangles.
Each vertex has its normal as triangle normal.
As a result, at same position( multiple very closed vertices), there is multiple normal value.
This leads to non-smooth surface of geometry when using Normal for lighting calculation.

Related

ThreejS: Uncaught TypeError: e.OBJMTLLoader is not a constructor

I am trying to replace a cube mesh with a car mesh using .obj and .mtl files (using three.js)
But every everytime I got this error:
Uncaught TypeError: e.OBJMTLLoader is not a constructor
I made sure to include the library () and there is no typo.
This is how I load the mesh model:
var loader = new THREE.OBJMTLLoader();
loader.load('./toycar.obj', './toycar.mtl',
function (object) {
toycar = object;
toycar.rotateX(-12); //toycar.rotateZ(-10.99);
toycar.rotateY(4.718);
scene.add(toycar);
});
and this is how I use it to move the model on the y axis:
if (toycar != undefined){
toycar.position.y = disp * 0.07; // z for rightLeft, y for upDown
var relativeCameraOffset = new THREE.Vector3(5, 0, 0); // change camera offset
var cameraOffset = relativeCameraOffset.applyMatrix4(toycar.matrixWorld);
camera.position.x = cameraOffset.x;
camera.position.y = cameraOffset.y;
camera.position.z = cameraOffset.z;
camera.lookAt(toycar.position);
}
At this point, I am not sure what caused this error to happen.
THREE.OBJMTLLoader does not exist in the repository. There is only OBJLoader and MTLLoader. The latter one is only required if you want to load materials, too.
I suggest you study webgl_loader_obj_mtl to show how both are used. Typical code looks like so:
new MTLLoader().load( 'materials.mtl', function ( materials ) {
materials.preload();
new OBJLoader()
.setMaterials( materials )
.load( 'object.obj', function ( object ) {
scene.add( object );
} );
} );

How to get states of the gltf texture rendered with cesium

I am the newbie and have a 3dsmax model that converted to gltf with lots of texture images, after loading with cesium and flyto the model, the white bone loaded first, then it'll take long time to render the texture. I want to show a loading image before all textures rendered. Is there anyway to get the texture rendering states?
If you're using the model directly as a graphics primitive (as opposed to using it on a Cesium Entity), then there's a Model.readyPromise that will tell you when the model has finished loading.
Here's a Sandcastle Demo for Cesium 1.54:
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
var model;
var modelUrl = '../../../../Apps/SampleData/models/GroundVehicle/GroundVehicle.glb';
var height = 0.0;
var heading = 0.0, pitch = 0.0, roll = 0.0;
var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
var origin = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height);
var modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(origin, hpr);
scene.primitives.removeAll(); // Remove previous model
model = scene.primitives.add(Cesium.Model.fromGltf({
url : modelUrl,
modelMatrix: modelMatrix
}));
console.log('Model is loading...');
model.readyPromise.then(function(model) {
console.log('Model loading complete.');
// Zoom to model
var camera = viewer.camera;
var controller = scene.screenSpaceCameraController;
var r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near);
controller.minimumZoomDistance = r * 0.5;
var center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3());
var heading = Cesium.Math.toRadians(230.0);
var pitch = Cesium.Math.toRadians(-20.0);
camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, r * 2.0));
}).otherwise(function(error){
console.error(error);
});

How can I get 3D object dimensions in ThreeJS?

I'm developing an AR application using WebRTC (webcam access), JSARToolKit (marker detection) and threeJS (3D library).
I want to place 3D objects (exported from Maya using threejs maya exporter) in the center of the detected marker.
This is the code where I load the 3D object using JSONLoader:
// load the model
var loader = new THREE.JSONLoader;
var object;
//var geometry = new THREE.BoxGeometry(1, 1, 1);
loader.load('js/cube.js', function(geometry, materials){
var material = new THREE.MeshFaceMaterial(materials);
object = new THREE.Mesh(geometry, material);
object.position.x -= ***3DobjectWidth/2***;
object.position.y -= ***3DobjectHeight/2***;
object.position.z -= ***3DobjectDepth/2***;
scene.add(object);
});
I need to get width, height and depth of the object to change his position (see 3DobjectWidth ecc).
Any suggestions?
The object size will be placed at geometry.boundingBox. But it has to be generated once.
try this.
geometry.computeBoundingBox();
var bb = geometry.boundingBox;
var object3DWidth = bb.max.x - bb.min.x;
var object3DHeight = bb.max.y - bb.min.y;
var object3DDepth = bb.max.z - bb.min.z;
Geometry has a .center() method. That might be more efficient and simpler if you need to center many meshes based on same geometry. It also calls computeBoundingBox for you.

Three.js JSONLoader blender model error: property 'length' undefined

I have been trying out Three.js lately and i used the exporter addon for Blender to test out making models in blender and exporting so i can use them in a three.js program.
I included the add-on to blender, and using just the basic cube model of blender, exported it to .json as the exporter says. Then i imported the model into my three.js using this as a guide
but that gave me an error:
Uncaught TypeError: Cannot read property 'length' of undefined.
Ive already searched online and tried a few different approaches(like including a materials in the function call of the loader) but nothing seems to work.
I also checked stackoverflow for answers but so far nothing seems solved. If anyone would clarify what im doing wrong I would be very grateful.
The code for my three.js program:
var WIDTH = 1000,
HEIGHT = 1000;
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000;
var radius = 50,
segments = 16,
rings = 16;
var sphereMaterial =
new THREE.MeshLambertMaterial(
{
color: 0xCCCCCC
});
var sphere = new THREE.Mesh(
new THREE.SphereGeometry(
radius,
segments,
rings),
sphereMaterial);
var pointLight =
new THREE.PointLight(0x660000);
var $container = $('#container');
var renderer = new THREE.WebGLRenderer();
var camera =
new THREE.PerspectiveCamera(
VIEW_ANGLE,
ASPECT,
NEAR,
FAR);
var scene = new THREE.Scene();
var loader = new THREE.JSONLoader(); // init the loader util
scene.add(camera);
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
scene.add(pointLight);
camera.position.z = 300;
renderer.setSize(WIDTH, HEIGHT);
$container.append(renderer.domElement);
window.requestAnimFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
loader.load('test.json', function (geometry, materials) {
var material = new THREE.MeshFaceMaterial(materials);
var object = new THREE.Mesh(geometry, material);
scene.add(object);
});
(function animloop() {
requestAnimFrame(animloop);
renderer.render(scene, camera);
})();
When you export, change the Type from buffergeometry to Geometry (Blender 2.76)
In my experience so far with the exporter, you have to be very careful which boxes you tick!
This length error is usually because you are either not exporting the vertexes, or exporting the scene rather the object.
I have the same issue with the new exporter.
If select SCENE then I usually get "Cannot read property 'length' of undefined"
If I select only the object and the materials and vertices. It usually just seems to get stuck forever.
UPDATE
Check this response to this issue!
Threejs blender exporter exports in wrong format
As roadkill stated earlier, exporting as Geometry instead of BufferedGeometry works.
Also note that loading JSON model loading is handled asyncronously (the callback is only called only when the operation is completed, the rest of your code keeps running). The model won't exist for the first frame or two, so check that your model exists before doing any operations with it inside the animation loop.

How to Save a Shape (or Sprite) to a Folder After Creation?

I am trying to create a county map of Illinois using x,y coordinates from a shp file. I have the x,y coordinates and the county names saved in a CSV file that I have no problem reading in via ActionScript 3 (and the map looks great), but in order to save time in my future application I would like to save each county shape as a permanent shape in my application and give them labels, i.e. Sprite1 (label='Champaign'). Is this possible? If so, any help would be greatly appreciated.
In case this is not possible I am trying an alternate solution: I create a new sprite (var spr:Sprite = new Sprite();) for each county, draw it using spr.graphics.drawPath, and give it a name (spr.name='Champaign') and then 'push' it to a vector of sprites (var xy_sprites:Vector. = new Vector.();). This would be great, but it doesn't work when I try to loop through each sprite in the vector and add an EventListener to that Sprite to pop up the name when you MouseOver any of the counties. Is the Sprite data type not the correct way to go about this or am a missing something about Sprites?
Some of my code to draw the shapes and save in a vector:
function drawXYMap(str:String):Vector.<Sprite> {
var arr:Array = str.split("\n");
var xy_Sprites:Vector.<Sprite> = new Vector.<Sprite>();
for (var i:int=0; i<arr.length-1; ++i) {
var spr:Sprite = new Sprite();
spr.graphics.lineStyle(1.0, 0x000000);
spr.graphics.beginFill(0x666699);
arr[i] = arr[i].split(',');
var xy_commands:Vector.<int> = new Vector.<int>();
var xy_coord:Vector.<Number> = new Vector.<Number>();
for (var j:int=1; j<arr[i].length; ++j) {
xy_coord.push(arr[i][j]*6);
if (j==1) {
xy_commands.push(1); // 1 is a move-to command
var cntry:String = arr[i][j-1] as String; //country name
}
else if (j % 2 == 1) {
xy_commands.push(2); // 2 is a line-to command
}
}
spr.graphics.drawPath(xy_commands, xy_coord);
spr.name = cntry;
xy_Sprites.push(spr);
addChild(spr);
}
return xy_Sprites;
}
But I can't seem to add an Event Listener to each sprite in the vector of sprites I created:
var str:String = csvLoader.data as String;
var xy_spr:Vector.<Sprite> = drawXYMap(str);
for each (var spr:Sprite in xy_spr) {
spr.addEventListener(MouseEvent.MOUSE_OVER,onOver);
}
function onOver(e:MouseEvent):void {
spr.alpha = .25;
}
Any help would be great.
Thanks!
You could put all of your x, y co-ords into a Vector data structure, and then use the graphics.drawPath() method, to iterate over the co-ords any time you instantiate the county.
If you created a class that extended Shape, you could add a Vector that references the CSV file, and every time you create the shape, it automatically draws the path.
Check out this useful document from Adobe:
http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WSA8BD9022-BAB1-46d3-9B26-0D9649743C8E.html
PS don't forget to use the proper drawing commands for the drawPath(). You should be able to interact with it as normal if you do.