/* * JaamSim Discrete Event Simulation * Copyright (C) 2013 Ausenco Engineering Canada Inc. * Copyright (C) 2015 JaamSim Software Inc. * * 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 com.jaamsim.MeshFiles; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.jaamsim.MeshFiles.DataBlock.Error; import com.jaamsim.math.AABB; import com.jaamsim.math.Color4d; import com.jaamsim.math.ConvexHull; import com.jaamsim.math.Mat4d; import com.jaamsim.math.Vec2d; import com.jaamsim.math.Vec2dInterner; import com.jaamsim.math.Vec3d; import com.jaamsim.math.Vec3dInterner; import com.jaamsim.math.Vec4d; import com.jaamsim.math.Vec4dInterner; import com.jaamsim.render.Action; import com.jaamsim.render.RenderException; import com.jaamsim.render.RenderUtils; import com.jaamsim.render.Renderer; /** * MeshData represents all the data contained in a mesh 3d object. This data is not yet in video ram and can be written back to disk * This is not useful for rendering, for that a MeshProto is needed (which is owned by the Renderer) * @author matt.chudleigh * */ public class MeshData { public final static int MAX_HULL_ATTEMPTS = 5; public final static int MAX_HULL_POINTS = 100; public final static int MAX_SUBINST_HULL_POINTS = 30; public final static int NO_TRANS = 0; public final static int A_ONE_TRANS = 1; public final static int RGB_ZERO_TRANS = 2; public final static int DIFF_ALPHA_TRANS = 3; public static class Material { public Color4d diffuseColor; public Color4d ambientColor; public Color4d specColor; public double shininess; public URI colorTex; // TODO Properly relativize this one day public String relColorTex; // the 'relative' name for this texture, used by the binary exporter public int transType; public Color4d transColour; public int getShaderID() { int ret = 0; if (colorTex != null) { ret += Renderer.DIFF_TEX_FLAG; } return ret; } } public static class SubMeshData { public ArrayList<Vec3d> verts; public ArrayList<Vec2d> texCoords; public ArrayList<Vec3d> normals; public int[] indices; public ConvexHull staticHull; public AABB localBounds; public boolean keepRuntimeData; } public static class SubLineData { public ArrayList<Vec3d> verts = new ArrayList<>(); public Color4d diffuseColor; public ConvexHull hull; } public static class StaticMeshInstance { public int subMeshIndex; public int materialIndex; public Mat4d transform; public Mat4d invTrans; } public static class StaticLineInstance { public int lineIndex; public Mat4d transform; public Mat4d invTrans; } public static class TransVal { Mat4d transform; Mat4d invTrans; public TransVal(Mat4d trans, Mat4d inverse) { transform = trans; invTrans = inverse; } } public static interface Trans { public abstract boolean isStatic(); public abstract TransVal value(ArrayList<Action.Queue> actions); public abstract void accept(TransVisitor visitor); } public static class StaticTrans implements Trans{ private final Mat4d matrix; private final Mat4d inverseMat; public StaticTrans(Mat4d mat) { matrix = mat; inverseMat = matrix.inverse(); } @Override public boolean isStatic() { return true; } @Override public TransVal value(ArrayList<Action.Queue> actions) { return new TransVal(matrix, inverseMat); } @Override public void accept(TransVisitor visitor) { visitor.visitStatic(this); } } private static class Act { public Mat4d[] matrices; public Mat4d[] invMatrices; public double[] times; public String name; } public static class AnimTrans implements Trans{ ArrayList<Act> actions = new ArrayList<>(); public Mat4d staticMat; public Mat4d staticInv; public AnimTrans(ArrayList<Act> actions, Mat4d staticMat) { this.actions = actions; this.staticMat = staticMat; this.staticInv = staticMat.inverse(); } public AnimTrans(double[][] times, Mat4d[][] mats, String[] actionNames, Mat4d staticMat) { this.staticMat = staticMat; staticInv = staticMat.inverse(); assert(actionNames.length > 0); assert(times.length == actionNames.length); assert(mats.length == actionNames.length); for (int i = 0; i < actionNames.length; ++i) { // Build up the actions Act act = new Act(); act.times = times[i]; act.matrices = mats[i]; act.name = actionNames[i]; act.invMatrices = new Mat4d[act.matrices.length]; for (int j = 0; j < act.matrices.length; ++j) { act.invMatrices[j] = act.matrices[j].inverse(); } actions.add(act); } } @Override public boolean isStatic() { return false; } @Override public void accept(TransVisitor visitor) { visitor.visitAnim(this); } @Override public TransVal value(ArrayList<Action.Queue> aqs) { if (aqs == null || aqs.size() == 0) { return new TransVal(staticMat, staticInv); } // See which action is applicable // For now, if more than one action is applicable on any scene node, priority is determined by the // order of the action queues requested. Act act = null; // The applicable actions double time = 0; for (Action.Queue q : aqs) { for (Act a : actions) { if (q.name.equals(a.name)) { act = a; time = q.time; break; } } if (act != null) { break; } } if (act == null) { return new TransVal(staticMat, staticInv); } // The action applies, interpolate the value // Check if we are past the ends if (time <= act.times[0]) { return new TransVal(act.matrices[0], act.invMatrices[0]); } if (time >= act.times[act.times.length -1]) { return new TransVal(act.matrices[act.times.length-1], act.invMatrices[act.times.length-1]); } // Basic binary search for appropriate segment int start = 0; int end = act.times.length; while ((end - start) > 1) { int test = (start + end)/2; double samp = act.times[test]; if (samp == time) { // perfect match return new TransVal(act.matrices[test], act.invMatrices[test]); } if (samp < time) { start = test; } else { end = test; } } assert(end - start == 1); // Linearly interpolate on the segment double t0 = act.times[start]; double t1 = act.times[end]; assert(time >= t0); assert(time <= t1); double endScale = (time-t0)/(t1-t0); double startScale = 1 - endScale; Mat4d temp = new Mat4d(); Mat4d retTrans = new Mat4d(act.matrices[start]); retTrans.scale4(startScale); temp.set4(act.matrices[end]); temp.scale4(endScale); retTrans.add4(temp); Mat4d retInv = new Mat4d(act.invMatrices[start]); retInv.scale4(startScale); temp.set4(act.invMatrices[end]); temp.scale4(endScale); retInv.add4(temp); // Mat4d test = new Mat4d(); // test.mult4(retTrans, retInv); // assert(test.nearIdentity()); return new TransVal(retTrans, retInv); } // Scan through the list of keys and remove any that are redundant public int optimize() { int discarded = 0; for (Act act : actions) { ArrayList<Mat4d> keptMatrices = new ArrayList<>(); ArrayList<Mat4d> keptInverses = new ArrayList<>(); ArrayList<Double> keptTimes = new ArrayList<>(); // Keep the first value keptMatrices.add(act.matrices[0]); keptInverses.add(act.invMatrices[0]); keptTimes.add(act.times[0]); for (int i = 0; i < act.matrices.length-2; ++i) { double t0 = act.times[i]; double t1 = act.times[i+2]; double checkTime = act.times[i+1]; Mat4d m0 = act.matrices[i]; Mat4d m1 = act.matrices[i+2]; Mat4d checkMat = act.matrices[i+1]; double s0 = (checkTime-t0)/(t1-t0); double s1 = 1 - s0; Mat4d val = new Mat4d(m0); val.scale4(s0); Mat4d temp = new Mat4d(m1); temp.scale4(s1); val.add4(temp); if (!val.near4(checkMat)) { // this matrix is different enough from the interpolated value that we need it keptMatrices.add(act.matrices[i+1]); keptInverses.add(act.invMatrices[i+1]); keptTimes.add(act.times[i+1]); } else { ++discarded; } } // keep the last value keptMatrices.add(act.matrices[act.matrices.length-1]); keptInverses.add(act.invMatrices[act.invMatrices.length-1]); keptTimes.add(act.times[act.times.length-1]); // Finally rewrite the arrays act.matrices = new Mat4d[keptMatrices.size()]; act.invMatrices = new Mat4d[keptMatrices.size()]; act.times = new double[keptMatrices.size()]; for (int i = 0; i < act.matrices.length; ++i) { act.matrices[i] = keptMatrices.get(i); act.invMatrices[i] = keptInverses.get(i); act.times[i] = keptTimes.get(i); } } return discarded; } } private interface TransVisitor { void visitStatic(StaticTrans trans); void visitAnim(AnimTrans trans); } public static class AnimMeshInstance { public AnimMeshInstance(int meshIndex, int materialIndex) { this.meshIndex = meshIndex; this.materialIndex = materialIndex; } public int meshIndex; public int materialIndex; public int nodeIndex; } public static class AnimLineInstance { public AnimLineInstance(int lineIndex) { this.lineIndex = lineIndex; } public int lineIndex; public int nodeIndex; } public static class TreeNode { public Trans trans; public ArrayList<TreeNode> children = new ArrayList<>(); public ArrayList<AnimMeshInstance> meshInstances = new ArrayList<>(); public ArrayList<AnimLineInstance> lineInstances = new ArrayList<>(); public int nodeIndex; // Return a list of siblings that are equivalent to this being children of this node. // Part of constant coalescing. public ArrayList<TreeNode> getEquivSiblings() { if (!(trans.isStatic())) { return null; } ArrayList<TreeNode> ret = new ArrayList<>(); Mat4d thisMat = trans.value(null).transform; Mat4d thisInvMat = trans.value(null).invTrans; for (TreeNode child : children) { if (child.trans.isStatic()) { Mat4d childMat = child.trans.value(null).transform; Mat4d sibMat = new Mat4d(); sibMat.mult4(thisMat, childMat); child.trans = new StaticTrans(sibMat); ret.add(child); } else { // Merge the static parent into the animated child AnimTrans at = (AnimTrans)child.trans; for (Act act : at.actions) { for (int matInd = 0; matInd < act.matrices.length; ++matInd) { act.matrices[matInd].mult4(thisMat, act.matrices[matInd]); act.invMatrices[matInd].mult4(act.invMatrices[matInd], thisInvMat); } } at.staticMat.mult4(thisMat, at.staticMat); at.staticInv.mult4(at.staticInv, thisInvMat); ret.add(child); } } children.clear(); return ret; } } private static class TreeWalker { public void onNode(Mat4d trans, Mat4d invTrans, TreeNode node) {} public void onMesh(Mat4d trans, Mat4d invTrans, TreeNode node, AnimMeshInstance inst) {} public void onLine(Mat4d trans, Mat4d invTrans, TreeNode node, AnimLineInstance inst) {} } public static class Pose { public Mat4d[] transforms; public Mat4d[] invTransforms; } private final ArrayList<SubMeshData> _subMeshesData = new ArrayList<>(); private final ArrayList<SubLineData> _subLinesData = new ArrayList<>(); private final ArrayList<Material> _materials = new ArrayList<>(); private final ArrayList<StaticMeshInstance> _staticMeshInstances = new ArrayList<>(); private final ArrayList<StaticLineInstance> _staticLineInstances = new ArrayList<>(); private final ArrayList<AnimMeshInstance> _animMeshInstances = new ArrayList<>(); private final ArrayList<AnimLineInstance> _animLineInstances = new ArrayList<>(); private TreeNode treeRoot; private int numTreeNodes; private ConvexHull _staticHull; // The AABB of this mesh with no transform applied private AABB _defaultBounds; private boolean _anyTransparent = false; private ArrayList<Action.Description> _actionDesc; private Vec2dInterner v2Interner = new Vec2dInterner(); private Vec3dInterner v3Interner = new Vec3dInterner(); private Vec4dInterner v4Interner = new Vec4dInterner(); public boolean keepRuntimeData; public MeshData(boolean keepRuntimeData) { this.keepRuntimeData = keepRuntimeData; } public void addStaticMeshInstance(int meshIndex, int matIndex, Mat4d mat) { Mat4d trans = new Mat4d(mat); StaticMeshInstance inst = new StaticMeshInstance(); inst.subMeshIndex = meshIndex; inst.materialIndex = matIndex; inst.transform = trans; inst.invTrans = trans.inverse(); _staticMeshInstances.add(inst); } public void setTree(TreeNode rootNode) { treeRoot = rootNode; } public void addStaticLineInstance(int lineIndex, Mat4d mat) { Mat4d trans = new Mat4d(mat); StaticLineInstance inst = new StaticLineInstance(); inst.lineIndex = lineIndex; inst.transform = trans; inst.invTrans = trans.inverse(); _staticLineInstances.add(inst); } public void addMaterial(URI colorTex, String relTexString, Color4d diffuseColor, Color4d ambientColor, Color4d specColor, double shininess, int transType, Color4d transColour) { Material mat = new Material(); if (colorTex == null) { assert diffuseColor != null; } mat.diffuseColor = diffuseColor; mat.ambientColor = ambientColor; mat.specColor = specColor; mat.shininess = shininess; mat.colorTex = colorTex; mat.relColorTex = relTexString; if (mat.ambientColor == null) mat.ambientColor = new Color4d(); if (mat.specColor == null) mat.specColor = new Color4d(); if (mat.specColor.r == 0.0 && mat.specColor.g == 0.0 && mat.specColor.b == 0.0) { // A black spec color means that the shininess has no effect // set it to one as the shader uses shininess < 2 to fast path // the spec calculations mat.shininess = 1; } mat.transType = transType; mat.transColour = transColour; _materials.add(mat); if (transType != NO_TRANS) { _anyTransparent = true; } } // Returns a new index list with any zero area triangles removed private int[] removeDegenerateTriangles(ArrayList<Vertex> vertices, int[] indices) { assert(indices.length % 3 == 0); int[] goodIndices = new int[indices.length]; int goodWritePos = 0; for (int triInd = 0; triInd < indices.length/3; ++triInd) { int ind0 = indices[triInd * 3 + 0]; int ind1 = indices[triInd * 3 + 1]; int ind2 = indices[triInd * 3 + 2]; Vec3d pos0 = vertices.get(ind0).getPos(); Vec3d pos1 = vertices.get(ind1).getPos(); Vec3d pos2 = vertices.get(ind2).getPos(); if (ind0 == ind1 || ind1 == ind2 || ind2 == ind0) { continue; } if (pos0.equals3(pos1) || pos1.equals3(pos2) || pos2.equals3(pos0)) { continue; } goodIndices[goodWritePos++] = ind0; goodIndices[goodWritePos++] = ind1; goodIndices[goodWritePos++] = ind2; } // Finally rebuild the index list if (goodIndices.length == indices.length) { return goodIndices; // No degenerates found } int[] ret = new int[goodWritePos]; for (int i = 0; i < goodWritePos; ++i) { ret[i] = goodIndices[i]; } return ret; } public void addSubMesh(ArrayList<Vertex> vertices, int[] indices) { if (vertices.size() < 3) { vertices = new ArrayList<>(); indices = new int[0]; } SubMeshData sub = new SubMeshData(); sub.keepRuntimeData = keepRuntimeData; _subMeshesData.add(sub); int[] goodIndices = removeDegenerateTriangles(vertices, indices); sub.indices = goodIndices; assert((sub.indices.length % 3) == 0); // Assume if there is one tex coordinate, there will be all of them boolean hasTexCoords = vertices.size() > 0 && vertices.get(0).getTexCoord() != null; sub.verts = new ArrayList<>(vertices.size()); sub.normals = new ArrayList<>(vertices.size()); if (hasTexCoords) { sub.texCoords = new ArrayList<>(vertices.size()); } for (Vertex v : vertices) { sub.verts.add(v3Interner.intern(v.getPos())); sub.normals.add(v3Interner.intern(v.getNormal())); if (hasTexCoords) { sub.texCoords.add(v2Interner.intern(v.getTexCoord())); } } sub.staticHull = ConvexHull.TryBuildHull(sub.verts, MAX_HULL_ATTEMPTS, MAX_HULL_POINTS, v3Interner); sub.localBounds = sub.staticHull.getAABB(new Mat4d()); } public void addSubLine(Vec3d[] vertices, Color4d diffuseColor) { SubLineData sub = new SubLineData(); sub.diffuseColor = diffuseColor; if (sub.diffuseColor == null) { sub.diffuseColor = new Color4d(); // Default to black } _subLinesData.add(sub); assert((vertices.length % 2) == 0); for (Vec3d v : vertices) { sub.verts.add(v3Interner.intern(v)); } sub.hull = ConvexHull.TryBuildHull(sub.verts, MAX_HULL_ATTEMPTS, MAX_HULL_POINTS, v3Interner); } public boolean hasTransparent() { return _anyTransparent; } private void scanTreeForStaticEntries(TreeNode node, Mat4d trans) { TransVal val = node.trans.value(null); Mat4d transform = new Mat4d(trans); transform.mult4(val.transform); for (AnimMeshInstance ti : node.meshInstances) { addStaticMeshInstance(ti.meshIndex, ti.materialIndex, transform); } node.meshInstances.clear(); for (AnimLineInstance li : node.lineInstances) { addStaticLineInstance(li.lineIndex, transform); } node.lineInstances.clear(); for (TreeNode child : node.children) { if (child.trans.isStatic()) { scanTreeForStaticEntries(child, transform); } } // Remove children that are now empty ArrayList<TreeNode> newChildren = new ArrayList<>(); for (TreeNode child : node.children) { if (child.children.size() != 0 || !child.trans.isStatic()) { // Keep children that still have sub nodes or are not static newChildren.add(child); } } node.children = newChildren; } private void walkTree(TreeWalker walker, TreeNode node, Mat4d trans, Mat4d inverse, ArrayList<Action.Queue> actions) { Mat4d newTrans = new Mat4d(); Mat4d newInv = new Mat4d(); TransVal tv = node.trans.value(actions); newTrans.mult4(trans, tv.transform); newInv.mult4(tv.invTrans, inverse); walker.onNode(newTrans, newInv, node); for (AnimMeshInstance ti : node.meshInstances) { walker.onMesh(newTrans, newInv, node, ti); } for (AnimLineInstance li : node.lineInstances) { walker.onLine(newTrans, newInv, node, li); } for (TreeNode child : node.children) { walkTree(walker, child, newTrans, newInv, actions); } } private boolean optimizeTree(TreeNode node) { boolean changed = false; for (TreeNode child : node.children) { changed = changed || optimizeTree(child); } ArrayList<TreeNode> newChildren = new ArrayList<>(); int i = 0; while (i < node.children.size()) { TreeNode child = node.children.get(i); ArrayList<TreeNode> newCs = child.getEquivSiblings(); if (newCs != null && newCs.size() != 0) { changed = true; newChildren.addAll(newCs); } if ( child.children.size() == 0 && child.meshInstances.size() == 0 && child.lineInstances.size() == 0) { node.children.remove(i); changed = true; } else { i++; } } node.children.addAll(newChildren); // Now scan all children to see if there are any siblings with identical transforms that can be merged for (int indA = 0; indA < node.children.size()-1; ++indA) { int indB = indA+1; while (indB < node.children.size()) { TreeNode nodeA = node.children.get(indA); TreeNode nodeB = node.children.get(indB); if (nodeA.trans.isStatic() && nodeB.trans.isStatic()) { Mat4d aMat = nodeA.trans.value(null).transform; Mat4d bMat = nodeB.trans.value(null).transform; if (aMat.near4(bMat)) { nodeA.children.addAll(nodeB.children); nodeA.meshInstances.addAll(nodeB.meshInstances); nodeA.lineInstances.addAll(nodeB.lineInstances); node.children.remove(indB); changed = true; continue; } } ++indB; } } return changed; } /** * Builds the convex hull of the current mesh based on all the existing sub meshes. */ public void finalizeData() { // Scan the tree to see if any animated transforms are effectively static class StaticWalker extends TreeWalker { public int numMatricesRemoved = 0; public int numMatricesRemaining = 0; @Override public void onNode(Mat4d trans, Mat4d invTrans, TreeNode node) { if (node.trans instanceof AnimTrans) { AnimTrans at = (AnimTrans)node.trans; numMatricesRemoved += at.optimize(); for (Act act : at.actions) { numMatricesRemaining += act.matrices.length; } boolean isSame = true; for (Act act : at.actions) { for (int i = 0; i < act.matrices.length; ++i) { isSame = isSame && at.staticMat.near4(act.matrices[i]); } if (isSame) { // All values are effectively the same node.trans = new StaticTrans(at.staticMat); } } } } } StaticWalker staticWalker = new StaticWalker(); long statStart = System.nanoTime(); walkTree(staticWalker, treeRoot, new Mat4d(), new Mat4d(), null); long statEnd = System.nanoTime(); double statMS = (statEnd - statStart) / 1000000.0; // Annoying way to disable debugging while avoiding compiler warnings boolean printDebug = false; if (printDebug) { System.out.printf("Matrices removed: %d remaining: %d in %fms\n", staticWalker.numMatricesRemoved, staticWalker.numMatricesRemaining, statMS); } // Pull all static information out of the root tree if (treeRoot != null && treeRoot.trans.isStatic()) { scanTreeForStaticEntries(treeRoot, new Mat4d()); } final ArrayList<Vec3d> totalHullPoints = new ArrayList<>(); // Collect all the points from the hulls of the individual sub meshes for (StaticMeshInstance subInst : _staticMeshInstances) { List<Vec3d> pointsRef = _subMeshesData.get(subInst.subMeshIndex).staticHull.getVertices(); List<Vec3d> subPoints = RenderUtils.transformPointsWithTrans(subInst.transform, pointsRef); totalHullPoints.addAll(subPoints); } // And the lines for (StaticLineInstance subInst : _staticLineInstances) { List<Vec3d> pointsRef = _subLinesData.get(subInst.lineIndex).hull.getVertices(); List<Vec3d> subPoints = RenderUtils.transformPointsWithTrans(subInst.transform, pointsRef); totalHullPoints.addAll(subPoints); } // Now scan the non-static part of the tree TreeWalker walker = new TreeWalker() { public int nextIndex = 0; @Override public void onNode(Mat4d trans, Mat4d InvTrans, TreeNode node) { node.nodeIndex = nextIndex++; numTreeNodes = nextIndex; } @Override public void onMesh(Mat4d trans, Mat4d InvTrans, TreeNode node, AnimMeshInstance inst) { List<Vec3d> pointsRef = _subMeshesData.get(inst.meshIndex).staticHull.getVertices(); List<Vec3d> subPoints = RenderUtils.transformPointsWithTrans(trans, pointsRef); totalHullPoints.addAll(subPoints); inst.nodeIndex = node.nodeIndex; _animMeshInstances.add(inst); } @Override public void onLine(Mat4d trans, Mat4d InvTrans, TreeNode node, AnimLineInstance inst) { List<Vec3d> pointsRef = _subLinesData.get(inst.lineIndex).hull.getVertices(); List<Vec3d> subPoints = RenderUtils.transformPointsWithTrans(trans, pointsRef); totalHullPoints.addAll(subPoints); inst.nodeIndex = node.nodeIndex; _animLineInstances.add(inst); } }; long optStart = System.nanoTime(); boolean changed = true; int numPasses = 0; while (changed) { changed = optimizeTree(treeRoot); ++numPasses; } long optEnd = System.nanoTime(); double optMS = (optEnd - optStart) / 1000000.0; walkTree(walker, treeRoot, new Mat4d(), new Mat4d(), null); int optimTreeNodes = numTreeNodes; if (printDebug) { System.out.printf("Tree optimization - nodes: %d, passes: %d in %fms\n", optimTreeNodes, numPasses, optMS); } _staticHull = ConvexHull.TryBuildHull(totalHullPoints, MAX_HULL_ATTEMPTS, MAX_HULL_POINTS, v3Interner); _defaultBounds = _staticHull.getAABB(new Mat4d()); populateActionList(); if (!keepRuntimeData) { v2Interner = null; // Drop ref to the interner to free memory v3Interner = null; // Drop ref to the interner to free memory v4Interner = null; // Drop ref to the interner to free memory } } private void populateActionList() { final HashMap<String, Double> actionMap = new HashMap<>(); class ActionWalker extends TreeWalker { @Override public void onNode(Mat4d trans, Mat4d invTrans, TreeNode node) { if (node.trans instanceof AnimTrans) { AnimTrans at = (AnimTrans)node.trans; for (Act act : at.actions) { Double existingTime = actionMap.get(act.name); double lastTime = act.times[act.times.length-1]; if (existingTime == null || lastTime > existingTime) { actionMap.put(act.name, lastTime); } } } } } ActionWalker actionWalker = new ActionWalker(); walkTree(actionWalker, treeRoot, new Mat4d(), new Mat4d(), null); _actionDesc = new ArrayList<>(); for (Map.Entry<String, Double> entry : actionMap.entrySet()) { Action.Description desc = new Action.Description(); desc.name = entry.getKey(); desc.duration = entry.getValue(); _actionDesc.add(desc); } } public ConvexHull getHull(Pose pose) { if (pose == null) return _staticHull; // Otherwise, we need to calculate a new hull ArrayList<Vec3d> hullPoints = new ArrayList<>(); for (StaticMeshInstance inst : _staticMeshInstances) { List<Vec3d> pointsRef = _subMeshesData.get(inst.subMeshIndex).staticHull.getVertices(); List<Vec3d> subPoints = RenderUtils.transformPointsWithTrans(inst.transform, pointsRef); hullPoints.addAll(subPoints); } for (AnimMeshInstance inst : _animMeshInstances) { Mat4d animatedTransform = pose.transforms[inst.nodeIndex]; List<Vec3d> pointsRef = _subMeshesData.get(inst.meshIndex).staticHull.getVertices(); List<Vec3d> subPoints = RenderUtils.transformPointsWithTrans(animatedTransform, pointsRef); hullPoints.addAll(subPoints); } // And the lines for (StaticLineInstance inst : _staticLineInstances) { List<Vec3d> pointsRef = _subLinesData.get(inst.lineIndex).hull.getVertices(); List<Vec3d> subPoints = RenderUtils.transformPointsWithTrans(inst.transform, pointsRef); hullPoints.addAll(subPoints); } for (AnimLineInstance inst : _animLineInstances) { Mat4d animatedTransform = pose.transforms[inst.nodeIndex]; List<Vec3d> pointsRef = _subLinesData.get(inst.lineIndex).hull.getVertices(); List<Vec3d> subPoints = RenderUtils.transformPointsWithTrans(animatedTransform, pointsRef); hullPoints.addAll(subPoints); } ConvexHull ret = ConvexHull.TryBuildHull(hullPoints, MAX_HULL_ATTEMPTS, MAX_HULL_POINTS, null); return ret; } public Pose getPose(ArrayList<Action.Queue> actions) { final Pose ret = new Pose(); ret.transforms = new Mat4d[numTreeNodes]; ret.invTransforms = new Mat4d[numTreeNodes]; TreeWalker walker = new TreeWalker() { @Override public void onNode(Mat4d trans, Mat4d invTrans, TreeNode node) { ret.transforms[node.nodeIndex] = trans; ret.invTransforms[node.nodeIndex] = invTrans; // // Debug // Mat4d test = new Mat4d(); // test.mult4(trans, invTrans); // assert(test.nearIdentityThresh3(0.01)); // // test.mult4(invTrans, trans); // assert(test.nearIdentityThresh3(0.01)); } }; walkTree(walker, treeRoot, new Mat4d(), new Mat4d(), actions); return ret; } public AABB getDefaultBounds() { return _defaultBounds; } public ArrayList<Action.Description> getActionDescriptions() { return _actionDesc; } public ArrayList<SubMeshData> getSubMeshData() { return _subMeshesData; } public ArrayList<SubLineData> getSubLineData() { return _subLinesData; } public ArrayList<StaticMeshInstance> getStaticMeshInstances() { return _staticMeshInstances; } public ArrayList<StaticLineInstance> getStaticLineInstances() { return _staticLineInstances; } public ArrayList<AnimMeshInstance> getAnimMeshInstances() { return _animMeshInstances; } public ArrayList<AnimLineInstance> getAnimLineInstances() { return _animLineInstances; } public ArrayList<Material> getMaterials() { return _materials; } public int getNumTriangles() { int numTriangles = 0; for (StaticMeshInstance inst : _staticMeshInstances) { SubMeshData data = _subMeshesData.get(inst.subMeshIndex); numTriangles += data.indices.length / 3; } for (AnimMeshInstance inst : _animMeshInstances) { SubMeshData data = _subMeshesData.get(inst.meshIndex); numTriangles += data.indices.length / 3; } return numTriangles; } public int getNumVertices() { int numVerts = 0; for (SubMeshData data : _subMeshesData) { numVerts += data.verts.size(); } return numVerts; } public int getNumSubInstances() { return _staticMeshInstances.size() + _staticLineInstances.size() + _animMeshInstances.size() + _animLineInstances.size(); } public int getNumSubMeshes() { return _subMeshesData.size() + _subLinesData.size(); } /** * Write the color as 4 bytes, RGBA * @param col * @param b */ private void writeColorToBlock(Color4d col, DataBlock b) { if (col == null) { b.writeInt(0); return; } b.writeByte((byte)(col.r*255)); b.writeByte((byte)(col.g*255)); b.writeByte((byte)(col.b*255)); b.writeByte((byte)(col.a*255)); } private Color4d readColorFromBlock(DataBlock block) { int r = block.readByte() & 0x000000FF; int g = block.readByte() & 0x000000FF; int b = block.readByte() & 0x000000FF; int a = block.readByte() & 0x000000FF; return new Color4d(r/255.0, g/255.0, b/255.0, a/255.0); } /** * Initialize a MeshData from a DataBlock, throws a RenderException if things go sideways. * @param topBlock */ public MeshData(boolean keepRuntimeData, DataBlock topBlock, URL contextURL) { this.keepRuntimeData = keepRuntimeData; DataBlock vectorsBlock = topBlock.findChildByName("VectorLib"); if (vectorsBlock == null) throw new RenderException("Binary MeshData: Missing Vectors Library"); DataBlock v2s = vectorsBlock.findChildByName("Vec2ds"); DataBlock v3s = vectorsBlock.findChildByName("Vec3ds"); DataBlock v4s = vectorsBlock.findChildByName("Vec4ds"); int vec2dSize = (v2s != null) ? v2s.getDataSize() / 16 : 0; Vec2d[] vec2ds = new Vec2d[vec2dSize]; for (int i = 0; i < vec2dSize; ++i) { vec2ds[i] = new Vec2d(v2s.readDouble(), v2s.readDouble()); } int vec3dSize = (v3s != null) ? v3s.getDataSize() / 24 : 0; Vec3d[] vec3ds = new Vec3d[vec3dSize]; for (int i = 0; i < vec3dSize; ++i) { vec3ds[i] = new Vec3d(v3s.readDouble(), v3s.readDouble(), v3s.readDouble()); } int vec4dSize = (v4s != null) ? v4s.getDataSize() / 32 : 0; Vec4d[] vec4ds = new Vec4d[vec4dSize]; for (int i = 0; i < vec4dSize; ++i) { vec4ds[i] = new Vec4d(v4s.readDouble(), v4s.readDouble(), v4s.readDouble(), v4s.readDouble()); } // Build up the sub mesh data DataBlock subMeshesBlock = topBlock.findChildByName("SubMeshes"); for (DataBlock subMeshBlock : subMeshesBlock.getChildren()) { if (!subMeshBlock.getName().equals("SubMeshData")) { continue; } SubMeshData subData = new SubMeshData(); subData.keepRuntimeData = keepRuntimeData; DataBlock vertBlock = subMeshBlock.findChildByName("Vertices"); if (vertBlock == null) throw new RenderException("Missing vertices in submesh"); subData.verts = new ArrayList<>(vertBlock.getDataSize() / 4); for (int i = 0; i < vertBlock.getDataSize() / 4; ++i) { int vertInd = vertBlock.readInt(); subData.verts.add(vec3ds[vertInd]); } DataBlock normBlock = subMeshBlock.findChildByName("Normals"); if (normBlock == null) throw new RenderException("Missing normals in submesh"); subData.normals = new ArrayList<>(normBlock.getDataSize() / 4); for (int i = 0; i < normBlock.getDataSize() / 4; ++i) { int normInd = normBlock.readInt(); subData.normals.add(vec3ds[normInd]); } DataBlock texCoordBlock = subMeshBlock.findChildByName("TexCoords"); if (texCoordBlock != null) { subData.texCoords = new ArrayList<>(texCoordBlock.getDataSize() / 4); for (int i = 0; i < texCoordBlock.getDataSize() / 4; ++i) { int texInd = texCoordBlock.readInt(); subData.texCoords.add(vec2ds[texInd]); } } DataBlock indicesBlock = subMeshBlock.findChildByName("Indices"); if (indicesBlock == null) throw new RenderException("Missing indices in submesh"); subData.indices = new int[indicesBlock.getDataSize() / 4]; for (int i = 0; i < indicesBlock.getDataSize() / 4; ++i) { subData.indices[i] = indicesBlock.readInt(); } DataBlock hullBlock = subMeshBlock.findChildByName("ConvexHull"); if (hullBlock == null) throw new RenderException("Missing hull in submesh"); subData.staticHull = ConvexHull.fromDataBlock(hullBlock, vec3ds); subData.localBounds = subData.staticHull.getAABB(new Mat4d()); _subMeshesData.add(subData); } DataBlock subLinesBlock = topBlock.findChildByName("SubLines"); for (DataBlock subLineBlock : subLinesBlock.getChildren()) { if (!subLineBlock.getName().equals("SubLineData")) { continue; } SubLineData subLine = new SubLineData(); DataBlock vertBlock = subLineBlock.findChildByName("Vertices"); if (vertBlock == null) throw new RenderException("Missing vertices in subline"); subLine.verts = new ArrayList<>(vertBlock.getDataSize() / 4); for (int i = 0; i < vertBlock.getDataSize() / 4; ++i) { int vertInd = vertBlock.readInt(); subLine.verts.add(vec3ds[vertInd]); } DataBlock colorBlock = subLineBlock.findChildByName("Color"); if (colorBlock == null) throw new RenderException("Missing color in subline"); subLine.diffuseColor = readColorFromBlock(colorBlock); subLine.hull = ConvexHull.TryBuildHull(subLine.verts, MAX_HULL_ATTEMPTS, MAX_HULL_POINTS, v3Interner); _subLinesData.add(subLine); } // Add Materials DataBlock matsBlock = topBlock.findChildByName("Materials"); if (matsBlock == null) throw new RenderException("Missing materials block"); for (DataBlock matBlock : matsBlock.getChildren()) { if (!matBlock.getName().equals("Material")) continue; DataBlock colorBlock = matBlock.findChildByName("DifAmbSpecShinTrans"); if (colorBlock == null) throw new RenderException("Missing color block in material"); Color4d diffuse = readColorFromBlock(colorBlock); Color4d ambient = readColorFromBlock(colorBlock); Color4d specular = readColorFromBlock(colorBlock); float shininess = colorBlock.readFloat(); Color4d transColor = readColorFromBlock(colorBlock); int transType = colorBlock.readInt(); URI texURI = null; String texString = null; DataBlock textureBlock = matBlock.findChildByName("DiffuseTexture"); if (textureBlock != null) { texString = textureBlock.readString(); try { texURI = new URL(contextURL, texString).toURI(); } catch (MalformedURLException ex){ throw new RenderException(String.format("Error with texture URL: %s", texString)); } catch (URISyntaxException e) { throw new RenderException(String.format("Error with texture URL: %s", texString)); } } addMaterial(texURI, texString, diffuse, ambient, specular, shininess, transType, transColor); } // Now for the instances DataBlock subMeshInstBlock = topBlock.findChildByName("SubMeshInstances"); if (subMeshInstBlock == null) throw new RenderException("Missing mesh instance block"); for (DataBlock subInstBlock : subMeshInstBlock.getChildren()) { if (!subInstBlock.getName().equals("StaticSubInstance")) continue; DataBlock indexBlock = subInstBlock.findChildByName("Indices"); if (indexBlock == null) throw new RenderException("Missing sub instance indices"); int meshIndex = indexBlock.readInt(); int matIndex = indexBlock.readInt(); DataBlock transBlock = subInstBlock.findChildByName("Transform"); if (transBlock == null) throw new RenderException("Missing sub instance transform"); double[] cmMat = new double[16]; for (int i = 0; i < 16; ++i) { cmMat[i] = transBlock.readDouble(); } Mat4d trans = new Mat4d(cmMat); trans.transpose4(); addStaticMeshInstance(meshIndex, matIndex, trans); } DataBlock subLineInstBlock = topBlock.findChildByName("SubLineInstances"); if (subLineInstBlock == null) throw new RenderException("Missing line instance block"); for (DataBlock subInstBlock : subLineInstBlock.getChildren()) { DataBlock indexBlock = subInstBlock.findChildByName("Indices"); if (indexBlock == null) throw new RenderException("Missing sub instance indices"); int lineIndex = indexBlock.readInt(); DataBlock transBlock = subInstBlock.findChildByName("Transform"); if (transBlock == null) throw new RenderException("Missing sub instance transform"); double[] cmMat = new double[16]; for (int i = 0; i < 16; ++i) { cmMat[i] = transBlock.readDouble(); } Mat4d trans = new Mat4d(cmMat); trans.transpose4(); addStaticLineInstance(lineIndex, trans); } DataBlock animTreeBlock = topBlock.findChildByName("AnimTree"); ArrayList<TreeNode> nodes = new ArrayList<>(); if (animTreeBlock != null) { ArrayList<int[]> childIndices = new ArrayList<>(); // Store child indices until after all nodes have been created int nodeIndex = 0; for (DataBlock child : animTreeBlock.getChildren()) { int numChildren = child.readInt(); int[] childInds = new int[numChildren]; for (int i = 0; i < numChildren; ++i) { childInds[i] = child.readInt(); } childIndices.add(childInds); TreeNode node = new TreeNode(); node.nodeIndex = nodeIndex; nodes.add(node); int numSubMeshes = child.readInt(); for (int i = 0; i < numSubMeshes; ++i) { int meshInd = child.readInt(); int matInd = child.readInt(); AnimMeshInstance ami = new AnimMeshInstance(meshInd, matInd); _animMeshInstances.add(ami); ami.nodeIndex = nodeIndex; node.meshInstances.add(ami); } int numSubLines = child.readInt(); for (int i = 0; i < numSubLines; ++i) { int lineIndex = child.readInt(); AnimLineInstance ali = new AnimLineInstance(lineIndex); _animLineInstances.add(ali); ali.nodeIndex = nodeIndex; node.lineInstances.add(ali); } DataBlock transBlock = child.findChildByName("StaticTrans"); if (transBlock != null) { Mat4d transMat = transBlock.readMat4d(); StaticTrans st = new StaticTrans(transMat); node.trans = st; } else { transBlock = child.findChildByName("AnimTrans"); if (transBlock == null) throw new RenderException("TreeNode missing transform block"); Mat4d staticTrans = transBlock.readMat4d(); ArrayList<Act> actions = new ArrayList<>(); for (DataBlock actBlock : transBlock.getChildren()) { if (!actBlock.getName().equals("Action")) continue; actions.add(readActionBlock(actBlock)); } node.trans = new AnimTrans(actions, staticTrans); } nodeIndex++; } // Now that all nodes exist, populate child references from indices for (int i = 0; i < nodes.size(); ++i) { TreeNode node = nodes.get(i); int[] childInds = childIndices.get(i); for (int ci : childInds) { node.children.add(nodes.get(ci)); } } } // Set the tree root node if (nodes.size() != 0) { treeRoot = nodes.get(0); numTreeNodes= nodes.size(); } else { treeRoot = new TreeNode(); treeRoot.nodeIndex = 0; treeRoot.trans = new StaticTrans(new Mat4d()); numTreeNodes = 1; } DataBlock hullBlock = topBlock.findChildByName("ConvexHull"); if (hullBlock == null) throw new RenderException("Missing mesh convex hull"); _staticHull = ConvexHull.fromDataBlock(hullBlock, vec3ds); _defaultBounds = _staticHull.getAABB(new Mat4d()); if (!keepRuntimeData) { v2Interner = null; // Drop ref to the interner to free memory v3Interner = null; // Drop ref to the interner to free memory v4Interner = null; // Drop ref to the interner to free memory } populateActionList(); } private class ExportTransVisitor implements TransVisitor { private DataBlock transBlock; @Override public void visitStatic(StaticTrans trans) { transBlock = new DataBlock("StaticTrans", 16*8); transBlock.writeMat4d(trans.matrix); } @Override public void visitAnim(AnimTrans trans) { transBlock = new DataBlock("AnimTrans", 16*8); transBlock.writeMat4d(trans.staticMat); for (Act act : trans.actions) { transBlock.addChildBlock(getActionBlock(act)); } } public DataBlock getTransBlock() { return transBlock; } } private DataBlock getActionBlock(Act act) { assert act.times.length == act.matrices.length; byte[] nameBytes = null; try { nameBytes = act.name.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new Error(e.getMessage()); } int blockSize = nameBytes.length + 1 + 4 + act.times.length*(8 + 16*8); DataBlock actBlock = new DataBlock("Action", blockSize); actBlock.writeString(act.name); actBlock.writeInt(act.times.length); for (double time : act.times) { actBlock.writeDouble(time); } for (Mat4d mat : act.matrices) { actBlock.writeMat4d(mat); } return actBlock; } private Act readActionBlock(DataBlock actBlock) { Act ret = new Act(); ret.name = actBlock.readString(); int numKeys = actBlock.readInt(); ret.times = new double[numKeys]; ret.matrices = new Mat4d[numKeys]; ret.invMatrices = new Mat4d[numKeys]; for (int i = 0; i < numKeys; ++i) { ret.times[i] = actBlock.readDouble(); } for (int i = 0; i < numKeys; ++i) { ret.matrices[i] = actBlock.readMat4d(); ret.invMatrices[i] = ret.matrices[i].inverse(); } return ret; } private DataBlock getTreeNodeBlock(TreeNode node) { // The data segment consists of 3 lists of indices int dataSize = 0; dataSize += 4 + 4*node.children.size(); dataSize += 4 + 8*node.meshInstances.size(); dataSize += 4 + 4*node.lineInstances.size(); DataBlock nodeBlock = new DataBlock("TreeNode", dataSize); ExportTransVisitor etv = new ExportTransVisitor(); node.trans.accept(etv); nodeBlock.addChildBlock(etv.getTransBlock()); nodeBlock.writeInt(node.children.size()); for (TreeNode child : node.children) { nodeBlock.writeInt(child.nodeIndex); } nodeBlock.writeInt(node.meshInstances.size()); for (AnimMeshInstance ami : node.meshInstances) { nodeBlock.writeInt(ami.meshIndex); nodeBlock.writeInt(ami.materialIndex); } nodeBlock.writeInt(node.lineInstances.size()); for (AnimLineInstance ali : node.lineInstances) { nodeBlock.writeInt(ali.lineIndex); } return nodeBlock; } /** * Build up a tree of 'DataBlock's and return it. This will return null if the runtime data needed as been discarded * @return */ public DataBlock getDataAsBlock() { if (!keepRuntimeData) { return null; } DataBlock topBlock = new DataBlock("MeshData", 0); DataBlock vectorsBlock = new DataBlock("VectorLib", 0); topBlock.addChildBlock(vectorsBlock); // Write out all the vector data to be indexed later DataBlock vec2Block = new DataBlock("Vec2ds", v2Interner.getMaxIndex() * 16); vectorsBlock.addChildBlock(vec2Block); for (int i = 0; i < v2Interner.getMaxIndex(); ++i) { Vec2d val = v2Interner.getValueForIndex(i); vec2Block.writeDouble(val.x); vec2Block.writeDouble(val.y); } DataBlock vec3Block = new DataBlock("Vec3ds", v3Interner.getMaxIndex() * 24); vectorsBlock.addChildBlock(vec3Block); for (int i = 0; i < v3Interner.getMaxIndex(); ++i) { Vec3d val = v3Interner.getValueForIndex(i); vec3Block.writeDouble(val.x); vec3Block.writeDouble(val.y); vec3Block.writeDouble(val.z); } DataBlock vec4Block = new DataBlock("Vec4ds", v4Interner.getMaxIndex() * 32); vectorsBlock.addChildBlock(vec4Block); for (int i = 0; i < v4Interner.getMaxIndex(); ++i) { Vec4d val = v4Interner.getValueForIndex(i); vec4Block.writeDouble(val.x); vec4Block.writeDouble(val.y); vec4Block.writeDouble(val.z); vec4Block.writeDouble(val.w); } // Sub mesh data DataBlock subMeshes = new DataBlock("SubMeshes", 0); topBlock.addChildBlock(subMeshes); for (SubMeshData subData : _subMeshesData) { DataBlock subDataBlock = new DataBlock("SubMeshData", 0); subMeshes.addChildBlock(subDataBlock); DataBlock subVertsBlock = new DataBlock("Vertices", subData.verts.size() * 4); subDataBlock.addChildBlock(subVertsBlock); for (Vec3d v : subData.verts) { int vecInd = v3Interner.getIndexForValue(v); subVertsBlock.writeInt(vecInd); } DataBlock subNormBlock = new DataBlock("Normals", subData.normals.size() * 4); subDataBlock.addChildBlock(subNormBlock); for (Vec3d v : subData.normals) { int vecInd = v3Interner.getIndexForValue(v); subNormBlock.writeInt(vecInd); } if (subData.texCoords != null) { DataBlock subTexBlock = new DataBlock("TexCoords", subData.texCoords.size() * 4); subDataBlock.addChildBlock(subTexBlock); for (Vec2d v : subData.texCoords) { int vecInd = v2Interner.getIndexForValue(v); subTexBlock.writeInt(vecInd); } } DataBlock indicesBlock = new DataBlock("Indices", subData.indices.length * 4); subDataBlock.addChildBlock(indicesBlock); for (int ind : subData.indices) { indicesBlock.writeInt(ind); } DataBlock hullBlock = subData.staticHull.toDataBlock(v3Interner); subDataBlock.addChildBlock(hullBlock); } // Sub line data DataBlock subLines = new DataBlock("SubLines", 0); topBlock.addChildBlock(subLines); for (SubLineData subData : _subLinesData) { DataBlock subDataBlock = new DataBlock("SubLineData", 0); subLines.addChildBlock(subDataBlock); DataBlock subVertsBlock = new DataBlock("Vertices", subData.verts.size() * 4); subDataBlock.addChildBlock(subVertsBlock); for (Vec3d v : subData.verts) { int vecInd = v3Interner.getIndexForValue(v); subVertsBlock.writeInt(vecInd); } DataBlock colorBlock = new DataBlock("Color", 4); subDataBlock.addChildBlock(colorBlock); writeColorToBlock(subData.diffuseColor, colorBlock); } DataBlock subMInsts = new DataBlock("SubMeshInstances", 0); topBlock.addChildBlock(subMInsts); for (StaticMeshInstance subInst : _staticMeshInstances) { DataBlock staticSubInst = new DataBlock("StaticSubInstance", 0); subMInsts.addChildBlock(staticSubInst); DataBlock indices = new DataBlock("Indices", 8); staticSubInst.addChildBlock(indices); indices.writeInt(subInst.subMeshIndex); indices.writeInt(subInst.materialIndex); DataBlock transBlock = new DataBlock("Transform", 128); staticSubInst.addChildBlock(transBlock); double[] transCMData = subInst.transform.toCMDataArray(); for (int i = 0; i < 16; ++i) { transBlock.writeDouble(transCMData[i]); } } DataBlock subLInsts = new DataBlock("SubLineInstances", 0); topBlock.addChildBlock(subLInsts); for (StaticLineInstance subInst : _staticLineInstances) { DataBlock subLineInst = new DataBlock("SubLineInstance", 0); subLInsts.addChildBlock(subLineInst); DataBlock indices = new DataBlock("Indices", 4); subLineInst.addChildBlock(indices); indices.writeInt(subInst.lineIndex); DataBlock transBlock = new DataBlock("Transform", 128); subLineInst.addChildBlock(transBlock); double[] transCMData = subInst.transform.toCMDataArray(); for (int i = 0; i < 16; ++i) { transBlock.writeDouble(transCMData[i]); } } final DataBlock animTreeBlock = new DataBlock("AnimTree", 0); topBlock.addChildBlock(animTreeBlock); class ExportWalker extends TreeWalker { @Override public void onNode(Mat4d trans, Mat4d invTrans, TreeNode node) { DataBlock nodeBlock = getTreeNodeBlock(node); animTreeBlock.addChildBlock(nodeBlock); } } // Walk the tree in order, so that the nodes are exported in index order walkTree(new ExportWalker(), treeRoot, new Mat4d(), new Mat4d(), null); // Materials DataBlock matsBlock = new DataBlock("Materials", 0); topBlock.addChildBlock(matsBlock); for (Material mat : _materials) { DataBlock matBlock = new DataBlock("Material", 0); matsBlock.addChildBlock(matBlock); DataBlock colors = new DataBlock("DifAmbSpecShinTrans", 24); matBlock.addChildBlock(colors); writeColorToBlock(mat.diffuseColor, colors); writeColorToBlock(mat.ambientColor, colors); writeColorToBlock(mat.specColor, colors); colors.writeFloat((float)mat.shininess); writeColorToBlock(mat.transColour, colors); colors.writeInt(mat.transType); if (mat.colorTex != null) { String texString = mat.relColorTex; DataBlock texBlock = new DataBlock("DiffuseTexture", texString.length()*4 + 1); // Worst case size matBlock.addChildBlock(texBlock); texBlock.writeString(texString); } } DataBlock hullBlock = _staticHull.toDataBlock(v3Interner); topBlock.addChildBlock(hullBlock); return topBlock; } /** * Returns an array of all the used shaders for this MeshData * @return */ public int[] getUsedMeshShaders() { ArrayList<Integer> shaderIDs = new ArrayList<>(); for (StaticMeshInstance smi : _staticMeshInstances) { int shaderID = _materials.get(smi.materialIndex).getShaderID(); if (!shaderIDs.contains(shaderID)) shaderIDs.add(shaderID); } for (AnimMeshInstance ami : _animMeshInstances) { int shaderID = _materials.get(ami.materialIndex).getShaderID(); if (!shaderIDs.contains(shaderID)) shaderIDs.add(shaderID); } int[] ret = new int[shaderIDs.size()]; for (int i = 0; i < shaderIDs.size(); ++i) { ret[i] = shaderIDs.get(i); } return ret; } }