Javascript 3D Effect using three.js - javascript

This is my second time using three.js and I've been playing around for at least 3 hours. I cannot seem to find a direction.
What I should build is something like this:
https://www.g-star.com/nl_nl/newdenimarrivals
I created the scene and everything, but I cannot seem to find a formula or anything on how to arrange the products like that (not to mention that I have to handle click events afterwards and move the camera to that product).
Do you guys have any leads or anything?
EDIT:
This is how I try to arrange the products.
arrangeProducts: function () {
var self = this;
this.products.forEach(function (element, index) {
THREE.ImageUtils.crossOrigin = '';
element.image = 'http://i.imgur.com/CSyFaYS.jpg';
//texture
var texture = THREE.ImageUtils.loadTexture(element.image, null);
texture.magFilter = THREE.LinearMipMapLinearFilter;
texture.minFilter = THREE.LinearMipMapLinearFilter;
//material
var material = new THREE.MeshBasicMaterial({
map: texture
});
//plane geometry
var geometry = new THREE.PlaneGeometry(element.width, element.height);
//plane
var plane = new THREE.Mesh(geometry, material);
plane.overdraw = true;
//set the random locations
/*plane.position.x = Math.random() * (self.container.width - element.width);
plane.position.y = Math.random() * (self.container.height - element.height);*/
plane.position.z = -2500 + (Math.random() * 50) * 50;
plane.position.x = Math.random() * self.container.width - self.container.width / 2;
plane.position.y = Math.random() * 200 - 100;
//add the plane to the scene
self.scene.add(plane);
});
},
EDIT 2:
I figured out: I need to add about 5 transparent concentric cilinders and put the products on each (random location) and have the camera in the center of all the cilinders and just rotate. Buut, how do I put the images on the cilinider randomly? I really have a blockout on that

On the three.js website you find a whole bunch of examples that can show you what is possible and how to do it. Don't expect to be an expert in only 3 hours.

Related

three.js calculate STL file mesh volume

I have to calculate the volume of an STL file, I successfully got the sizes of the model with
var box = new THREE.Box3().setFromObject( mesh );
var sizes = box.getSize();
but I just can't wrap my head around the concept of calculating it. I load the model with
var loader = new THREE.STLLoader();
loader.load(stlFileURL, function ( geometry ) {});
Can someone help me out and point me in the right direction? I'm doing it with javascript.
You can find it with the algorithm from my comment.
In the code snippet, the volume is computed without scaling.
Also, I've added a simple check that the algorithm calculates correctly by finding the volume of a hollow cylinder. As THREE.STLLoader() returns a non-indexed geometry, I've casted the geometry of the cylinder to non-indexed too.
Related forum topic
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 1000);
camera.position.setScalar(20);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x404040);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var loader = new THREE.STLLoader();
loader.load('https://threejs.org/examples/models/stl/binary/pr2_head_pan.stl', function(geometry) {
var mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
color: 0xff00ff,
wireframe: true
}));
mesh.rotation.set(-Math.PI / 2, 0, 0);
mesh.scale.setScalar(100);
scene.add(mesh);
console.log("stl volume is " + getVolume(geometry));
});
// check with known volume:
var hollowCylinderGeom = new THREE.LatheBufferGeometry([
new THREE.Vector2(1, 0),
new THREE.Vector2(2, 0),
new THREE.Vector2(2, 2),
new THREE.Vector2(1, 2),
new THREE.Vector2(1, 0)
], 90).toNonIndexed();
console.log("pre-computed volume of a hollow cylinder (PI * (R^2 - r^2) * h): " + Math.PI * (Math.pow(2, 2) - Math.pow(1, 2)) * 2);
console.log("computed volume of a hollow cylinder: " + getVolume(hollowCylinderGeom));
function getVolume(geometry) {
let position = geometry.attributes.position;
let faces = position.count / 3;
let sum = 0;
let p1 = new THREE.Vector3(),
p2 = new THREE.Vector3(),
p3 = new THREE.Vector3();
for (let i = 0; i < faces; i++) {
p1.fromBufferAttribute(position, i * 3 + 0);
p2.fromBufferAttribute(position, i * 3 + 1);
p3.fromBufferAttribute(position, i * 3 + 2);
sum += signedVolumeOfTriangle(p1, p2, p3);
}
return sum;
}
function signedVolumeOfTriangle(p1, p2, p3) {
return p1.dot(p2.cross(p3)) / 6.0;
}
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/loaders/STLLoader.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
This is a pretty tricky problem. One way is to decompose the object into a bunch of convex polyhedra and sum the volumes of those...
Another way is to voxelize it, and add up the voxels on the inside to get an estimate whos accuracy is limited by the resolution of your voxelization.
Edit: prisoner849 has a rad solution!
I'm also looking for a solution to this, And didn't have any implementation so far.
But extending from the voxelization idea like #manthrax mentioned.
I think we can voxelized into the octree structure.
If the cube still intersects with multiple triangles then voxelized deeper octree.
Until we reached the level of a single triangle cut through,
Then we calculate the volume of the cube using this method:
https://math.stackexchange.com/questions/454583/volume-of-cube-section-above-intersection-with-plane
After understood prisoner849's solution,
This idea is no more valid compared to his solution.

Three.js - Map multiple images to a sphere and control each one

I have a 3D sphere that I want to map an array of images onto, and I want to be able to control each individual image i.e. fading out/in each image independently. I'll provide an example image of what I'm trying to achieve as I feel like that's the best way to explain it.
So as you can see above, 8 images per column and 16(?) per row.
I have been able to recreate the above image by simply mapping that image to a SphereGeometry, however I would like to be able to dynamically swap out images, and fade them in at different times.
What I've tried so far / My ideas:
I tried pushing 8 test images to an array and using that as the material map, and then looping through each face of the SphereGeometry and assigning a material index of 1 through 8 and then resetting after every 8 using modulo, but that didn't work:
function createGlobe() {
var geomGlobe = new THREE.SphereGeometry(40, 32, 16);
var l = geomGlobe.faces.length;
imageArray.push(new THREE.MeshBasicMaterial({map: texture1}));
imageArray.push(new THREE.MeshBasicMaterial({map: texture2}));
imageArray.push(new THREE.MeshBasicMaterial({map: texture3}));
imageArray.push(new THREE.MeshBasicMaterial({map: texture4}));
imageArray.push(new THREE.MeshBasicMaterial({map: texture5}));
imageArray.push(new THREE.MeshBasicMaterial({map: texture6}));
imageArray.push(new THREE.MeshBasicMaterial({map: texture7}));
imageArray.push(new THREE.MeshBasicMaterial({map: texture8}));
for (var i = 0; i < l; i++) {
geomGlobe.faces[i].materialIndex = i % 8;
}
Globe = new THREE.Mesh(geomGlobe, imageArray);
scene.add(Globe);
}
I think I need to count every 4 or 8 faces and then set the material
index for each one of those faces to be the same so that they all use
the same image, but I'm not sure if the faces line up correctly in
that way.
So essentially what I need:
A way to dynamically add images to a sphere in an 8 per column, 16 per row fashion, and the ability to manipulate each one of those images individually.
Any help is very appreciated because I'm very stuck!
I recommend making a large canvas and using that as your texture, then animating your transitions into the canvas, followed by setting texture.needsUpdate = true to update it on the GPU.
You may find that the texture updating takes too much time.. in which case, you could try making 2 canvasses+spheres.. and crossfade between them by changing the frontmost ones opacity.
Below is a snippet showing one way to fade one sphere into another with some randomly filled canvasses..
var renderer = new THREE.WebGLRenderer();
var w = 300;
var h = 200;
renderer.setSize(w, h);
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
45, // Field of view
w / h, // Aspect ratio
0.1, // Near
10000 // Far
);
camera.position.set(15, 10, 15);
camera.lookAt(scene.position);
controls = new THREE.OrbitControls(camera, renderer.domElement);
var light = new THREE.PointLight(0xFFFF00);
light.position.set(20, 20, 20);
scene.add(light);
var light1 = new THREE.AmbientLight(0x808080);
light1.position.set(20, 20, 20);
scene.add(light1);
var light2 = new THREE.PointLight(0x00FFFF);
light2.position.set(-20, 20, -20);
scene.add(light2);
var light3 = new THREE.PointLight(0xFF00FF);
light3.position.set(-20, -20, -20);
scene.add(light3);
var sphereGeom = new THREE.SphereGeometry(5, 16, 16);
function rnd(rng) {
return (Math.random() * rng)
}
function irnd(rng) {
return rnd(rng) | 0
}
function randomCanvasTexture(sz) {
var canv = document.createElement('canvas');
canv.width = canv.height = sz;
var ctx = canv.getContext('2d')
for (var i = 0; i < 100; i++) {
ctx.fillStyle = `rgb(${irnd(256)},${irnd(256)},${irnd(256)})`
ctx.fillRect(irnd(sz), irnd(sz), 32, 32)
}
var tex = new THREE.Texture(canv);
tex.needsUpdate = true;
return tex;
}
var material = new THREE.MeshLambertMaterial({
color: 0x808080,
map: randomCanvasTexture(256)
});
var mesh = new THREE.Mesh(sphereGeom, material);
var mesh1 = mesh.clone()
mesh1.material = mesh.material.clone()
mesh1.material.transparent = true;
mesh1.material.opacity = 0.5;
mesh1.material.map = randomCanvasTexture(256)
scene.add(mesh);
scene.add(mesh1);
renderer.setClearColor(0xdddddd, 1);
(function animate() {
mesh1.material.opacity = (Math.sin(performance.now() * 0.001) + 1) * 0.5
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
})();
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
Without gunning for any optimizations, one could try something like this:
textures.forEach( tex=>{
const s = mySphere.clone()
s.material = s.material.clone()
tex.offset.copy(someOffset)
tex.repeat.copy(someRepeat)
tex.wrapS = tex.wrapT = THREE.ClampToEdgeWrapping // or something like that
s.material.map = tex
s.material.transparent = true
scene.add(s)
})
The idea is to just draw the same sphere over and over, but masked with different offsets. It might not work with just the .map but it might work with alphaMap which is either all black or all white.

