aboutsummaryrefslogtreecommitdiff
path: root/engine/src/tools/jme3tools
diff options
context:
space:
mode:
authorScott Barta <sbarta@google.com>2012-03-01 12:35:35 -0800
committerScott Barta <sbarta@google.com>2012-03-01 12:40:08 -0800
commit59b2e6871c65f58fdad78cd7229c292f6a177578 (patch)
tree2d4e7bfc05b93f40b34675d77e403dd1c25efafd /engine/src/tools/jme3tools
parentf9b30489e75ac1eabc365064959804e99534f7ab (diff)
downloadjmonkeyengine-59b2e6871c65f58fdad78cd7229c292f6a177578.tar.gz
Adds the jMonkeyEngine library to the build.
Adds the jMonkeyEngine open source 3D game engine to the build. This is built as a static library and is only used by the Finsky client. Change-Id: I06a3f054df7b8a67757267d884854f70c5a16ca0
Diffstat (limited to 'engine/src/tools/jme3tools')
-rw-r--r--engine/src/tools/jme3tools/converters/Converter.java39
-rw-r--r--engine/src/tools/jme3tools/converters/FolderConverter.java80
-rw-r--r--engine/src/tools/jme3tools/converters/RGB565.java69
-rw-r--r--engine/src/tools/jme3tools/converters/model/FloatToFixed.java349
-rw-r--r--engine/src/tools/jme3tools/converters/model/ModelConverter.java183
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/EdgeInfo.java53
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/EdgeInfoVec.java50
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/FaceInfo.java59
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/FaceInfoVec.java54
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/IntVec.java71
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/PrimitiveGroup.java107
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/StripInfo.java355
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/StripInfoVec.java51
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/StripStartInfo.java49
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/Stripifier.java1365
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/TriStrip.java311
-rw-r--r--engine/src/tools/jme3tools/converters/model/strip/VertexCache.java100
-rw-r--r--engine/src/tools/jme3tools/optimize/FastOctnode.java169
-rw-r--r--engine/src/tools/jme3tools/optimize/GeometryBatchFactory.java418
-rw-r--r--engine/src/tools/jme3tools/optimize/OCTTriangle.java80
-rw-r--r--engine/src/tools/jme3tools/optimize/Octnode.java317
-rw-r--r--engine/src/tools/jme3tools/optimize/Octree.java163
-rw-r--r--engine/src/tools/jme3tools/optimize/TestCollector.java54
-rw-r--r--engine/src/tools/jme3tools/optimize/TextureAtlas.java686
-rw-r--r--engine/src/tools/jme3tools/optimize/TriangleCollector.java248
-rw-r--r--engine/src/tools/jme3tools/optimize/pvsnotes40
-rw-r--r--engine/src/tools/jme3tools/savegame/SaveGame.java118
27 files changed, 5638 insertions, 0 deletions
diff --git a/engine/src/tools/jme3tools/converters/Converter.java b/engine/src/tools/jme3tools/converters/Converter.java
new file mode 100644
index 0000000..8e8df56
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/Converter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.converters;
+
+import java.util.Map;
+
+public interface Converter<T> {
+ public T convert(T input, Map<String, String> params);
+}
diff --git a/engine/src/tools/jme3tools/converters/FolderConverter.java b/engine/src/tools/jme3tools/converters/FolderConverter.java
new file mode 100644
index 0000000..8dc9922
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/FolderConverter.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.converters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.system.JmeSystem;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+public class FolderConverter {
+
+ private static AssetManager assetManager;
+ private static File sourceRoot;
+ private static JarOutputStream jarOut;
+ private static long time;
+
+ private static void process(File file) throws IOException{
+ String name = file.getName().replaceAll("[\\/\\.]", "_");
+ JarEntry entry = new JarEntry(name);
+ entry.setTime(time);
+
+ jarOut.putNextEntry(entry);
+ }
+
+ public static void main(String[] args) throws IOException{
+ if (args.length == 0){
+ System.out.println("Usage: java -jar FolderConverter <input folder>");
+ System.out.println();
+ System.out.println(" Converts files from input to output");
+ System.exit(1);
+ }
+
+ sourceRoot = new File(args[0]);
+
+ File jarFile = new File(sourceRoot.getParent(), sourceRoot.getName()+".jar");
+ FileOutputStream out = new FileOutputStream(jarFile);
+ jarOut = new JarOutputStream(out);
+
+ assetManager = JmeSystem.newAssetManager();
+ assetManager.registerLocator(sourceRoot.toString(),
+ "com.jme3.asset.plugins.FileSystemLocator");
+ for (File f : sourceRoot.listFiles()){
+ process(f);
+ }
+ }
+
+}
diff --git a/engine/src/tools/jme3tools/converters/RGB565.java b/engine/src/tools/jme3tools/converters/RGB565.java
new file mode 100644
index 0000000..c0e43f5
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/RGB565.java
@@ -0,0 +1,69 @@
+/*
+ * 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.converters;
+
+/**
+ *
+ * @author Kirill
+ */
+public class RGB565 {
+
+ public static short ARGB8_to_RGB565(int argb){
+ int a = (argb & 0xFF000000) >> 24;
+ int r = (argb & 0x00FF0000) >> 16;
+ int g = (argb & 0x0000FF00) >> 8;
+ int b = (argb & 0x000000FF);
+
+ r = r >> 3;
+ g = g >> 2;
+ b = b >> 3;
+
+ return (short) (b | (g << 5) | (r << (5 + 6)));
+ }
+
+ public static int RGB565_to_ARGB8(short rgb565){
+ int a = 0xff;
+ int r = (rgb565 & 0xf800) >> 11;
+ int g = (rgb565 & 0x07e0) >> 5;
+ int b = (rgb565 & 0x001f);
+
+ r = r << 3;
+ g = g << 2;
+ b = b << 3;
+
+ return (a << 24) | (r << 16) | (g << 8) | (b);
+ }
+
+
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/FloatToFixed.java b/engine/src/tools/jme3tools/converters/model/FloatToFixed.java
new file mode 100644
index 0000000..0d0647a
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/FloatToFixed.java
@@ -0,0 +1,349 @@
+/*
+ * 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.converters.model;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+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 java.nio.*;
+
+public class FloatToFixed {
+
+ private static final float shortSize = Short.MAX_VALUE - Short.MIN_VALUE;
+ private static final float shortOff = (Short.MAX_VALUE + Short.MIN_VALUE) * 0.5f;
+
+ private static final float byteSize = Byte.MAX_VALUE - Byte.MIN_VALUE;
+ private static final float byteOff = (Byte.MAX_VALUE + Byte.MIN_VALUE) * 0.5f;
+
+ public static void convertToFixed(Geometry geom, Format posFmt, Format nmFmt, Format tcFmt){
+ geom.updateModelBound();
+ BoundingBox bbox = (BoundingBox) geom.getModelBound();
+ Mesh mesh = geom.getMesh();
+
+ VertexBuffer positions = mesh.getBuffer(Type.Position);
+ VertexBuffer normals = mesh.getBuffer(Type.Normal);
+ VertexBuffer texcoords = mesh.getBuffer(Type.TexCoord);
+ VertexBuffer indices = mesh.getBuffer(Type.Index);
+
+ // positions
+ FloatBuffer fb = (FloatBuffer) positions.getData();
+ if (posFmt != Format.Float){
+ Buffer newBuf = VertexBuffer.createBuffer(posFmt, positions.getNumComponents(),
+ mesh.getVertexCount());
+ Transform t = convertPositions(fb, bbox, newBuf);
+ t.combineWithParent(geom.getLocalTransform());
+ geom.setLocalTransform(t);
+
+ VertexBuffer newPosVb = new VertexBuffer(Type.Position);
+ newPosVb.setupData(positions.getUsage(),
+ positions.getNumComponents(),
+ posFmt,
+ newBuf);
+ mesh.clearBuffer(Type.Position);
+ mesh.setBuffer(newPosVb);
+ }
+
+ // normals, automatically convert to signed byte
+ fb = (FloatBuffer) normals.getData();
+
+ ByteBuffer bb = BufferUtils.createByteBuffer(fb.capacity());
+ convertNormals(fb, bb);
+
+ normals = new VertexBuffer(Type.Normal);
+ normals.setupData(Usage.Static, 3, Format.Byte, bb);
+ normals.setNormalized(true);
+ mesh.clearBuffer(Type.Normal);
+ mesh.setBuffer(normals);
+
+ // texcoords
+ fb = (FloatBuffer) texcoords.getData();
+ if (tcFmt != Format.Float){
+ Buffer newBuf = VertexBuffer.createBuffer(tcFmt,
+ texcoords.getNumComponents(),
+ mesh.getVertexCount());
+ convertTexCoords2D(fb, newBuf);
+
+ VertexBuffer newTcVb = new VertexBuffer(Type.TexCoord);
+ newTcVb.setupData(texcoords.getUsage(),
+ texcoords.getNumComponents(),
+ tcFmt,
+ newBuf);
+ mesh.clearBuffer(Type.TexCoord);
+ mesh.setBuffer(newTcVb);
+ }
+ }
+
+ public static void compressIndexBuffer(Mesh mesh){
+ int vertCount = mesh.getVertexCount();
+ VertexBuffer vb = mesh.getBuffer(Type.Index);
+ Format targetFmt;
+ if (vb.getFormat() == Format.UnsignedInt && vertCount <= 0xffff){
+ if (vertCount <= 256)
+ targetFmt = Format.UnsignedByte;
+ else
+ targetFmt = Format.UnsignedShort;
+ }else if (vb.getFormat() == Format.UnsignedShort && vertCount <= 0xff){
+ targetFmt = Format.UnsignedByte;
+ }else{
+ return;
+ }
+
+ IndexBuffer src = mesh.getIndexBuffer();
+ Buffer newBuf = VertexBuffer.createBuffer(targetFmt, vb.getNumComponents(), src.size());
+
+ VertexBuffer newVb = new VertexBuffer(Type.Index);
+ newVb.setupData(vb.getUsage(), vb.getNumComponents(), targetFmt, newBuf);
+ mesh.clearBuffer(Type.Index);
+ mesh.setBuffer(newVb);
+
+ IndexBuffer dst = mesh.getIndexBuffer();
+ for (int i = 0; i < src.size(); i++){
+ dst.put(i, src.get(i));
+ }
+ }
+
+ private static void convertToFixed(FloatBuffer input, IntBuffer output){
+ if (output.capacity() < input.capacity())
+ throw new RuntimeException("Output must be at least as large as input!");
+
+ input.clear();
+ output.clear();
+ for (int i = 0; i < input.capacity(); i++){
+ output.put( (int) (input.get() * (float)(1<<16)) );
+ }
+ output.flip();
+ }
+
+ private static void convertToFloat(IntBuffer input, FloatBuffer output){
+ if (output.capacity() < input.capacity())
+ throw new RuntimeException("Output must be at least as large as input!");
+
+ input.clear();
+ output.clear();
+ for (int i = 0; i < input.capacity(); i++){
+ output.put( ((float)input.get() / (float)(1<<16)) );
+ }
+ output.flip();
+ }
+
+ private static void convertToUByte(FloatBuffer input, ByteBuffer output){
+ if (output.capacity() < input.capacity())
+ throw new RuntimeException("Output must be at least as large as input!");
+
+ input.clear();
+ output.clear();
+ for (int i = 0; i < input.capacity(); i++){
+ output.put( (byte) (input.get() * 255f) );
+ }
+ output.flip();
+ }
+
+
+ public static VertexBuffer convertToUByte(VertexBuffer vb){
+ FloatBuffer fb = (FloatBuffer) vb.getData();
+ ByteBuffer bb = BufferUtils.createByteBuffer(fb.capacity());
+ convertToUByte(fb, bb);
+
+ VertexBuffer newVb = new VertexBuffer(vb.getBufferType());
+ newVb.setupData(vb.getUsage(),
+ vb.getNumComponents(),
+ Format.UnsignedByte,
+ bb);
+ newVb.setNormalized(true);
+ return newVb;
+ }
+
+ public static VertexBuffer convertToFixed(VertexBuffer vb){
+ if (vb.getFormat() == Format.Int)
+ return vb;
+
+ FloatBuffer fb = (FloatBuffer) vb.getData();
+ IntBuffer ib = BufferUtils.createIntBuffer(fb.capacity());
+ convertToFixed(fb, ib);
+
+ VertexBuffer newVb = new VertexBuffer(vb.getBufferType());
+ newVb.setupData(vb.getUsage(),
+ vb.getNumComponents(),
+ Format.Int,
+ ib);
+ return newVb;
+ }
+
+ public static VertexBuffer convertToFloat(VertexBuffer vb){
+ if (vb.getFormat() == Format.Float)
+ return vb;
+
+ IntBuffer ib = (IntBuffer) vb.getData();
+ FloatBuffer fb = BufferUtils.createFloatBuffer(ib.capacity());
+ convertToFloat(ib, fb);
+
+ VertexBuffer newVb = new VertexBuffer(vb.getBufferType());
+ newVb.setupData(vb.getUsage(),
+ vb.getNumComponents(),
+ Format.Float,
+ fb);
+ return newVb;
+ }
+
+ private static void convertNormals(FloatBuffer input, ByteBuffer output){
+ if (output.capacity() < input.capacity())
+ throw new RuntimeException("Output must be at least as large as input!");
+
+ input.clear();
+ output.clear();
+ Vector3f temp = new Vector3f();
+ int vertexCount = input.capacity() / 3;
+ for (int i = 0; i < vertexCount; i++){
+ BufferUtils.populateFromBuffer(temp, input, i);
+
+ // offset and scale vector into -128 ... 127
+ temp.multLocal(127).addLocal(0.5f, 0.5f, 0.5f);
+
+ // quantize
+ byte v1 = (byte) temp.getX();
+ byte v2 = (byte) temp.getY();
+ byte v3 = (byte) temp.getZ();
+
+ // store
+ output.put(v1).put(v2).put(v3);
+ }
+ }
+
+ private static void convertTexCoords2D(FloatBuffer input, Buffer output){
+ if (output.capacity() < input.capacity())
+ throw new RuntimeException("Output must be at least as large as input!");
+
+ input.clear();
+ output.clear();
+ Vector2f temp = new Vector2f();
+ int vertexCount = input.capacity() / 2;
+
+ ShortBuffer sb = null;
+ IntBuffer ib = null;
+
+ if (output instanceof ShortBuffer)
+ sb = (ShortBuffer) output;
+ else if (output instanceof IntBuffer)
+ ib = (IntBuffer) output;
+ else
+ throw new UnsupportedOperationException();
+
+ for (int i = 0; i < vertexCount; i++){
+ BufferUtils.populateFromBuffer(temp, input, i);
+
+ if (sb != null){
+ sb.put( (short) (temp.getX()*Short.MAX_VALUE) );
+ sb.put( (short) (temp.getY()*Short.MAX_VALUE) );
+ }else{
+ int v1 = (int) (temp.getX() * ((float)(1 << 16)));
+ int v2 = (int) (temp.getY() * ((float)(1 << 16)));
+ ib.put(v1).put(v2);
+ }
+ }
+ }
+
+ private static Transform convertPositions(FloatBuffer input, BoundingBox bbox, Buffer output){
+ if (output.capacity() < input.capacity())
+ throw new RuntimeException("Output must be at least as large as input!");
+
+ Vector3f offset = bbox.getCenter().negate();
+ Vector3f size = new Vector3f(bbox.getXExtent(), bbox.getYExtent(), bbox.getZExtent());
+ size.multLocal(2);
+
+ ShortBuffer sb = null;
+ ByteBuffer bb = null;
+ float dataTypeSize;
+ float dataTypeOffset;
+ if (output instanceof ShortBuffer){
+ sb = (ShortBuffer) output;
+ dataTypeOffset = shortOff;
+ dataTypeSize = shortSize;
+ }else{
+ bb = (ByteBuffer) output;
+ dataTypeOffset = byteOff;
+ dataTypeSize = byteSize;
+ }
+ Vector3f scale = new Vector3f();
+ scale.set(dataTypeSize, dataTypeSize, dataTypeSize).divideLocal(size);
+
+ Vector3f invScale = new Vector3f();
+ invScale.set(size).divideLocal(dataTypeSize);
+
+ offset.multLocal(scale);
+ offset.addLocal(dataTypeOffset, dataTypeOffset, dataTypeOffset);
+
+ // offset = (-modelOffset * shortSize)/modelSize + shortOff
+ // scale = shortSize / modelSize
+
+ input.clear();
+ output.clear();
+ Vector3f temp = new Vector3f();
+ int vertexCount = input.capacity() / 3;
+ for (int i = 0; i < vertexCount; i++){
+ BufferUtils.populateFromBuffer(temp, input, i);
+
+ // offset and scale vector into -32768 ... 32767
+ // or into -128 ... 127 if using bytes
+ temp.multLocal(scale);
+ temp.addLocal(offset);
+
+ // quantize and store
+ if (sb != null){
+ short v1 = (short) temp.getX();
+ short v2 = (short) temp.getY();
+ short v3 = (short) temp.getZ();
+ sb.put(v1).put(v2).put(v3);
+ }else{
+ byte v1 = (byte) temp.getX();
+ byte v2 = (byte) temp.getY();
+ byte v3 = (byte) temp.getZ();
+ bb.put(v1).put(v2).put(v3);
+ }
+ }
+
+ Transform transform = new Transform();
+ transform.setTranslation(offset.negate().multLocal(invScale));
+ transform.setScale(invScale);
+ return transform;
+ }
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/ModelConverter.java b/engine/src/tools/jme3tools/converters/model/ModelConverter.java
new file mode 100644
index 0000000..2539574
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/ModelConverter.java
@@ -0,0 +1,183 @@
+/*
+ * 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.converters.model;
+
+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.mesh.IndexBuffer;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.nio.Buffer;
+import java.util.Arrays;
+import java.util.Comparator;
+import jme3tools.converters.model.strip.PrimitiveGroup;
+import jme3tools.converters.model.strip.TriStrip;
+
+public class ModelConverter {
+
+ private static final class PrimComparator
+ implements Comparator<PrimitiveGroup> {
+
+ public int compare(PrimitiveGroup g1, PrimitiveGroup g2) {
+ if (g1.type < g2.type)
+ return -1;
+ else if (g1.type > g2.type)
+ return 1;
+ else
+ return 0;
+ }
+ }
+
+ private static final PrimComparator primComp = new PrimComparator();
+
+ public static void generateStrips(Mesh mesh, boolean stitch, boolean listOnly, int cacheSize, int minStripSize){
+ TriStrip ts = new TriStrip();
+ ts.setStitchStrips(stitch);
+ ts.setCacheSize(cacheSize);
+ ts.setListsOnly(listOnly);
+ ts.setMinStripSize(minStripSize);
+
+ IndexBuffer ib = mesh.getIndexBuffer();
+ int[] indices = new int[ib.size()];
+ for (int i = 0; i < indices.length; i++)
+ indices[i] = ib.get(i);
+
+ PrimitiveGroup[] groups = ts.generateStrips(indices);
+ Arrays.sort(groups, primComp);
+
+ int numElements = 0;
+ for (PrimitiveGroup group : groups)
+ numElements += group.numIndices;
+
+ VertexBuffer original = mesh.getBuffer(Type.Index);
+ Buffer buf = VertexBuffer.createBuffer(original.getFormat(),
+ original.getNumComponents(),
+ numElements);
+ original.updateData(buf);
+ ib = mesh.getIndexBuffer();
+
+ int curIndex = 0;
+ int[] modeStart = new int[]{ -1, -1, -1 };
+ int[] elementLengths = new int[groups.length];
+ for (int i = 0; i < groups.length; i++){
+ PrimitiveGroup group = groups[i];
+ elementLengths[i] = group.numIndices;
+
+ if (modeStart[group.type] == -1){
+ modeStart[group.type] = i;
+ }
+
+ int[] trimmedIndices = group.getTrimmedIndices();
+ for (int j = 0; j < trimmedIndices.length; j++){
+ ib.put(curIndex + j, trimmedIndices[j]);
+ }
+
+ curIndex += group.numIndices;
+ }
+
+ if (modeStart[0] == -1 && modeStart[1] == 0 && modeStart[2] == -1 &&
+ elementLengths.length == 1){
+ original.compact(elementLengths[0]);
+ mesh.setMode(Mode.TriangleStrip);
+ }else{
+ mesh.setElementLengths(elementLengths);
+ mesh.setModeStart(modeStart);
+ mesh.setMode(Mode.Hybrid);
+ }
+
+ mesh.updateCounts();
+ }
+
+ public static void optimize(Mesh mesh, boolean toFixed){
+ // update any data that need updating
+ mesh.updateBound();
+ mesh.updateCounts();
+
+ // set all buffers into STATIC_DRAW mode
+ mesh.setStatic();
+
+ if (mesh.getBuffer(Type.Index) != null){
+ // compress index buffer from UShort to UByte (if possible)
+ FloatToFixed.compressIndexBuffer(mesh);
+
+ // generate triangle strips stitched with degenerate tris
+ generateStrips(mesh, false, false, 16, 0);
+ }
+
+ IntMap<VertexBuffer> bufs = mesh.getBuffers();
+ for (Entry<VertexBuffer> entry : bufs){
+ VertexBuffer vb = entry.getValue();
+ if (vb == null || vb.getBufferType() == Type.Index)
+ continue;
+
+ if (vb.getFormat() == Format.Float){
+ if (vb.getBufferType() == Type.Color){
+ // convert the color buffer to UByte
+ vb = FloatToFixed.convertToUByte(vb);
+ vb.setNormalized(true);
+ }else if (toFixed){
+ // convert normals, positions, and texcoords
+ // to fixed-point (16.16)
+ vb = FloatToFixed.convertToFixed(vb);
+// vb = FloatToFixed.convertToFloat(vb);
+ }
+ mesh.clearBuffer(vb.getBufferType());
+ mesh.setBuffer(vb);
+ }
+ }
+ mesh.setInterleaved();
+ }
+
+ private static void optimizeScene(Spatial source, boolean toFixed){
+ if (source instanceof Geometry){
+ Geometry geom = (Geometry) source;
+ Mesh mesh = geom.getMesh();
+ optimize(mesh, toFixed);
+ }else if (source instanceof Node){
+ Node node = (Node) source;
+ for (int i = node.getQuantity() - 1; i >= 0; i--){
+ Spatial child = node.getChild(i);
+ optimizeScene(child, toFixed);
+ }
+ }
+ }
+
+ public static void optimize(Spatial source, boolean toFixed){
+ optimizeScene(source, toFixed);
+ source.updateLogicalState(0);
+ source.updateGeometricState();
+ }
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/EdgeInfo.java b/engine/src/tools/jme3tools/converters/model/strip/EdgeInfo.java
new file mode 100644
index 0000000..02b7350
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/EdgeInfo.java
@@ -0,0 +1,53 @@
+/*
+ * 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.converters.model.strip;
+
+/**
+ *
+ */
+class EdgeInfo {
+
+ FaceInfo m_face0, m_face1;
+ int m_v0, m_v1;
+ EdgeInfo m_nextV0, m_nextV1;
+
+ public EdgeInfo(int v0, int v1) {
+ m_v0 = v0;
+ m_v1 = v1;
+ m_face0 = null;
+ m_face1 = null;
+ m_nextV0 = null;
+ m_nextV1 = null;
+
+ }
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/EdgeInfoVec.java b/engine/src/tools/jme3tools/converters/model/strip/EdgeInfoVec.java
new file mode 100644
index 0000000..f289d78
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/EdgeInfoVec.java
@@ -0,0 +1,50 @@
+/*
+ * 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.converters.model.strip;
+
+import java.util.ArrayList;
+
+class EdgeInfoVec extends ArrayList<EdgeInfo> {
+
+ private static final long serialVersionUID = 1L;
+
+ public EdgeInfoVec() {
+ super();
+ }
+
+ public EdgeInfo at(int index) {
+ return get(index);
+ }
+
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/FaceInfo.java b/engine/src/tools/jme3tools/converters/model/strip/FaceInfo.java
new file mode 100644
index 0000000..e917ec7
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/FaceInfo.java
@@ -0,0 +1,59 @@
+/*
+ * 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.converters.model.strip;
+
+
+class FaceInfo {
+
+ int m_v0, m_v1, m_v2;
+ int m_stripId; // real strip Id
+ int m_testStripId; // strip Id in an experiment
+ int m_experimentId; // in what experiment was it given an experiment Id?
+
+ public FaceInfo(int v0, int v1, int v2){
+ m_v0 = v0; m_v1 = v1; m_v2 = v2;
+ m_stripId = -1;
+ m_testStripId = -1;
+ m_experimentId = -1;
+ }
+
+ public void set(FaceInfo o) {
+ m_v0 = o.m_v0;
+ m_v1 = o.m_v1;
+ m_v2 = o.m_v2;
+
+ m_stripId = o.m_stripId;
+ m_testStripId = o.m_testStripId;
+ m_experimentId = o.m_experimentId;
+ }
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/FaceInfoVec.java b/engine/src/tools/jme3tools/converters/model/strip/FaceInfoVec.java
new file mode 100644
index 0000000..8319280
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/FaceInfoVec.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.converters.model.strip;
+
+import java.util.ArrayList;
+
+class FaceInfoVec extends ArrayList<FaceInfo> {
+
+
+ private static final long serialVersionUID = 1L;
+
+ public FaceInfoVec() {
+ super();
+ }
+
+ public FaceInfo at(int index) {
+ return get(index);
+ }
+
+ public void reserve(int i) {
+ super.ensureCapacity(i);
+ }
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/IntVec.java b/engine/src/tools/jme3tools/converters/model/strip/IntVec.java
new file mode 100644
index 0000000..7e7eecb
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/IntVec.java
@@ -0,0 +1,71 @@
+/*
+ * 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.converters.model.strip;
+
+
+
+public class IntVec {
+
+ private int[] data;
+ private int count = 0;
+
+ public IntVec() {
+ data = new int[16];
+ }
+
+ public IntVec(int startSize) {
+ data = new int[startSize];
+ }
+
+ public int size() {
+ return count;
+ }
+
+ public int get(int i) {
+ return data[i];
+ }
+
+ public void add(int val) {
+ if ( count == data.length ) {
+ int[] ndata = new int[count*2];
+ System.arraycopy(data,0,ndata,0,count);
+ data = ndata;
+ }
+ data[count] = val;
+ count++;
+ }
+
+ public void clear() {
+ count = 0;
+ }
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/PrimitiveGroup.java b/engine/src/tools/jme3tools/converters/model/strip/PrimitiveGroup.java
new file mode 100644
index 0000000..51697ee
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/PrimitiveGroup.java
@@ -0,0 +1,107 @@
+/*
+ * 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.converters.model.strip;
+
+/**
+ *
+ */
+public class PrimitiveGroup {
+
+ public static final int PT_LIST = 0;
+ public static final int PT_STRIP = 1;
+ public static final int PT_FAN = 2;
+
+ public int type;
+ public int[] indices;
+ public int numIndices;
+
+ public PrimitiveGroup() {
+ type = PT_STRIP;
+ }
+
+ public String getTypeString() {
+ switch(type) {
+ case PT_LIST : return "list";
+ case PT_STRIP: return "strip";
+ case PT_FAN: return "fan";
+ default: return "????";
+ }
+ }
+
+ public String toString() {
+ return getTypeString() + " : " + numIndices;
+ }
+
+ public String getFullInfo() {
+ if ( type != PT_STRIP )
+ return toString();
+
+ int[] stripLengths = new int[numIndices];
+
+ int prev = -1;
+ int length = -1;
+ for ( int i =0; i < numIndices; i++) {
+ if (indices[i] == prev) {
+ stripLengths[length]++;
+ length = -1;
+ prev = -1;
+ } else {
+ prev = indices[i];
+ length++;
+ }
+ }
+ stripLengths[length]++;
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("Strip:").append(numIndices).append("\n");
+ for ( int i =0; i < stripLengths.length; i++) {
+ if ( stripLengths[i] > 0) {
+ sb.append(i).append("->").append(stripLengths[i]).append("\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @return
+ */
+ public int[] getTrimmedIndices() {
+ if ( indices.length == numIndices )
+ return indices;
+ int[] nind = new int[numIndices];
+ System.arraycopy(indices,0,nind,0,numIndices);
+ return nind;
+ }
+
+}
+
diff --git a/engine/src/tools/jme3tools/converters/model/strip/StripInfo.java b/engine/src/tools/jme3tools/converters/model/strip/StripInfo.java
new file mode 100644
index 0000000..a9b1098
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/StripInfo.java
@@ -0,0 +1,355 @@
+/*
+ * 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.converters.model.strip;
+
+/**
+ *
+ */
+class StripInfo {
+
+ StripStartInfo m_startInfo;
+ FaceInfoVec m_faces = new FaceInfoVec();
+ int m_stripId;
+ int m_experimentId;
+
+ boolean visited;
+
+ int m_numDegenerates;
+
+
+ public StripInfo(StripStartInfo startInfo,int stripId, int experimentId) {
+
+ m_startInfo = startInfo;
+ m_stripId = stripId;
+ m_experimentId = experimentId;
+ visited = false;
+ m_numDegenerates = 0;
+ }
+
+ boolean isExperiment() {
+ return m_experimentId >= 0;
+ }
+
+ boolean isInStrip(FaceInfo faceInfo) {
+ if(faceInfo == null)
+ return false;
+
+ return (m_experimentId >= 0 ? faceInfo.m_testStripId == m_stripId : faceInfo.m_stripId == m_stripId);
+ }
+
+
+///////////////////////////////////////////////////////////////////////////////////////////
+// IsMarked()
+//
+// If either the faceInfo has a real strip index because it is
+// already assign to a committed strip OR it is assigned in an
+// experiment and the experiment index is the one we are building
+// for, then it is marked and unavailable
+ boolean isMarked(FaceInfo faceInfo){
+ return (faceInfo.m_stripId >= 0) || (isExperiment() && faceInfo.m_experimentId == m_experimentId);
+ }
+
+
+///////////////////////////////////////////////////////////////////////////////////////////
+// MarkTriangle()
+//
+// Marks the face with the current strip ID
+//
+ void markTriangle(FaceInfo faceInfo){
+ if (isExperiment()){
+ faceInfo.m_experimentId = m_experimentId;
+ faceInfo.m_testStripId = m_stripId;
+ }
+ else{
+ faceInfo.m_experimentId = -1;
+ faceInfo.m_stripId = m_stripId;
+ }
+ }
+
+
+ boolean unique(FaceInfoVec faceVec, FaceInfo face)
+ {
+ boolean bv0, bv1, bv2; //bools to indicate whether a vertex is in the faceVec or not
+ bv0 = bv1 = bv2 = false;
+
+ for(int i = 0; i < faceVec.size(); i++)
+ {
+ if(!bv0)
+ {
+ if( (faceVec.at(i).m_v0 == face.m_v0) ||
+ (faceVec.at(i).m_v1 == face.m_v0) ||
+ (faceVec.at(i).m_v2 == face.m_v0) )
+ bv0 = true;
+ }
+
+ if(!bv1)
+ {
+ if( (faceVec.at(i).m_v0 == face.m_v1) ||
+ (faceVec.at(i).m_v1 == face.m_v1) ||
+ (faceVec.at(i).m_v2 == face.m_v1) )
+ bv1 = true;
+ }
+
+ if(!bv2)
+ {
+ if( (faceVec.at(i).m_v0 == face.m_v2) ||
+ (faceVec.at(i).m_v1 == face.m_v2) ||
+ (faceVec.at(i).m_v2 == face.m_v2) )
+ bv2 = true;
+ }
+
+ //the face is not unique, all it's vertices exist in the face vector
+ if(bv0 && bv1 && bv2)
+ return false;
+ }
+
+ //if we get out here, it's unique
+ return true;
+ }
+
+
+///////////////////////////////////////////////////////////////////////////////////////////
+// Build()
+//
+// Builds a strip forward as far as we can go, then builds backwards, and joins the two lists
+//
+ void build(EdgeInfoVec edgeInfos, FaceInfoVec faceInfos)
+ {
+ // used in building the strips forward and backward
+ IntVec scratchIndices = new IntVec();
+
+ // build forward... start with the initial face
+ FaceInfoVec forwardFaces = new FaceInfoVec();
+ FaceInfoVec backwardFaces = new FaceInfoVec();
+ forwardFaces.add(m_startInfo.m_startFace);
+
+ markTriangle(m_startInfo.m_startFace);
+
+ int v0 = (m_startInfo.m_toV1 ? m_startInfo.m_startEdge.m_v0 : m_startInfo.m_startEdge.m_v1);
+ int v1 = (m_startInfo.m_toV1 ? m_startInfo.m_startEdge.m_v1 : m_startInfo.m_startEdge.m_v0);
+
+ // easiest way to get v2 is to use this function which requires the
+ // other indices to already be in the list.
+ scratchIndices.add(v0);
+ scratchIndices.add(v1);
+ int v2 = Stripifier.getNextIndex(scratchIndices, m_startInfo.m_startFace);
+ scratchIndices.add(v2);
+
+ //
+ // build the forward list
+ //
+ int nv0 = v1;
+ int nv1 = v2;
+
+ FaceInfo nextFace = Stripifier.findOtherFace(edgeInfos, nv0, nv1, m_startInfo.m_startFace);
+ while (nextFace != null && !isMarked(nextFace))
+ {
+ //check to see if this next face is going to cause us to die soon
+ int testnv0 = nv1;
+ int testnv1 = Stripifier.getNextIndex(scratchIndices, nextFace);
+
+ FaceInfo nextNextFace = Stripifier.findOtherFace(edgeInfos, testnv0, testnv1, nextFace);
+
+ if( (nextNextFace == null) || (isMarked(nextNextFace)) )
+ {
+ //uh, oh, we're following a dead end, try swapping
+ FaceInfo testNextFace = Stripifier.findOtherFace(edgeInfos, nv0, testnv1, nextFace);
+
+ if( ((testNextFace != null) && !isMarked(testNextFace)) )
+ {
+ //we only swap if it buys us something
+
+ //add a "fake" degenerate face
+ FaceInfo tempFace = new FaceInfo(nv0, nv1, nv0);
+
+ forwardFaces.add(tempFace);
+ markTriangle(tempFace);
+
+ scratchIndices.add(nv0);
+ testnv0 = nv0;
+
+ ++m_numDegenerates;
+ }
+
+ }
+
+ // add this to the strip
+ forwardFaces.add(nextFace);
+
+ markTriangle(nextFace);
+
+ // add the index
+ //nv0 = nv1;
+ //nv1 = NvStripifier::GetNextIndex(scratchIndices, nextFace);
+ scratchIndices.add(testnv1);
+
+ // and get the next face
+ nv0 = testnv0;
+ nv1 = testnv1;
+
+ nextFace = Stripifier.findOtherFace(edgeInfos, nv0, nv1, nextFace);
+
+ }
+
+ // tempAllFaces is going to be forwardFaces + backwardFaces
+ // it's used for Unique()
+ FaceInfoVec tempAllFaces = new FaceInfoVec();
+ for(int i = 0; i < forwardFaces.size(); i++)
+ tempAllFaces.add(forwardFaces.at(i));
+
+ //
+ // reset the indices for building the strip backwards and do so
+ //
+ scratchIndices.clear();
+ scratchIndices.add(v2);
+ scratchIndices.add(v1);
+ scratchIndices.add(v0);
+ nv0 = v1;
+ nv1 = v0;
+ nextFace = Stripifier.findOtherFace(edgeInfos, nv0, nv1, m_startInfo.m_startFace);
+ while (nextFace != null && !isMarked(nextFace))
+ {
+ //this tests to see if a face is "unique", meaning that its vertices aren't already in the list
+ // so, strips which "wrap-around" are not allowed
+ if(!unique(tempAllFaces, nextFace))
+ break;
+
+ //check to see if this next face is going to cause us to die soon
+ int testnv0 = nv1;
+ int testnv1 = Stripifier.getNextIndex(scratchIndices, nextFace);
+
+ FaceInfo nextNextFace = Stripifier.findOtherFace(edgeInfos, testnv0, testnv1, nextFace);
+
+ if( (nextNextFace == null) || (isMarked(nextNextFace)) )
+ {
+ //uh, oh, we're following a dead end, try swapping
+ FaceInfo testNextFace = Stripifier.findOtherFace(edgeInfos, nv0, testnv1, nextFace);
+ if( ((testNextFace != null) && !isMarked(testNextFace)) )
+ {
+ //we only swap if it buys us something
+
+ //add a "fake" degenerate face
+ FaceInfo tempFace = new FaceInfo(nv0, nv1, nv0);
+
+ backwardFaces.add(tempFace);
+ markTriangle(tempFace);
+ scratchIndices.add(nv0);
+ testnv0 = nv0;
+
+ ++m_numDegenerates;
+ }
+
+ }
+
+ // add this to the strip
+ backwardFaces.add(nextFace);
+
+ //this is just so Unique() will work
+ tempAllFaces.add(nextFace);
+
+ markTriangle(nextFace);
+
+ // add the index
+ //nv0 = nv1;
+ //nv1 = NvStripifier::GetNextIndex(scratchIndices, nextFace);
+ scratchIndices.add(testnv1);
+
+ // and get the next face
+ nv0 = testnv0;
+ nv1 = testnv1;
+ nextFace = Stripifier.findOtherFace(edgeInfos, nv0, nv1, nextFace);
+ }
+
+ // Combine the forward and backwards stripification lists and put into our own face vector
+ combine(forwardFaces, backwardFaces);
+ }
+
+
+///////////////////////////////////////////////////////////////////////////////////////////
+// Combine()
+//
+// Combines the two input face vectors and puts the result into m_faces
+//
+ void combine(FaceInfoVec forward, FaceInfoVec backward){
+
+ // add backward faces
+ int numFaces = backward.size();
+ for (int i = numFaces - 1; i >= 0; i--)
+ m_faces.add(backward.at(i));
+
+ // add forward faces
+ numFaces = forward.size();
+ for (int i = 0; i < numFaces; i++)
+ m_faces.add(forward.at(i));
+ }
+
+
+///////////////////////////////////////////////////////////////////////////////////////////
+// SharesEdge()
+//
+// Returns true if the input face and the current strip share an edge
+//
+ boolean sharesEdge(FaceInfo faceInfo, EdgeInfoVec edgeInfos)
+ {
+ //check v0.v1 edge
+ EdgeInfo currEdge = Stripifier.findEdgeInfo(edgeInfos, faceInfo.m_v0, faceInfo.m_v1);
+
+ if(isInStrip(currEdge.m_face0) || isInStrip(currEdge.m_face1))
+ return true;
+
+ //check v1.v2 edge
+ currEdge = Stripifier.findEdgeInfo(edgeInfos, faceInfo.m_v1, faceInfo.m_v2);
+
+ if(isInStrip(currEdge.m_face0) || isInStrip(currEdge.m_face1))
+ return true;
+
+ //check v2.v0 edge
+ currEdge = Stripifier.findEdgeInfo(edgeInfos, faceInfo.m_v2, faceInfo.m_v0);
+
+ if(isInStrip(currEdge.m_face0) || isInStrip(currEdge.m_face1))
+ return true;
+
+ return false;
+
+ }
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/StripInfoVec.java b/engine/src/tools/jme3tools/converters/model/strip/StripInfoVec.java
new file mode 100644
index 0000000..58165f9
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/StripInfoVec.java
@@ -0,0 +1,51 @@
+/*
+ * 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.converters.model.strip;
+
+import java.util.ArrayList;
+
+
+class StripInfoVec extends ArrayList<StripInfo> {
+
+
+ private static final long serialVersionUID = 1L;
+
+ public StripInfoVec() {
+ super();
+ }
+
+ public StripInfo at(int index) {
+ return get(index);
+ }
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/StripStartInfo.java b/engine/src/tools/jme3tools/converters/model/strip/StripStartInfo.java
new file mode 100644
index 0000000..69f5157
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/StripStartInfo.java
@@ -0,0 +1,49 @@
+/*
+ * 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.converters.model.strip;
+
+class StripStartInfo {
+
+
+ FaceInfo m_startFace;
+ EdgeInfo m_startEdge;
+ boolean m_toV1;
+
+
+ public StripStartInfo(FaceInfo startFace, EdgeInfo startEdge, boolean toV1){
+ m_startFace = startFace;
+ m_startEdge = startEdge;
+ m_toV1 = toV1;
+ }
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/Stripifier.java b/engine/src/tools/jme3tools/converters/model/strip/Stripifier.java
new file mode 100644
index 0000000..c8630fe
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/Stripifier.java
@@ -0,0 +1,1365 @@
+/*
+ * 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.converters.model.strip;
+
+import java.util.HashSet;
+import java.util.logging.Logger;
+
+/**
+ *
+ */
+class Stripifier {
+ private static final Logger logger = Logger.getLogger(Stripifier.class
+ .getName());
+
+ public static int CACHE_INEFFICIENCY = 6;
+
+ IntVec indices = new IntVec();
+
+ int cacheSize;
+
+ int minStripLength;
+
+ float meshJump;
+
+ boolean bFirstTimeResetPoint;
+
+ Stripifier() {
+ super();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // FindEdgeInfo()
+ //
+ // find the edge info for these two indices
+ //
+ static EdgeInfo findEdgeInfo(EdgeInfoVec edgeInfos, int v0, int v1) {
+
+ // we can get to it through either array
+ // because the edge infos have a v0 and v1
+ // and there is no order except how it was
+ // first created.
+ EdgeInfo infoIter = edgeInfos.at(v0);
+ while (infoIter != null) {
+ if (infoIter.m_v0 == v0) {
+ if (infoIter.m_v1 == v1)
+ return infoIter;
+
+ infoIter = infoIter.m_nextV0;
+ } else {
+ if (infoIter.m_v0 == v1)
+ return infoIter;
+
+ infoIter = infoIter.m_nextV1;
+ }
+ }
+ return null;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // FindOtherFace
+ //
+ // find the other face sharing these vertices
+ // exactly like the edge info above
+ //
+ static FaceInfo findOtherFace(EdgeInfoVec edgeInfos, int v0, int v1,
+ FaceInfo faceInfo) {
+ EdgeInfo edgeInfo = findEdgeInfo(edgeInfos, v0, v1);
+
+ if ((edgeInfo == null) || (v0 == v1)) {
+ //we've hit a degenerate
+ return null;
+ }
+
+ return (edgeInfo.m_face0 == faceInfo ? edgeInfo.m_face1
+ : edgeInfo.m_face0);
+ }
+
+ static boolean alreadyExists(FaceInfo faceInfo, FaceInfoVec faceInfos) {
+ for (int i = 0; i < faceInfos.size(); ++i) {
+ FaceInfo o = faceInfos.at(i);
+ if ((o.m_v0 == faceInfo.m_v0) && (o.m_v1 == faceInfo.m_v1)
+ && (o.m_v2 == faceInfo.m_v2))
+ return true;
+ }
+ return false;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // BuildStripifyInfo()
+ //
+ // Builds the list of all face and edge infos
+ //
+ void buildStripifyInfo(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos,
+ int maxIndex) {
+ // reserve space for the face infos, but do not resize them.
+ int numIndices = indices.size();
+ faceInfos.reserve(numIndices / 3);
+
+ // we actually resize the edge infos, so we must initialize to null
+ for (int i = 0; i < maxIndex + 1; i++)
+ edgeInfos.add(null);
+
+ // iterate through the triangles of the triangle list
+ int numTriangles = numIndices / 3;
+ int index = 0;
+ boolean[] bFaceUpdated = new boolean[3];
+
+ for (int i = 0; i < numTriangles; i++) {
+ boolean bMightAlreadyExist = true;
+ bFaceUpdated[0] = false;
+ bFaceUpdated[1] = false;
+ bFaceUpdated[2] = false;
+
+ // grab the indices
+ int v0 = indices.get(index++);
+ int v1 = indices.get(index++);
+ int v2 = indices.get(index++);
+
+ //we disregard degenerates
+ if (isDegenerate(v0, v1, v2))
+ continue;
+
+ // create the face info and add it to the list of faces, but only
+ // if this exact face doesn't already
+ // exist in the list
+ FaceInfo faceInfo = new FaceInfo(v0, v1, v2);
+
+ // grab the edge infos, creating them if they do not already exist
+ EdgeInfo edgeInfo01 = findEdgeInfo(edgeInfos, v0, v1);
+ if (edgeInfo01 == null) {
+ //since one of it's edges isn't in the edge data structure, it
+ // can't already exist in the face structure
+ bMightAlreadyExist = false;
+
+ // create the info
+ edgeInfo01 = new EdgeInfo(v0, v1);
+
+ // update the linked list on both
+ edgeInfo01.m_nextV0 = edgeInfos.at(v0);
+ edgeInfo01.m_nextV1 = edgeInfos.at(v1);
+ edgeInfos.set(v0, edgeInfo01);
+ edgeInfos.set(v1, edgeInfo01);
+
+ // set face 0
+ edgeInfo01.m_face0 = faceInfo;
+ } else {
+ if (edgeInfo01.m_face1 != null) {
+ logger.info("BuildStripifyInfo: > 2 triangles on an edge"
+ + v0 + "," + v1 + "... uncertain consequences\n");
+ } else {
+ edgeInfo01.m_face1 = faceInfo;
+ bFaceUpdated[0] = true;
+ }
+ }
+
+ // grab the edge infos, creating them if they do not already exist
+ EdgeInfo edgeInfo12 = findEdgeInfo(edgeInfos, v1, v2);
+ if (edgeInfo12 == null) {
+ bMightAlreadyExist = false;
+
+ // create the info
+ edgeInfo12 = new EdgeInfo(v1, v2);
+
+ // update the linked list on both
+ edgeInfo12.m_nextV0 = edgeInfos.at(v1);
+ edgeInfo12.m_nextV1 = edgeInfos.at(v2);
+ edgeInfos.set(v1, edgeInfo12);
+ edgeInfos.set(v2, edgeInfo12);
+
+ // set face 0
+ edgeInfo12.m_face0 = faceInfo;
+ } else {
+ if (edgeInfo12.m_face1 != null) {
+ logger.info("BuildStripifyInfo: > 2 triangles on an edge"
+ + v1
+ + ","
+ + v2
+ + "... uncertain consequences\n");
+ } else {
+ edgeInfo12.m_face1 = faceInfo;
+ bFaceUpdated[1] = true;
+ }
+ }
+
+ // grab the edge infos, creating them if they do not already exist
+ EdgeInfo edgeInfo20 = findEdgeInfo(edgeInfos, v2, v0);
+ if (edgeInfo20 == null) {
+ bMightAlreadyExist = false;
+
+ // create the info
+ edgeInfo20 = new EdgeInfo(v2, v0);
+
+ // update the linked list on both
+ edgeInfo20.m_nextV0 = edgeInfos.at(v2);
+ edgeInfo20.m_nextV1 = edgeInfos.at(v0);
+ edgeInfos.set(v2, edgeInfo20);
+ edgeInfos.set(v0, edgeInfo20);
+
+ // set face 0
+ edgeInfo20.m_face0 = faceInfo;
+ } else {
+ if (edgeInfo20.m_face1 != null) {
+ logger.info("BuildStripifyInfo: > 2 triangles on an edge"
+ + v2
+ + ","
+ + v0
+ + "... uncertain consequences\n");
+ } else {
+ edgeInfo20.m_face1 = faceInfo;
+ bFaceUpdated[2] = true;
+ }
+ }
+
+ if (bMightAlreadyExist) {
+ if (!alreadyExists(faceInfo, faceInfos))
+ faceInfos.add(faceInfo);
+ else {
+
+ //cleanup pointers that point to this deleted face
+ if (bFaceUpdated[0])
+ edgeInfo01.m_face1 = null;
+ if (bFaceUpdated[1])
+ edgeInfo12.m_face1 = null;
+ if (bFaceUpdated[2])
+ edgeInfo20.m_face1 = null;
+ }
+ } else {
+ faceInfos.add(faceInfo);
+ }
+
+ }
+ }
+
+ static boolean isDegenerate(FaceInfo face) {
+ if (face.m_v0 == face.m_v1)
+ return true;
+ else if (face.m_v0 == face.m_v2)
+ return true;
+ else if (face.m_v1 == face.m_v2)
+ return true;
+ else
+ return false;
+ }
+
+ static boolean isDegenerate(int v0, int v1, int v2) {
+ if (v0 == v1)
+ return true;
+ else if (v0 == v2)
+ return true;
+ else if (v1 == v2)
+ return true;
+ else
+ return false;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // GetNextIndex()
+ //
+ // Returns vertex of the input face which is "next" in the input index list
+ //
+ static int getNextIndex(IntVec indices, FaceInfo face) {
+
+ int numIndices = indices.size();
+
+ int v0 = indices.get(numIndices - 2);
+ int v1 = indices.get(numIndices - 1);
+
+ int fv0 = face.m_v0;
+ int fv1 = face.m_v1;
+ int fv2 = face.m_v2;
+
+ if (fv0 != v0 && fv0 != v1) {
+ if ((fv1 != v0 && fv1 != v1) || (fv2 != v0 && fv2 != v1)) {
+ logger.info("GetNextIndex: Triangle doesn't have all of its vertices\n");
+ logger.info("GetNextIndex: Duplicate triangle probably got us derailed\n");
+ }
+ return fv0;
+ }
+ if (fv1 != v0 && fv1 != v1) {
+ if ((fv0 != v0 && fv0 != v1) || (fv2 != v0 && fv2 != v1)) {
+ logger.info("GetNextIndex: Triangle doesn't have all of its vertices\n");
+ logger.info("GetNextIndex: Duplicate triangle probably got us derailed\n");
+ }
+ return fv1;
+ }
+ if (fv2 != v0 && fv2 != v1) {
+ if ((fv0 != v0 && fv0 != v1) || (fv1 != v0 && fv1 != v1)) {
+ logger.info("GetNextIndex: Triangle doesn't have all of its vertices\n");
+ logger.info("GetNextIndex: Duplicate triangle probably got us derailed\n");
+ }
+ return fv2;
+ }
+
+ // shouldn't get here, but let's try and fail gracefully
+ if ((fv0 == fv1) || (fv0 == fv2))
+ return fv0;
+ else if ((fv1 == fv0) || (fv1 == fv2))
+ return fv1;
+ else if ((fv2 == fv0) || (fv2 == fv1))
+ return fv2;
+ else
+ return -1;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // FindStartPoint()
+ //
+ // Finds a good starting point, namely one which has only one neighbor
+ //
+ static int findStartPoint(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos) {
+ int bestCtr = -1;
+ int bestIndex = -1;
+
+ for (int i = 0; i < faceInfos.size(); i++) {
+ int ctr = 0;
+
+ if (findOtherFace(edgeInfos, faceInfos.at(i).m_v0,
+ faceInfos.at(i).m_v1, faceInfos.at(i)) == null)
+ ctr++;
+ if (findOtherFace(edgeInfos, faceInfos.at(i).m_v1,
+ faceInfos.at(i).m_v2, faceInfos.at(i)) == null)
+ ctr++;
+ if (findOtherFace(edgeInfos, faceInfos.at(i).m_v2,
+ faceInfos.at(i).m_v0, faceInfos.at(i)) == null)
+ ctr++;
+ if (ctr > bestCtr) {
+ bestCtr = ctr;
+ bestIndex = i;
+ //return i;
+ }
+ }
+ //return -1;
+
+ if (bestCtr == 0)
+ return -1;
+
+ return bestIndex;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // FindGoodResetPoint()
+ //
+ // A good reset point is one near other commited areas so that
+ // we know that when we've made the longest strips its because
+ // we're stripifying in the same general orientation.
+ //
+ FaceInfo findGoodResetPoint(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos) {
+ // we hop into different areas of the mesh to try to get
+ // other large open spans done. Areas of small strips can
+ // just be left to triangle lists added at the end.
+ FaceInfo result = null;
+
+ if (result == null) {
+ int numFaces = faceInfos.size();
+ int startPoint;
+ if (bFirstTimeResetPoint) {
+ //first time, find a face with few neighbors (look for an edge
+ // of the mesh)
+ startPoint = findStartPoint(faceInfos, edgeInfos);
+ bFirstTimeResetPoint = false;
+ } else
+ startPoint = (int) (((float) numFaces - 1) * meshJump);
+
+ if (startPoint == -1) {
+ startPoint = (int) (((float) numFaces - 1) * meshJump);
+
+ //meshJump += 0.1f;
+ //if (meshJump > 1.0f)
+ // meshJump = .05f;
+ }
+
+ int i = startPoint;
+ do {
+
+ // if this guy isn't visited, try him
+ if (faceInfos.at(i).m_stripId < 0) {
+ result = faceInfos.at(i);
+ break;
+ }
+
+ // update the index and clamp to 0-(numFaces-1)
+ if (++i >= numFaces)
+ i = 0;
+
+ } while (i != startPoint);
+
+ // update the meshJump
+ meshJump += 0.1f;
+ if (meshJump > 1.0f)
+ meshJump = .05f;
+ }
+
+ // return the best face we found
+ return result;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // GetUniqueVertexInB()
+ //
+ // Returns the vertex unique to faceB
+ //
+ static int getUniqueVertexInB(FaceInfo faceA, FaceInfo faceB) {
+
+ int facev0 = faceB.m_v0;
+ if (facev0 != faceA.m_v0 && facev0 != faceA.m_v1
+ && facev0 != faceA.m_v2)
+ return facev0;
+
+ int facev1 = faceB.m_v1;
+ if (facev1 != faceA.m_v0 && facev1 != faceA.m_v1
+ && facev1 != faceA.m_v2)
+ return facev1;
+
+ int facev2 = faceB.m_v2;
+ if (facev2 != faceA.m_v0 && facev2 != faceA.m_v1
+ && facev2 != faceA.m_v2)
+ return facev2;
+
+ // nothing is different
+ return -1;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // GetSharedVertices()
+ //
+ // Returns the (at most) two vertices shared between the two faces
+ //
+ static void getSharedVertices(FaceInfo faceA, FaceInfo faceB, int[] vertex) {
+ vertex[0] = -1;
+ vertex[1] = -1;
+
+ int facev0 = faceB.m_v0;
+ if (facev0 == faceA.m_v0 || facev0 == faceA.m_v1
+ || facev0 == faceA.m_v2) {
+ if (vertex[0] == -1)
+ vertex[0] = facev0;
+ else {
+ vertex[1] = facev0;
+ return;
+ }
+ }
+
+ int facev1 = faceB.m_v1;
+ if (facev1 == faceA.m_v0 || facev1 == faceA.m_v1
+ || facev1 == faceA.m_v2) {
+ if (vertex[0] == -1)
+ vertex[0] = facev1;
+ else {
+ vertex[1] = facev1;
+ return;
+ }
+ }
+
+ int facev2 = faceB.m_v2;
+ if (facev2 == faceA.m_v0 || facev2 == faceA.m_v1
+ || facev2 == faceA.m_v2) {
+ if (vertex[0] == -1)
+ vertex[0] = facev2;
+ else {
+ vertex[1] = facev2;
+ return;
+ }
+ }
+
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // CommitStrips()
+ //
+ // "Commits" the input strips by setting their m_experimentId to -1 and
+ // adding to the allStrips
+ // vector
+ //
+ static void commitStrips(StripInfoVec allStrips, StripInfoVec strips) {
+ // Iterate through strips
+ int numStrips = strips.size();
+ for (int i = 0; i < numStrips; i++) {
+
+ // Tell the strip that it is now real
+ StripInfo strip = strips.at(i);
+ strip.m_experimentId = -1;
+
+ // add to the list of real strips
+ allStrips.add(strip);
+
+ // Iterate through the faces of the strip
+ // Tell the faces of the strip that they belong to a real strip now
+ FaceInfoVec faces = strips.at(i).m_faces;
+ int numFaces = faces.size();
+
+ for (int j = 0; j < numFaces; j++) {
+ strip.markTriangle(faces.at(j));
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // NextIsCW()
+ //
+ // Returns true if the next face should be ordered in CW fashion
+ //
+ static boolean nextIsCW(int numIndices) {
+ return ((numIndices % 2) == 0);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // UpdateCacheFace()
+ //
+ // Updates the input vertex cache with this face's vertices
+ //
+ static void updateCacheFace(VertexCache vcache, FaceInfo face) {
+ if (!vcache.inCache(face.m_v0))
+ vcache.addEntry(face.m_v0);
+
+ if (!vcache.inCache(face.m_v1))
+ vcache.addEntry(face.m_v1);
+
+ if (!vcache.inCache(face.m_v2))
+ vcache.addEntry(face.m_v2);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // UpdateCacheStrip()
+ //
+ // Updates the input vertex cache with this strip's vertices
+ //
+ static void updateCacheStrip(VertexCache vcache, StripInfo strip) {
+ for (int i = 0; i < strip.m_faces.size(); ++i) {
+ if (!vcache.inCache(strip.m_faces.at(i).m_v0))
+ vcache.addEntry(strip.m_faces.at(i).m_v0);
+
+ if (!vcache.inCache(strip.m_faces.at(i).m_v1))
+ vcache.addEntry(strip.m_faces.at(i).m_v1);
+
+ if (!vcache.inCache(strip.m_faces.at(i).m_v2))
+ vcache.addEntry(strip.m_faces.at(i).m_v2);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // CalcNumHitsStrip()
+ //
+ // returns the number of cache hits per face in the strip
+ //
+ static float calcNumHitsStrip(VertexCache vcache, StripInfo strip) {
+ int numHits = 0;
+ int numFaces = 0;
+
+ for (int i = 0; i < strip.m_faces.size(); i++) {
+ if (vcache.inCache(strip.m_faces.at(i).m_v0))
+ ++numHits;
+
+ if (vcache.inCache(strip.m_faces.at(i).m_v1))
+ ++numHits;
+
+ if (vcache.inCache(strip.m_faces.at(i).m_v2))
+ ++numHits;
+
+ numFaces++;
+ }
+
+ return ((float) numHits / (float) numFaces);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // AvgStripSize()
+ //
+ // Finds the average strip size of the input vector of strips
+ //
+ static float avgStripSize(StripInfoVec strips) {
+ int sizeAccum = 0;
+ int numStrips = strips.size();
+ for (int i = 0; i < numStrips; i++) {
+ StripInfo strip = strips.at(i);
+ sizeAccum += strip.m_faces.size();
+ sizeAccum -= strip.m_numDegenerates;
+ }
+ return ((float) sizeAccum) / ((float) numStrips);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // CalcNumHitsFace()
+ //
+ // returns the number of cache hits in the face
+ //
+ static int calcNumHitsFace(VertexCache vcache, FaceInfo face) {
+ int numHits = 0;
+
+ if (vcache.inCache(face.m_v0))
+ numHits++;
+
+ if (vcache.inCache(face.m_v1))
+ numHits++;
+
+ if (vcache.inCache(face.m_v2))
+ numHits++;
+
+ return numHits;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // NumNeighbors()
+ //
+ // Returns the number of neighbors that this face has
+ //
+ static int numNeighbors(FaceInfo face, EdgeInfoVec edgeInfoVec) {
+ int numNeighbors = 0;
+
+ if (findOtherFace(edgeInfoVec, face.m_v0, face.m_v1, face) != null) {
+ numNeighbors++;
+ }
+
+ if (findOtherFace(edgeInfoVec, face.m_v1, face.m_v2, face) != null) {
+ numNeighbors++;
+ }
+
+ if (findOtherFace(edgeInfoVec, face.m_v2, face.m_v0, face) != null) {
+ numNeighbors++;
+ }
+
+ return numNeighbors;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // IsCW()
+ //
+ // Returns true if the face is ordered in CW fashion
+ //
+ static boolean isCW(FaceInfo faceInfo, int v0, int v1) {
+ if (faceInfo.m_v0 == v0)
+ return (faceInfo.m_v1 == v1);
+ else if (faceInfo.m_v1 == v0)
+ return (faceInfo.m_v2 == v1);
+ else
+ return (faceInfo.m_v0 == v1);
+
+ }
+
+ static boolean faceContainsIndex(FaceInfo face, int index) {
+ return ((face.m_v0 == index) || (face.m_v1 == index) || (face.m_v2 == index));
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // FindTraversal()
+ //
+ // Finds the next face to start the next strip on.
+ //
+ static boolean findTraversal(FaceInfoVec faceInfos, EdgeInfoVec edgeInfos,
+ StripInfo strip, StripStartInfo startInfo) {
+
+ // if the strip was v0.v1 on the edge, then v1 will be a vertex in the
+ // next edge.
+ int v = (strip.m_startInfo.m_toV1 ? strip.m_startInfo.m_startEdge.m_v1
+ : strip.m_startInfo.m_startEdge.m_v0);
+
+ FaceInfo untouchedFace = null;
+ EdgeInfo edgeIter = edgeInfos.at(v);
+ while (edgeIter != null) {
+ FaceInfo face0 = edgeIter.m_face0;
+ FaceInfo face1 = edgeIter.m_face1;
+ if ((face0 != null && !strip.isInStrip(face0)) && face1 != null
+ && !strip.isMarked(face1)) {
+ untouchedFace = face1;
+ break;
+ }
+ if ((face1 != null && !strip.isInStrip(face1)) && face0 != null
+ && !strip.isMarked(face0)) {
+ untouchedFace = face0;
+ break;
+ }
+
+ // find the next edgeIter
+ edgeIter = (edgeIter.m_v0 == v ? edgeIter.m_nextV0
+ : edgeIter.m_nextV1);
+ }
+
+ startInfo.m_startFace = untouchedFace;
+ startInfo.m_startEdge = edgeIter;
+ if (edgeIter != null) {
+ if (strip.sharesEdge(startInfo.m_startFace, edgeInfos))
+ startInfo.m_toV1 = (edgeIter.m_v0 == v); //note! used to be
+ // m_v1
+ else
+ startInfo.m_toV1 = (edgeIter.m_v1 == v);
+ }
+ return (startInfo.m_startFace != null);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // RemoveSmallStrips()
+ //
+ // allStrips is the whole strip vector...all small strips will be deleted
+ // from this list, to avoid leaking mem
+ // allBigStrips is an out parameter which will contain all strips above
+ // minStripLength
+ // faceList is an out parameter which will contain all faces which were
+ // removed from the striplist
+ //
+ void removeSmallStrips(StripInfoVec allStrips, StripInfoVec allBigStrips,
+ FaceInfoVec faceList) {
+ faceList.clear();
+ allBigStrips.clear(); //make sure these are empty
+ FaceInfoVec tempFaceList = new FaceInfoVec();
+
+ for (int i = 0; i < allStrips.size(); i++) {
+ if (allStrips.at(i).m_faces.size() < minStripLength) {
+ //strip is too small, add faces to faceList
+ for (int j = 0; j < allStrips.at(i).m_faces.size(); j++)
+ tempFaceList.add(allStrips.at(i).m_faces.at(j));
+
+ } else {
+ allBigStrips.add(allStrips.at(i));
+ }
+ }
+
+ boolean[] bVisitedList = new boolean[tempFaceList.size()];
+
+ VertexCache vcache = new VertexCache(cacheSize);
+
+ int bestNumHits = -1;
+ int numHits;
+ int bestIndex = -9999;
+
+ while (true) {
+ bestNumHits = -1;
+
+ //find best face to add next, given the current cache
+ for (int i = 0; i < tempFaceList.size(); i++) {
+ if (bVisitedList[i])
+ continue;
+
+ numHits = calcNumHitsFace(vcache, tempFaceList.at(i));
+ if (numHits > bestNumHits) {
+ bestNumHits = numHits;
+ bestIndex = i;
+ }
+ }
+
+ if (bestNumHits == -1.0f)
+ break;
+ bVisitedList[bestIndex] = true;
+ updateCacheFace(vcache, tempFaceList.at(bestIndex));
+ faceList.add(tempFaceList.at(bestIndex));
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // CreateStrips()
+ //
+ // Generates actual strips from the list-in-strip-order.
+ //
+ int createStrips(StripInfoVec allStrips, IntVec stripIndices,
+ boolean bStitchStrips) {
+ int numSeparateStrips = 0;
+
+ FaceInfo tLastFace = new FaceInfo(0, 0, 0);
+ int nStripCount = allStrips.size();
+
+ //we infer the cw/ccw ordering depending on the number of indices
+ //this is screwed up by the fact that we insert -1s to denote changing
+ // strips
+ //this is to account for that
+ int accountForNegatives = 0;
+
+ for (int i = 0; i < nStripCount; i++) {
+ StripInfo strip = allStrips.at(i);
+ int nStripFaceCount = strip.m_faces.size();
+
+ // Handle the first face in the strip
+ {
+ FaceInfo tFirstFace = new FaceInfo(strip.m_faces.at(0).m_v0,
+ strip.m_faces.at(0).m_v1, strip.m_faces.at(0).m_v2);
+
+ // If there is a second face, reorder vertices such that the
+ // unique vertex is first
+ if (nStripFaceCount > 1) {
+ int nUnique = getUniqueVertexInB(strip.m_faces.at(1),
+ tFirstFace);
+ if (nUnique == tFirstFace.m_v1) {
+ int tmp = tFirstFace.m_v0;
+ tFirstFace.m_v0 = tFirstFace.m_v1;
+ tFirstFace.m_v1 = tmp;
+ } else if (nUnique == tFirstFace.m_v2) {
+ int tmp = tFirstFace.m_v0;
+ tFirstFace.m_v0 = tFirstFace.m_v2;
+ tFirstFace.m_v2 = tmp;
+ }
+
+ // If there is a third face, reorder vertices such that the
+ // shared vertex is last
+ if (nStripFaceCount > 2) {
+ if (isDegenerate(strip.m_faces.at(1))) {
+ int pivot = strip.m_faces.at(1).m_v1;
+ if (tFirstFace.m_v1 == pivot) {
+ int tmp = tFirstFace.m_v1;
+ tFirstFace.m_v1 = tFirstFace.m_v2;
+ tFirstFace.m_v2 = tmp;
+ }
+ } else {
+ int[] nShared = new int[2];
+ getSharedVertices(strip.m_faces.at(2), tFirstFace,
+ nShared);
+ if ((nShared[0] == tFirstFace.m_v1)
+ && (nShared[1] == -1)) {
+ int tmp = tFirstFace.m_v1;
+ tFirstFace.m_v1 = tFirstFace.m_v2;
+ tFirstFace.m_v2 = tmp;
+ }
+ }
+ }
+ }
+
+ if ((i == 0) || !bStitchStrips) {
+ if (!isCW(strip.m_faces.at(0), tFirstFace.m_v0,
+ tFirstFace.m_v1))
+ stripIndices.add(tFirstFace.m_v0);
+ } else {
+ // Double tap the first in the new strip
+ stripIndices.add(tFirstFace.m_v0);
+
+ // Check CW/CCW ordering
+ if (nextIsCW(stripIndices.size() - accountForNegatives) != isCW(
+ strip.m_faces.at(0), tFirstFace.m_v0,
+ tFirstFace.m_v1)) {
+ stripIndices.add(tFirstFace.m_v0);
+ }
+ }
+
+ stripIndices.add(tFirstFace.m_v0);
+ stripIndices.add(tFirstFace.m_v1);
+ stripIndices.add(tFirstFace.m_v2);
+
+ // Update last face info
+ tLastFace.set(tFirstFace);
+ }
+
+ for (int j = 1; j < nStripFaceCount; j++) {
+ int nUnique = getUniqueVertexInB(tLastFace, strip.m_faces.at(j));
+ if (nUnique != -1) {
+ stripIndices.add(nUnique);
+
+ // Update last face info
+ tLastFace.m_v0 = tLastFace.m_v1;
+ tLastFace.m_v1 = tLastFace.m_v2;
+ tLastFace.m_v2 = nUnique;
+ } else {
+ //we've hit a degenerate
+ stripIndices.add(strip.m_faces.at(j).m_v2);
+ tLastFace.m_v0 = strip.m_faces.at(j).m_v0; //tLastFace.m_v1;
+ tLastFace.m_v1 = strip.m_faces.at(j).m_v1; //tLastFace.m_v2;
+ tLastFace.m_v2 = strip.m_faces.at(j).m_v2; //tLastFace.m_v1;
+
+ }
+ }
+
+ // Double tap between strips.
+ if (bStitchStrips) {
+ if (i != nStripCount - 1)
+ stripIndices.add(tLastFace.m_v2);
+ } else {
+ //-1 index indicates next strip
+ stripIndices.add(-1);
+ accountForNegatives++;
+ numSeparateStrips++;
+ }
+
+ // Update last face info
+ tLastFace.m_v0 = tLastFace.m_v1;
+ tLastFace.m_v1 = tLastFace.m_v2;
+ tLastFace.m_v2 = tLastFace.m_v2;
+ }
+
+ if (bStitchStrips)
+ numSeparateStrips = 1;
+ return numSeparateStrips;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // FindAllStrips()
+ //
+ // Does the stripification, puts output strips into vector allStrips
+ //
+ // Works by setting runnning a number of experiments in different areas of
+ // the mesh, and
+ // accepting the one which results in the longest strips. It then accepts
+ // this, and moves
+ // on to a different area of the mesh. We try to jump around the mesh some,
+ // to ensure that
+ // large open spans of strips get generated.
+ //
+ void findAllStrips(StripInfoVec allStrips, FaceInfoVec allFaceInfos,
+ EdgeInfoVec allEdgeInfos, int numSamples) {
+ // the experiments
+ int experimentId = 0;
+ int stripId = 0;
+ boolean done = false;
+
+ int loopCtr = 0;
+
+ while (!done) {
+ loopCtr++;
+
+ //
+ // PHASE 1: Set up numSamples * numEdges experiments
+ //
+ StripInfoVec[] experiments = new StripInfoVec[numSamples * 6];
+ for (int i = 0; i < experiments.length; i++)
+ experiments[i] = new StripInfoVec();
+
+ int experimentIndex = 0;
+ HashSet<FaceInfo> resetPoints = new HashSet<FaceInfo>(); /* NvFaceInfo */
+ for (int i = 0; i < numSamples; i++) {
+ // Try to find another good reset point.
+ // If there are none to be found, we are done
+ FaceInfo nextFace = findGoodResetPoint(allFaceInfos,
+ allEdgeInfos);
+ if (nextFace == null) {
+ done = true;
+ break;
+ }
+ // If we have already evaluated starting at this face in this
+ // slew of experiments, then skip going any further
+ else if (resetPoints.contains(nextFace)) {
+ continue;
+ }
+
+ // trying it now...
+ resetPoints.add(nextFace);
+
+ // otherwise, we shall now try experiments for starting on the
+ // 01,12, and 20 edges
+
+ // build the strip off of this face's 0-1 edge
+ EdgeInfo edge01 = findEdgeInfo(allEdgeInfos, nextFace.m_v0,
+ nextFace.m_v1);
+ StripInfo strip01 = new StripInfo(new StripStartInfo(nextFace,
+ edge01, true), stripId++, experimentId++);
+ experiments[experimentIndex++].add(strip01);
+
+ // build the strip off of this face's 1-0 edge
+ EdgeInfo edge10 = findEdgeInfo(allEdgeInfos, nextFace.m_v0,
+ nextFace.m_v1);
+ StripInfo strip10 = new StripInfo(new StripStartInfo(nextFace,
+ edge10, false), stripId++, experimentId++);
+ experiments[experimentIndex++].add(strip10);
+
+ // build the strip off of this face's 1-2 edge
+ EdgeInfo edge12 = findEdgeInfo(allEdgeInfos, nextFace.m_v1,
+ nextFace.m_v2);
+ StripInfo strip12 = new StripInfo(new StripStartInfo(nextFace,
+ edge12, true), stripId++, experimentId++);
+ experiments[experimentIndex++].add(strip12);
+
+ // build the strip off of this face's 2-1 edge
+ EdgeInfo edge21 = findEdgeInfo(allEdgeInfos, nextFace.m_v1,
+ nextFace.m_v2);
+ StripInfo strip21 = new StripInfo(new StripStartInfo(nextFace,
+ edge21, false), stripId++, experimentId++);
+ experiments[experimentIndex++].add(strip21);
+
+ // build the strip off of this face's 2-0 edge
+ EdgeInfo edge20 = findEdgeInfo(allEdgeInfos, nextFace.m_v2,
+ nextFace.m_v0);
+ StripInfo strip20 = new StripInfo(new StripStartInfo(nextFace,
+ edge20, true), stripId++, experimentId++);
+ experiments[experimentIndex++].add(strip20);
+
+ // build the strip off of this face's 0-2 edge
+ EdgeInfo edge02 = findEdgeInfo(allEdgeInfos, nextFace.m_v2,
+ nextFace.m_v0);
+ StripInfo strip02 = new StripInfo(new StripStartInfo(nextFace,
+ edge02, false), stripId++, experimentId++);
+ experiments[experimentIndex++].add(strip02);
+ }
+
+ //
+ // PHASE 2: Iterate through that we setup in the last phase
+ // and really build each of the strips and strips that follow to
+ // see how
+ // far we get
+ //
+ int numExperiments = experimentIndex;
+ for (int i = 0; i < numExperiments; i++) {
+
+ // get the strip set
+
+ // build the first strip of the list
+ experiments[i].at(0).build(allEdgeInfos, allFaceInfos);
+ int experimentId2 = experiments[i].at(0).m_experimentId;
+
+ StripInfo stripIter = experiments[i].at(0);
+ StripStartInfo startInfo = new StripStartInfo(null, null, false);
+ while (findTraversal(allFaceInfos, allEdgeInfos, stripIter,
+ startInfo)) {
+
+ // create the new strip info
+ //TODO startInfo clone ?
+ stripIter = new StripInfo(startInfo, stripId++,
+ experimentId2);
+
+ // build the next strip
+ stripIter.build(allEdgeInfos, allFaceInfos);
+
+ // add it to the list
+ experiments[i].add(stripIter);
+ }
+ }
+
+ //
+ // Phase 3: Find the experiment that has the most promise
+ //
+ int bestIndex = 0;
+ double bestValue = 0;
+ for (int i = 0; i < numExperiments; i++) {
+ float avgStripSizeWeight = 1.0f;
+ //float numTrisWeight = 0.0f;
+ float numStripsWeight = 0.0f;
+ float avgStripSize = avgStripSize(experiments[i]);
+ float numStrips = experiments[i].size();
+ float value = avgStripSize * avgStripSizeWeight
+ + (numStrips * numStripsWeight);
+ //float value = 1.f / numStrips;
+ //float value = numStrips * avgStripSize;
+
+ if (value > bestValue) {
+ bestValue = value;
+ bestIndex = i;
+ }
+ }
+
+ //
+ // Phase 4: commit the best experiment of the bunch
+ //
+ commitStrips(allStrips, experiments[bestIndex]);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // SplitUpStripsAndOptimize()
+ //
+ // Splits the input vector of strips (allBigStrips) into smaller, cache
+ // friendly pieces, then
+ // reorders these pieces to maximize cache hits
+ // The final strips are output through outStrips
+ //
+ void splitUpStripsAndOptimize(StripInfoVec allStrips,
+ StripInfoVec outStrips, EdgeInfoVec edgeInfos,
+ FaceInfoVec outFaceList) {
+ int threshold = cacheSize;
+ StripInfoVec tempStrips = new StripInfoVec();
+ int j;
+
+ //split up strips into threshold-sized pieces
+ for (int i = 0; i < allStrips.size(); i++) {
+ StripInfo currentStrip;
+ StripStartInfo startInfo = new StripStartInfo(null, null, false);
+
+ int actualStripSize = 0;
+ for (j = 0; j < allStrips.at(i).m_faces.size(); ++j) {
+ if (!isDegenerate(allStrips.at(i).m_faces.at(j)))
+ actualStripSize++;
+ }
+
+ if (actualStripSize /* allStrips.at(i).m_faces.size() */
+ > threshold) {
+
+ int numTimes = actualStripSize /* allStrips.at(i).m_faces.size() */
+ / threshold;
+ int numLeftover = actualStripSize /* allStrips.at(i).m_faces.size() */
+ % threshold;
+
+ int degenerateCount = 0;
+ for (j = 0; j < numTimes; j++) {
+ currentStrip = new StripInfo(startInfo, 0, -1);
+
+ int faceCtr = j * threshold + degenerateCount;
+ boolean bFirstTime = true;
+ while (faceCtr < threshold + (j * threshold)
+ + degenerateCount) {
+ if (isDegenerate(allStrips.at(i).m_faces.at(faceCtr))) {
+ degenerateCount++;
+
+ //last time or first time through, no need for a
+ // degenerate
+ if ((((faceCtr + 1) != threshold + (j * threshold)
+ + degenerateCount) || ((j == numTimes - 1)
+ && (numLeftover < 4) && (numLeftover > 0)))
+ && !bFirstTime) {
+ currentStrip.m_faces
+ .add(allStrips.at(i).m_faces
+ .at(faceCtr++));
+ } else
+ ++faceCtr;
+ } else {
+ currentStrip.m_faces.add(allStrips.at(i).m_faces
+ .at(faceCtr++));
+ bFirstTime = false;
+ }
+ }
+ /*
+ * threshold; faceCtr < threshold+(j*threshold); faceCtr++) {
+ * currentStrip.m_faces.add(allStrips.at(i).m_faces.at(faceCtr]); }
+ */
+ ///*
+ if (j == numTimes - 1) //last time through
+ {
+ if ((numLeftover < 4) && (numLeftover > 0)) //way too
+ // small
+ {
+ //just add to last strip
+ int ctr = 0;
+ while (ctr < numLeftover) {
+ if (!isDegenerate(allStrips.at(i).m_faces
+ .at(faceCtr))) {
+ currentStrip.m_faces
+ .add(allStrips.at(i).m_faces
+ .at(faceCtr++));
+ ++ctr;
+ } else {
+ currentStrip.m_faces
+ .add(allStrips.at(i).m_faces
+ .at(faceCtr++));
+ ++degenerateCount;
+ }
+ }
+ numLeftover = 0;
+ }
+ }
+ //*/
+ tempStrips.add(currentStrip);
+ }
+
+ int leftOff = j * threshold + degenerateCount;
+
+ if (numLeftover != 0) {
+ currentStrip = new StripInfo(startInfo, 0, -1);
+
+ int ctr = 0;
+ boolean bFirstTime = true;
+ while (ctr < numLeftover) {
+ if (!isDegenerate(allStrips.at(i).m_faces.at(leftOff))) {
+ ctr++;
+ bFirstTime = false;
+ currentStrip.m_faces.add(allStrips.at(i).m_faces
+ .at(leftOff++));
+ } else if (!bFirstTime)
+ currentStrip.m_faces.add(allStrips.at(i).m_faces
+ .at(leftOff++));
+ else
+ leftOff++;
+ }
+ /*
+ * for(int k = 0; k < numLeftover; k++) {
+ * currentStrip.m_faces.add(allStrips.at(i).m_faces[leftOff++]); }
+ */
+
+ tempStrips.add(currentStrip);
+ }
+ } else {
+ //we're not just doing a tempStrips.add(allBigStrips[i])
+ // because
+ // this way we can delete allBigStrips later to free the memory
+ currentStrip = new StripInfo(startInfo, 0, -1);
+
+ for (j = 0; j < allStrips.at(i).m_faces.size(); j++)
+ currentStrip.m_faces.add(allStrips.at(i).m_faces.at(j));
+
+ tempStrips.add(currentStrip);
+ }
+ }
+
+ //add small strips to face list
+ StripInfoVec tempStrips2 = new StripInfoVec();
+ removeSmallStrips(tempStrips, tempStrips2, outFaceList);
+
+ outStrips.clear();
+ //screw optimization for now
+ // for(i = 0; i < tempStrips.size(); ++i)
+ // outStrips.add(tempStrips[i]);
+
+ if (tempStrips2.size() != 0) {
+ //Optimize for the vertex cache
+ VertexCache vcache = new VertexCache(cacheSize);
+
+ float bestNumHits = -1.0f;
+ float numHits;
+ int bestIndex = -99999;
+
+ int firstIndex = 0;
+ float minCost = 10000.0f;
+
+ for (int i = 0; i < tempStrips2.size(); i++) {
+ int numNeighbors = 0;
+
+ //find strip with least number of neighbors per face
+ for (j = 0; j < tempStrips2.at(i).m_faces.size(); j++) {
+ numNeighbors += numNeighbors(tempStrips2.at(i).m_faces
+ .at(j), edgeInfos);
+ }
+
+ float currCost = (float) numNeighbors
+ / (float) tempStrips2.at(i).m_faces.size();
+ if (currCost < minCost) {
+ minCost = currCost;
+ firstIndex = i;
+ }
+ }
+
+ updateCacheStrip(vcache, tempStrips2.at(firstIndex));
+ outStrips.add(tempStrips2.at(firstIndex));
+
+ tempStrips2.at(firstIndex).visited = true;
+
+ boolean bWantsCW = (tempStrips2.at(firstIndex).m_faces.size() % 2) == 0;
+
+ //this n^2 algo is what slows down stripification so much....
+ // needs to be improved
+ while (true) {
+ bestNumHits = -1.0f;
+
+ //find best strip to add next, given the current cache
+ for (int i = 0; i < tempStrips2.size(); i++) {
+ if (tempStrips2.at(i).visited)
+ continue;
+
+ numHits = calcNumHitsStrip(vcache, tempStrips2.at(i));
+ if (numHits > bestNumHits) {
+ bestNumHits = numHits;
+ bestIndex = i;
+ } else if (numHits >= bestNumHits) {
+ //check previous strip to see if this one requires it
+ // to switch polarity
+ StripInfo strip = tempStrips2.at(i);
+ int nStripFaceCount = strip.m_faces.size();
+
+ FaceInfo tFirstFace = new FaceInfo(
+ strip.m_faces.at(0).m_v0,
+ strip.m_faces.at(0).m_v1,
+ strip.m_faces.at(0).m_v2);
+
+ // If there is a second face, reorder vertices such
+ // that the
+ // unique vertex is first
+ if (nStripFaceCount > 1) {
+ int nUnique = getUniqueVertexInB(strip.m_faces
+ .at(1), tFirstFace);
+ if (nUnique == tFirstFace.m_v1) {
+ int tmp = tFirstFace.m_v0;
+ tFirstFace.m_v0 = tFirstFace.m_v1;
+ tFirstFace.m_v1 = tmp;
+ } else if (nUnique == tFirstFace.m_v2) {
+ int tmp = tFirstFace.m_v0;
+ tFirstFace.m_v0 = tFirstFace.m_v2;
+ tFirstFace.m_v2 = tmp;
+ }
+
+ // If there is a third face, reorder vertices such
+ // that the
+ // shared vertex is last
+ if (nStripFaceCount > 2) {
+ int[] nShared = new int[2];
+ getSharedVertices(strip.m_faces.at(2),
+ tFirstFace, nShared);
+ if ((nShared[0] == tFirstFace.m_v1)
+ && (nShared[1] == -1)) {
+ int tmp = tFirstFace.m_v2;
+ tFirstFace.m_v2 = tFirstFace.m_v1;
+ tFirstFace.m_v1 = tmp;
+ }
+ }
+ }
+
+ // Check CW/CCW ordering
+ if (bWantsCW == isCW(strip.m_faces.at(0),
+ tFirstFace.m_v0, tFirstFace.m_v1)) {
+ //I like this one!
+ bestIndex = i;
+ }
+ }
+ }
+
+ if (bestNumHits == -1.0f)
+ break;
+ tempStrips2.at(bestIndex).visited = true;
+ updateCacheStrip(vcache, tempStrips2.at(bestIndex));
+ outStrips.add(tempStrips2.at(bestIndex));
+ bWantsCW = (tempStrips2.at(bestIndex).m_faces.size() % 2 == 0) ? bWantsCW
+ : !bWantsCW;
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // Stripify()
+ //
+ //
+ // in_indices are the input indices of the mesh to stripify
+ // in_cacheSize is the target cache size
+ //
+ void stripify(IntVec in_indices, int in_cacheSize, int in_minStripLength,
+ int maxIndex, StripInfoVec outStrips, FaceInfoVec outFaceList) {
+ meshJump = 0.0f;
+ bFirstTimeResetPoint = true; //used in FindGoodResetPoint()
+
+ //the number of times to run the experiments
+ int numSamples = 10;
+
+ //the cache size, clamped to one
+ cacheSize = Math.max(1, in_cacheSize - CACHE_INEFFICIENCY);
+
+ minStripLength = in_minStripLength;
+ //this is the strip size threshold below which we dump the strip into
+ // a list
+
+ indices = in_indices;
+
+ // build the stripification info
+ FaceInfoVec allFaceInfos = new FaceInfoVec();
+ EdgeInfoVec allEdgeInfos = new EdgeInfoVec();
+
+ buildStripifyInfo(allFaceInfos, allEdgeInfos, maxIndex);
+
+ StripInfoVec allStrips = new StripInfoVec();
+
+ // stripify
+ findAllStrips(allStrips, allFaceInfos, allEdgeInfos, numSamples);
+
+ //split up the strips into cache friendly pieces, optimize them, then
+ // dump these into outStrips
+ splitUpStripsAndOptimize(allStrips, outStrips, allEdgeInfos,
+ outFaceList);
+
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/tools/jme3tools/converters/model/strip/TriStrip.java b/engine/src/tools/jme3tools/converters/model/strip/TriStrip.java
new file mode 100644
index 0000000..aa84d5c
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/TriStrip.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2003-2009 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.converters.model.strip;
+
+import java.util.Arrays;
+
+/**
+ * To use, call generateStrips method, passing your triangle index list and
+ * then construct geometry/render resulting PrimitiveGroup objects.
+ * Features:
+ * <ul>
+ * <li>generates strips from arbitrary geometry.
+ * <li>flexibly optimizes for post TnL vertex caches (16 on GeForce1/2, 24 on GeForce3).
+ * <li>can stitch together strips using degenerate triangles, or not.
+ * <li>can output lists instead of strips.
+ * <li>can optionally throw excessively small strips into a list instead.
+ * <li>can remap indices to improve spatial locality in your vertex buffers.
+ * </ul>
+ * On cache sizes: Note that it's better to UNDERESTIMATE the cache size
+ * instead of OVERESTIMATING. So, if you're targetting GeForce1, 2, and 3, be
+ * conservative and use the GeForce1_2 cache size, NOT the GeForce3 cache size.
+ * This will make sure you don't "blow" the cache of the GeForce1 and 2. Also
+ * note that the cache size you specify is the "actual" cache size, not the
+ * "effective" cache size you may have heard about. This is 16 for GeForce1 and 2,
+ * and 24 for GeForce3.
+ *
+ * Credit goes to Curtis Beeson and Joe Demers for the basis for this
+ * stripifier and to Jason Regier and Jon Stone at Blizzard for providing a
+ * much cleaner version of CreateStrips().
+ *
+ * Ported to java by Artur Biesiadowski <abies@pg.gda.pl>
+ */
+public class TriStrip {
+
+ public static final int CACHESIZE_GEFORCE1_2 = 16;
+ public static final int CACHESIZE_GEFORCE3 = 24;
+
+ int cacheSize = CACHESIZE_GEFORCE1_2;
+ boolean bStitchStrips = true;
+ int minStripSize = 0;
+ boolean bListsOnly = false;
+
+ /**
+ *
+ */
+ public TriStrip() {
+ super();
+ }
+
+ /**
+ * If set to true, will return an optimized list, with no strips at all.
+ * Default value: false
+ */
+ public void setListsOnly(boolean _bListsOnly) {
+ bListsOnly = _bListsOnly;
+ }
+
+ /**
+ * Sets the cache size which the stripfier uses to optimize the data.
+ * Controls the length of the generated individual strips. This is the
+ * "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 You may
+ * want to play around with this number to tweak performance. Default
+ * value: 16
+ */
+ public void setCacheSize(int _cacheSize) {
+ cacheSize = _cacheSize;
+ }
+
+ /**
+ * bool to indicate whether to stitch together strips into one huge strip
+ * or not. If set to true, you'll get back one huge strip stitched together
+ * using degenerate triangles. If set to false, you'll get back a large
+ * number of separate strips. Default value: true
+ */
+ public void setStitchStrips(boolean _bStitchStrips) {
+ bStitchStrips = _bStitchStrips;
+ }
+
+ /**
+ * Sets the minimum acceptable size for a strip, in triangles. All strips
+ * generated which are shorter than this will be thrown into one big,
+ * separate list. Default value: 0
+ */
+ public void setMinStripSize(int _minStripSize) {
+ minStripSize = _minStripSize;
+ }
+
+ /**
+ * @param in_indices
+ * input index list, the indices you would use to render
+ * @return array of optimized/stripified PrimitiveGroups
+ */
+ public PrimitiveGroup[] generateStrips(int[] in_indices) {
+ int numGroups = 0;
+ PrimitiveGroup[] primGroups;
+ //put data in format that the stripifier likes
+ IntVec tempIndices = new IntVec();
+ int maxIndex = 0;
+
+ for (int i = 0; i < in_indices.length; i++) {
+ tempIndices.add(in_indices[i]);
+ if (in_indices[i] > maxIndex)
+ maxIndex = in_indices[i];
+ }
+
+ StripInfoVec tempStrips = new StripInfoVec();
+ FaceInfoVec tempFaces = new FaceInfoVec();
+
+ Stripifier stripifier = new Stripifier();
+
+ //do actual stripification
+ stripifier.stripify(tempIndices, cacheSize, minStripSize, maxIndex, tempStrips, tempFaces);
+
+ //stitch strips together
+ IntVec stripIndices = new IntVec();
+ int numSeparateStrips = 0;
+
+ if (bListsOnly) {
+ //if we're outputting only lists, we're done
+ numGroups = 1;
+ primGroups = new PrimitiveGroup[numGroups];
+ primGroups[0] = new PrimitiveGroup();
+ PrimitiveGroup[] primGroupArray = primGroups;
+
+ //count the total number of indices
+ int numIndices = 0;
+ for (int i = 0; i < tempStrips.size(); i++) {
+ numIndices += tempStrips.at(i).m_faces.size() * 3;
+ }
+
+ //add in the list
+ numIndices += tempFaces.size() * 3;
+
+ primGroupArray[0].type = PrimitiveGroup.PT_LIST;
+ primGroupArray[0].indices = new int[numIndices];
+ primGroupArray[0].numIndices = numIndices;
+
+ //do strips
+ int indexCtr = 0;
+ for (int i = 0; i < tempStrips.size(); i++) {
+ for (int j = 0; j < tempStrips.at(i).m_faces.size(); j++) {
+ //degenerates are of no use with lists
+ if (!Stripifier.isDegenerate(tempStrips.at(i).m_faces.at(j))) {
+ primGroupArray[0].indices[indexCtr++] = tempStrips.at(i).m_faces.at(j).m_v0;
+ primGroupArray[0].indices[indexCtr++] = tempStrips.at(i).m_faces.at(j).m_v1;
+ primGroupArray[0].indices[indexCtr++] = tempStrips.at(i).m_faces.at(j).m_v2;
+ } else {
+ //we've removed a tri, reduce the number of indices
+ primGroupArray[0].numIndices -= 3;
+ }
+ }
+ }
+
+ //do lists
+ for (int i = 0; i < tempFaces.size(); i++) {
+ primGroupArray[0].indices[indexCtr++] = tempFaces.at(i).m_v0;
+ primGroupArray[0].indices[indexCtr++] = tempFaces.at(i).m_v1;
+ primGroupArray[0].indices[indexCtr++] = tempFaces.at(i).m_v2;
+ }
+ } else {
+ numSeparateStrips = stripifier.createStrips(tempStrips, stripIndices, bStitchStrips);
+
+ //if we're stitching strips together, we better get back only one
+ // strip from CreateStrips()
+
+ //convert to output format
+ numGroups = numSeparateStrips; //for the strips
+ if (tempFaces.size() != 0)
+ numGroups++; //we've got a list as well, increment
+ primGroups = new PrimitiveGroup[numGroups];
+ for (int i = 0; i < primGroups.length; i++) {
+ primGroups[i] = new PrimitiveGroup();
+ }
+
+ PrimitiveGroup[] primGroupArray = primGroups;
+
+ //first, the strips
+ int startingLoc = 0;
+ for (int stripCtr = 0; stripCtr < numSeparateStrips; stripCtr++) {
+ int stripLength = 0;
+
+ if (!bStitchStrips) {
+ int i;
+ //if we've got multiple strips, we need to figure out the
+ // correct length
+ for (i = startingLoc; i < stripIndices.size(); i++) {
+ if (stripIndices.get(i) == -1)
+ break;
+ }
+
+ stripLength = i - startingLoc;
+ } else
+ stripLength = stripIndices.size();
+
+ primGroupArray[stripCtr].type = PrimitiveGroup.PT_STRIP;
+ primGroupArray[stripCtr].indices = new int[stripLength];
+ primGroupArray[stripCtr].numIndices = stripLength;
+
+ int indexCtr = 0;
+ for (int i = startingLoc; i < stripLength + startingLoc; i++)
+ primGroupArray[stripCtr].indices[indexCtr++] = stripIndices.get(i);
+
+ //we add 1 to account for the -1 separating strips
+ //this doesn't break the stitched case since we'll exit the
+ // loop
+ startingLoc += stripLength + 1;
+ }
+
+ //next, the list
+ if (tempFaces.size() != 0) {
+ int faceGroupLoc = numGroups - 1; //the face group is the last
+ // one
+ primGroupArray[faceGroupLoc].type = PrimitiveGroup.PT_LIST;
+ primGroupArray[faceGroupLoc].indices = new int[tempFaces.size() * 3];
+ primGroupArray[faceGroupLoc].numIndices = tempFaces.size() * 3;
+ int indexCtr = 0;
+ for (int i = 0; i < tempFaces.size(); i++) {
+ primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces.at(i).m_v0;
+ primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces.at(i).m_v1;
+ primGroupArray[faceGroupLoc].indices[indexCtr++] = tempFaces.at(i).m_v2;
+ }
+ }
+ }
+ return primGroups;
+ }
+
+ /**
+ * Function to remap your indices to improve spatial locality in your
+ * vertex buffer.
+ *
+ * in_primGroups: array of PrimitiveGroups you want remapped numGroups:
+ * number of entries in in_primGroups numVerts: number of vertices in your
+ * vertex buffer, also can be thought of as the range of acceptable values
+ * for indices in your primitive groups. remappedGroups: array of remapped
+ * PrimitiveGroups
+ *
+ * Note that, according to the remapping handed back to you, you must
+ * reorder your vertex buffer.
+ *
+ */
+
+ public static int[] remapIndices(int[] indices, int numVerts) {
+ int[] indexCache = new int[numVerts];
+ Arrays.fill(indexCache, -1);
+
+ int numIndices = indices.length;
+ int[] remappedIndices = new int[numIndices];
+ int indexCtr = 0;
+ for (int j = 0; j < numIndices; j++) {
+ int cachedIndex = indexCache[indices[j]];
+ if (cachedIndex == -1) //we haven't seen this index before
+ {
+ //point to "last" vertex in VB
+ remappedIndices[j] = indexCtr;
+
+ //add to index cache, increment
+ indexCache[indices[j]] = indexCtr++;
+ } else {
+ //we've seen this index before
+ remappedIndices[j] = cachedIndex;
+ }
+ }
+
+ return remappedIndices;
+ }
+
+ public static void remapArrays(float[] vertexBuffer, int vertexSize, int[] indices) {
+ int[] remapped = remapIndices(indices, vertexBuffer.length / vertexSize);
+ float[] bufferCopy = vertexBuffer.clone();
+ for (int i = 0; i < remapped.length; i++) {
+ int from = indices[i] * vertexSize;
+ int to = remapped[i] * vertexSize;
+ for (int j = 0; j < vertexSize; j++) {
+ vertexBuffer[to + j] = bufferCopy[from + j];
+ }
+ }
+
+ System.arraycopy(remapped, 0, indices, 0, indices.length);
+ }
+
+}
diff --git a/engine/src/tools/jme3tools/converters/model/strip/VertexCache.java b/engine/src/tools/jme3tools/converters/model/strip/VertexCache.java
new file mode 100644
index 0000000..b60e9e5
--- /dev/null
+++ b/engine/src/tools/jme3tools/converters/model/strip/VertexCache.java
@@ -0,0 +1,100 @@
+/*
+ * 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.converters.model.strip;
+
+import java.util.Arrays;
+
+
+class VertexCache {
+
+ int[] entries;
+ int numEntries;
+
+ public VertexCache() {
+ this(16);
+ }
+
+ public VertexCache(int size) {
+ numEntries = size;
+ entries = new int[numEntries];
+ clear();
+ }
+
+ public boolean inCache(int entry) {
+ for(int i = 0; i < numEntries; i++)
+ {
+ if(entries[i] == entry)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int addEntry(int entry) {
+ int removed;
+
+ removed = entries[numEntries - 1];
+
+ //push everything right one
+ for(int i = numEntries - 2; i >= 0; i--)
+ {
+ entries[i + 1] = entries[i];
+ }
+
+ entries[0] = entry;
+
+ return removed;
+ }
+
+ public void clear() {
+ Arrays.fill(entries,-1);
+ }
+
+ public int at(int index) {
+ return entries[index];
+ }
+
+ public void set(int index, int value) {
+ entries[index] = value;
+ }
+
+ public void copy(VertexCache inVcache)
+ {
+ for(int i = 0; i < numEntries; i++)
+ {
+ inVcache.set(i, entries[i]);
+ }
+ }
+
+}
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
+
+
diff --git a/engine/src/tools/jme3tools/savegame/SaveGame.java b/engine/src/tools/jme3tools/savegame/SaveGame.java
new file mode 100644
index 0000000..56a693e
--- /dev/null
+++ b/engine/src/tools/jme3tools/savegame/SaveGame.java
@@ -0,0 +1,118 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package jme3tools.savegame;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.Savable;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.system.JmeSystem;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Tool for saving Savables as SaveGame entries in a system-dependent way.
+ * @author normenhansen
+ */
+public class SaveGame {
+
+ /**
+ * Saves a savable in a system-dependent way.
+ * @param gamePath A unique path for this game, e.g. com/mycompany/mygame
+ * @param dataName A unique name for this savegame, e.g. "save_001"
+ * @param data The Savable to save
+ */
+ public static void saveGame(String gamePath, String dataName, Savable data) {
+ BinaryExporter ex = BinaryExporter.getInstance();
+ OutputStream os = null;
+ try {
+ File daveFolder = new File(JmeSystem.getStorageFolder().getAbsolutePath() + File.separator + gamePath.replaceAll("/", File.separator));
+ if (!daveFolder.exists() && !daveFolder.mkdirs()) {
+ Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error creating save file!");
+ throw new IllegalStateException("SaveGame dataset cannot be created");
+ }
+ File saveFile = new File(daveFolder.getAbsolutePath() + File.separator + dataName);
+ if (!saveFile.exists()) {
+ if (!saveFile.createNewFile()) {
+ Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error creating save file!");
+ throw new IllegalStateException("SaveGame dataset cannot be created");
+ }
+ }
+ os = new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(saveFile)));
+ ex.save(data, os);
+ } catch (IOException ex1) {
+ Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error saving data: {0}", ex1);
+ ex1.printStackTrace();
+ throw new IllegalStateException("SaveGame dataset cannot be saved");
+ } finally {
+ try {
+ if (os != null) {
+ os.close();
+ }
+ } catch (IOException ex1) {
+ Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error saving data: {0}", ex1);
+ ex1.printStackTrace();
+ throw new IllegalStateException("SaveGame dataset cannot be saved");
+ }
+ }
+ }
+
+ /**
+ * Loads a savable that has been saved on this system with saveGame() before.
+ * @param gamePath A unique path for this game, e.g. com/mycompany/mygame
+ * @param dataName A unique name for this savegame, e.g. "save_001"
+ * @return The savable that was saved
+ */
+ public static Savable loadGame(String gamePath, String dataName) {
+ return loadGame(gamePath, dataName, null);
+ }
+
+ /**
+ * Loads a savable that has been saved on this system with saveGame() before.
+ * @param gamePath A unique path for this game, e.g. com/mycompany/mygame
+ * @param dataName A unique name for this savegame, e.g. "save_001"
+ * @param manager Link to an AssetManager if required for loading the data (e.g. models with textures)
+ * @return The savable that was saved or null if none was found
+ */
+ public static Savable loadGame(String gamePath, String dataName, AssetManager manager) {
+ InputStream is = null;
+ Savable sav = null;
+ try {
+ File file = new File(JmeSystem.getStorageFolder().getAbsolutePath() + File.separator + gamePath.replaceAll("/", File.separator) + File.separator + dataName);
+ if(!file.exists()){
+ return null;
+ }
+ is = new GZIPInputStream(new BufferedInputStream(new FileInputStream(file)));
+ BinaryImporter imp = BinaryImporter.getInstance();
+ if (manager != null) {
+ imp.setAssetManager(manager);
+ }
+ sav = imp.load(is);
+ } catch (IOException ex) {
+ Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error loading data: {0}", ex);
+ ex.printStackTrace();
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ex) {
+ Logger.getLogger(SaveGame.class.getName()).log(Level.SEVERE, "Error loading data: {0}", ex);
+ ex.printStackTrace();
+ }
+ }
+ }
+ return sav;
+ }
+}