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:
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:
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 scoping block:
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:
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:
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:
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:
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:
Polygons can be smoothed out by switching them to what I call ungons:
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:
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?
]]>
There are benefits to working on your local machine. You aren’t impacted by a slow network connection or service hiccups. There are also costs. You are on your own for troubleshooting your machine; your instructor is not going to help with your individual setup. Sharing your project with your instructor is a bit more work. If you willing to accept these costs and use your own computer, follow these instructions to install Visual Studio Code.
index.html.<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SLAI 2021: Geometric Modeling</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/TrackballControls.js"></script>
</head>
<body>
🥨
</body>
</html>
index.html. Return to the Explorer by way of View / Explorer. Right-click on the file and select Open with Live Server. A page with a pretzel in it should open in your web browser.Below are a few ideas for research projects that will build off of the 3D thinking that you’ve been doing. The descriptions are intentionally vague, as the projects are meant to be owned by you. Feel free to tailor the ideas below or ignore them altogether.
Triangulation Investigate algorithms for turning an arbitrary polygon into triangles. Implement a program that allows the user to draw a polygon, triangulate it, and then extrude or revolve it into a solid model.
Ramer–Douglas–Peucker algorithm Implement a program that allows the user to draw a cross section freehand, simplify the drawing using the RDP algorithm, and then extrude or revolve it. Or allow the user to draw multiple cross sections and then stitch them together.
Geometric Nets Develop an algorithm for unfolding a 3D model so that it lies flat. Flat meshes are easier to print on or cut from paper. Then they can reassembled into their 3D form.
Boolean Composition Investigate constructive solid geometry. Develop a program that allows the user to subtract, intersect, or union models.
Perlin Noise Investigate “organic” noise and use it to produce 3D models in some way.
Superquadrics Investigate the superquadric family of shapes. Develop a program that allows the user to explore their parameter space.
Octasphere Investigate the octasphere family of shapes. Develop a program that allows the user to explore their parameter space. See Philip Rideout’s overview.
Bézier Interface Investigate Bézier curves. Implement a program that allows the user to draw curved cross sections of models, convert them into polylines, and then revolve or extrude them.
Bézier Surfaces Investigate Bézier curves. Implement a program that allows the user to draw a grid of curves and convert it to a 3D mesh. See Scratch-a-pixel for an example.
Parametric Design Investigate direct manipulation. Implement a program that displays a parametric shape and allows the user to drag on it to change its parameters and regenerate the mesh.
Chaikin’s Algorithm Investigate Bézier curves. Implement a program that allows the user the draw a shape’s cross section as a polygon, converts the drawing to curves, and then samples the curves to produce a polyline that is extruded or revolved to produce a solid shape.
]]>As you have time, create other flat shape generators. Here are a few ideas:
Feed their return values into your extrude function to produce solid models.
All of our shape generators center the shapes around the origin. As we carve out new shapes using boolean operations, we want to be able to transform parts so that they have different sizes, locations, or orientations. In this exercise, you add a couple of utility functions to translate and scale your meshes.
Write a function named translate. Translation moves an object along the three axes, but does not rotate or resize the object. Have it accept these parameters:
mesh to shift around.offset by which to shift the mesh. It is an array of three numbers. Element 0 is the x-offset, element 1 is the y-offset, and element 2 is the z-offset.Inside the function, reassign every position in the mesh so that it is offset from its original position. Follow this pseudocode:
for each position apply offset to position
Test out your implementation of this method in render.js by adding a call like this:
const mesh = generateCube();
translate(mesh, [2, 0, 0]);
You should see the cube shifted right 2 units.
Write a function named scale. Have it accept these parameters:
mesh to shift around.factors by which to scale the mesh. It is an array of three numbers. Element 0 is the x-factor, element 1 is the y-factor, and element 2 is the z-factor.Inside the function, reassign every position in the mesh so that it is scaled from its original position. Follow this pseudocode:
for each position apply scale to position
Test out your implementation of this method in render.js by adding a call like this:
const mesh = generateCube();
scale(mesh, [1, 2, 1]);
You should see the cube scaled to be twice as tall as it is wide and deep.
Another common operation is rotation. The math behind rotation is more involved. If you find yourself needing to rotate a part or wanting to understand how rotation is done, let me know and we can talk through its implementation.
]]>Here’s an unsung hero whose worth isn’t recognized until it’s gone:

Just in case you lose one and need to print a replacement, you should have a 3D model of a button available.
Write a function named generateButton to generate the kind of shape you see in the image above. Have it accept these parameters:
nlongitudes that specifies the number of lines of longitude that the cylinder has. Higher numbers won’t have much of an effect on the appearance.height of the button, which measures the cylindrical height.majorRadius of the button, which marks the distance from the y-axis to the outer rim.minorRadius of the button, which marks the distance from the y-axis to the inner rim.holeRadius of the button, which measures the size of a thread hole.holeGap of the button, which measures the distance between thread holes.Use boolean operations to create your button.
]]>Future archaeologists will someday dig up our modern-day homes, and they will find many of these artifacts:

This is a six-sided die, known as a D6 to gamers who must distinguish it from D12s, D20s, and so on.
Write a function named generateD6 to generate this shape. Have it accept no parameters. Use boolean operations.
Every archaeological dig seems to turn up a bowl. Our species has been making them for millenia, so it only seems right to make one out of code.

Write a function named generateBowl to generate this kind of shape. Have it accept these parameters:
majorRadius of the bowl, which marks the distance from the y-axis to the outer rim.minorRadius of the bowl, which marks the distance from the y-axis to the inner rim.Use boolean operations to create your bowl.
]]>Consider the square pot below, which is just asking you to fill it with soil and plants. Or maybe pens and pencils. Or maybe ice and lemonade.

Write a function named generateSquarePot that creates square pots of all sizes and thicknesses. Have it accept these parameters:
side length of the pot.height of the pot.wall thickness of the pot.Use boolean operations to create your square pot.
]]>With these next few exercises, you are given only an image of a target shape and function specification. It’s up to you to deduce what boolean operations are happening to produce the shape. Here’s your first challenge, which we’ll call a chain for lack of a more specific term:

Create your shape in a function named generateChain. Have it accept these parameters:
nlatitudes that specifies the number of lines of latitude that each torus has.nlongitudes that specifies the number of lines of longitude that each torus has.majorRadius of each torus.minorRadius of each torus.Use boolean operations to weld together the three torii. Rendering three separate torii is cheating.
]]>