spline.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. import 'dart:math';
  2. import 'dart:ui';
  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/items/terms.dart';
  8. import 'package:fis_measure/interfaces/process/items/types.dart';
  9. import 'package:fis_measure/interfaces/process/workspace/point_info.dart';
  10. import 'package:fis_measure/process/calcuators/curve.dart';
  11. import 'package:fis_measure/process/calcuators/calculator.dart';
  12. import 'package:fis_measure/process/items/item.dart';
  13. import 'package:fis_measure/process/items/item_feature.dart';
  14. import 'package:fis_measure/process/primitives/utils/auto_snap.dart';
  15. import 'package:fis_measure/utils/canvas.dart';
  16. import 'curve_abstract.dart';
  17. /// 曲线连线
  18. class Spline extends CurveAbstract with AutoSnapMixin {
  19. Spline(ItemMeta meta, [IMeasureItem? parent]) : super(meta, parent);
  20. static Spline createAreaPerimeter(ItemMeta meta, [IMeasureItem? parent]) {
  21. Spline spline = Spline(meta, parent);
  22. spline.calculator = _AreaPerimeterCalc(spline);
  23. return spline;
  24. }
  25. static Spline createCurveLength(
  26. ItemMeta meta, [
  27. IMeasureItem? parent,
  28. ]) {
  29. Spline spline = Spline(meta, parent);
  30. spline.calculator = CurveLengthCal(spline);
  31. return spline;
  32. }
  33. @override
  34. bool onExecuteMouse(PointInfo args) {
  35. if (state == ItemStates.finished) {
  36. if (args.pointType == PointInfoType.mouseDown) {
  37. state = ItemStates.waiting;
  38. }
  39. }
  40. if (state == ItemStates.waiting) {
  41. if (args.pointType == PointInfoType.mouseDown) {
  42. handleMouseDownWhileWaiting(args);
  43. }
  44. } else if (state == ItemStates.running) {
  45. if (feature == null) return false;
  46. if (args.pointType == PointInfoType.mouseUp) return false;
  47. final f = feature!;
  48. if (args.pointType == PointInfoType.mouseDown) {
  49. f.innerPoints.add(args);
  50. } else {
  51. f.innerPoints.last = args;
  52. }
  53. doCalculate();
  54. f.isClosed = checkAutoFinish(args);
  55. }
  56. return true;
  57. }
  58. @override
  59. bool onExecuteTouch(PointInfo args) {
  60. // TODO: implement onExecuteTouch
  61. throw UnimplementedError();
  62. }
  63. void handleMouseDownWhileWaiting(PointInfo args) {
  64. // TODO: 判断是否当前area
  65. final point = args.toAreaLogicPoint();
  66. feature = SplineFeature(this, point);
  67. if (args.hostVisualArea != null) {
  68. feature!.hostVisualArea = args.hostVisualArea;
  69. }
  70. state = ItemStates.running;
  71. }
  72. }
  73. class SplineFeature extends CurveAbstractFeature {
  74. static const double _splineTension = 0.5;
  75. static const double _splineTolerance = 0.05;
  76. SplineFeature(CurveAbstract refItem, DPoint point) : super(refItem) {
  77. innerPoints.add(point.clone());
  78. innerPoints.add(point.clone());
  79. splineTension = _splineTension;
  80. }
  81. @override
  82. void paint(Canvas canvas, Size size) {
  83. if (innerPoints.isEmpty) return;
  84. final paintPoints = innerPoints;
  85. if (isClosed) {
  86. paintPoints.removeLast();
  87. }
  88. drawId(canvas, size);
  89. final startOffset = convert2ViewPoint(size, startPoint).toOffset();
  90. if (innerPoints.length == 1) {
  91. drawVertex(canvas, startOffset, true);
  92. return;
  93. } else {
  94. drawVertex(canvas, startOffset);
  95. }
  96. /// 全部innerPoints点集转为Offset集绘制
  97. for (var e in innerPoints) {
  98. drawVertex(canvas, convert2ViewPoint(size, e).toOffset());
  99. }
  100. /// 获取拟合点集
  101. final fittedPoints = getFitPoints(
  102. innerPoints, isClosed, splineTension, _splineTolerance / 2);
  103. /// 全部拟合点集转为Offset集
  104. final fittedOffsets =
  105. fittedPoints.map((e) => convert2ViewPoint(size, e).toOffset()).toList();
  106. canvas.drawDashPointsLine(fittedOffsets, 1, 10, paintPan, close: true);
  107. }
  108. List<DPoint> getFitPoints(
  109. List<DPoint> points,
  110. bool isClosed,
  111. double tension,
  112. double tolerance,
  113. ) {
  114. final List<DPoint> polyLineSegment = [];
  115. final len = points.length;
  116. for (var i = 0; i < points.length; i++) {
  117. if (i == 0) {
  118. addSegment(
  119. polyLineSegment, points, [len - 1, 0, 1, 2], tension, tolerance);
  120. } else if (i == points.length - 2) {
  121. addSegment(
  122. polyLineSegment, points, [i - 1, i, i + 1, 0], tension, tolerance);
  123. } else if (i == points.length - 1) {
  124. addSegment(
  125. polyLineSegment, points, [i - 1, i, 0, 1], tension, tolerance);
  126. } else {
  127. addSegment(polyLineSegment, points, [i - 1, i, i + 1, i + 2], tension,
  128. tolerance);
  129. }
  130. }
  131. return polyLineSegment;
  132. }
  133. static void addSegment(List<DPoint> polyLineSegment, List<DPoint> points,
  134. List<int> pointIndex, double tension, double tolerance) {
  135. segment(
  136. polyLineSegment,
  137. points[pointIndex[0]],
  138. points[pointIndex[1]],
  139. points[pointIndex[2]],
  140. points[pointIndex[3]],
  141. tension,
  142. tension,
  143. tolerance);
  144. }
  145. static double segment(List<DPoint> points, DPoint pt0, DPoint pt1, DPoint pt2,
  146. DPoint pt3, double t1, double t2, double tolerance) {
  147. double length = 0;
  148. final sx1 = t1 * (pt2.x - pt0.x);
  149. final sy1 = t1 * (pt2.y - pt0.y);
  150. final sx2 = t2 * (pt3.x - pt1.x);
  151. final sy2 = t2 * (pt3.y - pt1.y);
  152. final ax = sx1 + sx2 + 2 * pt1.x - 2 * pt2.x;
  153. final ay = sy1 + sy2 + 2 * pt1.y - 2 * pt2.y;
  154. final bx = -2 * sx1 - sx2 - 3 * pt1.x + 3 * pt2.x;
  155. final by = -2 * sy1 - sy2 - 3 * pt1.y + 3 * pt2.y;
  156. final cx = sx1;
  157. final cy = sy1;
  158. final dx = pt1.x;
  159. final dy = pt1.y;
  160. var num = (((pt1.x - pt2.x).abs() + (pt1.y - pt2.y).abs()) ~/ tolerance);
  161. // 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
  162. if (num < 2) num = 2;
  163. // Notice begins at 1 so excludes the first point (which is just pt1)
  164. for (var i = 1; i < num; i++) {
  165. var t = i / (num - 1);
  166. var pt = DPoint(ax * t * t * t + bx * t * t + cx * t + dx,
  167. ay * t * t * t + by * t * t + cy * t + dy);
  168. if (i == 1) {
  169. length += (pt - pt1).length;
  170. } else {
  171. length += (pt - points[points.length - 1]).length;
  172. }
  173. points.add(pt);
  174. }
  175. return length;
  176. }
  177. }
  178. class _AreaPerimeterCalc extends Calculator<Spline, double> {
  179. _AreaPerimeterCalc(Spline ref) : super(ref);
  180. @override
  181. void calculate() {
  182. if (ref.feature == null) return;
  183. final feature = ref.feature!;
  184. final viewport = feature.hostVisualArea!.viewport!;
  185. final points = feature.innerPoints.map((e) => viewport.convert(e)).toList();
  186. final tension = feature.splineTension;
  187. feature.values.clear();
  188. double area;
  189. double perimeter;
  190. //计算周长
  191. final res = calcPerimeterAndArea(points, feature.isClosed, tension, 0.25);
  192. perimeter = res[0];
  193. area = res[1];
  194. for (var output in ref.meta.outputs) {
  195. if (output.name == MeasureTerms.Perimeter) {
  196. var value = roundDouble(perimeter, output.fractionalDigits);
  197. feature.updateFloatValue(output, value, output.unit);
  198. } else if (output.name == MeasureTerms.Area) {
  199. var value = roundDouble(area, output.fractionalDigits);
  200. feature.updateFloatValue(output, value, output.unit);
  201. }
  202. }
  203. }
  204. static double calcArea(List<DPoint> points) {
  205. if (points.isEmpty) {
  206. return 0;
  207. }
  208. double sum = 0;
  209. var ax = points[0].x;
  210. var ay = points[0].y;
  211. for (var i = 1; i < points.length - 1; i++) {
  212. var bx = points[i].x;
  213. var by = points[i].y;
  214. var cx = points[i + 1].x;
  215. var cy = points[i + 1].y;
  216. sum += ax * by - ay * bx + ay * cx - ax * cy + bx * cy - cx * by;
  217. }
  218. return (-sum / 2).abs();
  219. }
  220. static List<double> calcPerimeterAndArea(
  221. List<DPoint> points,
  222. bool isClosed,
  223. double tension,
  224. double tolerance,
  225. ) {
  226. final List<DPoint> polyLineSegment = [];
  227. final len = points.length;
  228. double perimeter = 0;
  229. if (len < 2) {
  230. return [0, 0];
  231. }
  232. if (len == 2) {
  233. final p1 = points[0];
  234. final p2 = points[1];
  235. final dx = p1.x - p2.x;
  236. final dy = p1.y - p2.y;
  237. perimeter = sqrt(dx * dx + dy * dy);
  238. return [perimeter, 0];
  239. }
  240. for (var i = 0; i < points.length; i++) {
  241. if (i == 0) {
  242. perimeter += segment(
  243. polyLineSegment,
  244. isClosed ? points[points.length - 1] : points[0],
  245. points[0],
  246. points[1],
  247. points[2],
  248. tension,
  249. tension,
  250. tolerance);
  251. } else if (i == points.length - 2) {
  252. perimeter += segment(
  253. polyLineSegment,
  254. points[i - 1],
  255. points[i],
  256. points[i + 1],
  257. isClosed ? points[0] : points[i + 1],
  258. tension,
  259. tension,
  260. tolerance);
  261. } else if (i == points.length - 1) {
  262. if (isClosed) {
  263. perimeter += segment(polyLineSegment, points[i - 1], points[i],
  264. points[0], points[1], tension, tension, tolerance);
  265. }
  266. } else {
  267. perimeter += segment(polyLineSegment, points[i - 1], points[i],
  268. points[i + 1], points[i + 2], tension, tension, tolerance);
  269. }
  270. }
  271. print("面积计算拟合点数 ${polyLineSegment.length}");
  272. final area = calcArea(polyLineSegment);
  273. return [perimeter, area];
  274. }
  275. static double segment(List<DPoint> points, DPoint pt0, DPoint pt1, DPoint pt2,
  276. DPoint pt3, double t1, double t2, double tolerance) {
  277. double length = 0;
  278. final sx1 = t1 * (pt2.x - pt0.x);
  279. final sy1 = t1 * (pt2.y - pt0.y);
  280. final sx2 = t2 * (pt3.x - pt1.x);
  281. final sy2 = t2 * (pt3.y - pt1.y);
  282. final ax = sx1 + sx2 + 2 * pt1.x - 2 * pt2.x;
  283. final ay = sy1 + sy2 + 2 * pt1.y - 2 * pt2.y;
  284. final bx = -2 * sx1 - sx2 - 3 * pt1.x + 3 * pt2.x;
  285. final by = -2 * sy1 - sy2 - 3 * pt1.y + 3 * pt2.y;
  286. final cx = sx1;
  287. final cy = sy1;
  288. final dx = pt1.x;
  289. final dy = pt1.y;
  290. var num = (((pt1.x - pt2.x).abs() + (pt1.y - pt2.y).abs()) ~/ tolerance);
  291. // 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
  292. if (num < 2) num = 2;
  293. // Notice begins at 1 so excludes the first point (which is just pt1)
  294. for (var i = 1; i < num; i++) {
  295. var t = i / (num - 1);
  296. var pt = DPoint(ax * t * t * t + bx * t * t + cx * t + dx,
  297. ay * t * t * t + by * t * t + cy * t + dy);
  298. if (i == 1) {
  299. length += (pt - pt1).length;
  300. } else {
  301. length += (pt - points[points.length - 1]).length;
  302. }
  303. points.add(pt);
  304. }
  305. return length;
  306. }
  307. }