Three.js Lines larger than 65535 vertices (r71) - three.js

First, I'm currently stuck using Three.js r71 (it would currently be a pain to upgrade to the latest). That said, I don't think too much has changed with respect to my question, so hopefully there's still hope.
I'm trying to figure out how to use indexing and drawcalls in BufferGeometry. I think I'm 98% there, but something isn't clicking. I've created my BufferGeometry, and populated it with a single line with 70000 vertices (positions array has 210000 items, colors array has 210000 items, and index array has 139998 items ((70000 - 1) * 2 = 139998 ✓)).
I know that I can't reference an index above 65535, so I added items to geometry.drawcalls using BufferGeometry.addDrawCall. Those objects are:
[{"start":0,"count":65535,"index":0},{"start":65535,"count":4465,"index":65535}]
Now, when I run the sample (link and code below) with 65535 vertices, everything works as expected: A red spiral appears. But when I try to use 70000 vertices, something odd happens: I get An error in the console (below), and a smaller spiral appears.
Console error:
GL ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 0
I've tried re-aligning the indices (commented section starting at line 56), but it encounters the same issue. Am I missing a step? Is there some other switch or property I need to populate?
Footnote: I tried using a UInt32Array for index, but WebGL throws a type error instead.
JSFiddle: Line Indexing Test
Code:
var numVerts = 70000; // works with 65535
var hostDiv, scene, renderer, camera, controls, light;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
function init() {
hostDiv = document.createElement('div');
hostDiv.setAttribute('id', 'host');
document.body.appendChild(hostDiv);
renderer = new THREE.WebGLRenderer({ antialias: true, preserverDrawingBuffer: true });
renderer.setSize(WIDTH, HEIGHT);
hostDiv.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 250;
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 5.0;
controls.dynamicDampingFactor = 0.5;
light = new THREE.PointLight(0xffffff, 1, Infinity);
light.position.copy(camera.position);
scene = new THREE.Scene();
scene.add(camera);
scene.add(light);
scene.add(createSpiral());
debugger;
animate();
}
function createSpiral(){
var positions = [],
indices = [],
colors = [];
var vec = new THREE.Vector3(1, 0, 0),
rotVec = new THREE.Vector3(0, 0, 1);
for(var idx = 0, fakeIdx = 0; idx < numVerts; ++idx){
// update position vector
vec.setLength((idx + 1) / 1000); // slowly spiral outward
vec.applyAxisAngle(rotVec, 0.01); // spin around the z-axis
positions.push(vec.x, vec.y, vec.z);
if(idx + 1 < numVerts){
indices.push(idx, idx + 1);
}
//if((idx % 65535) + 1 < numVerts){
// indices.push((idx % 65535), (idx % 65535) + 1);
//}
if(idx > 65535){
colors.push(0, 1, 0); // GREEN!
}
else{
colors.push(1, 0, 0); // RED!
}
}
bg = new THREE.BufferGeometry();
if(numVerts > 65535){
var remainingVerts = numVerts,
start = 0,
count = 0,
index = 0;
while(remainingVerts > 0){
count = (remainingVerts < 65535)? remainingVerts : 65535;
bg.addDrawCall(start, count, start);
start += 65535;
remainingVerts -= 65535;
}
}
bg.addAttribute('index', new THREE.BufferAttribute(new Uint16Array( indices ), 1));
bg.addAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3));
bg.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3));
var mat = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors });
var spiral = new THREE.Line(bg, mat, THREE.LinePieces);
spiral.name = "SPIRAL";
debugger;
return spiral;
}
function render() {
light.position.copy(camera.position);
renderer.render(scene, camera);
controls.update();
}
function animate() {
requestAnimationFrame(animate);
render();
}
init();
html *{
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
#host {
width: 100%;
height: 100%;
}
<script src="https://rawgithub.com/mrdoob/three.js/f73593b00e3b2c927bb1a3236240cea5597166ec/build/three.js"></script>
<script src="http://threejs.org/examples/js/controls/TrackballControls.js"></script>

