sapeck-LineExercises

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