From d33cbce333b13b0f76aa5c9d99dbcda3c0876706 Mon Sep 17 00:00:00 2001 From: "rem..om" Date: Tue, 8 May 2012 19:02:13 +0000 Subject: [PATCH] - Changed BatchNode and GeometryBatchFactory according to material recent changes. - BatchNode now does incremental batch upon adding a new geom to the batch (was there before but didn't work properly). git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9361 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- engine/src/core/com/jme3/scene/BatchNode.java | 199 ++-- .../core/com/jme3/scene/SimpleBatchNode.java | 2 +- .../optimize/GeometryBatchFactory.java | 856 +++++++++--------- 3 files changed, 543 insertions(+), 514 deletions(-) diff --git a/engine/src/core/com/jme3/scene/BatchNode.java b/engine/src/core/com/jme3/scene/BatchNode.java index f4ddbd450..86b2f047a 100644 --- a/engine/src/core/com/jme3/scene/BatchNode.java +++ b/engine/src/core/com/jme3/scene/BatchNode.java @@ -37,6 +37,7 @@ 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.SafeArrayList; import com.jme3.util.TempVars; import java.io.IOException; import java.nio.Buffer; @@ -65,14 +66,14 @@ import java.util.logging.Logger; * @author Nehon */ public class BatchNode extends Node implements Savable { - + private static final Logger logger = Logger.getLogger(BatchNode.class.getName()); /** - * the map of geometry holding the batched meshes + * the list of geometry holding the batched meshes */ - protected Map batches = new HashMap(); + protected SafeArrayList batches = new SafeArrayList(Batch.class); /** - * a map storing he batches by geometry to qickly acces the batch when updating + * a map storing he batches by geometry to quickly acces the batch when updating */ protected Map batchesByGeom = new HashMap(); /** @@ -91,23 +92,23 @@ public class BatchNode extends Node implements Savable { 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 @@ -118,42 +119,42 @@ public class BatchNode extends Node implements Savable { for (Spatial child : children.getArray()) { child.updateGeometricState(); } - - for (Batch batch : batches.values()) { + + for (Batch batch : batches.getArray()) { if (batch.needMeshUpdate) { batch.geometry.getMesh().updateBound(); batch.geometry.updateWorldBound(); batch.needMeshUpdate = false; - + } } - - + + } - + if ((refreshFlags & RF_BOUND) != 0) { updateWorldBound(); } - + assert refreshFlags == 0; } - + protected Transform getTransforms(Geometry geom) { return geom.getWorldTransform(); } - + protected void updateSubBatch(Geometry bg) { Batch batch = batchesByGeom.get(bg); if (batch != null) { Mesh mesh = batch.geometry.getMesh(); - + VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position); FloatBuffer posBuf = (FloatBuffer) pvb.getData(); VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal); FloatBuffer normBuf = (FloatBuffer) nvb.getData(); - + if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) { - + VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent); FloatBuffer tanBuf = (FloatBuffer) tvb.getData(); doTransformsTangents(posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat); @@ -163,8 +164,8 @@ public class BatchNode extends Node implements Savable { } pvb.updateData(posBuf); nvb.updateData(normBuf); - - + + batch.needMeshUpdate = true; } } @@ -176,19 +177,19 @@ public class BatchNode extends Node implements Savable { 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()) { + for (Batch batch : batches.getArray()) { batch.geometry.setIgnoreTransform(true); } } - + protected void doBatch() { Map> matMap = new HashMap>(); maxVertCount = 0; int nbGeoms = 0; - + gatherGeomerties(matMap, this, needsFullRebatch); if (needsFullRebatch) { - for (Batch batch : batches.values()) { + for (Batch batch : batches.getArray()) { batch.geometry.removeFromParent(); } batches.clear(); @@ -199,25 +200,40 @@ public class BatchNode extends Node implements Savable { Material material = entry.getKey(); List list = entry.getValue(); nbGeoms += list.size(); + String batchName = name + "-batch" + batches.size(); + Batch batch; if (!needsFullRebatch) { - list.add(batches.get(material).geometry); + batch = findBatchByMaterial(material); + if (batch != null) { + list.add(0, batch.geometry); + batchName = batch.geometry.getName(); + batch.geometry.removeFromParent(); + } else { + batch = new Batch(); + } + } else { + batch = new Batch(); } mergeGeometries(m, list); m.setDynamic(); - Batch batch = new Batch(); + batch.updateGeomList(list); - batch.geometry = new Geometry(name + "-batch" + batches.size()); + batch.geometry = new Geometry(batchName); batch.geometry.setMaterial(material); this.attachChild(batch.geometry); - - + + batch.geometry.setMesh(m); batch.geometry.getMesh().updateCounts(); batch.geometry.getMesh().updateBound(); - batches.put(material, batch); + batches.add(batch); } - + if (batches.size() > 0) { + needsFullRebatch = false; + } + + logger.log(Level.INFO, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()}); @@ -228,11 +244,11 @@ public class BatchNode extends Node implements Savable { tmpFloatT = new float[maxVertCount * 4]; } } - + private void gatherGeomerties(Map> map, Spatial n, boolean rebatch) { - + if (n.getClass() == Geometry.class) { - + if (!isBatch(n) && n.getBatchHint() != BatchHint.Never) { Geometry g = (Geometry) n; if (!g.isBatched() || rebatch) { @@ -241,9 +257,9 @@ public class BatchNode extends Node implements Savable { } List list = map.get(g.getMaterial()); if (list == null) { - //trying to compare materials with the contentEquals method - for ( Map.Entry> mat : map.entrySet()) { - if (g.getMaterial().contentEquals(mat.getKey())) { + //trying to compare materials with the isEqual method + for (Map.Entry> mat : map.entrySet()) { + if (g.getMaterial().dynamicHashCode() == mat.getKey().dynamicHashCode()) { list = mat.getValue(); } } @@ -256,7 +272,7 @@ public class BatchNode extends Node implements Savable { list.add(g); } } - + } else if (n instanceof Node) { for (Spatial child : ((Node) n).getChildren()) { if (child instanceof BatchNode) { @@ -265,11 +281,20 @@ public class BatchNode extends Node implements Savable { gatherGeomerties(map, child, rebatch); } } - + } - + + private Batch findBatchByMaterial(Material m) { + for (Batch batch : batches.getArray()) { + if (batch.geometry.getMaterial().dynamicHashCode() == m.dynamicHashCode()) { + return batch; + } + } + return null; + } + private boolean isBatch(Spatial s) { - for (Batch batch : batches.values()) { + for (Batch batch : batches.getArray()) { if (batch.geometry == s) { return true; } @@ -302,7 +327,7 @@ public class BatchNode extends Node implements Savable { */ public Material getMaterial() { if (!batches.isEmpty()) { - Batch b = batches.get(batches.keySet().iterator().next()); + Batch b = batches.iterator().next(); return b.geometry.getMaterial(); } return null;//material; @@ -348,7 +373,7 @@ public class BatchNode extends Node implements Savable { // oc.write(material, "material", null); } - + @Override public void read(JmeImporter im) throws IOException { super.read(im); @@ -385,11 +410,11 @@ public class BatchNode extends Node implements Savable { private void mergeGeometries(Mesh outMesh, List 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(); @@ -420,12 +445,12 @@ public class BatchNode extends Node implements Savable { default: throw new UnsupportedOperationException(); } - + for (VertexBuffer vb : geom.getMesh().getBufferList().getArray()) { compsForBuf[vb.getBufferType().ordinal()] = vb.getNumComponents(); formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat(); } - + if (mode != null && mode != listMode) { throw new UnsupportedOperationException("Cannot combine different" + " primitive types: " + mode + " != " + listMode); @@ -433,7 +458,7 @@ public class BatchNode extends Node implements Savable { mode = listMode; compsForBuf[VertexBuffer.Type.Index.ordinal()] = components; } - + outMesh.setMode(mode); if (totalVerts >= 65536) { // make sure we create an UnsignedInt buffer so @@ -448,44 +473,46 @@ public class BatchNode extends Node implements Savable { 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.Dynamic, compsForBuf[i], formatForBuf[i], data); outMesh.setBuffer(vb); } - + int globalVertIndex = 0; int globalTriIndex = 0; - + for (Geometry geom : geometries) { Mesh inMesh = geom.getMesh(); - geom.batch(this, globalVertIndex); - + if (!isBatch(geom)) { + 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; @@ -511,17 +538,17 @@ public class BatchNode extends Node implements Savable { // } } } - + globalVertIndex += geomVertCount; globalTriIndex += geomTriCount; } } - + private void doTransforms(FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform) { TempVars vars = TempVars.get(); Vector3f pos = vars.vect1; Vector3f norm = vars.vect2; - + int length = (end - start) * 3; // offset is given in element units @@ -539,10 +566,10 @@ public class BatchNode extends Node implements Savable { norm.y = tmpFloatN[index++]; pos.z = tmpFloat[index]; norm.z = tmpFloatN[index]; - + transform.mult(pos, pos); transform.multNormal(norm, norm); - + index -= 2; tmpFloat[index] = pos.x; tmpFloatN[index++] = norm.x; @@ -550,7 +577,7 @@ public class BatchNode extends Node implements Savable { tmpFloatN[index++] = norm.y; tmpFloat[index] = pos.z; tmpFloatN[index++] = norm.z; - + } vars.release(); bufPos.position(offset); @@ -560,13 +587,13 @@ public class BatchNode extends Node implements Savable { //using bulk put as it's faster bufNorm.put(tmpFloatN, 0, length); } - + private void doTransformsTangents(FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) { TempVars vars = TempVars.get(); Vector3f pos = vars.vect1; Vector3f norm = vars.vect2; Vector3f tan = vars.vect3; - + int length = (end - start) * 3; int tanLength = (end - start) * 4; @@ -574,14 +601,14 @@ public class BatchNode extends Node implements Savable { // convert to be in component units int offset = start * 3; int tanOffset = start * 4; - + bufPos.position(offset); bufNorm.position(offset); bufTangents.position(tanOffset); bufPos.get(tmpFloat, 0, length); bufNorm.get(tmpFloatN, 0, length); bufTangents.get(tmpFloatT, 0, tanLength); - + int index = 0; int tanIndex = 0; while (index < length) { @@ -591,32 +618,32 @@ public class BatchNode extends Node implements Savable { norm.y = tmpFloatN[index++]; pos.z = tmpFloat[index]; norm.z = tmpFloatN[index]; - + tan.x = tmpFloatT[tanIndex++]; tan.y = tmpFloatT[tanIndex++]; tan.z = tmpFloatT[tanIndex++]; - + transform.mult(pos, pos); transform.multNormal(norm, norm); transform.multNormal(tan, tan); - + index -= 2; tanIndex -= 3; - + tmpFloat[index] = pos.x; tmpFloatN[index++] = norm.x; tmpFloat[index] = pos.y; tmpFloatN[index++] = norm.y; tmpFloat[index] = pos.z; tmpFloatN[index++] = norm.z; - + tmpFloatT[tanIndex++] = tan.x; tmpFloatT[tanIndex++] = tan.y; tmpFloatT[tanIndex++] = tan.z; //Skipping 4th element of tangent buffer (handedness) tanIndex++; - + } vars.release(); bufPos.position(offset); @@ -629,7 +656,7 @@ public class BatchNode extends Node implements Savable { //using bulk put as it's faster bufTangents.put(tmpFloatT, 0, tanLength); } - + private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) { TempVars vars = TempVars.get(); Vector3f pos = vars.vect1; @@ -637,19 +664,19 @@ public class BatchNode extends Node implements Savable { // offset is given in element units // convert to be in component units offset *= componentSize; - + for (int i = 0; i < inBuf.capacity() / componentSize; i++) { pos.x = inBuf.get(i * componentSize + 0); pos.y = inBuf.get(i * componentSize + 1); pos.z = inBuf.get(i * componentSize + 2); - + outBuf.put(offset + i * componentSize + 0, pos.x); outBuf.put(offset + i * componentSize + 1, pos.y); outBuf.put(offset + i * componentSize + 2, pos.z); } vars.release(); } - + protected class Batch { /** @@ -658,17 +685,19 @@ public class BatchNode extends Node implements Savable { */ void updateGeomList(List list) { for (Geometry geom : list) { - batchesByGeom.put(geom, this); + if (!isBatch(geom)) { + batchesByGeom.put(geom, this); + } } } Geometry geometry; boolean needMeshUpdate = false; } - + protected void setNeedsFullRebatch(boolean needsFullRebatch) { this.needsFullRebatch = needsFullRebatch; } - + public int getOffsetIndex(Geometry batchedGeometry) { return batchedGeometry.startIndex; } diff --git a/engine/src/core/com/jme3/scene/SimpleBatchNode.java b/engine/src/core/com/jme3/scene/SimpleBatchNode.java index 0f1ed2939..988451e2f 100644 --- a/engine/src/core/com/jme3/scene/SimpleBatchNode.java +++ b/engine/src/core/com/jme3/scene/SimpleBatchNode.java @@ -40,7 +40,7 @@ public class SimpleBatchNode extends BatchNode { refreshFlags |= RF_TRANSFORM; setBoundRefresh(); - for (Batch batch : batches.values()) { + for (Batch batch : batches.getArray()) { batch.geometry.setTransformRefresh(); } } diff --git a/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java b/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java index 88c63be18..79cea6fc0 100644 --- a/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java +++ b/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java @@ -1,428 +1,428 @@ -package jme3tools.optimize; - -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.Mode; -import com.jme3.scene.*; -import com.jme3.scene.VertexBuffer.Format; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.VertexBuffer.Usage; -import com.jme3.scene.mesh.IndexBuffer; -import com.jme3.util.BufferUtils; -import com.jme3.util.IntMap.Entry; -import java.nio.Buffer; -import java.nio.FloatBuffer; -import java.nio.ShortBuffer; -import java.util.*; -import java.util.logging.Logger; - -public class GeometryBatchFactory { - - private static final Logger logger = Logger.getLogger(GeometryBatchFactory.class.getName()); - - private static void doTransformVerts(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) { - Vector3f pos = new Vector3f(); - - // 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); - - transform.mult(pos, pos); - - outBuf.put(offset + i * 3 + 0, pos.x); - outBuf.put(offset + i * 3 + 1, pos.y); - outBuf.put(offset + i * 3 + 2, pos.z); - } - } - - private static void doTransformNorms(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) { - Vector3f norm = new Vector3f(); - - // offset is given in element units - // convert to be in component units - offset *= 3; - - for (int i = 0; i < inBuf.capacity() / 3; i++) { - norm.x = inBuf.get(i * 3 + 0); - norm.y = inBuf.get(i * 3 + 1); - norm.z = inBuf.get(i * 3 + 2); - - transform.multNormal(norm, norm); - - outBuf.put(offset + i * 3 + 0, norm.x); - outBuf.put(offset + i * 3 + 1, norm.y); - outBuf.put(offset + i * 3 + 2, norm.z); - } - } - - private static void doTransformTangents(FloatBuffer inBuf, int offset, int components, FloatBuffer outBuf, Matrix4f transform) { - Vector3f tan = new Vector3f(); - - // offset is given in element units - // convert to be in component units - offset *= components; - - for (int i = 0; i < inBuf.capacity() / components; i++) { - tan.x = inBuf.get(i * components + 0); - tan.y = inBuf.get(i * components + 1); - tan.z = inBuf.get(i * components + 2); - - transform.multNormal(tan, tan); - - outBuf.put(offset + i * components + 0, tan.x); - outBuf.put(offset + i * components + 1, tan.y); - outBuf.put(offset + i * components + 2, tan.z); - - if (components == 4) { - outBuf.put(offset + i * components + 3, inBuf.get(i * components + 3)); - } - } - } - - /** - * Merges all geometries in the collection into - * the output mesh. Creates a new material using the TextureAtlas. - * - * @param geometries - * @param outMesh - */ - public static void mergeGeometries(Collection geometries, Mesh outMesh) { - int[] compsForBuf = new int[VertexBuffer.Type.values().length]; - Format[] formatForBuf = new Format[compsForBuf.length]; - - int totalVerts = 0; - int totalTris = 0; - int totalLodLevels = 0; - - Mode mode = null; - for (Geometry geom : geometries) { - totalVerts += geom.getVertexCount(); - totalTris += geom.getTriangleCount(); - totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels()); - - Mode listMode; - int components; - switch (geom.getMesh().getMode()) { - case Points: - listMode = Mode.Points; - components = 1; - break; - case LineLoop: - case LineStrip: - case Lines: - listMode = Mode.Lines; - components = 2; - break; - case TriangleFan: - case TriangleStrip: - case Triangles: - listMode = Mode.Triangles; - components = 3; - break; - default: - throw new UnsupportedOperationException(); - } - - for (VertexBuffer vb : geom.getMesh().getBufferList().getArray()) { - compsForBuf[vb.getBufferType().ordinal()] = vb.getNumComponents(); - formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat(); - } - - if (mode != null && mode != listMode) { - throw new UnsupportedOperationException("Cannot combine different" - + " primitive types: " + mode + " != " + listMode); - } - mode = listMode; - compsForBuf[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[Type.Index.ordinal()] = Format.UnsignedInt; - } else { - formatForBuf[Type.Index.ordinal()] = 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 == Type.Index.ordinal()) { - data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris); - } else { - data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts); - } - - VertexBuffer vb = new VertexBuffer(Type.values()[i]); - vb.setupData(Usage.Static, compsForBuf[i], formatForBuf[i], data); - outMesh.setBuffer(vb); - } - - int globalVertIndex = 0; - int globalTriIndex = 0; - - for (Geometry geom : geometries) { - Mesh inMesh = geom.getMesh(); - geom.computeWorldMatrix(); - Matrix4f worldMatrix = geom.getWorldMatrix(); - - int geomVertCount = inMesh.getVertexCount(); - int geomTriCount = inMesh.getTriangleCount(); - - for (int bufType = 0; bufType < compsForBuf.length; bufType++) { - VertexBuffer inBuf = inMesh.getBuffer(Type.values()[bufType]); - VertexBuffer outBuf = outMesh.getBuffer(Type.values()[bufType]); - - if (inBuf == null || outBuf == null) { - continue; - } - - if (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 (Type.Position.ordinal() == bufType) { - FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); - FloatBuffer outPos = (FloatBuffer) outBuf.getData(); - doTransformVerts(inPos, globalVertIndex, outPos, worldMatrix); - } else if (Type.Normal.ordinal() == bufType) { - FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); - FloatBuffer outPos = (FloatBuffer) outBuf.getData(); - doTransformNorms(inPos, globalVertIndex, outPos, worldMatrix); - } else if (Type.Tangent.ordinal() == bufType) { - FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); - FloatBuffer outPos = (FloatBuffer) outBuf.getData(); - int components = inBuf.getNumComponents(); - doTransformTangents(inPos, globalVertIndex, components, outPos, worldMatrix); - } else { - inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); - } - } - - globalVertIndex += geomVertCount; - globalTriIndex += geomTriCount; - } - } - - public static void makeLods(Collection geometries, Mesh outMesh) { - int lodLevels = 0; - int[] lodSize = null; - int index = 0; - for (Geometry g : geometries) { - if (lodLevels == 0) { - lodLevels = g.getMesh().getNumLodLevels(); - } - if (lodSize == null) { - lodSize = new int[lodLevels]; - } - for (int i = 0; i < lodLevels; i++) { - lodSize[i] += g.getMesh().getLodLevel(i).getData().capacity(); - //if( i == 0) System.out.println(index + " " +lodSize[i]); - } - index++; - } - int[][] lodData = new int[lodLevels][]; - for (int i = 0; i < lodLevels; i++) { - lodData[i] = new int[lodSize[i]]; - } - VertexBuffer[] lods = new VertexBuffer[lodLevels]; - int bufferPos[] = new int[lodLevels]; - //int index = 0; - int numOfVertices = 0; - int curGeom = 0; - for (Geometry g : geometries) { - if (numOfVertices == 0) { - numOfVertices = g.getVertexCount(); - } - for (int i = 0; i < lodLevels; i++) { - ShortBuffer buffer = (ShortBuffer) g.getMesh().getLodLevel(i).getDataReadOnly(); - //System.out.println("buffer: " + buffer.capacity() + " limit: " + lodSize[i] + " " + index); - for (int j = 0; j < buffer.capacity(); j++) { - lodData[i][bufferPos[i] + j] = buffer.get() + numOfVertices * curGeom; - //bufferPos[i]++; - } - bufferPos[i] += buffer.capacity(); - } - curGeom++; - } - for (int i = 0; i < lodLevels; i++) { - lods[i] = new VertexBuffer(Type.Index); - lods[i].setupData(Usage.Dynamic, 1, Format.UnsignedInt, BufferUtils.createIntBuffer(lodData[i])); - } - System.out.println(lods.length); - outMesh.setLodLevels(lods); - } - - public static List makeBatches(Collection geometries) { - return makeBatches(geometries, false); - } - - /** - * Batches a collection of Geometries so that all with the same material get combined. - * @param geometries The Geometries to combine - * @return A List of newly created Geometries, each with a distinct material - */ - public static List makeBatches(Collection geometries, boolean useLods) { - ArrayList retVal = new ArrayList(); - HashMap> matToGeom = new HashMap>(); - - for (Geometry geom : geometries) { - List outList = matToGeom.get(geom.getMaterial()); - if (outList == null) { - //trying to compare materials with the contentEquals method - for (Material mat : matToGeom.keySet()) { - if (geom.getMaterial().contentEquals(mat)) { - outList = matToGeom.get(mat); - } - } - } - if (outList == null) { - outList = new ArrayList(); - matToGeom.put(geom.getMaterial(), outList); - } - outList.add(geom); - } - - int batchNum = 0; - for (Map.Entry> entry : matToGeom.entrySet()) { - Material mat = entry.getKey(); - List geomsForMat = entry.getValue(); - Mesh mesh = new Mesh(); - mergeGeometries(geomsForMat, mesh); - // lods - if (useLods) { - makeLods(geomsForMat, mesh); - } - mesh.updateCounts(); - mesh.updateBound(); - - Geometry out = new Geometry("batch[" + (batchNum++) + "]", mesh); - out.setMaterial(mat); - retVal.add(out); - } - - return retVal; - } - - public static void gatherGeoms(Spatial scene, List geoms) { - if (scene instanceof Node) { - Node node = (Node) scene; - for (Spatial child : node.getChildren()) { - gatherGeoms(child, geoms); - } - } else if (scene instanceof Geometry) { - geoms.add((Geometry) scene); - } - } - - /** - * Optimizes a scene by combining Geometry with the same material. - * All Geometries found in the scene are detached from their parent and - * a new Node containing the optimized Geometries is attached. - * @param scene The scene to optimize - * @return The newly created optimized geometries attached to a node - */ - public static Spatial optimize(Node scene) { - return optimize(scene, false); - } - - /** - * Optimizes a scene by combining Geometry with the same material. - * All Geometries found in the scene are detached from their parent and - * a new Node containing the optimized Geometries is attached. - * @param scene The scene to optimize - * @param useLods true if you want the resulting geometry to keep lod information - * @return The newly created optimized geometries attached to a node - */ - public static Node optimize(Node scene, boolean useLods) { - ArrayList geoms = new ArrayList(); - - gatherGeoms(scene, geoms); - - List batchedGeoms = makeBatches(geoms, useLods); - for (Geometry geom : batchedGeoms) { - scene.attachChild(geom); - } - - for (Iterator it = geoms.iterator(); it.hasNext();) { - Geometry geometry = it.next(); - geometry.removeFromParent(); - } - - // Since the scene is returned unaltered the transform must be reset - scene.setLocalTransform(Transform.IDENTITY); - - return scene; - } - - public static void printMesh(Mesh mesh) { - for (int bufType = 0; bufType < Type.values().length; bufType++) { - VertexBuffer outBuf = mesh.getBuffer(Type.values()[bufType]); - if (outBuf == null) { - continue; - } - - System.out.println(outBuf.getBufferType() + ": "); - for (int vert = 0; vert < outBuf.getNumElements(); vert++) { - String str = "["; - for (int comp = 0; comp < outBuf.getNumComponents(); comp++) { - Object val = outBuf.getElementComponent(vert, comp); - outBuf.setElementComponent(vert, comp, val); - val = outBuf.getElementComponent(vert, comp); - str += val; - if (comp != outBuf.getNumComponents() - 1) { - str += ", "; - } - } - str += "]"; - System.out.println(str); - } - System.out.println("------"); - } - } - - public static void main(String[] args) { - Mesh mesh = new Mesh(); - mesh.setBuffer(Type.Position, 3, new float[]{ - 0, 0, 0, - 1, 0, 0, - 1, 1, 0, - 0, 1, 0 - }); - mesh.setBuffer(Type.Index, 2, new short[]{ - 0, 1, - 1, 2, - 2, 3, - 3, 0 - }); - - Geometry g1 = new Geometry("g1", mesh); - - ArrayList geoms = new ArrayList(); - geoms.add(g1); - - Mesh outMesh = new Mesh(); - mergeGeometries(geoms, outMesh); - printMesh(outMesh); - } -} +package jme3tools.optimize; + +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.Mode; +import com.jme3.scene.*; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap.Entry; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; +import java.util.*; +import java.util.logging.Logger; + +public class GeometryBatchFactory { + + private static final Logger logger = Logger.getLogger(GeometryBatchFactory.class.getName()); + + private static void doTransformVerts(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) { + Vector3f pos = new Vector3f(); + + // 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); + + transform.mult(pos, pos); + + outBuf.put(offset + i * 3 + 0, pos.x); + outBuf.put(offset + i * 3 + 1, pos.y); + outBuf.put(offset + i * 3 + 2, pos.z); + } + } + + private static void doTransformNorms(FloatBuffer inBuf, int offset, FloatBuffer outBuf, Matrix4f transform) { + Vector3f norm = new Vector3f(); + + // offset is given in element units + // convert to be in component units + offset *= 3; + + for (int i = 0; i < inBuf.capacity() / 3; i++) { + norm.x = inBuf.get(i * 3 + 0); + norm.y = inBuf.get(i * 3 + 1); + norm.z = inBuf.get(i * 3 + 2); + + transform.multNormal(norm, norm); + + outBuf.put(offset + i * 3 + 0, norm.x); + outBuf.put(offset + i * 3 + 1, norm.y); + outBuf.put(offset + i * 3 + 2, norm.z); + } + } + + private static void doTransformTangents(FloatBuffer inBuf, int offset, int components, FloatBuffer outBuf, Matrix4f transform) { + Vector3f tan = new Vector3f(); + + // offset is given in element units + // convert to be in component units + offset *= components; + + for (int i = 0; i < inBuf.capacity() / components; i++) { + tan.x = inBuf.get(i * components + 0); + tan.y = inBuf.get(i * components + 1); + tan.z = inBuf.get(i * components + 2); + + transform.multNormal(tan, tan); + + outBuf.put(offset + i * components + 0, tan.x); + outBuf.put(offset + i * components + 1, tan.y); + outBuf.put(offset + i * components + 2, tan.z); + + if (components == 4) { + outBuf.put(offset + i * components + 3, inBuf.get(i * components + 3)); + } + } + } + + /** + * Merges all geometries in the collection into + * the output mesh. Creates a new material using the TextureAtlas. + * + * @param geometries + * @param outMesh + */ + public static void mergeGeometries(Collection geometries, Mesh outMesh) { + int[] compsForBuf = new int[VertexBuffer.Type.values().length]; + Format[] formatForBuf = new Format[compsForBuf.length]; + + int totalVerts = 0; + int totalTris = 0; + int totalLodLevels = 0; + + Mode mode = null; + for (Geometry geom : geometries) { + totalVerts += geom.getVertexCount(); + totalTris += geom.getTriangleCount(); + totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels()); + + Mode listMode; + int components; + switch (geom.getMesh().getMode()) { + case Points: + listMode = Mode.Points; + components = 1; + break; + case LineLoop: + case LineStrip: + case Lines: + listMode = Mode.Lines; + components = 2; + break; + case TriangleFan: + case TriangleStrip: + case Triangles: + listMode = Mode.Triangles; + components = 3; + break; + default: + throw new UnsupportedOperationException(); + } + + for (VertexBuffer vb : geom.getMesh().getBufferList().getArray()) { + compsForBuf[vb.getBufferType().ordinal()] = vb.getNumComponents(); + formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat(); + } + + if (mode != null && mode != listMode) { + throw new UnsupportedOperationException("Cannot combine different" + + " primitive types: " + mode + " != " + listMode); + } + mode = listMode; + compsForBuf[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[Type.Index.ordinal()] = Format.UnsignedInt; + } else { + formatForBuf[Type.Index.ordinal()] = 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 == Type.Index.ordinal()) { + data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris); + } else { + data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts); + } + + VertexBuffer vb = new VertexBuffer(Type.values()[i]); + vb.setupData(Usage.Static, compsForBuf[i], formatForBuf[i], data); + outMesh.setBuffer(vb); + } + + int globalVertIndex = 0; + int globalTriIndex = 0; + + for (Geometry geom : geometries) { + Mesh inMesh = geom.getMesh(); + geom.computeWorldMatrix(); + Matrix4f worldMatrix = geom.getWorldMatrix(); + + int geomVertCount = inMesh.getVertexCount(); + int geomTriCount = inMesh.getTriangleCount(); + + for (int bufType = 0; bufType < compsForBuf.length; bufType++) { + VertexBuffer inBuf = inMesh.getBuffer(Type.values()[bufType]); + VertexBuffer outBuf = outMesh.getBuffer(Type.values()[bufType]); + + if (inBuf == null || outBuf == null) { + continue; + } + + if (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 (Type.Position.ordinal() == bufType) { + FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + doTransformVerts(inPos, globalVertIndex, outPos, worldMatrix); + } else if (Type.Normal.ordinal() == bufType) { + FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + doTransformNorms(inPos, globalVertIndex, outPos, worldMatrix); + } else if (Type.Tangent.ordinal() == bufType) { + FloatBuffer inPos = (FloatBuffer) inBuf.getDataReadOnly(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + int components = inBuf.getNumComponents(); + doTransformTangents(inPos, globalVertIndex, components, outPos, worldMatrix); + } else { + inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); + } + } + + globalVertIndex += geomVertCount; + globalTriIndex += geomTriCount; + } + } + + public static void makeLods(Collection geometries, Mesh outMesh) { + int lodLevels = 0; + int[] lodSize = null; + int index = 0; + for (Geometry g : geometries) { + if (lodLevels == 0) { + lodLevels = g.getMesh().getNumLodLevels(); + } + if (lodSize == null) { + lodSize = new int[lodLevels]; + } + for (int i = 0; i < lodLevels; i++) { + lodSize[i] += g.getMesh().getLodLevel(i).getData().capacity(); + //if( i == 0) System.out.println(index + " " +lodSize[i]); + } + index++; + } + int[][] lodData = new int[lodLevels][]; + for (int i = 0; i < lodLevels; i++) { + lodData[i] = new int[lodSize[i]]; + } + VertexBuffer[] lods = new VertexBuffer[lodLevels]; + int bufferPos[] = new int[lodLevels]; + //int index = 0; + int numOfVertices = 0; + int curGeom = 0; + for (Geometry g : geometries) { + if (numOfVertices == 0) { + numOfVertices = g.getVertexCount(); + } + for (int i = 0; i < lodLevels; i++) { + ShortBuffer buffer = (ShortBuffer) g.getMesh().getLodLevel(i).getDataReadOnly(); + //System.out.println("buffer: " + buffer.capacity() + " limit: " + lodSize[i] + " " + index); + for (int j = 0; j < buffer.capacity(); j++) { + lodData[i][bufferPos[i] + j] = buffer.get() + numOfVertices * curGeom; + //bufferPos[i]++; + } + bufferPos[i] += buffer.capacity(); + } + curGeom++; + } + for (int i = 0; i < lodLevels; i++) { + lods[i] = new VertexBuffer(Type.Index); + lods[i].setupData(Usage.Dynamic, 1, Format.UnsignedInt, BufferUtils.createIntBuffer(lodData[i])); + } + System.out.println(lods.length); + outMesh.setLodLevels(lods); + } + + public static List makeBatches(Collection geometries) { + return makeBatches(geometries, false); + } + + /** + * Batches a collection of Geometries so that all with the same material get combined. + * @param geometries The Geometries to combine + * @return A List of newly created Geometries, each with a distinct material + */ + public static List makeBatches(Collection geometries, boolean useLods) { + ArrayList retVal = new ArrayList(); + HashMap> matToGeom = new HashMap>(); + + for (Geometry geom : geometries) { + List outList = matToGeom.get(geom.getMaterial()); + if (outList == null) { + //trying to compare materials with the contentEquals method + for (Material mat : matToGeom.keySet()) { + if (geom.getMaterial().dynamicHashCode() == mat.dynamicHashCode()) { + outList = matToGeom.get(mat); + } + } + } + if (outList == null) { + outList = new ArrayList(); + matToGeom.put(geom.getMaterial(), outList); + } + outList.add(geom); + } + + int batchNum = 0; + for (Map.Entry> entry : matToGeom.entrySet()) { + Material mat = entry.getKey(); + List geomsForMat = entry.getValue(); + Mesh mesh = new Mesh(); + mergeGeometries(geomsForMat, mesh); + // lods + if (useLods) { + makeLods(geomsForMat, mesh); + } + mesh.updateCounts(); + mesh.updateBound(); + + Geometry out = new Geometry("batch[" + (batchNum++) + "]", mesh); + out.setMaterial(mat); + retVal.add(out); + } + + return retVal; + } + + public static void gatherGeoms(Spatial scene, List geoms) { + if (scene instanceof Node) { + Node node = (Node) scene; + for (Spatial child : node.getChildren()) { + gatherGeoms(child, geoms); + } + } else if (scene instanceof Geometry) { + geoms.add((Geometry) scene); + } + } + + /** + * Optimizes a scene by combining Geometry with the same material. + * All Geometries found in the scene are detached from their parent and + * a new Node containing the optimized Geometries is attached. + * @param scene The scene to optimize + * @return The newly created optimized geometries attached to a node + */ + public static Spatial optimize(Node scene) { + return optimize(scene, false); + } + + /** + * Optimizes a scene by combining Geometry with the same material. + * All Geometries found in the scene are detached from their parent and + * a new Node containing the optimized Geometries is attached. + * @param scene The scene to optimize + * @param useLods true if you want the resulting geometry to keep lod information + * @return The newly created optimized geometries attached to a node + */ + public static Node optimize(Node scene, boolean useLods) { + ArrayList geoms = new ArrayList(); + + gatherGeoms(scene, geoms); + + List batchedGeoms = makeBatches(geoms, useLods); + for (Geometry geom : batchedGeoms) { + scene.attachChild(geom); + } + + for (Iterator it = geoms.iterator(); it.hasNext();) { + Geometry geometry = it.next(); + geometry.removeFromParent(); + } + + // Since the scene is returned unaltered the transform must be reset + scene.setLocalTransform(Transform.IDENTITY); + + return scene; + } + + public static void printMesh(Mesh mesh) { + for (int bufType = 0; bufType < Type.values().length; bufType++) { + VertexBuffer outBuf = mesh.getBuffer(Type.values()[bufType]); + if (outBuf == null) { + continue; + } + + System.out.println(outBuf.getBufferType() + ": "); + for (int vert = 0; vert < outBuf.getNumElements(); vert++) { + String str = "["; + for (int comp = 0; comp < outBuf.getNumComponents(); comp++) { + Object val = outBuf.getElementComponent(vert, comp); + outBuf.setElementComponent(vert, comp, val); + val = outBuf.getElementComponent(vert, comp); + str += val; + if (comp != outBuf.getNumComponents() - 1) { + str += ", "; + } + } + str += "]"; + System.out.println(str); + } + System.out.println("------"); + } + } + + public static void main(String[] args) { + Mesh mesh = new Mesh(); + mesh.setBuffer(Type.Position, 3, new float[]{ + 0, 0, 0, + 1, 0, 0, + 1, 1, 0, + 0, 1, 0 + }); + mesh.setBuffer(Type.Index, 2, new short[]{ + 0, 1, + 1, 2, + 2, 3, + 3, 0 + }); + + Geometry g1 = new Geometry("g1", mesh); + + ArrayList geoms = new ArrayList(); + geoms.add(g1); + + Mesh outMesh = new Mesh(); + mergeGeometries(geoms, outMesh); + printMesh(outMesh); + } +}