stickz – LineWalk

 



I was interested in creating a line that expresses a human-like scribble that has the stroke appearance of a 0.5mm ball point pen. I messed around with sine and cosine functions that ended up creating anelliptical, vortex-like, scribble that has a direction that varies with noise, where a new drawing is constantly generated (hitting the spacebar starts and stops the generating of new drawings) 

 

//applies the noise function in creating randomness
// Uses https://github.com/zenozeng/p5.js-svg to export SVG.

var bExportSvg = false;
var runDrawing = true;

function setup() {
  createCanvas(600, 600, SVG);
}

function draw() {
  if (runDrawing == true) {
    background(230);
    if (bExportSvg) clear();
    strokeWeight(0.2);
    noFill();

    var cx = width / 2;
    var cy = height / 2;
    var px = cx;
    var py = cy;

    var direction = random(PI / 2, PI);

    beginShape();

    for (var i = 0; i < 2000; i++) {
      var increment = 3 + noise(i / 1000);
      var displacementX = px - cx;
      var displacementY = py - cy;
      var offset = max(3, sqrt(displacementX ** 2 + displacementY ** 2) / 20);
      increment *= 0.7 * offset;
      direction += radians(50 * (noise(i) - 10));

      px += 5 * noise(i) * increment * cos(200 * direction);
      py += 5 * noise(i) * increment * sin(200 * direction);

      var pointX = cx + displacementX * offset ** -0.8;
      var pointY = cy + displacementY * offset ** -0.8;

       
      vertex(pointX, pointY);
    }

    endShape();
    var seed = millis()**0.5;
    randomSeed(seed);
    noiseSeed(seed); //random noise value generated for each drawing coordinated with the seconds function, inspired by Golan Levin's Linewalk
    loop();
  }
  if (bExportSvg) {
    saveSVG("stickz_line_on_a_walk.svg");
    bExportSvg = false;
  } 
}

function keyPressed() {
  if (key == "s") {
    bExportSvg = true;
    loop();
  }
  if (keyCode === 32) { //if SPACEBAR is pressed, pause drawing
    if (runDrawing == false) runDrawing = true;
    else runDrawing = false;
  }
}
link to code

grape – LineWalk

I was mainly inspired by this old Youtube clip in which the person “plays” the Imperial March by writing a math equation. So for my line walk, I wanted something that didn’t necessarily “look” cool, but performed…cool. I wanted to see if the axidraw could sync up to the music. So I started looking through Processing’s Sound Library for anything on pitch detection. Instead I found amplitude and beat detectors which I turned into the following “records.”

waltz of the flowers

we will rock you

rickroll

_______ beat detection  ________

take on me

cantina band

bad guy

I think in order to get closer to my goal of an axidraw performance I’m going to look for libraries with pitch detection + maybe smoothing on the output. Otherwise I’ll add an additional threshold to the amplitude and simplify the movement of the record to make different notes more distinct.

code

import processing.sound.*;
import processing.svg.*;

// Declare the sound source and FFT analyzer variables
SoundFile sample;
Amplitude amp;

int num = 3500;
int range = 6;

float[] ax = new float[num];
float[] ay = new float[num]; 
// Define how many FFT bands to use (this needs to be a power of two)
int bands = 256;
float inc;
float growth;

float smoothingFactor = 0.3;

// Create a vector to store the smoothed spectrum data in
float[] sum = new float[bands];

// Variables for drawing the spectrum:
// Declare a scaling factor for adjusting the height of the rectangles
int scale = 5;
// Declare a drawing variable for calculating the width of the
float barWidth;
float rotater;

void setup() {
  size(768, 768);
  background(255);
  beginRecord(SVG, "badguy.svg");
  rotater = 0.0;
  inc = 0.0;
  growth = 0.05;

  for(int i = 0; i < num; i++) {
    ax[i] = 0;
    ay[i] = 0;
  }
  frameRate(30);
  
  // Calculate the width of the rects depending on how many bands we have
  barWidth = width/float(bands);

  // Load and play a soundfile and loop it.
  sample = new SoundFile(this, "badguy.aif");
  sample.loop();

  // Create the FFT analyzer and connect the playing soundfile to it.
  amp = new Amplitude(this);
  amp.input(sample);
}

void draw() {
  // Perform the analysis
  translate(width/2, height/2);
  for(int i = 1; i < num; i++) {
    ax[i-1] = ax[i];// *cos(rotater);
    ay[i-1] = ay[i];// *sin(rotater);
  }
  float r = map(amp.analyze(), 0, 0.1, inc, 40+inc);
  float x = r * cos(rotater);
  float y = r * sin(rotater);
  ax[num-1] = x;
  ay[num-1] = y;
  stroke(0);
  for(int i=1; i<num; i++) {    
    line(ax[i-1], ay[i-1], ax[i], ay[i]);
  }
  //stroke(255);
  //line(0,0,x,y);
  inc += growth;
  rotater+=0.01;
  if(int(rotater)==360)rotater = 0;
  if(int(inc) == 0) growth = 0.05;
  if(int(inc) == height/3) growth =-0.05;
}

void mousePressed() {
  endRecord();
  exit();
} 

the beat detection code is the same as above except you use a new BeatDetector (it also uses the analyze() function). Also my code is pretty terrible. In order to store all the measurements of beats/amplitudes I just used an array (after looking at some brownian motion stuff) which is not good for optimization.

gabagoo-LineWalk

 

 

APPROACH: I really wanted to create a line that alternates between lines and curves. I got stuck with applying and reapplying matrix transformation with push() and pop(). I ended up using a stack of matrix transformations. I also wanted the animation of watching the line 'walk' be really satisfying so I used p5.func to use several different easing functions. I found that tuning various parameters in the way I transform the matrix between strokes was incredible sensitive and produced several interesting results.

LIVE EXAMPLE + CODE
let FUNCS = ['quadraticIn', 'quadraticOut', 'quadraticInOut', 'doubleQuadraticBezier', 'doubleQuadraticSigmoid', 'quadraticBezier', 'quadraticBezierStaircase', 'cubicIn', 'cubicOut', 'cubicInOut', 'brycesCubic', 'cubicBezier', 'cubicBezierThrough2Points', 'doubleCubicOgee', 'doubleCubicOgeeSimplified', 'quarticIn', 'quarticOut', 'quarticInOut', 'generalizedQuartic', 'quinticIn', 'quinticOut', 'quinticInOut']
let MIN_SPEED = 5, MAX_SPEED = 15, UPDATE = 0.1
let context = {}
let transforms = []
let e = new p5.Ease()

function setup() {
    createCanvas(windowWidth, windowHeight, SVG)
    angleMode(DEGREES)
    stroke(0)
    strokeWeight(5)
    updateContext(width/2, height/2)
    
}

function draw() {

    // apply transformations
    push()
    for (const [type, vec] of transforms) {
        switch (type) {
            case 'translate': translate(vec.x, vec.y); break;
            case 'rotate': rotate(atan2(vec.y, vec.x)); break;
        }
    }

    // draw line
    line(...lineargs())

    // exit condition
    if (context.prog >= 1) updateContext(context.width, 0)
    context.prog = min(1, context.prog + UPDATE * context.speed)
    pop()
}

function lineargs() {
    var val = e[context.easing](context.prog) * context.width
    return [0, 0, val, 0]
}

function updateContext(x, y) {
    context.start = createVector(x, y)

    if (context.dir == undefined) context.dir = p5.Vector.random2D()
    else context.dir = createVector(random(-.1, .1), random(1, 4))
    // else context.dir = createVector(1, random(-1, 1))

    context.speed = random(MIN_SPEED, MAX_SPEED)
    context.easing = random(FUNCS)
    context.width = random(random(0,10), random(10, 100))
    // context.width = 5
    context.prog = 0.

    transforms.push(['translate', context.start])
    transforms.push(['rotate', context.dir])
}

function keyPressed() {
    if (key == 'd') {
        noLoop()
        save('plot.svg')
        loop()
    }
}

 

sapeck-LineWalk

SVG:PNG (screenshot):

I began with a single curve and moving it in a circle around the center. I then modified the curve to go in and out of the circle to different radii. Next I flipped the in and out movement to be a back and forth movement. Lastly, I added some twist to every other point and exaggerated these twists and extensions.

This assignment pushed my ability to completely understand my code as I was writing it. When I hit a happy accident, I made sure to really think through how my change had affected the output.

/*
* sapeck_LineWalk.pde
* A drawing consisting of a single continuous line
* Originally created by sapeck 2021-09-07
* CMU 60-428 F21 Drawing With Machines
*/

import processing.svg.*;

float[] radii = { .5, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1 };
int MULTIPLIER = 30;
int OUTER = 10;
int OUTER_TWIST = 50;
int DIVISIONS = radii.length * MULTIPLIER;
int SMALLER_COORD = min(width, height);

void setup() {
size(1056, 816); // Letter: 11"x8.5" at 96 DPI.
noLoop();
}

void draw() {
background(255);
beginRecord(SVG, "sapeck-LineWalk.svg");

stroke(0);
noFill(); // Don't create duplicate shapes!

beginShape();

int current_radius = 0;

for (int i = 0; i < (2*DIVISIONS)+3; i++) {
float radius = OUTER;
if (i % 2 == 0) radius = radii[current_radius];
float offset = 0;
if (radius != OUTER) offset -= ((OUTER - radii[current_radius])/max(radii)) * MULTIPLIER*2*PI/DIVISIONS;
float theta = i*2*PI/DIVISIONS + offset;
float scale =(radii[current_radius]*SMALLER_COORD/2);
if (radii[current_radius] == max(radii)) theta -= OUTER_TWIST*2*PI/DIVISIONS;
float x = width/2 - scale*cos(theta);
float y = height/2 - scale*sin(theta);
curveVertex(x, y);
current_radius++;
if (current_radius == radii.length) current_radius = 0;
}

endShape(CLOSE);
endRecord();
saveFrame("sapeck-LineWalk.png");
}

 

dinkolas – LineWalk

I wanted to try to do a random walk weighted to walk through an image. The line mostly retains its velocity from step to step, but slightly steers towards bright areas. To be honest, it’s not much different from a pure random walk, and I had hoped to have the walks resemble to images much more. I intend to work more on it to improve the results…

const dosvg = false;
let img;

function preload() {
  img = loadImage('elephant.jpg');
}

function setup() {
  createCanvas(img.width, img.height, SVG);
  noLoop();
  img.loadPixels();
}

function draw() {
  if (!dosvg) image(img, width/4, height/4, width/2,height/2);
  let pvs = [];
  //console.log(img);
  for (let i = 0; i < img.pixels.length; i+=4) {
    pvs.push(img.pixels[i]);
  }
  //console.log(pvs);
  let index = sampleList(pvs);
  let loc = getLoc(index);
  stroke(255,0,0);
  strokeWeight(10);
  //point(loc.x, loc.y);
  let r = 5;
  let circle_r = 30;
  let theta = 0;
  
  stroke(255, 0,0);
  strokeWeight(1);
  
  for (let i = 0; i < 5000; i++) { let circle = getCircle(loc, theta, circle_r); let weighted = weightList(circle, t => t /*4*(t-0.5)**2*/);
    let circleIndex = sampleList(weighted);
    //let circleIndex = maxIndex(weighted);
    let targetTheta = theta + map(circleIndex, 0, circle.length, 0, 2*PI);
    
    
    let diff = ((targetTheta - theta) % TWO_PI + TWO_PI) % TWO_PI;
    if (diff > PI) diff -= TWO_PI;
    theta = (theta + 0.2*diff) % TWO_PI;
    let newLoc = {x: loc.x + r * Math.cos(theta), y: loc.y + r * Math.sin(theta)};
    line(map(loc.x, 0, width, width*0.25, width*0.75),
         map(loc.y, 0, height, height*0.25, height*0.75), 
         map(newLoc.x, 0, width, width*0.25, width*0.75), 
         map(newLoc.y, 0, height, height*0.25, height*0.75));
    loc = newLoc;
  }
}

function maxIndex(l) {
  let m = -Infinity;
  let id = 0;
  for (let i = 0; i < l.length; i++) { if (l[i] > m) {
      m = l[i];
      id = i;
    }
  }
  return id;
}

function weightList(l, f) {
  let w = [];
  for (let i = 0; i < l.length; i++) {
    let t = map(i, 0, l.length - 1, 0, 1);
    w.push(l[i] * f(t));
  }
  return w;
}

function getCircle(loc, theta, r) {
  let vs = [];
  for (let i = 0; i < 32; i++) { let angle = theta + map(i, 0, 32, 0, 2*PI); let l = {x: loc.x + r * Math.cos(angle), y: loc.y + r * Math.sin(angle)}; let index = getIndex(l); if (index === -1) { vs.push(0); } else { vs.push(1 - (img.pixels[index] / 255.0)); } } return vs; } function getLoc(i) { return {x: i % img.width, y: Math.floor(i / img.width)}; } function getIndex(loc) { if (loc.x >= img.width || loc.y >= img.height ||
     loc.x < 0 || loc.y < 0) return -1;
  return Math.floor(loc.x) + img.width * Math.floor(loc.y);
}

function keyPressed() {
  saveSVG("dingus.svg");
}

function sampleList(l) {
  let v = l[0];
  if (v < 0) console.log('negative!!!');
  let cumsum = [v];
  let sum = v;
  for (let i = 1; i < l.length; i++) {
    let v = l[i];
    if (v < 0) console.log('negative!!!');
    sum += v;
    cumsum.push(sum);
  }
  let t = sum * Math.random();
  //TODO: binary search
  for (let i = 0; i < cumsum.length; i++) { if (cumsum[i] >= t) return i;
  }
  console.log('shouldnt hapen');
  return cumsum.length - 1;
}

 

lemonbear-LineWalk

Here is my SVG (there are very bizarre lines in some browsers; feel free to click and open in a new tab):

Here is a screenshot:

Approach: I knew I wanted to do something sort of objective and I settled on flowers! Namely, roses. I thought it would be pretty simple algorithmically to create a spiral with some jitter for the petals, and after a little more finicky work, I was able to implement leaves on the outer layer of each rose. My friend pointed out that with some color and cursive writing this could be an album cover for an extremely mediocre emo band, and I think I agree with that.

Struggles: I had a little bit of a harder time getting into this creative portion of the homework, and actually found that recreating the Molnar was the most engaging out of these offerings for me. Also, there’s this weird bug where the line connecting one flower to the next’s center overshoots the center, and I spent like 45 minutes trying to debug it before giving up. My primary issue with this is that now it looks like there’s more than one line, but I promise it’s all just one.

Learned: I learned Javascript for these offerings! I had been kind of scared to learn p5.js in the past because I had never done JS before, but I learned that it’s basically gentler C (a language I am somewhat familiar with) plus some idiosyncrasies (there are no ints? everything is just floats? what kind of type system…)

Here is my code:

function setup() {
  createCanvas(816, 1056, SVG);
  noLoop();
}

