Skip to content

Instantly share code, notes, and snippets.

@mathdoodle
Last active August 25, 2016 19:19
Show Gist options
  • Save mathdoodle/e2af676c090252e7468d8476136a25ab to your computer and use it in GitHub Desktop.
Save mathdoodle/e2af676c090252e7468d8476136a25ab to your computer and use it in GitHub Desktop.
Demon Turning

Demon Turning

Overview

Credits

The idea for this program comes from the book:

Turtle Geometry

The Computer as a medium for Exploring Mathematics

Harold Abelson and Andrea diSessa.

/**
* Heading
*/
const INDEX_H = 0;
/**
* Left
*/
const INDEX_L = 1;
/**
* Up.
*/
const INDEX_X = 2;
export default class Bug {
public X: EIGHT.Geometric3 = EIGHT.Geometric3.zero()
public _frame: EIGHT.Geometric3[] = [];
constructor() {
this._frame[INDEX_H] = EIGHT.Geometric3.zero();
this._frame[INDEX_L] = EIGHT.Geometric3.zero()
this._frame[INDEX_X] = EIGHT.Geometric3.zero();
}
get frame(): EIGHT.VectorE3[] {
// Return a copy.
return this._frame.map(function(e){ return e.clone(); });
}
set frame(frame: EIGHT.VectorE3[]) {
// Copy the frame parameter.
for(let i = 0; i < 3; i++) {
this._frame[i] = EIGHT.Geometric3.fromVector(frame[i]);
}
}
forward(distance: number): EIGHT.Geometric3 {
const H = this._frame[INDEX_H];
const realWalk = H * distance;
this.X = this.X + realWalk;
return realWalk;
}
rotate(R: EIGHT.Geometric3): void {
this._frame[INDEX_H].rotate(R);
this._frame[INDEX_L].rotate(R);
this._frame[INDEX_X].rotate(R);
}
}
<!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 World from './World'
import {gapRatio,e_r,e_θ,turningPerAngle,r} from './surface'
const origin = EIGHT.Geometric3.zero()
const e1 = EIGHT.Geometric3.e1()
const e2 = EIGHT.Geometric3.e2()
const e3 = EIGHT.Geometric3.e3()
/**
* The initial frame sets the orientation.
*/
const INITIAL_FRAME = [e2, -e1, e3]
/**
*
*/
const world = new World()
const engine = world.engine;
const bug = new Bug()
const viz = new EIGHT.Turtle({engine})
const track = new EIGHT.Track({engine})
track.color = EIGHT.Color.white
const arrow = new EIGHT.Arrow({engine})
const grid = new EIGHT.GridXY({engine})
world.reset()
world.sideView()
const animate = function() {
world.beginFrame()
if (world.time === 0) {
bug.X = e1
bug.frame = INITIAL_FRAME
// Erasing and adding the start point means we only have one point upon reset.
track.clear()
track.addPoint(bug.X)
}
if (world.running) {
// We'll count steps.
// All that matters is that time moves on from zero so we can reset.
world.time = world.time + 1
const realWalk = bug.forward(0.01)
// bug.rotate((e1^e2).scale(-0.005).exp())
const s = e_θ(bug.X);
const leap = gapRatio(bug.X) * (realWalk | s) * s;
const walk = realWalk + leap
const angle = (1 / r(bug.X) * (walk|s).a);
const demonTurningAngle = angle * turningPerAngle(r(bug.X))
const demonRotor = (e1 ^ e2).scale(-demonTurningAngle/2).exp()
bug.X = bug.X + leap
bug.rotate(demonRotor)
track.addPoint(bug.X)
}
// Render the bug as a triangle.
viz.X.copyVector(bug.X)
viz.R.rotorFromFrameToFrame(INITIAL_FRAME, bug.frame)
viz.render(world.ambients)
// Render the track of the bug.
track.render(world.ambients)
grid.render(world.ambients)
arrow.X.copy(bug.X)
arrow.h.copy(e_r(bug.X))
arrow.color = EIGHT.Color.red
arrow.render(world.ambients)
arrow.h.copy(e_θ(bug.X))
arrow.color = EIGHT.Color.blue
arrow.render(world.ambients)
// This call keeps the animation going.
requestAnimationFrame(animate)
}
// This call starts the animation.
requestAnimationFrame(animate)
{
"description": "Demon Turning",
"dependencies": {
"DomReady": "1.0.0",
"jasmine": "2.4.1",
"davinci-eight": "2.302.0",
"dat-gui": "0.5.0",
"stats.js": "0.16.0"
},
"name": "copy-of-copy-of-copy-of-local-geometry-on-a-sphere",
"version": "0.1.0",
"keywords": [
"EIGHT",
"project",
"Getting",
"Started",
"WebGL",
"Local",
"Geometric",
"Physics"
],
"operatorOverloading": true,
"author": "David Geo Holmes"
}
body {
background-color: white;
}
const e1 = EIGHT.Geometric3.e1();
const e2 = EIGHT.Geometric3.e2();
const rotor = EIGHT.Geometric3.one();
/**
* The radis of the sphere.
*/
const R = 2;
export function gapRatio(X: EIGHT.Geometric3): number {
const d = r(X);
return (2 * Math.PI * d / C(d)) - 1;
}
export function e_r(X: EIGHT.Geometric3): EIGHT.Geometric3 {
rotor.rotorFromDirections(e1, X);
return e1.clone().rotate(rotor);
}
export function e_θ(X: EIGHT.Geometric3): EIGHT.Geometric3 {
return e_r(X).rotate((e1^e2).scale(-Math.PI/4).exp());
}
export function r(X: EIGHT.Geometric3): number {
return Math.sqrt(X.x * X.x + X.y * X.y);
}
function C(r: number) {
return 2 * Math.PI * Math.sin(r / R);
}
export function turningPerAngle(r: number) {
return 1 - Math.cos(r / R);
}
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() {
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.trackball.noPan = true
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(e2).scale(3)
this.camera.look.copy(origin)
this.camera.up.copy(-e3)
}
sideView(): void {
this.camera.eye.copy(e3).scale(3)
this.camera.look.copy(origin)
this.camera.up.copy(e2)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment