# teaching machines

## Twoville at Bridges Math Art 2021

August 3, 2021 by . Filed under public, twoville.

Following are the notes from a talk I gave at Brides Math Art 2021. My paper was a short paper, which means I had ten minutes to speak.

For the next ten minutes, I will tell some stories about a piece of software that I’ve been writing for making two-dimensional shapes using mathematics and computer science. There are a few things you should know before we begin:

• I am a computer science educator, not a mathematics educator. My goals are perhaps different than yours.
• I build software to help my students make things that have a life apart from the computer.
• The software I build is not the product. The products I care about are my students and the shapes they are able to make.
• My primary audience is young people in my community, not the whole world and professionals.

The software is called Twoville. It is a programming environment for generating scalable vector graphics files (SVGs). Twoville runs in the browser and is open-source and free. There are lots of 2D drawing languages and libraries. Twoville is different from most of them in that the shapes can be modified using either the mouse or code. This feature is best understood in a live demonstration.

Here we plot a rectangle:



r = rectangle()
r.corner = [10, 10]
r.size = [40, 80]
r.color = :cornflower

var twovilleDiv = jQuery('#twoville_rectangle');
twovilleDiv.closest('pre').replaceWith(twovilleDiv);
document.getElementById('twoville_form_rectangle').submit();

r = rectangle()
r.corner = [10, 10]
r.size = [40, 80]
r.color = :cornflower



I don’t use parameters much in Twoville for two reasons: I want all the data to be named and I want the lines of code to be short. When I work with young learners on iPads or Chromebooks, long lines make for clumsy editing. Instead of parameters, properties are assigned in separate statements. This can lead to repetitive prefixing, which can be eliminated using a with statement:



with rectangle()
corner = [10, 10]
size = [40, 80]
color = :cornflower

var twovilleDiv = jQuery('#twoville_with');
twovilleDiv.closest('pre').replaceWith(twovilleDiv);
document.getElementById('twoville_form_with').submit();

with rectangle()
corner = [10, 10]
size = [40, 80]
color = :cornflower



One of my jobs as a computer science educator is to help my students learn to program. This is a demoralizing experience for many, so I try to stay near the spatial and visual domains so that they can use their prior knowledge as a human with a body as they think about the code. However, there’s no getting around the fact that programming is indirect manipulation of the output. Trying to tweak a shape only through code throws away a good chunk of our brains. Twoville tries to support more of my students’ brains by allowing direct manipulation of the output. The programmer may drag on the handles to modify a shape’s properties.

The properties don’t have to be just raw numbers in order to directly manipulate them. They can be expressions, and Twoville will do its best to preserve their structure. Consider directly manipulating this circle, whose radius is derived from a diameter variable:



diameter = 100
with circle()
center = [50, 50]
radius = 0.5 * diameter
color = :purple

var twovilleDiv = jQuery('#twoville_twice-width');
twovilleDiv.closest('pre').replaceWith(twovilleDiv);
document.getElementById('twoville_form_twice-width').submit();

diameter = 100
with circle()
center = [50, 50]
radius = 0.5 * diameter
color = :purple



When you directly manipulate the radius handle, the expression is maintained. Instead the variable value is adjusted. Behind the scenes, Twoville has some heuristics in place that guide how the the expression is updated. The structure of the radius assignment above looks like this:

\begin{align}\textrm{property} &= \textrm{left} \times \textrm{right}\end{align}

When the mouse drags on the handle, we determine a new radius value strictly from the mouse position. Some part of the expression needs to change. We favor changing the right operand, whose new value can be solved for:

\begin{align}\textrm{property}’ &= \textrm{left} \times \textrm{right}’ \\\textrm{right}’ &= \frac{\textrm{property}’}{\textrm{left}}\end{align}

If the right operand is not a simple number or a variable whose value is a simple number, then the left operand is updated instead of the right. If neither are simple, then an offset is added to the unchanged original expression:

