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:
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 theSGObject
in the context of a specific parent is called a sub-instance. The main instance associated with a sub-instance can be retrieved with theSGObject.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, theSGObject.waitingGenerator
indicates that it has not finish to evaluate yet (background exection pending).SGObjectProperty
(SGObjectProperty.setGenerator
): when SGObjectProperty data is requested (value, type, version), theSGObjectProperty.waitingGenerator
method indicates that it has not finish to evaluate yet (background exection pending). TheSGObjectProperty.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, theSGObjectProperty.isReferenceTargetWaitingGenerator
orSGObjectProperty.isReferenceArrayTargetWaitingGenerator
methods indicate that it has not finish to evaluate yet (background exection pending). TheSGObjectProperty.forceGenerateReferenceTarget
orSGObjectProperty.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 aSGObjectProperty
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:
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.