Using a UInt32Array works fine for me in r73.
In r73 they don't use THREE.LineStrip and THREE.LinePieces in the material, but there are two different classes THREE.Line and THREE.LineSegments for drawing lines continuous or in segments.
I made some other changes. I changed to use a continuous line instead of segments because it saves you half the amount of indices. I think this could also be one of your issues. Since you inserted 2 indices for each line segment, which means to draw 32768 line segments you already reach your max 65536 indices.
Here a fiddle, which I updated to r73
Note my changes important changes:
indices.push(idx);
bg.setIndex( new THREE.BufferAttribute( new Uint32Array( indices ), 1 ) );
var spiral = new THREE.Line(bg, mat);
And I removed the unneeded lines of code.
UPDATE
Interesting, I just noticed that you don't even need the indices. It works totally fine as a non-indexed THREE.BufferGeometry. This saves you all the headaches and will virtually allow you to have infinite points if I am not mistaken...
Here another fiddle that shows that it works totally fine without with a huge amount of points.
And this also works for your r71. Check that out here

Related

Three.js place one box upon another

To display rack structure, placing one box upon another. But y Position calculation fails.Currently creates gap between boxes. Please inform how could it be fixed, whether camera or light effect creates a problem. As per rack size, altering y position. Data contain size and starting place.
```
var data = [{"id": 10075,"size": 3,"slotNumber": 1},{"id": 10174,"size": 7,"slotNumber": 4}];
var rackListGroup;
init();
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x999999 );
var light = new THREE.AmbientLight( 0xffffff );
light.position.set( 0.5, 1.0, 0.5 ).normalize();
scene.add( light );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.fromArray([0, 0, 140]);
scene.add( camera );
rackListGroup = new THREE.Mesh();
rackListGroup.name = "Rack List"
var i;
for (i = 0; i < 1; i++) {
rackListGroup.add(drawRack(10, i))
}
scene.add(rackListGroup);
render();
}
function drawRack(size, rackNo){
var rackGroup = new THREE.Group();
rackGroup.name = "rack "+rackNo;
var yPosition = -42;
var xPosition = -20 + parseInt(rackNo)*40;
var slot = 1, counter = 0;
var slotWidth = 5;
while(slot <= parseInt(size)){
var slotSize = data[counter].size;
slot = slot + slotSize;
yPosition = yPosition + slotSize* slotWidth;
var geometry = new THREE.BoxGeometry( 30, slotWidth*slotSize, 5 );
var material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
var shape = new THREE.Mesh( geometry, material );
shape.name = data[counter].name;
shape.position.set(xPosition, yPosition, 0);
rackGroup.add(shape);
var boxGeometry = new THREE.BoxBufferGeometry( 30, slotWidth*slotSize, 5, 1, 1, 1 );
var boxMaterial = new THREE.MeshBasicMaterial( { wireframe:true } );
var box = new THREE.Mesh( boxGeometry, boxMaterial );
box.name = data[counter].name;
box.position.set(xPosition, yPosition, 0);
rackGroup.add(box);
if(counter+1 < data.length){
counter++;
}
}
return rackGroup;
}
```
I've tried your code and I see a misunderstanding between the objects position and the objects height to be able to stack them on top of each other.
You use one variable for yPosition and you need 2 variables, the reason is that geometries are positioned based on its axes center, so it means a 15 units height mesh positioned at y=0 it will place indeed at -7.5 units below the y=0 position and the upper side of the geometry will be at 7.5. So next slot to stack will be needed to place (conceptually) at y = 7.5 + (topSlotHeight / 2).
That's why your calculation of the next slot to stack y position is wrong. I have created this fiddle with the solution, and I have added a gridHelper at y=0 for your reference and the OrbitControls to be able to check it better. Now it works perfectly doing like this, storing the accumulated base position of the previous slot in yBaseHeight and the yPosition for the slot on top:
var slotHeight = (slotSize * slotWidth);
yPosition = yBaseHeight + (slotHeight / 2);
yBaseHeight = yBaseHeight + slotHeight;
PD.- I saw you start placing objects at y=-42, I started from y=0 to show better the effect.

Three.js - Adding texture makes it look black

