Added support for 2D paths

gh-pages
Joost Nieuwenhuijse 2012-02-15 14:58:16 +01:00
parent 1b96f7ba6b
commit b059ed958a
2 changed files with 379 additions and 0 deletions

350
csg.js
View File

@ -473,6 +473,14 @@ CSG.parseOptionAs3DVector = function(options, optionname, defaultvalue) {
return result;
};
// Parse an option and force into a CSG.Vector2D. If a scalar is passed it is converted
// into a vector with equal x,y
CSG.parseOptionAs2DVector = function(options, optionname, defaultvalue) {
var result = CSG.parseOption(options, optionname, defaultvalue);
result = new CSG.Vector2D(result);
return result;
};
CSG.parseOptionAsFloat = function(options, optionname, defaultvalue) {
var result = CSG.parseOption(options, optionname, defaultvalue);
if(typeof(result) == "string")
@ -491,6 +499,18 @@ CSG.parseOptionAsInt = function(options, optionname, defaultvalue) {
return Number(Math.floor(result));
};
CSG.parseOptionAsBool = function(options, optionname, defaultvalue) {
var result = CSG.parseOption(options, optionname, defaultvalue);
if(typeof(result) == "string")
{
if(result == "true") result = true;
if(result == "false") result = false;
if(result == 0) result = false;
}
result = !!result;
return result;
};
// Construct an axis-aligned solid cuboid.
// Parameters:
// center: center of cube (default [0,0,0])
@ -2268,6 +2288,15 @@ CSG.Vector2D = function(x, y) {
};
CSG.Vector2D.fromAngle = function(radians) {
return CSG.Vector2D.fromAngleRadians(radians);
};
CSG.Vector2D.fromAngleDegrees = function(degrees) {
var radians = Math.PI * degrees / 180;
return CSG.Vector2D.fromAngleRadians(radians);
};
CSG.Vector2D.fromAngleRadians = function(radians) {
return new CSG.Vector2D(Math.cos(radians), Math.sin(radians));
};
@ -2337,6 +2366,15 @@ CSG.Vector2D.prototype = {
},
angle: function() {
return this.angleRadians();
},
angleDegrees: function() {
var radians = this.angleRadians();
return 180 * radians / Math.PI;
},
angleRadians: function() {
// y=sin, x=cos
return Math.atan2(this.y, this.x);
},
@ -3495,3 +3533,315 @@ CSG.Connector.prototype = {
return transformation;
},
};
//////////////////////////////////////
// # Class Path2D
CSG.Path2D = function(points, closed) {
closed = !!closed;
points = points || [];
// re-parse the points into CSG.Vector2D
// and remove any duplicate points
var prevpoint = null;
if(closed && (points.length > 0))
{
prevpoint = new CSG.Vector2D(points[points.length-1]);
}
var newpoints = [];
points.map(function(point) {
point = new CSG.Vector2D(point);
var skip = false;
if(prevpoint !== null)
{
var distance = point.distanceTo(prevpoint);
skip = distance < 1e-5;
}
if(!skip) newpoints.push(point);
prevpoint = point;
});
this.points = newpoints;
this.closed = closed;
};
/*
Construct a (part of a) circle. Parameters:
options.center: the center point of the arc (CSG.Vector2D or array [x,y])
options.radius: the circle radius (float)
options.startangle: the starting angle of the arc, in degrees
0 degrees corresponds to [1,0]
90 degrees to [0,1]
and so on
options.endangle: the ending angle of the arc, in degrees
options.resolution: number of points per 360 degree of rotation
options.maketangent: adds two extra tiny line segments at both ends of the circle
this ensures that the gradients at the edges are tangent to the circle
Returns a CSG.Path2D. The path is not closed (even if it is a 360 degree arc).
close() the resultin path if you want to create a true circle.
*/
CSG.Path2D.arc = function(options) {
var center = CSG.parseOptionAs2DVector(options, "center", 0);
var radius = CSG.parseOptionAsFloat(options, "radius", 1);
var startangle = CSG.parseOptionAsFloat(options, "startangle", 0);
var endangle = CSG.parseOptionAsFloat(options, "endangle", 360);
var resolution = CSG.parseOptionAsFloat(options, "resolution", 16);
var maketangent =CSG.parseOptionAsBool(options, "maketangent", false);
// no need to make multiple turns:
while(endangle - startangle >= 720)
{
endangle -= 360;
}
while(endangle - startangle <= -720)
{
endangle += 360;
}
var points = [];
var absangledif = Math.abs(endangle-startangle);
if(absangledif < 1e-5)
{
var point = CSG.Vector2D.fromAngle(startangle / 180.0 * Math.PI).times(radius);
points.push(point.plus(center));
}
else
{
var numsteps = Math.floor(resolution * absangledif / 360) + 1;
var edgestepsize = numsteps * 0.5 / absangledif; // step size for half a degree
if(edgestepsize > 0.25) edgestepsize = 0.25;
var numsteps_mod = maketangent? (numsteps+2):numsteps;
for(var i = 0; i <= numsteps_mod; i++)
{
var step = i;
if(maketangent)
{
step = (i-1)*(numsteps-2*edgestepsize)/numsteps+edgestepsize;
if(step < 0) step = 0;
if(step > numsteps) step = numsteps;
}
var angle = startangle + step * (endangle - startangle) / numsteps;
var point = CSG.Vector2D.fromAngle(angle / 180.0 * Math.PI).times(radius);
points.push(point.plus(center));
}
}
return new CSG.Path2D(points, false);
};
CSG.Path2D.prototype = {
concat: function(otherpath) {
if(this.closed || otherpath.closed)
{
throw new Error("Paths must not be closed");
}
var newpoints = this.points.concat(otherpath.points);
return new CSG.Path2D(newpoints);
},
appendPoint: function(point) {
if(this.closed)
{
throw new Error("Paths must not be closed");
}
var newpoints = this.points.concat([point]);
return new CSG.Path2D(newpoints);
},
close: function() {
return new CSG.Path2D(this.points, true);
},
// 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
// height: height of the extrusion in the z direction
// resolution: number of segments per 360 degrees for the curve in a corner
// roundEnds: if true, the ends of the polygon will be rounded, otherwise they will be flat
rectangularExtrude: function(width, height, resolution, roundEnds) {
var polygon2ds = this.toPolygon2Ds(width/2, resolution, roundEnds);
var result = new CSG();
var offsetvector = [0, 0, height];
polygon2ds.map(function(polygon) {
var csg = polygon.extrude({offset: offsetvector});
result = result.union(csg);
});
return result;
},
// expand the path (which is just a line with no width) to a 2D shape with a certain path width
// Returns an array of CSG.Polygon2D. Note that those polygons may overlap.
// pathradius: radius of the path, i.e. half of the diameter of the path
// resolution: number of segments per 360 degrees for the curve in a corner
// roundEnds: if true, the ends of the polygon will be rounded, otherwise they will be flat
toPolygon2Ds: function(pathradius, resolution, roundEnds) {
resolution = resolution || 16;
roundEnds = !!roundEnds;
if(this.closed) roundEnds = false; // a closed curve has no ends
if(resolution < 4) resolution = 4;
var polygons = [];
if(this.points.length >= 1)
{
for(var i = 0; i < this.points.length; i++)
{
var previ = i-1;
if(previ < 0)
{
if(this.closed)
{
previ += this.points.length;
}
else
{
if(this.points.length >= 2)
{
previ = i+1;
}
else
{
previ = i;
}
}
}
var prevpoint = this.points[previ];
var point = this.points[i];
var direction;
if(this.points.length >= 2)
{
direction = point.minus(prevpoint).unit();
}
else
{
direction = new CSG.Vector2D(1,0); // arbitrary
}
var normal = direction.normal().times(pathradius);
if(this.points.length >= 2)
{
if( (this.closed) || (i > 0) )
{
var segpoints = [
prevpoint.minus(normal),
prevpoint.plus(normal),
point.plus(normal),
point.minus(normal)
];
var polygon = new CSG.Polygon2D(segpoints, null);
polygons.push(polygon);
}
}
// make the curved parts between segments and optionally the rounded end:
if(roundEnds || this.closed || ((i > 0)&&(i+1 < this.points.length)))
{
var nexti = i+1;
if(nexti >= this.points.length)
{
if(this.closed)
{
nexti = 0;
}
else
{
// at the end: go backwards, this will create a rounded end
if(this.points.length >= 2)
{
nexti = i-1;
}
else
{
nexti = i;
}
}
}
var nextpoint = this.points[nexti];
var nextdirection;
if(this.points.length >= 2)
{
nextdirection = nextpoint.minus(point).unit();
}
else
{
nextdirection = new CSG.Vector2D(1,0); // arbitrary
}
var nextnormal = nextdirection.normal().times(pathradius);
var directionangle = direction.angleDegrees();
var nextangle = nextdirection.angleDegrees();
if(nextangle > directionangle+180)
{
nextangle -= 360;
}
else if(nextangle <= directionangle-180)
{
nextangle += 360;
}
if(this.points.length == 1)
{
nextangle += 360;
}
var diffangle = nextangle - directionangle;
var absdiffangle = Math.abs(diffangle);
if(absdiffangle > 1e-5)
{
var numsteps = Math.floor(resolution * absdiffangle / 360) + 1;
var prevcornerpoint = null;
for(var step = 0; step <= numsteps; step++)
{
var angle = directionangle + step*diffangle/numsteps;
if(diffangle > 0)
{
angle -= 90;
}
else
{
angle += 90;
}
var cornerpoint;
if(step == 0)
{
// first point of curve. To prevent rounding errors, use the exact point
if(diffangle > 0)
{
cornerpoint = normal;
}
else
{
cornerpoint = normal.negated();
}
}
else if(step == numsteps)
{
// last point of curve. To prevent rounding errors, use the exact point
if(diffangle > 0)
{
cornerpoint = nextnormal;
}
else
{
cornerpoint = nextnormal.negated();
}
}
else
{
cornerpoint = CSG.Vector2D.fromAngleDegrees(angle).times(pathradius);
}
cornerpoint = cornerpoint.plus(point);
if(step > 0)
{
var polygon = new CSG.Polygon2D([point, prevcornerpoint, cornerpoint], null);
polygons.push(polygon);
}
prevcornerpoint = cornerpoint;
} // for step
} // if(absdiffangle > 1e-5)
} // if( (i+1 < this.points.length) || roundEnds || this.closed)
} // for i
}
return polygons;
},
transform: function(matrix4x4) {
var newpoints = this.points.map(function(point) {
return point.multiply4x4(matrix4x4);
});
return new CSG.Path2D(newpoints, this.closed);
},
};

View File

@ -436,6 +436,35 @@ var extruded=shape2d.extrude({
});
</pre>
<h2>2D Paths</h2>
A path is simply a series of points, connected by lines. A path can be open or closed (an additional line
is drawn between the first and last point). 2D paths are supported through the CSG.Path2D class.
<br><br>
Paths can be contructed either by giving a series of 2D coordinates, or through the CSG.Path2D.arc() function,
which creates a circular curved path. Paths can be concatenated, the result is a new path.
<br><br>
Creating a 3D solid is currently supported by the rectangularExtrude() function. This creates a 3D shape
by following the path with a 2D rectangle (upright, perpendicular to the path direction).
<pre>
var path = new CSG.Path2D([[10,10], [-10,10]], /* closed = */ false);
var anotherpath = new CSG.Path2D([[-10,-10]]);
path = path.concat(anotherpath);
path = path.appendPoint([10,-10]);
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);
// 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
// height: height of the extrusion in the z direction
// resolution: number of segments per 360 degrees for the curve in a corner
// roundEnds: if true, the ends of the polygon will be rounded, otherwise they will be flat
var csg = path.rectangularExtrude(3, 4, 16, true);
return csg;
</pre>
<h2>Interactive parametric models</h2>
It is possible to make certain parameters
editable in the browser. This allows users not familiar with JavaScript to create customized STL files.