import 'dart:ui'; import 'dart:math' as math; 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/workspace/point_info.dart'; import 'package:fis_measure/process/calcuators/calculator.dart'; import 'package:fis_measure/process/calcuators/curve.dart'; import 'package:fis_measure/process/calcuators/formulas/general.dart'; import 'package:fis_measure/utils/canvas.dart'; import 'package:path_drawing/path_drawing.dart'; import 'package:vid/us/vid_us_unit.dart'; import 'area_abstract.dart'; class Ellipse extends AreaItemAbstract { Ellipse(ItemMeta meta, IMeasureItem? parent) : super(meta, parent); @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; final f = feature! as EllipseFeature; final activeIndex = f.activeIndex; switch (args.pointType) { case PointInfoType.mouseMove: f.innerPoints[activeIndex] = args; f.adjustPoints(args); break; case PointInfoType.mouseDown: if (activeIndex == 1) { if (f.xAxisEnd == f.xAxisStart) { break; } f.adjustPoints(args); f.activeIndex = 2; } else if (activeIndex == 2) { doFeatureFinish(); } break; default: return false; } doCalculate(); return true; } return false; } bool isFirstPointMove = true; DPoint touchStartPosition = DPoint(0, 0); // 相对位移起始触摸点 DPoint currPointLastPosition = DPoint(0, 0); // 当前操作的点之前所在位置 @override bool onExecuteTouch(PointInfo args) { if (state == ItemStates.finished) { if (args.pointType == PointInfoType.touchDown) { state = ItemStates.waiting; } } if (state == ItemStates.waiting) { if (args.pointType == PointInfoType.touchDown) { isFirstPointMove = true; handleTouchDownWhileWaiting(args); } } else if (state == ItemStates.running) { if (feature == null) return false; final f = feature! as EllipseFeature; if (args.pointType == PointInfoType.touchDown) { touchStartPosition = args; currPointLastPosition = f.innerPoints[f.activeIndex]; } if (args.pointType == PointInfoType.touchMove) { if (isFirstPointMove) { PointInfo newStartPoint = args; newStartPoint.addOffset(0, -0.2); if (isMoveTargetOutOfRange(newStartPoint)) return true; for (var element in f.innerPoints) { element.update(newStartPoint); } } else { PointInfo newPoint = PointInfo.fromOffset( currPointLastPosition .clone() .addVector(args - touchStartPosition) .toOffset(), args.pointType); newPoint.hostVisualArea = args.hostVisualArea; if (isMoveTargetOutOfRange(newPoint)) return true; f.innerPoints[f.activeIndex] = newPoint; f.adjustPoints(newPoint); } } if (args.pointType == PointInfoType.touchUp) { if (isFirstPointMove) { isFirstPointMove = false; f.activeIndex = 1; } if (f.activeIndex == 1) { if (f.xAxisEnd != f.xAxisStart) { f.adjustPoints(args); f.activeIndex = 2; } } else if (f.activeIndex == 2) { doFeatureFinish(); } } doCalculate(); return true; } return false; } void handleMouseDownWhileWaiting(PointInfo args) { // TODO: 判断是否当前area // 转换为Area逻辑位置 final point = args.toAreaLogicPoint(); feature = EllipseFeature(this, point); if (args.hostVisualArea != null) { feature!.hostVisualArea = args.hostVisualArea; } feature!.activeIndex = 1; state = ItemStates.running; } void handleTouchDownWhileWaiting(PointInfo args) { // TODO: 判断是否当前area // 转换为Area逻辑位置 final point = args.toAreaLogicPoint(); feature = EllipseFeature(this, point); if (args.hostVisualArea != null) { feature!.hostVisualArea = args.hostVisualArea; } feature!.activeIndex = 0; state = ItemStates.running; } static Ellipse createAreaPerimeter(ItemMeta meta, [IMeasureItem? parent]) { final ellipse = Ellipse(meta, parent); ellipse.calculator = AreaPerimeterEllipseCal(ellipse); return ellipse; } static Ellipse createVolume(ItemMeta meta, [IMeasureItem? parent]) { final ellipse = Ellipse(meta, parent); ellipse.calculator = _EllipseVolumeCal(ellipse); return ellipse; } } class EllipseFeature extends AreaItemFeatureAbstract { EllipseFeature(Ellipse refItem, DPoint point) : super(refItem) { innerPoints.add(point.clone()); innerPoints.add(point.clone()); innerPoints.add(point.clone()); innerPoints.add(point.clone()); } /// 质心 DPoint get centroid { final x = (xAxisStart.x + xAxisEnd.x) * 0.5; final y = (xAxisStart.y + xAxisEnd.y) * 0.5; return DPoint(x, y); } DPoint get xAxisStart => innerPoints[0]; DPoint get xAxisEnd => innerPoints[1]; DPoint get yAxisStart => innerPoints[2]; DPoint get yAxisEnd => innerPoints[3]; @override void paint(Canvas canvas, Size size) { if (innerPoints.isEmpty) return; drawId(canvas, size, idText); final xStartOffset = convert2ViewPoint(size, xAxisStart).toOffset(); if (activeIndex < 1) { drawVertex(canvas, xStartOffset, true); return; } else { drawVertex(canvas, xStartOffset); } final xEndOffset = convert2ViewPoint(size, xAxisEnd).toOffset(); canvas.drawDashLine(xStartOffset, xEndOffset, 1, 10, paintLinePan); final yStartOffset = convert2ViewPoint(size, innerPoints[2]).toOffset(); final yEndOffset = convert2ViewPoint(size, innerPoints[3]).toOffset(); final centroidOffset = convert2ViewPoint(size, centroid).toOffset(); canvas.drawDashLine(yStartOffset, yEndOffset, 1, 10, paintLinePan); // canvas.drawDashLine(yStartOffset, xEndOffset, 1, 10, paintLinePan); // canvas.drawDashLine(yEndOffset, xEndOffset, 1, 10, paintLinePan); final radiusX = getRadiusX(size); final radiusY = getRadiusY(size); canvas.save(); final path = Path(); path.moveTo(xStartOffset.dx, xStartOffset.dy); // 以质心为原点旋转到水平方向 canvas.translate(centroidOffset.dx, centroidOffset.dy); final rotateRad = (xEndOffset - xStartOffset).direction; canvas.rotate(rotateRad); canvas.translate(-centroidOffset.dx, -centroidOffset.dy); path.addOval( Rect.fromCenter( center: centroidOffset, width: radiusX * 2, height: radiusY * 2, ), ); canvas.drawPath( dashPath( path, dashArray: CircularIntervalList([2.0, 10.0]), ), paintPan, ); canvas.restore(); if (activeIndex == 1) { drawVertex(canvas, xEndOffset, true); drawVertex(canvas, yStartOffset); drawVertex(canvas, yEndOffset); } else if (activeIndex == 2) { drawVertex(canvas, xEndOffset); drawVertex(canvas, yEndOffset); drawVertex(canvas, yStartOffset, isActive); } } /// X轴半径 double getRadiusX([Size? fitSize]) { if (innerPoints.length < 2) { return 0; } var p1 = xAxisStart; var p2 = xAxisEnd; if (fitSize != null) { p1 = p1.scale2Size(fitSize); p2 = p2.scale2Size(fitSize); } return (p2 - p1).length / 2; } /// Y轴半径 double getRadiusY([Size? fitSize]) { if (activeIndex > 1) { var p = yAxisStart; var p2 = yAxisEnd; var c = centroid; if (fitSize != null) { p = p.scale2Size(fitSize); p2 = p2.scale2Size(fitSize); c = c.scale2Size(fitSize); } return (p - p2).length / 2; } else { return getRadiusX(fitSize); } } /// 圆周长 double getCircumference([Size? fitSize]) { double a = getRadiusX(fitSize), b = getRadiusY(fitSize); if (a < b) { a = b; b = a; } return 2 * math.pi * b + 4 * (a - b); } /// 面积 double getArea([Size? fitSize]) { return math.pi * getRadiusX(fitSize) * getRadiusY(fitSize); } /// 计算Y轴坐标点 void adjustPoints(DPoint point) { if (activeIndex < 1) return; final refSize = refItem.application.displaySize; // const refSize= Size(1000, 1000); // const restoreSize = Size(1 / 1000, 1 / 1000); final restoreSize = Size(1 / refSize.width, 1 / refSize.height); final p = point.scale2Size(refSize); final p1 = xAxisStart.scale2Size(refSize); final p2 = xAxisEnd.scale2Size(refSize); final double radius; if (activeIndex == 2) { radius = GeneralFormulas.distance2Line(p1, p2, p); } else { radius = getRadiusX(refSize); } final crossPoints = _findCrossPoints(p2, p1, radius); final p3Index = _findNearest(p, crossPoints); innerPoints[2] = crossPoints[p3Index].scale2Size(restoreSize); innerPoints[3] = crossPoints[1 - p3Index].scale2Size(restoreSize); } static List _findCrossPoints(DPoint a, DPoint b, distance) { final dx = b.x - a.x; final dy = b.y - a.y; final len = (b - a).length; /// 单位长度轴上距离增加量 final udx = dx / len; final udy = dy / len; final px = -udy; final py = udx; final centroidX = (a.x + b.x) * 0.5; final centroidY = (a.y + b.y) * 0.5; final centroid = DPoint(centroidX, centroidY); final x1 = centroid.x + px * distance; final y1 = centroid.y + py * distance; final out1 = DPoint(x1, y1); final x2 = centroid.x - px * distance; final y2 = centroid.y - py * distance; final out2 = DPoint(x2, y2); return [out1, out2]; } static int _findNearest(DPoint p, List source) { int rst = 0; double minLen = (source[0] - p).length; for (var i = 1; i < source.length; i++) { final len = (source[i] - p).length; if (len < minLen) { minLen = len; rst = i; } } return rst; } } class _EllipseVolumeCal extends Calculator { _EllipseVolumeCal(Ellipse ref) : super(ref); @override void calculate() { if (ref.feature == null) return; final feature = ref.feature! as EllipseFeature; final viewport = feature.hostVisualArea!.viewport!; final size = viewport.convertBoundary; double xAxis = feature.getRadiusX(size); double yAxis = feature.getRadiusY(size); var a = math.max(xAxis, yAxis); var b = math.min(xAxis, yAxis); var value = math.pi / 6.0 * a * b * b; updateFloatValue(value, unit: VidUsUnit.cm3, useRound: true); } }