multiple textures with single drawcall

hello i want to create multiple cubes with different textures for each cube.
to have the best performances i've created a single mesh with merged geometries. but i have a problem with the textures because until now i must have a draw call per texture.
i want to know if exist a method to have a single merged texture like the geometry, in this way i can create a one big texturized mesh with only one draw call.
my actual code is this.
var geometry = new THREE.Geometry();
var materials = [];
for(var p: number = 0; p < 1000; p++){
var height = Math.floor((Math.random() * 100) + 50);
var box = new THREE.BoxGeometry(10, 5, 10);
box.translate(
Math.floor((Math.random() * 100) + 50),
Math.floor((Math.random() * 100) + 50),
Math.floor((Math.random() * 100) + 50));
var texture = new THREE.MeshLambertMaterial({ map: new THREE.TextureLoader().load(textures[p])});
texture.needsUpdate = true;
geometry.merge(box);
materials.push(texture);
}
var mesh = new THREE.Mesh(geometry, materials);
el.setObject3D("mesh", mesh);
i think the way is to have a texture per geometry or to create different textures to merge in a single texture.
every advice is well accepted thanks in advance.
i use a-frame v.0.7.1 but in this case i think is more a threejs problem
You can merge all of your texture images into a single texture atlas. And then modify per geometry the UVs to point to the desired texture.
https://solutiondesign.com/blog/-/blogs/webgl-and-three-js-texture-mappi-1
Snippet from that guide:
var texture = new THREE.MeshLambertMaterial({ map: new THREE.TextureLoader().load('textureatlas.png')});
var bricks = [
new THREE.Vector2(0, .666),
new THREE.Vector2(.5, .666),
new THREE.Vector2(.5, 1),
new THREE.Vector2(0, 1)
];
geometry.faceVertexUvs[0] = [];
geometry.faceVertexUvs[0][0] = [ bricks[0], bricks[1], bricks[3] ];
geometry.faceVertexUvs[0][1] = [ bricks[1], bricks[2], bricks[3] ];
Since you are merging the geometries, just need to make sure the UVs transfer over (perhaps they do automatically).

What is the most efficient way to display 4 million 2D squares in a browser?

