Peter Gagliardi (ptrgags@gmail.com)
This repository is a simulation of various 3D drawing machines.
This is my alternative to this year's Inktober challenge. Instead of pen-and-ink drawings, I challenge myself to publish a new drawing machine simulation each day for the month of October.
Check back often, and enjoy!
Drawing machines are usually physical hand-operated machines that move a pen across the paper such that it makes intricate patterns. Think of the Spirograph you might have played with as a kid or the recent contender, the ThinkFun Hypnograph.
I was fascinated by these as a kid. Now, with much more math under my belt, I again find them fascinating for their mathematical intrigue.
For this project, I took the concept of drawing machines to a new level. I expanded some drawing machines that operate in 2D into 3-dimensional equivalents. Even if they are not always physically possible to build, the resulting shapes are still too pretty to dismiss.
I also combined various mechanisms to make
Inktober is a 31-day art challenge by artist Jake Parker. The goal is to draw something every day during the month of October and share the results. I've done this the past two years:
This year, I wanted to do something different. Instead of a traditional art
challenge, I am going to work on this simulation for a month. Each day I will
post a new machine to the gh-pages
branch for display at
https://ptrgags.dev/drawing-machines or https://ptrgags.github.io/drawing-machines.
After October, I'll take a break for November. Then I have some further plans for this project. Stay tuned :)
Below are the roles of key classes in the source code
index.js
is the entry point of the application. It loads the machines to display, constructs aRenderer
Renderer.js
handles setting up the BabylonJS engine, creating the GUI, and managing a collection of active drawing machines.machines/Machine.js
is the base class that defines a drawing machine. a machine is a DAG ofPart
s that represents a drawing machine. It handles building and updating the parts in the correct order based on dependencies (via topological sort).parts/Part.js
is the base class for drawing machine parts. Some represent concrete concepts such asWheel
, which is a cylinder that spins. Others are more abstract likeCentroid
which computes the centroid of several moving points in the scene.Part
s handle constructing BabylonJSTransformNode
s and other primitives. One unusual feature is thatParts
keep their translate/rotate/scale matrices as separate matrices rather than combining them into one matrix. This makes it easier to construct complex transformation hierarchies needed for some of the drawing machines. These nodes can be accessed throughJoint
s. Parts can be updated each frame with a time value that gets passed in fromRenderer -> Machine -> Part
Joint.js
This is a pair of(part, node_name)
. It provides a convenient way of connecting a part to any other transformation matrix of a second part. Furthermore, there are a few convenience methods like computing the world position of the origin of the model matrix thisJoint
represents.parts/Trace.js
ThisPart
is notable because it is what actually produces the intricate parametric curves. It is a polyline that keeps track of the most recent N positions of whichever transform it is attached to (source
) Furthermore, a second transformation can be used as the origin (origin
), and a third can be used as the reference frame (target
). Thistarget
transformation is particularly interesting, since it allows for drawing on rotated reference frames (like paper attached to a turntable).waves/Wave.js
This base class wraps 2-pi-periodic functions like sine and square waves using the Strategy design pattern. This can be used in a few parts such asOscillator
orXYZOscillator
to create more nuanced motion than simple harmonic motion.parts/Prefab.js
This class adapts aMachine
to match the interface ofPart
. This makes it much easier to combine machines into larger, more complex drawing machines. In terms of operation, it treats theMachine
as a sub-DAG that must complete its operations before thisPart
node is complete.machines/PartViewer.js
This class wraps aPart
into aMachine
, adding an originPoint
and aTrace
. This makes it easy to explore the motion of aPart
without creating boilerplate classes.