🛰️航天仿真算法库 SpaceAST 0.0.1
载入中...
搜索中...
未找到
path_converters.h
1// 从 matplotlib src/path_converters.h 复制,去除 Python/pybind11 依赖
2// 原始版权: matplotlib project
3#pragma once
4
5#include <cmath>
6#include <cstdint>
7#include <limits>
8
9#if defined(AST_WITH_AGG)
10
11#include "agg/agg_clip_liang_barsky.h"
12#include "agg/agg_conv_segmentator.h"
13
14#include "mpl_utils.h"
15
16/************************************************************
17 * EmbeddedQueue — 顶点队列基类,处理一个输入顶点产生多个输出
18 ************************************************************/
19template <int QueueSize>
20class EmbeddedQueue {
21protected:
22 EmbeddedQueue() : m_queue_read(0), m_queue_write(0) {}
23
24 struct item {
25 item() = default;
26 inline void set(const unsigned cmd_, const double x_, const double y_) {
27 cmd = cmd_; x = x_; y = y_;
28 }
29 unsigned cmd;
30 double x, y;
31 };
32 int m_queue_read, m_queue_write;
33 item m_queue[QueueSize];
34
35 inline void queue_push(const unsigned cmd, const double x, const double y) {
36 m_queue[m_queue_write++].set(cmd, x, y);
37 }
38 inline bool queue_nonempty() { return m_queue_read < m_queue_write; }
39 inline bool queue_pop(unsigned *cmd, double *x, double *y) {
40 if (queue_nonempty()) {
41 const item &front = m_queue[m_queue_read++];
42 *cmd = front.cmd; *x = front.x; *y = front.y;
43 return true;
44 }
45 m_queue_read = m_queue_write = 0;
46 return false;
47 }
48 inline void queue_clear() { m_queue_read = m_queue_write = 0; }
49};
50
51/* 每种路径段类型的额外顶点数 */
52static const size_t num_extra_points_map[] = {0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
53
54/************************************************************
55 * RandomNumberGenerator — 简单 LCG 随机数 (用于 Sketch)
56 ************************************************************/
57class RandomNumberGenerator {
58 static const uint32_t a = 214013, c = 2531011;
59 uint32_t m_seed;
60public:
61 RandomNumberGenerator() : m_seed(0) {}
62 explicit RandomNumberGenerator(int seed) : m_seed(seed) {}
63 void seed(int seed) { m_seed = seed; }
64 double get_double() {
65 m_seed = a * m_seed + c;
66 return (double)m_seed / (double)(1LL << 32);
67 }
68};
69
70/************************************************************
71 * PathNanRemover — 跳过含 NaN/Inf 的线段
72 ************************************************************/
73template <class VertexSource>
74class PathNanRemover : protected EmbeddedQueue<4> {
75 VertexSource *m_source;
76 bool m_remove_nans, m_has_codes;
77 bool valid_segment_exists, m_last_segment_valid, m_was_broken;
78 double m_initX, m_initY;
79
80public:
81 PathNanRemover(VertexSource &source, bool remove_nans, bool has_codes)
82 : m_source(&source), m_remove_nans(remove_nans), m_has_codes(has_codes),
83 m_last_segment_valid(false), m_was_broken(false) {
84 m_initX = std::nan(""); m_initY = std::nan("");
85 valid_segment_exists = false;
86 }
87
88 inline void rewind(unsigned path_id) {
89 queue_clear();
90 m_source->rewind(path_id);
91 }
92
93 inline unsigned vertex(double *x, double *y) {
94 unsigned code;
95 if (!m_remove_nans) return m_source->vertex(x, y);
96
97 if (m_has_codes) {
98 if (queue_pop(&code, x, y)) return code;
99
100 bool needs_move_to = false;
101 while (true) {
102 code = m_source->vertex(x, y);
103 if (code == agg::path_cmd_stop) return code;
104 if (code == (agg::path_cmd_end_poly | agg::path_flags_close) && valid_segment_exists) {
105 if (m_was_broken) {
106 if (m_last_segment_valid && std::isfinite(m_initX) && std::isfinite(m_initY)) {
107 queue_push(agg::path_cmd_line_to, m_initX, m_initY);
108 break;
109 } else { continue; }
110 m_was_broken = false; break;
111 } else { return code; }
112 } else if (code == agg::path_cmd_move_to) {
113 m_initX = *x; m_initY = *y; m_was_broken = false;
114 }
115
116 if (needs_move_to) queue_push(agg::path_cmd_move_to, *x, *y);
117
118 size_t num_extra = num_extra_points_map[code & 0xF];
119 m_last_segment_valid = std::isfinite(*x) && std::isfinite(*y);
120 queue_push(code, *x, *y);
121 for (size_t i = 0; i < num_extra; ++i) {
122 m_source->vertex(x, y);
123 m_last_segment_valid = m_last_segment_valid && std::isfinite(*x) && std::isfinite(*y);
124 queue_push(code, *x, *y);
125 }
126 if (m_last_segment_valid) { valid_segment_exists = true; break; }
127
128 m_was_broken = true; queue_clear();
129 if (std::isfinite(*x) && std::isfinite(*y)) {
130 queue_push(agg::path_cmd_move_to, *x, *y); needs_move_to = false;
131 } else { needs_move_to = true; }
132 }
133 if (queue_pop(&code, x, y)) return code;
134 return agg::path_cmd_stop;
135 } else {
136 code = m_source->vertex(x, y);
137 if (code == agg::path_cmd_stop ||
138 (code == (agg::path_cmd_end_poly | agg::path_flags_close) && valid_segment_exists))
139 return code;
140 if (!(std::isfinite(*x) && std::isfinite(*y))) {
141 do {
142 code = m_source->vertex(x, y);
143 if (code == agg::path_cmd_stop ||
144 (code == (agg::path_cmd_end_poly | agg::path_flags_close) && valid_segment_exists))
145 return code;
146 } while (!(std::isfinite(*x) && std::isfinite(*y)));
147 return agg::path_cmd_move_to;
148 }
149 valid_segment_exists = true;
150 return code;
151 }
152 }
153};
154
155/************************************************************
156 * PathClipper — Liang-Barsky 矩形裁剪
157 ************************************************************/
158template <class VertexSource>
159class PathClipper : public EmbeddedQueue<3> {
160 VertexSource *m_source;
161 bool m_do_clipping;
162 agg::rect_base<double> m_cliprect;
163 double m_lastX, m_lastY, m_initX, m_initY;
164 bool m_moveto, m_has_init, m_was_clipped;
165
166public:
167 PathClipper(VertexSource &source, bool do_clipping, double width, double height)
168 : m_source(&source), m_do_clipping(do_clipping),
169 m_cliprect(-1.0, -1.0, width + 1.0, height + 1.0),
170 m_moveto(true), m_has_init(false), m_was_clipped(false) {
171 m_lastX = std::nan(""); m_lastY = std::nan("");
172 m_initX = std::nan(""); m_initY = std::nan("");
173 }
174
175 PathClipper(VertexSource &source, bool do_clipping, const agg::rect_base<double> &rect)
176 : m_source(&source), m_do_clipping(do_clipping), m_cliprect(rect),
177 m_moveto(true), m_has_init(false), m_was_clipped(false) {
178 m_lastX = std::nan(""); m_lastY = std::nan("");
179 m_initX = std::nan(""); m_initY = std::nan("");
180 m_cliprect.x1 -= 1.0; m_cliprect.y1 -= 1.0; m_cliprect.x2 += 1.0; m_cliprect.y2 += 1.0;
181 }
182
183 inline void rewind(unsigned path_id) {
184 m_has_init = m_was_clipped = false; m_moveto = true;
185 m_source->rewind(path_id);
186 }
187
188 int draw_clipped_line(double x0, double y0, double x1, double y1, bool closed = false) {
189 unsigned moved = agg::clip_line_segment(&x0, &y0, &x1, &y1, m_cliprect);
190 m_was_clipped = m_was_clipped || (moved != 0);
191 if (moved < 4) {
192 if (moved & 1 || m_moveto) queue_push(agg::path_cmd_move_to, x0, y0);
193 queue_push(agg::path_cmd_line_to, x1, y1);
194 if (closed && !m_was_clipped)
195 queue_push(agg::path_cmd_end_poly | agg::path_flags_close, x1, y1);
196 m_moveto = false;
197 return 1;
198 }
199 return 0;
200 }
201
202 unsigned vertex(double *x, double *y) {
203 unsigned code; bool emit_moveto = false;
204 if (!m_do_clipping) return m_source->vertex(x, y);
205 if (queue_pop(&code, x, y)) return code;
206
207 while ((code = m_source->vertex(x, y)) != agg::path_cmd_stop) {
208 emit_moveto = false;
209 switch (code) {
210 case (agg::path_cmd_end_poly | agg::path_flags_close):
211 if (m_has_init) draw_clipped_line(m_lastX, m_lastY, m_initX, m_initY, true);
212 else queue_push(agg::path_cmd_end_poly | agg::path_flags_close, m_lastX, m_lastY);
213 if (queue_nonempty()) goto exit_loop;
214 break;
215 case agg::path_cmd_move_to:
216 if (m_moveto && m_has_init && m_lastX >= m_cliprect.x1 && m_lastX <= m_cliprect.x2
217 && m_lastY >= m_cliprect.y1 && m_lastY <= m_cliprect.y2) {
218 queue_push(agg::path_cmd_move_to, m_lastX, m_lastY); emit_moveto = true;
219 }
220 m_initX = m_lastX = *x; m_initY = m_lastY = *y;
221 m_has_init = true; m_moveto = true; m_was_clipped = false;
222 if (emit_moveto) goto exit_loop;
223 break;
224 case agg::path_cmd_line_to:
225 if (draw_clipped_line(m_lastX, m_lastY, *x, *y)) { m_lastX = *x; m_lastY = *y; goto exit_loop; }
226 m_lastX = *x; m_lastY = *y;
227 break;
228 default:
229 if (m_moveto) { queue_push(agg::path_cmd_move_to, m_lastX, m_lastY); m_moveto = false; }
230 queue_push(code, *x, *y); m_lastX = *x; m_lastY = *y;
231 goto exit_loop;
232 }
233 }
234 exit_loop:
235 if (queue_pop(&code, x, y)) return code;
236 if (m_moveto && m_has_init && m_lastX >= m_cliprect.x1 && m_lastX <= m_cliprect.x2
237 && m_lastY >= m_cliprect.y1 && m_lastY <= m_cliprect.y2) {
238 *x = m_lastX; *y = m_lastY; m_moveto = false;
239 return agg::path_cmd_move_to;
240 }
241 return agg::path_cmd_stop;
242 }
243};
244
245/************************************************************
246 * e_snap_mode — 像素对齐模式
247 ************************************************************/
248enum e_snap_mode { SNAP_AUTO, SNAP_FALSE, SNAP_TRUE };
249
250/************************************************************
251 * PathSnapper — 顶点像素对齐
252 ************************************************************/
253template <class VertexSource>
254class PathSnapper {
255 VertexSource *m_source;
256 bool m_snap;
257 double m_snap_value;
258
259public:
260 static bool should_snap(VertexSource &path, e_snap_mode snap_mode, unsigned total_vertices) {
261 double x0 = 0, y0 = 0, x1 = 0, y1 = 0; unsigned code;
262 switch (snap_mode) {
263 case SNAP_AUTO:
264 if (total_vertices > 1024) return false;
265 code = path.vertex(&x0, &y0);
266 if (code == agg::path_cmd_stop) return false;
267 while ((code = path.vertex(&x1, &y1)) != agg::path_cmd_stop) {
268 switch (code) {
269 case agg::path_cmd_curve3:
270 case agg::path_cmd_curve4: return false;
271 case agg::path_cmd_line_to:
272 if (fabs(x0 - x1) >= 1e-4 && fabs(y0 - y1) >= 1e-4) return false;
273 }
274 x0 = x1; y0 = y1;
275 }
276 return true;
277 case SNAP_FALSE: return false;
278 case SNAP_TRUE: return true;
279 }
280 return false;
281 }
282
283 PathSnapper(VertexSource &source, e_snap_mode snap_mode, unsigned total_vertices = 15,
284 double stroke_width = 0.0) : m_source(&source) {
285 m_snap = should_snap(source, snap_mode, total_vertices);
286 if (m_snap) {
287 int is_odd = mpl_round_to_int(stroke_width) % 2;
288 m_snap_value = (is_odd) ? 0.5 : 0.0;
289 }
290 source.rewind(0);
291 }
292
293 inline void rewind(unsigned path_id) { m_source->rewind(path_id); }
294 inline unsigned vertex(double *x, double *y) {
295 unsigned code = m_source->vertex(x, y);
296 if (m_snap && agg::is_vertex(code)) {
297 *x = floor(*x + 0.5) + m_snap_value;
298 *y = floor(*y + 0.5) + m_snap_value;
299 }
300 return code;
301 }
302 inline bool is_snapping() { return m_snap; }
303};
304
305/************************************************************
306 * PathSimplifier — 在线路径简化 (Douglas-Peucker 变体)
307 ************************************************************/
308template <class VertexSource>
309class PathSimplifier : protected EmbeddedQueue<9> {
310public:
311 PathSimplifier(VertexSource &source, bool do_simplify, double simplify_threshold)
312 : m_source(&source), m_simplify(do_simplify),
313 m_moveto(true), m_after_moveto(false), m_clipped(false),
314 m_has_init(false), m_simplify_threshold(simplify_threshold * simplify_threshold),
315 m_initX(0), m_initY(0),
316 m_lastx(0), m_lasty(0), m_origdx(0), m_origdy(0), m_origdNorm2(0),
317 m_dnorm2ForwardMax(0), m_dnorm2BackwardMax(0),
318 m_nextX(0), m_nextY(0), m_nextBackwardX(0), m_nextBackwardY(0),
319 m_currVecStartX(0), m_currVecStartY(0),
320 m_lastForwardMax(false), m_lastBackwardMax(false) {}
321
322 inline void rewind(unsigned path_id) { queue_clear(); m_moveto = true; m_source->rewind(path_id); }
323
324 unsigned vertex(double *x, double *y) {
325 unsigned cmd;
326 if (!m_simplify) return m_source->vertex(x, y);
327 if (queue_pop(&cmd, x, y)) return cmd;
328
329 while ((cmd = m_source->vertex(x, y)) != agg::path_cmd_stop) {
330 if (m_moveto || cmd == agg::path_cmd_move_to) {
331 if (m_origdNorm2 != 0.0 && !m_after_moveto) _push(x, y);
332 m_after_moveto = true;
333 m_has_init = std::isfinite(*x) && std::isfinite(*y);
334 if (m_has_init) { m_initX = *x; m_initY = *y; }
335 m_lastx = *x; m_lasty = *y;
336 m_moveto = false; m_origdNorm2 = 0.0; m_dnorm2BackwardMax = 0.0; m_clipped = true;
337 if (queue_nonempty()) break;
338 continue;
339 }
340 m_after_moveto = false;
341
342 if (agg::is_close(cmd)) {
343 if (m_has_init) { *x = m_initX; *y = m_initY; } else continue;
344 }
345
346 if (m_origdNorm2 == 0.0) {
347 if (m_clipped) { queue_push(agg::path_cmd_move_to, m_lastx, m_lasty); m_clipped = false; }
348 m_origdx = *x - m_lastx; m_origdy = *y - m_lasty;
349 m_origdNorm2 = m_origdx * m_origdx + m_origdy * m_origdy;
350 m_dnorm2ForwardMax = m_origdNorm2; m_dnorm2BackwardMax = 0.0;
351 m_lastForwardMax = true; m_lastBackwardMax = false;
352 m_currVecStartX = m_lastx; m_currVecStartY = m_lasty;
353 m_nextX = m_lastx = *x; m_nextY = m_lasty = *y;
354 continue;
355 }
356
357 double totdx = *x - m_currVecStartX, totdy = *y - m_currVecStartY;
358 double totdot = m_origdx * totdx + m_origdy * totdy;
359 double paradx = totdot * m_origdx / m_origdNorm2, parady = totdot * m_origdy / m_origdNorm2;
360 double perpdx = totdx - paradx, perpdy = totdy - parady;
361 double perpdNorm2 = perpdx * perpdx + perpdy * perpdy;
362
363 if (perpdNorm2 < m_simplify_threshold) {
364 double paradNorm2 = paradx * paradx + parady * parady;
365 m_lastForwardMax = m_lastBackwardMax = false;
366 if (totdot > 0.0) {
367 if (paradNorm2 > m_dnorm2ForwardMax) { m_lastForwardMax = true; m_dnorm2ForwardMax = paradNorm2; m_nextX = *x; m_nextY = *y; }
368 } else {
369 if (paradNorm2 > m_dnorm2BackwardMax) { m_lastBackwardMax = true; m_dnorm2BackwardMax = paradNorm2; m_nextBackwardX = *x; m_nextBackwardY = *y; }
370 }
371 m_lastx = *x; m_lasty = *y;
372 continue;
373 }
374 _push(x, y);
375 break;
376 }
377
378 if (cmd == agg::path_cmd_stop) {
379 if (m_origdNorm2 != 0.0) {
380 queue_push((m_moveto || m_after_moveto) ? agg::path_cmd_move_to : agg::path_cmd_line_to, m_nextX, m_nextY);
381 if (m_dnorm2BackwardMax > 0.0)
382 queue_push((m_moveto || m_after_moveto) ? agg::path_cmd_move_to : agg::path_cmd_line_to, m_nextBackwardX, m_nextBackwardY);
383 m_moveto = false;
384 }
385 queue_push((m_moveto || m_after_moveto) ? agg::path_cmd_move_to : agg::path_cmd_line_to, m_lastx, m_lasty);
386 m_moveto = false;
387 queue_push(agg::path_cmd_stop, 0.0, 0.0);
388 }
389
390 if (queue_pop(&cmd, x, y)) return cmd;
391 return agg::path_cmd_stop;
392 }
393
394private:
395 VertexSource *m_source;
396 bool m_simplify, m_moveto, m_after_moveto, m_clipped, m_has_init;
397 double m_simplify_threshold;
398 double m_initX, m_initY, m_lastx, m_lasty, m_origdx, m_origdy, m_origdNorm2;
399 double m_dnorm2ForwardMax, m_dnorm2BackwardMax, m_nextX, m_nextY;
400 double m_nextBackwardX, m_nextBackwardY, m_currVecStartX, m_currVecStartY;
401 bool m_lastForwardMax, m_lastBackwardMax;
402
403 inline void _push(double *x, double *y) {
404 bool needToPushBack = (m_dnorm2BackwardMax > 0.0);
405 if (needToPushBack) {
406 if (m_lastForwardMax) { queue_push(agg::path_cmd_line_to, m_nextBackwardX, m_nextBackwardY); queue_push(agg::path_cmd_line_to, m_nextX, m_nextY); }
407 else { queue_push(agg::path_cmd_line_to, m_nextX, m_nextY); queue_push(agg::path_cmd_line_to, m_nextBackwardX, m_nextBackwardY); }
408 } else { queue_push(agg::path_cmd_line_to, m_nextX, m_nextY); }
409 if (m_clipped) queue_push(agg::path_cmd_move_to, m_lastx, m_lasty);
410 else if (!m_lastForwardMax && !m_lastBackwardMax) queue_push(agg::path_cmd_line_to, m_lastx, m_lasty);
411 m_origdx = *x - m_lastx; m_origdy = *y - m_lasty;
412 m_origdNorm2 = m_origdx * m_origdx + m_origdy * m_origdy;
413 m_dnorm2ForwardMax = m_origdNorm2; m_lastForwardMax = true;
414 m_currVecStartX = m_queue[m_queue_write - 1].x;
415 m_currVecStartY = m_queue[m_queue_write - 1].y;
416 m_lastx = m_nextX = *x; m_lasty = m_nextY = *y;
417 m_dnorm2BackwardMax = 0.0; m_lastBackwardMax = false;
418 m_clipped = false;
419 }
420};
421
422/************************************************************
423 * Sketch — 手绘风格路径扰动
424 ************************************************************/
425template <class VertexSource>
426class Sketch {
427public:
428 Sketch(VertexSource &source, double scale, double length, double randomness)
429 : m_source(&source), m_scale(scale), m_length(length), m_randomness(randomness),
430 m_segmented(source), m_last_x(0), m_last_y(0), m_p(0), m_has_last(false), m_rand(0)
431 {
432 rewind(0);
433 const double d_M_PI = 3.14159265358979323846;
434 if (m_length <= std::numeric_limits<double>::epsilon() || m_randomness <= std::numeric_limits<double>::epsilon()) {
435 m_p_scale = 0.0;
436 } else {
437 m_p_scale = (2.0 * d_M_PI) / (m_length * m_randomness);
438 }
439 m_log_randomness = (m_randomness <= std::numeric_limits<double>::epsilon()) ? 0.0 : 2.0 * log(m_randomness);
440 }
441
442 unsigned vertex(double *x, double *y) {
443 if (m_scale == 0.0) return m_source->vertex(x, y);
444 unsigned code = m_segmented.vertex(x, y);
445 if (code == agg::path_cmd_move_to) { m_has_last = false; m_p = 0.0; }
446 if (m_has_last) {
447 double d_rand = m_rand.get_double();
448 m_p += exp(d_rand * m_log_randomness);
449 double den = m_last_x - *x, num = m_last_y - *y;
450 double len = num * num + den * den;
451 m_last_x = *x; m_last_y = *y;
452 if (len != 0) {
453 len = sqrt(len);
454 double r = sin(m_p * m_p_scale) * m_scale;
455 double roverlen = r / len;
456 *x += roverlen * num;
457 *y -= roverlen * den;
458 }
459 } else { m_last_x = *x; m_last_y = *y; }
460 m_has_last = true;
461 return code;
462 }
463
464 inline void rewind(unsigned path_id) {
465 m_has_last = false; m_p = 0.0;
466 if (m_scale != 0.0) { m_rand.seed(0); m_segmented.rewind(path_id); }
467 else m_source->rewind(path_id);
468 }
469
470private:
471 VertexSource *m_source;
472 double m_scale, m_length, m_randomness;
473 agg::conv_segmentator<VertexSource> m_segmented;
474 double m_last_x, m_last_y, m_p, m_p_scale, m_log_randomness;
475 bool m_has_last;
476 RandomNumberGenerator m_rand;
477};
478
479
480#endif // AST_WITH_AGG