Skip to content

Instantly share code, notes, and snippets.

@mathdoodle
Last active February 11, 2018 13:10
Show Gist options
  • Save mathdoodle/8571a36545d10f34bfef to your computer and use it in GitHub Desktop.
Save mathdoodle/8571a36545d10f34bfef to your computer and use it in GitHub Desktop.
Domain Coloring

Domain Coloring

Complex Analysis involves understanding the behavior of functions in the complex plane. However, visualizing a complex-valued function of a complex argument requires 4 dimensions.

This project demonstrates how to visualize a complex function using the WebGL API to perform the rendering.

The argument of the complex number is represented by a color.

The program is very fast because most of the program is executing on the GPU. Most of the TypeScript code is WebGL boilerplate that could be replaced by library code, e.g. EIGHT, or a lightweight 2D WebGL framework (see Euclidean Plane example).

This examples shows the complex function

$$ f(z) = exp(1/z) $$

/**
* Converts a null argument into an exception.
*/
export function denullify<T>(name: string, arg: T | null): T {
if (arg !== null) {
return arg
}
else {
throw new Error(`${name} was null.`)
}
}
/**
* Creates a WebGLProgram with compiled and linked shaders.
* Throws an exception if the creation does not complete normally.
*/
export function createProgram(gl: WebGLRenderingContext, vertexShader: string, fragmentShader: string): WebGLProgram {
const vs = denullify('createShader(VERTEX)', gl.createShader(gl.VERTEX_SHADER))
gl.shaderSource(vs, vertexShader)
gl.compileShader(vs)
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
throw new Error(denullify('shaderInfoLog(VERTEX)', gl.getShaderInfoLog(vs)))
}
const fs = denullify('createShader(FRAGMENT)', gl.createShader(gl.FRAGMENT_SHADER))
gl.shaderSource(fs, fragmentShader)
gl.compileShader(fs)
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
throw new Error(denullify('shaderInfoLog(FRAGMENT)', gl.getShaderInfoLog(fs)))
}
const program = denullify('createProgram', gl.createProgram())
gl.attachShader(program, vs)
gl.attachShader(program, fs)
gl.linkProgram(program)
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error(denullify('programInfoLog', gl.getProgramInfoLog(program)))
}
return program
}
<!doctype html>
<html>
<head>
<base href='/'>
<script src='https://jspm.io/[email protected]'></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id='canvas' width='500' height='500'>
Your browser does not support the HTML5 canvas element.
</canvas>
<script>
System.defaultJSExtensions = true
System.import('./script')
</script>
</body>
</html>
{
"uuid": "bd3ebdfc-67f9-4f70-860b-fac791d38d40",
"description": "Domain Coloring",
"dependencies": {},
"name": "domain-coloring-and-fundamental-theorem-of-algebra",
"version": "0.1.0",
"author": "David Geo Holmes",
"keywords": [
"WebGL",
"Domain coloring",
"mathdoodle"
],
"linting": true,
"hideConfigFiles": true
}
import { createProgram, denullify } from './createProgram'
const canvasElement = <HTMLCanvasElement> document.getElementById('canvas')
const gl = getWebGLContext(canvasElement)
const vs_source = denullify('shader-vs', document.getElementById('shader-vs')).innerText
const fs_source = denullify('shader-fs', document.getElementById('shader-fs')).innerText
const program = createProgram(gl, vs_source, fs_source)
gl.useProgram(program)
const size = 2.0
const vertices: number[] = [
0.0, 0.0, 0.0, // 0 center
-size / 2, -size / 2, 0.0, // 1 lower left
+size / 2, -size / 2, 0.0, // 2 lower right
+size / 2, +size / 2, 0.0, // 3 upper right
-size / 2, +size / 2, 0.0 // 4 upper left
]
const vbo = denullify('createBuffer', gl.createBuffer())
gl.bindBuffer(gl.ARRAY_BUFFER, vbo)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
const indices = [0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 1]
const ibo = denullify('createBuffer', gl.createBuffer())
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STREAM_DRAW)
gl.clearColor(0.5, 0.5, 0.6, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.viewport(0, 0, canvasElement.width, canvasElement.height)
const aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition')
gl.enableVertexAttribArray(aVertexPosition)
gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0)
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0)
function getWebGLContext(canvas: HTMLCanvasElement): WebGLRenderingContext {
// Try to grab the standard context. If it fails, fallback to experimental.
const context = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
if (context) {
return context
}
else {
throw new Error("Unable to initialize WebGL. Your browser may not support it.")
}
}
varying highp vec2 position;
highp vec2 cexp(in highp vec2 z);
highp vec2 cinv(in highp vec2 z);
highp float cmag(in highp vec2 z);
highp float modulus(in highp float x, in highp float m);
highp vec2 cmul(in highp vec2 a, in highp vec2 b);
highp float quad(in highp vec2 z);
highp vec3 rgb2hsv(in highp vec3 c);
highp vec3 hsv2rgb(in highp vec3 c);
highp vec2 f(in highp vec2 z)
{
return cexp(cinv(z));
}
void main(void) {
// position ranges from -1 to +1
highp vec2 z = f(1.0 * vec2(position.x, position.y));
// Division by 2 π puts theta into the range 0..1
highp float theta = atan(z.y, z.x) / (2.0 * 3.14159);
highp float shade = modulus(log2(cmag(z)), 1.0);
gl_FragColor = vec4(shade * hsv2rgb(vec3(theta, 1.0, 1.0)), 1.0);
}
highp vec3 hsv2rgb(in highp vec3 c)
{
highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
highp vec3 rgb2hsv(in highp vec3 c)
{
highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
highp float d = q.x - min(q.w, q.y);
highp float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
highp vec2 cexp(in highp vec2 z)
{
return exp(z.x) * vec2(cos(z.y), sin(z.y));
}
highp vec2 cinv(in highp vec2 z)
{
return vec2(z.x, -z.y) / quad(z);
}
highp float cmag(in highp vec2 z)
{
return sqrt(quad(z));
}
highp float modulus(in highp float x, in highp float y)
{
return x - floor(x / y);
}
highp vec2 cmul(in highp vec2 a, in highp vec2 b)
{
return vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
}
highp float quad(in highp vec2 z)
{
return z.x * z.x + z.y * z.y;
}
attribute vec3 aVertexPosition;
varying vec2 position;
void main(void) {
position = vec2(aVertexPosition);
gl_Position = vec4(position, 0.0, 1.0);
}
body {
background-color: blue;
}
canvas {
background-color: black;
position: absolute;
left: 10px;
top: 10px;
}
{
"allowJs": false,
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"jsx": "react",
"module": "system",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": false,
"sourceMap": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5",
"traceResolution": true
}
{
"rules": {
"array-type": [
true,
"array"
],
"curly": false,
"comment-format": [
true,
"check-space"
],
"eofline": true,
"forin": true,
"jsdoc-format": true,
"new-parens": true,
"no-conditional-assignment": false,
"no-consecutive-blank-lines": true,
"no-construct": true,
"no-for-in-array": true,
"no-inferrable-types": [
true
],
"no-magic-numbers": false,
"no-shadowed-variable": true,
"no-string-throw": true,
"no-trailing-whitespace": [
true,
"ignore-jsdoc"
],
"no-var-keyword": true,
"one-variable-per-declaration": [
true,
"ignore-for-loop"
],
"prefer-const": true,
"prefer-for-of": true,
"prefer-function-over-method": false,
"prefer-method-signature": true,
"radix": true,
"semicolon": [
true,
"never"
],
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"triple-equals": true,
"use-isnan": true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment