surfaceInertia.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2011-2016 OpenFOAM Foundation
9  Copyright (C) 2015-2024 OpenCFD Ltd.
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 Application
28  surfaceInertia
29 
30 Group
31  grpSurfaceUtilities
32 
33 Description
34  Calculates the inertia tensor, principal axes and moments of a
35  command line specified triSurface.
36 
37  Inertia can either be of the solid body or of a thin shell.
38 
39 \*---------------------------------------------------------------------------*/
40 
41 #include "argList.H"
42 #include "ListOps.H"
43 #include "triSurface.H"
44 #include "OFstream.H"
45 #include "meshTools.H"
46 #include "Random.H"
47 #include "transform.H"
48 #include "IOmanip.H"
49 #include "Pair.H"
50 #include "momentOfInertia.H"
51 
52 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
53 
54 using namespace Foam;
55 
56 int main(int argc, char *argv[])
57 {
59  (
60  "Calculates the inertia tensor and principal axes and moments"
61  " of the specified surface.\n"
62  "Inertia can either be of the solid body or of a thin shell."
63  );
64 
66  argList::addArgument("input", "The input surface file");
67 
69  (
70  "shellProperties",
71  "Inertia of a thin shell"
72  );
73 
75  (
76  "density",
77  "scalar",
78  "Specify density, "
79  "kg/m3 for solid properties, kg/m2 for shell properties"
80  );
81 
83  (
84  "referencePoint",
85  "vector",
86  "Inertia relative to this point, not the centre of mass"
87  );
88 
89  argList args(argc, argv);
90 
91  const auto surfFileName = args.get<fileName>(1);
92  const scalar density = args.getOrDefault<scalar>("density", 1);
93 
94  vector refPt = Zero;
95  bool calcAroundRefPt = args.readIfPresent("referencePoint", refPt);
96 
97  const triSurface surf(surfFileName);
98 
99  scalar m = 0.0;
100  vector cM = Zero;
101  tensor J = Zero;
102 
103  if (args.found("shellProperties"))
104  {
105  momentOfInertia::massPropertiesShell(surf, density, m, cM, J);
106  }
107  else
108  {
109  momentOfInertia::massPropertiesSolid(surf, density, m, cM, J);
110  }
111 
112  if (m < 0)
113  {
115  << "Negative mass detected, the surface may be inside-out." << endl;
116  }
117 
118  vector eVal = eigenValues(symm(J));
119 
120  tensor eVec = eigenVectors(symm(J));
121 
122  label pertI = 0;
123 
124  Random rand(57373);
125 
126  while ((magSqr(eVal) < VSMALL) && pertI < 10)
127  {
129  << "No eigenValues found, shape may have symmetry, "
130  << "perturbing inertia tensor diagonal" << endl;
131 
132  J.xx() *= 1.0 + SMALL*rand.sample01<scalar>();
133  J.yy() *= 1.0 + SMALL*rand.sample01<scalar>();
134  J.zz() *= 1.0 + SMALL*rand.sample01<scalar>();
135 
136  eVal = eigenValues(symm(J));
137 
138  eVec = eigenVectors(symm(J));
139 
140  pertI++;
141  }
142 
143  bool showTransform = true;
144 
145  if
146  (
147  (mag(eVec.x() ^ eVec.y()) > (1.0 - SMALL))
148  && (mag(eVec.y() ^ eVec.z()) > (1.0 - SMALL))
149  && (mag(eVec.z() ^ eVec.x()) > (1.0 - SMALL))
150  )
151  {
152  // Make the eigenvectors a right handed orthogonal triplet
153  eVec = tensor
154  (
155  eVec.x(),
156  eVec.y(),
157  eVec.z() * sign((eVec.x() ^ eVec.y()) & eVec.z())
158  );
159 
160  // Finding the most natural transformation. Using Lists
161  // rather than tensors to allow indexed permutation.
162 
163  // Cartesian basis vectors - right handed orthogonal triplet
164  FixedList<vector, 3> cartesian;
165 
166  cartesian[0] = vector(1, 0, 0);
167  cartesian[1] = vector(0, 1, 0);
168  cartesian[2] = vector(0, 0, 1);
169 
170  // Principal axis basis vectors - right handed orthogonal triplet
171  FixedList<vector, 3> principal;
172 
173  principal[0] = eVec.x();
174  principal[1] = eVec.y();
175  principal[2] = eVec.z();
176 
177  // Matching axis indices, first: cartesian, second:principal
178  Pair<label> match(-1, -1);
179  scalar maxMagDotProduct = -GREAT;
180 
181  forAll(cartesian, cI)
182  {
183  forAll(principal, pI)
184  {
185  scalar magDotProduct = mag(cartesian[cI] & principal[pI]);
186 
187  if (maxMagDotProduct < magDotProduct)
188  {
189  maxMagDotProduct = magDotProduct;
190 
191  match.first() = cI;
192  match.second() = pI;
193  }
194  }
195  }
196 
197  scalar sense = sign
198  (
199  cartesian[match.first()] & principal[match.second()]
200  );
201 
202  if (sense < 0)
203  {
204  // Invert the best match direction and swap the order of
205  // the other two vectors
206 
207  FixedList<vector, 3> tPrincipal = principal;
208 
209  tPrincipal[match.second()] *= -1;
210 
211  tPrincipal[(match.second() + 1) % 3] =
212  principal[(match.second() + 2) % 3];
213 
214  tPrincipal[(match.second() + 2) % 3] =
215  principal[(match.second() + 1) % 3];
216 
217  principal = tPrincipal;
218 
219  vector tEVal = eVal;
220 
221  tEVal[(match.second() + 1) % 3] = eVal[(match.second() + 2) % 3];
222 
223  tEVal[(match.second() + 2) % 3] = eVal[(match.second() + 1) % 3];
224 
225  eVal = tEVal;
226  }
227 
228  label permutationDelta = match.second() - match.first();
229 
230  if (permutationDelta != 0)
231  {
232  // Add 3 to the permutationDelta to avoid negative indices
233 
234  permutationDelta += 3;
235 
236  FixedList<vector, 3> tPrincipal = principal;
237 
238  vector tEVal = eVal;
239 
240  for (label i = 0; i < 3; i++)
241  {
242  tPrincipal[i] = principal[(i + permutationDelta) % 3];
243 
244  tEVal[i] = eVal[(i + permutationDelta) % 3];
245  }
246 
247  principal = tPrincipal;
248 
249  eVal = tEVal;
250  }
251 
252  const label matchedAlready = match.first();
253 
254  match = Pair<label>(-1, -1);
255  maxMagDotProduct = -GREAT;
256 
257  forAll(cartesian, cI)
258  {
259  if (cI == matchedAlready)
260  {
261  continue;
262  }
263 
264  forAll(principal, pI)
265  {
266  if (pI == matchedAlready)
267  {
268  continue;
269  }
270 
271  scalar magDotProduct = mag(cartesian[cI] & principal[pI]);
272 
273  if (maxMagDotProduct < magDotProduct)
274  {
275  maxMagDotProduct = magDotProduct;
276 
277  match.first() = cI;
278  match.second() = pI;
279  }
280  }
281  }
282 
283  sense = sign
284  (
285  cartesian[match.first()] & principal[match.second()]
286  );
287 
288  if (sense < 0 || (match.second() - match.first()) != 0)
289  {
290  principal[match.second()] *= -1;
291 
292  FixedList<vector, 3> tPrincipal = principal;
293 
294  tPrincipal[(matchedAlready + 1) % 3] =
295  principal[(matchedAlready + 2) % 3]*-sense;
296 
297  tPrincipal[(matchedAlready + 2) % 3] =
298  principal[(matchedAlready + 1) % 3]*-sense;
299 
300  principal = tPrincipal;
301 
302  vector tEVal = eVal;
303 
304  tEVal[(matchedAlready + 1) % 3] = eVal[(matchedAlready + 2) % 3];
305 
306  tEVal[(matchedAlready + 2) % 3] = eVal[(matchedAlready + 1) % 3];
307 
308  eVal = tEVal;
309  }
310 
311  eVec = tensor(principal[0], principal[1], principal[2]);
312 
313  // {
314  // tensor R = rotationTensor(vector(1, 0, 0), eVec.x());
315 
316  // R = rotationTensor(R & vector(0, 1, 0), eVec.y()) & R;
317 
318  // Info<< "R = " << nl << R << endl;
319 
320  // Info<< "R - eVec.T() " << R - eVec.T() << endl;
321  // }
322  }
323  else
324  {
326  << "Non-unique eigenvectors, cannot compute transformation "
327  << "from Cartesian axes" << endl;
328 
329  showTransform = false;
330  }
331 
332 
333  // Calculate total surface area and average normal vector
334  scalar surfaceArea = 0;
335  vector averageNormal(Zero);
336 
337  forAll(surf, facei)
338  {
339  const labelledTri& f = surf[facei];
340 
341  if (!f.good())
342  {
344  << "Illegal triangle " << facei << " vertices " << f
345  << " coords " << f.points(surf.points()) << endl;
346  }
347  else
348  {
349  triPointRef tri = f.tri(surf.points());
350 
351  surfaceArea += tri.mag();
352  averageNormal += tri.areaNormal();
353  }
354  }
355 
356  // The unit normal (area-averaged)
357  averageNormal.normalise();
358 
359 
360  Info<< nl << setprecision(12)
361  << "Density: " << density << nl
362  << "Mass: " << m << nl
363  << "Centre of mass: " << cM << nl
364  << "Surface area: " << surfaceArea << nl
365  << "Average normal: " << averageNormal << nl
366  << "Inertia tensor around centre of mass: " << nl << J << nl
367  << "eigenValues (principal moments): " << eVal << nl
368  << "eigenVectors (principal axes): " << nl
369  << eVec.x() << nl << eVec.y() << nl << eVec.z() << endl;
370 
371  if (showTransform)
372  {
373  Info<< "Transform tensor from reference state (orientation):" << nl
374  << eVec.T() << nl
375  << "Rotation tensor required to transform "
376  "from the body reference frame to the global "
377  "reference frame, i.e.:" << nl
378  << "globalVector = orientation & bodyLocalVector"
379  << nl;
380 
381  Info<< nl
382  << "Entries for sixDoFRigidBodyDisplacement boundary condition:"
383  << nl;
384 
385  Info()
386  .writeEntry("mass", m)
387  .writeEntry("centreOfMass", cM)
388  .writeEntry("momentOfInertia", eVal)
389  .writeEntry("orientation", eVec.T());
390  }
391 
392  if (calcAroundRefPt)
393  {
394  Info<< nl << "Inertia tensor relative to " << refPt << ": " << nl
396  << endl;
397  }
398 
399 
400  // Write (scaled) principal axes at centre of mass
401  {
402  OFstream str("axes.obj");
403 
404  Info<< nl << "Writing scaled principal axes at centre of mass of "
405  << surfFileName << " to " << str.name() << endl;
406 
407  scalar scale =
408  mag(cM - surf.points()[0])/eVal.component(findMin(eVal));
409 
410  meshTools::writeOBJ(str, cM);
411  meshTools::writeOBJ(str, cM + scale*eVal.x()*eVec.x());
412  meshTools::writeOBJ(str, cM + scale*eVal.y()*eVec.y());
413  meshTools::writeOBJ(str, cM + scale*eVal.z()*eVec.z());
414 
415  for (label i = 1; i < 4; i++)
416  {
417  str << "l " << 1 << ' ' << i + 1 << endl;
418  }
419  }
420 
421  Info<< "\nEnd\n" << endl;
422 
423  return 0;
424 }
425 
426 
427 // ************************************************************************* //
dimensionedScalar sign(const dimensionedScalar &ds)
static void addNote(const string &note)
Add extra notes for the usage information.
Definition: argList.C:462
A triangle primitive used to calculate face normals and swept volumes. Uses referred points...
Definition: triangle.H:228
A class for handling file names.
Definition: fileName.H:72
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
A 1D vector of objects of type <T> with a fixed length <N>.
Definition: HashTable.H:107
scalar mag() const
The magnitude of the triangle area.
Definition: triangleI.H:309
Output to file stream as an OSstream, normally using std::ofstream for the actual output...
Definition: OFstream.H:71
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
static void massPropertiesSolid(const pointField &pts, const triFaceList &triFaces, scalar density, scalar &mass, vector &cM, tensor &J)
Tensor< scalar > tensor
Definition: symmTensor.H:57
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
static void addBoolOption(const word &optName, const string &usage="", bool advanced=false)
Add a bool option to validOptions with usage information.
Definition: argList.C:374
static void noParallel()
Remove the parallel options.
Definition: argList.C:584
void writeOBJ(Ostream &os, const point &pt)
Write obj representation of a point.
Definition: meshTools.C:196
T getOrDefault(const word &optName, const T &deflt) const
Get a value from the named option if present, or return default.
Definition: argListI.H:300
bool match(const UList< wordRe > &selectors, const std::string &text)
True if text matches one of the selector expressions.
Definition: stringOps.H:77
Various functions to operate on Lists.
dimensionedTensor eigenVectors(const dimensionedSymmTensor &dt)
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
label findMin(const ListType &input, label start=0)
Linear search for the index of the min element, similar to std::min_element but for lists and returns...
Omanip< int > setprecision(const int i)
Definition: IOmanip.H:205
static vector areaNormal(const Point &p0, const Point &p1, const Point &p2)
The area normal for a triangle defined by three points (right-hand rule). Magnitude equal to area of ...
Definition: triangleI.H:169
3D tensor transformation operations.
Extract command arguments and options from the supplied argc and argv parameters. ...
Definition: argList.H:118
static void addOption(const word &optName, const string &param="", const string &usage="", bool advanced=false)
Add an option to validOptions with usage information.
Definition: argList.C:385
A triFace with additional (region) index.
Definition: labelledTri.H:53
Vector< scalar > vector
Definition: vector.H:57
Random number generator.
Definition: Random.H:55
Istream and Ostream manipulators taking arguments.
A Vector of values with scalar precision, where scalar is float/double depending on the compilation f...
labelList f(nPoints)
static tensor applyParallelAxisTheorem(scalar mass, const vector &cM, const tensor &J, const vector &refPt)
T get(const label index) const
Get a value from the argument at index.
Definition: argListI.H:271
#define WarningInFunction
Report a warning using Foam::Warning.
dimensionedSymmTensor symm(const dimensionedSymmTensor &dt)
static void addArgument(const string &argName, const string &usage="")
Append a (mandatory) argument to validArgs.
Definition: argList.C:351
messageStream Info
Information stream (stdout output on master, null elsewhere)
dimensionedVector eigenValues(const dimensionedSymmTensor &dt)
bool readIfPresent(const word &optName, T &val) const
Read a value from the named option if present.
Definition: argListI.H:316
Triangulated surface description with patch information.
Definition: triSurface.H:71
Foam::argList args(argc, argv)
Tensor of scalars, i.e. Tensor<scalar>.
static void massPropertiesShell(const pointField &pts, const triFaceList &triFaces, scalar density, scalar &mass, vector &cM, tensor &J, bool doReduce=false)
dimensioned< typename typeOfMag< Type >::type > magSqr(const dimensioned< Type > &dt)
bool found(const word &optName) const
Return true if the named option is found.
Definition: argListI.H:171
Namespace for OpenFOAM.
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:127