Three.js basics – Can't find variable – GLTFLoader animation - javascript

I’ve got very basic knowledge of JS. Following three.js docs Loading 3D models I have succesfully rendered 3D object and centered it:
const loader = new GLTFLoader();
loader.load( 'Duck.gltf', function ( duck ) {
const model = duck.scene
const box = new THREE.Box3().setFromObject( model );
const center = new THREE.Vector3();
box.getCenter( center );
model.position.sub( center ); // center the model
scene.add( model );
}, undefined, function ( error ) {
console.error( error );
} );
I would like to animate it now, begining with simple rotation:
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// Update objects
model.rotation.y = .5 * elapsedTime
// Update Orbital Controls
// controls.update()
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()
But the problem is console returns:
ReferenceError: Can't find variable: model

You have declared model variable inside the functional scope, try to declare it outside
const loader = new GLTFLoader();
let model, box, center;
loader.load( 'Duck.gltf', function ( duck ) {
model = duck.scene
box = new THREE.Box3().setFromObject( model );
center = new THREE.Vector3();
box.getCenter( center );
model.position.sub( center ); // center the model
scene.add( model );
}, undefined, function ( error ) {
console.error( error );
} );
Hopefully, this will work!
Edited
as #prisoner849 suggested in the comments
const clock = new THREE.Clock()
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// Update objects
if (model) {
model.rotation.y = .5 * elapsedTime
}
// Update Orbital Controls
// controls.update()
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()

Related

THREE.JS Skybox not displayed on screen

I am trying to include a skybox to a scene in a web page, so I followed a tutorial I found (https://dev.to/codypearce/how-to-create-a-skybox-with-three-js-2bn8), everything seems to work correctly as my console (on firefox) doesn't display any errors or warning.
First the code creates an array of texture, then assembles the textures in a box and finally adds the box to the scene. I don't have any light in my scene as every objects are visible without any so far (and when I try to add one it doesn't change anything, and the console doesn't show any error either).
I thought it could be a matter of distance of the camera to the skybox so I got the skybox closer to the camera but still nothing.
I put my code down here if you want to see what I did so far. Thanks in advance for your help!
var camera, scene, rendu;
var r, t;
init();
function init() {
r = 3;
t = 1.1;
// ---------- scene et camera --------- //
camera = new THREE.PerspectiveCamera( 70 , window.innerWidth / window.innerHeight , 0.01 , 2000 );
camera.position.set( 0 , 0 , 4 );
camera.lookAt(new THREE.Vector3( 0, 0, 0 ));
scene = new THREE.Scene();
loadSkybox();
// ---------- rendu ------------- //
rendu = new THREE.WebGLRenderer( { antialias: true} );
rendu.setSize( window.innerWidth, window.innerHeight );
rendu.setPixelRatio(window.devicePixelRatio);
rendu.setAnimationLoop( animation );
document.body.appendChild( rendu.domElement );
}
function animation() {
rendu.render( scene, camera );
}
function createPathStrings(filename) {
const basePath = "./ulukai/";
const baseFilename = basePath + filename;
const fileType = ".png";
const sides = ["ft", "bk", "up", "dn", "rt", "lf"];
const pathStrings = sides.map(side => {
return baseFilename + "_" + side + fileType;
});
return pathStrings;
}
function createMaterialArray(filename) {
const skyboxImagepaths = createPathStrings(filename);
const materialArray = skyboxImagepaths.map(image => {
let texture = new THREE.TextureLoader().load(image);
return new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide });
});
return materialArray;
}
function loadSkybox() {
// ----------- skybox -------------- //
var skyboxImage = "corona";
skyboxGeo = new THREE.BoxGeometry(1000, 1000, 1000);
skybox = new THREE.Mesh(skyboxGeo, createMaterialArray(skyboxImage));
scene.add(skybox);
}

Three Js GLTF loader model not showing up

I am trying to use Three js to load in a 3d heart model and attach a picture to the front of the heart but it seems the heart isn’t showing up at all even without loading the image in. I am new at Three js so I might be doing it all wrong but I tried using the code straight from the documents and it still isn’t working. I am getting no errors and I can see AxesHelper also I have loaded in a cube and that works so I don't think there is a problem with my scene.
function handleHeart(img) {
document.getElementById("divRight").innerHTML = ""
let renderer = new THREE.WebGLRenderer();
document.getElementById("divRight").appendChild(renderer.domElement);
let w = document.getElementById("divRight").clientWidth
let h = 600
renderer.setSize( w, h)
let camera = new THREE.PerspectiveCamera(35, w / h, 0.1, 3000 );
const controls = new THREE.OrbitControls( camera, renderer.domElement );
camera.position.set( 0, 0, 10 );
camera.lookAt(new THREE.Vector3(0, 0, 0))
controls.update();
let scene = new THREE.Scene();
scene.background = new THREE.Color( 'grey' );
light = new THREE.AmbientLight(0xffffff);
scene.add(light);
const loader = new THREE.GLTFLoader();
loader.load(
// resource URL
'models/heart_v1.glb',
// called when the resource is loaded
function ( gltf ) {
let material = new THREE.MeshBasicMaterial( { map: img } );
let model = gltf.scene || gltf.scenes[0];
//model.scale.set(1000,1000,1000)
model.material = material
scene.add(model)
model.position.z = -10
},
// called while loading is progressing
function ( xhr ) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
},
// called when loading has errors
function ( error ) {
console.log(error)
console.log( 'An error happened' );
})
scene.add(new THREE.AxesHelper(100))
renderer.render(scene, camera)
}
here is a replit : https://repl.it/#AlecStein44/Threejs-help#javascript.js
model.material = material
This line is incorrect. It should be:
model.traverse( function( object ) {
if ( object.isMesh ) object.material = material;
} );
Notice that applying a texture will still not work since your model heart_v1.glb has no texture coordinates.

Centering and Resizing GLTF models automatically in Three.js

After downloading 3D models from sketchfab.com and importing them into a Three.js scene, they are most of the times not centered and their size is very big.
Is there a way to automatically center and scale gltf models using a script or a software (working on Linux) or otherwise to do it on-the-fly in Three.js and having the camera to move around the object using OrbitControls.
Yep. This code takes a node and finds its size and centerpoint, then rescales it so the max extent is 1 and its centered at 0,0,0, and above the ground on the y axis.
var mroot = yourScene;
var bbox = new THREE.Box3().setFromObject(mroot);
var cent = bbox.getCenter(new THREE.Vector3());
var size = bbox.getSize(new THREE.Vector3());
//Rescale the object to normalized space
var maxAxis = Math.max(size.x, size.y, size.z);
mroot.scale.multiplyScalar(1.0 / maxAxis);
bbox.setFromObject(mroot);
bbox.getCenter(cent);
bbox.getSize(size);
//Reposition to 0,halfY,0
mroot.position.copy(cent).multiplyScalar(-1);
mroot.position.y-= (size.y * 0.5);
var loader = new THREE.GLTFLoader();
loader.load( 'models/yourmodel.gltf',
function ( gltf ) {
gltf.scene.traverse( function ( child ) {
if ( child.isMesh ) {
child.geometry.center(); // center here
}
});
gltf.scene.scale.set(10,10,10) // scale here
scene.add( gltf.scene );
}, (xhr) => xhr, ( err ) => console.error( e ));
To move around the object use controls.target() https://threejs.org/docs/#examples/controls/OrbitControls.target
I resized in REACT like this!
import React, { useState, useRef, useEffect } from "react";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
const Earth = () => {
const [model, setModel] = useState();
useEffect(() => {
new GLTFLoader().load("scene.gltf", (model) => {
model.scene.scale.set(0.03, 0.03, 0.03);
setModel(model);
});
});
return model ? <primitive object={model.scene} /> : null;
};
Just call <Earth />
Other proposed solutions did not work well for me.
However, I tested the simple approach below on several models to be loaded into my scenes and it works nicely.
const loader = new GLTFLoader();
loader.load("assets/model_path/scene.gltf",
(gltf) => {
gltf.scene.scale.multiplyScalar(1 / 100); // adjust scalar factor to match your scene scale
gltf.scene.position.x = 20; // once rescaled, position the model where needed
gltf.scene.position.z = -20;
scene.add(gltf.scene);
render();
},
(err) => {
console.error(err);
}
);

How can you make WebGL text geometry selectable after you add it to the scene of the viewer?

