Three.js ParametricGeometry animation (dynamically changing function) - javascript

I am really new to computer graphics, and I started experimenting with some things with THREE.js. So I wanted to an animation of a flag (wave motions) and I couldn't find anything (maybe I don't know what to search). So I made my flag with a parametric geometry, and the function is just a cos. And I wan't to animate the flag by dynamically changing the function of the parametric geometry. How can I do that, and is this the correct way of doing this?
P.S. The change in the function that I want is simply moving the cos along the X asis so it looks like the flag is moving.

In dependency on amount of vertices in your mesh, you can use shaders or simple changing of vertices in your animation loop with cos function.
Below, there's the approach with simple changing.
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 0, 2, 10 );
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var controls = new THREE.OrbitControls( camera, renderer.domElement );
scene.add( new THREE.GridHelper( 10, 10 ) );
var planeGeom = new THREE.PlaneGeometry( 10, 3, 20, 3 );
var plane = new THREE.Mesh( planeGeom, new THREE.MeshBasicMaterial( { color: "red", side: THREE.DoubleSide } ) );
scene.add( plane );
render();
function render(){
requestAnimationFrame( render );
planeGeom.vertices.forEach( v => {
v.z = Math.cos( .5 * v.x - Date.now() * .001 ) * .5;
});
planeGeom.verticesNeedUpdate = true; // the most important thing, when you change vertices
renderer.render( scene, camera );
}
body{
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

Related

How to make 3D objects jump smoothly apart from each other upon click? Three.JS

I'm very new to Three.JS and 3D web dev in general what I'm trying to do is mimic this action: https://www.youtube.com/watch?v=HWSTxPc8npk&feature=youtu.be&t=7s Essentially this is a set of 3D planes and upon click the whole stack reacts and gives space around the one that's clicked.
For now, my base case is 3 planes and figuring first out if I can click the the middle one, how do I get the others to jump back smoothly as if they were pushed rather than instant appear and disappear as they do now on the click of a button.
The long term goal is to have a separate button for every plane so that on click, the selected plane will have padding around it and the rest of the planes in stack move accordingly.
I've looked into Tween.js, and CSS3D but pretty overwhelmed as a newbie. Any tutorials or tips would be greatly appreciated!
// Our Javascript will go here.
window.addEventListener( 'resize', onWindowResize, false );
function onWindowResize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var geometry = new THREE.PlaneGeometry( 3, 3, 1 );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var plane = new THREE.Mesh( geometry, material );
plane.rotation.y = -.7;
var material2 = new THREE.MeshBasicMaterial( { color: 0x0000ff } );
var material3 = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
var plane2 = new THREE.Mesh(geometry, material2 );
plane2.rotation.y = -.7;
plane2.position.x = 1;
var plane3 = new THREE.Mesh(geometry, material3);
plane3.rotation.y = -.7;
plane3.position.x = -1;
scene.add( plane, plane2, plane3 );
camera.position.z = 5;
function render() {
requestAnimationFrame( render );
// cube.rotation.x += 0.1;
// cube.rotation.y += 0.1;
renderer.render( scene, camera );
}
render();
function clickFirst() {
TWEEN.removeAll();
var tween = new TWEEN.Tween(plane3.position).to({x: -2}, 1000).start();
tween.easing(TWEEN.Easing.Elastic.InOut);
render();
}
</script>
<button onclick="clickFirst();" style="background-color: white; z-index: 9999;">Click me</button>
First, you need to locate the 2 planes.
Second, you need to make the planes clickable:
https://threejs.org/examples/#webgl_interactive_cubes
https://github.com/josdirksen/learning-threejs/blob/master/chapter-09/02-selecting-objects.html
Third, you should use Tween.js for the transition.
after picking the right plane, make a tween for the other planes with a tween, all to move on the same Axis:
example:
createjs.Tween.get(plane3.position.z).to(
plane3.position.z + 100
, 1000, createjs.Ease.cubicOut)
If you will add some code here after starting to implement i would be able to help more.

THREE.js SphereGeometry Panorama hotspots using DOMElements

I've created a simple WebGL 3D Panorama application using a SphereGeometry, PerspectiveCamera and a CanvasTexture. Now, I'm looking to bring the scene to life by adding "HotSpots" over certain parts of the SphereGeometry. The problem I'm having is understanding how to update the various DOMElements so that their position is reflective of the updated Camera position.
Basically, as the Camera rotates the various DOMElements would move in and out of view relative to the direction the camera is spinning. I tried positioning a <div> absolute to the <canvas> and translating the X and Y position using the returned PerspectiveCamera camera.rotation but it didn't really work; here's my JS & CSS implentation:
CSS
#TestHotSpot {
/* not sure if using margins is the best method to position hotspot */
margin-top: 50px;
margin-left: 50px;
width: 50px;
height: 50px;
background: red;
position: absolute;
}
JavaScript
/**
CONFIG is my stored object variable; it contains the PerspectiveCamera instance
code is being called inside of my RequestAnimationFrame
**/
config.camera.updateProjectionMatrix();
config.controls.update(delta);
document.getElementById("TestHotSpot").style.transform = "translate("+ config.camera.rotation.x +"px, "+ config.camera.rotation.y +"px)";
Here is also a live example of the desired effect.
What would be the best solution to fix this problem? What I noticed when I ran this that the DOMElements would only slightly move; I also noticed that they wouldn't really take in account where along the SphereGeometry they were placed (for example, being positioned "behind" the Camera; really complex!
I'm happy to use any plugins for the THREE.js engine as well as follow any tutorials. Thank you so much for replying in advance!
Try to set a planemesh/mesh to the desired point.
Copy the position of the css elements (domElements created with three.js cssObject [if you already know]) along with planemesh/mesh 's position.
And you will be done !!
Okay, chiming in on my own question! I had a few problems but I've figured out how to get CSS3d mixing in with THREE.js WebGL scenes.
So the first thing I had to do was update my THREE.js Library; I was running 71 from an earlier download but I needed to update it to the library Mr.Doob was using in this example. I also updated my CSS3d library to the file included in that same example.
I then used this method (the same specified in the link) to create a demo scene.
var camera, scene, renderer;
var scene2, renderer2;
var controls;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 200, 200, 200 );
controls = new THREE.TrackballControls( camera );
scene = new THREE.Scene();
scene2 = new THREE.Scene();
var material = new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true, wireframeLinewidth: 1, side: THREE.DoubleSide } );
//
for ( var i = 0; i < 10; i ++ ) {
var element = document.createElement( 'div' );
element.style.width = '100px';
element.style.height = '100px';
element.style.opacity = 0.5;
element.style.background = new THREE.Color( Math.random() * 0xffffff ).getStyle();
var object = new THREE.CSS3DObject( element );
object.position.x = Math.random() * 200 - 100;
object.position.y = Math.random() * 200 - 100;
object.position.z = Math.random() * 200 - 100;
object.rotation.x = Math.random();
object.rotation.y = Math.random();
object.rotation.z = Math.random();
object.scale.x = Math.random() + 0.5;
object.scale.y = Math.random() + 0.5;
scene2.add( object );
var geometry = new THREE.PlaneGeometry( 100, 100 );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.copy( object.position );
mesh.rotation.copy( object.rotation );
mesh.scale.copy( object.scale );
scene.add( mesh );
}
//
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( 0xf0f0f0 );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer2 = new THREE.CSS3DRenderer();
renderer2.setSize( window.innerWidth, window.innerHeight );
renderer2.domElement.style.position = 'absolute';
renderer2.domElement.style.top = 0;
document.body.appendChild( renderer2.domElement );
}
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
renderer2.render( scene2, camera );
}
After I had that working, I re-purposed the CSS3d scene, CSS3d object and the CSS3d renderer to be included in my scene.
Good luck to anyone else and hopefully this helps!

