From c17f928a6bdb47fc31eb1c48e47c7dbbc1ebf89d Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Fri, 20 Jan 2012 15:16:07 +0100 Subject: [PATCH 01/48] first commit --- README | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README diff --git a/README b/README new file mode 100644 index 0000000..e69de29 From e96769b6094804ef455b7539ac00c2b0396f3c59 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Fri, 20 Jan 2012 15:20:13 +0100 Subject: [PATCH 02/48] First pages commit --- index.html | 1 + 1 file changed, 1 insertion(+) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 0000000..3ee6711 --- /dev/null +++ b/index.html @@ -0,0 +1 @@ +Test! From 4516cd57e77c8819620424c4ccb916cb12877cfc Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Fri, 20 Jan 2012 15:52:48 +0100 Subject: [PATCH 03/48] First release! --- csg.js | 2957 ++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 290 +++++- lightgl.js | 61 ++ viewer.js | 192 ++++ 4 files changed, 3499 insertions(+), 1 deletion(-) create mode 100644 csg.js create mode 100644 lightgl.js create mode 100644 viewer.js diff --git a/csg.js b/csg.js new file mode 100644 index 0000000..83f0edf --- /dev/null +++ b/csg.js @@ -0,0 +1,2957 @@ +var _CSGDEBUG=false; + +/* +## License + +Copyright (c) 2012 Joost Nieuwenhuijse (joost@newhouse.nl) under MIT license + +Based on original CSG.js: http://evanw.github.com/csg.js/ +Copyright (c) 2011 Evan Wallace under MIT license + +## Overview + +For an overview of the CSG process see the original csg.js code: +http://evanw.github.com/csg.js/ + +CSG operations through BSP trees suffer from one problem: heavy fragmentation +of polygons. If two CSG solids of n polygons are unified, the resulting solid may have +in the order of n*n polygons, because each polygon is split by the planes of all other +polygons. After a few operations the number of polygons explodes. + +This version of CSG.js solves the problem in 3 ways: + +1. Every polygon split is recorded in a tree (CSG.PolygonTreeNode). This is a separate +tree, not to be confused with the CSG tree. If a polygon is split into two parts but in +the end both fragments have not been discarded by the CSG operation, we can retrieve +the original unsplit polygon from the tree, instead of the two fragments. + +This does not completely solve the issue though: if a polygon is split multiple times +the number of fragments depends on the order of subsequent splits, and we might still +end up with unncessary splits: +Suppose a polygon is first split into A and B, and then into A1, B1, A2, B2. Suppose B2 is +discarded. We will end up with 2 polygons: A and B1. Depending on the actual split boundaries +we could still have joined A and B1 into one polygon. Therefore a second approach is used as well: + +2. After CSG operations all coplanar polygon fragments are joined by a retesselating +operation. See CSG.reTesselated(). Retesselation is done through a +linear sweep over the polygon surface. The sweep line passes over the y coordinates +of all vertices in the polygon. Polygons are split at each sweep line, and the fragments +are joined horizontally and vertically into larger polygons (making sure that we +will end up with convex polygons). +This still doesn't solve the problem completely: due to floating point imprecisions +we may end up with small gaps between polygons, and polygons may not be exactly coplanar +anymore, and as a result the retesselation algorithm may fail to join those polygons. +Therefore: + +3. A canonicalization algorithm is implemented: it looks for vertices that have +approximately the same coordinates (with a certain tolerance, say 1e-5) and replaces +them with the same vertex. If polygons share a vertex they will actually point to the +same CSG.Vertex instance. The same is done for polygon planes. See CSG.canonicalized(). + + +Performance improvements to the original CSG.js: + +Replaced the flip() and invert() methods by flipped() and inverted() which don't +modify the source object. This allows to get rid of all clone() calls, so that +multiple polygons can refer to the same CSG.Plane instance etc. + +The original union() used an extra invert(), clipTo(), invert() sequence just to remove the +coplanar front faces from b; this is now combined in a single b.clipTo(a, true) call. + +Detection whether a polygon is in front or in back of a plane: for each polygon +we are caching the coordinates of the bounding sphere. If the bounding sphere is +in front or in back of the plane we don't have to check the individual vertices +anymore. + + +Other additions to the original CSG.js: + +CSG.Vector class has been renamed into CSG.Vector3D + +Classes for 3D lines, 2D vectors, 2D lines, and methods to find the intersection of +a line and a plane etc. + +Transformations: CSG.transform(), CSG.translate(), CSG.rotate(), CSG.scale() + +Extrusion of 2D polygons (CSG.Polygon2D.extrude()) + +Expanding or contracting a solid: CSG.expand() and CSG.contract(). Creates nice +smooth corners. + +The vertex normal has been removed since it complicates retesselation. It's not needed +for solid CAD anyway. + +*/ + +// # class CSG + +// Holds a binary space partition tree representing a 3D solid. Two solids can +// be combined using the `union()`, `subtract()`, and `intersect()` methods. + +CSG = function() { + this.polygons = []; +}; + +// Construct a CSG solid from a list of `CSG.Polygon` instances. +CSG.fromPolygons = function(polygons) { + var csg = new CSG(); + csg.polygons = polygons; + return csg; +}; + +CSG.prototype = { + toPolygons: function() { + return this.polygons; + }, + + // Return a new CSG solid representing space in either this solid or in the + // solid `csg`. Neither this solid nor the solid `csg` are modified. + // + // A.union(B) + // + // +-------+ +-------+ + // | | | | + // | A | | | + // | +--+----+ = | +----+ + // +----+--+ | +----+ | + // | B | | | + // | | | | + // +-------+ +-------+ + // + union: function(csg) { + return this.unionSub(csg, true, true); + }, + + unionSub: function(csg, retesselate, canonicalize) { + var a = new CSG.Tree(this.polygons); + var b = new CSG.Tree(csg.polygons); + a.clipTo(b, false); + b.clipTo(a, true); + var newpolygons = a.allPolygons().concat(b.allPolygons()); + var csg = CSG.fromPolygons(newpolygons); + if(canonicalize) csg = csg.canonicalized(); + if(retesselate) csg = csg.reTesselated(); + return csg; + }, + + // Return a new CSG solid representing space in this solid but not in the + // solid `csg`. Neither this solid nor the solid `csg` are modified. + // + // A.subtract(B) + // + // +-------+ +-------+ + // | | | | + // | A | | | + // | +--+----+ = | +--+ + // +----+--+ | +----+ + // | B | + // | | + // +-------+ + // + subtract: function(csg) { + return this.subtractSub(csg, true, true); + }, + + subtractSub: function(csg, retesselate, canonicalize) { + var a = new CSG.Tree(this.polygons); + var b = new CSG.Tree(csg.polygons); + a.invert(); + a.clipTo(b); + b.clipTo(a, true); + a.addPolygons(b.allPolygons()); + a.invert(); + var csg = CSG.fromPolygons(a.allPolygons()); + if(canonicalize) csg = csg.canonicalized(); + if(retesselate) csg = csg.reTesselated(); + return csg; + }, + + // Return a new CSG solid representing space both this solid and in the + // solid `csg`. Neither this solid nor the solid `csg` are modified. + // + // A.intersect(B) + // + // +-------+ + // | | + // | A | + // | +--+----+ = +--+ + // +----+--+ | +--+ + // | B | + // | | + // +-------+ + // + intersect: function(csg) { + return this.intersectSub(csg, true, true); + }, + + intersectSub: function(csg, retesselate, canonicalize) { + var a = new CSG.Tree(this.polygons); + var b = new CSG.Tree(csg.polygons); + a.invert(); + b.clipTo(a); + b.invert(); + a.clipTo(b); + b.clipTo(a); + a.addPolygons(b.allPolygons()); + a.invert(); + var csg = CSG.fromPolygons(a.allPolygons()); + if(canonicalize) csg = csg.canonicalized(); + if(retesselate) csg = csg.reTesselated(); + return csg; + }, + + // Return a new CSG solid with solid and empty space switched. This solid is + // not modified. + inverse: function() { + var flippedpolygons = this.polygons.map(function(p) { p.flipped(); }); + return CSG.fromPolygons(flippedpolygons); + }, + + // Affine transformation of CSG object. Returns a new CSG object + transform: function(matrix4x4) { + var newpolygons = this.polygons.map(function(p) { return p.transform(matrix4x4); } ); + return CSG.fromPolygons(newpolygons); + }, + + translate: function(v) { + return this.transform(CSG.Matrix4x4.translation(v)); + }, + + scale: function(f) { + return this.transform(CSG.Matrix4x4.scaling(f)); + }, + + rotateX: function(deg) { + return this.transform(CSG.Matrix4x4.rotationX(deg)); + }, + + rotateY: function(deg) { + return this.transform(CSG.Matrix4x4.rotationY(deg)); + }, + + rotateZ: function(deg) { + return this.transform(CSG.Matrix4x4.rotationZ(deg)); + }, + + toStlString: function() { + var result="solid csg.js\n"; + this.polygons.map(function(p){ result += p.toStlString(); }); + result += "endsolid csg.js\n"; + return result; + }, + + toString: function() { + var result = ""; + this.polygons.map(function(p){ result += p.toString(); }); + return result; + }, + + // Expand the solid + // resolution: number of points per 360 degree for the rounded corners + expand: function(radius, resolution) { + var result=this; + this.polygons.map(function(p) { + var expanded=p.expand(radius, resolution); + result=result.unionSub(expanded, true, false); + }); + result = result.canonicalized(); + return result; + }, + + // Contract the solid + // resolution: number of points per 360 degree for the rounded corners + contract: function(radius, resolution) { + var result=this; + this.polygons.map(function(p) { + var expanded=p.expand(radius, resolution); + result=result.subtract(expanded); + }); + return result; + }, + + canonicalized: function () { + if(this.isCanonicalized) + { + return this; + } + else + { + var factory = new CSG.fuzzyCSGFactory(); + var result = factory.getCSG(this); + result.isCanonicalized = true; + return result; + } + }, + + reTesselated: function () { + if(this.isRetesselated) + { + return this; + } + else + { + var csg=this; //.canonicalized(); + var polygonsPerPlane = {}; + csg.polygons.map(function(polygon) { + var planetag = polygon.plane.getTag(); + if(! (planetag in polygonsPerPlane) ) + { + polygonsPerPlane[planetag] = []; + } + polygonsPerPlane[planetag].push(polygon); + }); + var destpolygons = []; + for(planetag in polygonsPerPlane) + { + var sourcepolygons = polygonsPerPlane[planetag]; + if(sourcepolygons.length < 2) + { + destpolygons = destpolygons.concat(sourcepolygons); + } + else + { + var retesselayedpolygons = []; + CSG.reTesselateCoplanarPolygons(sourcepolygons, retesselayedpolygons); + destpolygons = destpolygons.concat(retesselayedpolygons); + } + } + var result = CSG.fromPolygons(destpolygons); + result.isRetesselated = true; + return result; + } + }, +}; + +// Parse an option from the options object +// If the option is not present, return the default value +CSG.parseOption = function(options, optionname, defaultvalue) { + var result = defaultvalue; + if(options) + { + if(optionname in options) + { + result = options[optionname]; + } + } + return result; +}; + +// Parse an option and force into a CSG.Vector3D. If a scalar is passed it is converted +// into a vector with equal x,y,z +CSG.parseOptionAs3DVector = function(options, optionname, defaultvalue) { + var result = CSG.parseOption(options, optionname, defaultvalue); + result = new CSG.Vector3D(result); + return result; +}; + +CSG.parseOptionAsFloat = function(options, optionname, defaultvalue) { + var result = CSG.parseOption(options, optionname, defaultvalue); + if(typeof(result) == "string") + { + result = Number(result); + } + else if(typeof(result) != "number") + { + throw new Error("Parameter "+optionname+" should be a number"); + } + return result; +}; + +CSG.parseOptionAsInt = function(options, optionname, defaultvalue) { + var result = CSG.parseOption(options, optionname, defaultvalue); + return Number(Math.floor(result)); +}; + +// Construct an axis-aligned solid cuboid. +// Parameters: +// center: center of cube (default [0,0,0]) +// radius: radius of cube (default [1,1,1]), can be specified as scalar or as 3D vector +// +// Example code: +// +// var cube = CSG.cube({ +// center: [0, 0, 0], +// radius: 1 +// }); +CSG.cube = function(options) { + var c = CSG.parseOptionAs3DVector(options, "center", [0,0,0]); + var r = CSG.parseOptionAs3DVector(options, "radius", [1,1,1]); + return CSG.fromPolygons([ + [[0, 4, 6, 2], [-1, 0, 0]], + [[1, 3, 7, 5], [+1, 0, 0]], + [[0, 1, 5, 4], [0, -1, 0]], + [[2, 6, 7, 3], [0, +1, 0]], + [[0, 2, 3, 1], [0, 0, -1]], + [[4, 5, 7, 6], [0, 0, +1]] + ].map(function(info) { + var normal = new CSG.Vector3D(info[1]); + //var plane = new CSG.Plane(normal, 1); + var vertices = info[0].map(function(i) { + var pos = new CSG.Vector3D( + c.x + r.x * (2 * !!(i & 1) - 1), + c.y + r.y * (2 * !!(i & 2) - 1), + c.z + r.z * (2 * !!(i & 4) - 1) + ); + return new CSG.Vertex(pos); + }); + return new CSG.Polygon(vertices, null /* , plane */); + })); +}; + +// Construct a solid sphere +// +// Parameters: +// center: center of sphere (default [0,0,0]) +// radius: radius of sphere (default 1), must be a scalar +// resolution: determines the number of polygons per 360 degree revolution (default 12) +// +// Example usage: +// +// var sphere = CSG.sphere({ +// center: [0, 0, 0], +// radius: 2, +// resolution: 32, +// }); +CSG.sphere = function(options) { + options = options || {}; + var center = CSG.parseOptionAs3DVector(options, "center", [0,0,0]); + var radius = CSG.parseOptionAsFloat(options, "radius", 1); + var resolution = CSG.parseOptionAsInt(options, "resolution", 12); + if(resolution < 4) resolution = 4; + var qresolution = Math.round(resolution / 4); + var xvector = new CSG.Vector3D([1,0,0]).times(radius); + var yvector = new CSG.Vector3D([0,-1,0]).times(radius); + var zvector = new CSG.Vector3D([0,0,1]).times(radius); + var prevcylinderpoint; + var polygons = []; + for(var slice1 = 0; slice1 <= resolution; slice1++) + { + var angle = Math.PI * 2.0 * slice1 / resolution; + var cylinderpoint = xvector.times(Math.cos(angle)).plus(yvector.times(Math.sin(angle))); + if(slice1 > 0) + { + // cylinder vertices: + var vertices = []; + var prevcospitch, prevsinpitch; + for(var slice2 = 0; slice2 <= qresolution; slice2++) + { + var pitch = 0.5 * Math.PI * slice2 / qresolution; + var cospitch = Math.cos(pitch); + var sinpitch = Math.sin(pitch); + if(slice2 > 0) + { + vertices = []; + vertices.push(new CSG.Vertex(center.plus(prevcylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch))))); + vertices.push(new CSG.Vertex(center.plus(cylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch))))); + if(slice2 < qresolution) + { + vertices.push(new CSG.Vertex(center.plus(cylinderpoint.times(cospitch).minus(zvector.times(sinpitch))))); + } + vertices.push(new CSG.Vertex(center.plus(prevcylinderpoint.times(cospitch).minus(zvector.times(sinpitch))))); + polygons.push(new CSG.Polygon(vertices)); + vertices = []; + vertices.push(new CSG.Vertex(center.plus(prevcylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch))))); + vertices.push(new CSG.Vertex(center.plus(cylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch))))); + if(slice2 < qresolution) + { + vertices.push(new CSG.Vertex(center.plus(cylinderpoint.times(cospitch).plus(zvector.times(sinpitch))))); + } + vertices.push(new CSG.Vertex(center.plus(prevcylinderpoint.times(cospitch).plus(zvector.times(sinpitch))))); + vertices.reverse(); + polygons.push(new CSG.Polygon(vertices)); + } + prevcospitch = cospitch; + prevsinpitch = sinpitch; + } + } + prevcylinderpoint = cylinderpoint; + } + return CSG.fromPolygons(polygons); +}; + +// Construct a solid cylinder. +// +// Parameters: +// start: start point of cylinder (default [0, -1, 0]) +// end: end point of cylinder (default [0, 1, 0]) +// radius: radius of cylinder (default 1), must be a scalar +// resolution: determines the number of polygons per 360 degree revolution (default 12) +// +// Example usage: +// +// var cylinder = CSG.cylinder({ +// start: [0, -1, 0], +// end: [0, 1, 0], +// radius: 1, +// resolution: 16 +// }); +CSG.cylinder = function(options) { + var s = CSG.parseOptionAs3DVector(options, "start", [0, -1, 0]); + var e = CSG.parseOptionAs3DVector(options, "end", [0, 1, 0]); + var r = CSG.parseOptionAsFloat(options, "radius", 1); + var slices = CSG.parseOptionAsFloat(options, "resolution", 12); + var ray = e.minus(s); + var axisZ = ray.unit(), isY = (Math.abs(axisZ.y) > 0.5); + var axisX = new CSG.Vector3D(isY, !isY, 0).cross(axisZ).unit(); + var axisY = axisX.cross(axisZ).unit(); + var start = new CSG.Vertex(s); + var end = new CSG.Vertex(e); + var polygons = []; + function point(stack, slice, normalBlend) { + var angle = slice * Math.PI * 2; + var out = axisX.times(Math.cos(angle)).plus(axisY.times(Math.sin(angle))); + var pos = s.plus(ray.times(stack)).plus(out.times(r)); + var normal = out.times(1 - Math.abs(normalBlend)).plus(axisZ.times(normalBlend)); + return new CSG.Vertex(pos); + } + for (var i = 0; i < slices; i++) { + var t0 = i / slices, t1 = (i + 1) / slices; + polygons.push(new CSG.Polygon([start, point(0, t0, -1), point(0, t1, -1)])); + polygons.push(new CSG.Polygon([point(0, t1, 0), point(0, t0, 0), point(1, t0, 0), point(1, t1, 0)])); + polygons.push(new CSG.Polygon([end, point(1, t1, 1), point(1, t0, 1)])); + } + return CSG.fromPolygons(polygons); +}; + +// Like a cylinder, but with rounded ends instead of flat +// +// Parameters: +// start: start point of cylinder (default [0, -1, 0]) +// end: end point of cylinder (default [0, 1, 0]) +// radius: radius of cylinder (default 1), must be a scalar +// resolution: determines the number of polygons per 360 degree revolution (default 12) +// normal: a vector determining the starting angle for tesselation. Should be non-parallel to start.minus(end) +// +// Example usage: +// +// var cylinder = CSG.roundedCylinder({ +// start: [0, -1, 0], +// end: [0, 1, 0], +// radius: 1, +// resolution: 16 +// }); +CSG.roundedCylinder = function(options) { + var p1 = CSG.parseOptionAs3DVector(options, "start", [0, -1, 0]); + var p2 = CSG.parseOptionAs3DVector(options, "end", [0, 1, 0]); + var radius = CSG.parseOptionAsFloat(options, "radius", 1); + var direction = p2.minus(p1); + var defaultnormal; + if(Math.abs(direction.x) > Math.abs(direction.y)) + { + defaultnormal = new CSG.Vector3D(0,1,0); + } + else + { + defaultnormal = new CSG.Vector3D(1,0,0); + } + var normal = CSG.parseOptionAs3DVector(options, "normal", defaultnormal); + var resolution = CSG.parseOptionAsFloat(options, "resolution", 12); + if(resolution < 4) resolution = 4; + var polygons = []; + var qresolution = Math.floor(0.25*resolution); + var length = direction.length(); + if(length < 1e-10) + { + return CSG.sphere({center: p1, radius: radius, resolution: resolution}); + } + var zvector = direction.unit().times(radius); + var xvector = zvector.cross(normal).unit().times(radius); + var yvector = xvector.cross(zvector).unit().times(radius); + var prevcylinderpoint; + for(var slice1 = 0; slice1 <= resolution; slice1++) + { + var angle = Math.PI * 2.0 * slice1 / resolution; + var cylinderpoint = xvector.times(Math.cos(angle)).plus(yvector.times(Math.sin(angle))); + if(slice1 > 0) + { + // cylinder vertices: + var vertices = []; + vertices.push(new CSG.Vertex(p1.plus(cylinderpoint))); + vertices.push(new CSG.Vertex(p1.plus(prevcylinderpoint))); + vertices.push(new CSG.Vertex(p2.plus(prevcylinderpoint))); + vertices.push(new CSG.Vertex(p2.plus(cylinderpoint))); + polygons.push(new CSG.Polygon(vertices)); + var prevcospitch, prevsinpitch; + for(var slice2 = 0; slice2 <= qresolution; slice2++) + { + var pitch = 0.5 * Math.PI * slice2 / qresolution; + var cospitch = Math.cos(pitch); + var sinpitch = Math.sin(pitch); + if(slice2 > 0) + { + vertices = []; + vertices.push(new CSG.Vertex(p1.plus(prevcylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch))))); + vertices.push(new CSG.Vertex(p1.plus(cylinderpoint.times(prevcospitch).minus(zvector.times(prevsinpitch))))); + if(slice2 < qresolution) + { + vertices.push(new CSG.Vertex(p1.plus(cylinderpoint.times(cospitch).minus(zvector.times(sinpitch))))); + } + vertices.push(new CSG.Vertex(p1.plus(prevcylinderpoint.times(cospitch).minus(zvector.times(sinpitch))))); + polygons.push(new CSG.Polygon(vertices)); + vertices = []; + vertices.push(new CSG.Vertex(p2.plus(prevcylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch))))); + vertices.push(new CSG.Vertex(p2.plus(cylinderpoint.times(prevcospitch).plus(zvector.times(prevsinpitch))))); + if(slice2 < qresolution) + { + vertices.push(new CSG.Vertex(p2.plus(cylinderpoint.times(cospitch).plus(zvector.times(sinpitch))))); + } + vertices.push(new CSG.Vertex(p2.plus(prevcylinderpoint.times(cospitch).plus(zvector.times(sinpitch))))); + vertices.reverse(); + polygons.push(new CSG.Polygon(vertices)); + } + prevcospitch = cospitch; + prevsinpitch = sinpitch; + } + } + prevcylinderpoint = cylinderpoint; + } + return CSG.fromPolygons(polygons); +}; + +// Construct an axis-aligned solid rounded cuboid. +// Parameters: +// center: center of cube (default [0,0,0]) +// radius: radius of cube (default [1,1,1]), can be specified as scalar or as 3D vector +// roundradius: radius of rounded corners (default 0.2), must be a scalar +// resolution: determines the number of polygons per 360 degree revolution (default 8) +// +// Example code: +// +// var cube = CSG.roundedCube({ +// center: [0, 0, 0], +// radius: 1, +// roundradius: 0.2, +// resolution: 8, +// }); +CSG.roundedCube = function(options) { + var center = CSG.parseOptionAs3DVector(options, "center", [0,0,0]); + var cuberadius = CSG.parseOptionAs3DVector(options, "radius", [1,1,1]); + var resolution = CSG.parseOptionAsFloat(options, "resolution", 8); + if(resolution < 4) resolution = 4; + var roundradius = CSG.parseOptionAsFloat(options, "roundradius", 0.2); + var innercuberadius=cuberadius.clone(); + innercuberadius.x -= roundradius; + innercuberadius.y -= roundradius; + innercuberadius.z -= roundradius; + var result = CSG.cube({center: center, radius: [cuberadius.x, innercuberadius.y, innercuberadius.z]}); + result = result.unionSub( CSG.cube({center: center, radius: [innercuberadius.x, cuberadius.y, innercuberadius.z]}),false,false); + result = result.unionSub( CSG.cube({center: center, radius: [innercuberadius.x, innercuberadius.y, cuberadius.z]}),false,false); + for(var level=0; level < 2; level++) + { + var z = innercuberadius.z; + if(level == 1) z = -z; + var p1 = new CSG.Vector3D(innercuberadius.x, innercuberadius.y, z).plus(center); + var p2 = new CSG.Vector3D(innercuberadius.x, -innercuberadius.y, z).plus(center); + var p3 = new CSG.Vector3D(-innercuberadius.x, -innercuberadius.y, z).plus(center); + var p4 = new CSG.Vector3D(-innercuberadius.x, innercuberadius.y, z).plus(center); + var sphere = CSG.sphere({center: p1, radius: roundradius, resolution: resolution}); + result = result.unionSub(sphere,false,false); + sphere = CSG.sphere({center: p2, radius: roundradius, resolution: resolution}); + result = result.unionSub(sphere,false,false); + sphere = CSG.sphere({center: p3, radius: roundradius, resolution: resolution}); + result = result.unionSub(sphere,false,false); + sphere = CSG.sphere({center: p4, radius: roundradius, resolution: resolution}); + result = result.unionSub(sphere,true,true); + var cylinder = CSG.cylinder({start:p1, end: p2, radius: roundradius, resolution: resolution}); + result = result.unionSub(cylinder,false,false); + cylinder = CSG.cylinder({start:p2, end: p3, radius: roundradius, resolution: resolution}); + result = result.unionSub(cylinder,false,false); + cylinder = CSG.cylinder({start:p3, end: p4, radius: roundradius, resolution: resolution}); + result = result.unionSub(cylinder,false,false); + cylinder = CSG.cylinder({start:p4, end: p1, radius: roundradius, resolution: resolution}); + result = result.unionSub(cylinder,false,false); + if(level == 0) { + var d = new CSG.Vector3D(0, 0, -2*z); + cylinder = CSG.cylinder({start:p1, end: p1.plus(d), radius: roundradius, resolution: resolution}); + result = result.unionSub(cylinder); + cylinder = CSG.cylinder({start:p2, end: p2.plus(d), radius: roundradius, resolution: resolution}); + result = result.unionSub(cylinder); + cylinder = CSG.cylinder({start:p3, end: p3.plus(d), radius: roundradius, resolution: resolution}); + result = result.unionSub(cylinder); + cylinder = CSG.cylinder({start:p4, end: p4.plus(d), radius: roundradius, resolution: resolution}); + result = result.unionSub(cylinder,true,true); + } + } + return result; +} + + + +// # class Vector3D + +// Represents a 3D vector. +// +// Example usage: +// +// new CSG.Vector3D(1, 2, 3); +// new CSG.Vector3D([1, 2, 3]); +// new CSG.Vector3D({ x: 1, y: 2, z: 3 }); + +CSG.Vector3D = function(x, y, z) { + var ok = true; + if (arguments.length == 1) + { + if(typeof(x) == "object") + { + if(x instanceof Array) + { + this.x = x[0]; + this.y = x[1]; + this.z = x[2]; + } + else if( ('x' in x) && ('y' in x) && ('z' in x) ) + { + this.x = x.x; + this.y = x.y; + this.z = x.z; + } + else ok = false; + } + else + { + var v = Number(x); + this.x = v; + this.y = v; + this.z = v; + } + } + else if (arguments.length == 3) + { + this.x = Number(x); + this.y = Number(y); + this.z = Number(z); + } + else ok = false; + if(!ok) + { + throw new Error("wrong arguments"); + } +}; + +CSG.Vector3D.prototype = { + clone: function() { + return new CSG.Vector3D(this.x, this.y, this.z); + }, + + negated: function() { + return new CSG.Vector3D(-this.x, -this.y, -this.z); + }, + + plus: function(a) { + return new CSG.Vector3D(this.x + a.x, this.y + a.y, this.z + a.z); + }, + + minus: function(a) { + return new CSG.Vector3D(this.x - a.x, this.y - a.y, this.z - a.z); + }, + + times: function(a) { + return new CSG.Vector3D(this.x * a, this.y * a, this.z * a); + }, + + dividedBy: function(a) { + return new CSG.Vector3D(this.x / a, this.y / a, this.z / a); + }, + + dot: function(a) { + return this.x * a.x + this.y * a.y + this.z * a.z; + }, + + lerp: function(a, t) { + return this.plus(a.minus(this).times(t)); + }, + + lengthSquared: function() { + return this.dot(this); + }, + + length: function() { + return Math.sqrt(this.lengthSquared()); + }, + + unit: function() { + return this.dividedBy(this.length()); + }, + + cross: function(a) { + return new CSG.Vector3D( + this.y * a.z - this.z * a.y, + this.z * a.x - this.x * a.z, + this.x * a.y - this.y * a.x + ); + }, + + distanceTo: function(a) { + return this.minus(a).length(); + }, + + distanceToSquared: function(a) { + return this.minus(a).lengthSquared(); + }, + + equals: function(a) { + return (this.x == a.x) && (this.y == a.y) && (this.z == a.z); + }, + + // Right multiply by a 4x4 matrix (the vector is interpreted as a row vector) + // Returns a new CSG.Vector3D + multiply4x4: function(matrix4x4) { + return matrix4x4.rightMultiply1x3Vector(this); + }, + + toStlString: function() { + return this.x+" "+this.y+" "+this.z; + }, + + toString: function() { + return "("+this.x+", "+this.y+", "+this.z+")"; + }, + +}; + +// # class Vertex + +// Represents a vertex of a polygon. Use your own vertex class instead of this +// one to provide additional features like texture coordinates and vertex +// colors. Custom vertex classes need to provide a `pos` property +// `flipped()`, and `interpolate()` methods that behave analogous to the ones +// defined by `CSG.Vertex`. + +CSG.Vertex = function(pos) { + this.pos = pos; +}; + +CSG.Vertex.prototype = { + // Return a vertex with all orientation-specific data (e.g. vertex normal) flipped. Called when the + // orientation of a polygon is flipped. + flipped: function() { + return this; + }, + + getTag: function() { + var result = this.tag; + if(!result) + { + result = CSG.getTag(); + this.tag = result; + } + return result; + }, + + // Create a new vertex between this vertex and `other` by linearly + // interpolating all properties using a parameter of `t`. Subclasses should + // override this to interpolate additional properties. + interpolate: function(other, t) { + var newpos = this.pos.lerp(other.pos, t); + return new CSG.Vertex(newpos); + }, + + // Affine transformation of vertex. Returns a new CSG.Vertex + transform: function(matrix4x4) { + var newpos = this.pos.multiply4x4(matrix4x4); + return new CSG.Vertex(newpos); + }, + + toStlString: function() { + return "vertex "+this.pos.toStlString()+"\n"; + }, + + toString: function() { + return this.pos.toString(); + }, +}; + +// # class Plane + +// Represents a plane in 3D space. + +CSG.Plane = function(normal, w) { + this.normal = normal; + this.w = w; +}; + +// `CSG.Plane.EPSILON` is the tolerance used by `splitPolygon()` to decide if a +// point is on the plane. +CSG.Plane.EPSILON = 1e-5; + +CSG.Plane.fromPoints = function(a, b, c) { + var n = b.minus(a).cross(c.minus(a)).unit(); + return new CSG.Plane(n, n.dot(a)); +}; + +CSG.Plane.prototype = { + flipped: function() { + return new CSG.Plane(this.normal.negated(), -this.w); + }, + + getTag: function() { + var result = this.tag; + if(!result) + { + result = CSG.getTag(); + this.tag = result; + } + return result; + }, + + equals: function(n) { + return this.normal.equals(n.normal) && this.w == n.w; + }, + + transform: function(matrix4x4) { + var origin = new CSG.Vector3D(0,0,0); + var pointOnPlane = this.normal.times(this.w); + var neworigin = origin.multiply4x4(matrix4x4); + var neworiginPlusNormal = this.normal.multiply4x4(matrix4x4); + var newnormal = neworiginPlusNormal.minus(neworigin); + var newpointOnPlane = pointOnPlane.multiply4x4(matrix4x4); + var neww = newnormal.dot(newpointOnPlane); + return new CSG.Plane(newnormal, neww); + }, + + // Returns object: + // .type: + // 0: coplanar-front + // 1: coplanar-back + // 2: front + // 3: back + // 4: spanning + // In case the polygon is spanning, returns: + // .front: a CSG.Polygon of the front part + // .back: a CSG.Polygon of the back part + splitPolygon: function(polygon) { + var result = { + type: null, + front: null, + back: null, + }; + // cache in local vars (speedup): + var planenormal = this.normal; + var vertices = polygon.vertices; + var numvertices = vertices.length; + if(polygon.plane.equals(this)) + { + result.type = 0; + } + else + { + var EPS = CSG.Plane.EPSILON; + var thisw = this.w; + // first check if the polygon's bounding sphere is completely in front or in back: + var bound = polygon.boundingSphere(); + var spherecenter = bound[0]; + var sphereradius = bound[1]; + sphereradius += EPS; + //var d = this.signedDistanceToPoint(spherecenter); + var d = planenormal.dot(spherecenter) - thisw; + + if(d > sphereradius) + { + result.type = 2; + } + else if(d < -sphereradius) + { + result.type = 3; + } + else + { + // no, we really have to check each vertex separately: + var hasfront = false; + var hasback = false; + var vertexIsBack = []; + var MINEPS = -EPS; + for (var i = 0; i < numvertices; i++) { + var t = planenormal.dot(vertices[i].pos) - thisw; + var isback = (t < 0); + vertexIsBack.push(isback); + if(t > EPS) hasfront = true; + if(t < MINEPS) hasback = true; + } + if( (!hasfront) && (!hasback) ) + { + // all points coplanar + var t = planenormal.dot(polygon.plane.normal); + result.type = (t >= 0)? 0:1; + } + else if(!hasback) + { + result.type = 2; + } + else if(!hasfront) + { + result.type = 3; + } + else + { + // spanning + result.type = 4; + var frontvertices = [], backvertices = []; + var isback = vertexIsBack[0]; + for(var vertexindex = 0; vertexindex < numvertices; vertexindex++) + { + var vertex = vertices[vertexindex]; + var nextvertexindex = vertexindex + 1; + if(nextvertexindex >= numvertices) nextvertexindex = 0; + var nextisback = vertexIsBack[nextvertexindex]; + if(isback == nextisback) + { + // line segment is on one side of the plane: + if(isback) + { + backvertices.push(vertex); + } + else + { + frontvertices.push(vertex); + } + } + else + { + // line segment intersects plane: + var point = vertex.pos; + var nextpoint = vertices[nextvertexindex].pos; + var line = CSG.Line3D.fromPoints(point, nextpoint); + var intersectionpoint = this.intersectWithLine(line); + var intersectionvertex = new CSG.Vertex(intersectionpoint); + if(isback) + { + backvertices.push(vertex); + backvertices.push(intersectionvertex); + frontvertices.push(intersectionvertex); + } + else + { + frontvertices.push(vertex); + frontvertices.push(intersectionvertex); + backvertices.push(intersectionvertex); + } + } + isback = nextisback; + } // for vertexindex + + // remove duplicate vertices: + var EPS_SQUARED = CSG.Plane.EPSILON * CSG.Plane.EPSILON; + if(backvertices.length >= 3) + { + var prevvertex = backvertices[backvertices.length - 1]; + for(var vertexindex = 0; vertexindex < backvertices.length; vertexindex++) + { + var vertex = backvertices[vertexindex]; + if(vertex.pos.distanceToSquared(prevvertex.pos) < EPS_SQUARED) + { + backvertices.splice(vertexindex,1); + vertexindex--; + } + prevvertex = vertex; + } + } + if(frontvertices.length >= 3) + { + var prevvertex = frontvertices[frontvertices.length - 1]; + for(var vertexindex = 0; vertexindex < frontvertices.length; vertexindex++) + { + var vertex = frontvertices[vertexindex]; + if(vertex.pos.distanceToSquared(prevvertex.pos) < EPS_SQUARED) + { + frontvertices.splice(vertexindex,1); + vertexindex--; + } + prevvertex = vertex; + } + } + if (frontvertices.length >= 3) + { + result.front = new CSG.Polygon(frontvertices, polygon.shared, polygon.plane); + } + if (backvertices.length >= 3) + { + result.back = new CSG.Polygon(backvertices, polygon.shared, polygon.plane); + } + } + } + } + return result; + }, + + // returns CSG.Point3D + intersectWithLine: function(line3d) { + return line3d.intersectWithPlane(this); + }, + + // intersection of two planes + intersectWithPlane: function(plane) { + return CSG.Line3D.fromPlanes(this, plane); + }, + + signedDistanceToPoint: function(point) { + var t = this.normal.dot(point) - this.w; + return t; + }, + + toString: function() { + return "[normal: "+this.normal.toString()+", w: "+this.w+"]"; + }, +}; + + +// # class Polygon + +// Represents a convex polygon. The vertices used to initialize a polygon must +// be coplanar and form a convex loop. They do not have to be `CSG.Vertex` +// instances but they must behave similarly (duck typing can be used for +// customization). +// +// Each convex polygon has a `shared` property, which is shared between all +// polygons that are clones of each other or were split from the same polygon. +// This can be used to define per-polygon properties (such as surface color). +// +// The plane of the polygon is calculated from the vertex coordinates +// To avoid unnecessary recalculation, the plane can alternatively be +// passed as the third argument +CSG.Polygon = function(vertices, shared, plane) { + this.vertices = vertices; + this.shared = shared; + var numvertices = vertices.length; + + if(arguments.length >= 3) + { + this.plane = plane; + } + else + { + this.plane = CSG.Plane.fromPoints(vertices[0].pos, vertices[1].pos, vertices[2].pos); + } + + if(_CSGDEBUG) + { + this.checkIfConvex(); + } +}; + +CSG.Polygon.prototype = { + // check whether the polygon is convex (it should be, otherwise we will get unexpected results) + checkIfConvex: function() { + if(! CSG.Polygon.verticesConvex(this.vertices, this.plane.normal)) + { + throw new Error("Not convex!"); + } + }, + + // Extrude a polygon into the direction offsetvector + // Returns a CSG object + extrude: function(offsetvector) { + var newpolygons = []; + + var polygon1=this; + var direction = polygon1.plane.normal.dot(offsetvector); + if(direction > 0) + { + polygon1 = polygon1.flipped(); + } + newpolygons.push(polygon1); + var polygon2=polygon1.translate(offsetvector); + var numvertices=this.vertices.length; + for(var i=0; i < numvertices; i++) + { + var sidefacepoints = []; + var nexti = (i < (numvertices-1))? i+1:0; + sidefacepoints.push(polygon1.vertices[i].pos); + sidefacepoints.push(polygon2.vertices[i].pos); + sidefacepoints.push(polygon2.vertices[nexti].pos); + sidefacepoints.push(polygon1.vertices[nexti].pos); + var sidefacepolygon=CSG.Polygon.createFromPoints(sidefacepoints); + newpolygons.push(sidefacepolygon); + } + polygon2 = polygon2.flipped(); + newpolygons.push(polygon2); + return CSG.fromPolygons(newpolygons); + }, + + translate: function(offset) { + return this.transform(CSG.Matrix4x4.translation(offset)); + }, + + // Expand the polygon with a certain radius + // This extrudes the face of the polygon and adds rounded corners + // Returns a CSG object (not a polygon anymore!) + // resolution: number of points per 360 degree for the rounded corners + expand: function(radius, resolution) { + if( (!resolution) || (resolution < 4) ) resolution = 4; + resolution = 4 * Math.floor(resolution / 4); + + var result=new CSG(); + + // expand each side of the polygon. The expansion of a line is a roundedCylinder: + var numvertices=this.vertices.length; + for(var i=0; i < numvertices; i++) + { + var previ = (i == 0) ? (numvertices-1):i-1; + var p1 = this.vertices[previ].pos; + var p2 = this.vertices[i].pos; + + var roundedCylinder = CSG.roundedCylinder({start: p1, end: p2, normal: this.plane.normal, radius: radius, resolution: resolution}); + result = result.unionSub(roundedCylinder, false, false); + } + var extrudevector=this.plane.normal.unit().times(2*radius); + var translatedpolygon = this.translate(extrudevector.times(-0.5)); + var extrudedface = translatedpolygon.extrude(extrudevector); + result=result.unionSub(extrudedface, true, false); + return result; + }, + + // returns an array with a CSG.Vector3D (center point) and a radius + boundingSphere: function() { + if(!this.cachedBoundingSphere) + { + var box = this.boundingBox(); + var middle = box[0].plus(box[1]).times(0.5); + var radius3 = box[1].minus(middle); + var radius = radius3.length(); + this.cachedBoundingSphere = [middle, radius]; + } + return this.cachedBoundingSphere; + }, + + // returns an array of two CSG.Vector3Ds (minimum coordinates and maximum coordinates) + boundingBox: function() { + if(!this.cachedBoundingBox) + { + var minpoint, maxpoint; + var vertices = this.vertices; + var numvertices = vertices.length; + if(numvertices == 0) + { + minpoint=new CSG.Vector3D(0,0,0); + maxpoint=new CSG.Vector3D(0,0,0); + } + else + { + minpoint=vertices[0].pos.clone(); + maxpoint=vertices[0].pos.clone(); + } + for(var i=1; i < numvertices; i++) + { + var point = vertices[i].pos; + minpoint.x = Math.min(minpoint.x, point.x); + minpoint.y = Math.min(minpoint.y, point.y); + minpoint.z = Math.min(minpoint.z, point.z); + maxpoint.x = Math.max(maxpoint.x, point.x); + maxpoint.y = Math.max(maxpoint.y, point.y); + maxpoint.z = Math.max(maxpoint.z, point.z); + } + this.cachedBoundingBox = [minpoint, maxpoint]; + } + return this.cachedBoundingBox; + }, + + flipped: function() { + var newvertices = this.vertices.map(function(v) { return v.flipped(); }); + newvertices.reverse(); + var newplane = this.plane.flipped(); + return new CSG.Polygon(newvertices, this.shared, newplane); + }, + + // Affine transformation of polygon. Returns a new CSG.Polygon + transform: function(matrix4x4) { + var newvertices = this.vertices.map(function(v) { return v.transform(matrix4x4); } ); + var newplane = this.plane.transform(matrix4x4); + return new CSG.Polygon(newvertices, this.shared, newplane); + }, + + toStlString: function() { + var result=""; + if(this.vertices.length >= 3) // should be! + { + // STL requires triangular polygons. If our polygon has more vertices, create + // multiple triangles: + var firstVertexStl = this.vertices[0].toStlString(); + for(var i=0; i < this.vertices.length-2; i++) + { + result += "facet normal "+this.plane.normal.toStlString()+"\nouter loop\n"; + result += firstVertexStl; + result += this.vertices[i+1].toStlString(); + result += this.vertices[i+2].toStlString(); + result += "endloop\nendfacet\n"; + } + } + return result; + }, + + toString: function() { + var result = "Polygon plane: "+this.plane.toString()+"\n"; + this.vertices.map(function(vertex) { + result += " "+vertex.toString()+"\n"; + }); + return result; + }, +}; + +CSG.Polygon.verticesConvex = function(vertices, planenormal) { + var numvertices = vertices.length; + if(numvertices > 2) + { + var prevprevpos=vertices[numvertices-2].pos; + var prevpos=vertices[numvertices-1].pos; + for(var i=0; i < numvertices; i++) + { + var pos=vertices[i].pos; + if(!CSG.Polygon.isConvexPoint(prevprevpos, prevpos, pos, planenormal)) + { + return false; + } + prevprevpos=prevpos; + prevpos=pos; + } + } + return true; +}; + +// Create a polygon from the given points +CSG.Polygon.createFromPoints = function(points, shared, plane) { + var normal; + if(arguments.length < 3) + { + // initially set a dummy vertex normal: + normal = new CSG.Vector3D(0, 0, 0); + } + else + { + normal = plane.normal; + } + var vertices = []; + points.map( function(p) { + var vec = new CSG.Vector3D(p); + var vertex = new CSG.Vertex(vec); + vertices.push(vertex); + }); + var polygon; + if(arguments.length < 3) + { + polygon = new CSG.Polygon(vertices, shared); + } + else + { + polygon = new CSG.Polygon(vertices, shared, plane); + } + return polygon; +}; + +// calculate whether three points form a convex corner +// prevpoint, point, nextpoint: the 3 coordinates (CSG.Vector3D instances) +// normal: the normal vector of the plane +CSG.Polygon.isConvexPoint = function(prevpoint, point, nextpoint, normal) { + var crossproduct=point.minus(prevpoint).cross(nextpoint.minus(point)); + var crossdotnormal=crossproduct.dot(normal); + return (crossdotnormal >= 0); +}; + +CSG.Polygon.isStrictlyConvexPoint = function(prevpoint, point, nextpoint, normal) { + var crossproduct=point.minus(prevpoint).cross(nextpoint.minus(point)); + var crossdotnormal=crossproduct.dot(normal); + return (crossdotnormal >= 1e-5); +}; + +// # class PolygonTreeNode + +// This class manages hierarchical splits of polygons +// At the top is a root node which doesn hold a polygon, only child PolygonTreeNodes +// Below that are zero or more 'top' nodes; each holds a polygon. The polygons can be in different planes +// splitByPlane() splits a node by a plane. If the plane intersects the polygon, two new child nodes +// are created holding the splitted polygon. +// getPolygons() retrieves the polygon from the tree. If for PolygonTreeNode the polygon is split but +// the two split parts (child nodes) are still intact, then the unsplit polygon is returned. +// This ensures that we can safely split a polygon into many fragments. If the fragments are untouched, +// getPolygons() will return the original unsplit polygon instead of the fragments. +// remove() removes a polygon from the tree. Once a polygon is removed, the parent polygons are invalidated +// since they are no longer intact. + +// constructor creates the root node: +CSG.PolygonTreeNode = function() { + this.parent = null; + this.children = []; + this.polygon = null; + this.removed = false; +}; + +CSG.PolygonTreeNode.prototype = { + // fill the tree with polygons. Should be called on the root node only; child nodes must + // always be a derivate (split) of the parent node. + addPolygons: function(polygons) { + if(!this.isRootNode()) throw new Error("Assertion failed"); // new polygons can only be added to root node; children can only be splitted polygons + var _this = this; + polygons.map(function(polygon) { + _this.addChild(polygon); + }); + }, + + // remove a node + // - the siblings become toplevel nodes + // - the parent is removed recursively + remove: function() { + if(!this.removed) + { + this.removed=true; + + if(_CSGDEBUG) + { + if(this.isRootNode()) throw new Error("Assertion failed"); // can't remove root node + if(this.children.length) throw new Error("Assertion failed"); // we shouldn't remove nodes with children + } + + // remove ourselves from the parent's children list: + var parentschildren = this.parent.children; + var i = parentschildren.indexOf(this); + if(i < 0) throw new Error("Assertion failed"); + parentschildren.splice(i,1); + + // invalidate the parent's polygon, and of all parents above it: + this.parent.recursivelyInvalidatePolygon(); + } + }, + + isRemoved: function() { + return this.removed; + }, + + isRootNode: function() { + return !this.parent; + }, + + // invert all polygons in the tree. Call on the root node + invert: function() { + if(!this.isRootNode()) throw new Error("Assertion failed"); // can only call this on the root node + this.invertSub(); + }, + + getPolygon: function () { + if(!this.polygon) throw new Error("Assertion failed"); // doesn't have a polygon, which means that it has been broken down + return this.polygon; + }, + + getPolygons: function (result) { + if(this.polygon) + { + // the polygon hasn't been broken yet. We can ignore the children and return our polygon: + result.push(this.polygon); + } + else + { + // our polygon has been split up and broken, so gather all subpolygons from the children: + var childpolygons = []; + this.children.map(function(child) { + child.getPolygons(childpolygons); + }); + childpolygons.map(function(p) { + result.push(p); + }); + } + }, + + // split the node by a plane; add the resulting nodes to the frontnodes and backnodes array + // If the plane doesn't intersect the polygon, the 'this' object is added to one of the arrays + // If the plane does intersect the polygon, two new child nodes are created for the front and back fragments, + // and added to both arrays. + splitByPlane: function(plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes) { + var children = this.children; + var numchildren = children.length; + if(numchildren > 0) + { + // if we have children, split the children + for(var i = 0; i < numchildren; i++) + { + children[i].splitByPlane(plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes); + } + } + else + { + // no children. Split the polygon: + if(this.polygon) + { + var splitresult = plane.splitPolygon(this.polygon); + switch(splitresult.type) + { + case 0: // coplanar front: + coplanarfrontnodes.push(this); + break; + + case 1: // coplanar back: + coplanarbacknodes.push(this); + break; + + case 2: // front: + frontnodes.push(this); + break; + + case 3: // back: + backnodes.push(this); + break; + + case 4: // spanning: + if(splitresult.front) + { + var frontnode = this.addChild(splitresult.front); + frontnodes.push(frontnode); + } + if(splitresult.back) + { + var backnode = this.addChild(splitresult.back); + backnodes.push(backnode); + } + break; + } + } + } + }, + + + // PRIVATE methods from here: + + // add child to a node + // this should be called whenever the polygon is split + // a child should be created for every fragment of the split polygon + // returns the newly created child + addChild: function(polygon) { + var newchild = new CSG.PolygonTreeNode(); + newchild.parent = this; + newchild.polygon = polygon; + this.children.push(newchild); + return newchild; + }, + + invertSub: function() { + if(this.polygon) + { + this.polygon = this.polygon.flipped(); + } + this.children.map(function(child) { + child.invertSub(); + }); + }, + + recursivelyInvalidatePolygon: function() { + if(this.polygon) + { + this.polygon = null; + if(this.parent) + { + this.parent.recursivelyInvalidatePolygon(); + } + } + }, + +}; + + + +// # class Tree +// This is the root of a BSP tree +// We are using this separate class for the root of the tree, to hold the PolygonTreeNode root +// The actual tree is kept in this.rootnode +CSG.Tree = function(polygons) { + this.polygonTree = new CSG.PolygonTreeNode(); + this.rootnode = new CSG.Node(); + if (polygons) this.addPolygons(polygons); +}; + +CSG.Tree.prototype = { + invert: function() { + this.polygonTree.invert(); + this.rootnode.invert(); + }, + + // Remove all polygons in this BSP tree that are inside the other BSP tree + // `tree`. + clipTo: function(tree, alsoRemovecoplanarFront) { + alsoRemovecoplanarFront = alsoRemovecoplanarFront? true:false; + this.rootnode.clipTo(tree, alsoRemovecoplanarFront); + }, + + allPolygons: function() { + var result = []; + this.polygonTree.getPolygons(result); + return result; + }, + + addPolygons: function(polygons) { + var _this = this; + polygons.map(function(p) { + _this.addPolygon(p); + }); + }, + + addPolygon: function(polygon) { + var polygontreenode=this.polygonTree.addChild(polygon); + this.rootnode.addPolygonTreeNode(polygontreenode); + }, +}; + +// # class Node + +// Holds a node in a BSP tree. A BSP tree is built from a collection of polygons +// by picking a polygon to split along. +// Polygons are not stored directly in the tree, but in PolygonTreeNodes, stored in +// this.polygontreenodes. Those PolygonTreeNodes are children of the owning +// CSG.Tree.polygonTree +// This is not a leafy BSP tree since there is +// no distinction between internal and leaf nodes. + +CSG.Node = function() { + this.plane = null; + this.front = null; + this.back = null; + this.polygontreenodes = []; +}; + +CSG.Node.prototype = { + // Convert solid space to empty space and empty space to solid space. + invert: function() { + this.plane = this.plane.flipped(); + if (this.front) this.front.invert(); + if (this.back) this.back.invert(); + var temp = this.front; + this.front = this.back; + this.back = temp; + }, + + // clip polygontreenodes to our plane + // calls remove() for all clipped PolygonTreeNodes + clipPolygons: function(polygontreenodes, alsoRemovecoplanarFront) { + if(this.plane) + { + var backnodes = []; + var frontnodes = []; + var coplanarfrontnodes = alsoRemovecoplanarFront? backnodes:frontnodes; + var plane = this.plane; + var numpolygontreenodes = polygontreenodes.length; + for(i=0; i < numpolygontreenodes; i++) + { + var node = polygontreenodes[i]; + if(!node.isRemoved() ) + { + node.splitByPlane(plane, coplanarfrontnodes, backnodes, frontnodes, backnodes); + } + } + if(this.front && (frontnodes.length > 0) ) + { + this.front.clipPolygons(frontnodes, alsoRemovecoplanarFront); + } + var numbacknodes = backnodes.length; + if(this.back && (numbacknodes > 0) ) + { + this.back.clipPolygons(backnodes, alsoRemovecoplanarFront); + } + else + { + // there's nothing behind this plane. Delete the nodes behind this plane: + for(i=0; i < numbacknodes; i++) + { + backnodes[i].remove(); + } + } + } + }, + + // Remove all polygons in this BSP tree that are inside the other BSP tree + // `tree`. + clipTo: function(tree, alsoRemovecoplanarFront) { + if(this.polygontreenodes.length > 0) + { + tree.rootnode.clipPolygons(this.polygontreenodes, alsoRemovecoplanarFront); + } + if (this.front) this.front.clipTo(tree, alsoRemovecoplanarFront); + if (this.back) this.back.clipTo(tree, alsoRemovecoplanarFront); + }, + + addPolygonTreeNode: function(polygontreenode) { + if(!this.plane) + { + this.plane = polygontreenode.getPolygon().plane; + } + var frontnodes = []; + var backnodes = []; + polygontreenode.splitByPlane(this.plane, this.polygontreenodes, this.polygontreenodes, frontnodes, backnodes); + if(frontnodes.length > 0) + { + if (!this.front) this.front = new CSG.Node(); + this.front.addPolygonTreeNode(frontnodes[0]); + } + if(backnodes.length > 0) + { + if (!this.back) this.back = new CSG.Node(); + this.back.addPolygonTreeNode(backnodes[0]); + } + }, +}; + +////////// + +// # class Matrix4x4: +// Represents a 4x4 matrix. Elements are specified in row order +CSG.Matrix4x4 = function(elements) { + if (arguments.length >= 1) { + this.elements=elements; + } + else + { + // if no arguments passed: create unity matrix + this.elements=[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + } +} + +CSG.Matrix4x4.prototype = { + plus: function(m) { + var r=[]; + for(var i=0; i < 16; i++) + { + r[i]=this.elements[i]+m.elements[i]; + } + return new CSG.Matrix4x4(r); + }, + + minus: function(m) { + var r=[]; + for(var i=0; i < 16; i++) + { + r[i]=this.elements[i]-m.elements[i]; + } + return new CSG.Matrix4x4(r); + }, + + // right multiply by another 4x4 matrix: + multiply: function(m) { + // cache elements in local variables, for speedup: + var this0=this.elements[0]; + var this1=this.elements[1]; + var this2=this.elements[2]; + var this3=this.elements[3]; + var this4=this.elements[4]; + var this5=this.elements[5]; + var this6=this.elements[6]; + var this7=this.elements[7]; + var this8=this.elements[8]; + var this9=this.elements[9]; + var this10=this.elements[10]; + var this11=this.elements[11]; + var this12=this.elements[12]; + var this13=this.elements[13]; + var this14=this.elements[14]; + var this15=this.elements[15]; + var m0=m.elements[0]; + var m1=m.elements[1]; + var m2=m.elements[2]; + var m3=m.elements[3]; + var m4=m.elements[4]; + var m5=m.elements[5]; + var m6=m.elements[6]; + var m7=m.elements[7]; + var m8=m.elements[8]; + var m9=m.elements[9]; + var m10=m.elements[10]; + var m11=m.elements[11]; + var m12=m.elements[12]; + var m13=m.elements[13]; + var m14=m.elements[14]; + var m15=m.elements[15]; + + var result=[]; + result[0] = this0*m0 + this1*m4 + this2*m8 + this3*m12; + result[1] = this0*m1 + this1*m5 + this2*m9 + this3*m13; + result[2] = this0*m2 + this1*m6 + this2*m10 + this3*m14; + result[3] = this0*m3 + this1*m7 + this2*m11 + this3*m15; + result[4] = this4*m0 + this5*m4 + this6*m8 + this7*m12; + result[5] = this4*m1 + this5*m5 + this6*m9 + this7*m13; + result[6] = this4*m2 + this5*m6 + this6*m10 + this7*m14; + result[7] = this4*m3 + this5*m7 + this6*m11 + this7*m15; + result[8] = this8*m0 + this9*m4 + this10*m8 + this11*m12; + result[9] = this8*m1 + this9*m5 + this10*m9 + this11*m13; + result[10] = this8*m2 + this9*m6 + this10*m10 + this11*m14; + result[11] = this8*m3 + this9*m7 + this10*m11 + this11*m15; + result[12] = this12*m0 + this13*m4 + this14*m8 + this15*m12; + result[13] = this12*m1 + this13*m5 + this14*m9 + this15*m13; + result[14] = this12*m2 + this13*m6 + this14*m10 + this15*m14; + result[15] = this12*m3 + this13*m7 + this14*m11 + this15*m15; + return new CSG.Matrix4x4(result); + }, + + clone: function() { + var elements = this.elements.map(function(p) { return p; }); + return new CSG.Matrix4x4(elements); + }, + + // Multiply a CSG.Vector3D (interpreted as 1 row, 3 column) by this matrix + // Fourth element is taken as 1 + rightMultiply1x3Vector: function(v) { + var v0 = v.x; + var v1 = v.y; + var v2 = v.z; + var v3 = 1; + var x = v0*this.elements[0] + v1*this.elements[1] + v2*this.elements[2] + v3*this.elements[3]; + var y = v0*this.elements[4] + v1*this.elements[5] + v2*this.elements[6] + v3*this.elements[7]; + var z = v0*this.elements[8] + v1*this.elements[9] + v2*this.elements[10] + v3*this.elements[11]; + var w = v0*this.elements[12] + v1*this.elements[13] + v2*this.elements[14] + v3*this.elements[15]; + // scale such that fourth element becomes 1: + if(w != 1) + { + var invw=1.0/w; + x *= invw; + y *= invw; + z *= invw; + } + return new CSG.Vector3D(x,y,z); + }, + + // Multiply a CSG.Vector2D (interpreted as 1 row, 2 column) by this matrix + // Fourth element is taken as 1 + rightMultiply1x2Vector: function(v) { + var v0 = v.x; + var v1 = v.y; + var v2 = 0; + var v3 = 1; + var x = v0*this.elements[0] + v1*this.elements[1] + v2*this.elements[2] + v3*this.elements[3]; + var y = v0*this.elements[4] + v1*this.elements[5] + v2*this.elements[6] + v3*this.elements[7]; + var z = v0*this.elements[8] + v1*this.elements[9] + v2*this.elements[10] + v3*this.elements[11]; + var w = v0*this.elements[12] + v1*this.elements[13] + v2*this.elements[14] + v3*this.elements[15]; + // scale such that fourth element becomes 1: + if(w != 1) + { + var invw=1.0/w; + x *= invw; + y *= invw; + z *= invw; + } + return new CSG.Vector2D(x,y); + }, +}; + +// return the unity matrix +CSG.Matrix4x4.unity = function() { + return new CSG.Matrix4x4(); +}; + +// Create a rotation matrix for rotating around the x axis +CSG.Matrix4x4.rotationX = function(degrees) { + var radians = degrees * Math.PI * (1.0/180.0); + var cos = Math.cos(radians); + var sin = Math.sin(radians); + var els = [ + 1, 0, 0, 0, + 0, cos, -sin, 0, + 0, sin, cos, 0, + 0, 0, 0, 1 + ]; + return new CSG.Matrix4x4(els); +}; + +// Create a rotation matrix for rotating around the y axis +CSG.Matrix4x4.rotationY = function(degrees) { + var radians = degrees * Math.PI * (1.0/180.0); + var cos = Math.cos(radians); + var sin = Math.sin(radians); + var els = [ + cos, 0, sin, 0, + 0, 1, 0, 0, + -sin, 0, cos, 0, + 0, 0, 0, 1 + ]; + return new CSG.Matrix4x4(els); +}; + +// Create a rotation matrix for rotating around the z axis +CSG.Matrix4x4.rotationZ = function(degrees) { + var radians = degrees * Math.PI * (1.0/180.0); + var cos = Math.cos(radians); + var sin = Math.sin(radians); + var els = [ + cos, -sin, 0, 0, + sin, cos, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]; + return new CSG.Matrix4x4(els); +}; + +// Create an affine matrix for translation: +CSG.Matrix4x4.translation = function(v) { + // parse as CSG.Vector3D, so we can pass an array or a CSG.Vector3D + var vec = new CSG.Vector3D(v); + var els = [ + 1, 0, 0, vec.x, + 0, 1, 0, vec.y, + 0, 0, 1, vec.z, + 0, 0, 0, 1 + ]; + return new CSG.Matrix4x4(els); +}; + +// Create an affine matrix for scaling: +CSG.Matrix4x4.scaling = function(v) { + // parse as CSG.Vector3D, so we can pass an array or a CSG.Vector3D + var vec = new CSG.Vector3D(v); + var els = [ + vec.x, 0, 0, 0, + 0, vec.y, 0, 0, + 0, 0, vec.z, 0, + 0, 0, 0, 1 + ]; + return new CSG.Matrix4x4(els); +}; + +/////////////////////////////////////////////////// + +// # class Vector2D: +// Represents a 2 element vector +CSG.Vector2D = function(x, y) { + var ok = true; + if (arguments.length == 1) + { + if(typeof(x) == "object") + { + if(x instanceof Array) + { + this.x = x[0]; + this.y = x[1]; + } + else if( ('x' in x) && ('y' in x) ) + { + this.x = x.x; + this.y = x.y; + } + else ok = false; + } + else + { + var v = Number(x); + this.x = v; + this.y = v; + } + } + else if (arguments.length == 2) + { + this.x = Number(x); + this.y = Number(y); + } + else ok = false; + if(!ok) + { + throw new Error("wrong arguments"); + } +}; + +CSG.Vector2D.prototype = { + // extend to a 3D vector by adding a z coordinate: + toVector3D: function(z) { + return new CSG.Vector3D(this.x, this.y, z); + }, + + equals: function(a) { + return (this.x == a.x) && (this.y == a.y); + }, + + clone: function() { + return new CSG.Vector2D(this.x, this.y); + }, + + negated: function() { + return new CSG.Vector2D(-this.x, -this.y); + }, + + plus: function(a) { + return new CSG.Vector2D(this.x + a.x, this.y + a.y); + }, + + minus: function(a) { + return new CSG.Vector2D(this.x - a.x, this.y - a.y); + }, + + times: function(a) { + return new CSG.Vector2D(this.x * a, this.y * a); + }, + + dividedBy: function(a) { + return new CSG.Vector2D(this.x / a, this.y / a); + }, + + dot: function(a) { + return this.x * a.x + this.y * a.y; + }, + + lerp: function(a, t) { + return this.plus(a.minus(this).times(t)); + }, + + length: function() { + return Math.sqrt(this.dot(this)); + }, + + distanceTo: function(a) { + return this.minus(a).length(); + }, + + unit: function() { + return this.dividedBy(this.length()); + }, + + // returns the vector rotated by 90 degrees clockwise + normal: function() { + return new CSG.Vector2D(this.y, -this.x); + }, + + // Right multiply by a 4x4 matrix (the vector is interpreted as a row vector) + // Returns a new CSG.Vector2D + multiply4x4: function(matrix4x4) { + return matrix4x4.rightMultiply1x2Vector(this); + }, +}; + +// A polygon in 2D space: +CSG.Polygon2D = function(points, shared) { + var vectors = []; + if(arguments.length >= 1) { + points.map( function(p) { + vectors.push(new CSG.Vector2D(p) ); + }); + } + this.points = vectors; + this.shared = shared; +}; + +CSG.Polygon2D.prototype = { + // Matrix transformation of polygon. Returns a new CSG.Polygon2D + transform: function(matrix4x4) { + var newpoints = this.points.map(function(p) { return p.multiply4x4(matrix4x4); } ); + return new CSG.Polygon2D(newpoints, this.shared); + }, + + translate: function(v) { + v=new CSG.Vector2D(v); + return this.transform(CSG.Matrix4x4.translation(v.toVector3D(0))); + }, + + scale: function(f) { + f=new CSG.Vector2D(f); + return this.transform(CSG.Matrix4x4.scaling(f.toVector3D(1))); + }, + + rotate: function(deg) { + return this.transform(CSG.Matrix4x4.rotationZ(deg)); + }, + + // convert into a CSG.Polygon; set z coordinate to the given value + toPolygon3D: function(z) { + var points3d=[]; + this.points.map( function(p) { + var vec3d = p.toVector3D(z); + points3d.push(vec3d); + }); + var polygon = CSG.Polygon.createFromPoints(points3d, this.shared); + polygon.checkIfConvex(); + return polygon; + }, + + // extruded=shape2d.extrude({offset: [0,0,10], twistangle: 360, twiststeps: 100}); + // linear extrusion of 2D polygon, with optional twist + // The 2d polygon is placed in in z=0 plane and extruded into direction (a CSG.Vector3D) + // The final face is rotated degrees. Rotation is done around the origin of the 2d shape (i.e. x=0, y=0) + // twiststeps determines the resolution of the twist (should be >= 1) + // returns a CSG object + extrude: function(options) { + var offsetvector = CSG.parseOptionAs3DVector(options, "offset", [0,0,1]); + var twistangle = CSG.parseOptionAsFloat(options, "twistangle", 0); + var twiststeps = CSG.parseOptionAsInt(options, "twiststeps", 10); + + // create the polygons: + var newpolygons = []; + + // bottom face polygon: + var bottomfacepolygon = this.toPolygon3D(0); + var direction = bottomfacepolygon.plane.normal.dot(offsetvector); + if(direction > 0) + { + bottomfacepolygon = bottomfacepolygon.flipped(); + } + newpolygons.push(bottomfacepolygon); + + var getTwistedPolygon = function(twiststep) { + var fraction = (twiststep + 1) / twiststeps; + var rotation = twistangle * fraction; + var offset = offsetvector.times(fraction); + var transformmatrix = CSG.Matrix4x4.rotationZ(rotation).multiply( CSG.Matrix4x4.translation(offset) ); + var polygon = bottomfacepolygon.transform(transformmatrix); + return polygon; + }; + + // create the side face polygons: + var numvertices = bottomfacepolygon.vertices.length; + var prevlevelpolygon = bottomfacepolygon; + for(var twiststep=0; twiststep < twiststeps; ++twiststep) + { + var levelpolygon = getTwistedPolygon(twiststep); + for(var i=0; i < numvertices; i++) + { + var sidefacepoints = []; + var nexti = (i < (numvertices-1))? i+1:0; + sidefacepoints.push(prevlevelpolygon.vertices[i].pos); + sidefacepoints.push(levelpolygon.vertices[i].pos); + sidefacepoints.push(levelpolygon.vertices[nexti].pos); + sidefacepoints.push(prevlevelpolygon.vertices[nexti].pos); + var sidefacepolygon=CSG.Polygon.createFromPoints(sidefacepoints, this.shared); + newpolygons.push(sidefacepolygon); + } + if(twiststep == (twiststeps -1) ) + { + // last level; add the top face polygon: + levelpolygon = levelpolygon.flipped(); // flip so that the normal points outwards + newpolygons.push(levelpolygon); + } + prevlevelpolygon = levelpolygon; + } + + return CSG.fromPolygons(newpolygons); + } +}; + + + +// # class Line2D + +// Represents a directional line in 2D space +// A line is parametrized by its normal vector (perpendicular to the line, rotated 90 degrees counter clockwise) +// and w. The line passes through the point .times(w). +// normal must be a unit vector! +// Equation: p is on line if normal.dot(p)==w +CSG.Line2D = function(normal, w) { + this.normal = normal; + this.w = w; +}; + +CSG.Line2D.fromPoints = function(p1, p2) { + var direction = p2.minus(p1); + var normal = direction.normal().negated().unit(); + var w = p1.dot(normal); + return new CSG.Line2D(normal, w); +}; + +CSG.Line2D.prototype = { + // same line but opposite direction: + inverse: function() { + return new CSG.Line2D(this.normal.negated(), -this.w); + }, + + equals: function(l) { + return (l.normal.equals(this.normal) && (l.w == this.w)); + }, + + origin: function() { + return this.normal.times(this.w); + }, + + direction: function() { + return this.normal.normal(); + }, + + xAtY: function(y) { + // (py == y) && (normal * p == w) + // -> px = (w - normal.y * y) / normal.x + var x = (this.w - this.normal.y * y) / this.normal.x; + return x; + }, + + absDistanceToPoint: function(point) { + var point_projected = point.dot(this.normal); + var distance = Math.abs(point_projected - this.w); + return distance; + }, + + closestPoint: function(point) { + var vector = point.dot(this.direction()); + return origin.plus(vector); + }, +}; + +// # class Line3D + +// Represents a line in 3D space +// direction must be a unit vector +// point is a random point on the line + +CSG.Line3D = function(point, direction) { + this.point = point; + this.direction = direction; +}; + +CSG.Line3D.fromPoints = function(p1, p2) { + var direction = p2.minus(p1).unit(); + return new CSG.Line3D(p1, direction); +}; + +CSG.Line3D.fromPlanes = function(p1, p2) { + var direction = p1.normal.cross(p2.normal); + var l=direction.length(); + if(l < 1e-10) + { + throw new Error("Parallel planes"); + } + direction = direction.times(1.0/l); + + var mabsx = Math.abs(direction.x); + var mabsy = Math.abs(direction.y); + var mabsz = Math.abs(direction.z); + var origin; + if( (mabsx >= mabsy) && (mabsx >= mabsz) ) + { + // direction vector is mostly pointing towards x + // find a point p for which x is zero: + var r = CSG.Line3D.Solve2Linear(p1.normal.y, p1.normal.z, p2.normal.y, p2.normal.z, p1.w, p2.w); + origin = new CSG.Vector3D(0, r[0], r[1]); + } + else if( (mabsy >= mabsx) && (mabsy >= mabsz) ) + { + // find a point p for which y is zero: + var r = CSG.Line3D.Solve2Linear(p1.normal.x, p1.normal.z, p2.normal.x, p2.normal.z, p1.w, p2.w); + origin = new CSG.Vector3D(r[0], 0, r[1]); + } + else + { + // find a point p for which z is zero: + var r = CSG.Line3D.Solve2Linear(p1.normal.x, p1.normal.y, p2.normal.x, p2.normal.y, p1.w, p2.w); + origin = new CSG.Vector3D(r[0], r[1], 0); + } + return new CSG.Line3D(origin, direction); +}; + +// solve +// [ab][x] = [u] +// [cd][y] [v] +CSG.Line3D.Solve2Linear = function(a,b,c,d,u,v) { + var det = a*d - b*c; + var invdet = 1.0/det; + var x = u*d - b*v; + var y = -u*c + a*v; + x *= invdet; + y *= invdet; + return [x,y]; +}; + +CSG.Line3D.prototype = { + intersectWithPlane: function(plane) { + // plane: plane.normal * p = plane.w + // line: p=line.point + labda * line.direction + var labda = (plane.w - plane.normal.dot(this.point)) / plane.normal.dot(this.direction); + var point = this.point.plus(this.direction.times(labda)); + return point; + }, + + clone: function(line) { + return new CSG.Line3D(this.point.clone(), this.direction.clone()); + }, + + reverse: function() { + return new CSG.Line3D(this.point.clone(), this.direction.negated()); + }, + + transform: function(matrix4x4) { + var newpoint = this.point.multiply4x4(matrix4x4); + var pointPlusDirection = this.point.plus(this.direction); + var newPointPlusDirection = pointPlusDirection.multiply4x4(matrix4x4); + var newdirection = newPointPlusDirection.minus(newpoint); + return new CSG.Line3D(newpoint, newdirection); + }, + + closestPointOnLine: function(point) { + var t = point.minus(this.point).dot(this.direction) / this.direction.dot(this.direction); + var closestpoint = this.point.plus(this.direction.times(t)); + return closestpoint; + }, + + distanceToPoint: function(point) { + var closestpoint = this.closestPointOnLine(point); + var distancevector = point.minus(closestpoint); + var distance = distancevector.length(); + return distance; + }, + + equals: function(line3d) { + if(!this.direction.equals(line3d.direction)) return false; + var distance = this.distanceToPoint(line3d.point); + if(distance > 1e-8) return false; + return true; + }, +}; + + +// # class OrthoNormalBasis + +// Reprojects points on a 3D plane onto a 2D plane +// or from a 2D plane back onto the 3D plane + +CSG.OrthoNormalBasis = function (plane) { + // choose an arbitrary right hand vector, making sure it is somewhat orthogonal to the plane normal: + var rightvector; + if(Math.abs(plane.normal.x) > Math.abs(plane.normal.y)) + { + rightvector = new CSG.Vector3D(0, 1, 0); + } + else + { + rightvector = new CSG.Vector3D(1, 0, 0); + } + this.v = rightvector.cross(plane.normal).unit(); + this.u = plane.normal.cross(this.v); + this.planeorigin = plane.normal.times(plane.w); +}; + +CSG.OrthoNormalBasis.prototype = { + to2D: function(vec3) { + return new CSG.Vector2D(vec3.dot(this.u), vec3.dot(this.v)); + }, + + to3D: function(vec2) { + return this.planeorigin.plus(this.u.times(vec2.x)).plus(this.v.times(vec2.y)); + }, + + line3Dto2D: function(line3d) { + var a = line3d.point; + var b = line3d.direction.plus(a); + var a2d = this.to2D(a); + var b2d = this.to2D(b); + return CSG.Line2D.fromPoints(a2d, b2d); + }, + + line2Dto3D: function(line2d) { + var a = line2d.origin(); + var b = line2d.direction().plus(a); + var a3d = this.to3D(a); + var b3d = this.to3D(b); + return CSG.Line3D.fromPoints(a3d, b3d); + }, +}; + +function insertSorted(array, element, comparefunc) { + var leftbound = 0; + var rightbound = array.length; + while(rightbound > leftbound) + { + var testindex = Math.floor( (leftbound + rightbound) / 2); + var testelement = array[testindex]; + var compareresult = comparefunc(element, testelement); + if(compareresult > 0) // element > testelement + { + leftbound = testindex + 1; + } + else + { + rightbound = testindex; + } + } + array.splice(leftbound,0,element); +} + +// Get the x coordinate of a point with a certain y coordinate, interpolated between two +// points (CSG.Vector2D). +// Interpolation is robust even if the points have the same y coordinate +CSG.interpolateBetween2DPointsForY = function(point1, point2, y) { + var f1 = y - point1.y; + var f2 = point2.y - point1.y; + if(f2 < 0) + { + f1 = -f1; + f2 = -f2; + } + var t; + if(f1 <= 0) + { + t = 0.0; + } + else if(f1 >= f2) + { + t = 1.0; + } + else if(f2 < 1e-10) + { + t = 0.5; + } + else + { + t = f1 / f2; + } + var result = point1.x + t * (point2.x - point1.x); + return result; +}; + +// Retesselation function for a set of coplanar polygons. See the introduction at the top of +// this file. +CSG.reTesselateCoplanarPolygons = function(sourcepolygons, destpolygons) +{ + var EPS = 1e-5; + + var numpolygons = sourcepolygons.length; + if(numpolygons > 0) + { + var plane = sourcepolygons[0].plane; + var orthobasis = new CSG.OrthoNormalBasis(plane); + var polygonvertices2d = []; // array of array of CSG.Vector2D + var polygontopvertexindexes = []; // array of indexes of topmost vertex per polygon + var topy2polygonindexes = {}; + var ycoordinatetopolygonindexes = {}; + + var xcoordinatebins = {}; + var ycoordinatebins = {}; + + // convert all polygon vertices to 2D + // Make a list of all encountered y coordinates + // And build a map of all polygons that have a vertex at a certain y coordinate: + var ycoordinateBinningFactor = 1.0/EPS * 10; + for(var polygonindex=0; polygonindex < numpolygons; polygonindex++) + { + var poly3d = sourcepolygons[polygonindex]; + var vertices2d = []; + var numvertices = poly3d.vertices.length; + var minindex = -1; + if(numvertices > 0) + { + var miny, maxy, maxindex; + for(var i=0; i < numvertices; i++) + { + var pos2d = orthobasis.to2D(poly3d.vertices[i].pos); + // perform binning of y coordinates: If we have multiple vertices very + // close to each other, give them the same y coordinate: + var ycoordinatebin = Math.floor(pos2d.y * ycoordinateBinningFactor); + if(ycoordinatebin in ycoordinatebins) + { + pos2d.y = ycoordinatebins[ycoordinatebin]; + } + else if(ycoordinatebin+1 in ycoordinatebins) + { + pos2d.y = ycoordinatebins[ycoordinatebin+1]; + } + else if(ycoordinatebin-1 in ycoordinatebins) + { + pos2d.y = ycoordinatebins[ycoordinatebin-1]; + } + else + { + ycoordinatebins[ycoordinatebin] = pos2d.y; + } + vertices2d.push(pos2d); + var y = pos2d.y; + if( (i == 0) || (y < miny) ) + { + miny = y; + minindex = i; + } + if( (i == 0) || (y > maxy) ) + { + maxy = y; + maxindex = i; + } + if(! (y in ycoordinatetopolygonindexes)) + { + ycoordinatetopolygonindexes[y] = {}; + } + ycoordinatetopolygonindexes[y][polygonindex]=true; + } + if(miny >= maxy) + { + // degenerate polygon, all vertices have same y coordinate. Just ignore it from now: + vertices2d = []; + } + else + { + if(! (miny in topy2polygonindexes)) + { + topy2polygonindexes[miny] = []; + } + topy2polygonindexes[miny].push(polygonindex); + } + } // if(numvertices > 0) + polygonvertices2d.push(vertices2d); + polygontopvertexindexes.push(minindex); + } + var ycoordinates = []; + for(var ycoordinate in ycoordinatetopolygonindexes) ycoordinates.push(ycoordinate); + ycoordinates.sort(function(a,b) {return a-b}); + + // Now we will iterate over all y coordinates, from lowest to highest y coordinate + // activepolygons: source polygons that are 'active', i.e. intersect with our y coordinate + // Is sorted so the polygons are in left to right order + // Each element in activepolygons has these properties: + // polygonindex: the index of the source polygon (i.e. an index into the sourcepolygons and polygonvertices2d arrays) + // leftvertexindex: the index of the vertex at the left side of the polygon (lowest x) that is at or just above the current y coordinate + // rightvertexindex: dito at right hand side of polygon + // topleft, bottomleft: coordinates of the left side of the polygon crossing the current y coordinate + // topright, bottomright: coordinates of the right hand side of the polygon crossing the current y coordinate + var activepolygons = []; + var prevoutpolygonrow = []; + for(var yindex = 0; yindex < ycoordinates.length; yindex++) + { + var newoutpolygonrow = []; + var ycoordinate_as_string = ycoordinates[yindex]; + var ycoordinate = Number(ycoordinate_as_string); + + // update activepolygons for this y coordinate: + // - Remove any polygons that end at this y coordinate + // - update leftvertexindex and rightvertexindex (which point to the current vertex index + // at the the left and right side of the polygon + // Iterate over all polygons that have a corner at this y coordinate: + var polygonindexeswithcorner = ycoordinatetopolygonindexes[ycoordinate_as_string]; + for(var activepolygonindex = 0; activepolygonindex < activepolygons.length; ++activepolygonindex) + { + var activepolygon = activepolygons[activepolygonindex]; + var polygonindex = activepolygon.polygonindex; + if(polygonindexeswithcorner[polygonindex]) + { + // this active polygon has a corner at this y coordinate: + var vertices2d = polygonvertices2d[polygonindex]; + var numvertices = vertices2d.length; + var newleftvertexindex = activepolygon.leftvertexindex; + var newrightvertexindex = activepolygon.rightvertexindex; + // See if we need to increase leftvertexindex or decrease rightvertexindex: + while(true) + { + var nextleftvertexindex = newleftvertexindex+1; + if(nextleftvertexindex >= numvertices) nextleftvertexindex = 0; + if(vertices2d[nextleftvertexindex].y != ycoordinate) break; + newleftvertexindex = nextleftvertexindex; + } + var nextrightvertexindex = newrightvertexindex-1; + if(nextrightvertexindex < 0) nextrightvertexindex = numvertices-1; + if(vertices2d[nextrightvertexindex].y == ycoordinate) + { + newrightvertexindex = nextrightvertexindex; + } + if( (newleftvertexindex != activepolygon.leftvertexindex) && (newleftvertexindex == newrightvertexindex) ) + { + // We have increased leftvertexindex or decreased rightvertexindex, and now they point to the same vertex + // This means that this is the bottom point of the polygon. We'll remove it: + activepolygons.splice(activepolygonindex, 1); + --activepolygonindex; + } + else + { + activepolygon.leftvertexindex = newleftvertexindex; + activepolygon.rightvertexindex = newrightvertexindex; + activepolygon.topleft = vertices2d[newleftvertexindex]; + activepolygon.topright = vertices2d[newrightvertexindex]; + var nextleftvertexindex = newleftvertexindex+1; + if(nextleftvertexindex >= numvertices) nextleftvertexindex = 0; + activepolygon.bottomleft = vertices2d[nextleftvertexindex]; + var nextrightvertexindex = newrightvertexindex-1; + if(nextrightvertexindex < 0) nextrightvertexindex = numvertices-1; + activepolygon.bottomright = vertices2d[nextrightvertexindex]; + } + } // if polygon has corner here + } // for activepolygonindex + + var nextycoordinate; + if(yindex >= ycoordinates.length-1) + { + // last row, all polygons must be finished here: + activepolygons = []; + nextycoordinate = null; + } + else // yindex < ycoordinates.length-1 + { + nextycoordinate = Number(ycoordinates[yindex+1]); + var middleycoordinate = 0.5 * (ycoordinate + nextycoordinate); + // update activepolygons by adding any polygons that start here: + var startingpolygonindexes = topy2polygonindexes[ycoordinate_as_string]; + for(var polygonindex_key in startingpolygonindexes) + { + var polygonindex = startingpolygonindexes[polygonindex_key]; + var vertices2d = polygonvertices2d[polygonindex]; + var numvertices = vertices2d.length; + var topvertexindex = polygontopvertexindexes[polygonindex]; + // the top of the polygon may be a horizontal line. In that case topvertexindex can point to any point on this line. + // Find the left and right topmost vertices which have the current y coordinate: + var topleftvertexindex = topvertexindex; + while(true) + { + var i = topleftvertexindex + 1; + if(i >= numvertices) i = 0; + if(vertices2d[i].y != ycoordinate) break; + if(i == topvertexindex) break; // should not happen, but just to prevent endless loops + topleftvertexindex = i; + } + var toprightvertexindex = topvertexindex; + while(true) + { + var i = toprightvertexindex - 1; + if(i < 0) i = numvertices - 1; + if(vertices2d[i].y != ycoordinate) break; + if(i == topleftvertexindex) break; // should not happen, but just to prevent endless loops + toprightvertexindex = i; + } + var nextleftvertexindex = topleftvertexindex+1; + if(nextleftvertexindex >= numvertices) nextleftvertexindex = 0; + var nextrightvertexindex = toprightvertexindex-1; + if(nextrightvertexindex < 0) nextrightvertexindex = numvertices-1; + var newactivepolygon = { + polygonindex: polygonindex, + leftvertexindex: topleftvertexindex, + rightvertexindex: toprightvertexindex, + topleft: vertices2d[topleftvertexindex], + topright: vertices2d[toprightvertexindex], + bottomleft: vertices2d[nextleftvertexindex], + bottomright: vertices2d[nextrightvertexindex], + }; + insertSorted(activepolygons, newactivepolygon, function(el1, el2) { + var x1 = CSG.interpolateBetween2DPointsForY(el1.topleft, el1.bottomleft, middleycoordinate); + var x2 = CSG.interpolateBetween2DPointsForY(el2.topleft, el2.bottomleft, middleycoordinate); + if(x1 > x2) return 1; + if(x1 < x2) return -1; + return 0; + }); + } // for(var polygonindex in startingpolygonindexes) + } // yindex < ycoordinates.length-1 + //if( (yindex == ycoordinates.length-1) || (nextycoordinate - ycoordinate > EPS) ) + if(true) + { + // Now activepolygons is up to date + // Build the output polygons for the next row in newoutpolygonrow: + for(var activepolygon_key in activepolygons) + { + var activepolygon = activepolygons[activepolygon_key]; + var polygonindex = activepolygon.polygonindex; + var vertices2d = polygonvertices2d[polygonindex]; + var numvertices = vertices2d.length; + + var x = CSG.interpolateBetween2DPointsForY(activepolygon.topleft, activepolygon.bottomleft, ycoordinate); + var topleft=new CSG.Vector2D(x, ycoordinate); + x = CSG.interpolateBetween2DPointsForY(activepolygon.topright, activepolygon.bottomright, ycoordinate); + var topright=new CSG.Vector2D(x, ycoordinate); + x = CSG.interpolateBetween2DPointsForY(activepolygon.topleft, activepolygon.bottomleft, nextycoordinate); + var bottomleft=new CSG.Vector2D(x, nextycoordinate); + x = CSG.interpolateBetween2DPointsForY(activepolygon.topright, activepolygon.bottomright, nextycoordinate); + var bottomright=new CSG.Vector2D(x, nextycoordinate); + var outpolygon = { + topleft: topleft, + topright: topright, + bottomleft: bottomleft, + bottomright: bottomright, + leftline: CSG.Line2D.fromPoints(topleft, bottomleft), + rightline: CSG.Line2D.fromPoints(bottomright, topright), + }; + if(newoutpolygonrow.length > 0) + { + var prevoutpolygon = newoutpolygonrow[newoutpolygonrow.length - 1]; + var d1 = outpolygon.topleft.distanceTo(prevoutpolygon.topright); + var d2 = outpolygon.bottomleft.distanceTo(prevoutpolygon.bottomright); + if( (d1 < EPS) && (d2 < EPS) ) + { + // we can join this polygon with the one to the left: + outpolygon.topleft = prevoutpolygon.topleft; + outpolygon.leftline = prevoutpolygon.leftline; + outpolygon.bottomleft = prevoutpolygon.bottomleft; + newoutpolygonrow.splice(newoutpolygonrow.length - 1, 1); + } + } + newoutpolygonrow.push(outpolygon); + } // for(activepolygon in activepolygons) + if(yindex > 0) + { + // try to match the new polygons against the previous row: + var prevcontinuedindexes = {}; + var matchedindexes = {}; + for(var i = 0; i < newoutpolygonrow.length; i++) + { + var thispolygon = newoutpolygonrow[i]; + for(var ii = 0; ii < prevoutpolygonrow.length; ii++) + { + if(!matchedindexes[ii]) // not already processed? + { + // We have a match if the sidelines are equal or if the top coordinates + // are on the sidelines of the previous polygon + var prevpolygon = prevoutpolygonrow[ii]; + if(prevpolygon.bottomleft.distanceTo(thispolygon.topleft) < EPS) + { + if(prevpolygon.bottomright.distanceTo(thispolygon.topright) < EPS) + { + // Yes, the top of this polygon matches the bottom of the previous: + matchedindexes[ii] = true; + // Now check if the joined polygon would remain convex: + var d1 = thispolygon.leftline.direction().x - prevpolygon.leftline.direction().x; + var d2 = thispolygon.rightline.direction().x - prevpolygon.rightline.direction().x; + var leftlinecontinues = Math.abs(d1) < EPS; + var rightlinecontinues = Math.abs(d2) < EPS; + var leftlineisconvex = leftlinecontinues || (d1 >= 0); + var rightlineisconvex = rightlinecontinues || (d2 >= 0); + if(leftlineisconvex && rightlineisconvex) + { + // yes, both sides have convex corners: + // This polygon will continue the previous polygon + thispolygon.outpolygon = prevpolygon.outpolygon; + thispolygon.leftlinecontinues = leftlinecontinues; + thispolygon.rightlinecontinues = rightlinecontinues; + prevcontinuedindexes[ii] = true; + } + break; + } + } + } // if(!prevcontinuedindexes[ii]) + } // for ii + } // for i + for(var ii = 0; ii < prevoutpolygonrow.length; ii++) + { + if(!prevcontinuedindexes[ii]) + { + // polygon ends here + // Finish the polygon with the last point(s): + var prevpolygon = prevoutpolygonrow[ii]; + prevpolygon.outpolygon.rightpoints.push(prevpolygon.bottomright); + if(prevpolygon.bottomright.distanceTo(prevpolygon.bottomleft) > EPS) + { + // polygon ends with a horizontal line: + prevpolygon.outpolygon.leftpoints.push(prevpolygon.bottomleft); + } + // reverse the right half so we get a counterclockwise circle: + prevpolygon.outpolygon.rightpoints.reverse(); + var points2d = prevpolygon.outpolygon.leftpoints.concat(prevpolygon.outpolygon.rightpoints); + var vertices3d = []; + points2d.map(function(point2d) { + var point3d = orthobasis.to3D(point2d); + var vertex3d = new CSG.Vertex(point3d); + vertices3d.push(vertex3d); + }); + var shared = null; + var polygon = new CSG.Polygon(vertices3d, shared, plane); + destpolygons.push(polygon); + } + } + } // if(yindex > 0) + for(var i = 0; i < newoutpolygonrow.length; i++) + { + var thispolygon = newoutpolygonrow[i]; + if(!thispolygon.outpolygon) + { + // polygon starts here: + thispolygon.outpolygon = { + leftpoints: [], + rightpoints: [], + }; + thispolygon.outpolygon.leftpoints.push(thispolygon.topleft); + if(thispolygon.topleft.distanceTo(thispolygon.topright) > EPS) + { + // we have a horizontal line at the top: + thispolygon.outpolygon.rightpoints.push(thispolygon.topright); + } + } + else + { + // continuation of a previous row + if(! thispolygon.leftlinecontinues ) + { + thispolygon.outpolygon.leftpoints.push(thispolygon.topleft); + } + if(! thispolygon.rightlinecontinues ) + { + thispolygon.outpolygon.rightpoints.push(thispolygon.topright); + } + } + } + prevoutpolygonrow = newoutpolygonrow; + } + } // for yindex + } // if(numpolygons > 0) +} + +//////////////////////////////// + +// ## class fuzzyFactory + +// This class acts as a factory for objects. We can search for an object with approximately +// the desired properties (say a rectangle with width 2 and height 1) +// The lookupOrCreate() method looks for an existing object (for example it may find an existing rectangle +// with width 2.0001 and height 0.999. If no object is found, the user supplied callback is +// called, which should generate a new object. The new object is inserted into the database +// so it can be found by future lookupOrCreate() calls. + +// Constructor: +// numdimensions: the number of parameters for each object +// for example for a 2D rectangle this would be 2 +// tolerance: The maximum difference for each parameter allowed to be considered a match + +CSG.fuzzyFactory = function(numdimensions, tolerance) { + var lookuptable = []; + for(var i=0; i < numdimensions; i++) + { + lookuptable.push({}); + } + this.lookuptable = lookuptable; + this.nextElementId = 1; + this.multiplier = 1.0 / tolerance; + this.objectTable = {}; +}; + +CSG.fuzzyFactory.prototype = { + // var obj = f.lookupOrCreate([el1, el2, el3], function(elements) {/* create the new object */}); + // Performs a fuzzy lookup of the object with the specified elements. + // If found, returns the existing object + // If not found, calls the supplied callback function which should create a new object with + // the specified properties. This object is inserted in the lookup database. + lookupOrCreate: function(els, creatorCallback) { + var object; + var key = this.lookupKey(els); + if(key === null) + { + object = creatorCallback(els); + key = this.nextElementId++; + this.objectTable[key] = object; + for(var dimension = 0; dimension < els.length; dimension++) + { + var elementLookupTable = this.lookuptable[dimension]; + var value = els[dimension]; + var valueMultiplied = value * this.multiplier; + var valueQuantized1 = Math.floor(valueMultiplied); + var valueQuantized2 = Math.ceil(valueMultiplied); + CSG.fuzzyFactory.insertKey(key, elementLookupTable, valueQuantized1); + CSG.fuzzyFactory.insertKey(key, elementLookupTable, valueQuantized2); + } + } + else + { + object = this.objectTable[key]; + } + return object; + }, + + // ----------- PRIVATE METHODS: + lookupKey: function(els) { + var keyset = {}; + for(var dimension=0; dimension < els.length; dimension++) + { + var elementLookupTable = this.lookuptable[dimension]; + var value = els[dimension]; + var valueQuantized = Math.round(value * this.multiplier); + valueQuantized += ""; + if(valueQuantized in elementLookupTable) + { + if(dimension == 0) + { + keyset = elementLookupTable[valueQuantized]; + } + else + { + keyset = CSG.fuzzyFactory.intersectSets(keyset, elementLookupTable[valueQuantized]); + } + } + else + { + return null; + } + if(CSG.fuzzyFactory.isEmptySet(keyset)) return null; + } + // return first matching key: + for(var key in keyset) return key; + return null; + }, + + lookupKeySetForDimension: function(dimension, value) { + var result; + var elementLookupTable = this.lookuptable[dimension]; + var valueMultiplied = value * this.multiplier; + var valueQuantized = Math.floor(value * this.multiplier); + if(valueQuantized in elementLookupTable) + { + result = elementLookupTable[valueQuantized]; + } + else + { + result = {}; + } + return result; + }, +}; + +CSG.fuzzyFactory.insertKey = function(key, lookuptable, quantizedvalue) { + if(quantizedvalue in lookuptable) + { + lookuptable[quantizedvalue][key] = true; + } + else + { + var newset = {}; + newset[key] = true; + lookuptable[quantizedvalue] = newset; + } +}; + +CSG.fuzzyFactory.isEmptySet = function(obj) { + for(var key in obj) return false; + return true; +}; + +CSG.fuzzyFactory.intersectSets = function(set1, set2) { + var result = {}; + for(var key in set1) + { + if(key in set2) + { + result[key] = true; + } + } + return result; +}; + +CSG.fuzzyFactory.joinSets = function(set1, set2) { + var result = {}; + for(var key in set1) + { + result[key] = true; + } + for(var key in set2) + { + result[key] = true; + } + return result; +}; + +////////////////////////////////////// + +CSG.fuzzyCSGFactory = function() { + this.vertexfactory = new CSG.fuzzyFactory(3, 1e-5); + this.planefactory = new CSG.fuzzyFactory(4, 1e-5); +}; + +CSG.fuzzyCSGFactory.prototype = { + getVertex: function(sourcevertex) { + var elements = [sourcevertex.pos.x, sourcevertex.pos.y, sourcevertex.pos.z]; + var result = this.vertexfactory.lookupOrCreate(elements, function(els) { + return sourcevertex; + }); + return result; + }, + + getPlane: function(sourceplane) { + var elements = [sourceplane.normal.x, sourceplane.normal.y, sourceplane.normal.z, sourceplane.w]; + var result = this.planefactory.lookupOrCreate(elements, function(els) { + return sourceplane; + }); + return result; + }, + + getPolygon: function(sourcepolygon) { + var newplane = this.getPlane(sourcepolygon.plane); + var _this = this; + var newvertices = sourcepolygon.vertices.map(function(vertex) { + return _this.getVertex(vertex); + }); + return new CSG.Polygon(newvertices, sourcepolygon.shared, newplane); + }, + + getCSG: function(sourcecsg) { + var _this = this; + var newpolygons = sourcecsg.polygons.map(function(polygon) { + return _this.getPolygon(polygon); + }); + return CSG.fromPolygons(newpolygons); + }, +}; + +////////////////////////////////////// + +// Tag factory: we can request a unique tag through CSG.getTag() +CSG.staticTag = 1; + +CSG.getTag = function () { + return CSG.staticTag++; +}; \ No newline at end of file diff --git a/index.html b/index.html index 3ee6711..a2a646a 100644 --- a/index.html +++ b/index.html @@ -1 +1,289 @@ -Test! + + + + + + + + +OpenJsCad + + +

OpenJsCad

+ Please note: currently only works reliably in Google Chrome!
+ Create an STL file using constructive solid modeling. Click Update Preview to parse the source code from the textarea. + Click Get STL to generate the stl data, ready for 3d printing. See below for documentation. + + + + +
+ + +

+
+
+
+ +
+

About

+This is intended to become a Javascript based alternative to OpenSCAD, +for 3D solid modeling. +CSG model is contructed using Javascript. For example:
+var cube = CSG.cube(); return cube; 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. +

+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. +

License

+Copyright (c) 2012 Joost Nieuwenhuijse. +Uses CSG.js, original copyright (c) 2011 Evan Wallace, +extensively modified, copyright (c) 2012 Joost Nieuwenhuijse. +Uses lightgl.js by Evan Wallace for WebGL rendering. +All code released under MIT license. +

+Contributions are welcome! To contribute go to CSG.js at GitHub, +create your own fork and +send me a pull request. +

Viewer navigation

+Click and drag to rotate the model around the origin.
+Shift+Drag moves the model around.
+Alt+drag zooms (by changing the distance between camera and model). + +

Primitive solids

+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: radius: 1 will give radius: [1,1,1]. +

+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. +

+
+// 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,
+});
+
+ +

CSG operations

+The 3 standard CSG operations are supported. All CSG operations return a new solid; the source solids +are not modified: +

+
+var csg1 = cube.union(sphere);
+var csg2 = cube.intersect(sphere);
+var csg3 = cube.subtract(sphere);
+
+ +

Transformations

+Solids can be translated, scaled and rotated. Multiple transforms can be combined into a single matrix transform: +

+
+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);
+
+ +

Expansion and contraction

+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. +

+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. +

+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. +

+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). +
+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); 
+
+ +

2d shapes

+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. +
+// 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
+});
+
+ + + \ No newline at end of file diff --git a/lightgl.js b/lightgl.js new file mode 100644 index 0000000..f02987f --- /dev/null +++ b/lightgl.js @@ -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 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;a0,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+i0&&b.t0&&hf.x)-(b.xf.y)-(b.yf.z)-(b.z0){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 Date: Fri, 20 Jan 2012 15:54:37 +0100 Subject: [PATCH 04/48] Rendering now occurs on load --- index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index a2a646a..e01b7fc 100644 --- a/index.html +++ b/index.html @@ -53,6 +53,7 @@ function onload() { gViewer = new Viewer(new CSG(), 600, 600, 5); document.getElementById("viewer").appendChild(gViewer.gl.canvas); + updateSolid(); } function updateSolid() @@ -103,7 +104,7 @@ function getStl()
- +

About

This is intended to become a Javascript based alternative to OpenSCAD, diff --git a/openjscad.js b/openjscad.js new file mode 100644 index 0000000..cd17904 --- /dev/null +++ b/openjscad.js @@ -0,0 +1,209 @@ +OpenJsCad = function() { +}; + +// A viewer is a WebGL canvas that lets the user view a mesh. The user can +// tumble it around by dragging the mouse. +OpenJsCad.Viewer = function(containerelement, width, height, initialdepth) { + var gl = GL.create(); + if(!gl) + { + containerelement.innerHTML = "WebGL is required for the 3D viewer, but your browser doesn't seem to support this."; + } + else + { + this.gl = gl; + this.angleX = 0; + this.angleY = 0; + this.viewpointX = 0; + this.viewpointY = 0; + this.viewpointZ = initialdepth; + + // Draw triangle lines: + this.drawLines = false; + // Set to true so lines don't use the depth buffer + this.lineOverlay = false; + + // 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.5, 1000); + 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);\ + }\ + '); + + containerelement.appendChild(gl.canvas); + + var _this=this; + + gl.onmousemove = function(e) { + _this.onMouseMove(e); + }; + gl.ondraw = function() { + _this.onDraw(); + }; + this.clear(); + } +}; + +OpenJsCad.Viewer.prototype = { + setCsg: function(csg) { + this.mesh = OpenJsCad.Viewer.csgToMesh(csg); + this.onDraw(); + }, + + clear: function() { + // empty mesh: + this.mesh = new GL.Mesh(); + this.onDraw(); + }, + + supported: function() { + return !!this.gl; + }, + + onMouseMove: function(e) { + if (e.dragging) { + e.preventDefault(); + if(e.altKey) + { + var factor = 1e-2; + this.viewpointZ *= Math.pow(2,factor * e.deltaY); + } + else if(e.shiftKey) + { + var factor = 5e-3; + this.viewpointX += factor * e.deltaX * this.viewpointZ; + this.viewpointY -= factor * e.deltaY * this.viewpointZ; + } + else + { + this.angleY += e.deltaX * 2; + this.angleX += e.deltaY * 2; + this.angleX = Math.max(-90, Math.min(90, this.angleX)); + } + this.onDraw(); + } + }, + + onDraw: function(e) { + var gl = this.gl; + gl.makeCurrent(); + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.loadIdentity(); + gl.translate(this.viewpointX, this.viewpointY, -this.viewpointZ); + gl.rotate(this.angleX, 1, 0, 0); + gl.rotate(this.angleY, 0, 1, 0); + + if (!this.lineOverlay) gl.enable(gl.POLYGON_OFFSET_FILL); + this.lightingShader.draw(this.mesh, gl.TRIANGLES); + if (!this.lineOverlay) gl.disable(gl.POLYGON_OFFSET_FILL); + + if(this.drawLines) + { + if (this.lineOverlay) gl.disable(gl.DEPTH_TEST); + gl.enable(gl.BLEND); + this.blackShader.draw(this.mesh, gl.LINES); + gl.disable(gl.BLEND); + if (this.lineOverlay) gl.enable(gl.DEPTH_TEST); + } + }, +} + +// Convert from CSG solid to GL.Mesh object +OpenJsCad.Viewer.csgToMesh = function(csg) { + var csg = csg.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; +}; + +// parse javascript into solid: +OpenJsCad.javaScriptToSolid = function(script) { + var csg = new Function(script)(); + if( (typeof(csg) != "object") || (!('polygons' in csg))) + { + throw new Error("Your javascript code should return a CSG object. Try for example: return CSG.cube();"); + } + return csg; +}; \ No newline at end of file diff --git a/viewer.js b/viewer.js deleted file mode 100644 index 31752be..0000000 --- a/viewer.js +++ /dev/null @@ -1,192 +0,0 @@ -// 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.5, 1000); - 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); -} From a66db774a299db76b71e8650e409d9139f7b524f Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Sat, 21 Jan 2012 15:24:03 +0100 Subject: [PATCH 14/48] Added proper error message if WebGL not supported --- openjscad.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/openjscad.js b/openjscad.js index cd17904..811af96 100644 --- a/openjscad.js +++ b/openjscad.js @@ -4,13 +4,9 @@ OpenJsCad = function() { // A viewer is a WebGL canvas that lets the user view a mesh. The user can // tumble it around by dragging the mouse. OpenJsCad.Viewer = function(containerelement, width, height, initialdepth) { - var gl = GL.create(); - if(!gl) + try { - containerelement.innerHTML = "WebGL is required for the 3D viewer, but your browser doesn't seem to support this."; - } - else - { + var gl = GL.create(); this.gl = gl; this.angleX = 0; this.angleY = 0; @@ -85,7 +81,10 @@ OpenJsCad.Viewer = function(containerelement, width, height, initialdepth) { _this.onDraw(); }; this.clear(); - } + } + catch (e) { + containerelement.innerHTML = "

Error: "+e.toString()+"


OpenJsCad currently requires Google Chrome with WebGL enabled"; + } }; OpenJsCad.Viewer.prototype = { From 47372abdbde66b169a06f29e1271c5294d87321e Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Sat, 21 Jan 2012 17:28:19 +0100 Subject: [PATCH 15/48] OpenJsCad parser added, to parse local files with .jscad extension --- index.html | 40 ++++-- processfile.html | 320 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 349 insertions(+), 11 deletions(-) create mode 100644 processfile.html diff --git a/index.html b/index.html index 1380a63..69cec19 100644 --- a/index.html +++ b/index.html @@ -32,8 +32,8 @@ textarea:focus { outline: none; } -h1, h2 { font: bold 50px/50px 'Helvetica Neue', Helvetica, Arial; } -h2 { font-size: 30px; margin: 10px 0 0 0; } +//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; } @@ -42,6 +42,10 @@ td { padding: 5px; text-align: center; } td code { background: none; border: none; color: inherit; } canvas { cursor: move; } +#needchrome { + display: none; +} + + + + + +OpenJsCad parser + +

OpenJsCad parser

+
Please note: OpenJsCad currently only runs reliably on Google Chrome!
+
+
+
+
+
Drop your .jscad file here
+
+ dfghdfgh +
+ + +
+
+
+
+
+

Instructions:

+Create a new file in your favorite text editor. To get started enter the following text: +
+
+var cube = CSG.roundedCube({radius: 10, roundradius: 2, resolution: 16});
+var sphere = CSG.sphere({radius: 10, resolution: 16}).translate([5, 5, 5]);
+return cube.union(sphere);
+
+Save this to a file on your desktop with .jscad extension. Then drag & drop the file from your desktop +to the box above (marked with 'drop your .jscad file here).

+The 3d model should now appear. You can continue to make changes to your .jscad file. Just press Reload +to parse the changes, you do not need to drag & drop the file again. +

+When finished press Get STL to generate the STL file. Copy & paste the STL text to a file with +.stl extension. +

+For more information about OpenJsCad see the introduction. + + \ No newline at end of file From 720136cd8bc450799c9ce1495af0486ae5e0bbb0 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Sat, 21 Jan 2012 18:03:27 +0100 Subject: [PATCH 16/48] Added getBounds method --- csg.js | 43 +++++++++++++++++++++++++++++++++++++++++-- index.html | 20 +++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/csg.js b/csg.js index 83f0edf..01fc9fa 100644 --- a/csg.js +++ b/csg.js @@ -320,6 +320,38 @@ CSG.prototype = { return result; } }, + + // returns an array of two CSG.Vector3Ds (minimum coordinates and maximum coordinates) + getBounds: function() { + if(!this.cachedBoundingBox) + { + var minpoint = new CSG.Vector3D(0,0,0); + var maxpoint = new CSG.Vector3D(0,0,0); + var polygons = this.polygons; + var numpolygons = polygons.length; + for(var i=0; i < numpolygons; i++) + { + var polygon = polygons[i]; + var bounds = polygon.boundingBox(); + if(i == 0) + { + minpoint = bounds[0].clone(); + maxpoint = bounds[1].clone(); + } + else + { + minpoint.x = Math.min(minpoint.x, bounds[0].x); + minpoint.y = Math.min(minpoint.y, bounds[0].y); + minpoint.z = Math.min(minpoint.z, bounds[0].z); + maxpoint.x = Math.max(maxpoint.x, bounds[1].x); + maxpoint.y = Math.max(maxpoint.y, bounds[1].y); + maxpoint.z = Math.max(maxpoint.z, bounds[1].z); + } + } + this.cachedBoundingBox = [minpoint, maxpoint]; + } + return this.cachedBoundingBox; + } }; // Parse an option from the options object @@ -874,11 +906,18 @@ CSG.Plane = function(normal, w) { // point is on the plane. CSG.Plane.EPSILON = 1e-5; -CSG.Plane.fromPoints = function(a, b, c) { +CSG.Plane.fromVector3Ds = function(a, b, c) { var n = b.minus(a).cross(c.minus(a)).unit(); return new CSG.Plane(n, n.dot(a)); }; +CSG.Plane.fromPoints = function(a, b, c) { + a = new CSG.Vector3D(a); + b = new CSG.Vector3D(b); + c = new CSG.Vector3D(c); + return CSG.Plane.fromVector3Ds(a, b, c); +}; + CSG.Plane.prototype = { flipped: function() { return new CSG.Plane(this.normal.negated(), -this.w); @@ -1119,7 +1158,7 @@ CSG.Polygon = function(vertices, shared, plane) { } else { - this.plane = CSG.Plane.fromPoints(vertices[0].pos, vertices[1].pos, vertices[2].pos); + this.plane = CSG.Plane.fromVector3Ds(vertices[0].pos, vertices[1].pos, vertices[2].pos); } if(_CSGDEBUG) diff --git a/index.html b/index.html index 69cec19..91f969a 100644 --- a/index.html +++ b/index.html @@ -283,7 +283,25 @@ var csg = cube1.subtract(cube2); var rounded = csg.expand(0.2, 8); -

2d shapes

+

Determining the bounds of an object

+The getBounds() function can be used to retrieve the bounding box of an object. +This can be useful if you want to align two objects to each other. getBounds() returns +an array with two elements specifying the minimum x,y,z coordinate and the maximum x,y,z coordinate: + +
+var cube1 = CSG.cube({radius: 10});
+var cube2 = CSG.cube({radius: 5});
+
+// get the right bound of cube1 and the left bound of cube2:
+var deltax = cube1.getBounds()[1].x - cube2.getBounds()[0].x;
+
+// align cube2 so it touches cube1:
+cube2  = cube2.translate([deltax, 0, 0]);
+
+return cube1.union(cube2);
+
+ +

2D shapes

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() From 1eac51e2609309292283f0c0395df35b925b4fd4 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Sat, 21 Jan 2012 18:56:46 +0100 Subject: [PATCH 17/48] Added mirroring of solids --- csg.js | 37 ++++++++++++++++++++++++++++++++++++- index.html | 18 ++++++++++++++++-- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/csg.js b/csg.js index 01fc9fa..bf44224 100644 --- a/csg.js +++ b/csg.js @@ -203,7 +203,7 @@ CSG.prototype = { // Return a new CSG solid with solid and empty space switched. This solid is // not modified. inverse: function() { - var flippedpolygons = this.polygons.map(function(p) { p.flipped(); }); + var flippedpolygons = this.polygons.map(function(p) { return p.flipped(); }); return CSG.fromPolygons(flippedpolygons); }, @@ -212,6 +212,26 @@ CSG.prototype = { var newpolygons = this.polygons.map(function(p) { return p.transform(matrix4x4); } ); return CSG.fromPolygons(newpolygons); }, + + mirrored: function(plane) { + var newpolygons = this.polygons.map(function(p) { return p.mirrored(plane); } ); + return CSG.fromPolygons(newpolygons); + }, + + mirroredX: function() { + var plane = new CSG.Plane(new CSG.Vector3D(1,0,0), 0); + return this.mirrored(plane); + }, + + mirroredY: function() { + var plane = new CSG.Plane(new CSG.Vector3D(0,1,0), 0); + return this.mirrored(plane); + }, + + mirroredZ: function() { + var plane = new CSG.Plane(new CSG.Vector3D(0,0,1), 0); + return this.mirrored(plane); + }, translate: function(v) { return this.transform(CSG.Matrix4x4.translation(v)); @@ -1130,6 +1150,12 @@ CSG.Plane.prototype = { toString: function() { return "[normal: "+this.normal.toString()+", w: "+this.w+"]"; }, + + mirrorPoint: function(point3d) { + var distance = this.signedDistanceToPoint(point3d); + var mirrored = point3d.minus(this.normal.times(distance * 2.0)); + return mirrored; + }, }; @@ -1290,6 +1316,15 @@ CSG.Polygon.prototype = { return new CSG.Polygon(newvertices, this.shared, newplane); }, + mirrored: function(plane) { + var newvertices = this.vertices.map(function(v) { + var newpos = plane.mirrorPoint(v.pos); + return new CSG.Vertex(newpos); + }); + newvertices.reverse(); + return new CSG.Polygon(newvertices, this.shared); + }, + // Affine transformation of polygon. Returns a new CSG.Polygon transform: function(matrix4x4) { var newvertices = this.vertices.map(function(v) { return v.transform(matrix4x4); } ); diff --git a/index.html b/index.html index 91f969a..f0cca01 100644 --- a/index.html +++ b/index.html @@ -226,7 +226,6 @@ var cube = CSG.roundedCube({

CSG operations

The 3 standard CSG operations are supported. All CSG operations return a new solid; the source solids are not modified: -

 var csg1 = cube.union(sphere);
 var csg2 = cube.intersect(sphere);
@@ -235,7 +234,6 @@ var csg3 = cube.subtract(sphere);
 
 

Transformations

Solids can be translated, scaled and rotated. Multiple transforms can be combined into a single matrix transform: -

 var cube = CSG.cube();
 
@@ -262,6 +260,22 @@ m = m.multiply(CSG.Matrix4x4.scaling([1.1, 1.2, 1.3]));
 var cube3 = cube.transform(m);
 
+

Mirroring

+Solids can be mirrored in any plane in 3D space: +
+var cube = CSG.cube().translate([1,0,0]);
+
+var cube2 = cube.mirroredX(); // mirrored in the x=0 plane
+var cube3 = cube.mirroredY(); // mirrored in the y=0 plane
+var cube4 = cube.mirroredZ(); // mirrored in the z=0 plane
+
+// create a plane by specifying 3 points:
+var plane = CSG.Plane.fromPoints([5,0,0], [5, 1, 0], [3, 1, 7]);
+
+// and mirror in that plane:
+var cube5 = cube.mirrored(plane);
+
+

Expansion and contraction

Expansion can be seen as the 3D convolution of an object with a sphere. Contraction is the reverse: the area outside the solid From 5d1bc7152143cb629a18c770fb8136a1b721954b Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Sat, 21 Jan 2012 21:13:54 +0100 Subject: [PATCH 18/48] Added csg.cutByPlane() and CSG.Plane.fromNormalAndPoint() --- csg.js | 46 +++++++++++++++++++++++++++++++++++++++++++++- index.html | 20 ++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/csg.js b/csg.js index bf44224..d1fe3a9 100644 --- a/csg.js +++ b/csg.js @@ -371,7 +371,43 @@ CSG.prototype = { this.cachedBoundingBox = [minpoint, maxpoint]; } return this.cachedBoundingBox; - } + }, + + // Cut the solid by a plane. Returns the solid on the back side of the plane + cutByPlane: function(plane) { + // Ideally we would like to do an intersection with a polygon of inifinite size + // but this is not supported by our implementation. As a workaround, we will create + // a cube, with one face on the plane, and a size larger enough so that the entire + // solid fits in the cube. + + // find the max distance of any vertex to the center of the plane: + var planecenter = plane.normal.times(plane.w); + var maxdistance = 0; + this.polygons.map(function(polygon){ + polygon.vertices.map(function(vertex){ + var distance = vertex.pos.distanceToSquared(planecenter); + if(distance > maxdistance) maxdistance = distance; + }); + }); + maxdistance = Math.sqrt(maxdistance); + maxdistance *= 1.01; // make sure it's really larger + + // Now build a polygon on the plane, at any point farther than maxdistance from the plane center: + var vertices = []; + var orthobasis = new CSG.OrthoNormalBasis(plane); + vertices.push(new CSG.Vertex(orthobasis.to3D(new CSG.Vector2D(maxdistance,maxdistance)))); + vertices.push(new CSG.Vertex(orthobasis.to3D(new CSG.Vector2D(-maxdistance,maxdistance)))); + vertices.push(new CSG.Vertex(orthobasis.to3D(new CSG.Vector2D(-maxdistance,-maxdistance)))); + vertices.push(new CSG.Vertex(orthobasis.to3D(new CSG.Vector2D(maxdistance,-maxdistance)))); + var polygon = new CSG.Polygon(vertices, null, plane.flipped()); + + // and extrude the polygon into a cube, backwards of the plane: + var cube = polygon.extrude(plane.normal.times(-maxdistance)); + + // Now we can do the intersection: + return this.intersect(cube); + }, + }; // Parse an option from the options object @@ -938,6 +974,14 @@ CSG.Plane.fromPoints = function(a, b, c) { return CSG.Plane.fromVector3Ds(a, b, c); }; +CSG.Plane.fromNormalAndPoint = function(normal, point) { + normal = new CSG.Vector3D(normal); + point = new CSG.Vector3D(point); + normal = normal.unit(); + var w = point.dot(normal); + return new CSG.Plane(normal, w); +}; + CSG.Plane.prototype = { flipped: function() { return new CSG.Plane(this.normal.negated(), -this.w); diff --git a/index.html b/index.html index f0cca01..a684e22 100644 --- a/index.html +++ b/index.html @@ -276,6 +276,26 @@ var plane = CSG.Plane.fromPoints([5,0,0], [5, 1, 0], [3, 1, 7]); var cube5 = cube.mirrored(plane);
+

Cutting by a plane

+A solid can be cut by a plane; only the part on the back side is kept: + +
+var cube = CSG.cube({radius: 10});
+
+// create a plane by specifying 3 points:
+var plane1 = CSG.Plane.fromPoints([5,0,0], [7, 1, 0], [3, 1, 7]);
+
+// or by specifying a normal and a point on the plane:
+var plane2 = CSG.Plane.fromNormalAndPoint([3, 1, 2], [5, 0, 0]);
+
+// and cut by the plane:
+var part1 = cube.cutByPlane(plane2);
+
+// or if we need the other half of the cube:
+var part2 = cube.cutByPlane(plane2.flipped());
+
+ +

Expansion and contraction

Expansion can be seen as the 3D convolution of an object with a sphere. Contraction is the reverse: the area outside the solid From 90299872ad7f613c2d5531fdd8a5cfe0abfa7f75 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Thu, 26 Jan 2012 22:55:18 +0100 Subject: [PATCH 19/48] fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cda1250..9471dc2 100644 --- a/README.md +++ b/README.md @@ -17,5 +17,5 @@ https://github.com/joostn/csg.js # License -Copyright (c) 2012 Joost Nieuwenuijse (joost@newhouse.nl), under the [MIT license](http://www.opensource.org/licenses/mit-license.php). +Copyright (c) 2012 Joost Nieuwenhuijse (joost@newhouse.nl), under the [MIT license](http://www.opensource.org/licenses/mit-license.php). Copyright (c) 2011 Evan Wallace (http://madebyevan.com/), under the [MIT license](http://www.opensource.org/licenses/mit-license.php). From e926541595136a25bd1e71c48a6e43957c7c3bf9 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Fri, 27 Jan 2012 18:01:18 +0100 Subject: [PATCH 20/48] Connectors, first part --- csg.js | 345 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 324 insertions(+), 21 deletions(-) diff --git a/csg.js b/csg.js index d1fe3a9..295424d 100644 --- a/csg.js +++ b/csg.js @@ -90,6 +90,7 @@ for solid CAD anyway. CSG = function() { this.polygons = []; + this.properties = new CSG.Properties(); }; // Construct a CSG solid from a list of `CSG.Polygon` instances. @@ -128,10 +129,11 @@ CSG.prototype = { a.clipTo(b, false); b.clipTo(a, true); var newpolygons = a.allPolygons().concat(b.allPolygons()); - var csg = CSG.fromPolygons(newpolygons); - if(canonicalize) csg = csg.canonicalized(); - if(retesselate) csg = csg.reTesselated(); - return csg; + var result = CSG.fromPolygons(newpolygons); + result.properties = this.properties._merge(csg.properties); + if(canonicalize) result = result.canonicalized(); + if(retesselate) result = result.reTesselated(); + return result; }, // Return a new CSG solid representing space in this solid but not in the @@ -160,10 +162,11 @@ CSG.prototype = { b.clipTo(a, true); a.addPolygons(b.allPolygons()); a.invert(); - var csg = CSG.fromPolygons(a.allPolygons()); - if(canonicalize) csg = csg.canonicalized(); - if(retesselate) csg = csg.reTesselated(); - return csg; + var result = CSG.fromPolygons(a.allPolygons()); + result.properties = this.properties._merge(csg.properties); + if(canonicalize) result = result.canonicalized(); + if(retesselate) result = result.reTesselated(); + return result; }, // Return a new CSG solid representing space both this solid and in the @@ -194,10 +197,11 @@ CSG.prototype = { b.clipTo(a); a.addPolygons(b.allPolygons()); a.invert(); - var csg = CSG.fromPolygons(a.allPolygons()); - if(canonicalize) csg = csg.canonicalized(); - if(retesselate) csg = csg.reTesselated(); - return csg; + var result = CSG.fromPolygons(a.allPolygons()); + result.properties = this.properties._merge(csg.properties); + if(canonicalize) result = result.canonicalized(); + if(retesselate) result = result.reTesselated(); + return result; }, // Return a new CSG solid with solid and empty space switched. This solid is @@ -205,17 +209,21 @@ CSG.prototype = { inverse: function() { var flippedpolygons = this.polygons.map(function(p) { return p.flipped(); }); return CSG.fromPolygons(flippedpolygons); + // TODO: flip properties }, // Affine transformation of CSG object. Returns a new CSG object transform: function(matrix4x4) { var newpolygons = this.polygons.map(function(p) { return p.transform(matrix4x4); } ); - return CSG.fromPolygons(newpolygons); + var result=CSG.fromPolygons(newpolygons); + result.properties = this.properties._transform(matrix4x4); + return result; }, mirrored: function(plane) { var newpolygons = this.polygons.map(function(p) { return p.mirrored(plane); } ); - return CSG.fromPolygons(newpolygons); + return CSG.fromPolygons(newpolygons); + // TODO: also mirror properties }, mirroredX: function() { @@ -275,6 +283,7 @@ CSG.prototype = { result=result.unionSub(expanded, true, false); }); result = result.canonicalized(); + result.properties = this.properties; // keep original properties return result; }, @@ -286,6 +295,7 @@ CSG.prototype = { var expanded=p.expand(radius, resolution); result=result.subtract(expanded); }); + result.properties = this.properties; // keep original properties return result; }, @@ -299,6 +309,7 @@ CSG.prototype = { var factory = new CSG.fuzzyCSGFactory(); var result = factory.getCSG(this); result.isCanonicalized = true; + result.properties = this.properties; // keep original properties return result; } }, @@ -337,6 +348,7 @@ CSG.prototype = { } var result = CSG.fromPolygons(destpolygons); result.isRetesselated = true; + result.properties = this.properties; // keep original properties return result; } }, @@ -405,7 +417,9 @@ CSG.prototype = { var cube = polygon.extrude(plane.normal.times(-maxdistance)); // Now we can do the intersection: - return this.intersect(cube); + var result = this.intersect(cube); + result.properties = this.properties; // keep original properties + return result; }, }; @@ -464,7 +478,7 @@ CSG.parseOptionAsInt = function(options, optionname, defaultvalue) { CSG.cube = function(options) { var c = CSG.parseOptionAs3DVector(options, "center", [0,0,0]); var r = CSG.parseOptionAs3DVector(options, "radius", [1,1,1]); - return CSG.fromPolygons([ + var result = CSG.fromPolygons([ [[0, 4, 6, 2], [-1, 0, 0]], [[1, 3, 7, 5], [+1, 0, 0]], [[0, 1, 5, 4], [0, -1, 0]], @@ -484,6 +498,17 @@ CSG.cube = function(options) { }); return new CSG.Polygon(vertices, null /* , plane */); })); + result.properties.cube = new CSG.Properties(); + result.properties.cube.center = new CSG.Vertex(c); + result.properties.cube.facecenters = [ + new CSG.Vertex(new CSG.Vector3D([r.x, 0, 0]).plus(c)), + new CSG.Vertex(new CSG.Vector3D([-r.x, 0, 0]).plus(c)), + new CSG.Vertex(new CSG.Vector3D([0, r.y, 0]).plus(c)), + new CSG.Vertex(new CSG.Vector3D([0, -r.y, 0]).plus(c)), + new CSG.Vertex(new CSG.Vector3D([0, 0, r.z]).plus(c)), + new CSG.Vertex(new CSG.Vector3D([0, 0, -r.z]).plus(c)), + ]; + return result; }; // Construct a solid sphere @@ -554,7 +579,11 @@ CSG.sphere = function(options) { } prevcylinderpoint = cylinderpoint; } - return CSG.fromPolygons(polygons); + var result = CSG.fromPolygons(polygons); + result.properties.sphere = new CSG.Properties(); + result.properties.sphere.center = new CSG.Vertex(c); + result.properties.sphere.facepoint = new CSG.Vertex(c.plus(xvector)); + return result; }; // Construct a solid cylinder. @@ -598,7 +627,12 @@ CSG.cylinder = function(options) { polygons.push(new CSG.Polygon([point(0, t1, 0), point(0, t0, 0), point(1, t0, 0), point(1, t1, 0)])); polygons.push(new CSG.Polygon([end, point(1, t1, 1), point(1, t0, 1)])); } - return CSG.fromPolygons(polygons); + var result = CSG.fromPolygons(polygons); + result.properties.cylinder = new CSG.Properties(); + result.properties.cylinder.start = new CSG.Vertex(s); + result.properties.cylinder.end = new CSG.Vertex(e); + result.properties.cylinder.facepoint = new CSG.Vertex(s.plus(axisX.times(r))); + return result; }; // Like a cylinder, but with rounded ends instead of flat @@ -693,7 +727,12 @@ CSG.roundedCylinder = function(options) { } prevcylinderpoint = cylinderpoint; } - return CSG.fromPolygons(polygons); + var result = CSG.fromPolygons(polygons); + result.properties.roundedCylinder = new CSG.Properties(); + result.properties.roundedCylinder.start = new CSG.Vertex(p1); + result.properties.roundedCylinder.end = new CSG.Vertex(p2); + result.properties.roundedCylinder.facepoint = new CSG.Vertex(p1.plus(xvector)); + return result; }; // Construct an axis-aligned solid rounded cuboid. @@ -760,8 +799,18 @@ CSG.roundedCube = function(options) { result = result.unionSub(cylinder,true,true); } } + result.properties.roundedCube = new CSG.Properties(); + result.properties.roundedCube.center = new CSG.Vertex(center); + result.properties.roundedCube.facecenters = [ + new CSG.Vertex(new CSG.Vector3D([cuberadius.x, 0, 0]).plus(center)), + new CSG.Vertex(new CSG.Vector3D([-cuberadius.x, 0, 0]).plus(center)), + new CSG.Vertex(new CSG.Vector3D([0, cuberadius.y, 0]).plus(center)), + new CSG.Vertex(new CSG.Vector3D([0, -cuberadius.y, 0]).plus(center)), + new CSG.Vertex(new CSG.Vector3D([0, 0, cuberadius.z]).plus(center)), + new CSG.Vertex(new CSG.Vector3D([0, 0, -cuberadius.z]).plus(center)), + ]; return result; -} +}; @@ -825,6 +874,10 @@ CSG.Vector3D.prototype = { return new CSG.Vector3D(-this.x, -this.y, -this.z); }, + abs: function() { + return new CSG.Vector3D(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z)); + }, + plus: function(a) { return new CSG.Vector3D(this.x + a.x, this.y + a.y, this.z + a.z); }, @@ -895,6 +948,23 @@ CSG.Vector3D.prototype = { return "("+this.x+", "+this.y+", "+this.z+")"; }, + // find a vector that is somewhat perpendicular to this one + randomNonParallelVector: function() { + var abs = this.abs(); + if( (abs.x <= abs.y) && (abs.x <= abs.z) ) + { + return new CSG.Vector3D(1,0,0); + } + else if( (abs.y <= abs.x) && (abs.y <= abs.z) ) + { + return new CSG.Vector3D(0,1,0); + } + else + { + return new CSG.Vector3D(0,0,1); + } + }, + }; // # class Vertex @@ -967,6 +1037,30 @@ CSG.Plane.fromVector3Ds = function(a, b, c) { return new CSG.Plane(n, n.dot(a)); }; +// like fromVector3Ds, but allow the vectors to be on one point or one line +// in such a case a random plane through the given points is constructed +CSG.Plane.anyPlaneFromVector3Ds = function(a, b, c) { + var v1 = b.minus(a); + var v2 = c.minus(a); + if(v1.length() < 1e-5) + { + v1 = v2.randomNonParallelVector(); + } + if(v2.length() < 1e-5) + { + v2 = v1.randomNonParallelVector(); + } + var normal = v1.cross(v2); + if(normal.length() < 1e-5) + { + // this would mean that v1 == v2.negated() + v2 = v1.randomNonParallelVector(); + normal = v1.cross(v2); + } + normal = normal.unit(); + return new CSG.Plane(normal, normal.dot(a)); +}; + CSG.Plane.fromPoints = function(a, b, c) { a = new CSG.Vector3D(a); b = new CSG.Vector3D(b); @@ -2119,6 +2213,11 @@ CSG.Vector2D.prototype = { multiply4x4: function(matrix4x4) { return matrix4x4.rightMultiply1x2Vector(this); }, + + angle: function() { + // y=sin, x=cos + return Math.atan2(this.y, this.x); + }, }; // A polygon in 2D space: @@ -2415,10 +2514,29 @@ CSG.OrthoNormalBasis = function (plane) { } this.v = rightvector.cross(plane.normal).unit(); this.u = plane.normal.cross(this.v); + this.plane = plane; this.planeorigin = plane.normal.times(plane.w); }; CSG.OrthoNormalBasis.prototype = { + getProjectionMatrix: function() { + return new CSG.Matrix4x4([ + this.u.x, this.v.x, this.plane.normal.x, 0, + this.u.y, this.v.y, this.plane.normal.y, 0, + this.u.z, this.v.z, this.plane.normal.z, 0, + 0, 0, -this.plane.w, 1 + ]); + }, + + getInverseProjectionMatrix: function() { + return new CSG.Matrix4x4([ + this.u.x, this.u.y, this.u.z, 0, + this.v.x, this.v.y, this.v.z, 0, + this.plane.normal.x, this.plane.normal.y, this.plane.normal.z, this.plane.w, + 0,0,0,1 + ]); + }, + to2D: function(vec3) { return new CSG.Vector2D(vec3.dot(this.u), vec3.dot(this.v)); }, @@ -3072,4 +3190,189 @@ CSG.staticTag = 1; CSG.getTag = function () { return CSG.staticTag++; -}; \ No newline at end of file +}; + +////////////////////////////////////// + +// # Class Properties +// This class is used to store properties of a solid +// A property can for example be a CSG.Vertex, a CSG.Plane or a CSG.Line3D +// Whenever an affine transform is applied to the CSG solid, all its properties are +// transformed as well. +// The properties can be stored in a complex nested structure (using arrays and objects) +CSG.Properties = function() { +}; + +CSG.Properties.prototype = { + _transform: function(matrix4x4) { + var result = new CSG.Properties(); + CSG.Properties.transformObj(this, result, matrix4x4); + return result; + }, + _merge: function(otherproperties) { + var result = new CSG.Properties(); + CSG.Properties.cloneObj(this, result); + CSG.Properties.addFrom(result, otherproperties); + return result; + }, +}; + +CSG.Properties.transformObj = function(source, result, matrix4x4) +{ + for(var propertyname in source) + { + if(propertyname == "_transform") continue; + if(propertyname == "_merge") continue; + var propertyvalue = source[propertyname]; + var transformed = propertyvalue; + if(typeof(propertyvalue) == "object") + { + if( ('transform' in propertyvalue) && (typeof(propertyvalue.transform) == "function") ) + { + transformed = propertyvalue.transform(matrix4x4); + } + else if(propertyvalue instanceof Array) + { + transformed = []; + CSG.Properties.transformObj(propertyvalue, transformed, matrix4x4); + } + else if(propertyvalue instanceof CSG.Properties) + { + transformed = new CSG.Properties(); + CSG.Properties.transformObj(propertyvalue, transformed, matrix4x4); + } + } + result[propertyname] = transformed; + } +}; + +CSG.Properties.cloneObj = function(source, result) +{ + for(var propertyname in source) + { + if(propertyname == "_transform") continue; + if(propertyname == "_merge") continue; + var propertyvalue = source[propertyname]; + var cloned = propertyvalue; + if(typeof(propertyvalue) == "object") + { + if(propertyvalue instanceof Array) + { + cloned = []; + for(var i=0; i < propertyvalue.length; i++) + { + cloned.push(propertyvalue[i]); + } + } + else if(propertyvalue instanceof CSG.Properties) + { + cloned = new CSG.Properties(); + CSG.Properties.cloneObj(propertyvalue, cloned); + } + } + result[propertyname] = cloned; + } +}; + +CSG.Properties.addFrom = function(result, otherproperties) +{ + for(var propertyname in otherproperties) + { + if(propertyname == "_transform") continue; + if(propertyname == "_merge") continue; + if( (propertyname in result) + && (typeof(result[propertyname]) == "object") + && (result[propertyname] instanceof CSG.Properties) + && (typeof(otherproperties[propertyname]) == "object") + && (otherproperties[propertyname] instanceof CSG.Properties) ) + { + CSG.Properties.addFrom(result[propertyname], otherproperties[propertyname]); + } + else if(!(propertyname in result)) + { + result[propertyname] = otherproperties[propertyname]; + } + } +}; + +////////////////////////////////////// + +// # class Connector +// A connector allows to attach two objects at predefined positions +// For example a servo motor and a servo horn: +// Both can have a Connector called 'shaft' +// The horn can be moved and rotated such that the two connectors match +// and the horn is attached to the servo motor at the proper position. +// Connectors are stored in the properties of a CSG solid so they are +// ge the same transformations applied as the solid + +CSG.Connector = function(point, axisvector, normalvector) { + this.point = new CSG.Vector3D(point); + this.axisvector = new CSG.Vector3D(axisvector); + this.normalvector = new CSG.Vector3D(normalvector); +}; + +CSG.Connector.test = function() { + var con1 = new CSG.Connector([1,2,5], [1,1,0], [1, -0.8, 0]); + var con2 = new CSG.Connector([-5,1,-1], [2,3,5], [0,0,1]); + var transform = con1.getTransformationTo(con2, false, 0); + var check = con1.transform(transform); +}; + +CSG.Connector.prototype = { + normalized: function() { + var axisvector = this.axisvector.unit(); + // make the normal vector truly normal: + var n = this.normalvector.cross(axisvector).unit(); +// var normalvector = n.cross(axisvector); + var normalvector = axisvector.cross(n); + return new CSG.Connector(this.point, axisvector, normalvector); + }, + + transform: function(matrix4x4) { + var point = this.point.multiply4x4(matrix4x4); + var axisvector = this.point.plus(this.axisvector).multiply4x4(matrix4x4).minus(point); + var normalvector = this.point.plus(this.normalvector).multiply4x4(matrix4x4).minus(point); + return new CSG.Connector(point, axisvector, normalvector); + }, + + getTransformationTo: function(other, mirror, axisrotation) { + mirror = mirror? true:false; + axisrotation = axisrotation? Number(axisrotation):0; + var us = this.normalized(); + other = other.normalized(); + // shift to the origin: + var transformation = CSG.Matrix4x4.translation(this.point.negated()); +var check = us.transform(transformation); + // construct the plane crossing through the origin and the two axes: + var axesplane = CSG.Plane.anyPlaneFromVector3Ds( + new CSG.Vector3D(0,0,0), + us.axisvector, + other.axisvector + ); + var axesbasis = new CSG.OrthoNormalBasis(axesplane); + var angle1 = axesbasis.to2D(us.axisvector).angle(); + var angle2 = axesbasis.to2D(other.axisvector).angle(); + var rotation = 180.0 * (angle2 - angle1) / Math.PI; + if(mirror) rotation += 180.0; + transformation = transformation.multiply(axesbasis.getProjectionMatrix()); + transformation = transformation.multiply(CSG.Matrix4x4.rotationZ(rotation)); + transformation = transformation.multiply(axesbasis.getInverseProjectionMatrix()); + var usAxesAligned = us.transform(transformation); + // Now we have done the transformation for aligning the axes. + // We still need to align the normals: + var normalsplane = CSG.Plane.fromNormalAndPoint(other.axisvector, new CSG.Vector3D(0,0,0)); + var normalsbasis = new CSG.OrthoNormalBasis(normalsplane); + angle1 = normalsbasis.to2D(usAxesAligned.normalvector).angle(); + angle2 = normalsbasis.to2D(other.normalvector).angle(); + rotation = 180.0 * (angle2 - angle1) / Math.PI; + rotation += axisrotation; + transformation = transformation.multiply(normalsbasis.getProjectionMatrix()); + transformation = transformation.multiply(CSG.Matrix4x4.rotationZ(rotation)); + transformation = transformation.multiply(normalsbasis.getInverseProjectionMatrix()); + // and translate to the destination point: + transformation = transformation.multiply(CSG.Matrix4x4.translation(other.point)); + var usAligned = us.transform(transformation); + return transformation; + }, +}; From 6fb07da2addd188c23bd78e18283e283624a45d5 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Fri, 27 Jan 2012 18:44:37 +0100 Subject: [PATCH 21/48] Fixed: when concatenating multiple transforms by multiplying their matrices, transformations were done in wrong order --- csg.js | 76 +++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/csg.js b/csg.js index 295424d..eeedf49 100644 --- a/csg.js +++ b/csg.js @@ -937,7 +937,7 @@ CSG.Vector3D.prototype = { // Right multiply by a 4x4 matrix (the vector is interpreted as a row vector) // Returns a new CSG.Vector3D multiply4x4: function(matrix4x4) { - return matrix4x4.rightMultiply1x3Vector(this); + return matrix4x4.leftMultiply1x3Vector(this); }, toStlString: function() { @@ -1990,7 +1990,8 @@ CSG.Matrix4x4.prototype = { return new CSG.Matrix4x4(elements); }, - // Multiply a CSG.Vector3D (interpreted as 1 row, 3 column) by this matrix + // Right multiply the matrix by a CSG.Vector3D (interpreted as 3 row, 1 column) + // (result = M*v) // Fourth element is taken as 1 rightMultiply1x3Vector: function(v) { var v0 = v.x; @@ -2012,7 +2013,31 @@ CSG.Matrix4x4.prototype = { return new CSG.Vector3D(x,y,z); }, - // Multiply a CSG.Vector2D (interpreted as 1 row, 2 column) by this matrix + // Multiply a CSG.Vector3D (interpreted as 3 column, 1 row) by this matrix + // (result = v*M) + // Fourth element is taken as 1 + leftMultiply1x3Vector: function(v) { + var v0 = v.x; + var v1 = v.y; + var v2 = v.z; + var v3 = 1; + var x = v0*this.elements[0] + v1*this.elements[4] + v2*this.elements[8] + v3*this.elements[12]; + var y = v0*this.elements[1] + v1*this.elements[5] + v2*this.elements[9] + v3*this.elements[13]; + var z = v0*this.elements[2] + v1*this.elements[6] + v2*this.elements[10] + v3*this.elements[14]; + var w = v0*this.elements[3] + v1*this.elements[7] + v2*this.elements[11] + v3*this.elements[15]; + // scale such that fourth element becomes 1: + if(w != 1) + { + var invw=1.0/w; + x *= invw; + y *= invw; + z *= invw; + } + return new CSG.Vector3D(x,y,z); + }, + + // Right multiply the matrix by a CSG.Vector2D (interpreted as 2 row, 1 column) + // (result = M*v) // Fourth element is taken as 1 rightMultiply1x2Vector: function(v) { var v0 = v.x; @@ -2033,6 +2058,29 @@ CSG.Matrix4x4.prototype = { } return new CSG.Vector2D(x,y); }, + + // Multiply a CSG.Vector2D (interpreted as 2 column, 1 row) by this matrix + // (result = v*M) + // Fourth element is taken as 1 + leftMultiply1x2Vector: function(v) { + var v0 = v.x; + var v1 = v.y; + var v2 = 0; + var v3 = 1; + var x = v0*this.elements[0] + v1*this.elements[4] + v2*this.elements[8] + v3*this.elements[12]; + var y = v0*this.elements[1] + v1*this.elements[5] + v2*this.elements[9] + v3*this.elements[13]; + var z = v0*this.elements[2] + v1*this.elements[6] + v2*this.elements[10] + v3*this.elements[14]; + var w = v0*this.elements[3] + v1*this.elements[7] + v2*this.elements[11] + v3*this.elements[15]; + // scale such that fourth element becomes 1: + if(w != 1) + { + var invw=1.0/w; + x *= invw; + y *= invw; + z *= invw; + } + return new CSG.Vector2D(x,y); + }, }; // return the unity matrix @@ -2047,8 +2095,8 @@ CSG.Matrix4x4.rotationX = function(degrees) { var sin = Math.sin(radians); var els = [ 1, 0, 0, 0, - 0, cos, -sin, 0, - 0, sin, cos, 0, + 0, cos, sin, 0, + 0, -sin, cos, 0, 0, 0, 0, 1 ]; return new CSG.Matrix4x4(els); @@ -2060,9 +2108,9 @@ CSG.Matrix4x4.rotationY = function(degrees) { var cos = Math.cos(radians); var sin = Math.sin(radians); var els = [ - cos, 0, sin, 0, + cos, 0, -sin, 0, 0, 1, 0, 0, - -sin, 0, cos, 0, + sin, 0, cos, 0, 0, 0, 0, 1 ]; return new CSG.Matrix4x4(els); @@ -2074,8 +2122,8 @@ CSG.Matrix4x4.rotationZ = function(degrees) { var cos = Math.cos(radians); var sin = Math.sin(radians); var els = [ - cos, -sin, 0, 0, - sin, cos, 0, 0, + cos, sin, 0, 0, + -sin, cos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; @@ -2087,10 +2135,10 @@ CSG.Matrix4x4.translation = function(v) { // parse as CSG.Vector3D, so we can pass an array or a CSG.Vector3D var vec = new CSG.Vector3D(v); var els = [ - 1, 0, 0, vec.x, - 0, 1, 0, vec.y, - 0, 0, 1, vec.z, - 0, 0, 0, 1 + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + vec.x, vec.y, vec.z, 1 ]; return new CSG.Matrix4x4(els); }; @@ -2211,7 +2259,7 @@ CSG.Vector2D.prototype = { // Right multiply by a 4x4 matrix (the vector is interpreted as a row vector) // Returns a new CSG.Vector2D multiply4x4: function(matrix4x4) { - return matrix4x4.rightMultiply1x2Vector(this); + return matrix4x4.leftMultiply1x2Vector(this); }, angle: function() { From e1bd45c7d684e2903f677134cf4fc63fe56f5d6b Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Fri, 27 Jan 2012 18:50:58 +0100 Subject: [PATCH 22/48] Added CSG.Connector functionality --- csg.js | 1 - 1 file changed, 1 deletion(-) diff --git a/csg.js b/csg.js index eeedf49..81044fd 100644 --- a/csg.js +++ b/csg.js @@ -3391,7 +3391,6 @@ CSG.Connector.prototype = { other = other.normalized(); // shift to the origin: var transformation = CSG.Matrix4x4.translation(this.point.negated()); -var check = us.transform(transformation); // construct the plane crossing through the origin and the two axes: var axesplane = CSG.Plane.anyPlaneFromVector3Ds( new CSG.Vector3D(0,0,0), From fbafcf686489625d35bfaf4f2c9ec16be279f5e7 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Mon, 6 Feb 2012 12:32:34 +0100 Subject: [PATCH 23/48] Updates to CSG.Connector, added CSG.connectTo method --- csg.js | 56 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/csg.js b/csg.js index 81044fd..3821d29 100644 --- a/csg.js +++ b/csg.js @@ -421,6 +421,11 @@ CSG.prototype = { result.properties = this.properties; // keep original properties return result; }, + + connectTo: function(myConnector, otherConnector, mirror, axisrotation) { + var matrix = myConnector.getTransformationTo(otherConnector, mirror, axisrotation); + return this.transform(matrix); + }, }; @@ -499,14 +504,15 @@ CSG.cube = function(options) { return new CSG.Polygon(vertices, null /* , plane */); })); result.properties.cube = new CSG.Properties(); - result.properties.cube.center = new CSG.Vertex(c); + result.properties.cube.center = new CSG.Vector3D(c); + // add 6 connectors, at the centers of each face: result.properties.cube.facecenters = [ - new CSG.Vertex(new CSG.Vector3D([r.x, 0, 0]).plus(c)), - new CSG.Vertex(new CSG.Vector3D([-r.x, 0, 0]).plus(c)), - new CSG.Vertex(new CSG.Vector3D([0, r.y, 0]).plus(c)), - new CSG.Vertex(new CSG.Vector3D([0, -r.y, 0]).plus(c)), - new CSG.Vertex(new CSG.Vector3D([0, 0, r.z]).plus(c)), - new CSG.Vertex(new CSG.Vector3D([0, 0, -r.z]).plus(c)), + new CSG.Connector(new CSG.Vector3D([r.x, 0, 0]).plus(c), [1, 0, 0], [0, 0, 1]), + new CSG.Connector(new CSG.Vector3D([-r.x, 0, 0]).plus(c), [-1, 0, 0], [0, 0, 1]), + new CSG.Connector(new CSG.Vector3D([0, r.y, 0]).plus(c), [0, 1, 0], [0, 0, 1]), + new CSG.Connector(new CSG.Vector3D([0, -r.y, 0]).plus(c), [0, -1, 0], [0, 0, 1]), + new CSG.Connector(new CSG.Vector3D([0, 0, r.z]).plus(c), [0, 0, 1], [1, 0, 0]), + new CSG.Connector(new CSG.Vector3D([0, 0, -r.z]).plus(c), [0, 0, -1], [1, 0, 0]), ]; return result; }; @@ -581,8 +587,8 @@ CSG.sphere = function(options) { } var result = CSG.fromPolygons(polygons); result.properties.sphere = new CSG.Properties(); - result.properties.sphere.center = new CSG.Vertex(c); - result.properties.sphere.facepoint = new CSG.Vertex(c.plus(xvector)); + result.properties.sphere.center = new CSG.Vector3D(c); + result.properties.sphere.facepoint = c.plus(xvector); return result; }; @@ -629,9 +635,9 @@ CSG.cylinder = function(options) { } var result = CSG.fromPolygons(polygons); result.properties.cylinder = new CSG.Properties(); - result.properties.cylinder.start = new CSG.Vertex(s); - result.properties.cylinder.end = new CSG.Vertex(e); - result.properties.cylinder.facepoint = new CSG.Vertex(s.plus(axisX.times(r))); + result.properties.cylinder.start = new CSG.Connector(s, axisZ.negated(), axisX); + result.properties.cylinder.end = new CSG.Connector(e, axisZ, axisX); + result.properties.cylinder.facepoint = s.plus(axisX.times(r)); return result; }; @@ -727,11 +733,13 @@ CSG.roundedCylinder = function(options) { } prevcylinderpoint = cylinderpoint; } - var result = CSG.fromPolygons(polygons); + var result = CSG.fromPolygons(polygons); + var ray = zvector.unit(); + var axisX = xvector.unit(); result.properties.roundedCylinder = new CSG.Properties(); - result.properties.roundedCylinder.start = new CSG.Vertex(p1); - result.properties.roundedCylinder.end = new CSG.Vertex(p2); - result.properties.roundedCylinder.facepoint = new CSG.Vertex(p1.plus(xvector)); + result.properties.cylinder.start = new CSG.Connector(p1, ray.negated(), axisX); + result.properties.cylinder.end = new CSG.Connector(p2, ray, axisX); + result.properties.cylinder.facepoint = p1.plus(xvector); return result; }; @@ -802,12 +810,12 @@ CSG.roundedCube = function(options) { result.properties.roundedCube = new CSG.Properties(); result.properties.roundedCube.center = new CSG.Vertex(center); result.properties.roundedCube.facecenters = [ - new CSG.Vertex(new CSG.Vector3D([cuberadius.x, 0, 0]).plus(center)), - new CSG.Vertex(new CSG.Vector3D([-cuberadius.x, 0, 0]).plus(center)), - new CSG.Vertex(new CSG.Vector3D([0, cuberadius.y, 0]).plus(center)), - new CSG.Vertex(new CSG.Vector3D([0, -cuberadius.y, 0]).plus(center)), - new CSG.Vertex(new CSG.Vector3D([0, 0, cuberadius.z]).plus(center)), - new CSG.Vertex(new CSG.Vector3D([0, 0, -cuberadius.z]).plus(center)), + new CSG.Connector(new CSG.Vector3D([cuberadius.x, 0, 0]).plus(center), [1, 0, 0], [0, 0, 1]), + new CSG.Connector(new CSG.Vector3D([-cuberadius.x, 0, 0]).plus(center), [-1, 0, 0], [0, 0, 1]), + new CSG.Connector(new CSG.Vector3D([0, cuberadius.y, 0]).plus(center), [0, 1, 0], [0, 0, 1]), + new CSG.Connector(new CSG.Vector3D([0, -cuberadius.y, 0]).plus(center), [0, -1, 0], [0, 0, 1]), + new CSG.Connector(new CSG.Vector3D([0, 0, cuberadius.z]).plus(center), [0, 0, 1], [1, 0, 0]), + new CSG.Connector(new CSG.Vector3D([0, 0, -cuberadius.z]).plus(center), [0, 0, -1], [1, 0, 0]), ]; return result; }; @@ -940,6 +948,10 @@ CSG.Vector3D.prototype = { return matrix4x4.leftMultiply1x3Vector(this); }, + transform: function(matrix4x4) { + return matrix4x4.leftMultiply1x3Vector(this); + }, + toStlString: function() { return this.x+" "+this.y+" "+this.z; }, From 3b4e1fd1a683cd51a3e4c88ddfced3ea5af3a047 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Mon, 13 Feb 2012 15:29:37 +0100 Subject: [PATCH 24/48] CSG construction must now be done in a main() function instead of in global scope; Added CSG properties and connectors; file processing is now done in the background (Web Workers); STL file is now downloaded instead of copied from a text area --- csg.js | 109 +++++++++++++------ index.html | 249 +++++++++++++++++++++++++----------------- openjscad.js | 274 +++++++++++++++++++++++++++++++++++++++++++++++ processfile.html | 166 ++++++---------------------- 4 files changed, 536 insertions(+), 262 deletions(-) diff --git a/csg.js b/csg.js index 3821d29..b5c3aa0 100644 --- a/csg.js +++ b/csg.js @@ -100,6 +100,14 @@ CSG.fromPolygons = function(polygons) { return csg; }; +// create from an untyped object with identical property names: +CSG.fromObject = function(obj) { + var polygons = obj.polygons.map( function(p) { + return CSG.Polygon.fromObject(p); + }); + return CSG.fromPolygons(polygons); +}; + CSG.prototype = { toPolygons: function() { return this.polygons; @@ -221,9 +229,7 @@ CSG.prototype = { }, mirrored: function(plane) { - var newpolygons = this.polygons.map(function(p) { return p.mirrored(plane); } ); - return CSG.fromPolygons(newpolygons); - // TODO: also mirror properties + return this.transform(CSG.Matrix4x4.mirroring(plane)); }, mirroredX: function() { @@ -269,7 +275,7 @@ CSG.prototype = { }, toString: function() { - var result = ""; + var result = "CSG solid:\n"; this.polygons.map(function(p){ result += p.toString(); }); return result; }, @@ -422,8 +428,15 @@ CSG.prototype = { return result; }, - connectTo: function(myConnector, otherConnector, mirror, axisrotation) { - var matrix = myConnector.getTransformationTo(otherConnector, mirror, axisrotation); + // Connect a solid to another solid, such that two CSG.Connectors become connected + // myConnector: a CSG.Connector of this solid + // otherConnector: a CSG.Connector to which myConnector should be connected + // mirror: false: the 'axis' vectors of the connectors should point in the same direction + // true: the 'axis' vectors of the connectors should point in opposite direction + // normalrotation: degrees of rotation between the 'normal' vectors of the two + // connectors + connectTo: function(myConnector, otherConnector, mirror, normalrotation) { + var matrix = myConnector.getTransformationTo(otherConnector, mirror, normalrotation); return this.transform(matrix); }, @@ -587,8 +600,8 @@ CSG.sphere = function(options) { } var result = CSG.fromPolygons(polygons); result.properties.sphere = new CSG.Properties(); - result.properties.sphere.center = new CSG.Vector3D(c); - result.properties.sphere.facepoint = c.plus(xvector); + result.properties.sphere.center = new CSG.Vector3D(center); + result.properties.sphere.facepoint = center.plus(xvector); return result; }; @@ -737,9 +750,9 @@ CSG.roundedCylinder = function(options) { var ray = zvector.unit(); var axisX = xvector.unit(); result.properties.roundedCylinder = new CSG.Properties(); - result.properties.cylinder.start = new CSG.Connector(p1, ray.negated(), axisX); - result.properties.cylinder.end = new CSG.Connector(p2, ray, axisX); - result.properties.cylinder.facepoint = p1.plus(xvector); + result.properties.roundedCylinder.start = new CSG.Connector(p1, ray.negated(), axisX); + result.properties.roundedCylinder.end = new CSG.Connector(p2, ray, axisX); + result.properties.roundedCylinder.facepoint = p1.plus(xvector); return result; }; @@ -991,6 +1004,12 @@ CSG.Vertex = function(pos) { this.pos = pos; }; +// create from an untyped object with identical property names: +CSG.Vertex.fromObject = function(obj) { + var pos = new CSG.Vector3D(obj.pos); + return new CSG.Vertex(pos); +}; + CSG.Vertex.prototype = { // Return a vertex with all orientation-specific data (e.g. vertex normal) flipped. Called when the // orientation of a polygon is flipped. @@ -1040,6 +1059,13 @@ CSG.Plane = function(normal, w) { this.w = w; }; +// create from an untyped object with identical property names: +CSG.Plane.fromObject = function(obj) { + var normal = new CSG.Vector3D(obj.normal); + var w = parseFloat(obj.w); + return new CSG.Plane(normal, w); +}; + // `CSG.Plane.EPSILON` is the tolerance used by `splitPolygon()` to decide if a // point is on the plane. CSG.Plane.EPSILON = 1e-5; @@ -1343,6 +1369,16 @@ CSG.Polygon = function(vertices, shared, plane) { } }; +// create from an untyped object with identical property names: +CSG.Polygon.fromObject = function(obj) { + var vertices = obj.vertices.map(function(v) { + return CSG.Vertex.fromObject(v); + }); + var shared = null; + var plane = CSG.Plane.fromObject(obj.plane); + return new CSG.Polygon(vertices, shared, plane); +}; + CSG.Polygon.prototype = { // check whether the polygon is convex (it should be, otherwise we will get unexpected results) checkIfConvex: function() { @@ -1466,19 +1502,17 @@ CSG.Polygon.prototype = { return new CSG.Polygon(newvertices, this.shared, newplane); }, - mirrored: function(plane) { - var newvertices = this.vertices.map(function(v) { - var newpos = plane.mirrorPoint(v.pos); - return new CSG.Vertex(newpos); - }); - newvertices.reverse(); - return new CSG.Polygon(newvertices, this.shared); - }, - // Affine transformation of polygon. Returns a new CSG.Polygon transform: function(matrix4x4) { var newvertices = this.vertices.map(function(v) { return v.transform(matrix4x4); } ); var newplane = this.plane.transform(matrix4x4); + var scalefactor = matrix4x4.elements[0] * matrix4x4.elements[5] * matrix4x4.elements[10]; + if(scalefactor < 0) + { + // the transformation includes mirroring. We need to reverse the vertex order + // in order to preserve the inside/outside orientation: + newvertices.reverse(); + } return new CSG.Polygon(newvertices, this.shared, newplane); }, @@ -2155,6 +2189,21 @@ CSG.Matrix4x4.translation = function(v) { return new CSG.Matrix4x4(els); }; +// Create an affine matrix for mirroring into an arbitrary plane: +CSG.Matrix4x4.mirroring = function(plane) { + var nx = plane.normal.x; + var ny = plane.normal.y; + var nz = plane.normal.z; + var w = plane.w; + var els = [ + (1.0-2.0*nx*nx), (-2.0*ny*nx), (-2.0*nz*nx), 0, + (-2.0*nx*ny), (1.0-2.0*ny*ny), (-2.0*nz*ny), 0, + (-2.0*nx*nz), (-2.0*ny*nz), (1.0-2.0*nz*nz), 0, + (-2.0*nx*w), (-2.0*ny*w), (-2.0*nz*w), 1 + ]; + return new CSG.Matrix4x4(els); +}; + // Create an affine matrix for scaling: CSG.Matrix4x4.scaling = function(v) { // parse as CSG.Vector3D, so we can pass an array or a CSG.Vector3D @@ -3372,19 +3421,11 @@ CSG.Connector = function(point, axisvector, normalvector) { this.normalvector = new CSG.Vector3D(normalvector); }; -CSG.Connector.test = function() { - var con1 = new CSG.Connector([1,2,5], [1,1,0], [1, -0.8, 0]); - var con2 = new CSG.Connector([-5,1,-1], [2,3,5], [0,0,1]); - var transform = con1.getTransformationTo(con2, false, 0); - var check = con1.transform(transform); -}; - CSG.Connector.prototype = { normalized: function() { var axisvector = this.axisvector.unit(); // make the normal vector truly normal: var n = this.normalvector.cross(axisvector).unit(); -// var normalvector = n.cross(axisvector); var normalvector = axisvector.cross(n); return new CSG.Connector(this.point, axisvector, normalvector); }, @@ -3396,9 +3437,15 @@ CSG.Connector.prototype = { return new CSG.Connector(point, axisvector, normalvector); }, - getTransformationTo: function(other, mirror, axisrotation) { + // Get the transformation matrix to connect this Connector to another connector + // other: a CSG.Connector to which this connector should be connected + // mirror: false: the 'axis' vectors of the connectors should point in the same direction + // true: the 'axis' vectors of the connectors should point in opposite direction + // normalrotation: degrees of rotation between the 'normal' vectors of the two + // connectors + getTransformationTo: function(other, mirror, normalrotation) { mirror = mirror? true:false; - axisrotation = axisrotation? Number(axisrotation):0; + normalrotation = normalrotation? Number(normalrotation):0; var us = this.normalized(); other = other.normalized(); // shift to the origin: @@ -3425,7 +3472,7 @@ CSG.Connector.prototype = { angle1 = normalsbasis.to2D(usAxesAligned.normalvector).angle(); angle2 = normalsbasis.to2D(other.normalvector).angle(); rotation = 180.0 * (angle2 - angle1) / Math.PI; - rotation += axisrotation; + rotation += normalrotation; transformation = transformation.multiply(normalsbasis.getProjectionMatrix()); transformation = transformation.multiply(CSG.Matrix4x4.rotationZ(rotation)); transformation = transformation.multiply(normalsbasis.getInverseProjectionMatrix()); diff --git a/index.html b/index.html index a684e22..6c19165 100644 --- a/index.html +++ b/index.html @@ -32,130 +32,67 @@ 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; } -#needchrome { - display: none; -} - OpenJsCad

OpenJsCad

-
Please note: OpenJsCad currently only runs reliably on Google Chrome!
- Create an STL file for 3D printing using constructive solid modeling in Javascript. - - - - -
+Create an STL file for 3D printing using constructive solid modeling in Javascript. +

Playground

Try it by entering some code below. Anything you enter will be lost as soon as this page is reloaded; to build your own models you should instead store them in a .jscad file on your computer and use the OpenJsCad parser.

- - -

-
- -
- +
+

About

This is intended to become a Javascript based alternative to OpenSCAD, for 3D solid modeling. CSG model is contructed using Javascript. For example:
-var cube = CSG.cube(); return cube; creates a cube with a radius of 1 and centered at the origin. -The code should always end in a return statement, returning a CSG solid. +
function main() {
+  var cube = CSG.cube(); 
+  return cube;
+}
+creates a cube with a radius of 1 and centered at the origin. +The code should always contain a main() function, returning a CSG solid.

-To build your own modes, create a .jscad file with your javascript code and parse the file using the -OpenJsCad parser. When finished you can generate an .stl file, -ready to be printed on your 3d printer. +To build your own models, create a .jscad file with your javascript code and parse the file using the +OpenJsCad parser. When finished, click on Generate STL and save the result +in an .stl file, ready to be printed on your 3d printer.

License

Copyright (c) 2012 Joost Nieuwenhuijse. Uses CSG.js, original copyright (c) 2011 Evan Wallace, @@ -317,9 +254,129 @@ var csg = cube1.subtract(cube2); var rounded = csg.expand(0.2, 8); +

Using Properties

+The 'property' property of a solid can be used to store metdata for the object, +for example the coordinate of a specific point of interest of the solid. Whenever +the object is transformed (i.e. rotated, scaled or translated), the properties +are transformed with it. So the property will keep pointing to the same point +of interest even after several transformations have been applied to the solid. +

+Properties can have any type, but only the properties of classes supporting +a 'transform' method will actually be transformed. This includes CSG.Vector3D, +CSG.Plane and CSG.Connector. In particular CSG.Connector properties (see below) +can be very useful: these can +be used to attach a solid to another solid at a predetermined location regardless of the +current orientation. +

+It's even possible to include a CSG solid as a property of another solid. This could +be used for example +to define the cutout cylinders to create matching screw holes for an object. Those 'solid properties' +get the same transformations as the owning solid but they will not be visible in the result +of CSG operations such as union(). +

+Other kind of properties (for +example, strings) will still be included in the properties of the transformed +solid, but the properties will not get any transformation when the owning solid is transformed.

+All primitive solids have some predefined properties, such as the center point +of a sphere (TODO: document). +

+The solid resulting from CSG operations (union(), subtract(), intersect()) will get +the merged properties of both source solids. If identically named properties exist, only +one of them will be kept. +
+var cube = CSG.cube({radius: 1.0});
+cube.properties.aCorner = new CSG.Vector3D([1, 1, 1]);
+cube = cube.translate([5, 0, 0]);
+cube = cube.scale(2);
+// cube.properties.aCorner will now point to [12, 2, 2],
+// which is still the same corner point 
+
+// Properties can be stored in arrays; all properties in the array
+// will be transformed if the solid is transformed:
+cube.properties.otherCorners = [
+  new CSG.Vector3D([-1, 1, 1]),
+  new CSG.Vector3D([-1, -1, 1])
+];
+
+// and we can create sub-property objects; these must be of the 
+// CSG.Properties class. All sub properties will be transformed with
+// the solid:
+cube.properties.myProperties = new CSG.Properties();
+cube.properties.myProperties.someProperty = new CSG.Vector3D([-1, -1, -1]);
+
+ +

Connectors

+The CSG.Connector class is intended to facilitate +attaching two solids to each other at a predetermined +location and orientation. +For example suppose we have a CSG solid depicting a servo motor +and a solid of a servo arm: by defining a Connector property for each of them, we +can easily attach the servo arm to the servo motor at the correct position +(i.e. the motor shaft) and orientation (i.e. arm perpendicular to the shaft) +even if we don't know their current position and orientation +in 3D space.

+In other words Connector give us the freedom to rotate and translate objects at will without the need +to keep track of their positions and boundaries. And if a third party library exposes connectors for +its solids, the user of the library does not have to know the actual dimensions or +shapes, only the names of the connector properties. +

+A CSG.Connector consist of 3 properties:
+point: a CSG.Vector3D defining the connection point in 3D space
+axis: a CSG.Vector3D defining the direction vector of the connection +(in the case of the servo motor example it would point in the direction of the shaft)
+normal: a CSG.Vector3D direction vector somewhat perpendicular to axis; this +defines the "12 o'clock" orientation of the connection. +

+When connecting two connectors, the solid is transformed such that the point +properties will be identical, the axis properties will have the same direction +(or opposite direction if mirror == true), and the normals match as much as possible. +

+Connectors can be connected by means of two methods:
+A CSG solid's connectTo() function transforms a solid such that two connectors +become connected.
+Alternatively we can use a connector's getTransformationTo() method to obtain +a transformation matrix which would connect the connectors. This can be used if we +need to apply the same transform to multiple solids. + +
+var cube1 = CSG.cube({radius: 10});
+var cube2 = CSG.cube({radius: 4});
+
+// define a connector on the center of one face of cube1
+// The connector's axis points outwards and its normal points
+// towards the positive z axis:
+cube1.properties.myConnector = new CSG.Connector([10, 0, 0], [1, 0, 0], [0, 0, 1]);
+
+// define a similar connector for cube 2:
+cube2.properties.myConnector = new CSG.Connector([0, -4, 0], [0, -1, 0], [0, 0, 1]);
+
+// do some random transformations on cube 1:
+cube1 = cube1.rotateX(30);
+cube1 = cube1.translate([3.1, 2, 0]);
+
+// Now attach cube2 to cube 1:
+cube2 = cube2.connectTo(
+  cube2.properties.myConnector, 
+  cube1.properties.myConnector, 
+  true,   // mirror 
+  0       // normalrotation
+);
+
+// Or alternatively:
+var matrix = cube2.properties.myConnector.getTransformationTo(
+  cube1.properties.myConnector, 
+  true,   // mirror 
+  0       // normalrotation
+);
+cube2 = cube2.transform(matrix);
+
+var result = cube2.union(cube1);
+
+
+

Determining the bounds of an object

The getBounds() function can be used to retrieve the bounding box of an object. -This can be useful if you want to align two objects to each other. getBounds() returns +getBounds() returns an array with two elements specifying the minimum x,y,z coordinate and the maximum x,y,z coordinate:
diff --git a/openjscad.js b/openjscad.js
index 811af96..f314b8e 100644
--- a/openjscad.js
+++ b/openjscad.js
@@ -205,4 +205,278 @@ OpenJsCad.javaScriptToSolid = function(script) {
     throw new Error("Your javascript code should return a CSG object. Try for example: return CSG.cube();");
   }
   return csg;
+};
+
+// this is a bit of a hack; doesn't properly supports urls that start with '/'
+// but does handle relative urls containing ../
+OpenJsCad.makeAbsoluteUrl = function(url, baseurl) {
+  if(!url.match(/^[a-z]+\:/i))
+  {
+    var basecomps = baseurl.split("/");
+    if(basecomps.length > 0)
+    {
+      basecomps.splice(basecomps.length - 1, 1);
+    }
+    var urlcomps = url.split("/");
+    var comps = basecomps.concat(urlcomps);
+    var comps2 = [];
+    comps.map(function(c) {
+      if(c == "..")
+      {
+        if(comps2.length > 0)
+        {
+          comps2.splice(comps2.length - 1, 1);
+        }
+      }
+      else
+      {
+        comps2.push(c);
+      }
+    });  
+    url = "";
+    for(var i = 0; i < comps2.length; i++)
+    {
+      if(i > 0) url += "/";
+      url += comps2[i];
+    }
+  }
+  return url;
+};
+
+OpenJsCad.isChrome = function()
+{
+  return (navigator.userAgent.search("Chrome") >= 0);
+}
+
+// callback: should be function(error, csg)
+OpenJsCad.javaScriptToSolidASync = function(script, callback) {
+  var baselibraries = [
+    "csg.js",
+    "openjscad.js"
+  ];
+  var baseurl = document.location + "";
+  var workerscript = "";
+  workerscript += script;
+  workerscript += "\n//// END OF USER SUPPLIED SCRIPT\n";
+  workerscript += "var _csg_libraries=" + JSON.stringify(baselibraries)+";\n";
+  workerscript += "var _csg_baseurl=" + JSON.stringify(baseurl)+";\n";
+  workerscript += "var _csg_makeAbsoluteURL=" + OpenJsCad.makeAbsoluteUrl.toString()+";\n";
+  workerscript += "if(typeof(libs) == 'function') _csg_libraries = _csg_libraries.concat(libs());\n";
+  workerscript += "_csg_libraries = _csg_libraries.map(function(l){return _csg_makeAbsoluteURL(l,_csg_baseurl);});\n";
+  //workerscript += "importScripts.call(null, _csg_libraries);\n";
+  workerscript += "_csg_libraries.map(function(l){importScripts(l)});\n";
+  workerscript += "self.addEventListener('message', function(e) {if(e.data && e.data.cmd == 'render'){";
+  workerscript += "  if(typeof(main) != 'function') throw new Error('Your jscad file should contain a function main() which returns a CSG solid.');\n";
+  workerscript += "  var csg = main(); self.postMessage({cmd: 'rendered', csg: csg});";
+  workerscript += "}},false);\n";
+  
+  var blobURL = OpenJsCad.textToBlobUrl(workerscript);
+  
+  if(!window.Worker) throw new Error("Your browser doesn't support Web Workers");
+  var worker = new Worker(blobURL);
+  worker.onmessage = function(e) {
+    if(e.data && e.data.cmd == 'rendered')
+    {
+      var csg = CSG.fromObject(e.data.csg);
+      callback(null, csg);
+    }
+  };
+  worker.onerror = function(e) {
+    var errtxt = "Error in line "+e.lineno+": "+e.message;
+    callback(errtxt, null);
+  };
+  worker.postMessage({
+    cmd: "render"
+  }); // Start the worker.
+  return worker;
+};
+
+OpenJsCad.textToBlobUrl = function(txt) {
+  var bb;
+  if(window.BlobBuilder) bb = new window.BlobBuilder()
+  else if(window.WebKitBlobBuilder) bb = new window.WebKitBlobBuilder()
+  else if(window.MozBlobBuilder) bb = new window.MozBlobBuilder()
+  else throw new Error("Your browser doesn't support BlobBuilder");
+
+  bb.append(txt);
+  var blob = bb.getBlob();
+  var blobURL;
+  if(window.URL) blobURL = window.URL.createObjectURL(blob)
+  else if(window.webkitURL) blobURL = window.webkitURL.createObjectURL(blob)
+  else throw new Error("Your browser doesn't support window.URL");
+  return blobURL;
+};
+
+OpenJsCad.revokeBlobUrl = function(url) {
+  if(window.URL) window.URL.revokeObjectURL(url)
+  else if(window.webkitURL) window.webkitURL.revokeObjectURL(url)
+  else throw new Error("Your browser doesn't support window.URL");
+};
+
+OpenJsCad.Processor = function(containerdiv, onchange) {
+  this.containerdiv = containerdiv;
+  this.onchange = onchange;
+  this.viewerdiv = null;
+  this.viewer = null;
+  this.viewerwidth = 800;
+  this.viewerheight = 600;
+  this.initialViewerDistance = 50;
+  this.processing = false;
+  this.solid = null;
+  this.validcsg = false;
+  this.hasstl = false;
+  this.worker = null;
+  this.createElements();
+};
+
+OpenJsCad.Processor.prototype = {
+  createElements: function() {
+    while(this.containerdiv.children.length > 0)
+    {
+      this.containerdiv.removeChild(0);
+    }
+    if(!OpenJsCad.isChrome() )
+    {
+      var div = document.createElement("div");
+      div.innerHTML = "Please note: OpenJsCad currently only runs reliably on Google Chrome!";
+      this.containerdiv.appendChild(div);
+    }
+    var viewerdiv = document.createElement("div");
+    viewerdiv.className = "viewer";
+    viewerdiv.style.width = this.viewerwidth + "px";
+    viewerdiv.style.height = this.viewerheight + "px";
+    viewerdiv.style.backgroundColor = "rgb(200,200,200)";
+    this.containerdiv.appendChild(viewerdiv);
+    this.viewerdiv = viewerdiv;
+    try
+    {
+      this.viewer = new OpenJsCad.Viewer(this.viewerdiv, this.viewerwidth, this.viewerheight, this.initialViewerDistance);
+    } catch (e) {
+      this.viewerdiv.innerHTML = e.toString();
+    }
+    this.errordiv = document.createElement("div");
+    this.errordiv.style.display = "none";
+    this.statusdiv = document.createElement("div");
+    this.statusdiv.style.width = this.viewerwidth + "px";
+    this.statusspan = document.createElement("span");
+    this.statusbuttons = document.createElement("div");
+    this.statusbuttons.style.float = "right";
+    this.statusdiv.appendChild(this.statusspan);
+    this.statusdiv.appendChild(this.statusbuttons);
+    this.abortbutton = document.createElement("button");
+    this.abortbutton.innerHTML = "Abort";
+    var that = this;
+    this.abortbutton.onclick = function(e) {
+      that.abort();
+    }
+    this.statusbuttons.appendChild(this.abortbutton);
+    this.generateStlButton = document.createElement("button");
+    this.generateStlButton.innerHTML = "Generate STL";
+    this.generateStlButton.onclick = function(e) {
+      that.generateStl();
+    }
+    this.statusbuttons.appendChild(this.generateStlButton);
+    this.downloadStlLink = document.createElement("a");
+    this.downloadStlLink.innerHTML = "Download STL";
+    this.statusbuttons.appendChild(this.downloadStlLink);
+    this.enableItems();    
+    this.containerdiv.appendChild(this.statusdiv);
+    this.containerdiv.appendChild(this.errordiv);
+    this.clearViewer();
+  },
+  
+  clearViewer: function() {
+    this.clearStl();
+    this.solid = new CSG();
+    if(this.viewer)
+    {
+      this.viewer.setCsg(this.solid);
+    }
+    this.validcsg = false;
+    this.enableItems();
+  },
+  
+  abort: function() {
+    if(this.processing)
+    {
+      //todo: abort
+      this.processing=false;
+      this.statusspan.innerHTML = "Aborted.";
+      this.worker.terminate();
+      this.enableItems();
+      if(this.onchange) this.onchange();
+    }
+  },
+  
+  enableItems: function() {
+    this.abortbutton.style.display = this.processing? "inline":"none";
+    this.generateStlButton.style.display = ((!this.hasstl)&&(this.validcsg))? "inline":"none";
+    this.downloadStlLink.style.display = this.hasstl? "inline":"none";
+  },
+  
+  setError: function(txt) {
+    this.errordiv.innerHTML = txt;
+    this.errordiv.style.display = (txt == "")? "none":"block";    
+  },
+  
+  setJsCad: function(script) {
+    this.abort();
+    this.clearViewer();
+    this.setError("");
+    this.processing = true;
+    this.statusspan.innerHTML = "Processing, please wait...";
+    var that = this;
+    this.worker = OpenJsCad.javaScriptToSolidASync(script, function(err, csg) {
+      that.processing = false;
+      that.worker = null;
+      if(err)
+      {
+        that.setError(err);
+        that.statusspan.innerHTML = "Error.";
+      }
+      else
+      {
+        that.solid = csg;      
+        if(that.viewer) that.viewer.setCsg(csg);
+        that.validcsg = true;
+        that.statusspan.innerHTML = "Ready.";
+      }
+      that.enableItems();
+      if(that.onchange) that.onchange();
+    });
+    this.enableItems();
+    if(this.onchange) this.onchange();
+  },
+  
+  hasSolid: function() {
+    return this.validcsg;
+  },
+
+  isProcessing: function() {
+    return this.processing;
+  },
+  
+  clearStl: function() {
+    if(this.hasstl)
+    {
+      this.hasstl = false;
+      OpenJsCad.revokeBlobUrl(this.stlBlobUrl);
+      this.stlBlobUrl = null;
+      this.enableItems();
+      if(this.onchange) this.onchange();
+    }
+  },
+  
+  generateStl: function() {
+    this.clearStl();
+    if(this.validcsg)
+    {
+      var stltxt = this.solid.toStlString();
+      this.stlBlobUrl = OpenJsCad.textToBlobUrl(stltxt);
+      this.hasstl = true;
+      this.downloadStlLink.href = this.stlBlobUrl;
+      this.enableItems();
+      if(this.onchange) this.onchange();
+    }
+  },
 };
\ No newline at end of file
diff --git a/processfile.html b/processfile.html
index 3d60155..f0c7ebb 100644
--- a/processfile.html
+++ b/processfile.html
@@ -8,9 +8,6 @@
 
 body {
   font: 14px/20px 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, Arial, sans-serif;
-  max-width: 600px;
-  margin: 0 auto;
-  padding: 10px;
 }
 
 pre, code, textarea {
@@ -25,9 +22,6 @@ pre, textarea {
   padding: 10px;
   width: 100%;
 }
-textarea {
-  height: 200px;
-}
 textarea:focus {
   outline: none;
 }
@@ -55,90 +49,27 @@ textarea:focus {
 }
 
 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; }
-#errdiv {
-  border: 2px solid red;
-  display: none;
-  margin: 5px;
-}
 
-#needchrome {
-  display: none;
-}
+canvas { cursor: move; }
 
   
 
 OpenJsCad parser 
 
 

OpenJsCad parser

-
Please note: OpenJsCad currently only runs reliably on Google Chrome!
-
+

-
Drop your .jscad file here
@@ -297,23 +193,23 @@ function getStl()
-

Instructions:

Create a new file in your favorite text editor. To get started enter the following text:
-
-var cube = CSG.roundedCube({radius: 10, roundradius: 2, resolution: 16});
-var sphere = CSG.sphere({radius: 10, resolution: 16}).translate([5, 5, 5]);
-return cube.union(sphere);
+
function main() {
+  var cube = CSG.roundedCube({radius: 10, roundradius: 2, resolution: 16});
+  var sphere = CSG.sphere({radius: 10, resolution: 16}).translate([5, 5, 5]);
+  return cube.union(sphere);
+}
 
Save this to a file on your desktop with .jscad extension. Then drag & drop the file from your desktop to the box above (marked with 'drop your .jscad file here).

The 3d model should now appear. You can continue to make changes to your .jscad file. Just press Reload to parse the changes, you do not need to drag & drop the file again.

-When finished press Get STL to generate the STL file. Copy & paste the STL text to a file with -.stl extension. +When finished press Generate STL to generate the STL file. Then click Save STL and save it to +a file with .stl extension.

For more information about OpenJsCad see the introduction. From 8b51bd6b6879f09a0e4a80b3959a29be01058e00 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Mon, 13 Feb 2012 18:19:37 +0100 Subject: [PATCH 25/48] Now uses FileSystem API for downloading the STL file --- index.html | 6 ++- openjscad.js | 114 +++++++++++++++++++++++++++++++++++++++++++++-- processfile.html | 95 +++++++++++++++++++-------------------- 3 files changed, 161 insertions(+), 54 deletions(-) diff --git a/index.html b/index.html index 6c19165..848d61f 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,7 @@ body { font: 14px/20px 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, Arial, sans-serif; - max-width: 800px; + max-width: 820px; margin: 0 auto; padding: 10px; } @@ -36,8 +36,12 @@ canvas { cursor: move; } + + + + + + +OpenJsCad demo: involute gears + + +

OpenJsCad demo: involute gears

+
+

Source code

+Below is the OpenJsCad script for this demo. To build your own models, create a .jscad script +and use the OpenJsCad parser. For more information see the +OpenJsCad documentation. +

+
+ +

+ + \ No newline at end of file diff --git a/index.html b/index.html index 848d61f..62a0eeb 100644 --- a/index.html +++ b/index.html @@ -35,6 +35,8 @@ textarea:focus { canvas { cursor: move; } + + - -canvas { cursor: move; } + - + + + + + + +OpenJsCad demo: Parametric S hook + + +

OpenJsCad demo: Parametric S hook

+
+

Source code

+Below is the OpenJsCad script for this demo. To build your own models, create a .jscad script +and use the OpenJsCad parser. For more information see the +OpenJsCad documentation. +

+
+ +

+ + \ No newline at end of file diff --git a/index.html b/index.html index 56f5e5f..63d0055 100644 --- a/index.html +++ b/index.html @@ -115,7 +115,10 @@ Click and drag to rotate the model around the origin.
Shift+Drag moves the model around.
Alt+drag zooms (by changing the distance between camera and model).

Demos

-Try the Gears demo! +

License

Copyright (c) 2012 Joost Nieuwenhuijse. Uses CSG.js, original copyright (c) 2011 Evan Wallace, @@ -435,6 +438,7 @@ var extruded=shape2d.extrude({ twiststeps: 100 // create 100 slices });
+For an example of 2D shapes see the Parametric S hook demo.

2D Paths

A path is simply a series of points, connected by lines. A path can be open or closed (an additional line @@ -553,6 +557,7 @@ function main(params) { return result; }
+Or see the Gears demo for another example of interactive parameters. \ No newline at end of file From 5f147dee594640a4545e1ba8def114a40d16595f Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Thu, 16 Feb 2012 13:22:56 +0100 Subject: [PATCH 32/48] Speed optimizations, added setColor() method, added math documentation --- csg.js | 46 +++++++++++++++--- index.html | 134 +++++++++++++++++++++++++++++++++++++++++++++++++-- openjscad.js | 97 +++++++++++++++++++++++-------------- 3 files changed, 231 insertions(+), 46 deletions(-) diff --git a/csg.js b/csg.js index 24c5790..6fd47ec 100644 --- a/csg.js +++ b/csg.js @@ -105,7 +105,9 @@ CSG.fromObject = function(obj) { var polygons = obj.polygons.map( function(p) { return CSG.Polygon.fromObject(p); }); - return CSG.fromPolygons(polygons); + var csg = CSG.fromPolygons(polygons); + csg = csg.canonicalized(); + return csg; }; CSG.prototype = { @@ -234,6 +236,7 @@ CSG.prototype = { var newpolygons = this.polygons.map(function(p) { return p.transform(matrix4x4); } ); var result=CSG.fromPolygons(newpolygons); result.properties = this.properties._transform(matrix4x4); + result.isRetesselated = this.isRetesselated; return result; }, @@ -324,6 +327,7 @@ CSG.prototype = { var factory = new CSG.fuzzyCSGFactory(); var result = factory.getCSG(this); result.isCanonicalized = true; + result.isRetesselated = this.isRetesselated; result.properties = this.properties; // keep original properties return result; } @@ -336,10 +340,13 @@ CSG.prototype = { } else { - var csg=this; //.canonicalized(); + var csg=this.canonicalized(); var polygonsPerPlane = {}; csg.polygons.map(function(polygon) { var planetag = polygon.plane.getTag(); + var sharedtag = null; + if(polygon.shared !== undefined) sharedtag = polygon.shared; + planetag += "/"+JSON.stringify(sharedtag); if(! (planetag in polygonsPerPlane) ) { polygonsPerPlane[planetag] = []; @@ -363,6 +370,7 @@ CSG.prototype = { } var result = CSG.fromPolygons(destpolygons); result.isRetesselated = true; + result.isCanonicalized = true; result.properties = this.properties; // keep original properties return result; } @@ -447,7 +455,27 @@ CSG.prototype = { connectTo: function(myConnector, otherConnector, mirror, normalrotation) { var matrix = myConnector.getTransformationTo(otherConnector, mirror, normalrotation); return this.transform(matrix); + }, + + // set the .shared property of all polygons + // Returns a new CSG solid, the original is unmodified! + setShared: function(shared) { + var polygons = this.polygons.map( function(p) { + return new CSG.Polygon(p.vertices, shared, p.plane); + }); + var result = CSG.fromPolygons(polygons); + result.properties = this.properties; // keep original properties + result.isRetesselated = this.isRetesselated; + result.isCanonicalized = this.isCanonicalized; + return result; }, + + setColor: function(red,green,blue) { + var newshared = { + color: [red, green, blue], + }; + return this.setShared(newshared); + }, }; @@ -1337,7 +1365,7 @@ CSG.Plane.prototype = { return result; }, - // returns CSG.Point3D + // returns CSG.Vector3D intersectWithLine: function(line3d) { return line3d.intersectWithPlane(this); }, @@ -1403,7 +1431,7 @@ CSG.Polygon.fromObject = function(obj) { var vertices = obj.vertices.map(function(v) { return CSG.Vertex.fromObject(v); }); - var shared = null; + var shared = obj.shared; var plane = CSG.Plane.fromObject(obj.plane); return new CSG.Polygon(vertices, shared, plane); }; @@ -1439,7 +1467,7 @@ CSG.Polygon.prototype = { sidefacepoints.push(polygon2.vertices[i].pos); sidefacepoints.push(polygon2.vertices[nexti].pos); sidefacepoints.push(polygon1.vertices[nexti].pos); - var sidefacepolygon=CSG.Polygon.createFromPoints(sidefacepoints); + var sidefacepolygon=CSG.Polygon.createFromPoints(sidefacepoints, this.shared); newpolygons.push(sidefacepolygon); } polygon2 = polygon2.flipped(); @@ -2435,6 +2463,9 @@ CSG.Polygon2D.prototype = { var offsetvector = CSG.parseOptionAs3DVector(options, "offset", [0,0,1]); var twistangle = CSG.parseOptionAsFloat(options, "twistangle", 0); var twiststeps = CSG.parseOptionAsInt(options, "twiststeps", 10); + + if(twistangle == 0) twiststeps = 1; + if(twiststeps < 1) twiststeps = 1; // create the polygons: var newpolygons = []; @@ -2784,6 +2815,7 @@ CSG.reTesselateCoplanarPolygons = function(sourcepolygons, destpolygons) if(numpolygons > 0) { var plane = sourcepolygons[0].plane; + var shared = sourcepolygons[0].shared; var orthobasis = new CSG.OrthoNormalBasis(plane); var polygonvertices2d = []; // array of array of CSG.Vector2D var polygontopvertexindexes = []; // array of indexes of topmost vertex per polygon @@ -3108,7 +3140,6 @@ CSG.reTesselateCoplanarPolygons = function(sourcepolygons, destpolygons) var vertex3d = new CSG.Vertex(point3d); vertices3d.push(vertex3d); }); - var shared = null; var polygon = new CSG.Polygon(vertices3d, shared, plane); destpolygons.push(polygon); } @@ -3661,8 +3692,9 @@ CSG.Path2D.prototype = { var offsetvector = [0, 0, height]; polygon2ds.map(function(polygon) { var csg = polygon.extrude({offset: offsetvector}); - result = result.union(csg); + result = result.unionSub(csg, false, false); }); + result = result.reTesselated().canonicalized(); return result; }, diff --git a/index.html b/index.html index 63d0055..7c0a019 100644 --- a/index.html +++ b/index.html @@ -105,10 +105,11 @@ Some of the benefits:
  • Runs in your browser, no need to install any software.
  • You can create parametric models with user editable parameters: parameters can be changed in the browser window, without the need to edit the source script. See the Gears demo for example!
  • -
  • JavaScript is an extremely flexible language, supporting dynamic arrays and object oriented programming.
  • +
  • JavaScript is an extremely flexible language, supporting dynamic arrays, object oriented programming, closures, anonymous functions and more
  • Solids are stored in variables. This allows for example conditional cloning of objects, something which is nearly impossible in OpenSCAD.
  • Properties and Connectors (see below) make it very easy to attach objects to each other at predetermined points, even if you don't know the actual orientation or size.
  • +
  • Extensive built in support for 2D and 3D math (classes for Vector2D, Vector3D, Plane, Line3D, Line2D)
  • Viewer navigation

    Click and drag to rotate the model around the origin.
    @@ -191,7 +192,8 @@ var csg3 = cube.subtract(sphere);

    Transformations

    -Solids can be translated, scaled and rotated. Multiple transforms can be combined into a single matrix transform: +Solids can be translated, scaled and rotated. Multiple transforms can be combined into a single matrix transform. +For matrix and vector math see below.
     var cube = CSG.cube();
     
    @@ -219,7 +221,7 @@ var cube3 = cube.transform(m);
     

    Mirroring

    -Solids can be mirrored in any plane in 3D space: +Solids can be mirrored in any plane in 3D space. For plane math see below.
     var cube = CSG.cube().translate([1,0,0]);
     
    @@ -559,5 +561,129 @@ function main(params) {
     
    Or see the Gears demo for another example of interactive parameters. +

    Miscellaneous

    +Solids can be given a color using the setColor(r, g, b) function. Beware: this returns a new solid, +the original solid is not modified! Faces of the solid will keep their original color when doing +CSG operations (union() etc). Colors values range between 0.0 and 1.0 (not 255). +
    +var cube1 = CSG.cube({radius: 10});
    +cube1 = cube1.setColor(0.5, 0, 0);
    +
    +var cube2 = CSG.cube({radius: 10});
    +cube2 = cube2.setColor(0, 0.5, 0);
    +cube2 = cube2.translate([5,1,4]);
    +
    +var result = cube1.subtract(cube2);
    +// the resulting solid will have faces with 2 different colors
    +
    + +

    2D and 3D Math

    +There are utility classes for many 2D and 3D operations. Below is a quick summary, for details +view the source of csg.js: +
    +// --------- Vector3D ---------------------
    +var vec1 = new CSG.Vector3D(1,2,3);       // 3 arguments
    +var vec2 = new CSG.Vector3D( [1,2,3] );   // 1 array argument
    +var vec3 = new CSG.Vector3D(vec2);        // cloning a vector
    +// vector math. All operations return a new vector, the original is unmodified!
    +vec.negated()
    +vec.abs()
    +vec.plus(othervector)
    +vec.minus(othervector)
    +vec.times(3.0)
    +vec.dividedBy(-5)
    +vec.dot(othervector)
    +vec.lerp(othervector, t)  // linear interpolation (0 <= t <= 1)
    +vec.length()
    +vec.lengthSquared()       // == vec.length()^2
    +vec.unit()
    +vec.cross(othervector)    // cross product: returns a vector perpendicular to both
    +vec.distanceTo(othervector)
    +vec.distanceToSquared(othervector)  // == vec.distanceTo(othervector)^2
    +vec.equals(othervector)
    +vec.multiply4x4(matrix4x4)   // right multiply by a 4x4 matrix
    +
    +// --------- Vector2D ---------------------
    +var vec1 = new CSG.Vector2D(1,2);       // 2 arguments
    +var vec2 = new CSG.Vector2D( [1,2] );   // 1 array argument
    +var vec3 = new CSG.Vector2D(vec2);      // cloning a vector
    +// vector math. All operations return a new vector, the original is unmodified!
    +vec.negated()
    +vec.abs()
    +vec.plus(othervector)
    +vec.minus(othervector)
    +vec.times(3.0)
    +vec.dividedBy(-5)
    +vec.dot(othervector)
    +vec.lerp(othervector, t)  // linear interpolation (0 <= t <= 1)
    +vec.length()
    +vec.unit()
    +vec.normal()              // returns a 90 degree clockwise rotated vector
    +vec.distanceTo(othervector)
    +vec.equals(othervector)
    +vec.multiply4x4(matrix4x4)   // right multiply by a 4x4 matrix
    +vec.toVector3D(z)         // convert to a vector3D by adding a z coordinate
    +vec.angleDegrees()        // returns the angle of the vector: [1,0] = 0 degrees, [0, 1] = 90 degrees, etc
    +vec.angleRadians()        // ditto in radians
    +var vec = CSG.Vector2D.fromAngleDegrees(degrees);  // returns a vector at the specified angle
    +var vec = CSG.Vector2D.fromAngleRadians(radians);  // returns a vector at the specified angle
    +
    +// --------- Matrix4x4 ---------------------
    +var m1 = new CSG.Matrix4x4();          // unity matrix
    +var m2 = new CSG.Matrix4x4( [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] );
    +  // elements are passed in row order
    +var result = m1.plus(m2); 
    +var result = m1.minus(m2);
    +var result = m1.multiply(m2);
    +// matrix vector multiplication (vectors are padded with zeroes to get a 4x1 vector):
    +var vec3d = m1.rightMultiply1x3Vector(vec3d);  // matrix * vector 
    +var vec3d = m1.leftMultiply1x3Vector(vec3d);   // vector * matrix
    +var vec2d = m1.rightMultiply1x2Vector(vec2d);  // matrix * vector 
    +var vec2d = m1.leftMultiply1x2Vector(vec2d);   // vector * matrix
    +// common transformation matrices:
    +var m = CSG.Matrix4x4.rotationX(degrees);      // matrix for rotation about X axis
    +var m = CSG.Matrix4x4.rotationY(degrees);      // matrix for rotation about Y axis
    +var m = CSG.Matrix4x4.rotationZ(degrees);      // matrix for rotation about Z axis
    +var m = CSG.Matrix4x4.translation(vec3d);      // translation
    +var m = CSG.Matrix4x4.scaling(vec3d);          // scale
    +var m = CSG.Matrix4x4.mirroring(plane);        // mirroring in a plane; the argument must be a CSG.Plane
    +// matrix transformations can be concatenated:
    +var transform = CSG.Matrix4x4.rotationX(20).multiply(CSG.Matrix4x4.rotationY(30));
    +// Use a CSG solid's transform() method to apply the transformation to a CSG solid
    +
    +// ------------ Plane --------------------------
    +// a 3D plane is represented by a normal vector (should have unit length) and a distance from the origin w 
    +// the plane passes through normal.times(w)
    +var plane1 = new CSG.Plane(normal, w);         
    +// Or we can construct a plane from 3 points:
    +var plane2 = CSG.Plane.fromPoints(p1, p2, p3);
    +// Or from a normal vector and 1 point:
    +var plane3 = CSG.Plane.fromNormalAndPoint(normal, point);
    +// Flip a plane (front side becomes back side):
    +var plane4 = plane3.flipped();
    +// Apply transformations (rotation, scaling, translation):
    +var transformed = plane3.transformed(matrix4x4);  // argument is a CSG.Matrix4x4
    +// Intersection of plane and 3d line:
    +var point = plane3.intersectWithLine(line);        // argument is CSG.Line3D, returns a CSG.Vector3D
    +// Intersection of 2 planes:
    +var line = plane3.intersectWithPlane(plane);       // argument is another CSG.Plane, returns a CSG.Line3D
    +// Distance to point:
    +var w = signedDistanceToPoint(point);             // argument is CSG.Vector3D, returns a float (positive
    +                                                  //    if in front of plane, negative if in back)
    +
    +// ------------ Line3D --------------------------
    +// A line in 3d space is represented by a point and a direction vector.
    +// Direction should be a unit vector. Point can be any point on the line:
    +var line = new CSG.Line3D(point, direction);      // argumenst are CSG.Vector3D
    +// or by giving two points:
    +var line = CSG.Line3D.fromPoints(p1, p2);         // argumenst are CSG.Vector3D
    +var point = intersectWithPlane(plane);            // == plane.intersectWithLine(this)
    +var line2 = line.reverse();                       // same line but reverse direction
    +var line2 = line.transform(matrix4x4);            // for rotation, scaling, etc
    +var p = line.closestPointOnLine(point);           // project point onto the line
    +var d = line.distanceToPoint(point);
    +
    + + - \ No newline at end of file + diff --git a/openjscad.js b/openjscad.js index be371b5..df0b737 100644 --- a/openjscad.js +++ b/openjscad.js @@ -169,6 +169,11 @@ OpenJsCad.Viewer.csgToMesh = function(csg) { for(var polygonindex = 0; polygonindex < numpolygons; polygonindex++) { var polygon = polygons[polygonindex]; + var color = [0,0,1]; + if(polygon.shared && polygon.shared.color) + { + color = polygon.shared.color; + } var indices = polygon.vertices.map(function(vertex) { var vertextag = vertex.getTag(); var vertexindex; @@ -181,7 +186,7 @@ OpenJsCad.Viewer.csgToMesh = function(csg) { vertexindex = vertices.length; vertexTag2Index[vertextag] = vertexindex; vertices.push([vertex.pos.x, vertex.pos.y, vertex.pos.z]); - colors.push([0,0,1]); + colors.push(color); } return vertexindex; }); @@ -254,22 +259,29 @@ OpenJsCad.runMainInWorker = function(mainParameters) } catch(e) { - var errtxt = e.stack; + var errtxt = e.stack; + if(!errtxt) + { + errtxt = e.toString(); + } self.postMessage({cmd: 'error', err: errtxt}); } }; -OpenJsCad.javaScriptToSolidSync = function(script, mainParameters, callback) { +OpenJsCad.javaScriptToSolidSync = function(script, mainParameters, debugging) { var workerscript = ""; workerscript += script; - workerscript += "\n\n\n\n\n\n\n/* -------------------------------------------------------------------------\n"; - workerscript += "OpenJsCad debugging\n\nAssuming you are running Chrome:\nF10 steps over an instruction\nF11 steps into an instruction\n"; - workerscript += "F8 continues running\nPress the (||) button at the bottom to enable pausing whenever an error occurs\n"; - workerscript += "Click on a line number to set or clear a breakpoint\n"; - workerscript += "For more information see: http://code.google.com/chrome/devtools/docs/overview.html\n\n"; - workerscript += "------------------------------------------------------------------------- */\n"; - workerscript += "\n\n// Now press F11 twice to enter your main() function:\n\n"; - workerscript += "debugger;\n"; + if(debugging) + { + workerscript += "\n\n\n\n\n\n\n/* -------------------------------------------------------------------------\n"; + workerscript += "OpenJsCad debugging\n\nAssuming you are running Chrome:\nF10 steps over an instruction\nF11 steps into an instruction\n"; + workerscript += "F8 continues running\nPress the (||) button at the bottom to enable pausing whenever an error occurs\n"; + workerscript += "Click on a line number to set or clear a breakpoint\n"; + workerscript += "For more information see: http://code.google.com/chrome/devtools/docs/overview.html\n\n"; + workerscript += "------------------------------------------------------------------------- */\n"; + workerscript += "\n\n// Now press F11 twice to enter your main() function:\n\n"; + workerscript += "debugger;\n"; + } workerscript += "return main("+JSON.stringify(mainParameters)+");"; var f = new Function(workerscript); var csg = f(); @@ -341,6 +353,7 @@ OpenJsCad.textToBlobUrl = function(txt) { if(window.URL) blobURL = window.URL.createObjectURL(blob) else if(window.webkitURL) blobURL = window.webkitURL.createObjectURL(blob) else throw new Error("Your browser doesn't support window.URL"); + if(!blobURL) throw new Error("createObjectURL() failed"); return blobURL; }; @@ -638,11 +651,41 @@ OpenJsCad.Processor.prototype = { this.enableItems(); var that = this; var paramValues = this.getParamValues(); - if(this.debugging) + var useSync = this.debugging; + if(!useSync) { try { - var csg = OpenJsCad.javaScriptToSolidSync(this.script, paramValues); + this.worker = OpenJsCad.javaScriptToSolidASync(this.script, paramValues, function(err, csg) { + that.processing = false; + that.worker = null; + if(err) + { + that.setError(err); + that.statusspan.innerHTML = "Error."; + } + else + { + that.solid = csg; + if(that.viewer) that.viewer.setCsg(csg); + that.validcsg = true; + that.statusspan.innerHTML = "Ready."; + } + that.enableItems(); + if(that.onchange) that.onchange(); + }); + } + catch(e) + { + useSync = true; + } + } + + if(useSync) + { + try + { + var csg = OpenJsCad.javaScriptToSolidSync(this.script, paramValues, this.debugging); that.processing = false; that.solid = csg; if(that.viewer) that.viewer.setCsg(csg); @@ -652,33 +695,17 @@ OpenJsCad.Processor.prototype = { catch(e) { that.processing = false; - that.setError(e.toString()); + var errtxt = e.stack; + if(!errtxt) + { + errtxt = e.toString(); + } + that.setError(errtxt); that.statusspan.innerHTML = "Error."; } that.enableItems(); if(that.onchange) that.onchange(); } - else - { - this.worker = OpenJsCad.javaScriptToSolidASync(this.script, paramValues, function(err, csg) { - that.processing = false; - that.worker = null; - if(err) - { - that.setError(err); - that.statusspan.innerHTML = "Error."; - } - else - { - that.solid = csg; - if(that.viewer) that.viewer.setCsg(csg); - that.validcsg = true; - that.statusspan.innerHTML = "Ready."; - } - that.enableItems(); - if(that.onchange) that.onchange(); - }); - } }, hasSolid: function() { From 8c3aaac16de726562723903c7a5d975b4cea22b2 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Thu, 16 Feb 2012 13:27:29 +0100 Subject: [PATCH 33/48] text change --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 7c0a019..f273aa0 100644 --- a/index.html +++ b/index.html @@ -129,7 +129,7 @@ All code released under MIT license.

    Contributions are welcome! It's all written in Javascript, so if you know how to use it you know how to modify it as well.

    -To contribute go to CSG.js at GitHub, +To contribute go to OpenJsCad at GitHub (gh-pages tree), create your own fork and send me a pull request.

    Primitive solids

    From fb5cf382074ecf640f0312935a158e247e2d9412 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Thu, 16 Feb 2012 14:21:11 +0100 Subject: [PATCH 34/48] Now allows STL download on Firefox as well; better error reporting if browser lacks WebGL support --- csg.js | 1 + index.html | 4 +- openjscad.js | 275 ++++++++++++++++++++++++++------------------------- 3 files changed, 146 insertions(+), 134 deletions(-) diff --git a/csg.js b/csg.js index 6fd47ec..06804ac 100644 --- a/csg.js +++ b/csg.js @@ -773,6 +773,7 @@ CSG.roundedCylinder = function(options) { for(var slice2 = 0; slice2 <= qresolution; slice2++) { var pitch = 0.5 * Math.PI * slice2 / qresolution; + //var pitch = Math.asin(slice2/qresolution); var cospitch = Math.cos(pitch); var sinpitch = Math.sin(pitch); if(slice2 > 0) diff --git a/index.html b/index.html index f273aa0..193446e 100644 --- a/index.html +++ b/index.html @@ -110,6 +110,8 @@ without the need to edit the source script. See the Gea
  • Properties and Connectors (see below) make it very easy to attach objects to each other at predetermined points, even if you don't know the actual orientation or size.
  • Extensive built in support for 2D and 3D math (classes for Vector2D, Vector3D, Plane, Line3D, Line2D)
  • +
  • Debugging support: step through your code, set breakpoints, inspect variables, etc. See the + OpenJsCad parser for details.
  • Viewer navigation

    Click and drag to rotate the model around the origin.
    @@ -278,7 +280,7 @@ var rounded = csg.expand(0.2, 8);

    Using Properties

    -The 'property' property of a solid can be used to store metdata for the object, +The 'property' property of a solid can be used to store metadata for the object, for example the coordinate of a specific point of interest of the solid. Whenever the object is transformed (i.e. rotated, scaled or translated), the properties are transformed with it. So the property will keep pointing to the same point diff --git a/openjscad.js b/openjscad.js index df0b737..69a55d7 100644 --- a/openjscad.js +++ b/openjscad.js @@ -4,87 +4,81 @@ OpenJsCad = function() { // A viewer is a WebGL canvas that lets the user view a mesh. The user can // tumble it around by dragging the mouse. OpenJsCad.Viewer = function(containerelement, width, height, initialdepth) { - try - { - var gl = GL.create(); - this.gl = gl; - this.angleX = 0; - this.angleY = 0; - this.viewpointX = 0; - this.viewpointY = 0; - this.viewpointZ = initialdepth; + var gl = GL.create(); + this.gl = gl; + this.angleX = 0; + this.angleY = 0; + this.viewpointX = 0; + this.viewpointY = 0; + this.viewpointZ = initialdepth; - // Draw triangle lines: - this.drawLines = false; - // Set to true so lines don't use the depth buffer - this.lineOverlay = false; - - // 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.5, 1000); - 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);\ - }\ - '); + // Draw triangle lines: + this.drawLines = false; + // Set to true so lines don't use the depth buffer + this.lineOverlay = false; - containerelement.appendChild(gl.canvas); - - var _this=this; + // 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.5, 1000); + gl.matrixMode(gl.MODELVIEW); - gl.onmousemove = function(e) { - _this.onMouseMove(e); - }; - gl.ondraw = function() { - _this.onDraw(); - }; - this.clear(); - } - catch (e) { - containerelement.innerHTML = "

    Error: "+e.toString()+"


    OpenJsCad currently requires Google Chrome with WebGL enabled"; - } + // 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);\ + }\ + '); + + containerelement.appendChild(gl.canvas); + + var _this=this; + + gl.onmousemove = function(e) { + _this.onMouseMove(e); + }; + gl.ondraw = function() { + _this.onDraw(); + }; + this.clear(); }; OpenJsCad.Viewer.prototype = { @@ -470,7 +464,9 @@ OpenJsCad.Processor.prototype = { { this.viewer = new OpenJsCad.Viewer(this.viewerdiv, this.viewerwidth, this.viewerheight, this.initialViewerDistance); } catch (e) { - this.viewerdiv.innerHTML = e.toString(); +// this.viewer = null; + this.viewerdiv.innerHTML = "

    Error: "+e.toString()+"


    OpenJsCad currently requires Google Chrome with WebGL enabled"; +// this.viewerdiv.innerHTML = e.toString(); } this.errordiv = document.createElement("div"); this.errorpre = document.createElement("pre"); @@ -728,18 +724,6 @@ OpenJsCad.Processor.prototype = { } }, - generateStl1: function() { - this.clearStl(); - if(this.validcsg) - { - var stltxt = this.solid.toStlString(); - this.stlBlobUrl = OpenJsCad.textToBlobUrl(stltxt); - this.hasstl = true; - this.downloadStlLink.href = this.stlBlobUrl; - this.enableItems(); - if(this.onchange) this.onchange(); - } - }, */ clearStl: function() { @@ -751,63 +735,88 @@ OpenJsCad.Processor.prototype = { this.stlDirEntry.removeRecursively(function(){}); this.stlDirEntry=null; } + if(this.stlBlobUrl) + { + OpenJsCad.revokeBlobUrl(this.stlBlobUrl); + this.stlBlobUrl = null; + } this.enableItems(); if(this.onchange) this.onchange(); } }, - + generateStl: function() { this.clearStl(); if(this.validcsg) { - var stltxt = this.solid.toStlString(); - window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; - window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; - if(!window.requestFileSystem) + try { - throw new Error("Your browser does not support the HTML5 FileSystem API. Please try the Chrome browser instead."); + this.generateStlFileSystem(); } - if(!window.BlobBuilder) + catch(e) { - throw new Error("Your browser does not support the HTML5 BlobBuilder API. Please try the Chrome browser instead."); + this.generateStlBlobUrl(); } - // create a random directory name: - var dirname = "OpenJsCadStlOutput1_"+parseInt(Math.random()*1000000000, 10)+".stl"; - var filename = this.filename+".stl"; - var that = this; - window.requestFileSystem(TEMPORARY, 20*1024*1024, function(fs){ - fs.root.getDirectory(dirname, {create: true, exclusive: true}, function(dirEntry) { - that.stlDirEntry = dirEntry; - dirEntry.getFile(filename, {create: true, exclusive: true}, function(fileEntry) { - fileEntry.createWriter(function(fileWriter) { - fileWriter.onwriteend = function(e) { - that.hasstl = true; - that.downloadStlLink.href = fileEntry.toURL(); - that.enableItems(); - if(that.onchange) that.onchange(); - }; - fileWriter.onerror = function(e) { - throw new Error('Write failed: ' + e.toString()); - }; - // Create a new Blob and write it to log.txt. - var bb = new window.BlobBuilder(); // Note: window.WebKitBlobBuilder in Chrome 12. - bb.append(stltxt); - fileWriter.write(bb.getBlob()); - }, - function(fileerror){OpenJsCad.FileSystemApiErrorHandler(fileerror, "createWriter");} - ); - }, - function(fileerror){OpenJsCad.FileSystemApiErrorHandler(fileerror, "getFile('"+filename+"')");} - ); - }, - function(fileerror){OpenJsCad.FileSystemApiErrorHandler(fileerror, "getDirectory('"+dirname+"')");} - ); - }, - function(fileerror){OpenJsCad.FileSystemApiErrorHandler(fileerror, "requestFileSystem");} - ); } }, + generateStlBlobUrl: function() { + var stltxt = this.solid.toStlString(); + this.stlBlobUrl = OpenJsCad.textToBlobUrl(stltxt); + this.hasstl = true; + this.downloadStlLink.href = this.stlBlobUrl; + this.enableItems(); + if(this.onchange) this.onchange(); + }, + + generateStlFileSystem: function() { + var stltxt = this.solid.toStlString(); + window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; + window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; + if(!window.requestFileSystem) + { + throw new Error("Your browser does not support the HTML5 FileSystem API. Please try the Chrome browser instead."); + } + if(!window.BlobBuilder) + { + throw new Error("Your browser does not support the HTML5 BlobBuilder API. Please try the Chrome browser instead."); + } + // create a random directory name: + var dirname = "OpenJsCadStlOutput1_"+parseInt(Math.random()*1000000000, 10)+".stl"; + var filename = this.filename+".stl"; + var that = this; + window.requestFileSystem(TEMPORARY, 20*1024*1024, function(fs){ + fs.root.getDirectory(dirname, {create: true, exclusive: true}, function(dirEntry) { + that.stlDirEntry = dirEntry; + dirEntry.getFile(filename, {create: true, exclusive: true}, function(fileEntry) { + fileEntry.createWriter(function(fileWriter) { + fileWriter.onwriteend = function(e) { + that.hasstl = true; + that.downloadStlLink.href = fileEntry.toURL(); + that.enableItems(); + if(that.onchange) that.onchange(); + }; + fileWriter.onerror = function(e) { + throw new Error('Write failed: ' + e.toString()); + }; + // Create a new Blob and write it to log.txt. + var bb = new window.BlobBuilder(); // Note: window.WebKitBlobBuilder in Chrome 12. + bb.append(stltxt); + fileWriter.write(bb.getBlob()); + }, + function(fileerror){OpenJsCad.FileSystemApiErrorHandler(fileerror, "createWriter");} + ); + }, + function(fileerror){OpenJsCad.FileSystemApiErrorHandler(fileerror, "getFile('"+filename+"')");} + ); + }, + function(fileerror){OpenJsCad.FileSystemApiErrorHandler(fileerror, "getDirectory('"+dirname+"')");} + ); + }, + function(fileerror){OpenJsCad.FileSystemApiErrorHandler(fileerror, "requestFileSystem");} + ); + }, + createParamControls: function() { this.parameterstable.innerHTML = ""; this.paramControls = []; From fcefd5a257bed698b8cc0d30c3bbab761001120c Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Thu, 16 Feb 2012 18:13:55 +0100 Subject: [PATCH 35/48] Added servo motor demo --- index.html | 5 +- servodemo.html | 227 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 servodemo.html diff --git a/index.html b/index.html index 193446e..e76d696 100644 --- a/index.html +++ b/index.html @@ -120,7 +120,8 @@ Alt+drag zooms (by changing the distance between camera and model).

    Demos

    License

    Copyright (c) 2012 Joost Nieuwenhuijse. @@ -329,6 +330,7 @@ cube.properties.otherCorners = [ cube.properties.myProperties = new CSG.Properties(); cube.properties.myProperties.someProperty = new CSG.Vector3D([-1, -1, -1]); +For an example see the Servo motor demo.

    Connectors

    The CSG.Connector class is intended to facilitate @@ -398,6 +400,7 @@ cube2 = cube2.transform(matrix); var result = cube2.union(cube1); +For a more complete example see the Servo motor demo.

    Determining the bounds of an object

    The getBounds() function can be used to retrieve the bounding box of an object. diff --git a/servodemo.html b/servodemo.html new file mode 100644 index 0000000..876745e --- /dev/null +++ b/servodemo.html @@ -0,0 +1,227 @@ + + + + + + + + + + +OpenJsCad demo: servo motor + + +

    OpenJsCad demo: servo motor

    +
    +

    Source code

    +Below is the OpenJsCad script for this demo. To build your own models, create a .jscad script +and use the OpenJsCad parser. For more information see the +OpenJsCad documentation. +

    +
    + +

    + + \ No newline at end of file From 54787e2ed6f33ae3db6aeac476248b581a5fae39 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Tue, 21 Feb 2012 14:00:04 +0100 Subject: [PATCH 36/48] Speed optimizations, added OpenJsCad.log() function --- csg.js | 590 +++++++++++++++++++++++++++++++++++++-------------- openjscad.js | 38 +++- 2 files changed, 461 insertions(+), 167 deletions(-) diff --git a/csg.js b/csg.js index 06804ac..2496b29 100644 --- a/csg.js +++ b/csg.js @@ -91,12 +91,16 @@ for solid CAD anyway. CSG = function() { this.polygons = []; this.properties = new CSG.Properties(); + this.isCanonicalized = true; + this.isRetesselated = true; }; // Construct a CSG solid from a list of `CSG.Polygon` instances. CSG.fromPolygons = function(polygons) { var csg = new CSG(); csg.polygons = polygons; + csg.isCanonicalized = false; + csg.isRetesselated = false; return csg; }; @@ -110,6 +114,67 @@ CSG.fromObject = function(obj) { return csg; }; +// Reconstruct a CSG from the output of toCompactBinary() +CSG.fromCompactBinary = function(bin) { + var planes = []; + var planeData = bin.planeData; + var numplanes = planeData.length / 4; + var arrayindex = 0; + for(var planeindex = 0; planeindex < numplanes; planeindex++) + { + var x = planeData[arrayindex++]; + var y = planeData[arrayindex++]; + var z = planeData[arrayindex++]; + var w = planeData[arrayindex++]; + var normal = new CSG.Vector3D(x,y,z); + var plane = new CSG.Plane(normal, w); + planes.push(plane); + } + + var vertices = []; + var vertexData = bin.vertexData; + var numvertices = vertexData.length / 3; + arrayindex = 0; + for(var vertexindex = 0; vertexindex < numvertices; vertexindex++) + { + var x = vertexData[arrayindex++]; + var y = vertexData[arrayindex++]; + var z = vertexData[arrayindex++]; + var pos = new CSG.Vector3D(x,y,z); + var vertex = new CSG.Vertex(pos); + vertices.push(vertex); + } + + var shareds = bin.shared.map(function(shared){ + return CSG.Polygon.Shared.fromObject(shared); + }); + + var polygons = []; + var numpolygons = bin.numPolygons; + var numVerticesPerPolygon = bin.numVerticesPerPolygon; + var polygonVertices = bin.polygonVertices; + var polygonPlaneIndexes = bin.polygonPlaneIndexes; + var polygonSharedIndexes = bin.polygonSharedIndexes; + arrayindex = 0; + for(var polygonindex = 0; polygonindex < numpolygons; polygonindex++) + { + var numpolygonvertices = numVerticesPerPolygon[polygonindex]; + var polygonvertices = []; + for(var i = 0; i < numpolygonvertices; i++) + { + polygonvertices.push(vertices[polygonVertices[arrayindex++]]); + } + var plane = planes[polygonPlaneIndexes[polygonindex]]; + var shared = shareds[polygonSharedIndexes[polygonindex]]; + var polygon = new CSG.Polygon(polygonvertices, shared, plane); + polygons.push(polygon); + } + var csg = CSG.fromPolygons(polygons); + csg.isCanonicalized = true; + csg.isRetesselated = true; + return csg; +}; + CSG.prototype = { toPolygons: function() { return this.polygons; @@ -134,16 +199,23 @@ CSG.prototype = { }, unionSub: function(csg, retesselate, canonicalize) { - var a = new CSG.Tree(this.polygons); - var b = new CSG.Tree(csg.polygons); - a.clipTo(b, false); - b.clipTo(a, true); - var newpolygons = a.allPolygons().concat(b.allPolygons()); - var result = CSG.fromPolygons(newpolygons); - result.properties = this.properties._merge(csg.properties); - if(canonicalize) result = result.canonicalized(); - if(retesselate) result = result.reTesselated(); - return result; + if(! this.mayOverlap(csg)) + { + return this.unionForNonIntersecting(csg); + } + else + { + var a = new CSG.Tree(this.polygons); + var b = new CSG.Tree(csg.polygons); + a.clipTo(b, false); + b.clipTo(a, true); + var newpolygons = a.allPolygons().concat(b.allPolygons()); + var result = CSG.fromPolygons(newpolygons); + result.properties = this.properties._merge(csg.properties); + if(canonicalize) result = result.canonicalized(); + if(retesselate) result = result.reTesselated(); + return result; + } }, // Like union, but when we know that the two solids are not intersecting @@ -152,6 +224,8 @@ CSG.prototype = { var newpolygons = this.polygons.concat(csg.polygons); var result = CSG.fromPolygons(newpolygons); result.properties = this.properties._merge(csg.properties); + result.isCanonicalized = this.isCanonicalized && csg.isCanonicalized; + result.isRetesselated = this.isRetesselated && csg.isRetesselated; return result; }, @@ -232,7 +306,7 @@ CSG.prototype = { }, // Affine transformation of CSG object. Returns a new CSG object - transform: function(matrix4x4) { + transform1: function(matrix4x4) { var newpolygons = this.polygons.map(function(p) { return p.transform(matrix4x4); } ); var result=CSG.fromPolygons(newpolygons); result.properties = this.properties._transform(matrix4x4); @@ -240,6 +314,48 @@ CSG.prototype = { return result; }, + transform: function(matrix4x4) { + var scalefactor = matrix4x4.elements[0] * matrix4x4.elements[5] * matrix4x4.elements[10]; + var ismirror = (scalefactor < 0); + var transformedvertices = {}; + var transformedplanes = {}; + var newpolygons = this.polygons.map(function(p) { + var newplane; + var plane = p.plane; + var planetag = plane.getTag(); + if(planetag in transformedplanes) + { + newplane = transformedplanes[planetag]; + } + else + { + newplane = plane.transform(matrix4x4); + transformedplanes[planetag] = newplane; + } + var newvertices = p.vertices.map(function(v) { + var newvertex; + var vertextag = v.getTag(); + if(vertextag in transformedvertices) + { + newvertex = transformedvertices[vertextag]; + } + else + { + newvertex = v.transform(matrix4x4); + transformedvertices[vertextag] = newvertex; + } + return newvertex; + }); + if(ismirror) newvertices.reverse(); + return new CSG.Polygon(newvertices, p.shared, newplane); + }); + var result=CSG.fromPolygons(newpolygons); + result.properties = this.properties._transform(matrix4x4); + result.isRetesselated = this.isRetesselated; + result.isCanonicalized = this.isCanonicalized; + return result; + }, + mirrored: function(plane) { return this.transform(CSG.Matrix4x4.mirroring(plane)); }, @@ -344,9 +460,8 @@ CSG.prototype = { var polygonsPerPlane = {}; csg.polygons.map(function(polygon) { var planetag = polygon.plane.getTag(); - var sharedtag = null; - if(polygon.shared !== undefined) sharedtag = polygon.shared; - planetag += "/"+JSON.stringify(sharedtag); + var sharedtag = polygon.shared.getTag(); + planetag += "/"+sharedtag; if(! (planetag in polygonsPerPlane) ) { polygonsPerPlane[planetag] = []; @@ -408,6 +523,27 @@ CSG.prototype = { return this.cachedBoundingBox; }, + // returns true if there is a possibility that the two solids overlap + // returns false if we can be sure that they do not overlap + mayOverlap: function(csg) { + if( (this.polygons.length == 0) || (csg.polygons.length == 0) ) + { + return false; + } + else + { + var mybounds = this.getBounds(); + var otherbounds = csg.getBounds(); + if(mybounds[1].x < otherbounds[0].x) return false; + if(mybounds[0].x > otherbounds[1].x) return false; + if(mybounds[1].y < otherbounds[0].y) return false; + if(mybounds[0].y > otherbounds[1].y) return false; + if(mybounds[1].z < otherbounds[0].z) return false; + if(mybounds[0].z > otherbounds[1].z) return false; + return true; + } + }, + // Cut the solid by a plane. Returns the solid on the back side of the plane cutByPlane: function(plane) { // Ideally we would like to do an intersection with a polygon of inifinite size @@ -471,12 +607,99 @@ CSG.prototype = { }, setColor: function(red,green,blue) { - var newshared = { - color: [red, green, blue], - }; + var newshared = new CSG.Polygon.Shared([red, green, blue]); return this.setShared(newshared); }, - + + toCompactBinary: function() { + var csg = this.canonicalized(); + var numpolygons = csg.polygons.length; + var numpolygonvertices = 0; + var numvertices = 0; + var vertexmap = {}; + var vertices = []; + var numplanes = 0; + var planemap = {}; + var polygonindex = 0; + var planes = []; + var shareds = []; + var sharedmap = {}; + var numshared = 0; + csg.polygons.map(function(p){ + p.vertices.map(function(v){ + ++numpolygonvertices; + var vertextag = v.getTag(); + if(! (vertextag in vertexmap)) + { + vertexmap[vertextag] = numvertices++; + vertices.push(v); + } + }); + var planetag = p.plane.getTag(); + if(! (planetag in planemap)) + { + planemap[planetag] = numplanes++; + planes.push(p.plane); + } + var sharedtag = p.shared.getTag(); + if(! (sharedtag in sharedmap)) + { + sharedmap[sharedtag] = numshared++; + shareds.push(p.shared); + } + }); + var numVerticesPerPolygon = new Uint32Array(numpolygons); + var polygonSharedIndexes = new Uint32Array(numpolygons); + var polygonVertices = new Uint32Array(numpolygonvertices); + var polygonPlaneIndexes = new Uint32Array(numpolygons); + var vertexData = new Float64Array(numvertices * 3); + var planeData = new Float64Array(numplanes * 4); + var polygonVerticesIndex = 0; + for(var polygonindex = 0; polygonindex < numpolygons; ++polygonindex) + { + var p = csg.polygons[polygonindex]; + numVerticesPerPolygon[polygonindex] = p.vertices.length; + p.vertices.map(function(v){ + var vertextag = v.getTag(); + var vertexindex = vertexmap[vertextag]; + polygonVertices[polygonVerticesIndex++] = vertexindex; + }); + var planetag = p.plane.getTag(); + var planeindex = planemap[planetag]; + polygonPlaneIndexes[polygonindex] = planeindex; + var sharedtag = p.shared.getTag(); + var sharedindex = sharedmap[sharedtag]; + polygonSharedIndexes[polygonindex] = sharedindex; + } + var verticesArrayIndex = 0; + vertices.map(function(v){ + var pos = v.pos; + vertexData[verticesArrayIndex++] = pos.x; + vertexData[verticesArrayIndex++] = pos.y; + vertexData[verticesArrayIndex++] = pos.z; + }); + var planesArrayIndex = 0; + planes.map(function(p){ + var normal = p.normal; + planeData[planesArrayIndex++] = normal.x; + planeData[planesArrayIndex++] = normal.y; + planeData[planesArrayIndex++] = normal.z; + planeData[planesArrayIndex++] = p.w; + }); + var result = { + numPolygons: numpolygons, + numVerticesPerPolygon: numVerticesPerPolygon, + polygonPlaneIndexes: polygonPlaneIndexes, + polygonSharedIndexes: polygonSharedIndexes, + polygonVertices: polygonVertices, + vertexData: vertexData, + planeData: planeData, + shared: shareds, + }; + return result; + }, + + }; // Parse an option from the options object @@ -1230,136 +1453,116 @@ CSG.Plane.prototype = { { var EPS = CSG.Plane.EPSILON; var thisw = this.w; - // first check if the polygon's bounding sphere is completely in front or in back: - var bound = polygon.boundingSphere(); - var spherecenter = bound[0]; - var sphereradius = bound[1]; - sphereradius += EPS; - //var d = this.signedDistanceToPoint(spherecenter); - var d = planenormal.dot(spherecenter) - thisw; - - if(d > sphereradius) + var hasfront = false; + var hasback = false; + var vertexIsBack = []; + var MINEPS = -EPS; + for (var i = 0; i < numvertices; i++) { + var t = planenormal.dot(vertices[i].pos) - thisw; + var isback = (t < 0); + vertexIsBack.push(isback); + if(t > EPS) hasfront = true; + if(t < MINEPS) hasback = true; + } + if( (!hasfront) && (!hasback) ) + { + // all points coplanar + var t = planenormal.dot(polygon.plane.normal); + result.type = (t >= 0)? 0:1; + } + else if(!hasback) { result.type = 2; } - else if(d < -sphereradius) + else if(!hasfront) { result.type = 3; } else { - // no, we really have to check each vertex separately: - var hasfront = false; - var hasback = false; - var vertexIsBack = []; - var MINEPS = -EPS; - for (var i = 0; i < numvertices; i++) { - var t = planenormal.dot(vertices[i].pos) - thisw; - var isback = (t < 0); - vertexIsBack.push(isback); - if(t > EPS) hasfront = true; - if(t < MINEPS) hasback = true; - } - if( (!hasfront) && (!hasback) ) + // spanning + result.type = 4; + var frontvertices = [], backvertices = []; + var isback = vertexIsBack[0]; + for(var vertexindex = 0; vertexindex < numvertices; vertexindex++) { - // all points coplanar - var t = planenormal.dot(polygon.plane.normal); - result.type = (t >= 0)? 0:1; - } - else if(!hasback) - { - result.type = 2; - } - else if(!hasfront) - { - result.type = 3; - } - else - { - // spanning - result.type = 4; - var frontvertices = [], backvertices = []; - var isback = vertexIsBack[0]; - for(var vertexindex = 0; vertexindex < numvertices; vertexindex++) + var vertex = vertices[vertexindex]; + var nextvertexindex = vertexindex + 1; + if(nextvertexindex >= numvertices) nextvertexindex = 0; + var nextisback = vertexIsBack[nextvertexindex]; + if(isback == nextisback) { - var vertex = vertices[vertexindex]; - var nextvertexindex = vertexindex + 1; - if(nextvertexindex >= numvertices) nextvertexindex = 0; - var nextisback = vertexIsBack[nextvertexindex]; - if(isback == nextisback) + // line segment is on one side of the plane: + if(isback) { - // line segment is on one side of the plane: - if(isback) - { - backvertices.push(vertex); - } - else - { - frontvertices.push(vertex); - } + backvertices.push(vertex); } else { - // line segment intersects plane: - var point = vertex.pos; - var nextpoint = vertices[nextvertexindex].pos; - var line = CSG.Line3D.fromPoints(point, nextpoint); - var intersectionpoint = this.intersectWithLine(line); - var intersectionvertex = new CSG.Vertex(intersectionpoint); - if(isback) - { - backvertices.push(vertex); - backvertices.push(intersectionvertex); - frontvertices.push(intersectionvertex); - } - else - { - frontvertices.push(vertex); - frontvertices.push(intersectionvertex); - backvertices.push(intersectionvertex); - } + frontvertices.push(vertex); + } + } + else + { + // line segment intersects plane: + var point = vertex.pos; + var nextpoint = vertices[nextvertexindex].pos; + var line = CSG.Line3D.fromPoints(point, nextpoint); + var intersectionpoint = this.intersectWithLine(line); + var intersectionvertex = new CSG.Vertex(intersectionpoint); + if(isback) + { + backvertices.push(vertex); + backvertices.push(intersectionvertex); + frontvertices.push(intersectionvertex); } - isback = nextisback; - } // for vertexindex - - // remove duplicate vertices: - var EPS_SQUARED = CSG.Plane.EPSILON * CSG.Plane.EPSILON; - if(backvertices.length >= 3) - { - var prevvertex = backvertices[backvertices.length - 1]; - for(var vertexindex = 0; vertexindex < backvertices.length; vertexindex++) + else { - var vertex = backvertices[vertexindex]; - if(vertex.pos.distanceToSquared(prevvertex.pos) < EPS_SQUARED) - { - backvertices.splice(vertexindex,1); - vertexindex--; - } - prevvertex = vertex; - } + frontvertices.push(vertex); + frontvertices.push(intersectionvertex); + backvertices.push(intersectionvertex); + } } - if(frontvertices.length >= 3) + isback = nextisback; + } // for vertexindex + + // remove duplicate vertices: + var EPS_SQUARED = CSG.Plane.EPSILON * CSG.Plane.EPSILON; + if(backvertices.length >= 3) + { + var prevvertex = backvertices[backvertices.length - 1]; + for(var vertexindex = 0; vertexindex < backvertices.length; vertexindex++) { - var prevvertex = frontvertices[frontvertices.length - 1]; - for(var vertexindex = 0; vertexindex < frontvertices.length; vertexindex++) + var vertex = backvertices[vertexindex]; + if(vertex.pos.distanceToSquared(prevvertex.pos) < EPS_SQUARED) { - var vertex = frontvertices[vertexindex]; - if(vertex.pos.distanceToSquared(prevvertex.pos) < EPS_SQUARED) - { - frontvertices.splice(vertexindex,1); - vertexindex--; - } - prevvertex = vertex; - } - } - if (frontvertices.length >= 3) + backvertices.splice(vertexindex,1); + vertexindex--; + } + prevvertex = vertex; + } + } + if(frontvertices.length >= 3) + { + var prevvertex = frontvertices[frontvertices.length - 1]; + for(var vertexindex = 0; vertexindex < frontvertices.length; vertexindex++) { - result.front = new CSG.Polygon(frontvertices, polygon.shared, polygon.plane); - } - if (backvertices.length >= 3) - { - result.back = new CSG.Polygon(backvertices, polygon.shared, polygon.plane); - } + var vertex = frontvertices[vertexindex]; + if(vertex.pos.distanceToSquared(prevvertex.pos) < EPS_SQUARED) + { + frontvertices.splice(vertexindex,1); + vertexindex--; + } + prevvertex = vertex; + } + } + if (frontvertices.length >= 3) + { + result.front = new CSG.Polygon(frontvertices, polygon.shared, polygon.plane); + } + if (backvertices.length >= 3) + { + result.back = new CSG.Polygon(backvertices, polygon.shared, polygon.plane); } } } @@ -1409,6 +1612,7 @@ CSG.Plane.prototype = { // passed as the third argument CSG.Polygon = function(vertices, shared, plane) { this.vertices = vertices; + if(!shared) shared = CSG.Polygon.defaultShared; this.shared = shared; var numvertices = vertices.length; @@ -1432,7 +1636,7 @@ CSG.Polygon.fromObject = function(obj) { var vertices = obj.vertices.map(function(v) { return CSG.Vertex.fromObject(v); }); - var shared = obj.shared; + var shared = CSG.Polygon.Shared.fromObject(obj.shared); var plane = CSG.Plane.fromObject(obj.plane); return new CSG.Polygon(vertices, shared, plane); }; @@ -1667,6 +1871,37 @@ CSG.Polygon.isStrictlyConvexPoint = function(prevpoint, point, nextpoint, normal return (crossdotnormal >= 1e-5); }; +// # class CSG.Polygon.Shared + +// Holds the shared properties for each polygon (currently only color) + +CSG.Polygon.Shared = function(color) { + this.color = color; +}; + +CSG.Polygon.Shared.fromObject = function(obj) { + return new CSG.Polygon.Shared(obj.color); +}; + +CSG.Polygon.Shared.prototype = { + getTag: function() { + var result = this.tag; + if(!result) + { + result = CSG.getTag(); + this.tag = result; + } + return result; + }, + // get a string uniquely identifying this object + getHash: function() { + if(!this.color) return "null"; + return ""+this.color[0]+"/"+this.color[1]+"/"+this.color[2]; + }, +}; + +CSG.Polygon.defaultShared = new CSG.Polygon.Shared(null); + // # class PolygonTreeNode // This class manages hierarchical splits of polygons @@ -1781,39 +2016,57 @@ CSG.PolygonTreeNode.prototype = { else { // no children. Split the polygon: - if(this.polygon) + var polygon = this.polygon; + if(polygon) { - var splitresult = plane.splitPolygon(this.polygon); - switch(splitresult.type) + var bound = polygon.boundingSphere(); + var sphereradius = bound[1] + 1e-4; +// var d = plane.normal.dot(bound[0]) - plane.w; + var planenormal = plane.normal; + var spherecenter = bound[0]; + var d = planenormal.x*spherecenter.x + planenormal.y*spherecenter.y + planenormal.z*spherecenter.z - plane.w; + if(d > sphereradius) { - case 0: // coplanar front: - coplanarfrontnodes.push(this); - break; - - case 1: // coplanar back: - coplanarbacknodes.push(this); - break; - - case 2: // front: - frontnodes.push(this); - break; - - case 3: // back: - backnodes.push(this); - break; - - case 4: // spanning: - if(splitresult.front) - { - var frontnode = this.addChild(splitresult.front); - frontnodes.push(frontnode); - } - if(splitresult.back) - { - var backnode = this.addChild(splitresult.back); - backnodes.push(backnode); - } - break; + frontnodes.push(this); + } + else if(d < -sphereradius) + { + backnodes.push(this); + } + else + { + var splitresult = plane.splitPolygon(polygon); + switch(splitresult.type) + { + case 0: // coplanar front: + coplanarfrontnodes.push(this); + break; + + case 1: // coplanar back: + coplanarbacknodes.push(this); + break; + + case 2: // front: + frontnodes.push(this); + break; + + case 3: // back: + backnodes.push(this); + break; + + case 4: // spanning: + if(splitresult.front) + { + var frontnode = this.addChild(splitresult.front); + frontnodes.push(frontnode); + } + if(splitresult.back) + { + var backnode = this.addChild(splitresult.back); + backnodes.push(backnode); + } + break; + } } } } @@ -3338,9 +3591,23 @@ CSG.fuzzyFactory.joinSets = function(set1, set2) { CSG.fuzzyCSGFactory = function() { this.vertexfactory = new CSG.fuzzyFactory(3, 1e-5); this.planefactory = new CSG.fuzzyFactory(4, 1e-5); + this.polygonsharedfactory = {}; }; CSG.fuzzyCSGFactory.prototype = { + getPolygonShared: function(sourceshared) { + var hash = sourceshared.getHash(); + if(hash in this.polygonsharedfactory) + { + return this.polygonsharedfactory[hash]; + } + else + { + this.polygonsharedfactory[hash] = sourceshared; + return sourceshared; + } + }, + getVertex: function(sourcevertex) { var elements = [sourcevertex.pos.x, sourcevertex.pos.y, sourcevertex.pos.z]; var result = this.vertexfactory.lookupOrCreate(elements, function(els) { @@ -3359,11 +3626,12 @@ CSG.fuzzyCSGFactory.prototype = { getPolygon: function(sourcepolygon) { var newplane = this.getPlane(sourcepolygon.plane); + var newshared = this.getPolygonShared(sourcepolygon.shared); var _this = this; var newvertices = sourcepolygon.vertices.map(function(vertex) { return _this.getVertex(vertex); }); - return new CSG.Polygon(newvertices, sourcepolygon.shared, newplane); + return new CSG.Polygon(newvertices, newshared, newplane); }, getCSG: function(sourcecsg) { diff --git a/openjscad.js b/openjscad.js index 69a55d7..49ff188 100644 --- a/openjscad.js +++ b/openjscad.js @@ -1,6 +1,25 @@ OpenJsCad = function() { }; +OpenJsCad.log = function(txt) { + var timeInMs = Date.now(); + var prevtime = OpenJsCad.log.prevLogTime; + if(!prevtime) prevtime = timeInMs; + var deltatime = timeInMs - prevtime; + OpenJsCad.log.prevLogTime = timeInMs; + var timefmt = (deltatime*0.001).toFixed(3); + txt = "["+timefmt+"] "+txt; + if( (typeof(console) == "object") && (typeof(console.log) == "function") ) + { + console.log(txt); + } + else if( (typeof(self) == "object") && (typeof(self.postMessage) == "function") ) + { + self.postMessage({cmd: 'log', txt: txt}); + } + else throw new Error("Cannot log"); +}; + // A viewer is a WebGL canvas that lets the user view a mesh. The user can // tumble it around by dragging the mouse. OpenJsCad.Viewer = function(containerelement, width, height, initialdepth) { @@ -243,13 +262,16 @@ OpenJsCad.runMainInWorker = function(mainParameters) { try { - if(typeof(main) != 'function') throw new Error('Your jscad file should contain a function main() which returns a CSG solid.'); + if(typeof(main) != 'function') throw new Error('Your jscad file should contain a function main() which returns a CSG solid.'); + OpenJsCad.log.prevLogTime = Date.now(); var csg = main(mainParameters); if( (typeof(csg) != "object") || (!(csg instanceof CSG)) ) { throw new Error("Your main() function should return a CSG solid."); } - self.postMessage({cmd: 'rendered', csg: csg}); + var csg_bin = csg.toCompactBinary(); + csg = null; // not needed anymore + self.postMessage({cmd: 'rendered', csg: csg_bin}); } catch(e) { @@ -278,6 +300,7 @@ OpenJsCad.javaScriptToSolidSync = function(script, mainParameters, debugging) { } workerscript += "return main("+JSON.stringify(mainParameters)+");"; var f = new Function(workerscript); + OpenJsCad.log.prevLogTime = Date.now(); var csg = f(); return csg; }; @@ -314,7 +337,8 @@ OpenJsCad.javaScriptToSolidASync = function(script, mainParameters, callback) { { if(e.data.cmd == 'rendered') { - var csg = CSG.fromObject(e.data.csg); + //var csg = CSG.fromObject(e.data.csg); + var csg = CSG.fromCompactBinary(e.data.csg); callback(null, csg); } else if(e.data.cmd == "error") @@ -322,6 +346,10 @@ OpenJsCad.javaScriptToSolidASync = function(script, mainParameters, callback) { // var errtxt = "Error in line "+e.data.err.lineno+": "+e.data.err.message; callback(e.data.err, null); } + else if(e.data.cmd == "log") + { + console.log(e.data.txt); + } } }; worker.onerror = function(e) { @@ -923,6 +951,4 @@ OpenJsCad.Processor.prototype = { }); this.paramControls = paramControls; }, - - -}; \ No newline at end of file +}; From 533322a8275dcdddae26b2cf7c94675da8c2344d Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Tue, 21 Feb 2012 14:50:48 +0100 Subject: [PATCH 37/48] Speed optimizations --- csg.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/csg.js b/csg.js index 2496b29..76b1e99 100644 --- a/csg.js +++ b/csg.js @@ -412,11 +412,19 @@ CSG.prototype = { // resolution: number of points per 360 degree for the rounded corners expand: function(radius, resolution) { var result=this; + var count = 0; this.polygons.map(function(p) { var expanded=p.expand(radius, resolution); - result=result.unionSub(expanded, true, false); + result=result.unionSub(expanded, false, false); + count++; + if(count == 30) + { + result = result.reTesselated(); + count = 0; + } }); - result = result.canonicalized(); +// result = result.canonicalized(); + result = result.reTesselated(); result.properties = this.properties; // keep original properties return result; }, From e91a2f9de3c3e67781ad0dbbb310fa8b0afba315 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Tue, 21 Feb 2012 15:42:27 +0100 Subject: [PATCH 38/48] speed optimizations --- csg.js | 70 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/csg.js b/csg.js index 76b1e99..7b7b60d 100644 --- a/csg.js +++ b/csg.js @@ -417,7 +417,7 @@ CSG.prototype = { var expanded=p.expand(radius, resolution); result=result.unionSub(expanded, false, false); count++; - if(count == 30) + if(count == 300) { result = result.reTesselated(); count = 0; @@ -1716,7 +1716,7 @@ CSG.Polygon.prototype = { var extrudevector=this.plane.normal.unit().times(2*radius); var translatedpolygon = this.translate(extrudevector.times(-0.5)); var extrudedface = translatedpolygon.extrude(extrudevector); - result=result.unionSub(extrudedface, true, false); + result=result.unionSub(extrudedface, false, false); return result; }, @@ -2126,7 +2126,7 @@ CSG.PolygonTreeNode.prototype = { // The actual tree is kept in this.rootnode CSG.Tree = function(polygons) { this.polygonTree = new CSG.PolygonTreeNode(); - this.rootnode = new CSG.Node(); + this.rootnode = new CSG.Node(null); if (polygons) this.addPolygons(polygons); }; @@ -2151,15 +2151,11 @@ CSG.Tree.prototype = { addPolygons: function(polygons) { var _this = this; - polygons.map(function(p) { - _this.addPolygon(p); + var polygontreenodes = polygons.map(function(p) { + return _this.polygonTree.addChild(p); }); + this.rootnode.addPolygonTreeNodes(polygontreenodes); }, - - addPolygon: function(polygon) { - var polygontreenode=this.polygonTree.addChild(polygon); - this.rootnode.addPolygonTreeNode(polygontreenode); - }, }; // # class Node @@ -2172,11 +2168,12 @@ CSG.Tree.prototype = { // This is not a leafy BSP tree since there is // no distinction between internal and leaf nodes. -CSG.Node = function() { +CSG.Node = function(parent) { this.plane = null; this.front = null; this.back = null; this.polygontreenodes = []; + this.parent = parent; }; CSG.Node.prototype = { @@ -2239,23 +2236,60 @@ CSG.Node.prototype = { if (this.back) this.back.clipTo(tree, alsoRemovecoplanarFront); }, - addPolygonTreeNode: function(polygontreenode) { + addPolygonTreeNodes: function(polygontreenodes) { + if(polygontreenodes.length == 0) return; + var _this = this; if(!this.plane) { - this.plane = polygontreenode.getPolygon().plane; + var bestplane = polygontreenodes[0].getPolygon().plane; +/* + var parentnormals = []; + this.getParentPlaneNormals(parentnormals, 6); +//parentnormals = []; + var numparentnormals = parentnormals.length; + var minmaxnormal = 1.0; + polygontreenodes.map(function(polygontreenode){ + var plane = polygontreenodes[0].getPolygon().plane; + var planenormal = plane.normal; + var maxnormaldot = -1.0; + parentnormals.map(function(parentnormal){ + var dot = parentnormal.dot(planenormal); + if(dot > maxnormaldot) maxnormaldot = dot; + }); + if(maxnormaldot < minmaxnormal) + { + minmaxnormal = maxnormaldot; + bestplane = plane; + } + }); +*/ + this.plane = bestplane; } var frontnodes = []; var backnodes = []; - polygontreenode.splitByPlane(this.plane, this.polygontreenodes, this.polygontreenodes, frontnodes, backnodes); + polygontreenodes.map(function(polygontreenode){ + polygontreenode.splitByPlane(_this.plane, _this.polygontreenodes, _this.polygontreenodes, frontnodes, backnodes); + }); if(frontnodes.length > 0) { - if (!this.front) this.front = new CSG.Node(); - this.front.addPolygonTreeNode(frontnodes[0]); + if (!this.front) this.front = new CSG.Node(this); + this.front.addPolygonTreeNodes(frontnodes); } if(backnodes.length > 0) { - if (!this.back) this.back = new CSG.Node(); - this.back.addPolygonTreeNode(backnodes[0]); + if (!this.back) this.back = new CSG.Node(this); + this.back.addPolygonTreeNodes(backnodes); + } + }, + + getParentPlaneNormals: function(normals, maxdepth) { + if(maxdepth > 0) + { + if(this.parent) + { + normals.push(this.parent.plane.normal); + this.parent.getParentPlaneNormals(normals,maxdepth-1); + } } }, }; From 998abea367153c1e4d5e4aadb0cd5f4faa86c262 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Tue, 21 Feb 2012 16:30:21 +0100 Subject: [PATCH 39/48] using Float64Array for vector3D, but this turns out to be too slow --- csg.js | 169 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 104 insertions(+), 65 deletions(-) diff --git a/csg.js b/csg.js index 7b7b60d..31a41f7 100644 --- a/csg.js +++ b/csg.js @@ -513,17 +513,13 @@ CSG.prototype = { var bounds = polygon.boundingBox(); if(i == 0) { - minpoint = bounds[0].clone(); - maxpoint = bounds[1].clone(); + minpoint = bounds[0]; + maxpoint = bounds[1]; } else { - minpoint.x = Math.min(minpoint.x, bounds[0].x); - minpoint.y = Math.min(minpoint.y, bounds[0].y); - minpoint.z = Math.min(minpoint.z, bounds[0].z); - maxpoint.x = Math.max(maxpoint.x, bounds[1].x); - maxpoint.y = Math.max(maxpoint.y, bounds[1].y); - maxpoint.z = Math.max(maxpoint.z, bounds[1].z); + minpoint = minpoint.min(bounds[0]); + maxpoint = maxpoint.max(bounds[1]); } } this.cachedBoundingBox = [minpoint, maxpoint]; @@ -534,6 +530,7 @@ CSG.prototype = { // returns true if there is a possibility that the two solids overlap // returns false if we can be sure that they do not overlap mayOverlap: function(csg) { + return true; if( (this.polygons.length == 0) || (csg.polygons.length == 0) ) { return false; @@ -1135,77 +1132,109 @@ CSG.roundedCube = function(options) { // new CSG.Vector3D({ x: 1, y: 2, z: 3 }); CSG.Vector3D = function(x, y, z) { - var ok = true; - if (arguments.length == 1) + var value = []; //new Float64Array(3); + if (arguments.length == 3) { - if(typeof(x) == "object") +// this.x = Number(x); +// this.y = Number(y); +// this.z = Number(z); + value[0] = x; + value[1] = y; + value[2] = z; + } + else + { + var ok = true; + if (arguments.length == 1) { - if(x instanceof Array) + if(typeof(x) == "object") { - this.x = x[0]; - this.y = x[1]; - this.z = x[2]; - } - else if( ('x' in x) && ('y' in x) && ('z' in x) ) - { - this.x = x.x; - this.y = x.y; - this.z = x.z; + if(x instanceof CSG.Vector3D) + { + value = x.value; + } + else if(x instanceof Array) + { + value[0] = x[0]; + value[1] = x[1]; + value[2] = x[2]; + } + else if( ('x' in x) && ('y' in x) && ('z' in x) ) + { + value[0] = x.x; + value[1] = x.y; + value[2] = x.z; + } + else ok = false; + } + else + { + var v = Number(x); + value[0] = v; + value[1] = v; + value[2] = v; } - else ok = false; } - else + else ok = false; + if(!ok) { - var v = Number(x); - this.x = v; - this.y = v; - this.z = v; + throw new Error("wrong arguments"); } } - else if (arguments.length == 3) - { - this.x = Number(x); - this.y = Number(y); - this.z = Number(z); - } - else ok = false; - if(!ok) - { - throw new Error("wrong arguments"); - } + this.value = value; }; CSG.Vector3D.prototype = { + get x() { + return this.value[0]; + }, + get y() { + return this.value[1]; + }, + get z() { + return this.value[2]; + }, + + set x(v) { + throw new Error("Vector3D is immutable"); + }, + set y(v) { + throw new Error("Vector3D is immutable"); + }, + set z(v) { + throw new Error("Vector3D is immutable"); + }, + clone: function() { - return new CSG.Vector3D(this.x, this.y, this.z); + return new CSG.Vector3D(this); }, negated: function() { - return new CSG.Vector3D(-this.x, -this.y, -this.z); + return new CSG.Vector3D(-this.value[0], -this.value[1], -this.value[2]); }, abs: function() { - return new CSG.Vector3D(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z)); + return new CSG.Vector3D(Math.abs(this.value[0]), Math.abs(this.value[1]), Math.abs(this.value[2])); }, plus: function(a) { - return new CSG.Vector3D(this.x + a.x, this.y + a.y, this.z + a.z); + return new CSG.Vector3D(this.value[0] + a.value[0], this.value[1] + a.value[1], this.value[2] + a.value[2]); }, minus: function(a) { - return new CSG.Vector3D(this.x - a.x, this.y - a.y, this.z - a.z); + return new CSG.Vector3D(this.value[0] - a.value[0], this.value[1] - a.value[1], this.value[2] - a.value[2]); }, times: function(a) { - return new CSG.Vector3D(this.x * a, this.y * a, this.z * a); + return new CSG.Vector3D(this.value[0] * a, this.value[1] * a, this.value[2] * a); }, dividedBy: function(a) { - return new CSG.Vector3D(this.x / a, this.y / a, this.z / a); + return new CSG.Vector3D(this.value[0] / a, this.value[1] / a, this.value[2] / a); }, dot: function(a) { - return this.x * a.x + this.y * a.y + this.z * a.z; + return this.value[0] * a.value[0] + this.value[1] * a.value[1] + this.value[2] * a.value[2]; }, lerp: function(a, t) { @@ -1226,9 +1255,9 @@ CSG.Vector3D.prototype = { cross: function(a) { return new CSG.Vector3D( - this.y * a.z - this.z * a.y, - this.z * a.x - this.x * a.z, - this.x * a.y - this.y * a.x + this.value[1] * a.value[2] - this.value[2] * a.value[1], + this.value[2] * a.value[0] - this.value[0] * a.value[2], + this.value[0] * a.value[1] - this.value[1] * a.value[0] ); }, @@ -1241,7 +1270,7 @@ CSG.Vector3D.prototype = { }, equals: function(a) { - return (this.x == a.x) && (this.y == a.y) && (this.z == a.z); + return (this.value[0] == a.value[0]) && (this.value[1] == a.value[1]) && (this.value[2] == a.value[2]); }, // Right multiply by a 4x4 matrix (the vector is interpreted as a row vector) @@ -1255,21 +1284,21 @@ CSG.Vector3D.prototype = { }, toStlString: function() { - return this.x+" "+this.y+" "+this.z; + return this.value[0]+" "+this.value[1]+" "+this.value[2]; }, toString: function() { - return "("+this.x+", "+this.y+", "+this.z+")"; + return "("+this.value[0]+", "+this.value[1]+", "+this.value[2]+")"; }, // find a vector that is somewhat perpendicular to this one randomNonParallelVector: function() { var abs = this.abs(); - if( (abs.x <= abs.y) && (abs.x <= abs.z) ) + if( (abs.value[0] <= abs.value[1]) && (abs.value[0] <= abs.value[2]) ) { return new CSG.Vector3D(1,0,0); } - else if( (abs.y <= abs.x) && (abs.y <= abs.z) ) + else if( (abs.value[1] <= abs.value[0]) && (abs.value[1] <= abs.value[2]) ) { return new CSG.Vector3D(0,1,0); } @@ -1279,6 +1308,21 @@ CSG.Vector3D.prototype = { } }, + min: function(p) { + return new CSG.Vector3D( + Math.min(this.value[0], p.value[0]), + Math.min(this.value[1], p.value[1]), + Math.min(this.value[2], p.value[2]) + ); + }, + + max: function(p) { + return new CSG.Vector3D( + Math.max(this.value[0], p.value[0]), + Math.max(this.value[1], p.value[1]), + Math.max(this.value[2], p.value[2]) + ); + }, }; // # class Vertex @@ -1743,22 +1787,17 @@ CSG.Polygon.prototype = { if(numvertices == 0) { minpoint=new CSG.Vector3D(0,0,0); - maxpoint=new CSG.Vector3D(0,0,0); } else { - minpoint=vertices[0].pos.clone(); - maxpoint=vertices[0].pos.clone(); + minpoint=vertices[0].pos; } + maxpoint=minpoint; for(var i=1; i < numvertices; i++) { var point = vertices[i].pos; - minpoint.x = Math.min(minpoint.x, point.x); - minpoint.y = Math.min(minpoint.y, point.y); - minpoint.z = Math.min(minpoint.z, point.z); - maxpoint.x = Math.max(maxpoint.x, point.x); - maxpoint.y = Math.max(maxpoint.y, point.y); - maxpoint.z = Math.max(maxpoint.z, point.z); + minpoint = minpoint.min(point); + maxpoint = maxpoint.max(point); } this.cachedBoundingBox = [minpoint, maxpoint]; } @@ -2029,10 +2068,10 @@ CSG.PolygonTreeNode.prototype = { { var bound = polygon.boundingSphere(); var sphereradius = bound[1] + 1e-4; -// var d = plane.normal.dot(bound[0]) - plane.w; var planenormal = plane.normal; var spherecenter = bound[0]; - var d = planenormal.x*spherecenter.x + planenormal.y*spherecenter.y + planenormal.z*spherecenter.z - plane.w; + var d = planenormal.dot(spherecenter) - plane.w; +// var d = planenormal.x*spherecenter.x + planenormal.y*spherecenter.y + planenormal.z*spherecenter.z - plane.w; if(d > sphereradius) { frontnodes.push(this); From e2a146af4e5739e3f545ca35ac240c63b63c8a09 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Tue, 21 Feb 2012 16:41:39 +0100 Subject: [PATCH 40/48] Changed Vector3D to _x, _y, _z properties with getters --- csg.js | 111 +++++++++++++++++++++++++++------------------------------ 1 file changed, 53 insertions(+), 58 deletions(-) diff --git a/csg.js b/csg.js index 31a41f7..e07488d 100644 --- a/csg.js +++ b/csg.js @@ -530,7 +530,6 @@ CSG.prototype = { // returns true if there is a possibility that the two solids overlap // returns false if we can be sure that they do not overlap mayOverlap: function(csg) { - return true; if( (this.polygons.length == 0) || (csg.polygons.length == 0) ) { return false; @@ -679,16 +678,16 @@ CSG.prototype = { var verticesArrayIndex = 0; vertices.map(function(v){ var pos = v.pos; - vertexData[verticesArrayIndex++] = pos.x; - vertexData[verticesArrayIndex++] = pos.y; - vertexData[verticesArrayIndex++] = pos.z; + vertexData[verticesArrayIndex++] = pos._x; + vertexData[verticesArrayIndex++] = pos._y; + vertexData[verticesArrayIndex++] = pos._z; }); var planesArrayIndex = 0; planes.map(function(p){ var normal = p.normal; - planeData[planesArrayIndex++] = normal.x; - planeData[planesArrayIndex++] = normal.y; - planeData[planesArrayIndex++] = normal.z; + planeData[planesArrayIndex++] = normal._x; + planeData[planesArrayIndex++] = normal._y; + planeData[planesArrayIndex++] = normal._z; planeData[planesArrayIndex++] = p.w; }); var result = { @@ -1132,15 +1131,11 @@ CSG.roundedCube = function(options) { // new CSG.Vector3D({ x: 1, y: 2, z: 3 }); CSG.Vector3D = function(x, y, z) { - var value = []; //new Float64Array(3); if (arguments.length == 3) { -// this.x = Number(x); -// this.y = Number(y); -// this.z = Number(z); - value[0] = x; - value[1] = y; - value[2] = z; + this._x = x; + this._y = y; + this._z = z; } else { @@ -1151,28 +1146,30 @@ CSG.Vector3D = function(x, y, z) { { if(x instanceof CSG.Vector3D) { - value = x.value; + this._x = x._x; + this._y = x._y; + this._z = x._z; } else if(x instanceof Array) { - value[0] = x[0]; - value[1] = x[1]; - value[2] = x[2]; + this._x = x[0]; + this._y = x[1]; + this._z = x[2]; } else if( ('x' in x) && ('y' in x) && ('z' in x) ) { - value[0] = x.x; - value[1] = x.y; - value[2] = x.z; + this._x = x.x; + this._y = x.y; + this._z = x.z; } else ok = false; } else { var v = Number(x); - value[0] = v; - value[1] = v; - value[2] = v; + this._x = v; + this._y = v; + this._z = v; } } else ok = false; @@ -1181,18 +1178,17 @@ CSG.Vector3D = function(x, y, z) { throw new Error("wrong arguments"); } } - this.value = value; }; CSG.Vector3D.prototype = { get x() { - return this.value[0]; + return this._x; }, get y() { - return this.value[1]; + return this._y; }, get z() { - return this.value[2]; + return this._z; }, set x(v) { @@ -1210,31 +1206,31 @@ CSG.Vector3D.prototype = { }, negated: function() { - return new CSG.Vector3D(-this.value[0], -this.value[1], -this.value[2]); + return new CSG.Vector3D(-this._x, -this._y, -this._z); }, abs: function() { - return new CSG.Vector3D(Math.abs(this.value[0]), Math.abs(this.value[1]), Math.abs(this.value[2])); + return new CSG.Vector3D(Math.abs(this._x), Math.abs(this._y), Math.abs(this._z)); }, plus: function(a) { - return new CSG.Vector3D(this.value[0] + a.value[0], this.value[1] + a.value[1], this.value[2] + a.value[2]); + return new CSG.Vector3D(this._x + a._x, this._y + a._y, this._z + a._z); }, minus: function(a) { - return new CSG.Vector3D(this.value[0] - a.value[0], this.value[1] - a.value[1], this.value[2] - a.value[2]); + return new CSG.Vector3D(this._x - a._x, this._y - a._y, this._z - a._z); }, times: function(a) { - return new CSG.Vector3D(this.value[0] * a, this.value[1] * a, this.value[2] * a); + return new CSG.Vector3D(this._x * a, this._y * a, this._z * a); }, dividedBy: function(a) { - return new CSG.Vector3D(this.value[0] / a, this.value[1] / a, this.value[2] / a); + return new CSG.Vector3D(this._x / a, this._y / a, this._z / a); }, dot: function(a) { - return this.value[0] * a.value[0] + this.value[1] * a.value[1] + this.value[2] * a.value[2]; + return this._x * a._x + this._y * a._y + this._z * a._z; }, lerp: function(a, t) { @@ -1255,9 +1251,9 @@ CSG.Vector3D.prototype = { cross: function(a) { return new CSG.Vector3D( - this.value[1] * a.value[2] - this.value[2] * a.value[1], - this.value[2] * a.value[0] - this.value[0] * a.value[2], - this.value[0] * a.value[1] - this.value[1] * a.value[0] + this._y * a._z - this._z * a._y, + this._z * a._x - this._x * a._z, + this._x * a._y - this._y * a._x ); }, @@ -1270,7 +1266,7 @@ CSG.Vector3D.prototype = { }, equals: function(a) { - return (this.value[0] == a.value[0]) && (this.value[1] == a.value[1]) && (this.value[2] == a.value[2]); + return (this._x == a._x) && (this._y == a._y) && (this._z == a._z); }, // Right multiply by a 4x4 matrix (the vector is interpreted as a row vector) @@ -1284,21 +1280,21 @@ CSG.Vector3D.prototype = { }, toStlString: function() { - return this.value[0]+" "+this.value[1]+" "+this.value[2]; + return this._x+" "+this._y+" "+this._z; }, toString: function() { - return "("+this.value[0]+", "+this.value[1]+", "+this.value[2]+")"; + return "("+this._x+", "+this._y+", "+this._z+")"; }, // find a vector that is somewhat perpendicular to this one randomNonParallelVector: function() { var abs = this.abs(); - if( (abs.value[0] <= abs.value[1]) && (abs.value[0] <= abs.value[2]) ) + if( (abs._x <= abs._y) && (abs._x <= abs._z) ) { return new CSG.Vector3D(1,0,0); } - else if( (abs.value[1] <= abs.value[0]) && (abs.value[1] <= abs.value[2]) ) + else if( (abs._y <= abs._x) && (abs._y <= abs._z) ) { return new CSG.Vector3D(0,1,0); } @@ -1310,17 +1306,17 @@ CSG.Vector3D.prototype = { min: function(p) { return new CSG.Vector3D( - Math.min(this.value[0], p.value[0]), - Math.min(this.value[1], p.value[1]), - Math.min(this.value[2], p.value[2]) + Math.min(this._x, p._x), + Math.min(this._y, p._y), + Math.min(this._z, p._z) ); }, max: function(p) { return new CSG.Vector3D( - Math.max(this.value[0], p.value[0]), - Math.max(this.value[1], p.value[1]), - Math.max(this.value[2], p.value[2]) + Math.max(this._x, p._x), + Math.max(this._y, p._y), + Math.max(this._z, p._z) ); }, }; @@ -2071,7 +2067,6 @@ CSG.PolygonTreeNode.prototype = { var planenormal = plane.normal; var spherecenter = bound[0]; var d = planenormal.dot(spherecenter) - plane.w; -// var d = planenormal.x*spherecenter.x + planenormal.y*spherecenter.y + planenormal.z*spherecenter.z - plane.w; if(d > sphereradius) { frontnodes.push(this); @@ -2432,9 +2427,9 @@ CSG.Matrix4x4.prototype = { // (result = M*v) // Fourth element is taken as 1 rightMultiply1x3Vector: function(v) { - var v0 = v.x; - var v1 = v.y; - var v2 = v.z; + var v0 = v._x; + var v1 = v._y; + var v2 = v._z; var v3 = 1; var x = v0*this.elements[0] + v1*this.elements[1] + v2*this.elements[2] + v3*this.elements[3]; var y = v0*this.elements[4] + v1*this.elements[5] + v2*this.elements[6] + v3*this.elements[7]; @@ -2455,9 +2450,9 @@ CSG.Matrix4x4.prototype = { // (result = v*M) // Fourth element is taken as 1 leftMultiply1x3Vector: function(v) { - var v0 = v.x; - var v1 = v.y; - var v2 = v.z; + var v0 = v._x; + var v1 = v._y; + var v2 = v._z; var v3 = 1; var x = v0*this.elements[0] + v1*this.elements[4] + v2*this.elements[8] + v3*this.elements[12]; var y = v0*this.elements[1] + v1*this.elements[5] + v2*this.elements[9] + v3*this.elements[13]; @@ -3690,7 +3685,7 @@ CSG.fuzzyCSGFactory.prototype = { }, getVertex: function(sourcevertex) { - var elements = [sourcevertex.pos.x, sourcevertex.pos.y, sourcevertex.pos.z]; + var elements = [sourcevertex.pos._x, sourcevertex.pos._y, sourcevertex.pos._z]; var result = this.vertexfactory.lookupOrCreate(elements, function(els) { return sourcevertex; }); @@ -3698,7 +3693,7 @@ CSG.fuzzyCSGFactory.prototype = { }, getPlane: function(sourceplane) { - var elements = [sourceplane.normal.x, sourceplane.normal.y, sourceplane.normal.z, sourceplane.w]; + var elements = [sourceplane.normal._x, sourceplane.normal._y, sourceplane.normal._z, sourceplane.w]; var result = this.planefactory.lookupOrCreate(elements, function(els) { return sourceplane; }); From 471b99d5ba00103220a69265c05ba86346cc3395 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Tue, 21 Feb 2012 17:11:29 +0100 Subject: [PATCH 41/48] minor bug fixes, added note about OpenJsCad.log() --- csg.js | 8 +++----- index.html | 4 ++++ processfile.html | 8 +++++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/csg.js b/csg.js index e07488d..01c1be1 100644 --- a/csg.js +++ b/csg.js @@ -1062,10 +1062,8 @@ CSG.roundedCube = function(options) { var resolution = CSG.parseOptionAsFloat(options, "resolution", 8); if(resolution < 4) resolution = 4; var roundradius = CSG.parseOptionAsFloat(options, "roundradius", 0.2); - var innercuberadius=cuberadius.clone(); - innercuberadius.x -= roundradius; - innercuberadius.y -= roundradius; - innercuberadius.z -= roundradius; + var innercuberadius=cuberadius; + innercuberadius = innercuberadius.minus(new CSG.Vector3D(roundradius)); var result = CSG.cube({center: center, radius: [cuberadius.x, innercuberadius.y, innercuberadius.z]}); result = result.unionSub( CSG.cube({center: center, radius: [innercuberadius.x, cuberadius.y, innercuberadius.z]}),false,false); result = result.unionSub( CSG.cube({center: center, radius: [innercuberadius.x, innercuberadius.y, cuberadius.z]}),false,false); @@ -2213,7 +2211,7 @@ CSG.Node = function(parent) { CSG.Node.prototype = { // Convert solid space to empty space and empty space to solid space. invert: function() { - this.plane = this.plane.flipped(); + if (this.plane) this.plane = this.plane.flipped(); if (this.front) this.front.invert(); if (this.back) this.back.invert(); var temp = this.front; diff --git a/index.html b/index.html index e76d696..66ede79 100644 --- a/index.html +++ b/index.html @@ -590,7 +590,9 @@ view the source of csg.js: var vec1 = new CSG.Vector3D(1,2,3); // 3 arguments var vec2 = new CSG.Vector3D( [1,2,3] ); // 1 array argument var vec3 = new CSG.Vector3D(vec2); // cloning a vector +// get the values as: vec1.x, vec.y, vec1.z // vector math. All operations return a new vector, the original is unmodified! +// vectors cannot be modified. Instead you should create a new vector. vec.negated() vec.abs() vec.plus(othervector) @@ -607,6 +609,8 @@ vec.distanceTo(othervector) vec.distanceToSquared(othervector) // == vec.distanceTo(othervector)^2 vec.equals(othervector) vec.multiply4x4(matrix4x4) // right multiply by a 4x4 matrix +vec.min(othervector) // returns a new vector with the minimum x,y and z values +vec.max(othervector) // returns a new vector with the maximum x,y and z values // --------- Vector2D --------------------- var vec1 = new CSG.Vector2D(1,2); // 2 arguments diff --git a/processfile.html b/processfile.html index bfca818..3809a56 100644 --- a/processfile.html +++ b/processfile.html @@ -199,7 +199,13 @@ Then press the Debug button above. The debugger will stop just before executing

    For more information about debugging in Chrome see Chrome Developer Tools: Overview - +

    +You can output log messages from your script using: +
    +OpenJsCad.log("Hello");
    +
    +The log messages will appear in the browser's console (shown using Ctrl+Shift+I in Chrome). They will appear +even while not actually debugging.

    From 07475364a6a6ed64e735a76c68c0c31d1babd4a8 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Wed, 22 Feb 2012 13:30:59 +0100 Subject: [PATCH 42/48] Improved tesselation and speed for expand() and contract() --- csg.js | 356 ++++++++++++++++++++++++++++++++++++++++++++--------- index.html | 1 + 2 files changed, 299 insertions(+), 58 deletions(-) diff --git a/csg.js b/csg.js index 01c1be1..ef203c6 100644 --- a/csg.js +++ b/csg.js @@ -212,8 +212,8 @@ CSG.prototype = { var newpolygons = a.allPolygons().concat(b.allPolygons()); var result = CSG.fromPolygons(newpolygons); result.properties = this.properties._merge(csg.properties); - if(canonicalize) result = result.canonicalized(); if(retesselate) result = result.reTesselated(); + if(canonicalize) result = result.canonicalized(); return result; } }, @@ -257,8 +257,8 @@ CSG.prototype = { a.invert(); var result = CSG.fromPolygons(a.allPolygons()); result.properties = this.properties._merge(csg.properties); - if(canonicalize) result = result.canonicalized(); if(retesselate) result = result.reTesselated(); + if(canonicalize) result = result.canonicalized(); return result; }, @@ -292,8 +292,8 @@ CSG.prototype = { a.invert(); var result = CSG.fromPolygons(a.allPolygons()); result.properties = this.properties._merge(csg.properties); - if(canonicalize) result = result.canonicalized(); if(retesselate) result = result.reTesselated(); + if(canonicalize) result = result.canonicalized(); return result; }, @@ -411,19 +411,7 @@ CSG.prototype = { // Expand the solid // resolution: number of points per 360 degree for the rounded corners expand: function(radius, resolution) { - var result=this; - var count = 0; - this.polygons.map(function(p) { - var expanded=p.expand(radius, resolution); - result=result.unionSub(expanded, false, false); - count++; - if(count == 300) - { - result = result.reTesselated(); - count = 0; - } - }); -// result = result.canonicalized(); + var result = this.expandedShell(radius, resolution, true); result = result.reTesselated(); result.properties = this.properties; // keep original properties return result; @@ -432,15 +420,239 @@ CSG.prototype = { // Contract the solid // resolution: number of points per 360 degree for the rounded corners contract: function(radius, resolution) { - var result=this; - this.polygons.map(function(p) { - var expanded=p.expand(radius, resolution); - result=result.subtract(expanded); - }); + var expandedshell = this.expandedShell(radius, resolution, false); + var result = this.subtract(expandedshell); + result = result.reTesselated(); result.properties = this.properties; // keep original properties return result; }, + // Create the expanded shell of the solid: + // All faces are extruded to get a thickness of 2*radius + // Cylinders are constructed around every side + // Spheres are placed on every vertex + // unionWithThis: if true, the resulting solid will be united with 'this' solid; + // the result is a true expansion of the solid + // If false, returns only the shell + expandedShell: function(radius, resolution, unionWithThis) { + var csg = this.reTesselated(); + var result; + if(unionWithThis) + { + result = csg; + } + else + { + result = new CSG(); + } + + // first extrude all polygons: + csg.polygons.map(function(polygon){ + var extrudevector=polygon.plane.normal.unit().times(2*radius); + var translatedpolygon = polygon.translate(extrudevector.times(-0.5)); + var extrudedface = translatedpolygon.extrude(extrudevector); + result=result.unionSub(extrudedface, false, false); + }); + + // Make a list of all unique vertex pairs (i.e. all sides of the solid) + // For each vertex pair we collect the following: + // v1: first coordinate + // v2: second coordinate + // planenormals: array of normal vectors of all planes touching this side + var vertexpairs = {}; // map of 'vertex pair tag' to {v1, v2, planenormals} + csg.polygons.map(function(polygon){ + var numvertices = polygon.vertices.length; + var prevvertex = polygon.vertices[numvertices-1]; + var prevvertextag = prevvertex.getTag(); + for(var i = 0; i < numvertices; i++) + { + var vertex = polygon.vertices[i]; + var vertextag = vertex.getTag(); + var vertextagpair; + if(vertextag < prevvertextag) + { + vertextagpair = vertextag+"-"+prevvertextag; + } + else + { + vertextagpair = prevvertextag+"-"+vertextag; + } + var obj; + if(vertextagpair in vertexpairs) + { + obj = vertexpairs[vertextagpair]; + } + else + { + obj = { + v1: prevvertex, + v2: vertex, + planenormals: [], + }; + vertexpairs[vertextagpair] = obj; + } + obj.planenormals.push(polygon.plane.normal); + + prevvertextag = vertextag; + prevvertex = vertex; + } + }); + + // now construct a cylinder on every side + // The cylinder is always an approximation of a true cylinder: it will have polygons + // around the sides. We will make sure though that the cylinder will have an edge at every + // face that touches this side. This ensures that we will get a smooth fill even + // if two edges are at, say, 10 degrees and the resolution is low. + // Note: the result is not retesselated yet but it really should be! + for(vertextagpair in vertexpairs) + { + var vertexpair = vertexpairs[vertextagpair]; + var startpoint = vertexpair.v1.pos; + var endpoint = vertexpair.v2.pos; + // our x,y and z vectors: + var zbase = endpoint.minus(startpoint).unit(); + var xbase = vertexpair.planenormals[0].unit(); + var ybase = xbase.cross(zbase); + + // make a list of angles that the cylinder should traverse: + var angles = []; + + // first of all equally spaced around the cylinder: + for(var i = 0; i < resolution; i++) + { + var angle = i * Math.PI * 2 / resolution; + angles.push(angle); + } + + // and also at every normal of all touching planes: + vertexpair.planenormals.map(function(planenormal){ + var si = ybase.dot(planenormal); + var co = xbase.dot(planenormal); + var angle = Math.atan2(si,co); + if(angle < 0) angle += Math.PI*2; + angles.push(angle); + angle = Math.atan2(-si,-co); + if(angle < 0) angle += Math.PI*2; + angles.push(angle); + }); + + // this will result in some duplicate angles but we will get rid of those later. + // Sort: + angles = angles.sort(function(a,b){return a-b;}); + + // Now construct the cylinder by traversing all angles: + var numangles = angles.length; + var prevp1, prevp2; + var startfacevertices = [], endfacevertices = []; + var polygons = []; + var prevangle; + for(var i = -1; i < numangles; i++) + { + var angle = angles[(i < 0)?(i+numangles):i]; + var si = Math.sin(angle); + var co = Math.cos(angle); + var p = xbase.times(co * radius).plus(ybase.times(si * radius)); + var p1 = startpoint.plus(p); + var p2 = endpoint.plus(p); + var skip = false; + if(i >= 0) + { + if(p1.distanceTo(prevp1) < 1e-5) + { + skip = true; + } + } + if(!skip) + { + if(i >= 0) + { + startfacevertices.push(new CSG.Vertex(p1)); + endfacevertices.push(new CSG.Vertex(p2)); + var polygonvertices = [ + new CSG.Vertex(prevp2), + new CSG.Vertex(p2), + new CSG.Vertex(p1), + new CSG.Vertex(prevp1), + ]; + var polygon = new CSG.Polygon(polygonvertices); + polygons.push(polygon); + } + prevp1 = p1; + prevp2 = p2; + } + } + endfacevertices.reverse(); + polygons.push(new CSG.Polygon(startfacevertices)); + polygons.push(new CSG.Polygon(endfacevertices)); + var cylinder = CSG.fromPolygons(polygons); + result = result.unionSub(cylinder, false, false); + } + + // make a list of all unique vertices + // For each vertex we also collect the list of normals of the planes touching the vertices + var vertexmap = {}; + csg.polygons.map(function(polygon){ + polygon.vertices.map(function(vertex){ + var vertextag = vertex.getTag(); + var obj; + if(vertextag in vertexmap) + { + obj = vertexmap[vertextag]; + } + else + { + obj = { + pos: vertex.pos, + normals: [], + }; + vertexmap[vertextag] = obj; + } + obj.normals.push(polygon.plane.normal); + }); + }); + + // and build spheres at each vertex + // We will try to set the x and z axis to the normals of 2 planes + // This will ensure that our sphere tesselation somewhat matches 2 planes + for(vertextag in vertexmap) + { + var vertexobj = vertexmap[vertextag]; + // use the first normal to be the x axis of our sphere: + var xaxis = vertexobj.normals[0].unit(); + // and find a suitable z axis. We will use the normal which is most perpendicular to the x axis: + var bestzaxis = null; + var bestzaxisorthogonality = 0; + for(var i = 1; i < vertexobj.normals.length; i++) + { + var normal = vertexobj.normals[i].unit(); + var cross = xaxis.cross(normal); + var crosslength = cross.length(); + if(crosslength > 0.05) + { + if(crosslength > bestzaxisorthogonality) + { + bestzaxisorthogonality = crosslength; + bestzaxis = normal; + } + } + } + if(! bestzaxis) + { + bestzaxis = xaxis.randomNonParallelVector(); + } + var yaxis = xaxis.cross(bestzaxis).unit(); + var zaxis = yaxis.cross(xaxis); + var sphere = CSG.sphere({ + center: vertexobj.pos, + radius: radius, + resolution: resolution, + axes: [xaxis, yaxis, zaxis]}); + result = result.unionSub(sphere, false, false); + } + + return result; + }, + canonicalized: function () { if(this.isCanonicalized) { @@ -493,7 +705,8 @@ CSG.prototype = { } var result = CSG.fromPolygons(destpolygons); result.isRetesselated = true; - result.isCanonicalized = true; + result=result.canonicalized(); +// result.isCanonicalized = true; result.properties = this.properties; // keep original properties return result; } @@ -703,6 +916,31 @@ CSG.prototype = { return result; }, + // For debugging + // Creates a new solid with a tiny cube at every vertex of the source solid + toPointCloud: function(cuberadius) { + var csg = this.reTesselated(); + + var result = new CSG(); + + // make a list of all unique vertices + // For each vertex we also collect the list of normals of the planes touching the vertices + var vertexmap = {}; + csg.polygons.map(function(polygon){ + polygon.vertices.map(function(vertex){ + vertexmap[vertex.getTag()] = vertex.pos; + }); + }); + + for(vertextag in vertexmap) + { + var pos = vertexmap[vertextag]; + var cube = CSG.cube({center: pos, radius: cuberadius}); + result = result.unionSub(cube, false, false); + } + result = result.reTesselated(); + return result; + }, }; @@ -820,6 +1058,7 @@ CSG.cube = function(options) { // center: center of sphere (default [0,0,0]) // radius: radius of sphere (default 1), must be a scalar // resolution: determines the number of polygons per 360 degree revolution (default 12) +// axes: (optional) an array with 3 vectors for the x, y and z base vectors // // Example usage: // @@ -833,11 +1072,21 @@ CSG.sphere = function(options) { var center = CSG.parseOptionAs3DVector(options, "center", [0,0,0]); var radius = CSG.parseOptionAsFloat(options, "radius", 1); var resolution = CSG.parseOptionAsInt(options, "resolution", 12); + var xvector, yvector, zvector; + if('axes' in options) + { + xvector = options.axes[0].unit().times(radius); + yvector = options.axes[1].unit().times(radius); + zvector = options.axes[2].unit().times(radius); + } + else + { + xvector = new CSG.Vector3D([1,0,0]).times(radius); + yvector = new CSG.Vector3D([0,-1,0]).times(radius); + zvector = new CSG.Vector3D([0,0,1]).times(radius); + } if(resolution < 4) resolution = 4; var qresolution = Math.round(resolution / 4); - var xvector = new CSG.Vector3D([1,0,0]).times(radius); - var yvector = new CSG.Vector3D([0,-1,0]).times(radius); - var zvector = new CSG.Vector3D([0,0,1]).times(radius); var prevcylinderpoint; var polygons = []; for(var slice1 = 0; slice1 <= resolution; slice1++) @@ -1082,7 +1331,7 @@ CSG.roundedCube = function(options) { sphere = CSG.sphere({center: p3, radius: roundradius, resolution: resolution}); result = result.unionSub(sphere,false,false); sphere = CSG.sphere({center: p4, radius: roundradius, resolution: resolution}); - result = result.unionSub(sphere,true,true); + result = result.unionSub(sphere,false,true); var cylinder = CSG.cylinder({start:p1, end: p2, radius: roundradius, resolution: resolution}); result = result.unionSub(cylinder,false,false); cylinder = CSG.cylinder({start:p2, end: p3, radius: roundradius, resolution: resolution}); @@ -1100,9 +1349,10 @@ CSG.roundedCube = function(options) { cylinder = CSG.cylinder({start:p3, end: p3.plus(d), radius: roundradius, resolution: resolution}); result = result.unionSub(cylinder); cylinder = CSG.cylinder({start:p4, end: p4.plus(d), radius: roundradius, resolution: resolution}); - result = result.unionSub(cylinder,true,true); + result = result.unionSub(cylinder,false,true); } } + result = result.reTesselated(); result.properties.roundedCube = new CSG.Properties(); result.properties.roundedCube.center = new CSG.Vertex(center); result.properties.roundedCube.facecenters = [ @@ -1729,35 +1979,7 @@ CSG.Polygon.prototype = { translate: function(offset) { return this.transform(CSG.Matrix4x4.translation(offset)); }, - - // Expand the polygon with a certain radius - // This extrudes the face of the polygon and adds rounded corners - // Returns a CSG object (not a polygon anymore!) - // resolution: number of points per 360 degree for the rounded corners - expand: function(radius, resolution) { - if( (!resolution) || (resolution < 4) ) resolution = 4; - resolution = 4 * Math.floor(resolution / 4); - - var result=new CSG(); - // expand each side of the polygon. The expansion of a line is a roundedCylinder: - var numvertices=this.vertices.length; - for(var i=0; i < numvertices; i++) - { - var previ = (i == 0) ? (numvertices-1):i-1; - var p1 = this.vertices[previ].pos; - var p2 = this.vertices[i].pos; - - var roundedCylinder = CSG.roundedCylinder({start: p1, end: p2, normal: this.plane.normal, radius: radius, resolution: resolution}); - result = result.unionSub(roundedCylinder, false, false); - } - var extrudevector=this.plane.normal.unit().times(2*radius); - var translatedpolygon = this.translate(extrudevector.times(-0.5)); - var extrudedface = translatedpolygon.extrude(extrudevector); - result=result.unionSub(extrudedface, false, false); - return result; - }, - // returns an array with a CSG.Vector3D (center point) and a radius boundingSphere: function() { if(!this.cachedBoundingSphere) @@ -2561,6 +2783,18 @@ CSG.Matrix4x4.rotationZ = function(degrees) { return new CSG.Matrix4x4(els); }; +// Matrix for rotation about arbitrary point and axis +CSG.Matrix4x4.rotation = function(rotationCenter, rotationAxis, degrees) { + var rotationPlane = CSG.Plane.fromNormalAndPoint(rotationAxis, rotationCenter); + var orthobasis = new CSG.OrthoNormalBasis(rotationPlane); + var transformation = CSG.Matrix4x4.translation(rotationCenter.negated()); + transformation = transformation.multiply(orthobasis.getProjectionMatrix()); + transformation = transformation.multiply(CSG.Matrix4x4.rotationZ(degrees)); + transformation = transformation.multiply(orthobasis.getInverseProjectionMatrix()); + transformation = transformation.multiply(CSG.Matrix4x4.translation(rotationCenter)); + return transformation; +}; + // Create an affine matrix for translation: CSG.Matrix4x4.translation = function(v) { // parse as CSG.Vector3D, so we can pass an array or a CSG.Vector3D @@ -3048,11 +3282,12 @@ CSG.OrthoNormalBasis.prototype = { }, getInverseProjectionMatrix: function() { + var wtimesnormal = this.plane.normal.times(this.plane.w); return new CSG.Matrix4x4([ this.u.x, this.u.y, this.u.z, 0, this.v.x, this.v.y, this.v.z, 0, - this.plane.normal.x, this.plane.normal.y, this.plane.normal.z, this.plane.w, - 0,0,0,1 + this.plane.normal.x, this.plane.normal.y, this.plane.normal.z, 0, + wtimesnormal.x, wtimesnormal.y, wtimesnormal.z, 1 ]); }, @@ -3905,6 +4140,10 @@ CSG.Connector.prototype = { transformation = transformation.multiply(CSG.Matrix4x4.translation(other.point)); var usAligned = us.transform(transformation); return transformation; + }, + + axisLine: function() { + return new CSG.Line3D(this.point, this.axisvector); }, }; @@ -4219,4 +4458,5 @@ CSG.Path2D.prototype = { }); return new CSG.Path2D(newpoints, this.closed); }, -}; \ No newline at end of file +}; + \ No newline at end of file diff --git a/index.html b/index.html index 66ede79..20f218b 100644 --- a/index.html +++ b/index.html @@ -653,6 +653,7 @@ var vec2d = m1.leftMultiply1x2Vector(vec2d); // vector * matrix var m = CSG.Matrix4x4.rotationX(degrees); // matrix for rotation about X axis var m = CSG.Matrix4x4.rotationY(degrees); // matrix for rotation about Y axis var m = CSG.Matrix4x4.rotationZ(degrees); // matrix for rotation about Z axis +var m = CSG.Matrix4x4.rotation(rotationCenter, rotationAxis, degrees); // rotation about arbitrary point and axis var m = CSG.Matrix4x4.translation(vec3d); // translation var m = CSG.Matrix4x4.scaling(vec3d); // scale var m = CSG.Matrix4x4.mirroring(plane); // mirroring in a plane; the argument must be a CSG.Plane From cd14082786fab917f120ed1b7c4a0812e3077528 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Thu, 23 Feb 2012 09:48:13 +0100 Subject: [PATCH 43/48] Hook demo updated --- hookdemo.html | 53 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/hookdemo.html b/hookdemo.html index b360a75..7eb90db 100644 --- a/hookdemo.html +++ b/hookdemo.html @@ -69,13 +69,13 @@ and use the OpenJsCad parser. For more inf // Here we define the user editable parameters: function getParameterDefinitions() { return [ - { name: 'topdiameter', caption: 'Inner diameter of top hook:', type: 'float', default: 10 }, - { name: 'clampfactor', caption: 'Snugness of top hook (0 - 100):', type: 'float', default: 20 }, + { name: 'topdiameter', caption: 'Inner diameter of top hook:', type: 'float', default: 16.7 }, + { name: 'clampfactor', caption: 'Snugness of top hook (0 - 100):', type: 'float', default: 25 }, { name: 'cliplength', caption: 'Top hook clip length:', type: 'float', default: 5 }, - { name: 'bottomdiameter', caption: 'Inner diameter of bottom hook:', type: 'float', default: 15 }, - { name: 'height', caption: 'Outer height of the hook:', type: 'float', default: 50 }, + { name: 'bottomdiameter', caption: 'Inner diameter of bottom hook:', type: 'float', default: 20 }, + { name: 'height', caption: 'Outer height of the hook:', type: 'float', default: 60 }, { name: 'thickness', caption: 'Thickness:', type: 'float', default: 5 }, - { name: 'width', caption: 'Width:', type: 'float', default: 10 }, + { name: 'width', caption: 'Width:', type: 'float', default: 7 }, { name: 'rounded', type: 'choice', @@ -84,13 +84,17 @@ function getParameterDefinitions() { captions: ["No", "Yes (rendering will take a long time!)"], default: 0, }, - { name: 'roundness', caption: 'Diameter of rounded edges (if enabled):', type: 'float', default: 1 }, + { name: 'roundness', caption: 'Diameter of rounded edges (if enabled):', type: 'float', default: 1.5 }, + { name: 'buildwidth', caption: 'Width (x) of build area (to print multiple copies):', type: 'float', default: 90 }, + { name: 'builddepth', caption: 'Depth (y) of build area (to print multiple copies):', type: 'float', default: 90 }, ]; } function main(params) { + if(OpenJsCad.log) OpenJsCad.log("start"); + var pathresolution = 16; - var expandresolution = 16; + var expandresolution = 6; // construct the 2D path: var topradius = params.topdiameter/2; @@ -162,17 +166,44 @@ function main(params) { path = path.transform(matrix); // extrude the path to create a 3D solid - var result = path.rectangularExtrude(2*(halfthickness-roundness), params.width-2*roundness, pathresolution, true); - result = result.translate([0, 0, -params.width/2+roundness]); + var hook = path.rectangularExtrude(2*(halfthickness-roundness), params.width-2*roundness, pathresolution, true); + hook = hook.translate([0, 0, -params.width/2+roundness]); // expand to create rounded corners: if(roundness > 0) { - result = result.expand(roundness, expandresolution); + hook = hook.expand(roundness, expandresolution); } + // hook = hook.toPointCloud(0.1); + + // determine how many objects will fit in the build area: + var bounds = hook.getBounds(); + var objsize = bounds[1].minus(bounds[0]); + var margin = 5; // distance between the copies + var numx = Math.floor((params.buildwidth + margin) / (objsize.x + margin)); + var numy = Math.floor((params.builddepth + margin) / (objsize.y + margin)); + if(numx < 1) numx = 1; + if(numy < 1) numy = 1; + + // and make the copies: + var result = new CSG(); + for(var x = 0; x < numx; x++) + { + var deltax = ((1-numx)/2+x) * (objsize.x + margin); + var colresult = new CSG(); + for(var y = 0; y < numy; y++) + { + var deltay = ((1-numy)/2+y) * (objsize.y + margin); + var translated = hook.translate(new CSG.Vector3D(deltax, deltay, 0)); + colresult = colresult.union(translated); + } + result = result.union(colresult); + } + if(OpenJsCad.log) OpenJsCad.log("finish"); return result; -}
    +} +


    From c6a95b7de4d2b3e347184555b373484b7a5b10f0 Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Sat, 3 Mar 2012 13:00:38 +0100 Subject: [PATCH 44/48] Added Grille demo --- grilledemo.html | 338 ++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 10 ++ 2 files changed, 348 insertions(+) create mode 100644 grilledemo.html diff --git a/grilledemo.html b/grilledemo.html new file mode 100644 index 0000000..22a5c79 --- /dev/null +++ b/grilledemo.html @@ -0,0 +1,338 @@ + + + + + + + + + + +OpenJsCad demo: Parametric Grille + + +

    OpenJsCad demo: Parametric Grille

    +
    +

    Source code

    +Below is the OpenJsCad script for this demo. To build your own models, create a .jscad script +and use the OpenJsCad parser. For more information see the +OpenJsCad documentation. +

    +
    + +

    + + \ No newline at end of file diff --git a/index.html b/index.html index 20f218b..ee8c989 100644 --- a/index.html +++ b/index.html @@ -122,6 +122,7 @@ Alt+drag zooms (by changing the distance between camera and model).
  • Involute Gears
  • Parametric S hook: shows how to extrude 2d paths
  • Servo motor: shows how to work with properties and connectors
  • +
  • Parametric Grille
  • License

    Copyright (c) 2012 Joost Nieuwenhuijse. @@ -466,6 +467,15 @@ path = path.close(); // close the path // of course we could simply have done: // var path = new CSG.Path2D([[10,10], [-10,10], [-10,-10], [10,-10]], /* closed = */ true); +// We can make arcs and circles: +var curvedpath = CSG.Path2D.arc({ + center: [0,0,0], + radius: 10, + startangle: 0, + endangle: 180, + resolution: 16, +}); + // Extrude the path by following it with a rectangle (upright, perpendicular to the path direction) // Returns a CSG solid // width: width of the extrusion, in the z=0 plane From 6bb529834fcfb1f3fd10a5bb5d7552aa3a26a725 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Sat, 17 Mar 2012 20:42:33 +0100 Subject: [PATCH 45/48] jquery, coffeescript added. simple.html for a simple editor, which should look and work like openscad. --- coffee-script.js | 8 + jquery-1.7.1.min.js | 4 + jquery.js | 1 + simple.html | 693 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 706 insertions(+) create mode 100644 coffee-script.js create mode 100644 jquery-1.7.1.min.js create mode 120000 jquery.js create mode 100644 simple.html diff --git a/coffee-script.js b/coffee-script.js new file mode 100644 index 0000000..7b4871c --- /dev/null +++ b/coffee-script.js @@ -0,0 +1,8 @@ +/** + * CoffeeScript Compiler v1.2.0 + * http://coffeescript.org + * + * Copyright 2011, Jeremy Ashkenas + * Released under the MIT License + */ +(function(root){var CoffeeScript=function(){function require(a){return require[a]}require["./helpers"]=new function(){var a=this;(function(){var b,c;a.starts=function(a,b,c){return b===a.substr(c,b.length)},a.ends=function(a,b,c){var d;d=b.length;return b===a.substr(a.length-d-(c||0),d)},a.compact=function(a){var b,c,d,e;e=[];for(c=0,d=a.length;c=0)f+=1;else if(j=g[0],t.call(d,j)>=0)f-=1;a+=1}return a-1},a.prototype.removeLeadingNewlines=function(){var a,b,c,d;d=this.tokens;for(a=0,c=d.length;a=0)))return 1;d.splice(b,1);return 0})},a.prototype.closeOpenCalls=function(){var a,b;b=function(a,b){var c;return(c=a[0])===")"||c==="CALL_END"||a[0]==="OUTDENT"&&this.tag(b-1)===")"},a=function(a,b){return this.tokens[a[0]==="OUTDENT"?b-1:b][0]="CALL_END"};return this.scanTokens(function(c,d){c[0]==="CALL_START"&&this.detectEnd(d+1,b,a);return 1})},a.prototype.closeOpenIndexes=function(){var a,b;b=function(a,b){var c;return(c=a[0])==="]"||c==="INDEX_END"},a=function(a,b){return a[0]="INDEX_END"};return this.scanTokens(function(c,d){c[0]==="INDEX_START"&&this.detectEnd(d+1,b,a);return 1})},a.prototype.addImplicitBraces=function(){var a,b,c,f,g,i,j;f=[],g=null,j=null,c=!0,i=0,b=function(a,b){var d,e,f,g,i,k;i=this.tokens.slice(b+1,b+3+1||9e9),d=i[0],g=i[1],f=i[2];if("HERECOMMENT"===(d!=null?d[0]:void 0))return!1;e=a[0],t.call(l,e)>=0&&(c=!1);return(e==="TERMINATOR"||e==="OUTDENT"||t.call(h,e)>=0&&c)&&(!j&&this.tag(b-1)!==","||(g!=null?g[0]:void 0)!==":"&&((d!=null?d[0]:void 0)!=="@"||(f!=null?f[0]:void 0)!==":"))||e===","&&d&&(k=d[0])!=="IDENTIFIER"&&k!=="NUMBER"&&k!=="STRING"&&k!=="@"&&k!=="TERMINATOR"&&k!=="OUTDENT"},a=function(a,b){var c;c=["}","}",a[2]],c.generated=!0;return this.tokens.splice(b,0,c)};return this.scanTokens(function(h,i,k){var m,n,o,p,q,r,s,u;if(s=p=h[0],t.call(e,s)>=0){f.push([p==="INDENT"&&this.tag(i-1)==="{"?"{":p,i]);return 1}if(t.call(d,p)>=0){g=f.pop();return 1}if(p!==":"||(m=this.tag(i-2))!==":"&&((u=f[f.length-1])!=null?u[0]:void 0)==="{")return 1;c=!0,f.push(["{"]),n=m==="@"?i-2:i-1;while(this.tag(n-2)==="HERECOMMENT")n-=2;o=this.tag(n-1),j=!o||t.call(l,o)>=0,r=new String("{"),r.generated=!0,q=["{",r,h[2]],q.generated=!0,k.splice(n,0,q),this.detectEnd(i+2,b,a);return 2})},a.prototype.addImplicitParentheses=function(){var a,b,c,d,e;c=e=d=!1,b=function(a,b){var c,g,i,j;g=a[0];if(!e&&a.fromThen)return!0;if(g==="IF"||g==="ELSE"||g==="CATCH"||g==="->"||g==="=>"||g==="CLASS")e=!0;if(g==="IF"||g==="ELSE"||g==="SWITCH"||g==="TRY"||g==="=")d=!0;if((g==="."||g==="?."||g==="::")&&this.tag(b-1)==="OUTDENT")return!0;return!a.generated&&this.tag(b-1)!==","&&(t.call(h,g)>=0||g==="INDENT"&&!d)&&(g!=="INDENT"||(i=this.tag(b-2))!=="CLASS"&&i!=="EXTENDS"&&(j=this.tag(b-1),t.call(f,j)<0)&&(!(c=this.tokens[b+1])||!c.generated||c[0]!=="{"))},a=function(a,b){return this.tokens.splice(b,0,["CALL_END",")",a[2]])};return this.scanTokens(function(f,h,k){var m,n,o,p,q,r,s,u;q=f[0];if(q==="CLASS"||q==="IF")c=!0;r=k.slice(h-1,h+1+1||9e9),p=r[0],n=r[1],o=r[2],m=!c&&q==="INDENT"&&o&&o.generated&&o[0]==="{"&&p&&(s=p[0],t.call(i,s)>=0),e=!1,d=!1,t.call(l,q)>=0&&(c=!1),p&&!p.spaced&&q==="?"&&(f.call=!0);if(f.fromThen)return 1;if(!(m||(p!=null?p.spaced:void 0)&&(p.call||(u=p[0],t.call(i,u)>=0))&&(t.call(g,q)>=0||!f.spaced&&!f.newLine&&t.call(j,q)>=0)))return 1;k.splice(h,0,["CALL_START","(",f[2]]),this.detectEnd(h+1,b,a),p[0]==="?"&&(p[0]="FUNC_EXIST");return 2})},a.prototype.addImplicitIndentation=function(){var a,b,c,d,e;e=c=d=null,b=function(a,b){var c;return a[1]!==";"&&(c=a[0],t.call(m,c)>=0)&&(a[0]!=="ELSE"||e==="IF"||e==="THEN")},a=function(a,b){return this.tokens.splice(this.tag(b-1)===","?b-1:b,0,d)};return this.scanTokens(function(f,g,h){var i,j,k;i=f[0];if(i==="TERMINATOR"&&this.tag(g+1)==="THEN"){h.splice(g,1);return 0}if(i==="ELSE"&&this.tag(g-1)!=="OUTDENT"){h.splice.apply(h,[g,0].concat(u.call(this.indentation(f))));return 2}if(i==="CATCH"&&((j=this.tag(g+2))==="OUTDENT"||j==="TERMINATOR"||j==="FINALLY")){h.splice.apply(h,[g+2,0].concat(u.call(this.indentation(f))));return 4}if(t.call(n,i)>=0&&this.tag(g+1)!=="INDENT"&&(i!=="ELSE"||this.tag(g+1)!=="IF")){e=i,k=this.indentation(f),c=k[0],d=k[1],e==="THEN"&&(c.fromThen=!0),c.generated=d.generated=!0,h.splice(g+1,0,c),this.detectEnd(g+2,b,a),i==="THEN"&&h.splice(g,1);return 1}return 1})},a.prototype.tagPostfixConditionals=function(){var a,b,c;c=null,b=function(a,b){var c;return(c=a[0])==="TERMINATOR"||c==="INDENT"},a=function(a,b){if(a[0]!=="INDENT"||a.generated&&!a.fromThen)return c[0]="POST_"+c[0]};return this.scanTokens(function(d,e){if(d[0]!=="IF")return 1;c=d,this.detectEnd(e+1,b,a);return 1})},a.prototype.indentation=function(a){return[["INDENT",2,a[2]],["OUTDENT",2,a[2]]]},a.prototype.tag=function(a){var b;return(b=this.tokens[a])!=null?b[0]:void 0};return a}(),b=[["(",")"],["[","]"],["{","}"],["INDENT","OUTDENT"],["CALL_START","CALL_END"],["PARAM_START","PARAM_END"],["INDEX_START","INDEX_END"]],a.INVERSES=k={},e=[],d=[];for(q=0,r=b.length;q","=>","[","(","{","--","++"],j=["+","-"],f=["->","=>","{","[",","],h=["POST_IF","FOR","WHILE","UNTIL","WHEN","BY","LOOP","TERMINATOR"],n=["ELSE","->","=>","TRY","FINALLY","THEN"],m=["TERMINATOR","CATCH","FINALLY","ELSE","OUTDENT","LEADING_WHEN"],l=["TERMINATOR","INDENT","OUTDENT"]}).call(this)},require["./lexer"]=new function(){var a=this;(function(){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W=Array.prototype.indexOf||function(a){for(var b=0,c=this.length;b=0||W.call(g,c)>=0)&&(j=c.toUpperCase(),j==="WHEN"&&(l=this.tag(),W.call(v,l)>=0)?j="LEADING_WHEN":j==="FOR"?this.seenFor=!0:j==="UNLESS"?j="IF":W.call(N,j)>=0?j="UNARY":W.call(H,j)>=0&&(j!=="INSTANCEOF"&&this.seenFor?(j="FOR"+j,this.seenFor=!1):(j="RELATION",this.value()==="!"&&(this.tokens.pop(),c="!"+c)))),W.call(["eval","arguments"].concat(t),c)>=0&&(b?(j="IDENTIFIER",c=new String(c),c.reserved=!0):W.call(I,c)>=0&&this.error('reserved word "'+c+'"')),b||(W.call(e,c)>=0&&(c=f[c]),j=function(){switch(c){case"!":return"UNARY";case"==":case"!=":return"COMPARE";case"&&":case"||":return"LOGIC";case"true":case"false":case"null":case"undefined":return"BOOL";case"break":case"continue":return"STATEMENT";default:return j}}()),this.token(j,c),a&&this.token(":",":");return d.length},a.prototype.numberToken=function(){var a,b,c,d;if(!(c=E.exec(this.chunk)))return 0;d=c[0],b=d.length;if(a=/0b([01]+)/.exec(d))d=parseInt(a[1],2).toString();this.token("NUMBER",d);return b},a.prototype.stringToken=function(){var a,b;switch(this.chunk.charAt(0)){case"'":if(!(a=L.exec(this.chunk)))return 0;this.token("STRING",(b=a[0]).replace(A,"\\\n"));break;case'"':if(!(b=this.balancedString(this.chunk,'"')))return 0;0=0))return 0;if(!(c=G.exec(this.chunk)))return 0;g=c,c=g[0],e=g[1],a=g[2],e.slice(0,2)==="/*"&&this.error("regular expressions cannot begin with `*`"),e==="//"&&(e="/(?:)/"),this.token("REGEX",""+e+a);return c.length},a.prototype.heregexToken=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n;d=a[0],b=a[1],c=a[2];if(0>b.indexOf("#{")){e=b.replace(o,"").replace(/\//g,"\\/"),e.match(/^\*/)&&this.error("regular expressions cannot begin with `*`"),this.token("REGEX","/"+(e||"(?:)")+"/"+c);return d.length}this.token("IDENTIFIER","RegExp"),this.tokens.push(["CALL_START","("]),g=[],k=this.interpolateString(b,{regex:!0});for(i=0,j=k.length;ithis.indent){if(d){this.indebt=f-this.indent,this.suppressNewlines();return b.length}a=f-this.indent+this.outdebt,this.token("INDENT",a),this.indents.push(a),this.ends.push("OUTDENT"),this.outdebt=this.indebt=0}else this.indebt=0,this.outdentToken(this.indent-f,d);this.indent=f;return b.length},a.prototype.outdentToken=function(a,b){var c,d;while(a>0)d=this.indents.length-1,this.indents[d]===void 0?a=0:this.indents[d]===this.outdebt?(a-=this.outdebt,this.outdebt=0):this.indents[d]=0)&&this.error('reserved word "'+this.value()+"\" can't be assigned");if((h=b[1])==="||"||h==="&&"){b[0]="COMPOUND_ASSIGN",b[1]+="=";return f.length}}if(f===";")this.seenFor=!1,e="TERMINATOR";else if(W.call(z,f)>=0)e="MATH";else if(W.call(i,f)>=0)e="COMPARE";else if(W.call(j,f)>=0)e="COMPOUND_ASSIGN";else if(W.call(N,f)>=0)e="UNARY";else if(W.call(K,f)>=0)e="SHIFT";else if(W.call(x,f)>=0||f==="?"&&(b!=null?b.spaced:void 0))e="LOGIC";else if(b&&!b.spaced)if(f==="("&&(k=b[0],W.call(c,k)>=0))b[0]==="?"&&(b[0]="FUNC_EXIST"),e="CALL_START";else if(f==="["&&(l=b[0],W.call(q,l)>=0)){e="INDEX_START";switch(b[0]){case"?":b[0]="INDEX_SOAK"}}switch(f){case"(":case"{":case"[":this.ends.push(r[f]);break;case")":case"}":case"]":this.pair(f)}this.token(e,f);return f.length},a.prototype.sanitizeHeredoc=function(a,b){var c,d,e,f,g;e=b.indent,d=b.herecomment;if(d){l.test(a)&&this.error('block comment cannot contain "*/", starting');if(a.indexOf("\n")<=0)return a}else while(f=m.exec(a)){c=f[1];if(e===null||0<(g=c.length)&&gh;1<=h?c++:c--){switch(d=a.charAt(c)){case"\\":c++;continue;case b:g.pop();if(!g.length)return a.slice(0,c+1);b=g[g.length-1];continue}b!=="}"||d!=='"'&&d!=="'"?b==="}"&&d==="/"&&(e=n.exec(a.slice(c))||G.exec(a.slice(c)))?c+=e[0].length-1:b==="}"&&d==="{"?g.push(b="}"):b==='"'&&f==="#"&&d==="{"&&g.push(b="}"):g.push(b=d),f=d}return this.error("missing "+g.pop()+", starting")},a.prototype.interpolateString=function(b,c){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t;c==null&&(c={}),e=c.heredoc,m=c.regex,o=[],l=0,f=-1;while(j=b.charAt(f+=1)){if(j==="\\"){f+=1;continue}if(j!=="#"||b.charAt(f+1)!=="{"||!(d=this.balancedString(b.slice(f+1),"}")))continue;l1&&(k.unshift(["(","(",this.line]),k.push([")",")",this.line])),o.push(["TOKENS",k])}f+=d.length,l=f+1}f>l&&l1)&&this.token("(","(");for(f=0,q=o.length;f|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?\.|\.{2,3})/,O=/^[^\n\S]+/,h=/^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)?$)|^(?:\s*#(?!##[^#]).*)+/,d=/^[-=]>/,B=/^(?:\n[^\n\S]*)+/,L=/^'[^\\']*(?:\\.[^\\']*)*'/,s=/^`[^\\`]*(?:\\.[^\\`]*)*`/,G=/^(\/(?![\s=])[^[\/\n\\]*(?:(?:\\[\s\S]|\[[^\]\n\\]*(?:\\[\s\S][^\]\n\\]*)*])[^[\/\n\\]*)*\/)([imgy]{0,4})(?!\w)/,n=/^\/{3}([\s\S]+?)\/{3}([imgy]{0,4})(?!\w)/,o=/\s+(?:#.*)?/g,A=/\n/g,m=/\n+([^\n\S]*)/g,l=/\*\//,w=/^\s*(?:,|\??\.(?![.\d])|::)/,M=/\s+$/,j=["-=","+=","/=","*=","%=","||=","&&=","?=","<<=",">>=",">>>=","&=","^=","|="],N=["!","~","NEW","TYPEOF","DELETE","DO"],x=["&&","||","&","|","^"],K=["<<",">>",">>>"],i=["==","!=","<",">","<=",">="],z=["*","/","%"],H=["IN","OF","INSTANCEOF"],b=["TRUE","FALSE","NULL","UNDEFINED"],C=["NUMBER","REGEX","BOOL","++","--","]"],D=C.concat(")","}","THIS","IDENTIFIER","STRING"),c=["IDENTIFIER","STRING","REGEX",")","]","}","?","::","@","THIS","SUPER"],q=c.concat("NUMBER","BOOL"),v=["INDENT","OUTDENT","TERMINATOR"]}).call(this)},require["./parser"]=new function(){var a=this,b=function(){var a={trace:function(){},yy:{},symbols_:{error:2,Root:3,Body:4,Block:5,TERMINATOR:6,Line:7,Expression:8,Statement:9,Return:10,Comment:11,STATEMENT:12,Value:13,Invocation:14,Code:15,Operation:16,Assign:17,If:18,Try:19,While:20,For:21,Switch:22,Class:23,Throw:24,INDENT:25,OUTDENT:26,Identifier:27,IDENTIFIER:28,AlphaNumeric:29,NUMBER:30,STRING:31,Literal:32,JS:33,REGEX:34,DEBUGGER:35,BOOL:36,Assignable:37,"=":38,AssignObj:39,ObjAssignable:40,":":41,ThisProperty:42,RETURN:43,HERECOMMENT:44,PARAM_START:45,ParamList:46,PARAM_END:47,FuncGlyph:48,"->":49,"=>":50,OptComma:51,",":52,Param:53,ParamVar:54,"...":55,Array:56,Object:57,Splat:58,SimpleAssignable:59,Accessor:60,Parenthetical:61,Range:62,This:63,".":64,"?.":65,"::":66,Index:67,INDEX_START:68,IndexValue:69,INDEX_END:70,INDEX_SOAK:71,Slice:72,"{":73,AssignList:74,"}":75,CLASS:76,EXTENDS:77,OptFuncExist:78,Arguments:79,SUPER:80,FUNC_EXIST:81,CALL_START:82,CALL_END:83,ArgList:84,THIS:85,"@":86,"[":87,"]":88,RangeDots:89,"..":90,Arg:91,SimpleArgs:92,TRY:93,Catch:94,FINALLY:95,CATCH:96,THROW:97,"(":98,")":99,WhileSource:100,WHILE:101,WHEN:102,UNTIL:103,Loop:104,LOOP:105,ForBody:106,FOR:107,ForStart:108,ForSource:109,ForVariables:110,OWN:111,ForValue:112,FORIN:113,FOROF:114,BY:115,SWITCH:116,Whens:117,ELSE:118,When:119,LEADING_WHEN:120,IfBlock:121,IF:122,POST_IF:123,UNARY:124,"-":125,"+":126,"--":127,"++":128,"?":129,MATH:130,SHIFT:131,COMPARE:132,LOGIC:133,RELATION:134,COMPOUND_ASSIGN:135,$accept:0,$end:1},terminals_:{2:"error",6:"TERMINATOR",12:"STATEMENT",25:"INDENT",26:"OUTDENT",28:"IDENTIFIER",30:"NUMBER",31:"STRING",33:"JS",34:"REGEX",35:"DEBUGGER",36:"BOOL",38:"=",41:":",43:"RETURN",44:"HERECOMMENT",45:"PARAM_START",47:"PARAM_END",49:"->",50:"=>",52:",",55:"...",64:".",65:"?.",66:"::",68:"INDEX_START",70:"INDEX_END",71:"INDEX_SOAK",73:"{",75:"}",76:"CLASS",77:"EXTENDS",80:"SUPER",81:"FUNC_EXIST",82:"CALL_START",83:"CALL_END",85:"THIS",86:"@",87:"[",88:"]",90:"..",93:"TRY",95:"FINALLY",96:"CATCH",97:"THROW",98:"(",99:")",101:"WHILE",102:"WHEN",103:"UNTIL",105:"LOOP",107:"FOR",111:"OWN",113:"FORIN",114:"FOROF",115:"BY",116:"SWITCH",118:"ELSE",120:"LEADING_WHEN",122:"IF",123:"POST_IF",124:"UNARY",125:"-",126:"+",127:"--",128:"++",129:"?",130:"MATH",131:"SHIFT",132:"COMPARE",133:"LOGIC",134:"RELATION",135:"COMPOUND_ASSIGN"},productions_:[0,[3,0],[3,1],[3,2],[4,1],[4,3],[4,2],[7,1],[7,1],[9,1],[9,1],[9,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[5,2],[5,3],[27,1],[29,1],[29,1],[32,1],[32,1],[32,1],[32,1],[32,1],[17,3],[17,4],[17,5],[39,1],[39,3],[39,5],[39,1],[40,1],[40,1],[40,1],[10,2],[10,1],[11,1],[15,5],[15,2],[48,1],[48,1],[51,0],[51,1],[46,0],[46,1],[46,3],[53,1],[53,2],[53,3],[54,1],[54,1],[54,1],[54,1],[58,2],[59,1],[59,2],[59,2],[59,1],[37,1],[37,1],[37,1],[13,1],[13,1],[13,1],[13,1],[13,1],[60,2],[60,2],[60,2],[60,1],[60,1],[67,3],[67,2],[69,1],[69,1],[57,4],[74,0],[74,1],[74,3],[74,4],[74,6],[23,1],[23,2],[23,3],[23,4],[23,2],[23,3],[23,4],[23,5],[14,3],[14,3],[14,1],[14,2],[78,0],[78,1],[79,2],[79,4],[63,1],[63,1],[42,2],[56,2],[56,4],[89,1],[89,1],[62,5],[72,3],[72,2],[72,2],[84,1],[84,3],[84,4],[84,4],[84,6],[91,1],[91,1],[92,1],[92,3],[19,2],[19,3],[19,4],[19,5],[94,3],[24,2],[61,3],[61,5],[100,2],[100,4],[100,2],[100,4],[20,2],[20,2],[20,2],[20,1],[104,2],[104,2],[21,2],[21,2],[21,2],[106,2],[106,2],[108,2],[108,3],[112,1],[112,1],[112,1],[110,1],[110,3],[109,2],[109,2],[109,4],[109,4],[109,4],[109,6],[109,6],[22,5],[22,7],[22,4],[22,6],[117,1],[117,2],[119,3],[119,4],[121,3],[121,5],[18,1],[18,3],[18,3],[18,3],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,5],[16,3]],performAction:function(a,b,c,d,e,f,g){var h=f.length-1;switch(e){case 1:return this.$=new d.Block;case 2:return this.$=f[h];case 3:return this.$=f[h-1];case 4:this.$=d.Block.wrap([f[h]]);break;case 5:this.$=f[h-2].push(f[h]);break;case 6:this.$=f[h-1];break;case 7:this.$=f[h];break;case 8:this.$=f[h];break;case 9:this.$=f[h];break;case 10:this.$=f[h];break;case 11:this.$=new d.Literal(f[h]);break;case 12:this.$=f[h];break;case 13:this.$=f[h];break;case 14:this.$=f[h];break;case 15:this.$=f[h];break;case 16:this.$=f[h];break;case 17:this.$=f[h];break;case 18:this.$=f[h];break;case 19:this.$=f[h];break;case 20:this.$=f[h];break;case 21:this.$=f[h];break;case 22:this.$=f[h];break;case 23:this.$=f[h];break;case 24:this.$=new d.Block;break;case 25:this.$=f[h-1];break;case 26:this.$=new d.Literal(f[h]);break;case 27:this.$=new d.Literal(f[h]);break;case 28:this.$=new d.Literal(f[h]);break;case 29:this.$=f[h];break;case 30:this.$=new d.Literal(f[h]);break;case 31:this.$=new d.Literal(f[h]);break;case 32:this.$=new d.Literal(f[h]);break;case 33:this.$=function(){var a;a=new d.Literal(f[h]),f[h]==="undefined"&&(a.isUndefined=!0);return a}();break;case 34:this.$=new d.Assign(f[h-2],f[h]);break;case 35:this.$=new d.Assign(f[h-3],f[h]);break;case 36:this.$=new d.Assign(f[h-4],f[h-1]);break;case 37:this.$=new d.Value(f[h]);break;case 38:this.$=new d.Assign(new d.Value(f[h-2]),f[h],"object");break;case 39:this.$=new d.Assign(new d.Value(f[h-4]),f[h-1],"object");break;case 40:this.$=f[h];break;case 41:this.$=f[h];break;case 42:this.$=f[h];break;case 43:this.$=f[h];break;case 44:this.$=new d.Return(f[h]);break;case 45:this.$=new d.Return;break;case 46:this.$=new d.Comment(f[h]);break;case 47:this.$=new d.Code(f[h-3],f[h],f[h-1]);break;case 48:this.$=new d.Code([],f[h],f[h-1]);break;case 49:this.$="func";break;case 50:this.$="boundfunc";break;case 51:this.$=f[h];break;case 52:this.$=f[h];break;case 53:this.$=[];break;case 54:this.$=[f[h]];break;case 55:this.$=f[h-2].concat(f[h]);break;case 56:this.$=new d.Param(f[h]);break;case 57:this.$=new d.Param(f[h-1],null,!0);break;case 58:this.$=new d.Param(f[h-2],f[h]);break;case 59:this.$=f[h];break;case 60:this.$=f[h];break;case 61:this.$=f[h];break;case 62:this.$=f[h];break;case 63:this.$=new d.Splat(f[h-1]);break;case 64:this.$=new d.Value(f[h]);break;case 65:this.$=f[h-1].add(f[h]);break;case 66:this.$=new d.Value(f[h-1],[].concat(f[h]));break;case 67:this.$=f[h];break;case 68:this.$=f[h];break;case 69:this.$=new d.Value(f[h]);break;case 70:this.$=new d.Value(f[h]);break;case 71:this.$=f[h];break;case 72:this.$=new d.Value(f[h]);break;case 73:this.$=new d.Value(f[h]);break;case 74:this.$=new d.Value(f[h]);break;case 75:this.$=f[h];break;case 76:this.$=new d.Access(f[h]);break;case 77:this.$=new d.Access(f[h],"soak");break;case 78:this.$=[new d.Access(new d.Literal("prototype")),new d.Access(f[h])];break;case 79:this.$=new d.Access(new d.Literal("prototype"));break;case 80:this.$=f[h];break;case 81:this.$=f[h-1];break;case 82:this.$=d.extend(f[h],{soak:!0});break;case 83:this.$=new d.Index(f[h]);break;case 84:this.$=new d.Slice(f[h]);break;case 85:this.$=new d.Obj(f[h-2],f[h-3].generated);break;case 86:this.$=[];break;case 87:this.$=[f[h]];break;case 88:this.$=f[h-2].concat(f[h]);break;case 89:this.$=f[h-3].concat(f[h]);break;case 90:this.$=f[h-5].concat(f[h-2]);break;case 91:this.$=new d.Class;break;case 92:this.$=new d.Class(null,null,f[h]);break;case 93:this.$=new d.Class(null,f[h]);break;case 94:this.$=new d.Class(null,f[h-1],f[h]);break;case 95:this.$=new d.Class(f[h]);break;case 96:this.$=new d.Class(f[h-1],null,f[h]);break;case 97:this.$=new d.Class(f[h-2],f[h]);break;case 98:this.$=new d.Class(f[h-3],f[h-1],f[h]);break;case 99:this.$=new d.Call(f[h-2],f[h],f[h-1]);break;case 100:this.$=new d.Call(f[h-2],f[h],f[h-1]);break;case 101:this.$=new d.Call("super",[new d.Splat(new d.Literal("arguments"))]);break;case 102:this.$=new d.Call("super",f[h]);break;case 103:this.$=!1;break;case 104:this.$=!0;break;case 105:this.$=[];break;case 106:this.$=f[h-2];break;case 107:this.$=new d.Value(new d.Literal("this"));break;case 108:this.$=new d.Value(new d.Literal("this"));break;case 109:this.$=new d.Value(new d.Literal("this"),[new d.Access(f[h])],"this");break;case 110:this.$=new d.Arr([]);break;case 111:this.$=new d.Arr(f[h-2]);break;case 112:this.$="inclusive";break;case 113:this.$="exclusive";break;case 114:this.$=new d.Range(f[h-3],f[h-1],f[h-2]);break;case 115:this.$=new d.Range(f[h-2],f[h],f[h-1]);break;case 116:this.$=new d.Range(f[h-1],null,f[h]);break;case 117:this.$=new d.Range(null,f[h],f[h-1]);break;case 118:this.$=[f[h]];break;case 119:this.$=f[h-2].concat(f[h]);break;case 120:this.$=f[h-3].concat(f[h]);break;case 121:this.$=f[h-2];break;case 122:this.$=f[h-5].concat(f[h-2]);break;case 123:this.$=f[h];break;case 124:this.$=f[h];break;case 125:this.$=f[h];break;case 126:this.$=[].concat(f[h-2],f[h]);break;case 127:this.$=new d.Try(f[h]);break;case 128:this.$=new d.Try(f[h-1],f[h][0],f[h][1]);break;case 129:this.$=new d.Try(f[h-2],null,null,f[h]);break;case 130:this.$=new d.Try(f[h-3],f[h-2][0],f[h-2][1],f[h]);break;case 131:this.$=[f[h-1],f[h]];break;case 132:this.$=new d.Throw(f[h]);break;case 133:this.$=new d.Parens(f[h-1]);break;case 134:this.$=new d.Parens(f[h-2]);break;case 135:this.$=new d.While(f[h]);break;case 136:this.$=new d.While(f[h-2],{guard:f[h]});break;case 137:this.$=new d.While(f[h],{invert:!0});break;case 138:this.$=new d.While(f[h-2],{invert:!0,guard:f[h]});break;case 139:this.$=f[h-1].addBody(f[h]);break;case 140:this.$=f[h].addBody(d.Block.wrap([f[h-1]]));break;case 141:this.$=f[h].addBody(d.Block.wrap([f[h-1]]));break;case 142:this.$=f[h];break;case 143:this.$=(new d.While(new d.Literal("true"))).addBody(f[h]);break;case 144:this.$=(new d.While(new d.Literal("true"))).addBody(d.Block.wrap([f[h]]));break;case 145:this.$=new d.For(f[h-1],f[h]);break;case 146:this.$=new d.For(f[h-1],f[h]);break;case 147:this.$=new d.For(f[h],f[h-1]);break;case 148:this.$={source:new d.Value(f[h])};break;case 149:this.$=function(){f[h].own=f[h-1].own,f[h].name=f[h-1][0],f[h].index=f[h-1][1];return f[h]}();break;case 150:this.$=f[h];break;case 151:this.$=function(){f[h].own=!0;return f[h]}();break;case 152:this.$=f[h];break;case 153:this.$=new d.Value(f[h]);break;case 154:this.$=new d.Value(f[h]);break;case 155:this.$=[f[h]];break;case 156:this.$=[f[h-2],f[h]];break;case 157:this.$={source:f[h]};break;case 158:this.$={source:f[h],object:!0};break;case 159:this.$={source:f[h-2],guard:f[h]};break;case 160:this.$={source:f[h-2],guard:f[h],object:!0};break;case 161:this.$={source:f[h-2],step:f[h]};break;case 162:this.$={source:f[h-4],guard:f[h-2],step:f[h]};break;case 163:this.$={source:f[h-4],step:f[h-2],guard:f[h]};break;case 164:this.$=new d.Switch(f[h-3],f[h-1]);break;case 165:this.$=new d.Switch(f[h-5],f[h-3],f[h-1]);break;case 166:this.$=new d.Switch(null,f[h-1]);break;case 167:this.$=new d.Switch(null,f[h-3],f[h-1]);break;case 168:this.$=f[h];break;case 169:this.$=f[h-1].concat(f[h]);break;case 170:this.$=[[f[h-1],f[h]]];break;case 171:this.$=[[f[h-2],f[h-1]]];break;case 172:this.$=new d.If(f[h-1],f[h],{type:f[h-2]});break;case 173:this.$=f[h-4].addElse(new d.If(f[h-1],f[h],{type:f[h-2]}));break;case 174:this.$=f[h];break;case 175:this.$=f[h-2].addElse(f[h]);break;case 176:this.$=new d.If(f[h],d.Block.wrap([f[h-2]]),{type:f[h-1],statement:!0});break;case 177:this.$=new d.If(f[h],d.Block.wrap([f[h-2]]),{type:f[h-1],statement:!0});break;case 178:this.$=new d.Op(f[h-1],f[h]);break;case 179:this.$=new d.Op("-",f[h]);break;case 180:this.$=new d.Op("+",f[h]);break;case 181:this.$=new d.Op("--",f[h]);break;case 182:this.$=new d.Op("++",f[h]);break;case 183:this.$=new d.Op("--",f[h-1],null,!0);break;case 184:this.$=new d.Op("++",f[h-1],null,!0);break;case 185:this.$=new d.Existence(f[h-1]);break;case 186:this.$=new d.Op("+",f[h-2],f[h]);break;case 187:this.$=new d.Op("-",f[h-2],f[h]);break;case 188:this.$=new d.Op(f[h-1],f[h-2],f[h]);break;case 189:this.$=new d.Op(f[h-1],f[h-2],f[h]);break;case 190:this.$=new d.Op(f[h-1],f[h-2],f[h]);break;case 191:this.$=new d.Op(f[h-1],f[h-2],f[h]);break;case 192:this.$=function(){return f[h-1].charAt(0)==="!"?(new d.Op(f[h-1].slice(1),f[h-2],f[h])).invert():new d.Op(f[h-1],f[h-2],f[h])}();break;case 193:this.$=new d.Assign(f[h-2],f[h],f[h-1]);break;case 194:this.$=new d.Assign(f[h-4],f[h-1],f[h-3]);break;case 195:this.$=new d.Extends(f[h-2],f[h])}},table:[{1:[2,1],3:1,4:2,5:3,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,5],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[3]},{1:[2,2],6:[1,72]},{6:[1,73]},{1:[2,4],6:[2,4],26:[2,4],99:[2,4]},{4:75,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[1,74],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,7],6:[2,7],26:[2,7],99:[2,7],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,8],6:[2,8],26:[2,8],99:[2,8],100:88,101:[1,63],103:[1,64],106:89,107:[1,66],108:67,123:[1,87]},{1:[2,12],6:[2,12],25:[2,12],26:[2,12],47:[2,12],52:[2,12],55:[2,12],60:91,64:[1,93],65:[1,94],66:[1,95],67:96,68:[1,97],70:[2,12],71:[1,98],75:[2,12],78:90,81:[1,92],82:[2,103],83:[2,12],88:[2,12],90:[2,12],99:[2,12],101:[2,12],102:[2,12],103:[2,12],107:[2,12],115:[2,12],123:[2,12],125:[2,12],126:[2,12],129:[2,12],130:[2,12],131:[2,12],132:[2,12],133:[2,12],134:[2,12]},{1:[2,13],6:[2,13],25:[2,13],26:[2,13],47:[2,13],52:[2,13],55:[2,13],60:100,64:[1,93],65:[1,94],66:[1,95],67:96,68:[1,97],70:[2,13],71:[1,98],75:[2,13],78:99,81:[1,92],82:[2,103],83:[2,13],88:[2,13],90:[2,13],99:[2,13],101:[2,13],102:[2,13],103:[2,13],107:[2,13],115:[2,13],123:[2,13],125:[2,13],126:[2,13],129:[2,13],130:[2,13],131:[2,13],132:[2,13],133:[2,13],134:[2,13]},{1:[2,14],6:[2,14],25:[2,14],26:[2,14],47:[2,14],52:[2,14],55:[2,14],70:[2,14],75:[2,14],83:[2,14],88:[2,14],90:[2,14],99:[2,14],101:[2,14],102:[2,14],103:[2,14],107:[2,14],115:[2,14],123:[2,14],125:[2,14],126:[2,14],129:[2,14],130:[2,14],131:[2,14],132:[2,14],133:[2,14],134:[2,14]},{1:[2,15],6:[2,15],25:[2,15],26:[2,15],47:[2,15],52:[2,15],55:[2,15],70:[2,15],75:[2,15],83:[2,15],88:[2,15],90:[2,15],99:[2,15],101:[2,15],102:[2,15],103:[2,15],107:[2,15],115:[2,15],123:[2,15],125:[2,15],126:[2,15],129:[2,15],130:[2,15],131:[2,15],132:[2,15],133:[2,15],134:[2,15]},{1:[2,16],6:[2,16],25:[2,16],26:[2,16],47:[2,16],52:[2,16],55:[2,16],70:[2,16],75:[2,16],83:[2,16],88:[2,16],90:[2,16],99:[2,16],101:[2,16],102:[2,16],103:[2,16],107:[2,16],115:[2,16],123:[2,16],125:[2,16],126:[2,16],129:[2,16],130:[2,16],131:[2,16],132:[2,16],133:[2,16],134:[2,16]},{1:[2,17],6:[2,17],25:[2,17],26:[2,17],47:[2,17],52:[2,17],55:[2,17],70:[2,17],75:[2,17],83:[2,17],88:[2,17],90:[2,17],99:[2,17],101:[2,17],102:[2,17],103:[2,17],107:[2,17],115:[2,17],123:[2,17],125:[2,17],126:[2,17],129:[2,17],130:[2,17],131:[2,17],132:[2,17],133:[2,17],134:[2,17]},{1:[2,18],6:[2,18],25:[2,18],26:[2,18],47:[2,18],52:[2,18],55:[2,18],70:[2,18],75:[2,18],83:[2,18],88:[2,18],90:[2,18],99:[2,18],101:[2,18],102:[2,18],103:[2,18],107:[2,18],115:[2,18],123:[2,18],125:[2,18],126:[2,18],129:[2,18],130:[2,18],131:[2,18],132:[2,18],133:[2,18],134:[2,18]},{1:[2,19],6:[2,19],25:[2,19],26:[2,19],47:[2,19],52:[2,19],55:[2,19],70:[2,19],75:[2,19],83:[2,19],88:[2,19],90:[2,19],99:[2,19],101:[2,19],102:[2,19],103:[2,19],107:[2,19],115:[2,19],123:[2,19],125:[2,19],126:[2,19],129:[2,19],130:[2,19],131:[2,19],132:[2,19],133:[2,19],134:[2,19]},{1:[2,20],6:[2,20],25:[2,20],26:[2,20],47:[2,20],52:[2,20],55:[2,20],70:[2,20],75:[2,20],83:[2,20],88:[2,20],90:[2,20],99:[2,20],101:[2,20],102:[2,20],103:[2,20],107:[2,20],115:[2,20],123:[2,20],125:[2,20],126:[2,20],129:[2,20],130:[2,20],131:[2,20],132:[2,20],133:[2,20],134:[2,20]},{1:[2,21],6:[2,21],25:[2,21],26:[2,21],47:[2,21],52:[2,21],55:[2,21],70:[2,21],75:[2,21],83:[2,21],88:[2,21],90:[2,21],99:[2,21],101:[2,21],102:[2,21],103:[2,21],107:[2,21],115:[2,21],123:[2,21],125:[2,21],126:[2,21],129:[2,21],130:[2,21],131:[2,21],132:[2,21],133:[2,21],134:[2,21]},{1:[2,22],6:[2,22],25:[2,22],26:[2,22],47:[2,22],52:[2,22],55:[2,22],70:[2,22],75:[2,22],83:[2,22],88:[2,22],90:[2,22],99:[2,22],101:[2,22],102:[2,22],103:[2,22],107:[2,22],115:[2,22],123:[2,22],125:[2,22],126:[2,22],129:[2,22],130:[2,22],131:[2,22],132:[2,22],133:[2,22],134:[2,22]},{1:[2,23],6:[2,23],25:[2,23],26:[2,23],47:[2,23],52:[2,23],55:[2,23],70:[2,23],75:[2,23],83:[2,23],88:[2,23],90:[2,23],99:[2,23],101:[2,23],102:[2,23],103:[2,23],107:[2,23],115:[2,23],123:[2,23],125:[2,23],126:[2,23],129:[2,23],130:[2,23],131:[2,23],132:[2,23],133:[2,23],134:[2,23]},{1:[2,9],6:[2,9],26:[2,9],99:[2,9],101:[2,9],103:[2,9],107:[2,9],123:[2,9]},{1:[2,10],6:[2,10],26:[2,10],99:[2,10],101:[2,10],103:[2,10],107:[2,10],123:[2,10]},{1:[2,11],6:[2,11],26:[2,11],99:[2,11],101:[2,11],103:[2,11],107:[2,11],123:[2,11]},{1:[2,71],6:[2,71],25:[2,71],26:[2,71],38:[1,101],47:[2,71],52:[2,71],55:[2,71],64:[2,71],65:[2,71],66:[2,71],68:[2,71],70:[2,71],71:[2,71],75:[2,71],81:[2,71],82:[2,71],83:[2,71],88:[2,71],90:[2,71],99:[2,71],101:[2,71],102:[2,71],103:[2,71],107:[2,71],115:[2,71],123:[2,71],125:[2,71],126:[2,71],129:[2,71],130:[2,71],131:[2,71],132:[2,71],133:[2,71],134:[2,71]},{1:[2,72],6:[2,72],25:[2,72],26:[2,72],47:[2,72],52:[2,72],55:[2,72],64:[2,72],65:[2,72],66:[2,72],68:[2,72],70:[2,72],71:[2,72],75:[2,72],81:[2,72],82:[2,72],83:[2,72],88:[2,72],90:[2,72],99:[2,72],101:[2,72],102:[2,72],103:[2,72],107:[2,72],115:[2,72],123:[2,72],125:[2,72],126:[2,72],129:[2,72],130:[2,72],131:[2,72],132:[2,72],133:[2,72],134:[2,72]},{1:[2,73],6:[2,73],25:[2,73],26:[2,73],47:[2,73],52:[2,73],55:[2,73],64:[2,73],65:[2,73],66:[2,73],68:[2,73],70:[2,73],71:[2,73],75:[2,73],81:[2,73],82:[2,73],83:[2,73],88:[2,73],90:[2,73],99:[2,73],101:[2,73],102:[2,73],103:[2,73],107:[2,73],115:[2,73],123:[2,73],125:[2,73],126:[2,73],129:[2,73],130:[2,73],131:[2,73],132:[2,73],133:[2,73],134:[2,73]},{1:[2,74],6:[2,74],25:[2,74],26:[2,74],47:[2,74],52:[2,74],55:[2,74],64:[2,74],65:[2,74],66:[2,74],68:[2,74],70:[2,74],71:[2,74],75:[2,74],81:[2,74],82:[2,74],83:[2,74],88:[2,74],90:[2,74],99:[2,74],101:[2,74],102:[2,74],103:[2,74],107:[2,74],115:[2,74],123:[2,74],125:[2,74],126:[2,74],129:[2,74],130:[2,74],131:[2,74],132:[2,74],133:[2,74],134:[2,74]},{1:[2,75],6:[2,75],25:[2,75],26:[2,75],47:[2,75],52:[2,75],55:[2,75],64:[2,75],65:[2,75],66:[2,75],68:[2,75],70:[2,75],71:[2,75],75:[2,75],81:[2,75],82:[2,75],83:[2,75],88:[2,75],90:[2,75],99:[2,75],101:[2,75],102:[2,75],103:[2,75],107:[2,75],115:[2,75],123:[2,75],125:[2,75],126:[2,75],129:[2,75],130:[2,75],131:[2,75],132:[2,75],133:[2,75],134:[2,75]},{1:[2,101],6:[2,101],25:[2,101],26:[2,101],47:[2,101],52:[2,101],55:[2,101],64:[2,101],65:[2,101],66:[2,101],68:[2,101],70:[2,101],71:[2,101],75:[2,101],79:102,81:[2,101],82:[1,103],83:[2,101],88:[2,101],90:[2,101],99:[2,101],101:[2,101],102:[2,101],103:[2,101],107:[2,101],115:[2,101],123:[2,101],125:[2,101],126:[2,101],129:[2,101],130:[2,101],131:[2,101],132:[2,101],133:[2,101],134:[2,101]},{27:107,28:[1,71],42:108,46:104,47:[2,53],52:[2,53],53:105,54:106,56:109,57:110,73:[1,68],86:[1,111],87:[1,112]},{5:113,25:[1,5]},{8:114,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:116,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:117,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{13:119,14:120,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:121,42:61,56:47,57:48,59:118,61:25,62:26,63:27,73:[1,68],80:[1,28],85:[1,56],86:[1,57],87:[1,55],98:[1,54]},{13:119,14:120,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:121,42:61,56:47,57:48,59:122,61:25,62:26,63:27,73:[1,68],80:[1,28],85:[1,56],86:[1,57],87:[1,55],98:[1,54]},{1:[2,68],6:[2,68],25:[2,68],26:[2,68],38:[2,68],47:[2,68],52:[2,68],55:[2,68],64:[2,68],65:[2,68],66:[2,68],68:[2,68],70:[2,68],71:[2,68],75:[2,68],77:[1,126],81:[2,68],82:[2,68],83:[2,68],88:[2,68],90:[2,68],99:[2,68],101:[2,68],102:[2,68],103:[2,68],107:[2,68],115:[2,68],123:[2,68],125:[2,68],126:[2,68],127:[1,123],128:[1,124],129:[2,68],130:[2,68],131:[2,68],132:[2,68],133:[2,68],134:[2,68],135:[1,125]},{1:[2,174],6:[2,174],25:[2,174],26:[2,174],47:[2,174],52:[2,174],55:[2,174],70:[2,174],75:[2,174],83:[2,174],88:[2,174],90:[2,174],99:[2,174],101:[2,174],102:[2,174],103:[2,174],107:[2,174],115:[2,174],118:[1,127],123:[2,174],125:[2,174],126:[2,174],129:[2,174],130:[2,174],131:[2,174],132:[2,174],133:[2,174],134:[2,174]},{5:128,25:[1,5]},{5:129,25:[1,5]},{1:[2,142],6:[2,142],25:[2,142],26:[2,142],47:[2,142],52:[2,142],55:[2,142],70:[2,142],75:[2,142],83:[2,142],88:[2,142],90:[2,142],99:[2,142],101:[2,142],102:[2,142],103:[2,142],107:[2,142],115:[2,142],123:[2,142],125:[2,142],126:[2,142],129:[2,142],130:[2,142],131:[2,142],132:[2,142],133:[2,142],134:[2,142]},{5:130,25:[1,5]},{8:131,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,132],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,91],5:133,6:[2,91],13:119,14:120,25:[1,5],26:[2,91],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:121,42:61,47:[2,91],52:[2,91],55:[2,91],56:47,57:48,59:135,61:25,62:26,63:27,70:[2,91],73:[1,68],75:[2,91],77:[1,134],80:[1,28],83:[2,91],85:[1,56],86:[1,57],87:[1,55],88:[2,91],90:[2,91],98:[1,54],99:[2,91],101:[2,91],102:[2,91],103:[2,91],107:[2,91],115:[2,91],123:[2,91],125:[2,91],126:[2,91],129:[2,91],130:[2,91],131:[2,91],132:[2,91],133:[2,91],134:[2,91]},{8:136,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,45],6:[2,45],8:137,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[2,45],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],99:[2,45],100:39,101:[2,45],103:[2,45],104:40,105:[1,65],106:41,107:[2,45],108:67,116:[1,42],121:37,122:[1,62],123:[2,45],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,46],6:[2,46],25:[2,46],26:[2,46],52:[2,46],75:[2,46],99:[2,46],101:[2,46],103:[2,46],107:[2,46],123:[2,46]},{1:[2,69],6:[2,69],25:[2,69],26:[2,69],38:[2,69],47:[2,69],52:[2,69],55:[2,69],64:[2,69],65:[2,69],66:[2,69],68:[2,69],70:[2,69],71:[2,69],75:[2,69],81:[2,69],82:[2,69],83:[2,69],88:[2,69],90:[2,69],99:[2,69],101:[2,69],102:[2,69],103:[2,69],107:[2,69],115:[2,69],123:[2,69],125:[2,69],126:[2,69],129:[2,69],130:[2,69],131:[2,69],132:[2,69],133:[2,69],134:[2,69]},{1:[2,70],6:[2,70],25:[2,70],26:[2,70],38:[2,70],47:[2,70],52:[2,70],55:[2,70],64:[2,70],65:[2,70],66:[2,70],68:[2,70],70:[2,70],71:[2,70],75:[2,70],81:[2,70],82:[2,70],83:[2,70],88:[2,70],90:[2,70],99:[2,70],101:[2,70],102:[2,70],103:[2,70],107:[2,70],115:[2,70],123:[2,70],125:[2,70],126:[2,70],129:[2,70],130:[2,70],131:[2,70],132:[2,70],133:[2,70],134:[2,70]},{1:[2,29],6:[2,29],25:[2,29],26:[2,29],47:[2,29],52:[2,29],55:[2,29],64:[2,29],65:[2,29],66:[2,29],68:[2,29],70:[2,29],71:[2,29],75:[2,29],81:[2,29],82:[2,29],83:[2,29],88:[2,29],90:[2,29],99:[2,29],101:[2,29],102:[2,29],103:[2,29],107:[2,29],115:[2,29],123:[2,29],125:[2,29],126:[2,29],129:[2,29],130:[2,29],131:[2,29],132:[2,29],133:[2,29],134:[2,29]},{1:[2,30],6:[2,30],25:[2,30],26:[2,30],47:[2,30],52:[2,30],55:[2,30],64:[2,30],65:[2,30],66:[2,30],68:[2,30],70:[2,30],71:[2,30],75:[2,30],81:[2,30],82:[2,30],83:[2,30],88:[2,30],90:[2,30],99:[2,30],101:[2,30],102:[2,30],103:[2,30],107:[2,30],115:[2,30],123:[2,30],125:[2,30],126:[2,30],129:[2,30],130:[2,30],131:[2,30],132:[2,30],133:[2,30],134:[2,30]},{1:[2,31],6:[2,31],25:[2,31],26:[2,31],47:[2,31],52:[2,31],55:[2,31],64:[2,31],65:[2,31],66:[2,31],68:[2,31],70:[2,31],71:[2,31],75:[2,31],81:[2,31],82:[2,31],83:[2,31],88:[2,31],90:[2,31],99:[2,31],101:[2,31],102:[2,31],103:[2,31],107:[2,31],115:[2,31],123:[2,31],125:[2,31],126:[2,31],129:[2,31],130:[2,31],131:[2,31],132:[2,31],133:[2,31],134:[2,31]},{1:[2,32],6:[2,32],25:[2,32],26:[2,32],47:[2,32],52:[2,32],55:[2,32],64:[2,32],65:[2,32],66:[2,32],68:[2,32],70:[2,32],71:[2,32],75:[2,32],81:[2,32],82:[2,32],83:[2,32],88:[2,32],90:[2,32],99:[2,32],101:[2,32],102:[2,32],103:[2,32],107:[2,32],115:[2,32],123:[2,32],125:[2,32],126:[2,32],129:[2,32],130:[2,32],131:[2,32],132:[2,32],133:[2,32],134:[2,32]},{1:[2,33],6:[2,33],25:[2,33],26:[2,33],47:[2,33],52:[2,33],55:[2,33],64:[2,33],65:[2,33],66:[2,33],68:[2,33],70:[2,33],71:[2,33],75:[2,33],81:[2,33],82:[2,33],83:[2,33],88:[2,33],90:[2,33],99:[2,33],101:[2,33],102:[2,33],103:[2,33],107:[2,33],115:[2,33],123:[2,33],125:[2,33],126:[2,33],129:[2,33],130:[2,33],131:[2,33],132:[2,33],133:[2,33],134:[2,33]},{4:138,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,139],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:140,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,144],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,58:145,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],84:142,85:[1,56],86:[1,57],87:[1,55],88:[1,141],91:143,93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,107],6:[2,107],25:[2,107],26:[2,107],47:[2,107],52:[2,107],55:[2,107],64:[2,107],65:[2,107],66:[2,107],68:[2,107],70:[2,107],71:[2,107],75:[2,107],81:[2,107],82:[2,107],83:[2,107],88:[2,107],90:[2,107],99:[2,107],101:[2,107],102:[2,107],103:[2,107],107:[2,107],115:[2,107],123:[2,107],125:[2,107],126:[2,107],129:[2,107],130:[2,107],131:[2,107],132:[2,107],133:[2,107],134:[2,107]},{1:[2,108],6:[2,108],25:[2,108],26:[2,108],27:146,28:[1,71],47:[2,108],52:[2,108],55:[2,108],64:[2,108],65:[2,108],66:[2,108],68:[2,108],70:[2,108],71:[2,108],75:[2,108],81:[2,108],82:[2,108],83:[2,108],88:[2,108],90:[2,108],99:[2,108],101:[2,108],102:[2,108],103:[2,108],107:[2,108],115:[2,108],123:[2,108],125:[2,108],126:[2,108],129:[2,108],130:[2,108],131:[2,108],132:[2,108],133:[2,108],134:[2,108]},{25:[2,49]},{25:[2,50]},{1:[2,64],6:[2,64],25:[2,64],26:[2,64],38:[2,64],47:[2,64],52:[2,64],55:[2,64],64:[2,64],65:[2,64],66:[2,64],68:[2,64],70:[2,64],71:[2,64],75:[2,64],77:[2,64],81:[2,64],82:[2,64],83:[2,64],88:[2,64],90:[2,64],99:[2,64],101:[2,64],102:[2,64],103:[2,64],107:[2,64],115:[2,64],123:[2,64],125:[2,64],126:[2,64],127:[2,64],128:[2,64],129:[2,64],130:[2,64],131:[2,64],132:[2,64],133:[2,64],134:[2,64],135:[2,64]},{1:[2,67],6:[2,67],25:[2,67],26:[2,67],38:[2,67],47:[2,67],52:[2,67],55:[2,67],64:[2,67],65:[2,67],66:[2,67],68:[2,67],70:[2,67],71:[2,67],75:[2,67],77:[2,67],81:[2,67],82:[2,67],83:[2,67],88:[2,67],90:[2,67],99:[2,67],101:[2,67],102:[2,67],103:[2,67],107:[2,67],115:[2,67],123:[2,67],125:[2,67],126:[2,67],127:[2,67],128:[2,67],129:[2,67],130:[2,67],131:[2,67],132:[2,67],133:[2,67],134:[2,67],135:[2,67]},{8:147,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:148,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:149,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{5:150,8:151,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,5],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{27:156,28:[1,71],56:157,57:158,62:152,73:[1,68],87:[1,55],110:153,111:[1,154],112:155},{109:159,113:[1,160],114:[1,161]},{6:[2,86],11:165,25:[2,86],27:166,28:[1,71],29:167,30:[1,69],31:[1,70],39:163,40:164,42:168,44:[1,46],52:[2,86],74:162,75:[2,86],86:[1,111]},{1:[2,27],6:[2,27],25:[2,27],26:[2,27],41:[2,27],47:[2,27],52:[2,27],55:[2,27],64:[2,27],65:[2,27],66:[2,27],68:[2,27],70:[2,27],71:[2,27],75:[2,27],81:[2,27],82:[2,27],83:[2,27],88:[2,27],90:[2,27],99:[2,27],101:[2,27],102:[2,27],103:[2,27],107:[2,27],115:[2,27],123:[2,27],125:[2,27],126:[2,27],129:[2,27],130:[2,27],131:[2,27],132:[2,27],133:[2,27],134:[2,27]},{1:[2,28],6:[2,28],25:[2,28],26:[2,28],41:[2,28],47:[2,28],52:[2,28],55:[2,28],64:[2,28],65:[2,28],66:[2,28],68:[2,28],70:[2,28],71:[2,28],75:[2,28],81:[2,28],82:[2,28],83:[2,28],88:[2,28],90:[2,28],99:[2,28],101:[2,28],102:[2,28],103:[2,28],107:[2,28],115:[2,28],123:[2,28],125:[2,28],126:[2,28],129:[2,28],130:[2,28],131:[2,28],132:[2,28],133:[2,28],134:[2,28]},{1:[2,26],6:[2,26],25:[2,26],26:[2,26],38:[2,26],41:[2,26],47:[2,26],52:[2,26],55:[2,26],64:[2,26],65:[2,26],66:[2,26],68:[2,26],70:[2,26],71:[2,26],75:[2,26],77:[2,26],81:[2,26],82:[2,26],83:[2,26],88:[2,26],90:[2,26],99:[2,26],101:[2,26],102:[2,26],103:[2,26],107:[2,26],113:[2,26],114:[2,26],115:[2,26],123:[2,26],125:[2,26],126:[2,26],127:[2,26],128:[2,26],129:[2,26],130:[2,26],131:[2,26],132:[2,26],133:[2,26],134:[2,26],135:[2,26]},{1:[2,6],6:[2,6],7:169,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[2,6],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],99:[2,6],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,3]},{1:[2,24],6:[2,24],25:[2,24],26:[2,24],47:[2,24],52:[2,24],55:[2,24],70:[2,24],75:[2,24],83:[2,24],88:[2,24],90:[2,24],95:[2,24],96:[2,24],99:[2,24],101:[2,24],102:[2,24],103:[2,24],107:[2,24],115:[2,24],118:[2,24],120:[2,24],123:[2,24],125:[2,24],126:[2,24],129:[2,24],130:[2,24],131:[2,24],132:[2,24],133:[2,24],134:[2,24]},{6:[1,72],26:[1,170]},{1:[2,185],6:[2,185],25:[2,185],26:[2,185],47:[2,185],52:[2,185],55:[2,185],70:[2,185],75:[2,185],83:[2,185],88:[2,185],90:[2,185],99:[2,185],101:[2,185],102:[2,185],103:[2,185],107:[2,185],115:[2,185],123:[2,185],125:[2,185],126:[2,185],129:[2,185],130:[2,185],131:[2,185],132:[2,185],133:[2,185],134:[2,185]},{8:171,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:172,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:173,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:174,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:175,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:176,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:177,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:178,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,141],6:[2,141],25:[2,141],26:[2,141],47:[2,141],52:[2,141],55:[2,141],70:[2,141],75:[2,141],83:[2,141],88:[2,141],90:[2,141],99:[2,141],101:[2,141],102:[2,141],103:[2,141],107:[2,141],115:[2,141],123:[2,141],125:[2,141],126:[2,141],129:[2,141],130:[2,141],131:[2,141],132:[2,141],133:[2,141],134:[2,141]},{1:[2,146],6:[2,146],25:[2,146],26:[2,146],47:[2,146],52:[2,146],55:[2,146],70:[2,146],75:[2,146],83:[2,146],88:[2,146],90:[2,146],99:[2,146],101:[2,146],102:[2,146],103:[2,146],107:[2,146],115:[2,146],123:[2,146],125:[2,146],126:[2,146],129:[2,146],130:[2,146],131:[2,146],132:[2,146],133:[2,146],134:[2,146]},{8:179,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,140],6:[2,140],25:[2,140],26:[2,140],47:[2,140],52:[2,140],55:[2,140],70:[2,140],75:[2,140],83:[2,140],88:[2,140],90:[2,140],99:[2,140],101:[2,140],102:[2,140],103:[2,140],107:[2,140],115:[2,140],123:[2,140],125:[2,140],126:[2,140],129:[2,140],130:[2,140],131:[2,140],132:[2,140],133:[2,140],134:[2,140]},{1:[2,145],6:[2,145],25:[2,145],26:[2,145],47:[2,145],52:[2,145],55:[2,145],70:[2,145],75:[2,145],83:[2,145],88:[2,145],90:[2,145],99:[2,145],101:[2,145],102:[2,145],103:[2,145],107:[2,145],115:[2,145],123:[2,145],125:[2,145],126:[2,145],129:[2,145],130:[2,145],131:[2,145],132:[2,145],133:[2,145],134:[2,145]},{79:180,82:[1,103]},{1:[2,65],6:[2,65],25:[2,65],26:[2,65],38:[2,65],47:[2,65],52:[2,65],55:[2,65],64:[2,65],65:[2,65],66:[2,65],68:[2,65],70:[2,65],71:[2,65],75:[2,65],77:[2,65],81:[2,65],82:[2,65],83:[2,65],88:[2,65],90:[2,65],99:[2,65],101:[2,65],102:[2,65],103:[2,65],107:[2,65],115:[2,65],123:[2,65],125:[2,65],126:[2,65],127:[2,65],128:[2,65],129:[2,65],130:[2,65],131:[2,65],132:[2,65],133:[2,65],134:[2,65],135:[2,65]},{82:[2,104]},{27:181,28:[1,71]},{27:182,28:[1,71]},{1:[2,79],6:[2,79],25:[2,79],26:[2,79],27:183,28:[1,71],38:[2,79],47:[2,79],52:[2,79],55:[2,79],64:[2,79],65:[2,79],66:[2,79],68:[2,79],70:[2,79],71:[2,79],75:[2,79],77:[2,79],81:[2,79],82:[2,79],83:[2,79],88:[2,79],90:[2,79],99:[2,79],101:[2,79],102:[2,79],103:[2,79],107:[2,79],115:[2,79],123:[2,79],125:[2,79],126:[2,79],127:[2,79],128:[2,79],129:[2,79],130:[2,79],131:[2,79],132:[2,79],133:[2,79],134:[2,79],135:[2,79]},{1:[2,80],6:[2,80],25:[2,80],26:[2,80],38:[2,80],47:[2,80],52:[2,80],55:[2,80],64:[2,80],65:[2,80],66:[2,80],68:[2,80],70:[2,80],71:[2,80],75:[2,80],77:[2,80],81:[2,80],82:[2,80],83:[2,80],88:[2,80],90:[2,80],99:[2,80],101:[2,80],102:[2,80],103:[2,80],107:[2,80],115:[2,80],123:[2,80],125:[2,80],126:[2,80],127:[2,80],128:[2,80],129:[2,80],130:[2,80],131:[2,80],132:[2,80],133:[2,80],134:[2,80],135:[2,80]},{8:185,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],55:[1,189],56:47,57:48,59:36,61:25,62:26,63:27,69:184,72:186,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],89:187,90:[1,188],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{67:190,68:[1,97],71:[1,98]},{79:191,82:[1,103]},{1:[2,66],6:[2,66],25:[2,66],26:[2,66],38:[2,66],47:[2,66],52:[2,66],55:[2,66],64:[2,66],65:[2,66],66:[2,66],68:[2,66],70:[2,66],71:[2,66],75:[2,66],77:[2,66],81:[2,66],82:[2,66],83:[2,66],88:[2,66],90:[2,66],99:[2,66],101:[2,66],102:[2,66],103:[2,66],107:[2,66],115:[2,66],123:[2,66],125:[2,66],126:[2,66],127:[2,66],128:[2,66],129:[2,66],130:[2,66],131:[2,66],132:[2,66],133:[2,66],134:[2,66],135:[2,66]},{6:[1,193],8:192,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,194],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,102],6:[2,102],25:[2,102],26:[2,102],47:[2,102],52:[2,102],55:[2,102],64:[2,102],65:[2,102],66:[2,102],68:[2,102],70:[2,102],71:[2,102],75:[2,102],81:[2,102],82:[2,102],83:[2,102],88:[2,102],90:[2,102],99:[2,102],101:[2,102],102:[2,102],103:[2,102],107:[2,102],115:[2,102],123:[2,102],125:[2,102],126:[2,102],129:[2,102],130:[2,102],131:[2,102],132:[2,102],133:[2,102],134:[2,102]},{8:197,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,144],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,58:145,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],83:[1,195],84:196,85:[1,56],86:[1,57],87:[1,55],91:143,93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{47:[1,198],52:[1,199]},{47:[2,54],52:[2,54]},{38:[1,201],47:[2,56],52:[2,56],55:[1,200]},{38:[2,59],47:[2,59],52:[2,59],55:[2,59]},{38:[2,60],47:[2,60],52:[2,60],55:[2,60]},{38:[2,61],47:[2,61],52:[2,61],55:[2,61]},{38:[2,62],47:[2,62],52:[2,62],55:[2,62]},{27:146,28:[1,71]},{8:197,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,144],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,58:145,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],84:142,85:[1,56],86:[1,57],87:[1,55],88:[1,141],91:143,93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,48],6:[2,48],25:[2,48],26:[2,48],47:[2,48],52:[2,48],55:[2,48],70:[2,48],75:[2,48],83:[2,48],88:[2,48],90:[2,48],99:[2,48],101:[2,48],102:[2,48],103:[2,48],107:[2,48],115:[2,48],123:[2,48],125:[2,48],126:[2,48],129:[2,48],130:[2,48],131:[2,48],132:[2,48],133:[2,48],134:[2,48]},{1:[2,178],6:[2,178],25:[2,178],26:[2,178],47:[2,178],52:[2,178],55:[2,178],70:[2,178],75:[2,178],83:[2,178],88:[2,178],90:[2,178],99:[2,178],100:85,101:[2,178],102:[2,178],103:[2,178],106:86,107:[2,178],108:67,115:[2,178],123:[2,178],125:[2,178],126:[2,178],129:[1,76],130:[2,178],131:[2,178],132:[2,178],133:[2,178],134:[2,178]},{100:88,101:[1,63],103:[1,64],106:89,107:[1,66],108:67,123:[1,87]},{1:[2,179],6:[2,179],25:[2,179],26:[2,179],47:[2,179],52:[2,179],55:[2,179],70:[2,179],75:[2,179],83:[2,179],88:[2,179],90:[2,179],99:[2,179],100:85,101:[2,179],102:[2,179],103:[2,179],106:86,107:[2,179],108:67,115:[2,179],123:[2,179],125:[2,179],126:[2,179],129:[1,76],130:[2,179],131:[2,179],132:[2,179],133:[2,179],134:[2,179]},{1:[2,180],6:[2,180],25:[2,180],26:[2,180],47:[2,180],52:[2,180],55:[2,180],70:[2,180],75:[2,180],83:[2,180],88:[2,180],90:[2,180],99:[2,180],100:85,101:[2,180],102:[2,180],103:[2,180],106:86,107:[2,180],108:67,115:[2,180],123:[2,180],125:[2,180],126:[2,180],129:[1,76],130:[2,180],131:[2,180],132:[2,180],133:[2,180],134:[2,180]},{1:[2,181],6:[2,181],25:[2,181],26:[2,181],47:[2,181],52:[2,181],55:[2,181],64:[2,68],65:[2,68],66:[2,68],68:[2,68],70:[2,181],71:[2,68],75:[2,181],81:[2,68],82:[2,68],83:[2,181],88:[2,181],90:[2,181],99:[2,181],101:[2,181],102:[2,181],103:[2,181],107:[2,181],115:[2,181],123:[2,181],125:[2,181],126:[2,181],129:[2,181],130:[2,181],131:[2,181],132:[2,181],133:[2,181],134:[2,181]},{60:91,64:[1,93],65:[1,94],66:[1,95],67:96,68:[1,97],71:[1,98],78:90,81:[1,92],82:[2,103]},{60:100,64:[1,93],65:[1,94],66:[1,95],67:96,68:[1,97],71:[1,98],78:99,81:[1,92],82:[2,103]},{64:[2,71],65:[2,71],66:[2,71],68:[2,71],71:[2,71],81:[2,71],82:[2,71]},{1:[2,182],6:[2,182],25:[2,182],26:[2,182],47:[2,182],52:[2,182],55:[2,182],64:[2,68],65:[2,68],66:[2,68],68:[2,68],70:[2,182],71:[2,68],75:[2,182],81:[2,68],82:[2,68],83:[2,182],88:[2,182],90:[2,182],99:[2,182],101:[2,182],102:[2,182],103:[2,182],107:[2,182],115:[2,182],123:[2,182],125:[2,182],126:[2,182],129:[2,182],130:[2,182],131:[2,182],132:[2,182],133:[2,182],134:[2,182]},{1:[2,183],6:[2,183],25:[2,183],26:[2,183],47:[2,183],52:[2,183],55:[2,183],70:[2,183],75:[2,183],83:[2,183],88:[2,183],90:[2,183],99:[2,183],101:[2,183],102:[2,183],103:[2,183],107:[2,183],115:[2,183],123:[2,183],125:[2,183],126:[2,183],129:[2,183],130:[2,183],131:[2,183],132:[2,183],133:[2,183],134:[2,183]},{1:[2,184],6:[2,184],25:[2,184],26:[2,184],47:[2,184],52:[2,184],55:[2,184],70:[2,184],75:[2,184],83:[2,184],88:[2,184],90:[2,184],99:[2,184],101:[2,184],102:[2,184],103:[2,184],107:[2,184],115:[2,184],123:[2,184],125:[2,184],126:[2,184],129:[2,184],130:[2,184],131:[2,184],132:[2,184],133:[2,184],134:[2,184]},{8:202,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,203],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:204,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{5:205,25:[1,5],122:[1,206]},{1:[2,127],6:[2,127],25:[2,127],26:[2,127],47:[2,127],52:[2,127],55:[2,127],70:[2,127],75:[2,127],83:[2,127],88:[2,127],90:[2,127],94:207,95:[1,208],96:[1,209],99:[2,127],101:[2,127],102:[2,127],103:[2,127],107:[2,127],115:[2,127],123:[2,127],125:[2,127],126:[2,127],129:[2,127],130:[2,127],131:[2,127],132:[2,127],133:[2,127],134:[2,127]},{1:[2,139],6:[2,139],25:[2,139],26:[2,139],47:[2,139],52:[2,139],55:[2,139],70:[2,139],75:[2,139],83:[2,139],88:[2,139],90:[2,139],99:[2,139],101:[2,139],102:[2,139],103:[2,139],107:[2,139],115:[2,139],123:[2,139],125:[2,139],126:[2,139],129:[2,139],130:[2,139],131:[2,139],132:[2,139],133:[2,139],134:[2,139]},{1:[2,147],6:[2,147],25:[2,147],26:[2,147],47:[2,147],52:[2,147],55:[2,147],70:[2,147],75:[2,147],83:[2,147],88:[2,147],90:[2,147],99:[2,147],101:[2,147],102:[2,147],103:[2,147],107:[2,147],115:[2,147],123:[2,147],125:[2,147],126:[2,147],129:[2,147],130:[2,147],131:[2,147],132:[2,147],133:[2,147],134:[2,147]},{25:[1,210],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{117:211,119:212,120:[1,213]},{1:[2,92],6:[2,92],25:[2,92],26:[2,92],47:[2,92],52:[2,92],55:[2,92],70:[2,92],75:[2,92],83:[2,92],88:[2,92],90:[2,92],99:[2,92],101:[2,92],102:[2,92],103:[2,92],107:[2,92],115:[2,92],123:[2,92],125:[2,92],126:[2,92],129:[2,92],130:[2,92],131:[2,92],132:[2,92],133:[2,92],134:[2,92]},{8:214,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,95],5:215,6:[2,95],25:[1,5],26:[2,95],47:[2,95],52:[2,95],55:[2,95],64:[2,68],65:[2,68],66:[2,68],68:[2,68],70:[2,95],71:[2,68],75:[2,95],77:[1,216],81:[2,68],82:[2,68],83:[2,95],88:[2,95],90:[2,95],99:[2,95],101:[2,95],102:[2,95],103:[2,95],107:[2,95],115:[2,95],123:[2,95],125:[2,95],126:[2,95],129:[2,95],130:[2,95],131:[2,95],132:[2,95],133:[2,95],134:[2,95]},{1:[2,132],6:[2,132],25:[2,132],26:[2,132],47:[2,132],52:[2,132],55:[2,132],70:[2,132],75:[2,132],83:[2,132],88:[2,132],90:[2,132],99:[2,132],100:85,101:[2,132],102:[2,132],103:[2,132],106:86,107:[2,132],108:67,115:[2,132],123:[2,132],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,44],6:[2,44],26:[2,44],99:[2,44],100:85,101:[2,44],103:[2,44],106:86,107:[2,44],108:67,123:[2,44],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{6:[1,72],99:[1,217]},{4:218,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{6:[2,123],25:[2,123],52:[2,123],55:[1,220],88:[2,123],89:219,90:[1,188],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,110],6:[2,110],25:[2,110],26:[2,110],38:[2,110],47:[2,110],52:[2,110],55:[2,110],64:[2,110],65:[2,110],66:[2,110],68:[2,110],70:[2,110],71:[2,110],75:[2,110],81:[2,110],82:[2,110],83:[2,110],88:[2,110],90:[2,110],99:[2,110],101:[2,110],102:[2,110],103:[2,110],107:[2,110],113:[2,110],114:[2,110],115:[2,110],123:[2,110],125:[2,110],126:[2,110],129:[2,110],130:[2,110],131:[2,110],132:[2,110],133:[2,110],134:[2,110]},{6:[2,51],25:[2,51],51:221,52:[1,222],88:[2,51]},{6:[2,118],25:[2,118],26:[2,118],52:[2,118],83:[2,118],88:[2,118]},{8:197,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,144],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,58:145,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],84:223,85:[1,56],86:[1,57],87:[1,55],91:143,93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{6:[2,124],25:[2,124],26:[2,124],52:[2,124],83:[2,124],88:[2,124]},{1:[2,109],6:[2,109],25:[2,109],26:[2,109],38:[2,109],41:[2,109],47:[2,109],52:[2,109],55:[2,109],64:[2,109],65:[2,109],66:[2,109],68:[2,109],70:[2,109],71:[2,109],75:[2,109],77:[2,109],81:[2,109],82:[2,109],83:[2,109],88:[2,109],90:[2,109],99:[2,109],101:[2,109],102:[2,109],103:[2,109],107:[2,109],115:[2,109],123:[2,109],125:[2,109],126:[2,109],127:[2,109],128:[2,109],129:[2,109],130:[2,109],131:[2,109],132:[2,109],133:[2,109],134:[2,109],135:[2,109]},{5:224,25:[1,5],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,135],6:[2,135],25:[2,135],26:[2,135],47:[2,135],52:[2,135],55:[2,135],70:[2,135],75:[2,135],83:[2,135],88:[2,135],90:[2,135],99:[2,135],100:85,101:[1,63],102:[1,225],103:[1,64],106:86,107:[1,66],108:67,115:[2,135],123:[2,135],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,137],6:[2,137],25:[2,137],26:[2,137],47:[2,137],52:[2,137],55:[2,137],70:[2,137],75:[2,137],83:[2,137],88:[2,137],90:[2,137],99:[2,137],100:85,101:[1,63],102:[1,226],103:[1,64],106:86,107:[1,66],108:67,115:[2,137],123:[2,137],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,143],6:[2,143],25:[2,143],26:[2,143],47:[2,143],52:[2,143],55:[2,143],70:[2,143],75:[2,143],83:[2,143],88:[2,143],90:[2,143],99:[2,143],101:[2,143],102:[2,143],103:[2,143],107:[2,143],115:[2,143],123:[2,143],125:[2,143],126:[2,143],129:[2,143],130:[2,143],131:[2,143],132:[2,143],133:[2,143],134:[2,143]},{1:[2,144],6:[2,144],25:[2,144],26:[2,144],47:[2,144],52:[2,144],55:[2,144],70:[2,144],75:[2,144],83:[2,144],88:[2,144],90:[2,144],99:[2,144],100:85,101:[1,63],102:[2,144],103:[1,64],106:86,107:[1,66],108:67,115:[2,144],123:[2,144],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,148],6:[2,148],25:[2,148],26:[2,148],47:[2,148],52:[2,148],55:[2,148],70:[2,148],75:[2,148],83:[2,148],88:[2,148],90:[2,148],99:[2,148],101:[2,148],102:[2,148],103:[2,148],107:[2,148],115:[2,148],123:[2,148],125:[2,148],126:[2,148],129:[2,148],130:[2,148],131:[2,148],132:[2,148],133:[2,148],134:[2,148]},{113:[2,150],114:[2,150]},{27:156,28:[1,71],56:157,57:158,73:[1,68],87:[1,112],110:227,112:155},{52:[1,228],113:[2,155],114:[2,155]},{52:[2,152],113:[2,152],114:[2,152]},{52:[2,153],113:[2,153],114:[2,153]},{52:[2,154],113:[2,154],114:[2,154]},{1:[2,149],6:[2,149],25:[2,149],26:[2,149],47:[2,149],52:[2,149],55:[2,149],70:[2,149],75:[2,149],83:[2,149],88:[2,149],90:[2,149],99:[2,149],101:[2,149],102:[2,149],103:[2,149],107:[2,149],115:[2,149],123:[2,149],125:[2,149],126:[2,149],129:[2,149],130:[2,149],131:[2,149],132:[2,149],133:[2,149],134:[2,149]},{8:229,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:230,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{6:[2,51],25:[2,51],51:231,52:[1,232],75:[2,51]},{6:[2,87],25:[2,87],26:[2,87],52:[2,87],75:[2,87]},{6:[2,37],25:[2,37],26:[2,37],41:[1,233],52:[2,37],75:[2,37]},{6:[2,40],25:[2,40],26:[2,40],52:[2,40],75:[2,40]},{6:[2,41],25:[2,41],26:[2,41],41:[2,41],52:[2,41],75:[2,41]},{6:[2,42],25:[2,42],26:[2,42],41:[2,42],52:[2,42],75:[2,42]},{6:[2,43],25:[2,43],26:[2,43],41:[2,43],52:[2,43],75:[2,43]},{1:[2,5],6:[2,5],26:[2,5],99:[2,5]},{1:[2,25],6:[2,25],25:[2,25],26:[2,25],47:[2,25],52:[2,25],55:[2,25],70:[2,25],75:[2,25],83:[2,25],88:[2,25],90:[2,25],95:[2,25],96:[2,25],99:[2,25],101:[2,25],102:[2,25],103:[2,25],107:[2,25],115:[2,25],118:[2,25],120:[2,25],123:[2,25],125:[2,25],126:[2,25],129:[2,25],130:[2,25],131:[2,25],132:[2,25],133:[2,25],134:[2,25]},{1:[2,186],6:[2,186],25:[2,186],26:[2,186],47:[2,186],52:[2,186],55:[2,186],70:[2,186],75:[2,186],83:[2,186],88:[2,186],90:[2,186],99:[2,186],100:85,101:[2,186],102:[2,186],103:[2,186],106:86,107:[2,186],108:67,115:[2,186],123:[2,186],125:[2,186],126:[2,186],129:[1,76],130:[1,79],131:[2,186],132:[2,186],133:[2,186],134:[2,186]},{1:[2,187],6:[2,187],25:[2,187],26:[2,187],47:[2,187],52:[2,187],55:[2,187],70:[2,187],75:[2,187],83:[2,187],88:[2,187],90:[2,187],99:[2,187],100:85,101:[2,187],102:[2,187],103:[2,187],106:86,107:[2,187],108:67,115:[2,187],123:[2,187],125:[2,187],126:[2,187],129:[1,76],130:[1,79],131:[2,187],132:[2,187],133:[2,187],134:[2,187]},{1:[2,188],6:[2,188],25:[2,188],26:[2,188],47:[2,188],52:[2,188],55:[2,188],70:[2,188],75:[2,188],83:[2,188],88:[2,188],90:[2,188],99:[2,188],100:85,101:[2,188],102:[2,188],103:[2,188],106:86,107:[2,188],108:67,115:[2,188],123:[2,188],125:[2,188],126:[2,188],129:[1,76],130:[2,188],131:[2,188],132:[2,188],133:[2,188],134:[2,188]},{1:[2,189],6:[2,189],25:[2,189],26:[2,189],47:[2,189],52:[2,189],55:[2,189],70:[2,189],75:[2,189],83:[2,189],88:[2,189],90:[2,189],99:[2,189],100:85,101:[2,189],102:[2,189],103:[2,189],106:86,107:[2,189],108:67,115:[2,189],123:[2,189],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[2,189],132:[2,189],133:[2,189],134:[2,189]},{1:[2,190],6:[2,190],25:[2,190],26:[2,190],47:[2,190],52:[2,190],55:[2,190],70:[2,190],75:[2,190],83:[2,190],88:[2,190],90:[2,190],99:[2,190],100:85,101:[2,190],102:[2,190],103:[2,190],106:86,107:[2,190],108:67,115:[2,190],123:[2,190],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[2,190],133:[2,190],134:[1,83]},{1:[2,191],6:[2,191],25:[2,191],26:[2,191],47:[2,191],52:[2,191],55:[2,191],70:[2,191],75:[2,191],83:[2,191],88:[2,191],90:[2,191],99:[2,191],100:85,101:[2,191],102:[2,191],103:[2,191],106:86,107:[2,191],108:67,115:[2,191],123:[2,191],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[2,191],134:[1,83]},{1:[2,192],6:[2,192],25:[2,192],26:[2,192],47:[2,192],52:[2,192],55:[2,192],70:[2,192],75:[2,192],83:[2,192],88:[2,192],90:[2,192],99:[2,192],100:85,101:[2,192],102:[2,192],103:[2,192],106:86,107:[2,192],108:67,115:[2,192],123:[2,192],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[2,192],133:[2,192],134:[2,192]},{1:[2,177],6:[2,177],25:[2,177],26:[2,177],47:[2,177],52:[2,177],55:[2,177],70:[2,177],75:[2,177],83:[2,177],88:[2,177],90:[2,177],99:[2,177],100:85,101:[1,63],102:[2,177],103:[1,64],106:86,107:[1,66],108:67,115:[2,177],123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,176],6:[2,176],25:[2,176],26:[2,176],47:[2,176],52:[2,176],55:[2,176],70:[2,176],75:[2,176],83:[2,176],88:[2,176],90:[2,176],99:[2,176],100:85,101:[1,63],102:[2,176],103:[1,64],106:86,107:[1,66],108:67,115:[2,176],123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,99],6:[2,99],25:[2,99],26:[2,99],47:[2,99],52:[2,99],55:[2,99],64:[2,99],65:[2,99],66:[2,99],68:[2,99],70:[2,99],71:[2,99],75:[2,99],81:[2,99],82:[2,99],83:[2,99],88:[2,99],90:[2,99],99:[2,99],101:[2,99],102:[2,99],103:[2,99],107:[2,99],115:[2,99],123:[2,99],125:[2,99],126:[2,99],129:[2,99],130:[2,99],131:[2,99],132:[2,99],133:[2,99],134:[2,99]},{1:[2,76],6:[2,76],25:[2,76],26:[2,76],38:[2,76],47:[2,76],52:[2,76],55:[2,76],64:[2,76],65:[2,76],66:[2,76],68:[2,76],70:[2,76],71:[2,76],75:[2,76],77:[2,76],81:[2,76],82:[2,76],83:[2,76],88:[2,76],90:[2,76],99:[2,76],101:[2,76],102:[2,76],103:[2,76],107:[2,76],115:[2,76],123:[2,76],125:[2,76],126:[2,76],127:[2,76],128:[2,76],129:[2,76],130:[2,76],131:[2,76],132:[2,76],133:[2,76],134:[2,76],135:[2,76]},{1:[2,77],6:[2,77],25:[2,77],26:[2,77],38:[2,77],47:[2,77],52:[2,77],55:[2,77],64:[2,77],65:[2,77],66:[2,77],68:[2,77],70:[2,77],71:[2,77],75:[2,77],77:[2,77],81:[2,77],82:[2,77],83:[2,77],88:[2,77],90:[2,77],99:[2,77],101:[2,77],102:[2,77],103:[2,77],107:[2,77],115:[2,77],123:[2,77],125:[2,77],126:[2,77],127:[2,77],128:[2,77],129:[2,77],130:[2,77],131:[2,77],132:[2,77],133:[2,77],134:[2,77],135:[2,77]},{1:[2,78],6:[2,78],25:[2,78],26:[2,78],38:[2,78],47:[2,78],52:[2,78],55:[2,78],64:[2,78],65:[2,78],66:[2,78],68:[2,78],70:[2,78],71:[2,78],75:[2,78],77:[2,78],81:[2,78],82:[2,78],83:[2,78],88:[2,78],90:[2,78],99:[2,78],101:[2,78],102:[2,78],103:[2,78],107:[2,78],115:[2,78],123:[2,78],125:[2,78],126:[2,78],127:[2,78],128:[2,78],129:[2,78],130:[2,78],131:[2,78],132:[2,78],133:[2,78],134:[2,78],135:[2,78]},{70:[1,234]},{55:[1,189],70:[2,83],89:235,90:[1,188],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{70:[2,84]},{8:236,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{12:[2,112],28:[2,112],30:[2,112],31:[2,112],33:[2,112],34:[2,112],35:[2,112],36:[2,112],43:[2,112],44:[2,112],45:[2,112],49:[2,112],50:[2,112],70:[2,112],73:[2,112],76:[2,112],80:[2,112],85:[2,112],86:[2,112],87:[2,112],93:[2,112],97:[2,112],98:[2,112],101:[2,112],103:[2,112],105:[2,112],107:[2,112],116:[2,112],122:[2,112],124:[2,112],125:[2,112],126:[2,112],127:[2,112],128:[2,112]},{12:[2,113],28:[2,113],30:[2,113],31:[2,113],33:[2,113],34:[2,113],35:[2,113],36:[2,113],43:[2,113],44:[2,113],45:[2,113],49:[2,113],50:[2,113],70:[2,113],73:[2,113],76:[2,113],80:[2,113],85:[2,113],86:[2,113],87:[2,113],93:[2,113],97:[2,113],98:[2,113],101:[2,113],103:[2,113],105:[2,113],107:[2,113],116:[2,113],122:[2,113],124:[2,113],125:[2,113],126:[2,113],127:[2,113],128:[2,113]},{1:[2,82],6:[2,82],25:[2,82],26:[2,82],38:[2,82],47:[2,82],52:[2,82],55:[2,82],64:[2,82],65:[2,82],66:[2,82],68:[2,82],70:[2,82],71:[2,82],75:[2,82],77:[2,82],81:[2,82],82:[2,82],83:[2,82],88:[2,82],90:[2,82],99:[2,82],101:[2,82],102:[2,82],103:[2,82],107:[2,82],115:[2,82],123:[2,82],125:[2,82],126:[2,82],127:[2,82],128:[2,82],129:[2,82],130:[2,82],131:[2,82],132:[2,82],133:[2,82],134:[2,82],135:[2,82]},{1:[2,100],6:[2,100],25:[2,100],26:[2,100],47:[2,100],52:[2,100],55:[2,100],64:[2,100],65:[2,100],66:[2,100],68:[2,100],70:[2,100],71:[2,100],75:[2,100],81:[2,100],82:[2,100],83:[2,100],88:[2,100],90:[2,100],99:[2,100],101:[2,100],102:[2,100],103:[2,100],107:[2,100],115:[2,100],123:[2,100],125:[2,100],126:[2,100],129:[2,100],130:[2,100],131:[2,100],132:[2,100],133:[2,100],134:[2,100]},{1:[2,34],6:[2,34],25:[2,34],26:[2,34],47:[2,34],52:[2,34],55:[2,34],70:[2,34],75:[2,34],83:[2,34],88:[2,34],90:[2,34],99:[2,34],100:85,101:[2,34],102:[2,34],103:[2,34],106:86,107:[2,34],108:67,115:[2,34],123:[2,34],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{8:237,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:238,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,105],6:[2,105],25:[2,105],26:[2,105],47:[2,105],52:[2,105],55:[2,105],64:[2,105],65:[2,105],66:[2,105],68:[2,105],70:[2,105],71:[2,105],75:[2,105],81:[2,105],82:[2,105],83:[2,105],88:[2,105],90:[2,105],99:[2,105],101:[2,105],102:[2,105],103:[2,105],107:[2,105],115:[2,105],123:[2,105],125:[2,105],126:[2,105],129:[2,105],130:[2,105],131:[2,105],132:[2,105],133:[2,105],134:[2,105]},{6:[2,51],25:[2,51],51:239,52:[1,222],83:[2,51]},{6:[2,123],25:[2,123],26:[2,123],52:[2,123],55:[1,240],83:[2,123],88:[2,123],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{48:241,49:[1,58],50:[1,59]},{27:107,28:[1,71],42:108,53:242,54:106,56:109,57:110,73:[1,68],86:[1,111],87:[1,112]},{47:[2,57],52:[2,57]},{8:243,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,193],6:[2,193],25:[2,193],26:[2,193],47:[2,193],52:[2,193],55:[2,193],70:[2,193],75:[2,193],83:[2,193],88:[2,193],90:[2,193],99:[2,193],100:85,101:[2,193],102:[2,193],103:[2,193],106:86,107:[2,193],108:67,115:[2,193],123:[2,193],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{8:244,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,195],6:[2,195],25:[2,195],26:[2,195],47:[2,195],52:[2,195],55:[2,195],70:[2,195],75:[2,195],83:[2,195],88:[2,195],90:[2,195],99:[2,195],100:85,101:[2,195],102:[2,195],103:[2,195],106:86,107:[2,195],108:67,115:[2,195],123:[2,195],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,175],6:[2,175],25:[2,175],26:[2,175],47:[2,175],52:[2,175],55:[2,175],70:[2,175],75:[2,175],83:[2,175],88:[2,175],90:[2,175],99:[2,175],101:[2,175],102:[2,175],103:[2,175],107:[2,175],115:[2,175],123:[2,175],125:[2,175],126:[2,175],129:[2,175],130:[2,175],131:[2,175],132:[2,175],133:[2,175],134:[2,175]},{8:245,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,128],6:[2,128],25:[2,128],26:[2,128],47:[2,128],52:[2,128],55:[2,128],70:[2,128],75:[2,128],83:[2,128],88:[2,128],90:[2,128],95:[1,246],99:[2,128],101:[2,128],102:[2,128],103:[2,128],107:[2,128],115:[2,128],123:[2,128],125:[2,128],126:[2,128],129:[2,128],130:[2,128],131:[2,128],132:[2,128],133:[2,128],134:[2,128]},{5:247,25:[1,5]},{27:248,28:[1,71]},{117:249,119:212,120:[1,213]},{26:[1,250],118:[1,251],119:252,120:[1,213]},{26:[2,168],118:[2,168],120:[2,168]},{8:254,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],92:253,93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,93],5:255,6:[2,93],25:[1,5],26:[2,93],47:[2,93],52:[2,93],55:[2,93],70:[2,93],75:[2,93],83:[2,93],88:[2,93],90:[2,93],99:[2,93],100:85,101:[1,63],102:[2,93],103:[1,64],106:86,107:[1,66],108:67,115:[2,93],123:[2,93],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,96],6:[2,96],25:[2,96],26:[2,96],47:[2,96],52:[2,96],55:[2,96],70:[2,96],75:[2,96],83:[2,96],88:[2,96],90:[2,96],99:[2,96],101:[2,96],102:[2,96],103:[2,96],107:[2,96],115:[2,96],123:[2,96],125:[2,96],126:[2,96],129:[2,96],130:[2,96],131:[2,96],132:[2,96],133:[2,96],134:[2,96]},{8:256,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,133],6:[2,133],25:[2,133],26:[2,133],47:[2,133],52:[2,133],55:[2,133],64:[2,133],65:[2,133],66:[2,133],68:[2,133],70:[2,133],71:[2,133],75:[2,133],81:[2,133],82:[2,133],83:[2,133],88:[2,133],90:[2,133],99:[2,133],101:[2,133],102:[2,133],103:[2,133],107:[2,133],115:[2,133],123:[2,133],125:[2,133],126:[2,133],129:[2,133],130:[2,133],131:[2,133],132:[2,133],133:[2,133],134:[2,133]},{6:[1,72],26:[1,257]},{8:258,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{6:[2,63],12:[2,113],25:[2,63],28:[2,113],30:[2,113],31:[2,113],33:[2,113],34:[2,113],35:[2,113],36:[2,113],43:[2,113],44:[2,113],45:[2,113],49:[2,113],50:[2,113],52:[2,63],73:[2,113],76:[2,113],80:[2,113],85:[2,113],86:[2,113],87:[2,113],88:[2,63],93:[2,113],97:[2,113],98:[2,113],101:[2,113],103:[2,113],105:[2,113],107:[2,113],116:[2,113],122:[2,113],124:[2,113],125:[2,113],126:[2,113],127:[2,113],128:[2,113]},{6:[1,260],25:[1,261],88:[1,259]},{6:[2,52],8:197,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[2,52],26:[2,52],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,58:145,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],83:[2,52],85:[1,56],86:[1,57],87:[1,55],88:[2,52],91:262,93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{6:[2,51],25:[2,51],26:[2,51],51:263,52:[1,222]},{1:[2,172],6:[2,172],25:[2,172],26:[2,172],47:[2,172],52:[2,172],55:[2,172],70:[2,172],75:[2,172],83:[2,172],88:[2,172],90:[2,172],99:[2,172],101:[2,172],102:[2,172],103:[2,172],107:[2,172],115:[2,172],118:[2,172],123:[2,172],125:[2,172],126:[2,172],129:[2,172],130:[2,172],131:[2,172],132:[2,172],133:[2,172],134:[2,172]},{8:264,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:265,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{113:[2,151],114:[2,151]},{27:156,28:[1,71],56:157,57:158,73:[1,68],87:[1,112],112:266},{1:[2,157],6:[2,157],25:[2,157],26:[2,157],47:[2,157],52:[2,157],55:[2,157],70:[2,157],75:[2,157],83:[2,157],88:[2,157],90:[2,157],99:[2,157],100:85,101:[2,157],102:[1,267],103:[2,157],106:86,107:[2,157],108:67,115:[1,268],123:[2,157],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,158],6:[2,158],25:[2,158],26:[2,158],47:[2,158],52:[2,158],55:[2,158],70:[2,158],75:[2,158],83:[2,158],88:[2,158],90:[2,158],99:[2,158],100:85,101:[2,158],102:[1,269],103:[2,158],106:86,107:[2,158],108:67,115:[2,158],123:[2,158],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{6:[1,271],25:[1,272],75:[1,270]},{6:[2,52],11:165,25:[2,52],26:[2,52],27:166,28:[1,71],29:167,30:[1,69],31:[1,70],39:273,40:164,42:168,44:[1,46],75:[2,52],86:[1,111]},{8:274,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,275],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,81],6:[2,81],25:[2,81],26:[2,81],38:[2,81],47:[2,81],52:[2,81],55:[2,81],64:[2,81],65:[2,81],66:[2,81],68:[2,81],70:[2,81],71:[2,81],75:[2,81],77:[2,81],81:[2,81],82:[2,81],83:[2,81],88:[2,81],90:[2,81],99:[2,81],101:[2,81],102:[2,81],103:[2,81],107:[2,81],115:[2,81],123:[2,81],125:[2,81],126:[2,81],127:[2,81],128:[2,81],129:[2,81],130:[2,81],131:[2,81],132:[2,81],133:[2,81],134:[2,81],135:[2,81]},{8:276,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,70:[2,116],73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{70:[2,117],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,35],6:[2,35],25:[2,35],26:[2,35],47:[2,35],52:[2,35],55:[2,35],70:[2,35],75:[2,35],83:[2,35],88:[2,35],90:[2,35],99:[2,35],100:85,101:[2,35],102:[2,35],103:[2,35],106:86,107:[2,35],108:67,115:[2,35],123:[2,35],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{26:[1,277],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{6:[1,260],25:[1,261],83:[1,278]},{6:[2,63],25:[2,63],26:[2,63],52:[2,63],83:[2,63],88:[2,63]},{5:279,25:[1,5]},{47:[2,55],52:[2,55]},{47:[2,58],52:[2,58],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{26:[1,280],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{5:281,25:[1,5],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{5:282,25:[1,5]},{1:[2,129],6:[2,129],25:[2,129],26:[2,129],47:[2,129],52:[2,129],55:[2,129],70:[2,129],75:[2,129],83:[2,129],88:[2,129],90:[2,129],99:[2,129],101:[2,129],102:[2,129],103:[2,129],107:[2,129],115:[2,129],123:[2,129],125:[2,129],126:[2,129],129:[2,129],130:[2,129],131:[2,129],132:[2,129],133:[2,129],134:[2,129]},{5:283,25:[1,5]},{26:[1,284],118:[1,285],119:252,120:[1,213]},{1:[2,166],6:[2,166],25:[2,166],26:[2,166],47:[2,166],52:[2,166],55:[2,166],70:[2,166],75:[2,166],83:[2,166],88:[2,166],90:[2,166],99:[2,166],101:[2,166],102:[2,166],103:[2,166],107:[2,166],115:[2,166],123:[2,166],125:[2,166],126:[2,166],129:[2,166],130:[2,166],131:[2,166],132:[2,166],133:[2,166],134:[2,166]},{5:286,25:[1,5]},{26:[2,169],118:[2,169],120:[2,169]},{5:287,25:[1,5],52:[1,288]},{25:[2,125],52:[2,125],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,94],6:[2,94],25:[2,94],26:[2,94],47:[2,94],52:[2,94],55:[2,94],70:[2,94],75:[2,94],83:[2,94],88:[2,94],90:[2,94],99:[2,94],101:[2,94],102:[2,94],103:[2,94],107:[2,94],115:[2,94],123:[2,94],125:[2,94],126:[2,94],129:[2,94],130:[2,94],131:[2,94],132:[2,94],133:[2,94],134:[2,94]},{1:[2,97],5:289,6:[2,97],25:[1,5],26:[2,97],47:[2,97],52:[2,97],55:[2,97],70:[2,97],75:[2,97],83:[2,97],88:[2,97],90:[2,97],99:[2,97],100:85,101:[1,63],102:[2,97],103:[1,64],106:86,107:[1,66],108:67,115:[2,97],123:[2,97],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{99:[1,290]},{88:[1,291],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,111],6:[2,111],25:[2,111],26:[2,111],38:[2,111],47:[2,111],52:[2,111],55:[2,111],64:[2,111],65:[2,111],66:[2,111],68:[2,111],70:[2,111],71:[2,111],75:[2,111],81:[2,111],82:[2,111],83:[2,111],88:[2,111],90:[2,111],99:[2,111],101:[2,111],102:[2,111],103:[2,111],107:[2,111],113:[2,111],114:[2,111],115:[2,111],123:[2,111],125:[2,111],126:[2,111],129:[2,111],130:[2,111],131:[2,111],132:[2,111],133:[2,111],134:[2,111]},{8:197,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,58:145,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],91:292,93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:197,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,144],27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,58:145,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],84:293,85:[1,56],86:[1,57],87:[1,55],91:143,93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{6:[2,119],25:[2,119],26:[2,119],52:[2,119],83:[2,119],88:[2,119]},{6:[1,260],25:[1,261],26:[1,294]},{1:[2,136],6:[2,136],25:[2,136],26:[2,136],47:[2,136],52:[2,136],55:[2,136],70:[2,136],75:[2,136],83:[2,136],88:[2,136],90:[2,136],99:[2,136],100:85,101:[1,63],102:[2,136],103:[1,64],106:86,107:[1,66],108:67,115:[2,136],123:[2,136],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,138],6:[2,138],25:[2,138],26:[2,138],47:[2,138],52:[2,138],55:[2,138],70:[2,138],75:[2,138],83:[2,138],88:[2,138],90:[2,138],99:[2,138],100:85,101:[1,63],102:[2,138],103:[1,64],106:86,107:[1,66],108:67,115:[2,138],123:[2,138],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{113:[2,156],114:[2,156]},{8:295,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:296,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:297,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,85],6:[2,85],25:[2,85],26:[2,85],38:[2,85],47:[2,85],52:[2,85],55:[2,85],64:[2,85],65:[2,85],66:[2,85],68:[2,85],70:[2,85],71:[2,85],75:[2,85],81:[2,85],82:[2,85],83:[2,85],88:[2,85],90:[2,85],99:[2,85],101:[2,85],102:[2,85],103:[2,85],107:[2,85],113:[2,85],114:[2,85],115:[2,85],123:[2,85],125:[2,85],126:[2,85],129:[2,85],130:[2,85],131:[2,85],132:[2,85],133:[2,85],134:[2,85]},{11:165,27:166,28:[1,71],29:167,30:[1,69],31:[1,70],39:298,40:164,42:168,44:[1,46],86:[1,111]},{6:[2,86],11:165,25:[2,86],26:[2,86],27:166,28:[1,71],29:167,30:[1,69],31:[1,70],39:163,40:164,42:168,44:[1,46],52:[2,86],74:299,86:[1,111]},{6:[2,88],25:[2,88],26:[2,88],52:[2,88],75:[2,88]},{6:[2,38],25:[2,38],26:[2,38],52:[2,38],75:[2,38],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{8:300,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{70:[2,115],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,36],6:[2,36],25:[2,36],26:[2,36],47:[2,36],52:[2,36],55:[2,36],70:[2,36],75:[2,36],83:[2,36],88:[2,36],90:[2,36],99:[2,36],101:[2,36],102:[2,36],103:[2,36],107:[2,36],115:[2,36],123:[2,36],125:[2,36],126:[2,36],129:[2,36],130:[2,36],131:[2,36],132:[2,36],133:[2,36],134:[2,36]},{1:[2,106],6:[2,106],25:[2,106],26:[2,106],47:[2,106],52:[2,106],55:[2,106],64:[2,106],65:[2,106],66:[2,106],68:[2,106],70:[2,106],71:[2,106],75:[2,106],81:[2,106],82:[2,106],83:[2,106],88:[2,106],90:[2,106],99:[2,106],101:[2,106],102:[2,106],103:[2,106],107:[2,106],115:[2,106],123:[2,106],125:[2,106],126:[2,106],129:[2,106],130:[2,106],131:[2,106],132:[2,106],133:[2,106],134:[2,106]},{1:[2,47],6:[2,47],25:[2,47],26:[2,47],47:[2,47],52:[2,47],55:[2,47],70:[2,47],75:[2,47],83:[2,47],88:[2,47],90:[2,47],99:[2,47],101:[2,47],102:[2,47],103:[2,47],107:[2,47],115:[2,47],123:[2,47],125:[2,47],126:[2,47],129:[2,47],130:[2,47],131:[2,47],132:[2,47],133:[2,47],134:[2,47]},{1:[2,194],6:[2,194],25:[2,194],26:[2,194],47:[2,194],52:[2,194],55:[2,194],70:[2,194],75:[2,194],83:[2,194],88:[2,194],90:[2,194],99:[2,194],101:[2,194],102:[2,194],103:[2,194],107:[2,194],115:[2,194],123:[2,194],125:[2,194],126:[2,194],129:[2,194],130:[2,194],131:[2,194],132:[2,194],133:[2,194],134:[2,194]},{1:[2,173],6:[2,173],25:[2,173],26:[2,173],47:[2,173],52:[2,173],55:[2,173],70:[2,173],75:[2,173],83:[2,173],88:[2,173],90:[2,173],99:[2,173],101:[2,173],102:[2,173],103:[2,173],107:[2,173],115:[2,173],118:[2,173],123:[2,173],125:[2,173],126:[2,173],129:[2,173],130:[2,173],131:[2,173],132:[2,173],133:[2,173],134:[2,173]},{1:[2,130],6:[2,130],25:[2,130],26:[2,130],47:[2,130],52:[2,130],55:[2,130],70:[2,130],75:[2,130],83:[2,130],88:[2,130],90:[2,130],99:[2,130],101:[2,130],102:[2,130],103:[2,130],107:[2,130],115:[2,130],123:[2,130],125:[2,130],126:[2,130],129:[2,130],130:[2,130],131:[2,130],132:[2,130],133:[2,130],134:[2,130]},{1:[2,131],6:[2,131],25:[2,131],26:[2,131],47:[2,131],52:[2,131],55:[2,131],70:[2,131],75:[2,131],83:[2,131],88:[2,131],90:[2,131],95:[2,131],99:[2,131],101:[2,131],102:[2,131],103:[2,131],107:[2,131],115:[2,131],123:[2,131],125:[2,131],126:[2,131],129:[2,131],130:[2,131],131:[2,131],132:[2,131],133:[2,131],134:[2,131]},{1:[2,164],6:[2,164],25:[2,164],26:[2,164],47:[2,164],52:[2,164],55:[2,164],70:[2,164],75:[2,164],83:[2,164],88:[2,164],90:[2,164],99:[2,164],101:[2,164],102:[2,164],103:[2,164],107:[2,164],115:[2,164],123:[2,164],125:[2,164],126:[2,164],129:[2,164],130:[2,164],131:[2,164],132:[2,164],133:[2,164],134:[2,164]},{5:301,25:[1,5]},{26:[1,302]},{6:[1,303],26:[2,170],118:[2,170],120:[2,170]},{8:304,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{1:[2,98],6:[2,98],25:[2,98],26:[2,98],47:[2,98],52:[2,98],55:[2,98],70:[2,98],75:[2,98],83:[2,98],88:[2,98],90:[2,98],99:[2,98],101:[2,98],102:[2,98],103:[2,98],107:[2,98],115:[2,98],123:[2,98],125:[2,98],126:[2,98],129:[2,98],130:[2,98],131:[2,98],132:[2,98],133:[2,98],134:[2,98]},{1:[2,134],6:[2,134],25:[2,134],26:[2,134],47:[2,134],52:[2,134],55:[2,134],64:[2,134],65:[2,134],66:[2,134],68:[2,134],70:[2,134],71:[2,134],75:[2,134],81:[2,134],82:[2,134],83:[2,134],88:[2,134],90:[2,134],99:[2,134],101:[2,134],102:[2,134],103:[2,134],107:[2,134],115:[2,134],123:[2,134],125:[2,134],126:[2,134],129:[2,134],130:[2,134],131:[2,134],132:[2,134],133:[2,134],134:[2,134]},{1:[2,114],6:[2,114],25:[2,114],26:[2,114],47:[2,114],52:[2,114],55:[2,114],64:[2,114],65:[2,114],66:[2,114],68:[2,114],70:[2,114],71:[2,114],75:[2,114],81:[2,114],82:[2,114],83:[2,114],88:[2,114],90:[2,114],99:[2,114],101:[2,114],102:[2,114],103:[2,114],107:[2,114],115:[2,114],123:[2,114],125:[2,114],126:[2,114],129:[2,114],130:[2,114],131:[2,114],132:[2,114],133:[2,114],134:[2,114]},{6:[2,120],25:[2,120],26:[2,120],52:[2,120],83:[2,120],88:[2,120]},{6:[2,51],25:[2,51],26:[2,51],51:305,52:[1,222]},{6:[2,121],25:[2,121],26:[2,121],52:[2,121],83:[2,121],88:[2,121]},{1:[2,159],6:[2,159],25:[2,159],26:[2,159],47:[2,159],52:[2,159],55:[2,159],70:[2,159],75:[2,159],83:[2,159],88:[2,159],90:[2,159],99:[2,159],100:85,101:[2,159],102:[2,159],103:[2,159],106:86,107:[2,159],108:67,115:[1,306],123:[2,159],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,161],6:[2,161],25:[2,161],26:[2,161],47:[2,161],52:[2,161],55:[2,161],70:[2,161],75:[2,161],83:[2,161],88:[2,161],90:[2,161],99:[2,161],100:85,101:[2,161],102:[1,307],103:[2,161],106:86,107:[2,161],108:67,115:[2,161],123:[2,161],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,160],6:[2,160],25:[2,160],26:[2,160],47:[2,160],52:[2,160],55:[2,160],70:[2,160],75:[2,160],83:[2,160],88:[2,160],90:[2,160],99:[2,160],100:85,101:[2,160],102:[2,160],103:[2,160],106:86,107:[2,160],108:67,115:[2,160],123:[2,160],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{6:[2,89],25:[2,89],26:[2,89],52:[2,89],75:[2,89]},{6:[2,51],25:[2,51],26:[2,51],51:308,52:[1,232]},{26:[1,309],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{26:[1,310]},{1:[2,167],6:[2,167],25:[2,167],26:[2,167],47:[2,167],52:[2,167],55:[2,167],70:[2,167],75:[2,167],83:[2,167],88:[2,167],90:[2,167],99:[2,167],101:[2,167],102:[2,167],103:[2,167],107:[2,167],115:[2,167],123:[2,167],125:[2,167],126:[2,167],129:[2,167],130:[2,167],131:[2,167],132:[2,167],133:[2,167],134:[2,167]},{26:[2,171],118:[2,171],120:[2,171]},{25:[2,126],52:[2,126],100:85,101:[1,63],103:[1,64],106:86,107:[1,66],108:67,123:[1,84],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{6:[1,260],25:[1,261],26:[1,311]},{8:312,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{8:313,9:115,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:60,28:[1,71],29:49,30:[1,69],31:[1,70],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:23,42:61,43:[1,45],44:[1,46],45:[1,29],48:30,49:[1,58],50:[1,59],56:47,57:48,59:36,61:25,62:26,63:27,73:[1,68],76:[1,43],80:[1,28],85:[1,56],86:[1,57],87:[1,55],93:[1,38],97:[1,44],98:[1,54],100:39,101:[1,63],103:[1,64],104:40,105:[1,65],106:41,107:[1,66],108:67,116:[1,42],121:37,122:[1,62],124:[1,31],125:[1,32],126:[1,33],127:[1,34],128:[1,35]},{6:[1,271],25:[1,272],26:[1,314]},{6:[2,39],25:[2,39],26:[2,39],52:[2,39],75:[2,39]},{1:[2,165],6:[2,165],25:[2,165],26:[2,165],47:[2,165],52:[2,165],55:[2,165],70:[2,165],75:[2,165],83:[2,165],88:[2,165],90:[2,165],99:[2,165],101:[2,165],102:[2,165],103:[2,165],107:[2,165],115:[2,165],123:[2,165],125:[2,165],126:[2,165],129:[2,165],130:[2,165],131:[2,165],132:[2,165],133:[2,165],134:[2,165]},{6:[2,122],25:[2,122],26:[2,122],52:[2,122],83:[2,122],88:[2,122]},{1:[2,162],6:[2,162],25:[2,162],26:[2,162],47:[2,162],52:[2,162],55:[2,162],70:[2,162],75:[2,162],83:[2,162],88:[2,162],90:[2,162],99:[2,162],100:85,101:[2,162],102:[2,162],103:[2,162],106:86,107:[2,162],108:67,115:[2,162],123:[2,162],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{1:[2,163],6:[2,163],25:[2,163],26:[2,163],47:[2,163],52:[2,163],55:[2,163],70:[2,163],75:[2,163],83:[2,163],88:[2,163],90:[2,163],99:[2,163],100:85,101:[2,163],102:[2,163],103:[2,163],106:86,107:[2,163],108:67,115:[2,163],123:[2,163],125:[1,78],126:[1,77],129:[1,76],130:[1,79],131:[1,80],132:[1,81],133:[1,82],134:[1,83]},{6:[2,90],25:[2,90],26:[2,90],52:[2,90],75:[2,90]}],defaultActions:{58:[2,49],59:[2,50],73:[2,3],92:[2,104],186:[2,84]},parseError:function(a,b){throw new Error(a)},parse:function(a){function o(){var a;a=b.lexer.lex()||1,typeof a!="number"&&(a=b.symbols_[a]||a);return a}function n(a){c.length=c.length-2*a,d.length=d.length-a,e.length=e.length-a}var b=this,c=[0],d=[null],e=[],f=this.table,g="",h=0,i=0,j=0,k=2,l=1;this.lexer.setInput(a),this.lexer.yy=this.yy,this.yy.lexer=this.lexer,typeof this.lexer.yylloc=="undefined"&&(this.lexer.yylloc={});var m=this.lexer.yylloc;e.push(m),typeof this.yy.parseError=="function"&&(this.parseError=this.yy.parseError);var p,q,r,s,t,u,v={},w,x,y,z;for(;;){r=c[c.length-1],this.defaultActions[r]?s=this.defaultActions[r]:(p==null&&(p=o()),s=f[r]&&f[r][p]);if(typeof s=="undefined"||!s.length||!s[0]){if(!j){z=[];for(w in f[r])this.terminals_[w]&&w>2&&z.push("'"+this.terminals_[w]+"'");var A="";this.lexer.showPosition?A="Parse error on line "+(h+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+z.join(", "):A="Parse error on line "+(h+1)+": Unexpected "+(p==1?"end of input":"'"+(this.terminals_[p]||p)+"'"),this.parseError(A,{text:this.lexer.match,token:this.terminals_[p]||p,line:this.lexer.yylineno,loc:m,expected:z})}if(j==3){if(p==l)throw new Error(A||"Parsing halted.");i=this.lexer.yyleng,g=this.lexer.yytext,h=this.lexer.yylineno,m=this.lexer.yylloc,p=o()}for(;;){if(k.toString()in f[r])break;if(r==0)throw new Error(A||"Parsing halted.");n(1),r=c[c.length-1]}q=p,p=k,r=c[c.length-1],s=f[r]&&f[r][k],j=3}if(s[0]instanceof Array&&s.length>1)throw new Error("Parse Error: multiple actions possible at state: "+r+", token: "+p);switch(s[0]){case 1:c.push(p),d.push(this.lexer.yytext),e.push(this.lexer.yylloc),c.push(s[1]),p=null,q?(p=q,q=null):(i=this.lexer.yyleng,g=this.lexer.yytext,h=this.lexer.yylineno,m=this.lexer.yylloc,j>0&&j--);break;case 2:x=this.productions_[s[1]][1],v.$=d[d.length-x],v._$={first_line:e[e.length-(x||1)].first_line,last_line:e[e.length-1].last_line,first_column:e[e.length-(x||1)].first_column,last_column:e[e.length-1].last_column},u=this.performAction.call(v,g,i,h,this.yy,s[1],d,e);if(typeof u!="undefined")return u;x&&(c=c.slice(0,-1*x*2),d=d.slice(0,-1*x),e=e.slice(0,-1*x)),c.push(this.productions_[s[1]][0]),d.push(v.$),e.push(v._$),y=f[c[c.length-2]][c[c.length-1]],c.push(y);break;case 3:return!0}}return!0}};return a}();typeof require!="undefined"&&typeof a!="undefined"&&(a.parser=b,a.parse=function(){return b.parse.apply(b,arguments)},a.main=function(b){if(!b[1])throw new Error("Usage: "+b[0]+" FILE");if(typeof process!="undefined")var c=require("fs").readFileSync(require("path").join(process.cwd(),b[1]),"utf8");else var d=require("file").path(require("file").cwd()),c=d.join(b[1]).read({charset:"utf-8"});return a.parser.parse(c)},typeof module!="undefined"&&require.main===module&&a.main(typeof process!="undefined"?process.argv.slice(1):require("system").args))},require["./scope"]=new function(){var a=this;(function(){var b,c,d,e;e=require("./helpers"),c=e.extend,d=e.last,a.Scope=b=function(){function a(b,c,d){this.parent=b,this.expressions=c,this.method=d,this.variables=[{name:"arguments",type:"arguments"}],this.positions={},this.parent||(a.root=this)}a.root=null,a.prototype.add=function(a,b,c){if(this.shared&&!c)return this.parent.add(a,b,c);return Object.prototype.hasOwnProperty.call(this.positions,a)?this.variables[this.positions[a]].type=b:this.positions[a]=this.variables.push({name:a,type:b})-1},a.prototype.find=function(a,b){if(this.check(a,b))return!0;this.add(a,"var");return!1},a.prototype.parameter=function(a){if(!this.shared||!this.parent.check(a,!0))return this.add(a,"param")},a.prototype.check=function(a,b){var c,d;c=!!this.type(a);if(c||b)return c;return(d=this.parent)!=null?!!d.check(a):!!void 0},a.prototype.temporary=function(a,b){return a.length>1?"_"+a+(b>1?b:""):"_"+(b+parseInt(a,36)).toString(36).replace(/\d/g,"a")},a.prototype.type=function(a){var b,c,d,e;e=this.variables;for(c=0,d=e.length;c1&&a.level>=w?"("+c+")":c},b.prototype.compileRoot=function(a){var b,c,d,e,f,g;a.indent=a.bare?"":Q,a.scope=new M(null,this,null),a.level=z,this.spaced=!0,e="",a.bare||(f=function(){var a,b,e;b=this.expressions,e=[];for(d=0,a=b.length;d=u?"(void 0)":"void 0":this.value==="this"?((c=a.scope.method)!=null?c.bound:void 0)?a.scope.method.context:this.value:this.value.reserved&&(d=""+this.value)!=="eval"&&d!=="arguments"?'"'+this.value+'"':this.value;return this.isStatement()?""+this.tab+b+";":b},b.prototype.toString=function(){return' "'+this.value+'"'};return b}(e),a.Return=K=function(a){function b(a){a&&!a.unwrap().isUndefined&&(this.expression=a)}bj(b,a),b.prototype.children=["expression"],b.prototype.isStatement=X,b.prototype.makeReturn=R,b.prototype.jumps=R,b.prototype.compile=function(a,c){var d,e;d=(e=this.expression)!=null?e.makeReturn():void 0;return!d||d instanceof b?b.__super__.compile.call(this,a,c):d.compile(a,c)},b.prototype.compileNode=function(a){return this.tab+("return"+[this.expression?" "+this.expression.compile(a,y):void 0]+";")};return b}(e),a.Value=V=function(a){function b(a,c,d){if(!c&&a instanceof b)return a;this.base=a,this.properties=c||[],d&&(this[d]=!0);return this}bj(b,a),b.prototype.children=["base","properties"],b.prototype.add=function(a){this.properties=this.properties.concat(a);return this},b.prototype.hasProperties=function(){return!!this.properties.length},b.prototype.isArray=function(){return!this.properties.length&&this.base instanceof c},b.prototype.isComplex=function(){return this.hasProperties()||this.base.isComplex()},b.prototype.isAssignable=function(){return this.hasProperties()||this.base.isAssignable()},b.prototype.isSimpleNumber=function(){return this.base instanceof A&&L.test(this.base.value)},b.prototype.isAtomic=function(){var a,b,c,d;d=this.properties.concat(this.base);for(b=0,c=d.length;b"+this.equals],h=l[0],e=l[1],c=this.stepNum?+this.stepNum>0?""+h+" "+this.toVar:""+e+" "+this.toVar:g?(m=[+this.fromNum,+this.toNum],d=m[0],j=m[1],m,d<=j?""+h+" "+j:""+e+" "+j):(b=""+this.fromVar+" <= "+this.toVar,""+b+" ? "+h+" "+this.toVar+" : "+e+" "+this.toVar),i=this.stepVar?""+f+" += "+this.stepVar:g?d<=j?""+f+"++":""+f+"--":""+b+" ? "+f+"++ : "+f+"--";return""+k+"; "+c+"; "+i},b.prototype.compileArray=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p;if(this.fromNum&&this.toNum&&Math.abs(this.fromNum-this.toNum)<=20){j=function(){p=[];for(var a=n=+this.fromNum,b=+this.toNum;n<=b?a<=b:a>=b;n<=b?a++:a--)p.push(a);return p}.apply(this),this.exclusive&&j.pop();return"["+j.join(", ")+"]"}g=this.tab+Q,f=a.scope.freeVariable("i"),k=a.scope.freeVariable("results"),i="\n"+g+k+" = [];",this.fromNum&&this.toNum?(a.index=f,c=this.compileNode(a)):(l=""+f+" = "+this.fromC+(this.toC!==this.toVar?", "+this.toC:""),d=""+this.fromVar+" <= "+this.toVar,c="var "+l+"; "+d+" ? "+f+" <"+this.equals+" "+this.toVar+" : "+f+" >"+this.equals+" "+this.toVar+"; "+d+" ? "+f+"++ : "+f+"--"),h="{ "+k+".push("+f+"); }\n"+g+"return "+k+";\n"+a.indent,e=function(a){return a!=null?a.contains(function(a){return a instanceof A&&a.value==="arguments"&&!a.asKey}):void 0};if(e(this.from)||e(this.to))b=", arguments";return"(function() {"+i+"\n"+g+"for ("+c+")"+h+"}).apply(this"+(b!=null?b:"")+")"};return b}(e),a.Slice=N=function(a){function b(a){this.range=a,b.__super__.constructor.call(this)}bj(b,a),b.prototype.children=["range"],b.prototype.compileNode=function(a){var b,c,d,e,f,g;g=this.range,e=g.to,c=g.from,d=c&&c.compile(a,y)||"0",b=e&&e.compile(a,u),e&&(!!this.range.exclusive||+b!==-1)&&(f=", "+(this.range.exclusive?b:L.test(b)?(+b+1).toString():""+b+" + 1 || 9e9"));return".slice("+d+(f||"")+")"};return b}(e),a.Obj=E=function(a){function b(a,b){this.generated=b!=null?b:!1,this.objects=this.properties=a||[]}bj(b,a),b.prototype.children=["properties"],b.prototype.compileNode=function(a){var b,c,e,f,g,h,i,j,l,m,n;l=this.properties;if(!l.length)return this.front?"({})":"{}";if(this.generated)for(m=0,n=l.length;m=0?"[\n"+a.indent+b+"\n"+this.tab+"]":"["+b+"]"},b.prototype.assigns=function(a){var b,c,d,e;e=this.objects;for(c=0,d=e.length;c=0},c.prototype.assigns=function(a){return this[this.context==="object"?"value":"variable"].assigns(a)},c.prototype.unfoldSoak=function(a){return bf(a,this,"variable")},c.prototype.compileNode=function(a){var b,c,d,e,f,g,h,i,k;if(b=this.variable instanceof V){if(this.variable.isArray()||this.variable.isObject())return this.compilePatternMatch(a);if(this.variable.isSplice())return this.compileSplice(a);if((g=this.context)==="||="||g==="&&="||g==="?=")return this.compileConditional(a)}d=this.variable.compile(a,w);if(!this.context){if(!(f=this.variable.unwrapAll()).isAssignable())throw SyntaxError('"'+this.variable.compile(a)+'" cannot be assigned.');if(typeof f.hasProperties=="function"?!f.hasProperties():!void 0)this.param?a.scope.add(d,"var"):a.scope.find(d)}this.value instanceof j&&(c=B.exec(d))&&(c[1]&&(this.value.klass=c[1]),this.value.name=(h=(i=(k=c[2])!=null?k:c[3])!=null?i:c[4])!=null?h:c[5]),e=this.value.compile(a,w);if(this.context==="object")return""+d+": "+e;e=d+(" "+(this.context||"=")+" ")+e;return a.level<=w?e:"("+e+")"},c.prototype.compilePatternMatch=function(a){var d,e,f,g,h,i,j,k,l,m,n,p,q,r,s,u,v,y,B,C,D,E,F,G,J,K;s=a.level===z,v=this.value,m=this.variable.base.objects;if(!(n=m.length)){f=v.compile(a);return a.level>=x?"("+f+")":f}i=this.variable.isObject();if(s&&n===1&&!((l=m[0])instanceof O)){l instanceof c?(C=l,D=C.variable,h=D.base,l=C.value):l.base instanceof H?(E=(new V(l.unwrapAll())).cacheReference(a),l=E[0],h=E[1]):h=i?l["this"]?l.properties[0].name:l:new A(0),d=o.test(h.unwrap().value||0),v=new V(v),v.properties.push(new(d?b:t)(h));if(F=l.unwrap().value,bk.call(["arguments","eval"].concat(I),F)>=0)throw new SyntaxError("assignment to a reserved word: "+l.compile(a)+" = "+v.compile(a));return(new c(l,v,null,{param:this.param})).compile(a,z)}y=v.compile(a,w),e=[],r=!1;if(!o.test(y)||this.variable.assigns(y))e.push(""+(p=a.scope.freeVariable("ref"))+" = "+y),y=p;for(g=0,B=m.length;g=0)throw new SyntaxError("assignment to a reserved word: "+l.compile(a)+" = "+u.compile(a));e.push((new c(l,u,null,{param:this.param,subpattern:!0})).compile(a,w))}!s&&!this.subpattern&&e.push(y),f=e.join(", ");return a.level=0&&(a.isExistentialEquals=!0);return(new F(this.context.slice(0,-1),b,new c(d,this.value,"="))).compile(a)},c.prototype.compileSplice=function(a){var b,c,d,e,f,g,h,i,j,k,l,m;k=this.variable.properties.pop().range,d=k.from,h=k.to,c=k.exclusive,g=this.variable.compile(a),l=(d!=null?d.cache(a,x):void 0)||["0","0"],e=l[0],f=l[1],h?(d!=null?d.isSimpleNumber():void 0)&&h.isSimpleNumber()?(h=+h.compile(a)- +f,c||(h+=1)):(h=h.compile(a,u)+" - "+f,c||(h+=" + 1")):h="9e9",m=this.value.cache(a,w),i=m[0],j=m[1],b="[].splice.apply("+g+", ["+e+", "+h+"].concat("+i+")), "+j;return a.level>z?"("+b+")":b};return c}(e),a.Code=j=function(a){function b(a,b,c){this.params=a||[],this.body=b||new f,this.bound=c==="boundfunc",this.bound&&(this.context="_this")}bj(b,a),b.prototype.children=["params","body"],b.prototype.isStatement=function(){return!!this.ctor},b.prototype.jumps=D,b.prototype.compileNode=function(a){var b,e,f,g,h,i,j,k,l,m,n,o,p,q,s,t,v,w,x,y,z,B,C,D,E;a.scope=new M(a.scope,this.body,this),a.scope.shared=Z(a,"sharedScope"),a.indent+=Q,delete a.bare,o=[],e=[],z=this.params;for(q=0,v=z.length;q=u?"("+b+")":b},b.prototype.traverseChildren=function(a,c){if(a)return b.__super__.traverseChildren.call(this,a,c)};return b}(e),a.Param=G=function(a){function b(a,b,c){this.name=a,this.value=b,this.splat=c}bj(b,a),b.prototype.children=["name","value"],b.prototype.compile=function(a){return this.name.compile(a,w)},b.prototype.asReference=function(a){var b;if(this.reference)return this.reference;b=this.name,b["this"]?(b=b.properties[0].name,b.value.reserved&&(b=new A("_"+b.value))):b.isComplex()&&(b=new A(a.scope.freeVariable("arg"))),b=new V(b),this.splat&&(b=new O(b));return this.reference=b},b.prototype.isComplex=function(){return this.name.isComplex()};return b}(e),a.Splat=O=function(a){function b(a){this.name=a.compile?a:new A(a)}bj(b,a),b.prototype.children=["name"],b.prototype.isAssignable=X,b.prototype.assigns=function(a){return this.name.assigns(a)},b.prototype.compile=function(a){return this.index!=null?this.compileParam(a):this.name.compile(a)},b.prototype.unwrap=function(){return this.name},b.compileSplattedArray=function(a,c,d){var e,f,g,h,i,j,k;i=-1;while((j=c[++i])&&!(j instanceof b))continue;if(i>=c.length)return"";if(c.length===1){g=c[0].compile(a,w);if(d)return g;return""+bg("slice")+".call("+g+")"}e=c.slice(i);for(h=0,k=e.length;h1?b.expressions.unshift(new r((new H(this.guard)).invert(),new A("continue"))):this.guard&&(b=f.wrap([new r(this.guard,b)]))),b="\n"+b.compile(a,z)+"\n"+this.tab),c=e+this.tab+("while ("+this.condition.compile(a,y)+") {"+b+"}"),this.returns&&(c+="\n"+this.tab+"return "+d+";");return c};return b}(e),a.Op=F=function(a){function e(a,c,d,e){var f;if(a==="in")return new s(c,d);if(a==="do"){f=new g(c,c.params||[]),f["do"]=!0;return f}if(a==="new"){if(c instanceof g&&!c["do"]&&!c.isNew)return c.newInstance();if(c instanceof j&&c.bound||c["do"])c=new H(c)}this.operator=b[a]||a,this.first=c,this.second=d,this.flip=!!e;return this}var b,c;bj(e,a),b={"==":"===","!=":"!==",of:"in"},c={"!==":"===","===":"!=="},e.prototype.children=["first","second"],e.prototype.isSimpleNumber=D,e.prototype.isUnary=function(){return!this.second},e.prototype.isComplex=function(){var a;return!this.isUnary()||(a=this.operator)!=="+"&&a!=="-"||this.first.isComplex()},e.prototype.isChainable=function(){var a;return(a=this.operator)==="<"||a===">"||a===">="||a==="<="||a==="==="||a==="!=="},e.prototype.invert=function(){var a,b,d,f,g;if(this.isChainable()&&this.first.isChainable()){a=!0,b=this;while(b&&b.operator)a&&(a=b.operator in c),b=b.first;if(!a)return(new H(this)).invert();b=this;while(b&&b.operator)b.invert=!b.invert,b.operator=c[b.operator],b=b.first;return this}if(f=c[this.operator]){this.operator=f,this.first.unwrap()instanceof e&&this.first.invert();return this}return this.second?(new H(this)).invert():this.operator==="!"&&(d=this.first.unwrap())instanceof e&&((g=d.operator)==="!"||g==="in"||g==="instanceof")?d:new e("!",this)},e.prototype.unfoldSoak=function(a){var b;return((b=this.operator)==="++"||b==="--"||b==="delete")&&bf(a,this,"first")},e.prototype.compileNode=function(a){var b,c;c=this.isChainable()&&this.first.isChainable(),c||(this.first.front=this.front);if(this.isUnary())return this.compileUnary(a);if(c)return this.compileChain(a);if(this.operator==="?")return this.compileExistence(a);b=this.first.compile(a,x)+" "+this.operator+" "+this.second.compile(a,x);return a.level<=x?b:"("+b+")"},e.prototype.compileChain=function(a){var b,c,d,e;e=this.first.second.cache(a),this.first.second=e[0],d=e[1],c=this.first.compile(a,x),b=""+c+" "+(this.invert?"&&":"||")+" "+d.compile(a)+" "+this.operator+" "+this.second.compile(a,x);return"("+b+")"},e.prototype.compileExistence=function(a){var b,c;this.first.isComplex()&&a.level>z?(c=new A(a.scope.freeVariable("ref")),b=new H(new d(c,this.first))):(b=this.first,c=b);return(new r(new l(b),c,{type:"if"})).addElse(this.second).compile(a)},e.prototype.compileUnary=function(a){var b,c,d;c=[b=this.operator],d=b==="+"||b==="-",(b==="new"||b==="typeof"||b==="delete"||d&&this.first instanceof e&&this.first.operator===b)&&c.push(" ");if(d&&this.first instanceof e||b==="new"&&this.first.isStatement(a))this.first=new H(this.first);c.push(this.first.compile(a,x)),this.flip&&c.reverse();return c.join("")},e.prototype.toString=function(a){return e.__super__.toString.call(this,a,this.constructor.name+" "+this.operator)};return e}(e),a.In=s=function(a){function b(a,b){this.object=a,this.array=b}bj(b,a),b.prototype.children=["object","array"],b.prototype.invert=C,b.prototype.compileNode=function(a){var b,c,d,e,f;if(this.array instanceof V&&this.array.isArray()){f=this.array.base.objects;for(d=0,e=f.length;d= 0");if(d===c)return b;b=d+", "+b;return a.level1?b.expressions.unshift(new r((new H(this.guard)).invert(),new A("continue"))):this.guard&&(b=f.wrap([new r(this.guard,b)]))),this.pattern&&b.expressions.unshift(new d(this.name,new A(""+D+"["+k+"]"))),c+=this.pluckDirectCall(a,b),p&&(E="\n"+i+p+";"),this.object&&(e=""+k+" in "+D,this.own&&(h="\n"+i+"if (!"+bg("hasProp")+".call("+D+", "+k+")) continue;")),b=b.compile(bc(a,{indent:i}),z),b&&(b="\n"+b+"\n");return""+c+(s||"")+this.tab+"for ("+e+") {"+h+E+b+this.tab+"}"+(t||"")},b.prototype.pluckDirectCall=function(a,b){var c,e,f,h,i,k,l,m,n,o,p,q,r,s;e="",n=b.expressions;for(i=0,m=n.length;if.length+d.length)return""+this.tab+"if ("+f+") "+d.replace(/^\s+/,"");d&&(d="\n"+d+"\n"+this.tab),h="if ("+f+") {"+d+"}",e||(h=this.tab+h);if(!this.elseBody)return h;return h+" else "+(this.isChain?(a.indent=this.tab,a.chainChild=!0,this.elseBody.unwrap().compile(a,z)):"{\n"+this.elseBody.compile(a,z)+"\n"+this.tab+"}")},b.prototype.compileExpression=function(a){var b,c,d,e;e=this.condition.compile(a,v),c=this.bodyNode().compile(a,w),b=this.elseBodyNode()?this.elseBodyNode().compile(a,w):"void 0",d=""+e+" ? "+c+" : "+b;return a.level>=v?"("+d+")":d},b.prototype.unfoldSoak=function(){return this.soak&&this};return b}(e),i={wrap:function(a,c,d){var e,h,i,k,l;if(a.jumps())return a;i=new j([],f.wrap([a])),e=[];if((k=a.contains(this.literalArgs))||a.contains(this.literalThis))l=new A(k?"apply":"call"),e=[new A("this")],k&&e.push(new A("arguments")),i=new V(i,[new b(l)]);i.noReturn=d,h=new g(i,e);return c?f.wrap([h]):h},literalArgs:function(a){return a instanceof A&&a.value==="arguments"&&!a.asKey},literalThis:function(a){return a instanceof A&&a.value==="this"&&!a.asKey||a instanceof j&&a.bound}},bf=function(a,b,c){var d;if(!!(d=b[c].unfoldSoak(a))){b[c]=d.body,d.body=new V(b);return d}},U={"extends":function(){return"function(child, parent) { for (var key in parent) { if ("+bg("hasProp")+".call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }"},bind:function(){return"function(fn, me){ return function(){ return fn.apply(me, arguments); }; }"},indexOf:function(){return"Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }"},hasProp:function(){return"Object.prototype.hasOwnProperty"},slice:function(){return"Array.prototype.slice"}},z=1,y=2,w=3,v=4,x=5,u=6,Q=" ",p="[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*",o=RegExp("^"+p+"$"),L=/^[+-]?\d+$/,B=RegExp("^(?:("+p+")\\.prototype(?:\\.("+p+")|\\[(\"(?:[^\\\\\"\\r\\n]|\\\\.)*\"|'(?:[^\\\\'\\r\\n]|\\\\.)*')\\]|\\[(0x[\\da-fA-F]+|\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\]))|("+p+")$"),q=/^['"]/,bg=function(a){var b;b="__"+a,M.root.assign(b,U[a]());return b},bd=function(a,b){a=a.replace(/\n/g,"$&"+b);return a.replace(/\s+$/,"")}}).call(this)},require["./coffee-script"]=new function(){var a=this;(function(){var b,c,d,e,f,g,h,i,j,k=Object.prototype.hasOwnProperty;e=require("fs"),h=require("path"),j=require("./lexer"),b=j.Lexer,c=j.RESERVED,g=require("./parser").parser,i=require("vm"),require.extensions?require.extensions[".coffee"]=function(a,b){var c;c=d(e.readFileSync(b,"utf8"),{filename:b});return a._compile(c,b)}:require.registerExtension&&require.registerExtension(".coffee",function(a){return d(a)}),a.VERSION="1.2.0",a.RESERVED=c,a.helpers=require("./helpers"),a.compile=d=function(b,c){var d;c==null&&(c={}),d=a.helpers.merge;try{return g.parse(f.tokenize(b)).compile(d({},c))}catch(e){c.filename&&(e.message="In "+c.filename+", "+e.message);throw e}},a.tokens=function(a,b){return f.tokenize(a,b)},a.nodes=function(a,b){return typeof a=="string"?g.parse(f.tokenize(a,b)):g.parse(a)},a.run=function(a,b){var c;c=require.main,c.filename=process.argv[1]=b.filename?e.realpathSync(b.filename):".",c.moduleCache&&(c.moduleCache={}),c.paths=require("module")._nodeModulePaths(h.dirname(b.filename));return h.extname(c.filename)!==".coffee"||require.extensions?c._compile(d(a,b),c.filename):c._compile(a,c.filename)},a.eval=function(a,b){var c,e,f,g,j,l,m,n,o,p,q,r,s,t;b==null&&(b={});if(!!(a=a.trim())){e=i.Script;if(e){if(b.sandbox!=null){if(b.sandbox instanceof e.createContext().constructor)m=b.sandbox;else{m=e.createContext(),r=b.sandbox;for(g in r){if(!k.call(r,g))continue;n=r[g],m[g]=n}}m.global=m.root=m.GLOBAL=m}else m=global;m.__filename=b.filename||"eval",m.__dirname=h.dirname(m.__filename);if(m===global&&!m.module&&!m.require){c=require("module"),m.module=q=new c(b.modulename||"eval"),m.require=t=function(a){return c._load(a,q,!0)},q.filename=m.__filename,s=Object.getOwnPropertyNames(require);for(o=0,p=s.length;o").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"":"")+""),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;g=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
    a",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="
    "+""+"
    ",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="
    t
    ",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="
    ",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; +f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&i.push({elem:this,matches:d.slice(e)});for(j=0;j0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

    ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
    ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
    ","
    "]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() +{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
    ").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/jquery.js b/jquery.js new file mode 120000 index 0000000..92cf225 --- /dev/null +++ b/jquery.js @@ -0,0 +1 @@ +jquery-1.7.1.min.js \ No newline at end of file diff --git a/simple.html b/simple.html new file mode 100644 index 0000000..4b5c4fc --- /dev/null +++ b/simple.html @@ -0,0 +1,693 @@ + + + + + + + + + + + + OpenJsCad + + + +
    +

    OpenJsCad

    + [Update] +
    + +
    + +
    + +
    + +
    + + + + From 47955a0b3d3c7918e45315a4758a5c41e9192bf4 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Sat, 17 Mar 2012 22:59:19 +0100 Subject: [PATCH 46/48] Math.uuid.js added. openjscad: it loads csd and itself via xhr. the relative path is wrong in simple.html, because simple.html/filename -> simple.html/csd.js. openjscad loads ../csd.js and ../openjscad.js now. but in index.html and the other demos wont work now. --- Math.uuid.js | 92 +++++++++++++++++++++++++++ openjscad.js | 4 +- simple.html | 174 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 197 insertions(+), 73 deletions(-) create mode 100644 Math.uuid.js diff --git a/Math.uuid.js b/Math.uuid.js new file mode 100644 index 0000000..7538e67 --- /dev/null +++ b/Math.uuid.js @@ -0,0 +1,92 @@ +/*! +Math.uuid.js (v1.4) +http://www.broofa.com +mailto:robert@broofa.com + +Copyright (c) 2010 Robert Kieffer +Dual licensed under the MIT and GPL licenses. +*/ + +/* + * Generate a random uuid. + * + * USAGE: Math.uuid(length, radix) + * length - the desired number of characters + * radix - the number of allowable values for each character. + * + * EXAMPLES: + * // No arguments - returns RFC4122, version 4 ID + * >>> Math.uuid() + * "92329D39-6F5C-4520-ABFC-AAB64544E172" + * + * // One argument - returns ID of the specified length + * >>> Math.uuid(15) // 15 character ID (default base=62) + * "VcydxgltxrVZSTV" + * + * // Two arguments - returns ID of the specified length, and radix. (Radix must be <= 62) + * >>> Math.uuid(8, 2) // 8 character ID (base=2) + * "01001010" + * >>> Math.uuid(8, 10) // 8 character ID (base=10) + * "47473046" + * >>> Math.uuid(8, 16) // 8 character ID (base=16) + * "098F4D35" + */ +(function() { + // Private array of chars to use + var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); + + Math.uuid = function (len, radix) { + var chars = CHARS, uuid = [], i; + radix = radix || chars.length; + + if (len) { + // Compact form + for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix]; + } else { + // rfc4122, version 4 form + var r; + + // rfc4122 requires these characters + uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; + uuid[14] = '4'; + + // Fill in random data. At i==19 set the high bits of clock sequence as + // per rfc4122, sec. 4.1.5 + for (i = 0; i < 36; i++) { + if (!uuid[i]) { + r = 0 | Math.random()*16; + uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; + } + } + } + + return uuid.join(''); + }; + + // A more performant, but slightly bulkier, RFC4122v4 solution. We boost performance + // by minimizing calls to random() + Math.uuidFast = function() { + var chars = CHARS, uuid = new Array(36), rnd=0, r; + for (var i = 0; i < 36; i++) { + if (i==8 || i==13 || i==18 || i==23) { + uuid[i] = '-'; + } else if (i==14) { + uuid[i] = '4'; + } else { + if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0; + r = rnd & 0xf; + rnd = rnd >> 4; + uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; + } + } + return uuid.join(''); + }; + + // A more compact, but less performant, RFC4122v4 solution: + Math.uuidCompact = function() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); + }; +})(); diff --git a/openjscad.js b/openjscad.js index 49ff188..1039ca7 100644 --- a/openjscad.js +++ b/openjscad.js @@ -308,8 +308,8 @@ OpenJsCad.javaScriptToSolidSync = function(script, mainParameters, debugging) { // callback: should be function(error, csg) OpenJsCad.javaScriptToSolidASync = function(script, mainParameters, callback) { var baselibraries = [ - "csg.js", - "openjscad.js" + "../csg.js", + "../openjscad.js" ]; var baseurl = document.location + ""; var workerscript = ""; diff --git a/simple.html b/simple.html index 4b5c4fc..0833bce 100644 --- a/simple.html +++ b/simple.html @@ -1,27 +1,27 @@ - - - - - + + + + + + - OpenJsCad - +
    -

    OpenJsCad

    - [Update] + Update
    - +