Skip to content

Instantly share code, notes, and snippets.

@twolfson twolfson/.gitignore
Last active Oct 25, 2017

Embed
What would you like to do?
Interactive demo of an orthogonal vector
node_modules/

gist-orthogonal-vector

Interactive demo of an orthogonal vector

Getting started

To get our repo working locally, run the following:

# Clone our repo
git clone git@gist.github.com:687bda8e59b1785d7eb7c9efcb095c54.git gist-orthogonal-vector
cd gist-orthogonal-vector

# Install our dependencies
npm install

# Start our development server
npm start
# serving /home/todd/github/gist-orthogonal-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-orthogonal-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 and orthogonal 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")
line#line--c-c-prime.line(x1="60", y1="260", x2="60", y2="170",
stroke-width="10", stroke="limegreen")
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');
let lineCCPrimeEl = sceneEl.querySelector('#line--c-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 acPrimeVector = vec2.create();
let ccPrimeVector = 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);
lineCCPrimeEl.setAttribute('x1', vertexCDraggie.position.x);
lineCCPrimeEl.setAttribute('y1', vertexCDraggie.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);
// Perform vector addition to find our orthogonal vector
vec2.sub(ccPrimeVector, acVector, acPrimeVector);
// Update our line and projection dot
lineACPrimeEl.setAttribute('x2', aVertex[0] + acPrimeVector[0]);
lineACPrimeEl.setAttribute('y2', aVertex[1] + acPrimeVector[1]);
// DEV: We could use `acPrimeVector` but that defeats the point
lineCCPrimeEl.setAttribute('x2', cVertex[0] - ccPrimeVector[0]);
lineCCPrimeEl.setAttribute('y2', cVertex[1] - ccPrimeVector[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-orthogonal-vector",
"version": "1.0.0",
"description": "Interactive demo of an orthogonal 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
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.