package com.gebauz.bauzoid.graphics.model;
import java.io.IOException;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.gebauz.bauzoid.app.Consts;
import com.gebauz.bauzoid.file.File;
import com.gebauz.bauzoid.graphics.Graphics;
import com.gebauz.bauzoid.graphics.model.Mesh;
import com.gebauz.bauzoid.graphics.renderstates.RenderStatesConfiguration;
import com.gebauz.bauzoid.graphics.shader.Effect;
import com.gebauz.bauzoid.graphics.shader.ShaderProgram;
import com.gebauz.bauzoid.graphics.shader.ShaderUtil;
import com.gebauz.bauzoid.graphics.shader.Technique;
import com.gebauz.bauzoid.math.BoundingBox;
import com.gebauz.bauzoid.math.Matrix4;
import com.gebauz.bauzoid.math.Vector2;
import com.gebauz.bauzoid.math.Vector3;
import com.gebauz.bauzoid.math.Vector4;
public class ModelUtil
{
public static final String LOG_TAG = Consts.
LOG_TAG +
":ModelUtil";
public static boolean verbose =
false;
private ModelUtil
() {}
public static void log
(String tag,
String msg
)
{
if (verbose
)
Gdx.
app.
log(tag, msg
);
}
public static Model createModelFromFile
(Graphics graphics, FileHandle fileHandle
) throws IOException
{
File file =
new File(fileHandle.
read());
Model model = loadModel
(graphics, file
);
file.
close();
return model
;
}
public static Model loadModel
(Graphics graphics,
File modelFile
) throws IOException
{
log
(LOG_TAG,
"Loading file");
byte byteOrdering = modelFile.
readByte();
log
(LOG_TAG,
"Byte Ordering: " +
(byteOrdering ==
0 ? "Little Endian" :
"Big Endian"));
if (byteOrdering ==
0)
modelFile.
setEndianness(File.
Endianness.
LITTLE_ENDIAN);
else
modelFile.
setEndianness(File.
Endianness.
BIG_ENDIAN);
String header = modelFile.
readString(8);
log
(LOG_TAG,
"Header: " + header
);
if (header.
compareTo("SUXMODEL") !=
0)
throw new IOException("Header incorrect, must be 'SUXMODEL': " + header
);
int version = modelFile.
readInt();
log
(LOG_TAG,
"Version: " + version
);
if (version
!=
0)
throw new IOException("Version incorrect, must be 0: " + version
);
String name = modelFile.
readString();
log
(LOG_TAG,
"Name: " + name
);
int meshCount = modelFile.
readInt();
log
(LOG_TAG,
"Mesh Count: " + meshCount
);
Model model =
new Model
(name
);
Mesh
[] meshes =
new Mesh
[meshCount
];
for (int i =
0; i
< meshCount
; i++
)
{
String meshName = modelFile.
readString();
log
(LOG_TAG,
"[" + i +
"] Mesh Name: " + meshName
);
meshes
[i
] =
new Mesh
(graphics, meshName
);
BoundingBox box =
new BoundingBox
();
box.
min = modelFile.
readVector3();
log
(LOG_TAG,
"[" + i +
"] BB Min: " + box.
min.
x +
", " + box.
min.
y +
", " + box.
min.
z);
box.
max = modelFile.
readVector3();
log
(LOG_TAG,
"[" + i +
"] BB Max: " + box.
max.
x +
", " + box.
max.
y +
", " + box.
max.
z);
meshes
[i
].
setBoundingBox(box
);
boolean dynamic = modelFile.
readBool();
log
(LOG_TAG,
"[" + i +
"] Dynamic: " +
(dynamic
? "yes" :
"no"));
boolean deleteSource = modelFile.
readBool();
log
(LOG_TAG,
"[" + i +
"] DeleteSource: " +
(deleteSource
? "yes" :
"no"));
// geometry
Geometry geometry = loadGeometry
(graphics, modelFile
);
meshes
[i
].
setGeometry(geometry
);
// bones - currently ignored
int boneCount = modelFile.
readInt();
for (int bone =
0; bone
< boneCount
; bone++
)
{
String boneName = modelFile.
readString();
Matrix4 boneTransform = modelFile.
readMatrix4();
Matrix4 boneTransformInverse = modelFile.
readMatrix4();
Vector3 bonePosition = modelFile.
readVector3();
// TODO: Quaternion
Vector4 boneOrientation = modelFile.
readVector4();
Vector3 boneScale = modelFile.
readVector3();
}
for (int bone =
0; bone
< boneCount
; bone++
)
{
int parentBoneIndex = modelFile.
readInt();
}
// groups
int groupCount = modelFile.
readInt();
log
(LOG_TAG,
"Group Count: " + groupCount
);
MeshGroup
[] groups =
new MeshGroup
[groupCount
];
for (int group =
0; group
< groupCount
; group++
)
{
groups
[group
] = loadMeshGroup
(meshes
[i
], modelFile
);
}
meshes
[i
].
setGroups(groups
);
break;
}
model.
setMeshes(meshes
);
log
(LOG_TAG,
"Loading finished");
return model
;
}
public static Geometry loadGeometry
(Graphics graphics,
File modelFile
) throws IOException
{
log
(LOG_TAG,
"=== Geometry Begin ===");
int version = modelFile.
readInt();
log
(LOG_TAG,
"Geometry Version: " + version
);
if (version
!=
2)
throw new IOException("Geometry Version incorrect, must be 2: " + version
);
int primitiveType = modelFile.
readInt();
switch (primitiveType
)
{
case 0:
log
(LOG_TAG,
"PrimitiveType: Triangles");
break;
case 1:
log
(LOG_TAG,
"PrimitiveType: Triangle Strip");
break;
case 2:
log
(LOG_TAG,
"PrimitiveType: Lines");
break;
case 3:
log
(LOG_TAG,
"PrimitiveType: Line Strip");
break;
case 4:
log
(LOG_TAG,
"PrimitiveType: Points");
break;
}
int vertexCount = modelFile.
readInt();
log
(LOG_TAG,
"Vertex Count: " + vertexCount
);
int indexCount = modelFile.
readInt();
log
(LOG_TAG,
"Index Count: " + indexCount
);
int streamCount = modelFile.
readInt();
log
(LOG_TAG,
"Stream Count: " + streamCount
);
VertexStream
[] streams =
new VertexStream
[streamCount
];
for (int i =
0; i
< streamCount
; i++
)
{
log
(LOG_TAG,
"=== BEGIN VertexStream [" + i +
"] ===");
streams
[i
] = loadVertexStream
(graphics, modelFile
);
log
(LOG_TAG,
"=== END VertexStream [" + i +
"] ===");
}
boolean hasIndexStream = modelFile.
readBool();
log
(LOG_TAG,
"Has Index Stream" +
(hasIndexStream
? "Yes" :
"No"));
IndexStream indexStream =
null;
if (hasIndexStream
)
{
log
(LOG_TAG,
"=== BEGIN IndexStream ===");
indexStream = loadIndexStream
(graphics, modelFile
);
log
(LOG_TAG,
"=== END IndexStream ===");
}
Geometry geometry =
new Geometry
(graphics, Geometry.
PrimitiveType.
values()[primitiveType
], vertexCount, indexCount
);
geometry.
setVertexStreams(streams
);
geometry.
setIndexStream(indexStream
);
log
(LOG_TAG,
"=== Geometry End ===");
return geometry
;
}
public static VertexStream loadVertexStream
(Graphics graphics,
File modelFile
) throws IOException
{
VertexStream.
StreamType streamType =
(modelFile.
readInt() ==
1) ? VertexStream.
StreamType.
INDIVIDUAL : VertexStream.
StreamType.
INTERLEAVED;
log
(LOG_TAG,
"Stream Type: " + streamType.
toString());
String streamName = modelFile.
readString();
log
(LOG_TAG,
"Stream Name: " + streamName
);
int streamVertexCount = modelFile.
readInt();
log
(LOG_TAG,
"Stream Vertex Count: " + streamVertexCount
);
int coordsPerElement = modelFile.
readInt();
log
(LOG_TAG,
"Coords per Element: " + coordsPerElement
);
int bytesPerElement = modelFile.
readInt();
log
(LOG_TAG,
"Bytes per Element: " + bytesPerElement
);
// Stream Begin
byte[] streamBuffer = loadStream
(modelFile
);
// Stream End
VertexAttribute
[] attribs =
null;
if (streamType == VertexStream.
StreamType.
INDIVIDUAL)
{
attribs =
new VertexAttribute
[1];
attribs
[0] = loadAttribute
(modelFile
);
}
else if (streamType == VertexStream.
StreamType.
INTERLEAVED)
{
int vertexAttribCount = modelFile.
readInt();
log
(LOG_TAG,
"Vertex Attrib Count: " + vertexAttribCount
);
attribs =
new VertexAttribute
[vertexAttribCount
];
for (int attrib =
0; attrib
< vertexAttribCount
; attrib++
)
{
log
(LOG_TAG,
"=== BEGIN VertexAttribute [" + attrib +
"] ===");
// Vertex Attribute
attribs
[attrib
] = loadAttribute
(modelFile
);
log
(LOG_TAG,
"=== END VertexAttribute [" + attrib +
"] ===");
}
}
else
{
throw new IOException();
}
VertexStream stream =
new VertexStream
(graphics, streamType, streamName, attribs,
false);
stream.
setData(streamBuffer, coordsPerElement, bytesPerElement
);
return stream
;
}
public static VertexAttribute loadAttribute
(File modelFile
) throws IOException
{
int suxType = modelFile.
readInt();
log
(LOG_TAG,
"Attrib Data Type: " + suxType
);
if (!VertexAttribute.
isValidType(suxType
))
throw new IOException("Invalid vertex attribute type: " + suxType
);
int dataFormat = modelFile.
readInt();
log
(LOG_TAG,
"Attrib Data Format: " + dataFormat
);
if (dataFormat
!=
0)
throw new IOException("Unsupported Data Format (must be 0 / Float): " + dataFormat
);
int coordsPerElement = modelFile.
readInt();
log
(LOG_TAG,
"Attrib Coords Per Element: " + coordsPerElement
);
int byteOffset = modelFile.
readInt();
log
(LOG_TAG,
"Attrib Byte Offset: " + byteOffset
);
VertexAttribute attrib =
new VertexAttribute
(VertexAttribute.
toInternalType(suxType
), coordsPerElement, byteOffset
);
return attrib
;
}
public static IndexStream loadIndexStream
(Graphics graphics,
File modelFile
) throws IOException
{
int indexCount = modelFile.
readInt();
log
(LOG_TAG,
"Index Count: " + indexCount
);
int dataFormat = modelFile.
readInt();
log
(LOG_TAG,
"Data Format: " + dataFormat
);
if (dataFormat
!=
1)
throw new IOException("Data Format unsupported, must be 1 (Int16): " + dataFormat
);
// Stream Begin
byte[] streamBuffer = loadStream
(modelFile
);
// Stream End
IndexStream indexStream =
new IndexStream
(graphics
);
indexStream.
setData(streamBuffer
);
return indexStream
;
}
public static byte[] loadStream
(File modelFile
) throws IOException
{
// Store endianness because the stream might change it
File.
Endianness endianness = modelFile.
getEndianness();
byte byteOrdering = modelFile.
readByte();
log
(LOG_TAG,
"Stream Byte Ordering: " +
(byteOrdering ==
0 ? "Little Endian" :
"Big Endian"));
if (byteOrdering ==
0)
modelFile.
setEndianness(File.
Endianness.
LITTLE_ENDIAN);
else
modelFile.
setEndianness(File.
Endianness.
BIG_ENDIAN);
byte charFormat = modelFile.
readByte();
log
(LOG_TAG,
"Stream CharFormat: " +
(charFormat ==
0 ? "ASCII" :
"UTF16"));
int byteLength = modelFile.
readInt();
log
(LOG_TAG,
"Stream Byte Length: " + byteLength
);
byte[] buffer =
new byte[byteLength
];
modelFile.
readFully(buffer,
0, byteLength
);
// Restore endianness
modelFile.
setEndianness(endianness
);
return buffer
;
}
public static MeshGroup loadMeshGroup
(Mesh mesh,
File modelFile
) throws IOException
{
log
(LOG_TAG,
"=== Mesh.Group Begin ===");
String name = modelFile.
readString();
log
(LOG_TAG,
"Name: " + name
);
String effectFile = modelFile.
readString();
log
(LOG_TAG,
"-- Effect file: " + effectFile
);
MeshGroup group =
new MeshGroup
(mesh, name
);
// TODO: do actual parsing of external technique
Effect effect =
new Effect
(mesh.
getGraphics(), effectFile
);
ShaderProgram shader = ShaderUtil.
createShaderFromFile(mesh.
getGraphics(), Gdx.
files.
internal("data/shaders/default.vs"), Gdx.
files.
internal("data/shaders/default.fs"));
Technique
[] effectTechs =
new Technique
[1];
effectTechs
[0] =
new Technique
(effect,
"diffuse", shader
);
effect.
setTechniques(effectTechs
);
int techniqueCount = modelFile.
readInt();
log
(LOG_TAG,
"-- Technique Count: " + techniqueCount
);
MeshGroup.
TechniqueRef[] techRefs =
new MeshGroup.
TechniqueRef[techniqueCount
];
for (int i =
0; i
< techniqueCount
; i++
)
{
String staticTechniqueName = modelFile.
readString();
log
(LOG_TAG,
"-- Static Technique: " + staticTechniqueName
);
String skinnedTechniqueName = modelFile.
readString();
log
(LOG_TAG,
"-- Skinned Technique: " + skinnedTechniqueName
);
// TODO: read technique from parsed effect file
techRefs
[i
] = group.
new TechniqueRef
(effectTechs
[0], effectTechs
[0]);
}
group.
setTechniqueRefs(techRefs
);
String bonesParameterName = modelFile.
readString();
int firstIndex = modelFile.
readInt();
log
(LOG_TAG,
"First Index: " + firstIndex
);
int lastIndex = modelFile.
readInt();
log
(LOG_TAG,
"Last Index: " + lastIndex
);
int firstVertex = modelFile.
readInt();
log
(LOG_TAG,
"First Vertex: " + firstVertex
);
int lastVertex = modelFile.
readInt();
log
(LOG_TAG,
"Last Vertex: " + lastVertex
);
group.
setIndices(firstIndex, lastIndex, firstVertex, lastVertex
);
// TODO: make render state class
RenderStatesConfiguration rs = loadRenderState
(mesh.
getGraphics(), modelFile
);
group.
setRenderStates(rs
);
int variablesCount = modelFile.
readInt();
log
(LOG_TAG,
"== Variable Count: " + variablesCount
);
Effect.
Variable[] vars =
new Effect.
Variable[variablesCount
];
for (int i =
0; i
< variablesCount
; i++
)
{
String variableName = modelFile.
readString();
log
(LOG_TAG,
"-- [" + i +
"] Variable Name: " + variableName
);
int variableType = modelFile.
readInt();
switch (variableType
)
{
case Effect.
Variable.
TYPE_TEXTURE2D:
// Texture2D
case Effect.
Variable.
TYPE_TEXTURE3D:
// Texture3D
case Effect.
Variable.
TYPE_TEXTURECUBE:
// TextureCube
{
String texFile = modelFile.
readString();
log
(LOG_TAG,
"-- [" + i +
"] Variable Type: Texture (" + texFile +
")");
Texture texture =
new Texture
(Gdx.
files.
internal("data/" + texFile
));
vars
[i
] = effect.
new VariableTexture
(variableName, texture
);
break;
}
case Effect.
Variable.
TYPE_BOOL:
// Bool
{
boolean b = modelFile.
readBool();
log
(LOG_TAG,
"-- [" + i +
"] Variable Type: bool (" +
(b
? "true" :
"false") +
")");
vars
[i
] = effect.
new VariableBool
(variableName, b
);
break;
}
case Effect.
Variable.
TYPE_INT:
// Int
{
int n = modelFile.
readInt();
log
(LOG_TAG,
"-- [" + i +
"] Variable Type: int (" + n +
")");
vars
[i
] = effect.
new VariableInt
(variableName, n
);
break;
}
case Effect.
Variable.
TYPE_FLOAT:
// Float
{
float f = modelFile.
readFloat();
log
(LOG_TAG,
"-- [" + i +
"] Variable Type: float (" + f +
")");
vars
[i
] = effect.
new VariableFloat
(variableName, f
);
break;
}
case Effect.
Variable.
TYPE_VECTOR2:
// Vector2
{
Vector2 v2 = modelFile.
readVector2();
log
(LOG_TAG,
"-- [" + i +
"] Variable Type: Vector2 (" + v2.
x +
", " + v2.
y +
")");
vars
[i
] = effect.
new VariableVector2
(variableName, v2
);
break;
}
case Effect.
Variable.
TYPE_VECTOR3:
// Vector3
{
Vector3 v3 = modelFile.
readVector3();
log
(LOG_TAG,
"-- [" + i +
"] Variable Type: Vector3 (" + v3.
x +
", " + v3.
y +
", " + v3.
z +
")");
vars
[i
] = effect.
new VariableVector3
(variableName, v3
);
break;
}
case Effect.
Variable.
TYPE_VECTOR4:
// Vector4
{
Vector4 v4 = modelFile.
readVector4();
log
(LOG_TAG,
"-- [" + i +
"] Variable Type: Vector3 (" + v4.
x +
", " + v4.
y +
", " + v4.
z +
", " + v4.
w +
")");
vars
[i
] = effect.
new VariableVector4
(variableName, v4
);
break;
}
}
}
effect.
setVariables(vars
);
effect.
connectVariables();
group.
setEffect(effect
);
// Bone Reindexing for tighter usage of uniforms
int boneIndexCount = modelFile.
readInt();
for (int i =
0; i
< boneIndexCount
; i++
)
{
int originalIndex = modelFile.
readInt();
int newIndex = modelFile.
readInt();
}
log
(LOG_TAG,
"=== Mesh.Group End===");
return group
;
}
@
SuppressWarnings("unused")
public static RenderStatesConfiguration loadRenderState
(Graphics graphics,
File modelFile
) throws IOException
{
RenderStatesConfiguration rs =
new RenderStatesConfiguration
(graphics
);
// Alpha test not supported
boolean alphaTestEnabled = modelFile.
readBool();
float alphaTestThreshold = modelFile.
readFloat();
int alphaTestComparisonFunc = modelFile.
readInt();
boolean blendingEnabled = modelFile.
readBool();
int blendingMode = modelFile.
readInt();
rs.
setBlendingEnabled(blendingEnabled
);
rs.
setBlendingMode(blendingMode
);
boolean cullingEnabled = modelFile.
readBool();
int cullingWinding = modelFile.
readInt();
rs.
setCullingEnabled(cullingEnabled
);
rs.
setCullingWinding(cullingWinding
);
boolean depthTestEnabled = modelFile.
readBool();
boolean depthBufferWriteEnabled = modelFile.
readBool();
int depthTestComparisonFunc = modelFile.
readInt();
rs.
setDepthTestEnabled(depthTestEnabled
);
rs.
setDepthBufferWriteEnabled(depthBufferWriteEnabled
);
rs.
setDepthTestComparisonFunc(depthTestComparisonFunc
);
// Polygon Mode not supported
int polygonMode = modelFile.
readInt();
return rs
;
}
}