Create a gist now

Instantly share code, notes, and snippets.

@twolfson /.gitignore
Last active Oct 25, 2017

What would you like to do?
Interactive demo of a projection vector
node_modules/

gist-projection-vector

Interactive demo of a projection vector

Getting started

To get our repo working locally, run the following:

# Clone our repo
git clone git@gist.github.com:c6927d3c854087f74096e65c0db87279.git gist-projection-vector
cd gist-projection-vector

# Install our dependencies
npm install

# Start our development server
npm start
# serving /home/todd/github/gist-projection-vector on port 3000

Our development server will be running locally at http://localhost:3000/index.jade

doctype
html
head
meta(charset="utf-8")
title gist-projection-vector
style.
body {
background: linen;
/* https://github.com/corysimmons/typographic/blob/2.9.3/scss/typographic.scss#L34 */
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif';
}
#scene {
border: 1px solid #000;
}
.vertex {
cursor: pointer;
stroke: #000;
stroke-width: 5px;
}
.vertex--disabled {
cursor: not-allowed;
}
#vertex--a {
fill: orange;
}
#vertex--b {
fill: blue;
}
#vertex--c,
#vertex--c-prime {
fill: limegreen;
}
#vertex--c-prime {
fill: grey;
stroke: grey;
}
body
p Click and drag a circle to see the our projection vector update!
svg#scene(width="400", height="400", viewBox="0 0 400 400", xmlns="https://www.w3.org/2000/svg")
//- DEV: We put our line first so it's hidden behind our vertex z-indicies
line#line--a-b.line(x1="120", y1="170", x2="280", y2="170",
stroke-width="10", stroke="#000")
line#line--a-c.line(x1="60", y1="260", x2="120", y2="170",
stroke-width="10", stroke="#000")
line#line--a-c-prime.line(x1="60", y1="170", x2="120", y2="170",
stroke-width="10", stroke="#F00")
circle#vertex--a.vertex(cx="120", cy="170", r="20")
circle#vertex--b.vertex(cx="280", cy="170", r="20")
circle#vertex--c.vertex(cx="60", cy="260", r="20")
circle#vertex--c-prime.vertex.vertex--disabled(cx="60", cy="170", r="20")
//- Load in our dependencies
script(src="https://unpkg.com/draggabilly@2.1.1/dist/draggabilly.pkgd.js")
script(src="https://unpkg.com/gl-matrix@2.4.0/dist/gl-matrix.js")
//- Define our scripts/bindings
script.
const Draggabilly = window.Draggabilly;
document.addEventListener('DOMContentLoaded', function handleReady () {
// Resolve our circle
// TODO: Add assertion for element resolve
let sceneEl = document.querySelector('#scene');
let vertexAEl = sceneEl.querySelector('#vertex--a');
let vertexBEl = sceneEl.querySelector('#vertex--b');
let vertexCEl = sceneEl.querySelector('#vertex--c');
let vertexCPrimeEl = sceneEl.querySelector('#vertex--c-prime');
let lineABEl = sceneEl.querySelector('#line--a-b');
let lineACEl = sceneEl.querySelector('#line--a-c');
let lineACPrimeEl = sceneEl.querySelector('#line--a-c-prime');
// Add our drag bindings
function bindCircleDraggabilly(circleEl) {
// Initialize our bindings
let draggie = new Draggabilly(circleEl);
// Update our bindings to support SVG
// https://github.com/desandro/draggabilly/blob/v2.1.1/draggabilly.js#L266
// https://github.com/desandro/draggabilly/blob/v2.1.1/draggabilly.js#L184-L194
draggie._getPosition = function() {
let x = parseFloat(this.element.getAttribute('cx'), 10);
let y = parseFloat(this.element.getAttribute('cy'), 10);
// Clean up 'auto' or other non-integer values
this.position.x = isNaN(x) ? 0 : x;
this.position.y = isNaN(y) ? 0 : y;
};
// https://github.com/desandro/draggabilly/blob/v2.1.1/draggabilly.js#L424-L427
draggie.positionDrag = function () {
this.element.setAttribute('cx', this.startPosition.x + this.dragPoint.x);
this.element.setAttribute('cy', this.startPosition.y + this.dragPoint.y);
};
// https://github.com/desandro/draggabilly/blob/v2.1.1/draggabilly.js#L418-L422
draggie.setLeftTop = function () {
this.element.setAttribute('cx', this.position.x);
this.element.setAttribute('cy', this.position.y);
};
// Return our overridden binding
return draggie;
}
let vertexADraggie = bindCircleDraggabilly(vertexAEl);
let vertexBDraggie = bindCircleDraggabilly(vertexBEl);
let vertexCDraggie = bindCircleDraggabilly(vertexCEl);
// Eagerly calculate our positions so we can have a shared redraw
vertexADraggie._getPosition();
vertexBDraggie._getPosition();
vertexCDraggie._getPosition();
// When our vertices move, update our scene
// DEV: We could be more intelligent about what updated but it's saner to perform a declarative render
let aVertex = vec2.create();
let bVertex = vec2.create();
let cVertex = vec2.create();
let abVector = vec2.create();
let acVector = vec2.create();
let abUnitVector = vec2.create();
let acPrimeVector = vec2.create();
function updateScene() {
// Update our lines
lineABEl.setAttribute('x1', vertexADraggie.position.x);
lineABEl.setAttribute('y1', vertexADraggie.position.y);
lineABEl.setAttribute('x2', vertexBDraggie.position.x);
lineABEl.setAttribute('y2', vertexBDraggie.position.y);
lineACEl.setAttribute('x1', vertexADraggie.position.x);
lineACEl.setAttribute('y1', vertexADraggie.position.y);
lineACEl.setAttribute('x2', vertexCDraggie.position.x);
lineACEl.setAttribute('y2', vertexCDraggie.position.y);
lineACPrimeEl.setAttribute('x1', vertexADraggie.position.x);
lineACPrimeEl.setAttribute('y1', vertexADraggie.position.y);
// Unwrap our positions into vectors
vec2.set(aVertex, vertexADraggie.position.x, vertexADraggie.position.y);
vec2.set(bVertex, vertexBDraggie.position.x, vertexBDraggie.position.y);
vec2.set(cVertex, vertexCDraggie.position.x, vertexCDraggie.position.y);
vec2.sub(abVector, bVertex, aVertex);
vec2.sub(acVector, cVertex, aVertex);
// Lay out our mathematical logic
/*
axis
|
x offshoot
x/
origin
x's = axisProjection (from offshoot)
*/
// Knowns:
// cos(θ) = direction * |axisProjection| / |offshoot|
// axisUnitVector = direction * axisProjectionUnitVector
// axisUnitVector = axisVector / |axis|
// axisProjectionVector = axisProjectionUnitVector * |axisProjection|
// dot = |axis||offshoot|cos(θ)
// Formula:
// dot = |axis||offshoot|cos(θ)
// = |axis||offshoot|(direction * |axisProjection|/|offshoot|)
// = |axis||axisProjection| * direction
// Therefore: |axisProjection| = dot/(|axis|*direction)
// goal: axisProjectionVector
// = axisProjectionUnitVector * |axisProjection|
// = direction * axisUnitVector * dot/(|axis| * direction)
// = axisVector/|axis| * dot/|axis|
// = axisVector * (dot/|axis|^2)
// Perform our calculation of the projection vector
// axisVector = abVector
// offshootVector = acVector
let abAcDotProduct = vec2.dot(abVector, acVector);
// DEV: We prefer squared length as there's no square root during calculation so more precision
let abSquaredLength = vec2.squaredLength(abVector);
vec2.scale(acPrimeVector, abVector, abAcDotProduct / abSquaredLength);
// Update our line and projection dot
lineACPrimeEl.setAttribute('x2', aVertex[0] + acPrimeVector[0]);
lineACPrimeEl.setAttribute('y2', aVertex[1] + acPrimeVector[1]);
vertexCPrimeEl.setAttribute('cx', aVertex[0] + acPrimeVector[0]);
vertexCPrimeEl.setAttribute('cy', aVertex[1] + acPrimeVector[1]);
}
vertexADraggie.on('dragMove', updateScene);
vertexBDraggie.on('dragMove', updateScene);
vertexCDraggie.on('dragMove', updateScene);
});
{
"name": "gist-projection-vector",
"version": "1.0.0",
"description": "Interactive demo of a projection vector",
"main": "index.js",
"scripts": {
"start": "serve .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Todd Wolfson <todd@twolfson.com> (http://twolfson.com/)",
"license": "Unlicense",
"devDependencies": {
"serve": "~1.4.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment