-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Sam Lord
committed
Oct 5, 2018
0 parents
commit c8dda98
Showing
9 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
mandelbrot-design.surge.sh |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
class Mandlebrot { | ||
constructor (context) { | ||
this.context = context | ||
this.viewWidth = context.canvas.width | ||
this.viewHeight = context.canvas.height | ||
this.centerX = 0.25 | ||
this.centerY = 0 | ||
this.width = 0.003 | ||
this.height = 0.002 | ||
this.calculate() | ||
} | ||
|
||
static calculateColour (x, y) { | ||
const mandlebrotSteps = (x, y) => { | ||
let iter = 0 | ||
let a = 0 | ||
let b = 0 | ||
while (++iter < 256) { | ||
let newA = a * a - b * b | ||
let newB = 2 * a * b | ||
a = newA + x | ||
b = newB + y | ||
if (a * a + b * b > 4) { | ||
break | ||
} | ||
} | ||
return iter | ||
} | ||
|
||
const val = 255 - mandlebrotSteps(x, y) | ||
return `rgb(${val}, ${val}, ${val})` | ||
} | ||
|
||
clear () { | ||
this.context.fillStyle = 'rgb(255, 255, 255)' | ||
this.context.fillRect(0, 0, width, height) | ||
} | ||
|
||
draw () { | ||
this.context.fillStyle = 'rgb(0, 0, 0)' | ||
for (let x = width - 1; x >= 0; x--) { | ||
for (let y = height - 1; y >= 0; y--) { | ||
this.context.fillStyle = Mandlebrot.calculateColour( | ||
(x * this.scaleX) + this.offsetX, | ||
(y * this.scaleY) + this.offsetY) | ||
this.context.fillRect(x, y, 1, 1) | ||
} | ||
} | ||
} | ||
|
||
calculate () { | ||
this.scaleX = this.width / this.viewWidth | ||
this.scaleY = this.height / this.viewHeight | ||
this.offsetX = this.centerX - this.width / 2 | ||
this.offsetY = this.centerY - this.height / 2 | ||
} | ||
|
||
zoom () { | ||
this.width = this.width * 0.10 | ||
this.height = this.height * 0.10 | ||
} | ||
|
||
step () { | ||
this.clear() | ||
this.draw() | ||
this.zoom() | ||
this.calculate() | ||
} | ||
} | ||
|
||
const canvas = document.querySelector('canvas') | ||
|
||
const width = canvas.width = canvas.clientWidth | ||
const height = canvas.height = canvas.clientHeight | ||
|
||
const context = canvas.getContext('2d') | ||
|
||
const mandlebrot = new Mandlebrot(context) | ||
mandlebrot.step() | ||
|
||
// const frameRate = 60 | ||
// window.setInterval(() => { | ||
// mandlebrot.step() | ||
// }, 1000 / frameRate) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width,initial-scale=1"> | ||
<title>Fractal</title> | ||
<link rel="stylesheet" type="text/css" href="style.css"> | ||
</head> | ||
<body> | ||
<canvas></canvas> | ||
|
||
<script name="shader" data-src="shaders/vertex.glsl" data-type="vertex"></script> | ||
<script name="shader" data-src="shaders/fragment.glsl" data-type="fragment"></script> | ||
|
||
<script src="libs/gl-matrix-min.js"></script> | ||
<script src="libs/gl-utils.js"></script> | ||
|
||
<script src="script.js"></script> | ||
</body> | ||
</html> |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/* global fetch */ | ||
|
||
class GlUtils { | ||
static getGl (canvas, options) { | ||
return canvas.getContext('webgl', options) | ||
} | ||
|
||
static createProgram (gl, vertexShader, fragmentShader) { | ||
// Create and return a shader program | ||
var program = gl.createProgram() | ||
gl.attachShader(program, vertexShader) | ||
gl.attachShader(program, fragmentShader) | ||
gl.linkProgram(program) | ||
|
||
// Check that shader program was able to link to WebGL | ||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | ||
var error = gl.getProgramInfoLog(program) | ||
console.log('Failed to link program: ' + error) | ||
gl.deleteProgram(program) | ||
gl.deleteShader(fragmentShader) | ||
gl.deleteShader(vertexShader) | ||
return null | ||
} | ||
|
||
// Set the vertex and fragment shader to the program for easy access | ||
program.vertexShader = vertexShader | ||
program.fragmentShader = fragmentShader | ||
|
||
// Create buffers for all vertex attributes | ||
program.vertexShader.attributes.forEach(attribute => { | ||
program[attribute.name] = gl.createBuffer() | ||
}) | ||
|
||
return program | ||
} | ||
|
||
static getShader (gl, type, source) { | ||
// Get, compile, and return an embedded shader object | ||
var shader = gl.createShader(type) | ||
gl.shaderSource(shader, source) | ||
gl.compileShader(shader) | ||
|
||
// Check if compiled successfully | ||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | ||
console.error('An error occurred compiling the shaders:', gl.getShaderInfoLog(shader)) | ||
gl.deleteShader(shader) | ||
return null | ||
} | ||
|
||
// Set the attributes, varying, and uniform to shader | ||
shader.attributes = this.attributesFromSource(source) | ||
shader.varyings = this.varyingsFromSource(source) | ||
shader.uniforms = this.uniformsFromSource(source) | ||
return shader | ||
} | ||
|
||
static xFromSource (source, x) { | ||
const regex = new RegExp(`^${x} .*? (\\w+);`, 'g') | ||
let matches = [] | ||
let search | ||
while ((search = regex.exec(source)) !== null) { | ||
// Push first group | ||
matches.push(search[1]) | ||
} | ||
return matches | ||
} | ||
|
||
static attributesFromSource (source) { | ||
return this.xFromSource(source, 'attribute') | ||
} | ||
|
||
static varyingsFromSource (source) { | ||
return this.xFromSource(source, 'varying') | ||
} | ||
|
||
static uniformsFromSource (source) { | ||
return this.xFromSource(source, 'uniform') | ||
} | ||
|
||
static loadShaders (callback) { | ||
const elements = Array.from(document.getElementsByName('shader')) | ||
const requests = elements.map(element => fetch(element.getAttribute('data-src'))) | ||
Promise.all(requests) | ||
.then(responses => Promise.all(responses.map(response => response.text()))) | ||
.then(shaders => { | ||
GlUtils.shaders = shaders.reduce((acc, cur, idx) => { | ||
acc[elements[idx].getAttribute('data-type')] = cur | ||
return acc | ||
}, {}) | ||
try { | ||
callback() | ||
console.log('Started GL successfully') | ||
} catch (error) { | ||
console.error(error) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// Expose GlUtils globally | ||
window.GlUtils = GlUtils |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* global GlUtils */ | ||
|
||
class Mandlebrot { | ||
constructor (canvas) { | ||
// Globals | ||
this.canvas = canvas | ||
this.gl = GlUtils.getGl(this.canvas, { preserveDrawingBuffer: true }) | ||
this.initialTime = Date.now() | ||
} | ||
|
||
init () { | ||
this.initShaders() | ||
this.initGL() | ||
this.setSize() | ||
this.animate() | ||
} | ||
|
||
setSize () { | ||
this.canvas.width = this.canvas.clientWidth | ||
this.canvas.height = this.canvas.clientHeight | ||
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height) | ||
this.draw() | ||
} | ||
|
||
initShaders () { | ||
const vertexShader = GlUtils.getShader(this.gl, this.gl.VERTEX_SHADER, GlUtils.shaders.vertex) | ||
const fragmentShader = GlUtils.getShader(this.gl, this.gl.FRAGMENT_SHADER, GlUtils.shaders.fragment) | ||
this.program = GlUtils.createProgram(this.gl, vertexShader, fragmentShader) | ||
this.gl.useProgram(this.program) | ||
} | ||
|
||
initGL () { | ||
this.gl.clearColor(0, 0, 0, 1) | ||
} | ||
|
||
animate () { | ||
this.tick = () => { | ||
this.time = (Date.now() - this.initialTime) / 1000 | ||
this.draw() | ||
} | ||
|
||
// TODO: animate using tick function | ||
window.setInterval(this.tick, 1000 / 60) | ||
} | ||
|
||
renderBuffers (arrays, vertices) { | ||
this.program.vertexShader.attributes | ||
.forEach(name => { | ||
const attributeLocation = this.gl.getAttribLocation(this.program, name) | ||
this.gl.enableVertexAttribArray(attributeLocation) | ||
this.gl.vertexAttribPointer( | ||
attributeLocation, | ||
arrays[name].size, | ||
this.gl.FLOAT, | ||
false, | ||
vertices.BYTES_PER_ELEMENT * vertices.stride, | ||
vertices.BYTES_PER_ELEMENT * arrays[name].offset) | ||
}) | ||
} | ||
|
||
draw () { | ||
const vertices = new Float32Array([ | ||
// points | ||
-1.0, -1.0, -1.0, +1.0, +1.0, -1.0, +1.0, +1.0 | ||
]) | ||
vertices.stride = 2 | ||
|
||
const arrays = { | ||
aPosition: { | ||
size: 2, | ||
offset: 0 | ||
} | ||
} | ||
|
||
let n = vertices.length / vertices.stride | ||
|
||
const vertexBuffer = this.gl.createBuffer() | ||
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, vertexBuffer) | ||
this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW) | ||
this.renderBuffers(arrays, vertices) | ||
|
||
const uScale = this.gl.getUniformLocation(this.program, 'uScale') | ||
const uHeight = this.gl.getUniformLocation(this.program, 'uHeight') | ||
const uWidth = this.gl.getUniformLocation(this.program, 'uWidth') | ||
this.gl.uniform1f(uScale, 1 + this.time / 10) | ||
this.gl.uniform1f(uHeight, this.canvas.height) | ||
this.gl.uniform1f(uWidth, this.canvas.width) | ||
|
||
this.gl.clear(this.gl.COLOR_BUFFER_BIT) | ||
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, n) | ||
} | ||
} | ||
|
||
const mandlebrot = new Mandlebrot(document.querySelector('canvas')) | ||
|
||
GlUtils.loadShaders(mandlebrot.init.bind(mandlebrot)) | ||
|
||
window.addEventListener('resize', mandlebrot.setSize.bind(mandlebrot)) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
precision highp float; | ||
|
||
uniform float uScale; | ||
uniform float uWidth; | ||
uniform float uHeight; | ||
|
||
void mandelbrot(vec2 c, out int count) { | ||
vec2 z = vec2(0.0, 0.0); | ||
|
||
for (int i = 0; i < 256; i++) { | ||
if (z.x * z.x + z.y * z.y >= 4.0) { | ||
break; | ||
} | ||
count = i; | ||
z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; | ||
} | ||
} | ||
|
||
void scale(vec2 view, vec2 centre, float factor, out vec2 real) { | ||
real = (view / factor) + centre; | ||
} | ||
|
||
void main() { | ||
vec2 resolution = vec2(uWidth, uHeight); | ||
float aspectRatio = uWidth / uHeight; | ||
|
||
// View between -1 and +1 | ||
vec2 view = 2.0 * ((gl_FragCoord.xy / resolution) - 0.5); | ||
|
||
// Adjust for aspect ratio | ||
vec2 normalisedView = vec2(view.x * aspectRatio, view.y); | ||
|
||
vec2 real; | ||
scale(normalisedView, vec2(0.25, 0.0), uScale, real); | ||
|
||
int count; | ||
mandelbrot(real, count); | ||
|
||
float color = float(count); | ||
color /= 256.0; | ||
// depth is always the same in window coordinates | ||
//float z = gl_FragCoord.z; | ||
// gl_FragColor = vec4(x, y, z, 1.0); | ||
gl_FragColor = vec4(color, color, color, 1.0); | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
attribute vec4 aPosition; | ||
|
||
void main() { | ||
gl_Position = aPosition; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
body { | ||
margin: 0; | ||
overflow: hidden; | ||
} | ||
|
||
canvas { | ||
width: 100vw; | ||
height: 100vh; | ||
} |