omdl  v1.0
OpenSCAD Mechanical Design Library
polygon.scad
Go to the documentation of this file.
1 //! Polygon shapes, conversions, properties, and tests functions.
2 /***************************************************************************//**
3  \file
4  \author Roy Allen Sutton
5  \date 2015-2024,2026
6 
7  \copyright
8 
9  This file is part of [omdl] (https://github.com/royasutton/omdl),
10  an OpenSCAD mechanical design library.
11 
12  The \em omdl is free software; you can redistribute it and/or modify
13  it under the terms of the [GNU Lesser General Public License]
14  (http://www.gnu.org/licenses/lgpl.html) as published by the Free
15  Software Foundation; either version 2.1 of the License, or (at
16  your option) any later version.
17 
18  The \em omdl is distributed in the hope that it will be useful,
19  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21  Lesser General Public License for more details.
22 
23  You should have received a copy of the GNU Lesser General Public
24  License along with the \em omdl; if not, write to the Free Software
25  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26  02110-1301, USA; or see <http://www.gnu.org/licenses/>.
27 
28  \details
29 
30  \amu_define group_name (Polygons)
31  \amu_define group_brief (Polygon mathematical functions; 2-polytope.)
32 
33  \amu_include (include/amu/doxyg_init_pd_gds_ipg.amu)
34 *******************************************************************************/
35 
36 // auto-tests (add to test results page)
37 /***************************************************************************//**
38  \amu_include (include/amu/validate_log.amu)
39  \amu_include (include/amu/validate_results.amu)
40 *******************************************************************************/
41 
42 // group(s) begin (test summary and includes-required)
43 /***************************************************************************//**
44  \amu_include (include/amu/doxyg_define_in_parent_open.amu)
45  \amu_include (include/amu/validate_summary.amu)
46  \amu_include (include/amu/includes_required.amu)
47 *******************************************************************************/
48 
49 // member-wide reference definitions
50 /***************************************************************************//**
51  \amu_define note_p_not_defined
52  (
53  \note When \p p is not defined, the listed order of the coordinates
54  will be used.
55  )
56 
57  \amu_define warning_secondary_shapes
58  (
59  \warning This function does not track secondary shapes subtraction
60  as implemented by the polygon() function.
61  )
62 *******************************************************************************/
63 
64 // member-wide documentation and conventions
65 /***************************************************************************//**
66  \addtogroup \amu_eval(${group})
67  \details
68  \anchor \amu_eval(${group})_conventions
69  \par Conventions
70 
71  All functions in this group operate on polygons defined by a list
72  of 2d cartesian coordinates \p c and an optional list of paths \p p,
73  following OpenSCAD's native \c polygon() convention:
74 
75  - Coordinates are given as \c [[x,y], ...].
76  - When \p p is not supplied, the listed order of the coordinates is
77  used as the single implicit path; see the \c note_p_not_defined
78  note attached to individual functions.
79  - The primary (outer boundary) path is ordered \b counter-clockwise
80  when viewed from above in the standard 2d coordinate system (y-up).
81  Secondary (hole) paths must use the \b opposite winding to the
82  primary path, matching the convention of OpenSCAD's \c polygon()
83  and \c linear_extrude().
84  - Shape-generation functions (polygon_regular_p(),
85  polygon_trapezoid_p(), polygon_arc_sweep_p(),
86  polygon_arc_fillet_p(), polygon_arc_blend_p(),
87  polygon_elliptical_sector_p(), etc.) and curve-generation
88  functions (polygon_bezier_p(), polygon_spline_p())
89  default to \p cw = \b true and therefore produce \b clockwise
90  output. Callers that pass generated coordinates directly to \c
91  polygon() or to property/test functions that assume CCW winding
92  must either pass \p cw = \b false to the generator, or reverse
93  the coordinate list after the fact.
94  - Functions in this library that require a specific winding — such as
95  polygon_linear_extrude_pf() — call polygon_is_clockwise() and
96  normalise the winding internally; callers are \em not required to
97  pre-normalise their input.
98 *******************************************************************************/
99 
100 //----------------------------------------------------------------------------//
101 // members
102 //----------------------------------------------------------------------------//
103 
104 //----------------------------------------------------------------------------//
105 // helper functions
106 //----------------------------------------------------------------------------//
107 
108 //! Test whether vertex \p i of a polygon path forms a valid ear.
109 /***************************************************************************//**
110  \param c <points-2d> A list of 2d cartesian coordinates [[x, y], ...].
111 
112  \param path <integer-list> An ordered list of coordinate indexes
113  defining the current (possibly reduced) polygon path.
114 
115  \param i <integer> The index into \p path of the candidate ear tip.
116 
117  \returns <boolean> \b true if vertex \p path[\p i] is a valid ear tip,
118  \b false otherwise.
119 
120  \details
121 
122  A vertex is a valid ear when two conditions both hold: the triangle
123  formed by the previous, current, and next vertices is wound
124  counter-clockwise (i.e. the tip is a convex vertex), and no other
125  vertex remaining in \p path lies strictly inside that triangle.
126 
127  Assumes the path is wound counter-clockwise. If the input was
128  clockwise it must be reversed before calling this function. The
129  point-in-triangle test uses polygon_wn_is_p_inside() on the
130  three-vertex triangle. Uses any_equal() to test whether any
131  interior-point test returned \b true.
132 
133  \private
134 *******************************************************************************/
135 function _polygon_is_ear
136 (
137  c,
138  path,
139  i
140 ) =
141  let
142  (
143  n = len(path),
144  ip = (i == 0) ? n-1 : i-1,
145  in_ = (i == n-1) ? 0 : i+1,
146 
147  vp = c[path[ip]],
148  vc = c[path[i]],
149  vn = c[path[in_]],
150 
151  // ear tip must be a convex vertex in CCW winding: vc is left of vp to vn
152  convex = (is_left_ppp(vp, vn, vc) > 0),
153 
154  // triangle as a 3-point coordinate list for inclusion test
155  tri = [vp, vc, vn],
156 
157  // no other path vertex may lie strictly inside the ear triangle
158  no_interior =
159  convex &&
160  !any_equal
161  (
162  [for (j = [0 : n-1])
163  if (j != ip && j != i && j != in_)
164  polygon_wn_is_p_inside(c=tri, t=c[path[j]])
165  ],
166  true
167  )
168  )
169  convex && no_interior;
170 
171 //! Recursively triangulate a simple polygon path by ear clipping.
172 /***************************************************************************//**
173  \param c <points-2d> A list of 2d cartesian coordinates
174  [[x, y], ...].
175 
176  \param path <integer-list> An ordered list of coordinate indexes
177  defining the current (possibly reduced) polygon path.
178  Must be wound counter-clockwise.
179 
180  \param tris <integer-list-list> Accumulated triangle list; pass
181  \b empty_lst on the initial call. Defaults to
182  \b empty_lst.
183 
184  \returns <integer-list-list> A list of triangles [[i0, i1, i2], ...]
185  where each entry is a CCW-wound triple of coordinate indexes
186  into \p c.
187 
188  \details
189 
190  Implements the ear-clipping triangulation algorithm. At each step
191  the first valid ear found in \p path is clipped: its triangle is
192  appended to \p tris and its tip vertex is removed from \p path.
193  Recursion continues until \p path is reduced to 3 vertices, at
194  which point the final triangle is appended and the accumulated list
195  is returned.
196 
197  Assumes \p path is wound counter-clockwise. The caller is
198  responsible for normalizing winding before the initial call (see
199  _polygon_cap_triangles()).
200 
201  In the degenerate case where no ear is found in a pass over all
202  remaining vertices (which can occur for self-intersecting or
203  otherwise malformed paths), a \b echo warning is emitted and the
204  accumulated triangles so far are returned. This prevents infinite
205  recursion at the cost of an incomplete triangulation.
206 
207  \private
208 *******************************************************************************/
209 function _polygon_clip_ears
210 (
211  c,
212  path,
213  tris = empty_lst
214 ) =
215  let ( n = len(path) )
216  (n == 3)
217  ? concat(tris, [[path[0], path[1], path[2]]])
218  : let
219  (
220  ear_i = first
221  (
222  [for (i = [0 : n-1]) if (_polygon_is_ear(c, path, i)) i]
223  )
224  )
225  is_undef(ear_i)
226  ? let
227  (
228  _ = echo("WARNING: _polygon_clip_ears: no ear found — path may be self-intersecting or degenerate. Triangulation is incomplete.")
229  )
230  tris
231  : let
232  (
233  ip = (ear_i == 0) ? n-1 : ear_i-1,
234  in_ = (ear_i == n-1) ? 0 : ear_i+1,
235 
236  tri = [path[ip], path[ear_i], path[in_]],
237  new_path = [for (j = [0 : n-1]) if (j != ear_i) path[j]]
238  )
239  _polygon_clip_ears(c, new_path, concat(tris, [tri]));
240 
241 //! Triangulate one cap face of a polygon path for use with polyhedron().
242 /***************************************************************************//**
243  \param c <points-2d> A list of 2d cartesian coordinates [[x, y], ...].
244 
245  \param path <integer-list> An ordered list of coordinate indexes
246  defining the polygon path to triangulate.
247 
248  \param cw <boolean> \b true if \p path is wound clockwise,
249  \b false if counter-clockwise.
250 
251  \param flip <boolean> When \b true each output triangle's vertex
252  order is reversed. Used to produce the top cap with
253  outward-pointing normals. Default \b false.
254 
255  \param offset <integer> Added to every index in the output triangles.
256  Used to shift top cap indices into the upper z-layer.
257  Default \b 0.
258 
259  \returns <integer-list-list> A list of triangles [[i0, i1, i2], ...]
260  with \p offset applied to all indices, and winding reversed
261  when \p flip is \b true. Returns \b empty_lst when
262  triangulation fails (see _polygon_clip_ears()).
263 
264  \details
265 
266  Normalizes the path to CCW winding before ear clipping so that
267  _polygon_clip_ears() and _polygon_is_ear() can assume a fixed
268  winding. If the original path was CW the path is reversed prior to
269  clipping. After clipping, each triangle's index order is reversed
270  if \p flip is \b true, and \p offset is added to all indices to map
271  into the correct z-layer of the polyhedron point list.
272 
273  \private
274 *******************************************************************************/
275 function _polygon_cap_triangles
276 (
277  c,
278  path,
279  cw,
280  flip = false,
281  offset = 0
282 ) =
283  let
284  (
285  norm_path = (cw == true) ? reverse(path) : path,
286 
287  raw_tris = _polygon_clip_ears(c, norm_path),
288 
289  out_tris =
290  [
291  for (tri = raw_tris)
292  let (t = flip ? [tri[2], tri[1], tri[0]] : tri)
293  [t[0] + offset, t[1] + offset, t[2] + offset]
294  ]
295  )
296  (len(raw_tris) == 0) ? empty_lst : out_tris;
297 
298 //! Compute the sequential edge index pairs for one or more polygon paths.
299 /***************************************************************************//**
300  \param pm <integer-list-list> A list of paths where each path is an
301  ordered list of coordinate indexes into a coordinate array.
302 
303  \returns <integer-list-list> A flat list of index pairs [[j, i], ...]
304  where for each path of length \p n, \p j and \p i are
305  consecutive coordinate indexes with wrap-around: the pair
306  [n-1, 0] closes the polygon. The returned list is flat
307  across all paths with no per-path grouping.
308 
309  \details
310 
311  Yields one index pair per vertex per path, representing the edge
312  from vertex \p j (previous) to vertex \p i (current). The closing
313  edge from the last vertex back to the first is always included.
314  Callers dereference into their coordinate array as needed: \c
315  c[pair[0]] and \c c[pair[1]].
316 
317  This helper contains no reference to a coordinate array; it
318  operates purely on path index lists. It is the caller's
319  responsibility to pass the correct \p pm, including any
320  default-path construction via \c defined_or().
321 
322  \private
323 *******************************************************************************/
324 function _polygon_edge_indices
325 (
326  pm
327 ) =
328  [
329  for (k = pm)
330  let (n = len(k))
331  for (i = [0 : n-1])
332  let (j = (i == 0) ? n-1 : i-1)
333  [k[j], k[i]]
334  ];
335 
336 //! Compute the sequential vertex index triples for one or more polygon paths.
337 /***************************************************************************//**
338  \param pm <integer-list-list> A list of paths where each path is an
339  ordered list of coordinate indexes into a coordinate array.
340 
341  \returns <integer-list-list> A flat list of index triples
342  [[prev, curr, next], ...] where for each path of length
343  \p n, \p prev, \p curr, and \p next are consecutive
344  coordinate indexes with wrap-around at both ends. The
345  returned list is flat across all paths with no per-path
346  grouping.
347 
348  \details
349 
350  Yields one index triple per vertex per path, representing the
351  vertex neighborhood of \p curr: the preceding vertex \p prev and
352  the following vertex \p next. Both ends wrap: at \p i=0, \p prev =
353  \p n-1; at \p i = \p n-1, \p next = 0. Callers dereference into
354  their coordinate array as needed: \c c[triple[0]], \c c[triple[1]],
355  \c c[triple[2]].
356 
357  This helper contains no reference to a coordinate array; it
358  operates purely on path index lists. It is the caller's
359  responsibility to pass the correct \p pm, including any
360  default-path construction via \c defined_or().
361 
362  \private
363 *******************************************************************************/
364 function _polygon_vertex_indices
365 (
366  pm
367 ) =
368  [
369  for (k = pm)
370  let (n = len(k))
371  for (i = [0 : n-1])
372  let
373  (
374  prev = (i == 0) ? n-1 : i-1,
375  next = (i == n-1) ? 0 : i+1
376  )
377  [k[prev], k[i], k[next]]
378  ];
379 
380 //----------------------------------------------------------------------------//
381 // shape generation
382 //----------------------------------------------------------------------------//
383 
384 //! \name Shapes
385 //! @{
386 
387 //! Compute coordinates for an n-sided regular polygon in 2D.
388 /***************************************************************************//**
389  \param n <integer> The number of sides.
390 
391  \param r <decimal> The circumradius of the circumcircle.
392 
393  \param a <decimal> The inradius of the incircle.
394 
395  \param o <point-2d> The center coordinate [x, y].
396 
397  \param ao <decimal> The rotational angular offset in degrees.
398 
399  \param vr <decimal> The vertex rounding radius. Each computed
400  vertex is offset inward by \p vr / cos(180/\p n) along
401  the radial direction from \p o to the vertex. This is
402  correct for any value of \p o.
403 
404  \param cw <boolean> Vertex ordering. When \b true vertices are
405  generated clockwise (decreasing angle from \p ao); when
406  \b false they are generated counter-clockwise
407  (increasing angle from \p ao). Always produces exactly
408  \p n vertices.
409 
410  \returns <points-2d> A list of exactly \p n coordinate points
411  [[x, y], ...], one per polygon vertex.
412 
413  \details
414 
415  Both \p n and at least one of \p r or \p a must be provided. When
416  neither \p r nor \p a is defined, the circumradius defaults to 0
417  and an \em n-pointed degenerate polygon at \p o is returned.
418 
419  The radius can be specified by either the circumradius \p r or the
420  inradius \p a. If both are specified, \p r is used.
421 
422  Vertex angles are computed as \p ao ± \p i × (360/\p n) for \p i in
423  [0, n-1], giving exact uniform angular spacing with no
424  floating-point accumulation across the range. The closing vertex at
425  0° is always included.
426 
427  \b Example
428  \code{.C}
429  vr=5;
430 
431  hull()
432  {
433  for ( p = polygon_regular_p( r=20, n=5, vr=vr ) )
434  translate( p )
435  circle( r=vr );
436  }
437  \endcode
438 
439  See [Wikipedia] for more information.
440 
441  [Wikipedia]: https://en.wikipedia.org/wiki/Regular_polygon
442 *******************************************************************************/
443 function polygon_regular_p
444 (
445  n,
446  r,
447  a,
448  o = origin2d,
449  ao = 0,
450  vr,
451  cw = true
452 ) =
453  let
454  (
455  s = is_defined(r) ? r
456  : is_defined(a) ? a / cos(180/n)
457  : 0,
458 
459  step = 360/n
460  )
461  [
462  for (i = [0 : n-1])
463  let
464  (
465  ai = (cw == true) ? ao - i*step : ao + i*step,
466  v = o + s * [cos(ai), sin(ai)]
467  )
468  is_undef(vr) ? v : v - vr/cos(180/n) * unit_l(v - o)
469  ];
470 
471 //! Compute coordinates along a line in 2D.
472 /***************************************************************************//**
473  \param p1 <point-2d> The line initial coordinate [x, y].
474 
475  \param p2 <point-2d> The line terminal coordinate [x, y].
476 
477  \param l <line-2d> The line or vector.
478 
479  \param x <decimal-list | decimal> A list of \p x coordinates
480  [\p x1, \p x2, ...] or a single \p x coordinate at which
481  to interpolate along the line.
482 
483  \param y <decimal-list | decimal> A list of \p y coordinates
484  [\p y1, \p y2, ...] or a single \p y coordinate at which
485  to interpolate along the line.
486 
487  \param r <decimal-list | decimal> A list of ratios
488  [\p r1, \p r2, ...] or a single ratio \p r. The position
489  ratio along line \p p1 (\p r=\b 0) to \p p2 (\p r=\b 1).
490 
491  \param fs <decimal> A fixed segment size between each point along
492  the line.
493 
494  \param ft <decimal> A fixed segment size between each point,
495  centered, beginning at \p p1 and terminating at \p p2.
496 
497  \param fn <integer> A fixed number of equally spaced points.
498 
499  \returns <points-2d> A list of coordinates points [[x, y], ...].
500  Returns \b undef when the requested interpolation axis is
501  degenerate for the given line orientation (see \details).
502 
503  \details
504 
505  Linear interpolation is used to compute each point along the line.
506  The order of precedence for line specification is: \p l then \p p1
507  and \p p2. The order of precedence for interpolation is: \p x, \p
508  y, \p r, \p fs, \p ft, \p fn.
509 
510  When the line is vertical (delta-x ≈ 0), the interpolation axis
511  switches to \em y. Querying by \p x on a vertical line returns \b
512  undef. Querying by \p y on a horizontal line (zero delta-y) also
513  returns \b undef. In all other cases a list of interpolated
514  coordinates is returned.
515 *******************************************************************************/
516 function polygon_line_p
517 (
518  p1 = origin2d,
519  p2 = x_axis2d_uv,
520  l,
521 
522  x, // coordinate x [or vector of]
523  y, // coordinate y [or vector of]
524  r, // factor [0,1] [or vector of]
525 
526  fs, // fixed size
527  ft, // fixed size centered and terminating
528  fn = 1 // number
529 ) =
530  let
531  (
532  ip = is_defined(l) ? line_ip(l) : p1,
533  tp = is_defined(l) ? line_tp(l) : p2,
534 
535  zdx = almost_eq_nv(tp[0], ip[0]), // is delta-x zero
536  zdy = almost_eq_nv(tp[1], ip[1]), // is delta-y zero
537 
538  // axis: 'y' if zdx, else use 'x'
539  a = zdx ? 1 : 0,
540 
541  // sign / direction of line
542  s = (tp[a] > ip[a]) ? +1 : -1,
543 
544  pl = is_defined(x) ? is_list(x) ? x : [x]
545  : is_defined(y) ? is_list(y) ? y : [y]
546 
547  // list of ratios
548  : is_defined(r) ?
549  [for (i=is_list(r) ? r : [r]) (i*(tp[a]-ip[a])+ip[a])]
550 
551  // fixed segment size
552  : is_defined(fs) ?
553  let
554  (
555  // scale by line x-projection iff using 'x' (ie: zdx != 0)
556  sx = fs * (zdx ? 1 : cos(angle_ll(x_axis2d_uv, s*[ip, tp])))
557  )
558  [for (i=[ip[a] : s*sx : tp[a]]) i]
559 
560  // fixed segment size centered
561  : is_defined(ft) ?
562  let
563  (
564  // scale by line x-projection iff using 'x' (ie: zdx != 0)
565  sx = ft * (zdx ? 1 : cos(angle_ll(x_axis2d_uv, s*[ip, tp]))),
566  // center offset
567  co = ( abs(tp[a]-ip[a]) - sx*floor(abs(tp[a]-ip[a])/sx) )/2
568  )
569  [ip[a], for (i=[ip[a] + s*co : s*sx : tp[a]]) i, tp[a]]
570 
571  // fixed number
572  : [for (i=[0:fn]) (i*(tp[a]-ip[a])/fn+ip[a])]
573  )
574  (a == 1) ?
575  is_defined(x) ? undef // (a == 1)
576  : [ for (py = pl) interpolate2d_l_pp(ip, tp, y=py) ] // interpolate for 'x'
577  : is_defined(y) && zdy ? undef // (a == 0)
578  : [ for (px = pl) interpolate2d_l_pp(ip, tp, x=px) ]; // interpolate for 'y'
579 
580 //! Compute coordinates of an arc with constant radius between two vectors in 2D.
581 /***************************************************************************//**
582  \param r <decimal> The arc radius.
583 
584  \param o <point-2d> The arc center coordinate [x, y].
585 
586  \param v1 <line-2d | decimal> The arc start angle.
587  A 2d line, vector, or decimal angle 1.
588 
589  \param v2 <line-2d | decimal> The arc end angle.
590  A 2d line, vector, or decimal angle 2.
591 
592  \param fn <integer> The number of [facets] \‍(optional\‍).
593 
594  \param cw <boolean> Sweep direction. When \b true the arc sweeps
595  clockwise from the head of \p v1 to the head of \p v2;
596  when \b false it sweeps counter-clockwise. The returned
597  list always contains \p fn + 1 points (both endpoints
598  included).
599 
600  \returns <points-2d> A list of coordinates points [[x, y], ...].
601 
602  \details
603 
604  The arc coordinates will have radius \p r centered about \p o
605  contained within the heads of vectors \p v1 and \p v2. The arc will
606  start at the point coincident to \p v1 and will end at the point
607  coincident to \p v2. When vectors \p v1 and \p v2 are parallel, or
608  when the positive sweep angle is within the almost_eq_nv() tolerance
609  of zero (nearly parallel), the sweep angle is forced to 360° and a
610  full circle is returned. When \p fn is undefined, its value is
611  determined by get_fn().
612 
613  \note To round a polygon corner with a tangent arc whose center is
614  derived automatically from the corner geometry, use
615  polygon_arc_fillet_p().
616 
617  [facets]: \ref get_fn()
618 *******************************************************************************/
619 function polygon_arc_sweep_p
620 (
621  r = 1,
622  o = origin2d,
623  v1 = x_axis2d_uv,
624  v2 = x_axis2d_uv,
625  fn,
626  cw = true
627 ) =
628  let
629  (
630  // number of arc facets
631  naf = defined_or(fn, get_fn(r)),
632 
633  // create vectors if numerical angles have been specified.
634  va1 = is_number(v1) ? [cos(v1), sin(v1)] : v1,
635  va2 = is_number(v2) ? [cos(v2), sin(v2)] : v2,
636 
637  // arc positive start angle
638  iap = angle_ll(x_axis2d_uv, va1, false),
639 
640  // positive arc sweep angle; treat near-zero as full circle
641  vas = angle_ll(va2, va1, false),
642  vap = almost_eq_nv(vas, 0) ? 360 : vas,
643 
644  // arc cw and ccw signed sweep step
645  sas = (((cw == true) ? 0 : 360) - vap)/naf
646  )
647  [
648  for (as = [0 : naf])
649  let (aa = iap + as * sas)
650  o + r * [cos(aa), sin(aa)]
651  ];
652 
653 //! Compute coordinates for a circular fillet arc between two rays in 2D.
654 /***************************************************************************//**
655  \param r <decimal> The fillet radius. Must be greater than zero.
656 
657  \param o <point-2d> The corner coordinate [x, y] — the vertex where
658  the two rays \p v1 and \p v2 meet.
659 
660  \param v1 <line-2d | decimal> The first ray direction (incoming edge).
661  A 2d line, vector, or decimal angle in degrees. The fillet
662  begins at the tangent point on this ray.
663 
664  \param v2 <line-2d | decimal> The second ray direction (outgoing edge).
665  A 2d line, vector, or decimal angle in degrees. The fillet
666  ends at the tangent point on this ray.
667 
668  \param fn <integer> The number of [facets] \‍(optional\‍). When
669  undefined, its value is determined by get_fn() evaluated at
670  the fillet radius \p r.
671 
672  \param cw <boolean> Point ordering. When \b true the returned list
673  runs from the tangent point on \p v1 to the tangent point
674  on \p v2 in the clockwise direction; when \b false the list
675  is reversed to run counter-clockwise. Defaults to \b true,
676  consistent with polygon_arc_sweep_p() and the other
677  shape-generation functions in this group.
678 
679  \returns <points-2d> A list of coordinates points [[x, y], ...] tracing
680  the fillet arc from the tangent point on \p v1 to the tangent
681  point on \p v2. Returns \b empty_lst when the two rays are
682  (anti-)parallel within the almost_eq_nv() tolerance.
683 
684  \details
685 
686  A circular fillet of radius \p r is tangent to both rays \p v1 and
687  \p v2 that originate at the corner vertex \p o. The function computes
688  and returns only the arc of the fillet circle that lies between the
689  two tangent points; it does \em not include the corner vertex \p o or
690  the tangent points' projections back along the rays. The resulting
691  point list can be spliced directly into a polygon path in place of the
692  sharp corner at \p o to round it.
693 
694  [facets]: \ref get_fn()
695 *******************************************************************************/
696 function polygon_arc_fillet_p
697 (
698  r = 1,
699  o = origin2d,
700  v1 = x_axis2d_uv,
701  v2 = y_axis2d_uv,
702  fn,
703  cw = true
704 ) =
705  assert( r > 0, "polygon_arc_fillet_p: fillet radius r must be greater than zero." )
706  let
707  (
708  // normalise v1 / v2 to unit direction vectors from o
709  d1 = unit_l( is_number(v1) ? [cos(v1), sin(v1)] : (len(v1) == 2 && is_list(v1[0])) ? v1[1] - v1[0] : v1 ),
710  d2 = unit_l( is_number(v2) ? [cos(v2), sin(v2)] : (len(v2) == 2 && is_list(v2[0])) ? v2[1] - v2[0] : v2 ),
711 
712  cos_full = d1 * d2, // dot product
713  sin_half = sqrt( max(0, (1 - cos_full) / 2) ), // sin(α), always ≥ 0
714 
715  degenerate = almost_eq_nv(sin_half, 0)
716  )
717  degenerate ? empty_lst
718  : let
719  (
720  // bisector unit vector (points "into" the corner, toward the fillet center)
721  bisector = unit_l(d1 + d2),
722 
723  // distance from o to the fillet circle center along the bisector
724  dist_to_center = r / sin_half,
725 
726  // fillet circle center
727  fc = o + dist_to_center * bisector,
728 
729  // arc start angle: direction from fc toward the tangent point on v1
730  cos_half = sqrt( max(0, (1 + cos_full) / 2) ),
731  tan_half = sin_half / cos_half,
732 
733  tp1 = o + (r / tan_half) * d1,
734  tp2 = o + (r / tan_half) * d2,
735 
736  // angles from fillet center to each tangent point
737  a1 = angle_ll(x_axis2d_uv, tp1 - fc, false),
738  a2 = angle_ll(x_axis2d_uv, tp2 - fc, false)
739  )
740  polygon_arc_sweep_p( r=r, o=fc, v1=a1, v2=a2, fn=fn, cw=cw );
741 
742 
743 //! Compute coordinates for a circular arc blend between two line segments in 2D.
744 /***************************************************************************//**
745  \param p1 <point-2d> The start point of the incoming segment [x, y].
746 
747  \param p2 <point-2d> The corner vertex coordinate [x, y] — the point
748  where the two segments meet.
749 
750  \param p3 <point-2d> The end point of the outgoing segment [x, y].
751 
752  \param r <decimal> The blend radius. Must be greater than zero.
753 
754  \param fn <integer> The number of [facets] \‍(optional\‍). When
755  undefined, its value is determined by get_fn() evaluated at
756  the blend radius \p r.
757 
758  \param cw <boolean> Point ordering. When \b true the returned list
759  runs from the tangent point on the incoming segment to the
760  tangent point on the outgoing segment in the clockwise
761  direction; when \b false the list is reversed to run
762  counter-clockwise. Defaults to \b true, consistent with
763  polygon_arc_fillet_p() and the other shape-generation
764  functions in this group.
765 
766  \returns <points-2d> A list of coordinate points [[x, y], ...] tracing
767  the blend arc from the tangent point on the incoming segment
768  to the tangent point on the outgoing segment. Returns
769  \b empty_lst when the two segments are (anti-)parallel within
770  the almost_eq_nv() tolerance.
771 
772  \details
773 
774  Computes a circular fillet arc of radius \p r that is tangent to
775  both the incoming segment \p p1 to \p p2 and the outgoing segment
776  \p p2 to \p p3. The returned arc replaces the sharp corner at \p p2
777  and can be spliced directly into a polygon path to produce a
778  rounded vertex.
779 
780  The function derives the incoming and outgoing ray directions from
781  the three supplied points and delegates to polygon_arc_fillet_p().
782  The corner vertex \p p2 is not included in the returned list; only
783  the arc points between the two tangent points are returned.
784 
785  \b Example
786  \code{.C}
787  $fn = 32;
788 
789  c = [[0,0], [10,0], [10,10], [0,10]];
790 
791  // replace the corner at c[1] with a blend arc of radius 3
792  blend = polygon_arc_blend_p( p1=c[0], p2=c[1], p3=c[2], r=3 );
793 
794  polygon( concat( [c[0]], blend, [c[2]], [c[3]] ) );
795  \endcode
796 
797  [facets]: \ref get_fn()
798 *******************************************************************************/
799 function polygon_arc_blend_p
800 (
801  p1,
802  p2,
803  p3,
804  r = 1,
805  fn,
806  cw = true
807 ) =
809  (
810  r = r,
811  o = p2,
812  v1 = p2 - p1,
813  v2 = p3 - p2,
814  fn = fn,
815  cw = cw
816  );
817 
818 //! Compute coordinates for an elliptical sector in 2D.
819 /***************************************************************************//**
820  \param r <decimal-list-2 | decimal> The elliptical radius. A list
821  [rx, ry] of decimals where \p rx is the x-axis radius and
822  \p ry is the y-axis radius, or a single decimal for
823  (rx=ry).
824 
825  \param o <point-2d> The center coordinate [x, y].
826 
827  \param v1 <line-2d | decimal> The sector angle 1.
828  A 2d line, vector, or decimal.
829 
830  \param v2 <line-2d | decimal> The sector angle 2.
831  A 2d line, vector, or decimal.
832 
833  \param s <boolean> Use signed vector angle conversions. When
834  \b false, positive angle conversion will be used.
835 
836  \param fn <integer> The number of [facets] \‍(optional\‍).
837 
838  \param cw <boolean> Coordinate point ordering. When \b true the
839  returned list is in the natural computed order; when \b
840  false the list is reversed.
841 
842  \returns <points-2d> A list of coordinates points [[x, y], ...].
843 
844  \details
845 
846  The coordinates sweep from angle \p v1 to angle \p v2. When \p v1
847  and \p v2 are equal, a full ellipse is returned. The sweep
848  direction is determined by the signs of the angles; a positive
849  delta sweeps counter-clockwise and a negative delta sweeps
850  clockwise, regardless of the \p cw ordering parameter (which only
851  controls whether the returned list is reversed).
852 
853  When \p v1 and \p v2 are not equal, the origin point \p o is
854  prepended to the coordinate list, forming a closed pie-sector shape
855  when passed directly to \c polygon(). For a full ellipse, \p o is
856  omitted and the result is a closed ring of perimeter points.
857 
858  The parameter \p s controls how vector angles are converted to
859  decimal degrees. When \p s = \b true (default), signed angle
860  conversion is used, so vectors below the x-axis yield negative
861  angles. When \p s = \b false, all angles are mapped to [0, 360).
862  This affects sector orientation when \p v1 or \p v2 are given as 2d
863  lines or vectors rather than explicit decimal angles.
864 
865  When \p fn is undefined, its value is determined by get_fn().
866 
867  [facets]: \ref get_fn()
868 *******************************************************************************/
870 (
871  r = 1,
872  o = origin2d,
873  v1 = x_axis2d_uv,
874  v2 = x_axis2d_uv,
875  s = true,
876  fn,
877  cw = true
878 ) =
879  let
880  (
881  rx = defined_e_or(r, 0, r),
882  ry = defined_e_or(r, 1, rx),
883 
884  va1 = is_number(v1) ? v1 : angle_ll(x_axis2d_uv, v1, s),
885  va2 = is_number(v2) ? v2 : angle_ll(x_axis2d_uv, v2, s),
886 
887  // full return when angles are equal
888  va3 = (va1 == va2) ? va2+360 : va2,
889 
890  // number of arc facets
891  af = defined_or(fn, get_fn((rx+ry)/2)),
892 
893  // point generation ordering
894  as = (va3 > va1) ? [af:-1:0] : [0:af],
895 
896  // cw ordering
897  pp =
898  [
899  if (va1 != va2) o,
900  for (i = as)
901  let (pa = ((af-i)*va1 + i*va3) / af)
902  o + [rx*cos(pa), ry*sin(pa)]
903  ]
904  )
905  (cw == true) ? pp : reverse(pp);
906 
907 //! Compute the coordinates for a rounded trapezoid in 2D space.
908 /***************************************************************************//**
909  \param b <decimal-list-2 | decimal> The base lengths. A list [b1,
910  b2] of 2 decimals or a single decimal for (b1=b2).
911 
912  \param h <decimal> The perpendicular height between bases. Takes
913  precedence over \p l when both are specified. When
914  neither \p h nor \p l is specified, \p l defaults to 1.
915 
916  \param l <decimal> The left side leg length. Used only when \p h
917  is not specified; the resulting height is \p l × sin(\p
918  a).
919 
920  \param a <decimal> The angle in degrees between the lower base and
921  the left leg. When \p h is specified, restricted to [45,
922  135] to keep the upper-left vertex above the lower base.
923 
924  \param o <point-2d> The origin offset coordinate [x, y].
925 
926  \param cw <boolean> Polygon vertex ordering.
927 
928  \returns <points-2d> A list of exactly 4 coordinate points
929  [[x, y], ...] defining the trapezoid vertices.
930 
931  \details
932 
933  The four vertices are computed from the origin \p o as follows: \p
934  p1 = \p o (lower-left), \p p2 = upper-left leg endpoint, \p p3 = \p
935  p2 + [\p b2, 0] (upper-right), \p p4 = \p o + [\p b1, 0]
936  (lower-right). The lower base has length \p b1 and the upper base
937  has length \p b2.
938 
939  When both \p h and \p l are specified, \p h takes precedence and
940  the actual leg length is derived from \p h and \p a. When only \p l
941  is given, the perpendicular height is \p l × sin(\p a). If \p h is
942  specified, the angle \p a is restricted to the range [45, 135] to
943  keep the upper-left vertex above the lower base.
944 
945  Special cases: when \p b is a single decimal (\p b1 = \p b2) and \p
946  a = 90 the result is a rectangle; when additionally \p b1 = \p h
947  the result is a square. When \p a ≠ 90 and \p b1 = \p b2 the result
948  is a parallelogram.
949 
950  See [Wikipedia] for more general information on trapezoids.
951 
952  [Wikipedia]: https://en.wikipedia.org/wiki/Trapezoid
953  [parallelograms]: https://en.wikipedia.org/wiki/Parallelogram
954 *******************************************************************************/
955 function polygon_trapezoid_p
956 (
957  b = 1,
958  h,
959  l = 1,
960  a = 90,
961  o = origin2d,
962  cw = true
963 ) =
964  assert
965  (
966  !( !is_undef(h) && !is_between(a, 45, 135) ),
967  "given h, the angle a is restricted to the range [45, 135]."
968  )
969  let
970  (
971  b1 = defined_e_or(b, 0, b),
972  b2 = defined_e_or(b, 1, b1),
973 
974  // trapezoid vertices from origin
975  p1 = o,
976  p2 = o + (is_undef(h) ? l*[cos(a), sin(a)] : h*[cos(2*a - 90), 1]),
977  p3 = p2 + [b2, 0],
978  p4 = o + [b1, 0],
979 
980  // cw ordering
981  pp = [p1, p2, p3, p4]
982  )
983  (cw == true) ? pp : reverse(pp);
984 
985 //! @}
986 
987 //----------------------------------------------------------------------------//
988 // curves
989 //----------------------------------------------------------------------------//
990 
991 //! \name Curves
992 //! @{
993 
994 //! Compute coordinates for a degree-n Bézier curve in 2D.
995 /***************************************************************************//**
996  \param c <points-2d> A list of 2d control point coordinates
997  [[x, y], ...]. The curve interpolates \p c[0] and
998  \p c[last] and approximates the interior control points.
999  Minimum 2 points required (degree 1, linear).
1000 
1001  \param fn <integer> The number of [facets] \‍(optional\‍). When
1002  undefined, its value is determined by get_fn() using the
1003  chord length of the control polygon as a proxy radius.
1004 
1005  \param cw <boolean> Point ordering. When \b true the returned list
1006  is in the natural computed order (t = 0 to 1); when \b
1007  false the list is reversed. Defaults to \b true.
1008 
1009  \returns <points-2d> A list of \p fn + 1 coordinate points [[x, y], ...]
1010  sampled uniformly in the parameter \p t member of [0, 1], including
1011  both endpoints.
1012 
1013  \details
1014 
1015  Evaluates the Bézier curve defined by the control polygon \p c
1016  using the de Casteljau algorithm. The degree of the curve is
1017  `len(c) - 1`. Common cases:
1018 
1019  - Degree 1 (2 points): straight line segment.
1020  - Degree 2 (3 points): quadratic Bézier.
1021  - Degree 3 (4 points): cubic Bézier.
1022  - Higher degrees are supported but may exhibit Runge-like oscillation.
1023 
1024  The de Casteljau algorithm is used for its numerical stability. At
1025  each parameter value \p t it performs `degree` rounds of linear
1026  interpolation on the current level's point list until a single
1027  point remains. No external helper functions are required.
1028 
1029  \b Example
1030  \code{.C}
1031  $fn = 32;
1032 
1033  ctrl = [ [0,0], [5,20], [15,20], [20,0] ];
1034 
1035  polygon( polygon_bezier_p( c=ctrl ) );
1036  \endcode
1037 
1038  [facets]: \ref get_fn()
1039 *******************************************************************************/
1040 function polygon_bezier_p
1041 (
1042  c,
1043  fn,
1044  cw = true
1045 ) =
1046  let
1047  (
1048  // chord-length sum as proxy for get_fn() radius
1049  chord = sum( [for (i = [0 : len(c)-2]) distance_pp( c[i], c[i+1] )] ),
1050  nf = defined_or( fn, get_fn( chord / (2 * pi) ) ),
1051 
1052  // de Casteljau evaluation at parameter t in [0,1]
1053  // recursively interpolates the point list until one point remains
1054  de_casteljau =
1055  function (cp, t)
1056  (len(cp) == 1) ? cp[0]
1057  : de_casteljau
1058  (
1059  [for (i = [0 : len(cp)-2]) (1-t)*cp[i] + t*cp[i+1]],
1060  t
1061  ),
1062 
1063  pp =
1064  [
1065  for (i = [0 : nf])
1066  de_casteljau( c, i / nf )
1067  ]
1068  )
1069  (cw == true) ? pp : reverse(pp);
1070 
1071 //! Compute coordinates for a Catmull-Rom spline through a list of knot points in 2D.
1072 /***************************************************************************//**
1073  \param c <points-2d> A list of 2d knot coordinates [[x, y], ...].
1074  The curve passes through every knot. Minimum 2 points
1075  required. When \p closed is \b false the curve runs from
1076  \p c[0] to \p c[last]; when \p closed is \b true it
1077  also returns from \p c[last] back to \p c[0].
1078 
1079  \param fn <integer> The number of [facets] per segment \‍(optional\‍).
1080  Each segment is the span between two consecutive knot
1081  points. When undefined, the per-segment value is
1082  determined by get_fn() evaluated at the segment chord
1083  length divided by \p 2π.
1084 
1085  \param closed <boolean> When \b true the spline is closed: a segment
1086  is added from the last knot back to the first, and the
1087  phantom knots at both ends are wrapped accordingly.
1088  Defaults to \b false.
1089 
1090  \param cw <boolean> Point ordering. When \b true the returned list
1091  is in the natural computed order; when \b false the list
1092  is reversed. Defaults to \b true.
1093 
1094  \returns <points-2d> A list of coordinate points [[x, y], ...] tracing
1095  the spline. For an open spline with \p n knots and \p fn facets
1096  per segment the list contains `(n-1) × fn + 1` points. For a
1097  closed spline it contains `n × fn` points (the closing knot is
1098  not duplicated).
1099 
1100  \details
1101 
1102  Implements the centripetal Catmull-Rom spline. Each interior segment
1103  \p i (from knot \p i to knot \p i+1) uses the four control points
1104  \p c[i-1], \p c[i], \p c[i+1], \p c[i+2] to form a smooth
1105  cubic. Phantom knots at the ends of an open spline are synthesised by
1106  reflecting: the phantom before \p c[0] mirrors \p c[1], and the
1107  phantom after \p c[last] mirrors \p c[last-1].
1108 
1109  \b Example
1110  \code{.C}
1111  $fn = 32;
1112 
1113  knots = [ [0,0], [5,10], [15,10], [20,0], [15,-10], [5,-10] ];
1114 
1115  polygon( polygon_spline_p( c=knots, closed=true ) );
1116  \endcode
1117 
1118  [facets]: \ref get_fn()
1119 *******************************************************************************/
1120 function polygon_spline_p
1121 (
1122  c,
1123  fn,
1124  closed = false,
1125  cw = true
1126 ) =
1127  let
1128  (
1129  n = len(c),
1130 
1131  // build the augmented knot list with phantom end-points
1132  // open: reflect c[1] before c[0] and c[n-2] after c[n-1]
1133  // closed: wrap last and first knots to the phantom positions
1134  kp =
1135  closed ?
1136  concat( [c[n-1]], c, [c[0]], [c[1]] )
1137  : concat( [2*c[0] - c[1]], c, [2*c[n-1] - c[n-2]] ),
1138 
1139  // number of segments
1140  ns = closed ? n : n - 1,
1141 
1142  // Catmull-Rom segment sampler; returns fn+1 or fn points
1143  // kp — augmented knot array
1144  // seg — segment index (0-based, into original c)
1145  // last_seg — true when this is the final segment of an open spline
1146  cr_seg =
1147  function (kp, seg, nf, last_seg)
1148  let
1149  (
1150  p0 = kp[seg], // phantom / previous knot
1151  p1 = kp[seg+1], // segment start knot
1152  p2 = kp[seg+2], // segment end knot
1153  p3 = kp[seg+3], // next / phantom knot
1154 
1155  end_i = last_seg ? nf : nf - 1
1156  )
1157  [
1158  for (i = [0 : end_i])
1159  let
1160  (
1161  t = i / nf,
1162  t2 = t * t,
1163  t3 = t2 * t
1164  )
1165  0.5 * (
1166  2*p1
1167  + (-p0 + p2) * t
1168  + (2*p0 - 5*p1 + 4*p2 - p3) * t2
1169  + (-p0 + 3*p1 - 3*p2 + p3) * t3
1170  )
1171  ],
1172 
1173  pp =
1174  [
1175  for (seg = [0 : ns-1])
1176  let
1177  (
1178  chord = distance_pp( c[seg % n], c[(seg+1) % n] ),
1179  nf = defined_or( fn, max(1, get_fn( chord / (2*pi) )) ),
1180  last_s = (!closed) && (seg == ns-1)
1181  )
1182  for (pt = cr_seg(kp, seg, nf, last_s))
1183  pt
1184  ]
1185  )
1186  (cw == true) ? pp : reverse(pp);
1187 
1188 //! @}
1189 
1190 //----------------------------------------------------------------------------//
1191 // shape properties
1192 //----------------------------------------------------------------------------//
1193 
1194 //! \name Properties
1195 //! @{
1196 
1197 //! Compute the perimeter of an n-sided regular polygon in 2D.
1198 /***************************************************************************//**
1199  \param n <integer> The number of sides.
1200 
1201  \param r <decimal> The vertex circumradius of the circumcircle.
1202 
1203  \param a <decimal> The inradius of the incircle.
1204 
1205  \returns <decimal> Perimeter length of the n-sided regular polygon.
1206 
1207  \details
1208 
1209  The radius can be specified by either the circumradius \p r or the
1210  inradius \p a. If both are specified, \p r is used. Returns 0 when
1211  neither is defined.
1212 
1213  Formulae used:
1214  \code
1215  P = 2 × n × r × sin(180/n) (from circumradius r)
1216  P = 2 × n × a × tan(180/n) (from inradius a)
1217  \endcode
1218 *******************************************************************************/
1220 (
1221  n,
1222  r,
1223  a
1224 ) = is_defined(r) ? 2 * n * r * sin(180/n)
1225  : is_defined(a) ? 2 * n * a * tan(180/n)
1226  : 0;
1227 
1228 //! Compute the area of an n-sided regular polygon in 2D.
1229 /***************************************************************************//**
1230  \param n <integer> The number of sides.
1231 
1232  \param r <decimal> The vertex circumradius of the circumcircle.
1233 
1234  \param a <decimal> The inradius of the incircle.
1235 
1236  \returns <decimal> Area of the n-sided regular polygon.
1237 
1238  \details
1239 
1240  The radius can be specified by either the circumradius \p r or the
1241  inradius \p a. If both are specified, \p r is used. Returns 0 when
1242  neither is defined.
1243 
1244  Formulae used:
1245  \code
1246  A = r² × n × sin(360/n) / 2 (from circumradius r)
1247  A = a² × n × tan(180/n) (from inradius a)
1248  \endcode
1249 *******************************************************************************/
1250 function polygon_regular_area
1251 (
1252  n,
1253  r,
1254  a
1255 ) = is_defined(r) ? pow(r, 2) * n * sin(360/n) / 2
1256  : is_defined(a) ? pow(a, 2) * n * tan(180/n)
1257  : 0;
1258 
1259 //! Calculate the perimeter length of a polygon in 2d.
1260 /***************************************************************************//**
1261  \param c <points-2d> A list of 2d cartesian coordinates
1262  [[x, y], ...].
1263 
1264  \param p <integer-list-list> An \em optional list of paths that
1265  define one or more closed shapes where each is a list of
1266  coordinate indexes.
1267 
1268  \returns <decimal> The sum of all polygon primary and secondary
1269  perimeter lengths.
1270 
1271  \details
1272 
1273  Computes the total perimeter by summing the Euclidean distance
1274  between each consecutive pair of vertices in every path, including
1275  the closing edge from the last vertex back to the first. Uses
1276  _polygon_edge_indices() to enumerate edge pairs.
1277 
1278  \amu_eval (${note_p_not_defined})
1279 *******************************************************************************/
1280 function polygon_perimeter
1281 (
1282  c,
1283  p
1284 ) =
1285  let
1286  (
1287  pm = defined_or(p, [consts(len(c))]),
1288  ei = _polygon_edge_indices(pm)
1289  )
1290  sum([for (e = ei) distance_pp(c[e[0]], c[e[1]])]);
1291 
1292 //! Compute the signed area of a polygon in a Euclidean 2d-space.
1293 /***************************************************************************//**
1294  \param c <points-2d> A list of 2d cartesian coordinates
1295  [[x, y], ...].
1296 
1297  \param p <integer-list-list> An \em optional list of paths that
1298  define one or more closed shapes where each is a list of
1299  coordinate indexes.
1300 
1301  \param s <boolean> Return the signed area. When \b true the raw
1302  signed value is returned: negative for clockwise vertex
1303  ordering and positive for counter-clockwise. When \b
1304  false (default) the absolute area is returned.
1305 
1306  \returns <decimal> The area of the given polygon.
1307 
1308  \details
1309 
1310  Uses the shoelace formula applied to all edge pairs enumerated by
1311  _polygon_edge_indices(). See [Wikipedia] for more information.
1312 
1313  \amu_eval (${note_p_not_defined})
1314 
1315  \amu_eval (${warning_secondary_shapes})
1316 
1317  [Wikipedia]: https://en.wikipedia.org/wiki/Shoelace_formula
1318 *******************************************************************************/
1319 function polygon_area
1320 (
1321  c,
1322  p,
1323  s = false
1324 ) =
1325  let
1326  (
1327  pm = defined_or(p, [consts(len(c))]),
1328  ei = _polygon_edge_indices(pm),
1329  sa = sum
1330  (
1331  [
1332  for (e = ei)
1333  (c[e[0]][0] + c[e[1]][0]) * (c[e[1]][1] - c[e[0]][1])
1334  ]
1335  ) / 2
1336  )
1337  (s == false) ? abs(sa) : sa;
1338 
1339 //! Compute the area of a polygon in a Euclidean 3d-space.
1340 /***************************************************************************//**
1341  \param c <points-3d> A list of 3d cartesian coordinates
1342  [[x, y, z], ...].
1343 
1344  \param p <integer-list-list> An \em optional list of paths that
1345  define one or more closed shapes where each is a list of
1346  coordinate indexes.
1347 
1348  \param n <vector-3d> An \em optional normal vector, [x, y, z],
1349  to the polygon plane. When not given, a normal vector is
1350  constructed from the first three points of the primary
1351  path.
1352 
1353  \returns <decimal> The area of the given polygon.
1354 
1355  \details
1356 
1357  Computes the area using a coordinate-projection method: the dominant
1358  axis of the polygon's normal vector is identified, the polygon is
1359  projected onto the perpendicular plane, and the 2D shoelace formula
1360  is applied with a correction factor derived from the normal vector
1361  magnitude. Function patterned after [Dan Sunday, 2012].
1362 
1363  When \p n is not supplied the normal is derived from the first three
1364  vertices of the primary path. An assertion is raised if those three
1365  vertices are collinear (i.e. the derived normal is the zero vector),
1366  because no valid projection axis exists in that case. Supply an
1367  explicit \p n to override when the first three vertices are known to
1368  be collinear but the polygon as a whole is non-degenerate.
1369 
1370  \amu_eval (${note_p_not_defined})
1371 
1372  \amu_eval (${warning_secondary_shapes})
1373 
1374  [Dan Sunday, 2012]: http://geomalgorithms.com/a01-_area.html
1375 *******************************************************************************/
1376 function polygon3d_area
1377 (
1378  c,
1379  p,
1380  n
1381 ) =
1382  let
1383  (
1384  pm = defined_or(p, [consts(len(c))]),
1385  nv = defined_or(n, cross_ll([c[pm[0][0]], c[pm[0][1]]], [c[pm[0][0]], c[pm[0][2]]])),
1386 
1387  // planarity guard: the first three vertices must not be collinear when
1388  // n is derived automatically; a zero normal makes the projection axis
1389  // undefined and causes a division-by-zero in sf below.
1390  _check_planar =
1391  assert
1392  (
1393  distance_pp(nv) > 0,
1394  strl
1395  ([
1396  "polygon3d_area: first three vertices are collinear;",
1397  " cannot determine polygon plane. Supply an explicit normal n."
1398  ])
1399  ),
1400 
1401  ac = [abs(nv[0]), abs(nv[1]), abs(nv[2])],
1402  am = max(ac),
1403  ai = (am == ac[2]) ? 2 : (am == ac[1]) ? 1 : 0,
1404 
1405  pv = [
1406  for (k = pm) let (m = len(k))
1407  for (i=[1 : m])
1408  c[k[i%m]][(ai+1)%3] * (c[k[(i+1)%m]][(ai+2)%3] - c[k[(i-1)%m]][(ai+2)%3])
1409  ],
1410 
1411  sf = (distance_pp(nv)/(2*nv[ai]))
1412  )
1413  (sum(pv) * sf);
1414 
1415 //! Compute the center of mass of a polygon in a Euclidean 2d-space.
1416 /***************************************************************************//**
1417  \param c <points-2d> A list of 2d cartesian coordinates
1418  [[x, y], ...].
1419 
1420  \param p <integer-list-list> An \em optional list of paths that
1421  define one or more closed shapes where each is a list of
1422  coordinate indexes.
1423 
1424  \returns <point-2d> The center of mass of the given polygon.
1425 
1426  \details
1427 
1428  Uses the shoelace-derived centroid formula applied to all edge pairs
1429  enumerated by _polygon_edge_indices(). See [Wikipedia] for more
1430  information.
1431 
1432  \amu_eval (${note_p_not_defined})
1433 
1434  \amu_eval (${warning_secondary_shapes})
1435 
1436  \warning Returns \b undef (division by zero) for degenerate polygons
1437  with zero signed area, such as collinear point sets or
1438  self-intersecting shapes whose positive and negative areas
1439  cancel.
1440 
1441  [Wikipedia]: https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon
1442 *******************************************************************************/
1443 function polygon_centroid
1444 (
1445  c,
1446  p
1447 ) =
1448  let
1449  (
1450  pm = defined_or(p, [consts(len(c))]),
1451  ei = _polygon_edge_indices(pm),
1452  cv = [
1453  for (e = ei)
1454  let
1455  (
1456  xc = c[e[0]][0], yc = c[e[0]][1],
1457  xn = c[e[1]][0], yn = c[e[1]][1],
1458  cd = xc*yn - xn*yc
1459  )
1460  [(xc + xn) * cd, (yc + yn) * cd]
1461  ],
1462  sc = sum(cv),
1463  sa = polygon_area(c, pm, true)
1464  )
1465  sc / (6 * sa);
1466 
1467 //! Compute the winding number of a polygon about a point in a Euclidean 2d-space.
1468 /***************************************************************************//**
1469  \param c <points-2d> A list of 2d cartesian coordinates
1470  [[x, y], ...].
1471 
1472  \param p <integer-list-list> An \em optional list of paths that
1473  define one or more closed shapes where each is a list of
1474  coordinate indexes.
1475 
1476  \param t <point-2d> A test point coordinate [x, y].
1477 
1478  \returns <integer> The winding number. Positive values indicate the
1479  point is enclosed by net counter-clockwise turns; negative
1480  values indicate net clockwise turns; zero means the point
1481  is outside the polygon.
1482 
1483  \details
1484 
1485  Computes the [winding number], the total number of counterclockwise
1486  turns that the polygon paths makes around the test point in a
1487  Euclidean 2d-space. Will be 0 \em iff the point is outside of the
1488  polygon. The result for a test point exactly on a polygon edge is
1489  implementation-defined. Uses _polygon_edge_indices() to enumerate
1490  edge pairs. Function patterned after [Dan Sunday, 2012].
1491 
1492  \note
1493 
1494  Copyright 2000 softSurfer, 2012 Dan Sunday This code may be freely
1495  used and modified for any purpose providing that this copyright
1496  notice is included with it. iSurfer.org makes no warranty for this
1497  code, and cannot be held liable for any real or imagined damage
1498  resulting from its use. Users of this code must verify correctness
1499  for their application.
1500 
1501  [Dan Sunday, 2012]: http://geomalgorithms.com/a03-_inclusion.html
1502  [winding number]: https://en.wikipedia.org/wiki/Winding_number
1503 
1504  \amu_eval (${note_p_not_defined})
1505 
1506  \warning Where there are secondary paths, the vertex ordering of each
1507  must be the same as the primary path.
1508 *******************************************************************************/
1509 function polygon_winding
1510 (
1511  c,
1512  p,
1513  t
1514 ) =
1515  let
1516  (
1517  pm = defined_or(p, [consts(len(c))]),
1518  ei = _polygon_edge_indices(pm),
1519  wv = [
1520  for (e = ei)
1521  (c[e[0]][1] <= t[1] && c[e[1]][1] > t[1] && is_left_ppp(c[e[0]], c[e[1]], t) > 0) ? 1
1522  : (c[e[0]][1] > t[1] && c[e[1]][1] <= t[1] && is_left_ppp(c[e[0]], c[e[1]], t) < 0) ? -1
1523  : 0
1524  ]
1525  )
1526  sum(wv);
1527 
1528 //! @}
1529 
1530 //----------------------------------------------------------------------------//
1531 // shape property tests
1532 //----------------------------------------------------------------------------//
1533 
1534 //! \name Tests
1535 //! @{
1536 
1537 //! Test the vertex ordering of a polygon in a Euclidean 2d-space.
1538 /***************************************************************************//**
1539  \param c <points-2d> A list of 2d cartesian coordinates
1540  [[x, y], ...].
1541 
1542  \param p <integer-list-list> An \em optional list of paths that
1543  define one or more closed shapes where each is a list of
1544  coordinate indexes.
1545 
1546  \returns <boolean> \b true if the vertex are ordered \em clockwise,
1547  \b false if the vertex are \em counterclockwise ordered, and
1548  \b undef if the ordering can not be determined.
1549 
1550  \details
1551 
1552  Uses the sign of the polygon's signed area (shoelace formula). A
1553  negative signed area indicates clockwise vertex ordering in the
1554  standard 2d coordinate system (y-up). Returns \b undef for
1555  degenerate polygons with zero signed area (e.g. collinear
1556  vertices).
1557 
1558  \note Two separate winding conventions coexist in this library.
1559  Shape-generation functions (polygon_regular_p(),
1560  polygon_trapezoid_p(), polygon_elliptical_sector_p(), etc.)
1561  all default to \p cw = \b true, producing clockwise output.
1562  OpenSCAD's \c polygon() and \c linear_extrude() treat the
1563  primary path as counter-clockwise and hole paths as clockwise.
1564  Callers that bridge the two layers must reverse the coordinate
1565  list (or pass \p cw = \b false) as appropriate. Functions in
1566  this library that require a specific winding — such as
1567  polygon_linear_extrude_pf() — call polygon_is_clockwise()
1568  and normalise internally; callers are not required to
1569  pre-normalise their input.
1570 
1571  \amu_eval (${note_p_not_defined})
1572 *******************************************************************************/
1573 function polygon_is_clockwise
1574 (
1575  c,
1576  p
1577 ) =
1578  let
1579  (
1580  sa = polygon_area(c, p, true)
1581  )
1582  (sa < 0) ? true
1583  : (sa > 0) ? false
1584  : undef;
1585 
1586 //! Test the convexity of a polygon in a Euclidean 2d-space.
1587 /***************************************************************************//**
1588  \param c <points-2d> A list of 2d cartesian coordinates
1589  [[x, y], ...].
1590 
1591  \param p <integer-list-list> An \em optional list of paths that
1592  define one or more closed shapes where each is a list of
1593  coordinate indexes.
1594 
1595  \returns <boolean> \b true if the polygon is \em convex, \b false
1596  otherwise.
1597 
1598  \details
1599 
1600  Tests convexity by computing the cross product sign of each
1601  consecutive edge pair using _polygon_vertex_indices() to enumerate
1602  vertex triples. A polygon is convex when all cross products have
1603  the same sign (all left-turns or all right-turns). Collinear edges
1604  produce a cross product of zero, which counts as a distinct sign
1605  value and will cause the function to return \b false even for
1606  otherwise convex polygons with collinear points on an edge. Returns
1607  \b undef when \p c is undefined, has fewer than 3 points, or
1608  contains non-2d coordinates.
1609 
1610  \amu_eval (${note_p_not_defined})
1611 *******************************************************************************/
1612 function polygon_is_convex
1613 (
1614  c,
1615  p
1616 ) = is_undef(c) ? undef
1617  : len(c) < 3 ? undef
1618  : !all_len(c, 2) ? undef
1619  : let
1620  (
1621  pm = defined_or(p, [consts(len(c))]),
1622  vi = _polygon_vertex_indices(pm),
1623  sv = [
1624  for (triple = vi)
1625  sign
1626  (
1627  cross_ll
1628  (
1629  [c[triple[0]], c[triple[1]]],
1630  [c[triple[1]], c[triple[2]]]
1631  )
1632  )
1633  ],
1634  us = unique(sv)
1635  )
1636  (len(us) == 1);
1637 
1638 //! Test if a point is inside a polygon in a Euclidean 2d-space using winding number.
1639 /***************************************************************************//**
1640  \param c <points-2d> A list of 2d cartesian coordinates
1641  [[x, y], ...].
1642 
1643  \param p <integer-list-list> An \em optional list of paths that
1644  define one or more closed shapes where each is a list of
1645  coordinate indexes.
1646 
1647  \param t <point-2d> A test point coordinate [x, y].
1648 
1649  \returns <boolean> \b true when the point is \em inside the polygon and
1650  \b false otherwise.
1651 
1652  \details
1653 
1654  Delegates to polygon_winding() and returns \b true when the winding
1655  number is non-zero. A winding number of zero indicates the point is
1656  outside all enclosed regions. The result for a test point exactly
1657  on a polygon edge is implementation-defined (see
1658  polygon_winding()).
1659 
1660  \amu_eval (${note_p_not_defined})
1661 
1662  \sa polygon_winding for warning about secondary shapes.
1663 *******************************************************************************/
1664 function polygon_wn_is_p_inside
1665 (
1666  c,
1667  p,
1668  t
1669 ) = (polygon_winding(c=c, p=p, t=t) != 0);
1670 
1671 //! Test if a point is inside a polygon in a Euclidean 2d-space using angle summation.
1672 /***************************************************************************//**
1673  \param c <points-2d> A list of 2d cartesian coordinates
1674  [[x, y], ...].
1675 
1676  \param p <integer-list-list> An \em optional list of paths that
1677  define one or more closed shapes where each is a list of
1678  coordinate indexes.
1679 
1680  \param t <point-2d> A test point coordinate [x, y].
1681 
1682  \returns <boolean> \b true when the point is \em inside the polygon and
1683  \b false otherwise.
1684 
1685  \details
1686 
1687  Tests point inclusion by summing the signed angles subtended by
1688  each polygon edge as seen from the test point \p t. For a point
1689  strictly inside a simple polygon the absolute sum approximates
1690  360°; for a point outside it approximates 0°. The threshold used is
1691  180° to distinguish the two cases, which is reliable for
1692  non-degenerate simple polygons. Points exactly on an edge produce
1693  an undefined result.
1694 
1695  See [Wikipedia] for more information.
1696 
1697  \amu_eval (${note_p_not_defined})
1698 
1699  \amu_eval (${warning_secondary_shapes})
1700 
1701  [Wikipedia]: https://en.wikipedia.org/wiki/Point_in_polygon
1702 *******************************************************************************/
1703 function polygon_as_is_p_inside
1704 (
1705  c,
1706  p,
1707  t
1708 ) =
1709  let
1710  (
1711  pm = defined_or(p, [consts(len(c))]),
1712 
1713  av =
1714  [
1715  for (k = pm) let (n = len(k))
1716  for (i=[0 : n-1])
1717  let
1718  (
1719  j = (i == 0) ? n-1 : i-1
1720  )
1721  angle_ll([t, c[k[i]]], [t, c[k[j]]])
1722  ],
1723 
1724  sa = abs(sum(av))
1725  )
1726  (sa > 180);
1727 
1728 //! @}
1729 
1730 //----------------------------------------------------------------------------//
1731 // shape transforms
1732 //----------------------------------------------------------------------------//
1733 
1734 //! \name Transforms
1735 //! @{
1736 
1737 //! Convert a polygon in 2D to a polyhedron by adding a height dimension.
1738 /***************************************************************************//**
1739  \param c <points-2d> A list of 2d cartesian coordinates
1740  [[x, y], ...].
1741 
1742  \param p <integer-list-list> An \em optional list of paths that
1743  define one or more closed shapes where each is a list of
1744  coordinate indexes.
1745 
1746  \param h <decimal> The polyhedron height.
1747 
1748  \param centroid <boolean> Center polygon centroid at z-axis.
1749 
1750  \param center <boolean> Center polyhedron height about xy-plane.
1751 
1752  \returns <datastruct> A structure <tt>[points, faces]</tt>, where
1753  \c points are <points-3d> and \c faces are a
1754  <integer-list-list>, suitable for use with \c polyhedron().
1755 
1756  \details
1757 
1758  Extrudes the 2D polygon into a closed 3D polyhedron by duplicating
1759  the coordinate list at two z-levels and constructing bottom, top,
1760  and side faces.
1762  Each path in \p p produces its own set of triangulated cap faces
1763  (bottom and top) via ear-clipping triangulation, and its own band
1764  of quad side faces. This correctly handles multi-path polygons with
1765  holes, provided hole paths carry winding opposite to the primary
1766  path, matching the convention used by \c polygon() and
1767  \c linear_extrude().
1768 
1769  Bottom cap triangles follow each path's own vertex winding; top cap
1770  triangles are reversed so that all face normals point outward.
1771  Side face quad winding is determined per-path by calling
1772  polygon_is_clockwise() on each path's coordinates and signed area.
1773 
1774  \warning Triangulation uses ear clipping, which is correct for
1775  simple (non-self-intersecting) polygons only. If a path
1776  is self-intersecting, a warning is emitted via \b echo
1777  and the triangulation for that path will be incomplete,
1778  producing a non-manifold solid.
1779 
1780  \warning An assertion is raised if any path is degenerate (zero
1781  signed area), because the winding direction cannot be
1782  determined and the resulting polyhedron faces would be
1783  incorrect. Degenerate paths include collinear vertex sets
1784  and self-intersecting shapes whose positive and negative
1785  areas cancel exactly.
1786 
1787  \note Hole correctness depends on the caller providing hole
1788  paths with winding opposite to the primary path, matching
1789  the \c polygon() convention.
1790 
1791  When \p centroid is \b true, the polygon centroid is computed and
1792  subtracted from all x/y coordinates before extrusion, centering the
1793  shape on the z-axis.
1794 
1795  When \p center is \b true the z-range is [-h/2, h/2]; otherwise it
1796  is [0, h].
1797 
1798  \amu_eval (${note_p_not_defined})
1799 *******************************************************************************/
1801 (
1802  c,
1803  p,
1804  h = 1,
1805  centroid = false,
1806  center = false
1807 ) =
1808  let
1809  (
1810  pm = defined_or(p, [consts(len(c))]),
1811  pn = len([for (pi = pm) for (ci = pi) 1]), // total vertex count across all paths
1812 
1813  po = (centroid == true) ? polygon_centroid(c, p) : origin2d,
1814  zr = (center == true) ? [-h/2, h/2] : [0, h],
1815 
1816  // per-path flat index base within one z-layer
1817  po_offsets =
1818  [
1819  for (i = [0 : len(pm)-1])
1820  len([for (j = [0 : i-1]) for (ci = pm[j]) 1])
1821  ],
1822 
1823  // per-path winding: true = clockwise, determined via polygon_is_clockwise()
1824  // so that this function stays consistent with the public API. An explicit
1825  // assert guards the undef case (degenerate / zero-area path) so that the
1826  // silent (undef < 0) == false fallback can never produce wrong face winding.
1827  cw_per_path =
1828  [
1829  for (pi = pm)
1830  let (cw = polygon_is_clockwise(c, [pi]))
1831  assert
1832  (
1833  !is_undef(cw),
1834  "degenerate path (zero signed area) — winding cannot be determined."
1835  )
1836  cw
1837  ],
1838 
1839  // points: z=zr[0] layer followed by z=zr[1] layer, preserving path order
1840  pp =
1841  [
1842  for (zi = zr) for (pi = pm)
1843  for (ci = pi) concat(c[ci] - po, zi)
1844  ],
1845 
1846  pf =
1847  [
1848  // bottom cap: ear-clipped triangles per path, base winding
1849  for (k = [0 : len(pm)-1])
1850  for (tri = _polygon_cap_triangles
1851  (
1852  c = c,
1853  path = pm[k],
1854  cw = cw_per_path[k],
1855  flip = false,
1856  offset = po_offsets[k]
1857  ))
1858  tri,
1859 
1860  // top cap: ear-clipped triangles per path, winding reversed for outward normals
1861  for (k = [0 : len(pm)-1])
1862  for (tri = _polygon_cap_triangles
1863  (
1864  c = c,
1865  path = pm[k],
1866  cw = cw_per_path[k],
1867  flip = true,
1868  offset = po_offsets[k] + pn
1869  ))
1870  tri,
1871 
1872  // side faces: one quad per edge, per path, no cross-path index bleed
1873  for (k = [0 : len(pm)-1])
1874  let (pi = pm[k], pl = len(pi), base = po_offsets[k])
1875  for (li = [0 : pl-1])
1876  let (ci = base + li, ni = base + (li+1)%pl)
1877  (cw_per_path[k] == true) ?
1878  [ci, ci+pn, ni+pn, ni]
1879  : [ci, ni, ni+pn, ci+pn]
1880  ]
1881  )
1882  [pp, pf];
1883 
1884 //! @}
1885 
1886 //----------------------------------------------------------------------------//
1887 // profiles
1888 //----------------------------------------------------------------------------//
1889 
1890 //! \name Profiles
1891 //! @{
1892 
1893 //! Compute coordinates for a constant radius vertex round between two edge vectors in 2D.
1894 /***************************************************************************//**
1895  \param r <decimal> The round radius.
1896 
1897  \param m <integer> The round mode.
1898 
1899  \param o <point-2d> The vertex coordinate [x, y] at which the two
1900  edge vectors meet (the corner being rounded).
1901 
1902  \param v1 <line-2d | decimal> The first edge direction.
1903  A 2d line, vector, or decimal angle.
1904 
1905  \param v2 <line-2d | decimal> The second edge direction.
1906  A 2d line, vector, or decimal angle.
1907 
1908  \param fn <integer> The number of [facets] \‍(optional\‍).
1909 
1910  \param cw <boolean> Coordinate point ordering. When \b true the
1911  list runs from the inflection point on edge 1 toward
1912  the inflection point on edge 2; when \b false the order
1913  is reversed.
1914 
1915  \returns <points-2d> A list of coordinates points [[x, y], ...]
1916  beginning at the inflection point on \p v1, followed by the
1917  arc (or chamfer) segment, and ending at the inflection point
1918  on \p v2. These points replace the original corner vertex
1919  in a polygon path.
1920 
1921  \details
1922 
1923  Computes the replacement coordinate sequence for a single polygon
1924  corner at \p o between edges \p v1 and \p v2. Normally, edge angle
1925  1 should be less than edge angle 2.
1926 
1927  The round mode may be one of the following:
1928 
1929  mode | name | description
1930  :---:|:-----------:|:--------------------------------
1931  1 | fillet | concave arc (inward, tangent to both edges)
1932  2 | round | convex arc (outward, tangent to both edges)
1933  3 | chamfer | straight bevel between the two inflection points
1934 
1935  [facets]: \ref get_fn()
1936 *******************************************************************************/
1937 function polygon_round_eve_p
1938 (
1939  r = 1,
1940  m = 1,
1941  o = origin2d,
1942  v1 = x_axis2d_uv,
1943  v2 = y_axis2d_uv,
1944  fn,
1945  cw = true
1946 ) =
1947  let
1948  (
1949  // create vectors if numerical angles have been specified.
1950  va1 = is_number(v1) ? [cos(v1), sin(v1)] : v1,
1951  va2 = is_number(v2) ? [cos(v2), sin(v2)] : v2,
1952 
1953  // triangle coordinates for edge corner in cw order
1954  etc = [o + r*unit_l(va1), o, o + r*unit_l(va2)],
1955 
1956  // tangent circle radius
1957  tcr = (m == 1) ?triangle2d_exradius(etc, 2) : 0,
1958 
1959  // tangent circle center coordinate
1960  tcc = (m == 1) ?(o-r/(r-tcr) * triangle2d_excenter(etc, 2)) * (tcr-r)/tcr : o,
1961 
1962  // distance from vertex to inflection points
1963  vim = (m == 1) ? sqrt( pow(distance_pp(o, tcc),2) - pow(r,2) ) : r,
1964 
1965  // inflection coordinates
1966  tc1 = o + vim*unit_l(va1),
1967  tc2 = o + vim*unit_l(va2),
1968 
1969  // vertex rounding coordinate point list
1970  vpl = (m == 1) ? polygon_arc_sweep_p(r=r, o=tcc, v1=[tcc, tc1], v2=[tcc, tc2], fn=fn, cw=true)
1971  : (m == 2) ? polygon_arc_sweep_p(r=r, o=tcc, v1=[tcc, tc1], v2=[tcc, tc2], fn=fn, cw=false)
1972  : empty_lst,
1973 
1974  // cw ordering
1975  pp = concat([tc1], vpl, [tc2])
1976  )
1977  (cw == true) ? pp : reverse(pp);
1978 
1979 //! Compute coordinates that round all of the vertices between each adjacent edges in 2D.
1980 /***************************************************************************//**
1981  \param c <points-2d> A list of \em n 2d cartesian coordinates
1982  [[x1, y1], [x2, y2], ..., [xn, yn]].
1983 
1984  \param vr <decimal-list-n | decimal> The vertices rounding radius.
1985  A list [v1r, v2r, v3r, ... vnr] of \em n decimals or a
1986  single decimal for (v1r=v2r=v3r= ... =vnr). Undefined
1987  vertices are not rounded.
1988 
1989  \param vrm <integer-list-n | integer> The vertices rounding mode.
1990  A list [v1rm, v2rm, v3rm, ... vnrm] of \em n integers
1991  or a single integer for (v1rm=v2rm=v3rm= ... =vnrm).
1992  Mode 0 returns the vertex coordinate unchanged and
1993  disables rounding for that vertex regardless of \p vr.
1994  Undefined vertices are not rounded.
1995 
1996  \param vfn <integer-list-n> The vertices arc fragment number.
1997  A list [v1fn, v2fn, v3fn, ... vnfn] of \em n integers
1998  or a single integer for (v1fn=v2fn=v3fn= ... =vnfn).
1999  When any \p vfn entry is \b undef, the special
2000  variables \p $fa, \p $fs, and \p $fn control facet
2001  generation for that vertex.
2002 
2003  \param w <boolean> Wrap-at-end during 3-point coordinate selection.
2004  When \b true (default), the first and last vertices are
2005  included in the rounding sequence, connecting the polygon
2006  end back to its start. When \b false, the first and last
2007  vertices are returned unmodified, which is useful for
2008  open paths where the endpoints should remain fixed.
2009 
2010  \param cw <boolean> Polygon vertex ordering.
2011 
2012  \returns <points-2d> A new list of coordinates points [[x, y], ...]
2013  that define the polygon with rounded vertices.
2014 
2015  \details
2016 
2017  Assumes polygon is defined in 2D space on the x-y plane. There
2018  should be no repeating adjacent vertices along the polygon path
2019  (ie: no adjacent vertex with identical coordinates). Any vertex
2020  determined to be collinear with its adjacent previous and next
2021  vertex is returned unmodified.
2022 
2023  Each vertex may be individually rounded using one of the following
2024  modes:
2025 
2026  mode | name | description
2027  :---:|:-------------------:|:--------------------------------------
2028  0 | none | return vertex unchanged
2029  1 | round | previous to next edge round
2030  2 | e-hollow / i-circle | previous to next edge inverse round
2031  3 | n-fillet | next edge pass return fillet
2032  4 | p-fillet | previous edge pass return fillet
2033  5 | chamfer | previous to next edge bevel
2034  6 | e-circle / i-hollow | previous to next edge inverse round
2035  7 | n-round | next edge pass return round
2036  8 | p-round | previous edge pass return round
2037  9 | n-chamfer | next edge pass return bevel
2038  10 | p-chamfer | previous edge pass return bevel
2040  The following diagrams demonstrate each rounding mode by on the
2041  upper right vertex of a rectangular polygon.
2042 
2043  \amu_define title (Rounding modes)
2044  \amu_combine image_views (prefix="top" "1 2 3 4 5 6 7 8 9 10")
2045  \amu_define image_size (vga)
2046  \amu_define scope_id (polygon_round_eve_all_p_modes)
2047  \amu_define output_scad (false)
2048  \amu_define html_image_w (128)
2049  \amu_define image_columns (5)
2050 
2051  \amu_include (include/amu/scope_diagrams_3d.amu)
2052 
2053  \amu_undefine (html_image_w image_columns)
2054 
2055  Vertex arc fragments can be specified using \p vfn. When any \p vfn
2056  entry is \b undef, the special variables \p $fa, \p $fs, and \p $fn
2057  control facet generation. Each vertex is processed using 3-point
2058  selection (the previous and following vertex). Collinear vertices
2059  (where the previous, current, and next points are co-linear) are
2060  automatically detected and returned unmodified regardless of the
2061  specified \p vr and \p vrm values. The resulting triangle \ref
2062  triangle2d_incenter "incircles" and \ref triangle2d_excenter
2063  "excircles" are used to create the round and fillet \ref
2064  polygon_arc_sweep_p "arc" segments. All arcs and chamfers use constant
2065  radius.
2066 
2067  \amu_define title (Rounding example)
2068  \amu_define image_views (top)
2069  \amu_define image_size (sxga)
2070  \amu_define scope_id (polygon_round_eve_all_p_example)
2071  \amu_define output_scad (true)
2072 
2073  \amu_include (include/amu/scope_diagrams_3d.amu)
2074 *******************************************************************************/
2075 function polygon_round_eve_all_p
2076 (
2077  c,
2078  vr = 0,
2079  vrm = 1,
2080  vfn,
2081  w = true,
2082  cw = true
2083 ) =
2084  let
2085  (
2086  // constant vertex rounding radius, mode, and facets
2087  crr = is_scalar(vr) ? vr : 0,
2088  crm = is_scalar(vrm) ? vrm : 0,
2089  cfn = is_scalar(vfn) ? vfn : undef,
2090 
2091  // function assumes cw order, reverse if required
2092  cp = (cw == true) ? c : reverse(c),
2093 
2094  // adjacent vertices sequence [ [v[n-1], v[n], v[n+1]] ... ]
2095  avl = sequence_ns(cp, 3, w=w),
2096 
2097  // polygon coordinate point list
2098  ppl =
2099  [
2100  for ( i = [0 : len(avl)-1] )
2101  let
2102  (
2103  av = avl[i], // vertices [vp, vc, vn]
2104 
2105  vp = first(av), // vertex coordinate v[n-1]
2106  vc = second(av), // vertex coordinate v[n]
2107  vn = third(av), // vertex coordinate v[n+1]
2108 
2109  il = is_left_ppp(vp, vn, vc), // identify position of vc
2110 
2111  rr = defined_e_or(vr, i, crr), // vertex rounding radius
2112  rm = (rr == 0) ? 0 // vertex rounding mode
2113  : (il == 0) ? 0 // vp,vc,vn collinear, set rm=0
2114  : defined_e_or(vrm, i, crm),
2115  fn = defined_e_or(vfn, i, cfn), // vertex rounding arc fragments
2116 
2117  // reverse arc sweep on interior corners
2118  // not relevant for rm={0|5|9|10}
2119  ras = (il < 0),
2120 
2121  // tangent circle radius
2122  tcr = (rm == 0) ? 0
2123  : (rm == 1 || rm == 2) ?
2125  : (rm == 3) ?
2126  triangle2d_exradius(av, 1)
2127  : (rm == 4) ?
2128  triangle2d_exradius(av, 3)
2129  : 0,
2130 
2131  // tangent circle center coordinate
2132  tcc = (rm == 0) ? origin2d
2133  : (rm == 1 || rm == 2) ?
2134  (vc-rr/(rr-tcr) * triangle2d_incenter(av)) * (tcr-rr)/tcr
2135  : (rm == 3) ?
2136  (vc-rr/(rr-tcr) * triangle2d_excenter(av, 1)) * (tcr-rr)/tcr
2137  : (rm == 4) ?
2138  (vc-rr/(rr-tcr) * triangle2d_excenter(av, 3)) * (tcr-rr)/tcr
2140 
2141  // distance from vertex to inflection points
2142  vim = (rm == 0) ? 0
2143  : (rm <= 4) ?
2144  sqrt( pow(distance_pp(vc, tcc),2) - pow(rr,2) )
2145  : rr,
2146 
2147  // inflection coordinates
2148  tc1 = (rm == 0 || rm > 10) ? origin2d
2149  : (rm == 3 || rm == 7 || rm == 9) ?
2150  vc + vim * unit_l([vp, vc])
2151  : vc + vim * unit_l([vc, vp]),
2152 
2153  tc2 = (rm == 0 || rm > 10) ? origin2d
2154  : (rm == 4 || rm == 8 || rm == 10) ?
2155  vc + vim * unit_l([vn, vc])
2156  : vc + vim * unit_l([vc, vn]),
2157 
2158  // vertex rounding coordinate point list
2159  vpl = (rm == 0 || rm > 10) ? [vc]
2160  : (rm == 1) ?
2161  polygon_arc_sweep_p(r=rr, o=tcc, v1=[tcc, tc1], v2=[tcc, tc2], fn=fn, cw=!ras)
2162  : (rm == 2 || rm == 3 || rm == 4) ?
2163  polygon_arc_sweep_p(r=rr, o=tcc, v1=[tcc, tc1], v2=[tcc, tc2], fn=fn, cw=ras)
2164  : (rm == 6 || rm == 7 || rm == 8) ?
2165  polygon_arc_sweep_p(r=rr, o=vc, v1=[vc, tc1], v2=[vc, tc2], fn=fn, cw=!ras)
2166  : [tc1, tc2]
2167  )
2168  vpl
2169  ],
2170 
2171  // polygon points
2172  pp = merge_s( ppl )
2173  )
2174  (cw == true) ? pp : reverse(pp);
2175 
2176 //! Generate 2D coordinate points along a line with periodic waveform lateral displacement.
2177 /***************************************************************************//**
2178  \param p1 <point-2d> The line initial coordinate [x, y].
2179 
2180  \param p2 <point-2d> The line terminal coordinate [x, y].
2181 
2182  \param p <decimal> Period length; One full waveform cycle spans
2183  this distance along the line arc length.
2184 
2185  \param a <decimal-list-3 | decimal> Amplitude configuration; a
2186  list [\p ma, \p na, \p oa] or a single decimal for (\p
2187  ma) (see below).
2188 
2189  \param w <datastruct | integer> Waveform shape configuration;
2190  a list [\p shape, ...] or a single integer for (\p shape)
2191  (see below).
2192 
2193  \param m <datastruct | integer> Time-axis remapping mode
2194  configuration; a list [\p remap, ...] or a single integer
2195  for (\p remap) (see below).
2196 
2197  \param t <decimal-list-2 | decimal> Sampling range configuration;
2198  a list [\p t_min, \p t_max] or a single decimal for (\p
2199  t_min) (see below).
2200 
2201  \param fn <integer> Period fragment count; overrides the
2202  automatically computed step count (see below).
2203 
2204  \returns <points-2d> A list of coordinate points [[x, y], ...]
2205  representing the waveform-displaced line, suitable for
2206  direct use with \c polygon().
2207 
2208  \details
2209 
2210  Computes a sequence of 2D points by walking arc-length steps along
2211  the line defined by \p p1 and \p p2, displacing each point
2212  laterally (perpendicular to the line direction) by a shaped
2213  periodic waveform. The unit normal direction is 90° CCW from the
2214  line tangent.
2215 
2216  The displacement formula applied at each point is:
2217 
2218  \code
2219  offset = oa + ma × sign(wave) × |wave|^(1/na)
2220  \endcode
2221 
2222  where \c wave is the waveform value at the remapped period position
2223  \c u member of [0, 1).
2224 
2225  Packed parameters \p a, \p w, \p m, and \p t each accept either a
2226  single scalar (selecting only the primary value) or a list where
2227  each index corresponds to a sub-parameter as described in the
2228  sections below.
2229 
2230  ## Multi-value and structured parameters
2231 
2232  ### a
2233 
2234  e | data type | default value | parameter description
2235  ---:|:---------:|:-------------:|:------------------------------------
2236  0 | decimal | 1 | \p ma : maximum amplitude; peak perpendicular displacement from the line
2237  1 | decimal | 1 | \p na : nonlinear amplitude exponent; applied after waveform evaluation
2238  2 | decimal | 0 | \p oa : perpendicular offset; shifts the entire waveform baseline
2239 
2240  #### a[1]:na
2241 
2242  v | description
2243  ---:|:---------------------------------------
2244  <1 | peaks flatten, waveform widens
2245  =1 | linear, no reshaping
2246  >1 | peaks sharpen, waveform narrows
2247 
2248  ### w
2249 
2250  #### Shape selection
2251 
2252  v | shape | sub-parameters
2253  ---:|:----------------:|:-------------------:
2254  0 | none | (no displacement)
2255  1 | sine | -
2256  2 | triangle | -
2257  3 | square | -
2258  4 | sawtooth | -
2259  5 | sawtooth reverse | -
2260  6 | pulse | \p duty
2261  7 | trapezoid | \p rise, \p hold, \p fall
2262  8 | sine abs | -
2263  9 | sine half | -
2264  10 | lookup table | \p lut
2265 
2266  All waveforms are normalized to [-1, 1] before amplitude scaling,
2267  so \p ma always represents the true peak displacement regardless of
2268  shape.
2269 
2270  #### Sub-parameters: pulse (shape=6)
2271 
2272  e | data type | default value | parameter description
2273  ---:|:---------:|:-------------:|:------------------------------------
2274  1 | decimal | 1/2 | \p duty : fraction of the period spent at +1; range (0, 1)
2275 
2276  #### Sub-parameters: trapezoid (shape=7)
2277 
2278  e | data type | default value | parameter description
2279  ---:|:---------:|:-------------:|:------------------------------------
2280  1 | decimal | 1/4 | \p rise : fraction of the period ramping from -1 to +1
2281  2 | decimal | \p rise | \p hold : fraction of the period at +1 (top hold)
2282  3 | decimal | \p hold | \p fall : fraction of the period ramping from +1 to -1
2283 
2284  \warning \p w_rise + \p w_hold + \p w_fall must be <= 1.0. The
2285  remainder is the implicit bottom-hold duration.
2286 
2287  #### Sub-parameters: lookup table (shape=10)
2288 
2289  e | data type | default value | parameter description
2290  ---:|:------------:|:-------------:|:------------------------------------
2291  1 | decimal-list | [0, 1] | \p lut : list of amplitude values defining one period
2292 
2293  The values should be in [-1, 1] with a minimum 2 entries. The table
2294  is treated as periodic with interpolation between the last and
2295  first entry handles smooth looping
2296 
2297  ### m
2298 
2299  #### Mode selection
2300 
2301  v | mode | description | sub-parameters
2302  ---:|:-----:|:-------------------------------------------------------------------------:|:---------------
2303  0 | none | no remapping; waveform evaluated at natural u | -
2304  1 | phase | shifts peak and zero-crossings by a period fraction | \p shift
2305  2 | skew | warps time axis; peak repositioned, zero-crossings fixed at boundaries | \p shift
2306  3 | blend | interpolates in u-space between phase and skew before waveform evaluation | \p phase, \p skew, \p blend
2307 
2308  #### Sub-parameters: phase (mode=1)
2309 
2310  e | data type | default value | parameter description
2311  ---:|:---------:|:-------------:|:------------------------------------
2312  1 | decimal | 1/2 | \p shift : fraction of the period to shift
2313 
2314  The range is [0, 1] where \b 0.0 = no shift, \b 0.5 = half-period
2315  shift (waveform inverts), \b 1.0 = full shift identical to \b 0.0.
2316 
2317  #### Sub-parameters: skew (mode=2)
2318 
2319  e | data type | default value | parameter description
2320  ---:|:---------:|:-------------:|:------------------------------------
2321  1 | decimal | 1/2 | \p shift : fractional position within the period where the peak lands
2322 
2323  The range is (0, 1), clamped internally at \p grid_fine with \b
2324  0.25 = fast rise / slow fall, \b 0.5 = symmetric no skew, \b 0.75 =
2325  slow rise / fast fall.
2326 
2327  #### Sub-parameters: blend (mode=3)
2328 
2329  e | data type | default value | parameter description
2330  ---:|:---------:|:-------------:|:------------------------------------
2331  1 | decimal | 1/2 | \p phase : phase-shift amount
2332  2 | decimal | 1/2 | \p skew : skew target position
2333  3 | decimal | 1/2 | \p blend : interpolation weight
2334 
2335  In blend mode, three sub-parameters work together to shape the
2336  waveform. \p phase shifts the peak and zero-crossings together by a
2337  fraction of the period, range [0, 1]. \p skew warps the time axis
2338  to reposition the peak at a fractional position within the period
2339  while the zero-crossings remain fixed at the period boundaries,
2340  range (0, 1). \p blend controls the interpolation weight between
2341  the two axes; \b 0.0 yields pure phase shift, \b 1.0 yields pure
2342  skew, and \b 0.5 applies an equal mix of both.
2343 
2344  ### t
2345 
2346  e | data type | default value | parameter description
2347  ---:|:---------:|:-------------:|:------------------------------------
2348  0 | decimal | 0 | \p t_min : start of the sampling range;
2349  1 | decimal | \p t_min + 1 | \p t_max : end of the sampling range;
2350 
2351  The arc length at \p t_min = \p t_min × \|p2 - p1\|; \b 0.0 =
2352  starts at \p p1; negative values extend behind \p p1. The arc
2353  length at \p t_max = \p t_max × \|p2 - p1\|; \b 1.0 = ends at \p
2354  p2; values above \b 1.0 extend beyond \p p2.
2355 
2356  The waveform phase is continuous across both boundaries. Period
2357  counting begins at \p p1 (t=0) and extends in both directions.
2358  Setting \p t_min < 0 or \p t_max > 1 does not clamp; the line
2359  direction and waveform extend naturally and indefinitely in either
2360  direction. The segment \p p1 to \p p2 defines orientation and scale
2361  only; it is not a hard boundary.
2362 
2363  ### fn
2364 
2365  When undefined, the fragment count is derived from \p get_fn(\p
2366  ma). See get_fn() for more information.
2367 
2368  The total number of sampled points is:
2369 
2370  \code
2371  steps = ceil( (len × (t_max - t_min) / p) × fn ) + 1
2372  \endcode
2373 
2374  where \p len = \|p2 - p1\|. Choosing an appropriate value:
2375 
2376  - \b Period: at minimum 20 fragments per period for smooth sine
2377  curves; sawtooth and triangle need fewer.
2378  - \b Nonlinearity: for \p na > 3, consider doubling the base
2379  fragment count to resolve sharp peaks.
2380  - \b Waveform: square and pulse have hard transitions that no
2381  fragment count can perfectly resolve; fragment count affects
2382  only the flat regions for these shapes.
2383 
2384  \amu_define title (Line wave example)
2385  \amu_define image_views (top)
2386  \amu_define image_size (sxga)
2387  \amu_define scope_id (polygon_line_wave_p)
2388  \amu_define output_scad (true)
2389 
2390  \amu_include (include/amu/scope_diagrams_3d.amu)
2391 *******************************************************************************/
2392 function polygon_line_wave_p
2393 (
2394  p1 = origin2d,
2395  p2 = x_axis2d_uv,
2396 
2397  p = 1,
2398  a = 1,
2399 
2400  w = 1,
2401  m = 0,
2402 
2403  t,
2404 
2405  fn
2406 ) =
2407  let
2408  (
2409  // decode/unpack parameters
2410 
2411  // wave period
2412  wp = defined_or(p, 1),
2413 
2414  // max amplitude, nonlinear amplitude
2415  ma = defined_eon_or(a, 0, 1),
2416  na = defined_e_or (a, 1, 1),
2417  oa = defined_e_or (a, 2, 0),
2418 
2419  // waveform shape
2420  shape = defined_eon_or(w, 0, 1),
2421 
2422  // remapping mode
2423  remap = defined_eon_or(m, 0, 0),
2424 
2425  // sampling rage start and end
2426  t_min = defined_eon_or(t, 0, 0),
2427  t_max = defined_e_or (t, 1, t_min + 1),
2428 
2429  // waveform period fragments
2430  line_fn = defined_or(fn, get_fn( ma )),
2431 
2432  // line basis vectors
2433  dx = p2[0] - p1[0],
2434  dy = p2[1] - p1[1],
2435  len = sqrt(dx*dx + dy*dy),
2436 
2437  // line steps; cycles * fragments per cycle
2438  steps = ceil( (len * (t_max - t_min) / wp) * line_fn ),
2439 
2440  // unit tangent (along the line)
2441  tx = dx / len,
2442  ty = dy / len,
2443 
2444  // unit normal (perpendicular, 90° CCW)
2445  nx = -ty,
2446  ny = tx,
2447 
2448  safe_n = max(na, grid_fine),
2449 
2450  // hoist skew log constants (used in remap branches)
2451  safe_s = (remap == 2 || remap == 3) ?
2452  let( s = defined_e_or(m, 1, 1/2) ) max(min(s, 1 - grid_fine), grid_fine)
2453  : 0,
2454  log_s = (remap == 2) ? log(0.5) / log(safe_s) : 0,
2455  log_1ms = (remap == 2) ? log(0.5) / log(1 - safe_s) : 0,
2456 
2457  // hoist blend log constants (remap == 3); computed once here rather than
2458  // repeating log() calls on every loop iteration.
2459  blend_m_phase = (remap == 3) ? defined_e_or(m, 1, 1/2) : 0,
2460  blend_m_skew = (remap == 3) ? defined_e_or(m, 2, 1/2) : 0,
2461  blend_m_blend = (remap == 3) ? defined_e_or(m, 3, 1/2) : 0,
2462  blend_safe_skew = (remap == 3) ?
2463  max(min(blend_m_skew, 1 - grid_fine), grid_fine)
2464  : 0,
2465  blend_log_s = (remap == 3) ? log(0.5) / log(blend_safe_skew) : 0,
2466  blend_log_1ms = (remap == 3) ? log(0.5) / log(1 - blend_safe_skew) : 0
2467  )
2468  // return coordinate points: base point + lateral displacement
2469  [
2470  for (i = [0 : steps])
2471  let
2472  (
2473  // line point
2474  tp = t_min + (t_max - t_min) * (i / steps),
2475 
2476  // arc length along the line
2477  arc = tp * len,
2478 
2479  u_raw = (arc / wp) - floor(arc / wp),
2480  u = u_raw < 0 ? u_raw + 1 : u_raw,
2481 
2482  // time axis remap
2483  u_remapped =
2484  // phase
2485  remap == 1 ?
2486  let
2487  (
2488  m_shift = defined_e_or(m, 1, 1/2)
2489  )
2490  (u + m_shift) - floor(u + m_shift)
2491  // skew
2492  : remap == 2 ?
2493  u < safe_s ?
2494  0.5 * pow(u / safe_s, log_s)
2495  : 0.5 + 0.5 * pow((u - safe_s) / (1 - safe_s), log_1ms)
2496  // blend
2497  : remap == 3 ?
2498  let
2499  (
2500  u_phase = (u + blend_m_phase) - floor(u + blend_m_phase),
2501  u_skew = u < blend_safe_skew ?
2502  0.5 * pow(u / blend_safe_skew, blend_log_s)
2503  : 0.5 + 0.5 * pow((u - blend_safe_skew) / (1 - blend_safe_skew), blend_log_1ms)
2504  )
2505  (1 - blend_m_blend) * u_phase + blend_m_blend * u_skew
2506  // default is no remapping
2507  : u,
2508 
2509  // raw waveform; all accept u in [0,1) and return a value in [-1, 1]
2510  wave =
2511  let ( v = u_remapped )
2512  // sine
2513  shape == 1 ?
2514  sin(360 * v)
2515  // triangle
2516  : shape == 2 ?
2517  v < 0.25 ? 4 * v : v < 0.75 ? 2 - 4 * v : 4 * v - 4
2518  // square
2519  : shape == 3 ?
2520  v < 0.5 ? 1 : -1
2521  // sawtooth
2522  : shape == 4 ?
2523  2 * v - 1
2524  // sawtooth_reverse
2525  : shape == 5 ?
2526  1 - 2 * v
2527  // pulse
2528  : shape == 6 ?
2529  let
2530  (
2531  w_duty = defined_e_or(w, 1, 1/2)
2532  )
2533  v < w_duty ? 1 : -1
2534  // trapezoid
2535  : shape == 7 ?
2536  let
2537  (
2538  w_rise = defined_e_or(w, 1, 1/4),
2539  w_hold = defined_e_or(w, 2, w_rise),
2540  w_fall = defined_e_or(w, 3, w_hold)
2541  )
2542  v < w_rise ? -1 + 2 * (v / w_rise)
2543  : v < w_rise + w_hold ? 1
2544  : v < w_rise + w_hold + w_fall ? 1 - 2 * ((v - w_rise - w_hold) / w_fall)
2545  : -1
2546  // sine_abs
2547  : shape == 8 ?
2548  2 * abs(sin(180 * v)) - 1
2549  // sine_half
2550  : shape == 9 ?
2551  v < 0.5 ? 2 * sin(360 * v) - 1 : -1
2552  // lookup table
2553  : shape == 10 ?
2554  let
2555  (
2556  w_lut = defined_e_or(w, 1, [0, 1]),
2557 
2558  count = len(w_lut),
2559  scaled = v * count,
2560 
2561  i0 = floor(scaled) % count,
2562  i1 = (i0 + 1) % count, // wraps for smooth looping
2563  sf = scaled - floor(scaled),
2564  v0 = w_lut[i0],
2565  v1 = w_lut[i1]
2566  )
2567  v0 + sf * (v1 - v0)
2568  // default is no waveform
2569  : 0,
2570 
2571  signv = wave < 0 ? -1 : (wave > 0 ? 1 : 0),
2572  offset = oa + ma * signv * pow(abs(wave), 1 / safe_n)
2573  )
2574  [ p1.x + arc * tx + offset * nx, p1.y + arc * ty + offset * ny ]
2575  ];
2576 
2577 //! @}
2578 
2579 //! @}
2580 //! @}
2581 
2582 //----------------------------------------------------------------------------//
2583 // openscad-amu auxiliary scripts
2584 //----------------------------------------------------------------------------//
2585 
2586 /*
2587 BEGIN_SCOPE validate;
2588  BEGIN_OPENSCAD;
2589  include <omdl-base.scad>;
2590  include <common/validation.scad>;
2591 
2592  echo( str("openscad version ", version()) );
2593  for (i=[1:24]) echo( "not tested:" );
2594 
2595  // end_include
2596  END_OPENSCAD;
2597 
2598  BEGIN_MFSCRIPT;
2599  include --path "${INCLUDE_PATH}" {var_init,var_gen_term}.mfs;
2600  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
2601  END_MFSCRIPT;
2602 END_SCOPE;
2603 */
2604 
2605 /*
2606 BEGIN_SCOPE polygon_line_wave_p;
2607  BEGIN_OPENSCAD;
2608  include <omdl-base.scad>;
2609 
2610  p = polygon_line_wave_p
2611  (
2612  p2 = [200, 0],
2613  p = 40,
2614  a = [15, 1]
2615  );
2616 
2617  polygon(p);
2618 
2619  // end_include
2620  END_OPENSCAD;
2621 
2622  BEGIN_MFSCRIPT;
2623  include --path "${INCLUDE_PATH}" {var_init,var_gen_png2eps}.mfs;
2624  table_unset_all sizes;
2625 
2626  images name "sizes" types "sxga";
2627  views name "views" views "top diag";
2628 
2629  variables set_opts_combine "sizes views";
2630  variables add_opts "--viewall --autocenter";
2631 
2632  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
2633  END_MFSCRIPT;
2634 END_SCOPE;
2635 */
2636 
2637 /*
2638 BEGIN_SCOPE polygon_round_eve_all_p_modes;
2639  BEGIN_OPENSCAD;
2640  include <omdl-base.scad>;
2641 
2642  $fn=36;
2643 
2644  mode = 0;
2645 
2646  pp = polygon_trapezoid_p(10, 7);
2647  rp = polygon_round_eve_all_p( pp, 2.5, [0, 0, mode, 0] );
2648 
2649  polygon( rp );
2650 
2651  // end_include
2652  END_OPENSCAD;
2653 
2654  BEGIN_MFSCRIPT;
2655  include --path "${INCLUDE_PATH}" {var_init,var_gen_png2eps}.mfs;
2656  table_unset_all sizes;
2657 
2658  images name "sizes" types "vga";
2659  views name "views" views "top";
2660  defines name "modes" define "mode" integers "0 1 2 3 4 5 6 7 8 9 10";
2661 
2662  variables set_opts_combine "sizes views modes";
2663  variables add_opts "--viewall --autocenter";
2664 
2665  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
2666  END_MFSCRIPT;
2667 END_SCOPE;
2668 */
2669 
2670 /*
2671 BEGIN_SCOPE polygon_round_eve_all_p_example;
2672  BEGIN_OPENSCAD;
2673  include <omdl-base.scad>;
2674 
2675  $fn=36;
2676 
2677  c = [[1,1], [1,10], [10,12], [18,2]];
2678  r = [1, 1, 5, 8];
2679  m = [2, 3, 4, 3];
2680  n = [3, 8, undef, undef];
2681 
2682  p = polygon_round_eve_all_p(c=c, vr=r, vrm=m, vfn=n);
2683 
2684  polygon( p );
2685 
2686  // end_include
2687  END_OPENSCAD;
2688 
2689  BEGIN_MFSCRIPT;
2690  include --path "${INCLUDE_PATH}" {var_init,var_gen_png2eps}.mfs;
2691  table_unset_all sizes;
2692 
2693  images name "sizes" types "sxga";
2694  views name "views" views "top";
2695 
2696  variables set_opts_combine "sizes views";
2697  variables add_opts "--viewall --autocenter";
2698 
2699  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
2700  END_MFSCRIPT;
2701 END_SCOPE;
2702 */
2703 
2704 //----------------------------------------------------------------------------//
2705 // end of file
2706 //----------------------------------------------------------------------------//
origin2d
<point-2d> The origin point coordinate in 2d Euclidean space.
Definition: constants.scad:407
x_axis2d_uv
<vector-2d> The unit vector of the positive x-axis in 2d Euclidean space.
Definition: constants.scad:410
y_axis2d_uv
<vector-2d> The unit vector of the positive y-axis in 2d Euclidean space.
Definition: constants.scad:413
pi
<decimal> The ratio of a circle's circumference to its diameter.
Definition: constants.scad:198
empty_lst
<list> A list with no values (the empty list).
Definition: constants.scad:304
grid_fine
OpenSCAD fine grid limit.
Definition: constants.scad:310
function line_tp(l)
Return the terminal point of a line or vector.
function angle_ll(l1, l2, s=true)
Compute the angle between two lines or vectors in a 3d or 2d-space.
function line_ip(l)
Return the initial point of a line or vector.
function cross_ll(l1, l2)
Compute the cross product of two lines or vectors in a 3d or 2d-space.
function is_left_ppp(p1, p2, p3)
Test if a point is left, on, or right of an infinite line in a 2d-space.
function unit_l(l)
Compute the normalized unit vector of a line or vector.
function interpolate2d_l_pp(p1, p2, x, y)
Linearly interpolate along a line established by two points in 2d.
function distance_pp(p1, p2)
Compute the distance between two points.
function unique(v)
Return a list of the unique elements of an iterable value.
function defined_e_or(v, i, d)
Returns an element from an iterable if it exists, or a default value if not.
function third(v)
Return the third element of an iterable value.
function count(mv, v, s=true, i)
Count all occurrences of a match value in an iterable value.
function second(v)
Return the second element of an iterable value.
function defined_eon_or(v, i, d)
Returns the list element or scalar numeric value if defined, otherwise returns the default value.
function first(v)
Return the first element of an iterable value.
function sequence_ns(v, n=1, s=1, w=false)
Return a list of all n-element sequential-subsets of an iterable value.
function reverse(v)
Reverse the elements of an iterable value.
function all_len(v, l)
Test if all elements of an iterable value are iterable with a fixed length.
function any_equal(v, cv)
Test if any element of an iterable value equal a comparison value.
function almost_eq_nv(v1, v2, p=6)
Test to see if two numerical vectors are sufficiently equal.
function consts(l, v, u=false)
Create a list of constant or incrementing elements.
function strl(v)
Convert a list of values to a concatenated string.
function merge_s(v, r=false)
Serially merge the elements of a list.
function sum(v, i1, i2)
Compute the sum of a list of numbers.
function defined_or(v, d)
Return given value, if defined, or a secondary value, if primary is not defined.
function is_scalar(v)
Test if a value is a single non-iterable value.
function is_between(v, l, u)
Test if a numerical value is between an upper and lower bounds.
function is_defined(v)
Test if a value is defined.
function is_number(v)
Test if a value is a number.
function polygon_arc_sweep_p(r=1, o=origin2d, v1=x_axis2d_uv, v2=x_axis2d_uv, fn, cw=true)
Compute coordinates of an arc with constant radius between two vectors in 2D.
function polygon_regular_perimeter(n, r, a)
Compute the perimeter of an n-sided regular polygon in 2D.
function polygon3d_area(c, p, n)
Compute the area of a polygon in a Euclidean 3d-space.
function polygon_bezier_p(c, fn, cw=true)
Compute coordinates for a degree-n Bézier curve in 2D.
function polygon_regular_area(n, r, a)
Compute the area of an n-sided regular polygon in 2D.
function polygon_elliptical_sector_p(r=1, o=origin2d, v1=x_axis2d_uv, v2=x_axis2d_uv, s=true, fn, cw=true)
Compute coordinates for an elliptical sector in 2D.
function polygon_winding(c, p, t)
Compute the winding number of a polygon about a point in a Euclidean 2d-space.
function polygon_trapezoid_p(b=1, h, l=1, a=90, o=origin2d, cw=true)
Compute the coordinates for a rounded trapezoid in 2D space.
function polygon_linear_extrude_pf(c, p, h=1, centroid=false, center=false)
Convert a polygon in 2D to a polyhedron by adding a height dimension.
function polygon_as_is_p_inside(c, p, t)
Test if a point is inside a polygon in a Euclidean 2d-space using angle summation.
function polygon_round_eve_all_p(c, vr=0, vrm=1, vfn, w=true, cw=true)
Compute coordinates that round all of the vertices between each adjacent edges in 2D.
function polygon_regular_p(n, r, a, o=origin2d, ao=0, vr, cw=true)
Compute coordinates for an n-sided regular polygon in 2D.
function polygon_is_convex(c, p)
Test the convexity of a polygon in a Euclidean 2d-space.
function polygon_round_eve_p(r=1, m=1, o=origin2d, v1=x_axis2d_uv, v2=y_axis2d_uv, fn, cw=true)
Compute coordinates for a constant radius vertex round between two edge vectors in 2D.
function polygon_spline_p(c, fn, closed=false, cw=true)
Compute coordinates for a Catmull-Rom spline through a list of knot points in 2D.
function polygon_wn_is_p_inside(c, p, t)
Test if a point is inside a polygon in a Euclidean 2d-space using winding number.
function polygon_line_wave_p(p1=origin2d, p2=x_axis2d_uv, p=1, a=1, w=1, m=0, t, fn)
Generate 2D coordinate points along a line with periodic waveform lateral displacement.
function polygon_line_p(p1=origin2d, p2=x_axis2d_uv, l, x, y, r, fs, ft, fn=1)
Compute coordinates along a line in 2D.
function polygon_arc_blend_p(p1, p2, p3, r=1, fn, cw=true)
Compute coordinates for a circular arc blend between two line segments in 2D.
function polygon_centroid(c, p)
Compute the center of mass of a polygon in a Euclidean 2d-space.
function polygon_arc_fillet_p(r=1, o=origin2d, v1=x_axis2d_uv, v2=y_axis2d_uv, fn, cw=true)
Compute coordinates for a circular fillet arc between two rays in 2D.
function polygon_area(c, p, s=false)
Compute the signed area of a polygon in a Euclidean 2d-space.
function polygon_perimeter(c, p)
Calculate the perimeter length of a polygon in 2d.
function polygon_is_clockwise(c, p)
Test the vertex ordering of a polygon in a Euclidean 2d-space.
function triangle2d_exradius(c, vi=1)
Compute the exradius of a triangle's excircle in 2D.
function triangle2d_excenter(c, vi=1)
Compute the center coordinate of a triangle's excircle in 2D.
function triangle2d_inradius(c)
Compute the inradius of a triangle's incircle in 2D.
function triangle2d_incenter(c)
Compute the center coordinate of a triangle's incircle in 2D.
function get_fn(r=0)
Return facets number for the given arc radius.
function angle(a, from=angle_unit_default, to=angle_unit_base)
Convert an angle value from one unit to another.