I am new to three.js and I am making an augmented reality app on web to show plates of food. So far I have managed to show a cube. The problem raised when I tried to show a model.obj instead of geomerty and material in mesh. Can anyone please help me on how to put a model.obj in THREE.MESH since I cannot do it. Below is my code.
function createContainer() {
var model = new THREE.Object3D();
model.matrixAutoUpdate = false;
return model;
}
function createMarkerMesh(color) {
manager = new THREE.LoadingManager();
manager.onProgress = function (item, loaded, total) {
console.log(item, loaded, total);
};
var texture = new THREE.Texture();
var loader = new THREE.ImageLoader(manager);
loader.load(texturePath, function (image) {
texture.image = image;
texture.needsUpdate = true;
texture.transparent = true;
});
var loader = new THREE.OBJLoader(manager);
return loader.load(objectPath, function (object) {
object.traverse(function (child) {
if (child instanceof THREE.Mesh) {
child.material.map = texture;
child.material.transparent = true;
}
});
object.position.z = -50;
return object;
});
// var geometry = new THREE.CubeGeometry( 100,100,100 );
//var material = new THREE.MeshPhongMaterial( {color:color, side:THREE.DoubleSide } );
var mesh = new THREE.Mesh( object.geometry, object.material);
mesh.position.z = -50;
return mesh;
}
function createMarkerObject(params) {
//return createMarkerMesh(params.color);
var modelContainer = createContainer();
var modelMesh = createMarkerMesh(params.color);
console.log(modelMesh);
modelContainer.add( modelMesh);
function transform(matrix) {
modelContainer.transformFromArray( matrix );
}
return {
transform: transform,
model: modelContainer
}
return {
createMarkerObject:createMarkerObject
}
}
The code in function Create MArker MEsh is were the cube was created and worked fine now I have commented those parts and tried to show the object but nothing is happening please help me.
try this change in your code... let me know if any problem raise..
function createMarkerMesh(color) {
manager = new THREE.LoadingManager();
manager.onProgress = function (item, loaded, total) {
console.log(item, loaded, total);
};
var texture = new THREE.Texture();
var loader = new THREE.ImageLoader(manager);
loader.load(texturePath, function (image) {
texture.image = image;
texture.needsUpdate = true;
texture.transparent = true;
});
var tmpMesh;
var loader = new THREE.OBJLoader(manager);
loader.load(objectPath, function (object) {
var group = new THREE.Object3D()
object.traverse(function (child) {
if (child instanceof THREE.Mesh) {
child.material.map = texture;
child.material.transparent = true;
//here in child the geometry and material are available
var mesh = new THREE.Mesh( child.geometry, child.material);
//mesh.position.z = -50;
group.add(mesh);
}
});
group.position.z = -50;
scene.add(group);//return group;
});
}
Finally I have found an answer for this problem and here I am going to explain what the problem was with the above code. Hope that this will help.
The problem was that the createMarkerObject was being called before the mesh was created so the object3D container was being filled with an empty mesh. To arrange this I have declared the modelContainer as a global variable and inserted it in the createMarkerMesh function. In the createMarkerObject I have made an if condition so that it checks if the modelContainer is full before adding it to the object3D container. Below is the updated code.
//Loads the model and texture and creates a mesh returns mesh with model
function createMarkerMesh() {
manager = new THREE.LoadingManager();
manager.onProgress = function (item, loaded, total) {
console.log(item, loaded, total);
};
var texture = new THREE.Texture();
var loader = new THREE.ImageLoader(manager);
loader.load(texturePath, function (image) {
texture.image = image;
texture.needsUpdate = true;
texture.transparent = true;
});
var tmpMesh, mesh;
var loader = new THREE.OBJLoader(manager);
loader.load(objectPath, function (object) {
tmpMesh = object; //store the object in a variable
object.traverse(function (child) {
if (child instanceof THREE.Mesh) {
child.material.map = texture;
child.material.transparent = true;
}
});
mesh = new THREE.Mesh( tmpMesh.children[0].geometry, tmpMesh.children[0].material);
mesh.position.z = -10;
mesh.scale.set( 3, 3, 0 );
console.log(mesh);
modelContainer.add( mesh);
console.log(modelContainer);
//return mesh;
});
}
//Creates the position of the object on the marker
function createMarkerObject() {
modelContainer = createContainer();
var modelMesh = createMarkerMesh();
//console.log(modelMesh);
if(modelMesh){
modelContainer.add( modelMesh);
}
function transform(matrix) {
modelContainer.transformFromArray( matrix );
}
return {
transform: transform,
model: modelContainer
}
return {
createMarkerObject:createMarkerObject
}
}
Related
I'm trying to load an animated model(.glb) in three.js but I get the above error.
If I paste the Load animated model function in the main method then it works but if I use it in a seperate class then it doesn't work anymore. Also the LoadStaticModel function does work but not the animated function. Any ideas what's wrong?
Thanks in advance!
Here is the code:
class CharacterControllerInput{
mixers = [];
scene;
constructor(scene){
this.scene = scene;
this.LoadAnimatedModel();
}
LoadAnimatedModel(scene){
this.scene = scene;
const loader = new GLTFLoader();
loader.load( 'http://127.0.0.1:3000/docs/BeterWerktDit.glb', function ( gltf ) {
gltf.scene.traverse( function ( object ) {
if ( object.isMesh ) object.castShadow = true;
} );
const model1 = SkeletonUtils.clone( gltf.scene );
const mixer1 = new THREE.AnimationMixer( model1 );
mixer1.clipAction( gltf.animations[ 0 ] ).play();
this.scene.add( model1);
this.mixers.push( mixer1);
render();
} );
}
Here is an abbreviated version of the class where I instantiate the Class.
class Scene{
constructor(){
this.main();
}
main(){
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas, antialias: true});
const scene = new THREE.Scene();
this.character = new CharacterControllerInput(scene);
render();
function render(){
const width = window.innerWidth;
const height = window.innerHeight;
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(width, height, false);
const delta = clock.getDelta();
for (const mixer of mixers) mixer.update(delta);
renderer.render(scene, camera);
requestAnimationFrame(render)
}
requestAnimationFrame(render);
}
};
let _APP = null;
window.addEventListener('DOMContentLoaded', () => {
_APP = new Scene();
});
I resolved the error. The problem was that I used this.scene in the callback function. So this. refers to the callback function.
The code below works:
class CharacterControllerInput {
scene;
constructor(scene, mixers) {
this.scene = scene;
this.mixers = mixers;
this.LoadAnimatedModel();
}
_init() {
this.LoadAnimatedModel();
}
LoadAnimatedModel() {
const loader = new GLTFLoader();
const scene = this.scene;
const mixers = this.mixers;
loader.load('http://127.0.0.1:3000/docs/BeterWerktDit.glb', function (gltf) {
gltf.scene.traverse(function (object) {
if (object.isMesh) object.castShadow = true;
});
const model = SkeletonUtils.clone(gltf.scene);
const mixer = new THREE.AnimationMixer(model);
mixer.clipAction(gltf.animations[0]).play();
scene.add(model);
mixers.push(mixer);
});
}
I did a Drawing class:
export class Drawing {
constructor(texture) {
const material = new MeshBasicMaterial({
color: 0xffffff,
map: texture
});
this.mesh = new Mesh(new PlaneGeometry(texture.image.naturalWidth / 20, texture.image.naturalHeight / 20), material);
}
setPosition(x, y, z) {
this.mesh.position.x = x;
this.mesh.position.y = y;
this.mesh.position.z = z;
}
}
I would like to access the texture.image properties in order to set the PlaneGeometry. So, before invoking a Drawing object, I do some async/await calls to load the textures (the Drawing invokations are made in the World constructor):
let world;
let raycasterDown;
let prevTime = performance.now();
const direction = new Vector3();
const globalInputs = new GlobalInputs();
const textureLoader = new TextureLoader();
const promiseTextureBack = (pathName) => {
return new Promise((resolve) => {
resolve(textureLoader.load(pathName));
});
}
const allTexturesPromises = [];
drawingPaths.map(pathName => { //drawingPaths is an array of string
allTexturesPromises.push(promiseTextureBack(pathName));
});
const loadingWorld = async () => {
const allTextures = await Promise.all(allTexturesPromises);
console.log(allTextures[0]);
world = new World(allTextures);
document.body.appendChild(world.renderer.domElement);
world.instructions.addEventListener('click', function () {
world.controls.lock();
});
world.controls.addEventListener('lock', function () {
world.instructions.style.display = 'none';
world.blocker.style.display = 'none';
});
world.controls.addEventListener('unlock', function () {
world.blocker.style.display = 'block';
world.instructions.style.display = '';
});
}
init();
function init() {
loadingWorld();
raycasterDown = new Raycaster(new Vector3(), new Vector3(0, -1, 0), 0, 10);
document.addEventListener('keydown', (event) => {
globalInputs.onKeyDown(event);
});
document.addEventListener('keyup', (event) => {
globalInputs.onKeyUp(event);
});
window.addEventListener('resize', onWindowResize);
animate();
}
Nevertheless,
console.log(allTextures[0])
in the loadingWorld returns:
And the image is still undefined... I'm quite sure the issue comes from:
textureLoader.load(pathName)
I'm open to any suggestions !
The load method takes a callback. It doesn't return anything that you can call resolve with. Instead of all this promiseTextureBack code, just use the loadAsync method which returns a promise:
const allTexturesPromises = drawingPaths.map(pathName => {
return textureLoader.load(pathName);
});
I tried loading my .obj and .mtl files and now I'm trying to change the material of the object on keypress, I'm able to do changes as expected using texture images. However when I want to change materials using a phong effect with or without texture image it's not happening.
//THIS METHOD IS WORKING FINE WHEN I USE BASIC TEXTURE IMAGES ON KEYPRESS
var texture1 = THREE.ImageUtils.loadTexture("neavik/9.jpg");
var texture2 = THREE.ImageUtils.loadTexture("neavik/10.jpg");
if (child.material.name == "meta") {
child.material.map = texture1;
child.material.needsUpdate = true;
child.receiveShadow = true;
var index = 0;
var onKeyDown = function(event) {
if (event.keyCode == 67) { // when 'c' is pressed
object.traverse(function(child) {
if (child instanceof THREE.Mesh) {
var colors = [texture2, texture1];
if (child.material.name == "meta") {
if (index == colors.length) index = 0;
child.material.map = colors[index++];
child.material.needsUpdate = true;
child.geometry.buffersNeedUpdate = true;
child.geometry.uvsNeedUpdate = true;
}
}
}
};
document.addEventListener('keyup', onKeyDown, true);
});
// Now if i try to change materials with same effect like PHONG it doesn't reflect any changes on Keypress.[ No errors as well ]
var texture1 = THREE.ImageUtils.loadTexture("neavik/9.jpg");
var texture2 = THREE.ImageUtils.loadTexture("neavik/10.jpg");
var cleana = new THREE.MeshPhongMaterial({
map: texture1,
color: 0xffffef,
specular: 0x000000,
combine: THREE.AddOperation,
reflectivity: 0
})
var cleanb = new THREE.MeshPhongMaterial({
map: texture2,
color: 0xffffef,
specular: 0x000000,
combine: THREE.AddOperation,
reflectivity: 0
})
if (child.material.name == "meta") {
child.material = cleana;
child.material.needsUpdate = true;
child.receiveShadow = true;
var index = 0;
var onKeyDown = function(event) {
if (event.keyCode == 67) { // when 'c' is pressed
object.traverse(function(child) {
if (child instanceof THREE.Mesh) {
var colors = [cleana, cleanb];
if (child.material.name == "meta") {
if (index == colors.length) index = 0;
child.material = colors[index++];
child.material.needsUpdate = true;
child.geometry.buffersNeedUpdate = true;
child.geometry.uvsNeedUpdate = true;
}
}
}
};
document.addEventListener('keyup', onKeyDown, true);
});
Please let me know if there is another way to do this.
If you want to exchange the whole material you need to do:
child.material = cleana;
or
child.material = cleanb;
and then you most likely have to do:
child.material.needsUpdate = true;
Read more on how to update material in this How to update things reference page in the chapter Materials
UPDATE
So if you want to set using an array:
var materials = [cleana, cleanb];
child.material = materials[0]; // cleana
or
child.material = materials[1]; // cleanb
and then also most likely:
child.material.needsUpdate = true;
It works totally fine with an array of materials. You don't even need to set material.needsUpdate = true. A demonstration here in this fiddle.
My scene temporary freeze during the loading time of my model. Are there any options in preventing this?
var loader = new THREE.JSONLoader();
loader.load("model.js", function(smooth) {
smooth.mergeVertices();
smooth.computeFaceNormals();
smooth.computeVertexNormals();
var modifier = new THREE.SubdivisionModifier(1);
modifier.modify(smooth);
scene.add(smooth);
var mesh = new THREE.Mesh(smooth, new THREE.MeshPhongMaterial({
color : 0x222222
}));
$('input').change(function() {
if ($("#radio1, #radio2, #radio3, #radio4").is(":checked")) {
scene.add(vierring);
} else if ($("#radio1, #radio2, #radio3, #radio4").is(":not(:checked)")) {
scene.remove(vierring);
}
});
});
I am writing a class for rotating cube, but every time i rotate it or zoom, i get an error "Cannot read property 'render' of undefined". What am i doing wrong? I guess something is wrong with the scopes. Here is my class:
myclass = function() {
this.camera = null;
this.scene = null;
this.renderer = null;
this.product = null;
this.init = function (container) {
this.scene = new THREE.Scene();
this.camera = createCamera();
this.product = createProduct();
this.scene.add(this.product);
this.createRenderer();
this.setControls();
container.appendChild(this.renderer.domElement);
this.animate();
};
function createCamera() {
var camera = new THREE.PerspectiveCamera(20, 300 / 400, 1, 10000);
camera.position.z = 1800;
return camera;
}
function createProduct() {
var geometry = new THREE.BoxGeometry(300, 200, 200);
var materials = ...;
var product = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
return product;
}
this.createRenderer = function () {
this.renderer = new THREE.WebGLRenderer({antialias: true});
this.renderer.setClearColor(0xffffff);
this.renderer.setSize(this.sceneWidth, this.sceneHeight);
};
this.setControls = function () {
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.controls.addEventListener('change', this.render);
};
this.animate = function () {
requestAnimationFrame(this.animate.bind(this));
this.render();
};
this.render = function () {
this.renderer.render(this.scene, this.camera);
};
};
Tnanks.
You have a scoping problem in the callback specified here:
this.controls.addEventListener( 'change', this.render );
In your class, add
var scope = this;
Then rewrite your render() method like so:
this.render = function () {
scope.renderer.render( scope.scene, scope.camera );
};
Also, the point of adding the event listener is to remove the animation loop.
So..., remove it, and only render when the mouse causes the camera to move.
You will also have to call render() once initially, and after models load.
three.js r.69
You can use bind method as follows it will hold the scope for the render method
this.controls.addEventListener( 'change', this.render.bind(this) );