Skip to content

Commit

Permalink
Added code
Browse files Browse the repository at this point in the history
  • Loading branch information
Sam Lord committed Oct 5, 2018
0 parents commit c8dda98
Show file tree
Hide file tree
Showing 9 changed files with 391 additions and 0 deletions.
1 change: 1 addition & 0 deletions CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mandelbrot-design.surge.sh
84 changes: 84 additions & 0 deletions canvas-mandlebrot.js
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)
20 changes: 20 additions & 0 deletions index.html
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>
28 changes: 28 additions & 0 deletions libs/gl-matrix-min.js

Large diffs are not rendered by default.

101 changes: 101 additions & 0 deletions libs/gl-utils.js
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
98 changes: 98 additions & 0 deletions script.js
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))
45 changes: 45 additions & 0 deletions shaders/fragment.glsl
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);
}
5 changes: 5 additions & 0 deletions shaders/vertex.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
attribute vec4 aPosition;

void main() {
gl_Position = aPosition;
}
9 changes: 9 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
body {
margin: 0;
overflow: hidden;
}

canvas {
width: 100vw;
height: 100vh;
}

0 comments on commit c8dda98

Please sign in to comment.