Is it possible to load a JSON model once and add it to the scene multiple times with different scales, positions, etc?
If I add the Object3D() to an array, give a position and scale to the object in the array, add it to the scene, and then repeat this process, the position and scale are overwritten for every object in the array.
I can't think of anything that works, so I'm hoping someone could give me a working example of what I'm trying to accomplish.
Here's (one of many) of my failed attempts. Should give you a basic idea of what I'm trying to do, if my explanation wasn't sufficient.
function addModels(){
var models = [];
var model = new THREE.Object3D();
var modelTex = THREE.ImageUtils.loadTexture( "textures/model.jpg" );
var loader = new THREE.JSONLoader();
loader.load( "models/model.js", function( geometry ) {
mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { map: modelTex }) );
model.add(mesh);
} );
for(var i = 0; i < 5; i++){
model.scale.set(i,i,i);
model.position.set(i,i,i);
models[i] = model;
scene.add(models[i]);
}
}
You need to clone a model first and then set position and scale.
for(var i = 0; i < 5; i++){
var newModel = model.clone();
newModel.position.set(i,i,i);
newModel.scale.set(i,i,i);
scene.add(newModel);
}
Updated: Example how you can create json model without load : Fiddle example or just simple add loop inside load function.
You can create new meshes from the same geometries and materials:
loader.load( "models/model.js", function( geometry ) {
var mat = new THREE.MeshLambertMaterial( { map: modelTex });
for (var i = 0; i < 5; i++) {
var mesh = new THREE.Mesh( geometry, mat );
mesh.position.set(i, i, i);
mesh.scale.set(i, i, i);
scene.add(mesh);
}
});
Related
I was running into a problem when I was setting my light objects name light.name = 'globalLight' and im making variable that is lightGlobal = scene.getObjectByName('globalLight'); but when i tried to hide using lightGlobal.visible = false; it was just let the other light wasn’t hide the green one Is light0 and the blue one is light1 render example The rendered image example
The problem screenshot
As you can see the blue light is gone but the green one won’t hide
My code :
ambient = new THREE.AmbientLight( 0x404040 );
scene.add( ambient );
lightCam = new THREE.DirectionalLight( 0xffffff, 1.0 );
light0 = new THREE.PointLight( 0x0000ff, 1.0 );
light0.position.set( 4.0, 4.0, 4.0 );
light0.castShadow = true;
light0.name = "globalLight";
light1 = new THREE.PointLight( 0x00ff00, 1.0 );
light1.position.set( 4.0, 4.0, -4.0 );
light1.castShadow = true;
light1.name = "globalLight";
scene.add( light0, light1 );
globalLight = scene.getObjectByName( "globalLight", true );
globalLight.visible = false;
Object3D.name has to be unique. Hence, naming two lights in the same way is not supported. Object3D.getObjectByName() will return the first object that matches the given name.
One solution for this problem is to use Object3D.userData and define a custom property like:
light0.userData.tag = 'globalLight';
You can then consider to enhance Object3D by the following method:
THREE.Object3D.prototype.getObjectsByTag = function( tag, result ) {
// check the current object
if ( this.userData.tag === tag ) result.push( this );
// check children
for ( let i = 0, l = this.children.length; i < l; i ++ ) {
const child = this.children[ i ];
child.getObjectsByTag( tag, result );
}
return result;
};
I have a rather broad question, but no idea how to tackle that. So forgive me.
I am trying to have several (like 200 and more) objects and, let's just say, a container to the side of the field, where I draw the objects. Now what I want is, that each object has some non visual attributes and when I click on that object, the attributes should appear in that container.
Now I could go about it?
I mean, I know I can ask for the name of the selected object and then do a key value query from some dictionary. Question is, whether there is an easier way to go about it.
For the click event I used a library called threex.domevents, check the GitHub page for more information, the code for the event it's self explanatory.
First domevents needs to be initialized in your scene like this:
var domEvents = new THREEx.DomEvents(camera, renderer.domElement);
Then I created a custom Mesh object:
// random id
function genRandomId()
{
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
// random int for position
var min = -50;
var max = 50;
function genRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
// custom mesh --------------------------------------------
function MyMesh(geometry, material, destinationContainer) {
THREE.Mesh.call(this, geometry, material);
this.userData = {
foo1: genRandomId(),
foo2: genRandomId(),
foo3: genRandomId(),
};
this.position.x = genRandomInt(min, max);
this.position.y = genRandomInt(min, max);
this.position.z = genRandomInt(min, max);
var that = this;
// click event listener
domEvents.addEventListener(this, 'click', function(event) {
console.log('clicked object on position:');
console.log(that.position);
destinationContainer.userData = that.userData;
console.log('Now the conainer has:');
console.log(destinationContainer.userData);
destinationContainer.userData = that.userData;
}, false);
}
MyMesh.prototype = Object.create(THREE.Mesh.prototype);
MyMesh.prototype.constructor = MyMesh;
genRandomId and genRandomInt are random generators for the pourpose of illustrating this example, I took the code for the random ids from Generate random string/characters in JavaScript.
In your scene you can generate 200 (or more) MyMesh meshes and add them to the scene:
const color = 0x156289;
const emissive = 0x072534;
var planeGeometry = new THREE.PlaneGeometry(5, 5);
var planeMaterial = new THREE.MeshPhongMaterial({
color: color,
emissive: emissive,
side: THREE.DoubleSide,
shading: THREE.FlatShading
});
var planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(planeMesh);
var objGeometry = new THREE.BoxGeometry(1, 1, 1);
var objMaterial = new THREE.MeshPhongMaterial({
color: color,
emissive: emissive,
shading: THREE.FlatShading
});
var i = 0;
while (i < 200) {
scene.add(new MyMesh(objGeometry, objMaterial, planeMesh));
i++;
}
And finally render the scene:
var render = function() {
requestAnimationFrame(render);
planeMesh.rotation.x += 0.010;
planeMesh.rotation.y += 0.010;
renderer.render(scene, camera);
};
render();
This is a demo with the full source code: http://run.plnkr.co/plunks/W4x8XsXVroOaLUCSeXgO/
Open the browser console and click on a cube and you'll see the that planeMesh is switching its userData attributes with the ones of the clicked cube mesh.
Yes, that's fine. You can put your own custom keys directly on a Three.js object and it shouldn't bother it as long as you don't accidentally overwrite an important built-in Three.js key. For that reason I'd recommend that you put all of your custom keys in a "namespace" on the object so they're nice and neat and contained.
For example, if you had a Three.js object foo, you could put all your keys under foo.myCustomNamespace, so that your custom data like foo.myCustomNamespace.name, foo.myCustomNamespace.description, etc. are all together and won't interfere with THREE.js properties.
Edit: Three.js provides a built-in namespace for user data called, conveniently, userData. Access it on THREE.Object3D.userData.
https://github.com/mrdoob/three.js/blob/master/src/core/Object3D.js#L92
Given 200+ vertices generated by the AutoCad API in the form of an array of vectors {X:,Y:,Z:}, I am trying to render them in THREE.js with no luck.
For now I am making all the possible permutations for the number 200 and connecting all the vertices together - which I don't think is the way this is done as it gives over 200k faces.
EDIT: My AutoCAD code is getting all the vertices and then it tries to get it's connected vertices' ids (vertex1 and vertex2). GetHashCode() doesn't work though. The problem isn't that it returns huge id numbers like 148335760 and 682610240. The problem is that these ids are not unique and they happen to be defined and they are not connected to any other vertices.
AutoCAD code:
//data structures for serialisation
public class EdgeMe
{
public int vertex1;
public int vertex2;
}
public class VertexMe
{
public int id;
public Point3d Point;
public List<EdgeMe> Edges = new List<EdgeMe>();
}
public class DataMe{
public Extents3d extents;
public string layer;
public List<VertexMe> points = new List<VertexMe>();
}
//...
// Get each Solid3d in modelspace and add its extents
// to the list
foreach (var id in ms)
{
var obj = tr.GetObject(id, OpenMode.ForRead);
var sol = obj as Solid3d;
DataMe dataMe = new DataMe();
if (sol != null)
{
dataMe.extents = sol.GeometricExtents;
dataMe.layer = sol.Layer;
using (var brep = new Autodesk.AutoCAD.BoundaryRepresentation.Brep(sol))
{
foreach (var vertex in brep.Vertices)
{
VertexMe vertexMe = new VertexMe();
vertexMe.Point = vertex.Point;
vertexMe.id = vertex.Brep.GetHashCode();
foreach(var edge in vertex.Edges)
{
EdgeMe edgeMe = new EdgeMe();
edgeMe.vertex1 = edge.Vertex1.Brep.GetHashCode();
edgeMe.vertex2 = edge.Vertex2.Brep.GetHashCode();
vertexMe.Edges.Add(edgeMe);
}
dataMe.points.Add(vertexMe);
}
}
}
sols.Add(dataMe);
}
Javascript code:
var faces = function(vertices) {
var results = [];
var vertex = [0, 1, 2];
results.push(vertex.slice());
while(true) {
vertex[2]++;
if(vertex[2] > vertices) {
vertex[1]++;
if(vertex[1] >= vertices) {
vertex[0]++;
vertex[1] = vertex[0] + 1;
if(vertex[0] > vertices - 2)
return results;
}
vertex[2] = vertex[1] + 1;
}
results.push(vertex.slice());
}
};
var generate = function( ... ) {
// Process each box, adding it to the scene
for (var sol in sols) {
var s = sols[sol];
var vertices = [];
for(var vertix in s.points) {// deserialize
vertix = s.points[vertix];
vertices.push(new THREE.Vector3(vertix.X, vertix.Y, vertix.Z));
}
var holes = [];
var triangles, mesh;
var geometry = new THREE.Geometry();
geometry.vertices = vertices;
var xfaces = faces(vertices.length);
for(var i = 0; i < xfaces.length; i++) {
geometry.faces.push( new THREE.Face3( xfaces[i][0], xfaces[i][1], xfaces[i][2] ));
}
geometry.computeFaceNormals();
geometry.computeVertexNormals();
mesh = new THREE.Mesh( geometry, customimg );
mesh.rotation.set( Math.PI/2, 0, 0);
root.add(mesh);
}
}
Regards,
Ioan
You cannot do what you are doing. You are trying to guess the connectivity info. That should not be guess work. The application needs to return the connectivity info to you.
Instead I would suggest you use Forge Model Derivative API to extract the viewable from your file. Once you have the file translated, you can view it using the Viewer (which is based on Three.js, but optimized for engineering files). You'll find several examples at this Github.
The problem:
Bounding box volume is smaller than volume calculated from mesh.
What I've tried:
First I calculate the volume of a bounding box with the following code:
//loaded .obj mesh object:
var sizer = new THREE.Box3().setFromObject(obj);
console.log(sizer.size().x*sizer.size().z*sizer.size().y);
log output: 197112.65382983384
Then I calculate the volume of the mesh using this solution with this solution with the legacy geometry object in the method "calculateVolume" included below:
console.log(scope.calculateVolume(child));
calculateVolume is part of a class. My class methods are as follows:
threeDeeAsset.prototype.signedVolumeOfTriangle = function(p1, p2, p3) {
var v321 = p3.x*p2.y*p1.z;
var v231 = p2.x*p3.y*p1.z;
var v312 = p3.x*p1.y*p2.z;
var v132 = p1.x*p3.y*p2.z;
var v213 = p2.x*p1.y*p3.z;
var v123 = p1.x*p2.y*p3.z;
return (-v321 + v231 + v312 - v132 - v213 + v123)/6;
}
threeDeeAsset.prototype.calculateVolume = function(object){
var volumes = 0;
object.legacy_geometry = new THREE.Geometry().fromBufferGeometry(object.geometry);
for(var i = 0; i < object.legacy_geometry.faces.length; i++){
var Pi = object.legacy_geometry.faces[i].a;
var Qi = object.legacy_geometry.faces[i].b;
var Ri = object.legacy_geometry.faces[i].c;
var P = new THREE.Vector3(object.legacy_geometry.vertices[Pi].x, object.legacy_geometry.vertices[Pi].y, object.legacy_geometry.vertices[Pi].z);
var Q = new THREE.Vector3(object.legacy_geometry.vertices[Qi].x, object.legacy_geometry.vertices[Qi].y, object.legacy_geometry.vertices[Qi].z);
var R = new THREE.Vector3(object.legacy_geometry.vertices[Ri].x, object.legacy_geometry.vertices[Ri].y, object.legacy_geometry.vertices[Ri].z);
volumes += this.signedVolumeOfTriangle(P, Q, R);
}
return Math.abs(volumes);
}
log output: 336896.1562770668
Checking the source of the vertexes:
I also tried buffergeometry and of course, it's really the same array, but a typed Float32Array and predictably gave the same result:
var vol = 0;
scope.mesh.traverse(function (child) {
if (child instanceof THREE.Mesh) {
var positions = child.geometry.getAttribute("position").array;
for(var i=0;i<positions.length; i+=9){
var t1 = {};
t1.x = positions[i+0];
t1.y = positions[i+1];
t1.z = positions[i+2];
var t2 = {};
t2.x = positions[i+3];
t2.y = positions[i+4];
t2.z = positions[i+5];
var t3 = {};
t3.x = positions[i+6];
t3.y = positions[i+7];
t3.z = positions[i+8];
//console.log(t3);
vol += scope.signedVolumeOfTriangle(t1,t2,t3);
}
}
});
console.log(vol);
log output: 336896.1562770668
The question: Why is my bounding box volume smaller than the calculated volume of a closed mesh?
Perhaps I missing something such as an offset position or maybe I am calculating the bounding box volume wrong. I did several searches on google and stack, which is how I came to find the signedVolumeOfTriangle function above. it seems to be the most common accepted approach.
Could be a problem with winding order, you could try negating the result from signed volume, or reordering arguments.
The winding order will be clockwise or counterclockwise and determines the facing (normal) of the polygon,
volumes += this.signedVolumeOfTriangle(P, Q, R);
Swapping P and R inverts the normal,
volumes += this.signedVolumeOfTriangle(R, Q, P);
This can be complicated by storage techniques like triangle strips, where vertices are shared by adjacent triangles, causing winding order to alternate.
Another problem could be - especially if it works correctly for simple meshes - is degenerate vertices. If you're getting your meshes from an editor, and the mesh has been modified and tweaked a million times (usually the case), it's almost guaranteed to have degenerates.
There may be an option to weld close vertices in the editor that can help, or test with a known good mesh e.g. the Stanford bunny.
I am working on create an animation that displays a revolution of a 2D function about an axis to create a 3D surface. I've been able to render the surface successfully, and now I'm trying to create the animation section using MorphTargets.
Unfortunately, although I have successfully pushed all of the morph frames into my geometry, I'm having trouble getting the animation to play. All of the animation examples I have found use blender to import models, and so aren't completely helpful (many are outdated as well).
I was looking at the morphing horse as an example ( http://threejs.org/examples/#webgl_morphtargets_horse ), and tried to replicate the code as much as possible, but I haven't been successful.
Here's my code initializing the morphVerticeHolder, which is a 2D array to hold the vertices of each animation frame
//First indices of multiarray corresponds to morph frame, second to the vertice
var morphVerticeHolder = [];
for(var i = 0; i < 20; i++) {
morphVerticeHolder[i] = [];
}
And here is my code pushing the vertices along the axis. Note that I simultaneously push my morph vertices as well. mI stands for mesh interval.
for(var i = xstart; i <= xend; i+= mI) {
geo.vertices.push(new THREE.Vector3(i,0,0));
for(var j = 0; j < 20; j++) { //20 Frames of the morph animation
morphVerticeHolder[j].push(new THREE.Vector3(i,0,0));
}
}
This code pushes all of the vertices. i ranges along the x axis, while j ranges along theta, filling in the vertices for the mesh.
//Push the vertices
var tmesh = 2*Math.PI/meshPoints; //Theta mesh interval
verticeCounter = 0;
for(var i = xstart; i <= xend; i+=mI) {
var rMain = solveEquation(i);
var rSecond = solveEquation(i+mI);
var counter = 0;
for(var j = 0; j < 2*Math.PI; j += tmesh) {
geo.vertices.push(new THREE.Vector3(i, rMain*Math.cos(j/1000),rMain*Math.sin(j/1000)));
for(var k = 0; k < 20; k++) {
morphVerticeHolder[k].push(new THREE.Vector3(i, rMain*Math.cos(j*(k+1)/20),rMain*Math.sin(j*(k+1)/20)));
}
}
}
The dividing by 1000 is for the purpose of starting the original mesh out close to 0, and then animating it rotating using the morph vertices (which range from 1/20 of the desired value to 20/20.
Then I push the faces. You can try to interpret my algorithm, but essentially I figured out how to keep track of which vertice is where.
for(var i = 0; i < meshPoints; i++) {
var sI = meshPoints*(i+1);
var sI2 = meshPoints*(i+2);
for(var j = 0; j < meshPoints-1; j ++) {
if(i === 0) {
//First Point, Fill end
geo.faces.push(new THREE.Face3(sI+j, sI+j+1, 0));
geo.faces.push(new THREE.Face3(sI+j+1, sI+j, 0));
//Filll Body
geo.faces.push(new THREE.Face3(sI+j, sI+j+1, sI2+j));
geo.faces.push(new THREE.Face3(sI+j+1, sI+j, sI2+j));
geo.faces.push(new THREE.Face3(sI+j+1, sI2+j, sI2+j+1));
geo.faces.push(new THREE.Face3(sI2+j, sI+j+1, sI2+j+1));
} else if(i === meshPoints-1) {
//Last Point, Fill end
geo.faces.push(new THREE.Face3(sI+j, sI+j+1, meshPoints-1));
geo.faces.push(new THREE.Face3(sI+j+1, sI+j, meshPoints-1));
} else {
geo.faces.push(new THREE.Face3(sI+j, sI+j+1, sI2+j));
geo.faces.push(new THREE.Face3(sI+j+1, sI+j, sI2+j));
geo.faces.push(new THREE.Face3(sI+j+1, sI2+j, sI2+j+1));
geo.faces.push(new THREE.Face3(sI2+j, sI+j+1, sI2+j+1));
}
}
}
And here's the rest, initializing and such.
for(var k = 0; k < 20; k++) {
geo.morphTargets.push( { name: "target" + k, vertices: morphVerticeHolder[k]} );
}
var uniforms = {
resolution: { type: "v2", value: new THREE.Vector2 },
zmax: { type: "f", value: maxz},
zmin: { type: "f", value: minz}
};
var mat = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('cubeVertexShader').innerHTML,
fragmentShader: document.getElementById('cubeFragmentShader').innerHTML
});
functionObject = new THREE.Mesh( geo, mat);
this.scene.add(functionObject);
functionObject.name = 'current';
this.animation = new THREE.MorphAnimation( functionObject, 'Revolution' );
this.animation.play();
this.setWithinRender(function() {
this.animation.update(.1);
});
this.setWitinRender puts the anonymous function into the render loop of THREE.js (this is a function called separately from the setup for THREE.
As I mentioned above, the mesh renders, and if I get rid of the /1000 when pushing the original vertices I get the whole surface. However, the animation, set up as above, doesn't play. Does anyone have any insight into this?
Thanks!
After a little bit more research, and delving into the source code, I discovered that the material used must have the morphTargets attribute set to true. I had tried this with the shader material that I was using, but with that you have to manually apply the morphTargetInfluences in the vertex shader.
Therefore, I switched the material over to
var mat = new THREE.MeshNormalMaterial( { color: 0x990000, morphTargets: true } );
and everything worked out fine!