Related
I'm trying to implement instancing in Aframe using the ThreeJs InstancedMesh based on the example here: https://github.com/mrdoob/three.js/blob/master/examples/webgl_instancing_dynamic.html
Relevant section of code here:
init: function() {
const {count, radius, scale, colors, positions} = this.data;
this.start = true;
this.dummy = new THREE.Object3D();
this.count = count;
this.startObject = new THREE.Object3D();
this.endObject = new THREE.Object3D();
this.instanceColors = new Float32Array(count * 3);
this.instanceColorsBase = new Float32Array(this.instanceColors.length);
this.vertices = [];
this.rotations = [];
for ( var i = 0; i < this.data.count; i ++ ) {
var x = this.data.positions[i][0] * this.data.scale;
var y = this.data.positions[i][1] * this.data.scale;
var z = this.data.positions[i][2] * this.data.scale;
var xEnd = x + this.data.endPositions[i][0] * this.data.scale;
var yEnd = y + this.data.endPositions[i][1] * this.data.scale;
var zEnd = z + this.data.endPositions[i][2] * this.data.scale;
this.vertices.push( x, y, z );
const rotation = this.getDirection({'x':x,'y':y,'z':z},
{'x':xEnd,'y':yEnd,'z':zEnd});
this.rotations.push(rotation.x, rotation.y, rotation.z);
}
let mesh;
let geometry;
let material;
const loader = new THREE.GLTFLoader();
const el = this.el;
loader.load("/assets/arrow/arrow.gltf", function ( model ) {
geometry = model.scene.children[0].children[0].geometry;
geometry.computeVertexNormals();
geometry.scale( 0.03, 0.03, 0.03 );
material = new THREE.MeshNormalMaterial();
mesh = new THREE.InstancedMesh( geometry, material, count );
mesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
el.object3D.add(mesh);
} );
this.el.setAttribute("id", "cells");
},
setMatrix: function (start) {
if (this.mesh) {
for ( let i = 0; i < this.count; i ++ ) {
var x = this.data.positions[i][0] * this.data.scale;
var y = this.data.positions[i][1] * this.data.scale;
var z = this.data.positions[i][2] * this.data.scale;
var xEnd = x + this.data.endPositions[i][0] * this.data.scale;
var yEnd = y + this.data.endPositions[i][1] * this.data.scale;
var zEnd = z + this.data.endPositions[i][2] * this.data.scale;
if (start) {
this.dummy.position.set(xEnd, yEnd, zEnd);
} else {
this.dummy.position.set(x, y, z);
}
this.dummy.rotation.x = this.rotations[i][0];
this.dummy.rotation.y = this.rotations[i][1];
this.dummy.rotation.z = this.rotations[i][2];
this.dummy.updateMatrix();
this.mesh.setMatrixAt( i, this.dummy.matrix );
}
this.mesh.instanceMatrix.needsUpdate = true;
}
}
tick: function() {
this.setMatrix(this.start);
this.start = !this.start;
},
No errors or relevant messages that I can see, but none of the instanced objects are rendering. I don't really have a good way to post an example unfortunately. Anyone know what I'm doing wrong? Thanks in advance!
Note: It seems that the objects are being rendered because the number of triangles being drawn increases drastically when I add this component. However, they are not visible anywhere and I can't find them in the aframe inspector either
It's a very case specific question with a quite extensive topic, so:
In general, using THREE.InstancedMeshes is simple, and you got it right:
// create an instanced mesh
let iMesh = new THREE.InstancedMesh(geometry, material, count)
element.object3D.add(iMesh)
// manipulate the instances
let mtx = new Matrix4()
// set the position, rotation, scale of the matrix
// ...
// update the instance
iMesh.setMatrixAt(index, mtx);
iMesh.instanceMatrix.needsUpdate = true;
Example of an instanced gltf model here
Your code is doing a lot, and it would be easier if it could be stripped to a bare minimum. Yet I think there is only one major issue - this.model isn't set anywhere, so the setMatrix function does nothing. Other than that you may need to disable frustum culling (mesh.frustumCulling = false), or set a bounding sphere - otherwise the objects may dissapear when the base object is out of sight.
Once it's set, your code seems to be working
So I am having this code:
computeCarBoundingBox(mesh);
mesh.rotation.x = this.rotationVal[ 0 ];
mesh.rotation.y = this.rotationVal[ 1 ];
mesh.rotation.z = this.rotationVal[ 2 ];
Where I try to compute a bounding box for a mesh, if I compute it after rotation look like this:
If I compute it after the rotation look like this:
My compute bounding box function is this:
function computeCarBoundingBox(mesh){
var box = new THREE.Box3().setFromObject(mesh);
var boundingBoxHelper = new THREE.Box3Helper( box, 0xffff00 );
scope.carBoundingBox =boundingBoxHelper;
scene.add(scope.carBoundingBox);
console.log(box.min); // x, y, and z are all Infinity.
console.log(box.max); // x, y, and z are all -Infinity.
}
I do have a geometry. This is a part of my code :
this.loadCar = function ( carsVector,carName,roadName ) {
if(carName=='veyron')
{
var index = 0;
}
else if(carName=='F50')
{
var index = 1;
}
else
{
var index = 2;
}
console.log("Selected car name:"+carName);
var carLoader = new THREE.BinaryLoader();
carLoader.load( carsVector[Object.keys(carsVector)[index]].url, function( geometry ) {
geometry.sortFacesByMaterialIndex();
console.log("url--->"+carsVector[Object.keys(carsVector)[index]].url);
var materials = [];
this.scaleVal = carsVector[ Object.keys(carsVector)[index] ].scale * 1;
if(roadName =='road01'){
this.positionVal = carsVector[ Object.keys(carsVector)[index] ].position_r1;
}
else if(roadName=='road02'){
this.positionVal = carsVector[ Object.keys(carsVector)[index] ].position_r2;
}
this.rotationVal = carsVector[ Object.keys(carsVector)[index] ].init_rotation;
for ( var i in carsVector[ Object.keys(carsVector)[index] ].materialsMap ) {
materials[ i ] = carsVector[ Object.keys(carsVector)[index] ].materialsMap[ i ];
}
createObject(geometry,materials);
});
return scope.carMesh;
}
// internal helper methods
function createObject ( geometry, materials ) {
scope.carGeometry = geometry;
scope.carMaterials = materials;
createCar();
};
function createCar () {
console.log("CREATE CARRRRRRRRRRRRRRRRR");
if ( scope.carGeometry ) {
var carMaterial = new THREE.MeshFaceMaterial( scope.carMaterials );
var mesh = new THREE.Mesh( scope.carGeometry, carMaterial );
mesh.scale.x = mesh.scale.y = mesh.scale.z = this.scaleVal;
mesh.position.set( this.positionVal[0], this.positionVal[1], this.positionVal[2]);
mesh.rotation.x = this.rotationVal[ 0 ];
mesh.rotation.y = this.rotationVal[ 1 ];
mesh.rotation.z = this.rotationVal[ 2 ];
this.carMesh = mesh;
//
computeCarBoundingBox(mesh);
console.log("This car mesh"+this.carMesh);
addShadows();
scene.add(this.carMesh);
//this.carBoundingBox.rotation.x =this.r[0];
//this.carBoundingBox.rotation.y = this.r[1];
//this.carBoundingBox.rotation.z = this.r[2];
//scene.add( this.carBoundingBox );
}
if ( scope.callback ) {
scope.callback(this.carMesh);
}
}
These are the methods I'm using in my project where I add the bounding boxes after rotation. If you don't rotate first you don't need the adjustRelativeTo step see e.g. https://codepen.io/seppl2019/pen/zgJVKM
class ChildPart {
constructor(mesh) {
this.mesh=mesh;
this.boxwire=null;
}
// add my bounding box wire to the given mesh
addBoundingBoxWire(toMesh) {
var boxwire = new THREE.BoxHelper(this.mesh, 0xff8000);
this.boxwire=boxwire;
ChildPart.adjustRelativeTo(boxwire,toMesh);
toMesh.add(boxwire);
}
static adjustRelativeTo(mesh,toMesh) {
//logSelected("adjusting toMesh",toMesh);
//logSelected("beforeAdjust",this.mesh);
toMesh.updateMatrixWorld(); // important !
mesh.applyMatrix(new THREE.Matrix4().getInverse(toMesh.matrixWorld));
//logSelected("afterAdjust",this.mesh);
}
}
I run into this problem recently. Thanks to #Wolfgang Fahl 's resolution.
Tt’s the right direction, but when I was doing it, I found something was wrong.
When the mesh have rotation effection. the box is still bigger than original one.
So you need to remove rotation before create BoxHelper, then add rotation back.
static adjustRelativeTo(mesh, toMesh) {
toMesh.updateMatrixWorld(); // important !
mesh.applyMatrix4(new THREE.Matrix4().copy( toMesh.matrixWorld ).invert());
}
addBoundingBox(mesh, toMesh) {
// remove rotation
let rotate = mesh.rotation.clone();
mesh.rotation.set(0, 0 , 0);
let box = new THREE.BoxHelper( mesh, 0xffff00);
// apply to parent matrix
adjustRelativeTo(box, toMesh);
toMesh.add(box);
// 然后再把旋转加上
mesh.rotation.set(rotate.x, rotate.y, rotate.z);
}
That's how .setFromObject() works, when object is wide and when you rotate it, its box will be bigger, as it's world-axis-aligned:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 5, 5);
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);
var light = new THREE.DirectionalLight(0xffffff, 0.75);
light.position.set(-10, 10, -10);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff, 0.25));
scene.add(new THREE.GridHelper(10, 10));
var geometry = new THREE.BoxGeometry(2, 1, 3);
geometry.translate(0, 0.5, 0);
var mesh1 = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: "gray"
}));
mesh1.position.x = -2.5;
scene.add(mesh1);
var mesh2 = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
color: "aqua"
}));
mesh2.position.x = 2.5;
mesh2.rotation.y = THREE.Math.degToRad(45);
scene.add(mesh2);
var bbox1 = new THREE.Box3().setFromObject(mesh1);
var bbox2 = new THREE.Box3().setFromObject(mesh2);
var bhelp1 = new THREE.Box3Helper(bbox1, 0xffff00);
scene.add(bhelp1);
var bhelp2 = new THREE.Box3Helper(bbox2, 0xff00ff);
scene.add(bhelp2);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/93/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
Alright I have two theories, and no certain answer. sorry!
1) It is possible for meshes to be without geometry. Does your mesh have a geometry? If not the code called from setFromObject will fail. (expandByPoint will never be called and min and max will remain at Infinity since the previous makeEmpty-call).
2) Seeing how deeply dependent that recursive "expandByOject" code is on scope and this, I would try adding parenthesis to your new-operator var box = (new THREE.Box3()).setFromObject(mesh); It's a bit of a shot in the dark, but perhaps the scope is never properly set.
Sorry for not taking the time and testing things out first.
I've got a function that create mesh when you click on another. On the click, 2 or 3 mesh are created and go to their positions. Know i would like to do the reverse function : when the mesh are deployed, and you click anoter time on the mesh, the previously created mesh go back and are removed from the scene.
Here is my first function :
function sortiSphere(element, obj) {
var matStdParams = {
roughness: 1,
metalness: 0.8,
color: element.group,
emissive: element.group,
emissiveIntensity: 0.5
};
var sphereMaterial2 = new THREE.MeshStandardMaterial(matStdParams);
var mesh = new THREE.Mesh(sphereGeometry, sphereMaterial2);
mesh.position.x = x;
mesh.position.y = y;
mesh.position.z = z;
mesh.userData.children = element.children;
mesh.userData.name = element.label;
mesh.userData.rang = element.rang;
mesh.userData.def = element.définition;
mesh.userData.posx = element.posx;
mesh.userData.posy = element.posy;
mesh.userData.posz = element.posz;
mesh.userData.parent = obj;
mesh.userData.cartabs = element.cartabs;
mesh.position.normalize();
mesh.position.multiplyScalar(1 / element.rang);
mesh.scale.set(1 / (element.rang / 2), 1 / (element.rang / 2), 1 / (element.rang / 2))
mesh.position.x = obj.position.x;
mesh.position.y = obj.position.y;
mesh.position.z = obj.position.z;
var x = element.posx;
var y = element.posy;
var z = element.posz;
new TWEEN.Tween(mesh.position).to({
x: x,
y: y,
z: z
}, 1000)
.easing(TWEEN.Easing.Elastic.Out).start();
console.log(mesh);
scene.add(mesh);
lesMesh.push(mesh)
// lines
var material = new THREE.LineBasicMaterial({
color: 0xffffff
});
var geometry = new THREE.Geometry();
geometry.vertices.push(
obj.position,
new THREE.Vector3(x, y, z)
);
var line = new THREE.Line(geometry, material);
scene.add(line);
gen1 = [];
gen2 = [];
gen3 = [];
gen4 = [];
gen5 = [];
obj.userData.bool = true;
};
Notice that the mesh contain a information about their state : userData.bool (true if deployed, false if not)
Now, how do I tell them to go back ?
Here is what I got for now :
function deleteSphere(element, obj) {
console.log("--- deleteSphere / debut fonction")
for (i = 0; gen1.length > i; i++) {
if (gen1[i].userData.children) {
for (j = 0; gen1[i].userData.children.length > j; j++) {
console.log(gen2[i][j]);
scene.remove(gen2[i][j]);}
} else {
scene.remove(gen1[i])
console.log(gen1[i].userData.children)
}
}
};
Thanks for your time
When you use tween.js then you can use its .onUpdate() and .onComplete() methods.
This is not the ultimate solution, this is just an example from scratch.
Let's have a container for our base objects:
var bases = new THREE.Group();
scene.add(bases);
Now we need an event where we'll create our base objects, let it be a click on a button:
btnAdd.addEventListener("click", setBase);
and our setBase() function will look like this:
var rndBase = function() {
return THREE.Math.randFloatSpread(10);
}
function setBase() {
var base = new THREE.Mesh(new THREE.SphereGeometry(.5, 8, 4), new THREE.MeshBasicMaterial({
color: Math.random() * 0xffffff
}));
base.position.set(rndBase(), rndBase(), rndBase());
bases.add(base);
}
Everything's ready and we can start with clicking on base objects. Let it be an event listener for mousedown:
window.addEventListener("mousedown", setOrRemove, false);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects;
var obj;
function setOrRemove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObjects(bases.children);
if (intersects.length > 0) {
obj = intersects[0].object;
if (obj.children.length != 0) { // simple check for existence of children
removeObj(obj); // if children were deployed, then remove them and their parent (base object)
} else {
setChildren(obj); // otherwise, as a base object has no children, then we'll add some
}
}
}
The most funny part begins here.
Let's set the children:
function setChildren(parent) {
let radius = Math.random() + 1;
for (let i = 0; i < THREE.Math.randInt(2, 3); i++) { //as you said there are 2 or 3 children
var child = new THREE.Mesh(new THREE.BoxGeometry(.25, .25, .25), new THREE.MeshBasicMaterial({
color: parent.material.color,
wireframe: true
}));
child.userData.direction = new THREE.Vector3(rndBase(), rndBase(), rndBase()).normalize(); // random direction (must be a normalized vector)
child.userData.radius = radius;
parent.add(child);
}
let start = { val: 0 };
let end = { val: 1 };
new TWEEN.Tween(start).to(end, 1000) // we simply change from 0 to 1 during 1 second
.easing(TWEEN.Easing.Elastic.Out)
.onUpdate( // here we'll update position of each children in accordance to direction and radius
function() {
parent.children.forEach((child) => {
child.position.copy(child.userData.direction).multiplyScalar(child.userData.radius * this.val);
})
}
)
.start();
}
And removing of a base object and its children is:
function removeObj(baseObj) { // very similar to how we created children
let start = { val: 1 };
let end = { val: 0 };
new TWEEN.Tween(start).to(end, 1000)
.easing(TWEEN.Easing.Elastic.In)
.onUpdate(
function(){
baseObj.children.forEach((child) => {
child.position.copy(child.userData.direction).multiplyScalar(child.userData.radius * this.val);
})
}
)
.onComplete( // but the difference is here, it allow us to perform something when our tweening is completed
function() {
for (let i = baseObj.children - 1; i = 0; i--) {
baseObj.remove(baseObj.children[i]); //and here we simply delete children in reverse order
}
bases.remove(baseObj); //and then we remove the base object itself
}
)
.start()
}
So, this is it. Don't forget to put TWEEN.update(); in our animation loop :)
jsfiddle example r86.
I have a mesh defined which is at the origin.
var textureCanvas = new THREE.CanvasTexture( imageCanvas );
textureCanvas.repeat.set( 4, 4 );
textureCanvas.wrapS = THREE.RepeatWrapping;
textureCanvas.wrapT = THREE.RepeatWrapping;
var materialCanvas = new THREE.MeshBasicMaterial( { map: textureCanvas }
);
var geometry = new THREE.Geometry();
geometry.vertices.push(
new THREE.Vector3(-4,-4,0),
new THREE.Vector3(-4,4,0),
new THREE.Vector3(4,4,0),
new THREE.Vector3(4,-4,0),
);
geometry.faces.push(
new THREE.Face3(3, 1, 0),
new THREE.Face3(3, 2, 1)
);
var vertexMappings = [];
vertexMappings[0] = new THREE.Vector2(0,1);
vertexMappings[1] = new THREE.Vector2(0,0);
vertexMappings[2] = new THREE.Vector2(1,0);
vertexMappings[3] = new THREE.Vector2(1,1);
var vm = vertexMappings;
geometry.faceVertexUvs[ 0 ] = [];
geometry.faceVertexUvs[0][0] = [ vm[3], vm[1], vm[0] ];
geometry.faceVertexUvs[0][1] = [ vm[3], vm[2], vm[1] ];
meshCanvas = new THREE.Mesh( geometry, materialCanvas );
meshCanvas.rotation.x = - Math.PI / 3;
meshCanvas.rotation.z = - Math.PI / 2.5;
meshCanvas.scale.set( 80, 80, 80 );
scene.add( meshCanvas );
I also have a line which goes through the mesh. It was originally passing through the origin but I moved it a bit (See github issue below).
var linegeo = new THREE.Geometry();
linegeo.vertices.push(
new THREE.Vector3(55, 300, 0),
new THREE.Vector3(-10, -300, 32)
);
scene.add(linemesh);
I want to get the position where the line intersects the mesh, but the intersections result is always empty:
getInersectionPosition(linemesh, meshCanvas);
function getInersectionPosition(linemesh, meshCanvas) {
linemesh.updateMatrixWorld();
meshCanvas.updateMatrixWorld();
var p1 = linemesh.geometry.vertices[0].clone(),
p2 = linemesh.geometry.vertices[1].clone();
p1.applyMatrix4(linemesh.matrixWorld);
p2.applyMatrix4(linemesh.matrixWorld);
//console.log(`p1: ${JSON.stringify(p1)}, p2: ${JSON.stringify(p2)}`);
//console.log(`canvas position: ${JSON.stringify(meshCanvas.position)}`);
var raycaster = new THREE.Raycaster(p1, p2);
raycaster.linePrecision = 10;
//var intersections = raycaster.intersectObjects([meshCanvas]);
var intersections = raycaster.intersectObjects(scene.children, true);
if (intersections.length > 0)
console.log(`intersections: ${ intersections.length}`);
}
Full sample: https://jsfiddle.net/mribbons/103wwsda/
Is it possible that I have this issue?
https://github.com/mrdoob/three.js/issues/11449
The raycaster.intersectObjects() call seems to fail here, with very large values for distance (2.7 million or so, while sphere.radius is ~450).
https://github.com/mrdoob/three.js/blob/dev/build/three.js#L9761
intersectsSphere: function ( sphere ) {
var distance = this.distanceToPoint( sphere.center )
return distance <= sphere.radius;
},
My mistake, raycaster expects a point and a normal, this resolves the issue:
var normal = new THREE.Vector3();
normal.subVectors(p2, p1).normalize();
var raycaster = new THREE.Raycaster(p1, normal);
var intersections = raycaster.intersectObjects(scene.children, true);
if (intersections.length > 0)
console.log(`intersections: ${ intersections.length}`); // outputs "intersections: 2"
Based on a previous question I had recently posted:
How to create lines between nearby particles in ThreeJS?
I was able to create individual lines joining nearby particles. However, the lines are being drawn twice because of the logic of the particle system. This is because of how the original 2D particle system worked:
https://awingit.github.io/particles/
This also draws the lines twice. For each pair of particles connecting a line, the line is drawn.
I do not think this is ideal for performance. How would I only draw a line once for each joining points?
P.S. Here is exactly the effect I would like to achieve, but cannot make sense of the code:
http://freelance-html-developer.com/clock/
I would like to understand the fundamental logic.
UPDATE:
I have created a jsfiddle with my progress.
var canvas, canvasDom, ctx, scene, renderer, camera, controls, geocoder, deviceOrientation = false;
var width = 800,
height = 600;
var particleCount = 20;
var pMaterial = new THREE.PointsMaterial({
color: 0x000000,
size: 0.5,
blending: THREE.AdditiveBlending,
//depthTest: false,
//transparent: true
});
var particles = new THREE.Geometry;
var particleSystem;
var line;
var lines = {};
var lineGroup = new THREE.Group();
var lineMaterial = new THREE.LineBasicMaterial({
color: 0x000000,
linewidth: 1
});
var clock = new THREE.Clock();
var maxDistance = 15;
function init() {
canvasDom = document.getElementById('canvas');
setupStage();
setupRenderer();
setupCamera();
setupControls();
setupLights();
clock.start();
window.addEventListener('resize', onWindowResized, false);
onWindowResized(null);
createParticles();
scene.add(lineGroup);
animate();
}
function setupStage() {
scene = new THREE.Scene();
}
function setupRenderer() {
renderer = new THREE.WebGLRenderer({
canvas: canvasDom,
logarithmicDepthBuffer: true
});
renderer.setSize(width, height);
renderer.setClearColor(0xfff6e6);
}
function setupCamera() {
camera = new THREE.PerspectiveCamera(70, width / height, 1, 10000);
camera.position.set(0, 0, -60);
}
function setupControls() {
if (deviceOrientation) {
controls = new THREE.DeviceOrientationControls(camera);
controls.connect();
} else {
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target = new THREE.Vector3(0, 0, 0);
}
}
function setupLights() {
var light1 = new THREE.AmbientLight(0xffffff, 0.5); // soft white light
var light2 = new THREE.PointLight(0xffffff, 1, 0);
light2.position.set(100, 200, 100);
scene.add(light1);
scene.add(light2);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
animateParticles();
updateLines();
render();
}
function render() {
renderer.render(scene, camera);
}
function onWindowResized(event) {
width = window.innerWidth;
height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
function createParticles() {
for (var i = 0; i < particleCount; i++) {
var pX = Math.random() * 50 - 25,
pY = Math.random() * 50 - 25,
pZ = Math.random() * 50 - 25,
particle = new THREE.Vector3(pX, pY, pZ);
particle.diff = Math.random() + 0.2;
particle.default = new THREE.Vector3(pX, pY, pZ);
particle.offset = new THREE.Vector3(0, 0, 0);
particle.velocity = {};
particle.velocity.y = particle.diff * 0.5;
particle.nodes = [];
particles.vertices.push(particle);
}
particleSystem = new THREE.Points(particles, pMaterial);
particleSystem.position.y = 0;
scene.add(particleSystem);
}
function animateParticles() {
var pCount = particleCount;
while (pCount--) {
var particle = particles.vertices[pCount];
var move = Math.sin(clock.getElapsedTime() * (1 * particle.diff)) / 4;
particle.offset.y += move * particle.velocity.y;
particle.y = particle.default.y + particle.offset.y;
detectCloseByPoints(particle);
}
particles.verticesNeedUpdate = true;
particleSystem.rotation.y += 0.01;
lineGroup.rotation.y += 0.01;
}
function updateLines() {
for (var _lineKey in lines) {
if (!lines.hasOwnProperty(_lineKey)) {
continue;
}
lines[_lineKey].geometry.verticesNeedUpdate = true;
}
}
function detectCloseByPoints(p) {
var _pCount = particleCount;
while (_pCount--) {
var _particle = particles.vertices[_pCount];
if (p !== _particle) {
var _distance = p.distanceTo(_particle);
var _connection = checkConnection(p, _particle);
if (_distance < maxDistance) {
if (!_connection) {
createLine(p, _particle);
}
} else if (_connection) {
removeLine(_connection);
}
}
}
}
function checkConnection(p1, p2) {
var _childNode, _parentNode;
_childNode = p1.nodes[particles.vertices.indexOf(p2)] || p2.nodes[particles.vertices.indexOf(p1)];
if (_childNode && _childNode !== undefined) {
_parentNode = (_childNode == p1) ? p2 : p1;
}
if (_parentNode && _parentNode !== undefined) {
return {
parent: _parentNode,
child: _childNode,
lineId: particles.vertices.indexOf(_parentNode) + '-' + particles.vertices.indexOf(_childNode)
};
} else {
return false;
}
}
function removeLine(_connection) {
// Could animate line out
var childIndex = particles.vertices.indexOf(_connection.child);
_connection.parent.nodes.splice(childIndex, 1);
deleteLine(_connection.lineId);
}
function deleteLine(_id) {
lineGroup.remove(lines[_id]);
delete lines[_id];
}
function addLine(points) {
var points = points || [new THREE.Vector3(Math.random() * 10, Math.random() * 10, Math.random() * 10), new THREE.Vector3(0, 0, 0)];
var _lineId = particles.vertices.indexOf(points[0]) + '-' + particles.vertices.indexOf(points[1]);
var lineGeom = new THREE.Geometry();
if (!lines[_lineId]) {
lineGeom.dynamic = true;
lineGeom.vertices.push(points[0]);
lineGeom.vertices.push(points[1]);
var curLine = new THREE.Line(lineGeom, lineMaterial);
curLine.touched = false;
lines[_lineId] = curLine;
lineGroup.add(curLine);
return curLine;
} else {
return false;
}
}
function createLine(p1, p2) {
p1.nodes[particles.vertices.indexOf(p2)] = p2;
addLine([p1, p2]);
}
$(document).ready(function() {
init();
});
I am really close, but I am not sure if its optimized. There seem to be flickering lines, and sometimes a line just stays stuck in place.
So here are my thoughts. I clicked that all I have to do is make the Vector3 points of the lines equal to the relevant particle Vector3 points. I just need to update each lines geometry.verticesNeedUpdate = true;
Also, how I manage the lines is I create a unique ID using the indexes of the 2 points, e.g. lines['8-2'] = line
The problem you're actually trying to solve is that while looping through your list of points, you're doubling the number of successful matches.
Example:
Consider a list of points, [A, B, C, D]. Your looping tests each point against all other points. For this example, A and C are the only points close enough to be considered nearby.
During the first iteration, A vs. all, you find that A and C are nearby, so you add a line. But when you're doing your iteration for C, you also find that A is nearby. This causes the second line, which you want to avoid.
Fixing it:
The solution is simple: Don't re-visit nodes you already checked. This works because the answer of distance from A to C is no different from distance from C to A.
The best way to do this is adjust your indexing for your check loop:
// (Note: This is example code, and won't "just work.")
for(var check = 0, checkLength = nodes.length; check < checkLength; ++check){
for(var against = check + 1, against < checkLength; ++against){
if(nodes[check].distanceTo(nodes[against]) < delta){
buildThatLine(nodes[check], nodes[against]);
}
}
}
In the inner loop, the indexing is set to:
Skip the current node
Skip all nodes before the current node.
This is done by initializing the inner indexing to the outer index + 1.
Caveat:
This particular logic assumes that you discard all your lines for every frame. It's not the most efficient way to achieve the effect, but I'll leave making it more efficient as an exercise for you.