Three.js: How to animate particles along a line - javascript

I'm trying to animate particles along a path similar to this chrome expriement: http://armsglobe.chromeexperiments.com/
I've tried digging into the source of this project, and what I've groked so far is that they are using a built in curve method .getPoitns() to generate about 30 points on their lines.
Is there a better example on what I'm trying to accomplish? Is there a method for getting points on the line than using the .lerp() method 30 times to get 30 points? Should I just use TWEEN animations?
Any help or direction would be appreciated.

I've figured out a solution, not sure if it's the best or not, but it works well.
You can find a demo on JsFiddle: https://jsfiddle.net/L4beLw26/
//First create the line that we want to animate the particles along
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(-800, 0, -800));
geometry.vertices.push(new THREE.Vector3(800, 0, 0));
var line = new THREE.Line(geometry, material);
var startPoint = line.geometry.vertices[0];
var endPoint = line.geometry.vertices[1];
scene.add(line);
//next create a set of about 30 animation points along the line
var animationPoints = createLinePoints(startPoint, endPoint);
//add particles to scene
for ( i = 0; i < numParticles; i ++ ) {
var desiredIndex = i / numParticles * animationPoints.length;
var rIndex = constrain(Math.floor(desiredIndex),0,animationPoints.length-1);
var particle = new THREE.Vector3();
var particle = animationPoints[rIndex].clone();
particle.moveIndex = rIndex;
particle.nextIndex = rIndex+1;
if(particle.nextIndex >= animationPoints.length )
particle.nextIndex = 0;
particle.lerpN = 0;
particle.path = animationPoints;
particleGeometry.vertices.push( particle );
}
//set particle material
var pMaterial = new THREE.ParticleBasicMaterial({
color: 0x00FF00,
size: 50,
map: THREE.ImageUtils.loadTexture(
"assets/textures/map_mask.png"
),
blending: THREE.AdditiveBlending,
transparent: true
});
var particles = new THREE.ParticleSystem( particleGeometry, pMaterial );
particles.sortParticles = true;
particles.dynamic = true;
scene.add(particles);
//update function for each particle animation
particles.update = function(){
// var time = Date.now()
for( var i in this.geometry.vertices ){
var particle = this.geometry.vertices[i];
var path = particle.path;
particle.lerpN += 0.05;
if(particle.lerpN > 1){
particle.lerpN = 0;
particle.moveIndex = particle.nextIndex;
particle.nextIndex++;
if( particle.nextIndex >= path.length ){
particle.moveIndex = 0;
particle.nextIndex = 1;
}
}
var currentPoint = path[particle.moveIndex];
var nextPoint = path[particle.nextIndex];
particle.copy( currentPoint );
particle.lerp( nextPoint, particle.lerpN );
}
this.geometry.verticesNeedUpdate = true;
};
function createLinePoints(startPoint, endPoint){
var numPoints = 30;
var returnPoints = [];
for(i=0; i <= numPoints; i ++){
var thisPoint = startPoint.clone().lerp(endPoint, i/numPoints);
returnPoints.push(thisPoint);
}
return returnPoints;
}
function constrain(v, min, max){
if( v < min )
v = min;
else
if( v > max )
v = max;
return v;
}
and then in the animation loop:
particles.update();

