- To understand the basic properties of vectors and scalars.
- To understand the meaning of scalar multiplication on vectors.
- To add and subtract vectors graphically, mathematically, and computationally, either geometrically (coordinate-free) or by using coordinates.
- To decompose a vector into components.
- To assemble a vector from a magnitude and direction.
- To recognize and use the standard unit vectors.
- To work with tilted coordinate systems.
- To understand the meaning of a scalar and a vector field.
- To understand the definition of a position vector.
- To integrate units of measure into vector computation and mathematics.
Last active
November 2, 2016 13:02
-
-
Save mathdoodle/5d73beb2dc4431a7f7265c7872af5021 to your computer and use it in GitHub Desktop.
Vector Decomposition
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
table { | |
background: #FFFFFF; | |
border: solid 1px #DDDDDD; | |
margin-bottom: 1.25rem; | |
table-layout: auto; } | |
table caption { | |
background: transparent; | |
color: #222222; | |
font-size: 1rem; | |
font-weight: bold; } | |
table thead { | |
background: #F5F5F5; } | |
table thead tr th, | |
table thead tr td { | |
color: #222222; | |
font-size: 0.875rem; | |
font-weight: bold; | |
padding: 0.5rem 0.625rem 0.625rem; } | |
table tfoot { | |
background: #F5F5F5; } | |
table tfoot tr th, | |
table tfoot tr td { | |
color: #222222; | |
font-size: 0.875rem; | |
font-weight: bold; | |
padding: 0.5rem 0.625rem 0.625rem; } | |
table tr th, | |
table tr td { | |
color: #222222; | |
font-size: 0.875rem; | |
padding: 0.5625rem 0.625rem; | |
text-align: left; } | |
table tr.even, table tr.alt, table tr:nth-of-type(even) { | |
background: #F9F9F9; } | |
table thead tr th, | |
table tfoot tr th, | |
table tfoot tr td, | |
table tbody tr th, | |
table tbody tr td, | |
table tr td { | |
display: table-cell; | |
line-height: 1.125rem; } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<!-- STYLES-MARKER --> | |
<style> | |
/* STYLE-MARKER */ | |
</style> | |
<script src="https://jspm.io/system.js"></script> | |
<!-- SCRIPTS-MARKER --> | |
</head> | |
<body> | |
<canvas id='canvas3D'></canvas> | |
<canvas id='canvas2D'></canvas> | |
<div id='error'></div> | |
<script> | |
// CODE-MARKER | |
</script> | |
<script> | |
System.import('./index.js') | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {domReady, requestFrame} from './visual' | |
import {meter, kilogram, second, newton} from './units' | |
import {createArrow, Arrow} from './visual' | |
import {createBox, Box} from './visual' | |
import {createGridXY, createGridZX, Grid} from './visual' | |
import {sphere, Sphere} from './visual' | |
import {color} from './visual' | |
import {curve} from './visual' | |
import {World} from './visual' | |
// | |
// Vectors have been replaced by Multivectors so that we can multiply. | |
// | |
/** | |
* The standard basis vector along the x-axis. | |
*/ | |
const i = EIGHT.Geometric3.e1() | |
/** | |
* The standard basis vector along the y-axis. | |
*/ | |
const j = EIGHT.Geometric3.e2() | |
/** | |
* The standard basis vector along the z-axis. | |
*/ | |
const k = EIGHT.Geometric3.e3() | |
/** | |
* A composite containing a camera, the canvas, a scene, etc. | |
*/ | |
let world: World | |
/** | |
* A graphical arrow that will be used to visualize all vectors. | |
* This is done by moving it around (translation), by changing its color, | |
* and by changing the vector property that it is model that it represents. | |
*/ | |
let arrow: Arrow | |
/** | |
* A grid in the xy-plane. | |
*/ | |
let gridXY: Grid | |
/** | |
* A grid in the zx-plane. | |
*/ | |
let gridZX: Grid | |
/** | |
* Vector A | |
*/ | |
const A = 1.0 * j | |
/** | |
* Vector B | |
*/ | |
const B = 1.2 * i + 0.4 * j + 0.0 * k | |
/** | |
* Vector C, a random vector. | |
*/ | |
const C = Math.random() * i + Math.random() * j + Math.random() * k | |
/** | |
* Vector D, a random vector. | |
*/ | |
const D = Math.random() * i + Math.random() * j + Math.random() * k | |
/* | |
const gui = new dat.GUI() | |
const folderA = gui.addFolder("A (vector)") | |
folderA.add(A, 'x', -2, +2) | |
folderA.add(A, 'y', -2, +2) | |
folderA.add(A, 'z', -2, +2) | |
folderA.open() | |
const folderB = gui.addFolder("B (vector)") | |
folderB.add(B, 'x', -2, +2) | |
folderB.add(B, 'y', -2, +2) | |
folderB.add(B, 'z', -2, +2) | |
folderB.open() | |
*/ | |
/** | |
* The initialization function is called once, when the DOM has been loaded. | |
*/ | |
function init(): void { | |
world = new World() | |
// world.scaleFactor = meter | |
// world.camera.eye = 12 * world.scaleFactor * k | |
world.camera.eye = 6 * k | |
arrow = createArrow(world) | |
gridXY = createGridXY(world) | |
gridZX = createGridZX(world) | |
} | |
/** | |
* The update function is called repeatedly, for each animation frame. | |
*/ | |
function update() { | |
/** | |
* A shortcut to the origin defined on our World. | |
*/ | |
const origin = world.origin | |
// Draw the vector A and label it. | |
arrow.model = A | |
arrow.label = "A" | |
arrow.position = origin | |
arrow.color = color.red | |
arrow.draw() | |
arrow.model = B | |
arrow.label = "B" | |
arrow.position = origin | |
arrow.color = color.green | |
arrow.draw() | |
arrow.model = A - B | |
arrow.label = "A - B" | |
arrow.position = origin | |
arrow.color = color.blue | |
arrow.draw() | |
// Draw the grid in the XY plane to get a sense of the magnitude and direction of the vectors. | |
gridXY.draw() | |
// Draw the grid normal to A to avoid becoming disorientated! | |
gridZX.draw() | |
// Label the origin. | |
world.drawText("origin", origin) | |
} | |
/** | |
* The animation function is called repeatedly, as a callback for the browser. | |
*/ | |
function animate() { | |
world.clear() | |
update() | |
requestFrame(animate) | |
} | |
/** | |
* When the DOM has been loaded, initialize this program. | |
* Then request a frame to get the animation going. | |
*/ | |
domReady(function() { | |
init() | |
requestFrame(animate) | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"description": "Vector Decomposition", | |
"dependencies": { | |
"davinci-eight": "2.319.0", | |
"davinci-units": "1.5.3", | |
"DomReady": "1.0.0", | |
"jquery": "2.1.4", | |
"stats.js": "0.16.0", | |
"dat-gui": "0.5.0" | |
}, | |
"operatorOverloading": true, | |
"name": "copy-of-copy-of-a-ball-in-a-box-with-units", | |
"version": "0.1.0", | |
"keywords": [ | |
"visual", | |
"UNITS", | |
"G3" | |
], | |
"author": "David Geo Holmes" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
margin: 0; | |
overflow: hidden; | |
font-family: "Arial"; | |
} | |
#canvas3D { | |
position: absolute; | |
left: 0px; | |
top: 0px; | |
z-index: 0; | |
width: 500px; | |
height: 500px; | |
} | |
#canvas2D { | |
position: absolute; | |
left: 0px; | |
top: 0px; | |
z-index: 10; | |
width: 500px; | |
height: 500px; | |
/** | |
* Allow events to go to the other elements | |
*/ | |
pointer-events: none; | |
} | |
#error { | |
position: absolute; | |
left: 10px; | |
top: 10px; | |
z-index: 20; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const unitless = UNITS.G3.one | |
export const i = UNITS.G3.e1 | |
export const j = UNITS.G3.e2 | |
export const k = UNITS.G3.e3 | |
export const meter = UNITS.G3.meter | |
export const kilogram = UNITS.G3.kilogram | |
export const second = UNITS.G3.second | |
export const newton = kilogram * meter / (second * second) | |
export const coulomb = UNITS.G3.coulomb |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
*/ | |
export default class Vector { | |
constructor(x: number, y: number, z: number, uom: UNITS.Unit) { | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {meter, kilogram, second, unitless, newton, coulomb} from './units'; | |
// Change the lables used for the basis to i, j, k. | |
// These lables were borrowed from Hamilton's quaternions. | |
UNITS.G3.BASIS_LABELS = UNITS.G3.BASIS_LABELS_HAMILTON | |
///////////////////////////////////////////////////////////////////////////// | |
// Lighting | |
/** | |
* Ambient Lighting for the World. | |
*/ | |
const ambLight = new EIGHT.AmbientLight(EIGHT.Color.white.scale(0.4)) | |
/** | |
* Directional Lighting for the World. | |
*/ | |
const dirLight = new EIGHT.DirectionalLight() | |
///////////////////////////////////////////////////////////////////////////// | |
// Standard Colors | |
/** | |
* The standard colors. | |
*/ | |
export const color = { | |
red: EIGHT.Color.red, | |
green: EIGHT.Color.green, | |
blue: EIGHT.Color.blue, | |
yellow: EIGHT.Color.yellow, | |
magenta: EIGHT.Color.magenta, | |
cyan: EIGHT.Color.cyan, | |
orange: EIGHT.Color.fromRGB(1, 102 / 255, 0), | |
black: EIGHT.Color.black, | |
white: EIGHT.Color.white | |
} | |
///////////////////////////////////////////////////////////////////////////// | |
// Physical Constants | |
/** | |
* | |
*/ | |
export const ε0 = 8.854E-12 * (coulomb * coulomb) / (meter * meter * newton) | |
///////////////////////////////////////////////////////////////////////////// | |
// Validation | |
/** | |
* Determines whether a multivector is admissable as a vector. | |
*/ | |
function isVector(mv: EIGHT.GeometricE3): boolean { | |
if (mv.a !== 0 || mv.b !== 0 || mv.yz !== 0 || mv.zx !== 0 || mv.xy !== 0) { | |
return false; | |
} | |
else { | |
return true; | |
} | |
} | |
/** | |
* Determines whether a multivector is admissable as a scalar. | |
*/ | |
function isScalar(mv: EIGHT.GeometricE3): boolean { | |
if (mv.x !== 0 || mv.y !== 0 || mv.z !== 0 || mv.yz !== 0 || mv.zx !== 0 || mv.xy !== 0 || mv.b !== 0) { | |
return false; | |
} | |
else { | |
return true; | |
} | |
} | |
/** | |
* A camera is a frame of reference from which the scene is viewed. | |
*/ | |
export interface Camera { | |
/** | |
* The position of the camera, a position vector, measured in meters. | |
*/ | |
eye: EIGHT.Geometric3; | |
/** | |
* The point that the camera is looking at, a position vector, measured in meters. | |
*/ | |
look: UNITS.G3; | |
/** | |
* The desired up direction, a dimensionless vector. | |
*/ | |
up: UNITS.G3; | |
} | |
/** | |
* A ready-to-go composite for EIGHT animations. | |
*/ | |
export class World { | |
/** | |
* The scale factor for converting world units to dimensionless units. | |
*/ | |
public scaleFactor: UNITS.G3 = meter; | |
/** | |
* The frame of reference from which the world is viewed. | |
*/ | |
public camera: Camera; | |
public engine:EIGHT.Engine; | |
public scene: EIGHT.Scene; | |
public ambients: EIGHT.Facet[] = []; | |
private trackball:EIGHT.TrackballControls; | |
private dimlessCamera: EIGHT.PerspectiveCamera; | |
public framecounter: number = 0; | |
public overlay: EIGHT.Diagram3D; | |
constructor() { | |
// Notice that the canvas is "burned in". | |
this.engine = new EIGHT.Engine('canvas3D') | |
.clearColor(0.2, 0.2, 0.2, 1.0) | |
.enable(EIGHT.Capability.DEPTH_TEST) | |
// .enable(EIGHT.Capability.BLEND) | |
// .blendFunc(EIGHT.BlendingFactorSrc.SRC_ALPHA, EIGHT.BlendingFactorDest.ONE); | |
this.scene = new EIGHT.Scene(this.engine) | |
this.dimlessCamera = new EIGHT.PerspectiveCamera() | |
this.dimlessCamera.eye.x = 0 | |
this.dimlessCamera.eye.y = 0 | |
this.dimlessCamera.eye.z = 3 | |
this.ambients.push(this.dimlessCamera) | |
this.camera = worldCamera(this, this.dimlessCamera) | |
this.ambients.push(ambLight) | |
this.ambients.push(dirLight) | |
this.trackball = new EIGHT.TrackballControls(this.dimlessCamera, window) | |
// Workaround because Trackball no longer supports context menu for panning. | |
this.trackball.noPan = true | |
this.trackball.subscribe(this.engine.canvas) | |
this.overlay = new EIGHT.Diagram3D('canvas2D', this.dimlessCamera) | |
windowResize(this.engine, this.overlay, this.dimlessCamera).resize() | |
} | |
/** | |
* The underlying HTML5 Canvas. | |
*/ | |
get canvas(): HTMLCanvasElement { | |
return this.engine.canvas; | |
} | |
/** | |
* The origin is fixed to be zero. | |
*/ | |
get origin(): EIGHT.Geometric3 { | |
return EIGHT.Geometric3.e3().scale(0) | |
// return 0 * this.scaleFactor | |
} | |
/** | |
* Adds a drawable object to the world. | |
*/ | |
add(drawable: EIGHT.Renderable): void { | |
if (drawable){ | |
this.scene.add(drawable) | |
} | |
else { | |
// Throw Error | |
} | |
} | |
/** | |
* Clears the WebGL canvas and keeps the directional light pointing in the camera direction. | |
*/ | |
clear(): void { | |
this.engine.clear() | |
this.overlay.clear() | |
this.trackball.update() | |
dirLight.direction.copy(this.dimlessCamera.look).sub(this.dimlessCamera.eye) | |
} | |
/** | |
* Draws the objects that have been added to the world. | |
*/ | |
draw(): void { | |
this.scene.draw(this.ambients) | |
} | |
drawText(text: string, X: EIGHT.Geometric3): void { | |
const where = {x: 0, y: 0, z: 0} | |
// scale(text, X, this.scaleFactor, where) | |
this.overlay.fillText(text, X) | |
} | |
} | |
/** | |
* Divides the measure by the scaleFactor to produce a dimensionless quantity. | |
*/ | |
function scale(name: string, measure: UNITS.G3, scaleFactor: UNITS.G3, out: EIGHT.VectorE3): void { | |
if (!isScalar(scaleFactor)) { | |
throw new Error(`scaleFactor must be a scalar. scale(${name}, ${measure}, ${scaleFactor})`) | |
} | |
// We are expecting the result of scaling to produce a dimensionless quantity. | |
const dimless = measure / scaleFactor; | |
const uom = dimless.uom | |
if (!uom || uom.isOne()) { | |
out.x = dimless.x | |
out.y = dimless.y | |
out.z = dimless.z | |
// return EIGHT.Geometric3.copy(dimless) | |
} | |
else { | |
throw new Error(`Units of ${name}, ${scaleFactor}, is not consistent with units of quantity, ${measure}.`) | |
} | |
} | |
function scaleToNumber(name: string, measure: UNITS.G3, scaleFactor: UNITS.G3): number { | |
if (!isScalar(scaleFactor)) { | |
throw new Error(`scaleFactor must be a scalar. scale(${name}, ${measure}, ${scaleFactor})`) | |
} | |
// We are expecting the result of scaling to produce a dimensionless quantity. | |
const dimless = measure / scaleFactor; | |
const uom = dimless.uom | |
if (!uom || uom.isOne()) { | |
return dimless.a | |
} | |
else { | |
throw new Error(`Units of ${name}, ${scaleFactor}, is not consistent with units of quantity, ${measure}.`) | |
} | |
} | |
////////////////////////////////////////////////////////////////////////////// | |
/** | |
* A type that is useful for representing vectors. | |
*/ | |
export class Arrow { | |
private _scaleFactor: UNITS.G3 = meter; | |
private _label: string; | |
private inner: EIGHT.Arrow; | |
constructor(private world: World) { | |
this.inner = new EIGHT.Arrow() | |
world.add(this.inner) | |
} | |
get color() { | |
return this.inner.color; | |
} | |
set color(color: EIGHT.Color) { | |
this.inner.color = color; | |
} | |
get label() { | |
return this._label; | |
} | |
set label(value: string) { | |
if (typeof value === 'string') { | |
this._label = value | |
} | |
else { | |
throw new Error(`Arrow.label property must be a string.`); | |
} | |
} | |
get scaleFactor() { | |
return this._scaleFactor; | |
} | |
set scaleFactor(value: UNITS.G3) { | |
if (isScalar(value)) { | |
this._scaleFactor = value; | |
} | |
else { | |
throw new Error(`Arrow.scaleFactor property must be a scalar.`); | |
} | |
} | |
get model() { | |
return EIGHT.Geometric3.copy(this.inner.h) | |
// return UNITS.G3.copy(this.inner.h).mul(this.scaleFactor) | |
} | |
set model(value: EIGHT.Geometric3) { | |
this.inner.h.copyVector(value) | |
/* | |
if (isVector(value)) { | |
scale('axis', value, this.scaleFactor, this.inner.h) | |
} | |
else { | |
throw new Error(`Arrow.axis property must be a vector.`); | |
} | |
*/ | |
} | |
get position() { | |
return EIGHT.Geometric3.copy(this.inner.X) | |
// return UNITS.G3.copy(this.inner.X).mul(this.world.scaleFactor); | |
} | |
set position(value: EIGHT.Geometric3) { | |
this.inner.X.copyVector(value) | |
/* | |
if (isVector(value)) { | |
scale('pos', value, this.world.scaleFactor, this.inner.X) | |
} | |
else { | |
throw new Error(`Arrow.pos property must be a vector.`); | |
} | |
*/ | |
} | |
draw() { | |
this.inner.render(this.world.ambients) | |
if (typeof this._label === 'string' && this._label.length > 0) { | |
this.world.drawText(this._label, this.position + (this.model / 2)) | |
// this.world.drawText(this._label, this.position + (this.model / 2) * (this.world.scaleFactor / this.scaleFactor)) | |
} | |
} | |
} | |
/** | |
* Constructor function for an Arrow. | |
*/ | |
export function createArrow(world: World, options: {scaleFactor?: UNITS.G3; color?: EIGHT.Color} = {}): Arrow { | |
const that = new Arrow(world) | |
if (options.scaleFactor) { | |
if (options.scaleFactor instanceof UNITS.G3) { | |
that.scaleFactor = options.scaleFactor; | |
} | |
else { | |
throw new Error("pos option must have type UNITS.G3"); | |
} | |
} | |
if (options.color) { | |
if (options.color instanceof EIGHT.Color) { | |
that.color = options.color; | |
} | |
else { | |
throw new Error("color property must have type EIGHT.Color"); | |
} | |
} | |
return that; | |
} | |
/** | |
* | |
*/ | |
export class Box { | |
public scaleFactor: UNITS.G3 = meter; | |
private inner: EIGHT.Box; | |
constructor(private world: World) { | |
this.inner = new EIGHT.Box({k: 1}) | |
this.scaleFactor = world.scaleFactor | |
world.add(this.inner) | |
} | |
get color() { | |
return this.inner.color; | |
} | |
set color(color: EIGHT.Color) { | |
this.inner.color = color; | |
} | |
get width() { | |
return UNITS.G3.scalar(this.inner.width, this.scaleFactor.uom) | |
} | |
set width(value: UNITS.G3) { | |
if (isScalar(value)) { | |
this.inner.width = scaleToNumber('width', value, this.scaleFactor) | |
} | |
else { | |
throw new Error(`Box.width property must be a scalar.`); | |
} | |
} | |
get height() { | |
return UNITS.G3.scalar(this.inner.height, this.scaleFactor.uom) | |
} | |
set height(value: UNITS.G3) { | |
if (isScalar(value)) { | |
this.inner.height = scaleToNumber('height', value, this.scaleFactor) | |
} | |
else { | |
throw new Error(`Box.height property must be a scalar.`); | |
} | |
} | |
get depth() { | |
return UNITS.G3.scalar(this.inner.depth, this.scaleFactor.uom) | |
} | |
set depth(value: UNITS.G3) { | |
if (isScalar(value)) { | |
this.inner.depth = scaleToNumber('depth', value, this.scaleFactor) | |
} | |
else { | |
throw new Error(`Box.depth property must be a scalar.`); | |
} | |
} | |
get pos() { | |
return UNITS.G3.copy(this.inner.X).mul(this.world.scaleFactor); | |
} | |
set pos(value: UNITS.G3) { | |
if (isVector(value)) { | |
scale('X', value, this.world.scaleFactor, this.inner.X) | |
} | |
else { | |
throw new Error(`Box.pos property must be a vector.`); | |
} | |
} | |
get visible() { | |
return this.inner.visible | |
} | |
set visible(value: boolean) { | |
this.inner.visible = false | |
} | |
draw() { | |
this.inner.render(this.world.ambients) | |
} | |
} | |
/** | |
* Constructor function for a Box. | |
*/ | |
export function createBox(world: World, options: {pos?: UNITS.G3; color?: EIGHT.Color} = {}): Box { | |
if (world) { | |
const that = new Box(world); | |
if (options.pos) { | |
if (options.pos instanceof UNITS.G3) { | |
that.pos = options.pos; | |
} | |
else { | |
throw new Error("pos option must have type UNITS.G3"); | |
} | |
} | |
if (options.color) { | |
if (options.color instanceof EIGHT.Color) { | |
that.color = options.color; | |
} | |
else { | |
throw new Error("color property must have type EIGHT.Color"); | |
} | |
} | |
return that; | |
} | |
else { | |
throw new Error("World has not yet been initialized.") | |
} | |
} | |
/** | |
* TODO | |
*/ | |
export class Cylinder { | |
private inner: EIGHT.Cylinder; | |
public scaleFactor: UNITS.G3 = meter; | |
constructor(private world: World) { | |
this.inner = new EIGHT.Cylinder(); | |
world.add(this.inner) | |
} | |
get color() { | |
return this.inner.color; | |
} | |
set color(color: EIGHT.Color) { | |
this.inner.color = color; | |
} | |
get length() { | |
return UNITS.G3.copy(this.inner.length, void 0).mul(this.scaleFactor); | |
} | |
set length(length: UNITS.G3) { | |
scale('length', length, this.scaleFactor, this.inner.length) | |
} | |
get radius() { | |
return UNITS.G3.copy(this.inner.radius, void 0).mul(this.scaleFactor); | |
} | |
set radius(radius: UNITS.G3) { | |
scale('radius', radius, this.scaleFactor, this.inner.radius) | |
} | |
get axis() { | |
return UNITS.G3.copy(this.inner.axis, void 0) | |
} | |
set axis(axis: UNITS.G3) { | |
scale('axis', axis, unitless, this.inner.axis) | |
} | |
get transparent() { | |
return this.inner.transparent; | |
} | |
set transparent(transparent: boolean) { | |
this.inner.transparent = transparent; | |
} | |
get X() { | |
return UNITS.G3.copy(this.inner.X, void 0).mul(this.world.scaleFactor); | |
} | |
set X(X: UNITS.G3) { | |
scale('X', X, this.world.scaleFactor, this.inner.X) | |
} | |
} | |
/** | |
* | |
*/ | |
export class Grid { | |
constructor(private world: World, private inner: EIGHT.Grid) { | |
world.add(inner) | |
} | |
get color() { | |
return this.inner.color; | |
} | |
set color(color: EIGHT.Color) { | |
this.inner.color = color; | |
} | |
draw() { | |
this.inner.render(this.world.ambients) | |
} | |
} | |
export function createGridXY(world: World) { | |
const that = new Grid(world, new EIGHT.GridXY()) | |
return that; | |
} | |
export function createGridZX(world: World) { | |
const that = new Grid(world, new EIGHT.GridZX()) | |
return that; | |
} | |
/** | |
* | |
*/ | |
export class Sphere { | |
public scaleFactor: UNITS.G3 = meter; | |
public trail: Curve; | |
private inner: EIGHT.Sphere; | |
private _velocity: UNITS.G3 = 0 * meter / second; | |
constructor(private world: World) { | |
this.inner = new EIGHT.Sphere() | |
this.scaleFactor = world.scaleFactor; | |
this.inner.transparent = false | |
this.inner.opacity = 1 | |
world.add(this.inner) | |
} | |
get color() { | |
return this.inner.color; | |
} | |
set color(color: EIGHT.Color) { | |
this.inner.color = color; | |
} | |
get radius() { | |
return UNITS.G3.scalar(this.inner.radius, this.scaleFactor.uom) | |
} | |
set radius(value: UNITS.G3) { | |
this.inner.radius = scaleToNumber('radius', value, this.scaleFactor) | |
} | |
get velocity() { | |
return this._velocity; | |
} | |
set velocity(value: UNITS.G3) { | |
if (isVector(value)) { | |
this._velocity = value; | |
} | |
else { | |
throw new Error(`Sphere.velocity property must be a vector.`); | |
} | |
} | |
get pos() { | |
return UNITS.G3.copy(this.inner.X).mul(this.world.scaleFactor); | |
} | |
set pos(value: UNITS.G3) { | |
if (isVector(value)) { | |
scale('X', value, this.world.scaleFactor, this.inner.X) | |
} | |
else { | |
throw new Error(`Sphere.pos property must be a vector.`); | |
} | |
} | |
} | |
/** | |
* Constructor function for a Sphere. | |
*/ | |
export function sphere(world: World, options: {pos?: UNITS.G3; radius?: UNITS.G3; color?: EIGHT.Color} = {}): Sphere { | |
if (world) { | |
const that = new Sphere(world); | |
if (options.pos) { | |
if (options.pos instanceof UNITS.G3) { | |
that.pos = options.pos; | |
} | |
else { | |
throw new Error("pos option must have type UNITS.G3"); | |
} | |
} | |
if (options.radius) { | |
if (options.radius instanceof UNITS.G3) { | |
that.radius = options.radius; | |
} | |
else { | |
throw new Error("radius option must have type UNITS.G3"); | |
} | |
} | |
if (options.color) { | |
if (options.color instanceof EIGHT.Color) { | |
that.color = options.color; | |
} | |
else { | |
throw new Error("color option must have type EIGHT.Color"); | |
} | |
} | |
return that; | |
} | |
else { | |
throw new Error("World has not yet been initialized.") | |
} | |
} | |
export interface Curve { | |
append(point: UNITS.G3): void | |
} | |
/** | |
* | |
*/ | |
export function curve(world: World, options: {color?: EIGHT.Color} = {}): Curve { | |
const track = new EIGHT.Track({engine: world.engine, color: options.color}) | |
world.scene.add(track) | |
const that: Curve = { | |
append(point: UNITS.G3): void { | |
track.addPoint(point) | |
} | |
} | |
return that; | |
} | |
/////////////////////////////////////////////////////////////////////// | |
/** | |
* Wrapper object for the PerspectiveCamera so that the eye, look (vector) | |
* properties use the units of the World (usually meters). | |
*/ | |
function worldCamera(world: World, camera: EIGHT.PerspectiveCamera): Camera { | |
const that: Camera = { | |
get eye() { | |
return EIGHT.Geometric3.copy(camera.eye) | |
// return UNITS.G3.copy(camera.eye).mul(world.scaleFactor); | |
}, | |
set eye(value: EIGHT.Geometric3) { | |
camera.eye.copyVector(value) | |
/* | |
if (isVector(value)) { | |
scale('eye', value, world.scaleFactor, camera.eye) | |
} | |
else { | |
throw new Error(`Camera.eye property must be a vector.`); | |
} | |
*/ | |
}, | |
get look() { | |
return UNITS.G3.copy(camera.look).mul(world.scaleFactor); | |
}, | |
set look(value: UNITS.G3) { | |
if (isVector(value)) { | |
scale('look', value, world.scaleFactor, camera.look) | |
} | |
else { | |
throw new Error(`Camera.look property must be a vector.`); | |
} | |
}, | |
get up() { | |
return UNITS.G3.copy(camera.up).mul(unitless); | |
}, | |
set up(value: UNITS.G3) { | |
if (isVector(value)) { | |
scale('up', value, unitless, camera.up) | |
} | |
else { | |
throw new Error(`Camera.up property must be a vector.`); | |
} | |
} | |
} | |
return that; | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
/** | |
* Displays an exception by writing it to a <pre> element. | |
*/ | |
function displayError(e: any) { | |
const stderr = <HTMLPreElement>document.getElementById('error') | |
stderr.style.color = "#FF0000" | |
stderr.innerHTML = `${e}` | |
} | |
/** | |
* Calls the callback argument when the Document Object Model (DOM) has been loaded. | |
* Exceptions thrown by the callback function are caught and displayed. | |
*/ | |
export function domReady(callback: () => any): void { | |
DomReady.ready(function() { | |
try | |
{ | |
callback() | |
} | |
catch(e) { | |
displayError(e) | |
} | |
}) | |
} | |
/** | |
* Catches exceptions thrown in the animation callback and displays them. | |
* This function will have a slight performance impact owing to the try...catch statement. | |
* This function may be bypassed for production use by using window.requestAnimationFrame directly. | |
*/ | |
export function requestFrame(callback: FrameRequestCallback): number { | |
const wrapper: FrameRequestCallback = function(time: number) { | |
try { | |
callback(time) | |
} | |
catch(e) { | |
displayError(e) | |
} | |
} | |
return window.requestAnimationFrame(wrapper) | |
} | |
/** | |
* Creates an object that manages resizing of the output to fit the window. | |
*/ | |
function windowResize(engine: EIGHT.Engine, overlay: EIGHT.Diagram3D, camera: EIGHT.PerspectiveCamera){ | |
const callback = function() { | |
engine.size(window.innerWidth, window.innerHeight); | |
// engine.viewport(0, 0, window.innerWidth, window.innerHeight) | |
// engine.canvas.width = window.innerWidth | |
// engine.canvas.height = window.innerHeight | |
engine.canvas.style.width = `${window.innerWidth}px` | |
engine.canvas.style.height = `${window.innerHeight}px` | |
camera.aspect = window.innerWidth / window.innerHeight; | |
overlay.canvas.width = window.innerWidth | |
overlay.canvas.height = window.innerHeight | |
overlay.canvas.style.width = `${window.innerWidth}px` | |
overlay.canvas.style.height = `${window.innerHeight}px` | |
const ctxt = overlay.canvas.getContext('2d') | |
ctxt.font = '24px Helvetica' | |
ctxt.fillStyle = '#FFFFFF' | |
} | |
window.addEventListener('resize', callback, false); | |
const that = { | |
/** | |
* | |
*/ | |
resize: function() { | |
callback(); | |
return that; | |
}, | |
/** | |
* Stop watching window resize | |
*/ | |
stop : function() { | |
window.removeEventListener('resize', callback); | |
return that; | |
} | |
}; | |
return that; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment