I would like to animate a bezier curve in ThreeJS. The start, end and control points will update. Eventually I will need to have many curves animating at once. What's the most efficient way to do this?
If you run the code snippet below, you'll see that I'm creating the Bezier object, Geometry and Line each time the frame renders. I'm removing the previous line from the scene and then adding the new, updated line. Is there a better way? Perhaps updating only the geometry and not adding the line again?
var camera, scene, renderer, geometry, material, mesh;
init();
animate();
/**
Create the scene, camera, renderer
*/
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 500;
scene.add(camera);
addCurve();
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
/**
Add the initial bezier curve to the scene
*/
function addCurve() {
testPoint = 0;
curve = new THREE.CubicBezierCurve3(
new THREE.Vector3( testPoint, 0, 0 ),
new THREE.Vector3( -5, 150, 0 ),
new THREE.Vector3( 20, 150, 0 ),
new THREE.Vector3( 10, 0, 0 )
);
curveGeometry = new THREE.Geometry();
curveGeometry.vertices = curve.getPoints( 50 );
curveMaterial = new THREE.LineBasicMaterial( { color : 0xff0000 } );
curveLine = new THREE.Line( curveGeometry, curveMaterial );
scene.add(curveLine);
}
/**
On each frame render, remove the old line, create new curve, geometry and add the new line
*/
function updateCurve() {
testPoint ++;
scene.remove(curveLine);
curve = new THREE.CubicBezierCurve3(
new THREE.Vector3( testPoint, 0, 0 ),
new THREE.Vector3( -5, 150, 0 ),
new THREE.Vector3( 20, 150, 0 ),
new THREE.Vector3( 10, 0, 0 )
);
curveGeometry = new THREE.Geometry();
curveGeometry.vertices = curve.getPoints( 50 );
curveLine = new THREE.Line( curveGeometry, curveMaterial );
scene.add(curveLine);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
updateCurve();
renderer.render(scene, camera);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.min.js"></script>
Creating new lines per each frame is very exepnsive opertation.
What is that the best way to create animated curves?
Probably using shaders. But it can take much more time to implement, so if my next suggestions will be enough for you, just them.
Improve curve updating in your code
I tried not to change a lot of your code. Marked edited places with "// EDITED" comment. I added an array cause you said that there will be many curves.
Explanation
As #WestLangley said, try to avoid using new keyword inside animation loop.
CubicBezierCurve3 has v0, v1, v2 and v3 attributes. Those are THREE.Vector3`s that you provide from the beginning. .getPoints() uses them to return you vertices array. So you can simply change them and no new keyword is needed. See this line for more details.
Rather then recreating line, in your case you can simply update geometry. As you have a THREE.Line - your geometry only needs vertices. After changing vertices you should set geometry.verticesNeedUpdate = true or Three.js will ignore your changes.
var camera, scene, renderer, geometry, material, mesh, curves = [];
init();
animate();
/**
Create the scene, camera, renderer
*/
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 500;
scene.add(camera);
addCurve();
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
/**
Add the initial bezier curve to the scene
*/
function addCurve() {
testPoint = 0;
curve = new THREE.CubicBezierCurve3(
new THREE.Vector3( testPoint, 0, 0 ),
new THREE.Vector3( -5, 150, 0 ),
new THREE.Vector3( 20, 150, 0 ),
new THREE.Vector3( 10, 0, 0 )
);
curveGeometry = new THREE.Geometry();
curveGeometry.vertices = curve.getPoints( 50 );
curveMaterial = new THREE.LineBasicMaterial( { color : 0xff0000 } );
curveLine = new THREE.Line( curveGeometry, curveMaterial );
scene.add(curveLine);
// EDITED
curves.push(curveLine); // Add curve to curves array
curveLine.curve = curve; // Link curve object to this curveLine
}
/**
On each frame render, remove the old line, create new curve, geometry and add the new line
*/
function updateCurve() {
testPoint ++;
// EDITED
for (var i = 0, l = curves.length; i < l; i++) {
var curveLine = curves[i];
// Update x value of v0 vector
curveLine.curve.v0.x = testPoint;
// Update vertices
curveLine.geometry.vertices = curveLine.curve.getPoints( 50 );
// Let's three.js know that vertices are changed
curveLine.geometry.verticesNeedUpdate = true;
}
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
updateCurve();
renderer.render(scene, camera);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.min.js"></script>
Related
I´m trying to make use of layers in Three.js.
I have this script with a sphere, a triangle and a glTF object (car).
I made a second layer enable: camera.layers.enable(1);
Set the sphere, the triangle and the glTF object to the layer 1:
car.layers.set( 1 );
sphere.layers.set( 1 );
triangle.layers.set( 1 );
But when i set the camera to the layer 1 ( camera.layers.set(1); ), the glTF object does not display, but other elements do. So, it seens like i can´t set the glTF object to a different layer then default layer.
Here is the code. What could be wrong?
Thanks for the attention!
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(50, window.innerWidth/window.innerHeight, 0.1, 3000);
camera.position.set( 0, 0.1, 1 );
camera.layers.enable(1);
camera.layers.set(1);
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setClearColor("#f5e5e5");
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// LIGHT ------------------------------------------------------------------------------>
var dLight = new THREE.DirectionalLight( 0x0000ff, 6.5 );
dLight.position.set(1500, -700, 500);
dLight.castShadow = true;
dLight.layers.set(1);
scene.add( dLight);
// Load a glTF resource -------------------------------------------------------->
var loader = new THREE.GLTFLoader();
var car;
loader.load('car.gltf', function ( gltf ) {
car = gltf.scene.children[0];
car.scale.set(0.5, 0.5, 0.5);
car.position.z = 0;
car.position.y = -0.095;
car.layers.set(1);
scene.add( gltf.scene );
render();
});
// SPHERE --------------------------------------------------------------->
var material = new THREE.MeshLambertMaterial();
var geometry = new THREE.SphereGeometry(0.05, 20, 20);
var sphere = new THREE.Mesh(geometry, material);
sphere.position.x = 0.25;
scene.add(sphere);
sphere.layers.set( 1 );
// TRIANGLE ------------------------------------------------------------->
var geometre = new THREE.Geometry();
geometre.vertices.push(new THREE.Vector3(-0.25, -0.1, 0));
geometre.vertices.push(new THREE.Vector3(0, 0.30, 0));
geometre.vertices.push(new THREE.Vector3(0.25, -0.1, 0));
geometre.vertices.push(new THREE.Vector3(-0.25, -0.1, 0));
var triangle= new THREE.Line(geometre, new THREE.LineBasicMaterial({ color: 0x000000, linewidth: 12 }));
triangle.layers.set( 1 );
scene.add(triangle);
// POST-PROCESSING ------------------------------------------------------->
var composer = new THREE.EffectComposer(renderer);
var renderPass = new THREE.RenderPass(scene, camera);
composer.addPass(renderPass);
var pass1 = new THREE.GlitchPass(0);
composer.addPass(pass1);
// RENDER -------------------------------------------------------------->
requestAnimationFrame(render);
function render(){
sphere.rotation.y += -0.02;
car.rotation.y += 0.01;
composer.render();
requestAnimationFrame(render);
};
</script>
You have to set layers recursively for the entire hierarchy of objects like so:
gltf.scene.traverse( function( object ) {
object.layers.set( 1 );
} );
By default, a layer configuration does not automatically apply to its descendant nodes in the scene graph.
three.js r116
I'm pretty beginner in three.js and webgl programming. so I have created a box in three.js and its working fine but the problem is when I set camera position in z axis(eg: camera.position.z = 2; ) the box just disappears. could anyone explain me why is it happening and how can I properly set the position of the camera?
try uncommenting the camera.position.z = 2; in the fiddle
function init() {
var scene = new THREE.Scene();
var box = getBox(1, 1, 1);
scene.add(box);
var camera = new THREE.Camera(45, window.innerWidth/window.innerHeight, 1, 1000 );
//camera.position.z = 2;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("webgl").appendChild(renderer.domElement);
renderer.render(scene, camera);
}
function getBox(w, h, d) {
var geometry = new THREE.BoxGeometry(w, h, d);
var material = new THREE.MeshBasicMaterial({
color : 0x00ff00
});
var mesh = new THREE.Mesh(geometry, material);
return mesh;
}
init();
not sure if you're trying to create this scene with an orthographic camera or perspective camera, but you'll typically need to specify the camera by type (ie THREE.PerspectiveCamera(...)).
I also added a few extra lines to ensure the camera was configured correctly, namely, setting the "LookAt" point to (0,0,0) , as well as setting an actual position of the camera via the THREE.Vector3.set(...) method.
Here are my adjustments to your code:
function init() {
var scene = new THREE.Scene();
var box = getBox(1, 1, 1);
scene.add(box);
var camera = new THREE.PerspectiveCamera(70,
window.innerWidth/window.innerHeight, 0.1, 1000 ); // Specify camera type like this
camera.position.set(0,2.5,2.5); // Set position like this
camera.lookAt(new THREE.Vector3(0,0,0)); // Set look at coordinate like this
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("webgl").appendChild(renderer.domElement);
renderer.render(scene, camera);
}
function getBox(w, h, d) {
var geometry = new THREE.BoxGeometry(w, h, d);
var material = new THREE.MeshBasicMaterial({
color : 0x00ff00
});
var mesh = new THREE.Mesh(geometry, material);
return mesh;
}
init();
Try
camera.position.set( <X> , <Y> , <Z> );
where <X> and <Z> are 2D coordinates and <Y> is height
Noob Question: I'm trying to drop a ball to the floor and make it stick there or even roll over a plane. Right now it passes trough the plane. I'm not sure where I made a mistake or if I'm doing anything wrong.
var world, timeStep=1/60, scene, renderer, camera,
icosahedronBody, sphereShape, groundShape,
ground, groundBody, groundShape;
// CONSTANTS
var GRID_HELPER_SIZE = 40,
GRID_HELPER_STEP = 2,
MASS = 5;
initThree();
initCannon();
animate();
function initCannon() {
world = new CANNON.World();
world.broadphase = new CANNON.NaiveBroadphase();
sphereShape = new CANNON.Sphere();
groundShape = new CANNON.Plane();
icosahedronBody = new CANNON.Body({
mass: MASS,
});
groundBody = new CANNON.Body({
mass: 0, // mass == 0 makes the body static
});
world.solver.iterations = 10;
world.gravity.set(0,-9.8,0);
world.defaultContactMaterial.contactEquationStiffness = 1e9;
world.defaultContactMaterial.contactEquationRegularizationTime = 4;
icosahedronBody.addShape(sphereShape);
icosahedronBody.position.set(0,50,0)
icosahedronBody.linearDamping = 0.5;
world.addBody(icosahedronBody);
groundBody.addShape(groundShape);
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(0,1,0),-Math.PI/2);
world.addBody(groundBody);
var ballContact = new CANNON.ContactMaterial( groundBody, icosahedronBody, 0.0, 0.0);
world.addContactMaterial(ballContact);
}
function initThree(){
// INITIALIZE CANVAS
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
var light = new THREE.AmbientLight( 0x404040 ),
directionalLight = new THREE.DirectionalLight( 0xffffff ),
gridHelper = new THREE.GridHelper( GRID_HELPER_SIZE, GRID_HELPER_STEP );
renderer.setSize( window.innerWidth - 100 , window.innerHeight - 100 );
renderer.setClearColor( 0x757575 );
document.body.appendChild( renderer.domElement );
camera.position.set(1,25,100); // camera position to x , y , z
camera.lookAt( new THREE.Vector3() )
directionalLight.position.set( 1, 0.75, 0.5 ).normalize();
// INITIAL CANVAS
scene.add( directionalLight );
scene.add( light );
scene.add( camera );
scene.add( gridHelper );
// MATERIALS
var icoGeometry = new THREE.IcosahedronGeometry(10, 1),
icoMaterial = new THREE.MeshLambertMaterial( {color: 0xff0000} );
icosahedron = new THREE.Mesh( icoGeometry, icoMaterial );
var groundGeometry = new THREE.BoxGeometry(100 , 1, 100),
groundMaterial = new THREE.MeshLambertMaterial( {color: 0xcccccc} );
ground = new THREE.Mesh( groundGeometry, groundMaterial );
ground.receiveShadow = true;
// ADD OBJECTS TO SCENE
scene.add( icosahedron );
scene.add( ground );
}
function animate() {
requestAnimationFrame( animate );
updatePhysics();
render();
}
function updatePhysics() {
// Step the physics world
world.step(timeStep);
// Copy coordinates from Cannon.js to Three.js
icosahedron.position.copy(icosahedronBody.position);
icosahedron.quaternion.copy(icosahedronBody.quaternion);
ground.position.copy(groundBody.position);
ground.quaternion.copy(groundBody.quaternion);
}
function render() {
renderer.render( scene, camera );
}
It appears that your CANNON.Plane is oriented the wrong way. By default, it's normal is pointing in the Z direction, so you need to rotate it -90 degrees along the positive X axis to make its normal point in the positive Y direction (use the right hand rule):
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1,0,0),-Math.PI/2);
Secondly, you have to make the BoxGeometry match. Make sure its thinner along its Z axis.
var groundGeometry = new THREE.BoxGeometry(100, 100, 1),
I also note that the radius of the THREE.IcosahedronGeometry is 10, while the radius of the CANNON.Sphere is 1 (the default). Set its radius to 10 to match the three.js geometry:
sphereShape = new CANNON.Sphere(10);
Replace these three lines and your simulation looks like it should. Good luck!
I guess it's basic stuff, but can't find any solution. Say, I have 2 objects and want to display only their overlapping parts (i.e. their intersection). Any hints?
One way is to turn on the stencil. Draw the first object, updating the stencil buffer, then draw the second object with the stencil test set to only draw where the first object set the stencil buffer.
var camera, scene1, scene2, scene3, renderer, container;
var mesh1, mesh2, mesh3, mesh4;
init();
animate();
function init() {
container = document.body;
renderer = new THREE.WebGLRenderer();
renderer.setSize( container.clientWidth, container.clientHeight );
container.appendChild( renderer.domElement );
renderer.autoClear = false;
//
camera = new THREE.PerspectiveCamera( 70, container.clientWidth / container.clientHeight, 1, 1000 );
camera.position.z = 400;
// we have to make 2 scenes AFAICT because three has no way to render portions
// of scene. I suppose we could hide one object, render, the hide the other object and un hide
// the first. Or we could remove objects from the scene and add them back.
scene1 = new THREE.Scene();
scene2 = new THREE.Scene();
var geometry1 = new THREE.BoxGeometry( 200, 200, 200 );
var geometry2 = new THREE.IcosahedronGeometry ( 200, 0 );
var material1 = new THREE.MeshBasicMaterial( { color: 0xFF8040 } );
var material2 = new THREE.MeshBasicMaterial( { color: 0x8080FF, wireframe: true } );
// put the box in scene1
mesh1 = new THREE.Mesh( geometry1, material1 );
mesh1.position.x = -75;
scene1.add( mesh1 );
// put the icoahedron in scene2
mesh2 = new THREE.Mesh( geometry2, material1 );
mesh2.position.x = 75;
scene2.add( mesh2 );
// just for visualization lets draw a 3rd scene with both objects so we can
// where they would be if drawn
scene3 = new THREE.Scene();
mesh3 = new THREE.Mesh( geometry1, material2 );
scene3.add( mesh3 );
// put the icoahedron in scene2
mesh4 = new THREE.Mesh( geometry2, material2 );
scene3.add( mesh4 );
}
function animate() {
mesh1.rotation.x += 0.005;
mesh1.rotation.y += 0.01;
mesh2.rotation.x -= 0.003;
mesh2.rotation.y -= 0.007;
// Clear color, depth, and stencil
renderer.clear(true, true, true);
// Turn on stenciling, Set it up so that as we draw scene1 it will put
// a 1 in the stencil for ever pixel drawn
var gl = renderer.context;
gl.enable(gl.STENCIL_TEST);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
gl.stencilFunc(gl.ALWAYS, 1, 0xFF);
// renderer scene1
renderer.render( scene1, camera );
// Clear the color and depth but NOT the stencil
renderer.clear(true, true, false);
// Set the stencil up so we'll only draw if there's a 1 in the stencil buffer.
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);// sfail, spassdfail, spassdepthpass);
gl.stencilFunc(gl.EQUAL, 1, 0xFF); //func, ref, mask)
// renderer scene2
renderer.render( scene2, camera );
// this part is just so we can see both shapes since it's hard to see otherwise
gl.disable(gl.STENCIL_TEST);
mesh3.position.x = mesh1.position.x;
mesh3.rotation.x = mesh1.rotation.x;
mesh3.rotation.y = mesh1.rotation.y;
mesh4.position.x = mesh2.position.x;
mesh4.rotation.x = mesh2.rotation.x;
mesh4.rotation.y = mesh2.rotation.y;
renderer.render( scene3, camera );
requestAnimationFrame( animate );
}
html, body {
width: 100%;
height: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r69/three.min.js"></script>
Hmmm, this actually only works in 2D not 3D (the intersection is in 2d). DOH!
Take a look at: Constructive Solid Geometries. Intro here:
http://learningthreejs.com/blog/2011/12/10/constructive-solid-geometry-with-csg-js/
As an alternative to using the stencil #gman mentioned you could achieve this by cleverly switching between depth functions or blend functions.
I am trying to create a mesh from an .obj file. I am doing this rather than using the obj loader because I meed to morph the shape using different sliders, select specific faces/vertices and draw a path between vertices.
I am currently getting errors when I try to render the scene and it seems to be something to do with the faces from the obj file. When I manually enter the faces I can create a triangle no problem.
Here is my code
var camera, scene, renderer,
geometry, material, mesh;
init();
animate();
function init() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/img/originalMeanModel.obj', false);
xhr.send(null);
var text = xhr.responseText;
var lines = text.split("\n");
for (i=0; i<19343; i++){
lines[i] = lines[i].split(" ");
}
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 1, window.innerWidth / window.innerHeight, 1, 2000 );
camera.position.z = 20;
scene.add( camera );
var geometry = new THREE.Geometry()
/*
geometry.vertices.push( new THREE.Vector3( -0.01, 0.01, 0 ) );
geometry.vertices.push( new THREE.Vector3( -0.01, -0.01, 0 ) );
geometry.vertices.push( new THREE.Vector3( 0.01, -0.01, 0 ) );
*/
geometry.faces.push( new THREE.Face3( 0, 1, 2 ) );
for(i=0; i<6449; i++){
geometry.vertices.push(
new THREE.Vector3(
parseFloat(lines[i][1]),
parseFloat(lines[i][2]),
parseFloat(lines[i][3])) );
}
/*
geometry.faces.push( new THREE.Face3( 0, 1, 2 ) );
geometry.faces.push( new THREE.Face3( 600, 1, 3000 ) );
geometry.faces.push( new THREE.Face3( 3000, 6400, 70 ) );
*/
for(i=6449; i<19343; i++){
geometry.faces.push( new THREE.Face3( parseInt(lines[i][1]), parseInt(lines[i][2]), parseInt(lines[i][3]) ) );
}
console.log(geometry.faces);
console.log(geometry.vertices);
material = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: false } );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
renderer = new THREE.CanvasRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
}
function animate() {
// note: three.js includes requestAnimationFrame shim
requestAnimationFrame( animate );
render();
}
function render() {
renderer.render( scene, camera );
}
When I inspect the console the faces and vertices have been printed out as expected. I also have this error printed out 5700+ times
Uncaught TypeError: Cannot read property 'visible' of undefined Three.js:71
projectScene Three.js:71
render Three.js:245
render threeDemo.php:115
animate threeDemo.php:106
threeDemo.php:106 is this line of the animate function
render();
threeDemo.php:115 is this line of the render function
renderer.render( scene, camera );
Here is a link the the obj file that I am trying to create a mesh for. https://dl.dropbox.com/u/23384412/originalMeanModel.obj
Is anyone could point me in the right direction it would be really appreciated.
Thanks in advance.
You can use two OBJLoaders for loading each frame geometry. Then create a third Geometry and create the lines using as reference the vertices from the frame geometries.