/*
* Copyright 2013 MovingBlocks
*
* 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 org.terasology.rendering.assets.skeletalmesh;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;
import org.terasology.assets.AssetData;
import org.terasology.math.AABB;
import org.terasology.math.geom.Quat4f;
import org.terasology.math.geom.Vector2f;
import org.terasology.math.geom.Vector3f;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
*/
public class SkeletalMeshData implements AssetData {
private Bone rootBone;
private Map<String, Bone> boneLookup = Maps.newHashMap();
private List<Bone> bones = Lists.newArrayList();
private List<Vector2f> uvs = ImmutableList.of();
private List<BoneWeight> weights = Lists.newArrayList();
private TIntList vertexStartWeights = new TIntArrayList();
private TIntList vertexWeightCounts = new TIntArrayList();
private TIntList indices = new TIntArrayList();
private AABB staticAABB;
public SkeletalMeshData(List<Bone> bones, List<BoneWeight> weights, List<Vector2f> uvs, TIntList vertexStartWeights,
TIntList vertexWeightCounts, TIntList indices, AABB staticAABB) {
for (Bone bone : bones) {
if (bone.getParent() == null) {
rootBone = bone;
break;
}
}
this.bones.addAll(bones);
this.weights.addAll(weights);
this.uvs = ImmutableList.copyOf(uvs);
this.vertexStartWeights.addAll(vertexStartWeights);
this.vertexWeightCounts.addAll(vertexWeightCounts);
this.indices.addAll(indices);
this.staticAABB = staticAABB;
calculateNormals();
}
public Collection<Bone> getBones() {
return bones;
}
public Bone getRootBone() {
return rootBone;
}
public List<Vector3f> getBindPoseVertexPositions() {
List<Vector3f> positions = Lists.newArrayListWithCapacity(bones.size());
List<Quat4f> rotations = Lists.newArrayListWithCapacity(getBones().size());
for (Bone bone : bones) {
positions.add(bone.getObjectPosition());
rotations.add(bone.getObjectRotation());
}
return getVertexPositions(positions, rotations);
}
public List<Vector3f> getBindPoseVertexNormals() {
List<Vector3f> positions = Lists.newArrayListWithCapacity(bones.size());
List<Quat4f> rotations = Lists.newArrayListWithCapacity(getBones().size());
for (Bone bone : bones) {
positions.add(bone.getObjectPosition());
rotations.add(bone.getObjectRotation());
}
return getVertexNormals(positions, rotations);
}
public List<Vector3f> getVertexPositions(List<Vector3f> bonePositions, List<Quat4f> boneRotations) {
List<Vector3f> results = Lists.newArrayListWithCapacity(getVertexCount());
for (int i = 0; i < vertexStartWeights.size(); ++i) {
Vector3f vertexPos = new Vector3f();
for (int weightIndexOffset = 0; weightIndexOffset < vertexWeightCounts.get(i); ++weightIndexOffset) {
int weightIndex = vertexStartWeights.get(i) + weightIndexOffset;
BoneWeight weight = weights.get(weightIndex);
Vector3f current = boneRotations.get(weight.getBoneIndex()).rotate(weight.getPosition(), new Vector3f());
current.add(bonePositions.get(weight.getBoneIndex()));
current.scale(weight.getBias());
vertexPos.add(current);
}
results.add(vertexPos);
}
return results;
}
public List<Vector3f> getVertexNormals(List<Vector3f> bonePositions, List<Quat4f> boneRotations) {
List<Vector3f> results = Lists.newArrayListWithCapacity(getVertexCount());
for (int i = 0; i < vertexStartWeights.size(); ++i) {
Vector3f vertexNorm = new Vector3f();
for (int weightIndexOffset = 0; weightIndexOffset < vertexWeightCounts.get(i); ++weightIndexOffset) {
int weightIndex = vertexStartWeights.get(i) + weightIndexOffset;
BoneWeight weight = weights.get(weightIndex);
Vector3f current = boneRotations.get(weight.getBoneIndex()).rotate(weight.getNormal(), new Vector3f());
current.scale(weight.getBias());
vertexNorm.add(current);
}
results.add(vertexNorm);
}
return results;
}
public int getVertexCount() {
return vertexStartWeights.size();
}
public Bone getBone(String name) {
return boneLookup.get(name);
}
public TIntList getIndices() {
return indices;
}
public List<Vector2f> getUVs() {
return uvs;
}
public AABB getStaticAABB() {
return staticAABB;
}
private void calculateNormals() {
// TODO: Better algorithm (take into account triangle size and angles
List<Vector3f> vertices = getBindPoseVertexPositions();
List<Vector3f> normals = Lists.newArrayListWithCapacity(vertices.size());
for (int i = 0; i < vertices.size(); ++i) {
normals.add(new Vector3f());
}
Vector3f v1 = new Vector3f();
Vector3f v2 = new Vector3f();
Vector3f norm = new Vector3f();
for (int i = 0; i < indices.size() / 3; ++i) {
Vector3f baseVert = vertices.get(indices.get(i * 3));
v1.sub(vertices.get(indices.get(i * 3 + 1)), baseVert);
v2.sub(vertices.get(indices.get(i * 3 + 2)), baseVert);
v1.normalize();
v2.normalize();
norm.cross(v1, v2);
normals.get(indices.get(i * 3)).add(norm);
normals.get(indices.get(i * 3 + 1)).add(norm);
normals.get(indices.get(i * 3 + 2)).add(norm);
}
normals.forEach(Vector3f::normalize);
Quat4f inverseRot = new Quat4f();
for (int vertIndex = 0; vertIndex < vertices.size(); ++vertIndex) {
Vector3f normal = normals.get(vertIndex);
for (int weightIndex = 0; weightIndex < vertexWeightCounts.get(vertIndex); ++weightIndex) {
BoneWeight weight = weights.get(weightIndex + vertexStartWeights.get(vertIndex));
inverseRot.inverse(bones.get(weight.getBoneIndex()).getObjectRotation());
inverseRot.rotate(normal, norm);
weight.setNormal(norm);
}
}
}
/**
* Outputs the skeletal mesh as md5mesh file
*/
public String toMD5(String shader) {
StringBuilder sb = new StringBuilder();
sb.append("MD5Version 10\n" +
"commandline \"Exported from Terasology MD5SkeletonLoader\"\n" +
"\n");
sb.append("numJoints ").append(bones.size()).append("\n");
sb.append("numMeshes 1\n\n");
sb.append("joints {\n");
for (Bone bone : bones) {
sb.append("\t\"").append(bone.getName()).append("\" ").append(bone.getParentIndex()).append(" ( ");
sb.append(bone.getObjectPosition().x).append(" ");
sb.append(bone.getObjectPosition().y).append(" ");
sb.append(bone.getObjectPosition().z).append(" ) ( ");
Quat4f rot = new Quat4f(bone.getObjectRotation());
rot.normalize();
if (rot.w > 0) {
rot.x = -rot.x;
rot.y = -rot.y;
rot.z = -rot.z;
}
sb.append(rot.x).append(" ");
sb.append(rot.y).append(" ");
sb.append(rot.z).append(" )\n");
}
sb.append("}\n\n");
sb.append("mesh {\n");
sb.append("\tshader \"" + shader + "\"\n");
sb.append("\tnumverts ").append(uvs.size()).append("\n");
for (int i = 0; i < uvs.size(); i++) {
sb.append("\tvert ").append(i).append(" (").append(uvs.get(i).x).append(" ").append(uvs.get(i).y).append(") ");
sb.append(vertexStartWeights.get(i)).append(" ").append(vertexWeightCounts.get(i)).append("\n");
}
sb.append("\n");
sb.append("\tnumtris ").append(indices.size() / 3).append("\n");
for (int i = 0; i < indices.size() / 3; i++) {
int i1 = indices.get(i * 3);
int i2 = indices.get(i * 3 + 1);
int i3 = indices.get(i * 3 + 2);
sb.append("\ttri ").append(i).append(" ").append(i1).append(" ").append(i2).append(" ").append(i3).append("\n");
}
sb.append("\n");
sb.append("\tnumweights ").append(weights.size()).append("\n");
int meshId = 0;
for (BoneWeight weight : weights) {
sb.append("\tweight ").append(meshId).append(" ").append(weight.getBoneIndex()).append(" ");
sb.append(weight.getBias()).append(" ( ");
sb.append(weight.getPosition().x).append(" ").append(weight.getPosition().y).append(" ").append(weight.getPosition().z).append(")\n");
meshId++;
}
sb.append("}\n");
return sb.toString();
}
}