001/* 002 * Renderer 4. The MIT License. 003 * Copyright (c) 2022 rlkraft@pnw.edu 004 * See LICENSE for details. 005*/ 006 007package renderer.scene.util; 008 009import renderer.scene.*; 010import renderer.scene.primitives.Primitive; 011 012import java.io.File; 013import java.io.PrintWriter; 014import java.lang.Runtime; 015import java.util.List; 016import java.util.ArrayList; 017import java.awt.Color; 018 019/** 020 This program converts a {@link Scene} data structure into 021 a DOT description of the scene. The DOT description is 022 written to a file and that file is processed by the 023 dot.exe program to produce a png file graphical image 024 of the scene data structure. 025<p> 026 A <code>scene.png</code> image file is created from 027 a <code>scene.dot</code> file with the following 028 command-line. 029 <pre>{@code 030 > dot.exe -Tpng -O scene.dot 031 }</pre> 032<p> 033 See 034<br><a href="https://www.graphviz.org/Documentation.php" target="_top"> 035 https://www.graphviz.org/Documentation.php</a> 036<p> 037 This class has four static boolean variables that can be used 038 to control the complexity of the scene graph drawing. 039*/ 040public class DrawSceneGraph 041{ 042 // These four variables allow us to turn on and off the 043 // the drawing of many details in the scene graph. 044 // This helps control the complexity of the scene graph. 045 /** 046 Control the details shown in a {@link Camera} node. 047 */ 048 public static boolean drawCameraDetails = true; 049 /** 050 Control the display of {@link Vector} nodes. 051 */ 052 public static boolean drawVector = true; 053 /** 054 Control the details shown in a {@link Vector} node. 055 */ 056 public static boolean drawVectorDetails = true; 057 /** 058 Control the details shown below a {@link Model} node. 059 */ 060 public static boolean drawVertexList = false; 061 062 // These three variables are used to detect those 063 // nodes in the DAG that have multiple parents. 064 private static List<Object> visitedNodes = null; 065 private static List<String> visitedNodeNames = null; 066 private static int nodeNumber; 067 068 /** 069 This method converts a {@link Scene} data structure into a dot 070 language description. The dot code for the scene is written into 071 a dot file. Then the dot.exe program is called to convert the dot 072 file into a png image of the scene data structure. 073 074 @param scene {@link Scene} that needs to be converted to a dot description 075 @param fileName base name for the dot and png files 076 */ 077 public static void draw(final Scene scene, final String fileName) 078 { 079 // Convert the scene data structure into a dot language description. 080 final String dotDescription = scene2dot(scene); 081 082 // Write the dot language description stored in dotDescription 083 // into a dot file. Then use the dot.exe program to convert the 084 // dot file into a png file. 085 try 086 { 087 // Create the (empty) dot file. 088 final String baseName = fileName; 089 java.io.PrintWriter out = new PrintWriter( 090 new File(baseName + ".dot")); 091 092 // Write the dot commands into the dot file. 093 out.print( dotDescription ); 094 out.close(); 095 096 // Create a command-line for running the dot.exe program. 097 final String dotExecutable = "C:\\Graphviz\\bin\\dot.exe"; 098 final String[] cmd = {dotExecutable, 099 "-Tpng", 100 baseName + ".dot", 101 "-o", 102 baseName + ".png"}; 103 104 final File dot = new File(dotExecutable); 105 if(dot.exists() && !dot.isDirectory()) 106 { 107 // Execute the command-line to create the png file. 108 Runtime.getRuntime().exec(cmd); 109 } 110 else 111 { 112 System.out.println("\nPlease consider installing GraphViz:"); 113 System.out.println(" https://graphviz.org/download/"); 114 System.out.println("or upload the contents of " + baseName + ".dot to Graphviz Visual Editor:"); 115 System.out.println(" http://magjac.com/graphviz-visual-editor/"); 116 } 117 } 118 catch (Exception e) 119 { 120 System.out.println( e ); 121 } 122 } 123 124 125 /** 126 This method generates a dot language description of the 127 DAG rooted at a {@link Scene} node. 128 <p> 129 This method generates the dot code for the forest of top-level 130 positions just below the scene node. Each position node just 131 below the scene node is the root of a DAG. This method calls 132 the <code>position2dot()</code> method to traverse the DAG of 133 each top-level position. 134 135 @param scene {@link Scene} that needs to be converted to a dot description 136 @return a {@link String} containing the dot language description of the scene 137 */ 138 public static String scene2dot(final Scene scene) 139 { 140 // https://stackoverflow.com/questions/48266439/getting-graphviz-to-eliminate-identical-duplicate-edges 141 String result = "strict digraph {\n"; 142 143 // https://graphviz.org/docs/attrs/ordering/ 144 result += "graph [ordering=\"out\"];\n"; 145 146 // https://stackoverflow.com/questions/10879115/graphviz-change-font-for-the-whole-graph 147 // https://graphviz.org/docs/attrs/fontname/ 148 result += "graph [fontname=\"helvetica\"];\n"; 149 result += "node [fontname=\"helvetica\"];\n"; 150 result += "edge [fontname=\"helvetica\"];\n"; 151 152 // Scene node. 153 result += "scene [label=\"Scene: " + scene.name + "\"];\n"; 154 155 // Camera and List<Position> nodes under the Scene node. 156 157 // Camera node and label. 158 final String cameraNodeName = "Camera"; 159 result += cameraNodeName + " "; 160 if (drawCameraDetails) 161 { 162 result += "[label=\"" + scene.camera + "\"];\n"; 163 } 164 else 165 { 166 result += "[label=\"Camera\"];\n"; 167 } 168 // Camera edge. 169 result += "scene -> " + cameraNodeName + ";\n"; 170 171 // List<Position> node and label. 172 final String pListNodeName = "positionList"; 173 result += pListNodeName + " "; 174 result += "[label=\"List<Position>\"];\n"; 175 // List<Position> edge. 176 result += "scene -> " + pListNodeName + ";\n"; 177 178 visitedNodes = new ArrayList<>(); 179 visitedNodeNames = new ArrayList<>(); 180 nodeNumber = -1; 181 182 // For each top-level Position, create a node with two edges, 183 // its model and its translation vector. 184 for (int i = 0; i < scene.positionList.size(); ++i) 185 { 186 // Position node name. 187 ++nodeNumber; 188 final String pNodeName = "_p" + nodeNumber; 189 190 // Position node and label. 191 final Position positionReference = scene.getPosition(i); 192 result += pNodeName + " "; 193 result += "[label=\"Position: " + positionReference.name + "\"];\n"; 194 195 // Position edge. 196 result += pListNodeName + " -> " + pNodeName + ";\n"; 197 198 // This Position's translation vector and model. 199 result += position2dot(positionReference, pNodeName); 200 } 201 202 result += "}\n"; 203 204 return result; 205 } 206 207 208 /** 209 This method generates a dot language description of the 210 DAG rooted at a {@link Position} node. 211 <p> 212 {@code positionName} is the id that has been assigned to 213 the dot node representing the given {@link Position} node. 214 <p> 215 Every {@link Position} node has attached to it a translation {@link Vector} 216 and a {@link Model}. 217 218 @param position {@link Position} that needs to be converted to a dot description 219 @param positionName the {@link String} name that has been assigned to {@code position} 220 @return a {@link String} containing the dot language description of {@code position} 221 */ 222 public static String position2dot(final Position position, 223 final String positionName) 224 { 225 String result = ""; 226 227 if (drawVector || drawVectorDetails) 228 { 229 // Vector node name. 230 final String tNodeName = positionName + "_Matrix"; 231 232 // Vector node and label. 233 result += tNodeName + " "; 234 235 if (drawVectorDetails) 236 { 237 result += "[label=\"Vector:\n" + position.getTranslation() + "\"];\n"; 238 } 239 else 240 { 241 result += "[label=\"Vector\"];\n"; 242 } 243 244 // Vector edge. 245 result += positionName + " -> " + tNodeName + ";\n"; 246 //result += positionName + " -> " + tNodeName + " [constraint=false];\n"; 247 } 248 249 // The position's model. 250 final Model modelReference = position.getModel(); 251 252 // Check if the Model is being reused. 253 final boolean modelVisited = visitedNodes.contains(modelReference); 254 255 if ( ! modelVisited ) 256 { 257 // Model node name. 258 ++nodeNumber; 259 final String mNodeName = "_m" + nodeNumber; 260 // Mark this model as visited. 261 visitedNodes.add(modelReference); 262 visitedNodeNames.add(mNodeName); 263 264 // Model node and label. 265 result += mNodeName + " "; 266 result += "[label=\"Model: " + modelReference.name + "\"];\n"; 267 268 // Model edge. 269 result += positionName + " -> " + mNodeName + ";\n"; 270 271 // The model's vertex, color, and primitive lists. 272 result += model2dot(modelReference, mNodeName); 273 } 274 else // this Model has already been visited 275 { 276 final int index = visitedNodes.indexOf(modelReference); 277 // Model node name. 278 final String mNodeName = visitedNodeNames.get(index); 279 // Model edge (to a previously visited Model node). 280 result += positionName + " -> " + mNodeName + ";\n"; 281 } 282 283 return result; 284 } 285 286 287 /** 288 This method generates a dot language description of the 289 tree rooted at a {@link Model} node. 290 <p> 291 {@code nodeName} is the id that has been assigned to the 292 dot node representing the given {@link Model} node. 293 <p> 294 Every {@link Model} node has attached to it a {@link List} 295 of vertices, a {@link List} of colors, and a {@link List} 296 of primitives. 297 298 @param model {@link Model} that needs to be converted to a dot description 299 @param nodeName the {@link String} name that has been assigned to {@code model} 300 @return a {@link String} containing the dot language description of the model 301 */ 302 public static String model2dot(final Model model, 303 final String nodeName) 304 { 305 String result = ""; 306 307 if (drawVertexList) 308 { 309 // List<Vertex> node and label. 310 final String vertexListNodeName = nodeName + "_vertexList"; 311 result += vertexListNodeName + " "; 312 result += "[label=\"List<Vertex>\"];\n"; 313 // List<Vertex> edge. 314 result += nodeName + " -> " + vertexListNodeName + ";\n"; 315 // List<Vertex> children. 316 int vertexCounter = 0; 317 String lastVertexNodeName = vertexListNodeName; 318 for (Vertex v : model.vertexList) 319 { 320 // Vertex node name. 321 final String vertexNodeName = nodeName + "_v" + vertexCounter; 322 323 // Vertex node and label. 324 result += vertexNodeName + " "; 325 result += "[label=\"Vertex: " + v + "\"];\n"; 326 327 // Vertex edge. 328 result += lastVertexNodeName + " -> " + vertexNodeName + ";\n"; 329 330 lastVertexNodeName = vertexNodeName; 331 ++vertexCounter; 332 } 333 334 335 // List<Color> node and label. 336 final String colorListNodeName = nodeName + "_colorList"; 337 result += colorListNodeName + " "; 338 result += "[label=\"List<Color>\"];\n"; 339 // List<Color> edge. 340 result += nodeName + " -> " + colorListNodeName + ";\n"; 341 // List<Color> children. 342 int colorCounter = 0; 343 String lastColorNodeName = colorListNodeName; 344 for (Color c : model.colorList) 345 { 346 // Color node name. 347 final String colorNodeName = nodeName + "_c" + colorCounter; 348 349 // Color node and label. 350 result += colorNodeName + " "; 351 result += "[label=\"" + c + "\"];\n"; 352 353 // Color edge. 354 result += lastColorNodeName + " -> " + colorNodeName + ";\n"; 355 356 lastColorNodeName = colorNodeName; 357 ++colorCounter; 358 } 359 360 361 // List<Primitive> node and label. 362 final String primitiveListNodeName = nodeName + "_primitiveList"; 363 result += primitiveListNodeName + " "; 364 result += "[label=\"List<Primitive>\"];\n"; 365 // List<Primitive> edge. 366 result += nodeName + " -> " + primitiveListNodeName + ";\n"; 367 // List<Primitive> children. 368 int primitiveCounter = 0; 369 String lastPrimitiveNodeName = primitiveListNodeName; 370 for (Primitive p : model.primitiveList) 371 { 372 // Primitive node name. 373 final String primitiveNodeName = nodeName + "_p" + primitiveCounter; 374 375 // Primitive node and label. 376 result += primitiveNodeName + " "; 377 result += "[label=\"" + p + "\"];\n"; 378 379 // Primitive edge. 380 result += lastPrimitiveNodeName + " -> " + primitiveNodeName + ";\n"; 381 382 lastPrimitiveNodeName = primitiveNodeName; 383 ++primitiveCounter; 384 } 385 } 386 387 return result; 388 } 389 390 391 // Private default constructor to enforce noninstantiable class. 392 // See Item 4 in "Effective Java", 3rd Ed, Joshua Bloch. 393 private DrawSceneGraph() { 394 throw new AssertionError(); 395 } 396}