Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
730 lines (449 sloc) 18.4 KB
<!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 &lt;canvas&gt; 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>