Rendering Large number of moving objects

Hi,

I would like to know what is a good approach to render large number of movable objects.

in this topic

http://hub.jmonkeyengine.org/groups/general-2/forum/topic/vector-operations-without-creation-of-new-objects/#post-164528

EmpirePhoenix said that he use Mesh in point mode, I tried however I’m not able to edit mesh after it’s creation. I searched the wiki, and javadoc, bud nothing was working



also, the method of using Mesh in point mode (if working) would not be exactly what I want, because it would render only points. If I wond move something else - (lines, boxes, spheres, custom meshes)



I was trying how many boxes I can render using simple

myBoxes = new Geometry(“Box”, new Box(Vector3f.ZERO, 0.01f, 0.01f, 0.01f));new Geometry(“Box”, new Box(Vector3f.ZERO, 0.01f, 0.01f, 0.01f));



rootNode.attachChild(myBoxes);



It was ok until ~ 10000 moving boxes; I tried to use GeometryBatchFactory.optimize(rootNode); however it makes boxes not movable. :frowning:



I wonder - is it possible to use Tree system for this purpose?

https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:contributions:vegetationsystem:trees?s[]=instances

or Particles ? - can I control position of each particular particle?

https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:particle_emitters?s[]=particles

This a code where I tried to move points in Mesh

… see several versions of moving code in simpleUpdate(float tpf)

… I would prefer to directly modify vertexBuffer using cMesh.getBuffer(Type.Position); however I don’t know how to get it as a simple array of floats

(if it is possible?)



[java]package mygame;



import com.jme3.app.SimpleApplication;

import com.jme3.material.Material;

import com.jme3.math.Vector3f;

import com.jme3.renderer.RenderManager;

import com.jme3.scene.Geometry;



import com.jme3.scene.Mesh;

import com.jme3.scene.VertexBuffer.Type;

import com.jme3.scene.VertexBuffer;

import com.jme3.util.BufferUtils;

import com.jme3.math.FastMath;



public class Main extends SimpleApplication {



float[] vertexArray = new float[43];

float[] colorArray = new float[4
4];

Mesh cMesh = new Mesh();



public static void main(String[] args) {

Main app = new Main();

app.start();

}



@Override

public void simpleInitApp() {



Material mat = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

mat.setBoolean(“VertexColor”, true);



int colorIndex = 0;

for(int i = 0; i < 4; i++){

colorArray[colorIndex++]= FastMath.nextRandomFloat();

colorArray[colorIndex++]= FastMath.nextRandomFloat();

colorArray[colorIndex++]= FastMath.nextRandomFloat();

colorArray[colorIndex++]= 1.0f;

}



int vertexIndex = 0;

for(int i = 0; i < 4; i++){

vertexArray[vertexIndex++]= FastMath.nextRandomFloat();

vertexArray[vertexIndex++]= FastMath.nextRandomFloat();

vertexArray[vertexIndex++]= FastMath.nextRandomFloat();

}



//cMesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));

cMesh.setBuffer(Type.Position, 3, vertexArray);

cMesh.setBuffer(Type.Color, 4, colorArray);



// Set the color buffer

cMesh.setBuffer(Type.Color, 4, colorArray);

cMesh.setMode(Mesh.Mode.Points);

cMesh.setPointSize(10f);

// cMesh.setStreamed();

cMesh.setDynamic();

cMesh.updateBound();

Geometry pointMesh = new Geometry(“Points”, cMesh);

pointMesh.setMaterial(mat);

rootNode.attachChild(pointMesh);

}



@Override

public void simpleUpdate(float tpf) {

//vertexArray = cMesh.getBuffer(Type.Position);



/*

int vertexIndex = 0;

for(int i = 0; i < 4; i++){

vertexArray[vertexIndex++]= FastMath.nextRandomFloat();

vertexArray[vertexIndex++]= FastMath.nextRandomFloat();

vertexArray[vertexIndex++]= FastMath.nextRandomFloat();

}

cMesh.setBuffer(Type.Position, 3, vertexArray);

/



/


vertexArray = (float [] )cMesh.getBuffer(Type.Position);

int vertexIndex = 0;

for(int i = 0; i < 4; i++){

vertexArray[vertexIndex++]= FastMath.nextRandomFloat();

vertexArray[vertexIndex++]= FastMath.nextRandomFloat();

vertexArray[vertexIndex++]= FastMath.nextRandomFloat();

}

cMesh.setBuffer(Type.Position, 3, vertexArray);

*/



VertexBuffer buff = cMesh.getBuffer(Type.Position);

for(int i = 0; i < 4; i++){

buff.setElementComponent( i, 0, FastMath.nextRandomFloat());

buff.setElementComponent( i, 1, FastMath.nextRandomFloat());

buff.setElementComponent( i, 2, FastMath.nextRandomFloat());

}



cMesh.updateBound();

}



@Override

public void simpleRender(RenderManager rm) {

//TODO: add render code

}

}[/java]

