diff options
Diffstat (limited to 'engine/src/tools/jme3tools/optimize')
-rw-r--r-- | engine/src/tools/jme3tools/optimize/FastOctnode.java | 169 | ||||
-rw-r--r-- | engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java | 418 | ||||
-rw-r--r-- | engine/src/tools/jme3tools/optimize/OCTTriangle.java | 80 | ||||
-rw-r--r-- | engine/src/tools/jme3tools/optimize/Octnode.java | 317 | ||||
-rw-r--r-- | engine/src/tools/jme3tools/optimize/Octree.java | 163 | ||||
-rw-r--r-- | engine/src/tools/jme3tools/optimize/TestCollector.java | 54 | ||||
-rw-r--r-- | engine/src/tools/jme3tools/optimize/TextureAtlas.java | 686 | ||||
-rw-r--r-- | engine/src/tools/jme3tools/optimize/TriangleCollector.java | 248 | ||||
-rw-r--r-- | engine/src/tools/jme3tools/optimize/pvsnotes | 40 |
9 files changed, 2175 insertions, 0 deletions
diff --git a/engine/src/tools/jme3tools/optimize/FastOctnode.java b/engine/src/tools/jme3tools/optimize/FastOctnode.java new file mode 100644 index 0000000..d9e37da --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/FastOctnode.java @@ -0,0 +1,169 @@ +/* + * 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. + */ + +package jme3tools.optimize; + +import com.jme3.bounding.BoundingBox; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import java.util.Set; + +public class FastOctnode { + + int offset; + int length; + FastOctnode child; + FastOctnode next; + + private static final BoundingBox tempBox = new BoundingBox(); + + public int getSide(){ + return ((offset & 0xE0000000) >> 29) & 0x7; + } + + public void setSide(int side){ + offset &= 0x1FFFFFFF; + offset |= (side << 29); + } + + public void setOffset(int offset){ + if (offset < 0 || offset > 20000000){ + throw new IllegalArgumentException(); + } + + this.offset &= 0xE0000000; + this.offset |= offset; + } + + public int getOffset(){ + return this.offset & 0x1FFFFFFF; + } + + private void generateRenderSetNoCheck(Geometry[] globalGeomList, Set<Geometry> renderSet, Camera cam){ + if (length != 0){ + int start = getOffset(); + int end = start + length; + for (int i = start; i < end; i++){ + renderSet.add(globalGeomList[i]); + } + } + + if (child == null) + return; + + FastOctnode node = child; + while (node != null){ + node.generateRenderSetNoCheck(globalGeomList, renderSet, cam); + node = node.next; + } + } + + private static void findChildBound(BoundingBox bbox, int side){ + float extent = bbox.getXExtent() * 0.5f; + bbox.getCenter().set(bbox.getCenter().x + extent * Octnode.extentMult[side].x, + bbox.getCenter().y + extent * Octnode.extentMult[side].y, + bbox.getCenter().z + extent * Octnode.extentMult[side].z); + bbox.setXExtent(extent); + bbox.setYExtent(extent); + bbox.setZExtent(extent); + } + + public void generateRenderSet(Geometry[] globalGeomList, Set<Geometry> renderSet, Camera cam, BoundingBox parentBox, boolean isRoot){ + tempBox.setCenter(parentBox.getCenter()); + tempBox.setXExtent(parentBox.getXExtent()); + tempBox.setYExtent(parentBox.getYExtent()); + tempBox.setZExtent(parentBox.getZExtent()); + + if (!isRoot){ + findChildBound(tempBox, getSide()); + } + + tempBox.setCheckPlane(0); + cam.setPlaneState(0); + Camera.FrustumIntersect result = cam.contains(tempBox); + if (result != Camera.FrustumIntersect.Outside){ + if (length != 0){ + int start = getOffset(); + int end = start + length; + for (int i = start; i < end; i++){ + renderSet.add(globalGeomList[i]); + } + } + + if (child == null) + return; + + FastOctnode node = child; + + float x = tempBox.getCenter().x; + float y = tempBox.getCenter().y; + float z = tempBox.getCenter().z; + float ext = tempBox.getXExtent(); + + while (node != null){ + if (result == Camera.FrustumIntersect.Inside){ + node.generateRenderSetNoCheck(globalGeomList, renderSet, cam); + }else{ + node.generateRenderSet(globalGeomList, renderSet, cam, tempBox, false); + } + + tempBox.getCenter().set(x,y,z); + tempBox.setXExtent(ext); + tempBox.setYExtent(ext); + tempBox.setZExtent(ext); + + node = node.next; + } + } + } + + @Override + public String toString(){ + return "OCTNode[O=" + getOffset() + ", L=" + length + + ", S=" + getSide() + "]"; + } + + public String toStringVerbose(int indent){ + String str = "------------------".substring(0,indent) + toString() + "\n"; + if (child == null) + return str; + + FastOctnode children = child; + while (children != null){ + str += children.toStringVerbose(indent+1); + children = children.next; + } + + return str; + } + +} diff --git a/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java b/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java new file mode 100644 index 0000000..ae6ad8c --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java @@ -0,0 +1,418 @@ +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, FloatBuffer outBuf, Matrix4f transform) { + Vector3f tan = new Vector3f(); + float handedness = 0; + // offset is given in element units + // convert to be in component units + offset *= 4; + + for (int i = 0; i < inBuf.capacity() / 4; i++) { + tan.x = inBuf.get(i * 4 + 0); + tan.y = inBuf.get(i * 4 + 1); + tan.z = inBuf.get(i * 4 + 2); + handedness = inBuf.get(i * 4 + 3); + + transform.multNormal(tan, tan); + + outBuf.put(offset + i * 4 + 0, tan.x); + outBuf.put(offset + i * 4 + 1, tan.y); + outBuf.put(offset + i * 4 + 2, tan.z); + outBuf.put(offset + i * 4 + 3, handedness); + + } + } + + /** + * 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<Geometry> 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 (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[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(); + doTransformTangents(inPos, globalVertIndex, outPos, worldMatrix); + } else { + inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount); + } + } + + globalVertIndex += geomVertCount; + globalTriIndex += geomTriCount; + } + } + + public static void makeLods(Collection<Geometry> 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<Geometry> makeBatches(Collection<Geometry> 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<Geometry> makeBatches(Collection<Geometry> geometries, boolean useLods) { + ArrayList<Geometry> retVal = new ArrayList<Geometry>(); + HashMap<Material, List<Geometry>> matToGeom = new HashMap<Material, List<Geometry>>(); + + for (Geometry geom : geometries) { + List<Geometry> outList = matToGeom.get(geom.getMaterial()); + if (outList == null) { + outList = new ArrayList<Geometry>(); + matToGeom.put(geom.getMaterial(), outList); + } + outList.add(geom); + } + + int batchNum = 0; + for (Map.Entry<Material, List<Geometry>> entry : matToGeom.entrySet()) { + Material mat = entry.getKey(); + List<Geometry> 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<Geometry> 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<Geometry> geoms = new ArrayList<Geometry>(); + + gatherGeoms(scene, geoms); + + List<Geometry> batchedGeoms = makeBatches(geoms, useLods); + for (Geometry geom : batchedGeoms) { + scene.attachChild(geom); + } + + for (Iterator<Geometry> 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<Geometry> geoms = new ArrayList<Geometry>(); + geoms.add(g1); + + Mesh outMesh = new Mesh(); + mergeGeometries(geoms, outMesh); + printMesh(outMesh); + } +} diff --git a/engine/src/tools/jme3tools/optimize/OCTTriangle.java b/engine/src/tools/jme3tools/optimize/OCTTriangle.java new file mode 100644 index 0000000..b31a8a3 --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/OCTTriangle.java @@ -0,0 +1,80 @@ +/* + * 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. + */ + +package jme3tools.optimize; + +import com.jme3.math.Vector3f; + +public final class OCTTriangle { + + private final Vector3f pointa = new Vector3f(); + private final Vector3f pointb = new Vector3f(); + private final Vector3f pointc = new Vector3f(); + private final int index; + private final int geomIndex; + + public OCTTriangle(Vector3f p1, Vector3f p2, Vector3f p3, int index, int geomIndex) { + pointa.set(p1); + pointb.set(p2); + pointc.set(p3); + this.index = index; + this.geomIndex = geomIndex; + } + + public int getGeometryIndex() { + return geomIndex; + } + + public int getTriangleIndex() { + return index; + } + + public Vector3f get1(){ + return pointa; + } + + public Vector3f get2(){ + return pointb; + } + + public Vector3f get3(){ + return pointc; + } + + public Vector3f getNormal(){ + Vector3f normal = new Vector3f(pointb); + normal.subtractLocal(pointa).crossLocal(pointc.x-pointa.x, pointc.y-pointa.y, pointc.z-pointa.z); + normal.normalizeLocal(); + return normal; + } + +} diff --git a/engine/src/tools/jme3tools/optimize/Octnode.java b/engine/src/tools/jme3tools/optimize/Octnode.java new file mode 100644 index 0000000..e9a0080 --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/Octnode.java @@ -0,0 +1,317 @@ +/* + * 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. + */ + +package jme3tools.optimize; + +import com.jme3.bounding.BoundingBox; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.material.Material; +import com.jme3.math.Matrix4f; +import com.jme3.math.Ray; +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Geometry; +import com.jme3.scene.debug.WireBox; +import java.util.*; + +public class Octnode { + + static final Vector3f[] extentMult = new Vector3f[] + { + new Vector3f( 1, 1, 1), // right top forw + new Vector3f(-1, 1, 1), // left top forw + new Vector3f( 1,-1, 1), // right bot forw + new Vector3f(-1,-1, 1), // left bot forw + new Vector3f( 1, 1,-1), // right top back + new Vector3f(-1, 1,-1), // left top back + new Vector3f( 1,-1,-1), // right bot back + new Vector3f(-1,-1,-1) // left bot back + }; + + final BoundingBox bbox; + final ArrayList<OCTTriangle> tris; + Geometry[] geoms; + final Octnode[] children = new Octnode[8]; + boolean leaf = false; + FastOctnode fastNode; + + public Octnode(BoundingBox bbox, ArrayList<OCTTriangle> tris){ + this.bbox = bbox; + this.tris = tris; + } + + private BoundingBox getChildBound(int side){ + float extent = bbox.getXExtent() * 0.5f; + Vector3f center = new Vector3f(bbox.getCenter().x + extent * extentMult[side].x, + bbox.getCenter().y + extent * extentMult[side].y, + bbox.getCenter().z + extent * extentMult[side].z); + return new BoundingBox(center, extent, extent, extent); + } + + private float getAdditionCost(BoundingBox bbox, OCTTriangle t){ + if (bbox.intersects(t.get1(), t.get2(), t.get3())){ + float d1 = bbox.distanceToEdge(t.get1()); + float d2 = bbox.distanceToEdge(t.get2()); + float d3 = bbox.distanceToEdge(t.get3()); + return d1 + d2 + d3; + } + return Float.POSITIVE_INFINITY; + } + + private void expandBoxToContainTri(BoundingBox bbox, OCTTriangle t){ + Vector3f min = bbox.getMin(null); + Vector3f max = bbox.getMax(null); + BoundingBox.checkMinMax(min, max, t.get1()); + BoundingBox.checkMinMax(min, max, t.get2()); + BoundingBox.checkMinMax(min, max, t.get3()); + bbox.setMinMax(min, max); + } + + private boolean contains(BoundingBox bbox, OCTTriangle t){ + if (bbox.contains(t.get1()) && + bbox.contains(t.get2()) && + bbox.contains(t.get3())){ + return true; + } + return false; + } + + public void subdivide(int depth, int minTrisPerNode){ + if (tris == null || depth > 50 || bbox.getVolume() < 0.01f || tris.size() < minTrisPerNode){ + // no need to subdivide anymore + leaf = true; + return; + } + + ArrayList<OCTTriangle> keepTris = new ArrayList<OCTTriangle>(); + ArrayList[] trisForChild = new ArrayList[8]; + BoundingBox[] boxForChild = new BoundingBox[8]; + // create boxes for children + for (int i = 0; i < 8; i++){ + boxForChild[i] = getChildBound(i); + trisForChild[i] = new ArrayList<Triangle>(); + } + + for (OCTTriangle t : tris){ + float lowestCost = Float.POSITIVE_INFINITY; + int lowestIndex = -1; + int numIntersecting = 0; + for (int i = 0; i < 8; i++){ + BoundingBox childBox = boxForChild[i]; + float cost = getAdditionCost(childBox, t); + if (cost < lowestCost){ + lowestCost = cost; + lowestIndex = i; + numIntersecting++; + } + } + if (numIntersecting < 8 && lowestIndex > -1){ + trisForChild[lowestIndex].add(t); + expandBoxToContainTri(boxForChild[lowestIndex], t); + }else{ + keepTris.add(t); + } +// boolean wasAdded = false; +// for (int i = 0; i < 8; i++){ +// BoundingBox childBox = boxForChild[i]; +// if (contains(childBox, t)){ +// trisForChild[i].add(t); +// wasAdded = true; +// break; +// } +// } +// if (!wasAdded){ +// keepTris.add(t); +// } + } + tris.retainAll(keepTris); + for (int i = 0; i < 8; i++){ + if (trisForChild[i].size() > 0){ + children[i] = new Octnode(boxForChild[i], trisForChild[i]); + children[i].subdivide(depth + 1, minTrisPerNode); + } + } + } + + public void subdivide(int minTrisPerNode){ + subdivide(0, minTrisPerNode); + } + + public void createFastOctnode(List<Geometry> globalGeomList){ + fastNode = new FastOctnode(); + + if (geoms != null){ + Collection<Geometry> geomsColl = Arrays.asList(geoms); + List<Geometry> myOptimizedList = GeometryBatchFactory.makeBatches(geomsColl); + + int startIndex = globalGeomList.size(); + globalGeomList.addAll(myOptimizedList); + + fastNode.setOffset(startIndex); + fastNode.length = myOptimizedList.size(); + }else{ + fastNode.setOffset(0); + fastNode.length = 0; + } + + for (int i = 0; i < 8; i++){ + if (children[i] != null){ + children[i].createFastOctnode(globalGeomList); + } + } + } + + public void generateFastOctnodeLinks(Octnode parent, Octnode nextSibling, int side){ + fastNode.setSide(side); + fastNode.next = nextSibling != null ? nextSibling.fastNode : null; + + // We set the next sibling property by going in reverse order + Octnode prev = null; + for (int i = 7; i >= 0; i--){ + if (children[i] != null){ + children[i].generateFastOctnodeLinks(this, prev, i); + prev = children[i]; + } + } + fastNode.child = prev != null ? prev.fastNode : null; + } + + private void generateRenderSetNoCheck(Set<Geometry> renderSet, Camera cam){ + if (geoms != null){ + renderSet.addAll(Arrays.asList(geoms)); + } + for (int i = 0; i < 8; i++){ + if (children[i] != null){ + children[i].generateRenderSetNoCheck(renderSet, cam); + } + } + } + + public void generateRenderSet(Set<Geometry> renderSet, Camera cam){ +// generateRenderSetNoCheck(renderSet, cam); + + bbox.setCheckPlane(0); + cam.setPlaneState(0); + Camera.FrustumIntersect result = cam.contains(bbox); + if (result != Camera.FrustumIntersect.Outside){ + if (geoms != null){ + renderSet.addAll(Arrays.asList(geoms)); + } + for (int i = 0; i < 8; i++){ + if (children[i] != null){ + if (result == Camera.FrustumIntersect.Inside){ + children[i].generateRenderSetNoCheck(renderSet, cam); + }else{ + children[i].generateRenderSet(renderSet, cam); + } + } + } + } + } + + public void collectTriangles(Geometry[] inGeoms){ + if (tris.size() > 0){ + List<Geometry> geomsList = TriangleCollector.gatherTris(inGeoms, tris); + geoms = new Geometry[geomsList.size()]; + geomsList.toArray(geoms); + }else{ + geoms = null; + } + for (int i = 0; i < 8; i++){ + if (children[i] != null){ + children[i].collectTriangles(inGeoms); + } + } + } + + public void renderBounds(RenderQueue rq, Matrix4f transform, WireBox box, Material mat){ + int numChilds = 0; + for (int i = 0; i < 8; i++){ + if (children[i] != null){ + numChilds ++; + break; + } + } + if (geoms != null && numChilds == 0){ + BoundingBox bbox2 = new BoundingBox(bbox); + bbox.transform(transform, bbox2); +// WireBox box = new WireBox(bbox2.getXExtent(), bbox2.getYExtent(), +// bbox2.getZExtent()); +// WireBox box = new WireBox(1,1,1); + + Geometry geom = new Geometry("bound", box); + geom.setLocalTranslation(bbox2.getCenter()); + geom.setLocalScale(bbox2.getXExtent(), bbox2.getYExtent(), + bbox2.getZExtent()); + geom.updateGeometricState(); + geom.setMaterial(mat); + rq.addToQueue(geom, Bucket.Opaque); + box = null; + geom = null; + } + for (int i = 0; i < 8; i++){ + if (children[i] != null){ + children[i].renderBounds(rq, transform, box, mat); + } + } + } + + public final void intersectWhere(Ray r, Geometry[] geoms, float sceneMin, float sceneMax, + CollisionResults results){ + for (OCTTriangle t : tris){ + float d = r.intersects(t.get1(), t.get2(), t.get3()); + if (Float.isInfinite(d)) + continue; + + Vector3f contactPoint = new Vector3f(r.getDirection()).multLocal(d).addLocal(r.getOrigin()); + CollisionResult result = new CollisionResult(geoms[t.getGeometryIndex()], + contactPoint, + d, + t.getTriangleIndex()); + results.addCollision(result); + } + for (int i = 0; i < 8; i++){ + Octnode child = children[i]; + if (child == null) + continue; + + if (child.bbox.intersects(r)){ + child.intersectWhere(r, geoms, sceneMin, sceneMax, results); + } + } + } + +} diff --git a/engine/src/tools/jme3tools/optimize/Octree.java b/engine/src/tools/jme3tools/optimize/Octree.java new file mode 100644 index 0000000..880f984 --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/Octree.java @@ -0,0 +1,163 @@ +/* + * 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. + */ + +package jme3tools.optimize; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.CollisionResults; +import com.jme3.material.Material; +import com.jme3.math.Matrix4f; +import com.jme3.math.Ray; +import com.jme3.math.Triangle; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.WireBox; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class Octree { + + private final ArrayList<OCTTriangle> allTris = new ArrayList<OCTTriangle>(); + private final Geometry[] geoms; + private final BoundingBox bbox; + private final int minTrisPerNode; + private Octnode root; + + private CollisionResults boundResults = new CollisionResults(); + + private static List<Geometry> getGeometries(Spatial scene){ + if (scene instanceof Geometry){ + List<Geometry> geomList = new ArrayList<Geometry>(1); + geomList.add((Geometry) scene); + return geomList; + }else if (scene instanceof Node){ + Node n = (Node) scene; + List<Geometry> geoms = new ArrayList<Geometry>(); + for (Spatial child : n.getChildren()){ + geoms.addAll(getGeometries(child)); + } + return geoms; + }else{ + throw new UnsupportedOperationException("Unsupported scene element class"); + } + } + + public Octree(Spatial scene, int minTrisPerNode){ + scene.updateGeometricState(); + + List<Geometry> geomsList = getGeometries(scene); + geoms = new Geometry[geomsList.size()]; + geomsList.toArray(geoms); + // generate bound box for all geom + bbox = new BoundingBox(); + for (Geometry geom : geoms){ + BoundingVolume bv = geom.getWorldBound(); + bbox.mergeLocal(bv); + } + + // set largest extent + float extent = Math.max(bbox.getXExtent(), Math.max(bbox.getYExtent(), bbox.getZExtent())); + bbox.setXExtent(extent); + bbox.setYExtent(extent); + bbox.setZExtent(extent); + + this.minTrisPerNode = minTrisPerNode; + + Triangle t = new Triangle(); + for (int g = 0; g < geoms.length; g++){ + Mesh m = geoms[g].getMesh(); + for (int i = 0; i < m.getTriangleCount(); i++){ + m.getTriangle(i, t); + OCTTriangle ot = new OCTTriangle(t.get1(), t.get2(), t.get3(), i, g); + allTris.add(ot); + // convert triangle to world space +// geom.getWorldTransform().transformVector(t.get1(), t.get1()); +// geom.getWorldTransform().transformVector(t.get2(), t.get2()); +// geom.getWorldTransform().transformVector(t.get3(), t.get3()); + } + } + } + + public Octree(Spatial scene){ + this(scene,11); + } + + public void construct(){ + root = new Octnode(bbox, allTris); + root.subdivide(minTrisPerNode); + root.collectTriangles(geoms); + } + + public void createFastOctnodes(List<Geometry> globalGeomList){ + root.createFastOctnode(globalGeomList); + } + + public BoundingBox getBound(){ + return bbox; + } + + public FastOctnode getFastRoot(){ + return root.fastNode; + } + + public void generateFastOctnodeLinks(){ + root.generateFastOctnodeLinks(null, null, 0); + } + + public void generateRenderSet(Set<Geometry> renderSet, Camera cam){ + root.generateRenderSet(renderSet, cam); + } + + public void renderBounds(RenderQueue rq, Matrix4f transform, WireBox box, Material mat){ + root.renderBounds(rq, transform, box, mat); + } + + public void intersect(Ray r, float farPlane, Geometry[] geoms, CollisionResults results){ + boundResults.clear(); + bbox.collideWith(r, boundResults); + if (boundResults.size() > 0){ + float tMin = boundResults.getClosestCollision().getDistance(); + float tMax = boundResults.getFarthestCollision().getDistance(); + + tMin = Math.max(tMin, 0); + tMax = Math.min(tMax, farPlane); + + root.intersectWhere(r, geoms, tMin, tMax, results); + } + } +} diff --git a/engine/src/tools/jme3tools/optimize/TestCollector.java b/engine/src/tools/jme3tools/optimize/TestCollector.java new file mode 100644 index 0000000..cc451aa --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/TestCollector.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package jme3tools.optimize; + +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import java.util.ArrayList; +import java.util.List; + +public class TestCollector { + + public static void main(String[] args){ + Vector3f z = Vector3f.ZERO; + Geometry g = new Geometry("quad", new Quad(2,2)); + Geometry g2 = new Geometry("quad", new Quad(2,2)); + List<OCTTriangle> tris = new ArrayList<OCTTriangle>(); + tris.add(new OCTTriangle(z, z, z, 1, 0)); + tris.add(new OCTTriangle(z, z, z, 0, 1)); + List<Geometry> firstOne = TriangleCollector.gatherTris(new Geometry[]{ g, g2 }, tris); + System.out.println(firstOne.get(0).getMesh()); + } + +} diff --git a/engine/src/tools/jme3tools/optimize/TextureAtlas.java b/engine/src/tools/jme3tools/optimize/TextureAtlas.java new file mode 100644 index 0000000..13c8b69 --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/TextureAtlas.java @@ -0,0 +1,686 @@ +/* + * Copyright (c) 2009-2012 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. + */ +package jme3tools.optimize; + +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.material.MatParamTexture; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.util.BufferUtils; +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * <b><code>TextureAtlas</code></b> allows combining multiple textures to one texture atlas. + * + * <p>After the TextureAtlas has been created with a certain size, textures can be added for + * freely chosen "map names". The textures are automatically placed on the atlas map and the + * image data is stored in a byte array for each map name. Later each map can be retrieved as + * a Texture to be used further in materials.</p> + * + * <p>The first map name used is the "master map" that defines new locations on the atlas. Secondary + * textures (other map names) have to reference a texture of the master map to position the texture + * on the secondary map. This is necessary as the maps share texture coordinates and thus need to be + * placed at the same location on both maps.</p> + * + * <p>The helper methods that work with <code>Geometry</code> objects handle the <em>DiffuseMap</em> or <em>ColorMap</em> as the master map and + * additionally handle <em>NormalMap</em> and <em>SpecularMap</em> as secondary maps.</p> + * + * <p>The textures are referenced by their <b>asset key name</b> and for each texture the location + * inside the atlas is stored. A texture with an existing key name is never added more than once + * to the atlas. You can access the information for each texture or geometry texture via helper methods.</p> + * + * <p>The TextureAtlas also allows you to change the texture coordinates of a mesh or geometry + * to point at the new locations of its texture inside the atlas (if the texture exists inside the atlas).</p> + * + * <p>Note that models that use texture coordinates outside the 0-1 range (repeating/wrapping textures) + * will not work correctly as their new coordinates leak into other parts of the atlas and thus display + * other textures instead of repeating the texture.</p> + * + * <p>Also note that textures are not scaled and the atlas needs to be large enough to hold all textures. + * All methods that allow adding textures return false if the texture could not be added due to the + * atlas being full. Furthermore secondary textures (normal, spcular maps etc.) have to be the same size + * as the main (e.g. DiffuseMap) texture.</p> + * + * <p><b>Usage examples</b></p> + * Create one geometry out of several geometries that are loaded from a j3o file: + * <pre> + * Node scene = assetManager.loadModel("Scenes/MyScene.j3o"); + * Geometry geom = TextureAtlas.makeAtlasBatch(scene); + * rootNode.attachChild(geom); + * </pre> + * Create a texture atlas and change the texture coordinates of one geometry: + * <pre> + * Node scene = assetManager.loadModel("Scenes/MyScene.j3o"); + * //either auto-create from node: + * TextureAtlas atlas = TextureAtlas.createAtlas(scene); + * //or create manually by adding textures or geometries with textures + * TextureAtlas atlas = new TextureAtlas(1024,1024); + * atlas.addTexture(myTexture, "DiffuseMap"); + * atlas.addGeometry(myGeometry); + * //create material and set texture + * Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md"); + * mat.setTexture("DiffuseMap", atlas.getAtlasTexture("DiffuseMap")); + * //change one geometry to use atlas, apply texture coordinates and replace material. + * Geometry geom = scene.getChild("MyGeometry"); + * atlas.applyCoords(geom); + * geom.setMaterial(mat); + * </pre> + * + * @author normenhansen, Lukasz Bruun - lukasz.dk + */ +public class TextureAtlas { + + private static final Logger logger = Logger.getLogger(TextureAtlas.class.getName()); + private Map<String, byte[]> images; + private int atlasWidth, atlasHeight; + private Format format = Format.ABGR8; + private Node root; + private Map<String, TextureAtlasTile> locationMap; + private Map<String, String> mapNameMap; + private String rootMapName; + + public TextureAtlas(int width, int height) { + this.atlasWidth = width; + this.atlasHeight = height; + root = new Node(0, 0, width, height); + locationMap = new TreeMap<String, TextureAtlasTile>(); + mapNameMap = new HashMap<String, String>(); + } + + /** + * Add a geometries DiffuseMap (or ColorMap), NormalMap and SpecularMap to the atlas. + * @param geometry + * @return false if the atlas is full. + */ + public boolean addGeometry(Geometry geometry) { + Texture diffuse = getMaterialTexture(geometry, "DiffuseMap"); + Texture normal = getMaterialTexture(geometry, "NormalMap"); + Texture specular = getMaterialTexture(geometry, "SpecularMap"); + if (diffuse == null) { + diffuse = getMaterialTexture(geometry, "ColorMap"); + + } + if (diffuse != null && diffuse.getKey() != null) { + String keyName = diffuse.getKey().toString(); + if (!addTexture(diffuse, "DiffuseMap")) { + return false; + } else { + if (normal != null && normal.getKey() != null) { + addTexture(diffuse, "NormalMap", keyName); + } + if (specular != null && specular.getKey() != null) { + addTexture(specular, "SpecularMap", keyName); + } + } + return true; + } + return true; + } + + /** + * Add a texture for a specific map name + * @param texture A texture to add to the atlas. + * @param mapName A freely chosen map name that can be later retrieved as a Texture. The first map name supplied will be the master map. + * @return false if the atlas is full. + */ + public boolean addTexture(Texture texture, String mapName) { + if (texture == null) { + throw new IllegalStateException("Texture cannot be null!"); + } + String name = textureName(texture); + if (texture.getImage() != null && name != null) { + return addImage(texture.getImage(), name, mapName, null); + } else { + throw new IllegalStateException("Texture has no asset key name!"); + } + } + + /** + * Add a texture for a specific map name at the location of another existing texture on the master map. + * @param texture A texture to add to the atlas. + * @param mapName A freely chosen map name that can be later retrieved as a Texture. + * @param masterTexture The master texture for determining the location, it has to exist in tha master map. + */ + public void addTexture(Texture texture, String mapName, Texture masterTexture) { + String sourceTextureName = textureName(masterTexture); + if (sourceTextureName == null) { + throw new IllegalStateException("Supplied master map texture has no asset key name!"); + } else { + addTexture(texture, mapName, sourceTextureName); + } + } + + /** + * Add a texture for a specific map name at the location of another existing texture (on the master map). + * @param texture A texture to add to the atlas. + * @param mapName A freely chosen map name that can be later retrieved as a Texture. + * @param sourceTextureName Name of the master map used for the location. + */ + public void addTexture(Texture texture, String mapName, String sourceTextureName) { + if (texture == null) { + throw new IllegalStateException("Texture cannot be null!"); + } + String name = textureName(texture); + if (texture.getImage() != null && name != null) { + addImage(texture.getImage(), name, mapName, sourceTextureName); + } else { + throw new IllegalStateException("Texture has no asset key name!"); + } + } + + private String textureName(Texture texture) { + if (texture == null) { + return null; + } + AssetKey key = texture.getKey(); + if (key != null) { + return key.toString(); + } else { + return null; + } + } + + private boolean addImage(Image image, String name, String mapName, String sourceTextureName) { + if (rootMapName == null) { + rootMapName = mapName; + } + if (sourceTextureName == null && !rootMapName.equals(mapName)) { + throw new IllegalStateException("Atlas already has a master map called " + rootMapName + "." + + " Textures for new maps have to use a texture from the master map for their location."); + } + TextureAtlasTile location = locationMap.get(name); + if (location != null) { + //have location for texture + if (!mapName.equals(mapNameMap.get(name))) { + logger.log(Level.WARNING, "Same texture " + name + " is used in different maps! (" + mapName + " and " + mapNameMap.get(name) + "). Location will be based on location in " + mapNameMap.get(name) + "!"); + drawImage(image, location.getX(), location.getY(), mapName); + return true; + } else { + return true; + } + } else if (sourceTextureName == null) { + //need to make new tile + Node node = root.insert(image); + if (node == null) { + return false; + } + location = node.location; + } else { + //got old tile to align to + location = locationMap.get(sourceTextureName); + if (location == null) { + throw new IllegalStateException("Cannot find master map texture for " + name + "."); + } else if (location.width != image.getWidth() || location.height != image.getHeight()) { + throw new IllegalStateException(mapName + " " + name + " does not fit " + rootMapName + " tile size. Make sure all textures (diffuse, normal, specular) for one model are the same size."); + } + } + mapNameMap.put(name, mapName); + locationMap.put(name, location); + drawImage(image, location.getX(), location.getY(), mapName); + return true; + } + + private void drawImage(Image source, int x, int y, String mapName) { + if (images == null) { + images = new HashMap<String, byte[]>(); + } + byte[] image = images.get(mapName); + if (image == null) { + image = new byte[atlasWidth * atlasHeight * 4]; + images.put(mapName, image); + } + //TODO: all buffers? + ByteBuffer sourceData = source.getData(0); + int height = source.getHeight(); + int width = source.getWidth(); + Image newImage = null; + for (int yPos = 0; yPos < height; yPos++) { + for (int xPos = 0; xPos < width; xPos++) { + int i = ((xPos + x) + (yPos + y) * atlasWidth) * 4; + if (source.getFormat() == Format.ABGR8) { + int j = (xPos + yPos * width) * 4; + image[i] = sourceData.get(j); //a + image[i + 1] = sourceData.get(j + 1); //b + image[i + 2] = sourceData.get(j + 2); //g + image[i + 3] = sourceData.get(j + 3); //r + } else if (source.getFormat() == Format.BGR8) { + int j = (xPos + yPos * width) * 3; + image[i] = 1; //a + image[i + 1] = sourceData.get(j); //b + image[i + 2] = sourceData.get(j + 1); //g + image[i + 3] = sourceData.get(j + 2); //r + } else if (source.getFormat() == Format.RGB8) { + int j = (xPos + yPos * width) * 3; + image[i] = 1; //a + image[i + 1] = sourceData.get(j + 2); //b + image[i + 2] = sourceData.get(j + 1); //g + image[i + 3] = sourceData.get(j); //r + } else if (source.getFormat() == Format.RGBA8) { + int j = (xPos + yPos * width) * 4; + image[i] = sourceData.get(j + 3); //a + image[i + 1] = sourceData.get(j + 2); //b + image[i + 2] = sourceData.get(j + 1); //g + image[i + 3] = sourceData.get(j); //r + } else if (source.getFormat() == Format.Luminance8) { + int j = (xPos + yPos * width) * 1; + image[i] = 1; //a + image[i + 1] = sourceData.get(j); //b + image[i + 2] = sourceData.get(j); //g + image[i + 3] = sourceData.get(j); //r + } else if (source.getFormat() == Format.Luminance8Alpha8) { + int j = (xPos + yPos * width) * 2; + image[i] = sourceData.get(j + 1); //a + image[i + 1] = sourceData.get(j); //b + image[i + 2] = sourceData.get(j); //g + image[i + 3] = sourceData.get(j); //r + } else { + //ImageToAwt conversion + if (newImage == null) { + newImage = convertImageToAwt(source); + if (newImage != null) { + source = newImage; + sourceData = source.getData(0); + int j = (xPos + yPos * width) * 4; + image[i] = sourceData.get(j); //a + image[i + 1] = sourceData.get(j + 1); //b + image[i + 2] = sourceData.get(j + 2); //g + image[i + 3] = sourceData.get(j + 3); //r + }else{ + throw new UnsupportedOperationException("Cannot draw or convert textures with format " + source.getFormat()); + } + } else { + throw new UnsupportedOperationException("Cannot draw textures with format " + source.getFormat()); + } + } + } + } + } + + private Image convertImageToAwt(Image source) { + //use awt dependent classes without actual dependency via reflection + try { + Class clazz = Class.forName("jme3tools.converters.ImageToAwt"); + if (clazz == null) { + return null; + } + Image newImage = new Image(format, source.getWidth(), source.getHeight(), BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 4)); + clazz.getMethod("convert", Image.class, Image.class).invoke(clazz.newInstance(), source, newImage); + return newImage; + } catch (InstantiationException ex) { + } catch (IllegalAccessException ex) { + } catch (IllegalArgumentException ex) { + } catch (InvocationTargetException ex) { + } catch (NoSuchMethodException ex) { + } catch (SecurityException ex) { + } catch (ClassNotFoundException ex) { + } + return null; + } + + /** + * Get the <code>TextureAtlasTile</code> for the given Texture + * @param texture The texture to retrieve the <code>TextureAtlasTile</code> for. + * @return + */ + public TextureAtlasTile getAtlasTile(Texture texture) { + String sourceTextureName = textureName(texture); + if (sourceTextureName != null) { + return getAtlasTile(sourceTextureName); + } + return null; + } + + /** + * Get the <code>TextureAtlasTile</code> for the given Texture + * @param assetName The texture to retrieve the <code>TextureAtlasTile</code> for. + * @return + */ + private TextureAtlasTile getAtlasTile(String assetName) { + return locationMap.get(assetName); + } + + /** + * Creates a new atlas texture for the given map name. + * @param mapName + * @return + */ + public Texture getAtlasTexture(String mapName) { + if (images == null) { + return null; + } + byte[] image = images.get(mapName); + if (image != null) { + Texture2D tex = new Texture2D(new Image(format, atlasWidth, atlasHeight, BufferUtils.createByteBuffer(image))); + tex.setMagFilter(Texture.MagFilter.Bilinear); + tex.setMinFilter(Texture.MinFilter.BilinearNearestMipMap); + tex.setWrap(Texture.WrapMode.Clamp); + return tex; + } + return null; + } + + /** + * Applies the texture coordinates to the given geometry + * if its DiffuseMap or ColorMap exists in the atlas. + * @param geom The geometry to change the texture coordinate buffer on. + * @return true if texture has been found and coords have been changed, false otherwise. + */ + public boolean applyCoords(Geometry geom) { + return applyCoords(geom, 0, geom.getMesh()); + } + + /** + * Applies the texture coordinates to the given output mesh + * if the DiffuseMap or ColorMap of the input geometry exist in the atlas. + * @param geom The geometry to change the texture coordinate buffer on. + * @param offset Target buffer offset. + * @param outMesh The mesh to set the coords in (can be same as input). + * @return true if texture has been found and coords have been changed, false otherwise. + */ + public boolean applyCoords(Geometry geom, int offset, Mesh outMesh) { + Mesh inMesh = geom.getMesh(); + geom.computeWorldMatrix(); + + VertexBuffer inBuf = inMesh.getBuffer(Type.TexCoord); + VertexBuffer outBuf = outMesh.getBuffer(Type.TexCoord); + + if (inBuf == null || outBuf == null) { + throw new IllegalStateException("Geometry mesh has no texture coordinate buffer."); + } + + Texture tex = getMaterialTexture(geom, "DiffuseMap"); + if (tex == null) { + tex = getMaterialTexture(geom, "ColorMap"); + + } + if (tex != null) { + TextureAtlasTile tile = getAtlasTile(tex); + if (tile != null) { + FloatBuffer inPos = (FloatBuffer) inBuf.getData(); + FloatBuffer outPos = (FloatBuffer) outBuf.getData(); + tile.transformTextureCoords(inPos, offset, outPos); + return true; + } else { + return false; + } + } else { + throw new IllegalStateException("Geometry has no proper texture."); + } + } + + /** + * Create a texture atlas for the given root node, containing DiffuseMap, NormalMap and SpecularMap. + * @param root The rootNode to create the atlas for. + * @param atlasSize The size of the atlas (width and height). + * @return Null if the atlas cannot be created because not all textures fit. + */ + public static TextureAtlas createAtlas(Spatial root, int atlasSize) { + List<Geometry> geometries = new ArrayList<Geometry>(); + GeometryBatchFactory.gatherGeoms(root, geometries); + TextureAtlas atlas = new TextureAtlas(atlasSize, atlasSize); + for (Geometry geometry : geometries) { + if (!atlas.addGeometry(geometry)) { + logger.log(Level.WARNING, "Texture atlas size too small, cannot add all textures"); + return null; + } + } + return atlas; + } + + /** + * Creates one geometry out of the given root spatial and merges all single + * textures into one texture of the given size. + * @param spat The root spatial of the scene to batch + * @param mgr An assetmanager that can be used to create the material. + * @param atlasSize A size for the atlas texture, it has to be large enough to hold all single textures. + * @return A new geometry that uses the generated texture atlas and merges all meshes of the root spatial, null if the atlas cannot be created because not all textures fit. + */ + public static Geometry makeAtlasBatch(Spatial spat, AssetManager mgr, int atlasSize) { + List<Geometry> geometries = new ArrayList<Geometry>(); + GeometryBatchFactory.gatherGeoms(spat, geometries); + TextureAtlas atlas = createAtlas(spat, atlasSize); + if (atlas == null) { + return null; + } + Geometry geom = new Geometry(); + Mesh mesh = new Mesh(); + GeometryBatchFactory.mergeGeometries(geometries, mesh); + applyAtlasCoords(geometries, mesh, atlas); + mesh.updateCounts(); + mesh.updateBound(); + geom.setMesh(mesh); + + Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md"); + mat.getAdditionalRenderState().setAlphaTest(true); + Texture diffuseMap = atlas.getAtlasTexture("DiffuseMap"); + Texture normalMap = atlas.getAtlasTexture("NormalMap"); + Texture specularMap = atlas.getAtlasTexture("SpecularMap"); + if (diffuseMap != null) { + mat.setTexture("DiffuseMap", diffuseMap); + } + if (normalMap != null) { + mat.setTexture("NormalMap", normalMap); + } + if (specularMap != null) { + mat.setTexture("SpecularMap", specularMap); + } + mat.setFloat("Shininess", 16.0f); + + geom.setMaterial(mat); + return geom; + } + + private static void applyAtlasCoords(List<Geometry> geometries, Mesh outMesh, TextureAtlas atlas) { + int globalVertIndex = 0; + + for (Geometry geom : geometries) { + Mesh inMesh = geom.getMesh(); + geom.computeWorldMatrix(); + + int geomVertCount = inMesh.getVertexCount(); + + VertexBuffer inBuf = inMesh.getBuffer(Type.TexCoord); + VertexBuffer outBuf = outMesh.getBuffer(Type.TexCoord); + + if (inBuf == null || outBuf == null) { + continue; + } + + atlas.applyCoords(geom, globalVertIndex, outMesh); + + globalVertIndex += geomVertCount; + } + } + + private static Texture getMaterialTexture(Geometry geometry, String mapName) { + Material mat = geometry.getMaterial(); + if (mat == null || mat.getParam(mapName) == null || !(mat.getParam(mapName) instanceof MatParamTexture)) { + return null; + } + MatParamTexture param = (MatParamTexture) mat.getParam(mapName); + Texture texture = param.getTextureValue(); + if (texture == null) { + return null; + } + return texture; + + + } + + private class Node { + + public TextureAtlasTile location; + public Node child[]; + public boolean occupied; + + public Node(int x, int y, int width, int height) { + location = new TextureAtlasTile(x, y, width, height); + child = new Node[2]; + child[0] = null; + child[1] = null; + occupied = false; + } + + public boolean isLeaf() { + return child[0] == null && child[1] == null; + } + + // Algorithm from http://www.blackpawn.com/texts/lightmaps/ + public Node insert(Image image) { + if (!isLeaf()) { + Node newNode = child[0].insert(image); + + if (newNode != null) { + return newNode; + } + + return child[1].insert(image); + } else { + if (occupied) { + return null; // occupied + } + + if (image.getWidth() > location.getWidth() || image.getHeight() > location.getHeight()) { + return null; // does not fit + } + + if (image.getWidth() == location.getWidth() && image.getHeight() == location.getHeight()) { + occupied = true; // perfect fit + return this; + } + + int dw = location.getWidth() - image.getWidth(); + int dh = location.getHeight() - image.getHeight(); + + if (dw > dh) { + child[0] = new Node(location.getX(), location.getY(), image.getWidth(), location.getHeight()); + child[1] = new Node(location.getX() + image.getWidth(), location.getY(), location.getWidth() - image.getWidth(), location.getHeight()); + } else { + child[0] = new Node(location.getX(), location.getY(), location.getWidth(), image.getHeight()); + child[1] = new Node(location.getX(), location.getY() + image.getHeight(), location.getWidth(), location.getHeight() - image.getHeight()); + } + + return child[0].insert(image); + } + } + } + + public class TextureAtlasTile { + + private int x; + private int y; + private int width; + private int height; + + public TextureAtlasTile(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + /** + * Get the transformed texture coordinate for a given input location. + * @param previousLocation The old texture coordinate. + * @return The new texture coordinate inside the atlas. + */ + public Vector2f getLocation(Vector2f previousLocation) { + float x = (float) getX() / (float) atlasWidth; + float y = (float) getY() / (float) atlasHeight; + float w = (float) getWidth() / (float) atlasWidth; + float h = (float) getHeight() / (float) atlasHeight; + Vector2f location = new Vector2f(x, y); + float prevX = previousLocation.x; + float prevY = previousLocation.y; + location.addLocal(prevX * w, prevY * h); + return location; + } + + /** + * Transforms a whole texture coordinates buffer. + * @param inBuf The input texture buffer. + * @param offset The offset in the output buffer + * @param outBuf The output buffer. + */ + public void transformTextureCoords(FloatBuffer inBuf, int offset, FloatBuffer outBuf) { + Vector2f tex = new Vector2f(); + + // offset is given in element units + // convert to be in component units + offset *= 2; + + for (int i = 0; i < inBuf.capacity() / 2; i++) { + tex.x = inBuf.get(i * 2 + 0); + tex.y = inBuf.get(i * 2 + 1); + Vector2f location = getLocation(tex); + //TODO: add proper texture wrapping for atlases.. + outBuf.put(offset + i * 2 + 0, location.x); + outBuf.put(offset + i * 2 + 1, location.y); + } + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + } +} diff --git a/engine/src/tools/jme3tools/optimize/TriangleCollector.java b/engine/src/tools/jme3tools/optimize/TriangleCollector.java new file mode 100644 index 0000000..9ce4ffa --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/TriangleCollector.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2009-2012 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. + */ + +package jme3tools.optimize; + +import com.jme3.light.Light; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.nio.Buffer; +import java.nio.ShortBuffer; +import java.util.*; + +public class TriangleCollector { + + private static final GeomTriComparator comparator = new GeomTriComparator(); + + private static class GeomTriComparator implements Comparator<OCTTriangle> { + public int compare(OCTTriangle a, OCTTriangle b) { + if (a.getGeometryIndex() < b.getGeometryIndex()){ + return -1; + }else if (a.getGeometryIndex() > b.getGeometryIndex()){ + return 1; + }else{ + return 0; + } + } + } + + private static class Range { + + private int start, length; + + public Range(int start, int length) { + this.start = start; + this.length = length; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + } + + /** + * Grabs all the triangles specified in <code>tris</code> from the input array + * (using the indices OCTTriangle.getGeometryIndex() & OCTTriangle.getTriangleIndex()) + * then organizes them into output geometry. + * + * @param inGeoms + * @param tris + * @return + */ + public static final List<Geometry> gatherTris(Geometry[] inGeoms, List<OCTTriangle> tris){ + Collections.sort(tris, comparator); + HashMap<Integer, Range> ranges = new HashMap<Integer, Range>(); + + for (int i = 0; i < tris.size(); i++){ + Range r = ranges.get(tris.get(i).getGeometryIndex()); + if (r != null){ + // incremenet length + r.setLength(r.getLength()+1); + }else{ + // set offset, length is 1 + ranges.put(tris.get(i).getGeometryIndex(), new Range(i, 1)); + } + } + + List<Geometry> newGeoms = new ArrayList<Geometry>(); + int[] vertIndicies = new int[3]; + int[] newIndices = new int[3]; + boolean[] vertexCreated = new boolean[3]; + HashMap<Integer, Integer> indexCache = new HashMap<Integer, Integer>(); + for (Map.Entry<Integer, Range> entry : ranges.entrySet()){ + int inGeomIndex = entry.getKey().intValue(); + int outOffset = entry.getValue().start; + int outLength = entry.getValue().length; + + Geometry inGeom = inGeoms[inGeomIndex]; + Mesh in = inGeom.getMesh(); + Mesh out = new Mesh(); + + int outElementCount = outLength * 3; + ShortBuffer ib = BufferUtils.createShortBuffer(outElementCount); + out.setBuffer(Type.Index, 3, ib); + + // generate output buffers based on input buffers + IntMap<VertexBuffer> bufs = in.getBuffers(); + for (Entry<VertexBuffer> ent : bufs){ + VertexBuffer vb = ent.getValue(); + if (vb.getBufferType() == Type.Index) + continue; + + // NOTE: we are not actually sure + // how many elements will be in this buffer. + // It will be compacted later. + Buffer b = VertexBuffer.createBuffer(vb.getFormat(), + vb.getNumComponents(), + outElementCount); + + VertexBuffer outVb = new VertexBuffer(vb.getBufferType()); + outVb.setNormalized(vb.isNormalized()); + outVb.setupData(vb.getUsage(), vb.getNumComponents(), vb.getFormat(), b); + out.setBuffer(outVb); + } + + int currentVertex = 0; + for (int i = outOffset; i < outOffset + outLength; i++){ + OCTTriangle t = tris.get(i); + + // find vertex indices for triangle t + in.getTriangle(t.getTriangleIndex(), vertIndicies); + + // find indices in new buf + Integer i0 = indexCache.get(vertIndicies[0]); + Integer i1 = indexCache.get(vertIndicies[1]); + Integer i2 = indexCache.get(vertIndicies[2]); + + // check which ones were not created + // if not created in new IB, create them + if (i0 == null){ + vertexCreated[0] = true; + newIndices[0] = currentVertex++; + indexCache.put(vertIndicies[0], newIndices[0]); + }else{ + newIndices[0] = i0.intValue(); + vertexCreated[0] = false; + } + if (i1 == null){ + vertexCreated[1] = true; + newIndices[1] = currentVertex++; + indexCache.put(vertIndicies[1], newIndices[1]); + }else{ + newIndices[1] = i1.intValue(); + vertexCreated[1] = false; + } + if (i2 == null){ + vertexCreated[2] = true; + newIndices[2] = currentVertex++; + indexCache.put(vertIndicies[2], newIndices[2]); + }else{ + newIndices[2] = i2.intValue(); + vertexCreated[2] = false; + } + + // if any verticies were created for this triangle + // copy them to the output mesh + IntMap<VertexBuffer> inbufs = in.getBuffers(); + for (Entry<VertexBuffer> ent : inbufs){ + VertexBuffer vb = ent.getValue(); + if (vb.getBufferType() == Type.Index) + continue; + + VertexBuffer outVb = out.getBuffer(vb.getBufferType()); + // copy verticies that were created for this triangle + for (int v = 0; v < 3; v++){ + if (!vertexCreated[v]) + continue; + + // copy triangle's attribute from one + // buffer to another + vb.copyElement(vertIndicies[v], outVb, newIndices[v]); + } + } + + // write the indices onto the output index buffer + ib.put((short)newIndices[0]) + .put((short)newIndices[1]) + .put((short)newIndices[2]); + } + ib.clear(); + indexCache.clear(); + + // since some verticies were cached, it means there's + // extra data in some buffers + IntMap<VertexBuffer> outbufs = out.getBuffers(); + for (Entry<VertexBuffer> ent : outbufs){ + VertexBuffer vb = ent.getValue(); + if (vb.getBufferType() == Type.Index) + continue; + + vb.compact(currentVertex); + } + + out.updateBound(); + out.updateCounts(); + out.setStatic(); + //out.setInterleaved(); + Geometry outGeom = new Geometry("Geom"+entry.getKey(), out); + outGeom.setLocalTransform(inGeom.getWorldTransform()); + outGeom.setMaterial(inGeom.getMaterial()); + for (Light light : inGeom.getWorldLightList()){ + outGeom.addLight(light); + } + + outGeom.updateGeometricState(); + newGeoms.add(outGeom); + } + + return newGeoms; + } + +} diff --git a/engine/src/tools/jme3tools/optimize/pvsnotes b/engine/src/tools/jme3tools/optimize/pvsnotes new file mode 100644 index 0000000..61d83a5 --- /dev/null +++ b/engine/src/tools/jme3tools/optimize/pvsnotes @@ -0,0 +1,40 @@ +convert all leafs in octree to PvsNode, add to list pvsNodes
+
+for (every nodeX in pvsNodes):
+ for (every nodeY in pvsNodes):
+ if (nodeX == nodeY or nodeX adjecent or intersecting nodeY):
+ continue
+
+ setup camera for (nodeX, nodeY)
+ draw every node except nodeX & nodeY
+
+ turn on occlusion query
+ draw nodeY as bounding box
+ turn off occlusion query
+
+ if (numSamples > 0): // node is visible
+ add nodeY to nodeX's potentially visible set
+
+
+setup camera for node, sideI:
+
+ float width, height, near;
+
+ switch (sideI):
+ case X+
+ case X-
+ width = x extent
+ height = y extent
+ near = z extent / 2
+ case Y+
+ case Y-
+ width = x extent
+ height = z extent
+ near = y extent / 2
+ case Z+
+ case Z-
+ width = z extent
+ height = y extent
+ near = x extent / 2
+
+
|