Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                canvas#canvas(width="400", height="400")
.transport-bar
  button#playpause Play
  button#stop Stop
              
            
!

CSS

              
                body
  overflow: hidden
#canvas
  background: #000
.transport-bar
  position: absolute
  left: 0
  bottom: 0
  background: #000
  padding: 0.5em
  box-sizing: border-box
              
            
!

JS

              
                // Constants
const PI = Math.PI;
const TAU = 2 * PI;
const HALF_PI = PI / 2;
const QUARTER_PI = PI / 4;
const THIRD_PI = PI / 3;

// Elements
let playpauseButton = document.querySelector("#playpause");
let stopButton = document.querySelector("#stop");
let canvas = document.querySelector("#canvas");
let ctx = canvas.getContext("2d");

// variables
let width = 400;
let height = 400;

// parameters
let parameters = {
  numVerts: 7,
  radiusY: 0.25,
  radiusX: 0.35,
  innerRadius: 0.21,
  cornerThreshold: 0.06,
  edgeThreshold: 0.1,
  invert: false
};

// instances
let player;
let imageBuffer = ctx.getImageData(0, 0, width, height);
let verts = [];
let midpoints = [];

function setup() {
  player = new RealtimePlayer({
    tps: 10,
    update: update,
    draw: draw
  });

  // EventListeners
  window.addEventListener("resize", onResize);
  onResize();
  // UI
  playpauseButton.addEventListener("click", e => player.play());
  stopButton.addEventListener("click", e => player.stop());

  verts = getVerts();
  midpoints = getMidpoints(verts);
  update();
  draw();
}

function update(tick) {
  //   let px = imageBuffer.data;
  //   for (let i = 0; i < px.length; i+=4){
  //     let level = 0;
  //     let pos = getXY(i/4);
  //     for (let j = 0; j < numVerts-1; j++){
  //       drawPointInWedge(pos, verts[j], cp, verts[j+1], px, i);
  //     }
  //     drawPointInWedge(pos, verts[numVerts - 1], cp, verts[0], px, i);
  //   }
  let pointTolerance = 2;
  // make loopabe verts array
  let _verts = verts.slice();
  _verts.push(_verts[0]);
  midpoints.forEach(mp => {
    // get dist to each mp
    mp.forces = midpoints.map(partner => {
      if (mp === partner) return { d: 0, a: 0 };

      let dx = partner.x - mp.x;
      let dy = partner.y - mp.y;
      let dist = Math.sqrt(dx * dx + dy * dy);
      dist = dist < partner.force ? partner.force : dist;
      return {
        d: partner.force / dist,
        a: Math.atan2(dy, dx)
      };
    });
    // get dist to home vert
    let partner = verts[mp.lineIndex];
    if (partner.x === mp.x && partner.y === mp.y) return;

    let dx = partner.x - mp.x;
    let dy = partner.y - mp.y;
      let dist = Math.sqrt(dx * dx + dy * dy);
      dist = dist < partner.force ? partner.force : dist;
    mp.forces.push({
      d: partner.force / dist,
      a: Math.atan2(dy, dx)
    });
        console.log(mp.forces);

  });

  midpoints.forEach(mp => {
    mp.forces.forEach(f => {
      mp.x += f.d * Math.cos(f.a);
      mp.y += f.d * Math.sin(f.a);
    });
  });
  console.log(tick);
}

function draw() {
  ctx.clearRect(0, 0, width, height);

  ctx.fillStyle = "#0ff";
  verts.forEach(v => {
    ctx.beginPath();
    ctx.arc(v.x, v.y, 3, 0, TAU);
    ctx.fill();
  });

  ctx.fillStyle = "#f0f";
  midpoints.forEach(v => {
    ctx.beginPath();
    ctx.arc(v.x, v.y, 3, 0, TAU);
    ctx.fill();
  });
}

// finds the perpendicular distance between point q and line pr
function pointLineRelationship(p, q, r) {
  // find line slope-intercept coefficients
  let dx = r.x - p.x;
  let dy = r.y - p.y;
  let m = dy / dx;
  let b = -m * p.x + p.y;
  // convert to standard coefficients
  // Ax + By + C = 0;
  let A = -m;
  let B = 1;
  let C = -b;

  // find perpendicular distance w/ testpoint q
  // formula from https://www.intmath.com/plane-analytic-geometry/perpendicular-distance-point-line.php
  let dist = Math.abs(A * q.x + B * q.y + C) / Math.sqrt(A * A + B * B);
  return { dist: dist, angle: m };
}

function getVerts() {
  let numVerts = parameters.numVerts;
  let radiusX = parameters.radiusX * width;
  let radiusY = parameters.radiusY * height;

  let cp = {
    x: width * 0.5,
    y: height * 0.5
  };
  let verts = [];
  let wedgeAngle = TAU / numVerts;
  for (let v = 0; v < numVerts; v++) {
    verts.push({
      x: radiusX * Math.cos((0.37 + v) * wedgeAngle) + cp.x,
      y: radiusY * Math.sin((0.37 + v) * wedgeAngle) + cp.y,
      force: 300
    });
  }
  return verts;
}

function getMidpoints(verts) {
  let midpoints = [];
  verts = verts.slice();
  verts.push(verts[0]);
  verts.push(verts[1]);

  for (let v = 1; v < verts.length - 1; v++) {
    let dx = verts[v + 1].x - verts[v - 1].x;
    let dy = verts[v + 1].y - verts[v - 1].y;

    let mp = {
      lineIndex: v % verts.length - 1,
      x: verts[v].x,
      y: verts[v].y,
      force: 300
    };

    midpoints.push(mp);
  }
  return midpoints;
}

function drawPointInWedge(p, a, b, c, px, i, d, e, f) {
  let bary = barycentric(p.x, p.y, a.x, a.y, b.x, b.y, c.x, c.y);
  if (isInTriangle(bary.u, bary.v)) {
    let u = bary.u;
    let v = bary.v;
    let w = 1 - u - v;
    let isMasked = false;
    if (
      u < parameters.cornerThreshold ||
      w < parameters.cornerThreshold ||
      (u - w < parameters.edgeThreshold && w - u < parameters.edgeThreshold)
    ) {
      // masked
      isMasked = true;
    }

    if (isMasked && !parameters.invert) {
      px[i + 0] = 0;
      px[i + 1] = 0;
      px[i + 2] = 0;
      px[i + 3] = 0;
    } else {
      px[i + 0] = 255 * u;
      px[i + 1] = 255 * v;
      px[i + 2] = 255 * w;
      px[i + 3] = 255;
    }
  }

  return px;
}

function barycentric(px, py, ax, ay, bx, by, cx, cy) {
  //credit: http://www.blackpawn.com/texts/pointinpoly/default.html

  var v0 = [cx - ax, cy - ay];
  var v1 = [bx - ax, by - ay];
  var v2 = [px - ax, py - ay];

  var dot00 = v0[0] * v0[0] + v0[1] * v0[1];
  var dot01 = v0[0] * v1[0] + v0[1] * v1[1];
  var dot02 = v0[0] * v2[0] + v0[1] * v2[1];
  var dot11 = v1[0] * v1[0] + v1[1] * v1[1];
  var dot12 = v1[0] * v2[0] + v1[1] * v2[1];

  var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);

  var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
  var v = (dot00 * dot12 - dot01 * dot02) * invDenom;

  return { u, v };
}

function isInTriangle(u, v) {
  return u >= 0 && v >= 0 && u + v <= 1;
}

function onResize() {
  width = window.innerWidth;
  height = window.innerHeight;
  canvas.width = width;
  canvas.height = height;
  imageBuffer = ctx.getImageData(0, 0, width, height);
  update(player.tick);
  draw();
}

// ImageData Helpers

function getXY(index) {
  return new THREE.Vector2(
    index % canvas.width,
    Math.floor(index / canvas.width)
  );
}

function getPixelIndex(x, y) {
  return y * canvas.width + x;
}

function getUV(x, y) {
  return {
    x: x / canvas.width,
    y: y / canvas.height
  };
}

// Entities

// player

class RealtimePlayer {
  constructor(options) {
    options = options || {};
    this.isPlaying = false;
    this.tps = options.tps || 60;
    this._update = options.update;
    this._draw = options.draw;
    this.now = options.now || Date.now; // i.e. ()=>mediaElement.currentTime; to sync to a media element, audio or video node

    this.tick = 0;
    this._millisPerTick = 1000 / this.tps;
    this._lastTickTime = null;
    this._lastFrameTime = 0;
    this.currentFrameRate = 0;
    this._deltaTime = 0;
    this._elapsedTime = 0;
    this.duration = 0; // in seconds
    this.timelinePosition = 0;
  }

  play() {
    if (this.isPlaying) return;
    this.isPlaying = true;

    // reset time tracker on new play to zero deltas
    this._deltaTime = 0;
    this._lastTickTime = Date.now();

    // draw current state
    this.update(this.tick);
    this.draw();
  }

  stop() {
    this.isPlaying = false;
    clearTimeout(this.updateTimer);
    cancelAnimationFrame(this.raf);
  }

  update() {
    if (!this.isPlaying) return;

    // find the current time and tick state
    this._updateElapsedTime();

    // execute all the ticks that happened between updates, in case there was a delay w/ drawing

    // if tps < minTimeout or if updateTime > _millisPerFrame this should throttle to minTimeout.
    // else it will create an infinite update loop as updates will queue faster than they can process;
    let currentTick = Math.floor(this._elapsedTime / this._millisPerTick);
    let ticksSinceLastUpdate = currentTick - this.tick;

    // console.log(this._deltaTime,  this._millisPerTick);
    while (ticksSinceLastUpdate-- > 0) {
      this._update && this._update(++this.tick);
    }

    // schedule the next update
    // account for update Timing when scheduling the next update
    this._updateElapsedTime();
    let nextTickTime = (this.tick + 1) * this._millisPerTick;
    let nextTickDelta = nextTickTime - this._elapsedTime;
    this.updateTimer = setTimeout(this.update.bind(this), nextTickDelta);
  }

  _updateElapsedTime() {
    let now = Date.now();
    this._deltaTime = now - this._lastTickTime;
    this._lastTickTime = now;
    this._elapsedTime += this._deltaTime;

    // calculate timeline position if we're using that feature
    if (this.duration !== 0) {
      this.timelinePosition = this._elapsedTime / this.duration;
    }
  }

  draw() {
    if (!this.isPlaying) return;
    let now = Date.now();
    this.currentFrameRate = now - this._lastFrameTime;
    this._lastFrameTime = now;
    this._draw && this._draw();
    this.raf = requestAnimationFrame(this.draw.bind(this));
  }
}

setup();

              
            
!
999px

Console