123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 |
- import 'dart:math';
- import 'dart:ui';
- import 'package:fis_measure/interfaces/date_types/point.dart';
- import 'package:fis_measure/interfaces/enums/items.dart';
- import 'package:fis_measure/interfaces/process/items/item.dart';
- import 'package:fis_measure/interfaces/process/items/item_metas.dart';
- import 'package:fis_measure/interfaces/process/items/terms.dart';
- import 'package:fis_measure/interfaces/process/workspace/point_info.dart';
- import 'package:fis_measure/process/calcuators/curve.dart';
- import 'package:fis_measure/process/calcuators/calculator.dart';
- import 'package:fis_measure/process/primitives/utils/auto_snap.dart';
- import 'package:fis_measure/utils/canvas.dart';
- import 'area_abstract.dart';
- /// 曲线连线
- class Spline extends AreaItemAbstract with AutoSnapMixin {
- Spline(ItemMeta meta, [IMeasureItem? parent]) : super(meta, parent);
- static Spline createAreaPerimeter(ItemMeta meta, [IMeasureItem? parent]) {
- Spline spline = Spline(meta, parent);
- spline.calculator = _AreaPerimeterCalc(spline);
- spline.isClosed = false;
- spline.splineTension = 0.5;
- return spline;
- }
- static Spline createCurveLength(
- ItemMeta meta, [
- IMeasureItem? parent,
- ]) {
- Spline spline = Spline(meta, parent);
- spline.calculator = CurveLengthCal(spline);
- spline.isClosed = false;
- spline.splineTension = 0.5;
- return spline;
- }
- @override
- bool onExecuteMouse(PointInfo args) {
- if (state == ItemStates.finished) {
- if (args.pointType == PointInfoType.mouseDown) {
- state = ItemStates.waiting;
- }
- }
- if (state == ItemStates.waiting) {
- if (args.pointType == PointInfoType.mouseDown) {
- handleMouseDownWhileWaiting(args);
- }
- } else if (state == ItemStates.running) {
- if (feature == null) return false;
- if (args.pointType == PointInfoType.mouseUp) return false;
- final f = feature!;
- if (args.pointType == PointInfoType.mouseDown) {
- f.innerPoints.add(args);
- } else {
- f.innerPoints.last = args;
- }
- doCalculate();
- if (checkAutoSnap(args)) {
- f.innerPoints.removeLast();
- }
- }
- return true;
- }
- DPoint lastStartPoint = DPoint(0, 0); // 上一个起点
- bool isFirstPointNeedOffset = false; // 第一个点是否需要施加偏移
- DPoint splineTouchStartPoint = DPoint(0, 0); // 轨迹触摸起始点
- bool isOtherPointNeedOffset = false; // 其他点是否需要施加偏移
- @override
- bool onExecuteTouch(PointInfo args) {
- if (state == ItemStates.finished) {
- if (args.pointType == PointInfoType.touchDown) {
- state = ItemStates.waiting;
- }
- }
- if (state == ItemStates.waiting) {
- if (isFirstPointNeedOffset) args.addOffset(0, -0.2);
- switch (args.pointType) {
- case PointInfoType.touchDown:
- handleTouchDownWhileWaiting(args);
- isFirstPointNeedOffset = false;
- break;
- case PointInfoType.touchUp:
- lastStartPoint = args; // 设置线段起点
- state = ItemStates.running;
- feature?.innerPoints.first = args;
- break; // 按下立即抬起无事发生
- case PointInfoType.touchMove:
- if (isMoveTargetOutOfRange(args)) return true;
- isFirstPointNeedOffset = true;
- feature?.innerPoints.first = args;
- break;
- default:
- break;
- }
- } else if (state == ItemStates.running) {
- if (feature == null) return false;
- PointInfo newPoint = PointInfo.fromOffset(
- lastStartPoint
- .clone()
- .addVector(args - splineTouchStartPoint)
- .toOffset(),
- args.pointType);
- newPoint.hostVisualArea = args.hostVisualArea;
- final f = feature!;
- if (args.pointType == PointInfoType.touchUp) {
- if (!isOtherPointNeedOffset) {
- f.innerPoints.last = args;
- lastStartPoint = args;
- doCalculate();
- } else {
- lastStartPoint = newPoint;
- }
- }
- if (args.pointType == PointInfoType.touchDown) {
- isOtherPointNeedOffset = false;
- splineTouchStartPoint = args;
- f.innerPoints.add(lastStartPoint);
- }
- if (args.pointType == PointInfoType.touchMove) {
- if (isMoveTargetOutOfRange(newPoint)) return true;
- isOtherPointNeedOffset = true;
- f.innerPoints.last = newPoint;
- }
- doCalculate();
- PointInfo newPointInfo = isOtherPointNeedOffset
- ? PointInfo.fromOffset(newPoint.toOffset(), args.pointType)
- : args;
- if (checkAutoSnap(newPointInfo)) {
- f.innerPoints.removeLast();
- }
- }
- return true;
- }
- void handleMouseDownWhileWaiting(PointInfo args) {
- // TODO: 判断是否当前area
- final point = args.toAreaLogicPoint();
- feature = SplineFeature(this, point);
- if (args.hostVisualArea != null) {
- feature!.hostVisualArea = args.hostVisualArea;
- }
- state = ItemStates.running;
- }
- void handleTouchDownWhileWaiting(PointInfo args) {
- // TODO: 判断是否当前area
- final point = args.toAreaLogicPoint();
- feature = SplineFeature.withOneStartPoint(this, point);
- if (args.hostVisualArea != null) {
- feature!.hostVisualArea = args.hostVisualArea;
- }
- }
- }
- class SplineFeature extends AreaItemFeatureAbstract {
- static const double _splineTolerance = 0.05;
- SplineFeature(Spline refItem, DPoint point) : super(refItem) {
- innerPoints.add(point.clone());
- innerPoints.add(point.clone());
- }
- SplineFeature.withOneStartPoint(Spline refItem, DPoint point)
- : super(refItem) {
- innerPoints.add(point.clone());
- }
- @override
- void paint(Canvas canvas, Size size) {
- if (innerPoints.isEmpty) return;
- final paintPoints = innerPoints;
- drawId(canvas, size);
- final startOffset = convert2ViewPoint(size, startPoint).toOffset();
- if (paintPoints.length == 1) {
- drawVertex(canvas, startOffset, true);
- return;
- } else if (paintPoints.length == 2) {
- final endOffset = convert2ViewPoint(size, endPoint).toOffset();
- drawVertex(canvas, startOffset, true);
- drawVertex(canvas, endOffset, true);
- canvas.drawDashLine(startOffset, endOffset, 1, 10, paintLinePan);
- return;
- }
- /// 全部innerPoints点集转为Offset集绘制
- for (var e in innerPoints) {
- drawVertex(canvas, convert2ViewPoint(size, e).toOffset());
- }
- /// 获取拟合点集
- final fittedPoints = getFitPoints(
- innerPoints, isClosed, splineTension, _splineTolerance / 2);
- /// 全部拟合点集转为Offset集
- final fittedOffsets =
- fittedPoints.map((e) => convert2ViewPoint(size, e).toOffset()).toList();
- canvas.drawDashPointsLine(fittedOffsets, 1, 10, paintLinePan, close: true);
- }
- List<DPoint> getFitPoints(
- List<DPoint> points,
- bool isClosed,
- double tension,
- double tolerance,
- ) {
- final List<DPoint> polyLineSegment = [];
- final len = points.length;
- for (var i = 0; i < points.length; i++) {
- if (i == 0) {
- addSegment(
- polyLineSegment, points, [len - 1, 0, 1, 2], tension, tolerance);
- } else if (i == points.length - 2) {
- addSegment(
- polyLineSegment, points, [i - 1, i, i + 1, 0], tension, tolerance);
- } else if (i == points.length - 1) {
- addSegment(
- polyLineSegment, points, [i - 1, i, 0, 1], tension, tolerance);
- } else {
- addSegment(polyLineSegment, points, [i - 1, i, i + 1, i + 2], tension,
- tolerance);
- }
- }
- return polyLineSegment;
- }
- static void addSegment(List<DPoint> polyLineSegment, List<DPoint> points,
- List<int> pointIndex, double tension, double tolerance) {
- if (points.length < 2) return;
- segment(
- polyLineSegment,
- points[pointIndex[0]],
- points[pointIndex[1]],
- points[pointIndex[2]],
- points[pointIndex[3]],
- tension,
- tension,
- tolerance,
- );
- }
- static double segment(List<DPoint> points, DPoint pt0, DPoint pt1, DPoint pt2,
- DPoint pt3, double t1, double t2, double tolerance) {
- double length = 0;
- final sx1 = t1 * (pt2.x - pt0.x);
- final sy1 = t1 * (pt2.y - pt0.y);
- final sx2 = t2 * (pt3.x - pt1.x);
- final sy2 = t2 * (pt3.y - pt1.y);
- final ax = sx1 + sx2 + 2 * pt1.x - 2 * pt2.x;
- final ay = sy1 + sy2 + 2 * pt1.y - 2 * pt2.y;
- final bx = -2 * sx1 - sx2 - 3 * pt1.x + 3 * pt2.x;
- final by = -2 * sy1 - sy2 - 3 * pt1.y + 3 * pt2.y;
- final cx = sx1;
- final cy = sy1;
- final dx = pt1.x;
- final dy = pt1.y;
- var num = (((pt1.x - pt2.x).abs() + (pt1.y - pt2.y).abs()) ~/ tolerance);
- // Set num = 2 to calculate the length. when the distance of pt1,pt2 is very tiny, the num will be 0, the length will be 0
- if (num < 2) num = 2;
- // Notice begins at 1 so excludes the first point (which is just pt1)
- for (var i = 1; i < num; i++) {
- var t = i / (num - 1);
- var pt = DPoint(ax * t * t * t + bx * t * t + cx * t + dx,
- ay * t * t * t + by * t * t + cy * t + dy);
- if (i == 1) {
- length += (pt - pt1).length;
- } else {
- length += (pt - points[points.length - 1]).length;
- }
- points.add(pt);
- }
- return length;
- }
- }
- class _AreaPerimeterCalc extends Calculator<Spline, double> {
- _AreaPerimeterCalc(Spline ref) : super(ref);
- @override
- void calculate() {
- if (ref.feature == null) return;
- final feature = ref.feature!;
- final viewport = feature.hostVisualArea!.viewport!;
- final points = feature.innerPoints.map((e) => viewport.convert(e)).toList();
- final tension = feature.splineTension;
- feature.values.clear();
- double area;
- double perimeter;
- //计算周长
- final res = calcPerimeterAndArea(points, feature.isClosed, tension, 0.25);
- perimeter = res[0];
- area = res[1];
- for (var output in ref.meta.outputs) {
- if (output.name == MeasureTerms.Perimeter) {
- // var value = roundDouble(perimeter, output.fractionalDigits);
- feature.updateFloatValue(output, perimeter, output.unit);
- } else if (output.name == MeasureTerms.Area) {
- // var value = roundDouble(area, output.fractionalDigits);
- feature.updateFloatValue(output, area, output.unit);
- }
- }
- }
- static double calcArea(List<DPoint> points) {
- if (points.isEmpty) {
- return 0;
- }
- double sum = 0;
- var ax = points[0].x;
- var ay = points[0].y;
- for (var i = 1; i < points.length - 1; i++) {
- var bx = points[i].x;
- var by = points[i].y;
- var cx = points[i + 1].x;
- var cy = points[i + 1].y;
- sum += ax * by - ay * bx + ay * cx - ax * cy + bx * cy - cx * by;
- }
- return (-sum / 2).abs();
- }
- static List<double> calcPerimeterAndArea(
- List<DPoint> points,
- bool isClosed,
- double tension,
- double tolerance,
- ) {
- final List<DPoint> polyLineSegment = [];
- final len = points.length;
- double perimeter = 0;
- if (len < 2) {
- return [0, 0];
- }
- if (len == 2) {
- final p1 = points[0];
- final p2 = points[1];
- final dx = p1.x - p2.x;
- final dy = p1.y - p2.y;
- perimeter = sqrt(dx * dx + dy * dy);
- return [perimeter, 0];
- }
- for (var i = 0; i < points.length; i++) {
- if (i == 0) {
- perimeter += segment(
- polyLineSegment,
- isClosed ? points[points.length - 1] : points[0],
- points[0],
- points[1],
- points[2],
- tension,
- tension,
- tolerance);
- } else if (i == points.length - 2) {
- perimeter += segment(
- polyLineSegment,
- points[i - 1],
- points[i],
- points[i + 1],
- isClosed ? points[0] : points[i + 1],
- tension,
- tension,
- tolerance);
- } else if (i == points.length - 1) {
- if (isClosed) {
- perimeter += segment(polyLineSegment, points[i - 1], points[i],
- points[0], points[1], tension, tension, tolerance);
- }
- } else {
- perimeter += segment(polyLineSegment, points[i - 1], points[i],
- points[i + 1], points[i + 2], tension, tension, tolerance);
- }
- }
- final area = calcArea(polyLineSegment);
- return [perimeter, area];
- }
- static double segment(List<DPoint> points, DPoint pt0, DPoint pt1, DPoint pt2,
- DPoint pt3, double t1, double t2, double tolerance) {
- double length = 0;
- final sx1 = t1 * (pt2.x - pt0.x);
- final sy1 = t1 * (pt2.y - pt0.y);
- final sx2 = t2 * (pt3.x - pt1.x);
- final sy2 = t2 * (pt3.y - pt1.y);
- final ax = sx1 + sx2 + 2 * pt1.x - 2 * pt2.x;
- final ay = sy1 + sy2 + 2 * pt1.y - 2 * pt2.y;
- final bx = -2 * sx1 - sx2 - 3 * pt1.x + 3 * pt2.x;
- final by = -2 * sy1 - sy2 - 3 * pt1.y + 3 * pt2.y;
- final cx = sx1;
- final cy = sy1;
- final dx = pt1.x;
- final dy = pt1.y;
- var num = (((pt1.x - pt2.x).abs() + (pt1.y - pt2.y).abs()) ~/ tolerance);
- // Set num = 2 to calculate the length. when the distance of pt1,pt2 is very tiny, the num will be 0, the length will be 0
- if (num < 2) num = 2;
- // Notice begins at 1 so excludes the first point (which is just pt1)
- for (var i = 1; i < num; i++) {
- var t = i / (num - 1);
- var pt = DPoint(ax * t * t * t + bx * t * t + cx * t + dx,
- ay * t * t * t + by * t * t + cy * t + dy);
- if (i == 1) {
- length += (pt - pt1).length;
- } else {
- length += (pt - points[points.length - 1]).length;
- }
- points.add(pt);
- }
- return length;
- }
- }
|