orbit-1
2012. 06. 10.

orbit series can be said to be "circular"-variation of the previous tiles projects. The idea is to place circles, instead of rectangles, densely onto the circular grid without each overlapping each other.

Four sketches shown here are mostly identical, only differ in their presentation. Click on the screen to regenerate the pattern.

» launch orbit-1-wireframe

» launch orbit-1-sketchy

» launch orbit-1-solid
» launch orbit-1-space
rint    = (a, b) -> parseInt random(a, b)
fuzzy   = (m, p) -> m * p * random(-0.01, 0.01)
quintic = (t) -> 6 * pow(t, 5) - 15 * pow(t, 4) + 10 * pow(t, 3)
Array::sample = -> this[rint(0, this.length)]

class Planet
  constructor: (@rad, @theta, @r, @R, @c, @o) ->
    @h = hue @c
    @s = saturation @c
    @b = brightness @c
    @diameter = @r * 2
    @x = @R * cos(@rad)
    @y = @R * sin(@rad)
    @attrs = {}
    mind = parseInt(0)
    maxd = parseInt(@diameter)
    for s in [maxd..mind] by -1
      @attrs[s] = {
        rx: @diameter * random(-0.03, 0.03)
        ry: @diameter * random(-0.03, 0.03)
        sx: random(1, s * 3)
      }
    null


  draw: ->
    pushMatrix()
    translate @x, @y
    rotate @rad
    mind = parseInt(0)
    maxd = parseInt(@diameter)
    for s in [maxd..mind] by -1
      r = (s - mind) / (maxd - mind)
      r2 = if @R == 0 then 1 else @R / max_R
      fill @h, pow(r, 3), 1, r2 * (1 - r) * @o / @diameter

      pushMatrix()
      translate @attrs[s].rx, @attrs[s].ry
      ellipse(
        0, 0,
        @attrs[s].sx,
        pow(pow(@r / max_R, 0.15), 4) * s * s / @attrs[s].sx
      )
      popMatrix()
    popMatrix()

p5      = processing
min_r   = undefined
step    = undefined
min_R   = undefined
max_R   = undefined
colors  = undefined
suncol  = undefined
density = undefined
rings   = {}
opacity = 4
bg      = 0

setup = ->
  size $(window).width(), $(window).height()
  max_R = sqrt(pow(p5.width / 2, 2) + pow(p5.height / 2, 2))
  colorMode HSB, 1.0
  ellipseMode CENTER
  noLoop()
  noStroke()
  mousePressed()

draw = ->
  background bg

  # for i in [0...1000]
  #   [rx, ry] = [random(p5.width), random(p5.height)]
  #   fill 1, random(0.05, 0.1)
  #   ellipse rx, ry, 3, 3
  #   ellipse rx, ry, 2, 2
  #   ellipse rx, ry, 1, 1

  pushMatrix()
  translate p5.width / 2, p5.height / 2
  fill hue(suncol), saturation(suncol), brightness(suncol), 0.01
  for R in [min_R..min_R * 2] by 4
    ellipse random(-step, step) / 5, random(-step, step) / 5,
            R * random(0.7, 1.5), R * random(0.9, 1.1)
  _(rings).each (ps, R) ->
    _(ps).each (p) -> p.draw()
  popMatrix()

mousePressed = ->
  size $(window).width(), $(window).height()
  rings = {}

  colors = for i in [0...rint(5, 10)]
      color(random(1.0), random(0.4, 1.0), random(0.5, 1.0))
  suncol = colors.sample()

  density = random(0.1, 0.5)
  min_r = rint(10, 20)
  step  = rint(min_r * 3, 120)
  min_R = rint(step, max(step * 2, min(p5.width / 2, p5.height / 2)))
  center_r = min_R

  rings[0] = [new Planet(0, 0, center_r, 0, colors.sample(), opacity * 3)]

  for R in [min_R..max_R] by step
    rings[R] = []

  for R in [min_R..max_R] by step
    srad = random TWO_PI
    pring = rings[R - step]
    ring = rings[R]

    # Calculate thetas according to mininum and maximum radius
    min_theta = 2 * asin(min_r / (2 * R))
    max_theta = 2 * asin(step / (2 * R))
    max_margin = max( max_theta / density - max_theta, 0 )

    idx = 0
    while true
      prev  = _(ring).last() or { rad: srad, theta: 0, r: 0 }
      first = _(ring).first() or { rad: srad, theta: 0, r: 0 }

      # No more space
      avail_theta = (srad + TWO_PI - (prev.rad + prev.theta)) / 2
      break if avail_theta < min_theta

      # Determine theta and rad
      theta = random(min_theta, min(avail_theta, max_theta))
      rad = prev.rad + prev.theta + theta +
        random(min(max_margin, avail_theta - theta))

      if pring
        x = R * cos(rad)
        y = R * sin(rad)
        safed = 2.5 * step
        closest = _(_(pring).filter((p) ->
          abs(p.x - x) <= safed and abs(p.y - y) <= safed
        )).sortBy((p) -> dist(p.x, p.y, x, y) - p.r)[0]
        max_r =
          if closest?
            dist(closest.x, closest.y, x, y) - closest.r
          else
            step
      else
        closest = null
        max_r = step

      # (r + r')^2 = R^2 + R^2 - 2R^2cos(t)
      max_r =
        min([
          step,
          max_r,
          sqrt(2 * pow(R, 2) * (1 - cos(prev.theta + theta))) - prev.r,
          if rad - srad > TWO_PI * 3/4
            sqrt(2 * pow(R, 2) * (1 - cos(first.theta + theta))) - first.r
          else
            step
        ])

      d = max_r - min_r
      r = min_r + random() * d

      p = new Planet(rad, theta, r, R, colors.sample(), opacity)
      ring.push p
    rings[R] = for p in ring when p.r >= min_r
      p
  draw()
» capture | close