omdl  v1.0
OpenSCAD Mechanical Design Library
euclidean.scad
Go to the documentation of this file.
1 //! Tests and operations for Euclidean 3d space.
2 /***************************************************************************//**
3  \file
4  \author Roy Allen Sutton
5  \date 2015-2023
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 (Euclidean)
31  \amu_define group_brief (Tests and operations for Euclidean 3d space.)
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 group_references
52  (
53  [line conventions]: \ref data_types_lines
54  [plane normal]: \ref data_types_normals
55  )
56 *******************************************************************************/
57 
58 // member-wide documentation and conventions
59 /***************************************************************************//**
60  \addtogroup \amu_eval(${group})
61  \details
62  \anchor \amu_eval(${group})_conventions
63  \par Conventions
64 
65  - Points are represented as \b list-2d [x, y] or \b list-3d [x, y, z].
66  - Lines are represented as \b [p_initial, p_terminal], a two-element list
67  of points. The direction of a line is from p_initial to p_terminal.
68  - Planes are represented as \b [p, n] where \p p is a point on the plane
69  and \p n is the outward unit normal vector.
70  - All angles are in \b degrees, following OpenSCAD convention.
71  - Dimensionality is auto-detected from the input point type (2D vs 3D).
72  Mixing 2D and 3D points in the same call is undefined behaviour.
73  - Functions that return \b undef on invalid input state all validation
74  conditions explicitly in returns. No silent nan or inf is produced.
75 *******************************************************************************/
76 
77 //----------------------------------------------------------------------------//
78 // members
79 //----------------------------------------------------------------------------//
80 
81 //----------------------------------------------------------------------------//
82 // set 1: identify
83 //----------------------------------------------------------------------------//
84 
85 //! Test if a value defines a point.
86 /***************************************************************************//**
87  \param v <list-2d | list-3d> A 2d or 3d list of numbers.
88 
89  \returns (1) <boolean> \b true when the \p v can be interpreted as a
90  point and \b false otherwise.
91 
92  \details
93 
94  See [line conventions] for more information.
95 
96  \amu_eval(${group_references})
97 *******************************************************************************/
98 function is_point
99 (
100  v
101 ) = !is_list(v) ? false
102  : !all_numbers(v) ? false
103  : !is_between(len(v), 2, 3) ? false
104  : true;
105 
106 //! Test if a value defines a Euclidean vector.
107 /***************************************************************************//**
108  \param v <list-2d | list-3d> A 2d or 3d list of numbers.
109 
110  \returns (1) <boolean> \b true when the \p v can be interpreted as a
111  Euclidean vector and \b false otherwise.
112 
113  \details
114 
115  See [line conventions] for more information.
116 
117  \amu_eval(${group_references})
118 *******************************************************************************/
119 function is_vector
120 (
121  v
122 ) = !is_list(v) ? false
123  : (len(v) == 1) && !is_point(v[0]) ? false
124  : !is_point(v) ? false
125  : true;
126 
127 //! Test if a value defines a line.
128 /***************************************************************************//**
129  \param v <list-2d | list-3d> A 2d or 3d list of numbers.
130 
131  \returns (1) <boolean> \b true when the \p v can be interpreted as a
132  line and \b false otherwise.
133 
134  \details
135 
136  See [line conventions] for more information.
137 
138  \amu_eval(${group_references})
139 *******************************************************************************/
140 function is_line
141 (
142  v
143 ) = !is_list(v) ? false
144  : (len(v) != 2) ? false
145  : !is_point(v[0]) ? false
146  : !is_point(v[1]) ? false
147  : (len(v[0]) != len(v[1])) ? false
148  : true;
149 
150 //! Test if a value defines a vector or a line.
151 /***************************************************************************//**
152  \param v <list-2d | list-3d> A 2d or 3d list of numbers.
153 
154  \returns (1) <boolean> \b true when the \p v can be interpreted as a
155  vector or a line and \b false otherwise.
156 
157  \details
158 
159  See [line conventions] for more information.
160 
161  \amu_eval(${group_references})
162 *******************************************************************************/
163 function is_vol
164 (
165  v
166 ) = is_vector(v) ? true
167  : is_line(v) ? true
168  : false;
169 
170 //! Test if a value defines a plane.
171 /***************************************************************************//**
172  \param v <list-2d | list-3d> A 2d or 3d list of numbers.
173 
174  \returns (1) <boolean> \b true when the \p v can be interpreted as a
175  plane and \b false otherwise.
176 
177  \details
178 
179  See [line conventions] for more information.
180 
181  \amu_eval(${group_references})
182 *******************************************************************************/
183 function is_plane
184 (
185  v
186  // predetermined normal vector
187 ) = is_vector(v) ? true
188 
189  // intersecting vectors [v1, v2] like line.
190  : is_line(v) ? true
191 
192  // otherwise, 3 points of same dimension.
193  : !is_list(v) ? false
194  : (len(v) != 3) ? false
195  : !is_point(v[0]) ? false
196  : !is_point(v[1]) ? false
197  : !is_point(v[2]) ? false
198  : (len(v[0]) != len(v[1])) ? false
199  : (len(v[1]) != len(v[2])) ? false
200  : true;
201 
202 //----------------------------------------------------------------------------//
203 // set 2: point
204 //----------------------------------------------------------------------------//
205 
206 //! Compute the distance between two points.
207 /***************************************************************************//**
208  \param p1 <point> A point coordinate 1.
209  \param p2 <point> A point coordinate 2.
210 
211  \returns (1) <decimal> The distance between the two points.
212  (2) Returns \b undef when \p p1 or \p p2 are not points or
213  do not have equal dimensions.
214 
215  \details
216 
217  When \p p2 is not given, it is assumed to be at the origin. This
218  function is similar to [norm].
219 
220  [norm]: https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Mathematical_Functions#norm
221 *******************************************************************************/
222 function distance_pp
223 (
224  p1,
225  p2
226 ) = !is_point(p1) ? undef
227  : !is_undef(p2) && !is_point(p2) ? undef
228  : !is_undef(p2) && (len(p1) != len(p2)) ? undef
229  : is_undef(p2) ?
230  sqrt(sum([for (i=[0:len(p1)-1]) p1[i]*p1[i]]))
231  : sqrt(sum([for (i=[0:len(p1)-1]) (p1[i]-p2[i])*(p1[i]-p2[i])]));
232 
233 //! Compute the shortest distance between a point and a line.
234 /***************************************************************************//**
235  \param p <point> A point coordinate.
236  \param l <line> A line or vector.
237 
238  \returns (1) <decimal> The shortest distance between the point and
239  the line.
240  (2) Returns \b undef when \p p is not a point or \p l is
241  not a line.
242 
243  \details
244 
245  \sa point_closest_pl().
246 *******************************************************************************/
247 function distance_pl
248 (
249  p,
250  l
251 ) = distance_pp(p, point_closest_pl(p, l));
252 
253 //! Compute the shortest distance between a point and a plane.
254 /***************************************************************************//**
255  \param p <point> A point coordinate.
256  \param n <pnorm> A [plane normal].
257  \param np <point> A point coordinate on the plane \p n.
258 
259  \returns (1) <decimal> The shortest distance between the point and
260  the plane.
261  (2) Returns \b undef when \p p or \p np is not a point or
262  when \p n is not a plane.
263 
264  \details
265 
266  \sa point_closest_pn().
267 
268  \amu_eval(${group_references})
269 *******************************************************************************/
270 function distance_pn
271 (
272  p,
273  n,
274  np
275 ) = distance_pp(p, point_closest_pn(p, n, np));
276 
277 //! Test if a point is left, on, or right of an infinite line in a 2d-space.
278 /***************************************************************************//**
279  \param p1 <point-2d> A 2d point coordinate 1.
280  \param p2 <point-2d> A 2d point coordinate 2.
281  \param p3 <point-2d> A 2d point coordinate 3.
282 
283  \returns (1) <decimal> (\b > 0) for \p p3 \em left of the line
284  through \p p1 and \p p2, (\b = 0) for p3 \em on the
285  line, and (\b < 0) for p3 right of the line.
286  (2) Returns \b undef when \p p1, \p p2, or \p p3 is not a
287  point.
288 
289  \details
290 
291  Function patterned after [Dan Sunday, 2012].
292 
293  [Dan Sunday, 2012]: http://geomalgorithms.com/a01-_area.html
294 *******************************************************************************/
295 function is_left_ppp
296 (
297  p1,
298  p2,
299  p3
300 ) = !is_point(p1) ? undef
301  : !is_point(p2) ? undef
302  : !is_point(p3) ? undef
303  : ((p2[0]-p1[0]) * (p3[1]-p1[1]) - (p3[0]-p1[0]) * (p2[1]-p1[1]));
304 
305 //! Compute the coordinates of the closest point on a line to a given point.
306 /***************************************************************************//**
307  \param p <point> A point coordinate.
308  \param l <line> A line or vector.
309 
310  \returns (1) <point-3d> The coordinates of the point on the line
311  closest to the given point.
312  (2) Returns \b undef when \p p is not a point or \p l is
313  not a line.
314 *******************************************************************************/
315 function point_closest_pl
316 (
317  p,
318  l
319 ) = !is_point(p) ? undef
320  : !is_line(l) ? undef
321  : let
322  (
323  t = line_tp(l),
324  i = line_ip(l),
325 
326  v = t - i,
327  w = p - i,
328 
329  x = (w * v) / (v * v)
330  )
331  i + x * v;
332 
333 //! Compute the coordinates of the closest point on a plane to a given point.
334 /***************************************************************************//**
335  \param p <point> A point coordinate.
336  \param n <pnorm> A [plane normal].
337  \param np <point> A point coordinate on the plane \p n.
338 
339  \returns (1) <point-3d> The coordinates of the point on the plane
340  closest to the given point.
341  (2) Returns \b undef when \p p or \p np is not a point or
342  when \p n is not a plane.
343 
344  \amu_eval(${group_references})
345 *******************************************************************************/
346 function point_closest_pn
347 (
348  p,
349  n,
350  np
351 ) = !is_point(p) ? undef
352  : !is_plane(n) ? undef
353  : !is_point(np) ? undef
354  : let
355  (
356  m = plane_to_normal(n),
357 
358  q = m * (np - p),
359  r = m * m,
360 
361  s = q / r
362  )
363  p + s * m;
364 
365 //! Return 3d point unchanged or add a zeroed third dimension to 2d point.
366 /***************************************************************************//**
367  \param p <point-3d | point-2d> A point.
368 
369  \returns (1) <point-3d> The 3d point or the 2d point converted to 3d
370  with its third dimension assigned zero.
371  (2) Returns \b undef when \p p is not a point.
372 
373  \details
374 
375  This function assumes any point that is not 3d to be 2d.
376 *******************************************************************************/
377 function point_to_3d
378 (
379  p
380 ) = !is_point(p) ? undef
381  : (len(p) == 3) ? p : [p[0], p[1], 0];
382 
383 //! Linearly interpolate along a line established by two points in 2d.
384 /***************************************************************************//**
385  \param p1 <point-2d> The line initial coordinate [x, y].
386  \param p2 <point-2d> The line terminal coordinate [x, y].
387 
388  \param y <decimal> The \p y coordinate at which to interpolate
389  along the line.
390  \param x <decimal> The \p x coordinate at which to interpolate
391  along the line.
392 
393  \returns (1) <point-2d> The interpolated coordinates point [x, y].
394  (2) Returns \b undef when \p p1 or \p p2 is not a point,
395  when \p x or \p y are not numbers, or when the two
396  points share the same coordinate along the interpolation
397  axis (p1[0] == p2[0] for x-interpolation, or
398  p1[1] == p2[1] for y-interpolation), as this would
399  require division by zero.
400 
401  \details
402 
403  The order of precedence for interpolation is: \p y, \p x. See
404  [Wikipedia] for more information.
405 
406  [Wikipedia]: https://en.wikipedia.org/wiki/Linear_interpolation
407 *******************************************************************************/
408 function interpolate2d_l_pp
409 (
410  p1,
411  p2,
412  x,
413  y
414 ) = !is_point(p1) ? undef
415  : !is_point(p2) ? undef
416 
417  // 'y' is given, get 'lx' for given 'y'
418  : !is_undef(y) && !is_number(y) ? undef
419  : !is_undef(y) ?
420  (p2[1] == p1[1]) ? undef
421  : let( lx = (p1[0]*(p2[1]-y) + p2[0]*(y-p1[1])) / (p2[1]-p1[1]) )
422  [lx, y]
423 
424  // 'y' not given, get 'ly' for given 'x'
425  : !is_number(x) ? undef
426  : (p2[0] == p1[0]) ? undef
427  : let( ly = (p1[1]*(p2[0]-x) + p2[1]*(x-p1[0])) / (p2[0]-p1[0]) )
428  [x, ly];
429 
430 //----------------------------------------------------------------------------//
431 // set 3: vector
432 //----------------------------------------------------------------------------//
433 
434 //----------------------------------------------------------------------------//
435 // set 4: line (or vector)
436 //----------------------------------------------------------------------------//
437 
438 //! Construct a 2 dimensional directed line.
439 /***************************************************************************//**
440  \param m <decimal> The magnitude.
441  \param a <decimal> The azmuthal angle.
442  \param p1 <point-2d> The initial point.
443  \param p2 <point-2d> The terminal point.
444  \param v <vector-2d> An orientation line or vector.
445 
446  \returns (1) <line-2d> The 2d directed line.
447  (2) Returns \b undef when required parameters are not
448  defined.
449 
450  \details
451 
452  The initial point (or tail) of the line is specified by \p p1. The
453  terminal point (or head) can be specified by one of, in order of
454  precedence, \p p2, \p v, or \p a.
455 
456  See [line conventions] for more information.
457 
458  \amu_eval(${group_references})
459 *******************************************************************************/
460 function line2d_new
461 (
462  m = 1,
463  a = 0,
464  p1 = origin2d,
465  p2,
466  v
467 ) = !is_point(p1) ? undef
468 
469  // using 'p2' ('p1')
470  : !is_undef(p2) ? !is_point(p2) ? undef
471  : [p1, p2]
472 
473  // using 'v' ('p1' and 'm')
474  : !is_number(m) ? undef
475  : !is_undef(v) ? !is_vol(v) ? undef
476  : [p1, p1 + m*unit_l(v)]
477 
478  // using 'a' ('p1' and 'm')
479  : !is_number(a) ? undef
480  : [p1, p1 + m*[cos(a), sin(a)]];
481 
482 //! Construct a 3 dimensional directed line.
483 /***************************************************************************//**
484  \param m <decimal> The magnitude.
485  \param a <decimal> The azmuthal angle.
486  \param t <decimal> The polar angle.
487  \param p1 <point-3d> The initial point.
488  \param p2 <point-3d> The terminal point.
489  \param v <vector-3d> An orientation line or vector.
490 
491  \returns (1) <line-3d> The 3d directed line.
492  (2) Returns \b undef when required parameters are not
493  defined.
494 
495  \details
496 
497  The initial point (or tail) of the line is specified by \p p1. The
498  terminal point (or head) can be specified by one of, in order of
499  precedence, \p p2, \p v, or \p a and \p t.
500 
501  See [line conventions] for more information.
502 
503  \amu_eval(${group_references})
504 *******************************************************************************/
505 function line3d_new
506 (
507  m = 1,
508  a = 0,
509  t = 90,
510  p1 = origin3d,
511  p2,
512  v
513 ) = !is_point(p1) ? undef
514 
515  // using 'p2' ('p1')
516  : !is_undef(p2) ? !is_point(p2) ? undef
517  : [p1, p2]
518 
519  // using 'v' ('p1' and 'm')
520  : !is_number(m) ? undef
521  : !is_undef(v) ? !is_vol(v) ? undef
522  : [p1, p1 + m*unit_l(v)]
523 
524  // using 'a' and 't' ('p1' and 'm')
525  : (!is_number(a) || !is_number(t)) ? undef
526  : [p1, p1 + m*[sin(t)*cos(a), sin(t)*sin(a), cos(t)]];
527 
528 //! Construct a directed line.
529 /***************************************************************************//**
530  \param m <decimal> The magnitude.
531  \param a <decimal> The azmuthal angle.
532  \param t <decimal> The polar angle.
533  \param p1 <point> The initial point.
534  \param p2 <point> The terminal point.
535  \param v <vector> An orientation line or vector.
536 
537  \returns (1) <line> The directed line.
538  (2) Returns \b undef when required parameters are not
539  defined.
540 
541  \details
542 
543  The initial point (or tail) of the line is specified by \p p1. The
544  terminal point (or head) can be specified by one of, in order of
545  precedence, \p p2, \p v, or \p a and \p t.
546 
547  This function will construct a 2d or 3d line as required based on
548  the supplied arguments. A 3d line is constructed only when
549  required. Because the argument dimensions are inspected, this
550  function has more overhead when compared to line2d_new() and
551  line3d_new().
552 
553  See [line conventions] for more information.
554 
555  \amu_eval(${group_references})
556 *******************************************************************************/
557 function line_new
558 (
559  m = 1,
560  a = 0,
561  t,
562  p1,
563  p2,
564  v
565  // using 'p2'
566 ) = !is_undef(p2) ? !is_point(p2) ? undef
567  : let
568  (
569  pd = !is_undef(p1) ? p1
570  : (len(p2) == 2) ? origin2d : origin3d
571  )
572  [pd, p2]
573 
574  // using 'v' ('m')
575  : !is_number(m) ? undef
576  : !is_undef(v) ? !is_vol(v) ? undef
577  : let
578  (
579  pd = !is_undef(p1) ? p1
580  : (len(v) == 2) ? origin2d : origin3d
581  )
582  [pd, pd + m*unit_l(v)]
583 
584  // using 'a' ('m')
585  : is_undef(t) ? !is_number(a) ? undef
586  : let
587  (
588  pd = !is_undef(p1) ? p1 : origin2d
589  )
590  [pd, pd + m*[cos(a), sin(a)]]
591 
592  // using 'a' and 't' ('m')
593  : (!is_number(a) || !is_number(t)) ? undef
594  : let
595  (
596  pd = !is_undef(p1) ? p1 : origin3d
597  )
598  [pd, pd + m*[sin(t)*cos(a), sin(t)*sin(a), cos(t)]];
599 
600 //! Return the number of dimensions of a line or vector.
601 /***************************************************************************//**
602  \param l <line> A line or vector.
603 
604  \returns (1) <integer> The line or vector dimensions.
605  (2) Returns \b undef when \p l is not a line or vector.
606 
607  \details
608 
609  See [line conventions] for more information.
610 
611  \amu_eval(${group_references})
612 *******************************************************************************/
613 function line_dim
614 (
615  l
616 ) = is_vector(l) ? (len(l) == 1) ? len( l[0] ) : len( l )
617  : is_line(l) ? len( l[0] )
618  : undef;
619 
620 //! Return the terminal point of a line or vector.
621 /***************************************************************************//**
622  \param l <line> A line or vector.
623 
624  \returns (1) <point> The terminal point of the line or vector.
625  (2) Returns \b undef when \p l is not a line or vector.
626 
627  \details
628 
629  See [line conventions] for more information.
630 
631  \amu_eval(${group_references})
632 *******************************************************************************/
633 function line_tp
634 (
635  l
636 ) = is_vector(l) ? (len(l) == 1) ? l[0] : l
637  : is_line(l) ? l[1]
638  : undef;
639 
640 //! Return the initial point of a line or vector.
641 /***************************************************************************//**
642  \param l <line> A line or vector.
643 
644  \returns (1) <point> The initial point of the line or vector.
645  (2) Returns \b undef when \p l is not a line or vector.
646 
647  \details
648 
649  See [line conventions] for more information.
650 
651  \amu_eval(${group_references})
652 *******************************************************************************/
653 function line_ip
654 (
655  l
656 ) = is_vector(l) ?
657  let
658  (
659  d = (len(l) == 1) ? len(l[0]) : len(l)
660  )
661  consts(d, 0)
662  : is_line(l) ? l[0]
663  : undef;
664 
665 //! Convert line to vector by shifting it to the origin.
666 /***************************************************************************//**
667  \param l <line> A line or vector.
668 
669  \returns (1) <vector> The line shifted to the origin.
670  (2) Returns \b undef when \p l is not a line or vector.
671 
672  \details
673 
674  See [line conventions] for more information.
675 
676  \amu_eval(${group_references})
677 *******************************************************************************/
678 function vol_to_origin
679 (
680  l
681 ) = is_vector(l) ? (len(l) == 1) ? l[0] : l
682  : !is_line(l) ? undef
683  : (len(l) == 1) ? l[0]
684  : (len(l) == 2) ? (l[1]-l[0])
685  : undef;
686 
687 //! Convert a vector to a line or move a line to another coordinate point.
688 /***************************************************************************//**
689  \param l <line> A line or vector.
690  \param p <point> The new initial point.
691 
692  \returns (1) <line> The line or vector moved to \p p.
693  (2) Returns \b undef when \p l is not a line or vector or
694  when \p p is not a point.
695 
696  \details
697 
698  When \p p is not specified, the line or vector is moved to the
699  origin.
700 
701  See [line conventions] for more information.
702 
703  \amu_eval(${group_references})
704 *******************************************************************************/
705 function vol_to_point
706 (
707  l,
708  p
709 ) = !is_line(l) ? undef
710  : !is_undef(p) && !is_point(p) ? undef
711  : let
712  (
713  d = !is_undef(p) ? p
714  : (line_dim(l) == 2) ? origin2d
715  : origin3d
716  )
717  [d, d + vol_to_origin(l)];
718 
719 //! Compute the dot product of two lines or vectors in a 3d or 2d-space.
720 /***************************************************************************//**
721  \param l1 <line-3d | line-2d> A 3d or 2d line or vector 1.
722  \param l2 <line-3d | line-2d> A 3d or 2d line or vector 2.
723 
724  \returns (1) <decimal> The dot product of \p l1 with \p l2.
725  (2) Returns \b undef when \p l1 or \p l2 is not a line or
726  vector or have different dimensions.
727 
728  \details
729 
730  This function supports the abstraction in [line conventions]. See
731  [Wikipedia] for more information.
732 
733  [Wikipedia]: https://en.wikipedia.org/wiki/Dot_product
734  \amu_eval(${group_references})
735 *******************************************************************************/
736 function dot_ll
737 (
738  l1,
739  l2
740 ) = !is_vol(l1) ? undef
741  : !is_vol(l2) ? undef
742  : (len(l1) != len(l2)) ? undef
743  : (vol_to_origin(l1) * vol_to_origin(l2));
744 
745 //! Compute the cross product of two lines or vectors in a 3d or 2d-space.
746 /***************************************************************************//**
747  \param l1 <line-3d | line-2d> A 3d or 2d line or vector 1.
748  \param l2 <line-3d | line-2d> A 3d or 2d line or vector 2.
749 
750  \returns (1) <decimal | vector-2d> The cross product of \p l1 with
751  \p l2.
752  (2) Returns \b undef when \p l1 or \p l2 is not a line or
753  vector or have different dimensions.
754 
755  \details
756 
757  This function supports the abstraction in [line conventions]. See
758  [Wikipedia] for more information.
759 
760 
761  \note This function returns the 2x2 determinant for 2d vectors.
762 
763  [cross]: https://en.wikipedia.org/wiki/Cross_product
764  [determinant]: https://en.wikipedia.org/wiki/Determinant
765  \amu_eval(${group_references})
766 *******************************************************************************/
767 function cross_ll
768 (
769  l1,
770  l2
771 ) = !is_vol(l1) ? undef
772  : !is_vol(l2) ? undef
773  : (len(l1) != len(l2)) ? undef
774  : cross(vol_to_origin(l1), vol_to_origin(l2));
775 
776 //! Compute the scalar triple product of three lines or vectors in a 3d or 2d-space.
777 /***************************************************************************//**
778  \param l1 <line-3d | line-2d> A 3d or 2d line or vector 1.
779  \param l2 <line-3d | line-2d> A 3d or 2d line or vector 2.
780  \param l3 <line-3d | line-2d> A 3d or 2d line or vector 3.
781 
782  \returns (1) <decimal | vector-2d> The scalar triple product.
783  (2) Returns \b undef when \p l1, \p l2, or \p l3 is not a
784  line or vector or have different dimensions,
785 
786  \details
787 
788  Returns a 2d vector result for 2d vectors. The cross product
789  computes the 2x2 determinant of the vectors <tt>(l2 x l3)</tt>, a
790  scalar value, which is then \e multiplied by \c l1.
791 
792  \verbatim
793  [l1, l2, l3] = l1 * (l2 x l3)
794  \endverbatim
795 
796  See [line conventions] for more information. See [Wikipedia] for
797  more information.
798 
799  [Wikipedia]: https://en.wikipedia.org/wiki/Triple_product
800  \amu_eval(${group_references})
801 *******************************************************************************/
802 function striple_lll
803 (
804  l1,
805  l2,
806  l3
807 ) = !is_vol(l1) ? undef
808  : !is_vol(l2) ? undef
809  : !is_vol(l3) ? undef
810  : (len(l1) != len(l2)) ? undef
811  : (len(l2) != len(l3)) ? undef
812  : (vol_to_origin(l1) * cross_ll(l2, l3));
813 
814 //! Compute the angle between two lines or vectors in a 3d or 2d-space.
815 /***************************************************************************//**
816  \param l1 <line-3d | line-2d> A 3d or 2d line or vector 1.
817  \param l2 <line-3d | line-2d> A 3d or 2d line or vector 2.
818  \param s <boolean> Return the 2d signed angle.
819 
820  \returns (1) <decimal> The angle between the two lines or vectors in
821  degrees.
822  (2) Returns \b undef when \p l1 or \p l2 is not a line or
823  vector or have different dimensions or when they do not
824  intersect.
825 
826  \details
827 
828  For 2d lines or vectors, the signed angle is calculated. The
829  parameter \p s determines if the \em signed (\p s == \b true) or
830  \em positive (\p s == \b false) angle is returned. For 3d lines or
831  vectors, a normal is required to uniquely identify the
832  perpendicular plane and axis of rotation. This function calculates
833  the positive angle, and the plane and axis of rotation will be that
834  which fits this assumed positive angle.
835 
836  See [line conventions] for more information.
837 
838  \sa angle_lll().
839  \amu_eval(${group_references})
840 *******************************************************************************/
841 function angle_ll
842 (
843  l1,
844  l2,
845  s = true
846 ) = !is_vol(l1) ? undef
847  : !is_vol(l2) ? undef
848  : let
849  (
850  d = line_dim(l1) + line_dim(l2)
851  )
852  // two 2d
853  (d == 4) ?
854  let
855  (
856  sa = atan2(cross_ll(l1, l2), dot_ll(l1, l2))
857  )
858  ((sa < 0) && (s == false)) ? sa+360
859  : sa
860  // two 3d
861  : (d == 6) ?
862  atan2(distance_pp(cross_ll(l1, l2)), dot_ll(l1, l2))
863  : undef;
864 
865 //! Compute the angle between two lines or vectors in a 3d-space.
866 /***************************************************************************//**
867  \param l1 <line-3d> A 3d line or vector 1.
868  \param l2 <line-3d> A 3d line or vector 2.
869  \param n <line-3d> A 3d normal line or vector.
870 
871  \returns (1) <decimal> The angle between the two lines or vectors in
872  degrees.
873  (2) Returns \b undef when \p l1, \p l2, or \p n is not a
874  line or vector or have different dimensions or when
875  they do not intersect.
876 
877  \details
878 
879  See [line conventions] for more information.
880 
881  \sa angle_ll().
882  \amu_eval(${group_references})
883 *******************************************************************************/
884 function angle_lll
885 (
886  l1,
887  l2,
888  n
889 ) = !is_vol(l1) ? undef
890  : !is_vol(l2) ? undef
891  : !is_vol(n) ? undef
892  : (len(l1) != len(l2)) ? undef
893  : (len(l2) != len(n)) ? undef
894  : atan2(striple_lll(n, l1, l2), dot_ll(l1, l2));
895 
896 //! Compute the normalized unit vector of a line or vector.
897 /***************************************************************************//**
898  \param l <line> A line or vector.
899 
900  \returns (1) <vector> The normalized unit vector.
901  (2) Returns \b undef when \p l is not a line or vector.
902 
903  \details
904 
905  See [line conventions] for more information.
906 
907  \amu_eval(${group_references})
908 *******************************************************************************/
909 function unit_l
910 (
911  l
912 ) = !is_vol(l) ? undef
914 
915 //! Test if three lines or vectors are coplanar in 3d-space.
916 /***************************************************************************//**
917  \param l1 <line-3d> A 3d line or vector 1.
918  \param l2 <line-3d> A 3d line or vector 2.
919  \param l3 <line-3d> A 3d line or vector 3.
920  \param d <integer> The number of decimal places to consider.
921 
922  \returns (1) <boolean> \b true when all three lines or vectors are
923  coplanar, and \b false otherwise.
924  (2) Returns \b undef when \p l1, \p l2, or \p l3 is not a
925  line or vector or have different dimensions,
926 
927  \details
928 
929  See [line conventions] for more information. See [Wikipedia] for
930  more information.
931 
932  \note When lines or vectors are specified with start and end
933  points, this function tests if they are in a planes
934  parallel to the coplanar.
935 
936  [Wikipedia]: https://en.wikipedia.org/wiki/Coplanarity
937  \amu_eval(${group_references})
938 *******************************************************************************/
939 function are_coplanar_lll
940 (
941  l1,
942  l2,
943  l3,
944  d = 6
945 ) = !is_vol(l1) ? undef
946  : !is_vol(l2) ? undef
947  : !is_vol(l3) ? undef
948  : (len(l1) != len(l2)) ? undef
949  : (len(l2) != len(l3)) ? undef
950  : (round_d(striple_lll(l1, l2, l3), d) == 0);
951 
952 //----------------------------------------------------------------------------//
953 // set 5: plane and pnorm
954 //----------------------------------------------------------------------------//
955 
956 //! Convert a planes' normal specification into a normal vector.
957 /***************************************************************************//**
958  \param n <pnorm> A [plane normal].
959 
960  \param cw <boolean> Point ordering. When the plane specified as
961  non-collinear points, this indicates ordering.
962 
963  \returns (1) <normal> A vector-3d normal to the plane.
964  (2) Returns \b undef when \p n is not a plane.
965 
966  \details
967 
968  When \p n is not a valid plane, \b undef is returned. The computed
969  normal vector is not normalized to its unit vector.
970 
971  \amu_eval(${group_references})
972 *******************************************************************************/
973 function plane_to_normal
974 (
975  n,
976  cw = true
977 ) = !is_plane(n) ? undef
978 
979  // n is normal
981 
982  // make 3d
983  : let
984  (
985  q = [for (i=n) point_to_3d(i)]
986  )
987  (len(n) == 2) ?
988  // vectors [v1, v2].
989  cross(q[0], q[1])
990 
991  // 3 points.
992  : cross(q[0]-q[1], q[2]-q[1]) * ((cw == true) ? 1 : -1);
993 
994 //! @}
995 //! @}
996 
997 //----------------------------------------------------------------------------//
998 // openscad-amu auxiliary scripts
999 //----------------------------------------------------------------------------//
1000 
1001 /*
1002 BEGIN_SCOPE validate;
1003  BEGIN_OPENSCAD;
1004  include <omdl-base.scad>;
1005  include <common/validation.scad>;
1006 
1007  echo( str("openscad version ", version()) );
1008 
1009  // test-values columns
1010  test_c =
1011  [
1012  ["id", "identifier"],
1013  ["td", "description"],
1014  ["tv", "test value"]
1015  ];
1016 
1017  // test-values rows
1018  test_r =
1019  [
1020  ["fac", "Function argument count", undef
1021  ],
1022  ["crp", "Result precision", undef
1023  ],
1024  ["t01", "All undefined", [undef,undef,undef,undef,undef,undef]
1025  ],
1026  ["t02", "All empty lists", [empty_lst,empty_lst,empty_lst,empty_lst,empty_lst,empty_lst]
1027  ],
1028  ["t03", "All scalars", [60, 50, 40, 30, 20, 10]
1029  ],
1030  ["t04", "All 1d vectors", [[99], [58], [12], [42], [15], [1]]
1031  ],
1032  ["t05", "All 2d vectors", [
1033  [99,2], [58,16], [12,43],
1034  [42,13], [15,59], [1,85]
1035  ]
1036  ],
1037  ["t06", "All 3d vectors", [
1038  [199,20,55], [158,116,75], [12,43,90],
1039  [42,13,34], [15,59,45], [62,33,69]
1040  ]
1041  ],
1042  ["t07", "All 4d vectors", [
1043  [169,27,35,10], [178,016,25,20], [12,43,90,30],
1044  [42,13,34,60], [15,059,45,50], [62,33,69,40]
1045  ]
1046  ],
1047  ["t08", "Orthogonal vectors", [
1048  +x_axis3d_uv, +y_axis3d_uv, +z_axis3d_uv,
1049  -x_axis3d_uv, -y_axis3d_uv, -z_axis3d_uv,
1050  ]
1051  ],
1052  ["t09", "Coplanar vectors", [
1053  +x_axis3d_uv, +y_axis3d_uv, [2,2,0],
1054  origin3d, origin3d, origin3d,
1055  ]
1056  ]
1057  ];
1058 
1059  test_ids = table_get_row_ids( test_r );
1060 
1061  // expected columns: ("id" + one column for each test)
1062  good_c = merge_p([concat("id", test_ids), concat("identifier", test_ids)]);
1063 
1064  // expected rows: ("golden" test results), use 'skip' to skip test
1065  skip = -1; // skip test
1066 
1067  good_r =
1068  [ // function
1069  ["distance_pp",
1070  2, // fac
1071  4, // crp
1072  undef, // t01
1073  undef, // t02
1074  undef, // t03
1075  undef, // t04
1076  43.3244, // t05
1077  106.2873, // t06
1078  undef, // t07
1079  1.4142, // t08
1080  1.4142 // t09
1081  ],
1082  ["is_left_ppp",
1083  3, // fac
1084  4, // crp
1085  undef, // t01
1086  undef, // t02
1087  undef, // t03
1088  undef, // t04
1089  -463, // t05
1090  17009, // t06
1091  undef, // t07
1092  1, // t08
1093  -3 // t09
1094  ],
1095  ["point_to_3d",
1096  1, // fac
1097  4, // crp
1098  undef, // t01
1099  undef, // t02
1100  undef, // t03
1101  undef, // t04
1102  [99,2,0], // t05
1103  [199,20,55], // t06
1104  undef, // t07
1105  x_axis3d_uv, // t08
1106  x_axis3d_uv // t09
1107  ],
1108  ["line_dim",
1109  2, // fac
1110  4, // crp
1111  undef, // t01
1112  undef, // t02
1113  2, // t03
1114  undef, // t04
1115  2, // t05
1116  3, // t06
1117  undef, // t07
1118  3, // t08
1119  3 // t09
1120  ],
1121  ["line_tp",
1122  2, // fac
1123  4, // crp
1124  undef, // t01
1125  undef, // t02
1126  [60,50], // t03
1127  undef, // t04
1128  [58,16], // t05
1129  [158,116,75], // t06
1130  undef, // t07
1131  y_axis3d_uv, // t08
1132  y_axis3d_uv // t09
1133  ],
1134  ["line_ip",
1135  2, // fac
1136  4, // crp
1137  undef, // t01
1138  undef, // t02
1139  origin2d, // t03
1140  undef, // t04
1141  [99,2], // t05
1142  [199,20,55], // t06
1143  undef, // t07
1144  x_axis3d_uv, // t08
1145  x_axis3d_uv // t09
1146  ],
1147  ["vol_to_origin",
1148  2, // fac
1149  4, // crp
1150  undef, // t01
1151  undef, // t02
1152  [60,50], // t03
1153  undef, // t04
1154  [-41,14], // t05
1155  [-41,96,20], // t06
1156  undef, // t07
1157  [-1,1,0], // t08
1158  [-1,1,0] // t09
1159  ],
1160  ["dot_ll",
1161  4, // fac
1162  4, // crp
1163  undef, // t01
1164  undef, // t02
1165  3900, // t03
1166  undef, // t04
1167  -1650, // t05
1168  -5230, // t06
1169  undef, // t07
1170  1, // t08
1171  0 // t09
1172  ],
1173  ["cross_ll",
1174  4, // fac
1175  4, // crp
1176  skip, // t01
1177  skip, // t02
1178  skip, // t03
1179  skip, // t04
1180  810, // t05
1181  [-4776,-1696,-1650], // t06
1182  skip, // t07
1183  [-1,-1,1], // t08
1184  [0,0,4] // t09
1185  ],
1186  ["striple_lll",
1187  6, // fac
1188  4, // crp
1189  skip, // t01
1190  skip, // t02
1191  skip, // t03
1192  skip, // t04
1193  [-14760,5040], // t05
1194  -219976, // t06
1195  skip, // t07
1196  -2, // t08
1197  0 // t09
1198  ],
1199  ["angle_ll",
1200  4, // fac
1201  4, // crp
1202  undef, // t01
1203  undef, // t02
1204  -2.9357, // t03
1205  undef, // t04
1206  153.8532, // t05
1207  134.4573, // t06
1208  undef, // t07
1209  60, // t08
1210  90 // t09
1211  ],
1212  ["angle_lll",
1213  6, // fac
1214  4, // crp
1215  skip, // t01
1216  skip, // t02
1217  skip, // t03
1218  skip, // t04
1219  skip, // t05
1220  -91.362, // t06
1221  skip, // t07
1222  -63.4349, // t08
1223  0 // t09
1224  ],
1225  ["unit_l",
1226  2, // fac
1227  4, // crp
1228  undef, // t01
1229  undef, // t02
1230  [.7682,0.6402], // t03
1231  undef, // t04
1232  [-0.9464,0.3231], // t05
1233  [-0.3857,0.9032,0.1882], // t06
1234  undef, // t07
1235  [-0.7071,0.7071,0], // t08
1236  [-0.7071,0.7071,0] // t09
1237  ],
1238  ["are_coplanar_lll",
1239  6, // fac
1240  4, // crp
1241  skip, // t01
1242  skip, // t02
1243  skip, // t03
1244  skip, // t04
1245  skip, // t05
1246  false, // t06
1247  skip, // t07
1248  false, // t08
1249  true // t09
1250  ],
1251  ["plane_to_normal",
1252  2, // fac
1253  4, // crp
1254  skip, // t01
1255  skip, // t02
1256  [60,50,0], // t03
1257  skip, // t04
1258  [0,0,1468], // t05
1259  [-4880,-6235,19924], // t06
1260  skip, // t07
1261  z_axis3d_uv, // t08
1262  z_axis3d_uv // t09
1263  ]
1264  ];
1265 
1266  // sanity-test tables
1267  table_check( test_r, test_c, false );
1268  table_check( good_r, good_c, false );
1269 
1270  // validate helper function and module
1271  function get_value( vid ) = table_get_value(test_r, test_c, vid, "tv");
1272  function gv( vid, e ) = get_value( vid )[e];
1273  module log_test( m ) { log_type ( "test", m ); }
1274  module log_skip( f ) { log_test ( str("ignore: '", f, "'") ); }
1275  module run( fname, vid )
1276  {
1277  value_text = table_get_value(test_r, test_c, vid, "td");
1279  if ( table_get_value(good_r, good_c, fname, vid) != skip )
1280  children();
1281  else
1282  log_test( str(vid, " -skip-: '", fname, "(", value_text, ")'") );
1283  }
1284  module test( fname, fresult, vid, pair )
1285  {
1286  value_text = table_get_value(test_r, test_c, vid, "td");
1287  fname_argc = table_get_value(good_r, good_c, fname, "fac");
1288  comp_prcsn = table_get_value(good_r, good_c, fname, "crp");
1289  pass_value = table_get_value(good_r, good_c, fname, vid);
1290 
1291  test_pass = validate(cv=fresult, t="almost", ev=pass_value, p=comp_prcsn, pf=true);
1292  farg_text = (pair == true)
1293  ? strl(append_e(", ", sequence_ns(select_r(get_value(vid), [0:fname_argc-1]), n=2, s=2), r=false, j=false, l=false))
1294  : strl(append_e(", ", select_r(get_value(vid), [0:fname_argc-1]), r=false, j=false, l=false));
1295  test_text = validate(str(fname, "(", farg_text, ")=", pass_value), fresult, "almost", pass_value, comp_prcsn);
1296 
1297  if ( pass_value != skip )
1298  {
1299  if ( !test_pass )
1300  log_test( str(vid, " ", test_text, " (", value_text, ")") );
1301  else
1302  log_test( str(vid, " ", test_text) );
1303  }
1304  else
1305  log_test( str(vid, " -skip-: '", fname, "(", value_text, ")'") );
1306  }
1307 
1308  // Indirect function calls would be very useful here!!!
1309  run_ids = delete( test_ids, mv=["fac", "crp"] );
1310 
1311  // set 1: identify
1312  log_skip( "is_point()" );
1313  log_skip( "is_vector()" );
1314  log_skip( "is_line()" );
1315  log_skip( "is_vol()" );
1316  log_skip( "is_plane()" );
1317 
1318  // set 2: point
1319  for (vid=run_ids) run("distance_pp",vid) test( "distance_pp", distance_pp(gv(vid,0),gv(vid,1)), vid, false );
1320  log_skip( "distance_pl()" );
1321  log_skip( "distance_pn()" );
1322  for (vid=run_ids) run("is_left_ppp",vid) test( "is_left_ppp", is_left_ppp(gv(vid,0),gv(vid,1),gv(vid,2)), vid, false );
1323  log_skip( "point_closest_pl()" );
1324  log_skip( "point_closest_pn()" );
1325  for (vid=run_ids) run("point_to_3d",vid) test( "point_to_3d", point_to_3d(gv(vid,0)), vid, false );
1326  log_skip( "interpolate2d_l_pp()" );
1327 
1328  // set 3: vector
1329 
1330  // set 4: line (or vector)
1331  log_skip( "line2d_new()" );
1332  log_skip( "line3d_new()" );
1333  log_skip( "line_new()" );
1334  for (vid=run_ids) run("line_dim",vid) test( "line_dim", line_dim([gv(vid,0),gv(vid,1)]), vid, true );
1335  for (vid=run_ids) run("line_tp",vid) test( "line_tp", line_tp([gv(vid,0),gv(vid,1)]), vid, true );
1336  for (vid=run_ids) run("line_ip",vid) test( "line_ip", line_ip([gv(vid,0),gv(vid,1)]), vid, true );
1337  for (vid=run_ids) run("vol_to_origin",vid) test( "vol_to_origin", vol_to_origin([gv(vid,0),gv(vid,1)]), vid, true );
1338  log_skip( "vol_to_point()" );
1339  for (vid=run_ids) run("dot_ll",vid) test( "dot_ll", dot_ll([gv(vid,0),gv(vid,1)],[gv(vid,2),gv(vid,3)]), vid, true );
1340  for (vid=run_ids) run("cross_ll",vid) test( "cross_ll", cross_ll([gv(vid,0),gv(vid,1)],[gv(vid,2),gv(vid,3)]), vid, true );
1341  for (vid=run_ids) run("striple_lll",vid) test( "striple_lll", striple_lll([gv(vid,0),gv(vid,1)],[gv(vid,2),gv(vid,3)],[gv(vid,4),gv(vid,5)]), vid, true );
1342  for (vid=run_ids) run("angle_ll",vid) test( "angle_ll", angle_ll([gv(vid,0),gv(vid,1)],[gv(vid,2),gv(vid,3)]), vid, true );
1343  for (vid=run_ids) run("angle_lll",vid) test( "angle_lll", angle_lll([gv(vid,0),gv(vid,1)],[gv(vid,2),gv(vid,3)],[gv(vid,4),gv(vid,5)]), vid, true );
1344  for (vid=run_ids) run("unit_l",vid) test( "unit_l", unit_l([gv(vid,0),gv(vid,1)]), vid, true );
1345  for (vid=run_ids) run("are_coplanar_lll",vid) test( "are_coplanar_lll", are_coplanar_lll([gv(vid,0),gv(vid,1)],[gv(vid,2),gv(vid,3)],[gv(vid,4),gv(vid,5)]), vid, true );
1346 
1347  // set 5: plane and pnorm
1348  for (vid=run_ids) run("plane_to_normal",vid) test( "plane_to_normal", plane_to_normal([gv(vid,0),gv(vid,1)]), vid, true );
1349 
1350  // end_include
1351  END_OPENSCAD;
1352 
1353  BEGIN_MFSCRIPT;
1354  include --path "${INCLUDE_PATH}" {var_init,var_gen_term}.mfs;
1355  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
1356  END_MFSCRIPT;
1357 END_SCOPE;
1358 */
1359 
1360 //----------------------------------------------------------------------------//
1361 // end of file
1362 //----------------------------------------------------------------------------//
origin2d
<point-2d> The origin point coordinate in 2d Euclidean space.
Definition: constants.scad:407
origin3d
<point-3d> The origin point coordinate in 3-dimensional Euclidean space.
Definition: constants.scad:425
function is_vector(v)
Test if a value defines a Euclidean vector.
function line_tp(l)
Return the terminal point of a line or vector.
function line_new(m=1, a=0, t, p1, p2, v)
Construct a directed line.
function dot_ll(l1, l2)
Compute the dot product of two lines or vectors in a 3d or 2d-space.
function are_coplanar_lll(l1, l2, l3, d=6)
Test if three lines or vectors are coplanar in 3d-space.
function point_closest_pn(p, n, np)
Compute the coordinates of the closest point on a plane to a given point.
function angle_ll(l1, l2, s=true)
Compute the angle between two lines or vectors in a 3d or 2d-space.
function line3d_new(m=1, a=0, t=90, p1=origin3d, p2, v)
Construct a 3 dimensional directed line.
function line_dim(l)
Return the number of dimensions of a line or vector.
function striple_lll(l1, l2, l3)
Compute the scalar triple product of three lines or vectors in a 3d or 2d-space.
function angle_lll(l1, l2, n)
Compute the angle between two lines or vectors in a 3d-space.
function is_point(v)
Test if a value defines a point.
function distance_pn(p, n, np)
Compute the shortest distance between a point and a plane.
function vol_to_origin(l)
Convert line to vector by shifting it to the origin.
function point_to_3d(p)
Return 3d point unchanged or add a zeroed third dimension to 2d point.
function vol_to_point(l, p)
Convert a vector to a line or move a line to another coordinate point.
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 line2d_new(m=1, a=0, p1=origin2d, p2, v)
Construct a 2 dimensional directed line.
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 plane_to_normal(n, cw=true)
Convert a planes' normal specification into a normal vector.
function point_closest_pl(p, l)
Compute the coordinates of the closest point on a line to a given point.
function is_line(v)
Test if a value defines a line.
function distance_pl(p, l)
Compute the shortest distance between a point and a line.
function is_plane(v)
Test if a value defines a plane.
function is_vol(v)
Test if a value defines a vector or a line.
function distance_pp(p1, p2)
Compute the distance between two points.
function all_numbers(v)
Test if all elements of an iterable value are numbers.
function round_d(v, d=6)
Round a list of numbers to a fixed number of decimal point digits.
function consts(l, v, u=false)
Create a list of constant or incrementing elements.
function sum(v, i1, i2)
Compute the sum of a list of numbers.
function is_between(v, l, u)
Test if a numerical value is between an upper and lower bounds.
function is_number(v)
Test if a value is a number.