/*
* Copyright (C) 2014 James Lawrence.
*
* This file is part of LibLab.
*
* LibLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sqrt.liblab.codec;
import com.sqrt.liblab.entry.model.*;
import com.sqrt.liblab.io.DataSource;
import com.sqrt.liblab.threed.Vector2f;
import com.sqrt.liblab.threed.Vector3f;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class ModelCodec extends EntryCodec<GrimModel> {
public GrimModel _read(DataSource source) throws IOException {
if (source.getIntLE() != (('M' << 24) | ('O' << 16) | ('D' << 8) | 'L'))
throw new IOException("Invalid model format (text format W.I.P)"); // Todo
GrimModel mod = loadBinary(source);
//exportWavefront(new File(source.getName() + ".obj"), mod);
return mod;
}
private GrimModel loadBinary(DataSource source) throws IOException {
int numMaterials = source.getIntLE();
Material[] materials = new Material[numMaterials];
for (int i = 0; i < numMaterials; i++) {
String name = source.getString(32);
materials[i] = (Material) source.container.container.findByName(name);
if(materials[i] == null) {
System.out.println("No material named: " + name);
}
}
GrimModel model = new GrimModel(source.container, source.getName());
String name = source.getString(32);
source.skip(4);
int numGeosets = source.getIntLE();
for (int i = 0; i < numGeosets; i++)
model.geosets.add(loadGeosetBinary(source, materials));
source.skip(4);
int numNodes = source.getIntLE();
for(int i = 0; i < numNodes; i++)
model.hierarchy.add(loadModelNodeBinary(source, model.geosets.get(0)));
for(ModelNode node: model.hierarchy) {
if(node.childIdx >= 0)
node.child = model.hierarchy.get(node.childIdx);
if(node.parentIdx >= 0)
node.parent = model.hierarchy.get(node.parentIdx);
if(node.siblingIdx >= 0)
node.sibling = model.hierarchy.get(node.siblingIdx);
}
model.radius = source.getFloatLE();
source.skip(36);
model.off = source.getVector3f();
return model;
}
private ModelNode loadModelNodeBinary(DataSource source, Geoset geoset) throws IOException {
ModelNode node = new ModelNode();
node.name = source.getString(64);
node.flags = source.getIntLE();
source.skip(4);
node.type = source.getIntLE();
int meshNum = source.getIntLE();
node.mesh = meshNum < 0? null: geoset.meshes.get(meshNum);
node.depth = source.getIntLE();
boolean hasParent = source.getBoolean();
int numChildren = source.getIntLE();
boolean hasChild = source.getBoolean();
boolean hasSibling = source.getBoolean();
node.pivot = source.getVector3f();
node.pos = source.getVector3f();
node.pitch = source.getAngle();
node.yaw = source.getAngle();
node.roll = source.getAngle();
source.skip(48);
ModelNode parent, sibling, child;
if(hasParent)
node.parentIdx = source.getIntLE();
if(hasChild)
node.childIdx = source.getIntLE();
if(hasSibling)
node.siblingIdx = source.getIntLE();
return node;
}
private Geoset loadGeosetBinary(DataSource source, Material[] materials) throws IOException {
Geoset g = new Geoset();
int numMeshes = source.getIntLE();
for (int i = 0; i < numMeshes; i++)
g.meshes.add(loadMeshBinary(source, materials));
return g;
}
private Mesh loadMeshBinary(DataSource source, Material[] materials) throws IOException {
Mesh m = new Mesh();
m.name = source.getString(32);
source.skip(4);
m.geomMode = source.getIntLE();
m.lightMode = source.getIntLE();
m.texMode = source.getIntLE();
int numVertices = source.getIntLE();
int numTextureVerts = source.getIntLE();
int numFaces = source.getIntLE();
Vector3f[] vertices = new Vector3f[numVertices];
float[] verticesI = new float[numVertices];
Vector3f[] normals = new Vector3f[numVertices];
Vector2f[] textureVerts = new Vector2f[numTextureVerts];
for (int i = 0; i < numVertices; i++)
vertices[i] =source.getVector3f();
for (int i = 0; i < numTextureVerts; i++)
textureVerts[i] = source.getVector2f();
for (int i = 0; i < numVertices; i++)
verticesI[i] = source.getFloatLE();
source.skip(numVertices * 4);
int[][] normalTemp = new int[numFaces][];
for (int i = 0; i < numFaces; i++)
m.faces.add(loadMeshFaceBinary(source, materials, vertices, normalTemp, i, textureVerts));
for (int i = 0; i < numVertices; i++)
normals[i] = source.getVector3f();
for (int i = 0; i < numFaces; i++) {
MeshFace mf = m.faces.get(i);
for (int index : normalTemp[i])
mf.normals.add(normals[index]);
}
m.shadow = source.getIntLE();
source.skip(4);
m.radius = source.getFloatLE();
source.skip(24);
return m;
}
private MeshFace loadMeshFaceBinary(DataSource source, Material[] materials,
Vector3f[] vertexTable, int[][] normalTemp, int normalOff,
Vector2f[] texVertexTable) throws IOException {
source.skip(4);
int type = source.getIntLE();
int geo = source.getIntLE();
int light = source.getIntLE();
int tex = source.getIntLE();
int numVertices = source.getIntLE();
normalTemp[normalOff] = new int[numVertices];
source.skip(4);
boolean hasTexture = source.getBoolean();
boolean hasMaterial = source.getBoolean();
source.skip(12);
float extraLight = source.getFloatLE();
source.skip(12);
Vector3f normal = source.getVector3f();
MeshFace mf = new MeshFace();
for (int i = 0; i < numVertices; i++) {
int vid = source.getIntLE();
mf.vertices.add(vertexTable[vid]);
normalTemp[normalOff][i] = vid;
}
if (hasTexture) {
for (int i = 0; i < numVertices; i++)
mf.uv.add(texVertexTable[source.getIntLE()]);
}
if (hasMaterial) {
int matIdx = source.getIntLE();
Material mat = mf.material = materials[matIdx];
if(tex >= 0)
mf.texture = mat.textures.get(tex);
}
mf.extraLight = extraLight;
mf.geo = geo;
mf.light = light;
mf.normal = normal;
mf.type = type;
return mf;
}
public void exportWavefront(File f, GrimModel model, ColorMap colorMap) throws IOException {
// Todo: export MTL
String name = f.getName();
int idx = name.lastIndexOf('.');
if(idx != -1)
name = name.substring(0, idx);
File mtlF = new File(f.getParentFile(), name + ".mtl");
Map<Material, String> materialNames = writeMtl(mtlF, model, colorMap);
PrintStream ps = new PrintStream(f);
_vertexOff = 1;
ps.println("o " + name);
ps.println("mtllib " + name + ".mtl");
printNode(model, model.hierarchy.get(0), new Vector3f(0, 0, 0), ps, materialNames);
ps.close();
}
private Map<Material,String> writeMtl(File dest, GrimModel model, ColorMap map) throws IOException {
PrintStream ps = new PrintStream(dest);
Set<Material> materials = new HashSet<>();
Map<Material, String> materialNames = new HashMap<>();
// Find all the materials...
for(ModelNode node: model.hierarchy) {
if(node.mesh == null)
continue;
for(MeshFace face: node.mesh.faces) {
if(face.material == null)
continue;
materials.add(face.material);
}
}
// Write all the materials & textures...
for(Material mat: materials) {
String name = mat.getName();
int idx = name.lastIndexOf('.');
if(idx != -1)
name = name.substring(0, idx);
materialNames.put(mat, name);
ps.println("newmtl " + name);
ps.println("Kd 1 1 1");
ps.println("Ka 1 1 1");
ps.println("illum 1");
Texture t = mat.textures.get(0);
BufferedImage bi = t.render(map);
String texName = name + ".png";
ImageIO.write(bi, "PNG", new File(dest.getParentFile(), texName));
ps.println("map_Ka " + texName);
ps.println("map_Kd " + texName);
ps.println();
}
ps.close();
return materialNames;
}
private void printNode(GrimModel model, ModelNode node, Vector3f offset, PrintStream ps, Map<Material,String> matNames) {
Vector3f nodeOffset = offset.add(node.pos);
// The mesh is offset by the pivot
if(node.mesh != null)
printMesh(model, node.mesh, nodeOffset.add(node.pivot), ps, matNames);
if(node.child != null)
printNode(model, node.child, nodeOffset, ps, matNames);
if(node.sibling != null)
printNode(model, node.sibling, offset, ps, matNames);
}
private int _vertexOff;
private void printMesh(GrimModel model, Mesh mesh, Vector3f offset, PrintStream ps, Map<Material,String> matNames) {
ps.println("g " + mesh.name);
for(MeshFace face: mesh.faces) {
Texture tex = null;
if(face.texture != null)
tex = face.texture;
for (int i = 0; i < face.vertices.size(); i++) {
Vector3f v = face.vertices.get(i);
Vector3f n = face.normals.get(i);
Vector2f t = face.uv.get(i);
t = new Vector2f(t.x, -t.y);
if(tex != null)
t = t.div(new Vector2f(tex.width, tex.height)); // map to 0-1 range...
v = v.add(offset);
ps.println("v " + v.x + " " + v.z + " " + v.y);
ps.println("vn " + n.x + " " + n.z + " " + n.y);
ps.println("vt " + t.x + " " + t.y);
}
if(face.material != null)
ps.println("usemtl " + matNames.get(face.material));
ps.print("f ");
for(int i = 0; i < face.vertices.size(); i++) {
ps.print(" " + _vertexOff + "/" + _vertexOff + "/" + _vertexOff);
_vertexOff++;
}
ps.println();
}
}
public DataSource write(GrimModel source) throws IOException {
throw new UnsupportedOperationException(); // Todo: write encoder...
}
public String[] getFileExtensions() {
return new String[]{"3do"};
}
public Class<GrimModel> getEntryClass() {
return GrimModel.class;
}
}