If several boxes have the same material, perhaps you could use a BatchNode?

I believe you can still move the individual objects within the combined mesh with that.

@asija said:
... I would prefer to directly modify vertexBuffer using cMesh.getBuffer(Type.Position); however I don't know how to get it as a simple array of floats
(if it is possible?)


Note: even if it is possible it requires copying the entire buffer to give you a float[]. Not a good idea, really.

At any rate, it wouldn't be any faster than copying the buffer into an array yourself, operating on it, and then copying it back... all of which are pretty straight forward nio buffer operations.

pspeed >

Note: even if it is possible it requires copying the entire buffer to give you a float[]. Not a good idea, really.

but vertexBuffer i actually is just float array somewhere in memory, or not? I want just get pointer to it.
I mean like this
http://wiki.processing.org/w/1,000,000_points_in_OpenGL_using_Vertex_Arrays

all of which are pretty straight forward nio buffer operations.

you mean java.nio.Buffer using VertexBuffer.getData() ??
I tried, however I don't know what to do with java.nio.Buffer ? How to edit this?
And if I want just edit few points (not all) it's not effective to copy whole buffer. Is it possible to edit individial points with
buff.setElementComponent() as I tried (it wasn't working) or somehow?

could you please link or write few lines of example how to do that?

Tumaini >
Yeh, I tried GeometryBatchFactory.optimize(rootNode); as I said, but than the boxes wasn't movable.

These are native buffers. The float[] does not exist in Java but down in native code so you cannot access it directly as a Java array without copying it.



Getting a float array is not tough, though. Grab the buffer, cast it to FloatBuffer or whatever and snag the array.


Yeh, I tried GeometryBatchFactory.optimize(rootNode); as I said, but than the boxes wasn't movable.


GeometryBatchFactory is not the same as BatchNode.
In the former the mesh cannot be changed, in the latter it can, thus letting individual objects move by changing the combined mesh.
At least that's what I've gathered. :)
@Tumaini said:
GeometryBatchFactory is not the same as BatchNode.
In the former the mesh cannot be changed, in the latter it can, thus letting individual objects move by changing the combined mesh.
At least that's what I've gathered. :)

Exactly right, BatchNode allows you to use the Geometry/Node logic but you still technically just have one big mesh with one material. GeometryBatchFactory simply makes one Geometry out of multiple.

Aha, OK, I tried search BatchNode on wiki and nothing was found, so I thought that you mean GeometryBatchFactory.

Now I found some example how to use BatchNode in Forum. I tried.



However, it doesn’t give me big performance improvement

for 10000 boxes 9 fps, which is almost the same as without BatchNode ( 5 fps ).

if I use GeometryBatchFactory the prerformance increase to 59 fps, but nothing is moving.



I hope I’m doing it right:



[java]package mygame;



import com.jme3.app.SimpleApplication;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Vector3f;

import com.jme3.renderer.RenderManager;

import com.jme3.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.shape.Box;

import jme3tools.optimize.GeometryBatchFactory;

import com.jme3.scene.BatchNode;



import com.jme3.math.Quaternion;

import com.jme3.math.FastMath;

import com.jme3.light.DirectionalLight;



import java.util.logging.*;



