Skip to content

Reflection fractal

Click to create lines. The canvas below tries to draw a fractal formed by shooting rays and keeping track of the side they exit from.
... it is not a very good fractal, however, so this gets the WIP tag for now 😅

Two canvases: one lets you draw lines, while the other scans a colorful burst

let c2

function setup() {
  createCanvas(400, 400);
  c2 = new p5((x) => {
    let pt = 0
    let pti = random() * 400 * 400 +random() * 400 + random() + random() / 400 + random() / 400 / 400
    x.setup = () => {
      x.createCanvas(400, 400)
      x.background(0)
      x.strokeWeight(1.5)
      x.frameRate(180)
    }
    x.draw = () => {
      for (let i = 0; i < 80; i ++) {
        pt += pti//(Math.SQRT2 * 24 + 0.001 + 0.0003 + 0.000005) * 20
        p = [pt % width, (pt / width) % height]
        //let p = [x.random(0, 400), x.random(0, 400)]
        let {points, rayD, finished} = iterateRay(p, [200-p[0], 200-p[1]], 80)
        // let {points, rayD, finished} = iterateRay(p, [1, 0], 80)
        let wh = points.length / 80 * 200
        if (abs(rayD[0]) > abs(rayD[1])) {
          if (rayD[0] > 0) {
            x.stroke(255, wh, wh)
          } else {
            x.stroke(wh, 255, wh)
          }
        } else {
          if (rayD[1] > 0) {
            x.stroke(wh, wh, 255)
          } else {
            x.stroke(255, 255, wh)
          }
        }
        x.point(...p)
      }
    }
  })
}

let lines = []

function draw() {
  background(220);
  
  for (let [p1, p2] of lines) {
    line(...p1, ...p2)
  }
  
  let p = [mouseX, mouseY]
  let {points, rayO, rayD, finished} = iterateRay(p, [200-p[0], 200-p[1]], 80)
  
  stroke(255, 0, 0)
  for (let i = 1; i < 80 && i < points.length; i ++) {
    stroke(255, 0, 0, 255 - 255 / 80 * i)
    strokeWeight(1 + 3 * i / 80)
    line(...points[i - 1], ...points[i])
  }
  if (finished) {
    line(...rayO, rayD[0] * 600 + rayO[0], rayD[1] * 600 + rayO[1])
  }
  
  stroke(0)
  strokeWeight(1)
  if (newLine.length == 0) {
    point(mouseX, mouseY)
  } else if (newLine.length == 1) {
    line(...newLine[0], mouseX, mouseY)
  }
}

let newLine = []
function mousePressed() {
  newLine.push([mouseX, mouseY])
  if (newLine.length == 2) {
    c2.background(0)
    lines.push(newLine)
    newLine = []
  }
}
function iterateRay(rayO, rayD, maxI) {
  let points = []
  {
    let l = mag(...rayD)
    rayD = [rayD[0] / l, rayD[1] / l]
  }
  for (let i = 0; i < maxI; i ++) {
    let best = null
    let bestD = null
    for (let [p1, p2] of lines) {
      let res = line_intersect(...rayO, rayD[0] + rayO[0], rayD[1] + rayO[1], ...p1, ...p2)
      if (res && res.ub > 0 && res.ub < 1 && res.ua > 1e-8 && (!best || best.ua > res.ua)) {
        best = res
        bestD = [p1[1] - p2[1], p2[0] - p1[0]]
      }
    }
    points.push(rayO)
    if (!best) {
      return {points, rayO, rayD, finished: true}
      break
    }
    rayO = [best.x, best.y]
    let l = mag(...bestD)
    let normalized = [bestD[0] / l, bestD[1] / l]
    let dt = rayD[0] * normalized[0] + rayD[1] * normalized[1]
    rayD = [rayD[0] - 2 * dt * normalized[0], rayD[1] - 2 * dt * normalized[1]]
  }
  return {points, rayO, rayD, finished: false}
}

// https://stackoverflow.com/a/38977789
function line_intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
    let denom = (y4 - y3)*(x2 - x1) - (x4 - x3)*(y2 - y1);
    if (denom == 0) {
        return null;
    }
    let ua = ((x4 - x3)*(y1 - y3) - (y4 - y3)*(x1 - x3))/denom;
    let ub = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3))/denom;
    return {
        x: x1 + ua * (x2 - x1),
        y: y1 + ua * (y2 - y1),
        ua,
        ub
    };
}

(Originally seen at https://editor.p5js.org/bojidar-bg/sketches/RPDC8yBdq)

Experiments tagged p5 (72/85)

Experiments tagged interactive (23/26)

Experiments tagged wip (4/5)

Experiments on this site (72/85)