block by gabrielflorit f7991c1c43e6e296368bbfa7b580bc67

Racing track parameters

Full Screen

Made with blockup

script.js

import drawTrack from './drawTrack.js'
import drawCars from './drawCars.js'
import generateMarkers from './generateMarkers.js'

const container = d3.select('.circuit')

// Draw the track.
const { g, getCoords } = drawTrack({ container, track: circuitTracks[0] })

const inputs = document.querySelectorAll('input')

const getSettings = () => {
  const settings = {}
  inputs.forEach(input => {
    const { name, value } = input
    settings[name] = +value
    document.querySelector(`.control.${name} .number`).textContent = value
  })

  return settings
}

const draw = () => {
  const settings = getSettings()
  const markers = generateMarkers({ getCoords, settings })
  console.log(markers.length)
  drawCars({ g, cars: markers, getCoords })
}

draw()

inputs.forEach(input => {
  input.addEventListener('input', () => {
    draw()
  })
})

index.html

<!DOCTYPE html>
<title>blockup</title>
<link href='dist.css' rel='stylesheet' />
<body>
  <div class='circuit'></div>
  <div class='controls'>

    <div class='legend'>
      <p><span class='accelerating'>&#11044;</span>Accelerating</p>
      <p><span class='braking'>&#11044;</span>Braking</p>
      <p><span class='maxSpeed'>&#11044;</span>Maximum speed</p>
    </div>

    <div class='control maxSpeed'>
      <span class='title'>Maximum speed</span>
      <input name='maxSpeed' type=range min=0.01 max=0.25 value=0.02 step=0.005 />
      <span class='number'>a</span>
    </div>

    <div class='control acceleration'>
      <span class='title'>Acceleration</span>
      <input name='acceleration' type=range min=1.01 max=2 value=1.1 step=0.005 />
      <span class='number'>a</span>
    </div>

    <div class='control brake'>
      <span class='title'>Brake coefficient</span>
      <input name='brake' type=range min=0.2 max=0.99 value=0.9 step=0.01 />
      <span class='number'>a</span>
    </div>

    <!-- <div class='control angle'> -->
    <!--   <span class='title'>Angle cutoff</span> -->
    <!--   <input name='angle' type=range min=30 max=90 value=60 /> -->
    <!--   <span class='number'>a</span> -->
    <!-- </div> -->

  </div>
	<script src='d3.v4.min.js'></script>
	<script src='vectorious.min.js'></script>
	<script src='circuitTracks.js'></script>
	<script src='dist.js'></script>
</body>

colors.js

const colors = [
  '#000000',
  '#9D9D9D',
  '#FFFFFF',
  '#BE2633',
  '#E06F8B',
  '#493C2B',
  '#A46422',
  '#EB8931',
  '#F7E26B',
  '#2F484E',
  '#44891A',
  '#A3CE27',
  '#1B2632',
  '#005784',
  '#31A2F2',
  '#B2DCEF'
]

export default colors

dist.css

*{box-sizing:border-box}body{background:rgba(0,0,0,.1);margin:0 auto;padding:0;width:960px}.circuit{width:52%}svg{display:block;margin:0 auto}svg path{fill:#000;fill-opacity:.075;stroke:#000;stroke-width:.125px}svg circle.accelerating{fill:green}svg circle.braking{fill:red}svg circle.maxSpeed{fill:purple}.controls{font-family:sans-serif;width:50%;position:absolute;right:3em;top:2em}.controls .control{margin:1em 0}.controls .control .title{width:28%;display:inline-block;text-align:right;font-weight:700}.controls .control input{width:60%}.controls .legend{margin:0 0 2em 0;font-size:.8em;width:100%;overflow:hidden}.controls .legend p{float:left;padding-right:3em}.controls .legend p span{display:inline-block;padding-right:.5em}.controls .legend p span.accelerating{color:green}.controls .legend p span.braking{color:red}.controls .legend p span.maxSpeed{color:purple}

dist.js

!function(n){function e(a){if(t[a])return t[a].exports;var r=t[a]={i:a,l:!1,exports:{}};return n[a].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var t={};e.m=n,e.c=t,e.i=function(n){return n},e.d=function(n,t,a){e.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:a})},e.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return e.d(t,"a",t),t},e.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},e.p="",e(e.s=4)}([function(module,exports,__webpack_require__){"use strict";eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\nvar drawCars = function drawCars(_ref) {\n  var g = _ref.g,\n      cars = _ref.cars,\n      getCoords = _ref.getCoords;\n\n  // join\n  var circles = g.selectAll('circle.car').data(cars);\n\n  // update\n  circles.attr('class', function (d) {\n    return 'car ' + d.status;\n  }).attr('transform', function (d) {\n    return 'translate(' + getCoords(d.position) + ')';\n  });\n\n  // enter\n  circles.enter().append('circle').attr('class', function (d) {\n    return 'car ' + d.status;\n  }).attr('cx', 0).attr('cy', 0).attr('r', function (d) {\n    return d.status === 'maxSpeed' ? 3 : 4;\n  }).attr('transform', function (d) {\n    return 'translate(' + getCoords(d.position) + ')';\n  });\n\n  circles.exit().remove();\n};\n\nexports.default = drawCars;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9kcmF3Q2Fycy5qcz9hMDAwIl0sInNvdXJjZXNDb250ZW50IjpbImNvbnN0IGRyYXdDYXJzID0gKHsgZywgY2FycywgZ2V0Q29vcmRzIH0pID0+IHtcbiAgLy8gam9pblxuICBjb25zdCBjaXJjbGVzID0gZy5zZWxlY3RBbGwoJ2NpcmNsZS5jYXInKS5kYXRhKGNhcnMpXG5cbiAgLy8gdXBkYXRlXG4gIGNpcmNsZXNcbiAgICAuYXR0cignY2xhc3MnLCBkID0+IGBjYXIgJHtkLnN0YXR1c31gKVxuICAgIC5hdHRyKCd0cmFuc2Zvcm0nLCBkID0+IGB0cmFuc2xhdGUoJHtnZXRDb29yZHMoZC5wb3NpdGlvbil9KWApXG5cbiAgLy8gZW50ZXJcbiAgY2lyY2xlc1xuICAgIC5lbnRlcigpXG4gICAgLmFwcGVuZCgnY2lyY2xlJylcbiAgICAuYXR0cignY2xhc3MnLCBkID0+IGBjYXIgJHtkLnN0YXR1c31gKVxuICAgIC5hdHRyKCdjeCcsIDApXG4gICAgLmF0dHIoJ2N5JywgMClcbiAgICAuYXR0cigncicsIGQgPT4gZC5zdGF0dXMgPT09ICdtYXhTcGVlZCcgPyAzIDogNClcbiAgICAuYXR0cigndHJhbnNmb3JtJywgZCA9PiBgdHJhbnNsYXRlKCR7Z2V0Q29vcmRzKGQucG9zaXRpb24pfSlgKVxuXG4gIGNpcmNsZXMuZXhpdCgpLnJlbW92ZSgpXG59XG5cbmV4cG9ydCBkZWZhdWx0IGRyYXdDYXJzXG5cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gZHJhd0NhcnMuanMiXSwibWFwcGluZ3MiOiI7Ozs7O0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBQ0E7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUdBO0FBQUE7QUFHQTtBQUFBO0FBQ0E7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///0\n")},function(module,exports,__webpack_require__){"use strict";eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\nvar drawTrack = function drawTrack(_ref) {\n  var container = _ref.container,\n      track = _ref.track;\n\n  var margin = 10;\n  var dimension = container.node().offsetWidth - margin * 2;\n\n  var xExtent = d3.extent(track, function (d) {\n    return d.x;\n  });\n  var x = d3.scaleLinear().domain(xExtent);\n\n  var yExtent = d3.extent(track, function (d) {\n    return d.y;\n  });\n  var y = d3.scaleLinear().domain(yExtent);\n\n  var aspect = (xExtent[1] - xExtent[0]) / (yExtent[1] - yExtent[0]);\n\n  var width = Math.min(dimension * aspect, dimension);\n  var height = Math.min(dimension / aspect, dimension);\n\n  x.range([0, width]);\n  y.range([0, height]);\n\n  var svg = container.append('svg').attr('width', width + 2 * margin).attr('height', height + 2 * margin);\n\n  var g = svg.append('g').attr('transform', 'translate(' + margin + ', ' + margin + ')');\n\n  var line = d3.line().curve(d3.curveBasis).x(function (d) {\n    return x(d.x);\n  }).y(function (d) {\n    return y(d.y);\n  });\n\n  var path = g.append('path').datum(track).attr('d', line).node();\n\n  var totalLength = path.getTotalLength();\n\n  var getCoords = function getCoords(pct) {\n    var p = path.getPointAtLength(totalLength * (pct % 1));\n    return [p.x, p.y];\n  };\n\n  return { g: g, getCoords: getCoords };\n};\n\nexports.default = drawTrack;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9kcmF3VHJhY2suanM/MTY1OSJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBkcmF3VHJhY2sgPSAoeyBjb250YWluZXIsIHRyYWNrIH0pID0+IHtcbiAgY29uc3QgbWFyZ2luID0gMTBcbiAgY29uc3QgZGltZW5zaW9uID0gY29udGFpbmVyLm5vZGUoKS5vZmZzZXRXaWR0aCAtIG1hcmdpbiAqIDJcblxuICBjb25zdCB4RXh0ZW50ID0gZDMuZXh0ZW50KHRyYWNrLCBkID0+IGQueClcbiAgY29uc3QgeCA9IGQzLnNjYWxlTGluZWFyKCkuZG9tYWluKHhFeHRlbnQpXG5cbiAgY29uc3QgeUV4dGVudCA9IGQzLmV4dGVudCh0cmFjaywgZCA9PiBkLnkpXG4gIGNvbnN0IHkgPSBkMy5zY2FsZUxpbmVhcigpLmRvbWFpbih5RXh0ZW50KVxuXG4gIGNvbnN0IGFzcGVjdCA9ICh4RXh0ZW50WzFdIC0geEV4dGVudFswXSkgLyAoeUV4dGVudFsxXSAtIHlFeHRlbnRbMF0pXG5cbiAgY29uc3Qgd2lkdGggPSBNYXRoLm1pbihkaW1lbnNpb24gKiBhc3BlY3QsIGRpbWVuc2lvbilcbiAgY29uc3QgaGVpZ2h0ID0gTWF0aC5taW4oZGltZW5zaW9uIC8gYXNwZWN0LCBkaW1lbnNpb24pXG5cbiAgeC5yYW5nZShbMCwgd2lkdGhdKVxuICB5LnJhbmdlKFswLCBoZWlnaHRdKVxuXG4gIGNvbnN0IHN2ZyA9IGNvbnRhaW5lclxuICAgIC5hcHBlbmQoJ3N2ZycpXG4gICAgLmF0dHIoJ3dpZHRoJywgd2lkdGggKyAyICogbWFyZ2luKVxuICAgIC5hdHRyKCdoZWlnaHQnLCBoZWlnaHQgKyAyICogbWFyZ2luKVxuXG4gIGNvbnN0IGcgPSBzdmcuYXBwZW5kKCdnJykuYXR0cigndHJhbnNmb3JtJywgYHRyYW5zbGF0ZSgke21hcmdpbn0sICR7bWFyZ2lufSlgKVxuXG4gIGNvbnN0IGxpbmUgPSBkM1xuICAgIC5saW5lKClcbiAgICAuY3VydmUoZDMuY3VydmVCYXNpcylcbiAgICAueChkID0+IHgoZC54KSlcbiAgICAueShkID0+IHkoZC55KSlcblxuICBjb25zdCBwYXRoID0gZ1xuICAgIC5hcHBlbmQoJ3BhdGgnKVxuICAgIC5kYXR1bSh0cmFjaylcbiAgICAuYXR0cignZCcsIGxpbmUpXG4gICAgLm5vZGUoKVxuXG4gIGNvbnN0IHRvdGFsTGVuZ3RoID0gcGF0aC5nZXRUb3RhbExlbmd0aCgpXG5cbiAgY29uc3QgZ2V0Q29vcmRzID0gcGN0ID0+IHtcbiAgICBjb25zdCBwID0gcGF0aC5nZXRQb2ludEF0TGVuZ3RoKHRvdGFsTGVuZ3RoICogKHBjdCAlIDEpKVxuICAgIHJldHVybiBbcC54LCBwLnldXG4gIH1cblxuICByZXR1cm4geyBnLCBnZXRDb29yZHMgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBkcmF3VHJhY2tcblxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyBkcmF3VHJhY2suanMiXSwibWFwcGluZ3MiOiI7Ozs7O0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBSUE7QUFDQTtBQUNBO0FBR0E7QUFBQTtBQUNBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFLQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///1\n")},function(module,exports,__webpack_require__){"use strict";eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\n\nvar _moveCar = __webpack_require__(3);\n\nvar _moveCar2 = _interopRequireDefault(_moveCar);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar generateMarkers = function generateMarkers(_ref) {\n  var getCoords = _ref.getCoords,\n      settings = _ref.settings;\n\n  // Make a car that starts at 0.\n  // Move it, and save the outcome to an array.\n  // When it crosses the finish line, stop.\n\n  var markers = [{ position: 0, speed: 0.001, status: 'accelerating' }];\n\n  var stop = false;\n  var i = 0;\n\n  while (!stop && i < 1000) {\n    var oldCar = markers.slice(-1)[0];\n    var newCar = (0, _moveCar2.default)({ car: oldCar, getCoords: getCoords, settings: settings });\n\n    if (newCar.position < oldCar.position) {\n      stop = true;\n    }\n    markers.push(newCar);\n    i++;\n  }\n\n  // return markers.filter((d, i) => i % 8 === 0)\n  return markers;\n};\n\nexports.default = generateMarkers;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMi5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9nZW5lcmF0ZU1hcmtlcnMuanM/NTM2NyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgbW92ZUNhciBmcm9tICcuL21vdmVDYXIuanMnXG5cbmNvbnN0IGdlbmVyYXRlTWFya2VycyA9ICh7IGdldENvb3Jkcywgc2V0dGluZ3MgfSkgPT4ge1xuICAvLyBNYWtlIGEgY2FyIHRoYXQgc3RhcnRzIGF0IDAuXG4gIC8vIE1vdmUgaXQsIGFuZCBzYXZlIHRoZSBvdXRjb21lIHRvIGFuIGFycmF5LlxuICAvLyBXaGVuIGl0IGNyb3NzZXMgdGhlIGZpbmlzaCBsaW5lLCBzdG9wLlxuXG4gIGNvbnN0IG1hcmtlcnMgPSBbeyBwb3NpdGlvbjogMCwgc3BlZWQ6IDAuMDAxLCBzdGF0dXM6ICdhY2NlbGVyYXRpbmcnIH1dXG5cbiAgbGV0IHN0b3AgPSBmYWxzZVxuICBsZXQgaSA9IDBcblxuICB3aGlsZSAoIXN0b3AgJiYgaSA8IDEwMDApIHtcbiAgICBjb25zdCBvbGRDYXIgPSBtYXJrZXJzLnNsaWNlKC0xKVswXVxuICAgIGNvbnN0IG5ld0NhciA9IG1vdmVDYXIoeyBjYXI6IG9sZENhciwgZ2V0Q29vcmRzLCBzZXR0aW5ncyB9KVxuXG4gICAgaWYgKG5ld0Nhci5wb3NpdGlvbiA8IG9sZENhci5wb3NpdGlvbikge1xuICAgICAgc3RvcCA9IHRydWVcbiAgICB9XG4gICAgbWFya2Vycy5wdXNoKG5ld0NhcilcbiAgICBpKytcbiAgfVxuXG4gIC8vIHJldHVybiBtYXJrZXJzLmZpbHRlcigoZCwgaSkgPT4gaSAlIDggPT09IDApXG4gIHJldHVybiBtYXJrZXJzXG59XG5cbmV4cG9ydCBkZWZhdWx0IGdlbmVyYXRlTWFya2Vyc1xuXG5cblxuLy8gV0VCUEFDSyBGT09URVIgLy9cbi8vIGdlbmVyYXRlTWFya2Vycy5qcyJdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUE7QUFDQTs7Ozs7QUFDQTtBQUFBO0FBQUE7QUFDQTtBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///2\n")},function(module,exports,__webpack_require__){"use strict";eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _toRadians = __webpack_require__(5);\n\nvar _toRadians2 = _interopRequireDefault(_toRadians);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\n// import toDegrees from './toDegrees.js'\n\n// const brake = 0.5\n// const acceleration = 1.1\n// const maxSpeed = 0.5\n\n// Every tick,\nvar moveCar = function moveCar(_ref) {\n  var car = _ref.car,\n      getCoords = _ref.getCoords,\n      settings = _ref.settings;\n\n\n  // // const { brake, acceleration, maxSpeed } = settings\n  var acceleration = settings.acceleration,\n      maxSpeed = settings.maxSpeed,\n      brake = settings.brake;\n\n  // Use the current speed to find the next and previous locations.\n\n  var speed = car.speed,\n      position = car.position;\n\n  var next = getCoords(position + speed);\n  var current = getCoords(position);\n  var previous = getCoords(position + 1 - speed);\n\n  // Create the forward and backward vectors.\n  var forward = new Vector([next[0] - current[0], next[1] - current[1]]);\n  var backward = new Vector([previous[0] - current[0], previous[1] - current[1]]);\n\n  // console.log(JSON.stringify({ next, current, previous }, null, 2))\n\n  // Find the angle of the line.\n  var angle = Vector.angle(forward, backward);\n\n  var newSpeed = void 0,\n      newStatus = void 0;\n\n  // If it's more than 30 degrees, brake (i.e. cut speed by brake factor).\n  if (!isNaN(angle) && angle < (0, _toRadians2.default)(180 - 10)) {\n    newSpeed = speed * brake;\n    newStatus = 'braking';\n    // console.log({ status: 'braking', speed, newSpeed })\n  } else {\n    // Otherwise either maintain current speed (if we're at max speed),\n    if (speed >= maxSpeed) {\n      newSpeed = maxSpeed;\n      newStatus = 'maxSpeed';\n      // console.log({ status: 'same', speed, newSpeed })\n    } else {\n      // or accelerate (increase speed by acceleration factor).\n      newSpeed = speed * acceleration;\n      newStatus = 'accelerating';\n      // console.log({ status: 'accelerating', speed, newSpeed })\n    }\n  }\n\n  // Then use the new speed to find the new position.\n  var newPosition = (position + newSpeed) % 1;\n\n  // And move to new position.\n  return _extends({}, car, {\n    position: newPosition,\n    speed: newSpeed,\n    status: newStatus\n  });\n};\n\nexports.default = moveCar;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMy5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9tb3ZlQ2FyLmpzP2RlNzkiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHRvUmFkaWFucyBmcm9tICcuL3RvUmFkaWFucy5qcydcbi8vIGltcG9ydCB0b0RlZ3JlZXMgZnJvbSAnLi90b0RlZ3JlZXMuanMnXG5cbi8vIGNvbnN0IGJyYWtlID0gMC41XG4vLyBjb25zdCBhY2NlbGVyYXRpb24gPSAxLjFcbi8vIGNvbnN0IG1heFNwZWVkID0gMC41XG5cbi8vIEV2ZXJ5IHRpY2ssXG5jb25zdCBtb3ZlQ2FyID0gKHsgY2FyLCBnZXRDb29yZHMsIHNldHRpbmdzIH0pID0+IHtcblxuICAvLyAvLyBjb25zdCB7IGJyYWtlLCBhY2NlbGVyYXRpb24sIG1heFNwZWVkIH0gPSBzZXR0aW5nc1xuICBjb25zdCB7IGFjY2VsZXJhdGlvbiwgbWF4U3BlZWQsIGJyYWtlIH0gPSBzZXR0aW5nc1xuXG4gIC8vIFVzZSB0aGUgY3VycmVudCBzcGVlZCB0byBmaW5kIHRoZSBuZXh0IGFuZCBwcmV2aW91cyBsb2NhdGlvbnMuXG4gIGNvbnN0IHsgc3BlZWQsIHBvc2l0aW9uIH0gPSBjYXJcbiAgY29uc3QgbmV4dCA9IGdldENvb3Jkcyhwb3NpdGlvbiArIHNwZWVkKVxuICBjb25zdCBjdXJyZW50ID0gZ2V0Q29vcmRzKHBvc2l0aW9uKVxuICBjb25zdCBwcmV2aW91cyA9IGdldENvb3JkcygocG9zaXRpb24gKyAxKSAtIHNwZWVkKVxuXG4gIC8vIENyZWF0ZSB0aGUgZm9yd2FyZCBhbmQgYmFja3dhcmQgdmVjdG9ycy5cbiAgY29uc3QgZm9yd2FyZCA9IG5ldyBWZWN0b3IoW25leHRbMF0gLSBjdXJyZW50WzBdLCBuZXh0WzFdIC0gY3VycmVudFsxXV0pXG4gIGNvbnN0IGJhY2t3YXJkID0gbmV3IFZlY3RvcihbXG4gICAgcHJldmlvdXNbMF0gLSBjdXJyZW50WzBdLFxuICAgIHByZXZpb3VzWzFdIC0gY3VycmVudFsxXVxuICBdKVxuXG4gIC8vIGNvbnNvbGUubG9nKEpTT04uc3RyaW5naWZ5KHsgbmV4dCwgY3VycmVudCwgcHJldmlvdXMgfSwgbnVsbCwgMikpXG5cbiAgLy8gRmluZCB0aGUgYW5nbGUgb2YgdGhlIGxpbmUuXG4gIGNvbnN0IGFuZ2xlID0gVmVjdG9yLmFuZ2xlKGZvcndhcmQsIGJhY2t3YXJkKVxuXG4gIGxldCBuZXdTcGVlZCwgbmV3U3RhdHVzXG5cbiAgLy8gSWYgaXQncyBtb3JlIHRoYW4gMzAgZGVncmVlcywgYnJha2UgKGkuZS4gY3V0IHNwZWVkIGJ5IGJyYWtlIGZhY3RvcikuXG4gIGlmICghaXNOYU4oYW5nbGUpICYmIGFuZ2xlIDwgdG9SYWRpYW5zKDE4MCAtIDEwKSkge1xuICAgIG5ld1NwZWVkID0gc3BlZWQgKiBicmFrZVxuICAgIG5ld1N0YXR1cyA9ICdicmFraW5nJ1xuICAgIC8vIGNvbnNvbGUubG9nKHsgc3RhdHVzOiAnYnJha2luZycsIHNwZWVkLCBuZXdTcGVlZCB9KVxuICB9IGVsc2Uge1xuICAgIC8vIE90aGVyd2lzZSBlaXRoZXIgbWFpbnRhaW4gY3VycmVudCBzcGVlZCAoaWYgd2UncmUgYXQgbWF4IHNwZWVkKSxcbiAgICBpZiAoc3BlZWQgPj0gbWF4U3BlZWQpIHtcbiAgICAgIG5ld1NwZWVkID0gbWF4U3BlZWRcbiAgICAgIG5ld1N0YXR1cyA9ICdtYXhTcGVlZCdcbiAgICAgIC8vIGNvbnNvbGUubG9nKHsgc3RhdHVzOiAnc2FtZScsIHNwZWVkLCBuZXdTcGVlZCB9KVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBvciBhY2NlbGVyYXRlIChpbmNyZWFzZSBzcGVlZCBieSBhY2NlbGVyYXRpb24gZmFjdG9yKS5cbiAgICAgIG5ld1NwZWVkID0gc3BlZWQgKiBhY2NlbGVyYXRpb25cbiAgICAgIG5ld1N0YXR1cyA9ICdhY2NlbGVyYXRpbmcnXG4gICAgICAvLyBjb25zb2xlLmxvZyh7IHN0YXR1czogJ2FjY2VsZXJhdGluZycsIHNwZWVkLCBuZXdTcGVlZCB9KVxuICAgIH1cbiAgfVxuXG4gIC8vIFRoZW4gdXNlIHRoZSBuZXcgc3BlZWQgdG8gZmluZCB0aGUgbmV3IHBvc2l0aW9uLlxuICBjb25zdCBuZXdQb3NpdGlvbiA9IChwb3NpdGlvbiArIG5ld1NwZWVkKSAlIDFcblxuICAvLyBBbmQgbW92ZSB0byBuZXcgcG9zaXRpb24uXG4gIHJldHVybiB7XG4gICAgLi4uY2FyLFxuICAgIHBvc2l0aW9uOiBuZXdQb3NpdGlvbixcbiAgICBzcGVlZDogbmV3U3BlZWQsXG4gICAgc3RhdHVzOiBuZXdTdGF0dXNcbiAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBtb3ZlQ2FyXG5cblxuXG4vLyBXRUJQQUNLIEZPT1RFUiAvL1xuLy8gbW92ZUNhci5qcyJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7QUFBQTtBQUNBOzs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFBQTtBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQUE7QUFGQTtBQUFBO0FBQUE7QUFDQTtBQUlBO0FBQ0E7QUFOQTtBQUFBO0FBQ0E7QUFNQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBSUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUVBO0FBQ0E7QUFDQTtBQUpBO0FBTUE7QUFDQTtBQUNBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///3\n")},function(module,exports,__webpack_require__){"use strict";eval("\n\nvar _drawTrack2 = __webpack_require__(1);\n\nvar _drawTrack3 = _interopRequireDefault(_drawTrack2);\n\nvar _drawCars = __webpack_require__(0);\n\nvar _drawCars2 = _interopRequireDefault(_drawCars);\n\nvar _generateMarkers = __webpack_require__(2);\n\nvar _generateMarkers2 = _interopRequireDefault(_generateMarkers);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar container = d3.select('.circuit');\n\n// Draw the track.\n\nvar _drawTrack = (0, _drawTrack3.default)({ container: container, track: circuitTracks[0] }),\n    g = _drawTrack.g,\n    getCoords = _drawTrack.getCoords;\n\nvar inputs = document.querySelectorAll('input');\n\nvar getSettings = function getSettings() {\n  var settings = {};\n  inputs.forEach(function (input) {\n    var name = input.name,\n        value = input.value;\n\n    settings[name] = +value;\n    document.querySelector('.control.' + name + ' .number').textContent = value;\n  });\n\n  return settings;\n};\n\nvar draw = function draw() {\n  var settings = getSettings();\n  var markers = (0, _generateMarkers2.default)({ getCoords: getCoords, settings: settings });\n  console.log(markers.length);\n  (0, _drawCars2.default)({ g: g, cars: markers, getCoords: getCoords });\n};\n\ndraw();\n\ninputs.forEach(function (input) {\n  input.addEventListener('input', function () {\n    draw();\n  });\n});//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNC5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy9zY3JpcHQuanM/OWE5NSJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZHJhd1RyYWNrIGZyb20gJy4vZHJhd1RyYWNrLmpzJ1xuaW1wb3J0IGRyYXdDYXJzIGZyb20gJy4vZHJhd0NhcnMuanMnXG5pbXBvcnQgZ2VuZXJhdGVNYXJrZXJzIGZyb20gJy4vZ2VuZXJhdGVNYXJrZXJzLmpzJ1xuXG5jb25zdCBjb250YWluZXIgPSBkMy5zZWxlY3QoJy5jaXJjdWl0JylcblxuLy8gRHJhdyB0aGUgdHJhY2suXG5jb25zdCB7IGcsIGdldENvb3JkcyB9ID0gZHJhd1RyYWNrKHsgY29udGFpbmVyLCB0cmFjazogY2lyY3VpdFRyYWNrc1swXSB9KVxuXG5jb25zdCBpbnB1dHMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCdpbnB1dCcpXG5cbmNvbnN0IGdldFNldHRpbmdzID0gKCkgPT4ge1xuICBjb25zdCBzZXR0aW5ncyA9IHt9XG4gIGlucHV0cy5mb3JFYWNoKGlucHV0ID0+IHtcbiAgICBjb25zdCB7IG5hbWUsIHZhbHVlIH0gPSBpbnB1dFxuICAgIHNldHRpbmdzW25hbWVdID0gK3ZhbHVlXG4gICAgZG9jdW1lbnQucXVlcnlTZWxlY3RvcihgLmNvbnRyb2wuJHtuYW1lfSAubnVtYmVyYCkudGV4dENvbnRlbnQgPSB2YWx1ZVxuICB9KVxuXG4gIHJldHVybiBzZXR0aW5nc1xufVxuXG5jb25zdCBkcmF3ID0gKCkgPT4ge1xuICBjb25zdCBzZXR0aW5ncyA9IGdldFNldHRpbmdzKClcbiAgY29uc3QgbWFya2VycyA9IGdlbmVyYXRlTWFya2Vycyh7IGdldENvb3Jkcywgc2V0dGluZ3MgfSlcbiAgY29uc29sZS5sb2cobWFya2Vycy5sZW5ndGgpXG4gIGRyYXdDYXJzKHsgZywgY2FyczogbWFya2VycywgZ2V0Q29vcmRzIH0pXG59XG5cbmRyYXcoKVxuXG5pbnB1dHMuZm9yRWFjaChpbnB1dCA9PiB7XG4gIGlucHV0LmFkZEV2ZW50TGlzdGVuZXIoJ2lucHV0JywgKCkgPT4ge1xuICAgIGRyYXcoKVxuICB9KVxufSlcblxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyBzY3JpcHQuanMiXSwibWFwcGluZ3MiOiI7O0FBQUE7QUFDQTs7O0FBQUE7QUFDQTs7O0FBQUE7QUFDQTs7Ozs7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBQUE7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUFBO0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///4\n")},function(module,exports,__webpack_require__){"use strict";eval('\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nvar toRadians = function toRadians(x) {\n  return x * 2 * Math.PI / 360;\n};\n\nexports.default = toRadians;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy90b1JhZGlhbnMuanM/YWRlZiJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCB0b1JhZGlhbnMgPSB4ID0+IHggKiAyICogTWF0aC5QSSAvIDM2MFxuXG5leHBvcnQgZGVmYXVsdCB0b1JhZGlhbnNcblxuXG5cbi8vIFdFQlBBQ0sgRk9PVEVSIC8vXG4vLyB0b1JhZGlhbnMuanMiXSwibWFwcGluZ3MiOiI7Ozs7O0FBQUE7QUFBQTtBQUFBO0FBQ0E7QUFDQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///5\n')}]);

drawCars.js

const drawCars = ({ g, cars, getCoords }) => {
  // join
  const circles = g.selectAll('circle.car').data(cars)

  // update
  circles
    .attr('class', d => `car ${d.status}`)
    .attr('transform', d => `translate(${getCoords(d.position)})`)

  // enter
  circles
    .enter()
    .append('circle')
    .attr('class', d => `car ${d.status}`)
    .attr('cx', 0)
    .attr('cy', 0)
    .attr('r', d => d.status === 'maxSpeed' ? 3 : 4)
    .attr('transform', d => `translate(${getCoords(d.position)})`)

  circles.exit().remove()
}

export default drawCars

drawTrack.js

const drawTrack = ({ container, track }) => {
  const margin = 10
  const dimension = container.node().offsetWidth - margin * 2

  const xExtent = d3.extent(track, d => d.x)
  const x = d3.scaleLinear().domain(xExtent)

  const yExtent = d3.extent(track, d => d.y)
  const y = d3.scaleLinear().domain(yExtent)

  const aspect = (xExtent[1] - xExtent[0]) / (yExtent[1] - yExtent[0])

  const width = Math.min(dimension * aspect, dimension)
  const height = Math.min(dimension / aspect, dimension)

  x.range([0, width])
  y.range([0, height])

  const svg = container
    .append('svg')
    .attr('width', width + 2 * margin)
    .attr('height', height + 2 * margin)

  const g = svg.append('g').attr('transform', `translate(${margin}, ${margin})`)

  const line = d3
    .line()
    .curve(d3.curveBasis)
    .x(d => x(d.x))
    .y(d => y(d.y))

  const path = g
    .append('path')
    .datum(track)
    .attr('d', line)
    .node()

  const totalLength = path.getTotalLength()

  const getCoords = pct => {
    const p = path.getPointAtLength(totalLength * (pct % 1))
    return [p.x, p.y]
  }

  return { g, getCoords }
}

export default drawTrack

generateMarkers.js

import moveCar from './moveCar.js'

const generateMarkers = ({ getCoords, settings }) => {
  // Make a car that starts at 0.
  // Move it, and save the outcome to an array.
  // When it crosses the finish line, stop.

  const markers = [{ position: 0, speed: 0.001, status: 'accelerating' }]

  let stop = false
  let i = 0

  while (!stop && i < 1000) {
    const oldCar = markers.slice(-1)[0]
    const newCar = moveCar({ car: oldCar, getCoords, settings })

    if (newCar.position < oldCar.position) {
      stop = true
    }
    markers.push(newCar)
    i++
  }

  // return markers.filter((d, i) => i % 8 === 0)
  return markers
}

export default generateMarkers

moveCar.js

import toRadians from './toRadians.js'
// import toDegrees from './toDegrees.js'

// const brake = 0.5
// const acceleration = 1.1
// const maxSpeed = 0.5

// Every tick,
const moveCar = ({ car, getCoords, settings }) => {

  // // const { brake, acceleration, maxSpeed } = settings
  const { acceleration, maxSpeed, brake } = settings

  // Use the current speed to find the next and previous locations.
  const { speed, position } = car
  const next = getCoords(position + speed)
  const current = getCoords(position)
  const previous = getCoords((position + 1) - speed)

  // Create the forward and backward vectors.
  const forward = new Vector([next[0] - current[0], next[1] - current[1]])
  const backward = new Vector([
    previous[0] - current[0],
    previous[1] - current[1]
  ])

  // console.log(JSON.stringify({ next, current, previous }, null, 2))

  // Find the angle of the line.
  const angle = Vector.angle(forward, backward)

  let newSpeed, newStatus

  // If it's more than 30 degrees, brake (i.e. cut speed by brake factor).
  if (!isNaN(angle) && angle < toRadians(180 - 10)) {
    newSpeed = speed * brake
    newStatus = 'braking'
    // console.log({ status: 'braking', speed, newSpeed })
  } else {
    // Otherwise either maintain current speed (if we're at max speed),
    if (speed >= maxSpeed) {
      newSpeed = maxSpeed
      newStatus = 'maxSpeed'
      // console.log({ status: 'same', speed, newSpeed })
    } else {
      // or accelerate (increase speed by acceleration factor).
      newSpeed = speed * acceleration
      newStatus = 'accelerating'
      // console.log({ status: 'accelerating', speed, newSpeed })
    }
  }

  // Then use the new speed to find the new position.
  const newPosition = (position + newSpeed) % 1

  // And move to new position.
  return {
    ...car,
    position: newPosition,
    speed: newSpeed,
    status: newStatus
  }
}

export default moveCar

package.json

{
  "standard": {
    "globals": [
      "circuitTracks",
      "d3",
      "Vector"
    ]
  }
}

style.styl

// $red = #8f092a
// $yellow = #f7e4bd
// $blue = rgba(0,111,145, 0.75)
// $yellow = $blue
// $red = red
// $blue = green
// $blue = purple

$dimension = 200px
$accelerating = green
$maxSpeed = purple
$braking = red

*
  box-sizing border-box

body
  background rgba(0, 0, 0, 0.1)
  margin 0 auto
  padding 0
  width 960px

.circuit
  width 52%

svg
  display block
  margin 0 auto

  path
    fill black
    fill-opacity 0.075
    stroke black
    stroke-width 0.125px

  circle
    &.accelerating
      fill $accelerating
    &.braking
      fill $braking
    &.maxSpeed
      fill $maxSpeed

.controls
  font-family sans-serif
  width 50%
  position absolute
  right 3em
  top 2em

  .control
    margin 1em 0

    .title
      width 28%
      display inline-block
      text-align right
      font-weight bold

    input
      width 60%

  .legend
    margin 0 0 2em 0
    font-size 0.8em
    width 100%
    overflow hidden
    p
      float left
      padding-right 3em

      span
        display inline-block
        padding-right 0.5em
        &.accelerating
          color $accelerating
        &.braking
          color $braking
        &.maxSpeed
          color $maxSpeed

toDegrees.js

const toDegrees = radians => radians * 360 / (2 * Math.PI)

export default toDegrees

toRadians.js

const toRadians = x => x * 2 * Math.PI / 360

export default toRadians

vectorious.min.js

!function t(r,e,n){function o(i,s){if(!e[i]){if(!r[i]){var h="function"==typeof require&&require;if(!s&&h)return h(i,!0);if(a)return a(i,!0);var u=new Error("Cannot find module '"+i+"'");throw u.code="MODULE_NOT_FOUND",u}var p=e[i]={exports:{}};r[i][0].call(p.exports,function(t){var e=r[i][1][t];return o(e?e:t)},p,p.exports,t,r,e,n)}return e[i].exports}for(var a="function"==typeof require&&require,i=0;i<n.length;i++)o(n[i]);return o}({1:[function(t,r,e){!function(){"use strict";function e(t,r){return this.type=Float64Array,this.shape=[],t&&t.buffer&&t.buffer instanceof ArrayBuffer?e.fromTypedArray(t,r&&r.shape):t instanceof Array?e.fromArray(t):t instanceof n?e.fromVector(t,r&&r.shape):t instanceof e?e.fromMatrix(t):"number"==typeof t&&"number"==typeof r?e.fromShape([t,r]):t&&!t.buffer&&t.shape?e.fromShape(t.shape):void 0}var n=t("./vector");e.fromTypedArray=function(t,r){if(t.length!==r[0]*r[1])throw new Error("Shape does not match typed array dimensions.");var n=Object.create(e.prototype);return n.shape=r,n.data=t,n.type=t.constructor,n},e.fromArray=function(t){var r,n,o=t.length,a=t[0].length,i=new Float64Array(o*a);for(r=0;r<o;++r)for(n=0;n<a;++n)i[r*a+n]=t[r][n];return e.fromTypedArray(i,[o,a])},e.fromMatrix=function(t){var r=Object.create(e.prototype);return r.shape=[t.shape[0],t.shape[1]],r.data=new t.type(t.data),r.type=t.type,r},e.fromVector=function(t,r){if(r&&t.length!==r[0]*r[1])throw new Error("Shape does not match vector dimensions.");var n=Object.create(e.prototype);return n.shape=r?r:[t.length,1],n.data=new t.type(t.data),n.type=t.type,n},e.fromShape=function(t){var r=t[0],n=t[1];return e.fromTypedArray(new Float64Array(r*n),t)},e.binOp=function(t,r,n){return new e(t).binOp(r,n)},e.prototype.binOp=function(t,r){var e=this.shape[0],n=this.shape[1],o=e*n,a=this.data,i=t.data;if(e!==t.shape[0]||n!==t.shape[1])throw new Error("sizes do not match!");var s;for(s=0;s<o;s++)a[s]=r(a[s],i[s],s);return this},e.add=function(t,r){return new e(t).add(r)},e.prototype.add=function(t){return this.binOp(t,function(t,r){return t+r})},e.subtract=function(t,r){return new e(t).subtract(r)},e.prototype.subtract=function(t){return this.binOp(t,function(t,r){return t-r})},e.product=function(t,r){return new e(t).product(r)},e.prototype.product=function(t){return this.binOp(t,function(t,r){return t*r})},e.scale=function(t,r){return new e(t).scale(r)},e.prototype.scale=function(t){var r,e=this.shape[0],n=this.shape[1],o=e*n,a=this.data;for(r=0;r<o;r++)a[r]*=t;return this},e.fill=function(t,r,n,o){if(t<=0||r<=0)throw new Error("invalid size");n=n||0,o=o||Float64Array;var a,i,s=t*r,h=new o(s),u="function"==typeof n,p=0;for(a=0;a<t;a++)for(i=0;i<r;i++,p++)h[p]=u?n(a,i):n;return e.fromTypedArray(h,[t,r])},e.zeros=function(t,r,n){return e.fill(t,r,0,n)},e.ones=function(t,r,n){return e.fill(t,r,1,n)},e.random=function(t,r,n,o,a){return n=n||1,o=o||0,e.fill(t,r,function(){return n*Math.random()+o},a)},e.multiply=function(t,r){return t.multiply(r)},e.prototype.multiply=function(t){var r=this.shape[0],n=this.shape[1],o=t.shape[0],a=t.shape[1],i=this.data,s=t.data;if(n!==o)throw new Error("sizes do not match");var h,u,p,f,c=new this.type(r*a);for(h=0;h<r;h++)for(u=0;u<a;u++){for(f=0,p=0;p<n;p++)f+=i[h*n+p]*s[u+p*a];c[h*a+u]=f}return e.fromTypedArray(c,[r,a])},Object.defineProperty(e.prototype,"T",{get:function(){return this.transpose()}}),e.prototype.transpose=function(){var t,r,n=this.shape[0],o=this.shape[1],a=new this.type(o*n);for(t=0;t<n;t++)for(r=0;r<o;r++)a[r*n+t]=this.data[t*o+r];return e.fromTypedArray(a,[o,n])},e.prototype.inverse=function(){var t=this.shape[0],r=this.shape[1];if(t!==r)throw new Error("invalid dimensions");var n,o,a=e.identity(t),i=e.augment(this,a),s=i.gauss(),h=e.zeros(t,r),u=e.zeros(t,r),p=s.shape[1];for(n=0;n<t;n++)for(o=0;o<p;o++)o<r?h.set(n,o,s.get(n,o)):u.set(n,o-t,s.get(n,o));if(!h.equals(e.identity(t)))throw new Error("matrix is not invertible");return u},e.prototype.gauss=function(){var t,r,n,o,a,i=this.shape[0],s=this.shape[1],h=new e(this),u=0;for(r=0;r<i;r++){if(s<=u)return new Error("matrix is singular");for(n=r;0===h.data[n*s+u];)if(n++,i===n&&(n=r,u++,s===u))return new Error("matrix is singular");if(h.swap(r,n),t=h.data[r*s+u],0!==t)for(o=0;o<s;o++)h.data[r*s+o]=h.data[r*s+o]/t;for(n=0;n<i;n++)if(a=h.data[n*s+u],n!==r)for(o=0;o<s;o++)h.data[n*s+o]=h.data[n*s+o]-h.data[r*s+o]*a;u++}for(r=0;r<i;r++){for(t=0,n=0;n<s;n++)t||(t=h.data[r*s+n]);if(t)for(o=0;o<s;o++)h.data[r*s+o]=h.data[r*s+o]/t}return h},e.prototype.lu=function(){var t,r,n=this.shape[0],o=this.shape[1],a=e.plu(this),i=a[1],s=(e.identity(n),new e(a[0])),h=new e(a[0]);for(t=0;t<n;t++)for(r=t;r<o;r++)s.data[t*o+r]=t===r?1:0;for(t=0;t<n;t++)for(r=0;r<t&&r<o;r++)h.data[t*o+r]=0;return[s,h,i]},e.plu=function(t){return new e(t).plu()},e.prototype.plu=function(){var t,r,e,n,o,a,i,s=this.data,h=this.shape[0],u=new Int32Array(h);for(i=0;i<h;++i){for(n=i,t=Math.abs(s[i*h+i]),a=i+1;a<h;++a)r=Math.abs(s[a*h+i]),t<r&&(t=r,n=a);for(u[i]=n,n!==i&&this.swap(i,n),e=s[i*h+i],o=i+1;o<h;++o)s[o*h+i]/=e;for(o=i+1;o<h;++o){for(a=i+1;a<h-1;++a)s[o*h+a]-=s[o*h+i]*s[i*h+a],++a,s[o*h+a]-=s[o*h+i]*s[i*h+a];a===h-1&&(s[o*h+a]-=s[o*h+i]*s[i*h+a])}}return[this,u]},e.prototype.lusolve=function(t,r){var e,n,o,a=this.data,i=t.shape[0],s=t.shape[1],h=t.data;for(e=0;e<r.length;e++)e!==r[e]&&t.swap(e,r[e]);for(o=0;o<s;o++){for(e=0;e<i;e++)for(n=0;n<e;n++)h[e*s+o]-=a[e*i+n]*h[n*s+o];for(e=i-1;e>=0;e--){for(n=e+1;n<i;n++)h[e*s+o]-=a[e*i+n]*h[n*s+o];h[e*s+o]/=a[e*i+e]}}return t},e.prototype.solve=function(t){var r=e.plu(this),n=r[0],o=r[1];return n.lusolve(new e(t),o)},e.augment=function(t,r){return new e(t).augment(r)},e.prototype.augment=function(t){if(0===t.shape.length)return this;var r,e,n=this.shape[0],o=this.shape[1],a=t.shape[0],i=t.shape[1],s=this.data,h=t.data;if(n!==a)throw new Error("Rows do not match.");var u=o+i,p=new this.type(u*n);for(r=0;r<n;r++)for(e=0;e<o;e++)p[r*u+e]=s[r*o+e];for(r=0;r<a;r++)for(e=0;e<i;e++)p[r*u+e+o]=h[r*i+e];return this.shape=[n,u],this.data=p,this},e.identity=function(t,r){return e.fill(t,t,function(t,r){return t===r?1:0})},e.magic=function(t,r){function n(t,r,e){return(r+2*e+1)%t}if(t<0)throw new Error("invalid size");r=r||Float64Array;var o,a,i=new r(t*t);for(o=0;o<t;o++)for(a=0;a<t;a++)i[(t-o-1)*t+(t-a-1)]=n(t,t-a-1,o)*t+n(t,a,o)+1;return e.fromTypedArray(i,[t,t])},e.prototype.diag=function(){var t,r=this.shape[0],e=this.shape[1],o=new this.type(Math.min(r,e));for(t=0;t<r&&t<e;t++)o[t]=this.data[t*e+t];return new n(o)},e.prototype.determinant=function(){if(this.shape[0]!==this.shape[1])throw new Error("matrix is not square");var t,r=e.plu(this),n=r.pop(),o=r.pop(),a=this.shape[0],i=this.shape[1],s=1,h=1;for(t=0;t<a;t++)t!==n[t]&&(h*=-1);for(t=0;t<a;t++)s*=o.data[t*i+t];return h*s},e.prototype.trace=function(){var t,r,e=this.diag(),n=0;for(t=0,r=e.length;t<r;t++)n+=e.get(t);return n},e.equals=function(t,r){return t.equals(r)},e.prototype.equals=function(t){var r=this.shape[0],e=this.shape[1],n=r*e,o=this.data,a=t.data;if(r!==t.shape[0]||e!==t.shape[1]||this.type!==t.type)return!1;var i;for(i=0;i<n;i++)if(o[i]!==a[i])return!1;return!0},e.prototype.get=function(t,r){if(t<0||r<0||t>this.shape[0]-1||r>this.shape[1]-1)throw new Error("index out of bounds");return this.data[t*this.shape[1]+r]},e.prototype.set=function(t,r,e){if(t<0||r<0||t>this.shape[0]-1||r>this.shape[1]-1)throw new Error("index out of bounds");return this.data[t*this.shape[1]+r]=e,this},e.prototype.swap=function(t,r){if(t<0||r<0||t>this.shape[0]-1||r>this.shape[0]-1)throw new Error("index out of bounds");var e=this.shape[1],n=this.data.slice(t*e,(t+1)*e);return this.data.copyWithin(t*e,r*e,(r+1)*e),this.data.set(n,r*e),this},e.prototype.map=function(t){var r,n=this.shape[0],o=this.shape[1],a=n*o,i=new e(this),s=i.data;for(r=0;r<a;r++)s[r]=t.call(i,s[r],r/o|0,r%o,s);return i},e.prototype.each=function(t){var r,e=this.shape[0],n=this.shape[1],o=e*n;for(r=0;r<o;r++)t.call(this,this.data[r],r/n|0,r%n);return this},e.prototype.reduce=function(t,r){var e=this.shape[0],n=this.shape[1],o=e*n;if(0===o&&!r)throw new Error("Reduce of empty matrix with no initial value.");for(var a=0,i=r||this.data[a++];a<o;a++)i=t.call(this,i,this.data[a],a/n|0,a%n);return i},e.prototype.rank=function(){var t,r,e,o,a,i,s=this.toArray().map(function(t){return new n(t)}),h=this.shape[0],u=this.shape[1],p=0;for(t=0;t<h-1;t++){for(o=null,r=t;r<h;r++)if(s[t].get(t)){t!==r&&(e=s[t],s[t]=s[r],s[r]=e),o=s[t];break}if(o)for(r=t+1;r<h;r++)a=s[r],i=a.get(t)/o.get(t),s[r]=a.subtract(o.scale(i))}for(t=0;t<h;t++)for(r=0;r<u;r++)if(s[t].get(r)){p++;break}return p},e.rank=function(t){return new e(t).rank()},e.prototype.toString=function(){var t,r=[],e=this.shape[0],n=this.shape[1];for(t=0;t<e;t++)r.push("["+this.data.subarray(t*n,(t+1)*n).toString()+"]");return"["+r.join(", \n")+"]"},e.prototype.toArray=function(){var t,r=[],e=this.shape[0],n=this.shape[1];for(t=0;t<e;t++)r.push(Array.prototype.slice.call(this.data.subarray(t*n,(t+1)*n)));return r},r.exports=e;try{window.Matrix=e}catch(t){}}()},{"./vector":2}],2:[function(t,r,e){!function(){"use strict";function t(r){this.type=Float64Array,this.length=0,r instanceof t?this.combine(r):r&&r.shape?(this.data=new r.type(r.data),this.length=r.shape[0]*r.shape[1],this.type=r.type):r instanceof Array?(this.data=new this.type(r),this.length=r.length):r&&r.buffer&&r.buffer instanceof ArrayBuffer&&(this.data=r,this.length=r.length,this.type=r.constructor)}function e(t){return{get:function(){return this.get(t)},set:function(r){return this.set(t,r)}}}t.binOp=function(r,e,n){return new t(r).binOp(e,n)},t.prototype.binOp=function(t,r){var e=this.length,n=t.length;if(e!==n)throw new Error("sizes do not match!");if(!e&&!n)return this;var o;for(o=0;o<e;o++)this.data[o]=r(this.data[o],t.data[o],o);return this},t.add=function(r,e){return new t(r).add(e)},t.prototype.add=function(t){return this.binOp(t,function(t,r){return t+r})},t.subtract=function(r,e){return new t(r).subtract(e)},t.prototype.subtract=function(t){return this.binOp(t,function(t,r){return t-r})},t.scale=function(r,e){return new t(r).scale(e)},t.prototype.scale=function(t){return this.each(function(r,e,n){n[e]*=t})},t.normalize=function(r){return new t(r).normalize()},t.prototype.normalize=function(){return this.scale(1/this.magnitude())},t.project=function(r,e){return r.project(new t(e))},t.prototype.project=function(t){return t.scale(this.dot(t)/t.dot(t))},t.fill=function(r,e,n){if(r<0)throw new Error("invalid size");if(0===r)return new t;e=e||0,n=n||Float64Array;var o,a=new n(r),i="function"==typeof e;for(o=0;o<r;o++)a[o]=i?e(o):e;return new t(a)},t.zeros=function(r,e){return t.fill(r,0,e)},t.ones=function(r,e){return t.fill(r,1,e)},t.random=function(r,e,n,o){return e=e||1,n=n||0,t.fill(r,function(){return e*Math.random()+n},o)},t.range=function(){var r,e,n,o=[].slice.call(arguments,0),a=!1,i=Float64Array;switch("function"==typeof o[o.length-1]&&(i=o.pop()),o.length){case 2:n=o.pop(),e=1,r=o.pop();break;case 3:n=o.pop(),e=o.pop(),r=o.pop();break;default:throw new Error("invalid range")}if(n-r<0){var s=n;n=r,r=s,a=!0}if(e>n-r)throw new Error("invalid range");var h,u,p=new i(Math.ceil((n-r)/e));for(h=r,u=0;h<n;h+=e,u++)p[u]=a?n-h+r:h;return new t(p)},t.dot=function(t,r){return t.dot(r)},t.prototype.dot=function(t){if(this.length!==t.length)throw new Error("sizes do not match");var r,e,n=this.data,o=t.data,a=0;for(r=0,e=this.length;r<e;r++)a+=n[r]*o[r];return a},t.prototype.magnitude=function(){if(!this.length)return 0;var t,r,e=0,n=this.data;for(t=0,r=this.length;t<r;t++)e+=n[t]*n[t];return Math.sqrt(e)},t.angle=function(t,r){return t.angle(r)},t.prototype.angle=function(t){return Math.acos(this.dot(t)/this.magnitude()/t.magnitude())},t.equals=function(t,r){return t.equals(r)},t.prototype.equals=function(t){if(this.length!==t.length)return!1;for(var r=this.data,e=t.data,n=this.length,o=0;o<n&&r[o]===e[o];)o++;return o===n},t.prototype.min=function(){return this.reduce(function(t,r){return Math.min(t,r)},Number.POSITIVE_INFINITY)},t.prototype.max=function(){return this.reduce(function(t,r){return Math.max(t,r)},Number.NEGATIVE_INFINITY)},t.prototype.check=function(t){if(t<0||t>this.length-1)throw new Error("index out of bounds")},t.prototype.get=function(t){return this.check(t),this.data[t]},t.prototype.set=function(t,r){return this.check(t),this.data[t]=r,this},Object.defineProperties(t.prototype,{x:e(0),y:e(1),z:e(2),w:e(3)}),t.combine=function(r,e){return new t(r).combine(e)},t.prototype.combine=function(t){if(!t.length)return this;if(!this.length)return this.data=new t.type(t.data),this.length=t.length,this.type=t.type,this;var r=this.length,e=t.length,n=this.data,o=t.data,a=new this.type(r+e);return a.set(n),a.set(o,r),this.data=a,this.length=r+e,this},t.prototype.push=function(r){return this.combine(new t([r]))},t.prototype.map=function(r){var e,n=new t(this),o=n.data;for(e=0;e<this.length;e++)o[e]=r.call(n,o[e],e,o);return n},t.prototype.each=function(t){var r;for(r=0;r<this.length;r++)t.call(this,this.data[r],r,this.data);return this},t.prototype.reduce=function(t,r){var e=this.length;if(0===e&&!r)throw new Error("Reduce of empty matrix with no initial value.");for(var n=0,o=r||this.data[n++];n<e;n++)o=t.call(this,o,this.data[n],n,this.data);return o},t.prototype.toString=function(){var t,r=["["];for(t=0;t<this.length;t++)r.push(t>0?", "+this.data[t]:this.data[t]);return r.push("]"),r.join("")},t.prototype.toArray=function(){return this.data?Array.prototype.slice.call(this.data):[]},r.exports=t;try{window.Vector=t}catch(t){}}()},{}]},{},[2,1]);