From f9ce9e246c2608f34f18d3da5da86b33a667a59d Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 22 Nov 2015 13:33:29 -0500 Subject: [PATCH 1/2] FBX: add ear clipping triangulator --- .../jme3/scene/plugins/fbx/file/FbxDump.java | 25 ++ .../jme3/scene/plugins/fbx/mesh/FbxMesh.java | 8 +- .../jme3/scene/plugins/fbx/node/FbxNode.java | 11 +- .../java/com/jme3/scene/plugins/IrUtils.java | 35 ++- .../triangulator/EarClippingTriangulator.java | 241 ++++++++++++++++++ .../triangulator/TriangulatorTest.java | 64 +++++ 6 files changed, 369 insertions(+), 15 deletions(-) create mode 100644 jme3-plugins/src/main/java/com/jme3/scene/plugins/triangulator/EarClippingTriangulator.java create mode 100644 jme3-plugins/src/test/java/com/jme3/scene/plugins/triangulator/TriangulatorTest.java diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java index 00619dce7..d0e1d05c6 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java @@ -31,6 +31,9 @@ */ package com.jme3.scene.plugins.fbx.file; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.lang.reflect.Array; @@ -88,6 +91,28 @@ public final class FbxDump { dumpFile(file, System.out); } + /** + * Dump FBX to standard output. + * + * @param file the file to dump. + */ + public static void dumpFile(String file) { + InputStream in = null; + try { + in = new FileInputStream(file); + FbxFile scene = FbxReader.readFBX(in); + FbxDump.dumpFile(scene); + } catch (IOException ex) { + throw new RuntimeException(ex); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ex) { } + } + } + } + /** * Dump FBX to the given output stream. * diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java index 5dd911bed..2278b1695 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java @@ -147,7 +147,8 @@ public final class FbxMesh extends FbxNodeAttribute> { public void connectObject(FbxObject object) { if (object instanceof FbxSkinDeformer) { if (skinDeformer != null) { - logger.log(Level.WARNING, "This mesh already has a skin deformer attached. Ignoring."); + logger.log(Level.WARNING, "This mesh already has a skin " + + "deformer attached: {0}. Ignoring.", this); return; } skinDeformer = (FbxSkinDeformer) object; @@ -237,7 +238,7 @@ public final class FbxMesh extends FbxNodeAttribute> { if (jmeMeshes.size() == 0) { // When will this actually happen? Not sure. - logger.log(Level.WARNING, "Empty FBX mesh found (unusual)."); + logger.log(Level.WARNING, "Empty FBX mesh found: {0} (unusual).", this); } // IMPORTANT: If we have a -1 entry, those are triangles @@ -245,7 +246,8 @@ public final class FbxMesh extends FbxNodeAttribute> { // It makes sense only if the mesh uses a single material! if (jmeMeshes.containsKey(-1) && jmeMeshes.size() > 1) { logger.log(Level.WARNING, "Mesh has polygons with no material " - + "indices (unusual) - they will use material index 0."); + + "indices: {0} (unusual) - " + + "they will use material index 0.", this); } return jmeMeshes; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java index c0ce06d4b..05e031328 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNode.java @@ -293,7 +293,8 @@ public class FbxNode extends FbxObject { float z = ((Double) e2.properties.get(6)).floatValue(); userDataValue = new Vector3f(x, y, z); } else { - logger.log(Level.WARNING, "Unsupported user data type: {0}. Ignoring.", userDataType); + logger.log(Level.WARNING, "Unsupported user data type: {0}. " + + "Ignoring.", userDataType); continue; } @@ -329,6 +330,9 @@ public class FbxNode extends FbxObject { // Material index does not exist. Create default material. jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); jmeMat.setReceivesShadows(true); + logger.log(Level.WARNING, "Material index {0} is undefined in: {1}. " + + "Will use default material.", + new Object[]{materialIndex, this}); } else { FbxMaterial fbxMat = materials.get(materialIndex); jmeMat = fbxMat.getJmeObject(); @@ -400,7 +404,8 @@ public class FbxNode extends FbxObject { if (jmeMeshes == null || jmeMeshes.size() == 0) { // No meshes found on FBXMesh (??) - logger.log(Level.WARNING, "No meshes could be loaded. Creating empty node."); + logger.log(Level.WARNING, "No meshes could be loaded: {0}. " + + "Creating empty node.", this); spatial = new Node(getName() + "-node"); } else { // Multiple jME3 geometries required for a single FBXMesh. @@ -437,7 +442,7 @@ public class FbxNode extends FbxObject { if (!FastMath.approximateEquals(localScale.x, localScale.y) || !FastMath.approximateEquals(localScale.x, localScale.z)) { logger.log(Level.WARNING, "Non-uniform scale detected on parent node. " + - "The model may appear distorted."); + "The model {1} may appear distorted.", this); } } diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java index 7b20e1670..18b6a25e2 100644 --- a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java @@ -37,6 +37,7 @@ import com.jme3.scene.VertexBuffer; import com.jme3.scene.mesh.IndexBuffer; import com.jme3.scene.mesh.IndexIntBuffer; import com.jme3.scene.mesh.IndexShortBuffer; +import com.jme3.scene.plugins.triangulator.EarClippingTriangulator; import com.jme3.util.BufferUtils; import com.jme3.util.IntMap; import java.nio.ByteBuffer; @@ -172,23 +173,40 @@ public final class IrUtils { } } + private static void dumpPoly(IrPolygon polygon) { + System.out.println("Polygon with " + polygon.vertices.length + " vertices"); + for (IrVertex vertex : polygon.vertices) { + System.out.println("\t" + vertex.pos); + } + } + /** * Convert mesh from quads / triangles to triangles only. */ public static void triangulate(IrMesh mesh) { List newPolygons = new ArrayList(mesh.polygons.length); + EarClippingTriangulator triangulator = new EarClippingTriangulator(); for (IrPolygon inputPoly : mesh.polygons) { - if (inputPoly.vertices.length == 4) { + int numVertices = inputPoly.vertices.length; + + if (numVertices < 3) { + // point / edge + logger.log(Level.WARNING, "Point or edge encountered. Ignoring."); + } else if (numVertices == 3) { + // triangle + newPolygons.add(inputPoly); + } else if (numVertices == 4) { + // quad IrPolygon[] tris = quadToTri(inputPoly); newPolygons.add(tris[0]); newPolygons.add(tris[1]); - } else if (inputPoly.vertices.length == 3) { - newPolygons.add(inputPoly); } else { - // N-gon. We have to ignore it.. - logger.log(Level.WARNING, "N-gon encountered, ignoring. " - + "The mesh may not appear correctly. " - + "Triangulate your model prior to export."); + // N-gon + dumpPoly(inputPoly); + IrPolygon[] tris = triangulator.triangulate(inputPoly); + for (IrPolygon tri : tris) { + newPolygons.add(tri); + } } } mesh.polygons = new IrPolygon[newPolygons.size()]; @@ -373,12 +391,11 @@ public final class IrUtils { boneIndices.put((byte)0); boneWeights.put(0f); } + maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length); } else { boneIndices.putInt(0); boneWeights.put(0f).put(0f).put(0f).put(0f); } - - maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length); } } diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/triangulator/EarClippingTriangulator.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/triangulator/EarClippingTriangulator.java new file mode 100644 index 000000000..df6b98d02 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/triangulator/EarClippingTriangulator.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2009-2015 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 com.jme3.scene.plugins.triangulator; + +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.IrPolygon; +import com.jme3.scene.plugins.IrVertex; +import java.util.ArrayList; + +/** + * Implemented according to + *
    + *
  • http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
  • + *
  • http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/algorithm2.html
  • + *
+ */ +public final class EarClippingTriangulator { + + private static enum VertexType { + Convex, + Reflex, + Ear; + } + + private final ArrayList indices = new ArrayList(); + private final ArrayList types = new ArrayList(); + private final ArrayList positions = new ArrayList(); + + public EarClippingTriangulator() { + } + + private static int ccw(Vector2f p0, Vector2f p1, Vector2f p2) { + float result = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x); + if (result > 0) { + return 1; + } else if (result < 0) { + return -1; + } else { + return 0; + } + } + + private static boolean pointInTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vector2f p) { + float d = ((t1.y - t2.y) * (t0.x - t2.x) + (t2.x - t1.x) * (t0.y - t2.y)); + float a = ((t1.y - t2.y) * (p.x - t2.x) + (t2.x - t1.x) * (p.y - t2.y)) / d; + float b = ((t2.y - t0.y) * (p.x - t2.x) + (t0.x - t2.x) * (p.y - t2.y)) / d; + float c = 1 - a - b; + return 0 <= a && a <= 1 && 0 <= b && b <= 1 && 0 <= c && c <= 1; + } + + private static Matrix3f normalToMatrix(Vector3f norm) { + Vector3f tang1 = norm.cross(Vector3f.UNIT_X); + if (tang1.lengthSquared() < FastMath.ZERO_TOLERANCE) { + tang1 = norm.cross(Vector3f.UNIT_Y); + } + tang1.normalizeLocal(); + Vector3f tang2 = norm.cross(tang1).normalizeLocal(); + + return new Matrix3f( + tang1.x, tang1.y, tang1.z, + tang2.x, tang2.y, tang2.z, + norm.x, norm.y, norm.z); + } + + private int prev(int index) { + if (index == 0) { + return indices.size() - 1; + } else { + return index - 1; + } + } + + private int next(int index) { + if (index == indices.size() - 1) { + return 0; + } else { + return index + 1; + } + } + + private VertexType calcType(int index) { + int prev = prev(index); + int next = next(index); + + Vector2f p0 = positions.get(prev); + Vector2f p1 = positions.get(index); + Vector2f p2 = positions.get(next); + + if (ccw(p0, p1, p2) <= 0) { + return VertexType.Reflex; + } else { + for (int i = 0; i < positions.size() - 3; i++) { + int testIndex = (index + 2 + i) % positions.size(); + if (types.get(testIndex) != VertexType.Reflex) { + continue; + } + Vector2f p = positions.get(testIndex); + if (pointInTriangle(p0, p1, p2, p)) { + return VertexType.Convex; + } + } + return VertexType.Ear; + } + } + + private void updateType(int index) { + if (types.get(index) == VertexType.Convex) { + return; + } + types.set(index, calcType(index)); + } + + private void loadVertices(IrVertex[] vertices) { + indices.ensureCapacity(vertices.length); + types.ensureCapacity(vertices.length); + positions.ensureCapacity(vertices.length); + + Vector3f normal = FastMath.computeNormal( + vertices[0].pos, + vertices[1].pos, + vertices[2].pos); + + Matrix3f transform = normalToMatrix(normal); + + for (int i = 0; i < vertices.length; i++) { + Vector3f projected = transform.mult(vertices[i].pos); + indices.add(i); + positions.add(new Vector2f(projected.x, projected.y)); + types.add(VertexType.Reflex); + } + + for (int i = 0; i < vertices.length; i++) { + types.set(i, calcType(i)); + } + } + + private IrPolygon createTriangle(IrPolygon polygon, int prev, int index, int next) { + int p0 = indices.get(prev); + int p1 = indices.get(index); + int p2 = indices.get(next); + IrPolygon triangle = new IrPolygon(); + triangle.vertices = new IrVertex[] { + polygon.vertices[p0], + polygon.vertices[p1], + polygon.vertices[p2], + }; + return triangle; + } + + /** + * Triangulates the given polygon. + * + * Five or more vertices are required, if less are given, an exception + * is thrown. + * + * @param polygon The polygon to triangulate. + * @return N - 2 triangles, where N is the number of vertices in the polygon. + * + * @throws IllegalArgumentException If the polygon has less than 5 vertices. + */ + public IrPolygon[] triangulate(IrPolygon polygon) { + if (polygon.vertices.length < 5) { + throw new IllegalArgumentException("Only polygons with 5 or more vertices are supported"); + } + + try { + int numTris = 0; + IrPolygon[] triangles = new IrPolygon[polygon.vertices.length - 2]; + + loadVertices(polygon.vertices); + + int index = 0; + while (types.size() > 3) { + if (types.get(index) == VertexType.Ear) { + int prev = prev(index); + int next = next(index); + + triangles[numTris++] = createTriangle(polygon, prev, index, next); + + indices.remove(index); + types.remove(index); + positions.remove(index); + + next = next(prev); + updateType(prev); + updateType(next); + + index = next(next); + } else { + index = next(index); + } + } + + if (types.size() == 3) { + triangles[numTris++] = createTriangle(polygon, 0, 1, 2); + } + + if (numTris != triangles.length) { + throw new AssertionError("Triangulation failed to generate enough triangles"); + } + + return triangles; + } finally { + indices.clear(); + positions.clear(); + types.clear(); + } + } +} diff --git a/jme3-plugins/src/test/java/com/jme3/scene/plugins/triangulator/TriangulatorTest.java b/jme3-plugins/src/test/java/com/jme3/scene/plugins/triangulator/TriangulatorTest.java new file mode 100644 index 000000000..c1a0b2ffa --- /dev/null +++ b/jme3-plugins/src/test/java/com/jme3/scene/plugins/triangulator/TriangulatorTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2015 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 com.jme3.scene.plugins.triangulator; + +import com.jme3.math.Vector3f; +import com.jme3.scene.plugins.IrPolygon; +import com.jme3.scene.plugins.IrVertex; +import junit.framework.TestCase; + +public class TriangulatorTest extends TestCase { + + public void testTriangulator() { + Vector3f[] dataSet = new Vector3f[]{ + new Vector3f(0.75f, 0.3f, 1.2f), + new Vector3f(0.75f, 0.3f, 0.0f), + new Vector3f(0.75f, 0.17f, 0.0f), + new Vector3f(0.75000095f, 0.17f, 1.02f), + new Vector3f(0.75f, -0.17f, 1.02f), + new Vector3f(0.75f, -0.17f, 0.0f), + new Vector3f(0.75f, -0.3f, 0.0f), + new Vector3f(0.75f, -0.3f, 1.2f) + }; + + IrPolygon poly = new IrPolygon(); + poly.vertices = new IrVertex[dataSet.length]; + for (int i = 0; i < dataSet.length; i++) { + poly.vertices[i] = new IrVertex(); + poly.vertices[i].pos = dataSet[i]; + } + + EarClippingTriangulator triangulator = new EarClippingTriangulator(); + triangulator.triangulate(poly); + } + +} From 6db1d15045d25e9c8727948fd7f0601709852d1f Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 22 Nov 2015 19:00:18 -0500 Subject: [PATCH 2/2] Image: support for RGTC format --- .../src/main/java/com/jme3/renderer/Caps.java | 7 +- .../java/com/jme3/renderer/opengl/GLExt.java | 2 + .../jme3/renderer/opengl/GLImageFormats.java | 5 ++ .../com/jme3/renderer/opengl/GLRenderer.java | 4 + .../src/main/java/com/jme3/texture/Image.java | 16 +++- .../com/jme3/texture/plugins/DDSLoader.java | 80 ++++++++++++++++--- .../com/jme3/texture/plugins/DXTFlipper.java | 8 +- 7 files changed, 102 insertions(+), 20 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index 9387b687e..9abc46e71 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -349,7 +349,12 @@ public enum Caps { /** * GPU can provide and accept binary shaders. */ - BinaryShader; + BinaryShader, + + /** + * Supports {@link Format#RGTC} and {@link Format#RTC} texture compression. + */ + TextureCompressionRGTC; /** * Returns true if given the renderer capabilities, the texture diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java index c77ff6449..1fd675ec2 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLExt.java @@ -44,6 +44,8 @@ import java.nio.IntBuffer; public interface GLExt { public static final int GL_ALREADY_SIGNALED = 0x911A; + public static final int GL_COMPRESSED_RED_RGTC1 = 0x8DBB; + public static final int GL_COMPRESSED_RG_RGTC2 = 0x8DBD; public static final int GL_COMPRESSED_RGB8_ETC2 = 0x9274; public static final int GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1; public static final int GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java index ab80b9e2a..b4f669131 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java @@ -233,6 +233,11 @@ public final class GLImageFormats { formatComp(formatToGL, Format.ETC1, GLExt.GL_ETC1_RGB8_OES, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); } + if (caps.contains(Caps.TextureCompressionRGTC)) { + formatComp(formatToGL, Format.RGTC, GLExt.GL_COMPRESSED_RG_RGTC2, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); + formatComp(formatToGL, Format.RTC, GLExt.GL_COMPRESSED_RED_RGTC1, GL.GL_RED, GL.GL_UNSIGNED_BYTE); + } + return formatToGL; } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 6576095a5..fcaeaa7d1 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -348,6 +348,10 @@ public final class GLRenderer implements Renderer { } else if (hasExtension("GL_OES_compressed_ETC1_RGB8_texture")) { caps.add(Caps.TextureCompressionETC1); } + + if (hasExtension("GL_ARB_texture_compression_rgtc")) { + caps.add(Caps.TextureCompressionRGTC); + } // == end texture format extensions == diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java index ca9404720..f0fad2360 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -299,7 +299,21 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { * * Requires {@link Caps#TextureCompressionETC1}. */ - ETC1(4, false, true, false); + ETC1(4, false, true, false), + + /** + * RGTC with red channel only. + * + * Requires {@link Caps#TextureCompressionRGTC}. + */ + RTC(4, false, true, false), + + /** + * RGTC with red and green channels. + * + * Requires {@link Caps#TextureCompressionRGTC}. + */ + RGTC(8, false, true, false); private int bpp; private boolean isDepth; diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java index e7ef0bcd9..a4fdee761 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java @@ -85,6 +85,8 @@ public class DDSLoader implements AssetLoader { private static final int PF_DXT1 = 0x31545844; private static final int PF_DXT3 = 0x33545844; private static final int PF_DXT5 = 0x35545844; + private static final int PF_ETC1 = 0x31435445; + private static final int PF_ETC_ = 0x20435445; // the underscore represents a space private static final int PF_ATI1 = 0x31495441; private static final int PF_ATI2 = 0x32495441; // 0x41544932; private static final int PF_DX10 = 0x30315844; // a DX10 format @@ -94,6 +96,9 @@ public class DDSLoader implements AssetLoader { DX10DIM_TEXTURE3D = 0x4; private static final int DX10MISC_GENERATE_MIPS = 0x1, DX10MISC_TEXTURECUBE = 0x4; + private static final int DXGI_FORMAT_BC4_TYPELESS = 79; + private static final int DXGI_FORMAT_BC4_UNORM = 80; + private static final int DXGI_FORMAT_BC4_SNORM = 81; private static final double LOG2 = Math.log(2); private int width; private int height; @@ -105,9 +110,11 @@ public class DDSLoader implements AssetLoader { private int caps2; private boolean directx10; private boolean compressed; + private boolean dxtOrRgtc; private boolean texture3D; private boolean grayscaleOrAlpha; private boolean normal; + private ColorSpace colorSpace; private Format pixelFormat; private int bpp; private int[] sizes; @@ -133,7 +140,8 @@ public class DDSLoader implements AssetLoader { ((TextureKey) info.getKey()).setTextureTypeHint(Texture.Type.CubeMap); } ArrayList data = readData(((TextureKey) info.getKey()).isFlipY()); - return new Image(pixelFormat, width, height, depth, data, sizes, ColorSpace.sRGB); + + return new Image(pixelFormat, width, height, depth, data, sizes, colorSpace); } finally { if (stream != null){ stream.close(); @@ -145,18 +153,24 @@ public class DDSLoader implements AssetLoader { in = new LittleEndien(stream); loadHeader(); ArrayList data = readData(false); - return new Image(pixelFormat, width, height, depth, data, sizes, ColorSpace.sRGB); + return new Image(pixelFormat, width, height, depth, data, sizes, colorSpace); } private void loadDX10Header() throws IOException { int dxgiFormat = in.readInt(); + if (dxgiFormat == 0) { - pixelFormat = Format.ETC1; - bpp = 4; + pixelFormat = Format.ETC1; + compressed = true; + bpp = 4; } else { + pixelFormat = DXGIFormat.getJmeFormat(dxgiFormat); + if (pixelFormat == null) { throw new IOException("Unsupported DX10 format: " + dxgiFormat); + } + bpp = pixelFormat.getBitsPerPixel(); + compressed = pixelFormat.isCompressed(); } - compressed = true; int resDim = in.readInt(); if (resDim == DX10DIM_TEXTURE3D) { @@ -201,6 +215,7 @@ public class DDSLoader implements AssetLoader { caps2 = in.readInt(); in.skipBytes(12); texture3D = false; + colorSpace = ColorSpace.sRGB; if (!directx10) { if (!is(caps1, DDSCAPS_TEXTURE)) { @@ -268,10 +283,12 @@ public class DDSLoader implements AssetLoader { } else { pixelFormat = Image.Format.DXT1; } + dxtOrRgtc = true; break; case PF_DXT3: bpp = 8; pixelFormat = Image.Format.DXT3; + dxtOrRgtc = true; break; case PF_DXT5: bpp = 8; @@ -279,17 +296,24 @@ public class DDSLoader implements AssetLoader { if (swizzle == SWIZZLE_xGxR) { normal = true; } + dxtOrRgtc = true; break; - /* case PF_ATI1: bpp = 4; - pixelFormat = Image.Format.LTC; + pixelFormat = Image.Format.RTC; + dxtOrRgtc = true; break; case PF_ATI2: bpp = 8; - pixelFormat = Image.Format.LATC; + pixelFormat = Image.Format.RGTC; + dxtOrRgtc = true; + break; + case PF_ETC1: + case PF_ETC_: + bpp = 4; + pixelFormat = Image.Format.ETC1; + dxtOrRgtc = false; break; - */ case PF_DX10: compressed = false; directx10 = true; @@ -530,6 +554,30 @@ public class DDSLoader implements AssetLoader { return dataBuffer; } + public ByteBuffer readCompressed2Dor3D(boolean flip, int totalSize) throws IOException { + logger.log(Level.FINEST, "Source image format: {0}", pixelFormat); + + ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize * depth); + + // TODO: add support for flipping ETC1 + + for (int i = 0; i < depth; i++) { + int mipWidth = width; + int mipHeight = height; + for (int mip = 0; mip < mipMapCount; mip++) { + byte[] data = new byte[sizes[mip]]; + in.readFully(data); + buffer.put(data); + + mipWidth = Math.max(mipWidth / 2, 1); + mipHeight = Math.max(mipHeight / 2, 1); + } + } + buffer.rewind(); + + return buffer; + } + /** * Reads a DXT compressed image from the InputStream * @@ -738,8 +786,10 @@ public class DDSLoader implements AssetLoader { ArrayList allMaps = new ArrayList(); if (depth > 1 && !texture3D) { for (int i = 0; i < depth; i++) { - if (compressed) { + if (compressed && dxtOrRgtc) { allMaps.add(readDXT2D(flip, totalSize)); + } else if (compressed) { + allMaps.add(readCompressed2Dor3D(flip, totalSize)); } else if (grayscaleOrAlpha) { allMaps.add(readGrayscale2D(flip, totalSize)); } else { @@ -747,8 +797,10 @@ public class DDSLoader implements AssetLoader { } } } else if (texture3D) { - if (compressed) { + if (compressed && dxtOrRgtc) { allMaps.add(readDXT3D(flip, totalSize)); + } else if (compressed) { + allMaps.add(readCompressed2Dor3D(flip, totalSize)); } else if (grayscaleOrAlpha) { allMaps.add(readGrayscale3D(flip, totalSize)); } else { @@ -756,8 +808,10 @@ public class DDSLoader implements AssetLoader { } } else { - if (compressed) { + if (compressed && dxtOrRgtc) { allMaps.add(readDXT2D(flip, totalSize)); + } else if (compressed) { + allMaps.add(readCompressed2Dor3D(flip, totalSize)); } else if (grayscaleOrAlpha) { allMaps.add(readGrayscale2D(flip, totalSize)); } else { @@ -822,7 +876,7 @@ public class DDSLoader implements AssetLoader { buf.append((char) (value & 0xFF)); buf.append((char) ((value & 0xFF00) >> 8)); buf.append((char) ((value & 0xFF0000) >> 16)); - buf.append((char) ((value & 0xFF00000) >> 24)); + buf.append((char) ((value & 0xFF000000) >> 24)); return buf.toString(); } diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java index 726cf3e19..b7d41b53f 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java @@ -213,20 +213,18 @@ public class DXTFlipper { case DXT5: type = 3; break; - /* - case LATC: + case RGTC: type = 4; break; - case LTC: + case RTC: type = 5; break; - */ default: throw new IllegalArgumentException(); } // DXT1 uses 8 bytes per block, - // DXT3, DXT5, LATC use 16 bytes per block + // DXT3, DXT5, RGTC use 16 bytes per block int bpb = type == 1 || type == 5 ? 8 : 16; ByteBuffer retImg = BufferUtils.createByteBuffer(blocksX * blocksY * bpb);