ellipse.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. import 'dart:ui';
  2. import 'dart:math' as math;
  3. import 'package:fis_measure/interfaces/date_types/point.dart';
  4. import 'package:fis_measure/interfaces/enums/items.dart';
  5. import 'package:fis_measure/interfaces/process/items/item.dart';
  6. import 'package:fis_measure/interfaces/process/items/item_metas.dart';
  7. import 'package:fis_measure/interfaces/process/workspace/point_info.dart';
  8. import 'package:fis_measure/process/calcuators/calculator.dart';
  9. import 'package:fis_measure/process/calcuators/curve.dart';
  10. import 'package:fis_measure/process/calcuators/formulas/general.dart';
  11. import 'package:fis_measure/utils/canvas.dart';
  12. import 'package:path_drawing/path_drawing.dart';
  13. import 'package:vid/us/vid_us_unit.dart';
  14. import 'area_abstract.dart';
  15. class Ellipse extends AreaItemAbstract {
  16. Ellipse(ItemMeta meta, IMeasureItem? parent) : super(meta, parent);
  17. @override
  18. bool onExecuteMouse(PointInfo args) {
  19. if (state == ItemStates.finished) {
  20. if (args.pointType == PointInfoType.mouseDown) {
  21. state = ItemStates.waiting;
  22. }
  23. }
  24. if (state == ItemStates.waiting) {
  25. if (args.pointType == PointInfoType.mouseDown) {
  26. handleMouseDownWhileWaiting(args);
  27. }
  28. } else if (state == ItemStates.running) {
  29. if (feature == null) return false;
  30. final f = feature! as EllipseFeature;
  31. final activeIndex = f.activeIndex;
  32. switch (args.pointType) {
  33. case PointInfoType.mouseMove:
  34. f.innerPoints[activeIndex] = args;
  35. f.adjustPoints(args);
  36. break;
  37. case PointInfoType.mouseDown:
  38. if (activeIndex == 1) {
  39. if (f.xAxisEnd == f.xAxisStart) {
  40. break;
  41. }
  42. f.adjustPoints(args);
  43. f.activeIndex = 2;
  44. } else if (activeIndex == 2) {
  45. doFeatureFinish();
  46. }
  47. break;
  48. default:
  49. return false;
  50. }
  51. doCalculate();
  52. return true;
  53. }
  54. return false;
  55. }
  56. bool isFirstPointMove = true;
  57. DPoint touchStartPosition = DPoint(0, 0); // 相对位移起始触摸点
  58. DPoint currPointLastPosition = DPoint(0, 0); // 当前操作的点之前所在位置
  59. @override
  60. bool onExecuteTouch(PointInfo args) {
  61. if (state == ItemStates.finished) {
  62. if (args.pointType == PointInfoType.touchDown) {
  63. state = ItemStates.waiting;
  64. }
  65. }
  66. if (state == ItemStates.waiting) {
  67. if (args.pointType == PointInfoType.touchDown) {
  68. isFirstPointMove = true;
  69. handleTouchDownWhileWaiting(args);
  70. }
  71. } else if (state == ItemStates.running) {
  72. if (feature == null) return false;
  73. final f = feature! as EllipseFeature;
  74. if (args.pointType == PointInfoType.touchDown) {
  75. touchStartPosition = args;
  76. currPointLastPosition = f.innerPoints[f.activeIndex];
  77. }
  78. if (args.pointType == PointInfoType.touchMove) {
  79. if (isFirstPointMove) {
  80. PointInfo newStartPoint = args;
  81. newStartPoint.addOffset(0, -0.2);
  82. if (isMoveTargetOutOfRange(newStartPoint)) return true;
  83. for (var element in f.innerPoints) {
  84. element.update(newStartPoint);
  85. }
  86. } else {
  87. PointInfo newPoint = PointInfo.fromOffset(
  88. currPointLastPosition
  89. .clone()
  90. .addVector(args - touchStartPosition)
  91. .toOffset(),
  92. args.pointType);
  93. newPoint.hostVisualArea = args.hostVisualArea;
  94. if (isMoveTargetOutOfRange(newPoint)) return true;
  95. f.innerPoints[f.activeIndex] = newPoint;
  96. f.adjustPoints(newPoint);
  97. }
  98. }
  99. if (args.pointType == PointInfoType.touchUp) {
  100. if (isFirstPointMove) {
  101. isFirstPointMove = false;
  102. f.activeIndex = 1;
  103. }
  104. if (f.activeIndex == 1) {
  105. if (f.xAxisEnd != f.xAxisStart) {
  106. f.adjustPoints(args);
  107. f.activeIndex = 2;
  108. }
  109. } else if (f.activeIndex == 2) {
  110. doFeatureFinish();
  111. }
  112. }
  113. doCalculate();
  114. return true;
  115. }
  116. return false;
  117. }
  118. void handleMouseDownWhileWaiting(PointInfo args) {
  119. // TODO: 判断是否当前area
  120. // 转换为Area逻辑位置
  121. final point = args.toAreaLogicPoint();
  122. feature = EllipseFeature(this, point);
  123. if (args.hostVisualArea != null) {
  124. feature!.hostVisualArea = args.hostVisualArea;
  125. }
  126. feature!.activeIndex = 1;
  127. state = ItemStates.running;
  128. }
  129. void handleTouchDownWhileWaiting(PointInfo args) {
  130. // TODO: 判断是否当前area
  131. // 转换为Area逻辑位置
  132. final point = args.toAreaLogicPoint();
  133. feature = EllipseFeature(this, point);
  134. if (args.hostVisualArea != null) {
  135. feature!.hostVisualArea = args.hostVisualArea;
  136. }
  137. feature!.activeIndex = 0;
  138. state = ItemStates.running;
  139. }
  140. static Ellipse createAreaPerimeter(ItemMeta meta, [IMeasureItem? parent]) {
  141. final ellipse = Ellipse(meta, parent);
  142. ellipse.calculator = AreaPerimeterEllipseCal(ellipse);
  143. return ellipse;
  144. }
  145. static Ellipse createVolume(ItemMeta meta, [IMeasureItem? parent]) {
  146. final ellipse = Ellipse(meta, parent);
  147. ellipse.calculator = _EllipseVolumeCal(ellipse);
  148. return ellipse;
  149. }
  150. }
  151. class EllipseFeature extends AreaItemFeatureAbstract {
  152. EllipseFeature(Ellipse refItem, DPoint point) : super(refItem) {
  153. innerPoints.add(point.clone());
  154. innerPoints.add(point.clone());
  155. innerPoints.add(point.clone());
  156. innerPoints.add(point.clone());
  157. }
  158. /// 质心
  159. DPoint get centroid {
  160. final x = (xAxisStart.x + xAxisEnd.x) * 0.5;
  161. final y = (xAxisStart.y + xAxisEnd.y) * 0.5;
  162. return DPoint(x, y);
  163. }
  164. DPoint get xAxisStart => innerPoints[0];
  165. DPoint get xAxisEnd => innerPoints[1];
  166. DPoint get yAxisStart => innerPoints[2];
  167. DPoint get yAxisEnd => innerPoints[3];
  168. @override
  169. void paint(Canvas canvas, Size size) {
  170. if (innerPoints.isEmpty) return;
  171. drawId(canvas, size, idText);
  172. final xStartOffset = convert2ViewPoint(size, xAxisStart).toOffset();
  173. if (activeIndex < 1) {
  174. drawVertex(canvas, xStartOffset, true);
  175. return;
  176. } else {
  177. drawVertex(canvas, xStartOffset);
  178. }
  179. final xEndOffset = convert2ViewPoint(size, xAxisEnd).toOffset();
  180. canvas.drawDashLine(xStartOffset, xEndOffset, 1, 10, paintLinePan);
  181. final yStartOffset = convert2ViewPoint(size, innerPoints[2]).toOffset();
  182. final yEndOffset = convert2ViewPoint(size, innerPoints[3]).toOffset();
  183. final centroidOffset = convert2ViewPoint(size, centroid).toOffset();
  184. canvas.drawDashLine(yStartOffset, yEndOffset, 1, 10, paintLinePan);
  185. // canvas.drawDashLine(yStartOffset, xEndOffset, 1, 10, paintLinePan);
  186. // canvas.drawDashLine(yEndOffset, xEndOffset, 1, 10, paintLinePan);
  187. final radiusX = getRadiusX(size);
  188. final radiusY = getRadiusY(size);
  189. canvas.save();
  190. final path = Path();
  191. path.moveTo(xStartOffset.dx, xStartOffset.dy);
  192. // 以质心为原点旋转到水平方向
  193. canvas.translate(centroidOffset.dx, centroidOffset.dy);
  194. final rotateRad = (xEndOffset - xStartOffset).direction;
  195. canvas.rotate(rotateRad);
  196. canvas.translate(-centroidOffset.dx, -centroidOffset.dy);
  197. path.addOval(
  198. Rect.fromCenter(
  199. center: centroidOffset,
  200. width: radiusX * 2,
  201. height: radiusY * 2,
  202. ),
  203. );
  204. canvas.drawPath(
  205. dashPath(
  206. path,
  207. dashArray: CircularIntervalList<double>(<double>[2.0, 10.0]),
  208. ),
  209. paintPan,
  210. );
  211. canvas.restore();
  212. if (activeIndex == 1) {
  213. drawVertex(canvas, xEndOffset, true);
  214. drawVertex(canvas, yStartOffset);
  215. drawVertex(canvas, yEndOffset);
  216. } else if (activeIndex == 2) {
  217. drawVertex(canvas, xEndOffset);
  218. drawVertex(canvas, yEndOffset);
  219. drawVertex(canvas, yStartOffset, isActive);
  220. }
  221. }
  222. /// X轴半径
  223. double getRadiusX([Size? fitSize]) {
  224. if (innerPoints.length < 2) {
  225. return 0;
  226. }
  227. var p1 = xAxisStart;
  228. var p2 = xAxisEnd;
  229. if (fitSize != null) {
  230. p1 = p1.scale2Size(fitSize);
  231. p2 = p2.scale2Size(fitSize);
  232. }
  233. return (p2 - p1).length / 2;
  234. }
  235. /// Y轴半径
  236. double getRadiusY([Size? fitSize]) {
  237. if (activeIndex > 1) {
  238. var p = yAxisStart;
  239. var p2 = yAxisEnd;
  240. var c = centroid;
  241. if (fitSize != null) {
  242. p = p.scale2Size(fitSize);
  243. p2 = p2.scale2Size(fitSize);
  244. c = c.scale2Size(fitSize);
  245. }
  246. return (p - p2).length / 2;
  247. } else {
  248. return getRadiusX(fitSize);
  249. }
  250. }
  251. /// 圆周长
  252. double getCircumference([Size? fitSize]) {
  253. double a = getRadiusX(fitSize), b = getRadiusY(fitSize);
  254. if (a < b) {
  255. a = b;
  256. b = a;
  257. }
  258. return 2 * math.pi * b + 4 * (a - b);
  259. }
  260. /// 面积
  261. double getArea([Size? fitSize]) {
  262. return math.pi * getRadiusX(fitSize) * getRadiusY(fitSize);
  263. }
  264. /// 计算Y轴坐标点
  265. void adjustPoints(DPoint point) {
  266. if (activeIndex < 1) return;
  267. final refSize = refItem.application.displaySize;
  268. // const refSize= Size(1000, 1000);
  269. // const restoreSize = Size(1 / 1000, 1 / 1000);
  270. final restoreSize = Size(1 / refSize.width, 1 / refSize.height);
  271. final p = point.scale2Size(refSize);
  272. final p1 = xAxisStart.scale2Size(refSize);
  273. final p2 = xAxisEnd.scale2Size(refSize);
  274. final double radius;
  275. if (activeIndex == 2) {
  276. radius = GeneralFormulas.distance2Line(p1, p2, p);
  277. } else {
  278. radius = getRadiusX(refSize);
  279. }
  280. final crossPoints = _findCrossPoints(p2, p1, radius);
  281. final p3Index = _findNearest(p, crossPoints);
  282. innerPoints[2] = crossPoints[p3Index].scale2Size(restoreSize);
  283. innerPoints[3] = crossPoints[1 - p3Index].scale2Size(restoreSize);
  284. }
  285. static List<DPoint> _findCrossPoints(DPoint a, DPoint b, distance) {
  286. final dx = b.x - a.x;
  287. final dy = b.y - a.y;
  288. final len = (b - a).length;
  289. /// 单位长度轴上距离增加量
  290. final udx = dx / len;
  291. final udy = dy / len;
  292. final px = -udy;
  293. final py = udx;
  294. final centroidX = (a.x + b.x) * 0.5;
  295. final centroidY = (a.y + b.y) * 0.5;
  296. final centroid = DPoint(centroidX, centroidY);
  297. final x1 = centroid.x + px * distance;
  298. final y1 = centroid.y + py * distance;
  299. final out1 = DPoint(x1, y1);
  300. final x2 = centroid.x - px * distance;
  301. final y2 = centroid.y - py * distance;
  302. final out2 = DPoint(x2, y2);
  303. return [out1, out2];
  304. }
  305. static int _findNearest(DPoint p, List<DPoint> source) {
  306. int rst = 0;
  307. double minLen = (source[0] - p).length;
  308. for (var i = 1; i < source.length; i++) {
  309. final len = (source[i] - p).length;
  310. if (len < minLen) {
  311. minLen = len;
  312. rst = i;
  313. }
  314. }
  315. return rst;
  316. }
  317. }
  318. class _EllipseVolumeCal extends Calculator<Ellipse, double> {
  319. _EllipseVolumeCal(Ellipse ref) : super(ref);
  320. @override
  321. void calculate() {
  322. if (ref.feature == null) return;
  323. final feature = ref.feature! as EllipseFeature;
  324. final viewport = feature.hostVisualArea!.viewport!;
  325. final size = viewport.convertBoundary;
  326. double xAxis = feature.getRadiusX(size);
  327. double yAxis = feature.getRadiusY(size);
  328. var a = math.max(xAxis, yAxis);
  329. var b = math.min(xAxis, yAxis);
  330. var value = math.pi / 6.0 * a * b * b;
  331. updateFloatValue(value, unit: VidUsUnit.cm3, useRound: true);
  332. }
  333. }