The Modelzilla database is so useful that we have considered splitting it of into separate package that would work independently of the 3D graphics application framework. However as currently written it is geared towards 3D graphics. Possibly the parts not specific to 3D graphics could be separated for use in other interactive software.
The Modelzilla database resembles embedded object-oriented database. It is a tree based, rather than a table based. The tree based system is more appropriate for a physical modeling system than a table based system. Tree based systems mirror the physical agglomerations that occur in the physical systems. In most cases properties of an object are best described by sub objects. But sometimes properties or effectors of an object are better left as independent objects that have no special attachment. Because of this, the Modelzilla database also supports arbitrary cross linking between tree nodes. For example, say we have a model of a person, and one property effecting him is the quantity of hair on his head. This property is best made a child node of the person, possibly enclosed by a scalp or a head node. Another example, say we have a person sitting on a chair, which is affecting him by not letting him fall to the floor. The chair is best kept as a separate node in the tree whose relationship to the person is kept via a cross link.
The basic idea of the user input database is the following: in a computer modeling system such as Modelzilla the user inputs information that is then used compute derived information, such as a geometry or an in memory representation of a molecule. The derived data is the output. So Modelzilla database is an input/output machine. Derived data is then often used as input to compute further derived data. The Modelzilla database facilitates the storage user input and facilitates the transfer down to derived data that is eventually displayed in a view-port.
(In this documentation we're going to try to use a convention that "information" is a higher level idea with connotations towards direct human accessibility, wheras "data" is a lower level idea of this in the experimental case taken directly from observations of nature, or in the modeling case where the transfer goes the other way, the data is computed to try resemble observations of nature from the input information.)
Does this wonderful database hinder the creativity of the application developer? The answer of course is no. The application developer uses the database by extending generic data nodes with nodes designed to store his own specialized user input data, and nodes designed to compute his own specialized derived data. The database supports the application developer by allowing his code to interoperate with data from other applications. It allows the user to express relationships between models from different applications. Without the database the system would be a mess to use.
The decision of what information is stored in the database simple. Information that is directly controlled by the user should be the database. Most of the time, the application developer will need to store some information that only his application knows about in his own special place. This could be out side the database, or within a data node extension. For example, our GridZilla application has a data node that holds volumetric data. It keeps a file name as user input data, and reads the volumetric data in the process of computing its derived data. It keeps this data by assigning it to its internal fields that are separate from the database. Other data nodes defined by GridZilla know about this type of node and can ask it for its volumetric data. Derived data is often passed around between specialized nodes via specialized functions like GridNode.getVolumeData() in GridZilla. However it is possible to register that a node is a source of a certain type of derived data. This allows other nodes to request the data without knowing the data source node's exact type.
A statement of fact, although not a recommendation, is that it is possible for the application developer to go around the Modelzilla database, and add geometries directly to the ViewerCore layer. But this is a bad idea. This would make geometries or operations and application builds or does inoperable with other applications. The rouge application's geometries would be in the view-port but the user would have no handle to them from other applications. The data nodes are the main organizational feature of application interoperability. As an example, an application that computes a molecular structure and makes a geometrical depiction of it and just dumps the geometry in a viewport is not useful to a user who wants to use our GridZilla application to map electrostatic potential data onto its surface. The user would have no way of selecting the molecular depiction and inputting it into GridZilla surface coloring operator.
We see two main reasons for an application developer to develop application that uses the Modelzilla framework.
The first reason is to save to development effort. The first two layers of the Modelzilla framework, the geometry layer and the graphics layer, make a nice library for doing 3D graphics that is embeddable within any application. The third layer, the database, is also a nice library for storing user input data assuming its organization is appropriate for the specialized application. It is easy to use for all of the applications we have developed so far, but we are not assuming that this will be true for any application.
The second reason is to enable integration for your application(s) with other physical modeling applications. We have discussed this enough. Unfortunately, for full integration the application developer is forced to use a rather presumptuous database. This is because the Modelzilla database is serving both as a library to be used by applications, and the interface allowing applications to plug in to the inter-operation framework Conceivably there should be a separate set of classes or interfaces for achieving this goal.
Such a set of classes and interfaces would describe abstract functions to:
In the future we're hoping to separate the data storage mechanism and the application interoperation mechanism. In this improved system the application developer could define his own data structures that aren't linked to the database by extending ModelNodes.
Because we wanted it to be possible for class already extending other classes to be members of the database, the interface DataNode is really the minimum requirement. The ModelNode class is simply a default concrete implementation of DataNode. SimpleNode is another concrete implementation of DataNode, that is smaller and simpler than ModelNode. The general data storage and transfer mechanisms expect only a DataNode, for the most part. This is a rather large interface, and in practice all of the existing application modules subclass ModelNode for their storage, except for VincentVanMol, which builds molecular data over the SimpleNode.
Note: This system may change significantly. We are considering separating the "structure" and the "payload". In the new system, the specific data would still be kept under a ModelNode, which would be the payload of a TreeNode. This would simplify the DataNode interface. Or conceivably the payload wouldn't have to implement or extend any particular interface at all. A good design might be to have a third object that handles the communication between the payload and the general database system. This would enable special data objects (such as molecular data) from other independent libraries to live within the Modelzilla database while at the same time live in their own systems. It could be called DataNodeAdapter.
Individual pieces of user input data are to be stored in extensions of ModelNode. The package mz.data.fields has many examples of extensions to ModelNode. For example, ScalarField holds a single scalar value. It keeps this value internally as a Java primitive float. It has methods for returning the scalar value, and assigning the scalar value. When the value is reassigned via the access functions, the ScalarField class calls ModelNode.valChanged(), which is a convenient function that causes a NodeEvent.VALUE_CHANGED event to be sent to ModelNode event listeners. The ScalarField also knows how to build a user interface widget to control its value, it knows how to represent itself as XML, and it knows how to record its state for undo/redo.
But it is a very simple data node. The ScalarField class does not have the capability of computing derived data. It simply holds a scalar value. The ScalarField class is also totally self contained in is user specification; it does not accept input from other nodes. Its entirely is defined by that single scalar value. (Actually this is not strictly true. Any ModelNode can be linked by user to another ModelNode with the same type, but this is rather a separate idea.) In other words, the ScalarField is a leaf.
A ModelNode may be composed of other ModelNodes. The most common case of composing a ModelNode is declaring that is always has a fixed set of child nodes, where each of these children has a fixed name and type. But this is just like a JAVA field declared in a class. Because of this similarity, the function ModelNode.addField() is used to add a child nodes of guaranteed name and existence. Typically when adding a field node, it is convenient to also assign it to a real JAVA primitive field for convenient access to it from the parent node. Field nodes are typically added to the parent nodes in the parent node's constructor function, guaranteeing that they are always there. The user is not allowed to remove field nodes. Field nodes are represented differently in the Modelzilla tree/network editor. Fields are also stored differently in the MVG files. Field nodes are the mechanical equivalent of simply keeping user input data and an ad hoc Java primitive. (But of course keeping the data in a smart object such as a ModelNode instead of a primitive has the advantage user select-ability, participation in automatic file saving, change event notification, etc.)
As an example of using field nodes, in the CADZilla application there is a ModelNode that
holds the user specifications for a circle, called CircleFromCenterRadiusShape. In the
constructor, this class adds a center and a radius as ModelNode fields like this:
Possibly the classes in mz.data.fields should be renamed to end in Node instead of Field.
There is nothing special at all about a class that ends in Field. They do not have to be
added a node as a field. This is just the way they are most commonly used.
addField("center", center = new ScalarField());
addField("radius", radius = new PointField());
If a node is going have the ability to compute derived data, it must override the function ModelNode.validate(). The CircleFromCenterRadiusShape is a very simple example of a node that computes derived data. Based on the values of the center and radius fields that it keeps, it computes a polygon object that is the graphical representation of a circle. It does this in its validate() function. Because the CircleFromCenterRadiusShape is an extension of ShapeNode, (which is an extension of ModelNode), it uses ShapeNode.addGeometry() to store the polygon representation of a circle that it computes. This method keeps geometries in standard place that is looked at by the method that retrieves derived data of geometrical nature (ShapeNode.getGeom()). Using this function to store geometry also has the effect that the geometry is automatically added to the Viewports the CircleFromCenterRadiusShape is specified to be visible in, and will be removed and updated according to changes in the ShapeNode.
Notice that the CircleFromCenterRadiusShape shape is the important object here. The polygonal geometry that it generates is nothing but a byproduct of its validation function. The user does not care about the polygonal geometry. They cannot select it, they can not delete it, and they directly change it. It is simply the CircleFromCenterRadiusShape representation in a geometrical world. When the user clicks on the polygonal geometry in the viewport, it is actually the CircleFromCenterRadiusShape that is selected. A simpler organization would have CircleFromCenterRadiusShape actually extending the geometry. Years of experience in software design have led us to believe that this really is not the right way to do it. Such an organization would establish a one-to-one correspondence between the geometry and the object in the user input data model. It would be impossible for the user selectable shape generate more than one geometry. It also means the CircleFromCenterRadiusShape would not be able to extend the other data types oriented towards storing user input, like our ModelNode.
Often a ModelNode needs to accept a variable set of ModelNodes as input. Very often the user needs to say a certain node does its job based on the values of this node and that node. In fact this is the main feature that allows application interoperability through the database. A node that requires input from other nodes, the identity of which is to be specified by the user, holds these nodes in NodeSetField objects. The NodeSetField class is a ModelNode extension specialized to hold a list of other nodes. It can hold the list of other nodes as formal children, such that it their exclusive parent. It can also hold the list of nodes as a list that is independent of the child array, so that the list the nodes can be left in their original parents. This is how the database implements cross linking.
An example of a ModelNode that uses the NodeSetField to hold an arbitrary list of user
selected nodes is the GeneralSectionShape defined in GridZilla. This operator node treats any
input shape as a geometrical sectioning device into a grid of volumetric data. One of its
applications is can map electrostatic potential data onto a molecular surface. It requires both a
reference to a ModelNode that holds volumetric data, and a reference to a ShapeNode that has
geometry to use for the section. In its constructor function it calls:
In its validate() function, and whose purpose is to compute a geometry based on input data, it
uses:
addField("grid", grid = new NodeSetField());
addField("section", section = new NodeSetField());
addField("colorMap", colorMap = new NodeSetField());
to extract the user input from the database. Notice that it is keeping convenient references to
the NodeSetFields as JAVA primitive fields.
Grid grid = (Grid)this.grid.getSingleSelection();
ShapeNode[] sections = this.section.getShapeSelection();
ColorMap colorMap = (ColorMap)this.colorMap.getSingleSelction();
The ShapeNode class adds in its constructor function four field nodes. All ShapeNodes have:
The two most important functions in the ShapeNode are the functions for storing computed geometries and retrieving computed geometries. A shape node may add any number of geometries. The functions are:
In other words, the validation system is asynchronous. When a data value changes, a change notification is propagated to all data nodes directly or indirectly care about this data. These change notification can result in a data node being notified of changes more than once in response to a single action by the user. If we're not careful, is easily possible to re-compute the same derived data hundreds of times between redraws. The Modelzilla database handles the problem with an asynchronous invalidation/validation scheme. During change notifications, nodes that need to be recomputed are placed in an invalid node queue. A node is free to record precisely what part of its derived data as become invalid. Then, just before a redraw, the validation scheduler asks each invalid node to validate. This is implemented in the ValidationManager class. ValidationManager has a validation scheduling system that first validates a node that are input to other invalid nodes, in a recursive fashion. When the validation is complete, the resultant geometrical models should be ready to view the view-port.
Asynchronous updating works like this:
Many invalidations....
invalidate()
invalidate()
invalidate()
invalidate()
invalidate()
invalidate()
invalidate()
invalidate()
One validate....
validate()
To invalidate a node, when a piece of information in data node depends on is changed, call ModelNode.invalidate() or ValidationManager.addInvalid(DataNode node). If the piece of information is registered as input to the first node, then these functions will be called automatically. Otherwise, if some piece of internal information that an application keeps separately from the database is changed, such as a primitive field member of the model node subclass, or some central piece of information that many elements of your application are looking during the course of their computations, you will need to manually invalidate nodes that needs to be recomputed.
To validate a node, override ModelNode.validate() in your application defined ModelNode subclass. (Or implement DataNode.validate().). An application defined data node can keep track of exactly what part of the ModelNode has become invalid and only re-compute this part. This would be an incremental validation system. Our VincentVanMol application uses this technique a lot.
How does this work? When the user opens an MVG file, this file is parsed and the database is rebuilt. All other state information not stored within the database, if its state should return what was when the user saved the file, should be computed from information in the database. Because after all, the entire state of the program should be due to user input. The database causes the data nodes to compute the derived data that the user cares about.
All ModelNode subclasses must offer no argument constructor functions. This enables them to be instantiated while loading a database file. (An MVG file.) It is not possible to completely build a model node's user specification its no-arg constructor function. The node is first built, and then field values are filled in both interactively and when opening files.
An application module is free to save and open its own specialized files. Modelzilla does not try to do this for the application developer. The saving of an MVG file can be made to trigger the saving of other more specialized files. The opening of an MVG file can trigger the opening of other more specialized files. Or the application can have its own independent functions to make this happen. The MVG files are supposed to save the overlying situation. Our VincentVanMol application for example does not save the molecular information to an MVG file. It saves the molecular state information to a standard molecular file format like PDB. However VincentVanMol's job is to make pictures of proteins, and use input regarding the graphical renditions it allows to be stored to the MVG files. When the users opens an MVG file that VincentVanMol took part in building, the validation of the top molecule node in the database causes VincentVanMol to open a PDB file and fill in the molecular information it self.
Causing an application (a Module class extension) to load programmatically
Use MZ.modules().loadModule("full.qualified.class.name"). This can be used when an
application module has a dependency on another application module. The standard controls
application's class name is "harmonic.mz_controls.MZ_Controls". An application that wanted
only these controls plus its own could have a main function like this:
public void main(String[] args){
new MZ();
MZ.modules().loadModule("harmonic.mz_controls.MZ_Controls");
MZ.modules().loadModule("my module name");
}
The -m app_class_name command line option will startup an application module strait away
A user interface is responsible for controlling the ModelNodes an application defines and uses. Generally it takes input from the JAVA components it builds itself.
Modelzilla does come with some widgets you can use for controlling the values of ModelNodes. You can insert these within your GUI if you wish. The NodeValuesDialog is the most useful.
Changes to data model Modelzilla has no support for a node to generate other nodes as its output. This ability might create a whole new level of flexibility by letting the user select "output nodes". Things like geometry and molecular information could be wrapped in a general output node. Some interface allowing any object to be selectable and imputable into database. Reduces difficulty of making existing applications "friends with Modelzilla". Making it smaller and simpler, to enable regular app-mods run on web We have a problem in that Modelzilla applications can run on the web because the framework is too large and has dependencies a large amount of 3rd party code. But application code really uses directly only a small amount of the framework anyway. So splitting the use input layer of the framework into the core classes and a separate jar file for the periphery, so that the core has no dependency on the periphery, might solve this inconvenience. It also really simplifies the "core API". A simplified Modelzilla would have only a means allowing applications to flow data from one to another, and a way for the user to control this. Keeping ModelNode, ShapeNode, everything in mz.data. nodes in mz.data.fields mz.undo package mz.manager package Only the ability to keep data in the database, the validation system, the selection system. Can't get rid of mz.data.fields because these are so useful for storing data. Can't get rid of mz.undo because it is used within application code, and it's small anyway. Managers are too important too. Can get rid of file saving/opening code. Entries ? not so sure. Needed heavly by CAD-Zilla and St Module. Removing mz.file mz.docnode mz.collaboration mz.entries mz.visuals mz.imageencoder maybe some of mz.gui things in mz.util pertaining to file saving any jython dependancy Removing from external libs Removing file stuff enables many things to go away from external libs. JAI stuff goes ? that's jai_core, jai_codec, activation .jar mail, mailapi, pop3, smtp XML stuff goes ? xerces regex is now part of Java, so gnu-regexp goes Nothing left! Reduced from 2.5 MB to 0 MB. MZ then costs about 300 KB ViewerCore about 130 KB Geometry about 160KB Removed classes are kept in a jar called mz_full.jar. This is all code that a regular application should not care about, but provides file saving abilities and other tricks that very useful. A simplified ?basic controls? module would be used that would have no dependency on periphery ? maybe just one panel of buttons for things like rotations and undo. Enables regular application modules to run on the web!