My display has a resolution of 7680x4320 pixels. I want to display up to 4 million different colored squares. And I want to change the number of squares with a slider. If have currently two versions. One with canvas-fillRect which looks somethink like this:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
for (var i = 0; i < num_squares; i ++) {
ctx.fillStyle = someColor;
ctx.fillRect(pos_x, pos_y, pos_x + square_width, pos_y + square_height);
// set pos_x and pos_y for next square
}
And one with webGL and three.js. Same loop, but I create a box geometry and a mesh for every square:
var geometry = new THREE.BoxGeometry( width_height, width_height, 0);
for (var i = 0; i < num_squares; i ++) {
var material = new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } );
material.emissive = new THREE.Color( Math.random(), Math.random(), Math.random() );
var object = new THREE.Mesh( geometry, material );
}
They both work quite fine for a few thousand squares. The first version can do up to one million squares, but everything over a million is just awful slow. I want to update the color and the number of squares dynamically.
Does anyone has tips on how to be more efficient with three.js/ WebGL/ Canvas?
EDIT1: Second version: This is what I do at the beginning and when the slider has changed:
// Remove all objects from scene
var obj, i;
for ( i = scene.children.length - 1; i >= 0 ; i -- ) {
obj = scene.children[ i ];
if ( obj !== camera) {
scene.remove(obj);
}
}
// Fill scene with new objects
num_squares = gui_dat.squareNum;
var window_pixel = window.innerWidth * window.innerHeight;
var pixel_per_square = window_pixel / num_squares;
var width_height = Math.floor(Math.sqrt(pixel_per_square));
var geometry = new THREE.BoxGeometry( width_height, width_height, 0);
var pos_x = width_height/2;
var pos_y = width_height/2;
for (var i = 0; i < num_squares; i ++) {
//var object = new THREE.Mesh( geometry, );
var material = new THREE.Material()( { color: Math.random() * 0xffffff } );
material.emissive = new THREE.Color( Math.random(), Math.random(), Math.random() );
var object = new THREE.Mesh( geometry, material );
object.position.x = pos_x;
object.position.y = pos_y;
pos_x += width_height;
if (pos_x > window.innerWidth) {
pos_x = width_height/2;
pos_y += width_height;
}
scene.add( object );
}
The fastest way to draw squares is to use the gl.POINTS primitive and then setting gl_PointSize to the pixel size.
In three.js, gl.POINTS is wrapped inside the THREE.PointCloud object.
You'll have to create a geometry object with one position for each point and pass that to the PointCloud constructor.
Here is an example of THREE.PointCloud in action:
http://codepen.io/seanseansean/pen/EaBZEY
geometry = new THREE.Geometry();
for (i = 0; i < particleCount; i++) {
var vertex = new THREE.Vector3();
vertex.x = Math.random() * 2000 - 1000;
vertex.y = Math.random() * 2000 - 1000;
vertex.z = Math.random() * 2000 - 1000;
geometry.vertices.push(vertex);
}
...
materials[i] = new THREE.PointCloudMaterial({size:size});
particles = new THREE.PointCloud(geometry, materials[i]);
I didn't dig through all the code but I've set the particle count to 2m and from my understanding, 5 point clouds are generated so 2m*5 = 10m particles and I'm getting around 30fps.
The highest number of individual points I've seen so far was with potree.
http://potree.org/, https://github.com/potree
Try some demo, I was able to observe 5 millions of points in 3D at 20-30fps. I believe this is also current technological limit.
I didn't test potree on my own, so I cant say much about this tech. But there is data convertor and viewer (threejs based) so should only figure out how to convert the data.
Briefly about your question
The best way handle large data is group them as quad-tree (2d) or oct-tree (3d). This will allow you to not bother program with part that is too far from camera or not visible at all.
On the other hand, program doesnt like when you do too many webgl calls. Try to understand it like this, you want to do create ~60 images each second. But each time you set some parameter for GPU, program must do some sync. Spliting data means you will need to do more setup so tree must not be too detialed.
Last thing, someone said:
You'll probably want to pass an array of values as one of the shader uniforms
I dont suggest it, bad idea. Texture lookup is quite fast, but attributes are always faster. If we are talking about 4M points, you cant afford reading data from uniforms.
Sorry I cant help you with the code, I could do it without threejs, Im not threejs expert :)
I would recommend trying pixi framework( as mentioned in above comments ).
It has webgl renderer and some benchmarks are very promising.
http://www.goodboydigital.com/pixijs/bunnymark_v3/
It can handle allot of animated sprites.
If your app only displays the squares, and doesnt animate, and they are very simple sprites( only one color ) then it would give better performance than the demo link above.