I m making custom 3D Objects using an image. First i take outline from an image and after getting points i create the shape. Then i m using three.js extrude geometry to make it look like 3D Object.
The issue is the texture i m using is showing complete black. I used this code to scale the texture.
texture.wrapT = texture.wrapS = THREE.RepeatWrapping;
var rx = 1/img.width;
var ry = 1/img.height;
texture.repeat.set(rx,ry);
This gives me the result in the image below:
NOTE: I m using GLTF Exporter.
It is scaling the texture correctly, but i cant set the offset. The image is not arranged properly.
I want to dynamically set the offset as my images will be different every time. I can set the offset manually and achieve the results as show in the image below. But i want this to be dynamic.
NOTE: This is the offset i m setting manually for this image to achieve results.
texture.offset.set(0.188,0.934);
I really need help. Any help will be appreciated. Thanks.
It's not clear what you're trying to do but ...
texture.repeat set how many times a texture repeats so texture.repeat.set(2,3) means repeat twice across and three times down. That means your code texture.repeat.set(1 / img.width, 1 / img.height) will expand the texture so that only 1 pixel is visible.
repeat.set(2, 3);
repeats 2 across 3 down
repeat.set(1/2, 1/3);
repeats 0.5 across .33 down or in other words show half the texture across and 1/3 of the texture down
offset moves the texture where
1 = move it 100% to the left (if the texture repeats there will be no change at 1 since you've move it 100%)
0.5 = move it 50% to the left
0.25 = move it 25% to the left
-0.10 = move it -10% to the right
If you want to move it in pixels this is where you'd use img.width
1/img.width = move it one pixel left
See the example at the bottom of this page
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
const geometry = new THREE.PlaneGeometry(1, 1);
const obs = []; // just an array we can use to rotate the cubes
const loader = new THREE.TextureLoader();
for (let i = 0; i < 10; ++i) {
const texture = loader.load('https://i.imgur.com/ZKMnXce.png');
// expand the texture so only 40% of stretched across the plane
texture.repeat.set(0.4, 0.4);
// randomly offset the texture
texture.offset.set(rand(1), rand(1));
// make it repeat
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const material = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(geometry, material);
plane.position.set(rand(-1, 1), rand(-1, 1), 0);
plane.position.set(rand(-1, 1), rand(-1, 1), 0);
scene.add(plane);
obs.push(plane); // add to our list of obs to rotate
}
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
obs.forEach((obj, ndx) => {
const speed = .2 + ndx * .1;
const rot = time * speed;
obj.rotation.z = rot;
});
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>

three js drag and drop Uncaught TypeError

I have been trying to implement the drag and drop functionality found here...
http://www.smartjava.org/tjscb/07-animations-physics/07.08-drag-n-drop-object-around-scene.html
Whenever I customise it slightly and use it in my project I get the following..
"Uncaught TypeError: Cannot read property 'point' of undefined"
whenever I try to drag a cube. The rotation isn't occurring so it must be recognising that I'm trying to drag an object and it relates to this line of code..
"selectedObject.position.copy(intersects[0].point.sub(offset))"
I assumed since I am new to all of this that I had messed up, so I copied all of the code from the link above into a new page (so should be identical) and ran it and I get the same thing (everything else works good)
Im probably missing something really stupid, I have searched for this and looked at other examples on how to achieve this, but since I was working my way through a book which explained everything I thought I would stick with this, and also it would be a good learning experience to figure out why its not working. If anyone could point me in the right direction I would appreciate it
<!DOCTYPE html>
<html>
<head>
<title>07.08 - Drag and drop object around scene</title>
<script type="text/javascript" src="js/threejs/three.min.js"></script>
<script type="text/javascript" src ="js/threejs/OrbitControls.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
<script>
// global variables
var renderer;
var scene;
var camera;
var cube;
var control;
var orbit;
// used for drag and drop
var plane;
var selectedObject;
var offset = new THREE.Vector3();
var objects = [];
// based on http://mrdoob.github.io/three.js/examples/webgl_interactive_draggablecubes.html
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(0xffffff, 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
plane = new THREE.Mesh(new THREE.PlaneGeometry(2000, 2000, 18, 18), new THREE.MeshBasicMaterial({
color: 0x00ff00,
opacity: 0.25,
transparent: true
}));
plane.visible = false;
scene.add(plane);
var dirLight = new THREE.DirectionalLight();
dirLight.position.set(25, 23, 15);
scene.add(dirLight);
var dirLight2 = new THREE.DirectionalLight();
dirLight2.position.set(-25, 23, 15);
scene.add(dirLight2);
for (var i = 0; i < 200; i++) {
// create a cube and add to scene
var cubeGeometry = new THREE.BoxGeometry(2, 2, 2);
var cubeMaterial = new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff});
cubeMaterial.transparent = true;
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
objects.push(cube);
cube.scale.x = Math.random() + 0.5 * 2;
cube.scale.y = Math.random() + 0.5 * 2;
cube.scale.z = Math.random() + 0.5 * 2;
cube.position.x = Math.random() * 50 - 25;
cube.position.y = Math.random() * 50 - 25;
cube.position.z = Math.random() * 50 - 25;
cube.rotation.x = Math.random() * Math.PI * 2;
cube.rotation.y = Math.random() * Math.PI * 2;
cube.rotation.z = Math.random() * Math.PI * 2;
scene.add(cube);
}
// position and point the camera to the center of the scene
camera.position.x = 35;
camera.position.y = 35;
camera.position.z = 53;
camera.lookAt(scene.position);
// add some controls so we can rotate
orbit = new THREE.OrbitControls(camera);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
// call the render function
render();
}
function render() {
renderer.render(scene, camera);
orbit.update();
requestAnimationFrame(render);
}
document.onmousemove = function (event) {
// make sure we don't access anything else
event.preventDefault();
// get the mouse positions
var mouse_x = ( event.clientX / window.innerWidth ) * 2 - 1;
var mouse_y = -( event.clientY / window.innerHeight ) * 2 + 1;
// get the 3D position and create a raycaster
var vector = new THREE.Vector3(mouse_x, mouse_y, 0.5);
vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position,
vector.sub(camera.position).normalize());
// first check if we've already selected an object by clicking
if (selectedObject) {
// check the position where the plane is intersected
var intersects = raycaster.intersectObject(plane);
// reposition the selectedobject based on the intersection with the plane
selectedObject.position.copy(intersects[0].point.sub(offset));
} else {
// if we haven't selected an object, we check if we might need
// to reposition our plane. We need to do this here, since
// we need to have this position before the onmousedown
// to calculate the offset.
var intersects = raycaster.intersectObjects(objects);
if (intersects.length > 0) {
// now reposition the plane to the selected objects position
plane.position.copy(intersects[0].object.position);
// and align with the camera.
plane.lookAt(camera.position);
}
}
};
document.onmousedown = function (event) {
// get the mouse positions
var mouse_x = ( event.clientX / window.innerWidth ) * 2 - 1;
var mouse_y = -( event.clientY / window.innerHeight ) * 2 + 1;
// use the projector to check for intersections. First thing to do is unproject
// the vector.
var vector = new THREE.Vector3(mouse_x, mouse_y, 0.5);
// we do this by using the unproject function which converts the 2D mouse
// position to a 3D vector.
vector.unproject(camera);
// now we cast a ray using this vector and see what is hit.
var raycaster = new THREE.Raycaster(camera.position,
vector.sub(camera.position).normalize());
// intersects contains an array of objects that might have been hit
var intersects = raycaster.intersectObjects(objects);
if (intersects.length > 0) {
orbit.enabled = false;
// the first one is the object we'll be moving around
selectedObject = intersects[0].object;
// and calculate the offset
var intersects = raycaster.intersectObject(plane);
offset.copy(intersects[0].point).sub(plane.position);
}
};
document.onmouseup = function (event) {
orbit.enabled = true;
selectedObject = null;
}
// calls the init function when the window is done loading.
window.onload = init;
</script>
</head>
<body>
</body>
</html>
"Uncaught TypeError: Cannot read property 'point' of undefined"
"selectedObject.position.copy(intersects[0].point.sub(offset))"
This means, intersects[0] is undefined which means the array intersects has no element (length = 0). You are using raycasting and it isn't working properly.
You should share your modified code so that we can check what is going wrong in your raycasting.
Update: I think your three.js version is greater than 71 while three.js version of this website is 71 or less. In the 72th version, there is an update in the raycaster -
Ignore invisible objects. (#mrdoob, #tschw)
So, the problem is here -
var intersects = raycaster.intersectObject(plane);
Since the plane is invisible, the intersectObject is returning empty array.
Workaround: I found a workaround. You can remove the following line -
plane.visible = false;
You can hide the material of the plane instead in the following way -
plane = new THREE.Mesh(new THREE.PlaneGeometry(2000, 2000, 18, 18), new THREE.MeshBasicMaterial({
color: 0xffff00,
opacity: 0.50,
transparent: true,
visible: false
}));
In this way, the raycaster will work properly and the plane will be invisible as well.

THREE.js (r60) PointLight not reflected by a special plane object (heightmapped from image)

UPDATE Cause of problem has been found - see Update section end of question.
I have a complex app using THREE.js (r60) which adds a special plane object to the main scene. The plane geometry is determined by heightmapping from an internally-supplied base64 uri image (size 16x16, 32x32 or 64x64 pixels). The scene has two static lights (ambient and directional) and one moveable point light which switches on and off.
In the complex app the point light is not reflected by the plane object. (Point light is toggled by pressing "R" key or button).
I have made a first JSFiddle example using THREE.js latest version (r70) where the lights work fine.
[Update] I have now made a second JSFiddle example using the older THREE.js library (r60) it also works OK.
I suspect the problem in the complex app (r60) may have something to do with system capacity and or timing/sequencing. Capacity is definitely an issue because other simpler scene objects (boxes and cylinders) show individual responses or non-responses to the point light which vary from one run of the app to the next, seemingly depending on the overall level of system activity (cpu, memory usage). These simpler objects may reflect in one run but not in the next. But the heightmapped plane object is consistently non-reflective to the point light. These behaviors are observed on (i) a Win7 laptop and (ii) an Android Kitkat tablet.
The heightmapping process may be part of the cause. I say this because when I comment out the heightmapped plane and activate a simple similar plane object (with randomly assigned z-levels) the latter plane behaves as expected (i.e. it reflects point light).
I guess that the usual approach now would be to upgrade my complex app to r70 (not a trivial step) and then start disabling chunks of the app to narrow down the cause. However it may be that the way in which heightmapping is implemented (e.g. with a callback) is a factor in explaining the failure of the heightmapped plane to reflect point light.
[RE-WRITTEN] So I would be grateful if anyone could take a look at the code in the correctly-working, previously-cited, (r70) JSFiddle example and point out any glaring design faults which (if applied in more complex, heavilly-loaded apps) might lead to failure of the height-mapped plane to reflect point light.
Full code (javascript, not html or css) of the (r70) JSFiddle:-
//... Heightmap from Image file
//... see http://danni-three.blogspot.co.uk/2013/09/threejs-heightmaps.html
var camera, scene, renderer;
var lpos_x = -60,lpos_y = 20,lpos_z = 100;
var mz = 1;
var time = 0, dt = 0;
var MyPlane, HPlane;
base64_imgData = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAeAB4AAD/4QBoRXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAAExAAIAAAASAAAATgAAAAAAAAB4AAAAAQAAAHgAAAABUGFpbnQuTkVUIHYzLjUuMTAA/9sAQwANCQoLCggNCwsLDw4NEBQhFRQSEhQoHR4YITAqMjEvKi4tNDtLQDQ4RzktLkJZQkdOUFRVVDM/XWNcUmJLU1RR/9sAQwEODw8UERQnFRUnUTYuNlFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFR/8AAEQgAIAAgAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A19Z8SXdu5KOMKxBAFOi1uTUdNJguxFcAchv6Vz2so/mzKc8sc8VX8MyQjUVWYNweCO9AEsOuX8s+xrqWQh8DJ4rsJCphSN3Czsm7ArG1bT7fSFe7EZJzuX3J6VQsdRnvryJ2+/wooA6O501JY7yRh0U8Vyg1WzsghhsAkqnBO4nd713t8NsEqIhJfqRXEahotxPJlISDnOaANzWvL1rR4JiTG6ryorG0C2aDUI02lhu6kVZ02wvLVSpYtu65yRXQaZYvDL5rhRx2oA//2Q==";
init();
animate();
//==================================================================
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 10);
camera.position.x = 1300;
camera.position.y = 400;
camera.position.z = 0;
camera.lookAt(0, 0, 0);
scene.add(camera);
scene.add(new THREE.AmbientLight(0x001900));
SunLight = new THREE.DirectionalLight(0xff0000,.3,20000);//...color, intensity, range.
SunLight.position.set(0, 3000, -8000);
scene.add(SunLight);
//POINT LIGHT
PL_color = 0x0000ff;
PL_intensity = 10;
PL_range_to_zero_intensity = 1200;
PL = new THREE.PointLight(PL_color, PL_intensity, PL_range_to_zero_intensity);
scene.add(PL);
PL_pos_x = -100;
PL_pos_y = -100;
PL_pos_z = 120;
PL.position.set(PL_pos_x, PL_pos_y, PL_pos_z);
//INDICATOR SPHERE
var s_Geometry = new THREE.SphereGeometry(5, 20, 20);
var s_Material = new THREE.MeshBasicMaterial({
color: 0xaaaaff
});
i_Sphere = new THREE.Mesh(s_Geometry, s_Material);
i_Sphere.position.set(PL_pos_x, PL_pos_y, PL_pos_z);
scene.add(i_Sphere);
//Plane02
var Plane02Geo = new THREE.PlaneGeometry(50, 50); //...
var Plane02Material = new THREE.MeshPhongMaterial({
side: THREE.DoubleSide
}, {
color: 0xaaaaaa
});
Plane02 = new THREE.Mesh(Plane02Geo, Plane02Material);
Plane02.position.set(0, 0, -120);
scene.add(Plane02);
//PEAS
xxx = SOW_F_Make_peas();
//RENDERER
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = false;
document.body.appendChild(renderer.domElement);
// controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
xxx = SOW_F_Make_Heightmap_Object_from_Image_File(scene, camera);
} //...EOFunction Init
//==================================================================
function animate() {
dt = 0.1;
time += dt;
if (time < 10000) {
requestAnimationFrame(animate);
//move point light & indicator sphere
speed = 16;
if (Math.abs(PL_pos_z) > 400) mz = (-1)* mz;
PL_pos_x += 0.01 * speed * mz;
PL_pos_y += 0.05 * speed * mz;
PL_pos_z -= 0.2 * speed * mz;
PL.position.set(PL_pos_x, PL_pos_y, PL_pos_z);
i_Sphere.position.set(PL_pos_x, PL_pos_y, PL_pos_z);
renderer.render(scene, camera);
} else alert("Time=" + time + "Finished");
}
//==================================================================
function SOW_F_Make_Heightmap_Object_from_Image_File(givenScene, givenCamera) {
//... Read a Heightmap from a coloured image file
//... into a (pre-defined global) plane object called HPlane
MyImage = new Image();
MyImage.onload = function () {
var MyPlane_width = 1000;//6000; //...MyPlane width or height are in scene units and do not have to match image width or height
var MyPlane_height = 1000;//6000;
var MyPlane_w_segs = MyImage.naturalWidth - 1; //... important that this mapping is correct for texture 1 pixel :: 1 segment.
var MyPlane_h_segs = MyImage.naturalHeight - 1; //... important that this mapping is correct for texture 1 pixel :: 1 segment.
var Hgeometry = new THREE.PlaneGeometry(MyPlane_width, MyPlane_height, MyPlane_w_segs, MyPlane_h_segs);
//var texture = THREE.ImageUtils.loadTexture( '/images/Tri_VP_Texturemap.jpg' );
var texture = THREE.ImageUtils.loadTexture( base64_imgData );
//... Choose texture or color
//var Hmaterial = new THREE.MeshLambertMaterial( { map: texture, side: THREE.DoubleSide} );//....fails
var Hmaterial = new THREE.MeshPhongMaterial( {
color: 0x111111 , side: THREE.DoubleSide } ); //... works OK
HPlane = new THREE.Mesh(Hgeometry, Hmaterial);
//...get Height Data from Image
var scale = 0.6;//1//6; //0.25;
var Height_data = DA_getHeightData(MyImage, scale);
//... set height of vertices
X_offset = 0;
Y_offset = 0;
Z_offset = -100; //...this will (after rotation) add to the vertical height dimension (+ => up).
for (var iii = 0; iii < HPlane.geometry.vertices.length; iii++) {
//HPlane.geometry.vertices[iii].x = X_offset;
//HPlane.geometry.vertices[iii].y = Y_offset;
HPlane.geometry.vertices[iii].z = Z_offset + Height_data[iii];
}
//----------------------------------------------------------------------
//... Must do it in this order...Faces before Vertices
//... see WestLangley's response in http://stackoverflow.com/questions/13943907/my-object-isnt-reflects-the-light-in-three-js
HPlane.rotation.x = (-(Math.PI) / 2); //... rotate MyPlane -90 degrees on X
//alert("Rotated");
HPlane.geometry.computeFaceNormals(); //... for Lambert & Phong materials
HPlane.geometry.computeVertexNormals(); //... for Lambert & Phong materials
/*
HPlane.updateMatrixWorld();
HPlane.matrixAutoUpdate = false;
HPlane.geometry.verticesNeedUpdate = true;
*/
givenScene.add(HPlane);
HPlane.position.set(0, -150, 0);//... cosmetic
//return HPlane; //... not necessary, given that HPlane is global.
} ; //... End of MyImage.onload = function ()
//===============================================================
//... *** IMPORTANT ***
//... Only NOW do we command the script to actually load the image source
//... This .src statement will load the image from file into MyImage object
//... and invoke the pre-associated MyImage.OnLoad function
//... cause cross-origin problem: MyImage.src = '/images/Tri_VP_Heightmap_64x64.jpg'; //...if image file is local to this html file.
MyImage.src = base64_imgData;//... uses image data provided in the script to avoid Cross-origin file source restrictions.
} //... End of function SOW_F_Make_Heightmap_Object_from_Image_File
//===========================================================================
function DA_getHeightData(d_img, scale) {
//... This is used by function SOW_F_Make_Heightmap_Object_from_Image_File.
//if (scale == undefined) scale=1;
var canvas = document.createElement('canvas');
canvas.width = d_img.width; //OK
canvas.height = d_img.height;
var context = canvas.getContext('2d');
var size = d_img.width * d_img.height;
var data = new Float32Array(size);
context.drawImage(d_img, 0, 0);
for (var ii = 0; ii < size; ii++) {
data[ii] = 0;
}
var imgData = context.getImageData(0, 0, d_img.width, d_img.height);
var pix = imgData.data; //... Uint(8) UnClamped Array[1024] for a 16x16 = 256 pixel image = 4 slots per pixel.
var jjj = 0;
//... presumably each pix cell can have value 0 to 255
for (var iii = 0; iii < pix.length; iii += 4) {
var all = pix[iii] + pix[iii + 1] + pix[iii + 2];
//... I guess RGBA and we don't use the fourth cell (A, = Alpha channel)
jjj++;
data[jjj] = all * scale / 3; //...original code used 12 not 3 ??? and divided by scale.
//console.log (iii, all/(3*scale), data[jjj]);
}
return data;
} //... end of function DA_getHeightData(d_img,scale)
//==================================================================================================
function SOW_F_Get_A_Plane(givenScene, givenCamera) {
//...MyPlane width or height are in scene units and do not have to match image width or height
var MyPlane_width = 1000;
var MyPlane_height = 1000;
var MyPlane_w_segs = 64; //...
var MyPlane_h_segs = 64; //...
geometry = new THREE.PlaneGeometry(MyPlane_width, MyPlane_height, MyPlane_w_segs, MyPlane_h_segs);
//var material = new THREE.MeshLambertMaterial( { color: 0xeeee00, side: THREE.DoubleSide} );
var material = new THREE.MeshPhongMaterial({
color: 0xeeee00,side: THREE.DoubleSide
}); //... OK
MyPlane = new THREE.Mesh(geometry, material);
givenScene.add(MyPlane);
MyPlane.rotation.x = (-(Math.PI) / 2); // rotate it -90 degrees on X
MyPlane.position.set(0, 100, 0);
MyPlane.geometry.computeFaceNormals(); //...for Lambert & Phong materials
MyPlane.geometry.computeVertexNormals(); //...for Lambert & Phong materials
/*
MyPlane.geometry.verticesNeedUpdate = true;
MyPlane.updateMatrixWorld();
MyPlane.matrixAutoUpdate = false;
*/
} //... EOF SOW_F_Get_A_Plane
//====================================================================
function SOW_F_Make_peas()
{
//----------------- Make an array of spheres -----------------------
Pea_geometry = new THREE.SphereGeometry(5,16,16);
//Pea_material = new THREE.MeshNormalMaterial({ shading: THREE.SmoothShading});
Pea_material = new THREE.MeshPhongMaterial({ color: 0xaa5522});
// global...
num_peas = 1200;
for (var iii = 0; iii < num_peas; iii++)
{
//...now global
ob_Pea = new THREE.Mesh(Pea_geometry, Pea_material);
ob_Pea.position.set(
400 * Math.random() - 150,
300 * Math.random() - 150,
1200 * Math.random() - 150);
scene.add(ob_Pea);//TEST
}
}
UPDATE
It appears the problem is a result of phasing. See this new JSFiddle(r70). Pointlight is created in function init() but not added to scene, or is immediately removed from scene after being added. Then various graphical mesh objects are created. When pointlight is added back to the scene (in the animate loop) it is too late - the mesh objects will not be illuminated by the pointlight.
A procedural solution is simply to not remove pointlights from the scene if they are to be used later. If they need to be "extinguished" temporarilly then just turn down the intensity and turn it up later: e.g.
myPointLight.intensity = 0.00