function draw() {
  noFill();
  beginShape();
  var centerX = width/2;
  var centerY = height/2;
  var finalX, finalY;
  const array = [];
  for (var k = 1; k < 4; k++){
    for(var l = 1; l < 3; l++){
      var jitter1 = Math.floor(Math.random()*101)-50;
      var jitter2 = Math.floor(Math.random()*101)-50;
      array.push([width*l/3+jitter1, height*k/4+jitter2]);
    }
  }
  for (var j = 0; j < 6; j++){
    centerX = array[j][0];
    centerY = array[j][1];
    for (var i = 0; i < TWO_PI*10; i+=HALF_PI/6) {
      var jitter3 = Math.floor(Math.random()*i/2)-i/4;
      var jitter4 = Math.floor(Math.random()*i/2)-i/4;
      if ((jitter3 < 0 && jitter4 > 0) || (jitter3 > 0 && jitter4 < 0)) { jitter3 *= -1; } var x = centerX+cos(i)*(i*2+jitter3); var y = centerY+sin(i)*(i*2+jitter4); curveVertex(x, y); var leafTipX, leafTipY; if (i >= TWO_PI*9 && Math.random()<0.5){
        curveVertex(x, y);
        endShape();
        beginShape();
        curveVertex(x, y);
        curveVertex(x, y);
        leafTipX = (x-centerX)*0.4 + x;
        leafTipY = (y-centerY)*0.4 + y;
        var slope = (y-leafTipY)/(x-leafTipX);
        slope = -1/slope;
        var midX = (x+leafTipX)/2;
        var midY = (y+leafTipY)/2;
        var leftX = midX+10;
        var leftY = midY+10*slope;
        var rightX = midX-10;
        var rightY = midY-10*slope;
        curveVertex(leftX, leftY);
        curveVertex(leafTipX, leafTipY);
        curveVertex(rightX, rightY);
        curveVertex(x, y);
        curveVertex(x, y);
        endShape();
        beginShape();
        curveVertex(x, y);
        curveVertex(x, y);
      }
      finalX = x;
      finalY = y;
    }
  }
  curveVertex(finalX, finalY);
  endShape();
  // saveSVG("line_walk_v1.svg")
}

marimonda – LineWalk

a perfectly ordered line gets tickled

I have been recently interested in space-filling structures, in particular fractals and reaction-diffusion patterns. For my project, I decided to code a Hilbert Curve and play around with its structural integrity with Perlin noise and mouse input. This way, the resulting curve was also an interactive experience.

What I found hard: finding the perfect amount of chaos and order proved to be the hardest part, I spent a really long time trying to alter each parameter.

A few more images, gifs + code  under the cut:

Continue reading “marimonda – LineWalk”

lsh-LineWalk


As I worked on these, I began to think of them as pinwheels. Getting the right “flutter” out was a challenge, and I also had a bug for a little where content ended up mostly off the page. I ended up trying to “weave” in the iteration by flipping whether I add or subtract it in the simplex noise for the x or y.

const Simplex = require("https://unpkg.com/simplex-noise@2.4.0/simplex-noise.js");
const simplex = new Simplex();
const width = 761;
const height = width;
// source: https://rosettacode.org/wiki/Map_range#ES6
const rangeMap = (a, b) => (s) => {
  const [a1, a2] = a;
  const [b1, b2] = b;
  // Scaling up an order, and then down, to bypass a potential,
  // precision issue with negative numbers.
  return ((((b2 - b1) * (s - a1)) / (a2 - a1)) * 10 + 10 * b1) / 10;
}

function generatePaths() {
  const start = [Math.random() * width, Math.random() * height];
  const step = 200.0;
  const path = [start];
  for (let i = 0; i < 100; i++) {
    const [px, py] = path.at(-1);
    const theta = simplex.noise2D(px * px * 10 * +i * i, py * py * 10 - i * i);
    const t = rangeMap([-1, 1], [-Math.PI, Math.PI])(theta);
    const x =
      Math.cos(t) * step + simplex.noise2D(px + i, py - i) * 100 + width / 2;
    const y =
      Math.sin(t) * step + simplex.noise2D(px - i, py + i) * 100 + height / 2;
    path.push([x, y]);
  }
  return path;
}

function draw() {
  const svg = d3.create("svg").attr("width", width).attr("height", height);
  svg
    .append("path")
    .attr("d", d3.line()(generatePaths()))
    .attr("fill", "none")
    .attr("stroke", "black");
  return svg.node();
}