public class Main extends SimpleApplication {



Geometry [] myBoxes;

int n = 20000;

BatchNode batchNode;



@Override

public void simpleInitApp() {

// Logger.getLogger("").setLevel(Level.SEVERE);

Logger.getLogger("").setLevel(Level.OFF);

batchNode = new BatchNode(“batch”);

Material mat = createColoredMaterial(ColorRGBA.Red, true);

Box b = new Box(Vector3f.ZERO, 0.02f, 0.02f, 0.02f);



// This code use Batch Node

myBoxes = new Geometry[n];

for (int i=0;i<n;i++){

myBoxes = new Geometry(“Box”, b);

myBoxes.setMaterial(mat);

float rnx = FastMath.nextRandomFloat()*5;

float rny = FastMath.nextRandomFloat()*5;

float rnz = FastMath.nextRandomFloat()5;

myBoxes.setLocalTranslation(rnx, rny, rnz);

batchNode.attachChild(myBoxes);

}

batchNode.batch();

rootNode.attachChild(batchNode);



/


// This code Does not use Batch Node

myBoxes = new Geometry[n];

for (int i=0;i<n;i++){

myBoxes = new Geometry(“Box”, b);

myBoxes.setMaterial(mat);

float rnx = FastMath.nextRandomFloat()*5;

float rny = FastMath.nextRandomFloat()*5;

float rnz = FastMath.nextRandomFloat()*5;

myBoxes.setLocalTranslation(rnx, rny, rnz);

rootNode.attachChild(myBoxes);

}

rootNode.attachChild(batchNode);

*/



// jme3tools.optimize.GeometryBatchFactory.optimize(rootNode);



}



private Material createColoredMaterial(ColorRGBA color, boolean lit){

if(lit){

Material material = new Material(assetManager, “Common/MatDefs/Light/Lighting.j3md”);

material.setBoolean(“UseMaterialColors”, true);

material.setColor(“Diffuse”, color);

material.setColor(“Ambient”, color);

material.setColor(“Specular”, color);

DirectionalLight dl = new DirectionalLight();

dl.setDirection(new Vector3f(1, -1.0f, -1.0f).normalizeLocal());

dl.setColor(new ColorRGBA(0.9f, 0.9f, 0.9f, 1.0f));

rootNode.addLight(dl);

return material;

}

else{

Material material = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

material.setColor(“Color”, color);

return material;

}

}





@Override

public void simpleUpdate(float tpf) {

for (int i=0;i<n;i++){

float rnx = (FastMath.nextRandomFloat()-0.5f)*0.01f;

float rny = (FastMath.nextRandomFloat()-0.5f)*0.01f;

float rnz = (FastMath.nextRandomFloat()-0.5f)*0.01f;

myBoxes.move(rnx, rny, rnz);

}

}





public static void main(String[] args) {

Main tbp = new Main();

tbp.start();

}



}[/java]

Try making a tradeoff with multiple BatchNodes. Updating the vertices of 10,000 geometries also takes some time.

I tested and I don’t see any difference depending on number of BatchNodes



for m=1,5,10,20,40 … always I get 15 fps for 20000 boxes ( … fps is higher than befeore because I’m now on other computer in work )



Do I use it right?

[java]

package mygame;



import com.jme3.app.SimpleApplication;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Vector3f;

import com.jme3.renderer.RenderManager;

import com.jme3.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.shape.Box;

import jme3tools.optimize.GeometryBatchFactory;

import com.jme3.scene.BatchNode;



import com.jme3.math.Quaternion;

import com.jme3.math.FastMath;

import com.jme3.light.DirectionalLight;



import java.util.logging.*;



