diff --git a/README.md b/README.md index 8063562..ae60ad2 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,14 @@ intersections.forEach(p => { **Shape Tween** (in progress) -![geometrize circle to square tween](/shapetween.gif) +![geometrize tween between multiple shapes](/shapetween.gif) ```javascript -const {Plane, Tween, Polygon, Circle} = Geometrize, - r = 50, cx = 100, cy = 100; +const {Plane, Tween, Polygon, Circle, Ellipse, Arc} = Geometrize; +const r = 50, rx = 50, ry = 20, cx = 100, cy = 100, angle = -30; const plane = Plane(document.getElementById('container'), 300, 300); +const ellipse = Ellipse([cx, cy], rx, ry, angle); +const arc = Arc(ellipse.getPointAt(0), ellipse.getPointAt(0.6), rx, ry, angle, 0, 0); const circle = Circle([cx,cy], r); const square = Polygon([ [cx+r,cy], @@ -60,33 +62,60 @@ const square = Polygon([ const tween = Tween({ keyframes: { "0%": { + shape: arc, + style: { + stroke: 'cyan' + }, + easing: 'ease-out' + }, + "25%": { + shape: ellipse, + style: { + stroke: 'orange' + }, + easing: 'ease-out' + }, + "50%": { shape: circle, style: { stroke: 'cyan' }, easing: 'ease-out' }, - "30%": { + "75%": { shape: square, transform: { - rotate: [0, square.getCenter().x, square.getCenter().y] + rotate: { + angle: 0, + point: { + x: square.getCenter().x, + y: square.getCenter().y + } + } }, style: { stroke: 'orange' }, - easing: 'ease-out-elastic' + easing: 'ease-out-back' }, "100%": { shape: square, transform: { - rotate: [45, square.getCenter().x, square.getCenter().y] + rotate: { + angle: -45, + point: { + x: square.getCenter().x, + y: square.getCenter().y + } + } }, style: { stroke: 'blue' } } }, - duration: 2000 + duration: 4000, + delay: 400 }); plane.add(tween); tween.start(); diff --git a/build/Geometrize.js b/build/Geometrize.js index d3e2e2b..3744d2d 100644 --- a/build/Geometrize.js +++ b/build/Geometrize.js @@ -2,14 +2,14 @@ * Geometrize * computational geometry and rendering library for JavaScript * -* @version 0.4.0 (2022-12-05 19:02:19) +* @version 0.4.0 (2022-12-06 11:13:43) * https://github.com/foo123/Geometrize * **//** * Geometrize * computational geometry and rendering library for JavaScript * -* @version 0.4.0 (2022-12-05 19:02:19) +* @version 0.4.0 (2022-12-06 11:13:43) * https://github.com/foo123/Geometrize * **/ @@ -651,6 +651,33 @@ function parse_color(s) return Color.keywords[s].slice(); } } +function interpolateRGB(r0, g0, b0, a0, r1, g1, b1, a1, t) +{ + if (9 <= arguments.length) + { + var t0 = (t||0), t1 = 1 - t0; + return [ + clamp(stdMath.round(t1*r0 + t0*r1), 0, 255), + clamp(stdMath.round(t1*g0 + t0*g1), 0, 255), + clamp(stdMath.round(t1*b0 + t0*b1), 0, 255), + clamp(t1*a0 + t0*a1, 0, 1) + ]; + } + else + { + var t0 = (b0||0), t1 = 1 - t0, rgba0 = r0, rgba1 = g0; + return 3 < rgba0.length ? [ + clamp(stdMath.round(t1*rgba0[0] + t0*rgba1[0]), 0, 255), + clamp(stdMath.round(t1*rgba0[1] + t0*rgba1[1]), 0, 255), + clamp(stdMath.round(t1*rgba0[2] + t0*rgba1[2]), 0, 255), + clamp(t1*rgba0[3] + t0*rgba1[3], 0, 1) + ] : [ + clamp(stdMath.round(t1*rgba0[0] + t0*rgba1[0]), 0, 255), + clamp(stdMath.round(t1*rgba0[1] + t0*rgba1[1]), 0, 255), + clamp(stdMath.round(t1*rgba0[2] + t0*rgba1[2]), 0, 255) + ]; + } +} var Color = { keywords: { @@ -806,32 +833,7 @@ var Color = { ,'yellowgreen' : [ 154,205,50 ,1] }, parse: parse_color, - interpolateRGB: function(r0, g0, b0, a0, r1, g1, b1, a1, t) { - if (9 <= arguments.length) - { - var t0 = (t||0), t1 = 1 - t0; - return [ - clamp(stdMath.round(t1*r0 + t0*r1), 0, 255), - clamp(stdMath.round(t1*g0 + t0*g1), 0, 255), - clamp(stdMath.round(t1*b0 + t0*b1), 0, 255), - clamp(t1*a0 + t0*a1, 0, 1) - ]; - } - else - { - var t0 = (b0||0), t1 = 1 - t0, rgba0 = r0, rgba1 = g0; - return 3 < rgba0.length ? [ - clamp(stdMath.round(t1*rgba0[0] + t0*rgba1[0]), 0, 255), - clamp(stdMath.round(t1*rgba0[1] + t0*rgba1[1]), 0, 255), - clamp(stdMath.round(t1*rgba0[2] + t0*rgba1[2]), 0, 255), - clamp(t1*rgba0[3] + t0*rgba1[3], 0, 1) - ] : [ - clamp(stdMath.round(t1*rgba0[0] + t0*rgba1[0]), 0, 255), - clamp(stdMath.round(t1*rgba0[1] + t0*rgba1[1]), 0, 255), - clamp(stdMath.round(t1*rgba0[2] + t0*rgba1[2]), 0, 255) - ]; - } - }, + interpolateRGB: interpolateRGB, toCSS: function(r, g, b, a) { if (1 === arguments.length) { @@ -1189,7 +1191,7 @@ var Point = makeClass(Primitive, { y: this.y }; }, - toBezier3: function() { + bezierPoints: function() { var x = this.x, y = this.y; return [ [{x:x, y:y}, {x:x, y:y}, {x:x, y:y}, {x:x, y:y}] @@ -1464,8 +1466,8 @@ var Curve = makeClass(Primitive, { toLines: function() { return this._lines; }, - toBezier3: function() { - return this; + bezierPoints: function() { + return []; }, toTex: function() { return '\\text{Curve}'; @@ -1936,7 +1938,7 @@ var Bezier1 = makeClass(Bezier, { distanceToPoint: function(point) { return point_line_segment_distance(point, this._points[0], this._points[1]); }, - toBezier3: function() { + bezierPoints: function() { var p = this._points; return [ [bezier1(0, p), bezier1(0.5, p), bezier1(0.5, p), bezier1(1, p)] @@ -2170,7 +2172,7 @@ var Polyline = makeClass(Curve, { return dist; }, Infinity)); }, - toBezier3: function() { + bezierPoints: function() { var p = this._points, n = p.length; return p.reduce(function(b, _, i) { if (i+1 < n) @@ -2413,15 +2415,19 @@ var Arc = makeClass(Curve, { get: function() { if (null == _bbox) { - /* - x: 0 = -st*rX*cs[0]*dtheta - ct*rY*cs[1]*dtheta - y: 0 = ct*rY*cs[0]*dtheta - st*rX*cs[1]*dtheta - */ + var dtheta = self.dtheta, + p0, p1, p2, p3, p4; + // yminmax = [-90, 90], xminmax = [0, 180]; + p0 = self.f(0); + p1 = self.f(0.25); + p2 = self.f(0.5); + p3 = self.f(0.75); + p4 = self.f(1); _bbox = { - ymin: -Infinity, - xmin: -Infinity, - ymax: Infinity, - xmax: Infinity + ymin: stdMath.min(p0.y, p1.y, p2.y, p3.y, p4.y), + xmin: stdMath.min(p0.x, p1.x, p2.x, p3.x, p4.x), + ymax: stdMath.max(p0.y, p1.y, p2.y, p3.y, p4.y), + xmax: stdMath.max(p0.x, p1.x, p2.x, p3.x, p4.x) }; } return _bbox; @@ -2488,19 +2494,10 @@ var Arc = makeClass(Curve, { return this._hull; }, f: function(t) { - var c = this.center, - theta = this.theta, - dtheta = this.dtheta, - rX = this.rX, - rY = this.rY, - cs = this.cs, - ct = stdMath.cos(theta + t*dtheta), - st = stdMath.sin(theta + t*dtheta) - ; - return { - x: c.x + rX*cs[0]*ct - rY*cs[1]*st, - y: c.y + rY*cs[0]*st + rX*cs[1]*ct - }; + var c = this.center, cs = this.cs, + rx = this.rX, ry = this.rX, + theta = this.theta, dtheta = this.dtheta; + return arc(theta + t*dtheta, c.x, c.y, rx, ry, cs[0], cs[1]); }, getPointAt: function(t) { t = Num(t); @@ -2539,57 +2536,25 @@ var Arc = makeClass(Curve, { } return false; }, - toBezier3: function() { - var rx = this.rX, + bezierPoints: function() { + var c = this.center, + rx = this.rX, ry = this.rY, - c = this.center, cs = this.cs, cos = cs[0], sin = cs[1], theta = this.theta, dtheta = this.dtheta, - arc = function(x, y) { - x *= rx; - y *= ry; - return { - x:cos*x - sin*y + c.x, - y:sin*x + cos*y + c.y - }; - }, - b3 = function(theta, dtheta, rev) { - var f = is_almost_equal(dtheta, PI/2) - ? 0.551915024494 - : (is_almost_equal(dtheta, -PI/2) - ? -0.551915024494 - : stdMath.tan(dtheta/4)*4/3), - x1 = stdMath.cos(theta), - y1 = stdMath.sin(theta), - x2 = stdMath.cos(theta + dtheta), - y2 = stdMath.sin(theta + dtheta) - ; - return rev ? [ - arc(x2, y2), - arc(x2 + y2*f, y2 - x2*f), - arc(x1 - y1*f, y1 + x1*f), - arc(x1, y1) - ] : [ - arc(x1, y1), - arc(x1 - y1*f, y1 + x1*f), - arc(x2 + y2*f, y2 - x2*f), - arc(x2, y2) - ]; - }, - r = abs(dtheta) / (PI/2), + r = abs(dtheta)/(PI/2), i, j, n, beziers ; - if (is_almost_equal(r, 1)) r = 1; n = stdMath.max(stdMath.ceil(r), 1); dtheta /= n; beziers = new Array(n) for (j=0,i=0; i t || 1 < t) return null; + return Point(this.f(t)); + }, intersects: function(other) { var i; if (other instanceof Point) @@ -2823,7 +2797,7 @@ var Polygon = makeClass(Curve, { } return false; }, - toBezier3: function() { + bezierPoints: function() { var p = this._lines, n = p.length; return p.reduce(function(b, _, i) { if (i+1 < n) @@ -3007,11 +2981,7 @@ var Circle = makeClass(Curve, { }, f: function(t) { var c = this.center, r = this.radius; - t *= TWO_PI; - return { - x: c.x + r*stdMath.cos(t), - y: c.y + r*stdMath.sin(t) - }; + return arc(t*TWO_PI, c.x, c.y, r, r, 1, 0); }, getPointAt: function(t) { t = Num(t); @@ -3040,29 +3010,13 @@ var Circle = makeClass(Curve, { } return false; }, - toBezier3: function() { - var r = this.radius, - c = this.center, - b3 = function(cx, cy, rx, ry, rev) { - /*0.55228*/ - return rev ? [ - {x:cx, y:cy - ry}, - {x:cx - 0.551915024494*rx, y:cy - ry}, - {x:cx - rx, y:cy - 0.551915024494*ry}, - {x:cx - rx, y:cy} - ] : [ - {x:cx - rx, y:cy}, - {x:cx - rx, y:cy - 0.551915024494*ry}, - {x:cx - 0.551915024494*rx, y:cy - ry}, - {x:cx, y:cy - ry} - ]; - } - ; + bezierPoints: function() { + var c = this.center, r = this.radius; return [ - b3(c.x, c.y, -r, r, 0), - b3(c.x, c.y, r, r, 1), - b3(c.x, c.y, r, -r, 0), - b3(c.x, c.y, -r, -r, 1) + arc2bezier(0, -PI/2, c.x, c.y, r, r, 1, 0, 0), + arc2bezier(-PI/2, -PI/2, c.x, c.y, r, r, 1, 0, /*1*/0), + arc2bezier(-PI, -PI/2, c.x, c.y, r, r, 1, 0, 0), + arc2bezier(-3*PI/2, -PI/2, c.x, c.y, r, r, 1, 0, /*1*/0) ]; }, toSVG: function(svg) { @@ -3237,17 +3191,12 @@ var Ellipse = makeClass(Curve, { get: function() { if (null == _hull) { - var c = self.center, rX = _radiusX.val(), rX = _radiusY.val(), - m = new Matrix( - _cos, -_sin, c.x, - _sin, _cos, c.y, - 0, 0, 1 - ); + var c = self.center, rx = _radiusX.val(), ry = _radiusY.val(); _hull = [ - new Point(-rX, -rY).transform(m), - new Point(rX, -rY).transform(m), - new Point(rX, rY).transform(m), - new Point(-rX, rY).transform(m) + new Point(toarc(-1, -1, c.x, c.y, rx, ry, _cos, _sin)), + new Point(toarc(1, -1, c.x, c.y, rx, ry, _cos, _sin)), + new Point(toarc(1, 1, c.x, c.y, rx, ry, _cos, _sin)), + new Point(toarc(-1, 1, c.x, c.y, rx, ry, _cos, _sin)) ]; } return _hull; @@ -3302,17 +3251,9 @@ var Ellipse = makeClass(Curve, { return this._hull; }, f: function(t) { - var c = this.center, - rX = this.radiusX, - rY = this.radiusY, - cs = this.cs, - ct = stdMath.cos(t*TWO_PI), - st = stdMath.sin(t*TWO_PI) - ; - return { - x: c.x + rX*cs[0]*ct - rY*cs[1]*st, - y: c.y + rY*cs[0]*st + rX*cs[1]*ct - }; + var c = this.center, cs = this.cs, + rx = this.radiusX, ry = this.radiusY; + return arc(t*TWO_PI, c.x, c.y, rx, ry, cs[0], cs[1]); }, getPointAt: function(t) { t = Num(t); @@ -3347,33 +3288,15 @@ var Ellipse = makeClass(Curve, { } return false; }, - toBezier3: function() { - var rx = this.radiusX, - ry = this.radiusY, - c = this.center, - cs = this.cs, - b3 = function(cx, cy, rx, ry, cos, sin, rev) { - var x1 = -rx, y1 = 0, - x2 = -0.551915024494*rx, y2 = -0.551915024494*ry, - x3 = 0, y3 = -ry; - return rev ? [ - {x:cx + cos*x3 - sin*y3, y:cy + sin*x3 + cos*y3}, - {x:cx + cos*x2 - sin*y3, y:cy + sin*x2 + cos*y3}, - {x:cx + cos*x1 - sin*y2, y:cy + sin*x1 + cos*y2}, - {x:cx + cos*x1 - sin*y1, y:cy + sin*x1 + cos*y1} - ] : [ - {x:cx + cos*x1 - sin*y1, y:cy + sin*x1 + cos*y1}, - {x:cx + cos*x1 - sin*y2, y:cy + sin*x1 + cos*y2}, - {x:cx + cos*x2 - sin*y3, y:cy + sin*x2 + cos*y3}, - {x:cx + cos*x3 - sin*y3, y:cy + sin*x3 + cos*y3} - ]; - } - ; + bezierPoints: function() { + var c = this.center, cs = this.cs, + cos = cs[0], sin = cs[1], + rx = this.radiusX, ry = this.radiusY; return [ - b3(c.x, c.y, -rx, ry, cs[0], cs[1], 0), - b3(c.x, c.y, rx, ry, cs[0], cs[1], 1), - b3(c.x, c.y, rx, -ry, cs[0], cs[1], 0), - b3(c.x, c.y, -rx, -ry, cs[0], cs[1], 1) + arc2bezier(0, -PI/2, c.x, c.y, rx, ry, cos, sin, 0), + arc2bezier(-PI/2, -PI/2, c.x, c.y, rx, ry, cos, sin, /*1*/0), + arc2bezier(-PI, -PI/2, c.x, c.y, rx, ry, cos, sin, 0), + arc2bezier(-3*PI/2, -PI/2, c.x, c.y, rx, ry, cos, sin, /*1*/0) ]; }, toSVG: function(svg) { @@ -3426,29 +3349,69 @@ Geometrize.Ellipse = Ellipse; // container for primitives shapes var Shape = makeClass(Primitive, {}); Geometrize.Shape = Shape; -// Tween between 2D shapes +// https://easings.net/ var Easing = { - // https://easings.net/ - // x is in [0, 1], 0 start, 1 end of animation - // lin + // x is in [0, 1], 0=start, 1=end of animation + // linear 'linear': function(x) { return x; }, - // quad - 'ease-in': function(x) { + // quadratic + 'ease-in-quad': function(x) { return x*x; }, - 'ease-out': function(x) { + 'ease-out-quad': function(x) { var xp = 1 - x; return 1 - xp*xp; }, - 'ease-in-out': function(x) { + 'ease-in-out-quad': function(x) { return x < 0.5 ? 2*x*x : 1 - stdMath.pow(2 - 2*x, 2)/2; }, + // cubic + 'ease-in-cubic': function(x) { + return x*x*x; + }, + 'ease-out-cubic': function(x) { + return 1 - stdMath.pow(1 - x, 3); + }, + 'ease-in-out-cubic': function(x) { + return x < 0.5 ? 4*x*x*x : 1 - stdMath.pow(2 - 2*x, 3)/2; + }, // cubic bezier 'cubic-bezier': function(c0, c1, c2, c3) { return bezier([c0, c1, c2, c3]); }, + // exponential + 'ease-in-expo': function(x) { + return 0 === x ? 0 : stdMath.pow(2, 10*x - 10); + }, + 'ease-out-expo': function(x) { + return 1 === x ? 1 : 1 - stdMath.pow(2, -10*x); + }, + 'ease-in-out-expo': function(x) { + return 0 === x + ? 0 + : (1 === x + ? 1 + : (x < 0.5 + ? stdMath.pow(2, 20*x - 10)/2 + : (2 - stdMath.pow(2, 10 - 20*x))/2)); + }, + // back + 'ease-in-back': function(x) { + var c1 = 1.70158, c3 = c1 + 1; + return c3*x*x*x - c1*x*x; + }, + 'ease-out-back': function(x) { + var c1 = 1.70158, c3 = c1 + 1, xp = x - 1; + return 1 + c3*stdMath.pow(xp, 3) + c1*stdMath.pow(xp, 2); + }, + 'ease-in-out-back': function(x) { + var c1 = 1.70158, c2 = c1*1.525; + return x < 0.5 + ? (stdMath.pow(2*x, 2)*((c2 + 1)*2*x - c2))/2 + : (stdMath.pow(2*x - 2, 2)*((c2 + 1)*(x*2 - 2) + c2) + 2)/2; + }, //elastic 'ease-in-elastic': function(x) { return 0 === x @@ -3507,9 +3470,13 @@ function ease_out_bounce(x) x1 = x - 2.625 return n1*(x1/d1)*x1 + 0.984375; } +Easing['ease-in'] = Easing['ease-in-quad']; +Easing['ease-out'] = Easing['ease-out-quad']; +Easing['ease-in-out'] = Easing['ease-in-out-quad']; + function prepare_tween(tween, fps) { - tween = tween || EMPTY_OBJ; + tween = tween || {}; if (!tween.keyframes) { tween.keyframes = { @@ -3519,52 +3486,75 @@ function prepare_tween(tween, fps) } var t = { duration: null == tween.duration ? 1000 : (tween.duration || 0), + delay: tween.delay || 0, fps: fps, nframes: 0, keyframes: null, kf: 0, current: null }, + shapes = {}, maxCurves = -Infinity, easing = is_function(tween.easing) ? tween.easing : (is_string(tween.easing) && HAS.call(Tween.Easing, tween.easing) ? Tween.Easing[tween.easing] : Tween.Easing.linear) ; t.nframes = stdMath.ceil(t.duration/1000*t.fps); t.keyframes = Object.keys(tween.keyframes || EMPTY_OBJ).map(function(key) { var kf = tween.keyframes[key] || EMPTY_OBJ, + shape = kf.shape && is_function(kf.shape.bezierPoints) ? (shapes[kf.shape.id] || kf.shape.bezierPoints()) : [], transform = kf.transform || EMPTY_OBJ, + sc = transform.scale || EMPTY_OBJ, + rot = transform.rotate || EMPTY_OBJ, + rotAngle = rot.angle || 0, + rotPoint = rot.point || {x:0, y:0}, + tr = transform.translate || EMPTY_OBJ, style = kf.style || EMPTY_OBJ, - stroke = is_string(style.stroke) ? Color.parse(style.stroke) : null, - fill = is_string(style.fill) ? Color.parse(style.fill) : null, - rotate = transform.rotate || [0, 0, 0], - shape = kf.shape && is_function(kf.shape.toBezier3) ? kf.shape.toBezier3() : [] + stroke = is_string(style.stroke) ? (Color.parse(style.stroke) || style.stroke) : null, + fill = is_string(style.fill) ? (Color.parse(style.fill) || style.fill) : null, + hasStroke = is_array(stroke), + hasFill = is_array(fill) ; + if (kf.shape && kf.shape.id && (null == shapes[kf.shape.id])) shapes[kf.shape.id] = shape; maxCurves = stdMath.max(maxCurves, shape.length); return { frame: stdMath.round((parseFloat(key, 10) || 0)/100*(t.nframes - 1)), - easing: is_function(kf.easing) ? kf.easing : (is_string(kf.easing) && HAS.call(Tween.Easing, kf.easing) ? Tween.Easing[kf.easing] : easing), shape: shape, transform: { - scale: (transform.scale || [1, 1]).slice(0, 2), - rotate: !is_array(rotate) ? [(+rotate)||0, 0, 0] : (rotate.length < 3 ? [rotate[0]||0, rotate[1]||0, rotate[2]||0] : rotate.slice(0, 3)), - translate: (transform.translate || [0, 0]).slice(0, 2) + scale: { + x: (null == sc.x ? 1 : sc.x) || 0, + y: (null == sc.y ? 1 : sc.y) || 0 + }, + rotate: { + angle: rad(rotAngle || 0), + point: { + x: rotPoint.x || 0, + y: rotPoint.y || 0 + } + }, + translate: { + x: tr.x || 0, + y: tr.y || 0 + } }, style: { - 'stroke': stroke ? stroke.slice(0, 3) : null, - 'stroke-opacity': stroke ? stroke[3] : 1, - 'fill': fill ? fill.slice(0, 3) : null, - 'fill-opacity': fill ? fill[3] : 1 - } + 'stroke': hasStroke ? stroke.slice(0, 3) : stroke, + 'stroke-opacity': hasStroke ? stroke[3] : 1, + 'fill': hasFill ? fill.slice(0, 3) : fill, + 'fill-opacity': hasFill ? fill[3] : 1, + hasStroke: hasStroke, + hasFill: hasFill + }, + easing: is_function(kf.easing) ? kf.easing : (is_string(kf.easing) && HAS.call(Tween.Easing, kf.easing) ? Tween.Easing[kf.easing] : easing) }; }).sort(function(a, b) {return a.frame - b.frame}); var add_curves = function(curves, nCurves) { if (curves.length < nCurves) { - var i = curves.length ? 1 : 0, p = [{x:0, y:0}, {x:0, y:0}]; + var i = curves.length ? 1 : 0, p = {x:0, y:0}; nCurves -= curves.length; while (0 < nCurves) { - if (i >= 1) p = [curves[i-1][3], curves[i-1][3]]; - curves.splice(i, 0, [bezier1(0, p), bezier1(0.5, p), bezier1(0.5, p), bezier1(1, p)]); + if (i >= 1) p = curves[i-1][3]; + curves.splice(i, 0, [{x:p.x, y:p.y}, {x:p.x, y:p.y}, {x:p.x, y:p.y}, {x:p.x, y:p.y}]); --nCurves; i += curves.length > i+1 ? 2 : 1; } @@ -3578,10 +3568,47 @@ function prepare_tween(tween, fps) function first_frame(tween) { tween.kf = 0; - var frame = tween.keyframes[tween.kf]; + var frame = tween.keyframes[tween.kf], + a = frame, + // translation + tx = a.transform.translate.x, + ty = a.transform.translate.y, + // scale + sx = a.transform.scale.x, + sy = a.transform.scale.y, + // rotation of angle around point (xrot, yrot) + angle = a.transform.rotate.angle, + xrot = sx*a.transform.rotate.point.x, + yrot = sy*a.transform.rotate.point.y, + cos = 1, sin = 0, + as = a.shape, ai, aij, + i, j, n = as.length, x, y, + s, cs = new Array(n) + ; + if (!is_almost_equal(angle, 0)) + { + cos = stdMath.cos(angle); + sin = stdMath.sin(angle); + } + for (i=0; i t || 1 < t) return null; + return Point(this.f(t)); + }, intersects: function(other) { var i; if (other instanceof Point) @@ -183,7 +192,7 @@ var Polygon = makeClass(Curve, { } return false; }, - toBezier3: function() { + bezierPoints: function() { var p = this._lines, n = p.length; return p.reduce(function(b, _, i) { if (i+1 < n) diff --git a/src/Polyline.js b/src/Polyline.js index 6c67126..6712885 100644 --- a/src/Polyline.js +++ b/src/Polyline.js @@ -185,7 +185,7 @@ var Polyline = makeClass(Curve, { return dist; }, Infinity)); }, - toBezier3: function() { + bezierPoints: function() { var p = this._points, n = p.length; return p.reduce(function(b, _, i) { if (i+1 < n) diff --git a/src/Tween.js b/src/Tween.js index e1c6ff8..d2220e2 100644 --- a/src/Tween.js +++ b/src/Tween.js @@ -1,26 +1,66 @@ -// Tween between 2D shapes +// https://easings.net/ var Easing = { - // https://easings.net/ - // x is in [0, 1], 0 start, 1 end of animation - // lin + // x is in [0, 1], 0=start, 1=end of animation + // linear 'linear': function(x) { return x; }, - // quad - 'ease-in': function(x) { + // quadratic + 'ease-in-quad': function(x) { return x*x; }, - 'ease-out': function(x) { + 'ease-out-quad': function(x) { var xp = 1 - x; return 1 - xp*xp; }, - 'ease-in-out': function(x) { + 'ease-in-out-quad': function(x) { return x < 0.5 ? 2*x*x : 1 - stdMath.pow(2 - 2*x, 2)/2; }, + // cubic + 'ease-in-cubic': function(x) { + return x*x*x; + }, + 'ease-out-cubic': function(x) { + return 1 - stdMath.pow(1 - x, 3); + }, + 'ease-in-out-cubic': function(x) { + return x < 0.5 ? 4*x*x*x : 1 - stdMath.pow(2 - 2*x, 3)/2; + }, // cubic bezier 'cubic-bezier': function(c0, c1, c2, c3) { return bezier([c0, c1, c2, c3]); }, + // exponential + 'ease-in-expo': function(x) { + return 0 === x ? 0 : stdMath.pow(2, 10*x - 10); + }, + 'ease-out-expo': function(x) { + return 1 === x ? 1 : 1 - stdMath.pow(2, -10*x); + }, + 'ease-in-out-expo': function(x) { + return 0 === x + ? 0 + : (1 === x + ? 1 + : (x < 0.5 + ? stdMath.pow(2, 20*x - 10)/2 + : (2 - stdMath.pow(2, 10 - 20*x))/2)); + }, + // back + 'ease-in-back': function(x) { + var c1 = 1.70158, c3 = c1 + 1; + return c3*x*x*x - c1*x*x; + }, + 'ease-out-back': function(x) { + var c1 = 1.70158, c3 = c1 + 1, xp = x - 1; + return 1 + c3*stdMath.pow(xp, 3) + c1*stdMath.pow(xp, 2); + }, + 'ease-in-out-back': function(x) { + var c1 = 1.70158, c2 = c1*1.525; + return x < 0.5 + ? (stdMath.pow(2*x, 2)*((c2 + 1)*2*x - c2))/2 + : (stdMath.pow(2*x - 2, 2)*((c2 + 1)*(x*2 - 2) + c2) + 2)/2; + }, //elastic 'ease-in-elastic': function(x) { return 0 === x @@ -79,9 +119,13 @@ function ease_out_bounce(x) x1 = x - 2.625 return n1*(x1/d1)*x1 + 0.984375; } +Easing['ease-in'] = Easing['ease-in-quad']; +Easing['ease-out'] = Easing['ease-out-quad']; +Easing['ease-in-out'] = Easing['ease-in-out-quad']; + function prepare_tween(tween, fps) { - tween = tween || EMPTY_OBJ; + tween = tween || {}; if (!tween.keyframes) { tween.keyframes = { @@ -91,52 +135,75 @@ function prepare_tween(tween, fps) } var t = { duration: null == tween.duration ? 1000 : (tween.duration || 0), + delay: tween.delay || 0, fps: fps, nframes: 0, keyframes: null, kf: 0, current: null }, + shapes = {}, maxCurves = -Infinity, easing = is_function(tween.easing) ? tween.easing : (is_string(tween.easing) && HAS.call(Tween.Easing, tween.easing) ? Tween.Easing[tween.easing] : Tween.Easing.linear) ; t.nframes = stdMath.ceil(t.duration/1000*t.fps); t.keyframes = Object.keys(tween.keyframes || EMPTY_OBJ).map(function(key) { var kf = tween.keyframes[key] || EMPTY_OBJ, + shape = kf.shape && is_function(kf.shape.bezierPoints) ? (shapes[kf.shape.id] || kf.shape.bezierPoints()) : [], transform = kf.transform || EMPTY_OBJ, + sc = transform.scale || EMPTY_OBJ, + rot = transform.rotate || EMPTY_OBJ, + rotAngle = rot.angle || 0, + rotPoint = rot.point || {x:0, y:0}, + tr = transform.translate || EMPTY_OBJ, style = kf.style || EMPTY_OBJ, - stroke = is_string(style.stroke) ? Color.parse(style.stroke) : null, - fill = is_string(style.fill) ? Color.parse(style.fill) : null, - rotate = transform.rotate || [0, 0, 0], - shape = kf.shape && is_function(kf.shape.toBezier3) ? kf.shape.toBezier3() : [] + stroke = is_string(style.stroke) ? (Color.parse(style.stroke) || style.stroke) : null, + fill = is_string(style.fill) ? (Color.parse(style.fill) || style.fill) : null, + hasStroke = is_array(stroke), + hasFill = is_array(fill) ; + if (kf.shape && kf.shape.id && (null == shapes[kf.shape.id])) shapes[kf.shape.id] = shape; maxCurves = stdMath.max(maxCurves, shape.length); return { frame: stdMath.round((parseFloat(key, 10) || 0)/100*(t.nframes - 1)), - easing: is_function(kf.easing) ? kf.easing : (is_string(kf.easing) && HAS.call(Tween.Easing, kf.easing) ? Tween.Easing[kf.easing] : easing), shape: shape, transform: { - scale: (transform.scale || [1, 1]).slice(0, 2), - rotate: !is_array(rotate) ? [(+rotate)||0, 0, 0] : (rotate.length < 3 ? [rotate[0]||0, rotate[1]||0, rotate[2]||0] : rotate.slice(0, 3)), - translate: (transform.translate || [0, 0]).slice(0, 2) + scale: { + x: (null == sc.x ? 1 : sc.x) || 0, + y: (null == sc.y ? 1 : sc.y) || 0 + }, + rotate: { + angle: rad(rotAngle || 0), + point: { + x: rotPoint.x || 0, + y: rotPoint.y || 0 + } + }, + translate: { + x: tr.x || 0, + y: tr.y || 0 + } }, style: { - 'stroke': stroke ? stroke.slice(0, 3) : null, - 'stroke-opacity': stroke ? stroke[3] : 1, - 'fill': fill ? fill.slice(0, 3) : null, - 'fill-opacity': fill ? fill[3] : 1 - } + 'stroke': hasStroke ? stroke.slice(0, 3) : stroke, + 'stroke-opacity': hasStroke ? stroke[3] : 1, + 'fill': hasFill ? fill.slice(0, 3) : fill, + 'fill-opacity': hasFill ? fill[3] : 1, + hasStroke: hasStroke, + hasFill: hasFill + }, + easing: is_function(kf.easing) ? kf.easing : (is_string(kf.easing) && HAS.call(Tween.Easing, kf.easing) ? Tween.Easing[kf.easing] : easing) }; }).sort(function(a, b) {return a.frame - b.frame}); var add_curves = function(curves, nCurves) { if (curves.length < nCurves) { - var i = curves.length ? 1 : 0, p = [{x:0, y:0}, {x:0, y:0}]; + var i = curves.length ? 1 : 0, p = {x:0, y:0}; nCurves -= curves.length; while (0 < nCurves) { - if (i >= 1) p = [curves[i-1][3], curves[i-1][3]]; - curves.splice(i, 0, [bezier1(0, p), bezier1(0.5, p), bezier1(0.5, p), bezier1(1, p)]); + if (i >= 1) p = curves[i-1][3]; + curves.splice(i, 0, [{x:p.x, y:p.y}, {x:p.x, y:p.y}, {x:p.x, y:p.y}, {x:p.x, y:p.y}]); --nCurves; i += curves.length > i+1 ? 2 : 1; } @@ -150,10 +217,47 @@ function prepare_tween(tween, fps) function first_frame(tween) { tween.kf = 0; - var frame = tween.keyframes[tween.kf]; + var frame = tween.keyframes[tween.kf], + a = frame, + // translation + tx = a.transform.translate.x, + ty = a.transform.translate.y, + // scale + sx = a.transform.scale.x, + sy = a.transform.scale.y, + // rotation of angle around point (xrot, yrot) + angle = a.transform.rotate.angle, + xrot = sx*a.transform.rotate.point.x, + yrot = sy*a.transform.rotate.point.y, + cos = 1, sin = 0, + as = a.shape, ai, aij, + i, j, n = as.length, x, y, + s, cs = new Array(n) + ; + if (!is_almost_equal(angle, 0)) + { + cos = stdMath.cos(angle); + sin = stdMath.sin(angle); + } + for (i=0; i

Geometrize Intersections

+
@@ -45,7 +46,7 @@

Geometrize Intersections