Moving individual particles within a Three.JS particle system - javascript

Running into a bit of a problem with Three.js. I'm currently trying to move particles within a particle system when each frame is rendered. No errors are being reported, but nothing is moving either! The example I took from the code at http://aerotwist.com/tutorials/creating-particles-with-three-js/ uses the syntax particle.position.y, but the JS console returns Cannot set property 'y' of undefined when I change the below code to mirror that. Any help or pointers on where I'm going wrong is much appreciated.
Full source code:
var scene, camera, renderer, particleCount = 0, particleSystem, particles;
init();
animate();
function init()
{
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
scene.add(camera);
camera.position.z = 5;
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
particleCount = 1800,
particles = new THREE.Geometry();
var pMaterial = new THREE.ParticleBasicMaterial({color: 0xFFFFFF, size: 0.5});
for (var i = 0; i < particleCount; i++)
{
var pX = Math.random() * 500 - 250,
pY = Math.random() * 500 - 250,
pZ = Math.random() * 500 - 250,
particle = new THREE.Vector3(pX, pY, pZ);
particles.vertices.push(particle);
}
particleSystem = new THREE.ParticleSystem(particles, pMaterial);
scene.add(particleSystem);
}
function animate()
{
requestAnimationFrame(animate);
renderer.render(scene, camera);
var pCount = particleCount;
while (pCount--)
{
var particle = particles.vertices[pCount];
particle.y = Math.random() * 500 - 250;
particleSystem.geometry.vertices.needsUpdate = true;
}
}

It seems like you have to add particleSystem.sortParticles = true; after creating your particleSystem.

I encountered exactly the same issue in the same tutorial but, using ThreeJS 86, I needed neither the setY() nor the particleSystem.sortParticles = true; but particleSystem.geometry.verticesNeedUpdate = true; (not particleSystem.geometry.vertices.needsUpdate = true;).

Related

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'length') for vertices of three.js

So I'm converting an html project to a laravel/vue project.
The html file had a three.min.js file which I can not figure out the version of. And some custom script files.
I've imported all of them for my vue component.
I ran into different errors for the three.min.js file, so I npm installed version r69 of three.js becauseI read in recent versions, the geometry had changed.
One of the custom files, test.js is using three.js.
And I am running into the following error in my console.log
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'length')
at test.js?t=1674045010699:37:39
test.js file code:
import * as THREE from "three";
// import ('../../assets/js/three.min.js')
// import "../js/anime.js"
// import "../js/three.js"
var canvas = document.querySelector("#scene");
var width = canvas.offsetWidth,
height = canvas.offsetHeight;
var renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
});
renderer.setPixelRatio(window.devicePixelRatio > 1 ? 2 : 1);
renderer.setSize(width, height);
renderer.setClearColor(0x161d31);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(100, width / height, 0.1, 10000);
camera.position.set(120, 0, 300);
var light = new THREE.HemisphereLight(0xffffff, 0x0c056d, 0.6);
scene.add(light);
var light = new THREE.DirectionalLight(0x590d82, 0.5);
light.position.set(100, 300, 400);
scene.add(light);
var light2 = light.clone();
light2.position.set(-100, 300, 400);
scene.add(light2);
var material = new THREE.MeshPhongMaterial({
emissive: 0x23f660,
emissiveIntensity: 0.4,
shininess: 0,
});
var geometry = new THREE.IcosahedronGeometry(120, 4);
for (var i = 0; i < geometry.vertices.length; i++) {
var vector = geometry.vertices[i];
vector._o = vector.clone();
}
var shape = new THREE.Mesh(geometry, material);
scene.add(shape);
function updateVertices(a) {
for (var i = 0; i < geometry.vertices.length; i++) {
var vector = geometry.vertices[i];
vector.copy(vector._o);
var perlin = noise.simplex3(
vector.x * 0.006 + a * 0.0002,
vector.y * 0.006 + a * 0.0003,
vector.z * 0.006
);
var ratio = perlin * 0.4 * (mouse.y + 0.3) + 0.9;
vector.multiplyScalar(ratio);
}
geometry.verticesNeedUpdate = true;
}
function render(a) {
requestAnimationFrame(render);
updateVertices(a);
renderer.render(scene, camera);
}
function onResize() {
canvas.style.width = "";
canvas.style.height = "";
width = canvas.offsetWidth;
height = canvas.offsetHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
var mouse = new THREE.Vector2(0.8, 0.5);
function onMouseMove(e) {
TweenMax.to(mouse, 0.8, {
y: e.clientY / height,
x: e.clientX / width,
ease: Power1.easeOut,
});
}
requestAnimationFrame(render);
window.addEventListener("mousemove", onMouseMove);
var resizeTm;
window.addEventListener("resize", function () {
resizeTm = clearTimeout(resizeTm);
resizeTm = setTimeout(onResize, 200);
});
I don't know anything about three.js, my main task here is to convert the entire project into vue/laravel so I have other things to worry about.
Does anyone know how to resolve this?
I've migrated the code to a more recent version of three.js. The code now uses BufferGeometry (the new geometry format) so the original runtime error is gone.
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x161d31);
const camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(120, 0, 300);
const light = new THREE.HemisphereLight(0xffffff, 0x0c056d, 0.6);
scene.add(light);
const light1 = new THREE.DirectionalLight(0x590d82, 0.5);
light1.position.set(100, 300, 400);
scene.add(light1);
const light2 = light1.clone();
light2.position.set(-100, 300, 400);
scene.add(light2);
const material = new THREE.MeshLambertMaterial({
emissive: 0x23f660,
emissiveIntensity: 0.4
});
const geometry = new THREE.IcosahedronGeometry(120, 12);
const positionAttributeBase = geometry.getAttribute( 'position' ).clone();
const shape = new THREE.Mesh(geometry, material);
scene.add(shape);
const simplex = new THREE.SimplexNoise();
const vector = new THREE.Vector3();
function updateVertices(time) {
const positionAttribute = geometry.getAttribute('position');
for (let i = 0; i < positionAttributeBase.count; i++) {
vector.fromBufferAttribute(positionAttributeBase, i);
const noise = simplex.noise3d(
vector.x * 0.006 + time * 0.0002,
vector.y * 0.006 + time * 0.0003,
vector.z * 0.006
);
const ratio = noise * 0.4 + 0.9;
vector.multiplyScalar(ratio);
positionAttribute.setXYZ(i, vector.x, vector.y, vector.z)
}
positionAttribute.needsUpdate = true;
}
function animate(time=0) {
requestAnimationFrame(animate);
updateVertices(time);
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
animate();
body {
margin: 0px;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.147/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.147/examples/js/math/SimplexNoise.js"></script>

Uncaught TypeError: Cannot read properties of undefined (reading 'render')

I was practicing to make some animations with ThreeJS but it says "Uncaught TypeError: Cannot read properties of undefined (reading 'render')".
I have no clue about this error. This is my first time using ThreeJS, so I don't know much about it.
<body>
<script src="main.js" type="module" defer></script>
<script type="module">
import * as THREE from 'https://cdn.skypack.dev/three#0.124.0/build/three.module.js';
let scene, camera, renderer, starGeo, stars;
function init() {
//create scene object
const scene = new THREE.Scene();
//setup camera with facing upward
const camera = new THREE.PerspectiveCamera(60,window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 1;
camera.rotation.x = Math.PI/2;
//setup renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
starGeo = new THREE.Geometry();
for(let i=0;i<6000;i++) {
let star = new THREE.Vector3(
Math.random() * 600 - 300,
Math.random() * 600 - 300,
Math.random() * 600 - 300
);
star.velocity = 0;
star.acceleration = 0.02;
starGeo.vertices.push(star);
}
let sprite = new THREE.TextureLoader().load( 'star.png' );
let starMaterial = new THREE.PointsMaterial({
color: 0xaaaaaa,
size: 0.7,
map: sprite
});
stars = new THREE.Points(starGeo,starMaterial);
scene.add(stars);
animate();
}
//rendering loop
function animate() {
starGeo.vertices.forEach(p => {
p.velocity += p.acceleration
p.y -= p.velocity;
if (p.y < -200) {
p.y = 200;
p.velocity = 0;
}
});
starGeo.verticesNeedUpdate = true;
stars.rotation.y +=0.002;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
init();
</script>
Thanks!
You need to change this line:
const renderer = new THREE.WebGLRenderer();
To be:
renderer = new THREE.WebGLRenderer();
The problem was that you never assigned a value to the let renderer at the higher scope and had another constant with the same name at the init() function scope which is not available in animate() function scope.

THREE.js Random multidirectional orbits around point using trig

I'm trying to rotate multiple objects around the same Vector3 point but in different directions - so they effectively 'swarm' around the point.
I'm using the trig approach rather simply wrapping each object in a Container and applying some random rotations as I'm projecting their 3D vector to 2D positions to attach label DIVs above the canvas, and the container approach messes with with project class.
Here's my current code which makes all objects rotate around the point along the same orbit path:
for(var i = 0; i<objectsArr.length; i++){
var obj = objectsArr[i];
var radius = obj.angle * (Math.PI / 180);
obj.position.x = obj.radius * Math.cos(radius);
obj.position.y = obj.radius * Math.sin(radius);
obj.angle += obj.orbitSpeed;
}
Does anyone know how I can make them orbit in random directions along X, Y, Z axis?
First, see this answer about rotating objects about a point.
From your code, it looks like you're storing each object's orbit speed, and their current angle of rotation (which represents the vector to the position). Rather than storing a 2D angle, store it as a Vector3 which represents the normal of (perpendicular to) the object's orbital plane. (This will let you really get a "swarm" going later, when you can define different orbital planes.) I also recommend storing the orbit speed in radians per step, so you don't have to perform the conversion every pass.
The rest of problem actually becomes very simple with the Vector3.applyAxisAngle method.
Some pseudo-code:
Subtract the rotation point position from the object's position.
Use the object's orbit speed, and angle to update the temp position.
Add the rotation point position back to the object's position.
To see it in your code:
var obj;
for(var i = 0; i< objectsArr.length; i++){
obj = objectsArr[i];
obj.position.sub(rotationPoint); // rotationPoint is a Vector3
obj.position.applyAxisAngle(obj.angle, obj.orbitSpeed);
obj.add(rotationPoint);
}
And here's a live demo of a few objects orbiting randomly about a "nucleus" at (10, 10, 10).
var renderer, scene, camera, controls, stats, nucleus;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 60,
NEAR = 1,
FAR = 1000;
var electrons = [],
numElectrons = 100; // more electrons = slower updating
function populateScene() {
var geo = new THREE.SphereBufferGeometry(10, 16, 16);
var mat = new THREE.MeshPhongMaterial({color:"blue"});
nucleus = new THREE.Mesh(geo, mat);
nucleus.position.set(10, 10, 10); // you can change these values
scene.add(nucleus);
var electron = null,
plane = new THREE.Plane(),
point = new THREE.Vector3();
geo = new THREE.SphereBufferGeometry(1, 16, 16);
mat = new THREE.MeshPhongMaterial({color:"red"});
for(var i = 0; i < numElectrons; ++i){
electron = new THREE.Mesh(geo, mat);
electrons.push(electron);
electron.angle = new THREE.Vector3(
Math.random(),
Math.random(),
Math.random()
).normalize();
electron.orbitSpeed = (Math.random() * 0.05) + 0.05;
if(Math.random() > 0.5) electron.orbitSpeed *= -1;
plane.normal.copy(electron.angle);
point.set(Math.random(), Math.random(), Math.random());
plane.projectPoint(point, electron.position);
electron.position.setLength(Math.floor(Math.random() * 20) + 15);
electron.position.applyAxisAngle(electron.angle, Math.random() / 10);
electron.position.add(nucleus.position);
scene.add(electron);
}
}
function updateElectrons(){
var obj = null;
for(var i = 0; i < numElectrons; ++i){
obj = electrons[i]
obj.position.sub(nucleus.position);
obj.position.applyAxisAngle(obj.angle, obj.orbitSpeed);
obj.position.add(nucleus.position);
}
}
function init() {
document.body.style.backgroundColor = "slateGray";
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
document.body.style.overflow = "hidden";
document.body.style.margin = "0";
document.body.style.padding = "0";
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 100;
scene.add(camera);
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.dynamicDampingFactor = 0.5;
controls.rotateSpeed = 3;
var light = new THREE.PointLight(0xffffff, 1, Infinity);
camera.add(light);
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);
resize();
window.onresize = resize;
populateScene();
animate();
}
function resize() {
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
if (renderer && camera && controls) {
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
controls.handleResize();
}
}
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
updateElectrons();
render();
controls.update();
stats.update();
}
function threeReady() {
init();
}
(function() {
function addScript(url, callback) {
callback = callback || function() {};
var script = document.createElement("script");
script.addEventListener("load", callback);
script.setAttribute("src", url);
document.head.appendChild(script);
}
addScript("https://threejs.org/build/three.js", function() {
addScript("https://threejs.org/examples/js/controls/TrackballControls.js", function() {
addScript("https://threejs.org/examples/js/libs/stats.min.js", function() {
threeReady();
})
})
})
})();
three.js r86

FirstPersonMovement in Three.js

there!
Greeting to all of you :-)
Im sorry if my question not interesting one. I start to code not long ago, so i think that my problem is somewhat primitive.
I made my 3d city with help of Three.js and Isaac Sukin book named "Game Development with Three.js".
It was an easy part.
But when i tried to add movement options i wasnt able to success.
Here is code, that work:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: #ffffff;
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r57/three.min.js">
</script>
<script src="FirstPersonControls.js"></script>
<script>
var camera, scene, renderer, light;
var clock, controls;
function setup() {
document.body.style.backgroundColor = '#00BFFF';
setupThreeJS();
setupWorld();
requestAnimationFrame(function animate() {
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
renderer.render(scene, camera);
controls.update(clock.getDelta());
requestAnimationFrame(animate);
});
}
function setupThreeJS() {
scene = new THREE.Scene();
clock = new THREE.Clock();
controls = new THREE.FirstPersonControls(camera);
controls.movementSpeed = 100;
controls.lookSpeed = 0.1;
var light = new THREE.DirectionalLight( 0xf6e86d, 1 );
light.position.set( 1, 3, 2 );
scene.add( light );
scene.fog = new THREE.FogExp2 (0x9db3b5, 0.002);
light.castShadow = true;
light.shadowDarkness = 0.7;
light.shadowMapWidth = 2048;
light.shadowMapHeight = 2048;
light.position.set(500, 1500, 1000);
light.shadowCameraFar = 2500;
light.shadowCameraLeft = -1000;
light.shadowCameraRight = 1000;
light.shadowCameraTop = 1000;
light.shadowCameraBottom = -1000;
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.y = 410;
camera.position.z = 400;
camera.position.x = -55 * Math.PI / 180; // set camera position at 45 degrees down
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function setupWorld() {
// its floor
var geo = new THREE.PlaneGeometry(2500, 2500, 20, 20); // first 2 parametres its size of the plane, other 2 - split the plane into 20 x 20 grid
var mat = new THREE.MeshBasicMaterial({color: 0x228b22, overdraw: true});
var floor = new THREE.Mesh(geo, mat);
floor.receiveShadow = true;
floor.rotation.x = -90 * Math.PI / 180; // we set our plane at 90 degrees, so its lay directly under our building
scene.add(floor); // i cant understand. at first author tell that you should use mesg, but later, that you should use floor. ok, we'll see.
// building
var geometry = new THREE.CubeGeometry(1, 1, 1); // generate cube as basis for our builldings
geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0, 0.5, 0)); // set geometry origins, the point around which the geometry is scaled and rotated
var material = new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('images/house.jpg'), overdraw: true}); // create material for our cube
//cloned buildings
var cityGeometry = new THREE.Geometry();
for (var i = 0; i < 300; i++) {
var building = new THREE.Mesh(geometry.clone()); // create all our buildings. clone method allow us to create objects as clones
building.position.x = Math.floor(Math.random() * 200 - 100) * 4;
building.position.z = Math.floor(Math.random() * 200 - 100) * 4;
building.scale.x = Math.random() * 50 + 10;
building.scale.y = Math.random() * building.scale.x * 8 + 8;
building.scale.z = building.scale.x;
THREE.GeometryUtils.merge(cityGeometry, building);
}
var city = new THREE.Mesh(cityGeometry, material);
city.castShadow = true;
city.receiveShadow = true;
scene.add(city);
}
setup();
</script>
</body>
</html>
Here is screen with derictory, where my firstattempt.html and FirstPersonControls.js located.
Files directory
FirstPersonControls.js was took from three.js/examples/js on github dot com.
Problem is that when opened in browser my city "lose" texture (which you can see on first screen) and no movement can be reached through keys.
Moreover, in Developer tool i see this error:
Error
I'll be very grateful for any help. I think, that may be problem in the way how i use FirstPersonControls.js ?
Please, excuse me for my bad knowledge of javascript :-(
maybe init camera before you pass it to controls
camera is of type undefined here so even if you pass it to the controls and then modify, controls dont get the "reference" to it
controls = new THREE.FirstPersonControls(camera);
put camera declaration before it like so...
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
controls = new THREE.FirstPersonControls(camera);

three.js directional light shadows

http://jsfiddle.net/wp6E3/3/
var camera, scene, renderer;
var cubes = [];
init();
animate();
function init() {
scene = new THREE.Scene();
scene.add(new THREE.AmbientLight(0x212223));
for (var i = 0; i < 10; i++) {
var cubeGeometry = new THREE.CubeGeometry(1, 1.5, 1);
var cubeMaterial = new THREE.MeshLambertMaterial({ color: 0x1ec876 });
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(i*1.2, 0, 0.5);
cube.castShadow = true;
scene.add(cube);
cubes.push(cube);
}
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.x = -4;
camera.position.y = -4;
camera.position.z = 20;
camera.lookAt(cubes[5].position);
scene.add(camera);
var terrainGeo = new THREE.PlaneGeometry(50, 50);
var terrainMaterial = new THREE.MeshLambertMaterial({ color: 0xc0c0a0 });
var terrain = new THREE.Mesh(terrainGeo, terrainMaterial);
terrain.receiveShadow = true;
scene.add(terrain);
var light = new THREE.DirectionalLight(0xffffff, 1);
light.castShadow = true;
light.shadowCameraVisible = true;
light.position.set(-3, 1, 5);
scene.add(light);
scene.add( new THREE.DirectionalLightHelper(light, 0.2) );
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = false;
document.body.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
for (var i = 0; i < cubes.length; i++) {
cubes[i].rotation.x += 0.01 * i;
cubes[i].rotation.y += 0.02 * i;
}
renderer.render(scene, camera);
}
Why shadows doesn't work?
I've looked related questions and three.js references but don't understand what I do wrong.
Three.js shadows not working properly
How to create directional light shadow in Three.JS?
ThreeJS shadow not rendering
http://threejs.org/docs/#Reference/Lights/DirectionalLight
http://learningthreejs.com/blog/2012/01/20/casting-shadows/
First of all, add a camera controller to your scene so you can see what you are doing. Now you can rotate the camera for different views.
controls = new THREE.OrbitControls( camera, renderer.domElement );
Second, when using a jsfiddle, be sure to link to the recent version of the three.js library.
<script src="http://threejs.org/build/three.min.js"></script>
For proper resolution, is important that your shadow camera is positioned tight around your scene. You do that by setting the following:
light.shadowCameraLeft = -20; // or whatever value works for the scale of your scene
light.shadowCameraRight = 20;
light.shadowCameraTop = 20;
light.shadowCameraBottom = -20;
For directional lights, only the "direction to" the light's position matters. However, when shadow maps are involved, the actual position of the light is important, since it controls the shadow camera, too.
light.position.set( -60, 20, 100 );
Here is an updated fiddle. Rotate the camera with the mouse.
http://jsfiddle.net/wp6E3/4/
three.js r.66
Add these to your light definition:
light.shadowMapWidth =
light.shadowMapHeight = 1024;
light.shadowCameraNear = 1;
light.shadowCameraFar = 100;

Categories