Skip to content

Instantly share code, notes, and snippets.

@mathdoodle
Last active August 31, 2016 23:07
Show Gist options
  • Save mathdoodle/4fc06097889c18ead39cca366720d580 to your computer and use it in GitHub Desktop.
Save mathdoodle/4fc06097889c18ead39cca366720d580 to your computer and use it in GitHub Desktop.
Geometry Primer

Geometry Primer

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;
}
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);
/**
* 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}`
}
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);
}
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;
}
<!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>
{
"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"
}
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)
}
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)
/**
* 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);
}
/**
* 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;
}
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;
}
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