Merge branch 'renderer-rgtc' into experimental
This commit is contained in:
commit
f9500f955f
@ -349,7 +349,12 @@ public enum Caps {
|
|||||||
/**
|
/**
|
||||||
* GPU can provide and accept binary shaders.
|
* 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
|
* Returns true if given the renderer capabilities, the texture
|
||||||
|
@ -44,6 +44,8 @@ import java.nio.IntBuffer;
|
|||||||
public interface GLExt {
|
public interface GLExt {
|
||||||
|
|
||||||
public static final int GL_ALREADY_SIGNALED = 0x911A;
|
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_RGB8_ETC2 = 0x9274;
|
||||||
public static final int GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
|
public static final int GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
|
||||||
public static final int GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
|
public static final int GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
|
||||||
|
@ -233,6 +233,11 @@ public final class GLImageFormats {
|
|||||||
formatComp(formatToGL, Format.ETC1, GLExt.GL_ETC1_RGB8_OES, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
|
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;
|
return formatToGL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,6 +352,10 @@ public final class GLRenderer implements Renderer {
|
|||||||
caps.add(Caps.TextureCompressionETC1);
|
caps.add(Caps.TextureCompressionETC1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasExtension("GL_ARB_texture_compression_rgtc")) {
|
||||||
|
caps.add(Caps.TextureCompressionRGTC);
|
||||||
|
}
|
||||||
|
|
||||||
// == end texture format extensions ==
|
// == end texture format extensions ==
|
||||||
|
|
||||||
if (hasExtension("GL_ARB_vertex_array_object") || caps.contains(Caps.OpenGL30)) {
|
if (hasExtension("GL_ARB_vertex_array_object") || caps.contains(Caps.OpenGL30)) {
|
||||||
|
@ -299,7 +299,21 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
|
|||||||
*
|
*
|
||||||
* Requires {@link Caps#TextureCompressionETC1}.
|
* 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 int bpp;
|
||||||
private boolean isDepth;
|
private boolean isDepth;
|
||||||
|
@ -85,6 +85,8 @@ public class DDSLoader implements AssetLoader {
|
|||||||
private static final int PF_DXT1 = 0x31545844;
|
private static final int PF_DXT1 = 0x31545844;
|
||||||
private static final int PF_DXT3 = 0x33545844;
|
private static final int PF_DXT3 = 0x33545844;
|
||||||
private static final int PF_DXT5 = 0x35545844;
|
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_ATI1 = 0x31495441;
|
||||||
private static final int PF_ATI2 = 0x32495441; // 0x41544932;
|
private static final int PF_ATI2 = 0x32495441; // 0x41544932;
|
||||||
private static final int PF_DX10 = 0x30315844; // a DX10 format
|
private static final int PF_DX10 = 0x30315844; // a DX10 format
|
||||||
@ -94,6 +96,9 @@ public class DDSLoader implements AssetLoader {
|
|||||||
DX10DIM_TEXTURE3D = 0x4;
|
DX10DIM_TEXTURE3D = 0x4;
|
||||||
private static final int DX10MISC_GENERATE_MIPS = 0x1,
|
private static final int DX10MISC_GENERATE_MIPS = 0x1,
|
||||||
DX10MISC_TEXTURECUBE = 0x4;
|
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 static final double LOG2 = Math.log(2);
|
||||||
private int width;
|
private int width;
|
||||||
private int height;
|
private int height;
|
||||||
@ -105,9 +110,11 @@ public class DDSLoader implements AssetLoader {
|
|||||||
private int caps2;
|
private int caps2;
|
||||||
private boolean directx10;
|
private boolean directx10;
|
||||||
private boolean compressed;
|
private boolean compressed;
|
||||||
|
private boolean dxtOrRgtc;
|
||||||
private boolean texture3D;
|
private boolean texture3D;
|
||||||
private boolean grayscaleOrAlpha;
|
private boolean grayscaleOrAlpha;
|
||||||
private boolean normal;
|
private boolean normal;
|
||||||
|
private ColorSpace colorSpace;
|
||||||
private Format pixelFormat;
|
private Format pixelFormat;
|
||||||
private int bpp;
|
private int bpp;
|
||||||
private int[] sizes;
|
private int[] sizes;
|
||||||
@ -133,7 +140,8 @@ public class DDSLoader implements AssetLoader {
|
|||||||
((TextureKey) info.getKey()).setTextureTypeHint(Texture.Type.CubeMap);
|
((TextureKey) info.getKey()).setTextureTypeHint(Texture.Type.CubeMap);
|
||||||
}
|
}
|
||||||
ArrayList<ByteBuffer> data = readData(((TextureKey) info.getKey()).isFlipY());
|
ArrayList<ByteBuffer> 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 {
|
} finally {
|
||||||
if (stream != null){
|
if (stream != null){
|
||||||
stream.close();
|
stream.close();
|
||||||
@ -145,18 +153,24 @@ public class DDSLoader implements AssetLoader {
|
|||||||
in = new LittleEndien(stream);
|
in = new LittleEndien(stream);
|
||||||
loadHeader();
|
loadHeader();
|
||||||
ArrayList<ByteBuffer> data = readData(false);
|
ArrayList<ByteBuffer> 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 {
|
private void loadDX10Header() throws IOException {
|
||||||
int dxgiFormat = in.readInt();
|
int dxgiFormat = in.readInt();
|
||||||
|
|
||||||
if (dxgiFormat == 0) {
|
if (dxgiFormat == 0) {
|
||||||
pixelFormat = Format.ETC1;
|
pixelFormat = Format.ETC1;
|
||||||
|
compressed = true;
|
||||||
bpp = 4;
|
bpp = 4;
|
||||||
} else {
|
} else {
|
||||||
|
pixelFormat = DXGIFormat.getJmeFormat(dxgiFormat);
|
||||||
|
if (pixelFormat == null) {
|
||||||
throw new IOException("Unsupported DX10 format: " + dxgiFormat);
|
throw new IOException("Unsupported DX10 format: " + dxgiFormat);
|
||||||
}
|
}
|
||||||
compressed = true;
|
bpp = pixelFormat.getBitsPerPixel();
|
||||||
|
compressed = pixelFormat.isCompressed();
|
||||||
|
}
|
||||||
|
|
||||||
int resDim = in.readInt();
|
int resDim = in.readInt();
|
||||||
if (resDim == DX10DIM_TEXTURE3D) {
|
if (resDim == DX10DIM_TEXTURE3D) {
|
||||||
@ -201,6 +215,7 @@ public class DDSLoader implements AssetLoader {
|
|||||||
caps2 = in.readInt();
|
caps2 = in.readInt();
|
||||||
in.skipBytes(12);
|
in.skipBytes(12);
|
||||||
texture3D = false;
|
texture3D = false;
|
||||||
|
colorSpace = ColorSpace.sRGB;
|
||||||
|
|
||||||
if (!directx10) {
|
if (!directx10) {
|
||||||
if (!is(caps1, DDSCAPS_TEXTURE)) {
|
if (!is(caps1, DDSCAPS_TEXTURE)) {
|
||||||
@ -268,10 +283,12 @@ public class DDSLoader implements AssetLoader {
|
|||||||
} else {
|
} else {
|
||||||
pixelFormat = Image.Format.DXT1;
|
pixelFormat = Image.Format.DXT1;
|
||||||
}
|
}
|
||||||
|
dxtOrRgtc = true;
|
||||||
break;
|
break;
|
||||||
case PF_DXT3:
|
case PF_DXT3:
|
||||||
bpp = 8;
|
bpp = 8;
|
||||||
pixelFormat = Image.Format.DXT3;
|
pixelFormat = Image.Format.DXT3;
|
||||||
|
dxtOrRgtc = true;
|
||||||
break;
|
break;
|
||||||
case PF_DXT5:
|
case PF_DXT5:
|
||||||
bpp = 8;
|
bpp = 8;
|
||||||
@ -279,17 +296,24 @@ public class DDSLoader implements AssetLoader {
|
|||||||
if (swizzle == SWIZZLE_xGxR) {
|
if (swizzle == SWIZZLE_xGxR) {
|
||||||
normal = true;
|
normal = true;
|
||||||
}
|
}
|
||||||
|
dxtOrRgtc = true;
|
||||||
break;
|
break;
|
||||||
/*
|
|
||||||
case PF_ATI1:
|
case PF_ATI1:
|
||||||
bpp = 4;
|
bpp = 4;
|
||||||
pixelFormat = Image.Format.LTC;
|
pixelFormat = Image.Format.RTC;
|
||||||
|
dxtOrRgtc = true;
|
||||||
break;
|
break;
|
||||||
case PF_ATI2:
|
case PF_ATI2:
|
||||||
bpp = 8;
|
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;
|
break;
|
||||||
*/
|
|
||||||
case PF_DX10:
|
case PF_DX10:
|
||||||
compressed = false;
|
compressed = false;
|
||||||
directx10 = true;
|
directx10 = true;
|
||||||
@ -530,6 +554,30 @@ public class DDSLoader implements AssetLoader {
|
|||||||
return dataBuffer;
|
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
|
* Reads a DXT compressed image from the InputStream
|
||||||
*
|
*
|
||||||
@ -738,8 +786,10 @@ public class DDSLoader implements AssetLoader {
|
|||||||
ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>();
|
ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>();
|
||||||
if (depth > 1 && !texture3D) {
|
if (depth > 1 && !texture3D) {
|
||||||
for (int i = 0; i < depth; i++) {
|
for (int i = 0; i < depth; i++) {
|
||||||
if (compressed) {
|
if (compressed && dxtOrRgtc) {
|
||||||
allMaps.add(readDXT2D(flip, totalSize));
|
allMaps.add(readDXT2D(flip, totalSize));
|
||||||
|
} else if (compressed) {
|
||||||
|
allMaps.add(readCompressed2Dor3D(flip, totalSize));
|
||||||
} else if (grayscaleOrAlpha) {
|
} else if (grayscaleOrAlpha) {
|
||||||
allMaps.add(readGrayscale2D(flip, totalSize));
|
allMaps.add(readGrayscale2D(flip, totalSize));
|
||||||
} else {
|
} else {
|
||||||
@ -747,8 +797,10 @@ public class DDSLoader implements AssetLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (texture3D) {
|
} else if (texture3D) {
|
||||||
if (compressed) {
|
if (compressed && dxtOrRgtc) {
|
||||||
allMaps.add(readDXT3D(flip, totalSize));
|
allMaps.add(readDXT3D(flip, totalSize));
|
||||||
|
} else if (compressed) {
|
||||||
|
allMaps.add(readCompressed2Dor3D(flip, totalSize));
|
||||||
} else if (grayscaleOrAlpha) {
|
} else if (grayscaleOrAlpha) {
|
||||||
allMaps.add(readGrayscale3D(flip, totalSize));
|
allMaps.add(readGrayscale3D(flip, totalSize));
|
||||||
} else {
|
} else {
|
||||||
@ -756,8 +808,10 @@ public class DDSLoader implements AssetLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (compressed) {
|
if (compressed && dxtOrRgtc) {
|
||||||
allMaps.add(readDXT2D(flip, totalSize));
|
allMaps.add(readDXT2D(flip, totalSize));
|
||||||
|
} else if (compressed) {
|
||||||
|
allMaps.add(readCompressed2Dor3D(flip, totalSize));
|
||||||
} else if (grayscaleOrAlpha) {
|
} else if (grayscaleOrAlpha) {
|
||||||
allMaps.add(readGrayscale2D(flip, totalSize));
|
allMaps.add(readGrayscale2D(flip, totalSize));
|
||||||
} else {
|
} else {
|
||||||
@ -822,7 +876,7 @@ public class DDSLoader implements AssetLoader {
|
|||||||
buf.append((char) (value & 0xFF));
|
buf.append((char) (value & 0xFF));
|
||||||
buf.append((char) ((value & 0xFF00) >> 8));
|
buf.append((char) ((value & 0xFF00) >> 8));
|
||||||
buf.append((char) ((value & 0xFF0000) >> 16));
|
buf.append((char) ((value & 0xFF0000) >> 16));
|
||||||
buf.append((char) ((value & 0xFF00000) >> 24));
|
buf.append((char) ((value & 0xFF000000) >> 24));
|
||||||
|
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
@ -213,20 +213,18 @@ public class DXTFlipper {
|
|||||||
case DXT5:
|
case DXT5:
|
||||||
type = 3;
|
type = 3;
|
||||||
break;
|
break;
|
||||||
/*
|
case RGTC:
|
||||||
case LATC:
|
|
||||||
type = 4;
|
type = 4;
|
||||||
break;
|
break;
|
||||||
case LTC:
|
case RTC:
|
||||||
type = 5;
|
type = 5;
|
||||||
break;
|
break;
|
||||||
*/
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// DXT1 uses 8 bytes per block,
|
// 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;
|
int bpb = type == 1 || type == 5 ? 8 : 16;
|
||||||
|
|
||||||
ByteBuffer retImg = BufferUtils.createByteBuffer(blocksX * blocksY * bpb);
|
ByteBuffer retImg = BufferUtils.createByteBuffer(blocksX * blocksY * bpb);
|
||||||
|
@ -31,6 +31,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.jme3.scene.plugins.fbx.file;
|
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.OutputStream;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
@ -88,6 +91,28 @@ public final class FbxDump {
|
|||||||
dumpFile(file, System.out);
|
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.
|
* Dump FBX to the given output stream.
|
||||||
*
|
*
|
||||||
|
@ -147,7 +147,8 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
|
|||||||
public void connectObject(FbxObject object) {
|
public void connectObject(FbxObject object) {
|
||||||
if (object instanceof FbxSkinDeformer) {
|
if (object instanceof FbxSkinDeformer) {
|
||||||
if (skinDeformer != null) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
skinDeformer = (FbxSkinDeformer) object;
|
skinDeformer = (FbxSkinDeformer) object;
|
||||||
@ -237,7 +238,7 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
|
|||||||
|
|
||||||
if (jmeMeshes.size() == 0) {
|
if (jmeMeshes.size() == 0) {
|
||||||
// When will this actually happen? Not sure.
|
// 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
|
// IMPORTANT: If we have a -1 entry, those are triangles
|
||||||
@ -245,7 +246,8 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
|
|||||||
// It makes sense only if the mesh uses a single material!
|
// It makes sense only if the mesh uses a single material!
|
||||||
if (jmeMeshes.containsKey(-1) && jmeMeshes.size() > 1) {
|
if (jmeMeshes.containsKey(-1) && jmeMeshes.size() > 1) {
|
||||||
logger.log(Level.WARNING, "Mesh has polygons with no material "
|
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;
|
return jmeMeshes;
|
||||||
|
@ -293,7 +293,8 @@ public class FbxNode extends FbxObject<Spatial> {
|
|||||||
float z = ((Double) e2.properties.get(6)).floatValue();
|
float z = ((Double) e2.properties.get(6)).floatValue();
|
||||||
userDataValue = new Vector3f(x, y, z);
|
userDataValue = new Vector3f(x, y, z);
|
||||||
} else {
|
} else {
|
||||||
logger.log(Level.WARNING, "Unsupported user data type: {0}. Ignoring.", userDataType);
|
logger.log(Level.WARNING, "Unsupported user data type: {0}. "
|
||||||
|
+ "Ignoring.", userDataType);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,6 +330,9 @@ public class FbxNode extends FbxObject<Spatial> {
|
|||||||
// Material index does not exist. Create default material.
|
// Material index does not exist. Create default material.
|
||||||
jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||||
jmeMat.setReceivesShadows(true);
|
jmeMat.setReceivesShadows(true);
|
||||||
|
logger.log(Level.WARNING, "Material index {0} is undefined in: {1}. "
|
||||||
|
+ "Will use default material.",
|
||||||
|
new Object[]{materialIndex, this});
|
||||||
} else {
|
} else {
|
||||||
FbxMaterial fbxMat = materials.get(materialIndex);
|
FbxMaterial fbxMat = materials.get(materialIndex);
|
||||||
jmeMat = fbxMat.getJmeObject();
|
jmeMat = fbxMat.getJmeObject();
|
||||||
@ -400,7 +404,8 @@ public class FbxNode extends FbxObject<Spatial> {
|
|||||||
|
|
||||||
if (jmeMeshes == null || jmeMeshes.size() == 0) {
|
if (jmeMeshes == null || jmeMeshes.size() == 0) {
|
||||||
// No meshes found on FBXMesh (??)
|
// 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");
|
spatial = new Node(getName() + "-node");
|
||||||
} else {
|
} else {
|
||||||
// Multiple jME3 geometries required for a single FBXMesh.
|
// Multiple jME3 geometries required for a single FBXMesh.
|
||||||
@ -437,7 +442,7 @@ public class FbxNode extends FbxObject<Spatial> {
|
|||||||
if (!FastMath.approximateEquals(localScale.x, localScale.y) ||
|
if (!FastMath.approximateEquals(localScale.x, localScale.y) ||
|
||||||
!FastMath.approximateEquals(localScale.x, localScale.z)) {
|
!FastMath.approximateEquals(localScale.x, localScale.z)) {
|
||||||
logger.log(Level.WARNING, "Non-uniform scale detected on parent node. " +
|
logger.log(Level.WARNING, "Non-uniform scale detected on parent node. " +
|
||||||
"The model may appear distorted.");
|
"The model {1} may appear distorted.", this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ import com.jme3.scene.VertexBuffer;
|
|||||||
import com.jme3.scene.mesh.IndexBuffer;
|
import com.jme3.scene.mesh.IndexBuffer;
|
||||||
import com.jme3.scene.mesh.IndexIntBuffer;
|
import com.jme3.scene.mesh.IndexIntBuffer;
|
||||||
import com.jme3.scene.mesh.IndexShortBuffer;
|
import com.jme3.scene.mesh.IndexShortBuffer;
|
||||||
|
import com.jme3.scene.plugins.triangulator.EarClippingTriangulator;
|
||||||
import com.jme3.util.BufferUtils;
|
import com.jme3.util.BufferUtils;
|
||||||
import com.jme3.util.IntMap;
|
import com.jme3.util.IntMap;
|
||||||
import java.nio.ByteBuffer;
|
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.
|
* Convert mesh from quads / triangles to triangles only.
|
||||||
*/
|
*/
|
||||||
public static void triangulate(IrMesh mesh) {
|
public static void triangulate(IrMesh mesh) {
|
||||||
List<IrPolygon> newPolygons = new ArrayList<IrPolygon>(mesh.polygons.length);
|
List<IrPolygon> newPolygons = new ArrayList<IrPolygon>(mesh.polygons.length);
|
||||||
|
EarClippingTriangulator triangulator = new EarClippingTriangulator();
|
||||||
for (IrPolygon inputPoly : mesh.polygons) {
|
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);
|
IrPolygon[] tris = quadToTri(inputPoly);
|
||||||
newPolygons.add(tris[0]);
|
newPolygons.add(tris[0]);
|
||||||
newPolygons.add(tris[1]);
|
newPolygons.add(tris[1]);
|
||||||
} else if (inputPoly.vertices.length == 3) {
|
|
||||||
newPolygons.add(inputPoly);
|
|
||||||
} else {
|
} else {
|
||||||
// N-gon. We have to ignore it..
|
// N-gon
|
||||||
logger.log(Level.WARNING, "N-gon encountered, ignoring. "
|
dumpPoly(inputPoly);
|
||||||
+ "The mesh may not appear correctly. "
|
IrPolygon[] tris = triangulator.triangulate(inputPoly);
|
||||||
+ "Triangulate your model prior to export.");
|
for (IrPolygon tri : tris) {
|
||||||
|
newPolygons.add(tri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mesh.polygons = new IrPolygon[newPolygons.size()];
|
mesh.polygons = new IrPolygon[newPolygons.size()];
|
||||||
@ -373,12 +391,11 @@ public final class IrUtils {
|
|||||||
boneIndices.put((byte)0);
|
boneIndices.put((byte)0);
|
||||||
boneWeights.put(0f);
|
boneWeights.put(0f);
|
||||||
}
|
}
|
||||||
|
maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length);
|
||||||
} else {
|
} else {
|
||||||
boneIndices.putInt(0);
|
boneIndices.putInt(0);
|
||||||
boneWeights.put(0f).put(0f).put(0f).put(0f);
|
boneWeights.put(0f).put(0f).put(0f).put(0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
* <ul>
|
||||||
|
* <li>http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf</li>
|
||||||
|
* <li>http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/algorithm2.html</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class EarClippingTriangulator {
|
||||||
|
|
||||||
|
private static enum VertexType {
|
||||||
|
Convex,
|
||||||
|
Reflex,
|
||||||
|
Ear;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ArrayList<Integer> indices = new ArrayList<Integer>();
|
||||||
|
private final ArrayList<VertexType> types = new ArrayList<VertexType>();
|
||||||
|
private final ArrayList<Vector2f> positions = new ArrayList<Vector2f>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user