Looking for feedback and ideas.

View: New views
1 Messages — Rating Filter:   Alert me  

Looking for feedback and ideas.

by Paul Gregory-4 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hi,

I'm currently playing with Io as a potential basis for a procedural
animation framework, along the lines of Steve May's AL
<http://accad.osu.edu/%7Esmay/AL/>  . I started looking at Io because of
it's inherent introspective nature, which is quite important in the
concept. I've hit quite a few brick walls along the way, mostly due to
my lack of knowledge of Io and template based programming, and have had
some valuable feedback on #io. I'd like to get more feedback,
specifically to identify if I'm even heading in the 'right' direction
for Io, it could be that in trying to mimic some of the cool
functionality of AL, I'm missing something in Io that would be better
suited to the task.

If it's ok, I'll give a quick overview of the idea, and then show my
test code as it stands, and welcome any and all feedback.

Firstly, I have a RenderMan binding setup
(http://github.com/pgregory/ioman) that works fine, it initially just
provides a simple Object with mapping to the standard RenderMan API
calls, and then adds some sugar for the state management API calls
(RiTransformBegin/End, RiAttributeBegin/End etc.) that mean I don't need
to manage the state open/close calls, it's done in a method, and it
carries the target context through, making it more readable and
manageable. As an example, the following ioman calls in 'normal' mode...

rm := RenderMan clone
rm transformBegin
rm sphere(1, -1, 1, 360)
rm transformEnd

equate to the following in the 'simple' mode...

rm := RenderMan clone
rm transform(
     sphere(1, -1, 1, 360)
)

And onto the framework. The concept is to have 'model's and 'avar's, a
model is an encapsulation of the code necessary to draw a scene element
using the RenderMan API. The important part of the model concept is that
it stores the code necessary to recreate the representation, so it can
be re-executed at a later time. The reason for this is related to the
idea of avars. An avar is an 'animation variable', or a value that
changes during the animation based on various criteria, most commonly
time. By using the value of avars in the 'body' of a model, we can
change the value of avars, using various mechanisms, and have the models
that depend on them automatically recalculate if necessary. Other parts
of the scene, that are static, or whose avars aren't updating at that
point simply recall a cached version of their RenderMan representation,
making updates quicker. Ideally, the code in the 'model' body should be
as clear and simple as possible, and can include nested calls to other
models. The top level model is a world, which is no different to any
other model, except that there should only be one world, so that display
mechanisms can easily identify and use it.

Onto the code, here is my current implementation, along with a test use
example...


Range

#----------------------------------------------------------
# API
#----------------------------------------------------------

model := Object clone do (
     name ::= ""

     body := method(
         self theBody := call message argAt(0)
         m := self theBody
         x := nil
         self expressions := list
         while(m != nil,
             msg := m name asMessage
             msg setArguments(m arguments)
             self expressions append(msg)
             m = m next
             while(m != nil and m isEndOfLine == false,
                 x = m name asMessage
                 x setArguments(m arguments)
                 msg setNext(x)
                 msg = msg next
                 m = m next
             )
             if( m != nil,
                 m = m next
             )
         )
     )

     render := method(rm,
         self rm := rm
         self expressions foreach(i, exp,
             result := self doMessage(exp)
             if( result hasSlot("render"),
                 result render(rm)
             )
         )
     )
)

world := model clone do (
     name ::= "theWorld"
)

#----------------------------------------------------------
# Use example
#----------------------------------------------------------

blade := model clone do (
     name ::= "blade"
     length ::= 2
     width ::= 0.3
     thickness ::= 0.1

     body(
         left := - width / 2
         right := width / 2
         top := - thickness / 2
         bottom := thickness / 2
         rm transform(
             translate(0, 0, -length/2)
             transform(
                 rotate(90, 1, 0, 0)
                 rotate(180, 0, 0, 1)
                 cylinder(width/2, top, bottom, 180)
                 disk(top, width/2, 180)
                 disk(bottom, width/2, 180)
             )
             patchMesh("bilinear", 2, "nonperiodic", 4, "periodic", Map
with(
                 "P", list(RtPoint {left, top, 0},
                           RtPoint {left, top, length},
                           RtPoint {right, top, 0},
                           RtPoint {right, top, length},
                           RtPoint {right, bottom, 0},
                           RtPoint {right, bottom, length},
                           RtPoint {left, bottom, 0},
                           RtPoint {left, bottom, length}))
                         )
             transform(
                 translate(0,0,length)
                 rotate(90, 1, 0, 0)
                 cylinder(width/2, top, bottom, 180)
                 disk(top, width/2, 180)
                 disk(bottom, width/2, 180)
             )
         )
     )
)

scissor := model clone do (
     name ::= "scissor"
     segments ::= 5
     extension ::= 0.5

     body(
         minangle := (0.3 / 2) * (180/Number constants pi)
         maxangle := 90 - minangle
         0 to(segments) foreach(n,
             angle := 90 - (((90 * extension) max(minangle))
min(maxangle))
             offset := blade length * (((90 - angle) * (Number constants
pi / 180)) sin)
             rm transform(
                 rotate(-angle, 0, 1, 0)
                 blade render(rm)
                 rotate(2 * angle, 0, 1, 0)
                 translate(0, blade thickness, 0)
                 blade render(rm)
             )
             rm translate(0, 0, offset)
         )
     )
)

theWorld := world clone do (
     body(
         rm world(
             scissor render(rm)
         )
     )
)

rm := RenderMan clone

rm frame(1,
     format(320, 240, 1)
     display("test.tif", "framebuffer", "rgba", Map with("string
compression", "lzw"))
     projection("perspective", Map with("fov", 50))
     translate(0,0,4)
     rotate(-50, 1,1,0)
     theWorld render(rm)
)


As you can see, my base 'model' object has a 'body' method, that takes a
single arbitrary message tree, and processes it to store it as a list of
distinct messages on the model. They are then executes in the context of
the model (so that it can access local data defined on the model), and
with the render context (RenderMan clone) available via a local variable
'rm'.

Currently this works quite well, and produces an image (here
<http://pgregory.users.sourceforge.net/test.png> ) but there are some
things that don't 'feel' quite right to me...

1. The requirement to call render on nested models, i.e. blade
render(rm), ideally it might be nice to just put 'blade' there and have
the system work out it's a 'model' and render it. I tried this, and it
sort of works, you can see the code in model:render, which checks if a
result has a 'render' slot and calls it. It works in some cases,
specifically the 'scissor render(rm)' can be reduced to 'scissor', but
the 'blade' ones cannot, as they are in a parent message, the foreach,
so they are processed as a single message tree, not individual ones, and
don't ever see the 'hasSlot("render")' test.
2. The requirement to do rm .... in most places in the body, it would be
nice to be able to just have that resolved automatically, but I couldn't
find a reliable way of doing that without losing in other areas, such as
locals access etc.

Well, sorry for the long post, if you're still here and reading, thanks
for taking the time to read through. As I said at the start, I'm open to
any comments and suggestions you might have, especially if it's
highlighting an Io language feature that particularly suits my project
that I'm not aware of.

Thanks



Paul Gregory