I'm aware that this approach is terrible but I can't think of an alternative.
Design-wise, your approach is fine. A rendering interface should know how to consume objects that describe a single (or set of) drawing requests and translate those into state configuration and draw calls for the underlying graphics API. For your renderer, it sounds like your Renderable
objects are exactly that: a representation of something to draw. So this is fine.
Implementation-wise, however, your approach has some minors flaws.
My current approach is to feed my renderer with a pointer to a "Scene", iterate through its nodes, test them with a static_cast(yeah you read that right) to a "Renderable" object (for example a static mesh class that contains a material and a vertex array) and draw.
It sounds like you are coupling the renderer to the scene this way (by having the renderer "know about" scene types, such as Scene
and Node
; it sounds like Renderable
is a child of the latter and that the former is a broader type of scene than just a rendering one (that is, that you have Node
types that are not rendering-related).
Furthermore, static_cast
isn't a good way to "test" nodes. It will always "succeed" in performing a downcast (take a look at point #2 in under the Explanation section in the above link about static_cast
), but using the resulting Renderable
may result in a crash because the Node
really was not a Renderable
. dynamic_cast
would be the right tool here.
But there's no reason to involve down-casting at all. Rather than give you renderer a Scene
directly, just change its interface so it gets a set of Renderable
objects, such as via a const std::vector<Renderable*>&
parameter. Now the Renderer
doesn't "know about" the Scene
.
Renderable
itself doesn't need to derive from Node
, it can just be a base class on its own. Now your renderer is decoupled entirely from Scene
and its related higher-level API.
In the Scene
itself, store the array of Renderable
objects for the scene separately from the nodes in the scene. You can have nodes in the scene fill this list up with Renderable
objects they'd like to have drawn as part of the routine processing of your logic update. Then, at the start of rendering for that frame, you just give the renderer the list of Renderable
objects that the scene has built up for that frame, and let it do its job.
The key idea here is that the "scene" is a high level concept and the renderer is a lower-level one. High level APIs can know about, and use, lower-level ones but such visibility should not go the other way. By enforcing that kind of unidirectional data flow you can keep code less coupled and simpler.