public class Main extends SimpleApplication {



Geometry [] myBoxes;

int n = 40000;

int m = 40;

int k = n/m;

BatchNode [] batchNodes;



@Override

public void simpleInitApp() {

Logger.getLogger("").setLevel(Level.SEVERE);

// Logger.getLogger("").setLevel(Level.OFF);



Material mat = createColoredMaterial(ColorRGBA.Red, true);

Box b = new Box(Vector3f.ZERO, 0.02f, 0.02f, 0.02f);



batchNodes = new BatchNode[m];

myBoxes = new Geometry[n];



int in=0;

for (int im=0;im<m;im++){

batchNodes[im] = new BatchNode(“batch”);

for (int ik=0;ik<k;ik++){

myBoxes[in] = new Geometry(“Box”, b);

myBoxes[in].setMaterial(mat);

float rnx = FastMath.nextRandomFloat()*5;

float rny = FastMath.nextRandomFloat()*5;

float rnz = FastMath.nextRandomFloat()*5;

myBoxes[in].setLocalTranslation(rnx, rny, rnz);

batchNodes[im].attachChild(myBoxes[in]);

in++;

}

batchNodes[im].batch();

rootNode.attachChild(batchNodes[im]);

}



// jme3tools.optimize.GeometryBatchFactory.optimize(rootNode);



}



private Material createColoredMaterial(ColorRGBA color, boolean lit){

if(lit){

Material material = new Material(assetManager, “Common/MatDefs/Light/Lighting.j3md”);

material.setBoolean(“UseMaterialColors”, true);

material.setColor(“Diffuse”, color);

material.setColor(“Ambient”, color);

material.setColor(“Specular”, color);

DirectionalLight dl = new DirectionalLight();

dl.setDirection(new Vector3f(1, -1.0f, -1.0f).normalizeLocal());

dl.setColor(new ColorRGBA(0.9f, 0.9f, 0.9f, 1.0f));

rootNode.addLight(dl);

return material;

}

else{

Material material = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

material.setColor(“Color”, color);

return material;

}

}



@Override

public void simpleUpdate(float tpf) {

for (int i=0;i<n;i++){

float rnx = (FastMath.nextRandomFloat()-0.5f)*0.01f;

float rny = (FastMath.nextRandomFloat()-0.5f)*0.01f;

float rnz = (FastMath.nextRandomFloat()-0.5f)*0.01f;

myBoxes.move(rnx, rny, rnz);

}

}



public static void main(String[] args) {

Main tbp = new Main();

tbp.start();

}



}

[/java]

Hm… Do you get the same performance with the geometryBatchFactory (ignoring the fact that you cannot move them for now)? The issue might be they don’t get batched because the material is technically a new one each time (though the java object is the same)… Maybe try setting the material on the node to make sure its the same across geometries… @Momoko_Fan, @nehon, how is this in the BatchNode/GeometryBatchFactory now?

Do you get the same performance with the geometryBatchFactory

no, geometryBatchFactory is much faster - 59 fps for 10000boxes

Maybe try setting the material on the node to make sure its the same across geometries

I tried but it's not supported:
[java]java.lang.UnsupportedOperationException: Unsupported for now, please set the material on the geoms before batching[/java]

i tested your code and i confirm it only runs with 9 fps.



maybe you should try http://hub.jmonkeyengine.org/groups/contribution-depot-jme3/forum/topic/spritelibrary-efficient-render-of-sprites/. I had 444fps for 60000 sprites.

Maybe you get an idea how to manipulate buffers.

1 Like

your use of geometry batch factory was invalid. You tried to optimize root node = 0 fps gain.



i get 700 fps for 40000 boxes, with geometry batch factory.



[java]

@Override

public void simpleInitApp()

{

Material mat = createColoredMaterial(ColorRGBA.Red, false);

Box b = new Box(Vector3f.ZERO, 0.02f, 0.02f, 0.02f);

myBoxes = new Geometry[n];



Node node = new Node();

for (int ik = 0; ik < n; ik++)

{

myBoxes[ik] = new Geometry("Box", b);

myBoxes[ik].setMaterial(mat);

float rnx = FastMath.nextRandomFloat() * 5;

float rny = FastMath.nextRandomFloat() * 5;

float rnz = FastMath.nextRandomFloat() * 5;

myBoxes[ik].setLocalTranslation(rnx, rny, rnz);

node.attachChild(myBoxes[ik]);

}



jme3tools.optimize.GeometryBatchFactory.optimize(node);

rootNode.attachChild(node);

}

[/java]

i tested with my sprite library i only get 100 fps for 60.000 sprites :frowning:

40% of the time is spend on “FastMath.nextRandomFloat()”



From the remaining 60%:

half of the time (30%) is spend on com.jme3.scene.Geometry.updateModelBound(),

is there a way to disable that ? like having Math.infinity size model bound ? my sprites are never culled anyway.

the rest (30%) is spend on my library to construct the buffer from start.

Tralala > I tested your sprite library, That is great !!! Thank’s a lot.


your use of geometry batch factory was invalid. You tried to optimize root node = 0 fps gain.

Oh, sorry, I posted bad code, I tested also jme3tools.optimize.GeometryBatchFactory.optimize(node); as you say. I got good performance with
GeometryBatchFactory, nevertheless - it's not moving. Your Sprite library is probably solution I was looking for, Thanks again.
i tested with my sprite library i only get 100 fps for 60.000 sprites

