views:

121

answers:

3

Hi,

I've been trying to read through different implementations of Scene Graphs for 3D engine development to learn design patterns used in this domain, but unfortunately the code bases are too big for me grasp (yet, hopefully).

So, let's say we have a class Model that stores pointers to geometry, shaders, textures etc. and we want to allow animation of each of the members separately, say GeometryAnimator, TextureAnimator and so on, but a Model could also be static of course.

What I see is that both the strategy pattern (with no-op for static entities) and the decorator pattern could be used to achieve this. What are the benefits/drawbacks of each in this application? Or do I over complicate matters?

Thanks for your help!

+1  A: 

The Decorator pattern is usually used for structures that can't be modified. The Strategy pattern can be used in structures that you control completely, but will also let you allow others to change the behavior without having to write "around" it, like a Decorator.

Ignacio Vazquez-Abrams
Seeing it from this perspective now makes perfect sense, thanks!
bbtrb
+2  A: 

A simple yet perfectly fine solution for this is to establish interfaces/abstact base classes for all the various things that a scene node can represent.

class Texture
{
    ...
};

class Geometry
{
    ...
};

// etc

class SceneNode
{
public:
    // The following return null by default but
    // can be overriden by SceneNode subclasses
    // to return interface pointers.
    virtual Geometry* geometry()
    {
        return 0;
    }

    virtual Texture* texture()
    {
        return 0;
    }
};

class Model: public SceneNode, public Texture, public Geometry
{
public:
    // Override the functions of inherited interfaces

    virtual Geometry* geometry()
    {
        return this;
    }

    virtual Texture* texture()
    {
        return this;
    }   
};

This is actually the approach that high-end 3D packages take in some form or another. With it, given any scene node, you can query it if it supports a particular interface (ex: a texture one) and then do animation through that, e.g.

Maya and XSI do this but with an interface method capable of returning all interfaces that returns void* which the client has to cast accordingly. They then create reference types that hide the casting required.

You don't need to always resort to classic design patterns for all of your programming solutions. Consider them as tools and suggestions but always asking which design pattern would work for a given problem will not always lead to the most straightforward solution. You have to think for yourself but design patterns can help you.

Sounds similar to COM design
YeenFei
The XSI and Maya SDKs are practically following COM, just not strictly conforming to MS's conventions (ex: no IUnknown interface, wrappers to deal with reference counting and casting, etc.)
Ok, so what you are basically saying is to derive from `Geometry`, that should declare a `virtual void update()`, and overwrite this method for each animation type? I actually thought of this, but most probably I got lost in the effort to make everything "the right way". Thanks!
bbtrb
@bbtrd Something like that. You put whatever you need into Geometry, Texture, etc. I wouldn't call the Geometry method something as general as "update", maybe "update_geometry", since you don't want to collide with other interfaces a SceneNode can provide.
The Geometry class can provide methods like creating triangles, the Texture can provide methods to access the image data, etc. Basically you just pick and choose among these interfaces when you create a SceneNode like Model, and you override the necessary functions so that clients using the Model node can get a texture interface out of it, a geometry interface, etc. As YeenFei pointed out, it's pretty much just like COM but with slightly different conventions and I omitted reference counting from this example.
So let's say you define a virtual function like update_geometry for animation purposes in the GeometryInterface. You can override it in Model which inherits that interface. You also override a method to return GeometryInterface* from Model. Thus when clients traverse the scene graph, they can write code like Geometry* geometry = scene_node->geometry(); if (geometry) geometry->update_geometry();
A: 

I think that you over complicate. Maybe one class (Model) is sufficient? Remember to encapsulate only things that vary.

If you think it is not sufficient then strategy is ok. For example if you want to use many different TextureAnimator classes and be able to switch them in runtime.

Decorator pattern is like subclassing but can be done in runtime (and supports multiple inheritance). It is also slow (I guess you are coding a game). IMO this is not a solution.

In this case I'd code one class. If I needed strategy in the future I'd refactor the code.

lmmilewski
Yes, I want to be able to use different TextureAnimators. More importatnly however, I'd like to have a framework where I can easily prototype ideas, implement physics simulations and so one. I'll go with @stinky's ideas for now and see where it goes.
bbtrb