Moonlight|3D design overview

Core components and basic plugin architecture

Moonlight|3D has an architecture that relies heavily on plugins. They provide most of the functionality of the program. In this section we cover those portions of the code which build the core program. The main program consists of the following packages (in alphabetical order):

  • ml.backend.anim: The animation support base
  • ml.backend.cache: Persistent scene graph node cache
  • ml.backend.og: The operator graph base
  • ml.backend.renderer: Rendering engine support
  • ml.backend.selection: Framework for object selection
  • ml.backend.sg: The scene graph implementation
  • ml.colour: Basic colour handling functionality
  • ml.core: The very core of Moonlight|3D
  • ml.core.exceptions: Basic exceptions
  • ml.core.helpers: Various helper classes for different patterns
  • ml.core.preferences: The preferences framework
  • ml.core.plugins: The plugin loader
  • ml.core.scripts: Script language-independent scripting framework
  • ml.core.tasks: Background task management
  • ml.file: Loading and saving files
  • ml.image: Image frame buffer handling
  • ml.math: Math classes
  • ml.ui.core: The user interface core

These packages can be grouped into three big sections: the backend, the user interface and the core which binds everything together. We'll describe each group seperately.

The backend

The backend is responsible for managing the scene data and actually executing the manipulations on them. For that, the backend is split in two components: The scene graph in ml.backend.sg, which in itself is just a relatively simple scene graph without any fancy stuff. This is because the secod component, the operator graph, is the "real thing". While ml.backend.anim provides basic animation support, ml.backend.selection, ml.backend.renderer and ml.backend.cache provide additional abstract services for the user interface and for user scripts.

The operator graph is some sort of a log of the user actions to this date. Every manipulation on the scene results in a new node in this graph. This is done so that parameters of user actions are still editable after they were executed. This is quite similar to construction histories, which are usually found in similar, commercial packages. The ml.backend.og package only contains the very core of the operator graph. The actual node implementations, which carry out the scene graph manipulations, are loaded from plugins. Plugins supply a named node factory for each type of node they provide, which allows the operator graph manager to generate them without knowing any details of their implementation.

Each operator graph node has a set of named and typed properties, through which its parameters are exposed. This mechanism is needed for two things: first, the exact type of operator graph nodes does not have to be known in order to edit them and second, to expose them to an animation component, which can then change these parameters based on some animation data, which is managed seperately.

Currently, each operator graph node gets scene graphs into its (named) input slots, which are then processed by the node. The result is one or more new scene graphs, which are pushed into output slots, which may be linked to input slots in other nodes. Each Slot class instance represents a single slot, either input or output. Each Link class instance takes exactly one output slot and conects it to exactly one input slot of another operator graph node. Note that an output slot must be connected with the input of a link, and vice versa. This terminology can be confusing sometimes, so be careful.

Plugins may register own operator graph node factories with the operator graph manager, which allows for easy node creation by name. Operator graph nodes are always created through registered factories by the operator graph manager.

The ml.backend.animation package provides animation support. But since it's not yet completed and subject to change, it is not yet discussed here.

The ml.backend.selection package is the base framework for managing user selections on the operator graph and scene graph. The selection framework in Moonlight relies on a global naming schemes for operator graph and scene graph objects. Every object gets to have a unique name in the form of a path like OperatorGraph/?"og node"/"slot"/"sg node"/"sg node"/"sg node"/.../"sg node":"appendix". This is a complete path. Paths may end just after any element and since scene graph nodes can be nested to an arbitrary level there is no limit to the number of scene graph node names that can be mentioned in paths. One thing to note is the possibility of specifing appendices add the end of the path. Appendices exist to allow specifying non-node elements and values for selection. The Mesh scene graph node is a good example for this. It knows appendices in the form of vertices[index] and faces[index] to allow access to individual elements of a mesh.

Selections are named collections of such paths to elements of the same type. Internally, Moonlight allows for any number of different selections to co-exist at any time as they are distinguished by their name. The implementation of selections heavily relies on the PathElement interface, which is an interface that allows traversal of operator graph nodes, operator graph slots and scene graph nodes in a unified manner to verify selection paths and to retrieve individual objects by path.

The ml.backend.renderer package provides a generalised interface to rendering engines for the application. It defines the RenderEngine abstract class from which render engine support plugins are derived. This class is a PropertyContainer? and the properties that are held by it are treated as renderer settings and stored with each scene.

The ml.backend.cache package is a cache that keeps copies of scene graph segments which can be copied into the scene. These scene graph nodes have no construction history and are stored as is when saving the scene.

The core

The core of Moonlight is - you guessed it - in ml.core. You may have noted by now that the backend and the UI have some classes with singleton characteristics, but have no static accessor methods. This is because they should be accessed through ml.core.State, which is a singleton responsible for managing the other manager classes. If you want one of them, you have to go through ml.core.State to reach them, unless you are provided with a reference to the target anyway.

The plugin loader in ml.core.plugins takes care of loading the plugins in the right order. Every plugin must have a class which implements ml.core.plugins.Plugin. The loader takes a list of classes which implement this plugin interface and tries to load them. Then it queries their dependencies and tries to initialize them while respecting these dependencies. Currently, circular dependencies are not checked for and cannot be handled. Any other dependency structure should be fine, though.

Please note that plugin loading is a process which takes several steps. First the plugin class is instanced, which does not mean that the associated plugin will be loaded at all. This is done to be able to query the dependencies. In a second step the plugin loader checks, which plugins are loadable and have their dependencies satisfied. Only then does it try to load up those plugins which have passed the dependency check. And only at this stage may the plugin actually start to register it's payload (mostly factories) with the core components.

The scripting support in ml.core.scripting defines basic services through which arbitrary script languages can be integrated by plygins. Script language plugins are rather simple-looking plugins providing additional script language bindings through a surprisingly slim interface enabling the scripiting core to detect the language of a particular script file and to execute a given code snippet or file.

The user interface

The user interface core contains the implementation of the main window of Moonlight|3D together with its layout and view management mechanisms.

The so-called "views" are screen areas defined by sashes, which are oocupied by plugin-provided classes. They can be arranged into any given user-specified layout, which is usually loaded from an XML file.

User interface plugins provide all views in Moonlight|3D. For that they have to register a factory with ml.ui.core.ViewManager, which creates and returns a new View. The factory in this case is not the usual ml.core.helper.Factory interface, but a special ViewFactory, because the view must be constructed within a parent widget.

UI plugins can also add elements to frame menu bars. See MoonlightMenuBar for details.

Backend plugins

Backend plugins provide so-called operator graph nodes, which are essentially classes which implement algorithms that operate on scene graphs. One example of such an algorithm is a node which moves mesh vertices or adds faces to a mesh.

Operator graph nodes expose their parameters through properties, which are named and typed. The plane creator OG node for example has two properties, a name and a size. The name property contains the name of the plane that will be created and the size property is essentially the length of the edges of the quad that is generated to simulate that infinite plane.