Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
egraether.com/demos/booleans.html
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
730 lines (449 sloc)
18.4 KB
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
<!doctype html> | |
<html lang=en> | |
<head> | |
<meta name="author" content="Eberhard Gräther"> | |
<title>egraether - booleans</title> | |
<style type="text/css"> | |
* { | |
padding: 0; | |
margin: 0; | |
overflow: hidden; | |
} | |
#canvas { | |
z-index: -1; | |
position: absolute; | |
top: 0px; | |
} | |
p { | |
font: 16px/24px Georgia, serif; | |
padding: 10px; | |
} | |
</style> | |
<script type="text/javascript"> | |
var _gaq = _gaq || []; | |
_gaq.push(['_setAccount', 'UA-18871992-2']); | |
_gaq.push(['_trackPageview']); | |
(function() { | |
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; | |
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; | |
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); | |
})(); | |
</script> | |
</head> | |
<body> | |
<p> | |
boolean mesh operations by <a href="http://egraether.com">egraether</a> - click to switch between intersection and subtraction - <a href="http://egraether.com/index.html#booleans">more</a> | |
</p> | |
<canvas id="canvas"> | |
<p style="padding-top: 50px"> | |
Your browser does not support <canvas> and WebGL<br/> | |
<a href="http://get.webgl.org">http://get.webgl.org</a> | |
</p> | |
</canvas> | |
<script id="vertex-shader" type="x-shader/x-vertex" charset="utf-8"> | |
uniform mat4 uMVMatrix; | |
uniform mat4 uPMatrix; | |
uniform float uNormalDirection; | |
attribute vec3 aPosition; | |
attribute vec3 aNormal; | |
varying vec3 vNormal; | |
void main( void ) { | |
gl_Position = uPMatrix * uMVMatrix * vec4( aPosition, 1.0 ); | |
vNormal = aNormal * uNormalDirection; | |
} | |
</script> | |
<script id="fragment-shader" type="x-shader/x-fragment" charset="utf-8"> | |
#ifdef GL_ES | |
precision highp float; | |
#endif | |
uniform vec3 uColor; | |
uniform vec3 uLight; | |
varying vec3 vNormal; | |
const float alpha = 0.7; | |
void main(void) { | |
float val = dot( vNormal, uLight ) * 0.4 + 0.6; | |
gl_FragColor = vec4( uColor * val, alpha); | |
} | |
</script> | |
<script type="text/javascript" charset="utf-8" src="lib/requestAnimationFrame.js"></script> | |
<script type="text/javascript" charset="utf-8" src="lib/glMatrix.js"></script> | |
<script type="text/javascript" charset="utf-8"> | |
function loadShader(gl, vertexShaderID, fragmentShaderID) { | |
var vertexShader = loadShaderScript(gl, vertexShaderID), | |
fragmentShader = loadShaderScript(gl, fragmentShaderID), | |
shaderProgram = gl.createProgram(); | |
gl.attachShader(shaderProgram, vertexShader); | |
gl.attachShader(shaderProgram, fragmentShader); | |
gl.linkProgram(shaderProgram); | |
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { | |
console.log("Unable to initialize the shader program."); | |
} | |
return shaderProgram; | |
}; | |
function loadShaderScript(gl, shaderScriptID) { | |
var shaderScript = document.getElementById(shaderScriptID), | |
shader; | |
if (shaderScript.type === "x-shader/x-fragment") { | |
shader = gl.createShader(gl.FRAGMENT_SHADER); | |
} else if (shaderScript.type === "x-shader/x-vertex") { | |
shader = gl.createShader(gl.VERTEX_SHADER); | |
} else { | |
return null; | |
} | |
gl.shaderSource(shader, shaderScript.text); | |
gl.compileShader(shader); | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
console.log("shader " + gl.getShaderInfoLog(shader)); | |
return null; | |
} | |
return shader; | |
}; | |
function initShader(gl, vertexShaderID, fragmentShaderID) { | |
var shader = loadShader(gl, vertexShaderID, fragmentShaderID); | |
gl.useProgram(shader); | |
shader.mvMatrixUniform = gl.getUniformLocation(shader, "uMVMatrix"); | |
shader.pMatrixUniform = gl.getUniformLocation(shader, "uPMatrix"); | |
shader.normalDirectionUniform = gl.getUniformLocation(shader, "uNormalDirection"); | |
shader.colorUniform = gl.getUniformLocation(shader, "uColor"); | |
shader.lightUniform = gl.getUniformLocation(shader, "uLight"); | |
shader.positionAttribute = gl.getAttribLocation(shader, "aPosition"); | |
gl.enableVertexAttribArray(shader.positionAttribute); | |
shader.normalAttribute = gl.getAttribLocation(shader, "aNormal"); | |
gl.enableVertexAttribArray(shader.normalAttribute); | |
return shader; | |
} | |
function pushMatrix() { | |
if (!matrixStack) { | |
matrixStack = []; | |
} | |
matrixStack.push(mat4.create(mvMatrix)); | |
}; | |
function popMatrix() { | |
if (!matrixStack || !matrixStack.length) { | |
throw "error: popMatrix failed"; | |
} | |
mvMatrix = matrixStack.pop(); | |
} | |
var Mesh = function(type) { | |
this.type = type; | |
this.position = [ | |
Math.random() - 0.5, | |
Math.random() - 0.5, | |
Math.random() - 0.5 | |
]; | |
this.direction = [ | |
Math.random() - 0.5, | |
Math.random() - 0.5, | |
Math.random() - 0.5 | |
]; | |
vec3.normalize(this.direction); | |
vec3.scale(this.direction, 0.01); | |
}; | |
Mesh.prototype = { | |
update : function() { | |
vec3.add(this.position, this.direction); | |
for (var i = 0; i < 3; i++) { | |
if (this.position[i] > 1.0 || this.position[i] < -1.0) { | |
this.direction[i] *= -1; | |
} | |
} | |
}, | |
draw : function(gl) { | |
pushMatrix(); | |
mat4.translate(mvMatrix, this.position); | |
gl.uniformMatrix4fv(shader.mvMatrixUniform, false, mvMatrix); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.type.vertexBuffer); | |
gl.vertexAttribPointer(shader.positionAttribute, 3, gl.FLOAT, false, 0, 0); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.type.normalBuffer); | |
gl.vertexAttribPointer(shader.normalAttribute, 3, gl.FLOAT, false, 0, 0); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.type.indexBuffer); | |
gl.drawElements(gl.TRIANGLES, this.type.indexCount, gl.UNSIGNED_SHORT, 0); | |
popMatrix(); | |
} | |
}; | |
var Cube = { | |
init : function(gl) { | |
var vertices = [ | |
// front | |
1, 1, 1, | |
1, -1, 1, | |
1, -1, -1, | |
1, 1, -1, | |
// back | |
-1, 1, 1, | |
-1, 1, -1, | |
-1, -1, -1, | |
-1, -1, 1, | |
// right | |
1, 1, 1, | |
1, 1, -1, | |
-1, 1, -1, | |
-1, 1, 1, | |
// left | |
1, -1, 1, | |
-1, -1, 1, | |
-1, -1, -1, | |
1, -1, -1, | |
// top | |
1, 1, 1, | |
-1, 1, 1, | |
-1, -1, 1, | |
1, -1, 1, | |
// bottom | |
1, 1, -1, | |
1, -1, -1, | |
-1, -1, -1, | |
-1, 1, -1 | |
]; | |
var normals = [ | |
// front | |
1, 0, 0, | |
1, 0, 0, | |
1, 0, 0, | |
1, 0, 0, | |
// back | |
-1, 0, 0, | |
-1, 0, 0, | |
-1, 0, 0, | |
-1, 0, 0, | |
// right | |
0, 1, 0, | |
0, 1, 0, | |
0, 1, 0, | |
0, 1, 0, | |
// left | |
0, -1, 0, | |
0, -1, 0, | |
0, -1, 0, | |
0, -1, 0, | |
// top | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
0, 0, 1, | |
// bottom | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1, | |
0, 0, -1 | |
]; | |
var indices = [ | |
// front | |
0, 1, 2, 0, 2, 3, | |
// back | |
4, 5, 6, 4, 6, 7, | |
// right | |
8, 9, 10, 8, 10, 11, | |
// left | |
12, 13, 14, 12, 14, 15, | |
// top | |
16, 17, 18, 16, 18, 19, | |
// bottom | |
20, 21, 22, 20, 22, 23 | |
]; | |
this.vertexBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); | |
this.normalBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.normalBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW); | |
this.indexBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STREAM_DRAW); | |
this.indexCount = indices.length; | |
} | |
}; | |
// http://learningwebgl.com/cookbook/index.php/How_to_draw_a_sphere | |
var Sphere = { | |
init: function(gl, slices, stacks) { | |
var vertices = [], | |
normals = [], | |
indices = []; | |
for (var stack = 0; stack <= stacks; stack++) { | |
var theta = stack * Math.PI / stacks, | |
sinTheta = Math.sin(theta), | |
cosTheta = Math.cos(theta); | |
for (var slice = 0; slice <= slices; slice++) { | |
var phi = slice * 2 * Math.PI / slices, | |
sinPhi = Math.sin(phi), | |
cosPhi = Math.cos(phi); | |
var x = cosPhi * sinTheta, | |
y = cosTheta, | |
z = sinPhi * sinTheta; | |
vertices.push(x, y, z); | |
normals.push(x, y, z); | |
if (stack < stacks && slice < slices) { | |
var first = (stack * (slices + 1)) + slice, | |
second = first + slices + 1; | |
indices.push(first, first + 1, second, second + 1, second, first + 1); | |
} | |
} | |
} | |
this.vertexBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); | |
this.normalBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, this.normalBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW); | |
this.indexBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); | |
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STREAM_DRAW); | |
this.indexCount = indices.length; | |
} | |
}; | |
document.onmousemove = function(event) { | |
mouse.x = event.clientX / canvas.width * 2 - 1; | |
mouse.y = event.clientY / canvas.height * -2 + 1; | |
}; | |
document.onmouseup = function(event) { | |
if (!intersection) { | |
var m = meshA; | |
meshA = meshB; | |
meshB = m; | |
meshA.type = meshA.type === Sphere ? Cube : Sphere; | |
} | |
intersection = !intersection; | |
}; | |
function render() { | |
requestAnimationFrame(render, canvas); | |
update(); | |
draw(gl); | |
}; | |
function rotate(angle, axis, vector) { | |
mat4.identity(rotMatrix); | |
mat4.rotate(rotMatrix, angle, axis); | |
mat4.multiply(mvMatrix, rotMatrix); | |
mat4.inverse(rotMatrix); | |
mat4.multiplyVec3(rotMatrix, vector); | |
}; | |
function update() { | |
if (mouse.x > 0.2 || mouse.x < -0.2) { | |
rotate(mouse.x * 0.1, up, axis); | |
} | |
if (mouse.y > 0.2 || mouse.y < -0.2) { | |
rotate(mouse.y * 0.1, axis, up); | |
} | |
meshA.update(); | |
meshB.update(); | |
}; | |
function draw(gl) { | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); | |
gl.depthMask(false); | |
gl.uniform3f(shader.colorUniform, 0.0, 0.6, 0.0); | |
meshA.draw(gl); | |
gl.uniform3f(shader.colorUniform, 0.6, 0.0, 0.0); | |
meshB.draw(gl); | |
gl.depthMask(true); | |
gl.disable(gl.BLEND); | |
if (intersection) { | |
gl.uniform3f(shader.colorUniform, 1.0, 1.0, 0.5); | |
drawIntersections(meshA, meshB); | |
} else { | |
gl.uniform3f(shader.colorUniform, 1.0, 1.0, 0.5); | |
drawSubtraction(meshA, meshB); | |
} | |
gl.enable(gl.BLEND); | |
}; | |
function drawIntersections(meshA, meshB) { | |
drawIntersection(meshA, meshB); | |
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); | |
drawIntersection(meshB, meshA); | |
}; | |
function drawIntersection(meshA, meshB) { | |
// write A in depth buffer | |
gl.colorMask(false, false, false, false); | |
meshA.draw(gl); | |
// increment stencil each fragment where B is in front of A | |
gl.depthMask(false); | |
gl.disable(gl.CULL_FACE); | |
gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); | |
meshB.draw(gl); | |
// draw A where B hides it once | |
gl.colorMask(true, true, true, true); | |
gl.enable(gl.CULL_FACE); | |
gl.depthFunc(gl.ALWAYS); | |
gl.stencilFunc(gl.EQUAL, 1, 1); | |
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); | |
meshA.draw(gl); | |
// reset buffer behavior | |
gl.stencilFunc(gl.ALWAYS, 0, 0); | |
gl.depthMask(true); | |
gl.depthFunc(gl.LESS); | |
}; | |
function drawSubtraction(meshA, meshB) { | |
// write back of A in depth and stencil | |
gl.colorMask(false, false, false, false); | |
gl.cullFace(gl.FRONT); | |
gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR); | |
meshA.draw(gl); | |
// draw back of B where it hides backside of A | |
gl.colorMask(true, true, true, true); | |
gl.uniform1f(shader.normalDirectionUniform, -1.0); | |
gl.stencilFunc(gl.EQUAL, 1, 1); | |
meshB.draw(gl); | |
// draw front of A where it is falsly hiden by back of B | |
gl.cullFace(gl.BACK); | |
gl.uniform1f(shader.normalDirectionUniform, 1.0); | |
gl.stencilFunc(gl.EQUAL, 2, 2); | |
gl.depthFunc(gl.GREATER); | |
meshA.draw(gl); | |
gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); | |
// write front of B in depth | |
gl.stencilFunc(gl.ALWAYS, 0, 0); | |
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); | |
gl.depthFunc(gl.LESS); | |
gl.colorMask(false, false, false, false); | |
meshB.draw(gl); | |
// draw front of A | |
gl.colorMask(true, true, true, true); | |
meshA.draw(gl); | |
}; | |
var gl, shader, | |
meshA, meshB, | |
up, axis, | |
mvMatrix, rotMatrix = mat4.create(), | |
matrixStack = [], | |
mouse = {x : 0, y : 0}, | |
intersection = true, | |
canvas; | |
window.onload = function() { | |
canvas = document.querySelector("canvas"); | |
if ( !!window.WebGLRenderingContext ) { | |
gl = canvas.getContext("experimental-webgl", {stencil: true}); | |
} | |
if ( !gl ) { | |
canvas.parentNode.innerHTML += '<p>Your browser does not support WebGL.<br/>' + | |
'<a href="http://get.webgl.org">http://get.webgl.org</a></p>'; | |
return; | |
} | |
canvas.style.backgroundColor = "black"; | |
canvas.width = window.innerWidth, | |
canvas.height = window.innerHeight; | |
gl.clearColor(0.9, 0.9, 0.9, 1.0); | |
gl.viewport(0, 0, canvas.width, canvas.height); | |
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); | |
gl.enable(gl.CULL_FACE); | |
gl.enable(gl.DEPTH_TEST); | |
gl.enable(gl.STENCIL_TEST); | |
shader = initShader(gl, "vertex-shader", "fragment-shader"); | |
var light = [3.0, 4.0, 5.0]; | |
vec3.normalize(light); | |
gl.uniform3fv(shader.lightUniform, light); | |
gl.uniform1f(shader.normalDirectionUniform, 1.0); | |
var pMatrix = mat4.create(); | |
mat4.perspective(45, canvas.width / canvas.height, 0.1, 1000, pMatrix); | |
gl.uniformMatrix4fv(shader.pMatrixUniform, false, pMatrix); | |
var center = [0, 0, 0], | |
eye = [6, 0, 0]; | |
up = [0, 0, 1]; | |
axis = [0, -1, 0]; | |
mvMatrix = mat4.lookAt(eye, center, up); | |
Cube.init(gl); | |
Sphere.init(gl, 64, 64); | |
meshA = new Mesh(Cube); | |
meshB = new Mesh(Sphere); | |
render(); | |
}; | |
</script> | |
</body> | |
</html> |