/*
* 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;
}
}