BoxGeometry not aligning with SphereGeometry properly - javascript

I am trying to create spikes on earth(sphere geometry). Though everything works fines, but spikes dont align with globe. I want spike to align something like below image. But my spikes dont lookAt(new THREE.Vector3(0,0,0)) despite mentioned. Please help me out.
I purposefully mentioned code required for debugging. Let me know if you need more code for this. Below image is how i want my spikes to align with sphere.
But this is how it looks
My Main JS initialization file.
$(document).ready(function () {
// Initializing Camera
Influx.Camera = new Influx.Camera({
fov: 60,
aspectRatio: window.innerWidth / window.innerHeight,
near: 1,
far: 1000,
position: {
x: 0,
y: 0,
z: 750
}
});
//Initializing Scene
Influx.Scene = new Influx.Scene();
// Initializing renderer
Influx.Renderer = new Influx.Renderer({
clearColor: 0x000000,
size: {
width: window.innerWidth,
height: window.innerHeight
}
});
Influx.Globe = new Influx.Globe({
radius: 300,
width: 50,
height: 50
});
//
Influx.Stars = new Influx.Stars({
particleCount: 15000,
particle: {
color: 0xFFFFFF,
size: 1
}
});
Influx.moveTracker = new Influx.moveTracker();
Influx.EventListener = new Influx.EventListener();
(function animate() {
requestAnimationFrame( animate );
render();
controls.update();
})();
function render() {
camera.lookAt(scene.position);
group.rotation.y -= 0.001;
renderer.render( scene, camera );
};
});
Below is code responsible for generating spikes on Globe.
Influx.Spikes = function (lat, long) {
// convert the positions from a lat, lon to a position on a sphere.
var latLongToVector3 = function(lat, lon, RADIUS, heigth) {
var phi = (lat) * Math.PI/180,
theta = (lon-180) * Math.PI/180;
var x = -(RADIUS+heigth) * Math.cos(phi) * Math.cos(theta),
y = (RADIUS+heigth) * Math.sin(phi),
z = (RADIUS+heigth) * Math.cos(phi) * Math.sin(theta);
return new THREE.Vector3(x, y, z);
};
var geom = new THREE.Geometry();
var BoxGeometry = new THREE.BoxGeometry(1, 100, 1);
//iterates through the data points and makes boxes with the coordinates
var position = latLongToVector3(lat, long, 300, 2);
var box = new THREE.Mesh( BoxGeometry );
//each position axis needs to be set separately, otherwise the box
//will instantiate at (0,0,0)
box.position.x = position.x;
box.position.y = position.y;
box.position.z = position.z;
box.lookAt(new THREE.Vector3(0, 0, 0));
box.updateMatrix();
//merges the geometry to speed up rendering time, don't use THREE.GeometryUtils.merge because it's deprecated
geom.merge(box.geometry, box.matrix);
var total = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({
color: getRandomColor(),
morphTargets: true
}));
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
//add boxes to the group
group.add(total);
scene.add(group);
};
Influx.Camera = function(params = {}) {
if ( !$.isEmptyObject(params) ) {
window.camera = new THREE.PerspectiveCamera(params.fov, params.aspectRatio, params.near, params.far);
camera.position.set(params.position.x, params.position.y, params.position.z);
camera.lookAt(new THREE.Vector3(0,0,0));
} else {
console.log("Trouble with Initializing Camera");
return;
}
};

Remember that lookAt takes a direction vector, you give to this method the vector (0, 0, 0), this is actually not a normalized direction vector. So you must calculate the direction:
from your box position to the center of the sphere AND normalize it.
var dir = box.position.sub(world.position).normalize();
box.lookAt(dir);
And now just a set of code good conventions that may help you:
var BoxGeometry = new THREE.BoxGeometry(1, 100, 1);
Here I would rather use another var name for the box geometry, not to mix up with the "class" definition from THREE and to follow naming conventions:
var boxGeometry = new THREE.BoxGeometry(1, 100, 1);
And here:
box.position.x = position.x;
box.position.y = position.y;
box.position.z = position.z;
You can just set:
box.position.copy(position);

I also meet this problem, and I fixed it, the solution is: box.lookAt(new THREE.Vector3(0, 0, 0)) must after box.scale.z = xxxx

Related

How can I rotate a vector 90 degrees into a perpendicular plane, and then 15 degrees free from that plane?

