Three.js subdivisions and texture - three.js

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);
}
} );

Related

What does "Cannot set property 'getStrokeStyle' of undefined" mean for a Three.js program?

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?

Remove PointLight in ThreeJS Model Loader

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!

Error loading three.js Collada file: Works fine on first object, succeeding objects fail

I have a problem loading Collada files on three.js. The first object I load works properly:
But when I remove it from the scene and load another object (after removing from the scene and disposing assets), the geometry appears garbled:
What could be the problem?
Here's the code used:
(Special thanks to #gaitat for the dispose functions! originally answered here.
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);
});
}
var animFrame = null;
function animate() {
animFrame = requestAnimationFrame( threeAnimate );
renderer.render( scene, camera );
controls.update();
}
// special thanks to #gaitat for this function!
// originally answered here https://stackoverflow.com/questions/33152132/three-js-collada-whats-the-proper-way-to-dispose-and-release-memory-garbag/33199591
function disposeNode (node)
{
if (node instanceof THREE.Camera)
{
node = undefined;
}
else if (node instanceof THREE.Light)
{
node.dispose ();
node = undefined;
}
else if (node instanceof THREE.Mesh)
{
if (node.geometry)
{
node.geometry.dispose ();
node.geometry = undefined;
}
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 ();
mtrl.dispose (); // disposes any programs associated with the material
mtrl = undefined;
});
}
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
node.material = undefined;
}
}
node = undefined;
}
else if (node instanceof THREE.Object3D)
{
node = undefined;
}
} // 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);
}
}

Three.js Collada - What's the proper way to dispose() and release memory (garbage collection)?

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();
}
}

Targeting specific material by name in the imported model

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 } );
}
} );

Resources