My WebGL animation is slow despite requestAnimationFrame()

I'm trying to create webgl animation for my website background, inspired by "threejs - Cloud exemple" (http://mrdoob.com/lab/javascript/webgl/clouds/). On my computer it seems rather well... But for some PC it's very slow.
Is there a way to further optimize my code, and detect if the graphics card does not support webgl ?
My animation (in background) : http://wabeo.fr/?theme=auriga-7
My code :
var container = document.getElementById('container');
var wi = window.innerWidth;
var he = window.innerHeight;
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75,wi/he,1,10000);
var distance = 500;
var geometry2 = new THREE.Geometry();
renderer.setSize(wi ,he);
container.appendChild(renderer.domElement);
scene.add(camera);
var texture = THREE.ImageUtils.loadTexture( '/wp-content/themes/auriga-7/i/cloud.png' );
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
var m = new THREE.MeshBasicMaterial( {color:0x000000} );
var material = new THREE.MeshBasicMaterial( { map: texture,transparent: true} );
var plane = new THREE.PlaneGeometry( 400,400,4,4 );
for ( ix = 0; ix <45; ix++ ) {
item = new THREE.Mesh( plane, m );
item.position.x = ((Math.random()-0.5)*(Math.random() * wi/2) /4)*Math.random()*10;
item.position.y = ((Math.random()-0.5)*(Math.random() * he/2) /4)*Math.random()*10;
item.position.z = ix*10-50;
item.rotation.z = Math.random() *250;
item.scale.x = item.scale.y = Math.random() * Math.random() * 2 + 0.5;
THREE.GeometryUtils.merge(geometry2,item);
}
mesh = new THREE.Mesh( geometry2, material );
scene.add(mesh);
camera.position.z = distance;
camera.lookAt(scene.position);
renderer.sortObjects = false;
// create a point light
var pointLight =
new THREE.PointLight(0xFFFFFF);
// set its position
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
// add to the scene
scene.add(pointLight);
requestAnimationFrame(wanarender);
document.addEventListener('mousemove',onMouseMove, false);
window.addEventListener('resize',onResizeMyFuckinBrowser,false);
function onMouseMove(event){
var mouseX = event.clientX - wi/2;
var mouseY = event.clientY - he/2;
camera.position.x = (mouseX - camera.position.x) * 0.02;
camera.position.y = (-mouseY - camera.position.y) * 0.02;
camera.position.z = distance;
camera.lookAt(scene.position);
}
function onResizeMyFuckinBrowser(){
var wi = window.innerWidth;
var he = window.innerHeight;
renderer.setSize(wi ,he);
}
function wanarender(){
requestAnimationFrame(wanarender);
renderer.render(scene, camera);
}
Thanks for your help :-)
Just looking quickly at the Mr Doob code, I notice a couple of optimisations that might help you. If you inspect Mr Doob's example, you can see that his cloud texture is a 256 x 256 px image, while yours is 800 x 800. There are two things to consider here:
Firstly, try to use powers of 2 for your texture sizes, ie 256, 512, 1024... This is because the graphics card is optimised for textures with these dimensions.
Secondly, 800 x 800 is probably much bigger than you really need, as the Mr Doob demo demonstrates. Most of the time, your texture is being scaled down to half the size or less.
Another thing that stands out in the Mr Doob demo is that he is using mipmaps. Mipmaps are when the graphics card pre-caches multiple versions of the texture at different scales, and uses the closest one to the current level at any given time. This makes the texture scaling more efficient, so turning them on might speed things up for you a little.
Your Code:
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
Mr Doob's Code:
texture.magFilter = THREE.LinearMipMapLinearFilter;
texture.minFilter = THREE.LinearMipMapLinearFilter;
Regarding detection of WebGL, See this Stack Overflow answer for information:
Three.js detect webgl support and fallback to regular canvas
I'm new to Three.jS myself but it is quite problematic to optimise your code. Few things I learned. Render before you append element if you don't like the flash of black.
keep you geometry and textures simple. The more complicated the shape, and the more images used as textures, the slower it gets.
I'm sure there's a way to optimise the graphics, but I don't know it yet. Start by trying to solve that problem.

Categories

Resources