First release!

This commit is contained in:
Joost Nieuwenhuijse 2012-01-20 15:52:48 +01:00
parent e96769b609
commit 4516cd57e7
4 changed files with 3499 additions and 1 deletions

2957
csg.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -1 +1,289 @@
Test! <!DOCTYPE html>
<html><head>
<script src="lightgl.js"></script>
<script src="csg.js"></script>
<script src="viewer.js"></script>
<style>
body {
font: 14px/20px 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 10px;
}
pre, code, textarea {
font: 12px/20px Monaco, monospace;
border: 1px solid #CCC;
border-radius: 3px;
background: #F9F9F9;
padding: 0 3px;
color: #555;
}
pre, textarea {
padding: 10px;
width: 100%;
}
textarea {
height: 200px;
}
textarea:focus {
outline: none;
}
h1, h2 { font: bold 50px/50px 'Helvetica Neue', Helvetica, Arial; }
h2 { font-size: 30px; margin: 10px 0 0 0; }
a { color: inherit; }
.viewer { width: 200px; height: 200px; background: #EEE url(images.png); }
#combined .viewer { width: 150px; height: 150px; }
table { border-collapse: collapse; margin: 0 auto; }
td { padding: 5px; text-align: center; }
td code { background: none; border: none; color: inherit; }
canvas { cursor: move; }
</style>
<script>
var gViewer=null;
var gSolid=new CSG();
var gSolidSource="";
function onload()
{
gViewer = new Viewer(new CSG(), 600, 600, 5);
document.getElementById("viewer").appendChild(gViewer.gl.canvas);
}
function updateSolid()
{
var code=document.getElementById('code').value;
if(code != gSolidSource)
{
gSolidSource=code;
var errdiv=document.getElementById('error');
try {
gSolid = new Function(code)();
if( (typeof(gSolid) != "object") || (!('polygons' in gSolid)))
{
throw new Error("Your javascript code should return a CSG object. Try for example: return CSG.cube();");
}
errdiv.innerHTML = '';
} catch (e) {
errdiv.innerHTML = 'Error: <code>' + e.toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</code>';
gSolid=new CSG();
}
gViewer.mesh = gSolid.toMesh();
gViewer.gl.ondraw();
}
}
function updatePreview()
{
updateSolid();
}
function getStl()
{
updateSolid();
var stl=gSolid.toStlString();
document.getElementById('stloutput').value=stl;
}
</script>
<title>OpenJsCad</title>
</head>
<body onload="onload()">
<h1>OpenJsCad</h1>
Please note: currently only works reliably in Google Chrome!<br>
Create an STL file using constructive solid modeling. Click <b>Update Preview</b> to parse the source code from the textarea.
Click Get STL to generate the stl data, ready for 3d printing. See below for documentation.
<table>
<tr>
<td><div id="viewer" class="viewer" style="background-image:none;width:600px;height:600px;"></div></td>
</tr>
</table>
<textarea id="code">var resolution = 24; // increase to get smoother corners (will get slow!)
var cube1 = CSG.roundedCube({center: [0,0,0], radius: [1,1,1], roundradius: 0.2, resolution: resolution});
var sphere1 = CSG.sphere({center: [0.5, 0.5, 0.5], radius: 1, resolution: resolution });
var sphere2 = sphere1.translate([1.2, 0.5, 0]);
var sphere3 = CSG.sphere({center: [2, 0, 0], radius: 3, resolution: resolution });
var result = cube1;
result = result.union(sphere1);
result = result.subtract(sphere2);
result = result.intersect(sphere3);
return result;
</textarea>
<p id="error"></p>
<br>
<input type="submit" value="Update Preview" onclick="updatePreview(); return false;"><br>
<input type="submit" value="Get STL" onclick="getStl(); return false;"><br>
<textarea id="stloutput" readonly style="width: 80%; height: 100px"></textarea>
<br>
<h1>About</h1>
This is intended to become a Javascript based alternative to <a href="http://www.openscad.org/">OpenSCAD</a>,
for 3D solid modeling.
CSG model is contructed using Javascript. For example:<br>
<code>var cube = CSG.cube(); return cube;</code> creates a cube with a radius of 1 and centered at the origin.
Enter javascript code in the textbox above. The code should end in a return statement, returning a CSG solid.
Click on Update Preview to generate the mesh and update the viewer.
<br><br>
Click Get STL to generate the STL file. Create a file with .stl extension in a text editor and paste the contents
of the box into this file; this is ready to be printed on your 3d printer.
<h2>License</h2>
Copyright (c) 2012 Joost Nieuwenhuijse.
Uses CSG.js, <a href="https://github.com/evanw/csg.js">original</a> copyright (c) 2011 Evan Wallace,
<a href="https://github.com/joostn/csg.js">extensively modified</a>, copyright (c) 2012 Joost Nieuwenhuijse.
Uses <a href="https://github.com/evanw/lightgl.js">lightgl.js</a> by Evan Wallace for WebGL rendering.
All code released under MIT license.
<br><br>
Contributions are welcome! To contribute go to <a href="https://github.com/joostn/csg.js">CSG.js at GitHub</a>,
<a href="http://help.github.com/fork-a-repo/">create your own fork</a> and
<a href="http://help.github.com/send-pull-requests/">send me a pull request</a>.
<h2>Viewer navigation</h2>
Click and drag to rotate the model around the origin.<br>
Shift+Drag moves the model around.<br>
Alt+drag zooms (by changing the distance between camera and model).
<h2>Primitive solids</h2>
Currently the following solids are supported. The parameters are passed in an object; most
parameters are optional. 3D vectors can be passed in an array. If a scalar is passed
for a parameter which expects a 3D vector, it is used for the x, y and z value.
In other words: <code>radius: 1</code> will give <code>radius: [1,1,1]</code>.
<br><br>
All rounded solids have a 'resolution' parameter which controls tesselation. If resolution
is set to 8, then 8 polygons per 360 degree of revolution are used. Beware that rendering
time will increase dramatically when increasing the resolution. For a sphere the number of polygons
increases quadratically with the resolution used.
<br><br>
<pre>
// a cube:
var cube = CSG.cube({
center: [0, 0, 0],
radius: [1, 1, 1]
});
// a sphere:
var sphere = CSG.sphere({
center: [0, 0, 0],
radius: 2, // must be scalar
resolution: 32
});
// a cylinder:
var cylinder = CSG.cylinder({
start: [0, -1, 0],
end: [0, 1, 0],
radius: 1,
resolution: 16
});
// like a cylinder, but with spherical endpoints:
var roundedCylinder = CSG.roundedCylinder({
start: [0, -1, 0],
end: [0, 1, 0],
radius: 1,
resolution: 16
});
// a rounded cube:
var cube = CSG.roundedCube({
center: [0, 0, 0],
radius: 1,
roundradius: 0.2,
resolution: 8,
});
</pre>
<h2>CSG operations</h2>
The 3 standard CSG operations are supported. All CSG operations return a new solid; the source solids
are not modified:
<br><br>
<pre>
var csg1 = cube.union(sphere);
var csg2 = cube.intersect(sphere);
var csg3 = cube.subtract(sphere);
</pre>
<h2>Transformations</h2>
Solids can be translated, scaled and rotated. Multiple transforms can be combined into a single matrix transform:
<br><br>
<pre>
var cube = CSG.cube();
// translation:
var cube2 = cube.translate([1, 2, 3]);
// scaling:
var largecube = cube.scale(2.0);
var stretchedcube = cube.scale([1.5, 1, 0.5]);
// rotation:
var rotated1 = cube.rotateX(-45); // rotate around the X axis
var rotated2 = cube.rotateY(90); // rotate around the Y axis
var rotated3 = cube.rotateZ(20); // rotate around the Z axis
// combine multiple transforms into a single matrix transform:
var m = new CSG.Matrix4x4();
m = m.multiply(CSG.Matrix4x4.rotationX(40));
m = m.multiply(CSG.Matrix4x4.rotationZ(40));
m = m.multiply(CSG.Matrix4x4.translation([-.5, 0, 0]));
m = m.multiply(CSG.Matrix4x4.scaling([1.1, 1.2, 1.3]));
// and apply the transform:
var cube3 = cube.transform(m);
</pre>
<h2>Expansion and contraction</h2>
Expansion can be seen
as the 3D convolution of an object with a sphere. Contraction is the reverse: the area outside the solid
is expanded, and this is then subtracted from the solid.
<br><br>
Expansion and contraction are very powerful ways to get an object with nice smooth corners. For example
a rounded cube can be created by expanding a normal cube.
<br><br>
Note that these are expensive operations: spheroids are created around every corner and edge in the original
object, so the number of polygons quickly increases. Expansion and contraction therefore are only practical for simple
non-curved objects.
<br><br>
expand() and contract() take two parameters: the first is the radius of expansion or contraction; the second
parameter is optional and specififies the resolution (number of polygons on spherical surfaces, per 360 degree revolution).
<pre>
var cube1 = CSG.cube({radius: 1.0});
var cube2 = CSG.cube({radius: 1.0}).translate([-0.3, -0.3, -0.3]);
var csg = cube1.subtract(cube2);
var rounded = csg.expand(0.2, 8);
</pre>
<h2>2d shapes</h2>
Two dimensional shapes can be defined through the Polygon2D class. Currently this requires the polygon to be convex
(i.e. all corners less than 180 degrees). Shapes can be transformed (rotation, translation, scaling).
To actually use the shape it needs to be extruded into a 3D CSG object through the extrude() function. extrude()
places the 2D solid onto the z=0 plane, and extrudes in the specified direction. Extrusion can be done with an optional
twist. This rotates the solid around the z axis (and not necessariy around the extrusion axis) during extrusion.
The total degrees of rotation is specified in the twistangle parameter, and twiststeps determine the number of steps
between the bottom and top surface.
<pre>
// Create a shape; argument is an array of 2D coordinates
// The shape must be convex, can be specified in clockwise or in counterclockwise direction
var shape2d=new CSG.Polygon2D([[0,0], [5,0], [3,5], [0,5]]);
// Do some transformations:
shape2d=shape2d.translate([-2, -2]);
shape2d=shape2d.rotate(20);
shape2d=shape2d.scale([0.2, 0.2]);
// And extrude. This creates a CSG solid:
var extruded=shape2d.extrude({
offset: [0.5, 0, 2], // direction for extrusion
twistangle: 180, // top surface is rotated 180 degrees
twiststeps: 100 // create 100 slices
});
</pre>
</body>
</html>

61
lightgl.js Normal file
View file

@ -0,0 +1,61 @@
/*
* lightgl.js
* http://github.com/evanw/lightgl.js/
*
* Copyright 2011 Evan Wallace
* Released under the MIT license
*/
var GL=function(){function I(){d.MODELVIEW=C|1;d.PROJECTION=C|2;var b=new n,c=new n;d.modelviewMatrix=new n;d.projectionMatrix=new n;var a=[],f=[],g,j;d.matrixMode=function(h){switch(h){case d.MODELVIEW:g="modelviewMatrix";j=a;break;case d.PROJECTION:g="projectionMatrix";j=f;break;default:throw"invalid matrix mode "+h;}};d.loadIdentity=function(){n.identity(d[g])};d.loadMatrix=function(h){h=h.m;for(var i=d[g].m,m=0;m<16;m++)i[m]=h[m]};d.multMatrix=function(h){d.loadMatrix(n.multiply(d[g],h,c))};d.perspective=
function(h,i,m,k){d.multMatrix(n.perspective(h,i,m,k,b))};d.frustum=function(h,i,m,k,o,p){d.multMatrix(n.frustum(h,i,m,k,o,p,b))};d.ortho=function(h,i,m,k,o,p){d.multMatrix(n.ortho(h,i,m,k,o,p,b))};d.scale=function(h,i,m){d.multMatrix(n.scale(h,i,m,b))};d.translate=function(h,i,m){d.multMatrix(n.translate(h,i,m,b))};d.rotate=function(h,i,m,k){d.multMatrix(n.rotate(h,i,m,k,b))};d.lookAt=function(h,i,m,k,o,p,J,K,L){d.multMatrix(n.lookAt(h,i,m,k,o,p,J,K,L,b))};d.pushMatrix=function(){j.push(Array.prototype.slice.call(d[g].m))};
d.popMatrix=function(){var h=j.pop();d[g].m=D?new Float32Array(h):h};d.project=function(h,i,m,k,o,p){k=k||d.modelviewMatrix;o=o||d.projectionMatrix;p=p||d.getParameter(d.VIEWPORT);h=o.transformPoint(k.transformPoint(new l(h,i,m)));return new l(p[0]+p[2]*(h.x*0.5+0.5),p[1]+p[3]*(h.y*0.5+0.5),h.z*0.5+0.5)};d.unProject=function(h,i,m,k,o,p){k=k||d.modelviewMatrix;o=o||d.projectionMatrix;p=p||d.getParameter(d.VIEWPORT);h=new l((h-p[0])/p[2]*2-1,(i-p[1])/p[3]*2-1,m*2-1);return n.inverse(n.multiply(o,k,
b),c).transformPoint(h)};d.matrixMode(d.MODELVIEW)}function M(){var b={mesh:new q({coords:true,colors:true,triangles:false}),mode:-1,coord:[0,0,0,0],color:[1,1,1,1],pointSize:1,shader:new y("uniform float pointSize;varying vec4 color;varying vec4 coord;varying vec2 pixel;void main(){color=gl_Color;coord=gl_TexCoord;gl_Position=gl_ModelViewProjectionMatrix*gl_Vertex;pixel=gl_Position.xy/gl_Position.w*0.5+0.5;gl_PointSize=pointSize;}",
"uniform sampler2D texture;uniform float pointSize;uniform bool useTexture;uniform vec2 windowSize;varying vec4 color;varying vec4 coord;varying vec2 pixel;void main(){gl_FragColor=color;if(useTexture)gl_FragColor*=texture2D(texture,coord.xy);}")};d.pointSize=function(c){b.shader.uniforms({pointSize:c})};d.begin=function(c){if(b.mode!=-1)throw"mismatched gl.begin() and gl.end() calls";b.mode=c;b.mesh.colors=[];b.mesh.coords=
[];b.mesh.vertices=[]};d.color=function(c,a,f,g){b.color=arguments.length==1?c.toArray().concat(1):[c,a,f,g||1]};d.texCoord=function(c,a){b.coord=arguments.length==1?c.toArray(2):[c,a]};d.vertex=function(c,a,f){b.mesh.colors.push(b.color);b.mesh.coords.push(b.coord);b.mesh.vertices.push(arguments.length==1?c.toArray():[c,a,f])};d.end=function(){if(b.mode==-1)throw"mismatched gl.begin() and gl.end() calls";b.mesh.compile();b.shader.uniforms({windowSize:[d.canvas.width,d.canvas.height],useTexture:!!d.getParameter(d.TEXTURE_BINDING_2D)}).draw(b.mesh,
b.mode);b.mode=-1}}function N(){function b(){for(var k in i)if(i[k])return true;return false}function c(k){e=Object.create(k);e.original=k;e.x=e.pageX;e.y=e.pageY;for(k=d.canvas;k;k=k.offsetParent){e.x-=k.offsetLeft;e.y-=k.offsetTop}if(m){e.deltaX=e.x-j;e.deltaY=e.y-h}else{e.deltaX=0;e.deltaY=0;m=true}j=e.x;h=e.y;e.dragging=b();e.preventDefault=function(){e.original.preventDefault()};e.stopPropagation=function(){e.original.stopPropagation()};return e}function a(k){k=c(k);d.onmousemove&&d.onmousemove(k);
k.preventDefault()}function f(k){i[k.which]=false;if(!b()){document.removeEventListener("mousemove",a);document.removeEventListener("mouseup",f);d.canvas.addEventListener("mousemove",a);d.canvas.addEventListener("mouseup",f)}k=c(k);d.onmouseup&&d.onmouseup(k);k.preventDefault()}function g(){m=false}var j=0,h=0,i={},m=false;z(d.canvas,"mousedown",function(k){if(!b()){document.addEventListener("mousemove",a);document.addEventListener("mouseup",f);d.canvas.removeEventListener("mousemove",a);d.canvas.removeEventListener("mouseup",
f)}i[k.which]=true;k=c(k);d.onmousedown&&d.onmousedown(k);k.preventDefault()});d.canvas.addEventListener("mousemove",a);d.canvas.addEventListener("mouseup",f);d.canvas.addEventListener("mouseover",g);d.canvas.addEventListener("mouseout",g)}function E(b){return{8:"BACKSPACE",9:"TAB",13:"ENTER",16:"SHIFT",27:"ESCAPE",32:"SPACE",37:"LEFT",38:"UP",39:"RIGHT",40:"DOWN"}[b]||(b>=65&&b<=90?String.fromCharCode(b):null)}function z(b,c,a){b.addEventListener(c,a)}function O(){(function(b){d.makeCurrent=function(){d=
b}})(d);d.animate=function(){function b(){d=f;var g=new Date;d.onupdate&&d.onupdate((g-a)/1E3);d.ondraw&&d.ondraw();c(b);a=g}var c=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||function(g){setTimeout(g,1E3/60)},a=new Date,f=d;b()};d.fullscreen=function(b){function c(){d.canvas.width=window.innerWidth-f-g;d.canvas.height=window.innerHeight-a-j;d.viewport(0,0,d.canvas.width,d.canvas.height);if(b.camera||!("camera"in b)){d.matrixMode(d.PROJECTION);
d.loadIdentity();d.perspective(b.fov||45,d.canvas.width/d.canvas.height,b.near||0.1,b.far||1E3);d.matrixMode(d.MODELVIEW)}d.ondraw&&d.ondraw()}b=b||{};var a=b.paddingTop||0,f=b.paddingLeft||0,g=b.paddingRight||0,j=b.paddingBottom||0;if(!document.body)throw"document.body doesn't exist yet (call gl.fullscreen() from window.onload() or from inside the <body> tag)";document.body.appendChild(d.canvas);document.body.style.overflow="hidden";d.canvas.style.position="absolute";d.canvas.style.left=f+"px";d.canvas.style.top=
a+"px";window.addEventListener("resize",c);c()}}function n(){var b=Array.prototype.concat.apply([],arguments);b.length||(b=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]);this.m=D?new Float32Array(b):b}function w(){this.unique=[];this.indices=[];this.map={}}function x(b,c){this.buffer=null;this.target=b;this.type=c;this.data=[]}function q(b){b=b||{};this.vertexBuffers={};this.indexBuffers={};this.addVertexBuffer("vertices","gl_Vertex");b.coords&&this.addVertexBuffer("coords","gl_TexCoord");b.normals&&this.addVertexBuffer("normals",
"gl_Normal");b.colors&&this.addVertexBuffer("colors","gl_Color");if(!("triangles"in b)||b.triangles)this.addIndexBuffer("triangles");b.lines&&this.addIndexBuffer("lines")}function F(b){return new l((b&1)*2-1,(b&2)-1,(b&4)/2-1)}function t(b,c,a){this.t=arguments.length?b:Number.MAX_VALUE;this.hit=c;this.normal=a}function u(){var b=d.getParameter(d.VIEWPORT),c=d.modelviewMatrix.m,a=new l(c[0],c[4],c[8]),f=new l(c[1],c[5],c[9]),g=new l(c[2],c[6],c[10]);c=new l(c[3],c[7],c[11]);this.eye=new l(-c.dot(a),
-c.dot(f),-c.dot(g));a=b[0];f=a+b[2];g=b[1];c=g+b[3];this.ray00=d.unProject(a,g,1).subtract(this.eye);this.ray10=d.unProject(f,g,1).subtract(this.eye);this.ray01=d.unProject(a,c,1).subtract(this.eye);this.ray11=d.unProject(f,c,1).subtract(this.eye);this.viewport=b}function y(b,c){function a(i,m,k){for(;(result=i.exec(m))!=null;)k(result)}function f(i,m){var k={},o=/^((\s*\/\/.*\n|\s*#extension.*\n)+)[^]*$/.exec(m);m=o?o[1]+i+m.substr(o[1].length):i+m;a(/\bgl_\w+\b/g,i,function(p){if(!(p in k)){m=
m.replace(RegExp("\\b"+p+"\\b","g"),"_"+p);k[p]=true}});return m}function g(i,m){var k=d.createShader(i);d.shaderSource(k,m);d.compileShader(k);if(!d.getShaderParameter(k,d.COMPILE_STATUS))throw"compile error: "+d.getShaderInfoLog(k);return k}var j=b+c;this.needsMVPM=/(gl_ModelViewProjectionMatrix|ftransform)/.test(j);this.needsNM=/gl_NormalMatrix/.test(j);b=f("uniform mat3 gl_NormalMatrix;uniform mat4 gl_ModelViewMatrix;uniform mat4 gl_ProjectionMatrix;uniform mat4 gl_ModelViewProjectionMatrix;attribute vec4 gl_Vertex;attribute vec4 gl_TexCoord;attribute vec3 gl_Normal;attribute vec4 gl_Color;vec4 ftransform(){return gl_ModelViewProjectionMatrix*gl_Vertex;}",
b);c=f("precision highp float;uniform mat3 gl_NormalMatrix;uniform mat4 gl_ModelViewMatrix;uniform mat4 gl_ProjectionMatrix;uniform mat4 gl_ModelViewProjectionMatrix;",c);this.program=d.createProgram();d.attachShader(this.program,g(d.VERTEX_SHADER,b));d.attachShader(this.program,g(d.FRAGMENT_SHADER,c));d.linkProgram(this.program);if(!d.getProgramParameter(this.program,d.LINK_STATUS))throw"link error: "+d.getProgramInfoLog(this.program);this.attributes={};var h={};a(/uniform\s+sampler(1D|2D|3D|Cube)\s+(\w+)\s*;/g,
b+c,function(i){h[i[2]]=1});this.isSampler=h}function s(b,c,a){a=a||{};this.id=d.createTexture();this.width=b;this.height=c;this.format=a.format||d.RGBA;this.type=a.type||d.UNSIGNED_BYTE;d.bindTexture(d.TEXTURE_2D,this.id);d.pixelStorei(d.UNPACK_FLIP_Y_WEBGL,1);d.texParameteri(d.TEXTURE_2D,d.TEXTURE_MAG_FILTER,a.filter||a.magFilter||d.LINEAR);d.texParameteri(d.TEXTURE_2D,d.TEXTURE_MIN_FILTER,a.filter||a.minFilter||d.LINEAR);d.texParameteri(d.TEXTURE_2D,d.TEXTURE_WRAP_S,a.wrap||a.wrapS||d.CLAMP_TO_EDGE);
d.texParameteri(d.TEXTURE_2D,d.TEXTURE_WRAP_T,a.wrap||a.wrapT||d.CLAMP_TO_EDGE);d.texImage2D(d.TEXTURE_2D,0,this.format,b,c,0,this.format,this.type,null)}function l(b,c,a){this.x=b||0;this.y=c||0;this.z=a||0}var d,v={create:function(b){b=b||{};var c=document.createElement("canvas");c.width=800;c.height=600;if(!("alpha"in b))b.alpha=false;try{d=c.getContext("webgl",b)}catch(a){}try{d=d||c.getContext("experimental-webgl",b)}catch(f){}if(!d)throw"WebGL not supported";I();M();N();O();return d},keys:{},
Matrix:n,Indexer:w,Buffer:x,Mesh:q,HitTest:t,Raytracer:u,Shader:y,Texture:s,Vector:l};z(document,"keydown",function(b){if(!b.altKey&&!b.ctrlKey&&!b.metaKey){var c=E(b.keyCode);if(c)v.keys[c]=true;v.keys[b.keyCode]=true}});z(document,"keyup",function(b){if(!b.altKey&&!b.ctrlKey&&!b.metaKey){var c=E(b.keyCode);if(c)v.keys[c]=false;v.keys[b.keyCode]=false}});var C=305397760,D=typeof Float32Array!="undefined";n.prototype={inverse:function(){return n.inverse(this,new n)},transpose:function(){return n.transpose(this,
new n)},multiply:function(b){return n.multiply(this,b,new n)},transformPoint:function(b){var c=this.m;return(new l(c[0]*b.x+c[1]*b.y+c[2]*b.z+c[3],c[4]*b.x+c[5]*b.y+c[6]*b.z+c[7],c[8]*b.x+c[9]*b.y+c[10]*b.z+c[11])).divide(c[12]*b.x+c[13]*b.y+c[14]*b.z+c[15])},transformVector:function(b){var c=this.m;return new l(c[0]*b.x+c[1]*b.y+c[2]*b.z,c[4]*b.x+c[5]*b.y+c[6]*b.z,c[8]*b.x+c[9]*b.y+c[10]*b.z)}};n.inverse=function(b,c){c=c||new n;var a=b.m,f=c.m;f[0]=a[5]*a[10]*a[15]-a[5]*a[14]*a[11]-a[6]*a[9]*a[15]+
a[6]*a[13]*a[11]+a[7]*a[9]*a[14]-a[7]*a[13]*a[10];f[1]=-a[1]*a[10]*a[15]+a[1]*a[14]*a[11]+a[2]*a[9]*a[15]-a[2]*a[13]*a[11]-a[3]*a[9]*a[14]+a[3]*a[13]*a[10];f[2]=a[1]*a[6]*a[15]-a[1]*a[14]*a[7]-a[2]*a[5]*a[15]+a[2]*a[13]*a[7]+a[3]*a[5]*a[14]-a[3]*a[13]*a[6];f[3]=-a[1]*a[6]*a[11]+a[1]*a[10]*a[7]+a[2]*a[5]*a[11]-a[2]*a[9]*a[7]-a[3]*a[5]*a[10]+a[3]*a[9]*a[6];f[4]=-a[4]*a[10]*a[15]+a[4]*a[14]*a[11]+a[6]*a[8]*a[15]-a[6]*a[12]*a[11]-a[7]*a[8]*a[14]+a[7]*a[12]*a[10];f[5]=a[0]*a[10]*a[15]-a[0]*a[14]*a[11]-
a[2]*a[8]*a[15]+a[2]*a[12]*a[11]+a[3]*a[8]*a[14]-a[3]*a[12]*a[10];f[6]=-a[0]*a[6]*a[15]+a[0]*a[14]*a[7]+a[2]*a[4]*a[15]-a[2]*a[12]*a[7]-a[3]*a[4]*a[14]+a[3]*a[12]*a[6];f[7]=a[0]*a[6]*a[11]-a[0]*a[10]*a[7]-a[2]*a[4]*a[11]+a[2]*a[8]*a[7]+a[3]*a[4]*a[10]-a[3]*a[8]*a[6];f[8]=a[4]*a[9]*a[15]-a[4]*a[13]*a[11]-a[5]*a[8]*a[15]+a[5]*a[12]*a[11]+a[7]*a[8]*a[13]-a[7]*a[12]*a[9];f[9]=-a[0]*a[9]*a[15]+a[0]*a[13]*a[11]+a[1]*a[8]*a[15]-a[1]*a[12]*a[11]-a[3]*a[8]*a[13]+a[3]*a[12]*a[9];f[10]=a[0]*a[5]*a[15]-a[0]*
a[13]*a[7]-a[1]*a[4]*a[15]+a[1]*a[12]*a[7]+a[3]*a[4]*a[13]-a[3]*a[12]*a[5];f[11]=-a[0]*a[5]*a[11]+a[0]*a[9]*a[7]+a[1]*a[4]*a[11]-a[1]*a[8]*a[7]-a[3]*a[4]*a[9]+a[3]*a[8]*a[5];f[12]=-a[4]*a[9]*a[14]+a[4]*a[13]*a[10]+a[5]*a[8]*a[14]-a[5]*a[12]*a[10]-a[6]*a[8]*a[13]+a[6]*a[12]*a[9];f[13]=a[0]*a[9]*a[14]-a[0]*a[13]*a[10]-a[1]*a[8]*a[14]+a[1]*a[12]*a[10]+a[2]*a[8]*a[13]-a[2]*a[12]*a[9];f[14]=-a[0]*a[5]*a[14]+a[0]*a[13]*a[6]+a[1]*a[4]*a[14]-a[1]*a[12]*a[6]-a[2]*a[4]*a[13]+a[2]*a[12]*a[5];f[15]=a[0]*a[5]*
a[10]-a[0]*a[9]*a[6]-a[1]*a[4]*a[10]+a[1]*a[8]*a[6]+a[2]*a[4]*a[9]-a[2]*a[8]*a[5];a=a[0]*f[0]+a[1]*f[4]+a[2]*f[8]+a[3]*f[12];for(var g=0;g<16;g++)f[g]/=a;return c};n.transpose=function(b,c){c=c||new n;var a=b.m,f=c.m;f[0]=a[0];f[1]=a[4];f[2]=a[8];f[3]=a[12];f[4]=a[1];f[5]=a[5];f[6]=a[9];f[7]=a[13];f[8]=a[2];f[9]=a[6];f[10]=a[10];f[11]=a[14];f[12]=a[3];f[13]=a[7];f[14]=a[11];f[15]=a[15];return c};n.multiply=function(b,c,a){a=a||new n;b=b.m;c=c.m;var f=a.m;f[0]=b[0]*c[0]+b[1]*c[4]+b[2]*c[8]+b[3]*c[12];
f[1]=b[0]*c[1]+b[1]*c[5]+b[2]*c[9]+b[3]*c[13];f[2]=b[0]*c[2]+b[1]*c[6]+b[2]*c[10]+b[3]*c[14];f[3]=b[0]*c[3]+b[1]*c[7]+b[2]*c[11]+b[3]*c[15];f[4]=b[4]*c[0]+b[5]*c[4]+b[6]*c[8]+b[7]*c[12];f[5]=b[4]*c[1]+b[5]*c[5]+b[6]*c[9]+b[7]*c[13];f[6]=b[4]*c[2]+b[5]*c[6]+b[6]*c[10]+b[7]*c[14];f[7]=b[4]*c[3]+b[5]*c[7]+b[6]*c[11]+b[7]*c[15];f[8]=b[8]*c[0]+b[9]*c[4]+b[10]*c[8]+b[11]*c[12];f[9]=b[8]*c[1]+b[9]*c[5]+b[10]*c[9]+b[11]*c[13];f[10]=b[8]*c[2]+b[9]*c[6]+b[10]*c[10]+b[11]*c[14];f[11]=b[8]*c[3]+b[9]*c[7]+b[10]*
c[11]+b[11]*c[15];f[12]=b[12]*c[0]+b[13]*c[4]+b[14]*c[8]+b[15]*c[12];f[13]=b[12]*c[1]+b[13]*c[5]+b[14]*c[9]+b[15]*c[13];f[14]=b[12]*c[2]+b[13]*c[6]+b[14]*c[10]+b[15]*c[14];f[15]=b[12]*c[3]+b[13]*c[7]+b[14]*c[11]+b[15]*c[15];return a};n.identity=function(b){b=b||new n;var c=b.m;c[0]=c[5]=c[10]=c[15]=1;c[1]=c[2]=c[3]=c[4]=c[6]=c[7]=c[8]=c[9]=c[11]=c[12]=c[13]=c[14]=0;return b};n.perspective=function(b,c,a,f,g){b=Math.tan(b*Math.PI/360)*a;c=b*c;return n.frustum(-c,c,-b,b,a,f,g)};n.frustum=function(b,
c,a,f,g,j,h){h=h||new n;var i=h.m;i[0]=2*g/(c-b);i[1]=0;i[2]=(c+b)/(c-b);i[3]=0;i[4]=0;i[5]=2*g/(f-a);i[6]=(f+a)/(f-a);i[7]=0;i[8]=0;i[9]=0;i[10]=-(j+g)/(j-g);i[11]=-2*j*g/(j-g);i[12]=0;i[13]=0;i[14]=-1;i[15]=0;return h};n.ortho=function(b,c,a,f,g,j,h){h=h||new n;var i=h.m;i[0]=2/(c-b);i[1]=0;i[2]=0;i[3]=-(c+b)/(c-b);i[4]=0;i[5]=2/(f-a);i[6]=0;i[7]=-(f+a)/(f-a);i[8]=0;i[9]=0;i[10]=-2/(j-g);i[11]=-(j+g)/(j-g);i[12]=0;i[13]=0;i[14]=0;i[15]=1;return h};n.scale=function(b,c,a,f){f=f||new n;var g=f.m;
g[0]=b;g[1]=0;g[2]=0;g[3]=0;g[4]=0;g[5]=c;g[6]=0;g[7]=0;g[8]=0;g[9]=0;g[10]=a;g[11]=0;g[12]=0;g[13]=0;g[14]=0;g[15]=1;return f};n.translate=function(b,c,a,f){f=f||new n;var g=f.m;g[0]=1;g[1]=0;g[2]=0;g[3]=b;g[4]=0;g[5]=1;g[6]=0;g[7]=c;g[8]=0;g[9]=0;g[10]=1;g[11]=a;g[12]=0;g[13]=0;g[14]=0;g[15]=1;return f};n.rotate=function(b,c,a,f,g){if(!b||!c&&!a&&!f)return n.identity(g);g=g||new n;var j=g.m,h=Math.sqrt(c*c+a*a+f*f);b*=Math.PI/180;c/=h;a/=h;f/=h;h=Math.cos(b);b=Math.sin(b);var i=1-h;j[0]=c*c*i+h;
j[1]=c*a*i-f*b;j[2]=c*f*i+a*b;j[3]=0;j[4]=a*c*i+f*b;j[5]=a*a*i+h;j[6]=a*f*i-c*b;j[7]=0;j[8]=f*c*i-a*b;j[9]=f*a*i+c*b;j[10]=f*f*i+h;j[11]=0;j[12]=0;j[13]=0;j[14]=0;j[15]=1;return g};n.lookAt=function(b,c,a,f,g,j,h,i,m,k){k=k||new n;var o=k.m;b=new l(b,c,a);f=new l(f,g,j);i=new l(h,i,m);h=b.subtract(f).unit();i=i.cross(h).unit();m=h.cross(i).unit();o[0]=i.x;o[1]=i.y;o[2]=i.z;o[3]=-i.dot(b);o[4]=m.x;o[5]=m.y;o[6]=m.z;o[7]=-m.dot(b);o[8]=h.x;o[9]=h.y;o[10]=h.z;o[11]=-h.dot(b);o[12]=0;o[13]=0;o[14]=0;
o[15]=1;return k};w.prototype={add:function(b){var c=JSON.stringify(b);if(!(c in this.map)){this.map[c]=this.unique.length;this.unique.push(b)}return this.map[c]}};x.prototype={compile:function(b){for(var c=[],a=0;a<this.data.length;a+=1E4)c=Array.prototype.concat.apply(c,this.data.slice(a,a+1E4));a=this.data.length?c.length/this.data.length:0;if(a!=Math.round(a))throw"buffer elements not of consistent size, average size is "+a;this.buffer=this.buffer||d.createBuffer();this.buffer.length=c.length;
this.buffer.spacing=a;d.bindBuffer(this.target,this.buffer);d.bufferData(this.target,new this.type(c),b||d.STATIC_DRAW)}};q.prototype={addVertexBuffer:function(b,c){(this.vertexBuffers[c]=new x(d.ARRAY_BUFFER,Float32Array)).name=b;this[b]=[]},addIndexBuffer:function(b){this.indexBuffers[b]=new x(d.ELEMENT_ARRAY_BUFFER,Int16Array);this[b]=[]},compile:function(){for(var b in this.vertexBuffers){var c=this.vertexBuffers[b];c.data=this[c.name];c.compile()}for(var a in this.indexBuffers){c=this.indexBuffers[a];
c.data=this[a];c.compile()}},transform:function(b){this.vertices=this.vertices.map(function(a){return b.transformPoint(l.fromArray(a)).toArray()});if(this.normals){var c=b.inverse().transpose();this.normals=this.normals.map(function(a){return c.transformVector(l.fromArray(a)).unit().toArray()})}this.compile();return this},computeNormals:function(){this.normals||this.addVertexBuffer("normals","gl_Normal");for(var b=0;b<this.vertices.length;b++)this.normals[b]=new l;for(b=0;b<this.triangles.length;b++){var c=
this.triangles[b],a=l.fromArray(this.vertices[c[0]]),f=l.fromArray(this.vertices[c[1]]),g=l.fromArray(this.vertices[c[2]]);a=f.subtract(a).cross(g.subtract(a)).unit();this.normals[c[0]]=this.normals[c[0]].add(a);this.normals[c[1]]=this.normals[c[1]].add(a);this.normals[c[2]]=this.normals[c[2]].add(a)}for(b=0;b<this.vertices.length;b++)this.normals[b]=this.normals[b].unit().toArray();this.compile();return this},computeWireframe:function(){for(var b=new w,c=0;c<this.triangles.length;c++)for(var a=this.triangles[c],
f=0;f<a.length;f++){var g=a[f],j=a[(f+1)%a.length];b.add([Math.min(g,j),Math.max(g,j)])}this.lines||this.addIndexBuffer("lines");this.lines=b.unique;this.compile();return this},getAABB:function(){var b={min:new l(Number.MAX_VALUE,Number.MAX_VALUE,Number.MAX_VALUE)};b.max=b.min.negative();for(var c=0;c<this.vertices.length;c++){var a=l.fromArray(this.vertices[c]);b.min=l.min(b.min,a);b.max=l.max(b.max,a)}return b},getBoundingSphere:function(){var b=this.getAABB();b={center:b.min.add(b.max).divide(2),
radius:0};for(var c=0;c<this.vertices.length;c++)b.radius=Math.max(b.radius,l.fromArray(this.vertices[c]).subtract(b.center).length());return b}};q.plane=function(b){b=b||{};var c=new q(b);detailX=b.detailX||b.detail||1;detailY=b.detailY||b.detail||1;for(b=0;b<=detailY;b++)for(var a=b/detailY,f=0;f<=detailX;f++){var g=f/detailX;c.vertices.push([2*g-1,2*a-1,0]);c.coords&&c.coords.push([g,a]);c.normals&&c.normals.push([0,0,1]);if(f<detailX&&b<detailY){g=f+b*(detailX+1);c.triangles.push([g,g+1,g+detailX+
1]);c.triangles.push([g+detailX+1,g+1,g+detailX+2])}}c.compile();return c};var G=[[0,4,2,6,-1,0,0],[1,3,5,7,+1,0,0],[0,1,4,5,0,-1,0],[2,6,3,7,0,+1,0],[0,2,1,3,0,0,-1],[4,5,6,7,0,0,+1]];q.cube=function(b){b=new q(b);for(var c=0;c<G.length;c++){for(var a=G[c],f=c*4,g=0;g<4;g++){b.vertices.push(F(a[g]).toArray());b.coords&&b.coords.push([g&1,(g&2)/2]);b.normals&&b.normals.push(a.slice(4,7))}b.triangles.push([f,f+1,f+2]);b.triangles.push([f+2,f+1,f+3])}b.compile();return b};q.sphere=function(b){b=b||
{};var c=new q(b),a=new w;detail=b.detail||6;for(b=0;b<8;b++)for(var f=F(b),g=f.x*f.y*f.z>0,j=[],h=0;h<=detail;h++){for(var i=0;h+i<=detail;i++){var m=h/detail,k=i/detail,o=(detail-h-i)/detail;k={vertex:(new l(m+(m-m*m)/2,k+(k-k*k)/2,o+(o-o*o)/2)).unit().multiply(f).toArray()};if(c.coords)k.coord=f.y>0?[1-m,o]:[o,1-m];j.push(a.add(k))}if(h>0)for(i=0;h+i<=detail;i++){m=(h-1)*(detail+1)+(h-1-(h-1)*(h-1))/2+i;k=h*(detail+1)+(h-h*h)/2+i;c.triangles.push(g?[j[m],j[k],j[m+1]]:[j[m],j[m+1],j[k]]);h+i<detail&&
c.triangles.push(g?[j[k],j[k+1],j[m+1]]:[j[k],j[m+1],j[k+1]])}}c.vertices=a.unique.map(function(p){return p.vertex});if(c.coords)c.coords=a.unique.map(function(p){return p.coord});if(c.normals)c.normals=c.vertices;c.compile();return c};q.load=function(b,c){c=c||{};if(!("coords"in c))c.coords=!!b.coords;if(!("normals"in c))c.normals=!!b.normals;if(!("triangles"in c))c.triangles=!!b.triangles;if(!("lines"in c))c.lines=!!b.lines;var a=new q(c);a.vertices=b.vertices;if(a.coords)a.coords=b.coords;if(a.normals)a.normals=
b.normals;if(a.triangles)a.triangles=b.triangles;if(a.lines)a.lines=b.lines;a.compile();return a};t.prototype={mergeWith:function(b){if(b.t>0&&b.t<this.t){this.t=b.t;this.hit=b.hit;this.normal=b.normal}}};u.prototype={getRayForPixel:function(b,c){b=(b-this.viewport[0])/this.viewport[2];c=1-(c-this.viewport[1])/this.viewport[3];var a=l.lerp(this.ray00,this.ray10,b),f=l.lerp(this.ray01,this.ray11,b);return l.lerp(a,f,c).unit()}};u.hitTestBox=function(b,c,a,f){var g=a.subtract(b).divide(c),j=f.subtract(b).divide(c),
h=l.min(g,j);g=l.max(g,j);h=h.max();g=g.min();if(h>0&&h<g){b=b.add(c.multiply(h));a=a.add(1.0E-6);f=f.subtract(1.0E-6);return new t(h,b,new l((b.x>f.x)-(b.x<a.x),(b.y>f.y)-(b.y<a.y),(b.z>f.z)-(b.z<a.z)))}return null};u.hitTestSphere=function(b,c,a,f){var g=b.subtract(a),j=c.dot(c),h=2*c.dot(g);g=g.dot(g)-f*f;g=h*h-4*j*g;if(g>0){j=(-h-Math.sqrt(g))/(2*j);b=b.add(c.multiply(j));return new t(j,b,b.subtract(a).divide(f))}return null};u.hitTestTriangle=function(b,c,a,f,g){var j=f.subtract(a),h=g.subtract(a);
g=j.cross(h).unit();f=g.dot(a.subtract(b)).divide(g.dot(c));if(f>0){b=b.add(c.multiply(f));var i=b.subtract(a);a=h.dot(h);c=h.dot(j);h=h.dot(i);var m=j.dot(j);j=j.dot(i);i=a*m-c*c;m=(m*h-c*j)/i;j=(a*j-c*h)/i;if(m>=0&&j>=0&&m+j<=1)return new t(f,b,g)}return null};var P=new n,H=new n;y.prototype={uniforms:function(b){d.useProgram(this.program);for(var c in b){var a=d.getUniformLocation(this.program,c);if(a){var f=b[c];if(f instanceof l)f=[f.x,f.y,f.z];else if(f instanceof n)f=f.m;var g=Object.prototype.toString.call(f);
if(g=="[object Array]"||g=="[object Float32Array]")switch(f.length){case 1:d.uniform1fv(a,new Float32Array(f));break;case 2:d.uniform2fv(a,new Float32Array(f));break;case 3:d.uniform3fv(a,new Float32Array(f));break;case 4:d.uniform4fv(a,new Float32Array(f));break;case 9:d.uniformMatrix3fv(a,false,new Float32Array([f[0],f[3],f[6],f[1],f[4],f[7],f[2],f[5],f[8]]));break;case 16:d.uniformMatrix4fv(a,false,new Float32Array([f[0],f[4],f[8],f[12],f[1],f[5],f[9],f[13],f[2],f[6],f[10],f[14],f[3],f[7],f[11],
f[15]]));break;default:throw"don't know how to load uniform \""+c+'" of length '+f.length;}else{g=Object.prototype.toString.call(f);if(g=="[object Number]"||g=="[object Boolean]")(this.isSampler[c]?d.uniform1i:d.uniform1f).call(d,a,f);else throw'attempted to set uniform "'+c+'" to invalid value '+f;}}}return this},draw:function(b,c){this.drawBuffers(b.vertexBuffers,b.indexBuffers[c==d.LINES?"lines":"triangles"],arguments.length<2?d.TRIANGLES:c)},drawBuffers:function(b,c,a){this.uniforms({_gl_ModelViewMatrix:d.modelviewMatrix,
_gl_ProjectionMatrix:d.projectionMatrix});this.needsMVPM&&this.uniforms({_gl_ModelViewProjectionMatrix:n.multiply(d.projectionMatrix,d.modelviewMatrix,H)});if(this.needsNM){var f=n.transpose(n.inverse(d.modelviewMatrix,P),H).m;this.uniforms({_gl_NormalMatrix:[f[0],f[1],f[2],f[4],f[5],f[6],f[8],f[9],f[10]]})}f=0;for(var g in b){var j=b[g],h=this.attributes[g]||d.getAttribLocation(this.program,g.replace(/^gl_/,"_gl_"));if(!(h==-1||!j.buffer)){this.attributes[g]=h;d.bindBuffer(d.ARRAY_BUFFER,j.buffer);
d.enableVertexAttribArray(h);d.vertexAttribPointer(h,j.buffer.spacing,d.FLOAT,false,0,0);f=j.buffer.length/j.buffer.spacing}}for(g in this.attributes)g in b||d.disableVertexAttribArray(this.attributes[g]);if(f&&(!c||c.buffer))if(c){d.bindBuffer(d.ELEMENT_ARRAY_BUFFER,c.buffer);d.drawElements(a,c.buffer.length,d.UNSIGNED_SHORT,0)}else d.drawArrays(a,0,f);return this}};var A,r,B;s.prototype={bind:function(b){d.activeTexture(d.TEXTURE0+(b||0));d.bindTexture(d.TEXTURE_2D,this.id)},unbind:function(b){d.activeTexture(d.TEXTURE0+
(b||0));d.bindTexture(d.TEXTURE_2D,null)},drawTo:function(b){var c=d.getParameter(d.VIEWPORT);A=A||d.createFramebuffer();r=r||d.createRenderbuffer();d.bindFramebuffer(d.FRAMEBUFFER,A);d.bindRenderbuffer(d.RENDERBUFFER,r);if(this.width!=r.width||this.height!=r.height){r.width=this.width;r.height=this.height;d.renderbufferStorage(d.RENDERBUFFER,d.DEPTH_COMPONENT16,this.width,this.height)}d.framebufferTexture2D(d.FRAMEBUFFER,d.COLOR_ATTACHMENT0,d.TEXTURE_2D,this.id,0);d.framebufferRenderbuffer(d.FRAMEBUFFER,
d.DEPTH_ATTACHMENT,d.RENDERBUFFER,r);d.viewport(0,0,this.width,this.height);b();d.bindFramebuffer(d.FRAMEBUFFER,null);d.bindRenderbuffer(d.RENDERBUFFER,null);d.viewport(c[0],c[1],c[2],c[3])},swapWith:function(b){var c;c=b.id;b.id=this.id;this.id=c;c=b.width;b.width=this.width;this.width=c;c=b.height;b.height=this.height;this.height=c}};s.fromImage=function(b,c){c=c||{};var a=new s(b.width,b.height,c);try{d.texImage2D(d.TEXTURE_2D,0,a.format,a.format,a.type,b)}catch(f){if(location.protocol=="file:")throw'image not loaded for security reasons (serve this page over "http://" instead)';
else throw"image not loaded for security reasons (image must originate from the same domain as this page or use Cross-Origin Resource Sharing)";}c.minFilter&&c.minFilter!=d.NEAREST&&c.minFilter!=d.LINEAR&&d.generateMipmap(d.TEXTURE_2D);return a};s.fromURL=function(b,c){B=B||function(){var g=document.createElement("canvas").getContext("2d");g.canvas.width=g.canvas.height=128;for(var j=0;j<g.canvas.height;j+=16)for(var h=0;h<g.canvas.width;h+=16){g.fillStyle=(h^j)&16?"#FFF":"#DDD";g.fillRect(h,j,16,
16)}return g.canvas}();var a=s.fromImage(B,c),f=new Image;f.onload=function(){s.fromImage(f,c).swapWith(a)};f.src=b;return a};l.prototype={negative:function(){return new l(-this.x,-this.y,-this.z)},add:function(b){return b instanceof l?new l(this.x+b.x,this.y+b.y,this.z+b.z):new l(this.x+b,this.y+b,this.z+b)},subtract:function(b){return b instanceof l?new l(this.x-b.x,this.y-b.y,this.z-b.z):new l(this.x-b,this.y-b,this.z-b)},multiply:function(b){return b instanceof l?new l(this.x*b.x,this.y*b.y,this.z*
b.z):new l(this.x*b,this.y*b,this.z*b)},divide:function(b){return b instanceof l?new l(this.x/b.x,this.y/b.y,this.z/b.z):new l(this.x/b,this.y/b,this.z/b)},equals:function(b){return this.x==b.x&&this.y==b.y&&this.z==b.z},dot:function(b){return this.x*b.x+this.y*b.y+this.z*b.z},cross:function(b){return new l(this.y*b.z-this.z*b.y,this.z*b.x-this.x*b.z,this.x*b.y-this.y*b.x)},length:function(){return Math.sqrt(this.dot(this))},unit:function(){return this.divide(this.length())},min:function(){return Math.min(Math.min(this.x,
this.y),this.z)},max:function(){return Math.max(Math.max(this.x,this.y),this.z)},toAngles:function(){return{theta:Math.atan2(this.z,this.x),phi:Math.asin(this.y/this.length())}},toArray:function(b){return[this.x,this.y,this.z].slice(0,b||3)},clone:function(){return new l(this.x,this.y,this.z)},init:function(b,c,a){this.x=b;this.y=c;this.z=a;return this}};l.negative=function(b,c){c.x=-b.x;c.y=-b.y;c.z=-b.z;return c};l.add=function(b,c,a){if(c instanceof l){a.x=b.x+c.x;a.y=b.y+c.y;a.z=b.z+c.z}else{a.x=
b.x+c;a.y=b.y+c;a.z=b.z+c}return a};l.subtract=function(b,c,a){if(c instanceof l){a.x=b.x-c.x;a.y=b.y-c.y;a.z=b.z-c.z}else{a.x=b.x-c;a.y=b.y-c;a.z=b.z-c}return a};l.multiply=function(b,c,a){if(c instanceof l){a.x=b.x*c.x;a.y=b.y*c.y;a.z=b.z*c.z}else{a.x=b.x*c;a.y=b.y*c;a.z=b.z*c}return a};l.divide=function(b,c,a){if(c instanceof l){a.x=b.x/c.x;a.y=b.y/c.y;a.z=b.z/c.z}else{a.x=b.x/c;a.y=b.y/c;a.z=b.z/c}return a};l.cross=function(b,c,a){a.x=b.y*c.z-b.z*c.y;a.y=b.z*c.x-b.x*c.z;a.z=b.x*c.y-b.y*c.x;return a};
l.unit=function(b,c){var a=b.length();c.x=b.x/a;c.y=b.y/a;c.z=b.z/a;return c};l.fromAngles=function(b,c){return new l(Math.cos(b)*Math.cos(c),Math.sin(c),Math.sin(b)*Math.cos(c))};l.randomDirection=function(){return l.fromAngles(Math.random()*Math.PI*2,Math.asin(Math.random()*2-1))};l.min=function(b,c){return new l(Math.min(b.x,c.x),Math.min(b.y,c.y),Math.min(b.z,c.z))};l.max=function(b,c){return new l(Math.max(b.x,c.x),Math.max(b.y,c.y),Math.max(b.z,c.z))};l.lerp=function(b,c,a){return c.subtract(b).multiply(a).add(b)};
l.fromArray=function(b){return new l(b[0],b[1],b[2])};return v}();

