omdl  v1.0
OpenSCAD Mechanical Design Library
polyhedron.scad
Go to the documentation of this file.
1 //! Polyhedron 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 (Polyhedrons)
31  \amu_define group_brief (Polyhedron mathematical functions; 3-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 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  All functions in this group operate on polyhedra defined by a list
64  of 3d cartesian coordinates \p c and a list of faces \p f,
65  following OpenSCAD's native \c polyhedron() convention:
66 
67  - Coordinates are given as \c [[x,y,z], ...].
68  - Face vertex indices are ordered \b counter-clockwise when viewed
69  from \b outside the solid (right-hand rule outward normal).
70  - Functions whose names include the \c _tf_ infix require that all
71  faces be \b triangulated (exactly 3 vertex indices per face).
72  Non-triangulated meshes must be tessellated before being passed
73  to these functions. Passing a face with fewer or more than 3
74  indices to a \c _tf_ function produces \c undef in the
75  corresponding term and will silently corrupt the result.
76 *******************************************************************************/
77 
78 //----------------------------------------------------------------------------//
79 // members
80 //----------------------------------------------------------------------------//
81 
82 //----------------------------------------------------------------------------//
83 // shape properties
84 //----------------------------------------------------------------------------//
85 
86 //! \name Properties
87 //! @{
88 
89 //! Compute the surface area of a polyhedron in a Euclidean 3d-space.
90 /***************************************************************************//**
91  \param c <points-3d> A list of 3d cartesian coordinates
92  [[x, y, z], ...].
93 
94  \param f <integer-list-list> A list of faces that enclose the
95  polyhedron, where each face is a list of coordinate
96  indexes. Faces may be polygons with any number of
97  vertices >= 3 and need not be triangulated.
98 
99  \returns <decimal> The total surface area of the polyhedron in square
100  units of the coordinate space.
101 
102  \details
103 
104  The surface area is computed by summing the area of each face
105  polygon via \c polygon3d_area(). Faces are not required to be
106  triangular, but each face is assumed to be planar.
107 
108  \b Example — unit cube (expected area = 6.0):
109 
110  \code{.scad}
111  c = [[0,0,0],[1,0,0],[1,1,0],[0,1,0],
112  [0,0,1],[1,0,1],[1,1,1],[0,1,1]];
113 
114  f = [[0,3,2,1],[4,5,6,7],
115  [0,1,5,4],[1,2,6,5],[2,3,7,6],[3,0,4,7]];
116 
117  echo(polyhedron_area(c, f)); // ECHO: 6
118  \endcode
119 *******************************************************************************/
120 function polyhedron_area
121 (
122  c,
123  f
124 ) = sum([for (fi = f) polygon3d_area(c, [fi])]);
125 
126 //! Compute the signed volume of a triangulated polyhedron in a Euclidean 3d-space.
127 /***************************************************************************//**
128  \param c <points-3d> A list of 3d cartesian coordinates
129  [[x, y, z], ...].
130 
131  \param f <integer-list-3-list> A list of triangular faces that
132  enclose the polyhedron, where each face is a list of
133  exactly three coordinate indexes. Non-triangular faces
134  must be tessellated before calling this function; any
135  face with a vertex count other than 3 is skipped
136  (contributing 0 to the sum) and a warning is implicitly
137  indicated by an \c undef guard — see note below.
138 
139  \returns <decimal> The volume of the given polyhedron in cubic units
140  of the coordinate space. The sign of the result is positive
141  when face normals point outward (counter-clockwise winding
142  per OpenSCAD convention) and negative otherwise.
143 
144  \details
145 
146  Volume is computed using the [divergence theorem] applied to a
147  signed tetrahedral decomposition. For each triangular face with
148  vertices \f$ \mathbf{p}_0, \mathbf{p}_1, \mathbf{p}_2 \f$, the
149  signed tetrahedral volume relative to the origin is:
150 
151  \f[
152  V_i = \frac{1}{6}\, \mathbf{p}_0 \cdot (\mathbf{p}_1 \times \mathbf{p}_2)
153  \f]
154 
155  The total volume is the sum over all faces:
156 
157  \f[
158  V = \sum_i V_i
159  \f]
160 
161  This formula is exact for any closed, consistently wound
162  triangulated mesh regardless of whether the mesh encloses the
163  origin.
164 
165  See [Wikipedia: Polyhedron – Volume][Wikipedia] for background.
166 
167  \note All faces must be triangles (exactly 3 vertex indices) wound
168  counter-clockwise when viewed from outside (OpenSCAD's native
169  \c polyhedron() convention). Faces that do not satisfy
170  \c len(fi)==3 are excluded from the summation via a filtered
171  list comprehension, preventing silent \c undef corruption.
172 
173  \note The result is a signed volume. Take \c abs() of the return
174  value if the sign of winding is uncertain.
175 
176  [Wikipedia]: https://en.wikipedia.org/wiki/Polyhedron#Volume
177  [divergence theorem]: https://en.wikipedia.org/wiki/Divergence_theorem
178 
179  \b Example — unit cube triangulated (expected volume = 1.0):
180 
181  \code{.scad}
182  c = [[0,0,0],[1,0,0],[1,1,0],[0,1,0],
183  [0,0,1],[1,0,1],[1,1,1],[0,1,1]];
184 
185  f = [[0,1,2],[0,2,3],[4,6,5],[4,7,6],
186  [0,5,1],[0,4,5],[1,6,2],[1,5,6],
187  [2,7,3],[2,6,7],[3,4,0],[3,7,4]];
188 
189  echo(polyhedron_tf_volume(c, f)); // ECHO: 1
190  \endcode
191 *******************************************************************************/
192 function polyhedron_tf_volume
193 (
194  c,
195  f
196 ) =
197  let
198  (
199  vv =
200  [
201  for (fi = f)
202  if (len(fi) == 3)
203  let
204  (
205  p0 = c[fi[0]],
206  p1 = c[fi[1]],
207  p2 = c[fi[2]]
208  )
209  p0 * cross(p1, p2)
210  ],
211 
212  sv = sum(vv)
213  )
214  sv / 6;
215 
216 //! Compute the center of mass of a triangulated polyhedron in a Euclidean 3d-space.
217 /***************************************************************************//**
218  \param c <points-3d> A list of 3d cartesian coordinates
219  [[x, y, z], ...].
220 
221  \param f <integer-list-3-list> A list of triangular faces that
222  enclose the polyhedron, where each face is a list of
223  exactly three coordinate indexes. Non-triangular faces
224  must be tessellated before calling this function.
225 
226  \returns <point-3d> The center of mass (centroid) of the solid
227  polyhedron in the same coordinate space as \p c.
228 
229  \details
230 
231  The centroid is computed via the [divergence theorem] using first-
232  order midpoint quadrature. For each triangular face with vertices
233  \f$ \mathbf{p}_0, \mathbf{p}_1, \mathbf{p}_2 \f$ and signed weight
234 
235  \f[
236  w_i = \mathbf{p}_0 \cdot (\mathbf{p}_1 \times \mathbf{p}_2)
237  \f]
238 
239  the centroid contribution is:
240 
241  \f[
242  \mathbf{g}_i = (\mathbf{p}_0 + \mathbf{p}_1 + \mathbf{p}_2)\, w_i
243  \f]
244 
245  and the overall centroid is:
246 
247  \f[
248  \mathbf{G} = \frac{\displaystyle\sum_i \mathbf{g}_i}{24\,V}
249  \f]
250 
251  where \f$ V \f$ is the signed volume returned by \c
252  polyhedron_tf_volume(). Volume and the weighted centroid sum are
253  computed in a \b single pass over the face list to avoid redundant
254  iteration.
255 
256  \b Approximation notice: midpoint quadrature is exact only when the
257  integrand is linear over each face. For a flat triangulated mesh
258  this is exact; for curved or high-curvature approximations the
259  error is \f$ O(h^2) \f$ where \f$ h \f$ is the maximum edge length.
260 
261  See [Wikipedia: Centroid][Wikipedia] for background.
262 
263  \note All faces must be triangles (exactly 3 vertex indices) wound
264  counter-clockwise when viewed from outside (OpenSCAD's native
265  \c polyhedron() convention), consistent with
266  \c polyhedron_tf_volume(). Faces that do not satisfy
267  \c len(fi)==3 are excluded from the summation.
268 
269  [Wikipedia]: https://en.wikipedia.org/wiki/Centroid
270  [divergence theorem]: https://en.wikipedia.org/wiki/Divergence_theorem
271 
272  \b Example — unit cube triangulated (expected centroid = [0.5, 0.5, 0.5]):
273 
274  \code{.scad}
275  c = [[0,0,0],[1,0,0],[1,1,0],[0,1,0],
276  [0,0,1],[1,0,1],[1,1,1],[0,1,1]];
277 
278  f = [[0,1,2],[0,2,3],[4,6,5],[4,7,6],
279  [0,5,1],[0,4,5],[1,6,2],[1,5,6],
280  [2,7,3],[2,6,7],[3,4,0],[3,7,4]];
281 
282  echo(polyhedron_tf_centroid(c, f)); // ECHO: [0.5, 0.5, 0.5]
283  \endcode
284 *******************************************************************************/
285 function polyhedron_tf_centroid
286 (
287  c,
288  f
289 ) =
290  let
291  (
292  // Single pass: accumulate both the signed volume terms and the
293  // weighted centroid terms simultaneously to avoid iterating the
294  // face list twice.
295  terms =
296  [
297  for (fi = f)
298  if (len(fi) == 3)
299  let
300  (
301  p0 = c[fi[0]],
302  p1 = c[fi[1]],
303  p2 = c[fi[2]],
304 
305  // Signed tetrahedral scalar: p0 · (p1 × p2)
306  w = p0 * cross(p1, p2)
307  )
308  // Each entry carries [volume_term, weighted_centroid_term]
309  [w, (p0 + p1 + p2) * w]
310  ],
311 
312  sv = sum([for (t = terms) t[0]]), // sum of volume terms
313  sg = sum([for (t = terms) t[1]]), // sum of weighted centroid terms
314 
315  volume = sv / 6
316  )
317  sg / (24 * volume);
318 
319 //! @}
320 
321 //! @}
322 //! @}
323 
324 //----------------------------------------------------------------------------//
325 // openscad-amu auxiliary scripts
326 //----------------------------------------------------------------------------//
327 
328 /*
329 BEGIN_SCOPE validate;
330  BEGIN_OPENSCAD;
331  include <omdl-base.scad>;
332  include <common/validation.scad>;
333 
334  echo( str("openscad version ", version()) );
335  for (i=[1:3]) echo( "not tested:" );
336 
337  // end_include
338  END_OPENSCAD;
339 
340  BEGIN_MFSCRIPT;
341  include --path "${INCLUDE_PATH}" {var_init,var_gen_term}.mfs;
342  include --path "${INCLUDE_PATH}" scr_make_mf.mfs;
343  END_MFSCRIPT;
344 END_SCOPE;
345 */
346 
347 //----------------------------------------------------------------------------//
348 // end of file
349 //----------------------------------------------------------------------------//
function sum(v, i1, i2)
Compute the sum of a list of numbers.
function polygon3d_area(c, p, n)
Compute the area of a polygon in a Euclidean 3d-space.
function polyhedron_area(c, f)
Compute the surface area of a polyhedron in a Euclidean 3d-space.
function polyhedron_tf_volume(c, f)
Compute the signed volume of a triangulated polyhedron in a Euclidean 3d-space.
function polyhedron_tf_centroid(c, f)
Compute the center of mass of a triangulated polyhedron in a Euclidean 3d-space.