Oh, you get much better fps than I.

in your TestDynamicSprites I get only 25fps for 60000 sprites, however this is probably just because I have weaker GPU ( NVIDIA GForce GTX 275 )

TestDynamicSprites is a stress test trying to every possible action add, delete, change size, change color in order to “break it”.



try this:

[java]

package tests.engine.sprite;



import com.jme3.app.SimpleApplication;

import com.jme3.math.ColorRGBA;

import com.jme3.math.FastMath;

import com.jme3.math.Vector3f;

import engine.sprites.Sprite;

import engine.sprites.SpriteImage;

import engine.sprites.SpriteManager;

import engine.sprites.SpriteMesh;

import engine.sprites.SpriteMesh.Strategy;

import engine.util.FileUtilities;

import java.io.File;

import java.util.ArrayList;

import java.util.logging.Level;

import java.util.logging.Logger;



//87 fps : alocate new buffer.

public class TestBubbles extends SimpleApplication

{

private SpriteManager spriteManager;

private ArrayList<Sprite> sprites = new ArrayList<Sprite>();

private SpriteImage[] npcList ;



//performance

private int MAX_SPRITES = 60000;

private Strategy strategy = SpriteMesh.Strategy.ALLOCATE_NEW_BUFFER;

private int MAX_TEXTURE_WIDTH = 1024;

private int MAX_TEXTURE_HEIGHT = 1024;



//test

private float MIN_POS = 0;

private float MAX_POS = 30;



public static void main(String[] args)

{

Logger.getLogger("").setLevel(Level.SEVERE);

TestBubbles app = new TestBubbles();

app.start();

}



@Override

public void simpleInitApp()

{

spriteManager = new SpriteManager(MAX_TEXTURE_WIDTH, MAX_TEXTURE_HEIGHT, strategy, rootNode, assetManager);

getStateManager().attach(spriteManager);

getViewPort().setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));

getFlyByCamera().setMoveSpeed(50);

getCamera().setLocation(new Vector3f(-15, 0, 55));

getCamera().lookAtDirection(new Vector3f(12, 7.5f, -15), Vector3f.UNIT_Y);



File npcLocation = new File(FileUtilities.ASSET_DIRECTORY + “2d/npc/”);

String[] fileList = npcLocation.list(FileUtilities.SUPPORTED_IMAGES);

npcList = new SpriteImage[fileList.length];

for (int i = 0; i < fileList.length; i++)

{

npcList = spriteManager.createSpriteImage(“2d/npc/” + fileList, false);

}



for (int i = 0; i < MAX_SPRITES; i++)

{

Sprite sprite = new Sprite(npcList[i%npcList.length]);

sprite.getPosition().x = MIN_POS + (float) (Math.random() * MAX_POS);

sprite.getPosition().y = MIN_POS + (float) (Math.random() * MAX_POS);

sprite.getPosition().z = MIN_POS + (float) (Math.random() * MAX_POS);

sprites.add(sprite);

}

spriteManager.trim();

}



@Override

public void simpleUpdate(float tpf)

{

super.simpleUpdate(tpf);



for (Sprite s : sprites)

{

Vector3f newPosition = s.getPosition();

newPosition.x += (FastMath.nextRandomFloat() - 0.5f) * 0.1f;

newPosition.y += (FastMath.nextRandomFloat() - 0.5f) * 0.1f;

newPosition.z += (FastMath.nextRandomFloat() - 0.5f) * 0.1f;

s.setPosition(newPosition);

}

//spriteManager.update(tpf); //cause java profiler sucks, i put it here to measure its speed.

}

}

[/java]

OK, 50fps for 60000 sprites on my computer :slight_smile:

so I hope somebody can help you remove com.jme3.scene.Geometry.updateModelBound() to make it even 3 times faster



I think, now the evaluation the equations of motion for the particles start to be the Bottleneck, if FastMath.nextRandomFloat() is to slow :smiley: … The only solution would be write glsl shader to evaluate equations of motion on GPU (like this http://www.2ld.de/gdc2004/MegaParticlesSlides.pdf )

, however, I think this performence is already good for me :smiley: