As scene graphs are a such an important and frequently questioned topic here (and else where) i decided to begin compiling a comprehensive listing of resource links about them for those interested in implementing/using them. Hopefully this will become a sticky for all to find of great use.
- Scene Graph Basics
- Scene Graph design/implementation issues/notes etc
Scenegraph Design Notes
Scene Graph/Renderer interface
Scene Graph. DAG or Tree With Mesh List?
Design Patterns used in the OpenSceneGraph
API-agnostic vertex storage and conversion to API-usable storage
Some UML class diagrams from me [embarrass]
SG State Sorting
Shader/Material System/Integration
SG Optimizations
Portal Integration
- Portals (Old but described/relevant in the context of Scene Graphs)
Spatialization
To those who have not done so already and are new to the topic of scene graphs i recommend reading up on some/all design patterns in particular:
- Composite - reason being that scene graphs are typically implementated with this pattern
- Decorator - reason being it's related to Composite and is in fact a degenerate form/case of Composite where decorators only have a single or fixed number of children. It is also frequently used with the Composite pattern.
In the case of scene graphs implementated via the Composite pattern we have Grouping node types (they are the Composites) typically there will be an instantiable base Group node type who's sole purpose is child containment and
child management, this instantiable base is just a plain instance of the composite pattern and the most general grouping node type of all in a scene graph. Then we add more specific kinds of grouping nodes, like transforms, switch nodes, LOD nodes etc these are sub-types of the base group node type thus inherit child containment and child management (code-reuse) but also adding new behaviours/state to internal nodes of a tree/DAG (the reason why i emphasis this is it does not make sense to have something like a Switch/LOD node who's sole purpose is to select/switch between other nodes as terminating/leaf nodes of a tree/DAG). The specific kinds of grouping node types are Composites aswell they are also instances of the decorator pattern too!.
- Interpreter - reason being it's related to composite but the main point is the idea of a "global" context i.e SG could have global state or rendering context.
- Observer - Scene Graphs are/use the Observer pattern implicitly without the developer realizing it but it can be used explicitly when scene graphs are DAGs because there maybe occasions where leaf nodes need to notify state changes to all parents (and there parent's parents and so on all the way upto the root/roots if necessary). Therefore leaf node types are subjects and grouping node types are the observers (they are also subjects when viewed in a different context since grouping node types typically share a common base with leaf node types).
- flyweight - reason being you can permit node sharing in an n-ary tree without resorting to use a directed acyclic graph (DAG) which are harder to deal with but more flexible.
- Visitor - reason being seperating the operation and data of scene graphs, it will beome more apparent when this pattern is read
Caveat:
The main point/advantage of the composite pattern is the distinction between internal (non-terminal) and leaf (terminal) node
types (yet they have a common super-type), Composite intances are internal nodes of a tree/DAG, concreate component instances are terminating, leaf nodes of a tree/DAG.
The advantage of this there is no need todo a run-time check whether a node is a leaf/terminal, there is no redundant data of child containment in leaf node types (aswell as other state/behaviour that should only ever be in internal nodes of a tree/DAG).
Here is the Caveat, Composite pattern is generally applied to OO designs indeed Gang Of Four (GOF) design patterns are concise & general designs/concepts to recurring solutions that arise in OO systems/languages. As such Composite pattern is implementated in-terms/via sub-type polymorphism (the type of polymorphism most widely known).
However there is another method to achieve virtually the same (that is making a distinction between leafs and internal nodes) thing without sub-type polymorphism, without the need of a common
polymorphic base, even no need for pointers & heap allocation by using something called
recursive variants. Virtually all Functional programming languages (i don't mean imperative but some of them do also) natively support variants AKA disjoint
disjoint (discriminated) unions, one value - finite set of types, they also can also be recursive (this is the key here).
For example in SML we can describe a binary tree as such:
datatype 'a tree = Leaf of 'a | Node of (('a tree) * ('a tree));
This just basically says that a "tree" is either leaf of some type or an internal node. It's what we want.
We can do this in C/C++, in C++ the best method is by using
boost::variant, we an use boost::variant to achieve what the composite does and implement the basic structure of a typical scene graph interms of it:
#include <deque>
#include <boost/variant.hpp>
typedef boost::variant<
struct leaf,
struct leaf2,
struct leaf3,
boost::recursive_wrapper<struct group>,
> scene_node;
//...
struct leaf { /*...*/ };
struct leaf2 { /*...*/ };
struct leaf3 { /*...*/ };
struct group {
typedef std::deque<scene_node> child_list; // notice recursive composition
private:
child_list kids;
public:
void add_child(const scene_node& sn) { kids.push_back(sn); }
};
int main() {
group root;
root.add_child(leaf());
root.add_child(leaf2());
root.add_child(leaf3());
root.add_child(group());
group& inner_grp = boost::get<group>(grp.kids.back());
inner_grp.add_child(leaf());
inner_grp.add_child(leaf2());
}
You get the idea, read boost::variant docs on how to use it properly, use boost::static/apply_visitor etc. The interesting thing about this method you will still end up using inheritance but not for sub-type polymorphism or interfaces but purely for code reuse, no need for virtual destructors, not only that you will end up replacing sub-type polymorphism with static polymorphism using
Curiously Recurring Template Pattern (CRTP).
This idea is even possible in standard C (with some extra work) because C/C++ have union user-defined types but it is not a discriminated union (like boost::variant is) though, in C you can simulate discriminated unions using a simple technique called a tagged unions and enforcing state invariants through an interface. All tagged unions is is a union plus a type tag identifier, in pure standard C it will look like this:
typedef enum tag_type { FOO, BAR, .... } tag_type; // enumrated set of types
typedef struct my_variant_ {
tag_type type_id;
union { foo f, bar b, ... } var;
} my_variant;
Many C compilers have a non-standard extension that make this slightly less verbose:
typedef enum tag_type { FOO, BAR, .... } tag_type; // enumrated set of types
typedef struct my_variant_ {
tag_type type_id;
union { foo f, bar b, ... };
} my_variant;
Now we have variants in C we can apply this again to implement the composite pattern in C with recursive variants:
#include <stddef.h> // size_t
typedef enum tag_type { LEAF, LEAF2, GROUP };
typedef struct scene_node {
tag_type type_id;
union {
struct leaf* l1;
struct leaf2* l2;
struct group* grp;
};
} scene_node;
typedef struct group {
scene_node* kids; // pointer to an array of scene_node,
// notice recursive composition again
size_t num_of_kids;
} group;
typedef struct leaf {} leaf;
typedef struct leaf2 {} leaf2;
Another advantage of this specifically to C it's slightly more type-safe, you can (almost) completely avoid void pointer syndrome.
The obvious disadvantage to using recursive variants in C/C++ is the lack of pattern matching that statically typed functional languages enjoy aswell as the method completely automated, and type-checked. Saying that though its not to bad with boost::variant's static/apply_visitor.
Lastly i'm not suggesting this method to be best/better than the traditional OO method of the Composite. i'm just stating it's not the only method. You could probably get the best of both worlds in OCaml.
I'll continuously add/update this, please PM me if you have any more links and i'll stick up here for all to see [smile].
[Edited by - snk_kid on November 4, 2005 6:29:34 AM]