Last active
August 31, 2016 23:07
-
-
Save mathdoodle/4fc06097889c18ead39cca366720d580 to your computer and use it in GitHub Desktop.
Geometry Primer
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 interface ArcBall { | |
addRef(): number; | |
release(): number; | |
} | |
export default function arcBall(element: HTMLElement): ArcBall { | |
const mousedown = function(ev: MouseEvent) { | |
} | |
const mousemove = function(ev: MouseEvent) { | |
} | |
const mouseup = function(ev: MouseEvent) { | |
} | |
element.addEventListener('mousedown', mousedown); | |
element.addEventListener('mousemove', mousemove); | |
element.addEventListener('mouseup', mouseup); | |
let refCount = 1; | |
const addRef = function(): number { | |
refCount++; | |
return refCount; | |
} | |
const release = function(): number { | |
refCount--; | |
if (refCount === 0) { | |
element.removeEventListener('mousedown', mousedown); | |
element.removeEventListener('mousemove', mousemove); | |
element.removeEventListener('mouseup', mouseup); | |
} | |
return refCount; | |
} | |
const that : ArcBall = { | |
addRef, | |
release | |
}; | |
return that; | |
} |
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 interface Color { | |
r: number; | |
g: number; | |
b: number; | |
} | |
export function color(red: number, green: number, blue: number) { | |
const that: Color = { | |
get r() { | |
return red; | |
}, | |
get g() { | |
return green; | |
}, | |
get b() { | |
return blue; | |
} | |
}; | |
return that; | |
} | |
export const red = color(1, 0, 0); | |
export const green = color(0, 1, 0); | |
export const blue = color(0, 0, 1); |
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
/** | |
* Displays an exception by writing it to a <pre> element. | |
*/ | |
export default function displayError(e: any) { | |
const stderr = <HTMLPreElement>document.getElementById('my-output') | |
stderr.style.color = "#FF0000" | |
stderr.innerHTML = `${e}` | |
} |
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 interface Geometric3 { | |
a: number; | |
x: number; | |
y: number; | |
z: number; | |
copy(M: Geometric3): void; | |
normalize(): Geometric3; | |
} | |
function multivector(a: number, x: number, y: number, z: number): Geometric3 { | |
const that: Geometric3 = { | |
get a() { | |
return a; | |
}, | |
get x() { | |
return x; | |
}, | |
get y() { | |
return y; | |
}, | |
get z() { | |
return z; | |
}, | |
copy(M: Geometric3): void { | |
a = 0; | |
x = M.x; | |
y = M.y; | |
z = M.z; | |
}, | |
normalize(): Geometric3 { | |
const L = Math.sqrt(x * x + y * y + z * z); | |
// This is a bit hacky. | |
if (L !== 0) { | |
x = x / L; | |
y = y / L; | |
z = z / L; | |
} | |
return that; | |
} | |
}; | |
return that; | |
} | |
export function vec(x: number, y: number, z: number): Geometric3 { | |
return multivector(0, x, y, z); | |
} | |
export function scalar(a: number): Geometric3 { | |
return multivector(a, 0, 0, 0); | |
} |
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 {Geometric3, scalar, vec} from './Geometric3'; | |
import {} from './Color'; | |
export enum BeginMode { | |
POINTS = 0x0000, | |
LINES = 0x0001, | |
LINE_LOOP = 0x0002, | |
LINE_STRIP = 0x0003, | |
TRIANGLES = 0x0004, | |
TRIANGLE_STRIP = 0x0005, | |
TRIANGLE_FAN = 0x0006 | |
} | |
export enum Target { | |
ARRAY_BUFFER = 0x8892, | |
ELEMENT_ARRAY_BUFFER = 0x8893, | |
ARRAY_BUFFER_BINDING = 0x8894, | |
ELEMENT_ARRAY_BUFFER_BINDING = 0x8895 | |
} | |
export enum Usage { | |
STREAM_DRAW = 0x88E0, | |
STATIC_DRAW = 0x88E4, | |
DYNAMIC_DRAW = 0x88E8 | |
} | |
/** | |
* A wrapper around the WebGLRenderingContext. | |
*/ | |
export interface Engine { | |
gl: WebGLRenderingContext | |
} | |
export interface Material { | |
gl: WebGLRenderingContext; | |
program: WebGLProgram; | |
use(): void; | |
} | |
export interface Geometry { | |
bind(): void; | |
configAttribs(material: Material): void; | |
draw(): void; | |
disableAttribs(material: Material): void; | |
enableAttribs(material: Material): void; | |
unbind(): void; | |
upload(): void; | |
} | |
export interface Mesh { | |
X: Geometric3; | |
R: Geometric3; | |
N: Geometric3; | |
K: Geometric3; | |
render(): void; | |
} | |
/** | |
* Creates a WebGLProgram with compiled and linked shaders. | |
*/ | |
function createProgram(gl: WebGLRenderingContext, vertexShader: string, fragmentShader: string): WebGLProgram { | |
// TODO: Proper cleanup if we throw an error at any point. | |
const vs = gl.createShader(gl.VERTEX_SHADER) | |
gl.shaderSource(vs, vertexShader) | |
gl.compileShader(vs) | |
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) { | |
throw new Error(gl.getShaderInfoLog(vs)) | |
} | |
const fs = gl.createShader(gl.FRAGMENT_SHADER) | |
gl.shaderSource(fs, fragmentShader) | |
gl.compileShader(fs) | |
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) { | |
throw new Error(gl.getShaderInfoLog(fs)) | |
} | |
const program = gl.createProgram() | |
gl.attachShader(program, vs) | |
gl.attachShader(program, fs) | |
gl.linkProgram(program) | |
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | |
throw new Error(gl.getProgramInfoLog(program)) | |
} | |
return program | |
} | |
export function material(gl: WebGLRenderingContext, vs: string, fs: string): Material { | |
const program = createProgram(gl, vs, fs); | |
const use = function() { | |
gl.useProgram(program) | |
} | |
const that: Material = { | |
get gl() { | |
return gl; | |
}, | |
get program() { | |
return program; | |
}, | |
use | |
} | |
return that; | |
} | |
export function mesh(geometry: Geometry, material: Material): Mesh { | |
const X = vec(0, 0, 0); | |
const R = scalar(1); | |
const N = vec(0, 0, 0); | |
const K = vec(0, 0, 0); | |
const render = function() { | |
material.use() | |
const uPosition = material.gl.getUniformLocation(material.program, 'uPosition'); | |
material.gl.uniform3f(uPosition, X.x, X.y, X.z); | |
const uN = material.gl.getUniformLocation(material.program, 'uN'); | |
material.gl.uniform3f(uN, N.x, N.y, N.z); | |
const uK = material.gl.getUniformLocation(material.program, 'uK'); | |
material.gl.uniform3f(uK, K.x, K.y, K.z); | |
geometry.upload() | |
geometry.enableAttribs(material) | |
geometry.configAttribs(material) | |
geometry.bind() | |
geometry.draw() | |
geometry.unbind() | |
geometry.disableAttribs(material) | |
}; | |
const mesh: Mesh = { | |
get X() { | |
return X; | |
}, | |
set X(value) { | |
X.copy(value); | |
}, | |
get R() { | |
return R; | |
}, | |
set R(value) { | |
R.copy(value); | |
}, | |
get N() { | |
return N; | |
}, | |
set N(value) { | |
N.copy(value); | |
}, | |
get K() { | |
return K; | |
}, | |
set K(value) { | |
K.copy(value); | |
}, | |
render | |
} | |
return Object.freeze(mesh); | |
} | |
function getWebGLContext(canvas: HTMLCanvasElement): WebGLRenderingContext { | |
if (!(canvas instanceof HTMLCanvasElement)) { | |
throw new Error("canvas must be an HTMLCanvasElement") | |
} | |
let context: WebGLRenderingContext = null | |
try { | |
// Try to grab the standard context. If it fails, fallback to experimental. | |
context = canvas.getContext('webgl') || canvas.getContext('experimental-webgl') | |
} | |
catch(e) { | |
} | |
if (context) { | |
return context | |
} | |
else { | |
throw new Error("Unable to get WebGL context. Your browser may not support it.") | |
} | |
} | |
export function engine(canvasId: string): Engine { | |
const canvas = <HTMLCanvasElement>document.getElementById(canvasId) | |
if (!canvas) { | |
throw new Error(`Unable to get element with id '${canvasId}'`) | |
} | |
const gl = getWebGLContext(canvas) | |
const that: Engine = { | |
get gl() { | |
return gl; | |
} | |
} | |
return that; | |
} |
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> | |
<style> | |
/* STYLE-MARKER */ | |
</style> | |
<script src="https://jspm.io/system.js"></script> | |
<!-- SHADERS-MARKER --> | |
<!-- SCRIPTS-MARKER --> | |
</head> | |
<body> | |
<canvas id='my-canvas' width='500' height='500'> | |
Your browser does not support the HTML5 canvas element. | |
</canvas> | |
<pre id='my-output'></pre> | |
<script> | |
// CODE-MARKER | |
</script> | |
<script> | |
System.import('./script.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
{ | |
"name": "copy-of-geometry-2d-webgl", | |
"version": "0.1.0", | |
"description": "Geometry Primer", | |
"dependencies": {}, | |
"keywords": [ | |
"WebGL", | |
"shaders", | |
"low level", | |
"GLSL", | |
"Graphics", | |
"functions", | |
"2D", | |
"Introduction", | |
"Geometric", | |
"Algebra", | |
"Geometry", | |
"Plane" | |
], | |
"operatorOverloading": true, | |
"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
import displayError from './displayError' | |
/** | |
* Catches exceptions thrown in the animation callback and displays them. | |
*/ | |
export default function requestFrame(callback: FrameRequestCallback): number { | |
const wrapper: FrameRequestCallback = function(time: number) { | |
try { | |
callback(time) | |
} | |
catch(e) { | |
displayError(e) | |
} | |
} | |
return window.requestAnimationFrame(wrapper) | |
} |
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 {engine, material, mesh} from './graphics'; | |
import requestFrame from './requestFrame'; | |
import triangle from './triangle'; | |
import {vec} from './Geometric3'; | |
import arcBall from './ArcBall'; | |
const eng = engine('my-canvas') | |
const gl = eng.gl | |
gl.clearColor(0.0, 0.0, 0.0, 1.0) | |
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight) | |
gl.lineWidth(2) | |
const vs = document.getElementById('shader-vs').innerText | |
const fs = document.getElementById('shader-fs').innerText | |
const tri = triangle(gl, vec(0, 0, 0), vec(1, 0, 0), vec(0, 1, 0)) | |
const m1 = mesh(tri, material(gl, vs, fs)) | |
const m2 = mesh (tri, material(gl, vs, fs)) | |
const mouse = arcBall(gl.canvas); | |
const animate = function() { | |
gl.clear(gl.COLOR_BUFFER_BIT) | |
m1.render() | |
m2.N.copy(vec(1, 0, 0)) | |
m2.K.copy(vec(0, 1, 0)) | |
m2.render() | |
requestFrame(animate) | |
} | |
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
/** | |
* Fragment Shader. | |
* | |
* The primary purpose of this GLSL function is to determine the color and alpha value | |
* of a pixel. This is done by assigning a vec4, interpreted as [red, green, blue, alpha], | |
* to the reserved gl_FragColor variable. | |
*/ | |
/** | |
* The interpolated vertex color. | |
*/ | |
varying mediump vec3 vColor; | |
void main(void) { | |
gl_FragColor = vec4(vColor.r, vColor.g, vColor.b, 1.0); | |
} |
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
/** | |
* Vertex Shader | |
* | |
* The primary purpose of this GLSL function is to determine a vertex position | |
* in space from the specified attribute and uniform parameters. This is done by assigning | |
* a vec4, interpreted as [x, y, z, w], to the reserved gl_Position variable. | |
*/ | |
/** | |
* The position of the vertex relative to uPosition and before applying uAttitude. | |
*/ | |
attribute vec3 aPosition; | |
/** | |
* The color of the vertex. | |
*/ | |
attribute vec3 aColor; | |
/** | |
* The translation (vector) applied to all vertices. | |
*/ | |
uniform vec3 uPosition; | |
/** | |
* The rotation (rotor) applied to all vertices. | |
*/ | |
uniform vec2 uAttitude; | |
uniform vec3 uN; | |
uniform vec3 uK; | |
/** | |
* The color that will be passed to the fragment shader. | |
*/ | |
varying mediump vec3 vColor; | |
highp vec3 reflection(in vec3 v, in vec3 N) { | |
float dotVN = dot(v, N); | |
float dotNN = dot(N, N); | |
if (dotNN != 0.0) { | |
float x = v.x - 2.0 * dotVN * N.x / dotNN; | |
float y = v.y - 2.0 * dotVN * N.y / dotNN; | |
float z = v.z - 2.0 * dotVN * N.z / dotNN; | |
return vec3(x, y, z); | |
} | |
else { | |
return v; | |
} | |
} | |
/** | |
* Returns v + T | |
*/ | |
highp vec3 translate(in vec3 v, in vec3 T) { | |
// We compute the translation by components to be explicit. | |
// Note that WebGL permits the following form. | |
// return v + T; | |
return vec3(v.x + T.x, v.y + T.y, v.z + T.z); | |
} | |
void main(void) { | |
// Calculations are performed using Geometric Algebra. | |
// Note: It is usual in WebGL to use projective coordinates in order to unify | |
// translations and rotations into matrix multiplication | |
vec3 v = translate(reflection(reflection(aPosition, uN), uK), uPosition); | |
gl_Position = vec4(v.x, v.y, v.z, 1.0); | |
vColor = aColor; | |
} |
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 { | |
background-color: #242424; | |
overflow: hidden; | |
} | |
canvas { | |
background-color: white; | |
position: relative; | |
left: 10px; | |
top: 10px; | |
z-index: 2; | |
} | |
pre { | |
position: relative; | |
color: rgb(255, 255, 255); | |
left: 10px; | |
z-index: 1; | |
} |
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 {BeginMode, Geometry, Material, Target, Usage} from './graphics'; | |
import {Geometric3, vec} from './Geometric3'; | |
import {red, green, blue} from './Color'; | |
const BYTES_PER_FLOAT = 4; | |
export interface Triangle extends Geometry { | |
a: Geometric3; | |
b: Geometric3; | |
c: Geometric3; | |
} | |
export default function triangle(gl: WebGLRenderingContext, a: Geometric3, b: Geometric3, c: Geometric3): Triangle { | |
const colorA = red; | |
const colorB = green; | |
const colorC = blue; | |
const data = new Float32Array([ | |
a.x, a.y, colorA.r, colorA.g, colorA.b, | |
b.x, b.y, colorB.r, colorB.g, colorB.b, | |
c.x, c.y, colorC.r, colorC.g, colorC.b | |
]); | |
const vbo = gl.createBuffer(); | |
const bind = function() { | |
gl.bindBuffer(Target.ARRAY_BUFFER, vbo); | |
} | |
const draw = function() { | |
gl.drawArrays(BeginMode.LINE_LOOP, 0, 3); | |
} | |
const unbind = function() { | |
gl.bindBuffer(Target.ARRAY_BUFFER, null); | |
} | |
const configAttribs = function(material: Material) { | |
const aPosition = gl.getAttribLocation(material.program, 'aPosition') | |
const aColor = gl.getAttribLocation(material.program, 'aColor') | |
bind(); | |
const stride = 5 * BYTES_PER_FLOAT; | |
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, stride, 0); | |
gl.vertexAttribPointer(aColor, 3, gl.FLOAT, false, stride, 2 * BYTES_PER_FLOAT); | |
unbind(); | |
} | |
const enableAttribs = function(material: Material) { | |
const aPosition = gl.getAttribLocation(material.program, 'aPosition'); | |
gl.enableVertexAttribArray(aPosition); | |
const aColor = gl.getAttribLocation(material.program, 'aColor'); | |
gl.enableVertexAttribArray(aColor); | |
} | |
const disableAttribs = function(material: Material) { | |
const aPosition = gl.getAttribLocation(material.program, 'aPosition') | |
gl.disableVertexAttribArray(aPosition) | |
const aColor = gl.getAttribLocation(material.program, 'aColor') | |
gl.disableVertexAttribArray(aColor) | |
} | |
const upload = function() { | |
data[0] = a.x; | |
data[1] = a.y; | |
data[2] = colorA.r; | |
data[3] = colorA.g; | |
data[4] = colorA.b; | |
data[5] = b.x; | |
data[6] = b.y; | |
data[7] = colorB.r; | |
data[8] = colorB.g; | |
data[9] = colorB.b; | |
data[10] = c.x; | |
data[11] = c.y; | |
data[12] = colorC.r; | |
data[13] = colorC.g; | |
data[14] = colorC.b; | |
bind(); | |
gl.bufferData(Target.ARRAY_BUFFER, data, Usage.DYNAMIC_DRAW) | |
unbind(); | |
} | |
const geo: Triangle = { | |
get a() { | |
return a; | |
}, | |
get b() { | |
return b; | |
}, | |
get c() { | |
return c; | |
}, | |
bind, | |
configAttribs, | |
draw, | |
disableAttribs, | |
enableAttribs, | |
unbind, | |
upload | |
} | |
upload(); | |
return Object.freeze(geo); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment