- Dynamic Batching

- Added a BatchNode that can batch geometries in its sub graph
- added support for matching to Geometry
- created propper test cases
- added a refresh flag to spatial RF_REFRESHBATCH to refresh the batch only when needed

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8478 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
rem..om 13 years ago
parent 0868244b9a
commit 63b62269e8
  1. 508
      engine/src/core/com/jme3/scene/BatchNode.java
  2. 119
      engine/src/core/com/jme3/scene/Geometry.java
  3. 3
      engine/src/core/com/jme3/scene/Spatial.java
  4. 95
      engine/src/test/jme3test/batching/TestBatchNode.java
  5. 364
      engine/src/test/jme3test/batching/TestBatchNodeCluster.java
  6. 245
      engine/src/test/jme3test/batching/TestBatchNodeTower.java

@ -0,0 +1,508 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
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;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.material.Material;
import com.jme3.math.Matrix4f;
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.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* BatchNode holds a geometry that is a batched version off 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 batch() method once all geoms have been attached to the sub scene graph (see todo more automagic for further enhancements)
* all the geometry that have been batched are set to 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.
*
* 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
*/
public class BatchNode extends Node implements Savable {
private static final Logger logger = Logger.getLogger(BatchNode.class.getName());
/**
* the geometry holding the batched mesh
*/
protected Geometry batch;
protected Material material;
private boolean needMeshUpdate = false;
public BatchNode() {
super();
}
public BatchNode(String name) {
super(name);
}
@Override
public void updateGeometricState() {
if ((refreshFlags & RF_LIGHTLIST) != 0) {
updateWorldLightList();
}
if ((refreshFlags & RF_TRANSFORM) != 0) {
// combine with parent transforms- same for all spatial
// subclasses.
updateWorldTransforms();
}
if (!children.isEmpty()) {
// the important part- make sure child geometric state is refreshed
// first before updating own world bound. This saves
// a round-trip later on.
// NOTE 9/19/09
// Although it does save a round trip,
for (Spatial child : children.getArray()) {
child.updateGeometricState();
}
if (needMeshUpdate) {
batch.getMesh().updateBound();
batch.updateWorldBound();
needMeshUpdate = false;
}
}
if ((refreshFlags & RF_BOUND) != 0) {
updateWorldBound();
}
assert refreshFlags == 0;
}
protected void updateSubBatch(Geometry bg) {
if (batch != null) {
Mesh mesh = batch.getMesh();
FloatBuffer buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Position).getData();
doTransformVerts(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat);
mesh.getBuffer(VertexBuffer.Type.Position).updateData(buf);
buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Normal).getData();
doTransformNorm(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat);
mesh.getBuffer(VertexBuffer.Type.Normal).updateData(buf);
if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
buf = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Tangent).getData();
doTransformNorm(buf, 0, bg.startIndex, bg.startIndex + bg.getVertexCount(), buf, bg.cachedOffsetMat);
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();
}
}
//
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);
}
}
}
/**
* Batch this batchNode
* every geometry of the sub scene graph of this node will be batched into a single mesh that will be render in one call
*/
public void batch() {
List<Geometry> tmpList = new ArrayList<Geometry>();
Mesh m = new Mesh();
populateList(tmpList, this);
mergeGeometries(m, tmpList);
if (batch == null) {
batch = new Geometry(name + "-batch");
batch.setMaterial(material);
this.attachChild(batch);
}
batch.setMesh(m);
batch.getMesh().updateCounts();
batch.getMesh().updateBound();
}
private void populateList(List<Geometry> list, Spatial n) {
if (n instanceof Geometry) {
if (n != batch) {
list.add((Geometry) n);
}
} else if (n instanceof Node) {
for (Spatial child : ((Node) n).getChildren()) {
populateList(list, child);
}
}
}
/**
* Sets the material to use for this geometry.
*
* @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;
}
/**
* Returns the material that is used for this geometry.
*
* @return the material that is used for this geometry
*
* @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();
}
return super.getWorldBound();
}
/**
* 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;
}
@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);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
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);
}
}
/**
* Merges all geometries in the collection into
* the output mesh. Does not take into account materials.
*
* @param geometries
* @param outMesh
*/
private void mergeGeometries(Mesh outMesh, List<Geometry> geometries) {
int[] compsForBuf = new int[VertexBuffer.Type.values().length];
VertexBuffer.Format[] formatForBuf = new VertexBuffer.Format[compsForBuf.length];
int totalVerts = 0;
int totalTris = 0;
int totalLodLevels = 0;
Mesh.Mode mode = null;
for (Geometry geom : geometries) {
totalVerts += geom.getVertexCount();
totalTris += geom.getTriangleCount();
totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());
Mesh.Mode listMode;
int components;
switch (geom.getMesh().getMode()) {
case Points:
listMode = Mesh.Mode.Points;
components = 1;
break;
case LineLoop:
case LineStrip:
case Lines:
listMode = Mesh.Mode.Lines;
components = 2;
break;
case TriangleFan:
case TriangleStrip:
case Triangles:
listMode = Mesh.Mode.Triangles;
components = 3;
break;
default:
throw new UnsupportedOperationException();
}
for (Entry<VertexBuffer> entry : geom.getMesh().getBuffers()) {
compsForBuf[entry.getKey()] = entry.getValue().getNumComponents();
formatForBuf[entry.getKey()] = entry.getValue().getFormat();
}
if (mode != null && mode != listMode) {
throw new UnsupportedOperationException("Cannot combine different"
+ " primitive types: " + mode + " != " + listMode);
}
mode = listMode;
compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
}
outMesh.setMode(mode);
if (totalVerts >= 65536) {
// make sure we create an UnsignedInt buffer so
// we can fit all of the meshes
formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
} else {
formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort;
}
// generate output buffers based on retrieved info
for (int i = 0; i < compsForBuf.length; i++) {
if (compsForBuf[i] == 0) {
continue;
}
Buffer data;
if (i == VertexBuffer.Type.Index.ordinal()) {
data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
} else {
data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
}
VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.values()[i]);
vb.setupData(VertexBuffer.Usage.Static, compsForBuf[i], formatForBuf[i], data);
outMesh.setBuffer(vb);
}
int globalVertIndex = 0;
int globalTriIndex = 0;
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();
int geomTriCount = inMesh.getTriangleCount();
for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
VertexBuffer inBuf = inMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
if (outBuf == null) {
continue;
}
if (VertexBuffer.Type.Index.ordinal() == bufType) {
int components = compsForBuf[bufType];
IndexBuffer inIdx = inMesh.getIndicesAsList();
IndexBuffer outIdx = outMesh.getIndexBuffer();
for (int tri = 0; tri < geomTriCount; tri++) {
for (int comp = 0; comp < components; comp++) {
int idx = inIdx.get(tri * components + comp) + globalVertIndex;
outIdx.put((globalTriIndex + tri) * components + comp, idx);
}
}
} else if (VertexBuffer.Type.Position.ordinal() == bufType) {
FloatBuffer inPos = (FloatBuffer) inBuf.getData();
FloatBuffer outPos = (FloatBuffer) outBuf.getData();
doCopyBuffer(inPos, globalVertIndex, outPos);
} else if (VertexBuffer.Type.Normal.ordinal() == bufType || VertexBuffer.Type.Tangent.ordinal() == bufType) {
FloatBuffer inPos = (FloatBuffer) inBuf.getData();
FloatBuffer outPos = (FloatBuffer) outBuf.getData();
doCopyBuffer(inPos, globalVertIndex, outPos);
} else {
for (int vert = 0; vert < geomVertCount; vert++) {
int curGlobalVertIndex = globalVertIndex + vert;
inBuf.copyElement(vert, outBuf, curGlobalVertIndex);
}
}
}
globalVertIndex += geomVertCount;
globalTriIndex += geomTriCount;
}
}
private void doTransformVerts(FloatBuffer inBuf, int offset, int start, int end, FloatBuffer outBuf, Matrix4f transform) {
TempVars vars = TempVars.get();
Vector3f pos = vars.vect1;
// offset is given in element units
// convert to be in component units
offset *= 3;
for (int i = start; i < end; i++) {
int index = i * 3;
pos.x = inBuf.get(index);
pos.y = inBuf.get(index + 1);
pos.z = inBuf.get(index + 2);
transform.mult(pos, pos);
index += offset;
outBuf.put(index, pos.x);
outBuf.put(index + 1, pos.y);
outBuf.put(index + 2, pos.z);
}
vars.release();
}
private void doTransformNorm(FloatBuffer inBuf, int offset, int start, int end, FloatBuffer outBuf, Matrix4f transform) {
TempVars vars = TempVars.get();
Vector3f pos = vars.vect1;
// offset is given in element units
// convert to be in component units
offset *= 3;
for (int i = start; i < end; i++) {
int index = i * 3;
pos.x = inBuf.get(index);
pos.y = inBuf.get(index + 1);
pos.z = inBuf.get(index + 2);
transform.multNormal(pos, pos);
index += offset;
outBuf.put(index, pos.x);
outBuf.put(index + 1, pos.y);
outBuf.put(index + 2, pos.z);
}
vars.release();
}
private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf) {
TempVars vars = TempVars.get();
Vector3f pos = vars.vect1;
// offset is given in element units
// convert to be in component units
offset *= 3;
for (int i = 0; i < inBuf.capacity() / 3; i++) {
pos.x = inBuf.get(i * 3 + 0);
pos.y = inBuf.get(i * 3 + 1);
pos.z = inBuf.get(i * 3 + 2);
outBuf.put(offset + i * 3 + 0, pos.x);
outBuf.put(offset + i * 3 + 1, pos.y);
outBuf.put(offset + i * 3 + 2, pos.z);
}
vars.release();
}
}

@ -41,6 +41,7 @@ import com.jme3.export.InputCapsule;
import com.jme3.export.OutputCapsule; import com.jme3.export.OutputCapsule;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import java.io.IOException; import java.io.IOException;
@ -67,6 +68,22 @@ public class Geometry extends Spatial {
*/ */
protected boolean ignoreTransform = false; protected boolean ignoreTransform = false;
protected transient Matrix4f cachedWorldMat = new Matrix4f(); protected transient Matrix4f cachedWorldMat = new Matrix4f();
/**
* used when geometry is batched
*/
protected BatchNode batchNode = null;
/**
* the start index of this geom's mesh in the batchNode mesh
*/
protected int startIndex;
/**
* the previous transforms of the geometry used to compute world transforms
*/
protected Transform prevLocalTransform = null;
/**
* the cached offset matrix used when the geometry is batched
*/
protected Matrix4f cachedOffsetMat = null;
/** /**
* Serialization only. Do not use. * Serialization only. Do not use.
@ -181,6 +198,9 @@ public class Geometry extends Spatial {
if (mesh == null) { if (mesh == null) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
if (isBatched()) {
throw new UnsupportedOperationException("Cannot set the mesh of a batched geometry");
}
this.mesh = mesh; this.mesh = mesh;
setBoundRefresh(); setBoundRefresh();
@ -204,6 +224,9 @@ public class Geometry extends Spatial {
*/ */
@Override @Override
public void setMaterial(Material material) { public void setMaterial(Material material) {
if (isBatched()) {
throw new UnsupportedOperationException("Cannot set the material of a batched geometry, change the material of the parent BatchNode.");
}
this.material = material; this.material = material;
} }
@ -261,14 +284,100 @@ public class Geometry extends Spatial {
@Override @Override
protected void updateWorldTransforms() { protected void updateWorldTransforms() {
super.updateWorldTransforms();
super.updateWorldTransforms();
computeWorldMatrix(); computeWorldMatrix();
if ((refreshFlags & RF_REFRESHBATCH) != 0) {
computeOffsetTransform();
batchNode.updateSubBatch(this);
prevLocalTransform.set(localTransform);
refreshFlags &= ~RF_REFRESHBATCH;
}
// geometry requires lights to be sorted // geometry requires lights to be sorted
worldLights.sort(true); worldLights.sort(true);
} }
/**
* Batch this geometry, should only be called by the BatchNode.
* @param node the batchNode
* @param startIndex the starting index of this geometry in the batched mesh
*/
protected void batch(BatchNode node, int startIndex) {
this.batchNode = node;
this.startIndex = startIndex;
prevLocalTransform = new Transform();
cachedOffsetMat = new Matrix4f();
setCullHint(CullHint.Always);
refreshFlags |= RF_REFRESHBATCH;
}
/**
* unBatch this geometry.
*/
protected void unBatch() {
this.startIndex = 0;
prevLocalTransform = null;
cachedOffsetMat = null;
//once the geometry is removed from the screnegraph we call batch on the batchNode before unreferencing it.
this.batchNode.batch();
this.batchNode = null;
setCullHint(CullHint.Dynamic);
}
@Override
public boolean removeFromParent() {
boolean removed = super.removeFromParent();
//if the geometry is batched we also have to unbatch it
if (isBatched()) {
unBatch();
}
return removed;
}
/**
* Recomputes the cached offset matrix used when the geometry is batched *
*/
public void computeOffsetTransform() {
TempVars vars = TempVars.get();
Matrix4f tmpMat = vars.tempMat42;
// Compute the cached world matrix
cachedOffsetMat.loadIdentity();
cachedOffsetMat.setRotationQuaternion(prevLocalTransform.getRotation());
cachedOffsetMat.setTranslation(prevLocalTransform.getTranslation());
Matrix4f scaleMat = vars.tempMat4;
scaleMat.loadIdentity();
scaleMat.scale(prevLocalTransform.getScale());
cachedOffsetMat.multLocal(scaleMat);
cachedOffsetMat.invertLocal();
tmpMat.loadIdentity();
tmpMat.setRotationQuaternion(localTransform.getRotation());
tmpMat.setTranslation(localTransform.getTranslation());
scaleMat.loadIdentity();
scaleMat.scale(localTransform.getScale());
tmpMat.multLocal(scaleMat);
tmpMat.mult(cachedOffsetMat, cachedOffsetMat);
vars.release();
}
/**
* Indicate that the transform of this spatial has changed and that
* a refresh is required.
*/
@Override
protected void setTransformRefresh() {
refreshFlags |= RF_TRANSFORM;
setBoundRefresh();
if (isBatched()) {
refreshFlags |= RF_REFRESHBATCH;
}
}
/** /**
* Recomputes the matrix returned by {@link Geometry#getWorldMatrix() }. * Recomputes the matrix returned by {@link Geometry#getWorldMatrix() }.
* This will require a localized transform update for this geometry. * This will require a localized transform update for this geometry.
@ -316,7 +425,7 @@ public class Geometry extends Spatial {
this.worldBound = null; this.worldBound = null;
mesh.setBound(modelBound); mesh.setBound(modelBound);
setBoundRefresh(); setBoundRefresh();
// NOTE: Calling updateModelBound() would cause the mesh // NOTE: Calling updateModelBound() would cause the mesh
// to recompute the bound based on the geometry thus making // to recompute the bound based on the geometry thus making
// this call useless! // this call useless!
@ -354,6 +463,10 @@ public class Geometry extends Spatial {
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) { protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
} }
public boolean isBatched() {
return batchNode != null;
}
/** /**
* This version of clone is a shallow clone, in other words, the * This version of clone is a shallow clone, in other words, the
* same mesh is referenced as the original geometry. * same mesh is referenced as the original geometry.

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

@ -0,0 +1,95 @@
/*
* To change this template, choose Tools | Templates and open the template in
* the editor.
*/
package jme3test.batching;
import com.jme3.app.SimpleApplication;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.BatchNode;
import com.jme3.scene.BatchedGeometry;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.util.TangentBinormalGenerator;
/**
*
* @author Nehon
*/
public class TestBatchNode extends SimpleApplication {
public static void main(String[] args) {
TestBatchNode app = new TestBatchNode();
app.start();
}
BatchNode batch;
@Override
public void simpleInitApp() {
batch = new BatchNode("theBatchNode");
/**
* A cube with a color "bleeding" through transparent texture. Uses
* Texture from jme3-test-data library!
*/
Box boxshape4 = new Box(Vector3f.ZERO, 1f, 1f, 1f );
cube = new Geometry("cube1", boxshape4);
Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
// Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
// mat.setColor("Diffuse", ColorRGBA.Blue);
// mat.setBoolean("UseMaterialColors", true);
/**
* A cube with a color "bleeding" through transparent texture. Uses
* Texture from jme3-test-data library!
*/
Box box = new Box(Vector3f.ZERO, 1f, 1f, 1f);
cube2 = new Geometry("cube2", box);
TangentBinormalGenerator.generate(cube);
TangentBinormalGenerator.generate(cube2);
n = new Node("aNode");
// n.attachChild(cube2);
batch.attachChild(cube);
batch.attachChild(cube2);
batch.setMaterial(mat);
batch.batch();
rootNode.attachChild(batch);
cube.setLocalTranslation(3, 0, 0);
cube2.setLocalTranslation(0, 3, 0);
dl=new DirectionalLight();
dl.setColor(ColorRGBA.White.mult(2));
dl.setDirection(new Vector3f(1, -1, -1));
rootNode.addLight(dl);
flyCam.setMoveSpeed(10);
}
Node n;
Geometry cube;
Geometry cube2;
float time = 0;
DirectionalLight dl;
@Override
public void simpleUpdate(float tpf) {
time += tpf;
dl.setDirection(cam.getDirection());
cube2.setLocalTranslation(FastMath.sin(-time)*3, FastMath.cos(time)*3, 0);
cube2.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z));
cube2.setLocalScale(Math.max(FastMath.sin(time),0.5f));
batch.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z));
}
//
}

@ -0,0 +1,364 @@
/*
* To change this template, choose Tools | Templates and open the template in
* the editor.
*/
package jme3test.batching;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.input.controls.KeyTrigger;
import java.util.ArrayList;
import java.util.Random;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
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.Spatial;
import com.jme3.scene.debug.Arrow;
import com.jme3.system.NanoTimer;
public class TestBatchNodeCluster extends SimpleApplication {
public static void main(String[] args) {
TestBatchNodeCluster app = new TestBatchNodeCluster();
settingst = new AppSettings(true);
//settingst.setFrameRate(75);
settingst.setResolution(640, 480);
settingst.setVSync(false);
settingst.setFullscreen(false);
app.setSettings(settingst);
app.setShowSettings(false);
app.start();
}
private ActionListener al = new ActionListener() {
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("Start Game")) {
// randomGenerator();
}
}
};
protected Random rand = new Random();
protected int maxCubes = 2000;
protected int startAt = 0;
protected static int xPositions = 0, yPositions = 0, zPositions = 0;
protected int returner = 0;
protected ArrayList<Integer> xPosition = new ArrayList<Integer>();
protected ArrayList<Integer> yPosition = new ArrayList<Integer>();
protected ArrayList<Integer> zPosition = new ArrayList<Integer>();
protected int xLimitf = 60, xLimits = -60, yLimitf = 60, yLimits = -20, zLimitf = 60, zLimits = -60;
protected int circ = 8;//increases by 8 every time.
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;
Material mat1;
Material mat2;
Material mat3;
Material mat4;
Node terrain;
//protected
// protected Geometry player;
@Override
public void simpleInitApp() {
timer = new NanoTimer();
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);
//rootNode.attachChild(SkyFactory.createSky(
// assetManager, "Textures/SKY02.zip", false));
inputManager.addMapping("Start Game", new KeyTrigger(KeyInput.KEY_J));
inputManager.addListener(al, new String[]{"Start Game"});
cam.setLocation(new Vector3f(-34.403286f, 126.65158f, 434.791f));
cam.setRotation(new Quaternion(0.022630932f, 0.9749435f, -0.18736298f, 0.11776358f));
blue.batch();
brown.batch();
pink.batch();
orange.batch();
terrain = new Node("terrain");
terrain.setLocalTranslation(50, 0, 50);
terrain.attachChild(blue);
terrain.attachChild(brown);
terrain.attachChild(pink);
terrain.attachChild(orange);
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);
Arrow a = new Arrow(new Vector3f(0, 50, 0));
Geometry g = new Geometry("a", a);
g.setLocalTranslation(terrain.getLocalTranslation());
Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
m.setColor("Color", ColorRGBA.Blue);
g.setMaterial(m);
FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
fpp.addFilter(new BloomFilter(BloomFilter.GlowMode.Objects));
// SSAOFilter ssao = new SSAOFilter(8.630104f,22.970434f,2.9299977f,0.2999997f);
// fpp.addFilter(ssao);
viewPort.addProcessor(fpp);
// viewPort.setBackgroundColor(ColorRGBA.DarkGray);
}
public void randomGenerator() {
for (int i = startAt; i < maxCubes - 1; i++) {
randomize();
Geometry box = new Geometry("Box" + i, new Box(Vector3f.ZERO, 1, 1, 1));
box.setLocalTranslation(new Vector3f(xPosition.get(xPosition.size() - 1),
yPosition.get(yPosition.size() - 1),
zPosition.get(zPosition.size() - 1)));
if (i < 500) {
blue.attachChild(box);
} else if (i < 1000) {
brown.attachChild(box);
} else if (i < 1500) {
pink.attachChild(box);
} else {
orange.attachChild(box);
}
}
}
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);
if (randomn == 0) {
color = ColorRGBA.Orange;
} else if (randomn == 1) {
color = ColorRGBA.Blue;
} else if (randomn == 2) {
color = ColorRGBA.Brown;
} else if (randomn == 3) {
color = ColorRGBA.Magenta;
}
return color;
}
public void randomize() {
int xpos = xPosition.get(xPosition.size() - 1);
int ypos = yPosition.get(yPosition.size() - 1);
int zpos = zPosition.get(zPosition.size() - 1);
int x = 0;
int y = 0;
int z = 0;
boolean unTrue = true;
while (unTrue) {
unTrue = false;
boolean xChanged = false;
x = 0;
y = 0;
z = 0;
if (xpos >= lineLength * 2) {
x = 2;
xChanged = true;
} else {
x = xPosition.get(xPosition.size() - 1) + 2;
}
if (xChanged) {
//y = yPosition.get(yPosition.size() - lineLength) + 2;
} else {
y = rand.nextInt(3);
if (yPosition.size() > lineLength) {
if (yPosition.size() > 51) {
if (y == 0 && ypos < yLimitf && getym(lineLength) > ypos - 2) {
y = ypos + 2;
} else if (y == 1 && ypos > yLimits && getym(lineLength) < ypos + 2) {
y = ypos - 2;
} else if (y == 2 && getym(lineLength) > ypos - 2 && getym(lineLength) < ypos + 2) {
y = ypos;
} else {
if (ypos >= yLimitf) {
y = ypos - 2;
} else if (ypos <= yLimits) {
y = ypos + 2;
} else if (y == 0 && getym(lineLength) >= ypos - 4) {
y = ypos - 2;
} else if (y == 0 && getym(lineLength) >= ypos - 2) {
y = ypos;
} else if (y == 1 && getym(lineLength) >= ypos + 4) {
y = ypos + 2;
} else if (y == 1 && getym(lineLength) >= ypos + 2) {
y = ypos;
} else if (y == 2 && getym(lineLength) <= ypos - 2) {
y = ypos - 2;
} else if (y == 2 && getym(lineLength) >= ypos + 2) {
y = ypos + 2;
} else {
System.out.println("wtf");
}
}
} else if (yPosition.size() == lineLength) {
if (y == 0 && ypos < yLimitf) {
y = getym(lineLength) + 2;
} else if (y == 1 && ypos > yLimits) {
y = getym(lineLength) - 2;
}
}
} else {
if (y == 0 && ypos < yLimitf) {
y = ypos + 2;
} else if (y == 1 && ypos > yLimits) {
y = ypos - 2;
} else if (y == 2) {
y = ypos;
} else if (y == 0 && ypos >= yLimitf) {
y = ypos - 2;
} else if (y == 1 && ypos <= yLimits) {
y = ypos + 2;
}
}
}
if (xChanged) {
z = zpos + 2;
} else {
z = zpos;
}
// for (int i = 0; i < xPosition.size(); i++)
// {
// if (x - xPosition.get(i) <= 1 && x - xPosition.get(i) >= -1 &&
// y - yPosition.get(i) <= 1 && y - yPosition.get(i) >= -1
// &&z - zPosition.get(i) <= 1 && z - zPosition.get(i) >=
// -1)
// {
// unTrue = true;
// }
// }
}
xPosition.add(x);
yPosition.add(y);
zPosition.add(z);
}
public int getxm(int i) {
return xPosition.get(xPosition.size() - i);
}
public int getym(int i) {
return yPosition.get(yPosition.size() - i);
}
public int getzm(int i) {
return zPosition.get(zPosition.size() - i);
}
public int getx(int i) {
return xPosition.get(i);
}
public int gety(int i) {
return yPosition.get(i);
}
public int getz(int i) {
return zPosition.get(i);
}
long nbFrames = 0;
long cullTime = 0;
float time = 0;
Vector3f lookAtPos = new Vector3f(0, 0, 0);
float xpos = 0;
Spatial box;
@Override
public void simpleUpdate(float tpf) {
time += tpf;
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);
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);
}
terrain.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Y));
}
}

@ -0,0 +1,245 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package jme3test.batching;
/*
* Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import jme3test.bullet.*;
import com.jme3.bullet.BulletAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.font.BitmapText;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.Vector2f;
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.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.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
/**
*
* @author double1984 (tower mod by atom)
*/
public class TestBatchNodeTower extends SimpleApplication {
int bricksPerLayer = 8;
int brickLayers = 30;
static float brickWidth = .75f, brickHeight = .25f, brickDepth = .25f;
float radius = 3f;
float angle = 0;
Material mat;
Material mat2;
Material mat3;
PssmShadowRenderer bsr;
private Sphere bullet;
private Box brick;
private SphereCollisionShape bulletCollisionShape;
private BulletAppState bulletAppState;
BatchNode batchNode = new BatchNode("batch Node");
public static void main(String args[]) {
TestBatchNodeTower f = new TestBatchNodeTower();
f.start();
}
@Override
public void simpleInitApp() {
bulletAppState = new BulletAppState();
bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
// bulletAppState.setEnabled(false);
stateManager.attach(bulletAppState);
bullet = new Sphere(32, 32, 0.4f, true, false);
bullet.setTextureMode(TextureMode.Projected);
bulletCollisionShape = new SphereCollisionShape(0.4f);
brick = new Box(Vector3f.ZERO, brickWidth, brickHeight, brickDepth);
brick.scaleTextureCoordinates(new Vector2f(1f, .5f));
//bulletAppState.getPhysicsSpace().enableDebug(assetManager);
initMaterial();
initTower();
initFloor();
initCrossHairs();
this.cam.setLocation(new Vector3f(0, 25f, 8f));
cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0));
cam.setFrustumFar(80);
inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(actionListener, "shoot");
rootNode.setShadowMode(ShadowMode.Off);
batchNode.batch();
batchNode.setShadowMode(ShadowMode.CastAndReceive);
rootNode.attachChild(batchNode);
bsr = new PssmShadowRenderer(assetManager, 1024, 2);
bsr.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
bsr.setLambda(0.55f);
bsr.setShadowIntensity(0.6f);
bsr.setCompareMode(CompareMode.Hardware);
bsr.setFilterMode(FilterMode.PCF4);
viewPort.addProcessor(bsr);
}
private PhysicsSpace getPhysicsSpace() {
return bulletAppState.getPhysicsSpace();
}
private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean keyPressed, float tpf) {
if (name.equals("shoot") && !keyPressed) {
Geometry bulletg = new Geometry("bullet", bullet);
bulletg.setMaterial(mat2);
bulletg.setShadowMode(ShadowMode.CastAndReceive);
bulletg.setLocalTranslation(cam.getLocation());
RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1);
// RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1);
bulletNode.setLinearVelocity(cam.getDirection().mult(25));
bulletg.addControl(bulletNode);
rootNode.attachChild(bulletg);
getPhysicsSpace().add(bulletNode);
}
}
};
public void initTower() {
double tempX = 0;
double tempY = 0;
double tempZ = 0;
angle = 0f;
for (int i = 0; i < brickLayers; i++){
// Increment rows
if(i!=0)
tempY+=brickHeight*2;
else
tempY=brickHeight;
// Alternate brick seams
angle = 360.0f / bricksPerLayer * i/2f;
for (int j = 0; j < bricksPerLayer; j++){
tempZ = Math.cos(Math.toRadians(angle))*radius;
tempX = Math.sin(Math.toRadians(angle))*radius;
System.out.println("x="+((float)(tempX))+" y="+((float)(tempY))+" z="+(float)(tempZ));
Vector3f vt = new Vector3f((float)(tempX), (float)(tempY), (float)(tempZ));
// Add crenelation
if (i==brickLayers-1){
if (j%2 == 0){
addBrick(vt);
}
}
// Create main tower
else {
addBrick(vt);
}
angle += 360.0/bricksPerLayer;
}
}
}
public void initFloor() {
Box floorBox = new Box(Vector3f.ZERO, 10f, 0.1f, 5f);
floorBox.scaleTextureCoordinates(new Vector2f(3, 6));
Geometry floor = new Geometry("floor", floorBox);
floor.setMaterial(mat3);
floor.setShadowMode(ShadowMode.Receive);
floor.setLocalTranslation(0, 0, 0);
floor.addControl(new RigidBodyControl(0));
this.rootNode.attachChild(floor);
this.getPhysicsSpace().add(floor);
}
public void initMaterial() {
mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
key.setGenerateMips(true);
Texture tex = assetManager.loadTexture(key);
mat.setTexture("ColorMap", tex);
mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
key2.setGenerateMips(true);
Texture tex2 = assetManager.loadTexture(key2);
mat2.setTexture("ColorMap", tex2);
mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
key3.setGenerateMips(true);
Texture tex3 = assetManager.loadTexture(key3);
tex3.setWrap(WrapMode.Repeat);
mat3.setTexture("ColorMap", tex3);
}
public void addBrick(Vector3f ori) {
Geometry reBoxg = new Geometry("brick", brick);
reBoxg.setMaterial(mat);
reBoxg.setLocalTranslation(ori);
reBoxg.rotate(0f, (float)Math.toRadians(angle) , 0f );
reBoxg.addControl(new RigidBodyControl(1.5f));
reBoxg.setShadowMode(ShadowMode.CastAndReceive);
reBoxg.getControl(RigidBodyControl.class).setFriction(1.6f);
this.batchNode.attachChild(reBoxg);
this.getPhysicsSpace().add(reBoxg);
}
protected void initCrossHairs() {
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
BitmapText ch = new BitmapText(guiFont, false);
ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
ch.setText("+"); // crosshairs
ch.setLocalTranslation( // center
settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
guiNode.attachChild(ch);
}
}
Loading…
Cancel
Save