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}