1 /* 2 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.javafx.experiments.importers.maya; 33 34 import com.javafx.experiments.importers.SmoothingGroups; 35 36 import java.io.File; 37 import java.net.MalformedURLException; 38 import java.net.URL; 39 import java.util.ArrayList; 40 import java.util.Collection; 41 import java.util.HashMap; 42 import java.util.LinkedList; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.TreeMap; 46 import java.util.logging.Level; 47 import java.util.logging.Logger; 48 49 import javafx.animation.Interpolator; 50 import javafx.animation.KeyFrame; 51 import javafx.animation.KeyValue; 52 import javafx.beans.property.DoubleProperty; 53 import javafx.scene.DepthTest; 54 import javafx.scene.Group; 55 import javafx.scene.Node; 56 import javafx.scene.image.Image; 57 import javafx.scene.paint.Color; 58 import javafx.scene.paint.PhongMaterial; 59 import javafx.scene.shape.CullFace; 60 import javafx.scene.shape.Mesh; 61 import javafx.scene.shape.MeshView; 62 import javafx.scene.shape.TriangleMesh; 63 import javafx.scene.transform.Affine; 64 import javafx.util.Duration; 65 66 import com.javafx.experiments.importers.maya.parser.MParser; 67 import com.javafx.experiments.importers.maya.values.MArray; 68 import com.javafx.experiments.importers.maya.values.MBool; 69 import com.javafx.experiments.importers.maya.values.MCompound; 70 import com.javafx.experiments.importers.maya.values.MData; 71 import com.javafx.experiments.importers.maya.values.MFloat; 72 import com.javafx.experiments.importers.maya.values.MFloat2Array; 73 import com.javafx.experiments.importers.maya.values.MFloat3; 74 import com.javafx.experiments.importers.maya.values.MFloat3Array; 75 import com.javafx.experiments.importers.maya.values.MFloatArray; 76 import com.javafx.experiments.importers.maya.values.MInt; 77 import com.javafx.experiments.importers.maya.values.MInt3Array; 78 import com.javafx.experiments.importers.maya.values.MIntArray; 79 import com.javafx.experiments.importers.maya.values.MPolyFace; 80 import com.javafx.experiments.importers.maya.values.MString; 81 import com.javafx.experiments.shape3d.PolygonMesh; 82 import com.javafx.experiments.shape3d.PolygonMeshView; 83 import com.javafx.experiments.shape3d.SkinningMesh; 84 import com.sun.javafx.geom.Vec3f; 85 86 import java.util.HashSet; 87 import java.util.Set; 88 import java.util.Arrays; 89 90 import javafx.animation.AnimationTimer; 91 import javafx.scene.Parent; 92 93 /** Loader */ 94 class Loader { 95 public static final boolean DEBUG = false; 96 public static final boolean WARN = false; 97 98 MEnv env; 99 100 int startFrame; 101 int endFrame; 102 103 MNodeType transformType; 104 MNodeType jointType; 105 MNodeType meshType; 106 MNodeType cameraType; 107 MNodeType animCurve; 108 MNodeType animCurveTA; 109 MNodeType animCurveUA; 110 MNodeType animCurveUL; 111 MNodeType animCurveUT; 112 MNodeType animCurveUU; 113 114 MNodeType lambertType; 115 MNodeType reflectType; 116 MNodeType blinnType; 117 MNodeType phongType; 118 MNodeType fileType; 119 MNodeType skinClusterType; 120 MNodeType blendShapeType; 121 MNodeType groupPartsType; 122 MNodeType shadingEngineType; 123 124 // [Note to Alex]: I've re-enabled joints, but lets not use rootJoint [John] 125 // Joint rootJoint; //NO_JOINTS 126 Map<MNode, Node> loaded = new HashMap<MNode, Node>(); 127 128 Map<Float, List<KeyValue>> keyFrameMap = new TreeMap(); 129 130 Map<Node, MNode> meshParents = new HashMap(); 131 132 private MFloat3Array mVerts; 133 // Optionally force per-face per-vertex normal generation 134 private int[] edgeData; 135 136 private List<MData> uvSet; 137 private int uvChannel; 138 private MFloat3Array mPointTweaks; 139 private URL url; 140 private boolean asPolygonMesh; 141 142 //========================================================================= 143 // Loader.load 144 //------------------------------------------------------------------------- 145 // Called from MayaImporter.load 146 //========================================================================= 147 public void load(URL url, boolean asPolygonMesh) { 148 this.url = url; 149 this.asPolygonMesh = asPolygonMesh; 150 env = new MEnv(); 151 MParser parser = new MParser(env); 152 try { 153 parser.parse(url); 154 loadModel(); 155 for (MNode n : env.getNodes()) { 156 // System.out.println("____________________________________________________________"); 157 // System.out.println("==> .......Node: " + n); 158 resolveNode(n); 159 } 160 } catch (Exception e) { 161 if (WARN) System.err.println("Error loading url: [" + url + "]"); 162 throw new RuntimeException(e); 163 } 164 } 165 166 //========================================================================= 167 // Loader.loadModel 168 //========================================================================= 169 void loadModel() { 170 startFrame = (int) Math.round(env.getPlaybackStart() - 1); 171 endFrame = (int) Math.round(env.getPlaybackEnd() - 1); 172 transformType = env.findNodeType("transform"); 173 jointType = env.findNodeType("joint"); 174 meshType = env.findNodeType("mesh"); 175 cameraType = env.findNodeType("camera"); 176 animCurve = env.findNodeType("animCurve"); 177 animCurveTA = env.findNodeType("animCurveTA"); 178 animCurveUA = env.findNodeType("animCurveUA"); 179 animCurveUL = env.findNodeType("animCurveUL"); 180 animCurveUT = env.findNodeType("animCurveUT"); 181 animCurveUU = env.findNodeType("animCurveUU"); 182 183 lambertType = env.findNodeType("lambert"); 184 reflectType = env.findNodeType("reflect"); 185 blinnType = env.findNodeType("blinn"); 186 phongType = env.findNodeType("phong"); 187 fileType = env.findNodeType("file"); 188 skinClusterType = env.findNodeType("skinCluster"); 189 groupPartsType = env.findNodeType("groupParts"); 190 shadingEngineType = env.findNodeType("shadingEngine"); 191 blendShapeType = env.findNodeType("blendShape"); 192 } 193 194 //========================================================================= 195 // Loader.resolveNode 196 //------------------------------------------------------------------------- 197 // Loader.resolveNode looks up MNode in the HashMap Map<MNode, Node> loaded 198 // and returns the Node to which this map maps the MNode. 199 // Also, if the node that its looking up hasn't been processed yet, 200 // it processes the node. 201 //========================================================================= 202 Node resolveNode(MNode n) { 203 // System.out.println("--> resolveNode: " + n); 204 // if the node hasn't already been processed, then process the node 205 if (!loaded.containsKey(n)) { 206 // System.out.println("--> containsKey: " + n); 207 processNode(n); 208 // System.out.println(" loaded.get(n) " + loaded.get(n)); 209 } 210 return loaded.get(n); 211 } 212 213 //========================================================================= 214 // Loader.processNode 215 //========================================================================= 216 void processNode(MNode n) { 217 Group parentNode = null; 218 for (MNode p : n.getParentNodes()) { 219 parentNode = (Group) resolveNode(p); 220 } 221 Node result = loaded.get(n); 222 // if the result is null, then it hasn't been added to the map yet 223 // so go ahead and process it 224 if (result == null) { 225 if (n.isInstanceOf(shadingEngineType)) { 226 // System.out.println("==> Found a node of shadingEngineType: " + n); 227 } else if (n.isInstanceOf(lambertType)) { 228 // System.out.println("==> Found a node of lambertType: " + n); 229 } else if (n.isInstanceOf(reflectType)) { 230 // System.out.println("==> Found a node of reflectType: " + n); 231 } else if (n.isInstanceOf(blinnType)) { 232 // System.out.println("==> Found a node of blinnType: " + n); 233 } else if (n.isInstanceOf(phongType)) { 234 // System.out.println("==> Found a node of phongType: " + n); 235 } else if (n.isInstanceOf(fileType)) { 236 // System.out.println("==> Found a node of fileType: " + n); 237 } else if (n.isInstanceOf(skinClusterType)) { 238 processClusterType(n); 239 } else if (n.isInstanceOf(meshType)) { 240 processMeshType(n, parentNode); 241 } else if (n.isInstanceOf(jointType)) { 242 processJointType(n, parentNode); 243 } else if (n.isInstanceOf(transformType)) { 244 processTransformType(n, parentNode); 245 } else if (n.isInstanceOf(animCurve)) { 246 processAnimCurve(n); 247 } 248 } 249 } 250 251 protected void processClusterType(MNode n) { 252 loaded.put(n, null); 253 MArray ma = (MArray) n.getAttr("ma"); 254 255 List<Joint> jointNodes = new ArrayList<Joint>(); 256 Set<Parent> jointForest = new HashSet<Parent>(); // root's children that have joints in their trees 257 for (int i = 0; i < ma.getSize(); i++) { 258 // hack... ? 259 MNode c = n.getIncomingConnectionToType("ma[" + i + "]", "joint"); 260 Joint jn = (Joint) resolveNode(c); 261 jointNodes.add(jn); 262 263 Parent rootChild = jn; // root's child, which is an ancestor of joint jn 264 while (rootChild.getParent() != null) { 265 rootChild = rootChild.getParent(); 266 } 267 jointForest.add(rootChild); 268 } 269 270 MNode outputMeshMNode = resolveOutputMesh(n); 271 MNode inputMeshMNode = resolveInputMesh(n); 272 if (inputMeshMNode == null || outputMeshMNode == null) { 273 return; 274 } 275 // We must be able to find the original converter in the meshConverters map 276 MNode origOrigMesh = resolveOrigInputMesh(n); 277 // println("ORIG ORIG={origOrigMesh}"); 278 279 // TODO: What is with this? origMesh 280 resolveNode(origOrigMesh).setVisible(false); 281 282 MArray bindPreMatrixArray = (MArray) n.getAttr("pm"); 283 Affine bindGlobalMatrix = convertMatrix((MFloatArray) n.getAttr("gm")); 284 285 Affine[] bindPreMatrix = new Affine[bindPreMatrixArray.getSize()]; 286 for (int i = 0; i < bindPreMatrixArray.getSize(); i++) { 287 bindPreMatrix[i] = convertMatrix((MFloatArray) bindPreMatrixArray.getData(i)); 288 } 289 290 MArray mayaWeights = (MArray) n.getAttr("wl"); 291 float[][] weights = new float [jointNodes.size()][mayaWeights.getSize()]; 292 for (int i=0; i<mayaWeights.getSize(); i++) { 293 MFloatArray curWeights = (MFloatArray) mayaWeights.getData(i).getData("w"); 294 for (int j = 0; j < jointNodes.size(); j++) { 295 weights[j][i] = j < curWeights.getSize() ? curWeights.get(j) : 0; 296 } 297 } 298 299 Node sourceMayaMeshNode = resolveNode(inputMeshMNode); 300 Node targetMayaMeshNode = resolveNode(outputMeshMNode); 301 302 if (sourceMayaMeshNode.getClass().equals(PolygonMeshView.class)) { 303 PolygonMeshView sourceMayaMeshView = (PolygonMeshView) sourceMayaMeshNode; 304 PolygonMeshView targetMayaMeshView = (PolygonMeshView) targetMayaMeshNode; 305 306 PolygonMesh sourceMesh = (PolygonMesh) sourceMayaMeshView.getMesh(); 307 SkinningMesh targetMesh = new SkinningMesh(sourceMesh, weights, bindPreMatrix, bindGlobalMatrix, jointNodes, new ArrayList(jointForest)); 308 targetMayaMeshView.setMesh(targetMesh); 309 310 final SkinningMeshTimer skinningMeshTimer = new SkinningMeshTimer(targetMesh); 311 if (targetMayaMeshNode.getScene() != null) { 312 skinningMeshTimer.start(); 313 } 314 targetMayaMeshView.sceneProperty().addListener((observable, oldValue, newValue) -> { 315 if (newValue == null) { 316 skinningMeshTimer.stop(); 317 } else { 318 skinningMeshTimer.start(); 319 } 320 }); 321 } else { 322 Logger.getLogger(MayaImporter.class.getName()).log(Level.INFO, "Mesh skinning is not supported for triangle meshes. Select the 'Load as Polygons' option to load the mesh as polygon mesh."); 323 MeshView sourceMayaMeshView = (MeshView) sourceMayaMeshNode; 324 MeshView targetMayaMeshView = (MeshView) targetMayaMeshNode; 325 TriangleMesh sourceMesh = (TriangleMesh) sourceMayaMeshView.getMesh(); 326 TriangleMesh targetMesh = (TriangleMesh) targetMayaMeshView.getMesh(); 327 targetMesh.getPoints().setAll(sourceMesh.getPoints()); 328 targetMesh.getTexCoords().setAll(sourceMesh.getTexCoords()); 329 targetMesh.getFaces().setAll(sourceMesh.getFaces()); 330 targetMesh.getFaceSmoothingGroups().setAll(sourceMesh.getFaceSmoothingGroups()); 331 } 332 } 333 334 private class SkinningMeshTimer extends AnimationTimer { 335 private SkinningMesh mesh; 336 SkinningMeshTimer(SkinningMesh mesh) { 337 this.mesh = mesh; 338 } 339 @Override 340 public void handle(long l) { 341 mesh.update(); 342 } 343 } 344 345 protected Image loadImageFromFtnAttr(MNode fileNode, String name) { 346 Image image = null; 347 MString fileName = (MString) fileNode.getAttr("ftn"); 348 String imageFilename = (String) fileName.get(); 349 try { 350 File file = new File(imageFilename); 351 String filePath; 352 if (file.exists()) { 353 filePath = file.toURI().toString(); 354 } else { 355 filePath = new URL(url, imageFilename).toString(); 356 } 357 image = new Image(filePath); 358 if (DEBUG) { 359 System.out.println(name + " = " + filePath); 360 System.out.println(name + " w = " + image.getWidth() + " h = " + image.getHeight()); 361 } 362 } catch (MalformedURLException ex) { 363 Logger.getLogger(MayaImporter.class.getName()).log(Level.SEVERE, "Failed to load " + name + " '" + imageFilename + "'!", ex); 364 } 365 return image; 366 } 367 368 protected void processMeshType(MNode n, Group parentNode) throws RuntimeException { 369 //============================================================= 370 // When JavaFX supports polygon mesh geometry, 371 // add the polygon mesh geometry here. 372 // Until then, add a unit square as a placeholder. 373 //============================================================= 374 Node node = resolveNode(n.getParentNodes().get(0)); 375 // if (node != null) { 376 // if (node != null && !n.getName().endsWith("Orig")) { 377 // Original approach to mesh placeholder: 378 // meshParents.put(node, n); 379 380 // Try to find an image or color from n (MNode) 381 if (DEBUG) { System.out.println("________________________________________"); } 382 if (DEBUG) { System.out.println("n.getName(): " + n.getName()); } 383 if (DEBUG) { System.out.println("n.getNodeType(): " + n.getNodeType()); } 384 MNode shadingGroup = n.getOutgoingConnectionToType("iog", "shadingEngine", true); 385 MNode mat; 386 MNode mFile; 387 if (DEBUG) { System.out.println("shadingGroup: " + shadingGroup); } 388 389 MFloat3 mColor; 390 Vec3f diffuseColor = null; 391 Vec3f specularColor = null; 392 393 Image diffuseImage = null; 394 Image normalImage = null; 395 Image specularImage = null; 396 Float specularPower = null; 397 398 if (shadingGroup != null) { 399 mat = shadingGroup.getIncomingConnectionToType("ss", "lambert"); 400 if (mat != null) { 401 // shader = shaderMap.get(mat.getName()) as FixedFunctionShader; 402 if (DEBUG) { System.out.println("lambert mat: " + mat); } 403 mColor = (MFloat3) mat.getAttr("c"); 404 float diffuseIntensity = ((MFloat) mat.getAttr("dc")).get(); 405 if (mColor != null) { 406 diffuseColor = new Vec3f( 407 mColor.get()[0] * diffuseIntensity, 408 mColor.get()[1] * diffuseIntensity, 409 mColor.get()[2] * diffuseIntensity); 410 if (DEBUG) { System.out.println("diffuseColor = " + diffuseColor); } 411 } 412 413 mFile = mat.getIncomingConnectionToType("c", "file"); 414 if (mFile != null) { 415 diffuseImage = loadImageFromFtnAttr(mFile, "diffuseImage"); 416 } 417 MNode bump2d = mat.getIncomingConnectionToType("n", "bump2d"); 418 if (bump2d != null) { 419 mFile = bump2d.getIncomingConnectionToType("bv", "file"); 420 if (mFile != null) { 421 normalImage = loadImageFromFtnAttr(mFile, "normalImage"); 422 } 423 } 424 } 425 mat = shadingGroup.getIncomingConnectionToType("ss", "phong"); 426 if (mat != null) { 427 // shader = shaderMap.get(mat.getName()) as FixedFunctionShader; 428 if (DEBUG) { System.out.println("phong mat: " + mat); } 429 mColor = (MFloat3) mat.getAttr("sc"); 430 if (mColor != null) { 431 specularColor = new Vec3f( 432 mColor.get()[0], 433 mColor.get()[1], 434 mColor.get()[2]); 435 if (DEBUG) { System.out.println("specularColor = " + specularColor); } 436 } 437 mFile = mat.getIncomingConnectionToType("sc", "file"); 438 if (mFile != null) { 439 specularImage = loadImageFromFtnAttr(mFile, "specularImage"); 440 } 441 442 specularPower = ((MFloat) mat.getAttr("cp")).get(); 443 if (DEBUG) { System.out.println("specularPower = " + specularPower); } 444 } 445 } 446 447 PhongMaterial material = new PhongMaterial(); 448 449 if (diffuseImage != null) { 450 material.setDiffuseMap(diffuseImage); 451 material.setDiffuseColor(Color.WHITE); 452 } else { 453 if (diffuseColor != null) { 454 material.setDiffuseColor( 455 new Color( 456 diffuseColor.x, 457 diffuseColor.y, 458 diffuseColor.z, 1)); 459 // material.setDiffuseColor(new Color( 460 // 0.5, 461 // 0.5, 462 // 0.5, 0)); 463 } else { 464 material.setDiffuseColor(Color.GRAY); 465 } 466 } 467 468 if (normalImage != null) { 469 material.setBumpMap(normalImage); 470 } 471 472 if (specularImage != null) { 473 material.setSpecularMap(specularImage); 474 } else { 475 if (specularColor != null && specularPower != null) { 476 material.setSpecularColor( 477 new Color( 478 specularColor.x, 479 specularColor.y, 480 specularColor.z, 1)); 481 material.setSpecularPower(specularPower / 33); 482 // material.setSpecularColor(new Color( 483 // 0, 484 // 1, 485 // 0, 1)); 486 // material.setSpecularPower(1); 487 } else { 488 // material.setSpecularColor(new Color( 489 // 0.2, 490 // 0.2, 491 // 0.2, 1)); 492 // material.setSpecularPower(1); 493 material.setSpecularColor(null); 494 } 495 } 496 497 Object mesh = convertToFXMesh(n); 498 499 if (asPolygonMesh) { 500 PolygonMeshView mv = new PolygonMeshView(); 501 mv.setId(n.getName()); 502 mv.setMaterial(material); 503 mv.setMesh((PolygonMesh) mesh); 504 // mv.setCullFace(CullFace.NONE); //TODO 505 loaded.put(n, mv); 506 if (node != null) { 507 ((Group) node).getChildren().add(mv); 508 } 509 } else { 510 MeshView mv = new MeshView(); 511 mv.setId(n.getName()); 512 mv.setMaterial(material); 513 514 // // TODO HACK for [JIRA] (RT-30449) FX 8 3D: Need to handle mirror transformation (flip culling); 515 // mv.setCullFace(CullFace.FRONT); 516 517 mv.setMesh((TriangleMesh) mesh); 518 519 loaded.put(n, mv); 520 if (node != null) { 521 ((Group) node).getChildren().add(mv); 522 } 523 } 524 } 525 526 protected void processJointType(MNode n, Group parentNode) { 527 // [Note to Alex]: I've re-enabled joints, but not skinning yet [John] 528 Node result; 529 MFloat3 t = (MFloat3) n.getAttr("t"); 530 MFloat3 jo = (MFloat3) n.getAttr("jo"); 531 MFloat3 r = (MFloat3) n.getAttr("r"); 532 MFloat3 s = (MFloat3) n.getAttr("s"); 533 String id = n.getName(); 534 535 Joint j = new Joint(); 536 j.setId(id); 537 538 // There's various ways to get the same thing: 539 // n.getAttr("r").get()[0] 540 // n.getAttr("r").getX() 541 // n.getAttr("rx") 542 // Up to you which you prefer 543 544 j.t.setX(t.get()[0]); 545 j.t.setY(t.get()[1]); 546 j.t.setZ(t.get()[2]); 547 548 // if ssc (Segment Scale Compensate) is false, then it is = 1, 1, 1 549 boolean ssc = ((MBool) n.getAttr("ssc")).get(); 550 if (ssc) { 551 List<MNode> parents = n.getParentNodes(); 552 if (parents.size() > 0) { 553 MFloat3 parent_s = (MFloat3) n.getParentNodes().get(0).getAttr("s"); 554 j.is.setX(1f / parent_s.getX()); 555 j.is.setY(1f / parent_s.getY()); 556 j.is.setZ(1f / parent_s.getZ()); 557 } else { 558 j.is.setX(1f); 559 j.is.setY(1f); 560 j.is.setZ(1f); 561 } 562 } else { 563 j.is.setX(1f); 564 j.is.setY(1f); 565 j.is.setZ(1f); 566 } 567 568 /* 569 // This code doesn't seem to work right: 570 MFloat jox = (MFloat) n.getAttr("jox"); 571 MFloat joy = (MFloat) n.getAttr("joy"); 572 MFloat joz = (MFloat) n.getAttr("joz"); 573 j.jox.setAngle(jox.get()); 574 j.joy.setAngle(joy.get()); 575 j.joz.setAngle(joz.get()); 576 // The following code works right: 577 */ 578 579 if (jo != null) { 580 j.jox.setAngle(jo.getX()); 581 j.joy.setAngle(jo.getY()); 582 j.joz.setAngle(jo.getZ()); 583 } else { 584 j.jox.setAngle(0f); 585 j.joy.setAngle(0f); 586 j.joz.setAngle(0f); 587 } 588 589 MFloat rx = (MFloat) n.getAttr("rx"); 590 MFloat ry = (MFloat) n.getAttr("ry"); 591 MFloat rz = (MFloat) n.getAttr("rz"); 592 j.rx.setAngle(rx.get()); 593 j.ry.setAngle(ry.get()); 594 j.rz.setAngle(rz.get()); 595 596 j.s.setX(s.get()[0]); 597 j.s.setY(s.get()[1]); 598 j.s.setZ(s.get()[2]); 599 600 result = j; 601 // Add the Joint to the map 602 loaded.put(n, j); 603 j.setDepthTest(DepthTest.ENABLE); 604 // Add the Joint to its JavaFX parent 605 if (parentNode != null) { 606 parentNode.getChildren().add(j); 607 if (DEBUG) System.out.println("j.getDepthTest() : " + j.getDepthTest()); 608 } 609 if (parentNode == null || !(parentNode instanceof Joint)) { 610 // [Note to Alex]: I've re-enabled joints, but lets not use rootJoint [John] 611 // rootJoint = j; 612 } 613 } 614 615 protected void processTransformType(MNode n, Group parentNode) { 616 MFloat3 t = (MFloat3) n.getAttr("t"); 617 MFloat3 r = (MFloat3) n.getAttr("r"); 618 MFloat3 s = (MFloat3) n.getAttr("s"); 619 String id = n.getName(); 620 // ignore cameras 621 if ("persp".equals(id) || 622 "top".equals(id) || 623 "front".equals(id) || 624 "side".equals(id)) { 625 return; 626 } 627 628 MayaGroup mGroup = new MayaGroup(); 629 mGroup.setId(n.getName()); 630 // g.setBlendMode(BlendMode.SRC_OVER); 631 632 // if (DEBUG) System.out.println("t = " + t); 633 // if (DEBUG) System.out.println("r = " + r); 634 // if (DEBUG) System.out.println("s = " + s); 635 636 mGroup.t.setX(t.get()[0]); 637 mGroup.t.setY(t.get()[1]); 638 mGroup.t.setZ(t.get()[2]); 639 640 MFloat rx = (MFloat) n.getAttr("rx"); 641 MFloat ry = (MFloat) n.getAttr("ry"); 642 MFloat rz = (MFloat) n.getAttr("rz"); 643 mGroup.rx.setAngle(rx.get()); 644 mGroup.ry.setAngle(ry.get()); 645 mGroup.rz.setAngle(rz.get()); 646 647 mGroup.s.setX(s.get()[0]); 648 mGroup.s.setY(s.get()[1]); 649 mGroup.s.setZ(s.get()[2]); 650 651 MFloat rptx = (MFloat) n.getAttr("rptx"); 652 MFloat rpty = (MFloat) n.getAttr("rpty"); 653 MFloat rptz = (MFloat) n.getAttr("rptz"); 654 mGroup.rpt.setX(rptx.get()); 655 mGroup.rpt.setY(rpty.get()); 656 mGroup.rpt.setZ(rptz.get()); 657 658 MFloat rpx = (MFloat) n.getAttr("rpx"); 659 MFloat rpy = (MFloat) n.getAttr("rpy"); 660 MFloat rpz = (MFloat) n.getAttr("rpz"); 661 mGroup.rp.setX(rpx.get()); 662 mGroup.rp.setY(rpy.get()); 663 mGroup.rp.setZ(rpz.get()); 664 665 mGroup.rpi.setX(-rpx.get()); 666 mGroup.rpi.setY(-rpy.get()); 667 mGroup.rpi.setZ(-rpz.get()); 668 669 MFloat sptx = (MFloat) n.getAttr("sptx"); 670 MFloat spty = (MFloat) n.getAttr("spty"); 671 MFloat sptz = (MFloat) n.getAttr("sptz"); 672 mGroup.spt.setX(sptx.get()); 673 mGroup.spt.setY(spty.get()); 674 mGroup.spt.setZ(sptz.get()); 675 676 MFloat spx = (MFloat) n.getAttr("spx"); 677 MFloat spy = (MFloat) n.getAttr("spy"); 678 MFloat spz = (MFloat) n.getAttr("spz"); 679 mGroup.sp.setX(spx.get()); 680 mGroup.sp.setY(spy.get()); 681 mGroup.sp.setZ(spz.get()); 682 683 mGroup.spi.setX(-spx.get()); 684 mGroup.spi.setY(-spy.get()); 685 mGroup.spi.setZ(-spz.get()); 686 687 // Add the MayaGroup to the map 688 loaded.put(n, mGroup); 689 // Add the MayaGroup to its JavaFX parent 690 if (parentNode != null) { 691 parentNode.getChildren().add(mGroup); 692 } 693 } 694 695 protected void processAnimCurve(MNode n) { 696 // if (DEBUG) System.out.println("processing anim curve"); 697 List<MPath> toPaths = n.getPathsConnectingFrom("o"); 698 loaded.put(n, null); 699 for (MPath path : toPaths) { 700 MNode toNode = path.getTargetNode(); 701 // if (DEBUG) System.out.println("toNode = "+ toNode.getNodeType()); 702 if (toNode.isInstanceOf(transformType)) { 703 Node to = resolveNode(toNode); 704 if (to instanceof MayaGroup) { 705 MayaGroup g = (MayaGroup) to; 706 DoubleProperty ref = null; 707 String s = path.getComponentSelector(); 708 // if (DEBUG) System.out.println("selector = " + s); 709 if ("t[0]".equals(s)) { 710 ref = g.t.xProperty(); 711 } else if ("t[1]".equals(s)) { 712 ref = g.t.yProperty(); 713 } else if ("t[2]".equals(s)) { 714 ref = g.t.zProperty(); 715 } else if ("s[0]".equals(s)) { 716 ref = g.s.xProperty(); 717 } else if ("s[1]".equals(s)) { 718 ref = g.s.yProperty(); 719 } else if ("s[2]".equals(s)) { 720 ref = g.s.zProperty(); 721 } else if ("r[0]".equals(s)) { 722 ref = g.rx.angleProperty(); 723 } else if ("r[1]".equals(s)) { 724 ref = g.ry.angleProperty(); 725 } else if ("r[2]".equals(s)) { 726 ref = g.rz.angleProperty(); 727 } else if ("rp[0]".equals(s)) { 728 ref = g.rp.xProperty(); 729 } else if ("rp[1]".equals(s)) { 730 ref = g.rp.yProperty(); 731 } else if ("rp[2]".equals(s)) { 732 ref = g.rp.zProperty(); 733 } else if ("sp[0]".equals(s)) { 734 ref = g.sp.xProperty(); 735 } else if ("sp[1]".equals(s)) { 736 ref = g.sp.yProperty(); 737 } else if ("sp[2]".equals(s)) { 738 ref = g.sp.zProperty(); 739 } 740 // Note: may also want to consider adding rpt in addition to rp and sp 741 if (ref != null) { 742 convertAnimCurveRange(n, ref, true); 743 } 744 } 745 // [Note to Alex]: I've re-enabled joints, but not skinning yet [John] 746 if (to instanceof Joint) { 747 Joint j = (Joint) to; 748 DoubleProperty ref = null; 749 String s = path.getComponentSelector(); 750 // if (DEBUG) System.out.println("selector = " + s); 751 if ("t[0]".equals(s)) { 752 ref = j.t.xProperty(); 753 } else if ("t[1]".equals(s)) { 754 ref = j.t.yProperty(); 755 } else if ("t[2]".equals(s)) { 756 ref = j.t.zProperty(); 757 } else if ("s[0]".equals(s)) { 758 ref = j.s.xProperty(); 759 } else if ("s[1]".equals(s)) { 760 ref = j.s.yProperty(); 761 } else if ("s[2]".equals(s)) { 762 ref = j.s.zProperty(); 763 } else if ("jo[0]".equals(s)) { 764 ref = j.jox.angleProperty(); 765 } else if ("jo[1]".equals(s)) { 766 ref = j.joy.angleProperty(); 767 } else if ("jo[2]".equals(s)) { 768 ref = j.joz.angleProperty(); 769 } else if ("r[0]".equals(s)) { 770 ref = j.rx.angleProperty(); 771 } else if ("r[1]".equals(s)) { 772 ref = j.ry.angleProperty(); 773 } else if ("r[2]".equals(s)) { 774 ref = j.rz.angleProperty(); 775 } 776 if (ref != null) { 777 convertAnimCurveRange(n, ref, true); 778 } 779 } 780 break; 781 } 782 } 783 } 784 785 private Object convertToFXMesh(MNode n) { 786 mVerts = (MFloat3Array) n.getAttr("vt"); 787 MPolyFace mPolys = (MPolyFace) n.getAttr("fc"); 788 mPointTweaks = (MFloat3Array) n.getAttr("pt"); 789 MInt3Array mEdges = (MInt3Array) n.getAttr("ed"); 790 edgeData = mEdges.get(); 791 uvSet = ((MArray) n.getAttr("uvst")).get(); 792 String currentUVSet = ((MString) n.getAttr("cuvs")).get(); 793 for (int i = 0; i < uvSet.size(); i++) { 794 if (((MString) uvSet.get(i).getData("uvsn")).get().equals(currentUVSet)) { 795 uvChannel = i; 796 } 797 } 798 799 if (mPolys.getFaces() == null) { 800 if (asPolygonMesh) { 801 return new PolygonMesh(); 802 } else { 803 return new TriangleMesh(); 804 } 805 } 806 807 MFloat3Array normals = (MFloat3Array) n.getAttr("n"); 808 return buildMeshData(mPolys.getFaces(), normals); 809 } 810 811 private int edgeVert(int edgeNumber, boolean start) { 812 boolean reverse = (edgeNumber < 0); 813 if (reverse) { 814 edgeNumber = reverse(edgeNumber); 815 return edgeData[3 * edgeNumber + (start ? 1 : 0)]; 816 } else { 817 return edgeData[3 * edgeNumber + (start ? 0 : 1)]; 818 } 819 } 820 821 private int reverse(int edge) { 822 if (edge < 0) { 823 return -edge - 1; 824 } 825 return edge; 826 } 827 828 private boolean edgeIsSmooth(int edgeNumber) { 829 edgeNumber = reverse(edgeNumber); 830 return edgeData[3 * edgeNumber + 2] != 0; 831 } 832 833 private int edgeStart(int edgeNumber) { 834 return edgeVert(edgeNumber, true); 835 } 836 837 private int edgeEnd(int edgeNumber) { 838 return edgeVert(edgeNumber, false); 839 } 840 841 private float[] getTexCoords(int uvChannel) { 842 if (uvSet == null || uvChannel < 0 || uvChannel >= uvSet.size()) { 843 return new float[] {0,0}; 844 } 845 MCompound compound = (MCompound) uvSet.get(uvChannel); 846 MFloat2Array uvs = (MFloat2Array) compound.getFieldData("uvsp"); 847 if (uvs == null || uvs.get() == null) { 848 return new float[] {0,0}; 849 } 850 851 float[] texCoords = new float[uvs.getSize() * 2]; 852 float[] uvsData = uvs.get(); 853 for (int i = 0; i < uvs.getSize(); i++) { 854 //note the 1 - v 855 texCoords[i * 2] = uvsData[2 * i]; 856 texCoords[i * 2 + 1] = 1 - uvsData[2 * i + 1]; 857 } 858 return texCoords; 859 } 860 861 private void getVert(int index, Vec3f vert) { 862 float[] verts = mVerts.get(); 863 float[] tweaks = null; 864 if (mPointTweaks != null) { 865 tweaks = mPointTweaks.get(); 866 if (tweaks != null) { 867 if ((3 * index + 2) >= tweaks.length) { 868 tweaks = null; 869 } 870 } 871 } 872 if (tweaks == null) { 873 vert.set(verts[3 * index + 0], verts[3 * index + 1], verts[3 * index + 2]); 874 } else { 875 vert.set( 876 verts[3 * index + 0] + tweaks[3 * index + 0], 877 verts[3 * index + 1] + tweaks[3 * index + 1], 878 verts[3 * index + 2] + tweaks[3 * index + 2]); 879 } 880 } 881 882 float FPS = 24.0f; 883 float TAN_FIXED = 1; 884 float TAN_LINEAR = 2; 885 float TAN_FLAT = 3; 886 float TAN_STEPPED = 5; 887 float TAN_SPLINE = 9; 888 float TAN_CLAMPED = 10; 889 float TAN_PLATEAU = 16; 890 891 // Experimentally trying to land the frames on whole frame values 892 // Duration is still double, but internally, in Animation/Timeline, 893 // the time is discrete. 6000 units per second. 894 // Without this EPSILON, the frames might not land on whole frame values. 895 // 0.000001f seems to work for now 896 // 0.0000001f was too small on a trial run 897 static final float EPSILON = 0.000001f; 898 899 static final float MAXIMUM = 10000000.0f; 900 901 // Empirically derived from playing with animation curve editor 902 float TAN_EPSILON = 0.05f; 903 904 //========================================================================= 905 // Loader.convertAnimCurveRange 906 //------------------------------------------------------------------------- 907 // This method adds to keyFrameMap which is a 908 // TreeMap Map<Float, List<KeyValue>> 909 //========================================================================= 910 void convertAnimCurveRange( 911 MNode n, final DoubleProperty property, 912 boolean convertAnglesToDegrees) { 913 Collection inputs = n.getConnectionsTo("i"); 914 boolean isDrivenAnimCurve = (inputs.size() > 0); 915 boolean useTangentInterpolator = true; // use the NEW tangent interpolator 916 917 //--------------------------------------------------------------------- 918 // Tangent types we need to handle: 919 // 2 = Linear 920 // - The in/out tangent points in the direction of the previous/next key 921 // 3 = Flat 922 // - The in/out tangent has no y component 923 // 5 = Stepped 924 // - If this is seen on the out tangent of the previous 925 // frame, immediately goes to the next value 926 // 9 = Spline 927 // - The in / out tangents around the current keyframe 928 // match the slope defined by the previous and next 929 // keyframes. 930 // 10 = Clamped 931 // - Uses spline tangents unless the keyframe is very close to the next or 932 // previous value, in which case it uses linear tangents. 933 // 16 = Plateau 934 // - Generally speaking, if the keyframe is a local maximum or minimum, 935 // uses flat tangents to prevent the curve from overshooting the keyframe. 936 // Seems to use spline tangents when the keyframe is not a local extremum. 937 // There is an epsilon factor built in when deciding whether the flattening 938 // behavior is to be applied. 939 // Tangent types we aren't handling: 940 // 1 = Fixed 941 // 17 = StepNext 942 //--------------------------------------------------------------------- 943 944 MArray ktv = (MArray) n.getAttr("ktv"); 945 MInt tan = (MInt) n.getAttr("tan"); 946 int len = ktv.getSize(); 947 948 // Note: the kix, kiy, kox, koy from Maya 949 // are most likely unit vectors [kix, kiy] and [kox, koy] 950 // in some tricky units that Ken figured out. 951 MFloatArray kix = (MFloatArray) n.getAttr("kix"); 952 MFloatArray kiy = (MFloatArray) n.getAttr("kiy"); 953 MFloatArray kox = (MFloatArray) n.getAttr("kox"); 954 MFloatArray koy = (MFloatArray) n.getAttr("koy"); 955 MIntArray kit = (MIntArray) n.getAttr("kit"); 956 MIntArray kot = (MIntArray) n.getAttr("kot"); 957 boolean hasTangent = kix != null && kix.get() != null && kix.get().length > 0; 958 boolean isRotation = n.isInstanceOf(animCurveTA) || n.isInstanceOf(animCurveUA); 959 boolean keyTimesInSeconds = 960 (n.isInstanceOf(animCurveUA) || n.isInstanceOf(animCurveUL) || 961 n.isInstanceOf(animCurveUT) || n.isInstanceOf(animCurveUU)); 962 963 List<KeyFrame> drivenKeys = new LinkedList(); 964 965 // Many incoming animation curves start at keyframe 1; to 966 // correctly interpret these we need to subtract off one frame 967 // from each key time 968 boolean needsOneFrameAdjustment = false; 969 970 // For computing tangents around the current point 971 float[] keyTimes = new float[3]; 972 float[] keyValues = new float[3]; 973 boolean[] keysValid = new boolean[3]; 974 float[] prevOutTan = new float[3]; // for orig interpolator 975 float[] curOutTan = new float[3]; // for tan interpolator 976 float[] curInTan = new float[3]; // for both interpolators 977 Collection toPaths = n.getPathsConnectingFrom("o"); 978 String keyName = null; 979 String targetName = null; 980 for (Object obj : toPaths) { 981 MPath toPath = (MPath) obj; 982 keyName = toPath.getComponentSelector(); 983 targetName = toPath.getTargetNode().getName(); 984 } 985 986 for (int j = 0; j < len; j++) { 987 MCompound k1 = (MCompound) ktv.getData(j); 988 989 float kt = ((MFloat) k1.getData("kt")).get(); 990 float kv = ((MFloat) k1.getData("kv")).get(); 991 if (j == 0 && !keyTimesInSeconds) { 992 needsOneFrameAdjustment = (kt != 0.0f); 993 // if (DEBUG) System.out.println("needsOneFrameAdjustment = " + needsOneFrameAdjustment); 994 } 995 996 //------------------------------------------------------------ 997 // Find out the previous times, values, and durations, 998 // if they exist 999 // (this code is both for tan interpolator and orig interpolator) 1000 // Ken's duration is now called durationPrev 1001 // Ken's k0 is now called kPrev 1002 //------------------------------------------------------------ 1003 float durationPrev = 0.0f; 1004 float ktPrev = 0.0f; 1005 float kvPrev = 0.0f; 1006 if (j > 0) { 1007 MCompound kPrev = (MCompound) ktv.getData(j - 1); 1008 ktPrev = ((MFloat) kPrev.getData("kt")).get(); 1009 kvPrev = ((MFloat) kPrev.getData("kv")).get(); // NEW 1010 durationPrev = kt - ktPrev; 1011 } 1012 1013 //------------------------------------------------------------ 1014 // Find out the next times, values, and durations, 1015 // if they exist 1016 // (this code is specifically for TangentInterpolator) 1017 //------------------------------------------------------------ 1018 float durationNext = 0.0f; 1019 float ktNext = 0.0f; 1020 float kvNext = 0.0f; 1021 if ((j + 1) < len) { 1022 MCompound kNext = (MCompound) ktv.getData(j + 1); 1023 ktNext = ((MFloat) kNext.getData("kt")).get(); 1024 kvNext = ((MFloat) kNext.getData("kv")).get(); // NEW 1025 durationNext = ktNext - kt; 1026 } 1027 1028 if (!keyTimesInSeconds) { 1029 // convert frames to seconds 1030 kt /= FPS; 1031 ktPrev /= FPS; // NEW 1032 ktNext /= FPS; // NEW 1033 } else { 1034 // convert seconds to frames 1035 durationPrev *= FPS; 1036 durationNext *= FPS; // NEW 1037 } 1038 /* 1039 var ktd = kt; 1040 if (range != null) { 1041 if (range.start > ktd or range.end < ktd) { 1042 continue; 1043 } 1044 } 1045 */ 1046 1047 1048 // Determine the tangent types on both sides 1049 int prevOutTanType = tan.get(); // for orig interpolator 1050 int curInTanType = tan.get(); // for both interpolators 1051 int curOutTanType = tan.get(); // for tan intepolator 1052 if (j > 0 && j < kot.getSize()) { 1053 int tmp = kot.get(j - 1); 1054 // Will be 0 if not actually written in the file 1055 if (tmp != 0) { 1056 prevOutTanType = tmp; 1057 } 1058 } 1059 if (j < kot.getSize()) { // NEW 1060 int tmp = kot.get(j); 1061 if (tmp != 0) { 1062 curOutTanType = tmp; 1063 } 1064 } 1065 if (j < kit.getSize()) { 1066 int tmp = kit.get(j); 1067 if (tmp != 0) { 1068 curInTanType = tmp; 1069 } 1070 } 1071 1072 // Get previous out tangent 1073 getTangent( 1074 ktv, kix, kiy, kox, koy, 1075 j - 1, 1076 prevOutTanType, 1077 false, 1078 isRotation, 1079 keyTimesInSeconds, 1080 prevOutTan, 1081 // Temporaries 1082 keyTimes, keyValues, keysValid); 1083 1084 // NEW 1085 // for tangentInterpolator, we also need curOutTangent 1086 // Get current out tangent 1087 getTangent( 1088 ktv, kix, kiy, kox, koy, 1089 j, 1090 curOutTanType, 1091 false, 1092 isRotation, 1093 keyTimesInSeconds, 1094 curOutTan, 1095 // Temporaries 1096 keyTimes, keyValues, keysValid); 1097 1098 // Get current in tangent 1099 getTangent( 1100 ktv, kix, kiy, kox, koy, 1101 j, 1102 curInTanType, 1103 true, 1104 isRotation, 1105 keyTimesInSeconds, 1106 curInTan, 1107 // Temporaries 1108 keyTimes, keyValues, keysValid); 1109 1110 // Create the appropriate interpolator type: 1111 // [*] DISCRETE for STEPPED type for prevOutTanType 1112 // [*] Interpolator.TANGENT 1113 // [*] custom Maya animation curve interpolator if specified 1114 Interpolator interp = Interpolator.DISCRETE; 1115 if (prevOutTanType == TAN_STEPPED) { 1116 // interp = DISCRETE; 1117 } else { 1118 if (useTangentInterpolator) { 1119 //-------------------------------------------------- 1120 // TangentIntepolator 1121 double k_ix = curInTan[0]; 1122 double k_iy = curInTan[1]; 1123 // don't use prevOutTan for tangentInterpolator 1124 // double k_ox = prevOutTan[0]; 1125 // double k_oy = prevOutTan[1]; 1126 double k_ox = curOutTan[0]; 1127 double k_oy = curOutTan[1]; 1128 1129 /* 1130 if (DEBUG) System.out.println("n.getName(): " + n.getName()); 1131 if (DEBUG) System.out.println("(k_ix = " + k_ix + ", " + 1132 "k_iy = " + k_iy + ", " + 1133 "k_ox = " + k_ox + ", " + 1134 "k_oy = " + k_oy + ")" 1135 ); 1136 */ 1137 1138 // if (DEBUG) System.out.println("FPS = " + FPS); 1139 1140 double inTangent = 0.0; 1141 double outTangent = 0.0; 1142 1143 // Compute the in tangent 1144 if (k_ix != 0) { 1145 inTangent = k_iy / (k_ix * FPS); 1146 } 1147 // Compute the out tangent 1148 if (k_ox != 0) { 1149 outTangent = k_oy / (k_ox * FPS); 1150 } 1151 1152 // Compute 1/3 of the time interval of this keyframe 1153 double oneThirdDeltaPrev = durationPrev / 3.0f; 1154 double oneThirdDeltaNext = durationNext / 3.0f; 1155 1156 // Note: for angular animation curves, the tangents encode 1157 // changes in radians rather than degrees. Now that our 1158 // animation curves also emit radians, no conversion is 1159 // necessary here. 1160 double inTangentValue = -1 * inTangent * oneThirdDeltaPrev + kv; 1161 double outTangentValue = outTangent * oneThirdDeltaNext + kv; 1162 // We need to add "+ kv", because the value for the tangent 1163 // interpolator is in "world space" and not relative to the key 1164 1165 if (inTangentValue > MAXIMUM) { 1166 inTangentValue = MAXIMUM; 1167 } 1168 if (outTangentValue > MAXIMUM) { 1169 outTangentValue = MAXIMUM; 1170 } 1171 1172 double timeDeltaPrev = (durationPrev / FPS) * 1000f / 3.0f; // in ms 1173 double timeDeltaNext = (durationNext / FPS) * 1000f / 3.0f; // in ms 1174 1175 if (true) { 1176 // if (DEBUG) System.out.println("________________________________________"); 1177 // if (DEBUG) System.out.println("n.getName() = " + n.getName()); 1178 // if (DEBUG) System.out.println("kv = " + kv); 1179 // if (DEBUG) System.out.println("Interpolator.TANGENT(" + 1180 // "Duration.valueOf(" + 1181 // timeDeltaPrev + ")" + ", " + 1182 // inTangentValue + ", " + 1183 // "Duration.valueOf(" + 1184 // timeDeltaNext + ")" + ", " + 1185 // outTangentValue + ");" 1186 // ); 1187 1188 } 1189 1190 //-------------------------------------------------- 1191 // Given the diagram below, where 1192 // k = keyframe 1193 // i = inTangent 1194 // o = outTangent 1195 // + = timeDelta 1196 // Its extremely important to note that 1197 // inTangent's and outTangent's values for "i" and "o" 1198 // are NOT relative to "k". They are in "worldSpace". 1199 // However, the timeDeltaNext and timeDeltaPrev 1200 // are in fact relative to the keyframe "k", 1201 // and are always an absolute value. 1202 // So, in summary, 1203 // the Y-axis values are not relative, but 1204 // the X-axis values are relative, and always positive 1205 //-------------------------------------------------- 1206 // (Y-axis worldSpace value for i) 1207 // inTangent i 1208 // | 1209 // | timeDeltaNext (relative to x) 1210 // | |<------->| 1211 // +---------k---------+ 1212 // |<------->| | 1213 // timeDeltaPrev | 1214 // | 1215 // o outTangent 1216 // (Y-axis worldSpace value for o) 1217 //-------------------------------------------------- 1218 Duration inDuration = Duration.millis(timeDeltaPrev); 1219 if (inDuration.toMillis() == 0) { 1220 interp = Interpolator.TANGENT(Duration.millis(timeDeltaNext), outTangentValue); 1221 } else { 1222 interp = Interpolator.TANGENT( 1223 inDuration, inTangentValue, 1224 Duration.millis(timeDeltaNext), outTangentValue); 1225 } 1226 } else { 1227 MayaAnimationCurveInterpolator mayaInterp = 1228 createMayaAnimationCurveInterpolator( 1229 prevOutTan[0], prevOutTan[1], 1230 curInTan[0], curInTan[1], 1231 durationPrev, 1232 true); 1233 // mayaInterp.isRotation = isRotation; // was commented out long ago by Ken/Chris 1234 // mayaInterp.debug = targetName + "." + keyName + "@"+ kt; 1235 interp = mayaInterp; 1236 } 1237 } 1238 1239 float t = kt - EPSILON; 1240 if (t < 0.0) { 1241 continue; // just skipping all the negative frames 1242 } 1243 1244 /* 1245 // This was the old way of adjusting 1246 // for the one frame adjustment. 1247 if (needsOneFrameAdjustment) { 1248 t = kt - 1.0f/FPS; 1249 } else { 1250 t = kt; 1251 } 1252 // The new way is below ... 1253 // See: (needsOneFrameAdjustment && (j == 0)) 1254 */ 1255 1256 // if (DEBUG) System.out.println("j = " + j); 1257 // if (DEBUG) System.out.println("t = " + t); 1258 if (isRotation) { 1259 // Maya angular animation curves implicitly output in radians. 1260 // In order to properly process them throughout the utility node 1261 // network, we have to follow this convention, and implicitly 1262 // convert the inputs of transforms' rotation angles to degrees 1263 // at the end. 1264 if (!convertAnglesToDegrees) { 1265 kv = (float) Math.toRadians(kv); 1266 } 1267 } 1268 // if (DEBUG) System.out.println("creating key value at: " + t + ": " + targetName + "." + keyName); 1269 KeyValue keyValue = new KeyValue(property, kv, interp); // [!] API change 1270 1271 // If the first frame is at frame 1, 1272 // at least for now, try adding in a frame at frame 0 1273 // which is a duplicate of the frame at frame 1, 1274 // to counter-act some strange behavior we are seeing 1275 // if there is no key at frame 0. 1276 if (needsOneFrameAdjustment && (j == 0)) { 1277 if (DEBUG) System.out.println("[!] ATTEMPTING FRAME ONE ADJUSTMENT [!]"); 1278 // [!] API change 1279 // KeyValue keyValue0 = new KeyValue(property, kv, Interpolator.LINEAR); 1280 KeyValue keyValue0 = new KeyValue(property, kv); 1281 addKeyframe(0.0f, keyValue0); 1282 } 1283 1284 // Add keyframe 1285 addKeyframe(t, keyValue); 1286 1287 /* 1288 // If you're at the last keyframe, 1289 // at least for now, try adding in an extra frame 1290 // to pad the ending 1291 if (j == (len - 1)) { 1292 addKeyframe((t+0.0001667f), keyValue); 1293 } 1294 */ 1295 } 1296 } 1297 1298 //========================================================================= 1299 // Loader.addKeyframe 1300 //========================================================================= 1301 void addKeyframe(float t, KeyValue keyValue) { 1302 List<KeyValue> vals = keyFrameMap.get(t); 1303 if (vals == null) { 1304 vals = new LinkedList<KeyValue>(); 1305 keyFrameMap.put(t, vals); 1306 } 1307 vals.add(keyValue); 1308 } 1309 1310 //========================================================================= 1311 // Loader.createMayaAnimationCurveInterpolator 1312 //========================================================================= 1313 MayaAnimationCurveInterpolator createMayaAnimationCurveInterpolator( 1314 float kox, 1315 float koy, 1316 float kix, 1317 float kiy, 1318 float duration, 1319 boolean hasTangent) { 1320 if (duration == 0.0f) { 1321 return new MayaAnimationCurveInterpolator(0, 0, true); 1322 } else { 1323 // Compute the out tangent 1324 float outTangent = koy / (kox * FPS); 1325 // Compute the in tangent 1326 float inTangent = kiy / (kix * FPS); 1327 // Compute 1/3 of the time interval of this keyframe 1328 float oneThirdDelta = duration / 3.0f; 1329 1330 // Note: for angular animation curves, the tangents encode 1331 // changes in radians rather than degrees. Now that our 1332 // animation curves also emit radians, no conversion is 1333 // necessary here. 1334 float p1Delta = outTangent * oneThirdDelta; 1335 float p2Delta = -inTangent * oneThirdDelta; 1336 return new MayaAnimationCurveInterpolator(p1Delta, p2Delta, false); 1337 } 1338 } 1339 1340 //========================================================================= 1341 // Loader.getTangent 1342 //========================================================================= 1343 void getTangent( 1344 MArray ktv, 1345 MFloatArray kix, 1346 MFloatArray kiy, 1347 MFloatArray kox, 1348 MFloatArray koy, 1349 int index, 1350 int tangentType, 1351 boolean inTangent, 1352 boolean isRotation, 1353 boolean keyTimesInSeconds, 1354 float[] result, 1355 // Temporaries 1356 float[] tmpKeyTimes, 1357 float[] tmpKeyValues, 1358 boolean[] tmpKeysValid) { 1359 float[] output = result; 1360 float[] keyTimes = tmpKeyTimes; 1361 float[] keyValues = tmpKeyValues; 1362 boolean[] keysValid = tmpKeysValid; 1363 if (inTangent) { 1364 if (index >= 0 && index < kix.getSize() && index < kiy.getSize()) { 1365 output[0] = kix.get(index); 1366 output[1] = kiy.get(index); 1367 if (output[0] != 0.0f || 1368 output[1] != 0.0f) { 1369 // A keyframe was specified in the file 1370 return; 1371 } 1372 } 1373 } else { 1374 if (index >= 0 && index < kox.getSize() && index < koy.getSize()) { 1375 output[0] = kox.get(index); 1376 output[1] = koy.get(index); 1377 if (output[0] != 0.0f || 1378 output[1] != 0.0f) { 1379 // A keyframe was specified in the file 1380 return; 1381 } 1382 } 1383 } 1384 1385 // Need to compute the tangent from the surrounding key times and values 1386 int i = -1; 1387 while (i < 2) { 1388 int cur = index + i; 1389 if (cur >= 0 && cur < ktv.getSize()) { 1390 MCompound k1 = (MCompound) ktv.getData(cur); 1391 float kt = ((MFloat) k1.getData("kt")).get(); 1392 if (keyTimesInSeconds) { 1393 // Convert seconds to frames 1394 kt *= FPS; 1395 } 1396 float kv = ((MFloat) k1.getData("kv")).get(); 1397 if (isRotation) { 1398 // Maya angular animation curves implicitly output in radians -- see below 1399 kv = (float) Math.toRadians(kv); 1400 } 1401 keyTimes[1 + i] = kt; 1402 keyValues[1 + i] = kv; 1403 keysValid[1 + i] = true; 1404 } else { 1405 keysValid[1 + i] = false; 1406 } 1407 ++i; 1408 } 1409 computeTangent(keyTimes, keyValues, keysValid, tangentType, inTangent, result); 1410 } 1411 1412 //========================================================================= 1413 // Loader.computeTangent 1414 //========================================================================= 1415 void computeTangent( 1416 float[] keyTimes, 1417 float[] keyValues, 1418 boolean[] keysValid, 1419 float tangentType, 1420 boolean inTangent, 1421 float[] computedTangent) { 1422 float[] output = computedTangent; 1423 if (tangentType == TAN_LINEAR) { 1424 float x0; 1425 float x1; 1426 float y0; 1427 float y1; 1428 if (inTangent) { 1429 if (!keysValid[0]) { 1430 // Start of the animation curve: doesn't matter 1431 output[0] = 1.0f; 1432 output[1] = 0.0f; 1433 return; 1434 } 1435 x0 = keyTimes[0]; 1436 x1 = keyTimes[1]; 1437 y0 = keyValues[0]; 1438 y1 = keyValues[1]; 1439 } else { 1440 if (!keysValid[2]) { 1441 // End of the animation curve: doesn't matter 1442 output[0] = 1.0f; 1443 output[1] = 0.0f; 1444 return; 1445 } 1446 x0 = keyTimes[1]; 1447 x1 = keyTimes[2]; 1448 y0 = keyValues[1]; 1449 y1 = keyValues[2]; 1450 } 1451 float dx = x1 - x0; 1452 float dy = y1 - y0; 1453 output[0] = dx; 1454 output[1] = dy; 1455 // Fall through to perform normalization 1456 } else if (tangentType == TAN_FLAT) { 1457 output[0] = 1.0f; 1458 output[1] = 0.0f; 1459 return; 1460 } else if (tangentType == TAN_STEPPED) { 1461 // Doesn't matter what the tangent values are -- will use discrete type interpolator 1462 return; 1463 } else if (tangentType == TAN_SPLINE) { 1464 // Whether we're computing the in or out tangent, if we don't have one or the other 1465 // keyframe, it reduces to a simpler case 1466 if (!(keysValid[0] && keysValid[2])) { 1467 // Reduces to the linear case 1468 computeTangent(keyTimes, keyValues, keysValid, TAN_LINEAR, inTangent, computedTangent); 1469 return; 1470 } 1471 1472 // Figure out the slope between the adjacent keyframes 1473 output[0] = keyTimes[2] - keyTimes[0]; 1474 output[1] = keyValues[2] - keyValues[0]; 1475 } else if (tangentType == TAN_CLAMPED) { 1476 if (!(keysValid[0] && keysValid[2])) { 1477 // Reduces to the linear case at the ends of the animation curve 1478 computeTangent(keyTimes, keyValues, keysValid, TAN_LINEAR, inTangent, computedTangent); 1479 return; 1480 } 1481 1482 float inDiff = Math.abs(keyValues[1] - keyValues[0]); 1483 float outDiff = Math.abs(keyValues[2] - keyValues[1]); 1484 1485 if (inDiff <= TAN_EPSILON || outDiff <= TAN_EPSILON) { 1486 // The Maya docs say that this reduces to the linear 1487 // case. If this were true, then the apparent behavior 1488 // would be to compute the linear tangent between the 1489 // two keyframes which are closest together, and 1490 // reflect that tangent about the current keyframe. 1491 // computeTangent(keyTimes, keyValues, keysValid, TAN_LINEAR, (inDiff < outDiff), computedTangent); 1492 1493 // However, experimentation in the curve editor 1494 // clearly indicates for our test cases that flat 1495 // rather than linear interpolation is used in this 1496 // case. Therefore to match Maya's actual behavior 1497 // more closely we do the following. 1498 computeTangent(keyTimes, keyValues, keysValid, TAN_FLAT, inTangent, computedTangent); 1499 } else { 1500 // Use spline tangents 1501 computeTangent(keyTimes, keyValues, keysValid, TAN_SPLINE, inTangent, computedTangent); 1502 } 1503 1504 return; 1505 } else if (tangentType == TAN_PLATEAU) { 1506 if (!(keysValid[0] && keysValid[2])) { 1507 // Reduces to the flat case at the ends of the animation curve 1508 computeTangent(keyTimes, keyValues, keysValid, TAN_FLAT, inTangent, computedTangent); 1509 return; 1510 } 1511 1512 // Otherwise, figure out whether we have any local extremum 1513 if ((keyValues[1] > keyValues[0] && 1514 keyValues[1] > keyValues[2]) || 1515 (keyValues[1] < keyValues[0] && 1516 keyValues[1] < keyValues[2])) { 1517 // Use flat tangent 1518 computeTangent(keyTimes, keyValues, keysValid, TAN_FLAT, inTangent, computedTangent); 1519 } else { 1520 // The rule is that we use spline tangents unless 1521 // doing so would cause the curve to go outside the 1522 // envelope of the keyvalues. To figure this out, we 1523 // have to compute both the in and out tangents as 1524 // though we were using splines, and see whether the 1525 // intermediate bezier control points go outside the 1526 // hull. 1527 // 1528 // Note that it doesn't matter whether we compute the 1529 // "in" or "out" tangent at the current point -- the 1530 // result is the same. 1531 computeTangent(keyTimes, keyValues, keysValid, TAN_SPLINE, inTangent, computedTangent); 1532 1533 // Compute the values from the keyframe along the 1534 // tangent 1/3 of the way to the previous and next 1535 // keyframes 1536 float tangent = computedTangent[1] / (computedTangent[0] * FPS); 1537 float prev13 = keyValues[1] - tangent * ((keyTimes[1] - keyTimes[0]) / 3.0f); 1538 float next13 = keyValues[1] + tangent * ((keyTimes[2] - keyTimes[1]) / 3.0f); 1539 1540 if (isBetween(prev13, keyValues[0], keyValues[2]) && 1541 isBetween(next13, keyValues[0], keyValues[2])) { 1542 } else { 1543 // Use flat tangent 1544 computeTangent(keyTimes, keyValues, keysValid, TAN_FLAT, inTangent, computedTangent); 1545 } 1546 } 1547 1548 return; 1549 } 1550 1551 // Perform normalization 1552 // NOTE the scaling of the X coordinate -- this is needed to match Maya's math 1553 output[0] /= FPS; 1554 float len = (float) Math.sqrt( 1555 output[0] * output[0] + 1556 output[1] * output[1]); 1557 if (len != 0.0f) { 1558 output[0] /= len; 1559 output[1] /= len; 1560 } 1561 // println("TAN LINEAR {output[0]} {output[1]}"); 1562 } 1563 1564 //========================================================================= 1565 // Loader.isBetween 1566 //========================================================================= 1567 boolean isBetween( 1568 float value, 1569 float v1, 1570 float v2) { 1571 return ((v1 <= value && value <= v2) || 1572 (v1 >= value && value >= v2)); 1573 } 1574 1575 1576 static class VertexHash { 1577 private int vertexIndex; 1578 private int normalIndex; 1579 private int[] uvIndices; 1580 1581 VertexHash( 1582 int vertexIndex, 1583 int normalIndex, 1584 int[] uvIndices) { 1585 this.vertexIndex = vertexIndex; 1586 this.normalIndex = normalIndex; 1587 if (uvIndices != null) { 1588 this.uvIndices = (int[]) uvIndices.clone(); 1589 } 1590 } 1591 1592 @Override 1593 public int hashCode() { 1594 int code = vertexIndex; 1595 code *= 17; 1596 code += normalIndex; 1597 if (uvIndices != null) { 1598 for (int i = 0; i < uvIndices.length; i++) { 1599 code *= 17; 1600 code += uvIndices[i]; 1601 } 1602 } 1603 return code; 1604 } 1605 1606 @Override 1607 public boolean equals(Object arg) { 1608 if (arg == null || !(arg instanceof VertexHash)) { 1609 return false; 1610 } 1611 1612 VertexHash other = (VertexHash) arg; 1613 if (vertexIndex != other.vertexIndex) { 1614 return false; 1615 } 1616 if (normalIndex != other.normalIndex) { 1617 return false; 1618 } 1619 if ((uvIndices != null) != (other.uvIndices != null)) { 1620 return false; 1621 } 1622 if (uvIndices != null) { 1623 if (uvIndices.length != other.uvIndices.length) { 1624 return false; 1625 } 1626 for (int i = 0; i < uvIndices.length; i++) { 1627 if (uvIndices[i] != other.uvIndices[i]) { 1628 return false; 1629 } 1630 } 1631 } 1632 return true; 1633 } 1634 } 1635 1636 private Object buildMeshData(List<MPolyFace.FaceData> faces, MFloat3Array normals) { 1637 // Setup vertexes 1638 float[] verts = mVerts.get(); 1639 float[] tweaks = null; 1640 if (mPointTweaks != null) { 1641 tweaks = mPointTweaks.get(); 1642 } 1643 float[] points = new float[verts.length]; 1644 for (int index = 0; index < verts.length; index += 3) { 1645 if (tweaks != null && tweaks.length > index + 2) { 1646 points[index] = verts[index] + tweaks[index]; 1647 points[index + 1] = verts[index + 1] + tweaks[index + 1]; 1648 points[index + 2] = verts[index + 2] + tweaks[index + 2]; 1649 } else { 1650 points[index] = verts[index]; 1651 points[index + 1] = verts[index + 1]; 1652 points[index + 2] = verts[index + 2]; 1653 } 1654 } 1655 1656 // copy UV as-is (if any) 1657 float[] texCoords = getTexCoords(uvChannel); 1658 1659 if (asPolygonMesh) { 1660 List<int[]> ff = new ArrayList<int[]>(); 1661 for (int f = 0; f < faces.size(); f++) { 1662 MPolyFace.FaceData faceData = faces.get(f); 1663 int[] faceEdges = faceData.getFaceEdges(); 1664 int[][] uvData = faceData.getUVData(); 1665 int[] uvIndices = uvData == null ? null : uvData[uvChannel]; 1666 if (faceEdges != null && faceEdges.length > 0) { 1667 int[] polyFace = new int[faceEdges.length * 2]; 1668 for (int i = 0; i < faceEdges.length; i++) { 1669 int vIndex = edgeStart(faceEdges[i]); 1670 int uvIndex = uvIndices == null ? 0 : uvIndices[i]; 1671 polyFace[i*2] = vIndex; 1672 polyFace[i*2+1] = uvIndex; 1673 } 1674 ff.add(polyFace); 1675 } 1676 } 1677 int[][] facesArray = ff.toArray(new int[ff.size()][]); 1678 1679 int[][] faceNormals = new int[facesArray.length][]; 1680 int normalInd = 0; 1681 for (int f = 0; f < faceNormals.length; f++) { 1682 faceNormals[f] = new int[facesArray[f].length/2]; 1683 for (int e = 0; e < faceNormals[f].length; e++) { 1684 faceNormals[f][e] = normalInd++; 1685 } 1686 } 1687 int[] smGroups; 1688 // we can only figure out faces' normal indices if the faces' normal indices have a one-to-one ordered correspondence with the normals 1689 if (normalInd == normals.getSize()) { 1690 smGroups = SmoothingGroups.calcSmoothGroups(facesArray, faceNormals, normals.get()); 1691 } else { 1692 smGroups = new int[facesArray.length]; 1693 Arrays.fill(smGroups, 1); 1694 } 1695 1696 PolygonMesh mesh = new PolygonMesh(); 1697 mesh.getPoints().setAll(points); 1698 mesh.getTexCoords().setAll(texCoords); 1699 mesh.faces = facesArray; 1700 mesh.getFaceSmoothingGroups().setAll(smGroups); 1701 return mesh; 1702 } else { 1703 // Split the polygonal faces into triangle faces 1704 List<Integer> ff = new ArrayList<Integer>(); 1705 List<Integer> nn = new ArrayList<Integer>(); 1706 int nIndex = 0; 1707 1708 for (int f = 0; f < faces.size(); f++) { 1709 MPolyFace.FaceData faceData = faces.get(f); 1710 int[] faceEdges = faceData.getFaceEdges(); 1711 int[][] uvData = faceData.getUVData(); 1712 int[] uvIndices = uvData == null ? null : uvData[uvChannel]; 1713 if (faceEdges != null && faceEdges.length > 0) { 1714 1715 // Generate triangle fan about the first vertex 1716 int vIndex0 = edgeStart(faceEdges[0]); 1717 int uvIndex0 = uvIndices == null ? 0 : uvIndices[0]; 1718 int nIndex0 = nIndex++; 1719 1720 int vIndex1 = edgeStart(faceEdges[1]); 1721 int uvIndex1 = uvIndices == null ? 0 : uvIndices[1]; 1722 int nIndex1 = nIndex++; 1723 1724 for (int i = 2; i < faceEdges.length; i++) { 1725 int vIndex2 = edgeStart(faceEdges[i]); 1726 int uvIndex2 = uvIndices == null ? 0 : uvIndices[i]; 1727 int nIndex2 = nIndex++; 1728 1729 ff.add(vIndex0); 1730 ff.add(uvIndex0); 1731 ff.add(vIndex1); 1732 ff.add(uvIndex1); 1733 ff.add(vIndex2); 1734 ff.add(uvIndex2); 1735 nn.add(nIndex0); 1736 nn.add(nIndex1); 1737 nn.add(nIndex2); 1738 1739 vIndex1 = vIndex2; 1740 uvIndex1 = uvIndex2; 1741 } 1742 } 1743 } 1744 int[] fff = new int[ff.size()]; 1745 for (int i = 0; i < fff.length; i++) { 1746 fff[i] = ff.get(i); 1747 } 1748 1749 TriangleMesh mesh = new TriangleMesh(); 1750 int[] smGroups; 1751 // we can only figure out faces' normal indices if the faces' normal indices have a one-to-one ordered correspondence with the normals 1752 if (nIndex == normals.getSize()) { 1753 int[] faceNormals = new int[nn.size()]; 1754 for (int i = 0; i < faceNormals.length; i++) { 1755 faceNormals[i] = nn.get(i); 1756 } 1757 smGroups = SmoothingGroups.calcSmoothGroups(mesh, fff, faceNormals, normals.get()); 1758 } else { 1759 smGroups = new int[fff.length/6]; 1760 Arrays.fill(smGroups, 1); 1761 } 1762 1763 mesh.getPoints().setAll(points); 1764 mesh.getTexCoords().setAll(texCoords); 1765 mesh.getFaces().setAll(fff); 1766 mesh.getFaceSmoothingGroups().setAll(smGroups); 1767 return mesh; 1768 } 1769 } 1770 1771 MNode resolveOutputMesh(MNode n) { 1772 MNode og; 1773 List<MPath> ogc0 = n.getPathsConnectingFrom("og[0]"); 1774 if (ogc0.size() > 0) { 1775 og = ogc0.get(0).getTargetNode(); 1776 } else { 1777 ogc0 = n.getPathsConnectingFrom("og"); 1778 if (ogc0.size() > 0) { 1779 og = ogc0.get(0).getTargetNode(); 1780 } else { 1781 return null; 1782 } 1783 } 1784 if (og.isInstanceOf(meshType)) { 1785 return og; 1786 } 1787 // println("r.OG={og}"); 1788 while (og.isInstanceOf(groupPartsType)) { 1789 og = og.getPathsConnectingFrom("og").get(0).getTargetNode(); 1790 } 1791 if (og.isInstanceOf(meshType)) { 1792 return og; 1793 } 1794 // println("r1.OG={og}"); 1795 if (og == null) { 1796 return null; 1797 } 1798 return resolveOutputMesh(og); 1799 } 1800 1801 MNode resolveInputMesh(MNode n) { 1802 return resolveInputMesh(n, true); 1803 } 1804 1805 MNode resolveInputMesh(MNode n, boolean followBlend) { 1806 MNode groupParts; 1807 if (!n.isInstanceOf(groupPartsType)) { 1808 groupParts = n.getIncomingConnectionToType("ip[0].ig", "groupParts"); 1809 } else { 1810 groupParts = n; 1811 } 1812 MNode origMesh = groupParts.getPathsConnectingTo("ig").get(0).getTargetNode(); 1813 if (origMesh == null) { 1814 MNode tweak = groupParts.getIncomingConnectionToType("ig", "tweak"); 1815 groupParts = tweak.getIncomingConnectionToType("ip[0].ig", "groupParts"); 1816 origMesh = 1817 groupParts.getPathsConnectingTo("ig").get(0).getTargetNode(); 1818 } 1819 // println("N={n} ORIG_MESH={origMesh}"); 1820 if (origMesh == null) { 1821 return null; 1822 } 1823 if (origMesh.isInstanceOf(meshType)) { 1824 return origMesh; 1825 } 1826 if (origMesh.isInstanceOf(blendShapeType)) { 1827 // return the blend shape's output 1828 return resolveOutputMesh(origMesh); 1829 } 1830 return resolveInputMesh(origMesh); 1831 } 1832 1833 MNode resolveOrigInputMesh(MNode n) { 1834 1835 MNode groupParts; 1836 if (!n.isInstanceOf(groupPartsType)) { 1837 groupParts = n.getIncomingConnectionToType("ip[0].ig", "groupParts"); 1838 } else { 1839 groupParts = n; 1840 } 1841 MNode origMesh = groupParts.getPathsConnectingTo("ig").get(0).getTargetNode(); 1842 if (origMesh == null) { 1843 MNode tweak = groupParts.getIncomingConnectionToType("ig", "tweak"); 1844 groupParts = tweak.getIncomingConnectionToType("ip[0].ig", "groupParts"); 1845 origMesh = 1846 groupParts.getPathsConnectingTo("ig").get(0).getTargetNode(); 1847 } 1848 if (origMesh == null) { 1849 return null; 1850 } 1851 // println("N={n} ORIG_MESH={origMesh}"); 1852 if (origMesh.isInstanceOf(meshType)) { 1853 return origMesh; 1854 } 1855 return resolveOrigInputMesh(origMesh); 1856 } 1857 1858 Affine convertMatrix(MFloatArray mayaMatrix) { 1859 if (mayaMatrix == null || mayaMatrix.getSize() < 16) { 1860 return new Affine(); 1861 } 1862 1863 Affine result = new Affine(); 1864 result.setMxx(mayaMatrix.get(0 * 4 + 0)); 1865 result.setMxy(mayaMatrix.get(1 * 4 + 0)); 1866 result.setMxz(mayaMatrix.get(2 * 4 + 0)); 1867 result.setMyx(mayaMatrix.get(0 * 4 + 1)); 1868 result.setMyy(mayaMatrix.get(1 * 4 + 1)); 1869 result.setMyz(mayaMatrix.get(2 * 4 + 1)); 1870 result.setMzx(mayaMatrix.get(0 * 4 + 2)); 1871 result.setMzy(mayaMatrix.get(1 * 4 + 2)); 1872 result.setMzz(mayaMatrix.get(2 * 4 + 2)); 1873 result.setTx(mayaMatrix.get(3 * 4 + 0)); 1874 result.setTy(mayaMatrix.get(3 * 4 + 1)); 1875 result.setTz(mayaMatrix.get(3 * 4 + 2)); 1876 return result; 1877 } 1878 1879 }