SceneGraph Overview

The SceneGraph is the scene description component of the SceneHub. The SceneGraph is a generic container of scene objects (SGObject), their properties (SGObjectProperty) and object references (hierarchy). The SceneGraph allows for dynamic definition and caching of data, and value generation (generators). Additionally, it provides an implementation of interfaces allowing its elements to be processed by the Scene Assembly graphs.

On top of the SceneGraph, the SceneGraphWrappers Extension implements common higher-level object wrappers that define common scene types, such as SGDirectionalLight or SGCamera. The wrappers manage underlying scene graph objects’ property sets, relationships and value generators (operators).

The SceneGraph includes a minimal set of functionality for building common scene graph structures and objects. It provides support for defining objects containers (SGObject, their properties (SGObjectProperty), their metadata, the references to other objects (hierarchy) and value generators (operators). It supports, too, property propagation and notifications (observers).

The SceneGraph includes object and interfaces for defining dynamic depencies between objects (operators). This includes a generic operator object (SGBaseOperator), a Canvas graph operator that binds to SceneGraph properties (SGCanvasOperator) and lower-level generator and observer interfaces. See _sgoperators_overview for more details.

The SceneGraph supports animated storage and value generation, and the user can control how it caches the animated frames and property values. See Contexts and animation for more details.

The SceneGraph contains all the scene data, such as objects, properties and references, and manages their storage globally for a more efficiency. Adding objects and properties to the SceneGraph can be done dynamically at runtime and its internal structure allows these operations to be very fast.

For convenience, the SceneGraph pre-creates a ‘root’ SGObject. The image below illustrates the structure of a SceneGraph example:

../../_images/SceneGraph.png

The SceneGraph above presents a three-level hierarchy of SGObject, where “root” is the parent of “Model1” and “Model2”, and these are both parent of “Geometry1”.

Each object defines some SGObjectProperty, with different characteristics:

  • some are local to the owner SGObject, such as the “localTransform”
  • some are propagated to the child objects, such as “root.color”
  • some are dynamically computed by a generator, such as “globalTransform” and “globalBBox” (see Generators and observers)
  • some contain an Object references, such as “root.Chil1” and “Model1.Model”

The “Geometry1” object is owned by both “Model1” and “Model2”, which implies the creation of two sub-instances. While the “Geometry1.localBBox” and “Geometry1.geometry” properties are shared by both sub-instances, the values of “Geometry1.globalTransform” and “Geometry1.color” depend on the contextual parent. See SGObject for more details.

The SceneGraph objects are associated with higher-level wrappers, which are of type “MyAlembicGroupWrapper”, “MyAlembicInstanceWrapper” and “MyAlembicGeomWrapper” in the example above. The example shows, too, the object hierarchy (base classes) of these wrappers. See SGBaseObjectWrapper for more details.

The example below builds a simplified version of the scene presented above, with only the ‘color’ properties and without the object wrappers:

/*
** Example: Simple scene hierarchy with property propagation:
*/

require SceneGraph;

operator entry() {
  SceneGraph sg();

  // Create a color property on the root, and propagate as default value
  SGObjectProperty rootColor = sg.root.getOrAddLocalProperty( "color", SGPropertyPropagation_default );
  rootColor.setValue( Color( 1.0, 1.0, 1.0, 1.0 ) );

  // Add Model1 and Mode2 childs, root is their mainOwner
  SGObject model1 = sg.root.createChild( "Child1" );
  SGObject model2 = sg.root.createChild( "Child2" );

  // Set a distinct color on model2, and propagate as default value
  SGObjectProperty model2Color = model2.getOrAddLocalProperty( "color", SGPropertyPropagation_default );
  model2Color.setValue( Color( 0.0, 0.0, 0.0, 1.0 ) );

  // Create Geometry1 as an unowned (independant) object that will be shared
  SGObject geom1 = sg.addObject();
  geom1.setUserName( "Geometry1" );

  // Add geom1 as a child of both model1 and model2
  model1.addChild( geom1, "Model", false /*not main owner; shared*/ );
  model2.addChild( geom1, "Model", false /*not main owner; shared*/ );

  report( "Scene content, from root.\n" );
  report( "(Main) = main object instance" );
  report( "(Shared) = object has multiple owners" );
  report( "(Base) = property defined on the base object instance" );
  report( "(Local) = property defined on that object instance" );
  report( "(Parent) = property inherited from parent\n" );
  sg.reportContent( SceneGraphDumpOptions(), true );

  // Access the color values from their absolute path
  // Note: using String paths is not recommended for performance critical tasks
  SGObject resolvedInstance;
  SGObjectProperty resolvedProperty;

  sg.getFromFullPath( "root/Child1/Model/color", resolvedInstance, resolvedProperty );
  report( "\nValue for " + resolvedProperty.getFullPath() + ":" );
  resolvedProperty.reportContent( SceneGraphDumpOptions() );

  sg.getFromFullPath( "root/Child2/Model/color", resolvedInstance, resolvedProperty );
  report( "\nValue for " + resolvedProperty.getFullPath() + ":" );
  resolvedProperty.reportContent( SceneGraphDumpOptions() );
}

/*
** Output:
(stdin):6:9: error: no extension or type named 'SceneGraph'


*/

The main components of a SceneGraph are presented below.

SGObject

The SGObject represents a specific scene graph object (or ‘node’), such as a camera.

It mainly consists to a set of SGObjectProperty, which might either be locally defined (owned) or inherited from parents (owner objects). The SGObject might be wrapped by a higher-level wrapper object (SGObjectWrapper) which manages its properties and exposes them with user-friendly methods (eg: SGPointLight).

The SceneGraph allows that SGObject are shared by multiple parents. Because properties might be inherited by parent objects, this implies that a same SGObject might have different properties or property values depending on the contextual parent. The SGObject transparently embeds that notion of parent (owner) context.

  • The SGObject that represents the scene object in the context of no specific parent (or the unique mainOwner parent) is called the ‘main’ instance (SGObject.isMainInstance).
  • The SGObject that represents the SGObject in the context of a specific parent is called a sub-instance. The main instance associated with a sub-instance can be retrieved with the SGObject.getBaseInstance method.

In the illustration above, “Geometry1” is the main object, while “root/Child1/model” and “root/Child2/model” are its two sub-instances.

Additionally, the SGObject transparently embeds the evaluation context, such as the frame at which its property values are accessed.

See SGObject for more details.

注釈

The SGObject is a lightweight structure that contains a handle to internal data. Because it is performance-critical, it allows low memory fragmentation (centralized storage) and simple by-value copy.

注釈

Although an object might have multiple sub-instances, the SceneGraph works hard to optimize the memory usage by sharing as much as possible the data that is common to groups of sub-instances, such as the property set and values.

SGObjectProperty

The SGObjectProperty contains a named property value owned by a SGObject. Its value can be a simple type (such as Float32 and String), a simple structure (such as Mat44), a KL Object or an object reference (see Object references).

The value of a SGObjectProperty can be dynamically generated by attaching a SGPropertyGenerator. The value can depend on the context such as the current frame (see Contexts and animation), and that context is transparently embedded within the SGObjectProperty structure in order to reduce the need of passing explicit contexts.

Because of property inheritance, the value storage container of a SGObjectProperty can be shared by multiple objects. In such case, SGObjectProperty.getSGObject() will differ from SGObjectProperty.getOwnerInstance. Additionally, a property might be defined in the context of a specific parent (instance-specific), allowing it to have a different value depending on the parent. For example, a different “globalTransform” property can be defined for each sub-instances, in which case its value will be different depending on its parents (absolute path).

The following property propagation modes are supported (specified when creating the property):

  • SGPropertyPropagation_none: the property is local to the SGObject or its sub-instances
  • SGPropertyPropagation_default: the property is propagated to the owned children recursively.
    • If a child defines a property of the same name with SGPropertyPropagation_none, it will override it locally but the propagation of the default will continue to its own children.
    • A child with its own SGPropertyPropagation_default property of the same name will redefine the default for its own children.
  • SGPropertyPropagation_override: the property is propagated to the owned children recursively, even if these are locally defining a property of the same name, with any propagation mode.

注釈

The SGObjectProperty is a lightweight structure that contains a handle to internal data. Because it is performance-critical, it allows low memory fragmentation (centralized storage) and simple by-value copy.

注釈

Properties are versioned, and any type or value changes will increment the version number. This allows for detecting value changes asynchronously. This version is attached to the property data container itself, and might not be sufficient for detecting changes in general:

  • If there is a context change (eg: animated value, frame changed), each contextual value might have its own version, which could be the same.
  • If there is an added or removed property, an object that inherited a property from a parent might suddenly inherit from another property (eg: was inheriting value from a parent, but is now overridden by a new local property of the same name)

This is solve by the SGObjectPropertyWatch which provides a coherent property version that will take into account the context or propagation changes observed from a specific object instance.

See SGObjectProperty for more details.

Object references

A special value type for a SGObjectProperty is an object reference or an object reference array (SGObjectProperty.setReferenceTarget, SGObjectProperty.setAsReferenceArray).

A reference stores a link to the target SGObject. This link has a SGReferenceType, which defines the child is owned and if the properties are propagated. If the child is owned, then a child sub-instance will be created for the context of this parent.

注釈

For convenience, the SceneGraph.childrenReferenceType member defines an owner SGReferenceType with property propagation, and the SceneGraph.simpleReferenceType member defines an non-owner SGReferenceType with no property propagation.

When getting the reference target object (SGObjectProperty.getReferenceTarget or SGObjectProperty.getReferenceArrayTarget) referring an owned child, the returned SGObject will be in the context of this parent object. Calling child’s SGObject.getOwnerInstance will return this parent object, and calling SGObject.getOwnerReferenceProperty will return this reference property.

It is possible that an object is primarely owned by a unique parent, which is called the “mainOwner”. The “mainOwner” then directly owns the main instance of the child object, which can only have a single “mainOwner” parent. The SGObjectProperty.setReferenceTarget and SGObjectProperty.setAsReferenceArray method have a mainOwner parameter for this purpose. When a child is uniquely owned, the deletion of the parent owner will cause the child to be deleted.

SGBaseObjectWrapper

A SGBaseObjectWrapper allows to associate a custom KL object to a SGObject in order to provide higher-level functionality and behavior. For example, the SGBaseObjectWrapper can create properties with easy accessor methods and associate generators with these.

The SceneGraphWrappers Extension defines various object wrappers. For example, the SGTransformed creates a localTransform and a localBBox property, and generates the globalBBox based on the localBBox and the globalTransform upon request.

Operators

The SceneGraph provides the ability to compute data only when it is required (pull/lazy evaluation of graph dependencies). This is accessible though lower-level interfaces (Generators and observers) or higher-level objects (SGBaseOperator, SGCanvasOperator). Also, it is possible to define background computations through Asynchronous evaluation.

SGBaseOperator

SGBaseOperator provides a high-level operator base object which can define a data flow, dynamic relationship from source to target properties. Additionally, it provides services for storing operator-owned parameters (arguments), which can be stored on its own container or attached to an existing one (SGObject). A specialized object can then simply create its parameters (arguments), add input and output properties, and implement the SGBaseOperator.execute method to perform the actual computation.

The SGBaseOperator can support multiple output properties and handles automatically the contextual (animated) state of these based on inputs and arguments; see SGBaseOperator for more details.

/*
** Example: Simple multiply operator specializing SGBaseOperator:
*/

require SceneGraph;

// MultiplyOperator: a simple operator that multiplies the source by a multiplier.
// This simple implementation assumes a Float32 value.
object MultiplyOperator : SGBaseOperator {
  UInt32 multiplierPropertyKey;
  UInt32 sourcePropertyKey;
  UInt32 targetPropertyKey;
};

MultiplyOperator( SGObjectProperty source, SGObjectProperty target ) {
  // Base constructor
  // We create our own parameter container (SGObject) instead of attaching to an existing object
  this.initWithOwnedParameterContainer( "Multiply", source.SG );

  // We cache the property keys for better performance
  // Set multiplier default value to 4
  this.multiplierPropertyKey = this.getOrAddParameter( "multiplier", 4.0f );
  this.sourcePropertyKey = this.addInputProperty( source );
  this.targetPropertyKey = this.addOutputProperty( target );
}

// We pass in a context: would be required if the value was animated (varies with time/frame for the context)
MultiplyOperator.setMultiplier!( Float32 value, SGContext context ) {
  SGObjectProperty multiplier = this.getProperty( this.multiplierPropertyKey, context );
  multiplier.setValue( value );
}

Boolean MultiplyOperator.execute!( SGContext context ) {

  SGObjectProperty multiplier = this.getProperty( this.multiplierPropertyKey );
  SGObjectProperty source = this.getProperty( this.sourcePropertyKey );
  if( !source.isFloat32Value() ) {
    setError( this.getPrintName() + ": unexpected: source is not a Float32" );
    return false;
  }

  SGObjectProperty target = this.getProperty( this.targetPropertyKey );
  target.setValue( multiplier.getFloat32Value() * source.getFloat32Value() );

  return true;
}

operator entry() {
  SceneGraph sg();

  SGObjectProperty sourceProperty = sg.root.getOrAddLocalProperty( "source" );
  sourceProperty.setValue( 5.0f );

  SGObjectProperty targetProperty = sg.root.getOrAddLocalProperty( "target" );

  MultiplyOperator multiplyOp( sourceProperty, targetProperty );
  report( "Initial value: " + targetProperty.getFloat32Value() );

  multiplyOp.setMultiplier( 10, SGContext() );
  report( "New value: " + targetProperty.getFloat32Value() );
}

/*
** Output:
(stdin):6:9: error: no extension or type named 'SceneGraph'


*/

SGCanvasOperator

The SGCanvasOperator object is a specialization of SGBaseOperator that binds a Canvas graph to a SGObject, or more precisely, Canvas graph arguments to SGObjectProperty. The computation of the output properties will trigger Canvas graph’s execution. Some inputs such as time and frame are mapped by default to the contextual time or frame. In order to choose the scene parameters to be bound, a default input and output SGObject can be provided, or properties can be set explicitly for more complex cases.

/*
** Example: Using a Canvas graph to drive SGObjectProperty:
*/

require SceneGraphWrappers;

operator entry() {
  SceneGraph sg();

  SGGeometry geometry = sg.addGeometry( null );
  sg.root.addChild( geometry, "geom", true );

  // Create a CanvasGeometry graph. Its "geometry" output parameter matches the existing property on the geometry and will bind to it.
  FilePath path = FilePath( "${FABRIC_SCENE_GRAPH_DIR}/Test/Exts/SceneGraphWrappers/CanvasGeometry.canvas" ).expandEnvVars();
  SGCanvasOperator sgCanvasOp = sg.addCanvasOperator( path.string(), geometry.getWrapped(), true );
  SGObject operatorParameters = sgCanvasOp.getParameterContainer();

  SGObjectPropertyIterator iter = operatorParameters.getPropertyIterator();
  SGObjectProperty prop;
  while( iter.getNext( prop ) ) {
    if( prop.getName() == "graphFilePath" )
      continue;//don't print an absolute path...
    report( "Parameter: " + prop.getName() + " = " + prop.getValueString() );
  }
  PolygonMesh mesh = geometry.getGeometry( SGContext() );
  report( "Mesh poly count (nbSpheres = 5): " + mesh.polygonCount() );

  prop = operatorParameters.getProperty("nbSpheres");
  prop.setValue(3);

  mesh = geometry.getGeometry( SGContext() );
  report( "Mesh poly count (nbSpheres = 3): " + mesh.polygonCount() );
}

/*
** Output:
(stdin):6:9: error: no extension or type named 'SceneGraphWrappers'


*/

Generators and observers

A SGObjectGenerator or a SGObjectPropertyGenerator enables to compute the value of a a SGObject or a SGObjectProperty when requested. The interface provides the target object or property, from contains at the same time the evaluation context (eg: for a specific frame if animated).

This allows to implement data flow dependencies and dynamic computations in the SceneGraph, similarly to the Operators, but using lower-level interfaces that allows for more optimizations, or defining dynamic behaviors directly within a SGObjectWrapper.

The SGChangeObserver interface allows for receiving direct notifications upon changes from the object instance or property of interest. The following observers are available:

  • SGObjectProperty.addObserver: an observer for changes to a specific property.
  • SceneGraph.getByPropertyNameGlobalVersions: a global observer for changes related to all property with a given name.
  • SceneGraph.addInstancesObserver: a global observer for any added object instances (or sub-instances) or hierarchy changes.

注釈

The SGIncrementalObserver object allows to efficiently cumulate scene graph changes as a compact and centralized changes history, allowing clients (such as Scene Assembly) to update asynchronously. Only the required changes are kept (history scanned by all clients is flushed).

The combination of generators and observers allow to implement a data flow graph (operators) that can link the various objects or properties, including dirty propagation. For example, the SWTransformed uses these hooks to compute its global transform and global bounding box. The example below shows a simple operator implementation.

注釈

Currently, there is no higher-level operator object that is provided along with the SceneGraph, however it will be added in later releases. This will include support for visual DFGs (data flow graphs) within the SceneGraph.

The following example builds a 2-level hierarchy where the “int” value is generated as parent’s value + 1:

/*
** Example: Property operator using SGPropertyGenerator and SGChangeObserver:
*/

require SceneGraph;

// Example of an operator written with lower-level services, as opposed
// to specializing the SGBaseOperator class.
object AddOneOperator : SGPropertyGenerator, SGChangeObserver {
  SGObjectProperty inputProperty;
  SGObjectProperty outputProperty;
};

AddOneOperator( SGObjectProperty inputProperty, SGObjectProperty outputProperty ) {
  this.inputProperty = inputProperty;
  this.outputProperty = outputProperty;

  // Register ourselves as a generator observing inputProperty
  this.inputProperty.addGeneratorObserver( this, 0 );

  // Set ourselves as the generator of outputProperty
  this.outputProperty.setGenerator( this, 0 );
}

// SGPropertyGenerator implementation
// Sets the target property as source property + 1
UInt8 AddOneOperator._generate!( io SGObjectProperty property, UInt32 userData ) {
  //NOTE: the property might have been requested for a specific frame, but
  //      we want to output a static value: use the non-contextual version to indicate that
  SGObjectProperty staticProperty = property.getContextual(false);
  property.setValue( this.inputProperty.getUInt32Value() + 1 );
  return SGPropertyGenerator_succeeded;
}

// SGPropertyGenerator implementation
// Debug name used for tracing.
String AddOneOperator._getGeneratorDebugName( SGObjectProperty property, UInt32 userData ) {
  return "AddOneOperator";
}

// default implementation of SGPropertyGenerator
Boolean AddOneOperator._isAsynchronous( io Boolean asynchPrepareOnly, UInt32 userData ) { return false; }

// default implementation of SGPropertyGenerator
UInt8 AddOneOperator._asynchPrepare!( SGObjectProperty property, UInt32 userData ) { return SGPropertyGenerator_succeeded; }

// SGChangeObserver implementation
// Triggered when the input property changes
AddOneOperator._changed!(io SGObject instance, io SGObjectProperty property, UInt8 changeType, UInt32 userData) {
  this.outputProperty.setGeneratorDirty( changeType );
}

// SGChangeObserver implementation: triggered when the SceneGraph destroys
AddOneOperator._SGDestroying!() {}

operator entry() {
  SceneGraph sg();

  //Set rootInt initial value to 10
  SGObjectProperty rootInt = sg.root.getOrAddLocalProperty( "int" );
  rootInt.setValue( UInt32(10) );

  //Create a child, whose int value depends on root's int value
  SGObject child = sg.root.createChild( "child" );
  SGObjectProperty childInt = child.getOrAddLocalProperty( "int" );
  AddOneOperator childOp( rootInt, childInt );

  //Create a grandchild, whose int value depends on child's int value
  SGObject grandChild = sg.root.createChild( "grandchild" );
  SGObjectProperty grandChildInt = grandChild.getOrAddLocalProperty( "int" );
  AddOneOperator grandChildOp( childInt, grandChildInt );

  //Enable debug tracing of generators
  sg.enableGeneratorsTracing();

  report("Get grandChild value (PIdx = global property index):\n");
  grandChildInt.getUInt32Value();

  report("\nChanging the root value to 20:\n");
  rootInt.setValue( UInt32(20) );

  report("\nGet grandChild value:\n");
  grandChildInt.getUInt32Value();
}

/*
** Output:
(stdin):6:9: error: no extension or type named 'SceneGraph'


*/

Asynchronous evaluation

Asynchronous evaluation is a SceneGraph feature that allows some computations to be done in the background in a multithreaded fashion. However, it must be used with care and will provide performance gains only in specific situations. Asynchronous evaluation can be enabled from the SGPropertyGenerator low-level interface, but can also be enabled for SGBaseOperator (SGBaseOperator.isAsynchronous) and for various higher-level scene wrappers (eg: SGGeometry.enableGeometryGenerator). Since asynchronous evaluation requires careful implementations for both the generator and the SceneGraph client or user, it can be disabled globally at the SceneGraph level with SceneGraph.enableAsynchronousGenerators to avoid issues or complexity.

In order to provide some gains, asynchronous evaluation should be used in the following conditions:

  • The generator execution must be relatively long (not a simple matrix multiplication for example), otherwise the overhead will be more important than the gains
  • The generator execution should lock the graph only for a minor portion of its time, otherwise the UI thread will become unresponsive and other asynchronous generators will be blocked (no multithreading)
  • The generator should lock the graph if it does many changes to the SceneGraph, such that the SceneGraph is unlocked only once it is in a valid semantic state (eg: for drawing). Higher level mechanisms can be required for ensure this (eg: SGObject.loading).

A perfect example for potential gains is to generate a Geometry which doesn’t depend on other SceneGraph objects, but only depends on the “external world” (eg: Alembic loader).

All generators can declare to be asynchronous (SGPropertyGenerator._isAsynchronous). Requesting a value that depends on an asynchronous generator will automatically initiate the background computation process. However, the result will not be available immediately. The non-availability of the data should be tested with methods such as SGObjectProperty.waitingGenerator. Asynchronous generators can be attached to the following entities:

  • SGObject (SGObject.setGenerator): when the SGObject is accessed, the SGObject.waitingGenerator indicates that it has not finish to evaluate yet (background exection pending).
  • SGObjectProperty (SGObjectProperty.setGenerator): when SGObjectProperty data is requested (value, type, version), the SGObjectProperty.waitingGenerator method indicates that it has not finish to evaluate yet (background exection pending). The SGObjectProperty.forceGenerate method will wait for an asynchronous value to be ready, at the cost of performance (no gains from computing in backgound at that point).
  • SGObjectProperty references (SGObjectProperty.setReferenceTargetGenerator, SGObjectProperty.setReferenceArrayTargetGenerator): when the reference target is accessed, the SGObjectProperty.isReferenceTargetWaitingGenerator or SGObjectProperty.isReferenceArrayTargetWaitingGenerator methods indicate that it has not finish to evaluate yet (background exection pending). The SGObjectProperty.forceGenerateReferenceTarget or SGObjectProperty.forceGenerateReferenceArrayTarget methods will wait for an asynchronous reference target to be ready, at the cost of performance (no gains from computing in backgound at that point).

The asynchronous evaluation is implemented as one of the core mechanisms in the SceneGraph. When a value is in waiting state, it should be pulled again later to check if it is then available. One way of doing this is to have a timer in the UI thread which verifies if something changed in the SceneGraph (SceneGraph.getGlobalVersions), as implemented by the sceneHub.py standalone.

The SGInstanceTraverser allows to traverse a scene’s hierarchy, and is asynchronous evaluation-aware. An option of the SGInstanceTraverser allows it to asynchronously wait for hierarchy children to be generated in the background, and will resume its traversal once these are available. Also, it can optionally force the values to be generated, in which case it will synchronously block until these are finished. The SGLoadTraverser specializes SGInstanceTraverser in order to load the scene asynchronously.

The following illustrates how the low-level mechanism works. Note that this complexity is hidden if you use higher-level objects, such as SGBaseOperator or wrappers such as SGGeometry. Let say SGObjectProperty A’s generator depends on SGObjectProperty B, and SGObjectProperty B’s generator is asynchronous. The following steps are involved:

  • A.getValue() is called, its generator request B’s value, which triggers a call to B’s generator (since it depends on it), however since B’s generator is asynchronous, it will be queued and B’s value will not be up-to-date.
  • A’s generator should actually call B.valuesAreContextual( isWaiting ) to know if the stored value will be contextual (animated) and to know at the same time if B is waiting for its generator. If B is waiting, then A’s generator must return SGPropertyGenerator_waiting immediately to indicate that because of A is now also waiting on asynchronous computations.
  • Once B’s generator returns, a SGObserverChange_generatedAsync notification will be triggered, and A’s generator will receive it (since it depends on B, it must be registered as its observer - SGObserverChange._changed ). A’s generator must then relay the notification to its generated properties ( A.setGeneratorDirty( changeType ) ), just like it does it anyway to propagate dirty states. This will remove A’s waiting state.
  • Requesting A again will trigger its evaluation, which will now properly compute it since B’s value is available.

Contexts and animation

SceneGraph contexts allow to control how the SceneGraph data is accessed and stored, which enables to implement animation or simulation evaluation or caching. The context is transparently embedded with the SGObject and SGObjectProperty, and the SGContext allows to access information about that context (accessible with SGObject.getContext and SGObjectProperty.getContext, respectively).

SGContext

The context (SGContext) is a central notion in the SceneGraph design. When getting or setting a property value, the context allows the SceneGraph to access the right data by specifying the animation time or frame to be used, and can include the active hierarchy path (for SGObject instances shared by multiple parents).

The SGContext can represent an immutable set of parameters and the contextual instance (active hierarchy parent). A SGContext associated to a specific set of parameters can be created with SceneGraph.getContext. The SceneGraph.getFrameContext and SceneGraph.getTimeContext built-in helpers allow to easily create a context for a specific animation “time” and “frame”. If two SGContext are created for the same parameters, the same context will be returned (shared).

The SGObject and SGObjectProperty transparently embed a SGContext, which allows to access their values without worrying about the context. For example, the SGObject.getProperty method will return a SGObjectProperty which embeds owner SGObject’s context (contextual instance and parameters).

When needed, specialized methods allow to get or change the context associated with a SGContext (eg: SGObjectProperty.getContextual). For a specific SGObject or SGObjectProperty, the associated SGContext (SGObject.getContext or SGObjectProperty.getContext) embeds, too, the “contextual instance”, which provides the context of the active parent (hierarchy path) for a shared object. The contextual instance is important since depending on the owner parent, a shared SGObject might have different properties or property values (see SGObjectProperty).

However, for performance and other reasons, a SGObjectWrapper is shared by all instances of a same base SGObject, which is why the wrapper methods often require a SGContext to be provided in order to precise the contextual instance. The SGObject.getWrapper provides the right SGContext to be used for preserving the SGObject’s context.

警告

Contexts must be used with care: trying to access properties with a SGContext that embeds another SGObject instance will trigger an expection because the contextual instances will mismatch.

Various methods exist for accessing or modifying the context of an object or property, for example:

  • SGObjectProperty.valuesAreContextual: returns true if property’s values depend on the frame context (see Per-frame storage or animation)
  • SGObjectProperty.getContextual: returns a SGObjectProperty representing the same property and contextual instance, but with other contextual parameters. In particular, calling SGObjectProperty.getContextual(false) returns the property with no specific context, in which case setting its value will define a static (non-animated) value.

Frame parameters

The SceneGraph can handle and store animated, frame-specific property values. A ‘frame’ is a general term used by the SceneGraph for referencing an animation-specific storage or, more generally, a contextual storage. The frame context can be embedded within a SGContext, a SGObject or a SGObjectProperty, or even within the SceneGraph itself though its support of a ‘current context’ ( SceneGraph.defineCurrentFrame ).

While a SGContext can represent a set of arbitrary parameters, some of these parameters have the special status of being “frame parameters”. For each combination of frame parameters, a SGObjectProperty can define and store a different value, making it “animated” (function of the frame parameters).

By default, the SceneGraph defines the “time” and the “frame” context parameters as being “frame parameters”, and defines built-in helpers for accessing these (SceneGraph.getCurrentFrame , SceneGraph.getCurrentTime , SGContext.getFrame , SGContext.getTime ).

注釈

The relationship between the frame and the time can be customized with SceneGraph.setupSimpleFrameToTimeConversion or SceneGraph.setupFrameToTimeConversion .

If other frame parameters are required, these can be defined with SceneGraph.registerFrameParameter .

If a SGContext has frame parameters among other ones, the SGContext.getFrameContext method allows to efficiently retrieve an associated context which strictly contains these frame parameters.

Per-frame storage or animation

In the SceneGraph, the terms “animated value”, “per-frame value” or “contextual value” usually refer to the fact that a SGObjectProperty value can depend on the contextual frame. Per-frame storage can be controlled at a global level (all SceneGraph animated properties) or for individual properties.

The contextual frame can be specified either by defining it at the SceneGraph level (default current frame), or can be embedded within the contexts of the SGObject and the SGObjectProperty. The SceneGraph.defineCurrentFrame method sets the frame context to be used if none was specified. For example, calling sg.defineCurrentContext( sg.getFrameContext( 2 ) ); will set the default frame to ‘2’.

There are two ways to provide per-frame values for an SGObjectProperty. The first method is to push the required per-frame values in advance by using the SGObjectProperty.enableAllFramesCaching method. The SceneGraph will then select the right stored value depending on the frame.

The other way is to associate a SGPropertyGenerator with a property (SGObjectProperty.setGenerator). Generator’s implementation of SGPropertyGenerator._generate can then return a contextual value when requested.

注釈

In the SGPropertyGenerator._generate function, if the value to be stored is always the same for all frames (static value), then the setValue method must be called on a non-contextual SGObjectProperty to tell the SceneGraph that the values is not context dependent. Such a SGObjectProperty can be retrieved by calling SGObjectProperty.getContextual( false ).

The following example demonstrates the definition of contextual property values using both methods:

/*
** Example: Setting per-frame SGObjectProperty values
*/

require SceneGraph;

// This example shows a simple usage of SceneGraph per-frame (animation)
// storage and property generation.

// A simple generator outputting the current time from the frame index
object TimeGenerator : SGBaseOperator{
  UInt32 timePropertyKey;
};

TimeGenerator( SGObjectProperty timeProp ) {
  // Call base object constructor
  this.initWithOwnedParameterContainer( "TimeGenerator", timeProp.SG );
  this.timePropertyKey = this.addOutputProperty( timeProp );
  this.setForceContextSpecific( true );//Indicate that we are depending on the context even if no contextual inputs
}

Boolean TimeGenerator.execute!( SGContext context ) {
  SGObjectProperty timeProperty = this.getProperty( this.timePropertyKey );
  timeProperty.setValue( context.getTime() );
  return true;
}

operator entry() {
  SceneGraph sg();

  // Define the "frame to time" ratio to 2 frames per second, with no time offset
  sg.setupSimpleFrameToTimeConversion( 2, 0 );

  // Add a "time" generated property to 'root'
  SGObjectProperty timeProp = sg.root.getOrAddLocalProperty( "time" );
  TimeGenerator( timeProp );//Note: the property owns the generator so no need to keep a ref

  // Create a "weather" property with pre-cached per-frame values
  // (instead of being generated like the 'time' above)
  SGObjectProperty weatherProp = sg.root.getOrAddLocalProperty( "weather" );
  weatherProp.enableAllFramesCaching( true );

  // Pre-create the weather properties for frames 0, 1 and 2
  SGObjectProperty weatherPropAtFrame = weatherProp.getContextual( sg.getFrameContext(0) );
  weatherPropAtFrame.setValue( "sunny" );

  weatherPropAtFrame = weatherProp.getContextual( sg.getFrameContext(1) );
  weatherPropAtFrame.setValue( "cloudy" );

  weatherPropAtFrame = weatherProp.getContextual( sg.getFrameContext(2) );
  weatherPropAtFrame.setValue( "raining" );

  // Print the values for frames 0..2
  for( Size i = 0; i <= 2; ++i ) {
    // For simplicity, we will just change the "current frame" of the scene.
    sg.defineCurrentFrame( sg.getFrameContext(i) );
    report( "Frame " + i + ": time = " + timeProp.getFloat64Value() + " weather = " + weatherProp.getStringValue() );
  }
}

/*
** Output:
(stdin):6:9: error: no extension or type named 'SceneGraph'


*/

In order to provide a good control and performance relatively to contextual (animation) storage, the user must define the required storage at the scene level before requesting animated values. By default, the SceneGraph only provides contextual (animation) storage for the defined current context ( SceneGraph.defineCurrentFrame ). Trying to set or get the value of an animated property for another frame will cause an exception. If required, it is possible to enable parallel storage of multiple frames with the SceneGraph.pinFrameValues method. For example, this can allow to define a sliding window of multiple frames for implementing effects such as simulation or motion blur.

The following image illustrates the various storage modes available in the SceneGraphs:

../../_images/PropertyStorage.png

The figure above shows the storage of the SceneHub (sparse matrix of data). The static properties (non-contextual) have a dedicated storage, shared by all frames. Per-frame property value storage is created for stored frames, or can be pre-cached in advance and kept for all required frames.