CannonJS floor. Make a ball stick to the ground

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!

Repeating a bump map

I'm trying to apply a bump map to a plane to create a vaguely felt-like surface using Three.js r55.
Here's my code:
var mapHeight = THREE.ImageUtils.loadTexture("images/felt.png");
mapHeight.repeat.set(2, 2);
mapHeight.wrapS = mapHeight.wrapT = THREE.RepeatWrapping;
mapHeight.format = THREE.RGBFormat;
var groundMaterial = new THREE.MeshPhongMaterial({
ambient: 0x008800, color: 0x008800, specular: 0x888888,
shininess: 25, bumpMap: mapHeight, bumpScale: 10, metal: false
} );
scene.add(new THREE.Mesh(new THREE.PlaneGeometry(0.3, 0.3), groundMaterial));
Notice how I set the texture to repeat twice along x/y axes. However what I'm seeing only applies the texture in one quadrant:
I would expect this with clamp/repeat wrapping (or whatever it's called) but I've requested RepeatWrapping here.
How can I have the bump map correctly repeat an arbitrary number of times on the plane.
EDIT - Full Code
I set about making a simple reproduction case. This is pretty minimal and reproduces the image below (from a slightly different camera angle.) The output has the identical problem.
<!DOCTYPE html>
<html>
<head>
<script src="scripts/libs/three.min.js" type="text/javascript"></script>
</head>
<body>
<div id="scene-container"></div>
<script>
init();
function init() {
var camera, scene, renderer;
scene = new THREE.Scene();
scene.add( new THREE.AmbientLight( 0x555555 ) );
var light = new THREE.DirectionalLight( 0x555555 );
light.position.set( 0, 0, 10 );
scene.add( light );
var bumpMapTexture = THREE.ImageUtils.loadTexture( "images/felt.png", undefined, function () {
requestAnimationFrame( function () {
// render once texture has loaded
renderer.render( scene, camera );
} );
} );
bumpMapTexture.repeat.set( 2, 2 );
bumpMapTexture.wrapS = bumpMapTexture.wrapT = THREE.RepeatWrapping;
var groundMaterial = new THREE.MeshPhongMaterial( {
ambient: 0x00AA00,
color: 0x00AA00,
bumpMap: bumpMapTexture
} );
scene.add( new THREE.Mesh( new THREE.PlaneGeometry( 3, 3 ), groundMaterial ) );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.01, 100000 );
camera.position.set( 0, 0, 3 );
camera.lookAt( new THREE.Vector3( 0, 0, 0 ) );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );// renderer.render(scene, camera);
document.getElementById( 'scene-container' ).appendChild( renderer.domElement );
}
</script>
</body>
</html>
This links to Three.js r55 (minified).
Any help appreciated.
If you want a texture to repeat, it's size in pixels in each dimension must be a power of two ( e.g., 512 x 256 ).
If you have a diffuse map and a bumpMap, they must have the same offset/repeat settings. See for example this answer.
three.js r.55

