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.exporters.javasource; 33 34 import java.io.BufferedWriter; 35 import java.io.File; 36 import java.io.FileWriter; 37 import java.io.IOException; 38 import java.io.OutputStreamWriter; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Collections; 42 import java.util.HashMap; 43 import java.util.List; 44 import java.util.Map; 45 import javafx.animation.Interpolator; 46 import javafx.animation.KeyFrame; 47 import javafx.animation.KeyValue; 48 import javafx.animation.Timeline; 49 import javafx.beans.value.WritableValue; 50 import javafx.scene.Group; 51 import javafx.scene.Node; 52 import javafx.scene.image.Image; 53 import javafx.scene.paint.Color; 54 import javafx.scene.paint.PhongMaterial; 55 import javafx.scene.shape.MeshView; 56 import javafx.scene.shape.TriangleMesh; 57 import javafx.scene.transform.Rotate; 58 import javafx.scene.transform.Scale; 59 import javafx.scene.transform.Transform; 60 import javafx.scene.transform.Translate; 61 import javafx.util.Duration; 62 import com.javafx.experiments.importers.Optimizer; 63 import com.javafx.experiments.importers.maya.MayaImporter; 64 import com.sun.javafx.animation.TickCalculation; 65 import com.sun.scenario.animation.NumberTangentInterpolator; 66 import com.sun.scenario.animation.SplineInterpolator; 67 68 /** 69 * A exporter for 3D Models and animations that creates a Java Source file. 70 */ 71 public class JavaSourceExporter { 72 private int nodeCount = 0; 73 private int materialCount = 0; 74 private int meshCount = 0; 75 private int meshViewCount = 0; 76 private int methodCount = 1; 77 private int translateCount = 0; 78 private int rotateCount = 0; 79 private int scaleCount = 0; 80 private Map<WritableValue,String> writableVarMap = new HashMap<>(); 81 private StringBuilder nodeCode = new StringBuilder(); 82 private StringBuilder timelineCode = new StringBuilder(); 83 private final boolean hasTimeline; 84 private final String baseUrl; 85 private final String className; 86 private final String packageName; 87 private final File outputFile; 88 89 public JavaSourceExporter(String baseUrl, Node rootNode, Timeline timeline, File outputFile) { 90 this(baseUrl, rootNode, timeline,computePackageName(baseUrl), outputFile); 91 } 92 93 public JavaSourceExporter(String baseUrl, Node rootNode, Timeline timeline, String packageName, File outputFile) { 94 this.baseUrl = 95 (baseUrl.charAt(baseUrl.length()-1) == '/') ? 96 baseUrl.replaceAll("/+","/") : 97 baseUrl.replaceAll("/+","/") + '/'; 98 this.hasTimeline = timeline != null; 99 this.className = outputFile.getName().substring(0,outputFile.getName().lastIndexOf('.')); 100 this.packageName = packageName; 101 this.outputFile = outputFile; 102 process(" ",rootNode); 103 if (hasTimeline) process(" ",timeline); 104 } 105 106 private static String computePackageName(String baseUrl) { 107 // remove protocol from baseUrl 108 System.out.println("JavaSourceExporter.computePackageName baseUrl = " + baseUrl); 109 baseUrl = baseUrl.replaceAll("^[a-z]+:/+","/"); 110 System.out.println("baseUrl = " + baseUrl); 111 // try and work out package name from file 112 StringBuilder packageName = new StringBuilder(); 113 String[] pathSegments = baseUrl.split(File.separatorChar == '\\'?"\\\\":"/"); 114 System.out.println("pathSegments = " + Arrays.toString(pathSegments)); 115 loop: for (int i = pathSegments.length-1; i >= 0; i -- ) { 116 switch (pathSegments[i]){ 117 case "com": 118 case "org": 119 case "net": 120 packageName.insert(0,pathSegments[i]); 121 break loop; 122 case "src": 123 packageName.deleteCharAt(0); 124 break loop; 125 case "main": 126 if ("src".equals(pathSegments[i-1])) { 127 packageName.deleteCharAt(0); 128 break loop; 129 } 130 default: 131 packageName.insert(0,'.'+pathSegments[i]); 132 break; 133 } 134 // if we get all way to root of file system then we have failed 135 if (i==0) packageName = null; 136 } 137 System.out.println(" packageName = " + packageName); 138 return packageName==null?null:packageName.toString(); 139 } 140 141 public void export() { 142 try { 143 BufferedWriter out = new BufferedWriter(new FileWriter(outputFile)); 144 // BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out)); 145 StringBuilder nodeVars = new StringBuilder(); 146 nodeVars.append(" private static Node "); 147 for (int i=0; i<nodeCount; i++) { 148 if (i!=0) nodeVars.append(','); 149 nodeVars.append("NODE_"+i); 150 } 151 nodeVars.append(";\n"); 152 nodeVars.append(" private static TriangleMesh "); 153 for (int i=0; i<meshCount; i++) { 154 if (i!=0) nodeVars.append(','); 155 nodeVars.append("MESH_"+i); 156 } 157 nodeVars.append(";\n"); 158 if (translateCount > 0) { 159 nodeVars.append(" private static Translate "); 160 for (int i=0; i<translateCount; i++) { 161 if (i!=0) nodeVars.append(','); 162 nodeVars.append("TRANS_"+i); 163 } 164 nodeVars.append(";\n"); 165 } 166 if (rotateCount > 0) { 167 nodeVars.append(" private static Rotate "); 168 for (int i=0; i<rotateCount; i++) { 169 if (i!=0) nodeVars.append(','); 170 nodeVars.append("ROT_"+i); 171 } 172 nodeVars.append(";\n"); 173 } 174 if (scaleCount > 0) { 175 nodeVars.append(" private static Scale "); 176 for (int i=0; i<scaleCount; i++) { 177 if (i!=0) nodeVars.append(','); 178 nodeVars.append("SCALE_"+i); 179 } 180 nodeVars.append(";\n"); 181 } 182 nodeVars.append(" public static final MeshView[] MESHVIEWS = new MeshView[]{ "); 183 for (int i=0; i<meshViewCount; i++) { 184 if (i!=0) nodeVars.append(','); 185 nodeVars.append("new MeshView()"); 186 } 187 nodeVars.append("};\n"); 188 // nodeVars.append(" public static final MeshView "); 189 // for (int i=0; i<meshViewCount; i++) { 190 // if (i!=0) nodeVars.append(','); 191 // nodeVars.append("MESHVIEW_"+i+" = new MeshView()"); 192 // } 193 // nodeVars.append(";\n"); 194 195 nodeVars.append(" public static final Node ROOT;\n"); 196 nodeVars.append(" public static final Map<String,MeshView> MESHVIEW_MAP;\n"); 197 if (hasTimeline) { 198 nodeVars.append(" public static final Timeline TIMELINE = new Timeline();\n"); 199 } 200 StringBuilder methodCode = new StringBuilder(); 201 for (int m=0; m< methodCount;m++) { 202 methodCode.append(" method"+m+"();\n"); 203 } 204 205 if (packageName != null) out.write( 206 "package "+packageName+";\n\n"); 207 out.write( 208 "import java.util.*;\n" + 209 "import javafx.util.Duration;\n" + 210 "import javafx.animation.*;\n" + 211 "import javafx.scene.*;\n" + 212 "import javafx.scene.paint.*;\n" + 213 "import javafx.scene.image.*;\n" + 214 "import javafx.scene.shape.*;\n" + 215 "import javafx.scene.transform.*;\n\n" + 216 "public class "+className+" {\n" + 217 nodeVars + 218 " // ======== NODE CODE ===============\n" + 219 " private static void method0(){\n" + 220 nodeCode + 221 " }\n" + 222 " static {\n" + 223 methodCode + 224 " // ======== TIMELINE CODE ===============\n" + 225 timelineCode + 226 " // ======== SET PUBLIC VARS ===============\n" + 227 " Map<String,MeshView> meshViewMap = new HashMap<String,MeshView>();\n" + 228 " for (MeshView meshView: MESHVIEWS) meshViewMap.put(meshView.getId(),meshView);\n" + 229 " MESHVIEW_MAP = Collections.unmodifiableMap(meshViewMap);\n" + 230 " ROOT = NODE_0;\n" + 231 " }\n" + 232 "}\n"); 233 234 out.flush(); 235 out.close(); 236 } catch (IOException e) { 237 e.printStackTrace(); 238 } 239 } 240 241 private int process(String indent, Node node) { 242 if (node instanceof MeshView) { 243 return process(indent,(MeshView)node); 244 } else if (node instanceof Group) { 245 return process(indent,(Group)node); 246 } else { 247 throw new UnsupportedOperationException("Found unknown node type: "+node.getClass().getName()); 248 } 249 } 250 251 private int process(String indent, MeshView node) { 252 final int index = nodeCount ++; 253 final String varName = "NODE_"+index; 254 final int meshViewIndex = meshViewCount ++; 255 final String meshViewVarName = "MESHVIEWS["+meshViewIndex+"]"; 256 // nodeCode.append(indent+meshViewVarName+" = new MeshView();\n"); 257 nodeCode.append(indent+varName+" = "+meshViewVarName+";\n"); 258 if (node.getId() != null) nodeCode.append(indent+meshViewVarName+".setId(\""+node.getId()+"\");\n"); 259 nodeCode.append(indent+meshViewVarName+".setCullFace(CullFace."+node.getCullFace().name()+");\n"); 260 processNodeTransforms(indent,meshViewVarName,node); 261 process(indent,meshViewVarName,(PhongMaterial)node.getMaterial()); 262 process(indent,meshViewVarName,(TriangleMesh)node.getMesh()); 263 return index; 264 } 265 266 private void processNodeTransforms(String indent, String varName, Node node) { 267 if (node.getTranslateX() != 0) nodeCode.append(indent+varName+".setTranslateX("+node.getTranslateX()+");\n"); 268 if (node.getTranslateY() != 0) nodeCode.append(indent+varName+".setTranslateY("+node.getTranslateY()+");\n"); 269 if (node.getTranslateZ() != 0) nodeCode.append(indent+varName+".setTranslateZ("+node.getTranslateZ()+");\n"); 270 if (node.getRotate() != 0) nodeCode.append(indent+varName+".setRotate("+node.getRotate()+");\n"); 271 if (!node.getTransforms().isEmpty()) { 272 nodeCode.append(indent+varName+".getTransforms().addAll(\n"); 273 for (int i=0; i< node.getTransforms().size(); i++) { 274 if (i!=0) nodeCode.append(",\n"); 275 Transform transform = node.getTransforms().get(i); 276 if (transform instanceof Translate) { 277 Translate t = (Translate)transform; 278 nodeCode.append(indent+" "+storeTransform(t)+" = new Translate("+t.getX()+","+t.getY()+","+t.getZ()+")"); 279 } else if (transform instanceof Scale) { 280 Scale s = (Scale)transform; 281 nodeCode.append(indent+" "+storeTransform(s)+" = new Scale("+s.getX()+","+s.getY()+","+s.getZ()+","+s.getPivotX()+","+s.getPivotY()+","+s.getPivotZ()+")"); 282 } else if (transform instanceof Rotate) { 283 Rotate r = (Rotate)transform; 284 nodeCode.append(indent+" "+storeTransform(r)+" = new Rotate("+r.getAngle()+","+r.getPivotX()+","+r.getPivotY()+","+r.getPivotZ()+","); 285 if (r.getAxis() == Rotate.X_AXIS) { 286 nodeCode.append("Rotate.X_AXIS"); 287 } else if (r.getAxis() == Rotate.Y_AXIS) { 288 nodeCode.append("Rotate.Y_AXIS"); 289 } else if (r.getAxis() == Rotate.Z_AXIS) { 290 nodeCode.append("Rotate.Z_AXIS"); 291 } 292 nodeCode.append(")"); 293 } else { 294 throw new UnsupportedOperationException("Unknown Transform Type: "+transform.getClass()); 295 } 296 297 } 298 nodeCode.append("\n"+indent+");\n"); 299 } 300 } 301 302 private String storeTransform(Transform transform) { 303 String varName; 304 if (transform instanceof Translate) { 305 final int index = translateCount ++; 306 varName = "TRANS_"+index; 307 Translate t = (Translate)transform; 308 writableVarMap.put(t.xProperty(),varName+".xProperty()"); 309 writableVarMap.put(t.yProperty(),varName+".yProperty()"); 310 writableVarMap.put(t.zProperty(),varName+".zProperty()"); 311 } else if (transform instanceof Scale) { 312 final int index = scaleCount ++; 313 varName = "SCALE_"+index; 314 Scale s = (Scale)transform; 315 writableVarMap.put(s.xProperty(),varName+".xProperty()"); 316 writableVarMap.put(s.yProperty(),varName+".yProperty()"); 317 writableVarMap.put(s.zProperty(),varName+".zProperty()"); 318 writableVarMap.put(s.pivotXProperty(),varName+".pivotXProperty()"); 319 writableVarMap.put(s.pivotYProperty(),varName+".pivotYProperty()"); 320 writableVarMap.put(s.pivotZProperty(),varName+".pivotZProperty()"); 321 } else if (transform instanceof Rotate) { 322 final int index = rotateCount ++; 323 varName = "ROT_"+index; 324 Rotate r = (Rotate)transform; 325 writableVarMap.put(r.angleProperty(),varName+".angleProperty()"); 326 } else { 327 throw new UnsupportedOperationException("Unknown Transform Type: "+transform.getClass()); 328 } 329 return varName; 330 } 331 332 private int process(String indent, Group node) { 333 final int index = nodeCount ++; 334 final String varName = "NODE_"+index; 335 List<Integer> childIndex = new ArrayList<>(); 336 for (int i = 0; i < node.getChildren().size(); i++) { 337 Node child = node.getChildren().get(i); 338 childIndex.add( 339 process(indent,child)); 340 } 341 nodeCode.append(indent+varName+" = new Group("); 342 for (int i = 0; i < childIndex.size(); i++) { 343 if (i!=0) nodeCode.append(','); 344 nodeCode.append("NODE_"+childIndex.get(i)); 345 } 346 nodeCode.append(");\n"); 347 processNodeTransforms(indent, varName, node); 348 nodeCode.append(" }\n private static void method" + (methodCount++) + "(){\n"); 349 return index; 350 } 351 352 private void process(String indent, String varName, TriangleMesh mesh) { 353 final int index = meshCount ++; 354 final String meshName = "MESH_"+index; 355 356 nodeCode.append(indent+meshName+" = new TriangleMesh();\n"); 357 nodeCode.append(indent+varName+".setMesh("+meshName+");\n"); 358 359 nodeCode.append(indent+meshName+".getPoints().ensureCapacity("+mesh.getPoints().size()+");\n"); 360 nodeCode.append(indent + meshName + ".getPoints().addAll("); 361 for (int i = 0; i < mesh.getPoints().size(); i++) { 362 if (i!=0) nodeCode.append(','); 363 nodeCode.append(mesh.getPoints().get(i)+"f"); 364 } 365 nodeCode.append(");\n"); 366 nodeCode.append(" }\n private static void method" + (methodCount++) + "(){\n"); 367 nodeCode.append(indent+meshName+".getTexCoords().ensureCapacity("+mesh.getTexCoords().size()+");\n"); 368 nodeCode.append(indent + meshName + ".getTexCoords().addAll("); 369 for (int i = 0; i < mesh.getTexCoords().size(); i++) { 370 if (i!=0) nodeCode.append(','); 371 nodeCode.append(mesh.getTexCoords().get(i)+"f"); 372 } 373 nodeCode.append(");\n"); 374 nodeCode.append(" }\n private static void method" + (methodCount++) + "(){\n"); 375 nodeCode.append(indent+meshName+".getFaces().ensureCapacity("+mesh.getFaces().size()+");\n"); 376 nodeCode.append(indent + meshName + ".getFaces().addAll("); 377 for (int i = 0; i < mesh.getFaces().size(); i++) { 378 if ((i%5000) == 0 && i > 0) { 379 nodeCode.append(");\n"); 380 nodeCode.append(" }\n private static void method" + (methodCount++) + "(){\n"); 381 nodeCode.append(indent+meshName+".getFaces().addAll("); 382 } else if (i!=0) { 383 nodeCode.append(','); 384 } 385 nodeCode.append(mesh.getFaces().get(i)); 386 } 387 nodeCode.append(");\n"); 388 nodeCode.append(" }\n private static void method" + (methodCount++) + "(){\n"); 389 nodeCode.append(indent+meshName+".getFaceSmoothingGroups().ensureCapacity("+mesh.getFaceSmoothingGroups().size()+");\n"); 390 nodeCode.append(indent+meshName+".getFaceSmoothingGroups().addAll("); 391 for (int i = 0; i < mesh.getFaceSmoothingGroups().size(); i++) { 392 if (i!=0) nodeCode.append(','); 393 nodeCode.append(mesh.getFaceSmoothingGroups().get(i)); 394 } 395 nodeCode.append(");\n"); 396 397 // nodeCode.append(indent+varName+".setMesh("+process((TriangleMesh)node.getMesh())+");\n"); 398 } 399 400 private void process(String indent, String varName, PhongMaterial material) { 401 final int index = materialCount ++; 402 final String materialName = "MATERIAL_"+index; 403 404 nodeCode.append(indent + "PhongMaterial " + materialName + " = new PhongMaterial();\n"); 405 nodeCode.append(indent + materialName + ".setDiffuseColor(" + toCode(material.getDiffuseColor()) + ");\n"); 406 String specColor = toCode(material.getSpecularColor()); 407 if (specColor!=null) nodeCode.append(indent + materialName + ".setSpecularColor(" + specColor + ");\n"); 408 nodeCode.append(indent + materialName + ".setSpecularPower("+material.getSpecularPower()+");\n"); 409 if (material.getDiffuseMap() != null) { 410 nodeCode.append(indent + "try {\n"); 411 nodeCode.append(indent + " " + materialName + ".setDiffuseMap("+toString(material.getDiffuseMap())+");\n"); 412 nodeCode.append(indent + "} catch (NullPointerException npe) {\n"); 413 nodeCode.append(indent + " System.err.println(\"Could not load texture resource ["+material.getDiffuseMap().impl_getUrl()+"]\");\n"); 414 nodeCode.append(indent + "}\n"); 415 } 416 if (material.getBumpMap() != null) { 417 nodeCode.append(indent + materialName + ".setBumpMap("+toString(material.getBumpMap())+");\n"); 418 } 419 if (material.getSpecularMap() != null) { 420 nodeCode.append(indent + materialName + ".setSpecularMap()("+toString(material.getSpecularMap())+");\n"); 421 } 422 if (material.getSelfIlluminationMap() != null) { 423 nodeCode.append(indent + materialName + ".setSelfIlluminationMap()("+toString(material.getSelfIlluminationMap())+");\n"); 424 } 425 nodeCode.append(indent+varName+".setMaterial("+materialName+");\n"); 426 } 427 428 private String toString(Image image) { 429 String url = image.impl_getUrl(); 430 if (url.startsWith(baseUrl)) { 431 return "new Image("+className+".class.getResource(\""+url.substring(baseUrl.length())+"\").toExternalForm())"; 432 } else { 433 return "new Image(\""+url+"\")"; 434 } 435 } 436 437 private void process(String indent, Timeline timeline) { 438 int count = 0; 439 for (KeyFrame keyFrame: timeline.getKeyFrames()) { 440 if (keyFrame.getValues().isEmpty()) continue; 441 nodeCode.append(indent+"TIMELINE.getKeyFrames().add(new KeyFrame(Duration.millis("+keyFrame.getTime().toMillis()+"d),\n"); 442 boolean firstKeyValue = true; 443 for (KeyValue keyValue: keyFrame.getValues()) { 444 if (firstKeyValue) { 445 firstKeyValue = false; 446 } else { 447 nodeCode.append(",\n"); 448 } 449 String var = writableVarMap.get(keyValue.getTarget()); 450 if (var == null) System.err.println("Failed to find writable value in map for : "+keyValue.getTarget()); 451 nodeCode.append(indent+" new KeyValue("+var+","+keyValue.getEndValue()+","+toString(keyValue.getInterpolator())+")"); 452 } 453 nodeCode.append("\n"+indent+"));\n"); 454 455 if (count > 0 && ((count % 10) == 0)) { 456 nodeCode.append(" }\n private static void method" + (methodCount++) + "(){\n"); 457 } 458 count ++; 459 } 460 } 461 462 private String toString(Interpolator interpolator) { 463 // if (interpolator == Interpolator.DISCRETE || true) { 464 if (interpolator == Interpolator.DISCRETE) { 465 return "Interpolator.DISCRETE"; 466 } else if (interpolator == Interpolator.EASE_BOTH) { 467 return "Interpolator.EASE_BOTH"; 468 } else if (interpolator == Interpolator.EASE_IN) { 469 return "Interpolator.EASE_IN"; 470 } else if (interpolator == Interpolator.EASE_OUT) { 471 return "Interpolator.EASE_OUT"; 472 } else if (interpolator == Interpolator.LINEAR) { 473 return "Interpolator.LINEAR"; 474 } else if (interpolator instanceof SplineInterpolator) { 475 SplineInterpolator si = (SplineInterpolator)interpolator; 476 return "Interpolator.SPLINE("+si.getX1()+"d,"+si.getY1()+"d,"+si.getX2()+"d,"+si.getY2()+"d)"; 477 } else if (interpolator instanceof NumberTangentInterpolator) { 478 NumberTangentInterpolator ti = (NumberTangentInterpolator)interpolator; 479 return "Interpolator.TANGENT(Duration.millis("+ TickCalculation.toMillis((long)ti.getInTicks())+"d),"+ti.getInValue() 480 +"d,Duration.millis("+TickCalculation.toMillis((long)ti.getInTicks())+"d),"+ti.getOutValue()+"d)"; 481 } else { 482 throw new UnsupportedOperationException("Unknown Interpolator type: "+interpolator.getClass()); 483 } 484 } 485 486 private String toCode(Color color) { 487 return color == null ? null : "new Color("+color.getRed()+","+color.getGreen()+","+color.getBlue()+","+color.getOpacity()+")"; 488 } 489 }