I don't know if anyone else can't see the snippets working, but I took the answer that jigglebilly provided and put it into a full html page and is working. Just so that if you want to see a working version. `
Particle Test
<script src="../js/three.js"></script>
<script type="text/javascript">
var renderer = new THREE.WebGLRenderer( { antialias: true } );
var camera = new THREE.PerspectiveCamera( 45, (window.innerWidth) / (window.innerHeight), 100, 10000);
var container = document.getElementById("containerElement");
var numParticles = 40;
container.appendChild( renderer.domElement );
var scene = new THREE.Scene();
var material = new THREE.LineBasicMaterial({color: 0x0000ff });
//First create the line that we want to animate the particles along
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(-800, 0, -800));
geometry.vertices.push(new THREE.Vector3(800, 0, 0));
var line = new THREE.Line(geometry, material);
var startPoint = line.geometry.vertices[0];
var endPoint = line.geometry.vertices[1];
scene.add(line);
//next create a set of about 30 animation points along the line
var animationPoints = createLinePoints(startPoint, endPoint);
var particleGeometry = new THREE.Geometry();
//add particles to scene
for ( i = 0; i < numParticles; i ++ ) {
var desiredIndex = i / numParticles * animationPoints.length;
var rIndex = constrain(Math.floor(desiredIndex),0,animationPoints.length-1);
var particle = new THREE.Vector3();
var particle = animationPoints[rIndex].clone();
particle.moveIndex = rIndex;
particle.nextIndex = rIndex+1;
if(particle.nextIndex >= animationPoints.length )
particle.nextIndex = 0;
particle.lerpN = 0;
particle.path = animationPoints;
particleGeometry.vertices.push( particle );
}
//set particle material
var pMaterial = new THREE.ParticleBasicMaterial({
color: 0x00FF00,
size: 50,
blending: THREE.AdditiveBlending,
transparent: true
});
var particles = new THREE.ParticleSystem( particleGeometry, pMaterial );
particles.sortParticles = true;
particles.dynamic = true;
scene.add(particles);
function UpdateParticles(){
// var time = Date.now()
for( var i = 0; i < particles.geometry.vertices.length; i++ ){
var particle = particles.geometry.vertices[i];
var path = particle.path;
particle.lerpN += 0.05;
if(particle.lerpN > 1){
particle.lerpN = 0;
particle.moveIndex = particle.nextIndex;
particle.nextIndex++;
if( particle.nextIndex >= path.length ){
particle.moveIndex = 0;
particle.nextIndex = 1;
}
}
var currentPoint = path[particle.moveIndex];
var nextPoint = path[particle.nextIndex];
particle.copy( currentPoint );
particle.lerp( nextPoint, particle.lerpN );
}
particles.geometry.verticesNeedUpdate = true;
};
animate();
function createLinePoints(startPoint, endPoint){
var numPoints = 30;
var returnPoints = [];
for(i=0; i <= numPoints; i ++){
var thisPoint = startPoint.clone().lerp(endPoint, i/numPoints);
returnPoints.push(thisPoint);
}
return returnPoints;
}
function constrain(v, min, max){
if( v < min )
v = min;
else
if( v > max )
v = max;
return v;
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
UpdateParticles();
}
</script>
`
This uses three.js R67.

Related

Using raycaster to highlight a row of meshes three.js

