blur
webgl
p5js
Making Störungen

Making Störungen

written by un_erscore

28 Sep 2022256 EDITIONS
0.5 TEZ

The initial premise for this work was to take a simple, perfect form and twist and deform it in ways that make it both vastly different, but still recognizable. It is a commentary on the beauty of perfect and imperfect forms resultant from small, cumulative, and incremental changes. The butterfly effect in motion.

Creating the perfect form

The first step in this process is to create our perfect form, the canvas upon which our perturbations will be painted. I wanted the basis for this perfect form to both fill the space fully as well as be a single line, so for these criteria, a spiral made the most sense.

Accomplishing this is relatively simple. Create a walker class that determines the parameters required to draw a spiral and then draw it outward step by step.

const walker = {
  drawing: true,
  pos: new Vec2(0, 0),
  radius: 1000,          // the radius of the spiral
  coilsize: 3,           // The distance between coils
  stepSize: 3,           // The Size of each step
  coils: null,           // The number of rotations in the spiral
  thetaMax: null,        // The full distance travelled in radians
  stepTheta: null,       // How far to step away for each side
  theta: null,           // The current angle of the spiral
  steps: 0,
  origin: new Vec2(w / 2, h / 2),
  // Initialise our values
  init: function() {
    this.coils = this.radius / this.coilsize;      // The number of coils the spiral will have
    this.thetaMax = this.coils * 2 * Math.PI;      // The maximum value that theta can get to before escape
    this.stepTheta = this.radius / this.thetaMax;  // Theta stepsize
    this.theta = this.stepSize / this.stepTheta;   // The initial theta value
  },
  walk: function() {
    
    // If the radius has reached it's maximum, we stop drawing.
    if(this.theta > this.thetaMax) this.drawing = false;
    
    // The distance of this step from the center
    // Calculated by multiplying the angular step size by the current theta
    let distance = this.stepTheta * this.theta;
    
    // Straight up spiral - polar coordinates
    let p = new Vec2(distance, this.theta);
		
    // Update the theta by the stepSize / distance
    this.theta += this.stepSize / distance;
    
    // Creat our output cartesian coordinates
    const cart = polarToCartesian(p);
		
    this.steps++;
    
    // Finally, plot
    this.plot(cart);
  },
    
  }
}

We also need a way to translate to and from polar coordinates, this is a simple thing.

const cartesianToPolar = (p) => {
  return new Vec2(
    Math.hypot(p.x, p.y),
    Math.atan2(p.y, p.x)
  );
}
const polarToCartesian = (p) => {
  return new Vec2(
    Math.cos(p.y) * p.x,
    Math.sin(p.y) * p.x
  );
}

The result? An Archimedean spiral. (https://openprocessing.org/sketch/1671394)

Before moving onto the next major step, I also wanted to make sure that our final output is plottable, so to make sure that we're not drawing any lines outside of the boundaries of our piece, we can do this by introducing this code before our plot command.

if(
  cart.x < -this.origin.x + 10. ||
  cart.x > this.origin.x - 10. ||
  cart.y < -this.origin.y + 10. ||
  cart.y > this.origin.y - 10. ||
  this.steps <= 2
) return this.pos = cart;

Some simple perturbations

The next step in the process was to introduce some simple perturbations. These are trigonometric functions that are used to deform the spiral as it travels around its path. The idea with these is that they perform some small nudging of the walker's position as it moves, these small nudges accumulate and become more and more pronounced over time. Here is a basic function and the result that it produces.

{
  const cart = polarToCartesian(p);
  const cx = cart.x / 30;
  const cy = cart.y / 30 + 1.5;
  
  cart.x += (cos(cy)) * 30;
  cart.y += (sin(cx)) * 30;
  
  p = cartesianToPolar(cart);
}

(https://openprocessing.org/sketch/1671410)

From there it was a matter of playing with playing with further trigonometric perturbations until a library was built up. Here's another example:

{
  const cart = polarToCartesian(p);
  const cx = cart.x / 20 + 1.5;
  const cy = cart.y / 20 - 3.5;

  cart.x += cos(cy) * cos(cx) * 15;
  cart.y += (sin(cx) - sin(cy)) * 15;

  p = cartesianToPolar(cart);
}

(https://openprocessing.org/sketch/1672270)

Layering perturbations

One of the nice things about working with these sorts of fundamental functions is that they can be layered on top of each other. Consider the following, which comprises of the two functions I have previous illustrated, and you can clearly see in the image that this is the sum of the two functions, but that it has produced something new.

{
  const cart = polarToCartesian(p);
  let cx = cart.x / 20;
  let cy = cart.y / 20;

  cart.x += cos(cy) * cos(cx) * 15;
  cart.y += (sin(cx) - sin(cy)) * 15;

  cx = cart.x / 20;
  cy = cart.y / 20;

  cart.x += cos(cy) * 20;
  cart.y += sin(cx) * 20;

  p = cartesianToPolar(cart);
}

(https://openprocessing.org/sketch/1672311)

Generalizing the perturbations

This was the more difficult part, creating a structure that could adequately describe any perturbation function I could provide. This was necessary because the final token would have a variable number of very random perturbations, and so I needed to create a language to do so. The first part of doing so was to break the functions down into their different components. This was relatively easy as the components consist of, broadly speaking, variables and components.

{
  const cart = polarToCartesian(p);
  
  // THESE ARE THE VARIABLES
  const cx = cart.x / 20;
  const cy = cart.y / 20;

  // THESE ARE THE COMPONENTS
  cart.x += cos(cy) * cos(cx) * 15;
  cart.y += (sin(cx) - sin(cy)) * 15;

  p = cartesianToPolar(cart);
}

I decided early on that the easiest way to accomplish such a generalization was to provide a simple string syntax that would be parsed using regexp and the JavaScript function constructor, which allows you to provide a string that comprises the body of the function - N.B. this is a safer way to run eval on strings, but is still not entirely safe, use at your own risk.

This first block of code represents the generalization of the variables component.

/* This function provides a afacility with allows the operands to
 * pass back into the variables or components. It does this by
 * looking for any variable name with an _ in front of it and
 * replacing that with an `operands` property.
 */
const replaceOperands = (op, operands) => {
  for (let i in operands) {
    op = op.replaceAll(`_${i}`, `operands.${i}`);
  }
  return op;
};

// sc is the factor by which the cartesian components are divided
const sc = 10;
// The cartesian components. In the program, this would be replaced with a dynamic value
const cart = { x: 10, y: 10 };

// The operands object that will end up carrying all of the variables (including the loopbacks)
let operands = {};

/* The variable components. Each of these will be executed in order
 * You can see, in the last item, a demonstration of the loop back:
 * `c = _a / sc`, that _a will evaluate to `operands.a`, so if
 * `sc = 10` and `cart.x = 10`, this property will evaluate to `c = 0.1`
 */
const q = ["a = cart.x / sc", "b = cart.y / sc", "c = _a / sc"];

// Loop through all of the components and execute
for (let i = 0, o = q[i]; i < q.length; o = q[++i]) {
  // Find the variable name. a, b, c as above.
  const it = /(\w+)+/.exec(o)[1];
  // Replace any operand directives in the components
  const ops = replaceOperands(/.*=\s*(.+)/.exec(o)[1], operands);
  console.log(ops);
  /* This is where the magic happens. Take the variable string
   * and turn it into a function string that is immediately
   * executed with the operational components. Because this
   * executes in explicit order, previous components are passed
   * back into these functions as operands.
   */
  operands[it] = Function(
    `return function(cart, sc, operands) { return ${ops} }`
  )()(cart, sc, operands);
}

console.log(operands);

From here, I needed to codify and operate on the components part of the function. This is the part that changes the content on the resultant vector.

// Represents the amplitude of the function as applied to the component
const f = 20;

/* The components. These will execute on the provided cartesian
 * coordinates ad well as beig passed back into the operands
 * object for further processing, if necessary.
 */
const r = ["x += cos(_a) * f", "y += sin(_b) * f"];

// Loop through all of the components and execute
for (let i = 0, o = r[i]; i < r.length; o = r[++i]) {
  // The variable name, x, y etc.
  const it = /(\w)+/.exec(o)[1];
  // Separated out the various components of the function
  const ops = /.*(.=)\s*(.+)/.exec(o);
  // Builds the name of the component within the vector
  const prop = `cart.${it}`;
  // The method, +=, -= etc.
  let method = ops[1];
  // The replaced operands
  let op = replaceOperands(ops[2], operands);
  console.log(
    `return function(cart, operands, sc, f) { const os = (${op})*sc; operands['_${it}'] = os; ${prop}${method}os; }`
  );
  /* The final function. As before, this creates and executes
   * a function, in this case it updates both the variable on
   * the vector **and* adds the component back into operands
   * in case you want to use it in further calculations.
   */
  Function(
    `return function(cart, operands, sc, f) { const os = (${op})*sc; operands['_${it}'] = os; ${prop}${method}os; }`
  )()(cart, operands, sc, f);
}

Phew, that's a lot, if you would like to see this in an isolated format where you can play around with the variables to see how it works see https://openprocessing.org/sketch/1672345

See the final token here https://www.fxhash.xyz/generative/19682

project name project name project name

project name project name project name

project name project name project name

stay ahead with our newsletter

receive news on exclusive drops, releases, product updates, and more

feedback