First release!
This commit is contained in:
parent
e96769b609
commit
4516cd57e7
290
index.html
290
index.html
|
@ -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, '&').replace(/</g, '<').replace(/>/g, '>') + '</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
61
lightgl.js
Normal 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
192
viewer.js
Normal 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);
|
||||||
|
}
|
Loading…
Reference in a new issue