I am trying to use raycaster to identify a row of 3d cubes to be highlighted/colored on mousehover. I followed this post Change color of mesh using mouseover in three js. The issue I am facing is that it is only highlighting one cube i.e the cube the mouse is on and not the whole row. Please find my pseudocode below:
var cubesList = new THREE.Group();
function createScene () {
var cubeSize = 2;
for ( var i = 0; i < noOfEntries; i++ ) {
var entry = entries[ i ];
var entryObjects = entry.objects;
var entryCubesGroup = new THREE.Group();
var noOfObjects = entry.objects.length;
for ( var j = 0; j < noOfObjects; j++ ) {
var object = entryObjects[ j ];
var cube = createCube( cubeSize ); //THREE.Object3d group of 9 cubes
entryCubesGroup.add( cube );
if ( j === Math.round( noOfObjects / 4 ) - 1 && i === Math.round( noOfEntries / 4 ) - 1 ) {
cameraTarget = cube;
}
}
cubesList.add( entryCubesGroup );
}
scene.add( cubesList );
camera.position.x = 15;
camera.position.y = 15;
camera.position.z = 15;
camera.lookAt( new THREE.Vector3( cameraTarget.position.x, cameraTarget.position.y, cameraTarget.position.z ) );
var light = new THREE.PointLight( 0xffffff, 1, 0 );
light.position.set( 15, 15, 5 );
light.castShadow = true;
scene.add( light );
}
function animate () {
renderer.render( scene, camera );
update();
}
function onDocumentMouseMove ( event ) {
event.preventDefault();
mouse.x = ( event.clientX / renderer.domElement.width ) * 2 - 1;
mouse.y = -( event.clientY / renderer.domElement.height ) * 2 + 1;
animate();
}
function update() {
var vector = new THREE.Vector3(mouse.x, mouse.y, 1);
vector.unproject(camera);
var ray = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = ray.intersectObjects(eventCubesList.children, true);
if (intersects.length > 0) {
if (intersects[0].object != INTERSECTED) {
if (highlightedRow)
unhighlightRow(highlightedRow);
INTERSECTED = intersects[0].object;
var timestamp = INTERSECTED.userData;
var selectedRow = getSelectedRow(timestamp);
highlightedRow = selectedRow;
highlightRow(selectedRow);
}
else {
if (INTERSECTED) {
if (highlightedRow) {
var timestamp = INTERSECTED.userData;
var row = getSelectedRow(timestamp);
unhighlightRow(row);
}
highlightedRow = null;
}
INTERSECTED = null;
}
}
function unhighlightRow(cubes) {
for (var i= 0; i < cubes.length; i++) {
var cube = cubes[i];
for (var j = 0; j < cube.children.length; j++) {
var child = cube.children[j];
child.material.color.setHex(cube.originalColor);
}
}
}
function highlightRow(cubes) {
for (var i = 0; i < cubes.length; i++) {
var cube = cubes[i];
for (var j = 0; j < cube.children.length; j++) {
var child = cube.children[j];
child.material.color.setHex(0xffff00);
break;
}
}
}
Is there a way I can highlight all the cubes in a row as yellow instead of just one cube?
You need to keep your own data on which cubes are in which rows. When a cube is highlighted you need to look up the row its in and highlight the other cubes in the row
---pseudo code---
INTERSECTED = intersects[ index ].object;
row = getRowObjectIsIn(INTERSECTED)
for each object in row
highlight object

THREE.js smooth material color cycle in separate geometries

guys!
Look at the codepen provided. Multiple curves animation.
So i'm trying to reach this smooth hue change at every drawn curve.
The color of every next curve should be shifted in hue a bit.
And i need to control the duration of this shifting.
Right now the colors shift seem random and i cant control it's duration.
Need your help. Thanks.
'use strict';
var camera, scene, renderer, controls;
var params = {P0x: 0, P0y: 0,P1x: 0.6, P1y: 1.7,P2x: -0.1, P2y: 1.1,P3x: 0, P3y: 3,steps: 30};
var controlPoints = [[params.P0x, params.P0y, 0],[params.P1x, params.P1y, 0],[params.P2x, params.P2y, 0],[params.P3x, params.P3y, 0]];
var material = new THREE.LineBasicMaterial( { color: 0xd9e2ec, linewidth: 1 } );
var mat = new THREE.MeshBasicMaterial({wireframe:true,color: 0x4a4a4a, side: THREE.DoubleSide, opacity:0, transparent:true});
var angle1 = 0;
var angle2 = 0;
var color = 0;
var initialCurvesCount = 5;
var initialGroupsCount = 6;
var curveQuality = 500;
var hColor = 1;
var mesh = {};
var axis1 = new THREE.Vector3(0,0.8,1.2);
var axis2 = new THREE.Vector3(0,-0.8,3.2);
var geom = {};
var curveGeometry;
var curves;
var group = {};
var triangle = [[ 0, 0.5, -0.5, 0 ], [ 0.6, -0.5, -0.5, 0.6 ], [ 0, 0, 0, 0 ]];
init();
createCurveGroups();
createHelpers();
animate();
function createHelpers() {
// var gridHelper = new THREE.GridHelper( 4, 8, 0xadd6e8, 0xdddddd );
// var gridHelper2 = new THREE.GridHelper( 4, 8, 0xadd6e8, 0xdddddd );
// gridHelper2.rotation.x = 1.58;
// gridHelper.position.y = 0;
// gridHelper.position.x = 0;
//
// var axisHelper = new THREE.AxisHelper( 1 );
// axisHelper.position.y = 0;
// axisHelper.position.x = 0;
//
// scene.add( gridHelper );
// scene.add( gridHelper2 );
// scene.add( axisHelper );
}
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(-0,0,2);
camera.rotation.y = -0;
camera.frustumCulled = false;
controls = new THREE.OrbitControls( camera );
controls.addEventListener( 'change', render );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setClearColor( 0xffffff, 1 );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function createBezierCurveNEW(cpList, steps) {
var N = Math.round(steps)+1 || 20;
var geometry = new THREE.Geometry();
var curve = new THREE.CubicBezierCurve3();
var cp = cpList[0];
curve.v0 = new THREE.Vector3(cp[0], cp[1], cp[2]);
cp = cpList[1];
curve.v1 = new THREE.Vector3(cp[0], cp[1], cp[2]);
cp = cpList[2];
curve.v2 = new THREE.Vector3(cp[0], cp[1], cp[2]);
cp = cpList[3];
curve.v3 = new THREE.Vector3(cp[0], cp[1], cp[2]);
var j, stepSize = 1/(N-1);
for (j = 0; j < N; j++) {
geometry.vertices.push( curve.getPoint(j * stepSize) );
}
return geometry;
};
function createTriangle(number) {
geom[number] = new THREE.Geometry();
geom[number].vertices.push(new THREE.Vector3(0, 0.35, 0));
geom[number].vertices.push(new THREE.Vector3(0.35, -0.35, 0));
geom[number].vertices.push(new THREE.Vector3(-0.35,-0.35, 0));
geom[number].faces.push(new THREE.Face3(0, 1, 2));
geom[number].applyMatrix( new THREE.Matrix4().makeTranslation( 0, 0, 0 ) );
mesh[number] = new THREE.Mesh(geom[number], mat);
};
function createCurveGroups() {
for ( var i = 1; i <= initialGroupsCount; ++i ) {
group[i] = new THREE.Group();
scene.add( group[i] );
group[i].rotation.set( 0, 3.15, i/((initialGroupsCount/6 - initialGroupsCount/130)) );
};
};
function cloneCurvesToGroups() {
for ( var i = 1; i <= (Object.keys(group).length); ++i ) {
var curvesArray = {};
curvesArray[i] = curves.clone();
group[i].add(curvesArray[i]);
}
};
function colorChanger() {
}
function morphTriangle() {
group[1].add( mesh[1] );
mesh[1].rotateOnAxis(axis1,(angle2 + 1));
mesh[1].updateMatrix();
mesh[1].geometry.applyMatrix( mesh[1].matrix );
mesh[1].matrix.identity();
mesh[1].position.set( 0, 0, 0 );
mesh[1].geometry.verticesNeedUpdate = true;
group[1].add( mesh[2] );
mesh[2].rotateOnAxis(axis2,-angle2);
mesh[2].updateMatrix();
mesh[2].geometry.applyMatrix( mesh[2].matrix );
mesh[2].matrix.identity();
mesh[2].position.set( 0, 0, 0 );
mesh[2].geometry.verticesNeedUpdate = true;
};
function changeCreatedCurves() {
angle1 += 0.00450;
angle2 += 0.0020;
createTriangle(1);
createTriangle(2);
morphTriangle();
for ( var i = 1; i <= initialCurvesCount; ++i ) {
controlPoints[0][0] = -0.09 ;
controlPoints[0][1] = 0;
controlPoints[0][2] = -0.035 + i/10000; //optional + Math.sin(angle1)/6;
controlPoints[2][0] = mesh[2].geometry.vertices[0]['x'] + 0.1 - i/55 + Math.cos(angle1)/6;
controlPoints[2][1] = mesh[2].geometry.vertices[0]['y'];
controlPoints[2][2] = mesh[2].geometry.vertices[0]['z'] + i/20 + Math.sin(angle1)/6;
controlPoints[1][0] = mesh[1].geometry.vertices[0]['x'] - i/20 + Math.sin(angle1)/6;
controlPoints[1][1] = mesh[1].geometry.vertices[0]['y'];
controlPoints[1][2] = mesh[1].geometry.vertices[0]['z'] - i/20 + Math.sin(angle1)/6;
controlPoints[3][0] = triangle[0][0] - 0.05 + i/10;
controlPoints[3][1] = triangle[1][0] - 0.05 + i/10;
controlPoints[3][2] = triangle[2][0];
// !!! HERE IS THE PROBLEM !!!
hColor = hColor + i*0.3;
var wow = String("hsl(" + hColor*i + "," + 100 + "%" + "," + 70 + "%" + ")");
// console.log(wow)
// console.log("this is i "+ i);
material = new THREE.LineBasicMaterial( { color: wow, linewidth: 1 } );
// !!! HERE IS THE PROBLEM !!!
curveGeometry = createBezierCurveNEW(controlPoints, (curveQuality/initialGroupsCount));
curves = new THREE.Line(curveGeometry, material);
group[1].add(curves);
// debugger
render();
cloneCurvesToGroups();
}
};
function disposeCurveGeometry() {
for (var i = 0; i <= group[1].children.length; ++i) {
group[1].children[0].geometry.dispose();
group[1].children[0].material.dispose();
for (var j = 1; j <= (Object.keys(group).length); ++j) {
group[j].remove(group[j].children[0]);
};
};
};
function render() {
renderer.render(scene, camera);
};
function animate() {
requestAnimationFrame(animate);
changeCreatedCurves();
disposeCurveGeometry();
onWindowResize();
};
Working example code at codepen
Is something like this what you are looking for?
var vueDifference = 20;
var speed = 0.03;
hColor = hColor + speed;
var wow = String("hsl(" + (hColor + i * vueDifference) + "," + 100 + "%" + "," + 70 + "%" + ")");

Three.js render white part of plain geometry

I'm trying to turn this plainGeometry, into this (sort of mask).
This code works: (to sum-up, create two materials, divide the plane to segments and decide for each one which material is it with MeshFaceMaterial)
Button.onClick(function () {
var obj = editor.selected;
var material = obj.material;
var tex = material.map;
var objHeight = obj.geometry.parameters.height;
var objWidth = obj.geometry.parameters.width;
var texHeight = tex.image.height;
var texWidth = tex.image.width;
var geometry = new THREE.PlaneGeometry(objWidth, objHeight, objWidth, objHeight);
var facesNum = objHeight * objWidth * 2;
var facesX = objWidth * 2;
var facesInX = texWidth * 2;
var materials = [];
materials.push(material);
materials.push(new THREE.MeshBasicMaterial({ }));
for (var i = 0; i < facesNum; i++) {
if ((i % facesX >= objWidth - texWidth) &&
(i % facesX <= (facesInX + objWidth - texWidth - 1)) &&
(i <= (texHeight * objWidth * 2) - 1)) {
geometry.faces[i].materialIndex = 0;
}
else {
geometry.faces[i].materialIndex = 1;
}
}
obj.geometry = geometry;
obj.material = new THREE.MeshFaceMaterial(materials);
editor.signals.materialChanged.dispatch(obj);
});
But I'm wondering if there is a simpler way to go. Any suggestions?
Another way to do this is to use an alpha channel on your texture. You can do this with Gimp or Photoshop.
You then duplicate the mesh and push it just a tad out the axis with polygonOffsetFactor on the material. Apply the background material to the first mesh, and the foreground material with the texture with alpha to the second.
See this fiddle alphaTest. (you may need to disable cross-domain access security so the texture can load in this fiddle, chrome will allow this if you run it with the --disable-web-security flag)
The advantage to this method is that the image can be of any shape and location, and doesn't need to fit into a geometry face.
Another way, if the geometry you were using is complicated, is to use Three.DecalGeometry to cut a mesh piece out and use it shifted a bit with polygonOffsetFactor on the material. See the three.js decals example for this.
Source for the example fiddle is below:
var renderer;
var pointLight;
var scene;
var plane1;
var plane2;
function addPlane()
{
var material1 = new THREE.MeshPhongMaterial({ color: 0xFFFFFF });
var material2;
var loader = new THREE.TextureLoader();
loader.load('http://i.imgur.com/ETdl4De.png',
function ( texture ) {
var material2 = new THREE.MeshPhongMaterial({
color: 0xFFFFFF,
map: texture,
alphaTest: 0.7,
polygonOffset: true,
polygonOffsetFactor: - 4,
});
var geometry1 = new THREE.PlaneGeometry(12, 12, 12, 12);
var geometry2 = geometry1.clone();
plane1 = new THREE.Mesh(geometry1,material1);
plane2 = new THREE.Mesh(geometry2,material2);
scene.add(plane2);
scene.add(plane1);
}
);
}
(function() {
'use strict';
scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 10000);
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
addPlane();
camera.position.z = 52;
pointLight = new THREE.DirectionalLight(0xffffff,1);
pointLight.position.x = 11;
pointLight.position.y = 5;
pointLight.position.z = 25;
scene.add(pointLight);
var reqAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;
var render = function() {
reqAnimFrame(render);
renderer.render(scene, camera);
};
render();
}());
this did it, eventually:
var obj = editor.selected;
var tex = obj.material.map.;
var materials = [];
materials.push(new THREE.MeshBasicMaterial({ map: tex }));
materials.push(new THREE.MeshBasicMaterial({}));
var material = new THREE.MeshFaceMaterial(materials);
var objHeight = obj.geometry.parameters.height;
var objWidth = obj.geometry.parameters.width;
var texHeight = tex.image.height;
var texWidth = tex.image.width;
tex.repeat = new THREE.Vector2(3, 3);
tex.offset = new THREE.Vector2(0, 0);
var geometry = new THREE.PlaneGeometry(objWidth, objHeight, 3, 3);
var v = geometry.vertices;
var facesNum = geometry.faces.length;
v[1] = new THREE.Vector3(-texWidth / 2, objHeight / 2, 0);
v[2] = new THREE.Vector3(texWidth / 2, objHeight / 2, 0);
v[5] = new THREE.Vector3(-texWidth / 2, (objHeight / 2) - texHeight, 0);
v[6] = new THREE.Vector3(texWidth / 2, (objHeight / 2) - texHeight, 0);
v[9] = v[13];
v[10] = v[14];
v[4] = v[8] = v[12];
v[7] = v[11] = v[15];
for (var i = 0; i < facesNum; i++) {
if (i !== 2 && i !== 3) geometry.faces[i].materialIndex = 1;
}
obj.material = material;
obj.geometry = geometry;
editor.signals.materialChanged.dispatch(obj);

