Three js - Referencing individual meshes in imported blender scene - three.js

I am new to three.js and I have a basic blender scene with 2 different meshes which I have also named. I have managed to import the meshes into three but I would like to know how to reference and manipulate each mesh? is it possible if the 2 meshes are in the same file or should I load mesh1.js, mesh2.js etc?
This is the code:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body style="margin: 0;">
<script src="https://rawgithub.com/mrdoob/three.js/master/build/three.js"></script>
<script src="js/OrbitControls.js"></script>
<script>
// Set up the scene, camera, and renderer as global variables.
var scene, camera, renderer;
init();
animate();
// Sets up the scene.
function init() {
// Create the scene and set the scene size.
scene = new THREE.Scene();
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
// Create a renderer and add it to the DOM.
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(WIDTH, HEIGHT);
document.body.appendChild(renderer.domElement);
// Create a camera, zoom it out from the model a bit, and add it to the scene.
camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 20000);
camera.position.set(0,6,0);
scene.add(camera);
// Create an event listener that resizes the renderer with the browser window.
window.addEventListener('resize', function() {
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
// Set the background color of the scene.
renderer.setClearColorHex(0x333F47, 1);
// Create a light, set its position, and add it to the scene.
var light = new THREE.PointLight(0xffffff);
light.position.set(-100,-200,100);
scene.add(light);
var light2 = new THREE.PointLight(0xffffff);
light2.position.set(-100,200,100);
scene.add(light2);
// Load in the mesh and add it to the scene.
var loader = new THREE.JSONLoader();
loader.load( "tree-land.js", function( geometry, materials ){
var material = new THREE.MeshFaceMaterial( materials );
mesh = new THREE.Mesh( geometry, material );
scene.add(mesh);
});
// Add OrbitControls so that we can pan around with the mouse.
controls = new THREE.OrbitControls(camera, renderer.domElement);
}
// Renders the scene and updates the render as needed.
function animate() {
requestAnimationFrame(animate);
// Render the scene.
renderer.render(scene, camera);
controls.update();
}
</script>
</body>
</html>

The mesh variable you have declared is an Object3D, which can be manipulated using all the methods and properties as seen in the documentation: http://threejs.org/docs/#Reference/Core/Object3D
It looks like the call to JSONLoader.load returns a single geometry and material via the callback, so would not seem to support loading multiple files in a single call or multiple geometries in a single file. You might want to take a look at some of the other loaders for that purpose. I have successfully used the ColladaLoader (not in the documentation). Blender exports to Collada as well. The code is in examples/js/loaders of the repository.

Related

How to use three JS in html div tag

I use domElement to append the data in window. But how can I use html div tag to append it to the window?
I got the error
Cannot read property 'appendChild' of null
where I can't append my three js code in div element.
<html>
<head>
<title>
Ajay's Beginners guide
</title>
</head>
<body>
<script src="../build/three.js"></script>
<script>
var scene, camera, renderer;
function init() {
var scene = new THREE.Scene();
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
}
renderer = new THREE.WebGLRenderer();
// renderer.setSize(WIDTH, HEIGHT);
console.log(document.body);
canvas = document.getElementById('mycanvas');
canvas.appendChild(renderer.domElement);
renderer.setClearColorHex(0x333F47, 1);
//color and opacity
var light = new THREE.PointLight(0xffffff);
light.position.set(-100, 200, 100);
scene.add(light);
camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 2000);
camera.set.position(0, 6, 0);
scene.add(camera);
var loader = new THREE.JSONLoader();
loader.load("models/treehouse_logo.js", function (geometry) {
var material = new THREE.MeshLambertMaterial({ color: ox55B663 });
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
controls = new THREE.OrbitControls(camera, render.domElement);
window.addEventListener('resize', function () {
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
// init();
function animate() {
requestAnimationFrame();
renderer.render(scene, camera);
controls.update();
}
</script>
<div id="mycanvas">
</div>
</body>
</html>
Moving from comment to answer, thanks for the suggestion #Marquizzo.
Your script exists in the body before the div "canvas" element. The browser parses the page in order (mostly). So, you can move the script below the div so that it exists to be appended to as it is currently null when your code executes.
Here's a link to a related Stack Question on waiting for page contents before hooking them in code: Pure JavaScript equivalent of jQuery's $.ready() - how to call a function when the page/DOM is ready for it

Getting current geometry of animated mesh

How does one get the current geometry of an animated mesh? Say I have a mesh that's going through some animation and I want to grab a copy of the current pose and create a new mesh using it (not necessarily skinned the same way), how would I do this?
Edit: Here is a simple example. It loads one of the three.js example animations and runs it. The loaded mesh is skinnedMesh. Every time animate() is called, a copy of the mesh is made using skinnedMesh.geometry. The copy is called newMesh, and it's created with a simple red MeshBasicMaterial and offset to one side of the original.
If you run the code, you'll see that although skinnedMesh is animated, newMesh is always a copy of the untransformed geometry. If it was doing what I wanted it to do, newMesh would be in the same pose as skinnedMesh.
Clearly this particular example is very inefficient, as I could just make another copy of skinnedMesh and animate it separately. But that's not what I want to do. I want to be able to grab the state of the animated mesh at any point in time and make a copy of the pose it happens to be in at that moment.
I hope this makes everything clearer.
<!DOCTYPE html>
<html lang="en">
<head>
<title>test</title>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"> </script>
<script>
var container;
var camera, scene, renderer, loader, clock, light;
var skinnedMesh, animation, groundMaterial, planeGeometry, mixer;
var newMesh = null;
var loaded = false;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(10, 0, 10);
scene = new THREE.Scene();
loader = new THREE.JSONLoader();
clock = new THREE.Clock;
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
groundMaterial = new THREE.MeshPhongMaterial({
emissive: 0x010101
});
planeGeometry = new THREE.PlaneBufferGeometry(16000, 16000);
ground = new THREE.Mesh(planeGeometry, groundMaterial);
ground.position.set(0, -5, 0);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);
light = new THREE.HemisphereLight(0x777777, 0x003300, 1);
light.position.set(-80, 500, 50);
scene.add(light);
loader.load('http://threejs.org/examples/models/skinned/simple/simple.js', function(geometry, materials) {
for (var k in materials) {
materials[k].skinning = true;
}
skinnedMesh = new THREE.SkinnedMesh(geometry, new THREE.MeshFaceMaterial(materials));
skinnedMesh.scale.set(1, 1, 1);
skinnedMesh.position.y = 0;
scene.add(skinnedMesh);
mixer = new THREE.AnimationMixer(skinnedMesh);
mixer.addAction(new THREE.AnimationAction(skinnedMesh.geometry.animations[0]));
camera.lookAt(skinnedMesh.position);
loaded = true;
});
}
function animate() {
requestAnimationFrame(animate);
if (!loaded) {
return;
}
if (mixer) mixer.update(clock.getDelta());
if (newMesh) {
scene.remove(newMesh);
}
newMesh = new THREE.Mesh(skinnedMesh.geometry,
new THREE.MeshBasicMaterial({
color: 0xff0000
})
);
newMesh.position.x = skinnedMesh.position.x - 6;
scene.add(newMesh);
render();
}
function render() {
renderer.render(scene, camera);
}
</script>
</body>
</html>
You can directly reference the geometry property of a Mesh Object which contains an array of faces.
Manipulating the geometry ad-hoc is an adventure onto itself, but the structure should be clear enough.
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({
color: 0xffff00
});
var mesh = new THREE.Mesh(geometry, material);
console.log(mesh.geometry)
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.min.js"></script>
Bones don't affect the geometry in memory, their matrices are being applied to geometry in a shader during the rendering and that geometry can't be accessed from JS level.
You have to bake the current pose into positions and also to normals, implementation: https://stackoverflow.com/a/66404012/696535

What am I doing wrong, ReferenceError: sphereMaterial is not defined, following a getting started

I am reading this getting started https://aerotwist.com/tutorials/getting-started-with-three-js/.
I get the following error message,
ReferenceError: sphereMaterial is not defined
instead a sphere should show up.
What I have is a copy of three from http://threejs.org/build/three.min.js
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Try</title>
</head>
<body>
<div id="scene"><p>yo</p></div>
<script src="three.min.js"></script>
<script src="main.js"></script>
</body>
</html>
and main.js
// set the scene size
var WIDTH = 400,
HEIGHT = 300;
// set some camera attributes
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.1,
FAR = 10000;
// create a WebGL renderer, camera
// and a scene
var renderer = new THREE.WebGLRenderer();
var camera =
new THREE.PerspectiveCamera(
VIEW_ANGLE,
ASPECT,
NEAR,
FAR);
var scene = new THREE.Scene();
// add the camera to the scene
scene.add(camera);
// the camera starts at 0,0,0
// so pull it back
camera.position.z = 300;
// start the renderer
renderer.setSize(WIDTH, HEIGHT);
// attach the render-supplied DOM element
document.getElementById("scene").appendChild(renderer.domElement);
// set up the sphere vars
var radius = 50,
segments = 16,
rings = 16;
// create a new mesh with
// sphere geometry - we will cover
// the sphereMaterial next!
var sphere = new THREE.Mesh(
new THREE.SphereGeometry(
radius,
segments,
rings),
sphereMaterial);
// add the sphere to the scene
scene.add(sphere);
Well, you don't seem to be paying attention to the tutorial. Your JS obviously lacks any variable called sphereMaterial and had you searched your tutorial for that variable, you'd have seen this piece of code that you have not copied over to your script:
// create the sphere's material
var sphereMaterial =
new THREE.MeshLambertMaterial(
{
color: 0xCC0000
});

Apply MeshBasicMaterial to loaded obj+mtl with Three.js

When I load obj+mtl+jpg data with Three.js, the material of the object is set to MeshLambertMaterial according to three.js document. If I want to change the material to another one such as MeshBasicMaterial, how can I do that?
The following code loads obj+mtl+jpg from my server and display the data with MeshLambertMaterial. I tried to apply MeshBasicMaterial with the following code (commented), but it failed and the object with displayed in white.
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"></head>
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/loaders/MTLLoader.js"></script>
<script src="http://threejs.org/examples/js/loaders/OBJMTLLoader.js"></script>
<body>
<div id="canvas_frame"></div>
<script>
var canvasFrame, scene, renderer, camera;
canvasFrame = document.getElementById('canvas_frame');
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
canvasFrame.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set(50,50,50);
camera.lookAt( {x: 0, y: 0, z: 0} );
var ambient = new THREE.AmbientLight(0xFFFFFF);
scene.add(ambient);
function animate() {
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
function loadObjMtl(objUrl, mtlUrl, url, x, y, z){
var loader = new THREE.OBJMTLLoader();
loader.crossOrigin = 'anonymous';
loader.load( objUrl, mtlUrl,
function ( object ) {
object.url = url;
object.position.set(x,y,z);
object.traverse( function( node ) {
if( node.material ) {
////This doesn't work. How can I apply material for loaded obj?
//var material = new THREE.MeshBasicMaterial();
//if ( node instanceof THREE.Mesh ) {
// node.material = material;
//}
}
});
scene.add ( object );
});
}
objUrl = "http://test2.psychic-vr-lab.com/temp/mesh_reduced.obj";
mtlUrl = "http://test2.psychic-vr-lab.com/temp/mesh_reduced.mtl";
loadObjMtl(objUrl,mtlUrl,"",0,0,0);
animate();
</script>
</body>
</html>
You see it totally white because you are not specifying any color for MeshBasicMaterial.
Try this:
var material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
You'll see all red: it is working.
I think that you probably want to distinguish the different meshes, or apply any texture, do you?
I know this is an old question but I was looking to do something similar and did it this way:
As of today Three.js r143 , the MTLLoader loads the materials as MeshPhongMaterial as seen in
MTLLoader.js 495: this.materials[ materialName ] = new MeshPhongMaterial( params );
Because I didn't want any reflections from my lighting. So I set the materials to
MeshLambertMaterial (see the docs) by just changing that line to:
MTLLoader.js 495: this.materials[ materialName ] = new MeshLambertMaterial( params );
And that's it!.
I know this approach will change the way every other model is loaded but in my case that's how I wanted it.
Hope this helps

Texture applied only on canvas click with Three.js

I am using WebGLRenderer from Three.js to render an object reconstructed from an IndexedFaceStructure that has texture. My problem is that when the page loads the object shows up with no texture, only a black colored mesh displays, however, when i click on the canvas where i render the object the texture shows up.
I have been looking around and tried the texture.needsUpdate = true; trick, but this one removes also the black meshed object on page load so i am at a loss here.
These are the main bits of my code:
function webGLStart() {
container = document.getElementById("webgl-canvas");
renderer = new THREE.WebGLRenderer({canvas:container, alpha:true, antialias: true});
renderer.setClearColor(0x696969,1);
renderer.setSize(container.width, container.height) ;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, container.width / container.height, 1, 100000);
camera.position.set(60, 120,2000) ;
//computing the geometry2
controls = new THREE.OrbitControls( camera );
controls.addEventListener( 'change', render );
texture = new THREE.ImageUtils.loadTexture(texFile);
//texture.needsUpdate = true;
material = new THREE.MeshBasicMaterial( {wireframe:false, map: texture, vertexColors: THREE.VertexColors} );
mesh = new THREE.Mesh(geometry2, material);
scene.add(mesh);
render();
animate();
}
function render()
{
renderer.render(scene, camera);
}
function animate()
{
controls.update();
}
And the html part: canvas id="webgl-canvas" style="border: none;" width="900" height="900" (i could not add it properly).
Do you happen to have a clue why is this happening?
If you have a static scene, you do not need an animation loop, and you only need to render the scene when OrbitControls modifies the camera position/orientation.
Consequently, you can use this pattern -- without an animation loop:
controls.addEventListener( 'change', render );
However, you also need to force a render when the texture loads. You do that by specifying a callback to render in the ImageUtils.loadTexture() method:
var texture = THREE.ImageUtils.loadTexture( "textureFile", undefined, render );
Alternatively, you could add the mesh to the scene and call render in the callback.
three.js r.70

Resources