For my first iteration, I decided on "build" rather than "extend." I wanted the creation process to be interactive so I could edit the "code" in real time and immediately see results. I was also not sure about the level of abstraction I was going to use, and I wanted the language to be very loose, unconstrained by syntax requirements.
I also wanted to have more control over the speed of execution, so I decided to build a language parser using C++. I decided to call this language Whimsy and use .whimsy as a file extension to identify programs in that language (for example, lunar.whimsy).
The initial exploratory coding was fairly straightforward. The code would read in a file, split it into lines, split the lines into tokens, and then simply parse the lines with a series of "if" statements; a segment of the code is shown in Listing 1. (The complete code can be downloaded here.)
Listing 1: Ad Hoc DSL Parsing Code
if (token == string("rectangle"))
CWhim *p_whim = new CRectangle();
if (token == string("at"))
float x = (float)atof(tokenizer.NextToken().data());
float y = (float)atof(tokenizer.NextToken().data());
At its most basic description, Greenblat's painting is composed of colored shapes. There are concentric shapes within shapes, and some shapes have other shapes attached to them. There are shapes that split other shapes, and shapes that are lists of other shapes. It seemed to me that some form of hierarchical list of shape objects was needed.
I created an abstract base class of shape called a Whim (class CWhim), and from this I derived an abstract ConvexShape object and then derived other shapes, collections of shapes, and sub-shapes from these base classes. The "world" of a painting is just a std::vector container of these objects.
After the obvious primitives of rectangles and circles, I needed a way to create the ovals. I created a CWhim called CSuperEgg. A SuperEgg is a term for the solid version of a SuperEllipse, which is a shape defined by the equation (x/a)r +(y/b)r=1, where a and b are the length of the axes, and r defines the curvature. (See Wolfram in Resources for a full explanation.) I added a SuperEgg keyword to the parser and a little bit of code to read the parameters, and created the object.
Equations like the one above produce very nice squared ovals, but they are too precise to match the more freeform ovals in the paintings. To create a less precise shape, I created a new "distort" object, which simply takes a parent object and overrides the render function to add some periodic noise displacement from the center point. At a low frequency, it simulates the hand-drawn look of the original and can be applied to any of the ConvexShape primitives.
Next I added the petals. These simply took a parent ConvexShape object and positioned themselves at specified points along the perimeter. All ConvexShape objects (including the distorted shapes) have a member function that returns points at a given angular or linear distance around the perimeter, so the attachment points and base profiles of the petals can be calculated easily.
The height of the petals is specified as a multiple of the width of the base. I found that as much as possible it was best to keep all numbers relative to some other object or part of an object, as it makes dependent changes far easier.
Finally, I added an Inner shape, which simply takes one ConvexShape and creates a copy of it inside, shrunk by a certain ratio and optionally distorted. Using multiple Inner shapes made it very easy to reproduce the concentric multi-colored ovals.
These few primitives allowed me to reproduce, in part, a segment of "Lunar Module" (see Figure 3). The code used to generate the mock-up is shown in Listing 2.
Figure 3: Reproducing the detail using SuperEgg, Inner and Petal primitives of the Whimsy language.
Listing 2: Whimsy Code
superegg 0.15,0.10,3.5 at .3,.7 size 1.2 black distort .01
petals 14 0.05 size 1.8 petalblue
inner .88,.01 tvpurple
superegg .1,.2,2 at .20,.7 size .4 distort .01 tvlime
inner .65,.01 tvyellow
inner .45,.01 tvlightyellow
superegg .1,.2,2 at .3,.7 size .5 distort .01 tvblack
inner .85 tvbrown
inner .80 tvred
inner .75 distort .03 tvorange
inner .70 tvyellow
superegg .1,.2,2 at .4,.7 size .4 distort .05 tvblue
inner .6 distort .2 tvdarkblue
inner .4 petalblue