Draw dimension lines along with 3D cube using Three.js

Can we draw "lines" with Cube to show "Dimensions" at run time?
Here is how I have created the cube and getting dimensions from user and changing the cube at run time: http://jsfiddle.net/9Lvk61j3/
But now I want to show the Dimension, so the user knows what the length, width, and height is, which they will be changing.
This is what I am trying to make as end result:
Here is my code:
HTML:
<script src="http://www.html5canvastutorials.com/libraries/three.min.js"></script>
<div id="container"></div>
<div class="inputRow clear" id="dimensionsNotRound" data-role="tooltip">
<label class="grid-8">Dimensions (pixels):</label>
<br/>
<br/>
<div> <span>Length</span>
<input class="numeric-textbox" id="inp-length" type="text" value="100">
<br/>
<br/>
</div>
<div> <span>Width</span>
<input class="numeric-textbox" id="inp-width" type="text" value="50">
<br/>
<br/>
</div>
<div> <span>Height</span>
<input class="numeric-textbox" id="inp-height" type="text" value="40">
<br/>
<br/>
</div>
<button id="btn">Click me to change the Dimensions</button>
JS
var shape = null;
//Script for 3D Box
// revolutions per second
var angularSpeed = 0.2;
var lastTime = 0;
var cube = 0;
// this function is executed on each animation frame
function animate() {
// update
var time = (new Date()).getTime();
var timeDiff = time - lastTime;
var angleChange = angularSpeed * timeDiff * 2 * Math.PI / 1000;
//cube.rotation.y += angleChange; //Starts Rotating Object
lastTime = time;
// render
renderer.render(scene, camera);
// request new frame
requestAnimationFrame(function () {
animate();
});
}
// renderer
var container = document.getElementById("container");
var renderer = new THREE.WebGLRenderer();
renderer.setSize(container.offsetWidth, container.offsetHeight - 4);
container.appendChild(renderer.domElement);
// camera
var camera = new THREE.PerspectiveCamera(60, container.offsetWidth / container.offsetHeight, 1, 1000);
camera.position.z = 800;
// scene
var scene = new THREE.Scene();
scene.remove();
// cube
cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1), new THREE.MeshLambertMaterial({
color: '#cccccc'
}));
cube.overdraw = true;
cube.rotation.x = Math.PI * 0.1;
cube.rotation.y = Math.PI * 0.3;
scene.add(cube);
// add subtle ambient lighting
var ambientLight = new THREE.AmbientLight(0x319ec5);
scene.add(ambientLight);
// directional lighting
var directionalLight = new THREE.DirectionalLight(0x666666);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
shape = cube;
// start animation
animate();
var $ = function(id) { return document.getElementById(id); };
$('btn').onclick = function() {
console.log("Button Clicked");
var width = parseInt(document.getElementById('inp-width').value * 3.779528),
height = parseInt(document.getElementById('inp-height').value * 3.779528),
length = parseInt(document.getElementById('inp-length').value * 3.779528);
console.log("length " + length + " height " + height + " width " + width);
shape.scale.x = length;
shape.scale.y = height;
shape.scale.z = width;
};
Here is the Fiddle for the same! http://jsfiddle.net/9Lvk61j3/
Let me know if you need any other information.
Please suggest.
There's a bit of a problem with drawing dimensions:
You may have many of them, and not all of them may be perfectly visible:
some may be hidden,
some may appear too small, if the camera is far away from the object,
some may overlay other dimensions (or even object elements),
some may be seen from inconvenient angle.
The text should retain perfectly same size, no matter how you navigate camera,
Most of these points are addressed in my solution: https://jsfiddle.net/mmalex/j35p1fw8/
var geometry = new THREE.BoxGeometry(8.15, 0.5, 12.25);
var material = new THREE.MeshPhongMaterial({
color: 0x09f9f9,
transparent: true,
opacity: 0.75
});
var cube = new THREE.Mesh(geometry, material);
cube.geometry.computeBoundingBox ();
root.add(cube);
var bbox = cube.geometry.boundingBox;
var dim = new LinearDimension(document.body, renderer, camera);
// define start and end point of dimension
var from = new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.min.z);
var to = new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.max.z);
// in which direction to "extrude" dimension away from object
var direction = new THREE.Vector3(0, 0, 1);
// request LinearDimension to create threejs node
var newDimension = dim.create(from, to, direction);
// make it cube child
cube.add(newDimension);
var animate = function() {
requestAnimationFrame(animate);
// we need to reposition dimension label on each camera change
dim.update(camera);
renderer.render(scene, camera);
};
Let's see into helper classes now.
✔ Dimension line is only visible when camera angle is not too sharp (more than 45°),
class FacingCamera will let you know world plane, that is best facing to the camera. Useful to hide dimensions, which are facing camera with too sharp (acute) angle.
Separate fiddle to play with class FacingCamera can be found here: https://jsfiddle.net/mmalex/56gzn8pL/
class FacingCamera {
constructor() {
// camera looking direction will be saved here
this.dirVector = new THREE.Vector3();
// all world directions
this.dirs = [
new THREE.Vector3(+1, 0, 0),
new THREE.Vector3(-1, 0, 0),
new THREE.Vector3(0, +1, 0),
new THREE.Vector3(0, -1, 0),
new THREE.Vector3(0, 0, +1),
new THREE.Vector3(0, 0, -1)
];
// index of best facing direction will be saved here
this.facingDirs = [];
this.bestFacingDir = undefined;
// TODO: add other facing directions too
// event listeners are collected here
this.cb = {
facingDirChange: []
};
}
check(camera) {
camera.getWorldDirection(this.dirVector);
this.dirVector.negate();
var maxk = 0;
var maxdot = -1e19;
var oldFacingDirs = this.facingDirs;
var facingDirsChanged = false;
this.facingDirs = [];
for (var k = 0; k < this.dirs.length; k++) {
var dot = this.dirs[k].dot(this.dirVector);
var angle = Math.acos(dot);
if (angle > -Math.PI / 2 && angle < Math.PI / 2) {
this.facingDirs.push(k);
if (oldFacingDirs.indexOf(k) === -1) {
facingDirsChanged = true;
}
if (Math.abs(dot) > maxdot) {
maxdot = dot;
maxk = k;
}
}
}
// and if facing direction changed, notify subscribers
if (maxk !== this.bestFacingDir || facingDirsChanged) {
var prevDir = this.bestFacingDir;
this.bestFacingDir = maxk;
for (var i = 0; i < this.cb.facingDirChange.length; i++) {
this.cb.facingDirChange[i]({
before: {
facing: oldFacingDirs,
best: prevDir
},
current: {
facing: this.facingDirs,
best: this.bestFacingDir
}
}, this);
}
}
}
}
✔ Dimension text is HTML element, styled with CSS and positioned with three.js raycasting logic.
class LinearDimension creates an instance of linear dimension with arrows and text label, and controls it.
LinearDimension complete implementation:
class LinearDimension {
constructor(domRoot, renderer, camera) {
this.domRoot = domRoot;
this.renderer = renderer;
this.camera = camera;
this.cb = {
onChange: []
};
this.config = {
headLength: 0.5,
headWidth: 0.35,
units: "mm",
unitsConverter: function(v) {
return v;
}
};
}
create(p0, p1, extrude) {
this.from = p0;
this.to = p1;
this.extrude = extrude;
this.node = new THREE.Object3D();
this.hidden = undefined;
let el = document.createElement("div");
el.id = this.node.id;
el.classList.add("dim");
el.style.left = "100px";
el.style.top = "100px";
el.innerHTML = "";
this.domRoot.appendChild(el);
this.domElement = el;
this.update(this.camera);
return this.node;
}
update(camera) {
this.camera = camera;
// re-create arrow
this.node.children.length = 0;
let p0 = this.from;
let p1 = this.to;
let extrude = this.extrude;
var pmin, pmax;
if (extrude.x >= 0 && extrude.y >= 0 && extrude.z >= 0) {
pmax = new THREE.Vector3(
extrude.x + Math.max(p0.x, p1.x),
extrude.y + Math.max(p0.y, p1.y),
extrude.z + Math.max(p0.z, p1.z));
pmin = new THREE.Vector3(
extrude.x < 1e-16 ? extrude.x + Math.min(p0.x, p1.x) : pmax.x,
extrude.y < 1e-16 ? extrude.y + Math.min(p0.y, p1.y) : pmax.y,
extrude.z < 1e-16 ? extrude.z + Math.min(p0.z, p1.z) : pmax.z);
} else if (extrude.x <= 0 && extrude.y <= 0 && extrude.z <= 0) {
pmax = new THREE.Vector3(
extrude.x + Math.min(p0.x, p1.x),
extrude.y + Math.min(p0.y, p1.y),
extrude.z + Math.min(p0.z, p1.z));
pmin = new THREE.Vector3(
extrude.x > -1e-16 ? extrude.x + Math.max(p0.x, p1.x) : pmax.x,
extrude.y > -1e-16 ? extrude.y + Math.max(p0.y, p1.y) : pmax.y,
extrude.z > -1e-16 ? extrude.z + Math.max(p0.z, p1.z) : pmax.z);
}
var origin = pmax.clone().add(pmin).multiplyScalar(0.5);
var dir = pmax.clone().sub(pmin);
dir.normalize();
var length = pmax.distanceTo(pmin) / 2;
var hex = 0x0;
var arrowHelper0 = new THREE.ArrowHelper(dir, origin, length, hex, this.config.headLength, this.config.headWidth);
this.node.add(arrowHelper0);
dir.negate();
var arrowHelper1 = new THREE.ArrowHelper(dir, origin, length, hex, this.config.headLength, this.config.headWidth);
this.node.add(arrowHelper1);
// reposition label
if (this.domElement !== undefined) {
let textPos = origin.project(this.camera);
let clientX = this.renderer.domElement.offsetWidth * (textPos.x + 1) / 2 - this.config.headLength + this.renderer.domElement.offsetLeft;
let clientY = -this.renderer.domElement.offsetHeight * (textPos.y - 1) / 2 - this.config.headLength + this.renderer.domElement.offsetTop;
let dimWidth = this.domElement.offsetWidth;
let dimHeight = this.domElement.offsetHeight;
this.domElement.style.left = `${clientX - dimWidth/2}px`;
this.domElement.style.top = `${clientY - dimHeight/2}px`;
this.domElement.innerHTML = `${this.config.unitsConverter(pmin.distanceTo(pmax)).toFixed(2)}${this.config.units}`;
}
}
detach() {
if (this.node && this.node.parent) {
this.node.parent.remove(this.node);
}
if (this.domElement !== undefined) {
this.domRoot.removeChild(this.domElement);
this.domElement = undefined;
}
}
}

