I'm trying to select a particular material in the imported model (via Collada loader) in Three.js. I know this can be done with the getChildByName method but it just doesn't work for me. I couldn't find any working example with this method included, either.
What am I doing wrong here?
loader = new THREE.ColladaLoader();
loader.load('myModel.dae', function ( collada ) {
model = collada.scene;
var myMaterial = model.getChildByName( 'materialName', true );
myMaterial.material = new THREE.MeshBasicMaterial( { wireframe: true } );
});
I think this is what you're after.
collada.scene.traverse( function ( child ) {
if ( child.material && child.material.name === 'materialName' ) {
child.material = new THREE.MeshBasicMaterial( { wireframe: true } );
}
} );
Related
I have loaded different textures using textureLoader and I am trying to update them using dat.gui controls.
Why is the code below not working?
gui.add(mesh.position, "y", -1, 1, 0.1);
gui.add(mesh.material, "map", { alpha: alphaTexture, color: colorTexture, normal: normalTexture })
.onChange(() => {
mesh.material.needsUpdate = true;
console.log("updated");
});
It gives this error:
"Uncaught TypeError: m is undefined" [error][1]
After some tweaking, I found that the values of object(or array) in the third argument only supports string types, so passing a object as a value would not work.
This is the closest workaround that I could think of..
/* GUI options */
const guiOptions = {
mesh_material_map: "color",
};
/* Textures */
const textureLoader = new THREE.TextureLoader(loadingManager);
const colorTexture = textureLoader.load("/textures/door/color.jpg");
const alphaTexture = textureLoader.load("/textures/door/alpha.jpg");
const normalTexture = textureLoader.load("/textures/door/normal.jpg");
const guiTextureHash = {
color: colorTexture,
alpha: alphaTexture,
normal: normalTexture,
};
/* Add to gui */
gui.add(guiOptions, "mesh_material_map", Object.keys(guiTextureHash)).onChange((value) => {
mesh.material.map = guiTextureHash[value];
mesh.needsUpdate = true;
console.log("updated", value);
});
I found your topic looking for a texture picker. It's probably a little away from your starting point but could help some other. I finally made a simple texture picker with a dropdown selection key with dat.gui. The goal is to be able to change on the fly my matcap texture, going through an array of loaded texture.
const gui = new dat.GUI()
const textureLoader = new THREE.TextureLoader()
const myMatCap = [
textureLoader.load('./textures/matcaps/1.png'),
textureLoader.load('./textures/matcaps/2.png'),
textureLoader.load('./textures/matcaps/3.png')
]
const parameters = {
color: 0xff0000,
matCapTexture: 0
}
const updateAllMaterials = () => {
scene.traverse( (child)=>{
if(child instanceof THREE.Mesh && child.material instanceof THREE.MeshMatcapMaterial) {
child.material.matcap = myMatCap[ parameters.matCapTexture]
child.material.needsUpdate = true
}
})
}
gui.add(parameters, 'matCapTexture', {
terracotta: 0,
grey: 1,
chrome: 2,
}).onFinishChange(()=>{
updateAllMaterials()
})
let mesh = new THREE.Mesh(
geometry,
new THREE.MeshMatcapMaterial({
side: THREE.DoubleSide,
matcap: myMatCap[ parameters.matCapTexture ]
})
);
scene.add(mesh)
I'm using SVGloader to load an SVG so I can map it on my OBJ file. But when gave it url to the svg file it generates an error
TypeError:Cannot set property 'getStrokeStyle' of undefined
I'm using Angular 8 and rendering a Obj file using THREE.js. I want to load an svg and map it on the obj file to add texture to that file, but as I told above it is generating an error and I don't know how to solve it.
Here is code file.
import { Component, AfterViewInit, ViewChild, Input, ElementRef } from '#angular/core';
import * as THREE from 'three';
import { OrbitControls } from '#avatsaev/three-orbitcontrols-ts';
import {OBJLoader} from 'three-obj-mtl-loader';
import {SVGLoader} from 'three-svg-loader';
#Component({
selector: 'app-scene',
templateUrl: './scene.component.html',
styleUrls: ['./scene.component.css']
})
export class SceneComponent implements AfterViewInit {
#Input() name: string;
#ViewChild('canvas', {static:true}) canvasRef: ElementRef;
renderer = new THREE.WebGLRenderer;
scene = null;
camera = null;
controls = null;
mesh = null;
light = null;
loader;
svgLoader;
private calculateAspectRatio(): number {
const height = this.canvas.clientHeight;
if (height === 0) {
return 0;
}
return this.canvas.clientWidth / this.canvas.clientHeight;
}
private get canvas(): HTMLCanvasElement {
return this.canvasRef.nativeElement;
}
constructor() {
// this.loader = new OBJLoader();
this.scene = new THREE.Scene();
this.loader = new OBJLoader();
this.svgLoader = new SVGLoader();
this.camera = new THREE.PerspectiveCamera(15, window.innerWidth / window.innerHeight, 0.1, 1000)
}
ngAfterViewInit() {
this.configScene();
this.configCamera();
this.configRenderer();
this.configControls();
this.createLight();
this.createMesh();
this.animate();
}
configScene() {
// this.scene.background = new THREE.Color( 0xdddddd );
}
configCamera() {
this.camera.aspect = this.calculateAspectRatio();
this.camera.updateProjectionMatrix();
this.camera.position.set( 0, 0, 3 );
this.camera.lookAt( this.scene.position );
}
configRenderer() {
this.renderer = new THREE.WebGLRenderer({
canvas: this.canvas,
antialias: true,
alpha: true
});
this.renderer.setPixelRatio(devicePixelRatio);
// setClearColor for transparent background
// i.e. scene or canvas background shows through
this.renderer.setClearColor( 0x000000, 0 );
this.renderer.setSize((window.innerWidth/2), (window.innerHeight/2));
window.addEventListener('resize', ()=>{
this.renderer.setSize((window.innerWidth/2), (window.innerHeight)/2);
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
})
console.log('clientWidth', this.canvas.clientWidth);
console.log('clientHeight', this.canvas.clientHeight);
}
configControls() {
this.controls = new OrbitControls(this.camera);
this.controls.autoRotate = false;
this.controls.enableZoom = false;
// this.controls.maxDistance = 5;
// this.controls.minDistance = 10;
this.controls.enablePan = false;
this.controls.update();
}
createLight() {
this.light = new THREE.PointLight( 0xffffff );
this.light.position.set( -10, 10, 10 );
this.scene.add( this.light );
}
createMesh() {
this.svgLoader.load('../../../../assets/abc.svg')
console.log("SVG Loader", this.svgLoader)
this.loader.load('../../../../assets/nonunified.obj', (object)=>{
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.geometry.center();
}
} );
this.scene.add(object)
},
// called when loading is in progresses
function (xhr) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
},
// called when loading has errors
function ( error ) {
console.log( 'An error happened' );
}
)}
animate() {
window.requestAnimationFrame(() => this.animate());
this.controls.update();
this.renderer.render(this.scene, this.camera);
}
}
The SVGLoader is not for loading svg-files to be used as textures but for loading them as Geometry: https://threejs.org/docs/#examples/en/loaders/SVGLoader
If you want to use an svg-file as a texture, you should be able to use the TextureLoader like this:
obj.material.map = new TextureLoader().load('../../../../assets/abc.svg');
I'm not sure if you actually need to rasterize it to a canvas first, if the above doesn't work, try what is described here: How do you load and display SVG graphics in three.js?
I am trying to remove the point light (or preferrably any light source), but I am new to ThreeJS... I tried babylonScene.remove(object); but I get an error that "cannot call traverse on undefined" or something to that affect. Happy to post the error if that is the suggested approach.
The code below is taken from the ThreeJS example code with the additional removal code.
https://github.com/mrdoob/three.js/blob/master/examples/webgl_loader_babylon.html
var loader = new THREE.BabylonLoader( manager );
loader.load( data.src, function ( babylonScene ) {
babylonScene.traverse( function ( object ) {
if ( object instanceof THREE.Mesh ) {
object.material = new THREE.MeshPhongMaterial( {
color: Math.random() * 0xffffff
});
}
else if(object instanceof THREE.PointLight){
console.log("Removing PointLight");
object.remove();
}
});
...
}, onProgress, onError );
The best idea would be to set the light intensity at 0, this means you won't see it anymore:
var loader = new THREE.BabylonLoader( manager );
loader.load( data.src, function ( babylonScene ) {
babylonScene.traverse( function ( object ) {
if ( object instanceof THREE.Mesh ) {
object.material = new THREE.MeshPhongMaterial( {
color: Math.random() * 0xffffff
});
}
else if(object instanceof THREE.PointLight){
console.log("Removing PointLight");
object.intensity = 0
}
});
...
}, onProgress, onError );
Hope this helps!
I've successfully imported a .dae scene via ColladaLoader.
The problem is, I need to switch between several .dae files.
I can't seem to implement the dispose method properly.
dae.traverse(function(obj) {
console.log('unloading ' + obj.id);
scene.remove(obj);
if(obj.geometry)
obj.geometry.dispose();
if(obj.material)
obj.material.dispose();
if(obj.mesh)
obj.mesh.dispose();
if(obj.texture)
obj.texture.dispose();
});
scene.remove(dae);
What could I be possibly doing wrong?
Thanks so much in advance!
EDIT:
Here's the entire code.
var renderer = null;
var scene = null;
var camera = null;
var controls = null;
var dae = null;
//var loader = null;
function init() {
renderer = new THREE.WebGLRenderer( { alpha: 1, antialias: true, clearColor: 0xffffff } );
renderer.setSize( 800, 600 );
var elem = $('.main3d')[0];
elem.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 20, 800/600, 1, 1000 );
camera.position.set( 0, -100, 50 );
//camera.lookAt( scene.position );
controls = new THREE.TrackballControls( camera, renderer.domElement );
var light = new THREE.AmbientLight( 0xffffff ); // soft white light
scene.add( light );
threeAnimate();
}
function load(url) {
loader = new THREE.ColladaLoader();
loader.load(url, function (collada) {
dae = collada.scene;
scene.add(dae);
});
}
function unload() {
dae.traverse(function(obj) {
console.log('unloading ' + obj.id);
scene.remove(obj);
if(obj.geometry)
obj.geometry.dispose();
if(obj.material)
obj.material.dispose();
if(obj.mesh)
obj.mesh.dispose();
if(obj.texture)
obj.texture.dispose();
});
scene.remove(dae);
}
var animFrame = null;
function animate() {
animFrame = requestAnimationFrame( threeAnimate );
renderer.render( scene, camera );
controls.update();
}
This should do the job:
function disposeNode (node)
{
if (node instanceof THREE.Mesh)
{
if (node.geometry)
{
node.geometry.dispose ();
}
if (node.material)
{
if (node.material instanceof THREE.MeshFaceMaterial)
{
$.each (node.material.materials, function (idx, mtrl)
{
if (mtrl.map) mtrl.map.dispose ();
if (mtrl.lightMap) mtrl.lightMap.dispose ();
if (mtrl.bumpMap) mtrl.bumpMap.dispose ();
if (mtrl.normalMap) mtrl.normalMap.dispose ();
if (mtrl.specularMap) mtrl.specularMap.dispose ();
if (mtrl.envMap) mtrl.envMap.dispose ();
if (mtrl.alphaMap) mtrl.alphaMap.dispose();
if (mtrl.aoMap) mtrl.aoMap.dispose();
if (mtrl.displacementMap) mtrl.displacementMap.dispose();
if (mtrl.emissiveMap) mtrl.emissiveMap.dispose();
if (mtrl.gradientMap) mtrl.gradientMap.dispose();
if (mtrl.metalnessMap) mtrl.metalnessMap.dispose();
if (mtrl.roughnessMap) mtrl.roughnessMap.dispose();
mtrl.dispose (); // disposes any programs associated with the material
});
}
else
{
if (node.material.map) node.material.map.dispose ();
if (node.material.lightMap) node.material.lightMap.dispose ();
if (node.material.bumpMap) node.material.bumpMap.dispose ();
if (node.material.normalMap) node.material.normalMap.dispose ();
if (node.material.specularMap) node.material.specularMap.dispose ();
if (node.material.envMap) node.material.envMap.dispose ();
if (node.material.alphaMap) node.material.alphaMap.dispose();
if (node.material.aoMap) node.material.aoMap.dispose();
if (node.material.displacementMap) node.material.displacementMap.dispose();
if (node.material.emissiveMap) node.material.emissiveMap.dispose();
if (node.material.gradientMap) node.material.gradientMap.dispose();
if (node.material.metalnessMap) node.material.metalnessMap.dispose();
if (node.material.roughnessMap) node.material.roughnessMap.dispose();
node.material.dispose (); // disposes any programs associated with the material
}
}
}
} // disposeNode
function disposeHierarchy (node, callback)
{
for (var i = node.children.length - 1; i >= 0; i--)
{
var child = node.children[i];
disposeHierarchy (child, callback);
callback (child);
}
}
and you use it
disposeHierarchy (YOUR_OBJECT3D, disposeNode);
I tweaked gaitat's already awesome answer to just use the now built in scene traverse function, to remove $ and also handle MultiMaterial. Why, oh why is there not a built in memory cleanup in THREE!!? Surely it should do it when you do scene.dispose(). I'm still trying to track down a couple more textures I'm using but don't seem to get dispose()ed according to renderer.info.memory.textures
this.disposeNode = function (parentObject) {
parentObject.traverse(function (node) {
if (node instanceof THREE.Mesh) {
if (node.geometry) {
node.geometry.dispose();
}
if (node.material) {
if (node.material instanceof THREE.MeshFaceMaterial || node.material instanceof THREE.MultiMaterial) {
node.material.materials.forEach(function (mtrl, idx) {
if (mtrl.map) mtrl.map.dispose();
if (mtrl.lightMap) mtrl.lightMap.dispose();
if (mtrl.bumpMap) mtrl.bumpMap.dispose();
if (mtrl.normalMap) mtrl.normalMap.dispose();
if (mtrl.specularMap) mtrl.specularMap.dispose();
if (mtrl.envMap) mtrl.envMap.dispose();
mtrl.dispose(); // disposes any programs associated with the material
});
}
else {
if (node.material.map) node.material.map.dispose();
if (node.material.lightMap) node.material.lightMap.dispose();
if (node.material.bumpMap) node.material.bumpMap.dispose();
if (node.material.normalMap) node.material.normalMap.dispose();
if (node.material.specularMap) node.material.specularMap.dispose();
if (node.material.envMap) node.material.envMap.dispose();
node.material.dispose(); // disposes any programs associated with the material
}
}
}
});
}
Building off the answers here, this code handles arrays of materials.
function disposeNode(parentObject) {
parentObject.traverse(function (node) {
if (node instanceof THREE.Mesh) {
if (node.geometry) {
node.geometry.dispose();
}
if (node.material) {
var materialArray;
if (node.material instanceof THREE.MeshFaceMaterial || node.material instanceof THREE.MultiMaterial) {
materialArray = node.material.materials;
}
else if(node.material instanceof Array) {
materialArray = node.material;
}
if(materialArray) {
materialArray.forEach(function (mtrl, idx) {
if (mtrl.map) mtrl.map.dispose();
if (mtrl.lightMap) mtrl.lightMap.dispose();
if (mtrl.bumpMap) mtrl.bumpMap.dispose();
if (mtrl.normalMap) mtrl.normalMap.dispose();
if (mtrl.specularMap) mtrl.specularMap.dispose();
if (mtrl.envMap) mtrl.envMap.dispose();
mtrl.dispose();
});
}
else {
if (node.material.map) node.material.map.dispose();
if (node.material.lightMap) node.material.lightMap.dispose();
if (node.material.bumpMap) node.material.bumpMap.dispose();
if (node.material.normalMap) node.material.normalMap.dispose();
if (node.material.specularMap) node.material.specularMap.dispose();
if (node.material.envMap) node.material.envMap.dispose();
node.material.dispose();
}
}
}
});
}
After research on the web and couple of refactoring, here is what I came up with:
disposeNode = ( node, recursive = false ) => {
if ( !node ) return;
if ( recursive && node.children)
for ( const child of node.children )
disposeNode( child , recursive );
node.geometry && node.geometry.dispose();
if ( !node.material ) return;
const materials = node.material.length === undefined ? [ node.material ] : node.material
for ( const material of materials ) {
for ( const key in material ) {
const value = material[key];
if ( value && typeof value === 'object' && 'minFilter' in value )
value.dispose();
}
material && material.dispose();
}
}
I am trying to load an object with OBJLoader and subdivide every geometry, so it would look smoother... but when I do so, texture is being ruined. How to avoid that?
var modifier = new THREE.SubdivisionModifier(4);
var loader = new THREE.OBJLoader( manager );
loader.load( 'obj/'+model+'.obj', function ( object ) {
var reflection = THREE.ImageUtils.loadTextureCube( [ 'additional/background.jpg', 'additional/background.jpg', 'additional/background_top.jpg', 'additional/background_down.jpg', 'additional/background.jpg', 'additional/background.jpg' ] );
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.geometry.mergeVertices()
child.geometry.computeFaceNormals();
child.geometry.computeVertexNormals();
child.material.map = texture;
if(reflex){
child.material.envMap = reflection;
child.material.reflectivity = reflex;
}
modifier.modify(child.geometry);
}
} );