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()