import 'dart:ui'; import 'dart:math' as math; import 'package:fis_measure/interfaces/date_types/point.dart'; import 'package:fis_measure/interfaces/date_types/vector.dart'; import 'package:fis_measure/interfaces/enums/items.dart'; import 'package:fis_measure/interfaces/process/calculators/values.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/types.dart'; import 'package:fis_measure/interfaces/process/workspace/application.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/formulas/general.dart'; import 'package:fis_measure/process/items/item.dart'; import 'package:fis_measure/process/items/item_feature.dart'; import 'package:fis_measure/process/primitives/polyline.dart'; import 'package:fis_measure/process/primitives/area_abstract.dart'; import 'package:fis_measure/process/primitives/utils/auto_snap.dart'; import 'package:fis_measure/process/unit/convert/convert.dart'; import 'package:fis_measure/utils/canvas.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; import 'package:path_drawing/path_drawing.dart'; import 'package:vid/us/vid_us_unit.dart'; import 'spline.dart'; import 'utils/line.dart'; import 'utils/spline.dart'; enum LvSimpsonStep { none, splineBeginEdit, splineEditing, splineEndEdit, splineCompleted, done, } class SimpsonPath extends AreaItemAbstract with AutoSnapMixin { static const int splitterCount = 20; static const double maxPointsCount = 10000; SimpsonPath(ItemMeta meta, IMeasureItem? parent) : super(meta, parent); PointInfo? firstPoint; @protected LvSimpsonStep lvSimpsonStep = LvSimpsonStep.none; @override // SimpsonPathFeature? get feature => super.feature as SimpsonPathFeature; SimpsonPathFeature? get feature { if (super.feature == null) { return null; } return super.feature as SimpsonPathFeature; } @override bool onExecuteMouse(PointInfo args) { if (state == ItemStates.finished) { if (args.pointType == PointInfoType.mouseDown) { state = ItemStates.waiting; lvSimpsonStep = LvSimpsonStep.none; snapState = false; } } if (state == ItemStates.waiting) { if (args.pointType == PointInfoType.mouseDown) { handleMouseDownWhileWaiting(args); } } else if (state == ItemStates.running) { switch (args.pointType) { case PointInfoType.mouseUp: return false; case PointInfoType.mouseDown: if (lvSimpsonStep == LvSimpsonStep.splineCompleted) { feature!.adjustEndPoint(args.toAreaLogicPoint()); lvSimpsonStep = LvSimpsonStep.done; state = ItemStates.finished; // CaliperExtension.ShowCaliper(); doFeatureFinish(); } else { final lineLen = (args - firstPoint!).length; if ((feature!.innerPoints.length > 3 && GeneralFormulas.doubleAlmostEquals(lineLen, 0)) || feature!.innerPoints.length >= maxPointsCount) { lvSimpsonStep = LvSimpsonStep.splineCompleted; feature!.fixedSpline(); } else { feature!.adopt(args.toAreaLogicPoint()); } } break; case PointInfoType.mouseMove: if (lvSimpsonStep == LvSimpsonStep.splineCompleted) { feature!.adjustEndPoint(args.toAreaLogicPoint()); } else { if (!snapState) { // checkAutoSnap(args, false); if (checkAutoSnap(args, false)) { lvSimpsonStep = LvSimpsonStep.splineCompleted; feature!.fixedSpline(); } else { feature!.adopt(args.toAreaLogicPoint()); } } if (snapState) { feature!.updateActivePoint(args.toAreaLogicPoint()); } } break; default: break; } } doCalculate(); if (args.pointType == PointInfoType.mouseDown) { // doFeatureFinish(); // if (state == ItemStates.waiting || state == ItemStates.finished) { if (state == ItemStates.waiting) { state = ItemStates.running; } if (state == ItemStates.running) { updateStatus(); } } if (args.pointType == PointInfoType.mouseMove) { updateStatus(); } return true; } @override bool onExecuteTouch(PointInfo args) { // TODO: implement onExecuteTouch throw UnimplementedError(); } void handleMouseDownWhileWaiting(PointInfo args) { // TODO: 判断是否当前area // 转换为Area逻辑位置 feature = SimpsonPathFeature(this); if (args.hostVisualArea != null) { feature!.hostVisualArea = args.hostVisualArea; } final point = args.toAreaLogicPoint(); feature!.adopt(point); feature!.adopt(point); state = ItemStates.running; firstPoint = args; } void updateStatus() { switch (lvSimpsonStep) { case LvSimpsonStep.splineCompleted: firstPoint = null; if (feature != null) { feature!.activeIndex = feature!.innerPoints.length; } break; case LvSimpsonStep.done: state = ItemStates.finished; if (feature != null) { feature!.activeIndex = -1; } break; default: break; } } static SimpsonPath create(ItemMeta meta, [IMeasureItem? parent]) { final path = SimpsonPath(meta, parent); return path; } } class SimpsonPathFeature extends AreaItemFeatureAbstract { static const autoGetApexPoint = false; // TODO late Map horizontalSplitterLegths; late DPoint moveBasedPoint; IPathGeometry? _pathGeometry; List? _splinePoints; IPathGeometry? _spline; late DPoint _centerLineFixedPoint; late DPoint _centerLineMovablePoint; late MovablePointsInfo _movablePtsInfo; late DPoint _leftPoint; late DPoint _rightPoint; late DPoint _apexPoint; SimpsonPathFeature(AreaItemAbstract refItem) : super(refItem) { _splinePoints = []; _centerLineFixedPoint = DPointExt.empty; _centerLineMovablePoint = DPointExt.empty; _movablePtsInfo = MovablePointsInfo.empty(); _leftPoint = DPointExt.empty; _rightPoint = DPointExt.empty; _apexPoint = DPointExt.empty; moveBasedPoint = DPointExt.empty; horizontalSplitterLegths = {}; } @override SimpsonPath get refItem => super.refItem as SimpsonPath; DPoint get centerLineMovablePoint => _centerLineMovablePoint; set centerLineMovablePoint(DPoint val) { if (val != _centerLineMovablePoint) { _centerLineMovablePoint = val; updateSplitters(); _onVertexPointChanged(); } } DPoint get centerLineFixedPoint => _centerLineFixedPoint; MovablePointsInfo get movablePtsInfo => _movablePtsInfo; set movablePtsInfo(MovablePointsInfo val) { if (val != _movablePtsInfo) { _movablePtsInfo = val; _onSplineMovablePointsInfoChanged(); } } DPoint get leftPoint => _leftPoint; set leftPoint(DPoint val) { if (val != _leftPoint) { _leftPoint = val; _onVertexPointChanged(); } } DPoint get rightPoint => _rightPoint; set rightPoint(DPoint val) { if (val != _rightPoint) { _rightPoint = val; _onVertexPointChanged(); } } DPoint get apexPoint => _apexPoint; set apexPoint(DPoint val) { if (val != _apexPoint) { _apexPoint = val; _onVertexPointChanged(); } } double get centerLineLength { final p1 = convert2CmPoint(_centerLineFixedPoint); final p2 = convert2CmPoint(_centerLineMovablePoint); double value = (p2 - p1).length.abs(); return value; } double get area { final points = innerPoints.map((e) => convert2CmPoint(e)).toList(); double value = AreaPerimeterCal.calcArea(points); return value; } DPoint convert2CmPoint(DPoint logicPoint) { final viewport = hostVisualArea!.viewport!; final p = viewport.convert(logicPoint); p.x = UnitValueConverter.convert(viewport.xUnit, VidUsUnit.cm, p.x); p.y = UnitValueConverter.convert(viewport.yUnit, VidUsUnit.cm, p.y); return p; } bool isClosed = false; // TODO @override void paint(Canvas canvas, Size size) { if (innerPoints.isEmpty) return; drawId(canvas, size); final points = innerPoints.map((e) => convert2ViewPoint(size, e)).toList(); final startPoint = points.first; drawVertex(canvas, startPoint.toOffset(), points.length == 1); if (points.length > 1) { final Path path = Path(); path.moveTo(startPoint.x, startPoint.y); for (var i = 1; i < points.length; i++) { final point = points[i]; path.lineTo(point.x, point.y); } if (isClosed) { path.lineTo(startPoint.x, startPoint.y); } canvas.drawPath( path, paintLinePan, ); } if (_pathGeometry != null) { _drawGeometry(canvas, size, _pathGeometry!); } // 此处仅供调试绘制参考点 // canvas.drawCircle( // convert2ViewPoint(size, centerLineFixedPoint).toOffset(), // 10, // Paint() // ..color = Colors.blue // ..style = PaintingStyle.fill, // ); // canvas.drawCircle( // convert2ViewPoint(size, centerLineMovablePoint).toOffset(), // 10, // Paint() // ..color = Colors.red // ..style = PaintingStyle.fill, // ); drawVertex(canvas, centerLineMovablePoint.toOffset(), isActive); } void _drawGeometry(Canvas canvas, Size size, IPathGeometry geometry) { Path? path; if (geometry is PathGeometryContainer) { for (var childGeometry in geometry.geometries) { _drawGeometry(canvas, size, childGeometry); } } else if (geometry is PathGeometry) { path = Path(); for (var i = 0; i < geometry.points.length; i++) { final point = convert2ViewPoint(size, geometry.points[i]); if (i == 0) { path.moveTo(point.x, point.y); } else { path.lineTo(point.x, point.y); } } } else if (geometry is LineGeometry) { path = Path(); DPoint point; point = convert2ViewPoint(size, geometry.start); path.moveTo(point.x, point.y); point = convert2ViewPoint(size, geometry.end); path.lineTo(point.x, point.y); } if (path != null) { canvas.drawPath(path, paintLinePan); } } @override void adopt(DPoint point) { super.adopt(point); recreateSpline(false); } void updateActivePoint(DPoint point) { if (activeIndex > 0 && activeIndex <= innerPoints.length) { activePoint = point; recreateSpline(false); } } DPoint get activePoint { if (activeIndex < 0 || activeIndex >= innerPoints.length) { throw IndexError.withLength(activeIndex, innerPoints.length); } return innerPoints[activeIndex]; } set activePoint(DPoint val) { if (activeIndex < 0 || activeIndex >= innerPoints.length) { throw IndexError.withLength(activeIndex, innerPoints.length); } innerPoints[activeIndex] = val; } void fixedSpline() { if (innerPoints.isEmpty) { return; } _centerLineFixedPoint = DPoint( (innerPoints.last.x + innerPoints.first.x) * 0.5, (innerPoints.last.y + innerPoints.first.y) * 0.5, ); var movablePoint = DPoint(0, double.infinity); var isFindMin = innerPoints[innerPoints.length >> 1].y - innerPoints[0].y < 0; if (autoGetApexPoint) { movablePoint = isFindMin ? movablePoint : DPoint(0, double.negativeInfinity); } for (DPoint point in innerPoints) { if (autoGetApexPoint && innerPoints.isNotEmpty) { if (isFindMin) { if (point.y < movablePoint.y) { movablePoint = point; } } else { if (point.y > movablePoint.y) { movablePoint = point; } } } else { if (point.y < movablePoint.y) { movablePoint = point; } } } if (_centerLineMovablePoint != movablePoint) { _centerLineMovablePoint = movablePoint; if (!_centerLineMovablePoint.isEmpty) { // TODO 重设光标位置,暂不支持 // _centerLineMovablePoint.SynchToMainMonitorScreen(HostArea); } } updateSplitters(); _onVertexPointChanged(); } void adjustEndPoint(DPoint point) { if (innerPoints.isEmpty) { return; } var endPoint = point; double minDistance = double.infinity; // if (_splinePoints != null && _splinePoints!.isNotEmpty) { // for (DPoint splinePoint in _splinePoints!) { // double distance = (point - splinePoint).length; // if (distance < minDistance) { // minDistance = distance; // endPoint = splinePoint; // } // } // } // TODO: _splinePoints填充和此处时序有问题,先用innerPoints代替,有性能问题,后续解决 if (innerPoints.isNotEmpty) { final splinePoints = innerPoints.take(innerPoints.length - 1); for (DPoint splinePoint in splinePoints) { double distance = (point - splinePoint).length; if (distance < minDistance) { minDistance = distance; endPoint = splinePoint; } } } centerLineMovablePoint = endPoint; } void recreateSpline(bool isClosed) { // if (_breaker.Paused) { // return; // } final generator = SimpsonGeometryGenerator(); _pathGeometry ??= generator.createPathGeometry(); double tempCircumference = 0; _spline = generator.createSpline( innerPoints, 0.5, null, isClosed, false, 0.005, tempCircumference, _splinePoints, ); if (_spline != null && _splinePoints != null) { // _pathGeometry!.addGeometry(_spline!); } } void updateSplitters() { recreateSpline(true); if (_spline != null && !_centerLineMovablePoint.isEmpty) { var generator = SimpsonGeometryGenerator(); _pathGeometry!.clear(); if (innerPoints.isNotEmpty) { // _pathGeometry!.addGeometry(_spline!); horizontalSplitterLegths = {}; generator.createSplitters( _pathGeometry!, _spline!, _centerLineFixedPoint, _centerLineMovablePoint, horizontalSplitterLegths, ); _pathGeometry!.addLineGeometry( _centerLineFixedPoint, _centerLineMovablePoint, ); } } } void _onVertexPointChanged() {} void _onSplineMovablePointsInfoChanged() {} } class SimpsonGeometryGenerator { static const int splitterCount = 20; void createSplitters( IPathGeometry pathGeometry, IPathGeometry spline, DPoint centerLineFixedPoint, DPoint centerLineMovablePoint, Map horizontalSplitterLegths, ) { if (centerLineFixedPoint.isEmpty || centerLineMovablePoint.isEmpty) { return; } // 先放大点,防止计算过程中丢失过多精度,导致角度错误 final size = Get.find().displaySize; DPoint pointA = centerLineFixedPoint.clone().scale2Size(size); DPoint pointB = centerLineMovablePoint.clone().scale2Size(size); // 用于恢复百分比点 final reversalSize = Size(1 / size.width, 1 / size.height); // Center line DVector centerLine = pointB - pointA; DVector centerPerLine = centerLine / splitterCount.toDouble(); final verticalVector = DVector(-centerLine.y, centerLine.x) * 50; //Create a 20 splitters path geometry PathGeometryContainer temSplittersGeometry = PathGeometryContainer(Path()); for (var i = 0; i < splitterCount; i++) { final center = pointA .clone() .addVector(centerPerLine * i.toDouble()) .addVector(centerPerLine / 2); DPoint p1 = center.clone().addVector(verticalVector); DPoint p2 = center.clone().subtractVector(verticalVector); final lineGeometry = LineGeometry(p1, p2); temSplittersGeometry.addGeometry(lineGeometry); } final spline2 = PathGeometryContainer(Path()); final sss = (spline as PathGeometryContainer).geometries.first as PathGeometry; spline2.addGeometry( PathGeometry(sss.points.map((e) => e.scale2Size(size)).toList())); //Get intersection points List points = getIntersectionPoints(temSplittersGeometry, spline2); List leftPoints = []; List rightPoints = []; //Split point by center line const double constMagnify = 1.0; for (DPoint point in points) { var magnifyPoint = point.clone(); magnifyPoint.x *= constMagnify; magnifyPoint.y *= constMagnify; DVector line = magnifyPoint - DPoint( (pointA.x + pointB.x) / 2.0 * constMagnify, (pointA.y + pointB.y) / 2.0 * constMagnify, ); double angleToCenter = DVector.angleBetween(line, centerLine); if (angleToCenter >= 0.0) { leftPoints.add(point); } else { rightPoints.add(point); } } double angleToVertical = DVector.angleBetween(verticalVector, DVector(1, 0)); //order point by vertical axis value orderByCalc(DPoint point) => rotaeY( point, pointA, angleToVertical * math.pi / 180.0, ); leftPoints.sort((a, b) { final vA = orderByCalc(a); final vB = orderByCalc(b); return vA.compareTo(vB); }); rightPoints.sort((a, b) { final vA = orderByCalc(a); final vB = orderByCalc(b); return vA.compareTo(vB); }); var finalLeftPoint = leftPoints; var finalRightPoint = rightPoints; if (finalLeftPoint.length == finalRightPoint.length && finalLeftPoint.length == splitterCount) { for (int i = 0; i < splitterCount; i++) { // 恢复百分比点 final pL = finalLeftPoint[i].scale2Size(reversalSize); final pR = finalRightPoint[i].scale2Size(reversalSize); horizontalSplitterLegths[i + 1] = (pL - pR).length; // pathGeometry.addLineGeometry(finalLeftPoint[i], finalRightPoint[i]); pathGeometry.addLineGeometry(pL, pR); } // print("Output!!!!"); // print(((spline as PathGeometryContainer).geometries.first as PathGeometry) // .points); // print([centerLineFixedPoint, centerLineMovablePoint]); // print(leftPoints); // print(rightPoints); } else {} } IPathGeometry? createSpline( List sourcePoints, double tension, List? tensions, bool isClosed, bool isFilled, double tolerance, double length, List? splinePoints, ) { length = 0; if (sourcePoints.isEmpty) { return null; } // List samplePoints = []; // var points = SplineUtils.create( // sourcePoints, // tension: tension, // tensions: tensions, // isClosed: isClosed, // tolerance: tolerance, // closeByStraightLine: true, // samplePoints: samplePoints, // isSimpsonSpline: true, // ); final points = sourcePoints; // TODO: !!!!!!! 创建收缩Spline有问题,先不计算了,有性能问题再优化 splinePoints = points; final pathGeometry = PathGeometry(points); final continer = PathGeometryContainer(Path()); continer.addGeometry(pathGeometry); return continer; } IPathGeometry createPathGeometry() { return PathGeometryContainer(Path()); } static double rotaeY(DPoint point, DPoint cenPoint, double theta) { return math.sin(theta) * (point.x - cenPoint.x) + math.cos(theta) * (point.y - cenPoint.y) + cenPoint.y; } List getIntersectionPoints(IPathGeometry g1, IPathGeometry g2) { final result = []; final splittersGeometry = g1 as PathGeometryContainer; final splineGeometry = (g2 as PathGeometryContainer).geometries.first as PathGeometry; for (var splitter in splittersGeometry.geometries) { splitter as LineGeometry; DPoint lastPoint = splineGeometry.points.first; final endLimit = splineGeometry.points.length - 1; for (var i = 1; i < splineGeometry.points.length; i++) { final currPoint = splineGeometry.points[i]; final crossPoint = LineUtils.calculateIntersection( splitter.start, splitter.end, lastPoint, currPoint, ); if (crossPoint != null) { if (LineUtils.isPointOnLine(lastPoint, currPoint, crossPoint)) { result.add(crossPoint); } } if (i != endLimit) { lastPoint = currPoint; } } } return result; } } class PathGeometryContainer implements IPathGeometry { final geometries = []; late final Path control; PathGeometryContainer(Path geometry) { control = geometry; } @override void addGeometry(IPathGeometry geometry) { geometries.add(geometry); } @override void clear() { geometries.clear(); } @override void addLineGeometry( DPoint centerLineFixedPoint, DPoint centerLineMovablePoint, ) { addGeometry(LineGeometry(centerLineFixedPoint, centerLineMovablePoint)); } } class PathGeometry implements IPathGeometry { final List points; PathGeometry(this.points); @override void addGeometry(IPathGeometry spline) {} @override void addLineGeometry( DPoint centerLineFixedPoint, DPoint centerLineMovablePoint) {} @override void clear() {} } class LineGeometry implements IPathGeometry { final DPoint start; final DPoint end; LineGeometry(this.start, this.end); @override void addGeometry(IPathGeometry spline) {} @override void addLineGeometry( DPoint centerLineFixedPoint, DPoint centerLineMovablePoint) {} @override void clear() {} void rotateTransform(double angle, double centerX, double centerY) { rotatePoint(start, centerX, centerY, angle); rotatePoint(end, centerX, centerY, angle); } static void rotatePoint( DPoint point, double centerX, double centerY, double angle, ) { double radians = angle * math.pi / 180; double cosTheta = math.cos(radians); double sinTheta = math.sin(radians); point.x = centerX + (point.x - centerX) * cosTheta - (point.y - centerY) * sinTheta; point.y = centerY + (point.x - centerX) * sinTheta + (point.y - centerY) * cosTheta; } } abstract class IPathGeometry { void clear(); void addGeometry(IPathGeometry spline); void addLineGeometry( DPoint centerLineFixedPoint, DPoint centerLineMovablePoint); } extension DPointExt on DPoint { static final empty = DPoint(double.minPositive, double.minPositive); bool get isEmpty { return this == empty; } } class MovablePointsInfo { int index; int start; int end; MovablePointsInfo(this.index, this.start, this.end); factory MovablePointsInfo.empty() { return MovablePointsInfo(-1, -1, -1); } }