ForgePatch/src/main/java/net/minecraftforge/client/model/b3d/B3DModel.java

1148 lines
34 KiB
Java

/*
* Minecraft Forge
* Copyright (c) 2016-2019.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.client.model.b3d;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import net.minecraft.client.renderer.*;
import net.minecraft.util.math.Vec2f;
import net.minecraftforge.versions.forge.ForgeVersion;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.function.Function;
import com.google.common.base.Joiner;
import java.util.Optional;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Table;
public class B3DModel
{
static final Logger logger = LogManager.getLogger(ForgeVersion.MOD_ID + ".B3DModel");
private static final boolean printLoadedModels = "true".equals(System.getProperty("b3dloader.printLoadedModels"));
private final List<Texture> textures;
private final List<Brush> brushes;
private final Node<?> root;
private final ImmutableMap<String, Node<Mesh>> meshes;
public B3DModel(List<Texture> textures, List<Brush> brushes, Node<?> root, ImmutableMap<String, Node<Mesh>> meshes)
{
this.textures = textures;
this.brushes = brushes;
this.root = root;
this.meshes = meshes;
}
public static class Parser
{
private static final int version = 1;
private final ByteBuffer buf;
private byte[] tag = new byte[4];
private int length;
public Parser(InputStream in) throws IOException
{
if(in instanceof FileInputStream)
{
// fast shorthand for normal files
FileChannel channel = ((FileInputStream)in).getChannel();
buf = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()).order(ByteOrder.LITTLE_ENDIAN);
}
else
{
// slower default for others
IOUtils.readFully(in, tag);
byte[] tmp = new byte[4];
IOUtils.readFully(in, tmp);
int l = ByteBuffer.wrap(tmp).order(ByteOrder.LITTLE_ENDIAN).getInt();
if(l < 0 || l + 8 < 0) throw new IOException("File is too large");
buf = ByteBuffer.allocate(l + 8).order(ByteOrder.LITTLE_ENDIAN);
((Buffer)buf).clear();
buf.put(tag);
buf.put(tmp);
buf.put(IOUtils.toByteArray(in, l));
((Buffer)buf).flip();
}
}
private String dump = "";
private void dump(String str)
{
if(printLoadedModels)
{
dump += str + "\n";
}
}
private B3DModel res;
public B3DModel parse() throws IOException
{
if(res != null) return res;
dump = "\n";
readHeader();
res = bb3d();
if(printLoadedModels)
{
logger.info(dump);
}
return res;
}
private final List<Texture> textures = new ArrayList<>();
private Texture getTexture(int texture)
{
if(texture > textures.size())
{
logger.error("texture {} is out of range", texture);
return null;
}
else if(texture == -1) return Texture.White;
return textures.get(texture);
}
private final List<Brush> brushes = new ArrayList<>();
private @Nullable Brush getBrush(int brush) throws IOException
{
if(brush > brushes.size())
{
throw new IOException(String.format("brush %s is out of range", brush));
}
else if(brush == -1) return null;
return brushes.get(brush);
}
private final List<Vertex> vertices = new ArrayList<>();
private Vertex getVertex(int vertex) throws IOException
{
if(vertex > vertices.size())
{
throw new IOException(String.format("vertex %s is out of range", vertex));
}
return vertices.get(vertex);
}
private final ImmutableMap.Builder<String, Node<Mesh>> meshes = ImmutableMap.builder();
private void readHeader() throws IOException
{
buf.get(tag);
length = buf.getInt();
}
private boolean isChunk(String tag) throws IOException
{
return Arrays.equals(this.tag, tag.getBytes("US-ASCII"));
}
private void chunk(String tag) throws IOException
{
if(!isChunk(tag)) throw new IOException("Expected chunk " + tag + ", got " + new String(this.tag, "US-ASCII"));
pushLimit();
}
private String readString() throws IOException
{
int start = buf.position();
while(buf.get() != 0);
int end = buf.position();
byte[] tmp = new byte[end - start - 1];
((Buffer)buf).position(start);
buf.get(tmp);
buf.get();
return new String(tmp, "UTF8");
}
private Deque<Integer> limitStack = new ArrayDeque<>();
private void pushLimit()
{
limitStack.push(buf.limit());
((Buffer)buf).limit(buf.position() + length);
}
private void popLimit()
{
((Buffer)buf).limit(limitStack.pop());
}
private B3DModel bb3d() throws IOException
{
chunk("BB3D");
int version = buf.getInt();
if(version / 100 > Parser.version / 100)
throw new IOException("Unsupported major model version: " + ((float)version / 100));
if(version % 100 > Parser.version % 100)
logger.warn(String.format("Minor version difference in model: %s", ((float)version / 100)));
List<Texture> textures = Collections.emptyList();
List<Brush> brushes = Collections.emptyList();
Node<?> root = null;
dump("BB3D(version = " + version + ") {");
while(buf.hasRemaining())
{
readHeader();
if (isChunk("TEXS")) textures = texs();
else if(isChunk("BRUS")) brushes = brus();
else if(isChunk("NODE")) root = node();
else skip();
}
dump("}");
popLimit();
if (root == null) {
throw new IOException("not found the root node in the model");
}
return new B3DModel(textures, brushes, root, meshes.build());
}
private List<Texture> texs() throws IOException
{
chunk("TEXS");
List<Texture> ret = new ArrayList<>();
while(buf.hasRemaining())
{
String path = readString();
int flags = buf.getInt();
int blend = buf.getInt();
Vec2f pos = new Vec2f(buf.getFloat(), buf.getFloat());
Vec2f scale = new Vec2f(buf.getFloat(), buf.getFloat());
float rot = buf.getFloat();
ret.add(new Texture(path, flags, blend, pos, scale, rot));
}
dump("TEXS([" + Joiner.on(", ").join(ret) + "])");
popLimit();
this.textures.addAll(ret);
return ret;
}
private List<Brush> brus() throws IOException
{
chunk("BRUS");
List<Brush> ret = new ArrayList<>();
int n_texs = buf.getInt();
while(buf.hasRemaining())
{
String name = readString();
Vector4f color = new Vector4f(buf.getFloat(), buf.getFloat(), buf.getFloat(), buf.getFloat());
float shininess = buf.getFloat();
int blend = buf.getInt();
int fx = buf.getInt();
List<Texture> textures = new ArrayList<>();
for(int i = 0; i < n_texs; i++) textures.add(getTexture(buf.getInt()));
ret.add(new Brush(name, color, shininess, blend, fx, textures));
}
dump("BRUS([" + Joiner.on(", ").join(ret) + "])");
popLimit();
this.brushes.addAll(ret);
return ret;
}
private List<Vertex> vrts() throws IOException
{
chunk("VRTS");
List<Vertex> ret = new ArrayList<>();
int flags = buf.getInt();
int tex_coord_sets = buf.getInt();
int tex_coord_set_size = buf.getInt();
while(buf.hasRemaining())
{
Vector3f v = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat()), n = null;
Vector4f color = null;
if((flags & 1) != 0)
{
n = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat());
}
if((flags & 2) != 0)
{
color = new Vector4f(buf.getFloat(), buf.getFloat(), buf.getFloat(), buf.getFloat());
}
Vector4f[] tex_coords = new Vector4f[tex_coord_sets];
for(int i = 0; i < tex_coord_sets; i++)
{
switch(tex_coord_set_size)
{
case 1:
tex_coords[i] = new Vector4f(buf.getFloat(), 0, 0, 1);
break;
case 2:
tex_coords[i] = new Vector4f(buf.getFloat(), buf.getFloat(), 0, 1);
break;
case 3:
tex_coords[i] = new Vector4f(buf.getFloat(), buf.getFloat(), buf.getFloat(), 1);
break;
case 4:
tex_coords[i] = new Vector4f(buf.getFloat(), buf.getFloat(), buf.getFloat(), buf.getFloat());
break;
default:
logger.error(String.format("Unsupported number of texture coords: %s", tex_coord_set_size));
tex_coords[i] = new Vector4f(0, 0, 0, 1);
}
}
ret.add(new Vertex(v, n, color, tex_coords));
}
dump("VRTS([" + Joiner.on(", ").join(ret) + "])");
popLimit();
this.vertices.clear();
this.vertices.addAll(ret);
return ret;
}
private List<Face> tris() throws IOException
{
chunk("TRIS");
List<Face> ret = new ArrayList<>();
int brush_id = buf.getInt();
while(buf.hasRemaining())
{
ret.add(new Face(getVertex(buf.getInt()), getVertex(buf.getInt()), getVertex(buf.getInt()), getBrush(brush_id)));
}
dump("TRIS([" + Joiner.on(", ").join(ret) + "])");
popLimit();
return ret;
}
private Pair<Brush, List<Face>> mesh() throws IOException
{
chunk("MESH");
int brush_id = buf.getInt();
readHeader();
dump("MESH(brush = " + brush_id + ") {");
vrts();
List<Face> ret = new ArrayList<>();
while(buf.hasRemaining())
{
readHeader();
ret.addAll(tris());
}
dump("}");
popLimit();
return Pair.of(getBrush(brush_id), ret);
}
private List<Pair<Vertex, Float>> bone() throws IOException
{
chunk("BONE");
List<Pair<Vertex, Float>> ret = new ArrayList<>();
while(buf.hasRemaining())
{
ret.add(Pair.of(getVertex(buf.getInt()), buf.getFloat()));
}
dump("BONE(...)");
popLimit();
return ret;
}
private final Deque<Table<Integer, Optional<Node<?>>, Key>> animations = new ArrayDeque<>();
private Map<Integer, Key> keys() throws IOException
{
chunk("KEYS");
Map<Integer, Key> ret = new HashMap<>();
int flags = buf.getInt();
Vector3f pos = null, scale = null;
Quaternion rot = null;
while(buf.hasRemaining())
{
int frame = buf.getInt();
if((flags & 1) != 0)
{
pos = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat());
}
if((flags & 2) != 0)
{
scale = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat());
}
if((flags & 4) != 0)
{
rot = readQuat();
}
Key key = new Key(pos, scale, rot);
Key oldKey = animations.peek().get(frame, null);
if(oldKey != null)
{
if(pos != null)
{
if(oldKey.getPos() != null) logger.error("Duplicate keys: {} and {} (ignored)", oldKey, key);
else key = new Key(oldKey.getPos(), key.getScale(), key.getRot());
}
if(scale != null)
{
if(oldKey.getScale() != null) logger.error("Duplicate keys: {} and {} (ignored)", oldKey, key);
else key = new Key(key.getPos(), oldKey.getScale(), key.getRot());
}
if(rot != null)
{
if(oldKey.getRot() != null) logger.error("Duplicate keys: {} and {} (ignored)", oldKey, key);
else key = new Key(key.getPos(), key.getScale(), oldKey.getRot());
}
}
animations.peek().put(frame, Optional.empty(), key);
ret.put(frame, key);
}
dump("KEYS([(" + Joiner.on("), (").withKeyValueSeparator(" -> ").join(ret) + ")])");
popLimit();
return ret;
}
private Triple<Integer, Integer, Float> anim() throws IOException
{
chunk("ANIM");
int flags = buf.getInt();
int frames = buf.getInt();
float fps = buf.getFloat();
dump("ANIM(" + flags + ", " + frames + ", " + fps + ")");
popLimit();
return Triple.of(flags, frames, fps);
}
private Node<?> node() throws IOException
{
chunk("NODE");
animations.push(HashBasedTable.create());
Triple<Integer, Integer, Float> animData = null;
Pair<Brush, List<Face>> mesh = null;
List<Pair<Vertex, Float>> bone = null;
Map<Integer, Key> keys = new HashMap<>();
List<Node<?>> nodes = new ArrayList<>();
String name = readString();
Vector3f pos = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat());
Vector3f scale = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat());
Quaternion rot = readQuat();
dump("NODE(" + name + ", " + pos + ", " + scale + ", " + rot + ") {");
while(buf.hasRemaining())
{
readHeader();
if (isChunk("MESH")) mesh = mesh();
else if(isChunk("BONE")) bone = bone();
else if(isChunk("KEYS")) keys.putAll(keys());
else if(isChunk("NODE")) nodes.add(node());
else if(isChunk("ANIM")) animData = anim();
else skip();
}
dump("}");
popLimit();
Table<Integer, Optional<Node<?>>, Key> keyData = animations.pop();
Node<?> node;
if(mesh != null)
{
Node<Mesh> mNode = Node.create(name, pos, scale, rot, nodes, new Mesh(mesh));
meshes.put(name, mNode);
node = mNode;
}
else if(bone != null) node = Node.create(name, pos, scale, rot, nodes, new Bone(bone));
else node = Node.create(name, pos, scale, rot, nodes, new Pivot());
if(animData == null)
{
for(Table.Cell<Integer, Optional<Node<?>>, Key> key : keyData.cellSet())
{
animations.peek().put(key.getRowKey(), Optional.of(key.getColumnKey().orElse(node)), key.getValue());
}
}
else
{
node.setAnimation(animData, keyData);
}
return node;
}
private Quaternion readQuat()
{
float w = buf.getFloat();
float x = buf.getFloat();
float y = buf.getFloat();
float z = buf.getFloat();
return new Quaternion(x, y, z, w);
}
private void skip()
{
((Buffer)buf).position(buf.position() + length);
}
}
// boilerplate below
public List<Texture> getTextures()
{
return textures;
}
public List<Brush> getBrushes()
{
return brushes;
}
public Node<?> getRoot()
{
return root;
}
public ImmutableMap<String, Node<Mesh>> getMeshes()
{
return meshes;
}
public static class Texture
{
public static final Texture White = new Texture("builtin/white", 0, 0, new Vec2f(0, 0), new Vec2f(1, 1), 0);
private final String path;
private final int flags;
private final int blend;
private final Vec2f pos;
private final Vec2f scale;
private final float rot;
public Texture(String path, int flags, int blend, Vec2f pos, Vec2f scale, float rot)
{
this.path = path;
this.flags = flags;
this.blend = blend;
this.pos = pos;
this.scale = scale;
this.rot = rot;
}
public String getPath()
{
return path;
}
public int getFlags()
{
return flags;
}
public int getBlend()
{
return blend;
}
public Vec2f getPos()
{
return pos;
}
public Vec2f getScale()
{
return scale;
}
public float getRot()
{
return rot;
}
@Override
public String toString()
{
return String.format("Texture [path=%s, flags=%s, blend=%s, pos=%s, scale=%s, rot=%s]", path, flags, blend, pos, scale, rot);
}
}
public static class Brush
{
private final String name;
private final Vector4f color;
private final float shininess;
private final int blend;
private final int fx;
private final List<Texture> textures;
public Brush(String name, Vector4f color, float shininess, int blend, int fx, List<Texture> textures)
{
this.name = name;
this.color = color;
this.shininess = shininess;
this.blend = blend;
this.fx = fx;
this.textures = textures;
}
public String getName()
{
return name;
}
public Vector4f getColor()
{
return color;
}
public float getShininess()
{
return shininess;
}
public int getBlend()
{
return blend;
}
public int getFx()
{
return fx;
}
public List<Texture> getTextures()
{
return textures;
}
@Override
public String toString()
{
return String.format("Brush [name=%s, color=%s, shininess=%s, blend=%s, fx=%s, textures=%s]", name, color, shininess, blend, fx, textures);
}
}
public static class Vertex
{
private final Vector3f pos;
@Nullable
private final Vector3f normal;
@Nullable
private final Vector4f color;
private final Vector4f[] texCoords;
public Vertex(Vector3f pos, @Nullable Vector3f normal, @Nullable Vector4f color, Vector4f[] texCoords)
{
this.pos = pos;
this.normal = normal;
this.color = color;
this.texCoords = texCoords;
}
public Vertex bake(Mesh mesh, Function<Node<?>, Matrix4f> animator)
{
// geometry
Float totalWeight = 0f;
Matrix4f t = new Matrix4f();
if(mesh.getWeightMap().get(this).isEmpty())
{
t.setIdentity();
}
else
{
for(Pair<Float, Node<Bone>> bone : mesh.getWeightMap().get(this))
{
totalWeight += bone.getLeft();
Matrix4f bm = animator.apply(bone.getRight());
bm.mul(bone.getLeft());
t.add(bm);
}
if(Math.abs(totalWeight) > 1e-4) t.mul(1f / totalWeight);
else t.setIdentity();
}
TransformationMatrix trsr = new TransformationMatrix(t);
// pos
Vector4f pos = new Vector4f(this.pos);
pos.setW(1);
trsr.transformPosition(pos);
pos.normalize();
Vector3f rPos = new Vector3f(pos.getX(), pos.getY(), pos.getZ());
// normal
Vector3f rNormal = null;
if(this.normal != null)
{
rNormal = this.normal.copy();
trsr.transformNormal(rNormal);
}
// texCoords TODO
return new Vertex(rPos, rNormal, color, texCoords);
}
public Vector3f getPos()
{
return pos;
}
@Nullable
public Vector3f getNormal()
{
return normal;
}
@Nullable
public Vector4f getColor()
{
return color;
}
public Vector4f[] getTexCoords()
{
return texCoords;
}
@Override
public String toString()
{
return String.format("Vertex [pos=%s, normal=%s, color=%s, texCoords=%s]", pos, normal, color, java.util.Arrays.toString(texCoords));
}
}
public static class Face
{
private final Vertex v1, v2, v3;
@Nullable
private final Brush brush;
private final Vector3f normal;
public Face(Vertex v1, Vertex v2, Vertex v3, @Nullable Brush brush)
{
this(v1, v2, v3, brush, getNormal(v1, v2, v3));
}
public Face(Vertex v1, Vertex v2, Vertex v3, @Nullable Brush brush, Vector3f normal)
{
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
this.brush = brush;
this.normal = normal;
}
public Vertex getV1()
{
return v1;
}
public Vertex getV2()
{
return v2;
}
public Vertex getV3()
{
return v3;
}
@Nullable
public Brush getBrush()
{
return brush;
}
@Override
public String toString()
{
return String.format("Face [v1=%s, v2=%s, v3=%s]", v1, v2, v3);
}
public Vector3f getNormal()
{
return normal;
}
public static Vector3f getNormal(Vertex v1, Vertex v2, Vertex v3)
{
Vector3f a = v2.getPos().copy();
a.sub(v1.getPos());
Vector3f b = v3.getPos().copy();
b.sub(v1.getPos());
Vector3f c = a.copy();
c.cross(b);
c.normalize();
return c;
}
}
public static class Key
{
@Nullable
private final Vector3f pos;
@Nullable
private final Vector3f scale;
@Nullable
private final Quaternion rot;
public Key(@Nullable Vector3f pos, @Nullable Vector3f scale, @Nullable Quaternion rot)
{
this.pos = pos;
this.scale = scale;
this.rot = rot;
}
@Nullable
public Vector3f getPos()
{
return pos;
}
@Nullable
public Vector3f getScale()
{
return scale;
}
@Nullable
public Quaternion getRot()
{
return rot;
}
@Override
public String toString()
{
return String.format("Key [pos=%s, scale=%s, rot=%s]", pos, scale, rot);
}
}
public static class Animation
{
private final int flags;
private final int frames;
private final float fps;
private final ImmutableTable<Integer, Node<?>, Key> keys;
public Animation(int flags, int frames, float fps, ImmutableTable<Integer, Node<?>, Key> keys)
{
this.flags = flags;
this.frames = frames;
this.fps = fps;
this.keys = keys;
}
public int getFlags()
{
return flags;
}
public int getFrames()
{
return frames;
}
public float getFps()
{
return fps;
}
public ImmutableTable<Integer, Node<?>, Key> getKeys()
{
return keys;
}
@Override
public String toString()
{
return String.format("Animation [flags=%s, frames=%s, fps=%s, keys=...]", flags, frames, fps);
}
}
public static interface IKind<K extends IKind<K>>
{
void setParent(Node<K> parent);
Node<K> getParent();
}
public static class Node<K extends IKind<K>>
{
private final String name;
private final Vector3f pos;
private final Vector3f scale;
private final Quaternion rot;
private final ImmutableMap<String, Node<?>> nodes;
@Nullable
private Animation animation;
private final K kind;
@Nullable
private Node<? extends IKind<?>> parent;
public static <K extends IKind<K>> Node<K> create(String name, Vector3f pos, Vector3f scale, Quaternion rot, List<Node<?>> nodes, K kind)
{
return new Node<>(name, pos, scale, rot, nodes, kind);
}
public Node(String name, Vector3f pos, Vector3f scale, Quaternion rot, List<Node<?>> nodes, K kind)
{
this.name = name;
this.pos = pos;
this.scale = scale;
this.rot = rot;
this.nodes = buildNodeMap(nodes);
this.kind = kind;
kind.setParent(this);
for(Node<?> child : this.nodes.values())
{
child.setParent(this);
}
}
public void setAnimation(Animation animation)
{
this.animation = animation;
Deque<Node<?>> q = new ArrayDeque<>(nodes.values());
while(!q.isEmpty())
{
Node<?> node = q.pop();
if(node.getAnimation() != null) continue;
node.setAnimation(animation);
q.addAll(node.getNodes().values());
}
}
public void setAnimation(Triple<Integer, Integer, Float> animData, Table<Integer, Optional<Node<?>>, Key> keyData)
{
ImmutableTable.Builder<Integer, Node<?>, Key> builder = ImmutableTable.builder();
for(Table.Cell<Integer, Optional<Node<?>>, Key> key : keyData.cellSet())
{
builder.put(key.getRowKey(), key.getColumnKey().orElse(this), key.getValue());
}
setAnimation(new Animation(animData.getLeft(), animData.getMiddle(), animData.getRight(), builder.build()));
}
private ImmutableMap<String, Node<?>> buildNodeMap(List<Node<?>> nodes)
{
ImmutableMap.Builder<String, Node<?>> builder = ImmutableMap.builder();
for(Node<?> node : nodes)
{
builder.put(node.getName(), node);
}
return builder.build();
}
public String getName()
{
return name;
}
public K getKind()
{
return kind;
}
public Vector3f getPos()
{
return pos;
}
public Vector3f getScale()
{
return scale;
}
public Quaternion getRot()
{
return rot;
}
public ImmutableMap<String, Node<?>> getNodes()
{
return nodes;
}
@Nullable
public Animation getAnimation()
{
return animation;
}
@Nullable
public Node<? extends IKind<?>> getParent()
{
return parent;
}
public void setParent(Node<? extends IKind<?>> parent)
{
this.parent = parent;
}
@Override
public String toString()
{
return String.format("Node [name=%s, kind=%s, pos=%s, scale=%s, rot=%s, keys=..., nodes=..., animation=%s]", name, kind, pos, scale, rot, animation);
}
}
public static class Pivot implements IKind<Pivot>
{
private Node<Pivot> parent;
@Override
public void setParent(Node<Pivot> parent)
{
this.parent = parent;
}
@Override
public Node<Pivot> getParent()
{
return parent;
}
}
public static class Mesh implements IKind<Mesh>
{
private Node<Mesh> parent;
private final Brush brush;
private final ImmutableList<Face> faces;
//private final ImmutableList<Bone> bones;
private Set<Node<Bone>> bones = new HashSet<>();
private ImmutableMultimap<Vertex, Pair<Float, Node<Bone>>> weightMap = ImmutableMultimap.of();
public Mesh(Pair<Brush, List<Face>> data)
{
this.brush = data.getLeft();
this.faces = ImmutableList.copyOf(data.getRight());
}
public ImmutableMultimap<Vertex, Pair<Float, Node<Bone>>> getWeightMap()
{
return weightMap;
}
public ImmutableList<Face> bake(Function<Node<?>, Matrix4f> animator)
{
ImmutableList.Builder<Face> builder = ImmutableList.builder();
for(Face f : getFaces())
{
Vertex v1 = f.getV1().bake(this, animator);
Vertex v2 = f.getV2().bake(this, animator);
Vertex v3 = f.getV3().bake(this, animator);
builder.add(new Face(v1, v2, v3, f.getBrush()));
}
return builder.build();
}
public Brush getBrush()
{
return brush;
}
public ImmutableList<Face> getFaces()
{
return faces;
}
public ImmutableSet<Node<Bone>> getBones()
{
return ImmutableSet.copyOf(bones);
}
@Override
public String toString()
{
return String.format("Mesh [pivot=%s, brush=%s, data=...]", super.toString(), brush);
}
@Override
@SuppressWarnings("unchecked")
public void setParent(Node<Mesh> parent)
{
this.parent = parent;
Deque<Node<?>> queue = new ArrayDeque<>(parent.getNodes().values());
while(!queue.isEmpty())
{
Node<?> node = queue.pop();
if(node.getKind() instanceof Bone)
{
bones.add((Node<Bone>)node);
queue.addAll(node.getNodes().values());
}
}
ImmutableMultimap.Builder<Vertex, Pair<Float, Node<Bone>>> builder = ImmutableMultimap.builder();
for(Node<Bone> bone : getBones())
{
for(Pair<Vertex, Float> b : bone.getKind().getData())
{
builder.put(b.getLeft(), Pair.of(b.getRight(), bone));
}
}
weightMap = builder.build();
}
@Override
public Node<Mesh> getParent()
{
return parent;
}
}
public static class Bone implements IKind<Bone>
{
private Node<Bone> parent;
private final List<Pair<Vertex, Float>> data;
public Bone(List<Pair<Vertex, Float>> data)
{
this.data = data;
}
public List<Pair<Vertex, Float>> getData()
{
return data;
}
/*@Override
public String toString()
{
return String.format("Bone [data=%s]", data);
}*/
@Override
public void setParent(Node<Bone> parent)
{
this.parent = parent;
}
@Override
public Node<Bone> getParent()
{
return parent;
}
}
}