/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2017 OpenFOAM Foundation
Copyright (C) 2018-2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM 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.
OpenFOAM 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 OpenFOAM. If not, see .
\*---------------------------------------------------------------------------*/
#include "searchableCylinder.H"
#include "addToRunTimeSelectionTable.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
namespace Foam
{
defineTypeNameAndDebug(searchableCylinder, 0);
addToRunTimeSelectionTable
(
searchableSurface,
searchableCylinder,
dict
);
addNamedToRunTimeSelectionTable
(
searchableSurface,
searchableCylinder,
dict,
cylinder
);
}
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
Foam::tmp Foam::searchableCylinder::coordinates() const
{
return tmp::New(1, 0.5*(point1_ + point2_));
}
void Foam::searchableCylinder::boundingSpheres
(
pointField& centres,
scalarField& radiusSqr
) const
{
centres.setSize(1);
centres[0] = 0.5*(point1_ + point2_);
radiusSqr.setSize(1);
radiusSqr[0] = Foam::magSqr(point1_-centres[0]) + Foam::sqr(radius_);
// Add a bit to make sure all points are tested inside
radiusSqr += Foam::sqr(SMALL);
}
Foam::tmp Foam::searchableCylinder::points() const
{
auto tpts = tmp::New(2);
auto& pts = tpts.ref();
pts[0] = point1_;
pts[1] = point2_;
return tpts;
}
Foam::pointIndexHit Foam::searchableCylinder::findNearest
(
const point& sample,
const scalar nearestDistSqr
) const
{
pointIndexHit info(false, sample, -1);
vector v(sample - point1_);
// Decompose sample-point1 into normal and parallel component
scalar parallel = (v & unitDir_);
// Remove the parallel component and normalise
v -= parallel*unitDir_;
scalar magV = mag(v);
if (magV < ROOTVSMALL)
{
v = Zero;
}
else
{
v /= magV;
}
if (parallel <= 0)
{
// nearest is at point1 end cap. Clip to radius.
info.setPoint(point1_ + min(magV, radius_)*v);
}
else if (parallel >= magDir_)
{
// nearest is at point2 end cap. Clip to radius.
info.setPoint(point2_ + min(magV, radius_)*v);
}
else
{
// inbetween endcaps. Might either be nearer endcaps or cylinder wall
// distance to endpoint: parallel or parallel-magDir
// distance to cylinder wall: magV-radius_
// Nearest cylinder point
point cylPt;
if (magV < ROOTVSMALL)
{
// Point exactly on centre line. Take any point on wall.
vector e1 = point(1,0,0) ^ unitDir_;
scalar magE1 = mag(e1);
if (magE1 < SMALL)
{
e1 = point(0,1,0) ^ unitDir_;
magE1 = mag(e1);
}
e1 /= magE1;
cylPt = sample + radius_*e1;
}
else
{
cylPt = sample + (radius_-magV)*v;
}
if (parallel < 0.5*magDir_)
{
// Project onto p1 endcap
point end1Pt = point1_ + min(magV, radius_)*v;
if (magSqr(sample-cylPt) < magSqr(sample-end1Pt))
{
info.setPoint(cylPt);
}
else
{
info.setPoint(end1Pt);
}
}
else
{
// Project onto p2 endcap
point end2Pt = point2_ + min(magV, radius_)*v;
if (magSqr(sample-cylPt) < magSqr(sample-end2Pt))
{
info.setPoint(cylPt);
}
else
{
info.setPoint(end2Pt);
}
}
}
if (info.point().distSqr(sample) < nearestDistSqr)
{
info.setHit();
info.setIndex(0);
}
return info;
}
Foam::scalar Foam::searchableCylinder::radius2(const point& pt) const
{
const vector x = (pt-point1_) ^ unitDir_;
return (x & x);
}
// From http://www.gamedev.net/community/forums/topic.asp?topic_id=467789 -
// intersection of cylinder with ray
void Foam::searchableCylinder::findLineAll
(
const point& start,
const point& end,
pointIndexHit& near,
pointIndexHit& far
) const
{
near.setMiss();
far.setMiss();
vector point1Start(start-point1_);
vector point2Start(start-point2_);
vector point1End(end-point1_);
// Quick rejection of complete vector outside endcaps
scalar s1 = point1Start&unitDir_;
scalar s2 = point1End&unitDir_;
if ((s1 < 0 && s2 < 0) || (s1 > magDir_ && s2 > magDir_))
{
return;
}
// Line as P = start+t*V where V is unit vector and t=[0..mag(end-start)]
vector V(end-start);
scalar magV = mag(V);
if (magV < ROOTVSMALL)
{
return;
}
V /= magV;
// We now get the nearest intersections to start. This can either be
// the intersection with the end plane or with the cylinder side.
// Get the two points (expressed in t) on the end planes. This is to
// clip any cylinder intersection against.
scalar tPoint1;
scalar tPoint2;
// Maintain the two intersections with the endcaps
scalar tNear = VGREAT;
scalar tFar = VGREAT;
{
scalar s = (V&unitDir_);
if (mag(s) > VSMALL)
{
tPoint1 = -s1/s;
tPoint2 = -(point2Start&unitDir_)/s;
if (tPoint2 < tPoint1)
{
std::swap(tPoint1, tPoint2);
}
if (tPoint1 > magV || tPoint2 < 0)
{
return;
}
// See if the points on the endcaps are actually inside the cylinder
if (tPoint1 >= 0 && tPoint1 <= magV)
{
if (radius2(start+tPoint1*V) <= sqr(radius_))
{
tNear = tPoint1;
}
}
if (tPoint2 >= 0 && tPoint2 <= magV)
{
if (radius2(start+tPoint2*V) <= sqr(radius_))
{
// Check if already have a near hit from point1
if (tNear <= 1)
{
tFar = tPoint2;
}
else
{
tNear = tPoint2;
}
}
}
}
else
{
// Vector perpendicular to cylinder. Check for outside already done
// above so just set tpoint to allow all.
tPoint1 = -VGREAT;
tPoint2 = VGREAT;
}
}
const vector x = point1Start ^ unitDir_;
const vector y = V ^ unitDir_;
const scalar d = sqr(radius_);
// Second order equation of the form a*t^2 + b*t + c
const scalar a = (y&y);
const scalar b = 2*(x&y);
const scalar c = (x&x)-d;
const scalar disc = b*b-4*a*c;
scalar t1 = -VGREAT;
scalar t2 = VGREAT;
if (disc < 0)
{
// Fully outside
return;
}
else if (disc < ROOTVSMALL)
{
// Single solution
if (mag(a) > ROOTVSMALL)
{
t1 = -b/(2*a);
//Pout<< "single solution t:" << t1
// << " for start:" << start << " end:" << end
// << " c:" << c << endl;
if (t1 >= 0 && t1 <= magV && t1 >= tPoint1 && t1 <= tPoint2)
{
// valid. Insert sorted.
if (t1 < tNear)
{
tFar = tNear;
tNear = t1;
}
else if (t1 < tFar)
{
tFar = t1;
}
}
else
{
return;
}
}
else
{
// Aligned with axis. Check if outside radius
//Pout<< "small discriminant:" << disc
// << " for start:" << start << " end:" << end
// << " magV:" << magV
// << " c:" << c << endl;
if (c > 0)
{
return;
}
}
}
else
{
if (mag(a) > ROOTVSMALL)
{
scalar sqrtDisc = sqrt(disc);
t1 = (-b - sqrtDisc)/(2*a);
t2 = (-b + sqrtDisc)/(2*a);
if (t2 < t1)
{
std::swap(t1, t2);
}
if (t1 >= 0 && t1 <= magV && t1 >= tPoint1 && t1 <= tPoint2)
{
// valid. Insert sorted.
if (t1 < tNear)
{
tFar = tNear;
tNear = t1;
}
else if (t1 < tFar)
{
tFar = t1;
}
}
if (t2 >= 0 && t2 <= magV && t2 >= tPoint1 && t2 <= tPoint2)
{
// valid. Insert sorted.
if (t2 < tNear)
{
tFar = tNear;
tNear = t2;
}
else if (t2 < tFar)
{
tFar = t2;
}
}
//Pout<< "two solutions t1:" << t1 << " t2:" << t2
// << " for start:" << start << " end:" << end
// << " magV:" << magV
// << " c:" << c << endl;
}
else
{
// Aligned with axis. Check if outside radius
//Pout<< "large discriminant:" << disc
// << " small a:" << a
// << " for start:" << start << " end:" << end
// << " magV:" << magV
// << " c:" << c << endl;
if (c > 0)
{
return;
}
}
}
// Check tNear, tFar
if (tNear >= 0 && tNear <= magV)
{
near.hitPoint(start+tNear*V);
near.setIndex(0);
if (tFar <= magV)
{
far.hitPoint(start+tFar*V);
far.setIndex(0);
}
}
else if (tFar >= 0 && tFar <= magV)
{
near.hitPoint(start+tFar*V);
near.setIndex(0);
}
}
Foam::boundBox Foam::searchableCylinder::calcBounds() const
{
// Adapted from
// http://www.gamedev.net/community/forums
// /topic.asp?topic_id=338522&forum_id=20&gforum_id=0
// Let cylinder have end points A,B and radius r,
// Bounds in direction X (same for Y and Z) can be found as:
// Let A.X("point1")),
point2_(dict.get("point2")),
magDir_(mag(point2_-point1_)),
unitDir_((point2_-point1_)/magDir_),
radius_(dict.get("radius"))
{
bounds() = calcBounds();
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
const Foam::wordList& Foam::searchableCylinder::regions() const
{
if (regions_.empty())
{
regions_.setSize(1);
regions_[0] = "region0";
}
return regions_;
}
void Foam::searchableCylinder::findNearest
(
const pointField& samples,
const scalarField& nearestDistSqr,
List& info
) const
{
info.setSize(samples.size());
forAll(samples, i)
{
info[i] = findNearest(samples[i], nearestDistSqr[i]);
}
}
void Foam::searchableCylinder::findLine
(
const pointField& start,
const pointField& end,
List& info
) const
{
info.setSize(start.size());
forAll(start, i)
{
// Pick nearest intersection. If none intersected take second one.
pointIndexHit b;
findLineAll(start[i], end[i], info[i], b);
if (!info[i].hit() && b.hit())
{
info[i] = b;
}
}
}
void Foam::searchableCylinder::findLineAny
(
const pointField& start,
const pointField& end,
List& info
) const
{
info.setSize(start.size());
forAll(start, i)
{
// Discard far intersection
pointIndexHit b;
findLineAll(start[i], end[i], info[i], b);
if (!info[i].hit() && b.hit())
{
info[i] = b;
}
}
}
void Foam::searchableCylinder::findLineAll
(
const pointField& start,
const pointField& end,
List>& info
) const
{
info.setSize(start.size());
forAll(start, i)
{
pointIndexHit near, far;
findLineAll(start[i], end[i], near, far);
if (near.hit())
{
if (far.hit())
{
info[i].setSize(2);
info[i][0] = near;
info[i][1] = far;
}
else
{
info[i].setSize(1);
info[i][0] = near;
}
}
else
{
if (far.hit())
{
info[i].setSize(1);
info[i][0] = far;
}
else
{
info[i].clear();
}
}
}
}
void Foam::searchableCylinder::getRegion
(
const List& info,
labelList& region
) const
{
region.setSize(info.size());
region = 0;
}
void Foam::searchableCylinder::getNormal
(
const List& info,
vectorField& normal
) const
{
normal.setSize(info.size());
normal = Zero;
forAll(info, i)
{
if (info[i].hit())
{
vector v(info[i].point() - point1_);
// Decompose sample-point1 into normal and parallel component
const scalar parallel = (v & unitDir_);
// Remove the parallel component and normalise
v -= parallel*unitDir_;
scalar magV = mag(v);
if (parallel <= 0)
{
if ((magV-radius_) < mag(parallel))
{
// either above endcap (magV= radius_ || (radius_-magV) < parallel)
{
normal[i] = v/magV;
}
else
{
// closer to endcap
normal[i] = -unitDir_;
}
}
else if (parallel <= magDir_)
{
// See if endcap closer or sidewall
if (magV >= radius_ || (radius_-magV) < (magDir_-parallel))
{
normal[i] = v/magV;
}
else
{
// closer to endcap
normal[i] = unitDir_;
}
}
else // beyond cylinder
{
if ((magV-radius_) < (parallel-magDir_))
{
// above endcap
normal[i] = unitDir_;
}
else
{
normal[i] = v/magV;
}
}
}
}
}
void Foam::searchableCylinder::getVolumeType
(
const pointField& points,
List& volType
) const
{
volType.setSize(points.size());
forAll(points, pointi)
{
const point& pt = points[pointi];
volType[pointi] = volumeType::OUTSIDE;
vector v(pt - point1_);
// Decompose sample-point1 into normal and parallel component
const scalar parallel = (v & unitDir_);
// Quick rejection. Left of point1 endcap, or right of point2 endcap
if (parallel < 0 || parallel > magDir_)
{
volType[pointi] = volumeType::OUTSIDE;
}
else
{
// Remove the parallel component
v -= parallel*unitDir_;
volType[pointi] =
(
mag(v) <= radius_
? volumeType::INSIDE
: volumeType::OUTSIDE
);
}
}
}
// ************************************************************************* //