Scene Graph 102 : Introduction to the Scene Graph

November 20, 2012, Posted by Phil Taylor

The following post is based on a workshop presented at the Montreal Fabric Engine user group. All of the source code can be found online in the repository location:

https://github.com/fabric-engine/UsergroupWorkshops/tree/master/WorkshopNovember2012/IntroductionToTheCore

You can watch a video presentation of the complete workshop here.

Defining a Scene Graph Platform Application


import math, random
from PySide import QtGui, QtCore

from MandelbrotImage import MandelbrotImage

from FabricEngine.CreationPlatform.PySide import *
class MandelBrotViewerApp(Basic3DDemoApplication):

  def __init__(self):
      super(MandelBrotViewerApp, self).__init__(
      setupGlobalTimeNode = True,
      setupSelection = False,
      setupUndoRedo = True,
      setupPersistence = True,
      enableRaycasting = True,
      setupImport = True
    )

   self.setWindowTitle("MandelBrot Viewer App")
   self.constructionCompleted()

MandelBrotViewerApp().exec_()

Defining a Custom Scene Graph Node

Scene Graph Nodes are python classes that define the structure of the Creation Platform’s scene graph. There is a wide range of scene graph nodes defined in Creation Platform, making it easy to take one of the base nodes types and extend it according to your own requirements.
In this example, we define a new custom Image node that display a generated mandelbrot set. The MandelbrotImage node exposes a time port enabling it to be connected to time nodes so it can be driven by the time value.

from FabricEngine.CreationPlatform.Nodes.Images.Image2DImpl import Image2D
from FabricEngine.CreationPlatform.Nodes.Animation.TimeImpl import Time

class MandelbrotImage(Image2D):

  def __init__(self, scene, **options):

    ###########################################
    # 1.0 Setup the base image

    options.setdefault('width', 640)
    options.setdefault('height', 480)
    options.setdefault('format', 'RGB')
    options.setdefault('createSlicedDGNode', True)
    options.setdefault('forceRefresh', True)

    super(MandelbrotImage, self).__init__(scene, **options)

    ###########################################
    # 2.0 Setup the parameters for the madelbrot set.
    imageAttributesDGNode = self.getAttributesDGNode()

    imageAttributesDGNode.addMember("center", "Complex64", Complex64(0.0, 0.0))
    imageAttributesDGNode.addMember("zoom", "Float64", 1.0)
    imageAttributesDGNode.addMember("maxIterations", "Size", 1536)

    self._addMemberInterface(imageAttributesDGNode, 'center', True)
    self._addMemberInterface(imageAttributesDGNode, 'zoom', True)
    self._addMemberInterface(imageAttributesDGNode, 'maxIterations', True)

    ###########################################
    # 3.0 Setup the pixels node.
    pixelsDGNode = self.getPixelsDGNode()
    pixelsDGNode.setDependency(imageAttributesDGNode, 'parameters')

    self.bindDGOperator(pixelsDGNode.bindings,
      name = 'resizeNode',
      fileName = FabricEngine.CreationPlatform.buildAbsolutePath('Mandelbrot.kl'),
      layout = [
        'self',
        'attributes.width',
        'attributes.height',
      ]
    )
    self.bindDGOperator(pixelsDGNode.bindings,
      name = 'generateMandelbrot',
      fileName = FabricEngine.CreationPlatform.buildAbsolutePath('Mandelbrot.kl'),
      layout = [
        'self.pixels',
        'attributes.center',
        'attributes.zoom',
        'attributes.maxIterations',
        'self.index',
        'attributes.width',
        'attributes.height',
      ]
    )

    ###########################################
    # 4.0 Setup time reference

    self.__timeOpBindings = None
    def __changeTimeNode(data):
      if data['prevNode'] is not None and data['node'] is None:
        if self.__timeOpBindings is not None:
          self.removeDGOperatorBinding(imageAttributesDGNode.bindings, self.__timeOpBindings)

        imageAttributesDGNode.removeDependency('time')
      if data['node'] is not None:
        imageAttributesDGNode.setDependency(data['node'].getDGNode(), 'time')
        self.__timeOpBindings = self.bindDGOperator(imageAttributesDGNode.bindings,
          name = 'setTime',
          sourceCode = 'operator setTime(Scalar time, io Float64 zoom){ zoom = 1.0 + time; }',
          layout = [
            'time.time',
            'self.zoom'
          ]
        )

    self.addReferenceInterface('Time', Time, False, __changeTimeNode)
###########################################
# 5.0 Register the new custom node so that it will show up in the
# user interfaces, and be saved and loaded with the scene files.

MandelbrotImage.registerNodeClass('MandelbrotImage')

  1. The base class constructor can be invoked with parameters to setup an empty image node.
  2. New members can be added to the dependency graph node defined in the base class. The ‘AddMemberInterface’ methods expose the specified values on the user interface of the node.
  3. Operators are bound to the dependency graph node that will compute the pixel values of the mandelbrot set.
  4. A ‘reference interface is added to the node, enabling the node to be connected to Time nodes, to drive the zoom value of the mandelbrot set.

The resulting MandelbrotImage node can then be constructed in the node graph and connected to the background texture port on the Viewport node.

The next part of this tutorial can be found here.

Comments