diff --git a/README.md b/README.md index f3ae006..ebd0357 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,11 @@ Computational Geometry and Rendering library for JavaScript -**version: 0.9.9** (73 kB minified) +**version: 0.9.8** (72 kB minified) + + +[API Reference](/manual.md) + Examples: @@ -23,24 +27,34 @@ Examples: [See it](https://foo123.github.io/examples/geometrize/) ```javascript -const {Plane, Ellipse, Circle, Arc, QBezier, CBezier, Line, Point} = Geometrize; +const {Plane, CompositeCurve, ParametricCurve, Ellipse, Circle, Arc, QBezier, CBezier, Line, Polyline, Polygon, Rect, Matrix} = Geometrize; const plane = Plane(document.getElementById('container'), 0, 0, 300, 300); +const spiral = ParametricCurve((t) => ({x:190 + t*50*Math.cos(t*6*Math.PI), y:80 + t*50*Math.sin(t*6*Math.PI)})); const ellipse = Ellipse([40,40], 30, 10, -45); const circle = Circle([30,30], 20); const arc = Arc([100,100], [170,90], 30, 10, 30, 0, 1); const qbezier = QBezier([[80,110], [120,40], [160,120]]); const cbezier = CBezier([[40,80], [120,40], [140,200], [160,90]]); +const curve = CompositeCurve([ + Line([20,100], [40,100]), + Arc([40,100], [40,80], 10, 10, 0, 0, 0), + QBezier([[40,80],[20,100],[60,120]]), + Line([60,120], [70,100]) +]).transform(Matrix.translate(-20, 0)); const line1 = Line([20,20], [60,60]).setStyle('stroke', 'blue'); const line2 = Line([50,2], [20,70]).setStyle('stroke', 'green'); const line3 = Line([60,160], [300,0]).setStyle('stroke', 'orange'); const line4 = Line([60,120], [300,-40]).setStyle('stroke', 'cyan'); + let intersections = []; +plane.add(spiral); plane.add(ellipse); plane.add(circle); plane.add(arc); plane.add(qbezier); plane.add(cbezier); +plane.add(curve); plane.add(line1); plane.add(line2); plane.add(line3); diff --git a/beeld.config b/beeld.config index 6c23eec..5c77e8e 100644 --- a/beeld.config +++ b/beeld.config @@ -48,18 +48,16 @@ tasks =[{}] header = ./src/header.js replace =[{}] - "@@VERSION@@" = "0.9.9" + "@@VERSION@@" = "0.9.8" "@@DATE@@" = Xpresion::date("Y-m-d H:i:s") @ - # Extract documentation from the source (map) + # extract documentation doc ={} - - "startdoc" = "/**[DOC_MARKDOWN]" - "enddoc" = "[/DOC_MARKDOWN]**/" - "trim" = RegExp::^\\s*\\*[ ]? + "startdoc" = "/**[DOC_MD]" + "enddoc" = "[/DOC_MD]**/" + "trim" = RegExp::^\s*\*[ ]? "output" = "./manual.md" - @ out = ./build/Geometrize.js diff --git a/boundingboxes.png b/boundingboxes.png index 43f5cbf..77ab904 100644 Binary files a/boundingboxes.png and b/boundingboxes.png differ diff --git a/build/Geometrize.js b/build/Geometrize.js index 99c8ed3..95aff17 100644 --- a/build/Geometrize.js +++ b/build/Geometrize.js @@ -2,14 +2,14 @@ * Geometrize * computational geometry and rendering library for JavaScript * -* @version 0.9.9 (2023-01-03 18:38:02) +* @version 0.9.8 (2023-01-04 10:07:44) * https://github.com/foo123/Geometrize * **//** * Geometrize * computational geometry and rendering library for JavaScript * -* @version 0.9.9 (2023-01-03 18:38:02) +* @version 0.9.8 (2023-01-04 10:07:44) * https://github.com/foo123/Geometrize * **/ @@ -40,7 +40,7 @@ var HAS = Object.prototype.hasOwnProperty, isNode = ("undefined" !== typeof global) && ("[object global]" === toString.call(global)), isBrowser = ("undefined" !== typeof window) && ("[object Window]" === toString.call(window)), root = isNode ? global : (isBrowser ? window : this), - Geometrize = {VERSION: "0.9.9", Math: {}, Geometry: {}} + Geometrize = {VERSION: "0.9.8", Math: {}, Geometry: {}} ; // basic backwards-compatible "class" construction @@ -175,7 +175,17 @@ var Value = makeClass(null, merge(null, { val: null, valueOf: null, toString: null -}, Changeable));// 2D Homogeneous Transformation Matrix class +}, Changeable));/**[DOC_MD] + * ### 2D Homogeneous Transformation Matrix + * + * Represents a homogeneous transformation matrix for 2D transforms + * + * ```javascript + * const m = Matrix.translate(tx, ty).mul(Matrix.rotate(theta).mul(Matrix.scale(sx, sy))); + * // p is a point, p2 is a transformed point + * const p2 = m.transform(p); + * ``` +[/DOC_MD]**/ var Matrix = makeClass(null, { constructor: function Matrix( m00, m01, m02, @@ -256,12 +266,12 @@ var Matrix = makeClass(null, { if (other instanceof Matrix) { return new Matrix( - self.$00*other.$00 + self.$01*other.$10 + self.$02*other.$20, - self.$00*other.$01 + self.$01*other.$11 + self.$02*other.$21, - self.$00*other.$02 + self.$01*other.$12 + self.$02*other.$22, - self.$10*other.$00 + self.$11*other.$10 + self.$12*other.$20, - self.$10*other.$01 + self.$11*other.$11 + self.$12*other.$21, - self.$10*other.$02 + self.$11*other.$12 + self.$12*other.$22 + self.$00*other.$00 + self.$01*other.$10, + self.$00*other.$01 + self.$01*other.$11, + self.$00*other.$02 + self.$01*other.$12 + self.$02, + self.$10*other.$00 + self.$11*other.$10, + self.$10*other.$01 + self.$11*other.$11, + self.$10*other.$02 + self.$11*other.$12 + self.$12 ); } else @@ -275,13 +285,12 @@ var Matrix = makeClass(null, { }, det: function() { var self = this; - return self.$00*(self.$11*self.$22 - self.$12*self.$21) + self.$01*(self.$12*self.$20 - self.$10*self.$22) + self.$02*(self.$21*self.$10 - self.$11*self.$20); + return self.$00*(self.$11*/*self.$22*/1 - self.$12*/*self.$21*/0) + self.$01*(self.$12*/*self.$20*/0 - self.$10*/*self.$22*/1) + self.$02*(0/*self.$21*/*self.$10 - self.$11*/*self.$20*/0); }, inv: function() { var self = this, a00 = self.$00, a01 = self.$01, a02 = self.$02, a10 = self.$10, a11 = self.$11, a12 = self.$12, - //a20 = self.$20, a21 = self.$21, a22 = self.$22, det2 = a00*a11 - a01*a10, i00 = 0, i01 = 0, i10 = 0, i11 = 0; @@ -675,7 +684,15 @@ var Primitive = makeClass(null, merge(null, { } }, Changeable)); Geometrize.Primitive = Primitive; -// 2D Point class +/**[DOC_MD] + * ### 2D Point + * + * Represents a point in 2D space + * + * ```javascript + * const p = Point(x, y); + * ``` +[/DOC_MD]**/ var Point = makeClass(Primitive, { constructor: function Point(x, y) { var self = this, _x = 0, _y = 0, _n = null; @@ -886,7 +903,14 @@ var Point = makeClass(Primitive, { } }); Geometrize.Point = Point; -// 2D geometric Topos ie set of points +/**[DOC_MD] + * ### 2D Topos + * + * Represents a geometric topos, ie a set of points + * ```javascript + * const topos = Topos([p1, p2, p3, .., pn]); + * ``` +[/DOC_MD]**/ var Topos = makeClass(Primitive, { constructor: function Topos(points) { var self = this, @@ -1046,7 +1070,13 @@ var Topos = makeClass(Primitive, { } }); Geometrize.Topos = Topos; -// 2D generic Curve base class +/**[DOC_MD] + * ### 2D Generic Curve Base Class + * + * Represents a generic curve in 2D space + * (not used directly) + * +[/DOC_MD]**/ var Curve = makeClass(Topos, { constructor: function Curve(points, values) { var self = this, @@ -1288,7 +1318,13 @@ var Curve = makeClass(Topos, { }); Geometrize.Curve = Curve; -// 2D generic Bezier curve base class +/**[DOC_MD] + * ### 2D Generic Bezier Curve Base Class + * + * Represents a generic bezier curve in 2D space + * (not used directly) + * +[/DOC_MD]**/ var Bezier = makeClass(Curve, { constructor: function Bezier(points, values) { var self = this; @@ -1344,7 +1380,15 @@ var Bezier = makeClass(Curve, { }); Geometrize.Bezier = Bezier; -// 2D generix Parametric Curve class (defined by parametric function f) +/**[DOC_MD] + * ### 2D Generic Parametric Curve + * + * Represents a generic parametric curve in 2D space + * ```javascript + * // construct a spiral (0 <= t <= 1) + * const spiral = ParametricCurve((t) => ({x: cx + t*r*Math.cos(t*6*Math.PI), y: cy + t*r*Math.sin(t*6*Math.PI)})); + * ``` +[/DOC_MD]**/ var ParametricCurve = makeClass(Curve, { constructor: function ParametricCurve(f) { var self = this, _length = null, _bbox = null; @@ -1403,13 +1447,22 @@ var ParametricCurve = makeClass(Curve, { transform: function(matrix) { return (new ParametricCurve(this.f)).setMatrix(matrix); }, - fto: function(tt) { - var f = this.f, p1 = f(tt); - return new ParametricCurve(function(t) {return t > tt ? {x:p1.x, y:p1.y} : f(t);}); + fto: function(t1) { + var f = this.f, p1 = f(t1); + return new ParametricCurve(function(t) {return t >= t1 ? {x:p1.x, y:p1.y} : f(t*t1);}); + }, + isClosed: function() { + var self = this, p = self._lines; + return 2 < p.length ? p_eq(p[0], p[p.length-1]) : false; }, hasPoint: function(point) { return point_on_polyline(point, this._lines); }, + hasInsidePoint: function(point, strict) { + if (!this.isClosed()) return false; + var inside = point_inside_polyline(point, {x:this._bbox.xmax+10, y:point.y}, this._lines); + return strict ? 1 === inside : 0 < inside; + }, intersects: function(other) { var self = this, i; if (other instanceof Point) @@ -1517,7 +1570,7 @@ var ParametricCurve = makeClass(Curve, { path = 'M ' + p.map(function(p) { return Str(p.x)+' '+Str(p.y); }).join(' L '); - if (p_eq(p[0], p[p.length-1])) path += ' Z'; + if (self.isClosed()) path += ' Z'; return arguments.length ? SVG('path', { 'id': [self.id, false], 'd': [path, self.isChanged()], @@ -1536,7 +1589,7 @@ var ParametricCurve = makeClass(Curve, { ctx.beginPath(); ctx.moveTo(p[0].x, p[0].y); for (i=1; it.x?n:t),(!x&&(st.y?n:t),{ymin:h.y,xmin:f.x,ymax:a.y,xmax:o.x}}),configurable:!1}),void(l.isChanged=function(n){return!0===n&&(v=m=d=null),l.$super("isChanged",arguments)})):new n(t,e,r,i,o,u,s)},name:"Arc",clone:function(){var n=this;return new M(n.start.clone(),n.end.clone(),n.radiusX,n.radiusY,n.angle,n.largeArc,n.sweep)},transform:function(n){var t=this,e=t.radiusX,r=t.radiusY,i=t.angle,o=On(n.getRotationAngle()),u=n.getScale();return new M(t.start.transform(n),t.end.transform(n),e*u.x,r*u.y,i+o,t.largeArc,t.sweep)},isClosed:function(){return!1},isConvex:function(){return!1},hasMatrix:function(){return!1},f:function(n){var t=this,e=t.center,r=t.cs;return Vn(t.theta+n*t.dtheta,e.x,e.y,t.rX,t.rY,r[0],r[1])},fto:function(n){var t=this;return new M(t.start,t.f(n),t.radiusX,t.radiusY,t.angle,t.largeArc,t.sweep)},d:function(){var n,t,e,r,i,o,u,s=this,u=(n=s.center.x,t=s.center.y,e=s.rY,r=s.rX,i=[s.cs[0],-s.cs[1]],o=-s.theta,u=-s.dtheta,{p0:Vn(o,n,t,e,r,i[0],i[1]),p1:Vn(o+u,n,t,e,r,i[0],i[1]),fa:$(u)>w,fs:0=P&&n.closePath()},toTex:function(){var n=this;return"\\text{Arc: }\\left("+[ot(n.start),ot(n.end),rt(n.radiusX),rt(n.radiusY),rt(n.angle)+"\\text{°}",rt(n.largeArc?1:0),rt(n.sweep?1:0)].join(",")+"\\right)"},toString:function(){var n=this;return"Arc("+[rt(n.start),rt(n.end),rt(n.radiusX),rt(n.radiusY),rt(n.angle)+"°",rt(n.largeArc),rt(n.sweep)].join(",")+")"}});i.Arc=M;var k=e(n,{constructor:function n(t){var r,i=this,e=null,o=null,u=null;return t instanceof n?t:i instanceof n?(i.$super("constructor",[t]),b(i,"length",{get:function(){return null==e&&(e=fn(i._lines)),e},enumerable:!0,configurable:!1}),b(i,"_bbox",{get:function(){return null==o&&(o=r(i._points)),o},enumerable:!(r=function(t){var n=xn(t[0].x-2*t[1].x+t[2].x,t[1].x-t[0].x),e=!1===n?[t[1]]:n.map(function(n){return 0<=n&&n<=1?wn(n,t):t[1]}),r=xn(t[0].y-2*t[1].y+t[2].y,t[1].y-t[0].y),n=!1===r?[t[1]]:r.map(function(n){return 0<=n&&n<=1?wn(n,t):t[1]}),r=C.min.apply(C,e.concat([t[0],t[2]]).map(Nn)),e=C.max.apply(C,e.concat([t[0],t[2]]).map(Nn));return{ymin:C.min.apply(C,n.concat([t[0],t[2]]).map(Zn)),xmin:r,ymax:C.max.apply(C,n.concat([t[0],t[2]]).map(Zn)),xmax:e}}),configurable:!1}),b(i,"_hull",{get:function(){var t,n,e;return null==u&&(n=Tn(e=i._points),t=d.rotate(n.R).mul(d.translate(n.Tx,n.Ty)),n=r(e.map(function(n){return t.transform(n,{x:0,y:0})})),e=t.inv(),u=[e.transform(new G(n.xmin,n.ymin)),e.transform(new G(n.xmax,n.ymin)),e.transform(new G(n.xmax,n.ymax)),e.transform(new G(n.xmin,n.ymax))]),u},enumerable:!1,configurable:!1}),void(i.isChanged=function(n){return!0===n&&(u=o=e=null),i.$super("isChanged",arguments)})):new n(t)},name:"QBezier",clone:function(){return new k(this.points.map(function(n){return n.clone()}))},transform:function(t){return new k(this.points.map(function(n){return n.transform(t)}))},f:function(n){return wn(n,this._points)},hasPoint:function(n){return function(n,t){var e;if(!(e=yn(t[0].x-2*t[1].x+t[2].x,-2*t[0].x+2*t[1].x,t[0].x-n.x)))return!1;1"+rt(r)+"":"/>");return t}function Un(u,s,a,i){if(ct(u.onChange))return u;function l(t){u.$cb.forEach(function(n){n(t)})}var c=!0;i=i||Wn;var f=function(n,t){for(var e=n;et.x?n:t),(!x&&(st.y?n:t),{ymin:h.y,xmin:f.x,ymax:a.y,xmax:o.x}}),configurable:!1}),void(l.isChanged=function(n){return!0===n&&(v=m=d=null),l.$super("isChanged",arguments)})):new n(t,e,r,i,o,u,s)},name:"Arc",clone:function(){var n=this;return new M(n.start.clone(),n.end.clone(),n.radiusX,n.radiusY,n.angle,n.largeArc,n.sweep)},transform:function(n){var t=this,e=t.radiusX,r=t.radiusY,i=t.angle,o=On(n.getRotationAngle()),u=n.getScale();return new M(t.start.transform(n),t.end.transform(n),e*u.x,r*u.y,i+o,t.largeArc,t.sweep)},isClosed:function(){return!1},isConvex:function(){return!1},hasMatrix:function(){return!1},f:function(n){var t=this,e=t.center,r=t.cs;return Vn(t.theta+n*t.dtheta,e.x,e.y,t.rX,t.rY,r[0],r[1])},fto:function(n){var t=this;return new M(t.start,t.f(n),t.radiusX,t.radiusY,t.angle,t.largeArc,t.sweep)},d:function(){var n,t,e,r,i,o,u,s=this,u=(n=s.center.x,t=s.center.y,e=s.rY,r=s.rX,i=[s.cs[0],-s.cs[1]],o=-s.theta,u=-s.dtheta,{p0:Vn(o,n,t,e,r,i[0],i[1]),p1:Vn(o+u,n,t,e,r,i[0],i[1]),fa:$(u)>w,fs:0=P&&n.closePath()},toTex:function(){var n=this;return"\\text{Arc: }\\left("+[ot(n.start),ot(n.end),rt(n.radiusX),rt(n.radiusY),rt(n.angle)+"\\text{°}",rt(n.largeArc?1:0),rt(n.sweep?1:0)].join(",")+"\\right)"},toString:function(){var n=this;return"Arc("+[rt(n.start),rt(n.end),rt(n.radiusX),rt(n.radiusY),rt(n.angle)+"°",rt(n.largeArc),rt(n.sweep)].join(",")+")"}});i.Arc=M;var k=e(n,{constructor:function n(t){var r,i=this,e=null,o=null,u=null;return t instanceof n?t:i instanceof n?(i.$super("constructor",[t]),b(i,"length",{get:function(){return null==e&&(e=fn(i._lines)),e},enumerable:!0,configurable:!1}),b(i,"_bbox",{get:function(){return null==o&&(o=r(i._points)),o},enumerable:!(r=function(t){var n=xn(t[0].x-2*t[1].x+t[2].x,t[1].x-t[0].x),e=!1===n?[t[1]]:n.map(function(n){return 0<=n&&n<=1?wn(n,t):t[1]}),r=xn(t[0].y-2*t[1].y+t[2].y,t[1].y-t[0].y),n=!1===r?[t[1]]:r.map(function(n){return 0<=n&&n<=1?wn(n,t):t[1]}),r=C.min.apply(C,e.concat([t[0],t[2]]).map(Nn)),e=C.max.apply(C,e.concat([t[0],t[2]]).map(Nn));return{ymin:C.min.apply(C,n.concat([t[0],t[2]]).map(Zn)),xmin:r,ymax:C.max.apply(C,n.concat([t[0],t[2]]).map(Zn)),xmax:e}}),configurable:!1}),b(i,"_hull",{get:function(){var t,n,e;return null==u&&(n=Tn(e=i._points),t=d.rotate(n.R).mul(d.translate(n.Tx,n.Ty)),n=r(e.map(function(n){return t.transform(n,{x:0,y:0})})),e=t.inv(),u=[e.transform(new G(n.xmin,n.ymin)),e.transform(new G(n.xmax,n.ymin)),e.transform(new G(n.xmax,n.ymax)),e.transform(new G(n.xmin,n.ymax))]),u},enumerable:!1,configurable:!1}),void(i.isChanged=function(n){return!0===n&&(u=o=e=null),i.$super("isChanged",arguments)})):new n(t)},name:"QBezier",clone:function(){return new k(this.points.map(function(n){return n.clone()}))},transform:function(t){return new k(this.points.map(function(n){return n.transform(t)}))},f:function(n){return wn(n,this._points)},hasPoint:function(n){return function(n,t){var e;if(!(e=yn(t[0].x-2*t[1].x+t[2].x,-2*t[0].x+2*t[1].x,t[0].x-n.x)))return!1;1"+rt(r)+"":"/>");return t}function Un(u,s,a,i){if(ct(u.onChange))return u;function l(t){u.$cb.forEach(function(n){n(t)})}var c=!0;i=i||Wn;var f=function(n,t){for(var e=n;e ({x: cx + t*r*Math.cos(t*6*Math.PI), y: cy + t*r*Math.sin(t*6*Math.PI)})); +``` + + + +### 2D Generic Composite Curve + +Represents a container of multiple, not necessarily joined curves +```javascript +// construct a complex curve +const curve = CompositeCurve([Line(p1, p2), QBezier([p3, p4, p5]), Line(p6, p7)]); +``` + + + +### 2D Line Segment (equivalent to Linear Bezier) + +Represents a line segment between 2 points +```javascript +const line = Line(p1, p2); +``` + + + +### 2D Polyline + +Represents an assembly of consecutive line segments between given points +```javascript +const polyline = Polyline([p1, p2, .., pn]); +``` + + + +### 2D Elliptical Arc + +Represents an elliptic arc between start and end (points) having radiusX, radiusY and rotation angle and given largeArc and sweep flags +```javascript +const arc = Arc(start, end, radiusX, radiusY, angle, largeArc, sweep); +``` + + + +### 2D Quadratic Bezier + +Represents a quadratic bezier curve defined by its control points +```javascript +const qbezier = QBezier([p1, p2, p3]); +``` + + + +### 2D Cubic Bezier + +Represents a cubic bezier curve defined by its control points +```javascript +const cbezier = CBezier([p1, p2, p3, p4]); +``` + + + +### 2D Polygon + +Represents a polygon (a closed polyline) defined by its vertices +```javascript +const polygon = Polygon([p1, p2, .., pn]); +``` + + + +### 2D Circle + +Represents a circle of given center (point) and radius +```javascript +const circle = Circle(center, radius); +``` + + + +### 2D Ellipse + +Represents an ellipse of given center (point), radiusX, radiusY and rotation angle +```javascript +const ellipse = Ellipse(center, radiusX, radiusY, angle); +``` + + + +### 2D generic Shape + +container for 2D geometric objects, grouped together +(not implemented yet) + + + + +### 2D Plane + +scene container for 2D geometric objects + +```javascript +const plane = Plane(containerEl, viewBoxMinX, viewBoxMinY, viewBoxMaxX, viewBoxMaxY); +plane.add(Line([p1, p2])); +``` diff --git a/src/Arc.js b/src/Arc.js index 07359fe..80d8d84 100644 --- a/src/Arc.js +++ b/src/Arc.js @@ -1,4 +1,11 @@ -// 2D Elliptic Arc class +/**[DOC_MD] + * ### 2D Elliptical Arc + * + * Represents an elliptic arc between start and end (points) having radiusX, radiusY and rotation angle and given largeArc and sweep flags + * ```javascript + * const arc = Arc(start, end, radiusX, radiusY, angle, largeArc, sweep); + * ``` +[/DOC_MD]**/ var Arc = makeClass(Curve, { constructor: function Arc(start, end, radiusX, radiusY, angle, largeArc, sweep) { var self = this, diff --git a/src/CBezier.js b/src/CBezier.js index b9290f0..ea2e2f9 100644 --- a/src/CBezier.js +++ b/src/CBezier.js @@ -1,4 +1,11 @@ -// 2D Cubic Bezier class +/**[DOC_MD] + * ### 2D Cubic Bezier + * + * Represents a cubic bezier curve defined by its control points + * ```javascript + * const cbezier = CBezier([p1, p2, p3, p4]); + * ``` +[/DOC_MD]**/ var CBezier = makeClass(Bezier, { constructor: function CBezier(points) { var self = this, diff --git a/src/Circle.js b/src/Circle.js index f9c1eb3..49b685f 100644 --- a/src/Circle.js +++ b/src/Circle.js @@ -1,4 +1,11 @@ -// 2D Circle class +/**[DOC_MD] + * ### 2D Circle + * + * Represents a circle of given center (point) and radius + * ```javascript + * const circle = Circle(center, radius); + * ``` +[/DOC_MD]**/ var Circle = makeClass(Curve, { constructor: function Circle(center, radius) { var self = this, diff --git a/src/Color.js b/src/Color.js deleted file mode 100644 index e496b55..0000000 --- a/src/Color.js +++ /dev/null @@ -1,324 +0,0 @@ -// Color utilities -// eg for stroke, fill, .. -var hexRE = /^#([0-9a-fA-F]{3,6})\b/, - rgbRE = /^(rgba?)\b\s*\(([^\)]*)\)/i, - hslRE = /^(hsla?)\b\s*\(([^\)]*)\)/i, - hwbRE = /^(hwba?)\b\s*\(([^\)]*)\)/i, - sepRE = /\s+|,/gm, aRE = /\/\s*(\d*?\.?\d+%?)/; - -function hex2rgb(h) -{ - if (!h || 3 > h.length) - { - return [0, 0, 0, 0]; - } - else if (6 > h.length) - { - return [ - clamp(parseInt(h[0]+h[0], 16)||0, 0, 255), - clamp(parseInt(h[1]+h[1], 16)||0, 0, 255), - clamp(parseInt(h[2]+h[2], 16)||0, 0, 255), - 1 - ]; - } - else - { - return [ - clamp(parseInt(h[0]+h[1], 16)||0, 0, 255), - clamp(parseInt(h[2]+h[3], 16)||0, 0, 255), - clamp(parseInt(h[4]+h[5], 16)||0, 0, 255), - 1 - ]; - } -} -function hsl2rgb(h, s, l, a) -{ - var c, hp, d, x, m, r, g, b; - s /= 100; - l /= 100; - c = (1 - abs(2*l - 1))*s; - hp = h/60; - d = stdMath.floor(hp / 2); - x = c*(1 - abs(hp - 2*d - 1)); - m = l - c/2; - if (hp >= 0 && hp < 1) - { - r = c + m; - g = x + m; - b = 0 + m; - } - else if (hp >= 1 && hp < 2) - { - r = x + m; - g = c + m; - b = 0 + m; - } - else if (hp >= 2 && hp < 3) - { - r = 0 + m; - g = c + m; - b = x + m; - } - else if (hp >= 3 && hp < 4) - { - r = 0 + m; - g = x + m; - b = c + m; - } - else if (hp >= 4 && hp < 5) - { - r = x + m; - g = 0 + m; - b = c + m; - } - else //if (hp >= 5 && hp < 6) - { - r = c + m; - g = 0 + m; - b = x + m; - } - return [ - clamp(stdMath.round(r*255), 0, 255), - clamp(stdMath.round(g*255), 0, 255), - clamp(stdMath.round(b*255), 0, 255), - a - ]; -} -function hsv2rgb(h, s, v, a) -{ - v /= 100; - var l = v*(1 - s/200), lm = stdMath.min(l, 1-l); - return hsl2rgb(h, 0 === lm ? 0 : 100*(v-l)/lm, 100*l, a); -} -function hwb2rgb(h, w, b, a) -{ - var b1 = 1 - b/100; - return hsv2rgb(h, 100 - w/b1, 100*b1, a); -} -function parseColor(s) -{ - var m, hasOpacity; - s = trim(Str(s)).toLowerCase(); - if (m = s.match(hexRE)) - { - // hex - return hex2rgb(m[1]); - } - if (m = s.match(hwbRE)) - { - // hwb(a) - hasOpacity = m[2].match(aRE); - var col = trim(m[2]).split(sepRE).map(trim), - h = col[0] ? col[0] : '0', - w = col[1] ? col[1] : '0', - b = col[2] ? col[2] : '0', - a = hasOpacity ? hasOpacity[1] : '1'; - h = parseFloat(h, 10); - w = '%' === w.slice(-1) ? parseFloat(w, 10) : parseFloat(w, 10)*100/255; - b = '%' === b.slice(-1) ? parseFloat(b, 10) : parseFloat(b, 10)*100/255; - a = '%' === a.slice(-1) ? parseFloat(a, 10)/100 : parseFloat(a, 10); - return hwb2rgb(h, w, b, a); - } - if (m = s.match(hslRE)) - { - // hsl(a) - hasOpacity = m[2].match(aRE); - var col = trim(m[2]).split(sepRE).map(trim), - h = col[0] ? col[0] : '0', - s = col[1] ? col[1] : '0', - l = col[2] ? col[2] : '0', - a = hasOpacity ? hasOpacity[1] : ('hsla' === m[1] && null != col[3] ? col[3] : '1'); - h = parseFloat(h, 10); - s = '%' === s.slice(-1) ? parseFloat(s, 10) : parseFloat(s, 10)*100/255; - l = '%' === l.slice(-1) ? parseFloat(l, 10) : parseFloat(l, 10)*100/255; - a = '%' === a.slice(-1) ? parseFloat(a, 10)/100 : parseFloat(a, 10); - return hsl2rgb(h, s, l, a); - } - if (m = s.match(rgbRE)) - { - // rgb(a) - hasOpacity = m[2].match(aRE); - var col = trim(m[2]).split(sepRE).map(trim), - r = col[0] ? col[0] : '0', - g = col[1] ? col[1] : '0', - b = col[2] ? col[2] : '0', - a = hasOpacity ? hasOpacity[1] : ('rgba' === m[1] && null != col[3] ? col[3] : '1'); - r = '%' === r.slice(-1) ? parseFloat(r, 10)*2.55 : parseFloat(r, 10); - g = '%' === g.slice(-1) ? parseFloat(g, 10)*2.55 : parseFloat(g, 10); - b = '%' === b.slice(-1) ? parseFloat(b, 10)*2.55 : parseFloat(b, 10); - a = '%' === a.slice(-1) ? parseFloat(a, 10)/100 : parseFloat(a, 10); - return [r, g, b, a]; - } - if (HAS.call(Color.keywords, s)) - { - // keyword - return Color.keywords[s].slice(); - } -} -var Color = { - keywords: { - // https://developer.mozilla.org/en-US/docs/Web/CSS/color_value - /* extended */ - 'transparent' : [ 0,0,0 ,0] - ,'aliceblue' : [ 240,248,255 ,1] - ,'antiquewhite' : [ 250,235,215 ,1] - ,'aqua' : [ 0,255,255 ,1] - ,'aquamarine' : [ 127,255,212 ,1] - ,'azure' : [ 240,255,255 ,1] - ,'beige' : [ 245,245,220 ,1] - ,'bisque' : [ 255,228,196 ,1] - ,'black' : [ 0,0,0 , 1] - ,'blanchedalmond' : [ 255,235,205 ,1] - ,'blue' : [ 0,0,255 , 1] - ,'blueviolet' : [ 138,43,226 ,1] - ,'brown' : [ 165,42,42 ,1] - ,'burlywood' : [ 222,184,135 ,1] - ,'cadetblue' : [ 95,158,160 ,1] - ,'chartreuse' : [ 127,255,0 ,1] - ,'chocolate' : [ 210,105,30 ,1] - ,'coral' : [ 255,127,80 ,1] - ,'cornflowerblue' : [ 100,149,237 ,1] - ,'cornsilk' : [ 255,248,220 ,1] - ,'crimson' : [ 220,20,60 ,1] - ,'cyan' : [ 0,255,255 ,1] - ,'darkblue' : [ 0,0,139 , 1] - ,'darkcyan' : [ 0,139,139 ,1] - ,'darkgoldenrod' : [ 184,134,11 ,1] - ,'darkgray' : [ 169,169,169 ,1] - ,'darkgreen' : [ 0,100,0 , 1] - ,'darkgrey' : [ 169,169,169 ,1] - ,'darkkhaki' : [ 189,183,107 ,1] - ,'darkmagenta' : [ 139,0,139 ,1] - ,'darkolivegreen' : [ 85,107,47 ,1] - ,'darkorange' : [ 255,140,0 ,1] - ,'darkorchid' : [ 153,50,204 ,1] - ,'darkred' : [ 139,0,0 , 1] - ,'darksalmon' : [ 233,150,122 ,1] - ,'darkseagreen' : [ 143,188,143 ,1] - ,'darkslateblue' : [ 72,61,139 ,1] - ,'darkslategray' : [ 47,79,79 , 1] - ,'darkslategrey' : [ 47,79,79 , 1] - ,'darkturquoise' : [ 0,206,209 ,1] - ,'darkviolet' : [ 148,0,211 ,1] - ,'deeppink' : [ 255,20,147 ,1] - ,'deepskyblue' : [ 0,191,255 ,1] - ,'dimgray' : [ 105,105,105 ,1] - ,'dimgrey' : [ 105,105,105 ,1] - ,'dodgerblue' : [ 30,144,255 ,1] - ,'firebrick' : [ 178,34,34 ,1] - ,'floralwhite' : [ 255,250,240 ,1] - ,'forestgreen' : [ 34,139,34 ,1] - ,'fuchsia' : [ 255,0,255 ,1] - ,'gainsboro' : [ 220,220,220 ,1] - ,'ghostwhite' : [ 248,248,255 ,1] - ,'gold' : [ 255,215,0 ,1] - ,'goldenrod' : [ 218,165,32 ,1] - ,'gray' : [ 128,128,128 ,1] - ,'green' : [ 0,128,0 , 1] - ,'greenyellow' : [ 173,255,47 ,1] - ,'grey' : [ 128,128,128 ,1] - ,'honeydew' : [ 240,255,240 ,1] - ,'hotpink' : [ 255,105,180 ,1] - ,'indianred' : [ 205,92,92 ,1] - ,'indigo' : [ 75,0,130 , 1] - ,'ivory' : [ 255,255,240 ,1] - ,'khaki' : [ 240,230,140 ,1] - ,'lavender' : [ 230,230,250 ,1] - ,'lavenderblush' : [ 255,240,245 ,1] - ,'lawngreen' : [ 124,252,0 ,1] - ,'lemonchiffon' : [ 255,250,205 ,1] - ,'lightblue' : [ 173,216,230 ,1] - ,'lightcoral' : [ 240,128,128 ,1] - ,'lightcyan' : [ 224,255,255 ,1] - ,'lightgoldenrodyellow': [ 250,250,210 ,1] - ,'lightgray' : [ 211,211,211 ,1] - ,'lightgreen' : [ 144,238,144 ,1] - ,'lightgrey' : [ 211,211,211 ,1] - ,'lightpink' : [ 255,182,193 ,1] - ,'lightsalmon' : [ 255,160,122 ,1] - ,'lightseagreen' : [ 32,178,170 ,1] - ,'lightskyblue' : [ 135,206,250 ,1] - ,'lightslategray' : [ 119,136,153 ,1] - ,'lightslategrey' : [ 119,136,153 ,1] - ,'lightsteelblue' : [ 176,196,222 ,1] - ,'lightyellow' : [ 255,255,224 ,1] - ,'lime' : [ 0,255,0 , 1] - ,'limegreen' : [ 50,205,50 ,1] - ,'linen' : [ 250,240,230 ,1] - ,'magenta' : [ 255,0,255 ,1] - ,'maroon' : [ 128,0,0 , 1] - ,'mediumaquamarine' : [ 102,205,170 ,1] - ,'mediumblue' : [ 0,0,205 , 1] - ,'mediumorchid' : [ 186,85,211 ,1] - ,'mediumpurple' : [ 147,112,219 ,1] - ,'mediumseagreen' : [ 60,179,113 ,1] - ,'mediumslateblue' : [ 123,104,238 ,1] - ,'mediumspringgreen' : [ 0,250,154 ,1] - ,'mediumturquoise' : [ 72,209,204 ,1] - ,'mediumvioletred' : [ 199,21,133 ,1] - ,'midnightblue' : [ 25,25,112 ,1] - ,'mintcream' : [ 245,255,250 ,1] - ,'mistyrose' : [ 255,228,225 ,1] - ,'moccasin' : [ 255,228,181 ,1] - ,'navajowhite' : [ 255,222,173 ,1] - ,'navy' : [ 0,0,128 , 1] - ,'oldlace' : [ 253,245,230 ,1] - ,'olive' : [ 128,128,0 ,1] - ,'olivedrab' : [ 107,142,35 ,1] - ,'orange' : [ 255,165,0 ,1] - ,'orangered' : [ 255,69,0 , 1] - ,'orchid' : [ 218,112,214 ,1] - ,'palegoldenrod' : [ 238,232,170 ,1] - ,'palegreen' : [ 152,251,152 ,1] - ,'paleturquoise' : [ 175,238,238 ,1] - ,'palevioletred' : [ 219,112,147 ,1] - ,'papayawhip' : [ 255,239,213 ,1] - ,'peachpuff' : [ 255,218,185 ,1] - ,'peru' : [ 205,133,63 ,1] - ,'pink' : [ 255,192,203 ,1] - ,'plum' : [ 221,160,221 ,1] - ,'powderblue' : [ 176,224,230 ,1] - ,'purple' : [ 128,0,128 ,1] - ,'red' : [ 255,0,0 , 1] - ,'rosybrown' : [ 188,143,143 ,1] - ,'royalblue' : [ 65,105,225 ,1] - ,'saddlebrown' : [ 139,69,19 ,1] - ,'salmon' : [ 250,128,114 ,1] - ,'sandybrown' : [ 244,164,96 ,1] - ,'seagreen' : [ 46,139,87 ,1] - ,'seashell' : [ 255,245,238 ,1] - ,'sienna' : [ 160,82,45 ,1] - ,'silver' : [ 192,192,192 ,1] - ,'skyblue' : [ 135,206,235 ,1] - ,'slateblue' : [ 106,90,205 ,1] - ,'slategray' : [ 112,128,144 ,1] - ,'slategrey' : [ 112,128,144 ,1] - ,'snow' : [ 255,250,250 ,1] - ,'springgreen' : [ 0,255,127 ,1] - ,'steelblue' : [ 70,130,180 ,1] - ,'tan' : [ 210,180,140 ,1] - ,'teal' : [ 0,128,128 ,1] - ,'thistle' : [ 216,191,216 ,1] - ,'tomato' : [ 255,99,71 ,1] - ,'turquoise' : [ 64,224,208 ,1] - ,'violet' : [ 238,130,238 ,1] - ,'wheat' : [ 245,222,179 ,1] - ,'white' : [ 255,255,255 ,1] - ,'whitesmoke' : [ 245,245,245 ,1] - ,'yellow' : [ 255,255,0 ,1] - ,'yellowgreen' : [ 154,205,50 ,1] - }, - parse: parseColor, - toCSS: function(r, g, b, a) { - if (1 === arguments.length) - { - var rgba = r; - return 3 < rgba.length ? 'rgba('+rgba[0]+','+rgba[1]+','+rgba[2]+','+rgba[3]+')' : 'rgb('+rgba[0]+','+rgba[1]+','+rgba[2]+')'; - } - else - { - return 3 < arguments.length ? 'rgba('+r+','+g+','+b+','+a+')' : 'rgb('+r+','+g+','+b+')'; - } - } -}; -Geometrize.Color = Color; diff --git a/src/Curve.js b/src/Curve.js index 028fddd..2252170 100644 --- a/src/Curve.js +++ b/src/Curve.js @@ -1,4 +1,10 @@ -// 2D generic Curve base class +/**[DOC_MD] + * ### 2D Generic Curve Base Class + * + * Represents a generic curve in 2D space + * (not used directly) + * +[/DOC_MD]**/ var Curve = makeClass(Topos, { constructor: function Curve(points, values) { var self = this, @@ -240,7 +246,13 @@ var Curve = makeClass(Topos, { }); Geometrize.Curve = Curve; -// 2D generic Bezier curve base class +/**[DOC_MD] + * ### 2D Generic Bezier Curve Base Class + * + * Represents a generic bezier curve in 2D space + * (not used directly) + * +[/DOC_MD]**/ var Bezier = makeClass(Curve, { constructor: function Bezier(points, values) { var self = this; @@ -296,7 +308,15 @@ var Bezier = makeClass(Curve, { }); Geometrize.Bezier = Bezier; -// 2D generix Parametric Curve class (defined by parametric function f) +/**[DOC_MD] + * ### 2D Generic Parametric Curve + * + * Represents a generic parametric curve in 2D space + * ```javascript + * // construct a spiral (0 <= t <= 1) + * const spiral = ParametricCurve((t) => ({x: cx + t*r*Math.cos(t*6*Math.PI), y: cy + t*r*Math.sin(t*6*Math.PI)})); + * ``` +[/DOC_MD]**/ var ParametricCurve = makeClass(Curve, { constructor: function ParametricCurve(f) { var self = this, _length = null, _bbox = null; @@ -355,13 +375,22 @@ var ParametricCurve = makeClass(Curve, { transform: function(matrix) { return (new ParametricCurve(this.f)).setMatrix(matrix); }, - fto: function(tt) { - var f = this.f, p1 = f(tt); - return new ParametricCurve(function(t) {return t > tt ? {x:p1.x, y:p1.y} : f(t);}); + fto: function(t1) { + var f = this.f, p1 = f(t1); + return new ParametricCurve(function(t) {return t >= t1 ? {x:p1.x, y:p1.y} : f(t*t1);}); + }, + isClosed: function() { + var self = this, p = self._lines; + return 2 < p.length ? p_eq(p[0], p[p.length-1]) : false; }, hasPoint: function(point) { return point_on_polyline(point, this._lines); }, + hasInsidePoint: function(point, strict) { + if (!this.isClosed()) return false; + var inside = point_inside_polyline(point, {x:this._bbox.xmax+10, y:point.y}, this._lines); + return strict ? 1 === inside : 0 < inside; + }, intersects: function(other) { var self = this, i; if (other instanceof Point) @@ -469,7 +498,7 @@ var ParametricCurve = makeClass(Curve, { path = 'M ' + p.map(function(p) { return Str(p.x)+' '+Str(p.y); }).join(' L '); - if (p_eq(p[0], p[p.length-1])) path += ' Z'; + if (self.isClosed()) path += ' Z'; return arguments.length ? SVG('path', { 'id': [self.id, false], 'd': [path, self.isChanged()], @@ -488,7 +517,7 @@ var ParametricCurve = makeClass(Curve, { ctx.beginPath(); ctx.moveTo(p[0].x, p[0].y); for (i=1; iGeometrize Intersections, Bounding Boxes, Convex Hulls }; const drawConvexHull = (shape, color) => { const hull = shape.getConvexHull(); - console.log(shape.name, hull); plane.add(Polygon(hull).setStyle('stroke', color||'red')); };