Three.js - troubles with the size of texture

Less words, more code =)
var objects = [];
var camera, scene, renderer;
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
init();
render();
function onDocumentMouseDown( event ) {
event.preventDefault();
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
var intersects = ray.intersectObjects( objects );
if ( intersects.length > 0 ) {
console.log(intersects[ 0 ].object);
}
}
function init() {
container = document.getElementById( 'container' );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 90, window.innerWidth / window.innerHeight, 1, 1100 );
camera.position.z = 50;
scene.add( camera );
var particle = new THREE.Particle( new THREE.ParticleBasicMaterial( { map: THREE.ImageUtils.loadTexture( "img/satellite.png" ) } ) );
objects.push( particle );
//particle.scale.x = particle.scale.y = 0.25
scene.add( particle );
projector = new THREE.Projector();
renderer = new THREE.CanvasRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
}
function render() {
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
As a result, we get clickable particle with a texture. But I don't understand several things:
Why the "clickable" area of particle is so small? It works only if I click in the middle of a particle.
Why is that particle so huge? The texture is this .png file and the particle is way more bigger than 16×16. How can I fix that? Yes, I know about particle.scale, that will make particle look smaller. But, the "clickable" area of particle woukd also become smaller.
I know this is an old question but I came across the same issue today and i found this question unanswered, after some workaround i came across a solution for this.
The solution is to create 2 particles, one as a simple particle that draws a geometry (rect or arc) that is a ParticleCanvasMaterial and then the particle that displays the image on top of it.
So you can use the ParticleCanvasMaterial to track the intersections and display the other particle as a dummy object where it's only purpose is displaying an image on the 3D scene.
A little bit of code:
var programFill = function (context) {
context.beginPath();
context.rect(-0.5, -0.38, 1, 1);
//context.fill();
}
//creating particle to intersect with.
var p = new THREE.ParticleCanvasMaterial({ program: programFill, transparent: true });
var particle = new THREE.Particle(p);
particle.scale.set(23, 23);
//use same position for both particle and imgParticle
particle.position.set(200, 300, 200);
//creating particle that displays image.
var imgTexture = THREE.ImageUtils.loadTexture('images/image.png');
var p2 = new THREE.ParticleBasicMaterial({
map: imgTexture
, size: 1
});
var imgParticle = new THREE.Particle(p2);
imgParticle.scale.x = 0.5;
imgParticle.scale.y = 0.5;
imgParticle.position.set(200, 300, 200);

Categories

Resources