What I ultimately want is a vector, giving the direction of the green line in the image below, knowing only the position of the yellow and green dots.
To be more specific, it's angle can be random as long as it's endpoint ends up somewhere on the green-blue surface of the cylinder. So, 360° free around cylinder, and about 15° limited to the edges of the cylinder.
The cylinder is perpendicular to the line from the yellow and green dot.
Length is not important, only direction.
My main problem is I don't know how to go from vector Yellow to green dot, to any vector perpendicular to it.
PS None of these things are aligned on a x y z axis. That grid is not xyz, just to help visualize.
here is the code: given an angle theta and two points it will give you a vector starting from pointStart perpendicular to the vector from pointStart to pointEnd:
function perpendicularVector(pointStart,pointEnd,theta){
let vDiff = new THREE.Vector3(0, 0, 0)
.subVectors(pointEnd, pointStart)
.normalize()
let V = new THREE.Vector3(
vDiff.y + vDiff.x * vDiff.z,
vDiff.y * vDiff.z -vDiff.x,
-(vDiff.x * vDiff.x) - vDiff.y * vDiff.y
)
return
V .applyAxisAngle(vDiff, theta)
.applyAxisAngle( new THREE.Vector3().multiplyVectors(V, vDiff).normalize(), 15*Math.PI/180 )
}
here is a small showoff of what the above code do: (the snippet is intentionally bad because its there just to show the functionality of the above code)
(you can zoom rotate and pan using the mouse on the render that appears after you click run snippet)
body {
font-family: sans-serif;
margin: 0;
background-color: #e2cba9;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
<div id="app"></div>
<script type="module">
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three#0.121.1/examples/jsm/controls/OrbitControls.js";
import * as THREE from "https://cdn.jsdelivr.net/npm/three#0.121.1/build/three.module.js";
var scene = new THREE.Scene, theta = 0;
let point1 = new THREE.Vector3(4, 2, 1),
point2 = new THREE.Vector3(0, 3, 3);
function perpendicularVector(e, n, t) {
let r = new THREE.Vector3(0, 0, 0).subVectors(n, e).normalize(),
o = new THREE.Vector3(r.y, -r.x, 0),
i = new THREE.Vector3(r.x * r.z, r.y * r.z, -r.x * r.x - r.y * r.y);
var a = o.multiplyScalar(Math.cos(t)).add(i.multiplyScalar(Math.sin(t)));
return a.add(e), a
}
function pointAtCoords(e, n) {
let t = new THREE.MeshBasicMaterial({ color: n }),
r = new THREE.SphereGeometry(.1, 8, 8),
o = new
THREE.Mesh(r, t);
return o.position.add(e), o
}
function lineFromAtoB(e, n, t) {
let r = new THREE.LineBasicMaterial({ color: t }),
o = [];
o.push(e), o.push(n);
let i = (new THREE.BufferGeometry).setFromPoints(o);
return new THREE.Line(i, r)
}
var renderer = new THREE.WebGLRenderer({ antialias: !0 });
renderer.setSize(window.innerWidth, window.innerHeight), document.getElementById("app").appendChild(renderer.domElement);
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight,
.1, 1e3);
camera.position.set(7, 7, 8), camera.lookAt(new THREE.Vector3), camera.position.add(new THREE.Vector3(3, 0, 3));
var controls = new OrbitControls(camera, renderer.domElement);
function drawEverything(e) {
const n = new THREE.AxesHelper(30);
scene.add(n);
const t = new THREE.GridHelper(30, 30);
t.position.add(new THREE.Vector3(15, 0, 15)), scene.add(t);
const r = new THREE.GridHelper(30, 30);
r.rotateX(Math.PI / 2), r.position.add(new THREE.Vector3(15, 15, 0)), scene.add(r);
const o = new THREE.GridHelper(30, 30);
o.rotateZ(Math.PI / 2), o.position.add(new THREE.Vector3(0, 15, 15)), scene.add(o);
let i = new THREE.Vector3(0, 0, 0),
a = perpendicularVector(point1, point2, e);
scene.add(pointAtCoords(point1, 16776960)), scene.add(pointAtCoords(point2, 65280));
var d = pointAtCoords(a, 255);
scene.add(d), scene.add(lineFromAtoB(point1, point2, 16711935)), scene.add(lineFromAtoB(i, point1, 16711680)), scene.add(lineFromAtoB(i, point2, 16711680)), scene.add(lineFromAtoB(point1, a, 65280))
}
function animate() {
scene = new THREE.Scene, drawEverything(theta += .1),
setTimeout((() => {
requestAnimationFrame(animate)
}), 1e3 / 30), renderer.render(scene, camera)
}
animate();
</script>
This is totally achievable with some math calculations. The term you're looking for is "Orthogonal vectors", which means vectors that are perpendicular to each other. The cylinder radius is orthogonal to the line between blue to yellow points.
However, since you're already using Three.js, you can just let it do all the hard work for you with the help of an Object3D.
// Declare vectorA (center, green)
const vecA = new THREE.Vector3(xA, yA, zA);
// Declare vectorB (destination, yellow)
const vecB = new THREE.Vector3(xB, yB, zB);
// Create helper object
const helper = new THREE.Object3D();
// Center helper at vecA
helper.position.copy(vecA);
// Rotate helper towards vecB
helper.lookAt(vecB);
// Move helper perpendicularly along its own y-axis
const cylinderRadius = 27;
helper.translateY(cylinderRadius);
// Now you have your final position!
console.log(helper.position);
In the diagram below, the helper Object3D is shown as a red line only to give you a sense of its rotation and position, but in reality it is invisible unless you add a Mesh to it.
If you want to add/subtract 15 degrees from the perpendicular, you could just rotate the helper along its own x-axis before translateY()
const xAngle = THREE.MathUtils.degToRad(15);
helper.rotateX(xAngle);
const cylinderRadius = 27;
helper.translateY(cylinderRadius);

