-------- Original Message --------
From: Nitro <[EMAIL PROTECTED]>
To: Chris.Barker <[EMAIL PROTECTED]>
I thought a bit of the overall design.
1) We need an IRenderer interface ("who"). There will be two concrete
implementation, DCRenderer and GCRenderer. Right now I am not sure if we
can make both share the same interface, but it should be similar enough to
get away with it. The renderer is responsible for drawing an object (or
list of them whenever possible).
2) Separate the data to be drawn from the draw object itself ("what",
IRenderable). This way you can instantiate thousand circle objects which
all share the same circle object. This saves memory and has another
advantage I'll outline later. Basically IRenderable will have a "Render"
method which gets passed an IRenderer object. Then it can call various
methods on this object.
3) Separate the way the data is drawn from the draw object ("how", ILook).
This means things like brushes, pens, fonts. This allows you to draw the
same circle object in different fashions. You just create different
description for the Look.
4) Based on 2 and 3 it is possible to do more optimization. This means we
need some kind of RenderSet object which binds 2) and 3) together.
For example one can now sort objects by their RenderSet (data and look).
This might enable fc to automatically determine objects suitable for list
drawing without the user having to pay attention to this. If the look is
the same for lots of objects it also saves lots of calls to the DC's
"look" functions. The sorting can be cached.
Additionally this might screw up the order, so I suggest giving a
"priority" attribute to the RenderSet. Higher priorities are drawn more
front. This could replace the current "draw in foreground" mode and is
also an easy interface for layers. I suspect order will become a more
important problem with alpha blending since the order you render objects
in will change the final outcome. For convenience one can also add an
"enabled" property and if it is set to False skip rendering this object.
5) Create the concept of a scenegraph ("where"). A scene graph node has a
root node and can have children. Each node holds a transform and the
children inherit this. So if the parent's transform is called x and the
child transform is called Y the object will be drawn with a final
transform z = x * y. This implies adding yet another set of coordinates to
your suggestion: local coordinates. Meaning objects are always expressed
in local coordinates (for example a circle is likely to be centered around
(0,0)) and then the objects are transformed to world coordinates via their
node's transform. Note that not every node object is drawable, nodes are
logical entities. For example there will be at least two concrete
implementations, first is the default node (since it can have children it
will replace the current group drawobject, if you move this node the
children will move automatically, too, since they inherit the transform).
The second implementation is a RenderableNode. It holds a RenderSet object.
The scene graph can further be used to cull away large portions of the
drawing. We can establish a parallel bounding box tree where objects are
inserted/removed from whenever a node is added to the regular scene graph.
In the bounding box tree a parent node encompasses all of the children
bboxes. So if the bb of the parent is not visible its children don't have
to be tested any more since they're guarenteed to be non-visible. If
somebody feels up to the task he can implement this:
http://en.wikipedia.org/wiki/R-tree . That way it's even self balancing
:-) For now I suggest something less sophisticated.
If we'd allow a canvas to have multiple scene graphs this would allow just
another way to create layers.
6) So the common final high level object is probably the RenderableNode
one. It addresses the "object on multiple canvasses" problem as well. Each
canvas is associated with a scenegraph. So if you have 2 canvasses and you
want the same object to be drawn on both of them, you'd do this:
- create the renderer (let's say GCRenderer)
- create the IRenderable (let's say a line)
- create the ILook (say width = 3, colour = red)
- create the RenderSet (with the newly created renderable and look)
- create a renderable scene node, set its renderset to the newly created
renderset and then attach it to the first canvas
- create a renderable scene node, set its renderset to the newly created
renderset and then attach it to the second canvas
Note how objects are shared.
There can be helper functions to perform some of the steps (like creating
scene node, renderable and look could all be done in one call).
7) The nodes bind to the events.
Open issues:
8) How much do we care about backwards compatibility?
9) How do bounding boxes interact with arbitrary transformations? If the
transformations are non-continous bigger problems arise.
In conclusion there might be more performance overhead coming with this
approach, but I doubt that it will be the bottleneck. The number of draw
calls is probably the main limiting factor (in addition to the drawing
itself).
It occurred to me that the enabled and priority attributes should be
part of the nodes rather than of the RenderSets. The scene graph is
similar to a Document in the MVC sense (
http://en.wikipedia.org/wiki/Model-view-controller , using this for
reference so we are both thinking about the same kind of MVC) I think,
because it holds only the data and doesn't do any of the viewing
(drawing) itself.
Frankly I am not sure whether FloatCanvas should support any kind of
default controller/document model. It seems to be more the view
component of it. Of course there could be another layer around FC which
acts as a data provider, creates, updates and deletes the corresponding
RenderSets/Renderables/Looks.
The user application will have to provide the model and controller part
of the MVC pattern. He can then write adapters which convert his data to
one of the default Renderables (circle, line, ...) or he derives from
IRenderable himself. E.g.:
# adapter (assuming user data does not change)
def adoptMyDatabaseCircle(db_connection):
c = fc.Circle()
c.center, c.radius = db_connection.RetrieveCircleData()
return c
# deriving
class MyCircleFromSomeDatabase(fc.IRenderable):
def __init__(self, db_connection):
self.db = db_connection
def Render(self, renderer):
center, radius = self.db.RetrieveCircleData()
renderer.DrawCircle(center, radius)
Choosing the right approach is probably dependent on the users data and
its update frequency. I am not entirely sure about the MVC deal here,
what do you think? Say a user changes the position of a circle, should
fc be able to automatically change the underlying model (user does not
have to register for any event)? In this case it probably makes sense to
divide the IRenderable even further, having it split up in something
like CircleData (holds center, radius) and finally the Render(renderer)
method which knows how to draw a CircleData. What do you think?
As a sidenote, sharing objects like outlined in my last email should
also reduce the storage space for persisting by a great deal.
another addendum :-)
I think fc should be split into separate subpackages. The "core" package
would mainly hold the view component and the basic framework. There
should be another subpackage which holds the concrete objects like
Circle, Polygon etc.. This is basically the same as a convenience layer
around the fc core. This layer should also be the one that deals with
all MVC issues.
Example: A user has a database with blossom objects. He can retrieve
number of blades, colour of blades and colour of blossom center. A view
for this would probably draw a circle (blossom center) and a bunch of
polygons in some way. Now say the database changed, then the user has to
make sure the flower data is updated and flagged "dirty" so that the
bounding box is recalculated and all caches are invalidated. This is
basically the model sending an event to the view. Another situation: The
user creates + and - buttons so one can adjust the number of blades in
the view. In this case the buttons control the model which can then
update the view's data.
One problem I always had with the MVC pattern in the past was how to
store data related to the view. In some way it's only view-data in
another way it's tied strictly to a model. In the flower example above
view data is something like flower is at position (x,y) on screen. This
coordinate attribute is not part of the underlying data model though. Of
course one can save this in a different location from the model data,
but then you get problems when view and model are not in sync anymore
(maybe the data changed since you last saved the view). These problems
are not too bad though.
-Matthias
_______________________________________________
FloatCanvas mailing list
[email protected]
http://mail.mithis.com/cgi-bin/mailman/listinfo/floatcanvas