three.js - draw line at each segment in tube geometry

I've created a tube geometry with 200 co-ordinates from external json file. Please find the below code.
function plotPath()
{
var obj = getPath();
var segments = obj.path.length;
var closed = false;
var debug = false;
var radiusSegments = 12;
var tube;
var points = [];
var x=0,y=0,z=0; var vertices=[];
var point2x, point2y;
function v(x,y,z) {
return new THREE.Vertex(new THREE.Vector3(x,y,z));
};
for(var i=0; i<obj.path.length; i++)
{
var point = obj.path[i].point;
points.push(point);
}
extrudePath = new THREE.SplineCurve3(points);
extrudePath.dynamic = true;
tube = new THREE.TubeGeometry(extrudePath, segments, 60, radiusSegments, closed, debug);
tube.dynamic = true;
tube.verticesNeedUpdate = true;
tube.dynamic = true;
var faceIndices = [ 'a', 'b', 'c', 'd' ];
var f;
console.log(tube.faces[0]);
for ( var i = 0; i < tube.faces.length; i ++ )
{
f = tube.faces[i];
color = new THREE.Color( 0xffffff );
color.setRGB( Math.random(), Math.random(), Math.random());
for(var j=0;j<4;j++)
{
vertexIndex = f[ faceIndices[ j ] ];
p = tube.vertices[ vertexIndex ];
f.vertexColors[ j ] = color;
}
}
tubeMesh = new THREE.Mesh(tube ,new THREE.MeshBasicMaterial(
{ color: 0xffffff, shading: THREE.FlatShading, side: THREE.DoubleSide, wireframe: false, transparent: false,
vertexColors: THREE.VertexColors, overdraw: true } ));
var v = new THREE.Vector3(1,0,0).normalize();
tubeMesh.applyMatrix(matrix.makeRotationAxis(v, 0));
tubeMesh.applyMatrix(matrix.makeTranslation(-500,0,0));
if ( tube.debug ) tubeMesh.add( tube.debug );
scene.add(tubeMesh);
objects.push(tubeMesh);
}
Now I want to put/draw some marker such as line with text at each segment. I tried to draw line by adding 10 to x and y of each co-ordinates but the tube is translated so could not able to draw it from exact point. Below is the code I am using to add line.
for(var i=0; i<obj.path.length; i++)
{
var point = obj.path[i].point;
point2x = point.x+10;
point2y = point.y+10;
var lineGeo = new THREE.Geometry();
lineGeo.vertices.push(v(point.x, point.y, 0), v(point2x, point2y, 0));
var lineMat = new THREE.LineBasicMaterial({
color: 0x000000, lineWidth: 2});
var line = new THREE.Line(lineGeo, lineMat);
line.type = THREE.Lines;
scene.add(line);
points.push(point);
}
How do I draw/put such marker with text at each segment of tube geometry ?
If you want your lines to move/rotate with your tube, then add the lines as children of the tubeMesh, rather than as children of the scene.
tubeMesh.add( line );
If you need to know how to correctly draw lines, then have a look at the three.js line examples.

Categories

Resources