blob: beb67f2803dec217df00643aae09e899258d0213 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection' show IterableBase;
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:ui/ui.dart' as ui;
import 'conic.dart';
import 'cubic.dart';
import 'path_iterator.dart';
import 'path_ref.dart';
import 'path_utils.dart';
const double kEpsilon = 0.000000001;
/// An iterable collection of [PathMetric] objects describing a [Path].
///
/// A [PathMetrics] object is created by using the [Path.computeMetrics] method,
/// and represents the path as it stood at the time of the call. Subsequent
/// modifications of the path do not affect the [PathMetrics] object.
///
/// Each path metric corresponds to a segment, or contour, of a path.
///
/// For example, a path consisting of a [Path.lineTo], a [Path.moveTo], and
/// another [Path.lineTo] will contain two contours and thus be represented by
/// two [PathMetric] objects.
///
/// When iterating across a [PathMetrics]' contours, the [PathMetric] objects
/// are only valid until the next one is obtained.
class SurfacePathMetrics extends IterableBase<ui.PathMetric>
implements ui.PathMetrics {
SurfacePathMetrics(PathRef path, bool forceClosed)
: _iterator =
SurfacePathMetricIterator._(_SurfacePathMeasure(path, forceClosed));
final SurfacePathMetricIterator _iterator;
@override
Iterator<ui.PathMetric> get iterator => _iterator;
}
/// Maintains a single instance of computed segments for set of PathMetric
/// objects exposed through iterator.
///
/// [resScale] controls the precision of measure when values > 1.
class _SurfacePathMeasure {
_SurfacePathMeasure(this._path, this.forceClosed, {this.resScale = 1.0})
:
// nextContour will increment this to the zero based index.
_currentContourIndex = -1,
_pathIterator = PathIterator(_path, forceClosed);
final double resScale;
final PathRef _path;
PathIterator _pathIterator;
final List<_PathContourMeasure> _contours = <_PathContourMeasure>[];
// If the contour ends with a call to [Path.close] (which may
// have been implied when using [Path.addRect])
final bool forceClosed;
int _currentContourIndex;
int get currentContourIndex => _currentContourIndex;
double length(int contourIndex) {
assert(contourIndex <= currentContourIndex,
'Iterator must be advanced before index $contourIndex can be used.');
return _contours[contourIndex].length;
}
/// Computes the position of hte current contour at the given offset, and the
/// angle of the path at that point.
///
/// For example, calling this method with a distance of 1.41 for a line from
/// 0.0,0.0 to 2.0,2.0 would give a point 1.0,1.0 and the angle 45 degrees
/// (but in radians).
///
/// Returns null if the contour has zero [length].
///
/// The distance is clamped to the [length] of the current contour.
ui.Tangent? getTangentForOffset(int contourIndex, double distance) {
return _contours[contourIndex].getTangentForOffset(distance);
}
bool isClosed(int contourIndex) => _contours[contourIndex].isClosed;
// Move to the next contour in the path.
//
// A path can have a next contour if [Path.moveTo] was called after drawing began.
// Return true if one exists, or false.
bool _nextContour() {
final bool next = _nativeNextContour();
if (next) {
_currentContourIndex++;
}
return next;
}
// Iterator index into next contour.
int _verbIterIndex = 0;
// Move to the next contour in the path.
//
// A path can have a next contour if [Path.moveTo] was called after drawing
// began. Return true if one exists, or false.
//
// This is not exactly congruent with a regular [Iterator.moveNext].
// Typically, [Iterator.moveNext] should be called before accessing the
// [Iterator.current]. In this case, the [PathMetric] is valid before
// calling `_moveNext` - `_moveNext` should be called after the first
// iteration is done instead of before.
bool _nativeNextContour() {
if (_verbIterIndex == _path.countVerbs()) {
return false;
}
final _PathContourMeasure measure =
_PathContourMeasure(_path, _pathIterator, forceClosed);
_verbIterIndex = measure.verbEndIndex;
_contours.add(measure);
return true;
}
ui.Path extractPath(int contourIndex, double start, double end,
{bool startWithMoveTo = true}) {
return _contours[contourIndex].extractPath(start, end, startWithMoveTo);
}
}
/// Builds segments for a single contour to measure distance, compute tangent
/// and extract a sub path.
class _PathContourMeasure {
_PathContourMeasure(this.pathRef, PathIterator iter, this.forceClosed) {
_verbEndIndex = _buildSegments(iter);
}
final PathRef pathRef;
int _verbEndIndex = 0;
final List<_PathSegment> _segments = <_PathSegment>[];
// Allocate buffer large enough for returning cubic curve chop result.
// 2 floats for each coordinate x (start, end & control point 1 & 2).
static final Float32List _buffer = Float32List(8);
final bool forceClosed;
double get length => _contourLength;
bool get isClosed => _isClosed;
int get verbEndIndex => _verbEndIndex;
double _contourLength = 0.0;
bool _isClosed = false;
ui.Tangent? getTangentForOffset(double distance) {
final int segmentIndex = _segmentIndexAtDistance(distance);
if (segmentIndex == -1) {
return null;
}
return _getPosTan(segmentIndex, distance);
}
// Returns segment at [distance].
int _segmentIndexAtDistance(double distance) {
if (distance.isNaN) {
return -1;
}
// Pin distance to legal range.
if (distance < 0.0) {
distance = 0.0;
} else if (distance > _contourLength) {
distance = _contourLength;
}
// Binary search through segments to find segment at distance.
if (_segments.isEmpty) {
return -1;
}
int lo = 0;
int hi = _segments.length - 1;
while (lo < hi) {
final int mid = (lo + hi) >> 1;
if (_segments[mid].distance < distance) {
lo = mid + 1;
} else {
hi = mid;
}
}
if (_segments[hi].distance < distance) {
hi++;
}
return hi;
}
_SurfaceTangent _getPosTan(int segmentIndex, double distance) {
final _PathSegment segment = _segments[segmentIndex];
// Compute distance to segment. Since distance is cumulative to find
// t = 0..1 on the segment, we need to calculate start distance using prior
// segment.
final double startDistance =
segmentIndex == 0 ? 0 : _segments[segmentIndex - 1].distance;
final double totalDistance = segment.distance - startDistance;
final double t = totalDistance < kEpsilon
? 0
: (distance - startDistance) / totalDistance;
return segment.computeTangent(t);
}
ui.Path extractPath(
double startDistance, double stopDistance, bool startWithMoveTo) {
if (startDistance < 0) {
startDistance = 0;
}
if (stopDistance > _contourLength) {
stopDistance = _contourLength;
}
final ui.Path path = ui.Path();
if (startDistance > stopDistance || _segments.isEmpty) {
return path;
}
final int startSegmentIndex = _segmentIndexAtDistance(startDistance);
final int stopSegmentIndex = _segmentIndexAtDistance(stopDistance);
if (startSegmentIndex == -1 || stopSegmentIndex == -1) {
return path;
}
int currentSegmentIndex = startSegmentIndex;
_PathSegment seg = _segments[currentSegmentIndex];
final _SurfaceTangent startTangent =
_getPosTan(startSegmentIndex, startDistance);
if (startWithMoveTo) {
final ui.Offset startPosition = startTangent.position;
path.moveTo(startPosition.dx, startPosition.dy);
}
final _SurfaceTangent stopTangent =
_getPosTan(stopSegmentIndex, stopDistance);
double startT = startTangent.t;
final double stopT = stopTangent.t;
if (startSegmentIndex == stopSegmentIndex) {
// We only have a single segment that covers the complete distance.
_outputSegmentTo(seg, startT, stopT, path);
} else {
do {
// Write this segment from startT to end (t = 1.0).
_outputSegmentTo(seg, startT, 1.0, path);
// Move to next segment until we hit stop segment.
++currentSegmentIndex;
seg = _segments[currentSegmentIndex];
startT = 0;
} while (currentSegmentIndex != stopSegmentIndex);
// Final write last segment from t=0.0 to t=stopT.
_outputSegmentTo(seg, 0.0, stopT, path);
}
return path;
}
// Chops the segment at startT and endT and writes it to output [path].
void _outputSegmentTo(
_PathSegment segment, double startT, double stopT, ui.Path path) {
final List<double> points = segment.points;
switch (segment.segmentType) {
case SPath.kLineVerb:
final double toX = (points[2] * stopT) + (points[0] * (1.0 - stopT));
final double toY = (points[3] * stopT) + (points[1] * (1.0 - stopT));
path.lineTo(toX, toY);
break;
case SPath.kCubicVerb:
chopCubicBetweenT(points, startT, stopT, _buffer);
path.cubicTo(_buffer[2], _buffer[3], _buffer[4], _buffer[5], _buffer[6],
_buffer[7]);
break;
case SPath.kQuadVerb:
_chopQuadBetweenT(points, startT, stopT, _buffer);
path.quadraticBezierTo(_buffer[2], _buffer[3], _buffer[4], _buffer[5]);
break;
case SPath.kConicVerb:
// Implement this once we start writing out conic segments.
throw UnimplementedError();
default:
throw UnsupportedError('Invalid segment type');
}
}
/// Builds segments from contour starting at verb [_verbStartIndex] and
/// returns next contour verb index.
int _buildSegments(PathIterator iter) {
assert(_segments.isEmpty, '_buildSegments should be called once');
_isClosed = false;
double distance = 0.0;
bool haveSeenMoveTo = false;
final Function lineToHandler =
(double fromX, double fromY, double x, double y) {
final double dx = fromX - x;
final double dy = fromY - y;
final double prevDistance = distance;
distance += math.sqrt(dx * dx + dy * dy);
// As we accumulate distance, we have to check that the result of +=
// actually made it larger, since a very small delta might be > 0, but
// still have no effect on distance (if distance >>> delta).
if (distance > prevDistance) {
_segments.add(
_PathSegment(SPath.kLineVerb, distance, <double>[fromX, fromY, x, y]),
);
}
};
int verb = 0;
final Float32List points = Float32List(PathRefIterator.kMaxBufferSize);
do {
if (iter.peek() == SPath.kMoveVerb && haveSeenMoveTo) {
break;
}
verb = iter.next(points);
switch (verb) {
case SPath.kMoveVerb:
haveSeenMoveTo = true;
break;
case SPath.kLineVerb:
assert(haveSeenMoveTo);
lineToHandler(points[0], points[1], points[2], points[3]);
break;
case SPath.kCubicVerb:
assert(haveSeenMoveTo);
// Compute cubic curve distance.
distance = _computeCubicSegments(
points[0],
points[1],
points[2],
points[3],
points[4],
points[5],
points[6],
points[7],
distance,
0,
_kMaxTValue,
_segments);
break;
case SPath.kConicVerb:
assert(haveSeenMoveTo);
final double w = iter.conicWeight;
final Conic conic = Conic(points[0], points[1], points[2], points[3],
points[4], points[5], w);
final List<ui.Offset> conicPoints = conic.toQuads();
final int len = conicPoints.length;
double startX = conicPoints[0].dx;
double startY = conicPoints[0].dy;
for (int i = 1; i < len; i += 2) {
final double p1x = conicPoints[i].dx;
final double p1y = conicPoints[i].dy;
final double p2x = conicPoints[i + 1].dx;
final double p2y = conicPoints[i + 1].dy;
distance = _computeQuadSegments(
startX, startY, p1x, p1y, p2x, p2y, distance, 0, _kMaxTValue);
startX = p2x;
startY = p2y;
}
break;
case SPath.kQuadVerb:
assert(haveSeenMoveTo);
// Compute quad curve distance.
distance = _computeQuadSegments(points[0], points[1], points[2],
points[3], points[4], points[5], distance, 0, _kMaxTValue);
break;
case SPath.kCloseVerb:
_contourLength = distance;
return iter.pathVerbIndex;
default:
break;
}
} while (verb != SPath.kDoneVerb);
_contourLength = distance;
return iter.pathVerbIndex;
}
static bool _tspanBigEnough(int tSpan) => (tSpan >> 10) != 0;
static bool _cubicTooCurvy(double x0, double y0, double x1, double y1,
double x2, double y2, double x3, double y3) {
// Measure distance from start-end line at 1/3 and 2/3rds to control
// points. If distance is less than _fTolerance we should continue
// subdividing curve. Uses approx distance for speed.
//
// p1 = point 1/3rd between start,end points.
final double p1x = (x0 * 2 / 3) + (x3 / 3);
final double p1y = (y0 * 2 / 3) + (y3 / 3);
if ((p1x - x1).abs() > _fTolerance) {
return true;
}
if ((p1y - y1).abs() > _fTolerance) {
return true;
}
// p2 = point 2/3rd between start,end points.
final double p2x = (x0 / 3) + (x3 * 2 / 3);
final double p2y = (y0 / 3) + (y3 * 2 / 3);
if ((p2x - x2).abs() > _fTolerance) {
return true;
}
if ((p2y - y2).abs() > _fTolerance) {
return true;
}
return false;
}
// Recursively subdivides cubic and adds segments.
static double _computeCubicSegments(
double x0,
double y0,
double x1,
double y1,
double x2,
double y2,
double x3,
double y3,
double distance,
int tMin,
int tMax,
List<_PathSegment> segments) {
if (_tspanBigEnough(tMax - tMin) &&
_cubicTooCurvy(x0, y0, x1, y1, x2, y2, x3, y3)) {
// Chop cubic into two halves (De Cateljau's algorithm)
// See https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
final double abX = (x0 + x1) / 2;
final double abY = (y0 + y1) / 2;
final double bcX = (x1 + x2) / 2;
final double bcY = (y1 + y2) / 2;
final double cdX = (x2 + x3) / 2;
final double cdY = (y2 + y3) / 2;
final double abcX = (abX + bcX) / 2;
final double abcY = (abY + bcY) / 2;
final double bcdX = (bcX + cdX) / 2;
final double bcdY = (bcY + cdY) / 2;
final double abcdX = (abcX + bcdX) / 2;
final double abcdY = (abcY + bcdY) / 2;
final int tHalf = (tMin + tMax) >> 1;
distance = _computeCubicSegments(x0, y0, abX, abY, abcX, abcY, abcdX,
abcdY, distance, tMin, tHalf, segments);
distance = _computeCubicSegments(abcdX, abcdY, bcdX, bcdY, cdX, cdY, x3,
y3, distance, tHalf, tMax, segments);
} else {
final double dx = x0 - x3;
final double dy = y0 - y3;
final double startToEndDistance = math.sqrt(dx * dx + dy * dy);
final double prevDistance = distance;
distance += startToEndDistance;
if (distance > prevDistance) {
segments.add(_PathSegment(SPath.kCubicVerb, distance,
<double>[x0, y0, x1, y1, x2, y2, x3, y3]));
}
}
return distance;
}
static bool _quadTooCurvy(
double x0, double y0, double x1, double y1, double x2, double y2) {
// (a/4 + b/2 + c/4) - (a/2 + c/2) = -a/4 + b/2 - c/4
final double dx = (x1 / 2) - (x0 + x2) / 4;
if (dx.abs() > _fTolerance) {
return true;
}
final double dy = (y1 / 2) - (y0 + y2) / 4;
if (dy.abs() > _fTolerance) {
return true;
}
return false;
}
double _computeQuadSegments(double x0, double y0, double x1, double y1,
double x2, double y2, double distance, int tMin, int tMax) {
if (_tspanBigEnough(tMax - tMin) && _quadTooCurvy(x0, y0, x1, y1, x2, y2)) {
final double p01x = (x0 + x1) / 2;
final double p01y = (y0 + y1) / 2;
final double p12x = (x1 + x2) / 2;
final double p12y = (y1 + y2) / 2;
final double p012x = (p01x + p12x) / 2;
final double p012y = (p01y + p12y) / 2;
final int tHalf = (tMin + tMax) >> 1;
distance = _computeQuadSegments(
x0, y0, p01x, p01y, p012x, p012y, distance, tMin, tHalf);
distance = _computeQuadSegments(
p012x, p012y, p12x, p12y, x2, y2, distance, tMin, tHalf);
} else {
final double dx = x0 - x2;
final double dy = y0 - y2;
final double startToEndDistance = math.sqrt(dx * dx + dy * dy);
final double prevDistance = distance;
distance += startToEndDistance;
if (distance > prevDistance) {
_segments.add(_PathSegment(
SPath.kQuadVerb, distance, <double>[x0, y0, x1, y1, x2, y2]));
}
}
return distance;
}
}
/// Tracks iteration from one segment of a path to the next for measurement.
class SurfacePathMetricIterator implements Iterator<ui.PathMetric> {
SurfacePathMetricIterator._(this._pathMeasure);
SurfacePathMetric? _pathMetric;
_SurfacePathMeasure _pathMeasure;
@override
SurfacePathMetric get current => _pathMetric!;
@override
bool moveNext() {
if (_pathMeasure._nextContour()) {
_pathMetric = SurfacePathMetric._(_pathMeasure);
return true;
}
_pathMetric = null;
return false;
}
}
// Maximum range value used in curve subdivision using Casteljau algorithm.
const int _kMaxTValue = 0x3FFFFFFF;
// Distance at which we stop subdividing cubic and quadratic curves.
const double _fTolerance = 0.5;
/// Utilities for measuring a [Path] and extracting sub-paths.
///
/// Iterate over the object returned by [Path.computeMetrics] to obtain
/// [PathMetric] objects. Callers that want to randomly access elements or
/// iterate multiple times should use `path.computeMetrics().toList()`, since
/// [PathMetrics] does not memoize.
///
/// Once created, the metrics are only valid for the path as it was specified
/// when [Path.computeMetrics] was called. If additional contours are added or
/// any contours are updated, the metrics need to be recomputed. Previously
/// created metrics will still refer to a snapshot of the path at the time they
/// were computed, rather than to the actual metrics for the new mutations to
/// the path.
///
/// Implementation is based on
/// https://github.com/google/skia/blob/master/src/core/SkContourMeasure.cpp
/// to maintain consistency with native platforms.
class SurfacePathMetric implements ui.PathMetric {
SurfacePathMetric._(this._measure)
: length = _measure.length(_measure.currentContourIndex),
isClosed = _measure.isClosed(_measure.currentContourIndex),
contourIndex = _measure.currentContourIndex;
/// Return the total length of the current contour.
@override
final double length;
/// Whether the contour is closed.
///
/// Returns true if the contour ends with a call to [Path.close] (which may
/// have been implied when using methods like [Path.addRect]) or if
/// `forceClosed` was specified as true in the call to [Path.computeMetrics].
/// Returns false otherwise.
@override
final bool isClosed;
/// The zero-based index of the contour.
///
/// [Path] objects are made up of zero or more contours. The first contour is
/// created once a drawing command (e.g. [Path.lineTo]) is issued. A
/// [Path.moveTo] command after a drawing command may create a new contour,
/// although it may not if optimizations are applied that determine the move
/// command did not actually result in moving the pen.
///
/// This property is only valid with reference to its original iterator and
/// the contours of the path at the time the path's metrics were computed. If
/// additional contours were added or existing contours updated, this metric
/// will be invalid for the current state of the path.
@override
final int contourIndex;
final _SurfacePathMeasure _measure;
/// Computes the position of the current contour at the given offset, and the
/// angle of the path at that point.
///
/// For example, calling this method with a distance of 1.41 for a line from
/// 0.0,0.0 to 2.0,2.0 would give a point 1.0,1.0 and the angle 45 degrees
/// (but in radians).
///
/// Returns null if the contour has zero [length].
///
/// The distance is clamped to the [length] of the current contour.
@override
ui.Tangent? getTangentForOffset(double distance) {
return _measure.getTangentForOffset(contourIndex, distance);
}
/// Given a start and end distance, return the intervening segment(s).
///
/// `start` and `end` are pinned to legal values (0..[length])
/// Begin the segment with a moveTo if `startWithMoveTo` is true.
@override
ui.Path extractPath(double start, double end, {bool startWithMoveTo = true}) {
return _measure.extractPath(contourIndex, start, end,
startWithMoveTo: startWithMoveTo);
}
@override
String toString() => 'PathMetric';
}
// Given a vector dx, dy representing slope, normalize and return as [ui.Offset].
ui.Offset _normalizeSlope(double dx, double dy) {
final double length = math.sqrt(dx * dx + dy * dy);
return length < kEpsilon
? const ui.Offset(0.0, 0.0)
: ui.Offset(dx / length, dy / length);
}
class _SurfaceTangent extends ui.Tangent {
const _SurfaceTangent(ui.Offset position, ui.Offset vector, this.t)
: super(position, vector);
// Normalized distance of tangent point from start of a contour.
final double t;
}
class _PathSegment {
_PathSegment(this.segmentType, this.distance, this.points);
final int segmentType;
final double distance;
final List<double> points;
_SurfaceTangent computeTangent(double t) {
switch (segmentType) {
case SPath.kLineVerb:
// Simple line. Position is simple interpolation from start to end point.
final double xAtDistance = (points[2] * t) + (points[0] * (1.0 - t));
final double yAtDistance = (points[3] * t) + (points[1] * (1.0 - t));
return _SurfaceTangent(ui.Offset(xAtDistance, yAtDistance),
_normalizeSlope(points[2] - points[0], points[3] - points[1]), t);
case SPath.kCubicVerb:
return tangentForCubicAt(t, points[0], points[1], points[2], points[3],
points[4], points[5], points[6], points[7]);
case SPath.kQuadVerb:
return tangentForQuadAt(t, points[0], points[1], points[2], points[3],
points[4], points[5]);
default:
throw UnsupportedError('Invalid segment type');
}
}
_SurfaceTangent tangentForQuadAt(double t, double x0, double y0, double x1,
double y1, double x2, double y2) {
assert(t >= 0 && t <= 1);
final SkQuadCoefficients _quadEval =
SkQuadCoefficients(x0, y0, x1, y1, x2, y2);
final ui.Offset pos = ui.Offset(_quadEval.evalX(t), _quadEval.evalY(t));
// Derivative of quad curve is 2(b - a + (a - 2b + c)t).
// If control point is at start or end point, this yields 0 for t = 0 and
// t = 1. In that case use the quad end points to compute tangent instead
// of derivative.
final ui.Offset tangentVector = ((t == 0 && x0 == x1 && y0 == y1) ||
(t == 1 && x1 == x2 && y1 == y2))
? _normalizeSlope(x2 - x0, y2 - y0)
: _normalizeSlope(
2 * ((x2 - x0) * t + (x1 - x0)), 2 * ((y2 - y0) * t + (y1 - y0)));
return _SurfaceTangent(pos, tangentVector, t);
}
_SurfaceTangent tangentForCubicAt(double t, double x0, double y0, double x1,
double y1, double x2, double y2, double x3, double y3) {
assert(t >= 0 && t <= 1);
final _SkCubicCoefficients _cubicEval =
_SkCubicCoefficients(x0, y0, x1, y1, x2, y2, x3, y3);
final ui.Offset pos = ui.Offset(_cubicEval.evalX(t), _cubicEval.evalY(t));
// Derivative of cubic is zero when t = 0 or 1 and adjacent control point
// is on the start or end point of curve. Use the other control point
// to compute the tangent or if both control points are on end points
// use end points for tangent.
final bool tAtZero = t == 0;
ui.Offset tangentVector;
if ((tAtZero && x0 == x1 && y0 == y1) || (t == 1 && x2 == x3 && y2 == y3)) {
double dx = tAtZero ? x2 - x0 : x3 - x1;
double dy = tAtZero ? y2 - y0 : y3 - y1;
if (dx == 0 && dy == 0) {
dx = x3 - x0;
dy = y3 - y0;
}
tangentVector = _normalizeSlope(dx, dy);
} else {
final double ax = x3 + (3 * (x1 - x2)) - x0;
final double ay = y3 + (3 * (y1 - y2)) - y0;
final double bx = 2 * (x2 - (2 * x1) + x0);
final double by = 2 * (y2 - (2 * y1) + y0);
final double cx = x1 - x0;
final double cy = y1 - y0;
final double tx = (ax * t + bx) * t + cx;
final double ty = (ay * t + by) * t + cy;
tangentVector = _normalizeSlope(tx, ty);
}
return _SurfaceTangent(pos, tangentVector, t);
}
}
// Evaluates A * t^3 + B * t^2 + Ct + D = 0 for cubic curve.
class _SkCubicCoefficients {
final double ax, ay, bx, by, cx, cy, dx, dy;
_SkCubicCoefficients(double x0, double y0, double x1, double y1, double x2,
double y2, double x3, double y3)
: ax = x3 + (3 * (x1 - x2)) - x0,
ay = y3 + (3 * (y1 - y2)) - y0,
bx = 3 * (x2 - (2 * x1) + x0),
by = 3 * (y2 - (2 * y1) + y0),
cx = 3 * (x1 - x0),
cy = 3 * (y1 - y0),
dx = x0,
dy = y0;
double evalX(double t) => (((ax * t + bx) * t) + cx) * t + dx;
double evalY(double t) => (((ay * t + by) * t) + cy) * t + dy;
}
/// Chops quadratic curve at startT and stopT and writes result to buffer.
void _chopQuadBetweenT(
List<double> points, double startT, double stopT, Float32List buffer) {
assert(startT != 0 || stopT != 0);
final double p2y = points[5];
final double p0x = points[0];
final double p0y = points[1];
final double p1x = points[2];
final double p1y = points[3];
final double p2x = points[4];
// If startT == 0 chop at end point and return curve.
final bool chopStart = startT != 0;
final double t = chopStart ? startT : stopT;
final double ab1x = interpolate(p0x, p1x, t);
final double ab1y = interpolate(p0y, p1y, t);
final double bc1x = interpolate(p1x, p2x, t);
final double bc1y = interpolate(p1y, p2y, t);
final double abc1x = interpolate(ab1x, bc1x, t);
final double abc1y = interpolate(ab1y, bc1y, t);
if (!chopStart) {
// Return left side of curve.
buffer[0] = p0x;
buffer[1] = p0y;
buffer[2] = ab1x;
buffer[3] = ab1y;
buffer[4] = abc1x;
buffer[5] = abc1y;
return;
}
if (stopT == 1) {
// Return right side of curve.
buffer[0] = abc1x;
buffer[1] = abc1y;
buffer[2] = bc1x;
buffer[3] = bc1y;
buffer[4] = p2x;
buffer[5] = p2y;
return;
}
// We chopped at startT, now the right hand side of curve is at
// abc1x, abc1y, bc1x, bc1y, p2x, p2y
final double endT = (stopT - startT) / (1 - startT);
final double ab2x = interpolate(abc1x, bc1x, endT);
final double ab2y = interpolate(abc1y, bc1y, endT);
final double bc2x = interpolate(bc1x, p2x, endT);
final double bc2y = interpolate(bc1y, p2y, endT);
final double abc2x = interpolate(ab2x, bc2x, endT);
final double abc2y = interpolate(ab2y, bc2y, endT);
buffer[0] = abc1x;
buffer[1] = abc1y;
buffer[2] = ab2x;
buffer[3] = ab2y;
buffer[4] = abc2x;
buffer[5] = abc2y;
}