- BatchNode can now batch a scene graph with several materials. It creates a batch by material
- Added a SimpleBatchNode that batch only geometries (no sub node graph) for better performances
- removed RF_REFRESHBATCH from Spatial as it's no longer needed
- changed test cases a bit

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8527 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
rem..om 13 years ago
parent d81cb1f812
commit 7193c0b2d3
  1. 303
      engine/src/core/com/jme3/scene/BatchNode.java
  2. 29
      engine/src/core/com/jme3/scene/Geometry.java
  3. 56
      engine/src/core/com/jme3/scene/SimpleBatchNode.java
  4. 3
      engine/src/core/com/jme3/scene/Spatial.java
  5. 7
      engine/src/test/jme3test/batching/TestBatchNode.java
  6. 91
      engine/src/test/jme3test/batching/TestBatchNodeCluster.java
  7. 9
      engine/src/test/jme3test/batching/TestBatchNodeTower.java

@ -31,8 +31,6 @@
*/
package com.jme3.scene;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.bounding.BoundingVolume;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
@ -40,31 +38,33 @@ import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.material.Material;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.IntMap.Entry;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* BatchNode holds a geometry that is a batched version of all geometries that are in its sub scenegraph.
* this geometry is directly attached to the node in the scene graph.
* usage is like any other node except you have to call the {@link #batch()} method once all geoms have been attached to the sub scene graph (see todo more automagic for further enhancements)
* BatchNode holds geometrie that are batched version of all geometries that are in its sub scenegraph.
* There is one geometry per different material in the sub tree.
* this geometries are directly attached to the node in the scene graph.
* usage is like any other node except you have to call the {@link #batch()} method once all geoms have been attached to the sub scene graph and theire material set
* (see todo more automagic for further enhancements)
* all the geometry that have been batched are set to {@link CullHint#Always} to not render them.
* the sub geometries can be transformed as usual their transforms are used to update the mesh of the geometryBatch.
* sub geoms can be removed but it may be slower than the normal spatial removing
* Sub geoms can be added after the batch() method has been called but won't be batched and will be rendered as normal geometries.
* To integrated them in the batch you have to call the batch() method again on the batchNode.
* To integrate them in the batch you have to call the batch() method again on the batchNode.
*
* TODO account for sub-BatchNodes
* TODO account for geometries that have different materials
* TODO normal or tangents or both looks a bit weird
* TODO more automagic (batch when needed in the updateLigicalState)
* @author Nehon
@ -73,12 +73,13 @@ public class BatchNode extends Node implements Savable {
private static final Logger logger = Logger.getLogger(BatchNode.class.getName());
/**
* the geometry holding the batched mesh
* the map of geometry holding the batched meshes
*/
protected Geometry batch;
protected Material material;
private boolean needMeshUpdate = false;
protected Map<Material, Batch> batches = new HashMap<Material, Batch>();
/**
* Construct a batchNode
*/
public BatchNode() {
super();
}
@ -109,12 +110,17 @@ public class BatchNode extends Node implements Savable {
for (Spatial child : children.getArray()) {
child.updateGeometricState();
}
if (needMeshUpdate) {
batch.getMesh().updateBound();
batch.updateWorldBound();
needMeshUpdate = false;
for (Batch batch : batches.values()) {
if (batch.needMeshUpdate) {
batch.geometry.getMesh().updateBound();
batch.geometry.updateWorldBound();
batch.needMeshUpdate = false;
}
}
}
if ((refreshFlags & RF_BOUND) != 0) {
@ -124,9 +130,14 @@ public class BatchNode extends Node implements Savable {
assert refreshFlags == 0;
}
protected Transform getTransforms(Geometry geom){
return geom.getWorldTransform();
}
protected void updateSubBatch(Geometry bg) {
Batch batch = batches.get(bg.getMaterial());
if (batch != null) {
Mesh mesh = batch.getMesh();
Mesh mesh = batch.geometry.getMesh();
FloatBuffer buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Position).getData();
doTransformVerts(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat);
@ -144,40 +155,8 @@ public class BatchNode extends Node implements Savable {
mesh.getBuffer(VertexBuffer.Type.Tangent).updateData(buf);
}
needMeshUpdate = true;
}
}
@Override
protected void setTransformRefresh() {
refreshFlags |= RF_TRANSFORM;
setBoundRefresh();
for (Spatial child : children.getArray()) {
if ((child.refreshFlags & RF_TRANSFORM) != 0) {
continue;
}
innerTransformRefresh(child);
//child.setTransformRefresh();
}
batch.needMeshUpdate = true;
}
//
private void innerTransformRefresh(Spatial s) {
s.refreshFlags |= RF_TRANSFORM;
s.setBoundRefresh();
if (s instanceof Node) {
Node n = (Node) s;
for (Spatial child :((SafeArrayList<Spatial>) n.getChildren()).getArray()) {
if ((child.refreshFlags & RF_TRANSFORM) != 0) {
continue;
}
innerTransformRefresh(child);
}
}
}
/**
@ -185,121 +164,146 @@ public class BatchNode extends Node implements Savable {
* every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call
*/
public void batch() {
doBatch();
//we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice
for (Batch batch : batches.values()) {
batch.geometry.setIgnoreTransform(true);
}
}
List<Geometry> tmpList = new ArrayList<Geometry>();
protected void doBatch() {
///List<Geometry> tmpList = new ArrayList<Geometry>();
Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();
gatherGeomerties(matMap, this);
batches.clear();
int nbGeoms = 0;
for (Material material : matMap.keySet()) {
Mesh m = new Mesh();
populateList(tmpList, this);
mergeGeometries(m, tmpList);
List<Geometry> list = matMap.get(material);
nbGeoms += list.size();
mergeGeometries(m, list);
Batch batch = new Batch();
batch.geometry = new Geometry(name + "-batch" + batches.size());
batch.geometry.setMaterial(material);
this.attachChild(batch.geometry);
if (batch == null) {
batch = new Geometry(name + "-batch");
batch.setMaterial(material);
this.attachChild(batch);
batch.geometry.setMesh(m);
batch.geometry.getMesh().updateCounts();
batch.geometry.getMesh().updateBound();
batches.put(material, batch);
}
batch.setMesh(m);
batch.getMesh().updateCounts();
batch.getMesh().updateBound();
logger.log(Level.INFO, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()});
}
private void populateList(List<Geometry> list, Spatial n) {
private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n) {
if (n instanceof Geometry) {
if (n != batch) {
list.add((Geometry) n);
if (n.getClass() == Geometry.class) {
if (!isBatch(n)) {
Geometry g = (Geometry) n;
if (g.getMaterial() == null) {
throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching");
}
List<Geometry> list = map.get(g.getMaterial());
if (list == null) {
list = new ArrayList<Geometry>();
map.put(g.getMaterial(), list);
}
list.add(g);
}
} else if (n instanceof Node) {
for (Spatial child : ((Node) n).getChildren()) {
populateList(list, child);
if (child instanceof BatchNode) {
continue;
}
gatherGeomerties(map, child);
}
}
}
private boolean isBatch(Spatial s) {
for (Batch batch : batches.values()) {
if (batch.geometry == s) {
return true;
}
}
return false;
}
/**
* Sets the material to use for this geometry.
* Sets the material to the all the batches of this BatchNode
* use setMaterial(Material material,int batchIndex) to set a material to a specific batch
*
* @param material the material to use for this geometry
*/
@Override
public void setMaterial(Material material) {
super.setMaterial(material);
if (batch != null) {
batch.setMaterial(material);
}
this.material = material;
// for (Batch batch : batches.values()) {
// batch.geometry.setMaterial(material);
// }
throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching");
}
/**
* Returns the material that is used for this geometry.
* Returns the material that is used for the first batch of this BatchNode
*
* @return the material that is used for this geometry
* use getMaterial(Material material,int batchIndex) to get a material from a specific batch
*
* @return the material that is used for the first batch of this BatchNode
*
* @see #setMaterial(com.jme3.material.Material)
*/
public Material getMaterial() {
return material;
}
/**
* @return The bounding volume of the mesh, in model space.
*/
public BoundingVolume getModelBound() {
if (batch != null) {
return batch.getMesh().getBound();
if (!batches.isEmpty()) {
Batch b = batches.get(batches.keySet().iterator().next());
return b.geometry.getMaterial();
}
return super.getWorldBound();
return null;//material;
}
/**
* This version of clone is a shallow clone, in other words, the
* same mesh is referenced as the original geometry.
* Exception: if the mesh is marked as being a software
* animated mesh, (bind pose is set) then the positions
* and normals are deep copied.
*/
@Override
public BatchNode clone(boolean cloneMaterial) {
BatchNode clone = (BatchNode) super.clone(cloneMaterial);
clone.batch = batch.clone(cloneMaterial);
return clone;
}
/**
* This version of clone is a shallow clone, in other words, the
* same mesh is referenced as the original geometry.
* Exception: if the mesh is marked as being a software
* animated mesh, (bind pose is set) then the positions
* and normals are deep copied.
*/
@Override
public BatchNode clone() {
return clone(true);
}
/**
* Creates a deep clone of the geometry,
* this creates an identical copy of the mesh
* with the vertexbuffer data duplicated.
*/
@Override
public Spatial deepClone() {
BatchNode clone = clone(true);
clone.batch = (Geometry) batch.deepClone();
return clone;
}
// /**
// * Sets the material to the a specific batch of this BatchNode
// *
// *
// * @param material the material to use for this geometry
// */
// public void setMaterial(Material material,int batchIndex) {
// if (!batches.isEmpty()) {
//
// }
//
// }
//
// /**
// * Returns the material that is used for the first batch of this BatchNode
// *
// * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
// *
// * @return the material that is used for the first batch of this BatchNode
// *
// * @see #setMaterial(com.jme3.material.Material)
// */
// public Material getMaterial(int batchIndex) {
// if (!batches.isEmpty()) {
// Batch b = batches.get(batches.keySet().iterator().next());
// return b.geometry.getMaterial();
// }
// return null;//material;
// }
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
if (material != null) {
oc.write(material.getAssetName(), "materialName", null);
}
oc.write(material, "material", null);
//
// if (material != null) {
// oc.write(material.getAssetName(), "materialName", null);
// }
// oc.write(material, "material", null);
}
@ -309,23 +313,23 @@ public class BatchNode extends Node implements Savable {
InputCapsule ic = im.getCapsule(this);
material = null;
String matName = ic.readString("materialName", null);
if (matName != null) {
// Material name is set,
// Attempt to load material via J3M
try {
material = im.getAssetManager().loadMaterial(matName);
} catch (AssetNotFoundException ex) {
// Cannot find J3M file.
logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.",
matName);
}
}
// If material is NULL, try to load it from the geometry
if (material == null) {
material = (Material) ic.readSavable("material", null);
}
// material = null;
// String matName = ic.readString("materialName", null);
// if (matName != null) {
// // Material name is set,
// // Attempt to load material via J3M
// try {
// material = im.getAssetManager().loadMaterial(matName);
// } catch (AssetNotFoundException ex) {
// // Cannot find J3M file.
// logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.",
// matName);
// }
// }
// // If material is NULL, try to load it from the geometry
// if (material == null) {
// material = (Material) ic.readSavable("material", null);
// }
}
@ -418,9 +422,6 @@ public class BatchNode extends Node implements Savable {
for (Geometry geom : geometries) {
Mesh inMesh = geom.getMesh();
if (geom.getMaterial() != null && material == null) {
material = geom.getMaterial();
}
geom.batch(this, globalVertIndex);
int geomVertCount = inMesh.getVertexCount();
@ -532,4 +533,10 @@ public class BatchNode extends Node implements Savable {
}
vars.release();
}
protected class Batch {
Geometry geometry;
boolean needMeshUpdate = false;
}
}

@ -79,7 +79,7 @@ public class Geometry extends Spatial {
/**
* the previous transforms of the geometry used to compute world transforms
*/
protected Transform prevLocalTransform = null;
protected Transform prevBatchTransforms = null;
/**
* the cached offset matrix used when the geometry is batched
*/
@ -287,11 +287,12 @@ public class Geometry extends Spatial {
super.updateWorldTransforms();
computeWorldMatrix();
if ((refreshFlags & RF_REFRESHBATCH) != 0) {
if (isBatched()) {
computeOffsetTransform();
batchNode.updateSubBatch(this);
prevLocalTransform.set(localTransform);
refreshFlags &= ~RF_REFRESHBATCH;
prevBatchTransforms.set(batchNode.getTransforms(this));
}
// geometry requires lights to be sorted
worldLights.sort(true);
@ -305,10 +306,9 @@ public class Geometry extends Spatial {
protected void batch(BatchNode node, int startIndex) {
this.batchNode = node;
this.startIndex = startIndex;
prevLocalTransform = new Transform();
prevBatchTransforms = new Transform();
cachedOffsetMat = new Matrix4f();
setCullHint(CullHint.Always);
refreshFlags |= RF_REFRESHBATCH;
}
/**
@ -316,7 +316,7 @@ public class Geometry extends Spatial {
*/
protected void unBatch() {
this.startIndex = 0;
prevLocalTransform = null;
prevBatchTransforms = null;
cachedOffsetMat = null;
//once the geometry is removed from the screnegraph we call batch on the batchNode before unreferencing it.
this.batchNode.batch();
@ -343,21 +343,21 @@ public class Geometry extends Spatial {
// Compute the cached world matrix
cachedOffsetMat.loadIdentity();
cachedOffsetMat.setRotationQuaternion(prevLocalTransform.getRotation());
cachedOffsetMat.setTranslation(prevLocalTransform.getTranslation());
cachedOffsetMat.setRotationQuaternion(prevBatchTransforms.getRotation());
cachedOffsetMat.setTranslation(prevBatchTransforms.getTranslation());
Matrix4f scaleMat = vars.tempMat4;
scaleMat.loadIdentity();
scaleMat.scale(prevLocalTransform.getScale());
scaleMat.scale(prevBatchTransforms.getScale());
cachedOffsetMat.multLocal(scaleMat);
cachedOffsetMat.invertLocal();
tmpMat.loadIdentity();
tmpMat.setRotationQuaternion(localTransform.getRotation());
tmpMat.setTranslation(localTransform.getTranslation());
tmpMat.setRotationQuaternion(batchNode.getTransforms(this).getRotation());
tmpMat.setTranslation(batchNode.getTransforms(this).getTranslation());
scaleMat.loadIdentity();
scaleMat.scale(localTransform.getScale());
scaleMat.scale(batchNode.getTransforms(this).getScale());
tmpMat.multLocal(scaleMat);
tmpMat.mult(cachedOffsetMat, cachedOffsetMat);
@ -373,9 +373,6 @@ public class Geometry extends Spatial {
protected void setTransformRefresh() {
refreshFlags |= RF_TRANSFORM;
setBoundRefresh();
if (isBatched()) {
refreshFlags |= RF_REFRESHBATCH;
}
}
/**

@ -0,0 +1,56 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.scene;
import com.jme3.math.Transform;
/**
*
* SimpleBatchNode comes with some restrictions, but can yield better performances.
* Geometries to be batched has to be attached directly to the BatchNode
* You can't attach a Node to a SimpleBatchNode
* SimpleBatchNode is recommended when you have a large number of geometries using the same material that does not require a complex scene graph structure.
* @see BatchNode
* @author Nehon
*/
public class SimpleBatchNode extends BatchNode {
public SimpleBatchNode() {
super();
}
public SimpleBatchNode(String name) {
super(name);
}
@Override
public int attachChild(Spatial child) {
if (!(child instanceof Geometry)) {
throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child of type Geometry, use BatchMode.Complex to use a complex structure");
}
return super.attachChild(child);
}
@Override
protected void setTransformRefresh() {
refreshFlags |= RF_TRANSFORM;
setBoundRefresh();
for (Batch batch : batches.values()) {
batch.geometry.setTransformRefresh();
}
}
protected Transform getTransforms(Geometry geom){
return geom.getLocalTransform();
}
@Override
public void batch() {
doBatch();
}
}

@ -112,8 +112,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Asset {
*/
protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
RF_BOUND = 0x02,
RF_LIGHTLIST = 0x04, // changes in light lists
RF_REFRESHBATCH = 0x08; // chamge in geometry transforms that require refreshing the batched mesh
RF_LIGHTLIST = 0x04; // changes in light lists
protected CullHint cullHint = CullHint.Inherit;

@ -16,6 +16,7 @@ import com.jme3.scene.BatchNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.system.NanoTimer;
import com.jme3.util.TangentBinormalGenerator;
/**
@ -33,6 +34,7 @@ public class TestBatchNode extends SimpleApplication {
@Override
public void simpleInitApp() {
timer = new NanoTimer();
batch = new BatchNode("theBatchNode");
/**
@ -42,6 +44,7 @@ public class TestBatchNode extends SimpleApplication {
Box boxshape4 = new Box(Vector3f.ZERO, 1f, 1f, 1f );
cube = new Geometry("cube1", boxshape4);
Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
cube.setMaterial(mat);
// Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
// mat.setColor("Diffuse", ColorRGBA.Blue);
// mat.setBoolean("UseMaterialColors", true);
@ -51,7 +54,7 @@ public class TestBatchNode extends SimpleApplication {
*/
Box box = new Box(Vector3f.ZERO, 1f, 1f, 1f);
cube2 = new Geometry("cube2", box);
cube2.setMaterial(mat);
TangentBinormalGenerator.generate(cube);
TangentBinormalGenerator.generate(cube2);
@ -61,7 +64,7 @@ public class TestBatchNode extends SimpleApplication {
// n.attachChild(cube2);
batch.attachChild(cube);
batch.attachChild(cube2);
batch.setMaterial(mat);
// batch.setMaterial(mat);
batch.batch();
rootNode.attachChild(batch);
cube.setLocalTranslation(3, 0, 0);

@ -22,6 +22,7 @@ import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.BloomFilter;
import com.jme3.scene.BatchNode;
import com.jme3.scene.Node;
import com.jme3.scene.SimpleBatchNode;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.Arrow;
import com.jme3.system.NanoTimer;
@ -60,13 +61,8 @@ public class TestBatchNodeCluster extends SimpleApplication {
protected int dynamic = 4;
protected static AppSettings settingst;
protected boolean isTrue = true;
private int lineLength = 50;
protected BatchNode blue;
protected BatchNode brown;
protected BatchNode pink;
protected BatchNode orange;
protected BatchNode batchNode;
Material mat1;
Material mat2;
Material mat3;
@ -77,34 +73,33 @@ public class TestBatchNodeCluster extends SimpleApplication {
@Override
public void simpleInitApp() {
timer = new NanoTimer();
batchNode = new SimpleBatchNode("BatchNode");
blue = new BatchNode("blue");
brown = new BatchNode("brown");
pink = new BatchNode("pink");
orange = new BatchNode("orange");
xPosition.add(0);
yPosition.add(0);
zPosition.add(0);
randomGenerator();
mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat1.setColor("Color", ColorRGBA.White);
mat1.setColor("GlowColor", ColorRGBA.Blue.mult(10));
blue.setMaterial(mat1);
mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat2.setColor("Color", ColorRGBA.White);
mat2.setColor("GlowColor", ColorRGBA.Red.mult(10));
brown.setMaterial(mat2);
mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat3.setColor("Color", ColorRGBA.White);
mat3.setColor("GlowColor", ColorRGBA.Yellow.mult(10));
pink.setMaterial(mat3);
mat4 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat4.setColor("Color", ColorRGBA.White);
mat4.setColor("GlowColor", ColorRGBA.Orange.mult(10));
orange.setMaterial(mat4);
randomGenerator();
//rootNode.attachChild(SkyFactory.createSky(
// assetManager, "Textures/SKY02.zip", false));
inputManager.addMapping("Start Game", new KeyTrigger(KeyInput.KEY_J));
@ -115,24 +110,17 @@ public class TestBatchNodeCluster extends SimpleApplication {
cam.setRotation(new Quaternion(0.022630932f, 0.9749435f, -0.18736298f, 0.11776358f));
blue.batch();
brown.batch();
pink.batch();
orange.batch();
batchNode.batch();
terrain = new Node("terrain");
terrain.setLocalTranslation(50, 0, 50);
terrain.attachChild(blue);
terrain.attachChild(brown);
terrain.attachChild(pink);
terrain.attachChild(orange);
terrain.attachChild(batchNode);
flyCam.setMoveSpeed(100);
rootNode.attachChild(terrain);
Vector3f pos = new Vector3f(-40, 0, -40);
blue.setLocalTranslation(pos);
brown.setLocalTranslation(pos);
pink.setLocalTranslation(pos);
orange.setLocalTranslation(pos);
batchNode.setLocalTranslation(pos);
Arrow a = new Arrow(new Vector3f(0, 50, 0));
@ -159,35 +147,37 @@ public class TestBatchNodeCluster extends SimpleApplication {
box.setLocalTranslation(new Vector3f(xPosition.get(xPosition.size() - 1),
yPosition.get(yPosition.size() - 1),
zPosition.get(zPosition.size() - 1)));
batchNode.attachChild(box);
if (i < 500) {
blue.attachChild(box);
box.setMaterial(mat1);
} else if (i < 1000) {
brown.attachChild(box);
box.setMaterial(mat2);
} else if (i < 1500) {
pink.attachChild(box);
box.setMaterial(mat3);
} else {
orange.attachChild(box);
}
box.setMaterial(mat4);
}
}
public BatchNode randomBatch() {
int randomn = rand.nextInt(4);
if (randomn == 0) {
return blue;
} else if (randomn == 1) {
return brown;
} else if (randomn == 2) {
return pink;
} else if (randomn == 3) {
return orange;
}
return null;
}
// public BatchNode randomBatch() {
//
// int randomn = rand.nextInt(4);
// if (randomn == 0) {
// return blue;
// } else if (randomn == 1) {
// return brown;
// } else if (randomn == 2) {
// return pink;
// } else if (randomn == 3) {
// return orange;
// }
// return null;
// }
public ColorRGBA randomColor() {
ColorRGBA color = ColorRGBA.Black;
int randomn = rand.nextInt(4);
@ -334,28 +324,23 @@ public class TestBatchNodeCluster extends SimpleApplication {
int random = rand.nextInt(2000);
float mult1 = 1.0f;
float mult2 = 1.0f;
BatchNode b = null;
if (random < 500) {
b = blue;
mult1 = 1.0f;
mult2 = 1.0f;
} else if (random < 1000) {
b = brown;
mult1 = -1.0f;
mult2 = 1.0f;
} else if (random < 1500) {
b = pink;
mult1 = 1.0f;
mult2 = -1.0f;
} else if (random <= 2000) {
b = orange;
mult1 = -1.0f;
mult2 = -1.0f;
}
box = b.getChild("Box" + random);
box = batchNode.getChild("Box" + random);
if (box != null) {
Vector3f v = box.getLocalTranslation();
box.setLocalTranslation(v.x + FastMath.sin(time * mult1) * 20, v.y +( FastMath.sin(time * mult1)* FastMath.cos(time * mult1)* 20), v.z + FastMath.cos(time * mult2) * 20);
box.setLocalTranslation(v.x + FastMath.sin(time * mult1) * 20, v.y + (FastMath.sin(time * mult1) * FastMath.cos(time * mult1) * 20), v.z + FastMath.cos(time * mult2) * 20);
}
terrain.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Y));

@ -54,12 +54,15 @@ import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.BatchNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.SimpleBatchNode;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.shadow.PssmShadowRenderer;
import com.jme3.shadow.PssmShadowRenderer.CompareMode;
import com.jme3.shadow.PssmShadowRenderer.FilterMode;
import com.jme3.system.AppSettings;
import com.jme3.system.NanoTimer;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
@ -90,11 +93,14 @@ public class TestBatchNodeTower extends SimpleApplication {
public static void main(String args[]) {
TestBatchNodeTower f = new TestBatchNodeTower();
AppSettings s = new AppSettings(true);
f.setSettings(s);
f.start();
}
@Override
public void simpleInitApp() {
timer = new NanoTimer();
bulletAppState = new BulletAppState();
bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
// bulletAppState.setEnabled(false);
@ -219,7 +225,7 @@ public class TestBatchNodeTower extends SimpleApplication {
tex3.setWrap(WrapMode.Repeat);
mat3.setTexture("ColorMap", tex3);
}
int nbBrick =0;
public void addBrick(Vector3f ori) {
Geometry reBoxg = new Geometry("brick", brick);
reBoxg.setMaterial(mat);
@ -230,6 +236,7 @@ public class TestBatchNodeTower extends SimpleApplication {
reBoxg.getControl(RigidBodyControl.class).setFriction(1.6f);
this.batchNode.attachChild(reBoxg);
this.getPhysicsSpace().add(reBoxg);
nbBrick++;
}
protected void initCrossHairs() {

Loading…
Cancel
Save