I'm currently working an a project which will visualize data on browser by rendering excessive amounts of animated stroked circles. I started evaluating 3D libraries and ended up trying to create a proof of concept application with three.js. It is capable of animating and rendering up to 150 000 point sprites at 60 fps on my 1440p monitor. Everything looks great until you start looking at the details. It has two rendering issues:
It creates strange horizontal lines even when you turn animation off
https://imgur.com/a/3Ugd0
When you turn the camera, transparent areas of overlapping point sprites will show the background instead of underlying point sprites
https://imgur.com/a/NcIwX
Here is the proof of concept application: https://jsfiddle.net/tcpvfbsd/1/
var renderer, scene, camera, controls;
var points;
var stats;
var controls;
var worldWidth = 200;
var worldRadius = worldWidth / 2;
var patchSize = 10;
var pointsAmount = 100000;
function init() {
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
scene.background = new THREE.Color(0x1d252d);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.set(0, worldWidth * 1.5, 0);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.minDistance = 100;
controls.maxDistance = 1100;
scene.add(new THREE.GridHelper(2 * worldRadius, 2 * worldWidth / patchSize, 0x444444, 0x444444));
var geometry = new THREE.BufferGeometry();
var positions = new Float32Array(pointsAmount * 3);
var rotations = new Float32Array(pointsAmount * 1);
for (var i = 0; i < pointsAmount; i++) {
positions[i] = 0;
positions[i + 1] = 0;
positions[i + 2] = 0;
rotations[i] = 2 * Math.PI * Math.random();
}
controls = new function() {
this.speed = 10;
this.amount = 10;
};
var gui = new dat.GUI();
gui.add(controls, 'speed', 0, 100);
//gui.add(controls, 'amount', 0, 10000).step(1);;
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.addAttribute('rotation', new THREE.BufferAttribute(rotations, 1));
var loader = new THREE.TextureLoader();
loader.load('//i.imgur.com/AmQQnZc.png', function(texture) {
var material = new THREE.PointsMaterial({
size: 5,
transparent: true,
map: texture
});
points = new THREE.Points(geometry, material);
scene.add(points);
stats = new Stats();
document.body.appendChild(stats.dom);
animate();
});
}
function animate() {
requestAnimationFrame(animate);
var position = points.geometry.attributes.position;
var count = position.count;
var rotation = points.geometry.attributes.rotation;
var speed = patchSize * controls.speed / 100;
if (speed > 0) {
for (var i = 0; i < count; i++) {
var wiggle = Math.random() > 0.9 ? THREE.Math.randFloat(-0.1, 0.1) : 0;
var theta = rotation.getX(i) + wiggle;
let dx = speed * Math.cos(theta);
let dz = speed * Math.sin(theta);
var x0 = position.getX(i);
var z0 = position.getZ(i);
var x = THREE.Math.clamp(x0 + dx, -worldRadius, worldRadius);
var z = THREE.Math.clamp(z0 + dz, -worldRadius, worldRadius);
if (Math.abs(x) === worldRadius) dx = -dx;
if (Math.abs(z) === worldRadius) dz = -dz;
position.setX(i, x);
position.setZ(i, z);
position.setY(i, 1);
rotation.setX(i, Math.atan2(dz, dx));
}
}
position.needsUpdate = true;
stats.update();
renderer.render(scene, camera);
}
init();
Best way to see the issues is to wait couple of seconds for the point sprites to spread across to area, use speed control on top right corner to pause animation and the use mouse's left button to turn and rotate the camera.
Using alphaTest with value 0.5 clipped the corners off the circles without affecting the rendering of other circles
Setting transparent value to false removed the buggy fade effect which came after setting alphaTest to 0.5
Adding fading to the texture itself made the circles' borders smooth even thought the transparent setting which caused the fade effect was disabled
Randomizing the position on y axis by 0.01 units removed the strange horizontal lines
Related
I am trying to build a working Rubikscube with ThreeJS. Now I have a problem with rotating the sides. At the moment, I am adding the smaller cubes to the Rubikscube and create it like this:
const rubikscube = new THREE.Group();
for (var i = 0; i < 27; i++) {
// 3x3x3 (0,0,0) is on the top left corner
var cube = createPartOfCube(i, scene);
cube.name = i;
cube.position.x = (i % 3) * gap;
cube.position.y = Math.floor(i / 9) * gap;
cube.position.z = (Math.floor(i / 3) % 3) * gap;
rubikscube.add(cube);
}
scene.add(rubikscube);
And this all works fine until I try to rotate, e.g. the right side. I select the pieces on the right side and add them to their own group. Now, when I try to rotate the group, it's rotating around the x axes. Here is the method I want to use for moving a side (just ignore the bool and eval part, its just for getting the right pieces):
function move(direction) {
var bool = moves[direction];
var pivot = new THREE.Group();
pivot.updateMatrixWorld();
for (var i = 0; i < 27; i++) {
if (eval(format(bool, i))) {
pivot.add(scene.getObjectByName(i));
}
}
scene.add(pivot);
animateMove(pivot);
}
And animating it:
function animateMove(pivot) {
requestAnimationFrame(() => {
animateMove(pivot);
});
// missing part
renderer.render(scene, camera);
}
What I've tried:
I have tried different methods, to rotate it the right way but nothing worked and moving the cube is also not the right answer.
One thing I tried was on this thread, but when I tried to rotate it this way, the side just moved, so it's still rotating around the x-axes.
Minimal reproducible example
function main() {
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const gap = 1.1;
scene.add(new THREE.AxesHelper(100));
scene.background = new THREE.Color('white');
camera.position.z = 5;
renderer.setSize(window.innerWidth, window.innerHeight);
const rubikscube = new THREE.Group();
for (var i = 0; i < 27; i++) {
// 3x3x3 (0,0,0) is on the top left corner
var cube = createPartOfCube(i, scene);
cube.name = i;
cube.position.x = (i % 3) * gap;
cube.position.y = Math.floor(i / 9) * gap;
cube.position.z = (Math.floor(i / 3) % 3) * gap;
rubikscube.add(cube);
}
scene.add(rubikscube);
animate();
}
function animate() {
requestAnimationFrame(animate);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(1, 1, 1);
controls.update();
var movingIds = [2, 5, 8, 11, 14, 17, 20, 23, 26]; // IDs of the pieces needed to rotate
var group = new THREE.Group();
movingIds.forEach((i) => {
group.add(scene.getObjectByName(i));
});
scene.add(group);
animateMove(group);
}
function animateMove(group) {
requestAnimationFrame(() => {
animateMove(group);
});
group.rotation.x = 2; // Wrong part I need help with
renderer.render(scene, camera);
}
function createPartOfCube() {
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({ color: 'black' });
var cube = new THREE.Mesh(geometry, material);
return cube;
}
main();
I hope that someone understands my issue and helps me to solve it. Thanks!
All rotations take place around the object's point of origin. So if your object's position is at (0, 0, 0), then that's going to be the pivot point.
I recommend you nest your cube pieces into a THREE.Group() each, that way you can keep the pivot point constant at (0, 0, 0) for all 27 pieces. Then you can displace each piece inside its group to the desired position:
const geom = new THREE.BoxGeometry(1, 1, 1);
const Piece = new THREE.Mesh(geom, mat);
const Group = new THREE.Group();
// Nest Piece inside of its Group
Group.add(Piece);
// Parent Group position is at (0, 0, 0)
// Inner piece position is at (-1, -1, -1)
Piece.position.set(-1, -1, -1);
// Now we can apply rotations to the parent group
// and the child will maintain its distance from the center
Group.rotation.set(Math.PI / 2, 0, 0);
You'll want to repeat this for all values of [-1, 0, +1] for X,Y,Z. That's 3^3 = 27 pieces.
I have a situation where I use an OrbitControls to have a limited view in a room/space. The OrbitControls gets min/max values for azimuth and polar angles to control the view.
The OrbitControls' target is close by the camera to achieve the correct view, compare to the "panorama / cube" example (https://threejs.org/examples/?q=cube#webgl_panorama_cube).
In one situation this works fine. In an other situation this does not work. The reason is that because of the placement of objects, the starting azimuth is -179.999 degrees.
I have not found a way to tell the orbitcontrol to have the azimuth between -179.999 +/- 20 degrees.
I have experimented a little with the algorithm of #Αλέκος (Is angle in between two angles) and it looks like I can calculate the correct angles (not fully implemented). For that I would have set an azimuth and a delta in stead of min/max (and change the OrbitControls code).
Any suggestions for an easier solution? Thanks!
Test code:
var scene = new THREE.Scene();
var orbitControls;
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var material = new THREE.MeshBasicMaterial({
color: 0xffffff,
vertexColors: THREE.FaceColors
});
var geometry = new THREE.BoxGeometry(1, 1, 1);
// colors
red = new THREE.Color(1, 0, 0);
green = new THREE.Color(0, 1, 0);
blue = new THREE.Color(0, 0, 1);
var colors = [red, green, blue];
console.log("FACES", geometry.faces.length)
for (var i = 0; i < 3; i++) {
geometry.faces[4 * i].color = colors[i];
geometry.faces[4 * i + 1].color = colors[i];
geometry.faces[4 * i + 2].color = colors[i];
geometry.faces[4 * i + 3].color = colors[i];
}
geometry.faces[0].color = new THREE.Color(1, 1, 1);
geometry.faces[4].color = new THREE.Color(1, 1, 1);
geometry.faces[8].color = new THREE.Color(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
cube.position.x = 0;
cube.position.y = 4;
cube.position.z = 44;
console.log("CUBE", cube.position);
var cubeAxis = new THREE.AxesHelper(20);
cube.add(cubeAxis);
scene.add(cube);
camera.position.x = 0.8;
camera.position.y = 4.5;
camera.position.z = 33;
orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
orbitControls.enablePan = false;
orbitControls.enableZoom = false;
orbitControls.minPolarAngle = (90 - 10) * Math.PI / 180;
orbitControls.maxPolarAngle = (90 + 10) * Math.PI / 180;
// These values do not work:
orbitControls.maxAzimuthAngle = 200 * Math.PI / 180;
orbitControls.minAzimuthAngle = 160 * Math.PI / 180;
orbitControls.target.x = 0.8;
orbitControls.target.y = 4.5;
orbitControls.target.z = 33.1;
orbitControls.enabled = true;
var animate = function () {
requestAnimationFrame(animate);
if (orbitControls) {
orbitControls.update();
}
renderer.render(scene, camera);
// console.log("orbitControls", "azi", orbitControls.getAzimuthalAngle() * 180 / Math.PI);
};
animate();
I'm new to three.js and I'm following this tutorial. I've followed the source code in the tutorial correctly but I'm not getting the same animated results. I'm using three.js 86 whereas the tutorial (https://www.youtube.com/watch?v=_Lz268dlvmE) was from 3 years ago. I realise the problem its most likely stems from outdated syntax but I'm struggling to find the related updates.
I want the vertices in the bottom sphere to drop away as the sphere above rises on the y-axis. Here's my code with the vertex animations detailed under draw() towards the bottom. I've excluded the HTML to just show the JavaScript.
Thanks!
var scene = new THREE.Scene();
var start_breaking=0;
var w = window.innerWidth, h = window.innerHeight;
var camera = new THREE.PerspectiveCamera(45, w/h, 0.1, 10000);
camera.position.set(0,100,400);
var renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
renderer.setSize(w,h);
var geometry = new THREE.SphereGeometry(70,64,64);
var colors = [];
for (var i = 0; i < geometry.vertices.length; i++) {
colors[i] = new THREE.Color();
colors[i].setRGB(Math.random(),Math.random(),Math.random());
}
geometry.colors = colors;
material = new THREE.PointsMaterial({
size:7,
vertexColors: true
});
particleSystem = new THREE.Points(geometry, material);
particleSystem.position.y = 100;
scene.add(particleSystem);
function create_particles() {
var geomx = new THREE.Geometry();
geomx.colors = colors;
var materiax = new THREE.PointsMaterial({
size: 5,
vertexColors: true
});
var verticex = particleSystem.geometry.vertices;
verticex.forEach(function (p) {
var particle = new THREE.Vector3(
p.x * 1.0,
p.y * 1.0,
p.z * 1.0
);
geomx.vertices.push(particle);
});
particleSystemx = new THREE.Points(geomx, material);
particleSystemx.sortParticles = true;
scene.add(particleSystemx);
}
create_particles();
renderer.render(scene, camera);
setTimeout(draw, 500);
function draw() {
particleSystem.rotation.y += 0.0075;
particleSystem.position.y += 0.275;
if (particleSystem.position.y <= 181 && particleSystem.position >= 180.7) {
create_particles();
//scene.remove(particleSystem);
start_breaking=1;
}
if (start_breaking) {
var vertices = particleSystemx.geometry.vertices;
vertices.forEach(function (v){
v.y -= v.vy;
v.x -= v.vx;
v.z -= v.vz;
});
}
renderer.render(scene, camera);
requestAnimationFrame(function() {draw(); });
}
In the end what I want to do is create simple particle system which is around in a circle with the name of the site in the middle. The particles move around and eventually "die off" and get recreated. I am relatively new to three.js. I have already tried to find a solution but either all of the tutorials are to old and a lot of things have changed or the tutorial doesnt working for what I want to do. Below is what I have so far. It creates the circle with the pixels around in a circle but what I cant get the to do is to get them to move. That is where I need your guys help. Thanks.
var scene = new THREE.Scene();
var VIEW_ANGLE = window.innerWidth / -2,
NEAR = 0.1,
FAR = 1000;
var camera = new THREE.OrthographicCamera( VIEW_ANGLE,
window.innerWidth / 2,
window.innerHeight / 2,
window.innerHeight / -2,
NEAR,
FAR);
// pull the camera position back
camera.position.z = 300;
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth , window.innerHeight );
document.body.appendChild( renderer.domElement );
// Create the particle variables
var particleCount = 1000;
var particles = new THREE.Geometry();
var particle_Material = new THREE.PointsMaterial({
color: 'red',
size: 1
});
var min = -10,
max = 10;
// Create the individual particles
for (var i = 0; i < particleCount; i++) {
var radius = 200;
var random = Math.random();
var variance = Math.random();
var max = 10,
min = -10;
radius += (Math.floor(variance * (max - min + 1)) + min);
var pX = radius * Math.cos(random * (2 * Math.PI)),
pY = radius * Math.sin(random * (2 * Math.PI)),
pZ = Math.random() * 100;
var particle = new THREE.Vector3(
pX,pY,pZ
);
particle.velocity = new THREE.Vector3(
0,
-Math.random(),
0
);
// Add the particle to the geometry
particles.vertices.push(particle);
}
// Create the particle system
var particleSystem = new THREE.Points(
particles,particle_Material
);
particleSystem.sortParticles = true;
// Add the particle system to the scene
scene.add(particleSystem);
// Animation Loop
function render() {
requestAnimationFrame(render);
var pCount = particleCount;
while(pCount--) {
// Get particle
var particle = particles.vertices[pCount];
console.log(particle);
particle.y -= Math.random(5) * 10;
console.log(particle);
}
renderer.render(scene,camera);
}
render();
You can use....
particleSystem.rotateZ(0.1);
....in your render loop. 0.1 is the amount you'd like your particle system to rotate per frame.
Hope that helps!
The interaction between pointlight and a plane seems to have changed from r.67 to r.68
I'm trying to learn three.js, going through a book that is a year old.
I've stripped down the tutorial example to just a plane, a cube, and a pointlight and The "Shinyness" effect of the light on the plane goes away when i use r.68, which is the point of the light effect tutorial.
I'm guessing it must have something to do with the material reflectivity of planes now?
I didn't get any clues going through three.js github revision notes or history of the function sourcecode or similar current three.js examples, but my three.js rookie status is probably holding me back from knowing what to look for.
If someone could explain what changed and why it's not working I would love to turn this broken tutorial into a learning experience.
EDITED TO ADD FIDDLE EXAMPLES INSTEAD OF SOURCE
Here is r.68:
http://jsfiddle.net/nnu3qnq8/5/
Here is r.67:
http://jsfiddle.net/nnu3qnq8/4/
Code:
$(function () {
var stats = initStats();
// create a scene, that will hold all our elements such as objects, cameras and lights.
var scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render and set the size
var renderer = new THREE.WebGLRenderer();
renderer.setClearColorHex(0xEEEEEE, 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
// create the ground plane
var planeGeometry = new THREE.PlaneGeometry(60, 20, 1, 1);
var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
// rotate and position the plane
plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 15
plane.position.y = 0
plane.position.z = 0
// add the plane to the scene
scene.add(plane);
// create a cube
var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff7777});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.castShadow = true;
// position the cube
cube.position.x = -4;
cube.position.y = 3;
cube.position.z = 0;
// add the cube to the scene
scene.add(cube);
// position and point the camera to the center of the scene
camera.position.x = -25;
camera.position.y = 30;
camera.position.z = 25;
camera.lookAt(new THREE.Vector3(10, 0, 0));
// add subtle ambient lighting
var ambiColor = "#0c0c0c";
var ambientLight = new THREE.AmbientLight(ambiColor);
scene.add(ambientLight);
// add spotlight for the shadows
// add spotlight for the shadows
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(-40, 60, -10);
spotLight.castShadow = true;
// scene.add( spotLight );
var pointColor = "#ccffcc";
var pointLight = new THREE.PointLight(pointColor);
pointLight.distance = 100;
pointLight.position = new THREE.Vector3(3, 5, 3);
scene.add(pointLight);
// add the output of the renderer to the html element
$("#WebGL-output").append(renderer.domElement);
// call the render function
var step = 0;
// used to determine the switch point for the light animation
var invert = 1;
var phase = 0;
var controls = new function () {
this.rotationSpeed = 0.03;
this.ambientColor = ambiColor;
this.pointColor = pointColor;
this.intensity = 1;
this.distance = 100;
}
var gui = new dat.GUI();
gui.addColor(controls, 'ambientColor').onChange(function (e) {
ambientLight.color = new THREE.Color(e);
});
gui.addColor(controls, 'pointColor').onChange(function (e) {
pointLight.color = new THREE.Color(e);
});
gui.add(controls, 'intensity', 0, 3).onChange(function (e) {
pointLight.intensity = e;
});
gui.add(controls, 'distance', 0, 100).onChange(function (e) {
pointLight.distance = e;
});
render();
function render() {
stats.update();
// move the light simulation
if (phase > 2 * Math.PI) {
invert = invert * -1;
phase -= 2 * Math.PI;
} else {
phase += controls.rotationSpeed;
}
pointLight.position.z = +(7 * (Math.sin(phase)));
pointLight.position.x = +(14 * (Math.cos(phase)));
if (invert < 0) {
var pivot = 14;
pointLight.position.x = (invert * (pointLight.position.x - pivot)) + pivot;
}
// render using requestAnimationFrame
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
// Align top-left
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
$("#Stats-output").append(stats.domElement);
return stats;
}
});
You are using a pattern that is no longer supported.
pointLight.position = new THREE.Vector3( 3, 5, 3 );
Do not create a new object. Instead do this:
pointLight.position.set( 3, 5, 3 );
three.js r.68