/*******************************************************************************
* Copyright 2015 Maximilian Stark | Dakror <mail@dakror.de>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package de.dakror.vloxlands.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import com.badlogic.gdx.assets.AssetDescriptor;
import com.badlogic.gdx.assets.AssetLoaderParameters;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.assets.loaders.AsynchronousAssetLoader;
import com.badlogic.gdx.assets.loaders.FileHandleResolver;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes;
import com.badlogic.gdx.graphics.g3d.Material;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.attributes.BlendingAttribute;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.attributes.FloatAttribute;
import com.badlogic.gdx.graphics.g3d.model.Node;
import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntMap;
import de.dakror.vloxlands.render.ColorFace;
import de.dakror.vloxlands.render.Mesher;
import de.dakror.vloxlands.util.VxiLoader.VxiParameter;
/**
* @author Dakror
*/
public class VxiLoader extends AsynchronousAssetLoader<Model, VxiParameter> {
public static class VxiParameter extends AssetLoaderParameters<Model> {}
class ReferencePoint {
String name;
int x, y, z;
ReferencePoint(String name, int x, int y, int z) {
this.name = name;
this.x = x;
this.y = y;
this.z = z;
}
}
class Vertex {
Vector3 pos;
Vector3 nor;
Color col;
public Vertex(Vector3 pos, Vector3 nor, Color col) {
this.pos = pos;
this.nor = nor;
this.col = col;
}
}
public static final float defaultResolution = 0.25f;
Color[] colors;
ReferencePoint[] referencePoints;
IntMap<ColorFace> faces;
int[] data;
float resolution;
int width, height, depth, offsetX, offsetY, offsetZ;
final Vector3 tmp = new Vector3();
AssetManager assets;
Material material;
public VxiLoader(AssetManager assets, FileHandleResolver resolver) {
super(resolver);
this.assets = assets;
}
@Override
public void loadAsync(AssetManager manager, String fileName, FileHandle file, VxiParameter parameter) {
resolution = getResolution(fileName);
faces = new IntMap<ColorFace>();
byte[] fileData = new byte[(int) file.length()];
file.readBytes(fileData, 0, fileData.length);
ByteBuffer bb = ByteBuffer.wrap(fileData);
bb.order(ByteOrder.LITTLE_ENDIAN);
width = bb.getInt();
height = bb.getInt();
depth = bb.getInt();
offsetX = bb.getInt();
offsetY = bb.getInt();
offsetZ = bb.getInt();
data = new int[width * height * depth];
for (int i = 0; i < data.length; i++)
data[i] = bb.get() & 0xff;
colors = new Color[256];
for (int i = 0; i < colors.length; i++)
colors[i] = new Color((bb.get() & 0xff) / 255f, (bb.get() & 0xff) / 255f, (bb.get() & 0xff) / 255f, 1);
int refPointCount = bb.get() & 0xff;
referencePoints = new ReferencePoint[refPointCount];
for (int i = 0; i < referencePoints.length; i++) {
StringBuffer sb = new StringBuffer();
byte lastRead = 0;
while ((lastRead = (bb.get())) != 0)
sb.append((char) lastRead);
int x = bb.getInt();
int y = bb.getInt();
int z = bb.getInt();
referencePoints[i] = new ReferencePoint(sb.toString(), x, y, z);
}
material = new Material();
FileHandle mtl = file.sibling(file.nameWithoutExtension() + ".mtl");
if (mtl.exists()) loadMaterial(mtl);
generateFaces();
}
@Override
public Model loadSync(AssetManager manager, String fileName, FileHandle file, VxiParameter parameter) {
ModelBuilder mb = new ModelBuilder();
mb.begin();
MeshPartBuilder mpb = mb.part(file.nameWithoutExtension(), GL20.GL_TRIANGLES, new VertexAttributes(VertexAttribute.Position(), VertexAttribute.Normal(), VertexAttribute.ColorPacked()), material);
Array<Vertex> vertices = new Array<Vertex>();
offsetZ -= depth * (depth < height ? 0.3f : 1 / 3f);// TODO is this doing the trick?
for (ColorFace f : faces.values()) {
Vertex tl = new Vertex(f.tl.cpy().add(f.pos).add(offsetX, offsetY, offsetZ).scl(resolution), f.dir.dir, f.c);
Vertex bl = new Vertex(f.bl.cpy().add(f.pos).add(offsetX, offsetY, offsetZ).scl(resolution), f.dir.dir, f.c);
Vertex br = new Vertex(f.br.cpy().add(f.pos).add(offsetX, offsetY, offsetZ).scl(resolution), f.dir.dir, f.c);
Vertex tr = new Vertex(f.tr.cpy().add(f.pos).add(offsetX, offsetY, offsetZ).scl(resolution), f.dir.dir, f.c);
rotate(tl.pos);
rotate(bl.pos);
rotate(br.pos);
rotate(tr.pos);
if (!vertices.contains(tr, true)) {
mpb.vertex(tr.pos, tr.nor, f.c, null);
vertices.add(tr);
}
if (!vertices.contains(br, true)) {
mpb.vertex(br.pos, br.nor, f.c, null);
vertices.add(br);
}
if (!vertices.contains(tl, true)) {
mpb.vertex(tl.pos, tl.nor, f.c, null);
vertices.add(tl);
}
if (!vertices.contains(bl, true)) {
mpb.vertex(bl.pos, bl.nor, f.c, null);
vertices.add(bl);
}
mpb.index((short) vertices.indexOf(br, true), (short) vertices.indexOf(bl, true), (short) vertices.indexOf(tl, true));
mpb.index((short) vertices.indexOf(tr, true), (short) vertices.indexOf(br, true), (short) vertices.indexOf(tl, true));
}
Model model = mb.end();
for (ReferencePoint p : referencePoints) {
Node node = new Node();
node.id = p.name;
node.parent = model.nodes.get(0);
node.translation.add(p.x, p.y, p.z - depth * (depth < height ? 0.165f : 1 / 4f)).scl(resolution);
rotate(node.translation);
model.nodes.get(0).children.add(node);
}
return model;
}
public void rotate(Vector3 v) {
tmp.set(offsetX * resolution, offsetY * resolution, offsetZ * resolution).sub(v);
v.add(tmp);
v.rotate(Vector3.X, -90);
tmp.rotate(Vector3.X, -90);
v.sub(tmp);
}
public float getResolution(String fileName) {
if (!fileName.contains("[") || !fileName.contains("]")) return defaultResolution;
int res = Integer.parseInt(fileName.substring(fileName.lastIndexOf("[") + 1, fileName.lastIndexOf("]")));
return 1.0f / res;
}
@SuppressWarnings("rawtypes")
@Override
public Array<AssetDescriptor> getDependencies(String fileName, FileHandle file, VxiParameter parameter) {
return null;
}
private void loadMaterial(FileHandle file) {
String line;
String[] tokens;
Color difcolor = Color.WHITE;
Color speccolor = Color.WHITE;
float opacity = 1.f;
float shininess = 0.f;
BufferedReader reader = new BufferedReader(new InputStreamReader(file.read()), 4096);
try {
while ((line = reader.readLine()) != null) {
if (line.length() > 0 && line.charAt(0) == '\t') line = line.substring(1).trim();
tokens = line.split("\\s+");
if (tokens[0].length() == 0) {
continue;
} else if (tokens[0].charAt(0) == '#') continue;
else {
final String key = tokens[0].toLowerCase();
if (key.equals("kd") || key.equals("ks")) // diffuse or specular
{
float r = Float.parseFloat(tokens[1]);
float g = Float.parseFloat(tokens[2]);
float b = Float.parseFloat(tokens[3]);
float a = 1;
if (tokens.length > 4) a = Float.parseFloat(tokens[4]);
if (tokens[0].toLowerCase().equals("kd")) {
difcolor = new Color();
difcolor.set(r, g, b, a);
} else {
speccolor = new Color();
speccolor.set(r, g, b, a);
}
} else if (key.equals("tr") || key.equals("d")) {
opacity = Float.parseFloat(tokens[1]);
} else if (key.equals("ns")) {
shininess = Float.parseFloat(tokens[1]);
}
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
material.set(new ColorAttribute(ColorAttribute.Diffuse, difcolor));
material.set(new ColorAttribute(ColorAttribute.Specular, speccolor));
material.set(new FloatAttribute(FloatAttribute.Shininess, shininess));
if (opacity != 1) material.set(new BlendingAttribute(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, opacity));
}
private void generateFaces() {
for (int i = 0; i < data.length; i++) {
int c = data[i];
if (c == 255) continue;
int x = i / (depth * height);
int y = (i / depth) % height;
int z = i % depth;
Vector3 pos = new Vector3(x, y, z);
for (Direction d : Direction.values()) {
int index2 = (int) ((x + d.dir.x) * height * depth + (y + d.dir.y) * depth + (z + d.dir.z));
if (x + d.dir.x > -1 && y + d.dir.y > -1 && z + d.dir.z > -1 && x + d.dir.x < width && y + d.dir.y < height && z + d.dir.z < depth && data[index2] != 255) continue;
ColorFace f = new ColorFace(d, pos, colors[c]);
faces.put(f.hashCode(), f);
}
}
Mesher.generateGreedyMesh(0, 0, 0, width, height, depth, faces);
}
}