three.js reflector (mirror) clipping object when HDRi background is set

I have scene with elements size over 500 units and i want to create mirror effect for them. to Reach descripted effect i used Reflector library from three.js webgl_mirror example.
I placed mirror on ground and most of meshes disappears or showing only small parts of surface when i set background hdri without its displayes normally. I builded other scene for tests and it looks like this unexpected effect begins when distance between mirror and obiect is over around 75 units (sometimes its less i dont know what its depends).
Image to preview on that effect
Is there any possibility that i could increase range of this clipping box size for that mirror? (i really want to avoid of scaling my actual created scene)
What i already tryed:
-changing my perspective camera far and near distances. - no effect
-manipulate paramets for clipBias and recursion or even increasing texture size. -no effect
-adding multiple lights around elements. -no effect
code that i used for experiment:
sceneSetup = () => {
//initialize
const width = this.mount.clientWidth;
const height = this.mount.clientHeight;
this.scene = new THREE.Scene();
let helperScene = this.scene;
this.camera = new THREE.PerspectiveCamera(60, width / height, 1, 500);
this.camera.position.z = 200;
this.controls = new OrbitControls(this.camera, document.body);
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(width, height);
this.mount.appendChild(this.renderer.domElement); //render to container (React staff)
///Load HDR map
new RGBELoader()
.setDataType(THREE.UnsignedByteType)
.load(HdrFile, function(texture) {
var envMap = pmremGenerator.fromEquirectangular(texture).texture;
helperScene.background = envMap; // comment to see issue
helperScene.environment = envMap;
texture.dispose();
pmremGenerator.dispose();
});
var pmremGenerator = new THREE.PMREMGenerator(this.renderer);
pmremGenerator.compileEquirectangularShader();
//create ground mirror
let geometry = new THREE.PlaneBufferGeometry(200, 200);
let groundMirror = new Reflector(geometry, {
clipBias: 0,
textureWidth: 1024,
textureHeight: 1024,
color: 0x889999,
recursion: 1
});
groundMirror .position.z = -20;
groundMirror .rotation.x = Math.PI * -0.5;
//change groundMirror .position.y to -104 and evrything looks fine;
groundMirror .position.y = -105;
this.scene.add(groundMirror );
};
addCustomSceneObjects = () => {
//create cube for reflect
const geometry = new THREE.BoxGeometry(50, 50, 50);
const material = new THREE.MeshPhongMaterial({
color: 0x156289,
emissive: 0x072534,
side: THREE.DoubleSide,
depthTest: true,
depthWrite: true
});
this.cube = new THREE.Mesh(geometry, material);
this.cube.position.y = 0;
this.scene.add(this.cube);
//radding lights
const lights = [];
lights[0] = new THREE.PointLight(0xffffff, 1, 0);
lights[1] = new THREE.PointLight(0xffffff, 1, 0);
lights[2] = new THREE.PointLight(0xffffff, 1, 0);
lights[0].position.set(0, 200, 0);
lights[1].position.set(100, 200, 100);
lights[2].position.set(-100, -200, -100);
this.scene.add(lights[0]);
this.scene.add(lights[1]);
this.scene.add(lights[2]);
};
startAnimationLoop = () => {
//rotate cube
this.cube.rotation.x += 0.01;
this.cube.rotation.y += 0.01;
this.requestID = window.requestAnimationFrame(this.startAnimationLoop);
this.renderer.render(this.scene, this.camera);
};

Three.js - Bigger bounding box after rotation

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.

Threejs Particle System with joining lines. Programming logic?

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.

