Forked from
waLBerla / waLBerla
30 commits behind, 8 commits ahead of the upstream repository.
GeometricalFunctions.cpp 14.37 KiB
//======================================================================================================================
//
// This file is part of waLBerla. waLBerla is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// waLBerla is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
//
//! \file GeometricalFunctions.cpp
//! \ingroup geometry
//! \author Klaus Iglberger
//! \author Sebastian Eibl <sebastian.eibl@fau.de>
//! \brief Utility functions for geometrical calculations.
//
//======================================================================================================================
//*************************************************************************************************
// Includes
//*************************************************************************************************
#include "GeometricalFunctions.h"
#include "core/math/Shims.h"
#include "core/math/Utility.h"
namespace walberla {
namespace geometry {
//=================================================================================================
//
// GEOMETRY FUNCTIONS
//
//=================================================================================================
//*************************************************************************************************
/*!\brief Find the closest points on a box and a line segment.
*
* \param p1 The first end point of the line segment.
* \param p2 The second end point of the line segment.
* \param c The center position of the box.
* \param R The rotation of the box.
* \param side The box's sides lengths.
* \param [out] lret The closest point on the line.
* \param [out] bret The closest point on the box.
* \return void
*
* This function calculates the closest points between a box and a line segment. The variable
* \a lret will be set to the closest point on the line, the variable \a bret to the closest
* point on the box. In case \a p1 lies outside the box and the line intersects the box, the
* intersection point is returned (in both variables). In case \a p1 lies inside the box, both
* \a lret and \a bret will refer to \a p1.
*
* \image html lineBox.png
* \image latex lineBox.eps "Calculation of the closest points between a line and a box" width=455pt
*/
void getClosestLineBoxPoints( const Vector3<real_t>& p1, const Vector3<real_t>& p2, const Vector3<real_t>& c, const Matrix3<real_t>& R,
const Vector3<real_t>& side, Vector3<real_t>& lret, Vector3<real_t>& bret )
{
WALBERLA_ABORT("This function produces incorrect results! See https://i10git.cs.fau.de/walberla/walberla/-/issues/25");
using namespace walberla::math;
//----- Note: All computations will be performed in the box-relative coordinate-system -----
// Calculating the global and relative direction of the line p1--p2
const Vector3<real_t> l( p2 - p1 );
Vector3<real_t> tmp( R * l );
// Saving the sign of the direction p1--p2
const std::array<real_t, 3> sign{ math::sign(tmp[0]), math::sign(tmp[1]), math::sign(tmp[2]) };
// Calculating the absolute values of the direction direction
const std::array<real_t, 3> v { sign[0]*tmp[0], sign[1]*tmp[1], sign[2]*tmp[2] };
const std::array<real_t, 3> v2{ sq( v[0] ) , sq( v[1] ) , sq( v[2] ) };
// Calculating the starting point of the line p1--p2 in box-relative coordinates
tmp = p1 - c;
const std::array<real_t, 3> s{ sign[0]*( R[0]*tmp[0] + R[3]*tmp[1] + R[6]*tmp[2] ),
sign[1]*( R[1]*tmp[0] + R[4]*tmp[1] + R[7]*tmp[2] ),
sign[2]*( R[2]*tmp[0] + R[5]*tmp[1] + R[8]*tmp[2] ) };
// Calculating the half lengths of the box
const std::array<real_t, 3> h{ real_t(0.5)*side[0], real_t(0.5)*side[1], real_t(0.5)*side[2] };
// Estimating the region of the starting point depending on which side of the
// box planes the coordinates are (-1,0,1). 'tanchor' stores the next t value
// causing a transition from one to another region, or the last one if there
// are no more. Additionally, d|d|^2/dt is computed for t=0. If it is >= 0
// then p1 is the closest point since the line points away from the box.
std::array<int, 3> region{ 0 };
std::array<real_t, 3> tanchor{ 2 }; // Invalid t values; t cannot be greater than 1
real_t dd2dt( 0 );
if( v[0] > Limits<real_t>::epsilon() )
{
if( s[0] < -h[0] ) {
region[0] = -1;
tanchor[0] = ( -h[0]-s[0] ) / v[0];
dd2dt -= v2[0] * tanchor[0];
}
else if( s[0] > h[0] ) {
region[0] = 1;
tanchor[0] = ( h[0]-s[0] ) / v[0];
dd2dt -= v2[0] * tanchor[0];
}
else {
tanchor[0] = ( h[0]-s[0] ) / v[0];
}
}
if( v[1] > Limits<real_t>::epsilon() )
{
if( s[1] < -h[1] ) {
region[1] = -1;
tanchor[1] = ( -h[1]-s[1] ) / v[1];
dd2dt -= v2[1] * tanchor[1];
}
else if( s[1] > h[1] ) {
region[1] = 1;
tanchor[1] = ( h[1]-s[1] ) / v[1];
dd2dt -= v2[1] * tanchor[1];
}
else {
tanchor[1] = ( h[1]-s[1] ) / v[1];
}
}
if( v[2] > Limits<real_t>::epsilon() )
{
if( s[2] < -h[2] ) {
region[2] = -1;
tanchor[2] = ( -h[2]-s[2] ) / v[2];
dd2dt -= v2[2] * tanchor[2];
}
else if( s[2] > h[2] ) {
region[2] = 1;
tanchor[2] = ( h[2]-s[2] ) / v[2];
dd2dt -= v2[2] * tanchor[2];
}
else {
tanchor[2] = ( h[2]-s[2] ) / v[2];
}
}
// Calculating the value t for the closest point on the line
real_t t( 0 );
if( dd2dt < -Limits<real_t>::accuracy() )
{
do {
// Finding the t value for the next clip plane/line intersection
real_t next_t( 1 );
if( ( tanchor[0] > t ) && ( tanchor[0] < real_t(1) ) && ( tanchor[0] < next_t ) ) {
next_t = tanchor[0];
}
if( ( tanchor[1] > t ) && ( tanchor[1] < real_t(1) ) && ( tanchor[1] < next_t ) ) {
next_t = tanchor[1];
}
if( ( tanchor[2] > t ) && ( tanchor[2] < real_t(1) ) && ( tanchor[2] < next_t ) ) {
next_t = tanchor[2];
}
// Computing d|d|^2/dt for the next t
real_t next_dd2dt( 0 );
if( region[0] != 0 ) {
next_dd2dt += v2[0] * ( next_t - tanchor[0] );
}
if( region[1] != 0 ) {
next_dd2dt += v2[1] * ( next_t - tanchor[1] );
}
if( region[2] != 0 ) {
next_dd2dt += v2[2] * ( next_t - tanchor[2] );
}
// if the sign of d|d|^2/dt has changed, solution = the crossover point
if( next_dd2dt >= 0 ) {
t -= dd2dt / ( ( next_dd2dt - dd2dt ) / ( next_t - t ) );
break;
}
// Advancing to the next anchor point / region
if( floatIsEqual(tanchor[0], next_t) ) {
tanchor[0] = ( h[0] - s[0] ) / v[0];
++region[0];
}
if( floatIsEqual(tanchor[1], next_t) ) {
tanchor[1] = ( h[1] - s[1] ) / v[1];
++region[1];
}
if( floatIsEqual(tanchor[2], next_t) ) {
tanchor[2] = ( h[2] - s[2] ) / v[2];
++region[2];
}
t = next_t;
dd2dt = next_dd2dt;
}
while( t < real_t(1) );
}
WALBERLA_ASSERT_GREATER_EQUAL( t, real_t(0), "Invalid line point" );
WALBERLA_ASSERT_LESS_EQUAL( t, real_t(1), "Invalid line point" );
// Computing the closest point on the line
lret = p1 + t * l;
// Computing the closest point on the box
tmp[0] = sign[0] * ( s[0] + t * v[0] );
if ( tmp[0] < -h[0] ) tmp[0] = -h[0];
else if( tmp[0] > h[0] ) tmp[0] = h[0];
tmp[1] = sign[1] * ( s[1] + t * v[1] );
if ( tmp[1] < -h[1] ) tmp[1] = -h[1];
else if( tmp[1] > h[1] ) tmp[1] = h[1];
tmp[2] = sign[2] * ( s[2] + t * v[2] );
if ( tmp[2] < -h[2] ) tmp[2] = -h[2];
else if( tmp[2] > h[2] ) tmp[2] = h[2];
bret = ( R * tmp ) + c;
}
//*************************************************************************************************
//*************************************************************************************************
/*!\brief Find the closest points on two line segments.
*
* \param a1 The first end point of the first line segment.
* \param a2 The second end point of the first line segment.
* \param b1 The first end point of the second line segment.
* \param b2 The second end point of the second line segment.
* \param [out] cp1 The closest point on line one.
* \param [out] cp2 The closest point on line two.
* \return void
*
* Given the two line segments A and B, this function returns the points that are closest to
* each other. In the case of parallel lines, where multiple solutions are possible, a solution
* involving the endpoint of at least one line will be returned. This will also work correctly
* for zero length lines, e.g. if \a a1 = \a a2 and/or \a b1 = \a b2.
*/
void getClosestLineSegmentPoints( const Vector3<real_t>& a1, const Vector3<real_t>& a2, const Vector3<real_t>& b1, const Vector3<real_t>& b2,
Vector3<real_t>& cp1, Vector3<real_t>& cp2 )
{
using namespace walberla::math;
//----- Checking the vertex-vertex features -----
const Vector3<real_t> a1a2( a2 - a1 );
const Vector3<real_t> b1b2( b2 - b1 );
const Vector3<real_t> a1b1( b1 - a1 );
const real_t da1 ( a1a2 * a1b1 );
const real_t db1 ( b1b2 * a1b1 );
if( da1 <= +Limits<real_t>::fpuAccuracy() && db1 >= -Limits<real_t>::fpuAccuracy() ) {
cp1 = a1;
cp2 = b1;
return;
}
const Vector3<real_t> a1b2( b2 - a1 );
const real_t da2 ( a1a2 * a1b2 );
const real_t db2 ( b1b2 * a1b2 );
if( da2 <= +Limits<real_t>::fpuAccuracy() && db2 <= +Limits<real_t>::fpuAccuracy() ) {
cp1 = a1;
cp2 = b2;
return;
}
const Vector3<real_t> a2b1( b1 - a2 );
const real_t da3 ( a1a2 * a2b1 );
const real_t db3 ( b1b2 * a2b1 );
if( da3 >= -Limits<real_t>::fpuAccuracy() && db3 >= -Limits<real_t>::fpuAccuracy() ) {
cp1 = a2;
cp2 = b1;
return;
}
const Vector3<real_t> a2b2( b2 - a2 );
const real_t da4 ( a1a2 * a2b2 );
const real_t db4 ( b1b2 * a2b2 );
if( da4 >= -Limits<real_t>::fpuAccuracy() && db4 <= +Limits<real_t>::fpuAccuracy() ) {
cp1 = a2;
cp2 = b2;
return;
}
//----- Checking the edge-vertex features -----
// If one or both of the line segments have zero length, we will never get here. Therefore
// we don't have to worry about possible divisions by zero in the following calculations.
Vector3<real_t> n;
Vector3<real_t> k;
const real_t la( a1a2 * a1a2 );
if( da1 >= -Limits<real_t>::fpuAccuracy() && da3 <= +Limits<real_t>::fpuAccuracy() ) {
k = (da1 / la) * a1a2;
n = a1b1 - k;
if( b1b2 * n >= -Limits<real_t>::fpuAccuracy() ) {
cp1 = a1 + k;
cp2 = b1;
return;
}
}
if( da2 >= -Limits<real_t>::fpuAccuracy() && da4 <= +Limits<real_t>::fpuAccuracy() ) {
k = (da2 / la) * a1a2;
n = a1b2 - k;
if( b1b2 * n <= +Limits<real_t>::fpuAccuracy() ) {
cp1 = a1 + k;
cp2 = b2;
return;
}
}
const real_t lb( b1b2 * b1b2 );
if( db1 <= +Limits<real_t>::fpuAccuracy() && db2 >= -Limits<real_t>::fpuAccuracy() ) {
k = (-db1 / lb) * b1b2;
n = -a1b1 - k;
if( a1a2 * n >= -Limits<real_t>::fpuAccuracy() ) {
cp1 = a1;
cp2= b1 + k;
return;
}
}
if( db3 <= +Limits<real_t>::fpuAccuracy() && db4 >= -Limits<real_t>::fpuAccuracy() ) {
k = (-db3 / lb) * b1b2;
n = -a2b1 - k;
if( a1a2 * n <= +Limits<real_t>::fpuAccuracy() ) {
cp1 = a2;
cp2 = b1 + k;
return;
}
}
//----- Checking the edge-edge features -----
const real_t scale( a1a2 * b1b2 );
real_t div( la * lb - math::sq(scale) );
WALBERLA_ASSERT_GREATER( div, real_t(0), std::setprecision(16) << "Invalid division\n" << a1 << "\n" << a2 << "\n" << b1 << "\n" << b2 );
div = real_t(1) / div;
const real_t s( ( lb * da1 - scale * db1 ) * div );
const real_t t( ( scale * da1 - la * db1 ) * div );
cp1 = a1 + s * a1a2;
cp2 = b1 + t * b1b2;
}
//*************************************************************************************************
//*************************************************************************************************
/*!\brief Find the intersection point or the point of the closest approach between two straight
* \brief lines \f$ l_1(s) = o_1+sd_1 \f$ and \f$ l_2(t) = o_2+td_2 \f$.
* \ingroup geometry
*
* \param o1 A point on line \f$ l_1 \f$.
* \param d1 The direction of line \f$ l_1 \f$.
* \param o2 A point on line \f$ l_2 \f$.
* \param d2 The direction of line \f$ l_2 \f$.
* \param [out] s The resolved parameter for line \f$ l_1 \f$.
* \param [out] t The resolved parameter for line \f$ l_2 \f$.
* \return void
\f[ o_1+sd_1 = o_2+td_2 \f]
\f[ \Longleftrightarrow \f]
\f[ s = \frac{\det(o_2-o_1,d_2,d_1 \times d_2)}{\left \Vert d_1 \times d_2 \right \| ^2 } \f]
\f[ t = \frac{\det(o_2-o_1,d_1,d_1 \times d_2)}{\left \Vert d_1 \times d_2 \right \| ^2 } \f]
*/
void intersectLines( const Vector3<real_t>& o1, const Vector3<real_t>& d1, const Vector3<real_t>& o2, const Vector3<real_t>& d2,
real_t& s, real_t& t )
{
using namespace walberla::math;
const real_t sqrlen( ( d1 % d2 ).sqrLength() );
if( floatIsEqual(sqrlen, 0) )
{
s = real_t(0);
t = real_t(0);
}
else
{
const real_t isqrlen( real_t(1) / sqrlen );
const Vector3<real_t> p( o2 - o1 );
const real_t dot( d1 * d2 );
const real_t a ( d1 * p );
const real_t b ( -d2 * p );
s = ( a * ( d2 * d2 ) + dot*b ) * isqrlen;
t = ( b * ( d1 * d1 ) + dot*a ) * isqrlen;
}
}
//*************************************************************************************************
} // namespace math
} // namespace walberla