From 5f147dee594640a4545e1ba8def114a40d16595f Mon Sep 17 00:00:00 2001 From: Joost Nieuwenhuijse Date: Thu, 16 Feb 2012 13:22:56 +0100 Subject: [PATCH] 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() {