/* * 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.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.SAXParserFactory; import com.jaamsim.math.Color4d; import com.jaamsim.math.Mat4d; import com.jaamsim.math.Quaternion; import com.jaamsim.math.Vec2d; import com.jaamsim.math.Vec3d; import com.jaamsim.math.Vec4d; import com.jaamsim.render.Action; import com.jaamsim.render.Armature; import com.jaamsim.render.RenderException; import com.jaamsim.ui.LogBox; import com.jaamsim.xml.XmlNode; import com.jaamsim.xml.XmlParser; // TODO: Delete this whole class, this is now deprecated public class MeshReader { public static MeshData parse(URI asset) throws RenderException { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setValidating(false); try { MeshReader reader = new MeshReader(asset.toURL()); reader.processContent(); return reader.getMeshData(); } catch (Exception e) { LogBox.renderLogException(e); throw new RenderException(e.getMessage()); } } private static void parseAssert(boolean b) { if (!b) { throw new RenderException("Failed JSM parsing assert"); } } private XmlParser _parser; private final URL contentURL; private MeshData finalData; private XmlNode _meshObjectNode; public MeshReader(URL asset) { contentURL = asset; } static final List<String> DOUBLE_ARRAY_TAGS; static final List<String> INT_ARRAY_TAGS; static final List<String> STRING_ARRAY_TAGS; static final List<String> BOOLEAN_ARRAY_TAGS; static { DOUBLE_ARRAY_TAGS = new ArrayList<>(); DOUBLE_ARRAY_TAGS.add("Positions"); DOUBLE_ARRAY_TAGS.add("Normals"); DOUBLE_ARRAY_TAGS.add("TexCoords"); DOUBLE_ARRAY_TAGS.add("BoneIndices"); DOUBLE_ARRAY_TAGS.add("BoneWeights"); DOUBLE_ARRAY_TAGS.add("Color"); DOUBLE_ARRAY_TAGS.add("Matrix"); DOUBLE_ARRAY_TAGS.add("T"); DOUBLE_ARRAY_TAGS.add("W"); DOUBLE_ARRAY_TAGS.add("X"); DOUBLE_ARRAY_TAGS.add("Y"); DOUBLE_ARRAY_TAGS.add("Z"); INT_ARRAY_TAGS = new ArrayList<>(); INT_ARRAY_TAGS.add("Faces"); STRING_ARRAY_TAGS = new ArrayList<>(); STRING_ARRAY_TAGS.add("BoneNames"); BOOLEAN_ARRAY_TAGS = new ArrayList<>(); } private void processContent() { _parser = new XmlParser(contentURL); _parser.setDoubleArrayTags(DOUBLE_ARRAY_TAGS); _parser.setIntArrayTags(INT_ARRAY_TAGS); _parser.setBooleanArrayTags(BOOLEAN_ARRAY_TAGS); _parser.setStringArrayTags(STRING_ARRAY_TAGS); _parser.parse(); finalData = new MeshData(false); _meshObjectNode = _parser.getRootNode().findChildTag("MeshObject", false); parseAssert(_meshObjectNode != null); parseGeometries(); parseMaterials(); parseArmatures(); parseInstances(); MeshData.TreeNode emptyTree = new MeshData.TreeNode(); emptyTree.trans = new MeshData.StaticTrans(new Mat4d()); finalData.setTree(emptyTree); finalData.finalizeData(); } private MeshData getMeshData() { return finalData; } private void parseGeometries() { XmlNode geosNode = _meshObjectNode.findChildTag("Geometries", false); for (XmlNode child : geosNode.children()) { if (!child.getTag().equals("Geometry")) { continue; } parseGeometry(child); } } private void parseMaterials() { XmlNode matsNode = _meshObjectNode.findChildTag("Materials", false); for (XmlNode child : matsNode.children()) { if (!child.getTag().equals("Material")) { continue; } parseMaterial(child); } } private void parseArmatures() { XmlNode armsNode = _meshObjectNode.findChildTag("Armatures", false); if (armsNode == null) { return; // Armatures are optional } for (XmlNode child : armsNode.children()) { if (!child.getTag().equals("Armature")) { continue; } parseArmature(child); } } private void parseInstances() { XmlNode instNode = _meshObjectNode.findChildTag("MeshInstances", false); for (XmlNode child : instNode.children()) { if (!child.getTag().equals("MeshInstance")) { continue; } parseInstance(child); } } private void parseGeometry(XmlNode geoNode) { int numVerts = Integer.parseInt(geoNode.getAttrib("vertices")); // Start by parsing vertices XmlNode vertsNode = geoNode.findChildTag("Positions", false); parseAssert(vertsNode != null); parseAssert(vertsNode.getAttrib("dims").equals("3")); double[] positions = (double[])vertsNode.getContent(); parseAssert(positions.length == numVerts * 3); XmlNode normNode = geoNode.findChildTag("Normals", false); parseAssert(normNode != null); parseAssert(normNode.getAttrib("dims").equals("3")); double[] normals = (double[])normNode.getContent(); parseAssert(normals.length == numVerts * 3); XmlNode texCoordNode = geoNode.findChildTag("TexCoords", false); double[] texCoords = null; boolean hasTex = false; if (texCoordNode != null) { parseAssert(texCoordNode.getAttrib("dims").equals("2")); texCoords = (double[])texCoordNode.getContent(); parseAssert(texCoords.length == numVerts * 2); hasTex = true; } XmlNode boneIndicesNode = geoNode.findChildTag("BoneIndices", false); XmlNode boneWeightsNode = geoNode.findChildTag("BoneWeights", false); double[] boneIndices = null; double[] boneWeights = null; boolean hasBoneInfo = false; int numBoneWeights = 0; if (boneIndicesNode != null) { numBoneWeights = Integer.parseInt(boneIndicesNode.getAttrib("entriesPerVert")); parseAssert(numBoneWeights == Integer.parseInt(boneWeightsNode.getAttrib("entriesPerVert"))); // Make sure these are the same parseAssert(numBoneWeights <= 4); // TODO handle more than this by discarding extras and renormalizing boneIndices = (double[])boneIndicesNode.getContent(); boneWeights = (double[])boneWeightsNode.getContent(); parseAssert(boneIndices.length == numBoneWeights * numVerts); parseAssert(boneWeights.length == numBoneWeights * numVerts); hasBoneInfo = true; } // Finally get the indices XmlNode faceNode = geoNode.findChildTag("Faces", false); parseAssert(faceNode != null); int numTriangles = Integer.parseInt(faceNode.getAttrib("count")); parseAssert(faceNode.getAttrib("type").equals("Triangles")); int[] indices = (int[])faceNode.getContent(); parseAssert(numTriangles*3 == indices.length); ArrayList<Vertex> verts = new ArrayList<>(numVerts); for (int i = 0; i < numVerts; ++i) { Vec3d posVec = new Vec3d(positions[i*3+0], positions[i*3+1], positions[i*3+2]); Vec3d normVec = new Vec3d(normals[i*3+0], normals[i*3+1], normals[i*3+2]); Vec2d texCoordVec = null; Vec4d boneIndicesVec = null; Vec4d boneWeightsVec = null; if (hasTex) { texCoordVec = new Vec2d(texCoords[i*2+0], texCoords[i*2+1]); } if (hasBoneInfo) { boneIndicesVec = new Vec4d(0, 0, 0, 0); boneWeightsVec = new Vec4d(0, 0, 0, 0); if (numBoneWeights >= 1) { boneIndicesVec.x = boneIndices[i*numBoneWeights + 0]; boneWeightsVec.x = boneWeights[i*numBoneWeights + 0]; } if (numBoneWeights >= 2) { boneIndicesVec.y = boneIndices[i*numBoneWeights + 1]; boneWeightsVec.y = boneWeights[i*numBoneWeights + 1]; } if (numBoneWeights >= 3) { boneIndicesVec.z = boneIndices[i*numBoneWeights + 2]; boneWeightsVec.z = boneWeights[i*numBoneWeights + 2]; } if (numBoneWeights >= 4) { boneIndicesVec.w = boneIndices[i*numBoneWeights + 3]; boneWeightsVec.w = boneWeights[i*numBoneWeights + 3]; } } verts.add(new Vertex(posVec, normVec, texCoordVec, boneIndicesVec, boneWeightsVec)); } finalData.addSubMesh(verts, indices); } private void parseMaterial(XmlNode matNode) { XmlNode diffuseNode = matNode.findChildTag("Diffuse", false); parseAssert(diffuseNode != null); XmlNode textureNode = diffuseNode.findChildTag("Texture", false); if (textureNode != null) { parseAssert(textureNode.getAttrib("coordIndex").equals("0")); String file = (String)textureNode.getContent(); try { URI texURI = new URL(contentURL, file).toURI(); finalData.addMaterial(texURI, file, null, null, null, 1, MeshData.NO_TRANS, null); return; } catch (MalformedURLException ex) { parseAssert(false); } catch (URISyntaxException e) { parseAssert(false); } } // Not a texture so it must be a color XmlNode colorNode = diffuseNode.findChildTag("Color", false); parseAssert(colorNode != null); double[] c = (double[])colorNode.getContent(); parseAssert(c.length == 4); Color4d color = new Color4d(c[0], c[1], c[2], c[3]); finalData.addMaterial(null, null, color, null, null, 1, MeshData.NO_TRANS, null); } private void parseBone(XmlNode boneNode, Armature arm, String parentName) { String name = boneNode.getAttrib("name"); double length = Double.parseDouble(boneNode.getAttrib("length")); XmlNode matNode = boneNode.findChildTag("Matrix", false); Mat4d mat = nodeToMat4d(matNode); arm.addBone(name, mat, parentName, length); for (XmlNode child : boneNode.children()) { if (!child.getTag().equals("Bone")) { continue; } parseBone(child, arm, name); } } private void parseKeys(XmlNode node, Action.Channel chan, boolean isTrans) { XmlNode tNode = node.findChildTag("T", false); XmlNode xNode = node.findChildTag("X", false); XmlNode yNode = node.findChildTag("Y", false); XmlNode zNode = node.findChildTag("Z", false); parseAssert(tNode != null); parseAssert(xNode != null); parseAssert(yNode != null); parseAssert(zNode != null); double[] ts = (double[])tNode.getContent(); double[] xs = (double[])xNode.getContent(); double[] ys = (double[])yNode.getContent(); double[] zs = (double[])zNode.getContent(); parseAssert(ts.length == xs.length); parseAssert(ts.length == ys.length); parseAssert(ts.length == zs.length); double [] ws = null; if (!isTrans) { XmlNode wNode = node.findChildTag("W", false); parseAssert(wNode != null); ws = (double[])wNode.getContent(); parseAssert(ts.length == ws.length); } for (int i = 0; i < ts.length; ++i) { if (!isTrans) { Action.RotKey rk = new Action.RotKey(); rk.time = ts[i]; rk.rot = new Quaternion(xs[i], ys[i], zs[i], ws[i]); chan.rotKeys.add(rk); } else { Action.TransKey tk = new Action.TransKey(); tk.time = ts[i]; tk.trans = new Vec3d(xs[i], ys[i], zs[i]); chan.transKeys.add(tk); } } } private void populateChannel(XmlNode node, Action.Channel chan) { XmlNode rotNode = node.findChildTag("Rotation", false); if (rotNode != null) { chan.rotKeys = new ArrayList<>(); parseKeys(rotNode, chan, false); } XmlNode transNode = node.findChildTag("Location", false); if (transNode != null) { chan.transKeys = new ArrayList<>(); parseKeys(transNode, chan, true); } } private Action.Channel parseGroup(XmlNode groupNode) { Action.Channel chan = new Action.Channel(); chan.name = groupNode.getAttrib("name"); parseAssert(chan.name != null); populateChannel(groupNode, chan); return chan; } private Action parseAction(XmlNode actionNode) { Action act = new Action(); act.name = actionNode.getAttrib("name"); act.duration = Double.parseDouble(actionNode.getAttrib("length")); parseAssert(act.name != null); for (XmlNode child : actionNode.children()) { if (!child.getTag().equals("Group")) { continue; } Action.Channel channel = parseGroup(child); act.channels.add(channel); } return act; } private void parseArmature(XmlNode armNode) { Armature arm = new Armature(); for (XmlNode child : armNode.children()) { if (!child.getTag().equals("Bone")) { continue; } parseBone(child, arm, null); } // Now parse the actions for this armature for (XmlNode child : armNode.children()) { if (!child.getTag().equals("Action")) { continue; } Action act = parseAction(child); arm.addAction(act); } } private Action parseInstAction(XmlNode actNode) { Action act = new Action(); act.name = actNode.getAttrib("name"); act.duration = Double.parseDouble(actNode.getAttrib("length")); parseAssert(act.name != null); // Submesh Instance actions can only have a single channel act.channels = new ArrayList<>(1); Action.Channel chan = new Action.Channel(); populateChannel(actNode, chan); act.channels.add(chan); return act; } private void parseInstance(XmlNode instNode) { int geoIndex = Integer.parseInt(instNode.getAttrib("geoIndex")); int matIndex = Integer.parseInt(instNode.getAttrib("matIndex")); XmlNode matrixNode = instNode.findChildTag("Matrix", false); parseAssert(matrixNode != null); Mat4d mat = nodeToMat4d(matrixNode); int armIndex = -1; String armIndString = instNode.getAttrib("armIndex"); if (armIndString != null) { armIndex = Integer.parseInt(armIndString); } XmlNode boneNamesNode = instNode.findChildTag("BoneNames", false); String[] boneNames = null; if (boneNamesNode != null) { boneNames = (String[])boneNamesNode.getContent(); } // Iff we have an armature, we also have bone names parseAssert((armIndex==-1) == (boneNames==null)); XmlNode actionsNode = instNode.findChildTag("Actions", false); if (actionsNode != null) { for (XmlNode child : actionsNode.children()) { if (!child.getTag().equals("Action")) { continue; } Action act = parseInstAction(child); parseAssert(act.channels.size() == 1); // Sub instance key frames can only have one channel } } finalData.addStaticMeshInstance(geoIndex, matIndex, mat); } private Mat4d nodeToMat4d(XmlNode node) { double[] matDoubles = (double[])node.getContent(); parseAssert(matDoubles.length == 16); Mat4d mat = new Mat4d(matDoubles); mat.transpose4(); return mat; } }