How to efficiently draw lots of objects using Three.js

I am new to WebGL and Three.js. I am trying to visualize a large grid of circles changing colors at once.
As I increase the number of instances, it gets noticeably slower, where it takes seconds to update. What are some suggestions for improving my code? Can I update 4000 circles at once?
Here is my existing implementation:
<html>
<head>
<title>My first Three.js app</title>
<style></style>
</head>
<body>
<script src="./three.js"></script>
<script>
var ROWS = 40
var COLS = 100
var SEGMENTS = 10;
var windowWidth = window.innerWidth, windowHeight = window.innerHeight;
var camera, scene, renderer;
var group, text, plane;
function init() {
// create and append container/canvas
container = document.createElement( 'div' );
document.body.appendChild( container );
// create camera
camera = new THREE.PerspectiveCamera(100, windowWidth / windowHeight, 0.1, 1000 );
// set position of camera
camera.position.z = 500;
camera.position.x = windowWidth/2
camera.position.y = windowHeight/2
// Create a scene
scene = new THREE.Scene();
renderer = new THREE.CanvasRenderer();
renderer.setClearColor( 0xf0f0f0 );
renderer.setSize( windowWidth, windowHeight );
renderer.sortElements = false;
container.appendChild( renderer.domElement );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}
function addCircle(color, x, y, z, s , radius) {
var geometry = new THREE.CircleGeometry(radius, SEGMENTS, SEGMENTS)
var material = new THREE.MeshBasicMaterial( { color: color, overdraw: true } );
var mesh = new THREE.Mesh( geometry, material );
mesh.position.set( x, y, z );
mesh.scale.set( s, s, s );
scene.add( mesh );
}
function toHex(d) {
var valueStr = d.toString(16);
valueStr = valueStr.length < 2 ? "0"+valueStr : valueStr;
var fillColor = "0x00" + valueStr + "00";
return parseInt(fillColor);
}
function drawData(data) {
var rows = data.length;
var cols = data[0].length;
distanceBetweenCircles = Math.min(windowWidth/(cols), windowHeight/(rows));
var radius = distanceBetweenCircles/2.0
for(var i = 0; i < data.length; i++) {
for (var j = 0; j < data[0].length; j++) {
var color = toHex(data[i][j])
var x = distanceBetweenCircles*j - radius
var y = distanceBetweenCircles*i - radius
addCircle( color, x, y, 0, 1 , radius-3);
}
}
}
function newData(){
var newData = []
for (var i = 0; i < ROWS; i++) {
var row = [];
for (var j = 0; j < COLS; j ++) {
row.push(Math.floor(Math.random()*255));
}
newData.push(row);
}
return newData;
}
function onDocumentMouseDown ( event ) {
event.preventDefault();
// Update circles
var randomData = newData()
drawData(randomData);
}
var render = function() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
init();
render();
</script>
</body>
</html>
To add a CircleGeometry object to a scene requires the circle to be drawn. Adding an image/texture of a circle to a scene requires the circle to be printed, so to speak.
Multiply that by 4000 and the drawing becomes quite expensive.
It would be faster to maintain an array of the meshes and update their properties, rather than creating a new set of geometries, materials and meshes every mouse click.
Memory management is extremely important in software design. There is a cost to every variable you introduce, especially those who's instantiation invokes a cascade of allocations behind some API call. This is the down side of using layerings like THREE which hide complexity but also hide ramifications of using their calls. Short of avoiding THREE and doing all the WebGL plumbing yourself (which is always a good first step before ignoring the plumbing and just using a shim like THREE), do some homework to identify what is getting created as you make calls to any API, like THREE. Rip out of inner loops variable creation for objects which should be reused across calls. To your question, yes you can easily update 4000 circles across each animation event loop time slice once your architecture is carefully thought through especially if you use shaders to craft your objects and avoid such computation back in the CPU
For pure speed I suggest you learn graphics by writing OpenGL/WebGL by hand instead of the higher level abstraction library Three.js ... the price of ease of use too often is higher computational load of unnecessary logic which can be cut out if written by hand
Here is a WebGL toy I built which has no Three.js ... it does real-time updates to geometry of 10's of thousands of objects as well as rendering audio using Web Audio API
https://github.com/scottstensland/webgl-3d-animation

Resources