I'm loading models using the collada loader. The loader returns an Object3D, "dae", with many child meshes. I'd like to instantiate the parent "dae" object many times without duplicating the meshes. Can I just use dae.clone()?
Put another way: I'd like to make shallow copies that all have their own transformation matrix but share the same geometry. What's the most efficient way to do this?
By default Object3D.clone() does create a deep copy. Let's take a look at the source
clone: function ( object, recursive ) {
if ( object === undefined ) object = new THREE.Object3D();
if ( recursive === undefined ) recursive = true;
object.name = this.name;
object.up.copy( this.up );
object.position.copy( this.position );
object.quaternion.copy( this.quaternion );
object.scale.copy( this.scale );
object.renderDepth = this.renderDepth;
object.rotationAutoUpdate = this.rotationAutoUpdate;
object.matrix.copy( this.matrix );
object.matrixWorld.copy( this.matrixWorld );
object.matrixAutoUpdate = this.matrixAutoUpdate;
object.matrixWorldNeedsUpdate = this.matrixWorldNeedsUpdate;
object.visible = this.visible;
object.castShadow = this.castShadow;
object.receiveShadow = this.receiveShadow;
object.frustumCulled = this.frustumCulled;
object.userData = JSON.parse( JSON.stringify( this.userData ) );
if ( recursive === true ) {
for ( var i = 0; i < this.children.length; i ++ ) {
var child = this.children[ i ];
object.add( child.clone() );
}
}
return object;
}
As we can see the clone function accepts two optional arguments:
the object to clone the Object3D into.
a flag to indicate whether or not to recursively clone the children.
So yes it is possible to make shallow copies with respect to the Object3D.children, but that's not what you want (based on your comment).
I believe you can actually use the default behavior of Object3D.clone() to get what you are after. Mesh.clone() does not clone the Geometry and Material properties.
THREE.Mesh.prototype.clone = function ( object ) {
if ( object === undefined ) object = new THREE.Mesh( this.geometry, this.material );
THREE.Object3D.prototype.clone.call( this, object );
return object;
};
Here is a couple of function I use to clone object's materials deeply. You could modify this to your needs
Also consider this information : https://github.com/mrdoob/three.js/issues/5754
/** Gives the aptitude for an object3D to clone recursively with its material cloned (normal clone does not clone material)*/
THREE.Object3D.prototype.GdeepCloneMaterials = function() {
var object = this.clone( new THREE.Object3D(), false );
for ( var i = 0; i < this.children.length; i++ ) {
var child = this.children[ i ];
if ( child.GdeepCloneMaterials ) {
object.add( child.GdeepCloneMaterials() );
} else {
object.add( child.clone() );
}
}
return object;
};
THREE.Mesh.prototype.GdeepCloneMaterials = function( object, recursive ) {
if ( object === undefined ) {
object = new THREE.Mesh( this.geometry, this.material.clone() );
}
THREE.Object3D.prototype.GdeepCloneMaterials.call( this, object, recursive );
return object;
};
You can refer to the copyand clone methods in Object3D to deep clone mesh materials.
First,extends two new methods in THREE:
THREE.Object3D.prototype.deepClone = function ( recursive ) {
return new this.constructor().deepCopy( this, recursive );
},
THREE.Object3D.prototype.deepCopy = function( source, recursive ) {
if ( recursive === undefined ) recursive = true;
this.name = source.name;
this.up.copy( source.up );
this.position.copy( source.position );
this.quaternion.copy( source.quaternion );
this.scale.copy( source.scale );
this.matrix.copy( source.matrix );
this.matrixWorld.copy( source.matrixWorld );
if(source.material){
//changed
this.material = source.material.clone()
}
if(source.geometry){
//changed
this.geometry = source.geometry.clone()
}
this.matrixAutoUpdate = source.matrixAutoUpdate;
this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate;
this.layers.mask = source.layers.mask;
this.visible = source.visible;
this.castShadow = source.castShadow;
this.receiveShadow = source.receiveShadow;
this.frustumCulled = source.frustumCulled;
this.renderOrder = source.renderOrder;
this.userData = JSON.parse( JSON.stringify( source.userData ) );
if ( recursive === true ) {
for ( var i = 0; i < source.children.length; i ++ ) {
var child = source.children[ i ];
this.add( child.deepClone() ); //changed
}
}
return this;
}
Second,when you want to deep clone a Object3D or Scene named originalObj.just do var newObj = originalObj.deepClone()
three.js Object3D.clone() will not create a deep copy of geometry and material. It will reference geometry and material of the cloned object instead.
You can see this in three.module.js:
Mesh.prototype = ... {
...,
copy: function ( source ) {
Object3D.prototype.copy.call( this, source );
...
>>>>>>> this.material = source.material; <<<<<<<
>>>>>>> this.geometry = source.geometry; <<<<<<<
return this;
}
...
}
So, regarding the OP's question: Yes, you can just use dae.clone().
For a deep clone including geometry and material, just change two lines of the above Mesh.prototype.copy() function:
this.material = source.material.clone();
this.geometry = source.geometry.clone();
Keep in mind this is a performance issue because three.js has to calculate separate geometries for each object3D, including all Meshes.
Related
In three.js, I am trying to add gui by custom values. but it gives me error.
this is my code
var arm = document.getElementById('arm');
var model_arr = [];
model_arr['narm'] = (arm)? arm:0;
function init(){
...create scene, camera, load obj,mtl render it etc.
initGUI();
}
function initGUI() {
initGUICalled = true;
if (typeof model_arr !== 'undefined' && model_arr.length > 0) {
params = {
Arm: (model_arr['narm'])? model_arr['narm']:20.75,
resetval: function() { resetBody(); }
};
}
// Set up dat.GUI to control targets
lower = gui.addFolder( 'Lower Measurement' );
let ArmCtrl = lower.add( params, 'Arm', 18.75, 22.75 ).step( 0.21 ).name("Arm in Inch").listen();
ArmCtrl.onChange( function ( value ) {
mesh.morphTargetInfluences[ 0 ] = (value - 20.75)/2.1;
} );
lower.close();
gui.close();
}
function resetBody(){
initGUICalled = true ;
params.Arm = 20.75;
mesh.morphTargetInfluences[ 0 ] = 0;
}
I am trying to give value from model_arr object. So for this I have tried.
let ArmCtrl = lower.add( params, 'Arm', 18.75, 22.75 ).step( 0.21 ).name("Arm in Inch").listen();
ArmCtrl.onChange( function ( value ) {
mesh.morphTargetInfluences[ 0 ] = (value - model_arr['narm'])/2.1;
} );
and got error
Uncaught TypeError: folder.add(...).step is not a function
at new_women_xl_initGUI
I have check these reference
Saving parameters with dat.gui seems broken?
Uncaught TypeError: $(...).steps is not a function
but not get luck.
With model_arr, you're flip-flopping between using it as an array and as an object:
// Here you initialize it as an array
var model_arr = [];
// Here you're accessing it as an object, not adding any values to the array
model_arr['narm'] = (arm) ? arm : 0;
// The array's length is still 0, so params never gets a value
if (model_arr.length > 0) {
params = {
Arm: xxx
};
}
// Now when you try to access params.Arm, it doesn't exist
let ArmCtrl = lower.add( params, 'Arm', 18.75, 22.75 )
If you want to use an array, stick to an array throughout the lifetime of the variable. If you want to create an object, start with a new variable so you don't confuse the two.
If you want to add a value at the end of an array, you could use the .push() method:
var narm = (arm) ? arm : 0;
// Add value to the end of the array
model_arr.push(narm);
// Now the array's length is 1
Hi I have some very complex models to display which comes from Revit files that my customer provides.
But sometimes the amount of details in the model is just way too much for the purpose of the website.
I would like to reduce the amount of vertexes/triangles in the model to simplify the display and enhance performance.
I have used simply modifiers where I cant able to view the model itself.
Is this possible from within ThreeJS? Or is there maybe an other solution for this?
const renderMeshFunction = async (material: any, geometry: any, id: any) => {
let count = 0;
const emptyFunction = () => { };
const onBR = () => {
const _count = (count < 3 ? count : Math.floor(Math.random() * count) + 1);
return ((renderer: WebGLRenderer, scene: Scene,
camera: Camera, geometry: Geometry | BufferGeometry,
_material: Material, group: Group) => {
_material.clippingPlanes = checkIsClipping();
});
};
count = geometry.maxInstancedCount;
const mesh = new Mesh(geometry, material);
let modifier = new SimplifyModifier();
let simplified = mesh.clone();
simplified.material = simplified.material.clone();
let countSimple = Math.floor((simplified.geometry as any).attributes.position.count * 1); // number of vertices to remove
simplified.geometry = modifier.modify(simplified.geometry, countSimple);
that.scene.add(simplified);
mesh.name = id || Date.now().toString();
mesh.matrixAutoUpdate = false;
mesh.drawMode = 0;
mesh.onBeforeRender = onBR.apply(that);
mesh.onAfterRender = emptyFn;
mesh.geometry.dispose();
mesh.material.dispose();
geometry.dispose();
material.dispose();
material = undefined;
return mesh;
};
I have a hierarchy of groups, A -> B -> C. I wish to create a clone of this hierarchy, A2 -> B2 -> C2.
But Object3D.clone() removes the parent reference of the group.
Other than manually setting the parent for each of the child-groups after cloning, what other way is there?
If the hierarchy is deep this could get take to compute.
Thanks for the help!
Maybe you can check out this question Will three.js Object3D.clone() create a deep copy of the geometry?
I extends the copyand clone methods in Object3D to deep clone mesh materials.
And in your case , this should works too.
First,extends two new methods in THREE:
THREE.Object3D.prototype.deepClone = function ( recursive ) {
return new this.constructor().deepCopy( this, recursive );
},
THREE.Object3D.prototype.deepCopy = function( source, recursive ) {
if ( recursive === undefined ) recursive = true;
this.name = source.name;
this.up.copy( source.up );
this.position.copy( source.position );
this.quaternion.copy( source.quaternion );
this.scale.copy( source.scale );
this.matrix.copy( source.matrix );
this.matrixWorld.copy( source.matrixWorld );
if(source.material){
//changed
this.material = source.material.clone()
}
if(source.geometry){
//changed
this.geometry = source.geometry.clone()
}
this.matrixAutoUpdate = source.matrixAutoUpdate;
this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate;
this.layers.mask = source.layers.mask;
this.visible = source.visible;
this.castShadow = source.castShadow;
this.receiveShadow = source.receiveShadow;
this.frustumCulled = source.frustumCulled;
this.renderOrder = source.renderOrder;
this.userData = JSON.parse( JSON.stringify( source.userData ) );
if ( recursive === true ) {
for ( var i = 0; i < source.children.length; i ++ ) {
var child = source.children[ i ];
this.add( child.deepClone() ); //changed
}
}
return this;
}
Second,when you want to deep clone a Object3D or Scene named originalObj.just do var newObj = originalObj.deepClone()
I'm working on a project in the medical field using threejs, and OBJ files shin bone, my goal is that I get to cut part of the tibia on the basis of a given position, for now I can do that by replacing the vertix of the object, but my problem is that I can not directly update the object, so every time I delete the old object and I upload it, although i put verticeNeedUpdate …
My question is if it's possible to directly update the object?
loader.load('img/cube.obj',function(object){
var material = new THREE.MeshLambertMaterial({color:0xA0A0A0});
slice_onZ.onChange(function(z){
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material = material;
geometry = new THREE.Geometry().fromBufferGeometry(child.geometry);
for(var i=0 ; i< geometry.vertices.length ; i++) {
if (geometry.vertices[ i ].x != 0) {
geometry.vertices[ i ].x = z;
}
if (geometry.vertices[ i ].y != 0) {
geometry.vertices[ i ].y = z;
}
if (geometry.vertices[ i ].z != 0) {
geometry.vertices[ i ].z = z;
}
}
geometry.verticesNeedUpdate = true;
mesh = new THREE.Mesh( geometry, material );
console.log(mesh.geometry.attributes);
scene.add(mesh);
}
});
});
},onProgress,onError);
}
there is my code, i try to update geometry but it's doesnt work, i have to reload everytime the object and delete the old one for display the new on.
I would like to load multiple JSON files and control the visibility of their meshes. To achieve this, I associated them with their JSON file names. I got it working, but the solutions doesn't please me.
I modified the THREE.JSONLoader and passed a new parameter to the callback function. So with every new release of three.js, I would have to patch the three.js file again.
Here is my working solution (client side). See the new third parameter of loader.load(filename, callback, meshname).
Is there a better solution, which doesn't need a patched three.js library?
Thanks
// Load the JSON files
var meshes = new Object();
var jsonFileNames = ['assembly/part1.json', 'assembly/part2.json', 'assembly/part3.json'];
for(var i = 0; i < jsonFileNames.length; i++){
var loader = new THREE.JSONLoader();
var meshName = jsonFileNames[i].split("/")[1].split(".")[0];
loader.load(jsonFileNames[i], function(geometry, meshName){
mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({vertexColors: THREE.FaceColors}));
mesh.scale.set(0.2, 0.2, 0.2);
mesh.doubleSided = true;
scene.add(mesh);
meshes[meshName] = mesh;
}, meshName);
}
// ....
// Access their meshes
meshes[meshName].visible = true;
You could create a callback factory like below. It will work around the problem with closures when created inside loops.
function makeHandler(meshName) {
return function(geometry) {
mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial({vertexColors: THREE.FaceColors}));
mesh.scale.set(0.2, 0.2, 0.2);
mesh.doubleSided = true;
scene.add(mesh);
meshes[meshName] = mesh;
};
}
// Load the JSON files
var meshes = new Object();
var jsonFileNames = ['assembly/part1.json', 'assembly/part2.json', 'assembly/part3.json'];
for(var i = 0; i < jsonFileNames.length; i++){
var loader = new THREE.JSONLoader();
var meshName = jsonFileNames[i].split("/")[1].split(".")[0];
loader.load(jsonFileNames[i], makeHandler(meshName));
}
// ....
// Access their meshes
meshes[meshName].visible = true;
A variant of Tapio's great answer. This variant avoids the need to create and manage the meshes object. It does this by creating each new mesh object with the desired object name directly in the made handler function. As before the desired name meshName for each mesh is defined by the user and passed to the handler. But then the new mesh is created with the desired name by using the javascript eval function.
function makeHandler(meshName) {
return function(geometry) {
material = new THREE.MeshPhongMaterial({vertexColors: THREE.FaceColors})
eval (meshName + "= new THREE.Mesh(geometry, material);" ); //***NEW ***
eval ( "var mesh = " + meshName +";" ); //*** NEW ***
mesh.scale.set(0.2, 0.2, 0.2);
mesh.doubleSided = true;
scene.add(mesh);
//*** NOT REQUIRED *** meshes[meshName] = mesh;
};
}
// Load the JSON files
var meshes = new Object();
var jsonFileNames = ['assembly/part1.json', 'assembly/part2.json', 'assembly/part3.json'];
for(var i = 0; i < jsonFileNames.length; i++){
var loader = new THREE.JSONLoader();
var meshName = jsonFileNames[i].split("/")[1].split(".")[0];
loader.load(jsonFileNames[i], makeHandler(meshName));
}
// ....
The various meshes can subsequently (when loading is completed) be referenced by their object names:-
// Access the meshes
//*** OLD *** meshes["part1"].visible = true;
//*** OLD *** meshes["part2"].visible = true;
//*** OLD *** meshes["part3"].visible = true;
part1.visible = true;//*** NEW ***
part2.visible = true;//*** NEW ***
part3.visible = true;//*** NEW ***