After adding a text geometry object to the viewer, I noticed I am not able to select it with the mouse like I can with the rest of the objects in the viewer. How can I make this selectable? I haven't tried anything, as I have not seen any ideas in the docs. I want to be able to listen for the selection event, which I have down, I just don't know how to make this new TextGeometry object selectable. Here is my code, sorry I didn't include it before.
createText (params) {
const geometry = new TextGeometry(params.text,
Object.assign({}, {
font: new Font(FontJson),
params
}))
const material = this.createColorMaterial(
params.color)
const text = new THREE.Mesh(
geometry , material)
text.position.set(
params.position.x,
params.position.y,
params.position.z)
this.viewer.impl.scene.add(text)
this.viewer.impl.sceneUpdated(true)
}
Thank you!
According to my experience, Forge Viewer will only interact with translated models (SVF, F2D) from Forge Model Derivative service, as well as the Built-in APIs. The text geometry seems like a custom one which is created via THREE.TextGeometry, isn't it? You didn't tell much about that.
If you want to interact with custom geometries in Forge Viewer, you must have to implement a Viewer Tool and add some self-logic in, such as highlight a text geometry while mouse clicking. You can refer here for the more details: https://forge.autodesk.com/cloud_and_mobile/2015/03/creating-tools-for-the-view-data-api.html
Since I didn't see any codes from you here, I assume your text is put into _viewer.sceneAfter and here is an example for mouse left clicking:
// change color of custom geometries while mouse clicking
handleSingleClick( event, button ) {
const _viewer = this.viewer;
const intersectObjects = (function () {
const pointerVector = new THREE.Vector3();
const pointerDir = new THREE.Vector3();
const ray = new THREE.Raycaster();
const camera = _viewer.impl.camera;
return function(pointer, objects, recursive) {
const rect = _viewer.impl.canvas.getBoundingClientRect();
const x = (( pointer.clientX - rect.left) / rect.width ) * 2 - 1;
const y = - (( pointer.clientY - rect.top) / rect.height ) * 2 + 1;
if (camera.isPerspective) {
pointerVector.set( x, y, 0.5 );
pointerVector.unproject( camera );
ray.set( camera.position, pointerVector.sub( camera.position ).normalize() );
} else {
pointerVector.set( x, y, -1 );
pointerVector.unproject( camera );
pointerDir.set( 0, 0, -1 );
ray.set( pointerVector, pointerDir.transformDirection( camera.matrixWorld ) );
}
const intersections = ray.intersectObjects( objects, recursive );
return intersections[0] ? intersections[0] : null;
};
})();
const pointer = event.pointers ? event.pointers[ 0 ] : event;
// Get interseted custom geometries
const result = intersectObjects( pointer, _viewer.sceneAfter.children );
if( result && result.object ) {
const mesh = result.object;
// Change object color
let curColor = mesh.material.color;
curColor = ( curColor.getHex() == 0xff0000 ? 0x00ff00 : 0xff0000 );
mesh.material.color.setHex( curColor );
// Rerender
this.viewer.impl.invalidate( true, true, false );
}
return false;
}
Hope it helps.
I wrote an article dedicated to that topic: Handling custom meshes selection along model components in the Forge Viewer

How to apply texture on an object loaded by OBJLoader?

I'm currently working on my little project using three.js, but I am having hard time mapping texture on objects loaded by THREE.OBJLoader. There is no such problem for three.js built in geometry. I'm really confused now...
// Load the model from obj file [teapot, 74KB]
var onProgress = function ( xhr ) {
};
var onError = function ( xhr ) {
};
//var oCubeMap = THREE.ImageUtils.loadTexture(imageNames);
var objTexture = new THREE.ImageUtils.loadTexture(textureImage);
var oMaterial = new THREE.MeshLambertMaterial( { map: objTexture } );
var ooMaterial = new THREE.MeshPhongMaterial( { color: 0x99CCFF } );
var thisTexture = THREE.ImageUtils.loadTexture( textureImage, {}, function() {
renderer.render(scene, camera);
} );
var loader = new THREE.OBJLoader();
loader.load(teapot, function (object) {
object.position.set(8, -5, -25);
object.scale.set(0.04, 0.04, 0.04);
object.rotation.set(0, 180 * Math.PI / 180, 0);
// object.color = '0x99CCFF';
object.material = ooMaterial;
// object.texture = thisTexture;
scene.add(object);
}, onProgress, onError);
As you can see I've tried several things like color, material and texture but nothing worked so far. Can anyone tell me how to apply a texture onto this object?
OBJLoader returns an object with children. Add the following to your loader callback function:
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.material.map = texture;
}
} );
Make sure your model geometry has UVs, otherwise textures are not supported.
three.js r.73

Categories

Resources