omdl  v1.0
OpenSCAD Mechanical Design Library
coordinate.scad
Go to the documentation of this file.
1 //! Coordinate systems and conversions.
2 /***************************************************************************//**
3  \file
4  \author Roy Allen Sutton
5  \date 2017-2024
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 (Coordinates Systems)
31  \amu_define group_brief (Coordinate systems and conversions.)
32 
33  \amu_include (include/amu/doxyg_init_pd_gds_ipg.amu)
34 *******************************************************************************/
35 
36 // auto-tests (append 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 group_references
52  (
53  )
54 *******************************************************************************/
55 
56 // member-wide documentation and conventions
57 /***************************************************************************//**
58  \addtogroup \amu_eval(${group})
59  \details
60  \anchor \amu_eval(${group})_conventions
61  \par Conventions
62 
63  \b Parameter \b naming
64 
65  - \p c is the coordinate point (or list of coordinate points for
66  the scale utility functions) to convert. It has no default and must
67  always be supplied by the caller. Its required dimensionality
68  depends on the target or source system (see Dimension requirements
69  below).
70  - \p from identifies the source coordinate system; defaults to
71  \ref coordinate_unit_default.
72  - \p to identifies the target coordinate system; defaults to
73  \ref coordinate_unit_base.
74  - \p s is the coordinate system identifier string used in
75  \ref coordinate_unit_name. It is distinct from \p c (a numeric
76  point) in both type and role; defaults to
77  \ref coordinate_unit_default.
78  - \p r is a radial scale factor used in the scale utility functions.
79  - \p t is a boolean flag in the scale utility functions: \b true
80  translates all points to exactly radius \p r; \b false scales
81  each point's existing radius by \p r.
82 
83  \b Return \b values
84 
85  - All conversion functions return \b undef for unrecognised system
86  identifiers, for input points of wrong dimensionality, or for
87  inputs that are not valid points.
88 
89  \b Dimension \b requirements
90 
91  - \c "c" (cartesian) accepts both 2d \c [x,y] and 3d \c [x,y,z].
92  - \c "p" (polar) requires a 2d point \c [r,aa]; a 3d input returns
93  \b undef.
94  - \c "y" (cylindrical) and \c "s" (spherical) require a 3d point;
95  a 2d input returns \b undef.
96 
97  \b Angular \b convention
98 
99  - All angular components (\c aa, \c pa) are in degrees.
100  - The azimuthal angle \c aa is measured from the positive x-axis
101  in the xy-plane. When \ref coordinate_positive_angle is \b true,
102  negative azimuthal results are shifted by +360° so that \c aa is
103  always in [0°, 360°). This applies to polar, cylindrical, and
104  spherical output.
105  - The polar angle \c pa (spherical only) is always in [0°, 180°]
106  and is not affected by \ref coordinate_positive_angle.
107  - When the input point is the origin \c [0,0,0], the polar angle
108  \c pa is defined as 0° by convention.
109 
110  \b Global \b configuration
111 
112  - \ref coordinate_unit_base sets the target system when \p to is
113  omitted. Defaults to \c "c". Intended to be overridden at the top
114  of a design file or child scope before any coordinate function is
115  called.
116  - \ref coordinate_unit_default sets the assumed source system when
117  \p from or \p s is omitted. Defaults to \c "c".
118  - \ref coordinate_positive_angle controls whether negative azimuthal
119  angles are normalised to [0°, 360°). Defaults to \b true. Unlike
120  \ref coordinate_unit_base and \ref coordinate_unit_default, this
121  variable may be changed between individual calls to alter angle
122  normalisation on a per-conversion basis.
123 
124  These functions allow for geometric points in space to be specified
125  using multiple coordinate systems. Some geometric calculations are
126  specified more naturally in one or another coordinate system. These
127  conversion functions allow for the movement between the most
128  convenient for a particular application.
129 
130  For more information see Wikipedia on [coordinate system].
131 
132  The table below enumerates the supported coordinate systems.
133 
134  | system id | description | dimensions | point convention |
135  |:----------:|:--------------:|:-----------:|:-------------------:|
136  | c | [cartesian] | 2d or 3d | [x, y] or [x, y, z] |
137  | p | [polar] | 2d | [r, aa] |
138  | y | [cylindrical] | 3d | [r, aa, z] |
139  | s | [spherical] | 3d | [r, aa, pa] |
140 
141  The symbols used in the convention column are as follows:
142 
143  | symbol | description | units | reference |
144  |:-------:|:------------------------|:-------:|:-------------------:|
145  | x, y, z | coordinate distance | any | xyz-axis |
146  | r | radial distance | any | z-axis / xyz-origin |
147  | aa | [azimuthal] angle | degrees | positive x-axis |
148  | pa | polar / [zenith] angle | degrees | positive z-axis |
149 
150  \note The [azimuthal] angle is a measure of the radial vector orthogonal
151  projection onto the xy-plane measured from the positive x-axis.
152  The polar angle is measured from the z-axis ([zenith]) to the
153  radial vector.
154 
155  \amu_define title (Coordinate system base example)
156  \amu_define scope_id (example)
157  \amu_define output_scad (true)
158  \amu_define output_console (false)
159  \amu_include (include/amu/scope.amu)
160 
161  \amu_define output_scad (false)
162  \amu_define output_console (true)
163 
164  \amu_define title (coordinate_unit_base=c)
165  \amu_define scope_id (example_c)
166  \amu_include (include/amu/scope.amu)
167 
168  \amu_define title (coordinate_unit_base=p)
169  \amu_define scope_id (example_p)
170  \amu_include (include/amu/scope.amu)
171 
172  \amu_define title (coordinate_unit_base=y)
173  \amu_define scope_id (example_y)
174  \amu_include (include/amu/scope.amu)
175 
176  \amu_define title (coordinate_unit_base=s)
177  \amu_define scope_id (example_s)
178  \amu_include (include/amu/scope.amu)
179 
180  [coordinate system]: https://en.wikipedia.org/wiki/Coordinate_system
181  [cartesian]: https://en.wikipedia.org/wiki/Cartesian_coordinate_system
182  [polar]: https://en.wikipedia.org/wiki/Polar_coordinate_system
183  [cylindrical]: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system
184  [spherical]: https://en.wikipedia.org/wiki/Spherical_coordinate_system
185  [azimuthal]: https://en.wikipedia.org/wiki/Azimuth
186  [zenith]: https://en.wikipedia.org/wiki/Zenith
187 *******************************************************************************/
188 
189 //----------------------------------------------------------------------------//
190 // members
191 //----------------------------------------------------------------------------//
192 
193 //! <string> The base units for value storage.
194 //! \note This variable is intended to be overridden at the top of a
195 //! design file or in a child scope. All coordinate functions that
196 //! omit the \p to parameter will convert to this system.
198 
199 //! <string> The default units when unspecified.
200 //! \note This variable is intended to be overridden at the top of a
201 //! design file or in a child scope. All coordinate functions that
202 //! omit the \p from or \p s parameter will assume this system.
204 
205 //! <boolean> When converting to angular measures add 360 to negative angles.
206 //! \note When \b true, any negative azimuthal angle \c aa produced during
207 //! conversion is shifted by +360° so that \c aa is always in [0°, 360°).
208 //! Applies to polar, cylindrical, and spherical output. May be changed
209 //! between individual calls; does not affect the polar angle \c pa.
211 
212 //! Return the name of the given coordinate system identifier.
213 /***************************************************************************//**
214  \param s <string> A coordinate system identifier.
215 
216  \returns <string> The system name for the given identifier.
217  Returns \b undef for identifiers that are not defined.
218 *******************************************************************************/
219 function coordinate_unit_name
220 (
222 ) = (s == "c") ? "cartesian"
223  : (s == "p") ? "polar"
224  : (s == "y") ? "cylindrical"
225  : (s == "s") ? "spherical"
226  : undef;
227 
228 //! Convert a point from Cartesian to other coordinate systems.
229 /***************************************************************************//**
230  \param c <point> A point to convert. Must be 2d for \c "p" output,
231  and 3d for \c "y" or \c "s" output. Cartesian \c "c"
232  accepts both 2d and 3d.
233  \param to <string> The coordinate system identifier to which the point
234  should be converted. Defaults to \ref coordinate_unit_base.
235 
236  \returns <point> The converted result.
237  Returns \b undef for identifiers that are not defined, or
238  if \p c is not a valid point, or if the dimensionality of
239  \p c does not match the requirements of \p to.
240 
241  \note Azimuthal angle normalisation is controlled by
242  \ref coordinate_positive_angle.
243  \note For spherical output, when \p c is the origin \c [0,0,0]
244  the polar angle \c pa is defined as 0° by convention.
245 
246  \private
247 *******************************************************************************/
248 function _coordinate_unit_c2
249 (
250  c,
252 ) = !is_point(c) ? undef
253 
254  // cartesian (2d, 3d)
255  : (to == "c") ? c
256 
257  : let( d = line_dim(c) )
258 
259  // polar (2d)
260  (to == "p") ? (d != 2) ? undef
261  : (
262  let
263  (
264  r = sqrt(pow(c[0],2) + pow(c[1],2)),
265  aa = atan2(c[1], c[0]),
266  aap = ((aa<0) && coordinate_positive_angle) ? aa+360 : aa
267  )
268  [r, aap]
269  )
270 
271  // cylindrical (3d)
272  : (to == "y") ? (d != 3) ? undef
273  : (
274  let
275  (
276  r = sqrt(pow(c[0],2) + pow(c[1],2)),
277  aa = atan2(c[1], c[0]),
278  aap = ((aa<0) && coordinate_positive_angle) ? aa+360 : aa
279  )
280  [r, aap, c[2]]
281  )
282 
283  // spherical (3d)
284  : (to == "s") ? (d != 3) ? undef
285  : (
286  let
287  (
288  r = sqrt(pow(c[0],2) + pow(c[1],2) + pow(c[2],2)),
289  aa = atan2(c[1], c[0]),
290  aap = ((aa<0) && coordinate_positive_angle) ? aa+360 : aa,
291  // guard against r==0 (origin): acos(0/0) is undefined; pa=0 by convention
292  pa = (r == 0) ? 0 : acos(c[2] / r)
293  )
294  [r, aap, pa]
295  )
296 
297  : undef;
298 
299 //! Convert a point from some coordinate system to the Cartesian coordinate system.
300 /***************************************************************************//**
301  \param c <point> A point to convert. Must be 2d for \c "p" input,
302  and 3d for \c "y" or \c "s" input. Cartesian \c "c"
303  accepts both 2d and 3d.
304  \param from <string> The coordinate system identifier of the point
305  to be converted. Defaults to \ref coordinate_unit_default.
306 
307  \returns <point> The converted result.
308  Returns \b undef for identifiers that are not defined, or
309  if \p c is not a valid point, or if the dimensionality of
310  \p c does not match the requirements of \p from.
311 
312  \private
313 *******************************************************************************/
314 function _coordinate_unit_2c
315 (
316  c,
318 ) = !is_point(c) ? undef
319 
320  // cartesian (2d, 3d)
321  : (from == "c") ? c
322 
323  : let( d = line_dim(c) )
324 
325  // polar (2d)
326  (from == "p") ? (d != 2 ) ? undef
327  : (
328  let
329  (
330  x = c[0]*cos(c[1]),
331  y = c[0]*sin(c[1])
332  )
333  [x, y]
334  )
335 
336  // cylindrical (3d)
337  : (from == "y") ? (d != 3 ) ? undef
338  : (
339  let
340  (
341  x = c[0]*cos(c[1]),
342  y = c[0]*sin(c[1])
343  )
344  [x, y, c[2]]
345  )
346 
347  // spherical (3d)
348  : (from == "s") ? (d != 3 ) ? undef
349  : (
350  let
351  (
352  x = c[0]*sin(c[2])*cos(c[1]),
353  y = c[0]*sin(c[2])*sin(c[1]),
354  z = c[0]*cos(c[2])
355  )
356  [x, y, z]
357  )
358  : undef;
359 
360 //! Convert a point from one coordinate system to another.
361 /***************************************************************************//**
362  \param c <point> A point to convert. Dimensionality must satisfy
363  the requirements of both \p from and \p to.
364  \param from <string> The coordinate system identifier of the point
365  to be converted. Defaults to \ref
366  coordinate_unit_default.
367  \param to <string> The coordinate system identifier to which the
368  point should be converted. Defaults to \ref
369  coordinate_unit_base.
370 
371  \returns <point> The converted result.
372  Returns \b undef for unrecognised identifiers, invalid
373  points, or dimensionality mismatches.
374 
375  \note \c "p" requires 2d input; \c "y" and \c "s" require 3d input;
376  \c "c" accepts both. See conventions for full details.
377 *******************************************************************************/
378 function coordinate
379 (
380  c,
383 ) = (from == to) ? c
384  : let( cc = _coordinate_unit_2c( c, from ) )
385  (cc == undef) ? undef
386  : _coordinate_unit_c2( cc, to );
387 
388 //! Convert a point from one coordinate system to another (direction-swapped defaults).
389 /***************************************************************************//**
390  \param c <point> A point to convert. Dimensionality must satisfy
391  the requirements of both \p from and \p to.
392  \param from <string> The coordinate system identifier of the point
393  to be converted. Defaults to \ref coordinate_unit_base.
394  \param to <string> The coordinate system identifier to which the
395  point should be converted. Defaults to
396  \ref coordinate_unit_default.
397 
398  \returns <point> The converted result.
399  Returns \b undef for unrecognised identifiers, invalid
400  points, or dimensionality mismatches.
401 
402  \note This is a convenience alias for \ref coordinate with \p from
403  and \p to defaults swapped. It is useful when the natural
404  direction of a design is from the base system back to a
405  display or input system.
406 *******************************************************************************/
407 function coordinate_inv
408 (
409  c,
410  from = coordinate_unit_base,
412 ) = coordinate(c=c, from=from, to=to);
413 
414 //! Radially scale a list of 2d cartesian coordinates.
415 /***************************************************************************//**
416  \param c <points-2d> A list of cartesian coordinates [[x, y], ...].
417  \param r <decimal> A polar radius.
418  \param t <boolean> Translate or scale radius.
419 
420  \returns <points-2d> A list of scaled cartesian coordinates.
421 
422  \details
423 
424  When \p t is \b true, all coordinates will terminate on a circle of
425  radius \p r. When \p t is \b false, the radius of each coordinate
426  is scaled by \p r.
427 *******************************************************************************/
428 function coordinate_scale2d_cpc
429 (
430  c,
431  r,
432  t = false
433 ) =
434  [
435  for (i = c)
436  let (p = coordinate(i, from="c", to="p"))
437  coordinate([(t == true) ? r : r*p[0], p[1]], from="p", to="c")
438  ];
439 
440 //! Radially scale and convert a list of 2d polar coordinates to cartesian.
441 /***************************************************************************//**
442  \param c <points-2d> A list of polar coordinates [[r, aa], ...].
443  \param r <decimal> A polar radius.
444  \param t <boolean> Translate or scale radius.
445 
446  \returns <points-2d> A list of scaled cartesian coordinates.
447 
448  \details
449 
450  When \p t is \b true, all coordinates will terminate on a circle of
451  radius \p r. When \p t is \b false, the radius of each coordinate
452  is scaled by \p r.
453 *******************************************************************************/
454 function coordinate_scale2d_p2c
455 (
456  c,
457  r,
458  t = false
459 ) =
460  [
461  for (i = c)
462  coordinate([(t == true) ? r : r*i[0], i[1]], from="p", to="c")
463  ];
464 
465 //! Spherically scale a list of 3d cartesian coordinates.
466 /***************************************************************************//**
467  \param c <points-3d> A list of cartesian coordinates [[x, y, z], ...].
468  \param r <decimal> A spherical radius.
469  \param t <boolean> Translate or scale radius.
470 
471  \returns <points-3d> A list of scaled cartesian coordinates.
472 
473  \details
474 
475  When \p t is \b true, all coordinates will terminate on a sphere of
476  radius \p r. When \p t is \b false, the radius of each coordinate
477  is scaled by \p r.
478 *******************************************************************************/
479 function coordinate_scale3d_csc
480 (
481  c,
482  r,
483  t = false
484 ) =
485  [
486  for (i = c)
487  let (s = coordinate(i, from="c", to="s"))
488  coordinate([(t == true) ? r : r*s[0], s[1], s[2]], from="s", to="c")
489  ];
490 
491 //! Spherically scale and convert a list of 3d spherical coordinates to cartesian.
492 /***************************************************************************//**
493  \param c <points-3d> A list of spherical coordinates [[r, aa, pa], ...].
494  \param r <decimal> A spherical radius.
495  \param t <boolean> Translate or scale radius.
496 
497  \returns <points-3d> A list of scaled cartesian coordinates.
498 
499  \details
500 
501  When \p t is \b true, all coordinates will terminate on a sphere of
502  radius \p r. When \p t is \b false, the radius of each coordinate
503  is scaled by \p r.
504 *******************************************************************************/
505 function coordinate_scale3d_s2c
506 (
507  c,
508  r,
509  t = false
510 ) =
511  [
512  for (i = c)
513  coordinate([(t == true) ? r : r*i[0], i[1], i[2]], from="s", to="c")
514  ];
515 
516 //! @}
517 //! @}
518 
519 //----------------------------------------------------------------------------//
520 // openscad-amu auxiliary scripts
521 //----------------------------------------------------------------------------//
522 
523 /*
524 BEGIN_SCOPE validate;
525  BEGIN_OPENSCAD;
526  include <omdl-base.scad>;
527  include <common/validation.scad>;
528 
529  echo( str("openscad version ", version()) );
530  for (i=[1:7]) echo( "not tested:" );
531 
532  // end_include
533  END_OPENSCAD;
534 
535  BEGIN_MFSCRIPT;
536  include --path "${INCLUDE_PATH}" {var_init,var_gen_term}.mfs;
537  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
538  END_MFSCRIPT;
539 END_SCOPE;
540 */
541 
542 /*
543 BEGIN_SCOPE example;
544  BEGIN_OPENSCAD;
545  include <omdl-base.scad>;
546  include <units/coordinate.scad>;
547 
548  coordinate_unit_base = "c";
549  coordinate_unit_default = "p";
550 
551  // get unit names
552  bu = coordinate_unit_name(coordinate_unit_base);
553  du = coordinate_unit_name();
554 
555  // absolute coordinates in a specified coordinate system.
556  c1 = coordinate([1, 1, 1], "c");
557  c2 = coordinate([1, 180]);
558  c3 = coordinate([1, 90, -1], "y");
559  c4 = coordinate([1, 5, 50], "s");
560 
561  // convert between system.
562  c5 = coordinate([10*sqrt(2), 45, 45], from="s", to="y");
563  c6 = coordinate([sqrt(2), 45], to="c");
564 
565  // end_include
566 
567  echo( bu=bu );
568  echo( du=du );
569  echo( );
570  echo( c1=c1 );
571  echo( c2=c2 );
572  echo( c3=c3 );
573  echo( c4=c4 );
574  echo( c5=c5 );
575  echo( c6=c6 );
576  END_OPENSCAD;
577 
578  BEGIN_MFSCRIPT;
579  include --path "${INCLUDE_PATH}" {var_init,var_gen_term}.mfs;
580 
581  defines name "system" define "coordinate_unit_base" strings "c p y s";
582  variables add_opts_combine "system";
583 
584  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
585  END_MFSCRIPT;
586 END_SCOPE;
587 */
588 
589 //----------------------------------------------------------------------------//
590 // end of file
591 //----------------------------------------------------------------------------//
function line_dim(l)
Return the number of dimensions of a line or vector.
function is_point(v)
Test if a value defines a point.
function coordinate_scale2d_p2c(c, r, t=false)
Radially scale and convert a list of 2d polar coordinates to cartesian.
coordinate_unit_base
function coordinate(c, from=coordinate_unit_default, to=coordinate_unit_base)
Convert a point from one coordinate system to another.
function coordinate_scale3d_s2c(c, r, t=false)
Spherically scale and convert a list of 3d spherical coordinates to cartesian.
function coordinate_inv(c, from=coordinate_unit_base, to=coordinate_unit_default)
Convert a point from one coordinate system to another (direction-swapped defaults).
function coordinate_scale2d_cpc(c, r, t=false)
Radially scale a list of 2d cartesian coordinates.
function coordinate_scale3d_csc(c, r, t=false)
Spherically scale a list of 3d cartesian coordinates.
function coordinate_unit_name(s=coordinate_unit_default)
Return the name of the given coordinate system identifier.
coordinate_unit_default
coordinate_positive_angle