Incrementally display three.js TubeGeometry

I am able to display a THREE.TubeGeometry figure as follows
Code below, link to jsbin
<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>
<script>
// global variables
var renderer;
var scene;
var camera;
var geometry;
var control;
var count = 0;
var animationTracker;
init();
drawSpline();
function init()
{
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
renderer.setClearColor('lightgray', 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
// position and point the camera to the center of the scene
camera.position.x = 0;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
}
function drawSpline(numPoints)
{
var numPoints = 100;
// var start = new THREE.Vector3(-5, 0, 20);
var start = new THREE.Vector3(-5, 0, 20);
var middle = new THREE.Vector3(0, 35, 0);
var end = new THREE.Vector3(5, 0, -20);
var curveQuad = new THREE.QuadraticBezierCurve3(start, middle, end);
var tube = new THREE.TubeGeometry(curveQuad, numPoints, 0.5, 20, false);
var mesh = new THREE.Mesh(tube, new THREE.MeshNormalMaterial({
opacity: 0.9,
transparent: true
}));
scene.add(mesh);
renderer.render(scene, camera);
}
</script>
</body>
</html>
However, I would like to display incrementally, as in, like an arc that is loading, such that it starts as the start point, draws incrementally and finally looks the below arc upon completion.
I have been putting in some effort, and was able to do this by storing all the points/coordinates covered by the arc, and drawing lines between the consecutive coordinates, such that I get the 'arc loading incrementally' feel. However, is there a better way to achieve this? This is the link to jsbin
Adding the code here as well
<!DOCTYPE html>
<html>
<head>
<title>Incremental Spline Curve</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<script>
// global variables
var renderer;
var scene;
var camera;
var splineGeometry;
var control;
var count = 0;
var animationTracker;
// var sphereCamera;
var sphere;
var light;
function init() {
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
// renderer.setClearColor(0x000000, 1.0);
renderer.setClearColor( 0xffffff, 1 );
renderer.setSize(window.innerWidth, window.innerHeight);
// position and point the camera to the center of the scene
camera.position.x = 0;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
// //init for sphere
// sphereCamera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
// sphereCamera.position.y = -400;
// sphereCamera.position.z = 400;
// sphereCamera.rotation.x = .70;
sphere = new THREE.Mesh(new THREE.SphereGeometry(0.8,31,31), new THREE.MeshLambertMaterial({
color: 'yellow',
}));
light = new THREE.DirectionalLight('white', 1);
// light.position.set(0,-400,400).normalize();
light.position.set(0,10,10).normalize();
//get points covered by Spline
getSplineData();
}
//save points in geometry.vertices
function getSplineData() {
var curve = new THREE.CubicBezierCurve3(
new THREE.Vector3( -5, 0, 10 ),
new THREE.Vector3(0, 20, 0 ),
new THREE.Vector3(0, 20, 0 ),
new THREE.Vector3( 2, 0, -25 )
);
splineGeometry = new THREE.Geometry();
splineGeometry.vertices = curve.getPoints( 50 );
animate();
}
//scheduler loop
function animate() {
if(count == 50)
{
cancelAnimationFrame(animationTracker);
return;
}
//add line to the scene
drawLine();
renderer.render(scene, camera);
// renderer.render(scene, sphereCamera);
count += 1;
// camera.position.z -= 0.25;
// camera.position.y -= 0.25;
animationTracker = requestAnimationFrame(animate);
}
function drawLine() {
var lineGeometry = new THREE.Geometry();
var lineMaterial = new THREE.LineBasicMaterial({
color: 0x0000ff
});
console.log(splineGeometry.vertices[count]);
console.log(splineGeometry.vertices[count+1]);
lineGeometry.vertices.push(
splineGeometry.vertices[count],
splineGeometry.vertices[count+1]
);
var line = new THREE.Line( lineGeometry, lineMaterial );
scene.add( line );
}
// calls the init function when the window is done loading.
window.onload = init;
</script>
<body>
</body>
</html>
Drawback : The drawback of doing it the above way is that, end of the day, I'm drawing a line between consecutive points, and so I lose out on a lot of the effects possible in TubeGeometry such as, thickness, transparency etc.
Please suggest me an alternative way to get a smooth incremental load for the TubeGeometry.
THREE.TubeGeometry returns a THREE.BufferGeometry.
With THREE.BufferGeometry, you have access to a property drawRange that you can set to animate the drawing of the mesh:
let nEnd = 0, nMax, nStep = 90; // 30 faces * 3 vertices/face
...
const geometry = new THREE.TubeGeometry( path, pathSegments, tubeRadius, radiusSegments, closed );
nMax = geometry.attributes.position.count;
...
function animate() {
requestAnimationFrame( animate );
nEnd = ( nEnd + nStep ) % nMax;
mesh.geometry.setDrawRange( 0, nEnd );
renderer.render( scene, camera );
}
EDIT: For another approach, see this SO answer.
three.js r.144
Normally you would be able to use the method .getPointAt() to "get a vector for point at relative position in curve according to arc length" to get a point at a certain percentage of the length of the curve.
So normally if you want to draw 70% of the curve and a full curve is drawn in 100 segments. Then you could do:
var percentage = 70;
var curvePath = new THREE.CurvePath();
var end, start = curveQuad.getPointAt( 0 );
for(var i = 1; i < percentage; i++){
end = curveQuad.getPointAt( percentage / 100 );
lineCurve = new THREE.LineCurve( start, end );
curvePath.add( lineCurve );
start = end;
}
But I think this is not working for your curveQuad since the getPointAt method is not implemented for this type. A work around is to get a 100 points for your curve in an array like this:
points = curve.getPoints(100);
And then you can do almost the same:
var percentage = 70;
var curvePath = new THREE.CurvePath();
var end, start = points[ 0 ];
for(var i = 1; i < percentage; i++){
end = points[ percentage ]
lineCurve = new THREE.LineCurve( start, end );
curvePath.add( lineCurve );
start = end;
}
now your curvePath holds the line segments you want to use for drawing the tube:
// draw the geometry
var radius = 5, radiusSegments = 8, closed = false;
var geometry = new THREE.TubeGeometry(curvePath, percentage, radius, radiusSegments, closed);
Here a fiddle with a demonstration on how to use this dynamically
I'm not really that familiar with three.js. But I think I can be of assistance. I have two solutions for you. Both based on the same principle: build a new TubeGeometry or rebuild the current one, around a new curve.
Solution 1 (Simple):
var CurveSection = THREE.Curve.create(function(base, from, to) {
this.base = base;
this.from = from;
this.to = to;
}, function(t) {
return this.base.getPoint((1 - t) * this.from + t * this.to);
});
You define a new type of curve which just selects a segment out of a given curve. Usage:
var curve = new CurveSection(yourCurve, 0, .76); // Where .76 is your percentage
Now you can build a new tube.
Solution 2 (Mathematics!):
You are using for your arc a quadratic bezier curve, that's awesome! This curve is a parabola. You want just a segment of that parabola and that is again a parabola, just with other bounds.
What we need is a section of the bezier curve. Let's say the curve is defined by A (start), B (direction), C (end). If we want to change the start to a point D and the end to a point F we need the point E that is the direction of the curve in D and F. So the tangents to our parabola in D and F have to intersect in E. So the following code will give us the desired result:
// Calculates the instersection point of Line3 l1 and Line3 l2.
function intersection(l1, l2) {
var A = l1.start;
var P = l2.closestPointToPoint(A);
var Q = l1.closestPointToPoint(P);
var l = P.distanceToSquared(A) / Q.distanceTo(A);
var d = (new THREE.Vector3()).subVectors(Q, A);
return d.multiplyScalar(l / d.length()).add(A);
}
// Calculate the tangentVector of the bezier-curve
function tangentQuadraticBezier(bezier, t) {
var s = bezier.v0,
m = bezier.v1,
e = bezier.v2;
return new THREE.Vector3(
THREE.CurveUtils.tangentQuadraticBezier(t, s.x, m.x, e.x),
THREE.CurveUtils.tangentQuadraticBezier(t, s.y, m.y, e.y),
THREE.CurveUtils.tangentQuadraticBezier(t, s.z, m.z, e.z)
);
}
// Returns a new QuadraticBezierCurve3 with the new bounds.
function sectionInQuadraticBezier(bezier, from, to) {
var s = bezier.v0,
m = bezier.v1,
e = bezier.v2;
var ns = bezier.getPoint(from),
ne = bezier.getPoint(to);
var nm = intersection(
new THREE.Line3(ns, tangentQuadraticBezier(bezier, from).add(ns)),
new THREE.Line3(ne, tangentQuadraticBezier(bezier, to).add(ne))
);
return new THREE.QuadraticBezierCurve3(ns, nm, ne);
}
This is a very mathematical way, but if you should need the special properties of a Bezier curve, this is the way to go.
Note: The first solution is the simplest. I am not familiar with Three.js so I wouldn't know what the most efficient way to implement the animation is. Three.js doesn't seem to use the special properties of a bezier curve so maybe solution 2 isn't that useful.
I hope you have gotten something useful out of this.

Categories

Resources