Skip to content

Instantly share code, notes, and snippets.

@mathdoodle
Last active September 6, 2015 03:55
Show Gist options
  • Save mathdoodle/b903a8175bcea71b8660 to your computer and use it in GitHub Desktop.
Save mathdoodle/b903a8175bcea71b8660 to your computer and use it in GitHub Desktop.
BARN
{
"uuid": "d5b74e6d-e0e8-44e7-a691-798ca0939a7e",
"description": "BARN",
"dependencies": {
"DomReady": "latest",
"davinci-blade": "1.1.1",
"davinci-eight": "latest"
},
"operatorOverloading": true
}
interface ITrackBall {
enabled: boolean;
rotateSpeed: number;
zoomSpeed: number;
panSpeed: number;
noRotate: boolean;
noZoom: boolean;
noPan: boolean;
staticMoving: boolean;
dynamicDampingFactor: number;
minDistance: number;
maxDistance: number;
keys: number[];
update(): void;
handleResize(): void;
setSize(width: number, height: number): void;
}
var trackball = function(object: {position: EIGHT.Vector3; up: EIGHT.Vector3; lookAt: (where: EIGHT.Cartesian3)=>void}, wnd: Window): ITrackBall {
var document = wnd.document;
var documentElement = document.documentElement;
var screen = {left: 0, top: 0, width: 0, height: 0};
var api: ITrackBall = {
// Provide initial values for all interface properties.
enabled: true,
rotateSpeed: 1.0,
zoomSpeed: 1.2,
panSpeed: 0.3,
noRotate: false,
noZoom: false,
noPan: false,
staticMoving: false,
dynamicDampingFactor: 0.2,
minDistance: 0,
maxDistance: Infinity,
keys: [65, 83, 68], // A S D
/**
* update
*/
update() {
// target is initially the origin, so eye becomes the object position.
eye.subVectors(object.position, target);
if (!api.noRotate) {
rotateCamera();
}
if (!api.noZoom) {
zoomCamera();
}
if (!api.noPan) {
panCamera();
}
object.position.addVectors(target, eye);
checkDistances();
object.lookAt(target);
if (lastPosition.quadranceTo(object.position) > EPS) {
// TODO dispatchEvent( changeEvent );
lastPosition.copy(object.position);
}
},
/**
* Determine and cache screen coordinates
*/
handleResize() {
var box = documentElement.getBoundingClientRect();
screen.left = box.left + wnd.pageXOffset - documentElement.clientLeft;
screen.top = box.top + wnd.pageYOffset - documentElement.clientTop;
screen.width = box.width;
screen.height = box.height;
},
/**
*
*/
setSize(width: number, height: number) {
screen.width = width;
screen.height = height;
}
};
var getMouseOnScreen: (pageX: number, pageY: number) => EIGHT.Vector2 = (function() {
var vector = new EIGHT.Vector2();
return function (pageX: number, pageY: number) {
vector.set((pageX - screen.left) / screen.width, (pageY - screen.top) / screen.height);
return vector;
};
}());
var getMouseOnCircle: (pageX: number, pageY: number) => EIGHT.Vector2 = ( function () {
var vector = new EIGHT.Vector2();
return function (pageX: number, pageY: number) {
vector.set(
( ( pageX - screen.width * 0.5 - screen.left ) / ( screen.width * 0.5 ) ),
( ( screen.height + 2 * ( screen.top - pageY ) ) / screen.width )
);
return vector;
};
}() );
// The following variables are the state for the camera.
var moveCurr = new EIGHT.Vector2();
var movePrev = new EIGHT.Vector2();
var eye = new EIGHT.Vector3();
var target = new EIGHT.Vector3();
var lastAxis = new EIGHT.Vector3();
var lastAngle = 0;
/**
* The effect of this function is to manipulate the up vector of the 'object' argument.
* rotateCamera is an immediately executed function that returns another function.
* This seems rather heavyweight but it does save on some temporaries being frequently created.
* This seems to be a common pattern.
* We might alternatively create a class and keep an instance around.
*/
var rotateCamera = (function() {
var axis = new EIGHT.Vector3();
var quaternion = new EIGHT.Quaternion();
var eyeDirection = new EIGHT.Vector3();
var objectUpDirection = new EIGHT.Vector3();
var objectSidewaysDirection = new EIGHT.Vector3();
var moveDirection = new EIGHT.Vector3();
var angle: number;
return function () {
moveDirection.set( moveCurr.x - movePrev.x, moveCurr.y - movePrev.y, 0 );
angle = moveDirection.magnitude();
if (angle) {
eye.copy( object.position ).sub(target);
eyeDirection.copy(eye).normalize();
objectUpDirection.copy(object.up).normalize();
objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
objectUpDirection.setMagnitude(moveCurr.y - movePrev.y);
objectSidewaysDirection.setMagnitude(moveCurr.x - movePrev.x);
moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
axis.crossVectors( moveDirection, eye ).normalize();
angle *= api.rotateSpeed;
quaternion.setFromAxisAngle( axis, angle );
eye.applyQuaternion( quaternion );
object.up.applyQuaternion( quaternion );
lastAxis.copy( axis );
lastAngle = angle;
}
else if ( !api.staticMoving && lastAngle ) {
lastAngle *= Math.sqrt( 1.0 - api.dynamicDampingFactor );
eye.copy(object.position).sub(target);
quaternion.setFromAxisAngle(lastAxis, lastAngle);
eye.applyQuaternion( quaternion );
object.up.applyQuaternion(quaternion);
}
movePrev.copy(moveCurr);
};
}());
var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
var state = STATE.NONE;
var prevState = STATE.NONE;
var zoomStart = new EIGHT.Vector2();
var zoomEnd = new EIGHT.Vector2();
var touchZoomDistanceStart = 0;
var touchZoomDistanceEnd = 0;
var panStart = new EIGHT.Vector2();
var panEnd = new EIGHT.Vector2();
/**
* zoomCamera manipulates the eye variable
*/
var zoomCamera = function () {
var factor;
if (state === STATE.TOUCH_ZOOM_PAN) {
factor = touchZoomDistanceStart / touchZoomDistanceEnd;
touchZoomDistanceStart = touchZoomDistanceEnd;
eye.multiplyScalar( factor );
}
else {
factor = 1.0 + ( zoomEnd.y - zoomStart.y ) * api.zoomSpeed;
if ( factor !== 1.0 && factor > 0.0 ) {
eye.multiplyScalar( factor );
if ( api.staticMoving ) {
zoomStart.copy( zoomEnd );
}
else {
zoomStart.y += ( zoomEnd.y - zoomStart.y ) * api.dynamicDampingFactor;
}
}
}
};
/**
* panCamera manipulates the objects position property.
*/
var panCamera = (function() {
var mouseChange = new EIGHT.Vector2(),
objectUp = new EIGHT.Vector3(),
pan = new EIGHT.Vector3();
return function () {
mouseChange.copy(panEnd).sub(panStart);
if (mouseChange.quaditude()) {
mouseChange.multiplyScalar(eye.magnitude() * api.panSpeed);
pan.copy(eye).cross(object.up).setMagnitude(mouseChange.x);
pan.add( objectUp.copy(object.up).setMagnitude(mouseChange.y));
object.position.add( pan );
target.add( pan );
if (api.staticMoving ) {
panStart.copy(panEnd);
}
else {
panStart.add( mouseChange.subVectors( panEnd, panStart ).multiplyScalar( api.dynamicDampingFactor ) );
}
}
};
}());
var checkDistances = function () {
if ( !api.noZoom || !api.noPan ) {
if (eye.quaditude() > api.maxDistance * api.maxDistance) {
object.position.addVectors(target, eye.setMagnitude(api.maxDistance));
}
if (eye.quaditude() < api.minDistance * api.minDistance) {
object.position.addVectors(target, eye.setMagnitude(api.minDistance));
}
}
};
var EPS = 0.000001;
var lastPosition = new EIGHT.Vector3();
// events
var changeEvent = { type: 'change' };
var startEvent = { type: 'start' };
var endEvent = { type: 'end' };
// for reset
var target0 = target.clone();
var position0 = object.position.clone();
var up0 = object.up.clone();
var reset = function () {
state = STATE.NONE;
prevState = STATE.NONE;
target.copy( target0 );
object.position.copy( position0 );
object.up.copy( up0 );
eye.subVectors( object.position, target );
object.lookAt( target );
// TODO dispatchEvent( changeEvent );
lastPosition.copy( object.position );
};
// listeners
function keydown( event ) {
if ( api.enabled === false )
return;
wnd.removeEventListener( 'keydown', keydown );
prevState = state;
if ( state !== STATE.NONE ) {
return;
}
else if ( event.keyCode === api.keys[ STATE.ROTATE ] && !api.noRotate ) {
state = STATE.ROTATE;
}
else if ( event.keyCode === api.keys[ STATE.ZOOM ] && !api.noZoom ) {
state = STATE.ZOOM;
}
else if ( event.keyCode === api.keys[ STATE.PAN ] && !api.noPan ) {
state = STATE.PAN;
}
}
function keyup(event) {
if (api.enabled === false)
return;
state = prevState;
wnd.addEventListener('keydown', keydown, false);
}
function mousedown(event) {
if (api.enabled === false)
return;
event.preventDefault();
event.stopPropagation();
if (state === STATE.NONE) {
state = event.button;
}
if (state === STATE.ROTATE && !api.noRotate) {
moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
movePrev.copy(moveCurr);
}
else if (state === STATE.ZOOM && !api.noZoom ) {
zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
zoomEnd.copy(zoomStart);
}
else if (state === STATE.PAN && !api.noPan) {
panStart.copy(getMouseOnScreen(event.pageX, event.pageY));
panEnd.copy(panStart);
}
document.addEventListener('mousemove', mousemove, false);
document.addEventListener('mouseup', mouseup, false);
// TODO dispatchEvent(startEvent);
}
function mousemove( event ) {
if (api.enabled === false)
return;
event.preventDefault();
event.stopPropagation();
if (state === STATE.ROTATE && !api.noRotate ) {
movePrev.copy(moveCurr);
moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
}
else if (state === STATE.ZOOM && !api.noZoom) {
zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY));
} else if (state === STATE.PAN && !api.noPan) {
panEnd.copy(getMouseOnScreen(event.pageX, event.pageY));
}
}
function mouseup(event) {
if (api.enabled === false)
return;
event.preventDefault();
event.stopPropagation();
state = STATE.NONE;
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
// TODO dispatchEvent( endEvent );
}
function mousewheel(event: MouseWheelEvent) {
if (api.enabled === false)
return;
event.preventDefault();
event.stopPropagation();
var delta = 0;
if (event.wheelDelta) { // WebKit / Opera / Explorer 9
delta = event.wheelDelta / 40;
}
else if (event.detail) { // Firefox
delta = - event.detail / 3;
}
zoomStart.y += delta * 0.01;
// dispatchEvent( startEvent );
// dispatchEvent( endEvent );
}
function touchstart( event ) {
if (api.enabled === false )
return;
switch (event.touches.length) {
case 1:
state = STATE.TOUCH_ROTATE;
moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
movePrev.copy(moveCurr);
break;
case 2:
state = STATE.TOUCH_ZOOM_PAN;
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
touchZoomDistanceEnd = touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
panStart.copy( getMouseOnScreen( x, y ) );
panEnd.copy(panStart);
break;
default:
state = STATE.NONE;
}
// dispatchEvent( startEvent );
}
function touchmove(event) {
if (api.enabled === false)
return;
event.preventDefault();
event.stopPropagation();
switch (event.touches.length) {
case 1:
movePrev.copy(moveCurr);
moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
break;
case 2:
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
panEnd.copy( getMouseOnScreen( x, y ) );
break;
default:
state = STATE.NONE;
}
}
function touchend( event ) {
if (api.enabled === false)
return;
switch (event.touches.length) {
case 1:
movePrev.copy(moveCurr);
moveCurr.copy(getMouseOnCircle(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY));
break;
case 2:
touchZoomDistanceStart = touchZoomDistanceEnd = 0;
var x = (event.touches[ 0 ].pageX + event.touches[ 1 ].pageX) / 2;
var y = (event.touches[ 0 ].pageY + event.touches[ 1 ].pageY) / 2;
panEnd.copy(getMouseOnScreen(x, y));
panStart.copy(panEnd);
break;
}
state = STATE.NONE;
// dispatchEvent( endEvent );
}
// This works, bit we don't unhook it.
documentElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
documentElement.addEventListener( 'mousedown', mousedown, false );
documentElement.addEventListener( 'mousewheel', mousewheel, false );
documentElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
documentElement.addEventListener( 'touchstart', touchstart, false );
documentElement.addEventListener( 'touchend', touchend, false );
documentElement.addEventListener( 'touchmove', touchmove, false );
wnd.addEventListener( 'keydown', keydown, false );
wnd.addEventListener( 'keyup', keyup, false );
api.handleResize();
// force an update at start
api.update();
return api;
};
DomReady.ready(function() {
try {
main();
}
catch(e) {
console.error(e);
}
});
/**
* Standard basis vector in the x-axis direction.
*/
var e1 = blade.e3ga.e1;
/**
* Standard basis vector in the y-axis direction.
*/
var e2 = blade.e3ga.e2;
/**
* Standard basis vector in the z-axis direction.
*/
var e3 = blade.e3ga.e3;
/**
* Returns the cosine of a number.
*/
var cos = blade.universals.cos;
/**
* Returns e (the base of natural logarithms) raised to a power.
*/
var exp = blade.universals.exp;
/**
* Returns the sine of a number.
*/
var sin = blade.universals.sin;
/**
* S.I. units of measure.
*/
var kilogram = blade.e3ga.units.kilogram;
var meter = blade.e3ga.units.meter;
var second = blade.e3ga.units.second;
var hertz = blade.e3ga.units.hertz;
class BarnMesh extends EIGHT.DefaultAttribProvider {
private aVertexPositionArray: Float32Array;
constructor() {
super();
}
draw(context: WebGLRenderingContext) {
context.drawArrays(context.TRIANGLES, 0, this.aVertexPositionArray.length / 3);
}
dots(vertex: (x: number, y: number, z: number) => void, point: (a: number) => void) {
}
wire(vertex: (x: number, y: number, z: number) => void, line: (a: number, b: number) => void) {
}
mesh(vertex: (x: number, y: number, z: number) => void, face: (a: number, b: number, c: number) => void) {
// You have freedom to compute vertices and faces how you want, but return the results in canonical form.
vertex(-0.5, 0.0, -1.0);
vertex( 0.5, 0.0, -1.0);
vertex( 0.5, 1.0, -1.0);
vertex( 0.0, 1.5, -1.0);
vertex(-0.5, 1.0, -1.0);
vertex(-0.5, 0.0, 1.0);
vertex( 0.5, 0.0, 1.0);
vertex( 0.5, 1.0, 1.0);
vertex( 0.0, 1.5, 1.0);
vertex(-0.5, 1.0, 1.0);
face(1, 0, 2);
face(2, 0, 4);
face(2, 4, 3);
face(5, 6, 7);
face(5, 7, 9);
face(9, 7, 8);
face(6, 1, 2);
face(6, 2, 7);
face(9, 0, 5);
face(9, 4, 0);
face(8, 3, 4);
face(8, 4, 9);
face(7, 2, 3);
face(7, 3, 8);
face(5, 0, 1);
face(5, 1, 6);
}
update(attributes: EIGHT.ShaderVariableDecl[]) {
var faceList: {a: number; b: number; c: number}[] = [];
var vertexList: blade.Euclidean3[] = [];
function face(a: number, b: number, c: number) {
faceList.push({a: a, b: b, c: c});
}
function vertex(x: number, y: number, z: number) {
vertexList.push(new blade.Euclidean3(0, x, y, z, 0, 0, 0, 0));
}
this.mesh(vertex, face);
var vertices: number[] = [];
var normals: number[] = [];
var elements: number[] = [];
faceList.forEach(function(face) {
elements.push(face.a);
elements.push(face.b);
elements.push(face.c);
var vertexA = vertexList[face.a];
vertices.push(vertexA.x);
vertices.push(vertexA.y);
vertices.push(vertexA.z);
var vertexB = vertexList[face.b];
vertices.push(vertexB.x);
vertices.push(vertexB.y);
vertices.push(vertexB.z);
var vertexC = vertexList[face.c];
vertices.push(vertexC.x);
vertices.push(vertexC.y);
vertices.push(vertexC.z);
});
this.aVertexPositionArray = new Float32Array(vertices);
return super.update(attributes);
}
getAttribArray(name: string): {usage: EIGHT.DataUsage; data: Float32Array} {
switch(name) {
case 'aVertexPosition': {
return {usage: EIGHT.DataUsage.STATIC_DRAW, data: this.aVertexPositionArray};
}
break;
default: {
return super.getAttribArray(name);
}
}
}
getAttribMeta(): EIGHT.AttribMetaInfos {
return super.getAttribMeta();
}
hasElementArray(): boolean {
return super.hasElementArray();
}
getElementArray(): {usage: EIGHT.DataUsage; data: Uint16Array} {
return super.getElementArray();
}
}
<!doctype html>
<html>
<head>
<style>
/* STYLE-MARKER */
</style>
<script id='vs' type='x-shader/x-vertex'>
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal;
uniform vec3 uColor;
uniform mat4 uModelMatrix;
uniform mat3 uNormalMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;
uniform vec3 uAmbientLight;
uniform vec3 uDirectionalLightColor;
uniform vec3 uDirectionalLightDirection;
varying highp vec4 vColor;
varying highp vec3 vLight;
void main(void) {
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0);
vColor = vec4(uColor, 1.0);
vec3 L = normalize(uDirectionalLightDirection);
vec3 N = normalize(uNormalMatrix * aVertexNormal);
float cosineFactor = max(dot(N,L), 0.0);
vLight = uAmbientLight + cosineFactor * uDirectionalLightColor;
}
</script>
<script id='fs' type='x-shader/x-fragment'>
varying highp vec4 vColor;
varying highp vec3 vLight;
void main(void) {
gl_FragColor = vec4(vColor.xyz * vLight, vColor.a);
}
</script>
<!-- SCRIPTS-MARKER -->
</head>
<body>
<script>
// LIBS-MARKER
</script>
<script>
// CODE-MARKER
</script>
<canvas id='my-canvas'>
Your browser does not support the canvas element.
</canvas>
</body>
</html>
/**
* The angle of tilt of the precessing vector.
*/
var tiltAngle = 30 * Math.PI / 180;
var S = exp(-(e2 ^ e1) * tiltAngle / 2);
var B = e3 ^ e1;
/**
* The period of the motions in the animation.
*/
var T = 10 * second;
/**
* The frequency of the motions in the animation.
*/
var f = (1 / T);
var omega = 2 * Math.PI * f;
class Camera {
private perspective: EIGHT.Perspective;
constructor(perspective: EIGHT.Perspective) {
this.perspective = perspective;
}
get position(): EIGHT.Vector3 {
return this.perspective.eye;
}
get up(): EIGHT.Vector3 {
return this.perspective.up;
}
lookAt(where: EIGHT.Cartesian3){
this.perspective.look = where;
}
}
function main() {
var scene = EIGHT.scene();
var canvas = <HTMLCanvasElement>document.getElementById('my-canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var renderer = EIGHT.renderer(canvas);
renderer.clearColor(0.2, 0.2, 0.2, 1.0);
var monitor = EIGHT.webgl(canvas).addContextUser(renderer).addContextUser(scene).start();
var gl = monitor.context;
var camera = EIGHT.perspective().setAspect(canvas.clientWidth / canvas.clientHeight).setEye(2 * e1 + (4/3) * e2 + (8/3) * e3).setFov(75*Math.PI/180);
var program = EIGHT.programFromScripts('vs','fs');
var barnMesh = new EIGHT.GeometryAdapter(new EIGHT.BarnGeometry(), {drawMode: EIGHT.DrawMode.TRIANGLES});
monitor.addContextUser(barnMesh);
//var barnMesh = new BarnMesh();
var model = new EIGHT.Node();
var barn = EIGHT.primitive(barnMesh, program, model);
//var barn = EIGHT.primitive(barnMesh, EIGHT.smartProgram(barnMesh.getAttribMeta(),[model.getUniformMeta(), ambients.getUniformMeta()]), model);
//console.log(barn.program.vertexShader);
//console.log(barn.program.fragmentShader);
barn.model.color = new EIGHT.Vector3([1, 1, 0]);
scene.add(barn);
var t = trackball(new Camera(camera), window);
t.setSize(canvas.clientWidth, canvas.clientHeight);
//console.log(JSON.stringify(aLight.getUniformData()));
scene.uniform3f('uAmbientLight',0.3, 0.3, 0.3);
scene.uniform3f('uDirectionalLightColor', 0.7, 0.7, 0.7);
scene.uniform3f('uDirectionalLightDirection', 2, 3, 5);
EIGHT.animation((time: number) => {
camera.accept(scene);
var theta = omega * (time * second);
// simple harmonic motion
//barn.model.position.copy(1.2 * sin(theta) * e2);
// precession
var R = exp(-B * theta / 2);
//barn.model.attitude.copy(R * S * ~R);
// rotation
var R = exp(-B * theta / 2);
barn.model.attitude.copy(R);
// orbit
//barn.model.position.copy(2 * cos(theta) * e1 - sin(theta) * (e3 - 0.5 * e2));
t.update();
renderer.render(scene);
}).start();
}
body { margin: 0; }
canvas { width: 100%; height: 100% }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment