Skip to content

Instantly share code, notes, and snippets.

@mathdoodle
Last active August 8, 2016 02:56
Show Gist options
  • Save mathdoodle/817aace27e05804fc2833709e33630e9 to your computer and use it in GitHub Desktop.
Save mathdoodle/817aace27e05804fc2833709e33630e9 to your computer and use it in GitHub Desktop.
Local Physics Basics

Local Physics Basics

Overview

const FLOATS_PER_VERTEX = 3;
const BYTES_PER_FLOAT = 4;
const STRIDE = BYTES_PER_FLOAT * FLOATS_PER_VERTEX;
function primitive(): EIGHT.Primitive {
const aPosition: EIGHT.Attribute = {
values: [
[0,+0.1,0, 0,-0.1,0, -0.1,0,0, +0.1,0,0], // center
[0,+1,0, -1,-1,0], // LHS
[0,+1,0, +1,-1,0], // RHS
[-1,-1,0, +1,-1,0] // BASE
].reduce(function(a,b){return a.concat(b)}),
size: 3,
type: EIGHT.DataType.FLOAT
};
const result: EIGHT.Primitive = {
mode: EIGHT.BeginMode.LINES,
attributes: {
}
};
result.attributes['aPosition'] = aPosition;
return result;
}
/**
* The geometry of the Bug is static so we use the conventional
* approach based upon GeometryArrays
*/
class BugGeometry extends EIGHT.GeometryArrays {
private w = 1
private h = 1
private d = 1
constructor(private contextManager: EIGHT.ContextManager) {
super(primitive(), contextManager);
}
getPrincipalScale(name: string): number {
switch (name) {
case 'width': {
return this.w
}
case 'height': {
return this.h
}
case 'depth': {
return this.d
}
default: {
throw new Error(`getPrincipalScale(${name}): name is not a principal scale property.`)
}
}
}
setPrincipalScale(name: string, value: number): void {
switch (name) {
case 'width': {
this.w = value
}
break
case 'height': {
this.h = value
}
break
case 'depth': {
this.d = value
}
break
default: {
throw new Error(`setPrinciplaScale(${name}): name is not a principal scale property.`)
}
}
this.setScale(this.w, this.h, this.d)
}
}
export default class Bug extends EIGHT.RigidBody {
constructor(contextManager: EIGHT.ContextManager) {
super(new BugGeometry(contextManager), new EIGHT.LineMaterial(void 0, contextManager), contextManager, {x:0,y:0,z:1})
this.height = 1;
this.width = 1;
}
get width() {
return this.getPrincipalScale('width');
}
set width(width: number) {
this.setPrincipalScale('width', width);
}
/**
*
*/
get height() {
return this.getPrincipalScale('height');
}
set height(height: number) {
this.setPrincipalScale('height', height);
}
}
import Steppable from './Steppable';
import Stepper from './Stepper';
export default class Forward implements Steppable {
constructor(private body: EIGHT.RigidBody, private distance: number) {
}
stepper(): Stepper {
return new ForwardStepper(this.body, this.distance);
}
}
class ForwardStepper implements Stepper {
private todo = true;
constructor(private body: EIGHT.RigidBody, private distance: number) {
}
hasNext(): boolean {
return this.todo;
}
next(): void {
if (this.todo) {
// We're assuming that P is a heading unit vector.
// We need to change out the model representation.
this.body.X.add(this.body.P, this.distance);
this.todo = false;
}
}
}
<!DOCTYPE html>
<html>
<head>
<!-- STYLES-MARKER -->
<style>
/* STYLE-MARKER */
</style>
<script src='https://jspm.io/system.js'></script>
<!-- SHADERS-MARKER -->
<!-- SCRIPTS-MARKER -->
</head>
<body>
<canvas id='my-canvas'></canvas>
<script>
// CODE-MARKER
</script>
<script>
System.import('./index.js')
</script>
</body>
</html>
import Bug from './Bug';
import Forward from './Forward'
import Repeat from './Repeat'
import Rotate from './Rotate'
import Steppable from './Steppable';
import SteppableList from './SteppableList';
import Track from './Track';
import World from './World'
const e1 = EIGHT.Geometric3.e1()
const e2 = EIGHT.Geometric3.e2()
const e3 = EIGHT.Geometric3.e3()
/**
* The space will have a natural scale of [-SIZE, SIZE].
*/
const SIZE = 2;
/**
* A brave new World.
*/
const world = new World(SIZE)
const body = new Bug(world.engine)
body.height = 0.1
body.width = 0.0618
body.color = new EIGHT.Color(0.8984, 0.1133, 0.3711)
const track = new Track(world.engine);
// const gridXY = world.createGridXY({segments: 20, min: -SIZE/2, max: +SIZE/2})
// gridXY.color = new EIGHT.Color(0.4, 0.4, 0.4)
world.reset()
world.planView()
/**
* Use to control the speed of the animation.
* Roughly the number of frames used to execute a motion step.
*/
const N = 40;
const list = new SteppableList()
list.add(new Repeat(new Forward(body, 1 / N), N))
list.add(new Repeat(new Rotate(body, e1 ^ e2, Math.PI/(2*N)), N))
const program = new Repeat(list, 4)
let stepper = program.stepper();
const animate = function() {
world.beginFrame()
if (world.time === 0) {
// Initialize
body.X.scale(0)
body.P.copy(e2).normalize()
track.erase()
track.addPoint(body.X.x, body.X.y, body.X.z)
// We also need a new stepper.
stepper = program.stepper()
}
if (world.running && stepper.hasNext()) {
// We'll count steps.
// All that matters is that time moves on from zero so we can reset.
world.time = world.time + 1;
stepper.next();
track.addPoint(body.X.x, body.X.y, body.X.z)
}
body.render(world.ambients)
track.render(world.ambients)
world.draw()
// This call keeps the animation going.
requestAnimationFrame(animate)
}
// This call starts the animation.
requestAnimationFrame(animate)
{
"description": "Local Physics Basics",
"dependencies": {
"DomReady": "1.0.0",
"jasmine": "2.4.1",
"davinci-eight": "2.245.0",
"dat-gui": "0.5.0",
"stats.js": "0.16.0"
},
"name": "copy-of-Vector Modeling Problem Framework",
"version": "0.1.0",
"keywords": [
"EIGHT",
"project",
"Getting",
"Started",
"WebGL"
],
"operatorOverloading": true,
"author": "David Geo Holmes"
}
import Steppable from './Steppable';
import Stepper from './Stepper';
export default class Repeat implements Steppable {
constructor(private steppable: Steppable, private N: number) {
}
stepper(): Stepper {
return new RepeatStepper(this.steppable, this.N);
}
}
class RepeatStepper implements Stepper {
private i = 0;
private stepper: Stepper;
constructor(private steppable: Steppable, private N: number) {
this.stepper = steppable.stepper();
}
hasNext() {
if (this.stepper.hasNext()) {
return true;
}
else {
if (this.i < this.N - 1) {
this.i++;
this.stepper = this.steppable.stepper();
return this.hasNext();
}
else {
return false;
}
}
}
next(): void {
if (this.hasNext()) {
this.stepper.next()
}
}
}
import Steppable from './Steppable'
import Stepper from './Stepper'
/**
* Scratch variable to avoid creating temporary objects.
*/
const R = EIGHT.Geometric3.one()
export default class Rotate implements Steppable {
constructor(private body: EIGHT.RigidBody, private B: EIGHT.Geometric3, private θ: number) {
}
stepper(): Stepper {
return new RotateStepper(this.body, this.B, this.θ);
}
}
class RotateStepper implements Stepper {
private todo = true;
constructor(private body: EIGHT.RigidBody, private B: EIGHT.Geometric3, private θ: number) {
}
hasNext() {
return this.todo;
}
next(): void {
if (this.todo) {
this.todo = false;
R.rotorFromGeneratorAngle(this.B, this.θ);
this.body.P.rotate(R);
this.body.R = R * this.body.R;
}
}
}
import Stepper from './Stepper';
/**
* Capable of being traversed or controlled by steps.
*/
interface Steppable {
stepper(): Stepper;
}
export default Steppable;
import Steppable from './Steppable';
import Stepper from './Stepper';
export default class SteppableList implements Steppable {
private steppables: Steppable[] = [];
constructor() {
}
stepper(): Stepper {
return new StepperList(this.steppables.map(function(steppable){return steppable.stepper();}));
}
add(steppable: Steppable): void {
this.steppables.push(steppable);
}
}
class StepperList implements Stepper {
private i = 0;
private N: number;
constructor(private steppers: Stepper[]) {
this.N = steppers.length;
}
hasNext(): boolean {
if (this.i < this.N) {
if (this.steppers[this.i].hasNext()) {
return true;
}
else {
this.i++;
return this.hasNext();
}
}
else {
return false;
}
}
next(): void {
if (this.hasNext()) {
this.steppers[this.i].next();
}
}
}
/**
* A device that moves or rotates in a series of small discrete steps.
*/
interface Stepper {
hasNext(): boolean;
next(): void;
}
export default Stepper;
body {
background-color: white;
}
<!DOCTYPE html>
<html>
<head>
<!-- STYLES-MARKER -->
<style>
/* STYLE-MARKER */
</style>
<script src='https://jspm.io/system.js'></script>
<!-- SCRIPTS-MARKER -->
</head>
<body>
<script>
// CODE-MARKER
</script>
<script>
System.import('./tests.js')
</script>
</body>
</html>
import Vector3 from './Vector3.spec'
window['jasmine'] = jasmineRequire.core(jasmineRequire)
jasmineRequire.html(window['jasmine'])
const env = jasmine.getEnv()
const jasmineInterface = jasmineRequire.interface(window['jasmine'], env)
extend(window, jasmineInterface)
const htmlReporter = new jasmine.HtmlReporter({
env: env,
getContainer: function() { return document.body },
createElement: function() { return document.createElement.apply(document, arguments) },
createTextNode: function() { return document.createTextNode.apply(document, arguments) },
timer: new jasmine.Timer()
})
env.addReporter(htmlReporter)
DomReady.ready(function() {
htmlReporter.initialize()
describe("Vector3", Vector3)
env.execute()
})
/*
* Helper function for extending the properties on objects.
*/
export default function extend<T>(destination: T, source: any): T {
for (let property in source) {
destination[property] = source[property]
}
return destination
}
const FLOATS_PER_VERTEX = 3;
const BYTES_PER_FLOAT = 4;
const STRIDE = BYTES_PER_FLOAT * FLOATS_PER_VERTEX;
/**
*
*/
class LineGeometry implements EIGHT.Geometry {
scaling = EIGHT.Matrix4.one();
private data: Float32Array;
private count = 0;
private N = 2;
private dirty = true;
private vbo: EIGHT.VertexBuffer;
private refCount = 1;
private contextProvider: EIGHT.ContextProvider;
constructor(private contextManager: EIGHT.ContextManager) {
this.data = new Float32Array(this.N * FLOATS_PER_VERTEX);
this.vbo = new EIGHT.VertexBuffer(contextManager);
}
bind(material: EIGHT.Material): LineGeometry {
if (this.dirty) {
this.vbo.bufferData(this.data, EIGHT.Usage.DYNAMIC_DRAW);
this.dirty = false;
}
this.vbo.bind();
const aPosition = material.getAttrib('aPosition');
aPosition.config(FLOATS_PER_VERTEX, EIGHT.DataType.FLOAT, true, STRIDE, 0);
aPosition.enable();
return this;
}
unbind(material: EIGHT.Material): LineGeometry {
const aPosition = material.getAttrib('aPosition');
aPosition.disable();
this.vbo.unbind()
return this;
}
draw(material: EIGHT.Material): LineGeometry {
// console.log(`LineGeometry.draw(${this.i})`)
this.contextProvider.gl.drawArrays(EIGHT.BeginMode.LINE_STRIP, 0, this.count);
return this;
}
getPrincipalScale(name: string): number {
throw new Error("LineGeometry.getPrincipalScale");
}
hasPrincipalScale(name: string): boolean {
throw new Error("LineGeometry.hasPrincipalScale");
}
setPrincipalScale(name: string, value: number): void {
throw new Error("LineGeometry.setPrincipalScale");
}
contextFree(contextProvider: EIGHT.ContextProvider): void {
this.vbo.contextFree(contextProvider);
}
contextGain(contextProvider: EIGHT.ContextProvider): void {
this.contextProvider = contextProvider;
this.vbo.contextGain(contextProvider);
}
contextLost(): void {
this.vbo.contextLost();
}
addRef(): number {
this.refCount++;
return this.refCount;
}
release(): number {
this.refCount--;
if (this.refCount === 0) {
// Clean Up
}
return this.refCount;
}
addPoint(x: number, y: number, z: number): void {
if (this.count === this.N) {
this.N = this.N * 2;
const temp = new Float32Array(this.N * FLOATS_PER_VERTEX);
temp.set(this.data)
this.data = temp;
}
const offset = this.count * FLOATS_PER_VERTEX;
this.data[offset + 0] = x;
this.data[offset + 1] = y;
this.data[offset + 2] = z;
this.count++;
this.dirty = true;
}
erase(): void {
this.count = 0;
}
}
export default class Track extends EIGHT.Mesh {
constructor(contextManager: EIGHT.ContextManager) {
super(new LineGeometry(contextManager), new EIGHT.LineMaterial(void 0, contextManager), contextManager)
}
addPoint(x: number, y: number, z: number): void {
const geometry = <LineGeometry>this.geometry;
geometry.addPoint(x, y, z);
geometry.release();
}
erase(): void {
const geometry = <LineGeometry>this.geometry;
geometry.erase();
geometry.release();
}
}
import Vector3 from './Vector3';
export default function() {
describe("constructor", function() {
const x = Math.random();
const y = Math.random();
const z = Math.random();
const v = new Vector3(x, y, z);
it("should initialize x-coordinate", function() {
expect(v.x).toBe(x)
})
})
}
export default class Vector3 {
constructor(public x = 0, public y = 0, public z = 0) {
}
}
const origin = EIGHT.Geometric3.vector(0, 0, 0)
const e1 = EIGHT.Geometric3.vector(1, 0, 0)
const e2 = EIGHT.Geometric3.vector(0, 1, 0)
const e3 = EIGHT.Geometric3.vector(0, 0, 1)
export default class World {
public engine: EIGHT.Engine;
private scene: EIGHT.Scene;
public ambients: EIGHT.Facet[] = [];
private camera: EIGHT.PerspectiveCamera;
private trackball: EIGHT.TrackballControls;
private dirLight: EIGHT.DirectionalLight;
private gui: dat.GUI;
/**
* An flag that determines whether the simulation should move forward.
*/
public running = false;
/**
* Universal Newtonian Time.
*/
public time = 0;
/**
* Creates a new Worls containg a WebGL canvas, a camera, lighting,
* and controllers.
*/
constructor(private size: number) {
this.engine = new EIGHT.Engine('my-canvas')
.size(500, 500)
.clearColor(0.1, 0.1, 0.1, 1.0)
.enable(EIGHT.Capability.DEPTH_TEST);
this.engine.gl.lineWidth(1)
this.scene = new EIGHT.Scene(this.engine);
this.camera = new EIGHT.PerspectiveCamera();
this.ambients.push(this.camera)
this.dirLight = new EIGHT.DirectionalLight();
this.ambients.push(this.dirLight)
this.trackball = new EIGHT.TrackballControls(this.camera, window)
// Subscribe to mouse events from the canvas.
this.trackball.subscribe(this.engine.canvas)
this.gui = new dat.GUI({name: 'Yahoo'});
const simFolder = this.gui.addFolder("Simulation")
simFolder.add(this, 'start');
simFolder.add(this, 'stop');
simFolder.add(this, 'reset');
simFolder.open();
const cameraFolder = this.gui.addFolder("Camera")
cameraFolder.add(this, 'planView');
cameraFolder.add(this, 'sideView');
cameraFolder.open();
this.sideView();
}
/**
* This method should be called at the beginning of an animation frame.
* It performs the following tasks:
* 1. Clears the graphics output.
* 2. Updates the camera based upon movements of the mouse.
* 3. Aligns the directional light with the viewing direction.
*/
beginFrame(): void {
this.engine.clear();
// Update the camera based upon mouse events received.
this.trackball.update();
// Keep the directional light pointing in the same direction as the camera.
this.dirLight.direction.copy(this.camera.look).sub(this.camera.eye)
}
/**
* This method should be called after objects have been moved.
*/
draw(): void {
this.scene.draw(this.ambients);
}
/**
* Puts the simulation into the running state.
*/
start(): void {
this.running = true
}
stop(): void {
this.running = false
}
/**
* Resets the universal time property back to zero.
*/
reset(): void {
this.running = false
this.time = 0
}
planView(): void {
this.camera.eye.copy(e3).normalize().scale(this.size * 1.4)
this.camera.look.copy(origin)
this.camera.up.copy(e2)
}
sideView(): void {
this.camera.eye.sub2(e3, e2).normalize().scale(this.size * 1.6)
this.camera.look.copy(origin)
this.camera.up.copy(e3)
}
/**
* Convenience method for creating a grid in the xy-plane.
*/
createGridXY(options: {
segments?: number;
min?: number;
max?: number;
} = {}): EIGHT.Grid {
const grid = new EIGHT.Grid({
uSegments: options.segments,
uMin: options.min,
uMax: options.max,
vSegments: options.segments,
vMin: options.min,
vMax: options.max
});
this.scene.add(grid);
return grid;
}
createBox(): EIGHT.Box {
const box = new EIGHT.Box();
this.scene.add(box);
return box;
}
/**
* Convenience method for creating a sphere.
*/
createSphere(options: {radius?: number} = {}): EIGHT.Sphere {
const sphere = new EIGHT.Sphere({radius: options.radius});
this.scene.add(sphere);
return sphere;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment