straight skeleton finding2

In this example below you will see how to do a straight skeleton finding2 with some HTML / CSS and Javascript

Thumbnail
This awesome code was written by gunderson, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright gunderson ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>straight skeleton finding2</title>
  
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">

  
      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  
<canvas id="canvas" width="400" height="400"></canvas>
<div class="transport-bar">
  <button id="playpause">Play</button>
  <button id="stop">Stop</button>
</div>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/gunderson/straight-skeleton-finding2-KXjvmb */
body {
  overflow: hidden;
}

#canvas {
  background: #000;
}

.transport-bar {
  position: absolute;
  left: 0;
  bottom: 0;
  background: #000;
  padding: 0.5em;
  box-sizing: border-box;
}


/*Downloaded from https://www.codeseek.co/gunderson/straight-skeleton-finding2-KXjvmb */
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

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

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

// variables
var width = 400;
var height = 400;

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

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

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

  // EventListeners
  window.addEventListener("resize", onResize);
  onResize();
  // UI
  playpauseButton.addEventListener("click", function (e) {
    return player.play();
  });
  stopButton.addEventListener("click", function (e) {
    return 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);
  //   }
  var pointTolerance = 2;
  // make loopabe verts array
  var _verts = verts.slice();
  _verts.push(_verts[0]);
  midpoints.forEach(function (mp) {
    // get dist to each mp
    mp.forces = midpoints.map(function (partner) {
      if (mp === partner) return { d: 0, a: 0 };

      var dx = partner.x - mp.x;
      var dy = partner.y - mp.y;
      var 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
    var partner = verts[mp.lineIndex];
    if (partner.x === mp.x && partner.y === mp.y) return;

    var dx = partner.x - mp.x;
    var dy = partner.y - mp.y;
    var 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(function (mp) {
    mp.forces.forEach(function (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(function (v) {
    ctx.beginPath();
    ctx.arc(v.x, v.y, 3, 0, TAU);
    ctx.fill();
  });

  ctx.fillStyle = "#f0f";
  midpoints.forEach(function (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
  var dx = r.x - p.x;
  var dy = r.y - p.y;
  var m = dy / dx;
  var b = -m * p.x + p.y;
  // convert to standard coefficients
  // Ax + By + C = 0;
  var A = -m;
  var B = 1;
  var C = -b;

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

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

  var cp = {
    x: width * 0.5,
    y: height * 0.5
  };
  var verts = [];
  var wedgeAngle = TAU / numVerts;
  for (var 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) {
  var midpoints = [];
  verts = verts.slice();
  verts.push(verts[0]);
  verts.push(verts[1]);

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

    var 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) {
  var bary = barycentric(p.x, p.y, a.x, a.y, b.x, b.y, c.x, c.y);
  if (isInTriangle(bary.u, bary.v)) {
    var u = bary.u;
    var v = bary.v;
    var w = 1 - u - v;
    var 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: u, v: 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

var RealtimePlayer = function () {
  function RealtimePlayer(options) {
    _classCallCheck(this, RealtimePlayer);

    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;
  }

  _createClass(RealtimePlayer, [{
    key: "play",
    value: function 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();
    }
  }, {
    key: "stop",
    value: function stop() {
      this.isPlaying = false;
      clearTimeout(this.updateTimer);
      cancelAnimationFrame(this.raf);
    }
  }, {
    key: "update",
    value: function 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;
      var currentTick = Math.floor(this._elapsedTime / this._millisPerTick);
      var 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();
      var nextTickTime = (this.tick + 1) * this._millisPerTick;
      var nextTickDelta = nextTickTime - this._elapsedTime;
      this.updateTimer = setTimeout(this.update.bind(this), nextTickDelta);
    }
  }, {
    key: "_updateElapsedTime",
    value: function _updateElapsedTime() {
      var 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;
      }
    }
  }, {
    key: "draw",
    value: function draw() {
      if (!this.isPlaying) return;
      var now = Date.now();
      this.currentFrameRate = now - this._lastFrameTime;
      this._lastFrameTime = now;
      this._draw && this._draw();
      this.raf = requestAnimationFrame(this.draw.bind(this));
    }
  }]);

  return RealtimePlayer;
}();

setup();

Comments