192
viewer.js Normal file
View file

@ -0,0 +1,192 @@
// Draw triangle lines:
Viewer.drawLines = false;
// Set to true so lines don't use the depth buffer
Viewer.lineOverlay = false;
// Set the color of all polygons in this solid
CSG.prototype.setColor = function(r, g, b) {
this.toPolygons().map(function(polygon) {
polygon.shared = [r, g, b];
});
};
// Convert from CSG solid to GL.Mesh object
CSG.prototype.toMesh = function() {
var csg = this.canonicalized();
var mesh = new GL.Mesh({ normals: true, colors: true });
var vertexTag2Index = {};
var vertices = [];
var colors = [];
var triangles = [];
// set to true if we want to use interpolated vertex normals
// this creates nice round spheres but does not represent the shape of
// the actual model
var smoothlighting = false;
var polygons = csg.toPolygons();
var numpolygons = polygons.length;
for(var polygonindex = 0; polygonindex < numpolygons; polygonindex++)
{
var polygon = polygons[polygonindex];
var indices = polygon.vertices.map(function(vertex) {
var vertextag = vertex.getTag();
var vertexindex;
if(smoothlighting && (vertextag in vertexTag2Index))
{
vertexindex = vertexTag2Index[vertextag];
}
else
{
vertexindex = vertices.length;
vertexTag2Index[vertextag] = vertexindex;
vertices.push([vertex.pos.x, vertex.pos.y, vertex.pos.z]);
colors.push([0,0,1]);
}
return vertexindex;
});
for (var i = 2; i < indices.length; i++) {
triangles.push([indices[0], indices[i - 1], indices[i]]);
}
}
mesh.triangles = triangles;
mesh.vertices = vertices;
mesh.colors = colors;
mesh.computeWireframe();
mesh.computeNormals();
return mesh;
};
var viewers = [];
// A viewer is a WebGL canvas that lets the user view a mesh. The user can
// tumble it around by dragging the mouse.
function Viewer(csg, width, height, depth) {
var angleX = 0;
var angleY = 0;
var viewpointX = 0;
var viewpointY = 0;
viewers.push(this);
// Get a new WebGL canvas
var gl = GL.create();
this.gl = gl;
this.mesh = csg.toMesh();
// Set up the viewport
gl.canvas.width = width;
gl.canvas.height = height;
gl.viewport(0, 0, width, height);
gl.matrixMode(gl.PROJECTION);
gl.loadIdentity();
gl.perspective(45, width / height, 0.1, 100);
gl.matrixMode(gl.MODELVIEW);
// Set up WebGL state
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.clearColor(0.93, 0.93, 0.93, 1);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.polygonOffset(1, 1);
// Black shader for wireframe
this.blackShader = new GL.Shader('\
void main() {\
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
}\
', '\
void main() {\
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.1);\
}\
');
// Shader with diffuse and specular lighting
this.lightingShader = new GL.Shader('\
varying vec3 color;\
varying vec3 normal;\
varying vec3 light;\
void main() {\
const vec3 lightDir = vec3(1.0, 2.0, 3.0) / 3.741657386773941;\
light = lightDir;\
color = gl_Color.rgb;\
normal = gl_NormalMatrix * gl_Normal;\
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
}\
', '\
varying vec3 color;\
varying vec3 normal;\
varying vec3 light;\
void main() {\
vec3 n = normalize(normal);\
float diffuse = max(0.0, dot(light, n));\
float specular = pow(max(0.0, -reflect(light, n).z), 10.0) * sqrt(diffuse);\
gl_FragColor = vec4(mix(color * (0.3 + 0.7 * diffuse), vec3(1.0), specular), 1.0);\
}\
');
var _this=this;
gl.onmousemove = function(e) {
if (e.dragging) {
e.preventDefault();
if(e.altKey)
{
var factor = 1e-2;
depth *= Math.pow(2,factor * e.deltaY);
}
else if(e.shiftKey)
{
var factor = 5e-3;
viewpointX += factor * e.deltaX * depth;
viewpointY -= factor * e.deltaY * depth;
}
else
{
angleY += e.deltaX * 2;
angleX += e.deltaY * 2;
angleX = Math.max(-90, Math.min(90, angleX));
}
_this.gl.ondraw();
}
};
gl.onmousewheel = function(e) {
e.preventDefault();
var delta = e.wheelDelta();
var factor = 1e-5;
depth *= Math.pow(factor * delta);
};
var that = this;
gl.ondraw = function() {
gl.makeCurrent();
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.loadIdentity();
gl.translate(viewpointX, viewpointY, -depth);
gl.rotate(angleX, 1, 0, 0);
gl.rotate(angleY, 0, 1, 0);
if (!Viewer.lineOverlay) gl.enable(gl.POLYGON_OFFSET_FILL);
that.lightingShader.draw(that.mesh, gl.TRIANGLES);
if (!Viewer.lineOverlay) gl.disable(gl.POLYGON_OFFSET_FILL);
if(Viewer.drawLines)
{
if (Viewer.lineOverlay) gl.disable(gl.DEPTH_TEST);
gl.enable(gl.BLEND);
that.blackShader.draw(that.mesh, gl.LINES);
gl.disable(gl.BLEND);
if (Viewer.lineOverlay) gl.enable(gl.DEPTH_TEST);
}
};
gl.ondraw();
}
var nextID = 0;
function addViewer(viewer) {
document.getElementById(nextID++).appendChild(viewer.gl.canvas);
}