Disposing of elements in a ring - three.js

I'm trying to create a ring of spheres on Three.js, with a control for the number of spheres in the ring. Every time I change the control and run the function, I would like to dispose the previous ring so that it can be replaced by the new one. However, while the new ring appears, only one sphere seems to be getting disposed. The solution seems simple but I can't understand what's going wrong:
const params = {}
params.num = 10
let geometry = null
let material = null
let sphere = null
function generateMandala() {
//Destroy Materials. This is the problem: only a single sphere is getting destroyed.
if (sphere !== null) {
geometry.dispose()
material.dispose()
scene.remove(sphere)
}
geometry = new THREE.SphereGeometry(0.25, 32, 32)
material = new THREE.MeshBasicMaterial()
//Create Ring
let radius = 2
let angle = (Math.PI * 2) / params.num
for (let i = 0; i < params.num; i++) {
let x = Math.sin(i * angle) * radius
let y = Math.cos(i * angle) * radius
sphere = new THREE.Mesh(geometry, material)
sphere.position.set(x, y, 0)
scene.add(sphere)
}
}
generateMandala()
gui.add(params, 'num').min(1).max(50).onFinishChange(generateMandala)
Thanks in advance!

Solved! By adding into a group, then disposing of the entire group :)
let geometry = null
let material = null
let sphere = null
let group = null
function generateMandala() {
if (sphere !== null) {
geometry.dispose()
material.dispose()
scene.remove(group)
}
geometry = new THREE.SphereGeometry(0.25, 32, 32)
material = new THREE.MeshBasicMaterial({
color: new THREE.Color(1, random(0, 1), random(0, 1))
})
group = new THREE.Group();
let radius = 2
let angle = (Math.PI * 2) / params.num
for (let i = 0; i < params.num; i++) {
let x = Math.sin(i * angle) * radius
let y = Math.cos(i * angle) * radius
sphere = new THREE.Mesh(geometry, material)
sphere.position.set(x, y, 0)
group.add(sphere)
}
scene.add(group)
}
generateMandala()

Related

Three.js: Rotate object with lookAt() while located at the current lookAt() position

I'm trying to implement a simple turn-around-and-move feature with Three.js. On mouse click, the object is supposed to first turn around and then move to the clicked location.
Codepen
The rotation is achieved with raycasting and lookAt(). It works by itself and it always works on the first click. If you remove the translation, it works continuously. The issue occurs when rotation and translation are implemented together. If you click a second time, after the object has moved to the previous clicked location, it doesn't rotate as expected. Depending on the mouse location it can flip to the other side without rotating at all.
Clarification: When you click the first time, notice how the object slowly and steadily turns around to face that direction? But the second time, after the object has moved, the rotation is quicker and/or flimsier or it simply flips over and there is no rotation at all. It depends on where you click in relation to the object.
I believe the issue stems from trying to implement lookAt while being located at the current lookAt location? If I stop the translation half way, the next rotation will work better. But of course I need it to go all the way.
I'm somewhat lost on how to proceed with this issue. Any help would be appreciated.
/*** Setup scene ***/
let width = 800
let height = 600
let scene
let renderer
let worldAxis
let box
let angle
let boxAxes
scene = new THREE.Scene()
worldAxis = new THREE.AxesHelper(200);
scene.add(worldAxis);
// Setup renderer
renderer = new THREE.WebGLRenderer({alpha: true, antialias: true})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
document.body.appendChild(renderer.domElement)
// Setup camera
const camera = new THREE.OrthographicCamera(
width / - 2, // left
width / 2, // right
height / 2, // top
height / - 2, // bottom
0, // near
1000 ); // far
camera.position.set(0, 0, 500)
camera.updateProjectionMatrix()
// Setup box
let geometry = new THREE.BoxGeometry( 15, 15, 15 );
let material = new THREE.MeshBasicMaterial( { color: "grey" } );
box = new THREE.Mesh( geometry, material );
box.position.set(100, 150, 0)
box.lookAt(getPointOfIntersection(new THREE.Vector2(0, 0)))
addAngle()
boxAxes = new THREE.AxesHelper(50);
box.add(boxAxes)
scene.add(box)
renderer.render(scene, camera);
/*** Setup animation ***/
let animate = false
let currentlyObservedPoint = new THREE.Vector2();
let rotationIncrement = {}
let translationIncrement = {}
let frameCount = 0
document.addEventListener('click', (event) => {
let mousePosForRotate = getMousePos(event.clientX, event.clientY)
rotationIncrement.x = (mousePosForRotate.x - currentlyObservedPoint.x)/100
rotationIncrement.y = (mousePosForRotate.y - currentlyObservedPoint.y)/100
let mousePosForTranslate = getMousePosForTranslate(event)
translationIncrement.x = (mousePosForTranslate.x - box.position.x)/100
translationIncrement.y = (mousePosForTranslate.y - box.position.y)/100
animate = true
})
function animationLoop() {
if (animate === true) {
if (frameCount < 100) {
rotate()
} else if (frameCount < 200) {
translate()
} else {
animate = false
frameCount = 0
}
frameCount++
renderer.render(scene, camera)
}
requestAnimationFrame(animationLoop)
}
function rotate() {
currentlyObservedPoint.x += rotationIncrement.x
currentlyObservedPoint.y += rotationIncrement.y
let pointOfIntersection = getPointOfIntersection(currentlyObservedPoint)
box.lookAt(pointOfIntersection)
addAngle()
}
function translate() {
box.position.x += translationIncrement.x
box.position.y += translationIncrement.y
}
function getMousePos(x, y) {
let mousePos = new THREE.Vector3(
(x / width) * 2 - 1,
- (y / height) * 2 + 1,
0)
return mousePos
}
function getMousePosForTranslate(event) {
let rect = event.target.getBoundingClientRect();
let mousePos = { x: event.clientX - rect.top, y: event.clientY - rect.left }
let vec = getMousePos(mousePos.x, mousePos.y)
vec.unproject(camera);
vec.sub(camera.position).normalize();
let distance = - camera.position.z / vec.z;
let pos = new THREE.Vector3(0, 0, 0);
pos.copy(camera.position).add(vec.multiplyScalar(distance));
return pos
}
function getPointOfIntersection(mousePos) {
let plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
let pointOfIntersection = new THREE.Vector3()
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mousePos, camera)
raycaster.ray.intersectPlane(plane, pointOfIntersection)
return pointOfIntersection
}
function addAngle() {
let angle = box.rotation.x - 32
box.rotation.x = angle
}
animationLoop()
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/105/three.min.js'></script>

How to make Two lines coincident in ThreeJS?

I have two lines that will be created by the user at runtime, so the positions of these two lines are dynamic. I want one line to coincide with another line as shown in the below image.
How should I proceed
Here is my code
var camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 15;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("app").appendChild(renderer.domElement);
let point1 = new THREE.Vector3(14, 25, -159);
let point2 = new THREE.Vector3(-5, 2, 65);
let rightLine = createLine(point1, point2);
let point1LineTwo = new THREE.Vector3(-45, 11, -4);
let point2LineTwo = new THREE.Vector3(-26, -8, -30);
let leftLine = createLine(point1LineTwo, point2LineTwo);
function createLine(point1, point2) {
const linePoints = [];
linePoints.push(new THREE.Vector3(point1.x, point1.y, point1.z));
linePoints.push(new THREE.Vector3(point2.x, point2.y, point2.z));
let lineGeometry = new THREE.BufferGeometry().setFromPoints(linePoints);
var lineMaterial = new THREE.LineBasicMaterial({
color: 0xff5555,
linewidth: 2,
});
let line = new THREE.Line(lineGeometry, lineMaterial);
scene.add(line);
return line;
}
function makeCoincident() {
let rightLineVector = new THREE.Vector3();
const positions = rightLine.geometry.attributes.position.array;
rightLineVector.x = positions[3] - positions[0];
rightLineVector.y = positions[4] - positions[1];
rightLineVector.z = positions[5] - positions[2];
let leftLineVector = new THREE.Vector3();
const lineLeftPosition = leftLine.geometry.attributes.position.array;
leftLineVector.x = lineLeftPosition[3] - lineLeftPosition[0];
leftLineVector.y = lineLeftPosition[4] - lineLeftPosition[1];
leftLineVector.z = lineLeftPosition[5] - lineLeftPosition[2];
//Calulate angle Between leftLineVector and rightLineVector
let angle = leftLineVector.clone().angleTo(rightLineVector);
//calculate cross prduct of lineOneVector and lineTwoVector
let crossPoductVector = new THREE.Vector3();
crossPoductVector.crossVectors(leftLineVector, rightLineVector);
crossPoductVector.normalize();
rightLineVector.applyAxisAngle(crossPoductVector.clone(), angle);
//align right line on left line
var axis = new THREE.Vector3(0, 1, 0);
rightLine.quaternion.setFromUnitVectors(
axis,
rightLineVector.clone().normalize()
);
}
window.addEventListener("keydown", function (event) {
switch (event.keyCode) {
case 81: // Q
makeCoincident();
break;
default:
}
});
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
I created a sandbox [ Link to code sandbox where I created the same problem without dynamic line creation.
I have two lines with random position. one function named makeCoincident() which makes them coincident which gets called when you press Q key. I tried to solve it but it is not working if you can look into sandbox and tell me where I am going wrong and what is the solution It will be big help thanks
The code you gave is confusing because of the right and left naming. I rewrote the makeCoincident function
https://codepen.io/cdeep/pen/dydQKoV
To summarize briefly, I first move the vertices of the green line to make the center of the green line intersect with the center of the red. And then, set the vertices of green to a point on the extension of red corresponding to the length of green line.
There is infact, no need to move the green line to make intersecting centres. It's just for clarity incase there's a requirement to make them intersecting without the need for coinciding. Can be omitted for the current question.
function makeCoincident() {
const greenLinePositions = rightLine.geometry.attributes.position.array;
const redLinePositions = leftLine.geometry.attributes.position.array;
const greenLineCenter = new THREE.Vector3(
(greenLinePositions[3] + greenLinePositions[0]) * 0.5,
(greenLinePositions[4] + greenLinePositions[1]) * 0.5,
(greenLinePositions[5] + greenLinePositions[2]) * 0.5,
);
const redLineCenter = new THREE.Vector3(
(redLinePositions[3] + redLinePositions[0]) * 0.5,
(redLinePositions[4] + redLinePositions[1]) * 0.5,
(redLinePositions[5] + redLinePositions[2]) * 0.5,
);
const translationVector = redLineCenter.clone().sub(greenLineCenter);
// Vector pointing from center of green to center of red
const translationArray = translationVector.toArray();
for(let i = 0; i < 6; i++) {
greenLinePositions[i] = greenLinePositions[i] + translationArray[i % 3];
// Move vertices of green line towards red
}
rightLine.geometry.attributes.position.needsUpdate = true;
// Centres of red and green now intersect
const greenLineLength = new THREE.Vector3(
greenLinePositions[0] - greenLinePositions[3],
greenLinePositions[1] - greenLinePositions[4],
greenLinePositions[2] - greenLinePositions[5],
).length();
const redLineDirection = new THREE.Vector3(
redLinePositions[0] - redLinePositions[3],
redLinePositions[1] - redLinePositions[4],
redLinePositions[2] - redLinePositions[5],
).normalize();
// Get new positions of green on the extension of red
const greenPoint1 = redLineCenter.clone().add(redLineDirection.clone().multiplyScalar(greenLineLength * 0.5));
const greenPoint2 = redLineCenter.clone().add(redLineDirection.clone().multiplyScalar(greenLineLength * -0.5));
// Set the attribute values from the new position vectors
greenLinePositions[0] = greenPoint1.x;
greenLinePositions[1] = greenPoint1.y;
greenLinePositions[2] = greenPoint1.z;
greenLinePositions[3] = greenPoint2.x;
greenLinePositions[4] = greenPoint2.y;
greenLinePositions[5] = greenPoint2.z;
rightLine.geometry.attributes.position.needsUpdate = true;
}

Where do i place the sin() formula for plane geometry to create a three.js particle wave?

pictureim trying to create a particle wave effect in three.js , losing my mind as i tried different methods. anyone have an idea what i could do? my code is down below.
// geomatry, material, mesh
const geometry = new THREE.PlaneBufferGeometry(5, 5, 64, 64)
const material = new THREE.PointsMaterial({color: 'aqua',
size: 0.007})
const particles = new THREE.Points(geometry, material);
particles.rotation.x = 181;
scene.add(particles);
// Lighting
const light = new THREE.PointLight(0xffffff, 2)
light.position.set(2, 3, 4);
scene.add(light);
const render = function() {
requestAnimationFrame(render)
// particles.rotation.z += .005
renderer.render(scene,camera);
}
const positions = geometry.attributes.position.array;
let k = 0;
setInterval(() => {
for (let i = 0; i < positions.length; i += 3) {
positions[i + 2] = Math.sin(((i+2)%(65*3)) / 20+k) * 0.5;
}
k+=0.1;
geometry.attributes.position.needsUpdate = true;
}, 60);

Three.JS - Particles orbiting a point in random directions forming a sphere

I have a particle system where all the particles are positioned at the same coordinates and one after another, in random directions, they (should) start orbiting the center of the scene forming a sphere.
What I managed to achieve until now is a group of Vector3 objects (the particles) that one after another start orbiting the center along the Z axis simply calculating their sine and cosine based on the current angle.
I'm not that good at math and I don't even know what to look for precisely.
Here's what I wrote:
var scene = new THREE.Scene();
let container = document.getElementById('container'),
loader = new THREE.TextureLoader(),
renderer,
camera,
maxParticles = 5000,
particlesDelay = 50,
radius = 50,
sphereGeometry,
sphere;
loader.crossOrigin = true;
function init() {
let vw = window.innerWidth,
vh = window.innerHeight;
renderer = new THREE.WebGLRenderer();
renderer.setSize(vw, vh);
renderer.setPixelRatio(window.devicePixelRatio);
camera = new THREE.PerspectiveCamera(45, vw / vh, 1, 1000);
camera.position.z = 200;
camera.position.x = 30;
camera.position.y = 30;
camera.lookAt(scene.position);
scene.add(camera);
let controls = new THREE.OrbitControls(camera, renderer.domElement);
let axisHelper = new THREE.AxisHelper(50);
scene.add(axisHelper);
container.appendChild(renderer.domElement);
window.addEventListener('resize', onResize, false);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function draw() {
sphereGeometry = new THREE.Geometry();
sphereGeometry.dynamic = true;
let particleTexture = loader.load('https://threejs.org/examples/textures/particle2.png'),
material = new THREE.PointsMaterial({
color: 0xffffff,
size: 3,
transparent: true,
blending: THREE.AdditiveBlending,
map: particleTexture,
depthWrite: false
});
for ( let i = 0; i < maxParticles; i++ ) {
let vertex = new THREE.Vector3(radius, 0, 0);
vertex.delay = Date.now() + (particlesDelay * i);
vertex.angle = 0;
sphereGeometry.vertices.push(vertex);
}
sphere = new THREE.Points(sphereGeometry, material);
scene.add(sphere);
}
function update() {
for ( let i = 0; i < maxParticles; i++ ) {
let particle = sphereGeometry.vertices[i];
if ( Date.now() > particle.delay ) {
let angle = particle.angle += 0.01;
particle.x = radius * Math.cos(angle);
if ( i % 2 === 0 ) {
particle.y = radius * Math.sin(angle);
} else {
particle.y = -radius * Math.sin(angle);
}
}
}
sphere.geometry.verticesNeedUpdate = true;
}
function render() {
update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
init();
draw();
render();
And here's the JSFiddle if you want to see it live:
https://jsfiddle.net/kekkorider/qs6s0wv2/
EDIT: Working example
Can someone please give me a hand?
Thanks in advance!
You want each particle to rotate around a specific random axis. You can either let them follow a parametric equation of a circle in 3D space, or you can make use of THREE.js rotation matrices.
Right now all your particles are rotating round the vector (0, 0, 1). Since your particles start off on the x-axis, you want them all to rotate around a random vector in the y-z plane (0, y, z). This can be defined during the creation of the vertices:
vertex.rotationAxis = new THREE.Vector3(0, Math.random() * 2 - 1, Math.random() * 2 - 1);
vertex.rotationAxis.normalize();
now you can just call the THREE.Vector3.applyAxisAngle(axis, angle) method on each of your particles with the random rotation axis you created each update:
particle.applyAxisAngle(particle.rotationAxis, 0.01);
To sum up, this is how it should look like:
draw():
...
for ( let i = 0; i < maxParticles; i++ ) {
let vertex = new THREE.Vector3(radius, 0, 0);
vertex.delay = Date.now() + (particlesDelay * i);
vertex.rotationAxis = new THREE.Vector3(0, Math.random() * 2 - 1, Math.random() * 2 - 1);
vertex.rotationAxis.normalize();
sphereGeometry.vertices.push(vertex);
}
...
update():
...
for ( let i = 0; i < maxParticles; i++ ) {
let particle = sphereGeometry.vertices[i];
if ( Date.now() > particle.delay ) {
particle.applyAxisAngle(particle.rotationAxis, 0.01);
}
}
...

ThreeJS - how to pick just one type of objects?

I'm new to ThreeJS and I have an issue with picking objects by raycasting. I have created some spheres and some lines but only want to change the spheres on mouseover. I think I need to add some condition in the raycast code but I have no idea what...
Here's my code, hope anyone can help:
This creates the objects:
var numSpheres = 10;
var angRand = [numSpheres];
var spread = 10;
var radius = windowY/5;
var radiusControl = 20;
//sphere
var sphereGeometry = new THREE.SphereGeometry(0.35, 100, 100);
//line
var lineGeometry = new THREE.Geometry();
var lineMaterial = new THREE.LineBasicMaterial({
color: 0xCCCCCC
});
//create dynamically
for (var i = 0; i < numSpheres; i++) {
var sphereMaterial = new THREE.MeshBasicMaterial({color: 0x334455});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
var line = new THREE.Line(lineGeometry, lineMaterial);
angRand[i] = Math.floor((Math.random() * 360) + 1);//random angle for each sphere/line
var radiusIncr = spread * (angRand[i]+200)/180;
var xPos = Math.cos((360/numSpheres * (i) + angRand[i]/2 )) * (radius - radiusIncr);
var yPos = Math.sin((360/numSpheres * (i) + angRand[i]/2 )) * (radius - radiusIncr);
var offsetY = Math.floor((Math.random()*5)+1);
sphere.position.x = xPos/radiusControl;
sphere.position.y = yPos/radiusControl + offsetY;
lineGeometry.vertices.push(
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(sphere.position.x, sphere.position.y, 0)
);
scene.add(sphere);
scene.add(line);
}
And this is my raycast:
var mouse = {
x: 0,
y: 0
},
INTERSECTED;
window.addEventListener('mousemove', onMouseMove, false);
window.requestAnimationFrame(render);
function onMouseMove(event) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
//event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
//console.log(mouse.x + " | " + mouse.y);
}
function mousePos() {
// find intersections
// create a Ray with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject(camera);
var ray = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
ray.linePrecision = 1;
// create an array containing all objects in the scene with which the ray intersects
var intersects = ray.intersectObjects(scene.children, true);
//console.log(intersects.length);
// INTERSECTED = the object in the scene currently closest to the camera
// and intersected by the Ray projected from the mouse position
// if there is one (or more) intersections
if (intersects.length > 0) {
// if the closest object intersected is not the currently stored intersection object
if (intersects[0].object != INTERSECTED) {
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// store reference to closest object as current intersection object
INTERSECTED = intersects[0].object;
// store color of closest object (for later restoration)
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// set a new color for closest object
INTERSECTED.material.color.setHex(0xEE7F00);
//INTERSECTED.radius.set( 1, 2, 2 );
}
} else // there are no intersections
{
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
//INTERSECTED.scale.set( 1, 1, 1 );
// remove previous intersection object reference
// by setting current intersection object to "nothing"
INTERSECTED = null;
}
}
The raycast returns an intersect array of objects which itself contains information about what the ray hit.
Since you only have spheres and lines you can branch on the geometry type intersects[0].object.geometry.type which would be either 'LineGeometry' or 'SphereGeometry'.
Edit: Obligatory jsfiddle, see console for hit output.
http://jsfiddle.net/z43hjqm9/1/
To simplify working with the mouse, you can use the class EventsControls. Try to make through this example.
<script src="js/controls/EventsControls.js"></script>
EventsControls = new EventsControls( camera, renderer.domElement );
EventsControls.attachEvent('mouseOver', function() {
this.container.style.cursor = 'pointer';
this.mouseOvered.material = selMaterial;
...
});
EventsControls.attachEvent('mouseOut', function() {
this.container.style.cursor = 'auto';
this.mouseOvered.material = autoMaterial;
...
});
//
function render() {
EventsControls.update();
controls.update();
renderer.render(scene, camera);
}
In your code,
var intersects = ray.intersectObjects(scene.children, true);
the first parameter to the call is an object that will be evaluated to see if it, or any of its descendants (recursive is true) intersect the ray.
So, simply create an object target and add the spheres to it (but not the lines).
This will make your call also more effective
1.use different arrays to place different objects
a.for all objectType1,after scene.add(objectType1)-> do array1.push(objectType1)
b.for all objectType 2,after scene.add(objectType2)-> do array2.push(objectType2)
now whichever type of objects you want to interact, pass that array in intersect as-
var intersects = raycaster.intersectObjects( arrayType1,true);
now only the arrayType1 objects will interact.

Resources