Philip Quaife 13 Oct 05
There are a number of 3D canvas extensions for Tcl. The latest is canvas3D [L1 ].
I myself have been working on a number of systems along the same vein and have released QAnim - tclogl Animation Demo for the purposes of demonstrating a different approch to widget design for Tk.
This page exists to show how the traditional design of Tk widgets locks developers into a restricted subset of the capabilities of Tcl/Tk.
While I am going to be biased towards by own creation, I am not pushing it over canvas3d. What I am trying to do, is show by code that there are distinct advantages in implementing the visual systems of Tk from within Tcl rather than the traditional approach of C-based extensions.
Some basics on the two systems.
Cvs version as at 13 Oct 05.
Modelled on Tk canvas, it would be understandable by anyone familiar with the latter. It is implemented as a Tk widget and can be configured like any other Tk widget.
It currently supports 2D primatives like Tk canvas as well as 3D lines and polygons. Images can be mapped to a polygon as a texture.
Documentation is sparse and only four basic demonstration programs are included, none of which show off the true capabilites of the widget.
# Lets make a red sphere and a blue cone. pack [canvas3d .tut] .tut create polygon [canvas3d::sphere -radius 3 -detail 10] -tags ball -color red .tut create polygon [canvas3d::cylinder -radiustwo 0 -heigth 5 -radius1 3 ] -tags cone -color blue .tut transform ball {move -2 0 0} .tut transform cone {move 2 0 0} # # make the objects visible by pointing the camera at them # .tut configure -cameralocation {0 5 10} .tut transform -camera {} {lookat all} # #Rotate the ball item around its Y axis by 25 degrees. # .tut transform ball {move 2 0 0 rotate 25 0 1 0 move -2 0 0} # Change the cone to yellow .tut itemconfigure cone -color yellow # hide the ball .tut itemconfigure ball -hidden 1
Coordinates appear to be unitless floating point values. The widget stores the coordinates of the vertices for every item created. When an item is transformed the widget converts the coordinates for all items matching the specified tags.
The transformed coordinates are then used by openGL to render the shape.
One disadvantage of this is that, to rotate an object about its center, we have to translate the coordinates back to the origin, do the rotation, and then translate back to the original position. This is all done via the CPU not the GPU.
Objects are grouped by tags. This is has its limitations for complex items and when shapes are made out of groups of shapes. So it works for flat groups but starts to become tedious to manage when implementing heirarchical groups.
Based on the code released as Qanim Tutorial 1.1 10 Oct 05.
This code is not really a Canvas widget as such. It is distributed as a demonstration showing the capabilies of tclogl and a Tcl-based canvas rather then providing a base for another extension.
The tutorial provides approximately 20 screens showing various capabilities of the system, from basic shapes through to exploding images and spheres, as well as rotating text mapped to a circle.
Unlike canvas3d there is no dispensation for 2D objects. Everything is rendered in 3D coordinates including text. There is no facility to place 2D bitmaps et al. on the screen. Nothing in the design would prevent someone from doing so if they desired.
The reason it is called an animation system is that the design of the shapes (canvas items) is aligned with the operations that one would want to perform on them, such as changing size, colour, position. It also includes an event system for performing time-synchronised effects.
The core of the animation system deals with display lists. Each displayed item exists as an openGL display list. These display lists are defined and manipulated by name. It could be viewed as a tag much like the tk canvas.
On top of this are built other higher levels of abstraction such as:
Each subsystem is just a collection of Tcl procs that operate like an ensemble, but work without using namespaces.
# Let's make a red sphere and a blue cone. pack [Scene .tut] # step back a bit to see the objects. .tut view update OffsetZ -8 .tut shape define BallRotation {} .tut shape define ConeColour { Colour b 1} .tut shape define Ball { Colour r 1 Translate x -2 Shape BallRotation glutSolidSphere 3 32 32 } .tut shape define Cone { Shape ConeColour Translate x 2 glutSolidCone 3 5 32 32 } # # make the objects visible by putting them on the display list # .tut vlist add Objects Ball .tut vlist add Objects Cone # # Rotate the ball item around its axis by 25 degrees. # .tut shape define BallRotation { Rotate 25 0 1 0 } # Change the cone to yellow .tut shape define ConeColour {Colour r 1 g 1} # Hide the ball .tut vlist remove Objects Ball
There are more efficient ways of altering the attributes of a shape than those shown above, but I want to keep the example simple. We also would not normally place translations or colours inside the base shapes.
There are no coordinates stored by the Qanim system. Each shape is defined by drawing commands stored in a display list. Once created the information used to create it is lost.
You can get the coordinates (or bbox) by rendering the shape into the feedback buffer using a 1:1 transformation. There is a subsystem that makes this available via simple commands.
Each shape is drawn relative to the origin (0,0,0). This means that its location is based on the environment that exists at the time the object is drawn. We can move and scale a shape by placing openGL commands prior to drawing the shape.
We can preserve the shape and position of a shape by surrounding it with calls to Push and Pop the transformation matrix.
Qanim supports a special form of display list called a VList; this just does all the housekeeping for you and makes working with groups easier. However there is no need for any special code to implement groups.
Since all shapes are just display lists there are no limits on how they are nested. For example, let's make a shape out of our two example shapes.
.tut shape define TwoShapes { Shape Ball Shape Cone }
What about Two Balls or more:
.tut shape define MoreShapes { Shape Ball Translate y 3 Shape Ball Translate y -6 Shape Ball Translate x 4 Scale 0.5 0.5 0.5 Shape Ball ;# A half sized ball }
We can make heirarchys as simply as defining a basic shape.
We can see by the examples that at the basic level both canvas3d and Qanim perform similar functions. We can define shapes, display them, and move them around relative to each other.
I am not sure how canvas3d will be extended in the future. As it stands every 3D shape is defined by the polygon command. Unlike Tk canvas there may not be the need for extensions as you can create your own object shapes above and beyond what is provided with tk canvas.
As for features, such as gradients, grouping, etc., they would only appear by modifying the 'C' code. If for example a New gradient type was thought up, the C code would have to modified to allow this.
Every feature of the system is a Tcl proc. To add a new feature you just create a new proc. Here is a simple example.
# Make it simpler to define a Sphere shape .tut shape primative sphere {{radius 1}} { glutSolidSphere $radius 32 32 } # Let's now make our red ball from the original example .tut shape define Ball {sphere 3} # Or ... .tut shape define Ball {sphere radius 3}
The shape primative command is just an interface to calling proc with appropriate arguments.
In essence it is equivalent to:
proc Shape/sphere {{radius 1}} { glutSolidCube $radius 32 32 }
I could have used namespaces and thus it would be Shape::sphere, but until we have 8.5, the above is simpler.
Let's try another example, what about a gradient background?
# grad left to right .tut shape primative gradientLR {colour1 colour2} { glBegin GL_QUADS Colourb $colour1 glVertex3f -0.5 -0.5 0 Colourb $colour2 glVertex3f 0.5 -0.5 0 glVertex3f 0.5 0.5 0 Colourb $colour1 glVertex3f -0.5 0.5 0 glEnd }
Not a complete example as it lacks a normal and texture coordinates. However, since it is just a proc we can create our own version that has our exacting requirements. If you look at the Qanim tutorial you will see that the image primitive is just a quad with texture coordinates specified.
What if we wanted the gradient to run top to bottom?
.tut shape primative gradientTB {colour1 colour2} { glBegin GL_QUADS Colourb $colour2 glVertex3f -0.5 -0.5 0 glVertex3f 0.5 -0.5 0 Colourb $colour1 glVertex3f 0.5 0.5 0 glVertex3f -0.5 0.5 0 glEnd }
Qanim lacks the sophisticated camera that canvas3d has, but it can easily be added by creating the appropriate code.
.tut scene proc camera {cmd args} {uplevel 1 Camera/$cmd $args} .tut scene proc Camera/lookat {shape} {....}
Then the application code can then use it:
.tut camera lookat Ball
Tcl has traditionally been viewed as a language that provides the glue for the application code written in C.
In Qanim I have attempted to show that Tcl can be used in its own right to do something that it was never designed to do.
While we need the tclogl extension to access the openGL API, this is only because Tcl provides no (core) foreign function interface. Everything 3D is done in Tcl. Quite a showcase for the language I would say.
GS through 3D polyhedra with simple tk canvas showed us how tantalisingly close Tcl came to performing real-time 3D transformations using the existing Tk canvas. It is a shame that back then no-one seized on this to look at what modifications to Tk-canvas could have been made to make it practical.
That moment has been lost; I would hope that others can see the potential in Tcl as a language in its own right, and seize the moment, and build on Tcl to further enhance it as a programming language.