Skip to content

Instantly share code, notes, and snippets.

@mathdoodle
Created November 2, 2016 18:13
Show Gist options
  • Save mathdoodle/6c8f0d4a91dd0622dd46272c00f2fbbf to your computer and use it in GitHub Desktop.
Save mathdoodle/6c8f0d4a91dd0622dd46272c00f2fbbf to your computer and use it in GitHub Desktop.
Cartesian Components
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; }

Cartesian Components

Student Learning Objectives

  • 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.

Teacher Notes

There are two ways to approach Vector Algebra in general and specifically for decomposing a vector according to a basis.

  1. the traditional approach that rapidly adopts cartesian coordinates.
  2. the modern coordinate-free approach that is used by Geometric Algebra.

Coordinate-based vector algebra.

Why do traditional approaches adopt coordinate based approaches so rapidly? One pedagogical answer may be that it makes the subject more concrete and familiar to students. That is certainly a consideration, but is it the only way? The truth is that without the ability to multiply vectors together, it is impossible to get to the concept of projection (upon which decomposition depends) without recourse to cartesian components.

Here is how the argument goes. To determine a projection one must talk about orthogonal projection as being defined in terms of the shortest rejection from a baseline. Finding the shortest distance requires invoking the pythagorian theorem which leads to the necessity for cartesian coordinates.

As a result, the majority of mathematical texts quickly reach for cartesian coordinates, or standard basis vectors, then introduce direction cosines as a way to deduce the cartesian coordinates of a vector.

What then follows is usually the definition of the scalar (dot) product, either in terms of cartesian components or in terms of magnitudes of the vectors and the angle between them. Starting from either definition, the other is derived.

This all seems rather unsatisfying and shady. Concepts such as angles and cosines are introduced which assume results from Cartesian coordinate geometry in a Euclidean space.

Geometric (coordinate-free) geometric algebra.

A more natural evolution from vector addition, subtraction, and scalar multiplication is to consider how projection may be defined in terms of vector multiplication. In effect we give a geometric interpretation to a symmetric vector product. We use this definition to define a scalar product and arrive at a projection function. To complete the definition of vector multiplication we must then consider the meaning of asymmetric multiplication leading to the concept of directed areas.

This nicely answers earlier speculations about the nature of the geometric product (what are the possible aspect properties of the resulting objects).

Pedagogical Challenges

Besides the concreteness of using cartesian coordinates or standard basis vectors, the traditional approach has the advantage of much simpler computational objects. A vector has only three coordinates and the possible methods or operations are limited.

A geometric quantity, on the other hand has eight coordinates. What is worse is that the implementation of the geometric product either becomes sixty-four multiplications, or the student has to become familiar with techniques for representing multivectors in a sparse manner. With limited time, the only reasonable approach may be to use a pre-existing multivector type and observe the consequences.

Taking the path more trodden.

As the title suggests, this unit takes the traditional path of introducing cartesian components. The coordinate-free geometric approach will be considered in another unit.

<!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>
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'
const i = EIGHT.Vector3.e1()
const j = EIGHT.Vector3.e2()
const k = EIGHT.Vector3.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()
// 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)
})
{
"description": "Cartesian Components",
"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"
]
}
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;
}
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
/**
*
*/
export default class Vector {
constructor(x: number, y: number, z: number, uom: UNITS.Unit) {
}
}
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.Vector3;
/**
* 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.Vector3 {
return EIGHT.Vector3.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.Vector3): 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.Vector3.copy(this.inner.h)
// return UNITS.G3.copy(this.inner.h).mul(this.scaleFactor)
}
set model(value: EIGHT.Vector3) {
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.Vector3.copy(this.inner.X)
// return UNITS.G3.copy(this.inner.X).mul(this.world.scaleFactor);
}
set position(value: EIGHT.Vector3) {
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.Vector3.copy(camera.eye)
// return UNITS.G3.copy(camera.eye).mul(world.scaleFactor);
},
set eye(value: EIGHT.Vector3) {
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