A: Dashed Line

/*
* sapeck_03_A_DashedLine.pde
* A dashed line between the center and the mouse
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/
int DASH_INTERVAL = 20;
void setup() {
size(640, 480);
}
void draw() {
background(255, 255, 255);
float d = dist(width/2, height/2, mouseX, mouseY);
float num = d/DASH_INTERVAL;
for (int i = 0; i <= num; i += 2) {
float x1 = lerp(width/2, mouseX, i/num);
float y1 = lerp(height/2, mouseY, i/num);
float x2 = lerp(width/2, mouseX, min(1.0, (i+1)/num));
float y2 = lerp(height/2, mouseY, min(1.0, (i+1)/num));
line(x1, y1, x2, y2);
}
}
void mouseClicked() {
saveFrame("sapeck_03_A_DashedLine_####.png");
}
B: Living Line

/*
* sapeck_03_B_LivingLine.pde
* A mouse trail
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/
int NUM_POSITIONS = 100;
// Two 2D arrays
ArrayList x_positions = new ArrayList();
ArrayList y_positions = new ArrayList();
// One 2D Array
ArrayList<Integer[]> xy_positions = new ArrayList<Integer[]>();
// One Array of Objects (PVectors)
ArrayList pv_positions = new ArrayList();
void setup() {
size(640, 480);
}
void mouseMoved() {
// If there are 100 points recorded, remove the first one
// This is a FIFO list, so the items are appended to the end
// and removed from the beginning.
if (x_positions.size() >= 100) {
x_positions.remove(0);
y_positions.remove(0);
xy_positions.remove(0);
pv_positions.remove(0);
}
// Add the new location
x_positions.add(mouseX);
y_positions.add(mouseY);
Integer[] xy = { mouseX, mouseY };
xy_positions.add(xy);
PVector pv = new PVector(mouseX, mouseY);
pv_positions.add(pv);
}
void draw() {
background(255, 255, 255);
beginShape();
for (int i = 0; i < pv_positions.size(); i++) {
// Loop over each item in the list and add a point
// to the line
PVector pv = pv_positions.get(i);
vertex(pv.x, pv.y);
}
endShape();
}
void mouseClicked() {
saveFrame("sapeck_03_B_LivingLine_####.png");
}
C: Spicy Line

/*
* sapeck_03_C_SpicyLine.pde
* A mouse trail that gets spicy
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/
int NUM_POSITIONS = 100;
float RANDOMNESS_START = 1;
float RANDOMNESS_RATE = 0.008;
// One Array of Objects (PVectors)
ArrayList pv_positions = new ArrayList();
float randomness = RANDOMNESS_START;
void setup() {
size(640, 480);
}
void mouseMoved() {
// Increase the randomness over time of movements
randomness += RANDOMNESS_RATE;
// If there are 100 points recorded, remove the first one
// This is a FIFO list, so the items are appended to the end
// and removed from the beginning.
if (pv_positions.size() >= 100) pv_positions.remove(0);
// Add the new location
float x = mouseX + random(-1 * randomness, randomness);
float y = mouseY + random(-1 * randomness, randomness);
PVector pv = new PVector(x, y);
pv_positions.add(pv);
}
void draw() {
background(255, 255, 255);
beginShape();
for (int i = 0; i < pv_positions.size(); i++) {
// Loop over each item in the list and add a point
// to the curve
PVector pv = pv_positions.get(i);
vertex(pv.x, pv.y);
}
endShape();
}
void mouseClicked() {
saveFrame("sapeck_03_C_SpicyLine_####.png");
}
D: One Circle, Two Ways
Sine and Cosine Points

/*
* sapeck_03_D_OneCircleTwoWays_SINCOSPoints.pde
* A circle made from sine and cosine points
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/
int NUM_POINTS = 20;
float CIRCLE_RADIUS = 100;
// One Array of Objects (PVectors)
PVector pv_positions[] = new PVector[NUM_POINTS];
void setup() {
size(640, 480);
for (int i = 0; i < NUM_POINTS; i++) {
float x = CIRCLE_RADIUS * cos(i * (TWO_PI / NUM_POINTS)) + (width / 2);
float y = CIRCLE_RADIUS * sin(i * (TWO_PI / NUM_POINTS)) + (height / 2);
pv_positions[i] = new PVector(x, y);
}
}
void draw() {
background(255, 255, 255);
beginShape();
for (int i = 0; i < NUM_POINTS; i++) {
// Loop over each item in the list and add a point
// to the curve
PVector pv = pv_positions[i];
vertex(pv.x, pv.y);
}
endShape(CLOSE);
}
void mouseClicked() {
saveFrame("sapeck_03_D_OneCircleTwoWays_SINCOSPoints_####.png");
}
Cosine and Square Root

/*
* sapeck_03_D_OneCircleTwoWays_SQRT.pde
* A circle made from cosine and square root
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/
int NUM_POINTS = 20;
float CIRCLE_RADIUS = 100;
// One Array of Objects (PVectors)
PVector pv_positions[] = new PVector[NUM_POINTS];
float CIRCLE_RADIUS_SQ = pow(CIRCLE_RADIUS, 2);
void setup() {
size(640, 480);
for (int i = 0; i < NUM_POINTS; i++) { float x = CIRCLE_RADIUS * cos(i * (TWO_PI / NUM_POINTS)); float x_sq = pow(x, 2); float y = sqrt(CIRCLE_RADIUS_SQ - x_sq); float x_aligned = x + (width / 2); float y_aligned = (height / 2) + y; if (i > NUM_POINTS / 2) y_aligned -= 2 * y;
pv_positions[i] = new PVector(x_aligned, y_aligned);
}
}
void draw() {
background(255, 255, 255);
beginShape();
for (int i = 0; i < NUM_POINTS; i++) {
// Loop over each item in the list and add a point
// to the curve
PVector pv = pv_positions[i];
vertex(pv.x, pv.y);
}
endShape(CLOSE);
}
void mouseClicked() {
saveFrame("sapeck_03_D_OneCircleTwoWays_SQRT_####.png");
}
E: Spiral

/*
* sapeck_03_E_Spiral.pde
* A spiral made from a circle with increasing radius
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/
int POINTS_PER_ROTATION = 10;
int NUM_POINTS = 50;
float RADIUS_GROWTH_PER_POINT = 4;
// One Array of Objects (PVectors)
PVector pv_positions[] = new PVector[NUM_POINTS];
float radius = 0;
void setup() {
size(640, 480);
for (int i = 0; i < NUM_POINTS; i++) {
float theta = i * (TWO_PI / POINTS_PER_ROTATION);
float x = radius * cos(theta) + (width / 2);
float y = radius * sin(theta) + (height / 2);
pv_positions[i] = new PVector(x, y);
radius += RADIUS_GROWTH_PER_POINT;
}
}
void draw() {
background(255, 255, 255);
beginShape();
for (int i = 0; i < NUM_POINTS; i++) {
// Loop over each item in the list and add a point
// to the curve
PVector pv = pv_positions[i];
curveVertex(pv.x, pv.y);
}
endShape();
}
void mouseClicked() {
saveFrame("sapeck_03_E_Spiral_####.png");
}
F: Parallel Polyline
For this one, I used vsketch, vpype, and shapely. It’s as simple as installing these Python packages and then running “vsk run” and it will bring a viewer, auto-reload your code, and save an SVG for you when you click the “LIKE!”button.
#!/usr/bin/env python3
#
# sapeck_03_F_ParallelPolyline.py
# Draw a teardrop-like shape with inner and outer offset lines
# Originally created by sapeck 2021-09-15
# CMU 60-428 F21 Drawing With Machines
#
from math import cos, sin
from shapely.geometry import LineString, MultiLineString
import vsketch
PI = 3.14159
class Sapeck03FParallelpolylineVskSketch(vsketch.SketchClass):
CIRCLE_POINTS = 100
RADIUS = 2
def draw(self, vsk: vsketch.Vsketch) -> None:
vsk.size("letter", landscape=False)
vsk.scale("in")
# This makes a teardrop-like shape
points = [(0, 0)]
for i in range(0, self.CIRCLE_POINTS):
x = self.RADIUS * cos(i * (PI / self.CIRCLE_POINTS))
y = self.RADIUS * sin(i * (PI / self.CIRCLE_POINTS)) + self.RADIUS + 1
points.append((x, y))
points.append((0, 0))
# points is just an array of (x, y) points
line = LineString(points)
vsk.stroke(1)
vsk.geometry(line)
# Now make the inner and outer offsets
vsk.stroke(2)
offset0 = line.parallel_offset(1, 'left', join_style=2, mitre_limit=10.0) # inner offset
offset1 = line.parallel_offset(1, 'right', join_style=2, mitre_limit=10.0) # outer offset
vsk.geometry(offset0)
vsk.geometry(offset1)
def finalize(self, vsk: vsketch.Vsketch) -> None:
# We can automatically apply vpype settings here too!
vsk.vpype("linemerge linesimplify reloop linesort")
if __name__ == "__main__":
Sapeck03FParallelpolylineVskSketch.display()
G: Lines of Different Weights
#!/usr/bin/env python3
#
# sapeck_03_G_LinesOfDifferentWeights.py
# Draw different weight lines using offset curves
# Originally created by sapeck 2021-09-15
# CMU 60-428 F21 Drawing With Machines
#
from math import cos, sin
from shapely.geometry import LineString, MultiLineString
import vsketch
PI = 3.14159
class Sapeck03GLinesofdifferentweightsSketch(vsketch.SketchClass):
CIRCLE_POINTS = 100
RADIUS = 2
OFFSET_AMOUNT = 0.0025
OFFSET_NUM = 10
def draw(self, vsk: vsketch.Vsketch) -> None:
vsk.size("letter", landscape=False)
vsk.scale("in")
# This makes a teardrop-like shape
points = [(0, 0)]
for i in range(0, self.CIRCLE_POINTS):
x = self.RADIUS * cos(i * (PI / self.CIRCLE_POINTS))
y = self.RADIUS * sin(i * (PI / self.CIRCLE_POINTS)) + self.RADIUS + 1
points.append((x, y))
points.append((0, 0))
# points is just an array of (x, y) points
line = LineString(points)
vsk.stroke(1)
vsk.geometry(line)
# Now make the inner offsets
vsk.stroke(2)
for i in range(0, self.OFFSET_NUM):
offset = line.parallel_offset(i * self.OFFSET_AMOUNT, 'left', join_style=2, mitre_limit=10.0) # inner offset
vsk.geometry(offset)
def finalize(self, vsk: vsketch.Vsketch) -> None:
vsk.vpype("linemerge linesimplify reloop linesort")
if __name__ == "__main__":
Sapeck03GLinesofdifferentweightsSketch.display()
H: Calligraphic Polyline
#!/usr/bin/env python3
#
# sapeck_03_H_CalligraphicPolyline.py
# Draw a calligraphic sine wave
# Originally created by sapeck 2021-09-15
# CMU 60-428 F21 Drawing With Machines
#
from math import cos, dist, sin
import numpy as np
from shapely.geometry import LineString, MultiLineString, Point
from shapely.ops import unary_union
import vsketch
PI = 3.14159
class Sapeck03HCalligraphicpolylineSketch(vsketch.SketchClass):
CIRCLE_POINTS = 1000
RADIUS = 3
#OFFSET_AMOUNT = 0.0025
OFFSET_AMOUNT = 0.0025
OFFSET_MULTIPLIER = 600
BLEED_OVER = 1
def draw(self, vsk: vsketch.Vsketch) -> None:
vsk.size("letter", landscape=True)
vsk.scale("in")
# This makes a teardrop-like shape
points = []
for i in range(0, self.CIRCLE_POINTS):
x = i * (2 * PI / self.CIRCLE_POINTS)
y = self.RADIUS * sin(x)
points.append((x, y))
last = None
for point_i in range(0, len(points) - 1):
prev_point = points[len(points) - 1] if point_i == 0 else points[point_i - 1]
point = points[point_i]
next_point = points[0] if point_i == len(points) - 1 else points[point_i + 1]
change = dist(point, next_point)
if change == 0:
continue
weight = 1 / (change * self.OFFSET_MULTIPLIER) # width of line
num_lines = int(change / self.OFFSET_AMOUNT)
orig = LineString([point, next_point])
for i in range(1, num_lines + self.BLEED_OVER):
next_point_shortened = orig.interpolate(i / num_lines, normalized=True)
orig_short = LineString([Point(point[0], point[1]), next_point_shortened])
# find perpendicular https://stackoverflow.com/a/57072678
left = orig_short.parallel_offset(weight / 2, 'left')
right = orig_short.parallel_offset(weight / 2, 'right')
c = left.boundary[1]
d = right.boundary[0] # note the different orientation for right offset
last = d
vsk.geometry(LineString([c, d]))
def finalize(self, vsk: vsketch.Vsketch) -> None:
vsk.vpype("linemerge linesimplify reloop linesort")
if __name__ == "__main__":
Sapeck03HCalligraphicpolylineSketch.display()