├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── arcball.js ├── buffer.js ├── controller.js ├── frustum.js ├── shader.js └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.raw 3 | build/ 4 | *.rsf 5 | webgl-util.js 6 | webgl-util.min.js 7 | 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Will Usher 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | FILES=$(filter-out webgl-util.js webgl-util.min.js, $(wildcard *.js)) 2 | 3 | all: webgl-util.min.js 4 | 5 | webgl-util.js: $(FILES) 6 | echo "'use strict';" > $@ 7 | cat $^ >> $@ 8 | 9 | webgl-util.min.js: webgl-util.js 10 | minify $^ -o $@ 11 | 12 | .PHONY: clean 13 | clean: 14 | rm webgl-util.js webgl-util.min.js 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGL Util 2 | 3 | A set of barebones utilities for playing with WebGL, used in some of my side projects: 4 | 5 | - [WebGL Volume Raycaster](https://github.com/Twinklebear/webgl-volume-raycaster) 6 | - [WebGL EWA Splatter](https://github.com/Twinklebear/webgl-ewa-splatter) 7 | - [WebGL Neuron Viewer](https://github.com/Twinklebear/webgl-neuron) 8 | - [WebGL Marching Cubes](https://github.com/Twinklebear/webgl-marching-cubes/) 9 | 10 | -------------------------------------------------------------------------------- /arcball.js: -------------------------------------------------------------------------------- 1 | /* The arcball camera will be placed at the position 'eye', rotating 2 | * around the point 'center', with the up vector 'up'. 'screenDims' 3 | * should be the dimensions of the canvas or region taking mouse input 4 | * so the mouse positions can be normalized into [-1, 1] from the pixel 5 | * coordinates. 6 | */ 7 | var ArcballCamera = function(eye, center, up, zoomSpeed, screenDims) { 8 | var veye = vec3.set(vec3.create(), eye[0], eye[1], eye[2]); 9 | var vcenter = vec3.set(vec3.create(), center[0], center[1], center[2]); 10 | var vup = vec3.set(vec3.create(), up[0], up[1], up[2]); 11 | vec3.normalize(vup, vup); 12 | 13 | var zAxis = vec3.sub(vec3.create(), vcenter, veye); 14 | var viewDist = vec3.len(zAxis); 15 | vec3.normalize(zAxis, zAxis); 16 | 17 | var xAxis = vec3.cross(vec3.create(), zAxis, vup); 18 | vec3.normalize(xAxis, xAxis); 19 | 20 | var yAxis = vec3.cross(vec3.create(), xAxis, zAxis); 21 | vec3.normalize(yAxis, yAxis); 22 | 23 | vec3.cross(xAxis, zAxis, yAxis); 24 | vec3.normalize(xAxis, xAxis); 25 | 26 | this.zoomSpeed = zoomSpeed; 27 | this.invScreen = [1.0 / screenDims[0], 1.0 / screenDims[1]]; 28 | 29 | this.centerTranslation = mat4.fromTranslation(mat4.create(), center); 30 | mat4.invert(this.centerTranslation, this.centerTranslation); 31 | 32 | var vt = vec3.set(vec3.create(), 0, 0, -1.0 * viewDist); 33 | this.translation = mat4.fromTranslation(mat4.create(), vt); 34 | 35 | var rotMat = mat3.fromValues(xAxis[0], xAxis[1], xAxis[2], 36 | yAxis[0], yAxis[1], yAxis[2], 37 | -zAxis[0], -zAxis[1], -zAxis[2]); 38 | mat3.transpose(rotMat, rotMat); 39 | this.rotation = quat.fromMat3(quat.create(), rotMat); 40 | quat.normalize(this.rotation, this.rotation); 41 | 42 | this.camera = mat4.create(); 43 | this.invCamera = mat4.create(); 44 | this.updateCameraMatrix(); 45 | } 46 | 47 | ArcballCamera.prototype.rotate = function(prevMouse, curMouse) { 48 | var mPrev = vec2.set(vec2.create(), 49 | clamp(prevMouse[0] * 2.0 * this.invScreen[0] - 1.0, -1.0, 1.0), 50 | clamp(1.0 - prevMouse[1] * 2.0 * this.invScreen[1], -1.0, 1.0)); 51 | 52 | var mCur = vec2.set(vec2.create(), 53 | clamp(curMouse[0] * 2.0 * this.invScreen[0] - 1.0, -1.0, 1.0), 54 | clamp(1.0 - curMouse[1] * 2.0 * this.invScreen[1], -1.0, 1.0)); 55 | 56 | var mPrevBall = screenToArcball(mPrev); 57 | var mCurBall = screenToArcball(mCur); 58 | // rotation = curBall * prevBall * rotation 59 | this.rotation = quat.mul(this.rotation, mPrevBall, this.rotation); 60 | this.rotation = quat.mul(this.rotation, mCurBall, this.rotation); 61 | 62 | this.updateCameraMatrix(); 63 | } 64 | 65 | ArcballCamera.prototype.zoom = function(amount) { 66 | var vt = vec3.set(vec3.create(), 0.0, 0.0, amount * this.invScreen[1] * this.zoomSpeed); 67 | var t = mat4.fromTranslation(mat4.create(), vt); 68 | this.translation = mat4.mul(this.translation, t, this.translation); 69 | if (this.translation[14] >= -0.2) { 70 | this.translation[14] = -0.2; 71 | } 72 | this.updateCameraMatrix(); 73 | } 74 | 75 | ArcballCamera.prototype.pan = function(mouseDelta) { 76 | var delta = vec4.set(vec4.create(), mouseDelta[0] * this.invScreen[0] * Math.abs(this.translation[14]), 77 | mouseDelta[1] * this.invScreen[1] * Math.abs(this.translation[14]), 0, 0); 78 | var worldDelta = vec4.transformMat4(vec4.create(), delta, this.invCamera); 79 | var translation = mat4.fromTranslation(mat4.create(), worldDelta); 80 | this.centerTranslation = mat4.mul(this.centerTranslation, translation, this.centerTranslation); 81 | this.updateCameraMatrix(); 82 | } 83 | 84 | ArcballCamera.prototype.updateCameraMatrix = function() { 85 | // camera = translation * rotation * centerTranslation 86 | var rotMat = mat4.fromQuat(mat4.create(), this.rotation); 87 | this.camera = mat4.mul(this.camera, rotMat, this.centerTranslation); 88 | this.camera = mat4.mul(this.camera, this.translation, this.camera); 89 | this.invCamera = mat4.invert(this.invCamera, this.camera); 90 | } 91 | 92 | ArcballCamera.prototype.eyePos = function() { 93 | return [this.invCamera[12], this.invCamera[13], this.invCamera[14]]; 94 | } 95 | 96 | ArcballCamera.prototype.eyeDir = function() { 97 | var dir = vec4.set(vec4.create(), 0.0, 0.0, -1.0, 0.0); 98 | dir = vec4.transformMat4(dir, dir, this.invCamera); 99 | dir = vec4.normalize(dir, dir); 100 | return [dir[0], dir[1], dir[2]]; 101 | } 102 | 103 | ArcballCamera.prototype.upDir = function() { 104 | var dir = vec4.set(vec4.create(), 0.0, 1.0, 0.0, 0.0); 105 | dir = vec4.transformMat4(dir, dir, this.invCamera); 106 | dir = vec4.normalize(dir, dir); 107 | return [dir[0], dir[1], dir[2]]; 108 | } 109 | 110 | var screenToArcball = function(p) { 111 | var dist = vec2.dot(p, p); 112 | if (dist <= 1.0) { 113 | return quat.set(quat.create(), p[0], p[1], Math.sqrt(1.0 - dist), 0); 114 | } else { 115 | var unitP = vec2.normalize(vec2.create(), p); 116 | // cgmath is w, x, y, z 117 | // glmatrix is x, y, z, w 118 | return quat.set(quat.create(), unitP[0], unitP[1], 0, 0); 119 | } 120 | } 121 | var clamp = function(a, min, max) { 122 | return a < min ? min : a > max ? max : a; 123 | } 124 | 125 | var pointDist = function(a, b) { 126 | var v = [b[0] - a[0], b[1] - a[1]]; 127 | return Math.sqrt(Math.pow(v[0], 2.0) + Math.pow(v[1], 2.0)); 128 | } 129 | 130 | -------------------------------------------------------------------------------- /buffer.js: -------------------------------------------------------------------------------- 1 | var Buffer = function(capacity, dtype) { 2 | this.len = 0; 3 | this.capacity = capacity; 4 | if (dtype == "uint8") { 5 | this.buffer = new Uint8Array(capacity); 6 | } else if (dtype == "int8") { 7 | this.buffer = new Int8Array(capacity); 8 | } else if (dtype == "uint16") { 9 | this.buffer = new Uint16Array(capacity); 10 | } else if (dtype == "int16") { 11 | this.buffer = new Int16Array(capacity); 12 | } else if (dtype == "uint32") { 13 | this.buffer = new Uint32Array(capacity); 14 | } else if (dtype == "int32") { 15 | this.buffer = new Int32Array(capacity); 16 | } else if (dtype == "float32") { 17 | this.buffer = new Float32Array(capacity); 18 | } else if (dtype == "float64") { 19 | this.buffer = new Float64Array(capacity); 20 | } else { 21 | console.log("ERROR: unsupported type " + dtype); 22 | } 23 | } 24 | 25 | Buffer.prototype.append = function(buf) { 26 | if (this.len + buf.length >= this.capacity) { 27 | var newCap = Math.floor(Math.max(this.capacity * 1.5), this.len + buf.length); 28 | var tmp = new (this.buffer.constructor)(newCap); 29 | tmp.set(this.buffer); 30 | 31 | this.capacity = newCap; 32 | this.buffer = tmp; 33 | } 34 | this.buffer.set(buf, this.len); 35 | this.len += buf.length; 36 | } 37 | 38 | Buffer.prototype.clear = function() { 39 | this.len = 0; 40 | } 41 | 42 | Buffer.prototype.stride = function() { 43 | return this.buffer.BYTES_PER_ELEMENT; 44 | } 45 | 46 | Buffer.prototype.view = function(offset, length) { 47 | return new (this.buffer.constructor)(this.buffer.buffer, offset, length); 48 | } 49 | 50 | -------------------------------------------------------------------------------- /controller.js: -------------------------------------------------------------------------------- 1 | /* The controller can register callbacks for various events on a canvas: 2 | * 3 | * mousemove: function(prevMouse, curMouse, evt) 4 | * receives both regular mouse events, and single-finger drags (sent as a left-click), 5 | * 6 | * press: function(curMouse, evt) 7 | * receives mouse click and touch start events 8 | * 9 | * wheel: function(amount) 10 | * mouse wheel scrolling 11 | * 12 | * pinch: function(amount) 13 | * two finger pinch, receives the distance change between the fingers 14 | * 15 | * twoFingerDrag: function(dragVector) 16 | * two finger drag, receives the drag movement amount 17 | */ 18 | var Controller = function() { 19 | this.mousemove = null; 20 | this.press = null; 21 | this.wheel = null; 22 | this.twoFingerDrag = null; 23 | this.pinch = null; 24 | } 25 | 26 | Controller.prototype.registerForCanvas = function(canvas) { 27 | var prevMouse = null; 28 | var mouseState = [false, false]; 29 | var self = this; 30 | canvas.addEventListener("mousemove", function(evt) { 31 | evt.preventDefault(); 32 | var rect = canvas.getBoundingClientRect(); 33 | var curMouse = [evt.clientX - rect.left, evt.clientY - rect.top]; 34 | if (!prevMouse) { 35 | prevMouse = [evt.clientX - rect.left, evt.clientY - rect.top]; 36 | } else if (self.mousemove) { 37 | self.mousemove(prevMouse, curMouse, evt); 38 | } 39 | prevMouse = curMouse; 40 | }); 41 | 42 | canvas.addEventListener("mousedown", function(evt) { 43 | evt.preventDefault(); 44 | var rect = canvas.getBoundingClientRect(); 45 | var curMouse = [evt.clientX - rect.left, evt.clientY - rect.top]; 46 | if (self.press) { 47 | self.press(curMouse, evt); 48 | } 49 | }); 50 | 51 | canvas.addEventListener("wheel", function(evt) { 52 | evt.preventDefault(); 53 | if (self.wheel) { 54 | self.wheel(-evt.deltaY); 55 | } 56 | }); 57 | 58 | canvas.oncontextmenu = function (evt) { 59 | evt.preventDefault(); 60 | }; 61 | 62 | var touches = {}; 63 | canvas.addEventListener("touchstart", function(evt) { 64 | var rect = canvas.getBoundingClientRect(); 65 | evt.preventDefault(); 66 | for (var i = 0; i < evt.changedTouches.length; ++i) { 67 | var t = evt.changedTouches[i]; 68 | touches[t.identifier] = [t.clientX - rect.left, t.clientY - rect.top]; 69 | if (evt.changedTouches.length == 1 && self.press) { 70 | self.press(touches[t.identifier], evt); 71 | } 72 | } 73 | }); 74 | 75 | canvas.addEventListener("touchmove", function(evt) { 76 | evt.preventDefault(); 77 | var rect = canvas.getBoundingClientRect(); 78 | var numTouches = Object.keys(touches).length; 79 | // Single finger to rotate the camera 80 | if (numTouches == 1) { 81 | if (self.mousemove) { 82 | var t = evt.changedTouches[0]; 83 | var prevTouch = touches[t.identifier]; 84 | var curTouch = [t.clientX - rect.left, t.clientY - rect.top]; 85 | evt.buttons = 1; 86 | self.mousemove(prevTouch, curTouch, evt); 87 | } 88 | } else { 89 | var curTouches = {}; 90 | for (var i = 0; i < evt.changedTouches.length; ++i) { 91 | var t = evt.changedTouches[i]; 92 | curTouches[t.identifier] = [t.clientX - rect.left, t.clientY - rect.top]; 93 | } 94 | 95 | // If some touches didn't change make sure we have them in 96 | // our curTouches list to compute the pinch distance 97 | // Also get the old touch points to compute the distance here 98 | var oldTouches = []; 99 | for (t in touches) { 100 | if (!(t in curTouches)) { 101 | curTouches[t] = touches[t]; 102 | } 103 | oldTouches.push(touches[t]); 104 | } 105 | 106 | var newTouches = []; 107 | for (t in curTouches) { 108 | newTouches.push(curTouches[t]); 109 | } 110 | 111 | // Determine if the user is pinching or panning 112 | var motionVectors = [ 113 | vec2.set(vec2.create(), newTouches[0][0] - oldTouches[0][0], 114 | newTouches[0][1] - oldTouches[0][1]), 115 | vec2.set(vec2.create(), newTouches[1][0] - oldTouches[1][0], 116 | newTouches[1][1] - oldTouches[1][1]) 117 | ]; 118 | var motionDirs = [vec2.create(), vec2.create()]; 119 | vec2.normalize(motionDirs[0], motionVectors[0]); 120 | vec2.normalize(motionDirs[1], motionVectors[1]); 121 | 122 | var pinchAxis = vec2.set(vec2.create(), oldTouches[1][0] - oldTouches[0][0], 123 | oldTouches[1][1] - oldTouches[0][1]); 124 | vec2.normalize(pinchAxis, pinchAxis); 125 | 126 | var panAxis = vec2.lerp(vec2.create(), motionVectors[0], motionVectors[1], 0.5); 127 | vec2.normalize(panAxis, panAxis); 128 | 129 | var pinchMotion = [ 130 | vec2.dot(pinchAxis, motionDirs[0]), 131 | vec2.dot(pinchAxis, motionDirs[1]) 132 | ]; 133 | var panMotion = [ 134 | vec2.dot(panAxis, motionDirs[0]), 135 | vec2.dot(panAxis, motionDirs[1]) 136 | ]; 137 | 138 | // If we're primarily moving along the pinching axis and in the opposite direction with 139 | // the fingers, then the user is zooming. 140 | // Otherwise, if the fingers are moving along the same direction they're panning 141 | if (self.pinch && Math.abs(pinchMotion[0]) > 0.5 && Math.abs(pinchMotion[1]) > 0.5 142 | && Math.sign(pinchMotion[0]) != Math.sign(pinchMotion[1])) 143 | { 144 | // Pinch distance change for zooming 145 | var oldDist = pointDist(oldTouches[0], oldTouches[1]); 146 | var newDist = pointDist(newTouches[0], newTouches[1]); 147 | self.pinch(newDist - oldDist); 148 | } else if (self.twoFingerDrag && Math.abs(panMotion[0]) > 0.5 && Math.abs(panMotion[1]) > 0.5 149 | && Math.sign(panMotion[0]) == Math.sign(panMotion[1])) 150 | { 151 | // Pan by the average motion of the two fingers 152 | var panAmount = vec2.lerp(vec2.create(), motionVectors[0], motionVectors[1], 0.5); 153 | panAmount[1] = -panAmount[1]; 154 | self.twoFingerDrag(panAmount); 155 | } 156 | } 157 | 158 | // Update the existing list of touches with the current positions 159 | for (var i = 0; i < evt.changedTouches.length; ++i) { 160 | var t = evt.changedTouches[i]; 161 | touches[t.identifier] = [t.clientX - rect.left, t.clientY - rect.top]; 162 | } 163 | }); 164 | 165 | var touchEnd = function(evt) { 166 | evt.preventDefault(); 167 | for (var i = 0; i < evt.changedTouches.length; ++i) { 168 | var t = evt.changedTouches[i]; 169 | delete touches[t.identifier]; 170 | } 171 | } 172 | canvas.addEventListener("touchcancel", touchEnd); 173 | canvas.addEventListener("touchend", touchEnd); 174 | } 175 | 176 | -------------------------------------------------------------------------------- /frustum.js: -------------------------------------------------------------------------------- 1 | // Compute the view frustum in world space from the provided 2 | // column major projection * view matrix 3 | var Frustum = function(projView) { 4 | var rows = [vec4.create(), vec4.create(), vec4.create(), vec4.create()]; 5 | for (var i = 0; i < rows.length; ++i) { 6 | rows[i] = vec4.set(rows[i], projView[i], projView[4 + i], 7 | projView[8 + i], projView[12 + i]); 8 | } 9 | 10 | this.planes = [ 11 | // -x plane 12 | vec4.add(vec4.create(), rows[3], rows[0]), 13 | // +x plane 14 | vec4.sub(vec4.create(), rows[3], rows[0]), 15 | // -y plane 16 | vec4.add(vec4.create(), rows[3], rows[1]), 17 | // +y plane 18 | vec4.sub(vec4.create(), rows[3], rows[1]), 19 | // -z plane 20 | vec4.add(vec4.create(), rows[3], rows[2]), 21 | // +z plane 22 | vec4.sub(vec4.create(), rows[3], rows[2]) 23 | ]; 24 | 25 | // Normalize the planes 26 | for (var i = 0; i < this.planes.length; ++i) { 27 | var s = 1.0 / Math.sqrt(this.planes[i][0] * this.planes[i][0] + 28 | this.planes[i][1] * this.planes[i][1] + this.planes[i][2] * this.planes[i][2]); 29 | this.planes[i][0] *= s; 30 | this.planes[i][1] *= s; 31 | this.planes[i][2] *= s; 32 | this.planes[i][3] *= s; 33 | } 34 | 35 | // Compute the frustum points as well 36 | var invProjView = mat4.invert(mat4.create(), projView); 37 | this.points = [ 38 | // x_l, y_l, z_l 39 | vec4.set(vec4.create(), -1, -1, -1, 1), 40 | // x_h, y_l, z_l 41 | vec4.set(vec4.create(), 1, -1, -1, 1), 42 | // x_l, y_h, z_l 43 | vec4.set(vec4.create(), -1, 1, -1, 1), 44 | // x_h, y_h, z_l 45 | vec4.set(vec4.create(), 1, 1, -1, 1), 46 | // x_l, y_l, z_h 47 | vec4.set(vec4.create(), -1, -1, 1, 1), 48 | // x_h, y_l, z_h 49 | vec4.set(vec4.create(), 1, -1, 1, 1), 50 | // x_l, y_h, z_h 51 | vec4.set(vec4.create(), -1, 1, 1, 1), 52 | // x_h, y_h, z_h 53 | vec4.set(vec4.create(), 1, 1, 1, 1) 54 | ]; 55 | for (var i = 0; i < 8; ++i) { 56 | this.points[i] = vec4.transformMat4(this.points[i], this.points[i], invProjView); 57 | this.points[i][0] /= this.points[i][3]; 58 | this.points[i][1] /= this.points[i][3]; 59 | this.points[i][2] /= this.points[i][3]; 60 | this.points[i][3] = 1.0; 61 | } 62 | } 63 | 64 | // Check if the box is contained in the Frustum 65 | // The box should be [x_lower, y_lower, z_lower, x_upper, y_upper, z_upper] 66 | // This is done using Inigo Quilez's approach to help with large 67 | // bounds: https://www.iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm 68 | Frustum.prototype.containsBox = function(box) { 69 | // Test the box against each plane 70 | var vec = vec4.create(); 71 | var out = 0; 72 | for (var i = 0; i < this.planes.length; ++i) { 73 | out = 0; 74 | // x_l, y_l, z_l 75 | vec4.set(vec, box[0], box[1], box[2], 1.0); 76 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 77 | // x_h, y_l, z_l 78 | vec4.set(vec, box[3], box[1], box[2], 1.0); 79 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 80 | // x_l, y_h, z_l 81 | vec4.set(vec, box[0], box[4], box[2], 1.0); 82 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 83 | // x_h, y_h, z_l 84 | vec4.set(vec, box[3], box[4], box[2], 1.0); 85 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 86 | // x_l, y_l, z_h 87 | vec4.set(vec, box[0], box[1], box[5], 1.0); 88 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 89 | // x_h, y_l, z_h 90 | vec4.set(vec, box[3], box[1], box[5], 1.0); 91 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 92 | // x_l, y_h, z_h 93 | vec4.set(vec, box[0], box[4], box[5], 1.0); 94 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 95 | // x_h, y_h, z_h 96 | vec4.set(vec, box[3], box[4], box[5], 1.0); 97 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0; 98 | 99 | if (out == 8) { 100 | return false; 101 | } 102 | } 103 | 104 | // Test the frustum against the box 105 | out = 0; 106 | for (var i = 0; i < 8; ++i) { 107 | out += this.points[i][0] > box[3] ? 1 : 0; 108 | } 109 | if (out == 8) { 110 | return false; 111 | } 112 | 113 | out = 0; 114 | for (var i = 0; i < 8; ++i) { 115 | out += this.points[i][0] < box[0] ? 1 : 0; 116 | } 117 | if (out == 8) { 118 | return false; 119 | } 120 | 121 | out = 0; 122 | for (var i = 0; i < 8; ++i) { 123 | out += this.points[i][1] > box[4] ? 1 : 0; 124 | } 125 | if (out == 8) { 126 | return false; 127 | } 128 | 129 | out = 0; 130 | for (var i = 0; i < 8; ++i) { 131 | out += this.points[i][1] < box[1] ? 1 : 0; 132 | } 133 | if (out == 8) { 134 | return false; 135 | } 136 | 137 | out = 0; 138 | for (var i = 0; i < 8; ++i) { 139 | out += this.points[i][2] > box[5] ? 1 : 0; 140 | } 141 | if (out == 8) { 142 | return false; 143 | } 144 | 145 | out = 0; 146 | for (var i = 0; i < 8; ++i) { 147 | out += this.points[i][2] < box[2] ? 1 : 0; 148 | } 149 | if (out == 8) { 150 | return false; 151 | } 152 | return true; 153 | } 154 | 155 | 156 | -------------------------------------------------------------------------------- /shader.js: -------------------------------------------------------------------------------- 1 | var Shader = function(gl, vertexSrc, fragmentSrc) { 2 | var self = this; 3 | this.program = compileShader(gl, vertexSrc, fragmentSrc); 4 | 5 | var regexUniform = /uniform[^;]+[ ](\w+);/g 6 | var matchUniformName = /uniform[^;]+[ ](\w+);/ 7 | 8 | this.uniforms = {}; 9 | 10 | var vertexUnifs = vertexSrc.match(regexUniform); 11 | var fragUnifs = fragmentSrc.match(regexUniform); 12 | 13 | if (vertexUnifs) { 14 | vertexUnifs.forEach(function(unif) { 15 | var m = unif.match(matchUniformName); 16 | self.uniforms[m[1]] = -1; 17 | }); 18 | } 19 | if (fragUnifs) { 20 | fragUnifs.forEach(function(unif) { 21 | var m = unif.match(matchUniformName); 22 | self.uniforms[m[1]] = -1; 23 | }); 24 | } 25 | 26 | for (var unif in this.uniforms) { 27 | this.uniforms[unif] = gl.getUniformLocation(this.program, unif); 28 | } 29 | } 30 | 31 | Shader.prototype.use = function(gl) { 32 | gl.useProgram(this.program); 33 | } 34 | 35 | // Compile and link the shaders vert and frag. vert and frag should contain 36 | // the shader source code for the vertex and fragment shaders respectively 37 | // Returns the compiled and linked program, or null if compilation or linking failed 38 | var compileShader = function(gl, vert, frag){ 39 | var vs = gl.createShader(gl.VERTEX_SHADER); 40 | gl.shaderSource(vs, vert); 41 | gl.compileShader(vs); 42 | if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)){ 43 | alert("Vertex shader failed to compile, see console for log"); 44 | console.log(gl.getShaderInfoLog(vs)); 45 | return null; 46 | } 47 | 48 | var fs = gl.createShader(gl.FRAGMENT_SHADER); 49 | gl.shaderSource(fs, frag); 50 | gl.compileShader(fs); 51 | if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)){ 52 | alert("Fragment shader failed to compile, see console for log"); 53 | console.log(gl.getShaderInfoLog(fs)); 54 | return null; 55 | } 56 | 57 | var program = gl.createProgram(); 58 | gl.attachShader(program, vs); 59 | gl.attachShader(program, fs); 60 | gl.linkProgram(program); 61 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)){ 62 | alert("Shader failed to link, see console for log"); 63 | console.log(gl.getProgramInfoLog(program)); 64 | return null; 65 | } 66 | return program; 67 | } 68 | 69 | var getGLExtension = function(gl, ext) { 70 | if (!gl.getExtension(ext)) { 71 | alert("Missing " + ext + " WebGL extension"); 72 | return false; 73 | } 74 | return true; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | // Various utilities that don't really fit anywhere else 2 | 3 | // Parse the hex string to RGB values in [0, 255] 4 | var hexToRGB = function(hex) { 5 | var val = parseInt(hex.substr(1), 16); 6 | var r = (val >> 16) & 255; 7 | var g = (val >> 8) & 255; 8 | var b = val & 255; 9 | return [r, g, b]; 10 | } 11 | 12 | // Parse the hex string to RGB values in [0, 1] 13 | var hexToRGBf = function(hex) { 14 | var c = hexToRGB(hex); 15 | return [c[0] / 255.0, c[1] / 255.0, c[2] / 255.0]; 16 | } 17 | 18 | --------------------------------------------------------------------------------