I am combining many long arrays (80 arrays of 80,000 elements each, eventually more) into several single arrays (8 arrays of 1,600,000 elements each) to be uploaded as attributes in a Three.js BufferGeometry. I struggled to get the process efficient enough not to freeze the browser. I'm past that point, but it is still painfully slow. Is there any approach that might speed this up - optimizations I should consider? I tried using push.apply, which sped the process considerably, but eventually exceeded the call stack. I'm currently working with concat, but wonder if converting the process to strings or another data structure, and then back again might help? Other ideas? I'm all ears.
Here are the code blocks in question:
var motifMinBufferSize = 80000;
function setMinimumBufferSize( pointCloudAttributeArray, itemSize, fillValue ) {
// buffers cannot be resized once they've been sent to the graphics card, so I am emulating resizing by setting a minimum buffer size that exceeds the number of vertex positions in the largest known point cloud.
supplementalArray.fill( fillValue );
var fullArray = pointCloudAttributeArray.concat( supplementalArray );
return fullArray;
}
function flattenVertexArray( array ) {
var flattenedArray = [];
for ( var i = 0; i < array.length; i++ ) {
flattenedArray.push( array[i].x, array[i].y, array[i].z );
}
return flattenedArray;
}
function concatArrays( gridArray, motifArray ) {
var newGridArray = [];
newGridArray = gridArray.concat( motifArray );
return newGridArray;
}
function compileGridOfPointCloudAttributes( ... ) {
...code to compile the attributes for a BufferGeometry representing a grid of point clouds...
// Skipping ahead in the function:
for ( var i = 0; i < 80; i++ ) {
...
// I have 8 of these attributes that gradually accumulate representing 80,000 values each for 80 different particle clouds:
var position = flattenVertexArray( motif.position );
var aGridPosition = flattenVertexArray ( motif.aGridPosition );
pointCloudGridAttributes.aPointCloudIDPerVertex = concatArrays( pointCloudGridAttributes.aMotifIDPerVertex, setMinimumBufferSize( motif.aPointCloudIDPerVertex, 1, 0 ) );
pointCloudGridAttributes.position = concatArrays( pointCloudGridAttributes.position, setMinimumBufferSize( position, 3, gridDimensions.gridWidth ) );
pointCloudGridAttributes.aGridPosition = concatArrays( pointCloudAttributes.aGridPosition, setMinimumBufferSize( motif.aGridPosition, 1, 0 ) );
...continue like this for 5 more attributes...
}
}
Context:
I'm making a visualization with Three.js composed of 80 or so particle clouds, each with a unique number of points (50,000+ points per cloud) and all composed into a single BufferGeometry for efficient rendering. I periodically swap out on point cloud for another, but learned that the buffers in a buffer geometry are not resizable once they are implemented, so I now have a a fixed, oversized section of the array dedicated to each point cloud.
Related
I am trying to spawn a set of objects on a setInterval and give each of these objects their own animation on a path (currently using requestAnimationFrame to do so). I managed to add one object and animate this on a path. With this code:
var psGeometry = new THREE.PlaneGeometry(3,2,10,1);
var psPlane = new THREE.Mesh(psGeometry, new THREE.MeshBasicMaterial({color:0x0000ff}));
scene.add(psPlane);
function animatePaper(obj = psPlane, offset= 0.007)
{
if(counter <=( 1-obj.geometry.vertices.length/2 *offset))
{
for (var i=0; i < obj.geometry.vertices.length/2; i++)
{
obj.geometry.vertices[i].y = curvePath.getPoint(counter + i * offset).y;
obj.geometry.vertices[i].z = -0.5;
obj.geometry.vertices[i + obj.geometry.vertices.length/2].y = curvePath.getPoint(counter + i * offset).y;
obj.geometry.vertices[i + obj.geometry.vertices.length/2].z = -2.5;
obj.geometry.vertices[i].x = curvePath.getPoint(counter + i * offset).x;
obj.geometry.vertices[i + obj.geometry.vertices.length/2].x = curvePath.getPoint(counter + i * offset).x;
}
obj.geometry.verticesNeedUpdate = true;
counter += 0.005;
}
else{
console.log("Removing...");
scene.remove(obj);
}
}
function animate() {
requestAnimationFrame(animate);
animatePaper(psPlane, 0.007);
render();
}
Example can be found here: jsfiddle.net.
Since this animates the object along the curvePath (see jsfiddle example), I figured that spawning these objects on an interval and applying the above code should work. Wrong!.
I tried: creating a function spawning objects and applying the above code:
setInterval(drawSheets, 1000);
function drawSheets()
{
var psGeometry = new THREE.PlaneGeometry(3,2,10,1);
var psPlane = new THREE.Mesh(psGeometry, new THREE.MeshBasicMaterial({color:0x0000ff}));
scene.add(psPlane);
setInterval(function(){animatePaper(psPlane, 0.007);}, 30);
}
I also tried on the basis of this answer:
setInterval(objArray.forEach(function(obj){setInterval(function(){animatePaper(obj);},300);}), 3000);
Expected:
Spawning multiple objects on an interval and animate each of these objects seperately over a curve.
Hopefully anyone could help me out! Cheers.
Version: Three.js r82
** EDIT: ** Small refinement. After another small test (jsfiddle). I found out that when I use setInterval on a function, it shares the same variable (thus speeding up the animation). Since this is part of the problem I would like to ask if someone knows how to make these variables local to an object.
Consider creating an array containing each of your Path and Plane objects (or perhaps one array for Paths and one array for Planes) along with their distinctive offsets or other values, then loop though these in an update function in your animation loop, running each through your animatePaper function.
In pseudo code:
var planesAndMeshesArray = [
{ path1 : (your plane here), plane1 : (your curved mesh here), offset : (an offset value), extrudeSettings : (your settings object here) },
{ path2 : (your plane here), plane2 : (your curved mesh here), offset : (an offset value), extrudeSettings : (your settings object here) },
{ path3 : (your plane here), plane3 : (your curved mesh here), offset : (an offset value), extrudeSettings : (your settings object here) },
...]
- create a loop to write the above array with random values in an appropriate range to suit the effects you're looking for
- loop through the above array to add each of the meshes and planes to the scene
function update() {
- update each object by looping through the above array through your `animatePaper` function. It works as a handy set of pointers to each of the objects in your scene - if you change them in the array, they will change in your scene.
- also update your controls
}
function animate() {
requestAnimationFrame(animate);
update();
render();
}
Going one step further, you can write object-oriented Javascript to create each of your curve-and-paper objects. I'd recommend starting with the array first and adding further complexity as needed.
I need to scan through every pixel in a canvas image and do some fiddling with the colors etc. For optimal performance, should I grab all the data in one go and work on it through the array? Or should I call each pixel as I work on it.
So basically...
data = context.getImageData(x, y, height, width);
VS
data = context.getImageData(x, y, 1, 1); //in a loop height*width times.
You'll get much higher performances by grabbing the image all at once since :
a) a (contiguous) acces to an array is way faster than a function call.
b) especially when this function isa method of a DOM object having some overhead.
c) and there might be buffer refresh issues that might delay response (if canvas is
on sight... or not depending on double buffering implementation).
So go for a one-time grab.
I'll suggest you look into Javascript Typed Arrays to get the most of the
imageData result.
If i may quote myself, look at how you can handle pixels fast in this old post of mine
(look after 2) ):
Nice ellipse on a canvas?
(i quoted the relevant part below : )
You can get a UInt32Array view on your ImageData with :
var myGetImageData = myTempCanvas.getImageData(0,0,sizeX, sizeY);
var sourceBuffer32 = new Uint32Array(myGetImageData.data.buffer);
then sourceBuffer32[i] contains Red, Green, Blue, and transparency packed into one unsigned 32 bit int. Compare it to 0 to know if pixel is non-black ( != (0,0,0,0) )
OR you can be more precise with a Uint8Array view :
var myGetImageData = myTempCanvas.getImageData(0,0,sizeX, sizeY);
var sourceBuffer8 = new Uint8Array(myGetImageData.data.buffer);
If you deal only with shades of grey, then R=G=B, so watch for
sourceBuffer8[4*i]>Threshold
and you can set the i-th pixel to black in one time using the UInt32Array view :
sourceBuffer32[i]=0xff000000;
set to any color/alpha with :
sourceBuffer32[i]= (A<<24) | (B<<16) | (G<<8) | R ;
or just to any color :
sourceBuffer32[i]= 0xff000000 | (B<<16) | (G<<8) | R ;
(be sure R is rounded).
Listening to #Ken's comment, yes endianness can be an issue when you start fighting with bits 32 at a time.
Most computer are using little-endian, so RGBA becomes ABGR when dealing with them 32bits a once.
Since it is the vast majority of systems, if dealing with 32bit integer assume this is the case,
and you can -for compatibility- reverse your computation before writing the 32 bits results on Big endian systems.
Let me share those two functions :
function isLittleEndian() {
// from TooTallNate / endianness.js. https://gist.github.com/TooTallNate/4750953
var b = new ArrayBuffer(4);
var a = new Uint32Array(b);
var c = new Uint8Array(b);
a[0] = 0xdeadbeef;
if (c[0] == 0xef) { isLittleEndian = function() {return true }; return true; }
if (c[0] == 0xde) { isLittleEndian = function() {return false }; return false; }
throw new Error('unknown endianness');
}
function reverseUint32 (uint32) {
var s32 = new Uint32Array(4);
var s8 = new Uint8Array(s32.buffer);
var t32 = new Uint32Array(4);
var t8 = new Uint8Array(t32.buffer);
reverseUint32 = function (x) {
s32[0] = x;
t8[0] = s8[3];
t8[1] = s8[2];
t8[2] = s8[1];
t8[3] = s8[0];
return t32[0];
}
return reverseUint32(uint32);
};
Additionally to what GameAlchemist said, if you want to get or set all the colors of a pixel simultaneously, but you don't want to check endianness, you can use a DataView:
var data = context.getImageData(0, 0, canvas.width, canvas.height);
var view = new DataView(data.data.buffer);
// Read or set pixel (x,y) as #RRGGBBAA (big endian)
view.getUint32(4 * (x + y*canvas.width));
view.setUint32(4 * (x + y*canvas.width), 0xRRGGBBAA);
// Read or set pixel (x,y) as #AABBGGRR (little endian)
view.getUint32(4 * (x + y*canvas.width), true);
view.setUint32(4 * (x + y*canvas.width), 0xAABBGGRR, true);
// Save changes
ctx.putImageData(data, 0, 0);
It depends on what exactly you're doing, but I'd suggest grabbing it all at once, and then looping through it.
Grabbing it all at once is faster than grabbing it pixel by pixel, since searching through an array is a lot faster than searching through a canvas, once for each pixel.
If you're really in need of speed, look into web workers. You can set each one to grab a specific section of the canvas, and since they can run simultaneously, they'll make much better use out of your CPU.
getImageData() isn't really slow enough for you to notice the difference if you were to grab it all at once or individually, in my experiences using the function.
I am trying to run an animation from a JSON file. I am using a custom JSON loader, (i.e. not the one included with three.js).
So I have an object named frames, which contain many frames, all of them have shape information, and a simulation_matrix, which contains data required for animation in the form of a 4by4 transformation matrix(generated from a python script).
So I am using this code for animation ..
and this is a sample JSON script to load.
// This method is for adding static shapes
// This works perfectly fine ..
parent.add_shape = function(frame)
{
var material = new THREE.MeshLambertMaterial({
color: frame.shape.color,
wireframe: true,
wireframeLinewidth: 0.1,
opacity: 0.5
})
var geometry = new THREE.CylinderGeometry(frame.shape.radius,frame.shape.radius,frame.shape.height,50,50);
// mesh_dict dictionary maps a mesh(shape) to its frame
parent.mesh_dict[frame] = new THREE.Mesh(geometry,material);
var init_orientation = frame.simulation_matrix[0];
var orienter = new THREE.Matrix4();
orienter.elements = [];
//Since simulation_matrix is generated from python, it is a
// list of lists, We need to push it to the elemens of Matrix4 manually ..
for(var i in init_orientation)
{
for(var j in init_orientation[i])
{
orienter.elements.push(init_orientation[i][j]) ;
}
}
parent.mesh_dict[frame].applyMatrix(new THREE.Matrix4());
parent.mesh_dict[frame].applyMatrix(orienter);
parent.scene.add(parent.mesh_dict[frame]);
parent.renderer.render(parent.scene,parent.camera);
}
// This method basically takes the meshes defined in add_shape, and
// applies simulation matrix to it, and requests animation frame for
// animation.
parent.animate = function()
{
for(var frame in JSONObj.frames)
{
// defining simulation_matrix in a var.
var matrix = JSONObj.frames[frame].simulation_matrix[parent.animation_counter];
var animation_matrix = new THREE.Matrix4();
animation_matrix.elements = [];
// pushing it to a Matrix4
for(var i in matrix)
{
for(var j in matrix[i])
{
animation_matrix.elements.push(matrix[i][j]) ;
}
}
console.log(animation_matrix);
console.log(animation_matrix.elements);
// Making sure we are not applying matrix to the earlier transform
//mesh_dict is a dictionary of meshes, used in creating shapes,mapped to the
//frame which contains them
parent.mesh_dict[JSONObj.frames[frame]].applyMatrix(new THREE.Matrix4());
// now applying transform, after setting to identity matrix ...
parent.mesh_dict[JSONObj.frames[frame]].applyMatrix(animation_matrix);
}
console.log(parent.animation_counter);
//update timestep ...
parent.animation_counter++;
// This is to loop over again and again ...
// assuming 10 animations frames
if(parent.animation_counter == 10){ parent.animation_counter = 0; }
requestAnimationFrame(parent.animate);
}
The problem is that I am able to create the multiple shapes, but when I apply simulation matrix to them in the loop, only one of them is animating, that too in very unexpected manner.
Well I have figured out what was wrong. Somehow, all the dictionary parent.mesh_dict[] keys were mapped to a same single object, instead of all objects as required. Now I debugged it, and it is working like a charm. Also your point is valid #WestLangley, as I now use mesh.matrix.identity() to get things done. Thanks, I will close this question now.
I'm writing a 2D gravity simulation game and I'm trying to add save/load functionality. In the game I store all of the current planets in an array. Each planet is represented by a Body object which contains the coordinates, mass, and motion vector of the planet. It also stores an array of the last 100 coordinates of the planet in order to draw the planet's trail on the screen.
I want to use JSON.stringify() to serialize the planets array. I'd like to save the first attributes of each planet (mass, location, motion) but I don't need to save the last 100 coordinates (the trail array). I don't want to completely delete the coordinates otherwise the trails will disappear from the screen. Can I stringify only a portion of each object? If not, can I remove that portion of the JSON string after it's been encoded? Or should I move the coordinates elsewhere during the save process then copy them back into each planet once it's been saved?
In modern web browsers you can use Array#map.
var serialized = JSON.stringify(planets.map(function(planet){
return {
mass: planet.mass,
location: planet.location,
motion: planet.motion
};
}));
Or, the equivalent using a for loop.
try it this way
var saved = JSON.stringify( {mass:body.mass,location:body.location,motion:body.motion} );
it shall give you just the three parts as a json string.
A bit more extended you could provide your body class such an export function.
For example:
Bodyclass.export = function( toexport ) {
if ( undefined === toexport || toexport.constructor != Array ) {
var toexport = [ 'mass', 'location', 'motion' ];
}
var export = {};
for ( var i = 0; i < toexport; i++) {
export[ toexport[ i ] ] = this[ toexport[ i ] ];
]
}
var saved = JSON.stringify( body.export() );
The best would be to create both a serialization and deserialization method. This will allow you to create the most efficient storage format while still allowing you to reconstruct as much of the objects as you deem necessary.
You can use export/import, save/restore, serialize/deserialize terminology, whichever you see fit.
Having methods like this will increase you maintainability in the long run as well.
You can use second parameter of JSON.stringify (replacer)
const planet = {
name: "Pluto",
lastCoords: [[0, 0], [1,1,]]
}
const json = JSON.stringify(planet, (key, value) => key === "lastCoords" ? undefined : value)
// json === {"name":"Pluto"}
I am working off of a 480x480px sprite, with 225 individual 32x32px images laid out 15x15 grid so it's a perfectly square jpeg. I am trying to:
Create a way to generate random numbers in the background-position fields that will inject a random number within the range and increment I specify (range would be 0-480, inc is 32). This is in order to call random sprite background images for the div.
Generate a similar random number (just range) to be used in the timing of a div transform: rotateY(180deg) style. This is in order to get the div to "flip" and return. I have a front/back transition that already works in the code, but of course the timing is uniform.
Find a way to clone or duplicate that div, and its contents to fill a containing one.
I've been working to try and make something that would work like:
<div class="face back" style="
background-position-x: [myNumber]px;
background-position-y: [myNumber]px;
"></div>
Using a script such as:
var myNumber = Math.floor((Math.random()*14)+1);
myNumber = myNumber *32;
I know I haven't been going about it the right way at all.
The desired result is a div (background or footer maybe) with dozens (maybe hundreds) of 32x32px seemingly random images. Each one transitioning at different intervals to different images. Obviously I'm looking for any elegant way.
I am not that sure about what you are about but I would suggest the following procedure:
get an reference to the <div> that should be filled up with the tiled grid of images.
get its width and height and modulo divide (%) these values by the tile width plus one:
var rect = div.getBoundingClientRect();
var numTiles = ( rect.width % tileWidth + 1 ) * ( rect.height % tileWidth + 1 );
create numTiles is <div> elements and set their background position randomly:
var bgTop = Math.floor( Math.random() * numTilesInMap );
var bgLeft = Math.floor( Math.random() * numTilesInMap );
I would simply float all the div elements and append them to the container, thereby the container should have overflow : hidden because the grid is always a big bigger.
If you want to have unique random numbers and only as much tiles as images available in the map, than you can do something like this:
var randoms = {};
for( var i = 0; i < numImages.length; i++ ){
randoms[i] = i;
};
var rand, index;
while( Object.keys( randoms ).length > 0 ){
index = Math.round( Math.random() * ( Object.keys( randoms ).length - 1 ) );
rand = randoms[ index ];
//do something with the random number here
delete randoms[ index ];
}
I have not tested this, it should just deal as suggestion how to generate random unique numbers. It can also be done with an array what perhaps is faster.
Greetings...
Edit
What I forgot to mention here is that next to the »div« solution you can also create a <canvas> and use the drawImage() method with random values, where you just draw regions of the tile map on the canvas as long as it is full. That reduces the size of the DOM and you can create the canvas it the exact size of the container. If you do not catch any events that lead to the area of the tilemap or another image depended reference this might be the fastest solution.
procces= function() {
TimeSpeed=100;
startRange=1;
stopRange=100;
incrementRenge=5;
setTimeout(function run(){
if(startRange >=stopRange) {
console.info("stop");
}
else {
console.info("rinning");
startRange+=Math.floor(Math.random()*incrementRenge);
$(".face back").css({"background-position":startRange+"px ,"+startRange+"px"});
setTimeout(function() {
run()
},TimeSpeed)
}
},TimeSpeed)
}