\begin{align}\textrm{property}’ &= \textrm{left} \times \textrm{right} + \Delta \\\Delta &= \textrm{property}’ – \textrm{left} \times \textrm{right} \\\end{align}

If you don’t like these heuristics, you can lock an operand from being updated by surrounding it in parentheses.

There are other shapes beyond rectangles and circles. General polygons are available:



with viewport
center = :zero2
size = [25, 25]

with polygon()
color = :tomato
for i to 6
vertex().position = unpolar(10, i * 60)

var twovilleDiv = jQuery('#twoville_polygon');
twovilleDiv.closest('pre').replaceWith(twovilleDiv);
document.getElementById('twoville_form_polygon').submit();

with viewport
center = :zero2
size = [25, 25]

with polygon()
color = :tomato
for i to 6
vertex().position = unpolar(10, i * 60)



Polygons can be smoothed out by switching them to what I call ungons:



with viewport
center = :zero2
size = [25, 25]

with ungon()
formula = :absolute
rounding = 3
color = :tomato
for i to 6
vertex().position = unpolar(10, i * 60)

var twovilleDiv = jQuery('#twoville_ungon');
twovilleDiv.closest('pre').replaceWith(twovilleDiv);
document.getElementById('twoville_form_ungon').submit();

with viewport
center = :zero2
size = [25, 25]

with ungon()
formula = :absolute
rounding = 3
color = :tomato
for i to 6
vertex().position = unpolar(10, i * 60)



The smoothing is done by applying Chaikin’s algorithm, which turns polygons into sequences of Bézier curves.

The most general shape is a path, which supports straight edges, circular arcs, Bézier curves, and holes. Here I stitched together a couple of quadratic Bézier curves, omitting the second’s control point and letting it be placed automatically to maintain continuity:



with path()
with stroke
color = :black
size = 1
color = :mint
go().position = [50, 100]
position = [50, 0]
control = [100, 0]
position = [50, 100]

var twovilleDiv = jQuery('#twoville_path');
twovilleDiv.closest('pre').replaceWith(twovilleDiv);
document.getElementById('twoville_form_path').submit();

with path()
with stroke
color = :black
size = 1
color = :mint
go().position = [50, 100]
position = [50, 0]
control = [100, 0]
position = [50, 100]



Twoville is my vehicle for talking about computer science and math with young people in my community. We managed to hold a fabrication summer camp in June. The students used Twoville to create vinyl stickers, stencils for painting T-shirts, and acrylic structures. Here are a few of their creations:

(student work is shared only in the live talk)

Thus we have Twoville, a environment for making shapes that can be both indirectly manipulated with code and directly manipulated with a mouse. The shapes can then be fabricated. What questions do you have?



x = 20
y = 60

with path()
with stroke
color = :black
size = 1
// opacity = 0
color = :lavender
go().position = [x, y]
line().position = [x + 10, y]
with cubic()
control1 = [49.72, 93.94]
control2 = [90.48, 48.81]
position = [45, 36]
line().position = [45, 10]
line().position = [55, 10]
line().position = [55, 30]
with cubic()
control1 = [117.74, 50.08]
control2 = [40.15, 121.34]
position = [x, y]
back()
with circle()
center = [50, 0]
with translate()
offset = [0, 10]

var twovilleDiv = jQuery('#twoville_questions');
twovilleDiv.closest('pre').replaceWith(twovilleDiv);
document.getElementById('twoville_form_questions').submit();

x = 20
y = 60

with path()
with stroke
color = :black
size = 1
// opacity = 0
color = :lavender
go().position = [x, y]
line().position = [x + 10, y]
with cubic()
control1 = [49.72, 93.94]
control2 = [90.48, 48.81]
position = [45, 36]
line().position = [45, 10]
line().position = [55, 10]
line().position = [55, 30]
with cubic()
control1 = [117.74, 50.08]
control2 = [40.15, 121.34]
position = [x, y]
back()
with circle()
center = [50, 0]