1023 lines
34 KiB
C++
1023 lines
34 KiB
C++
//
|
|
// ********************************************************************
|
|
// * License and Disclaimer *
|
|
// * *
|
|
// * The Geant4 software is copyright of the Copyright Holders of *
|
|
// * the Geant4 Collaboration. It is provided under the terms and *
|
|
// * conditions of the Geant4 Software License, included in the file *
|
|
// * LICENSE and available at http://cern.ch/geant4/license . These *
|
|
// * include a list of copyright holders. *
|
|
// * *
|
|
// * Neither the authors of this software system, nor their employing *
|
|
// * institutes,nor the agencies providing financial support for this *
|
|
// * work make any representation or warranty, express or implied, *
|
|
// * regarding this software system or assume any liability for its *
|
|
// * use. Please see the license in the file LICENSE and URL above *
|
|
// * for the full disclaimer and the limitation of liability. *
|
|
// * *
|
|
// * This code implementation is the result of the scientific and *
|
|
// * technical work of the GEANT4 collaboration. *
|
|
// * By using, copying, modifying or distributing the software (or *
|
|
// * any work based on the software) you agree to acknowledge its *
|
|
// * use in resulting scientific publications, and indicate your *
|
|
// * acceptance of all terms of the Geant4 Software license. *
|
|
// ********************************************************************
|
|
//
|
|
// Implementation of G4MultiUnion class
|
|
//
|
|
// 19.10.12 M.Gayer - Original implementation from USolids module
|
|
// 06.04.17 G.Cosmo - Adapted implementation in Geant4 for VecGeom migration
|
|
// --------------------------------------------------------------------
|
|
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
#include "G4MultiUnion.hh"
|
|
#include "Randomize.hh"
|
|
#include "G4GeometryTolerance.hh"
|
|
#include "G4BoundingEnvelope.hh"
|
|
#include "G4AffineTransform.hh"
|
|
#include "G4DisplacedSolid.hh"
|
|
|
|
#include "G4VGraphicsScene.hh"
|
|
#include "G4Polyhedron.hh"
|
|
#include "G4PolyhedronArbitrary.hh"
|
|
#include "HepPolyhedronProcessor.h"
|
|
|
|
#include "G4BooleanSolid.hh"
|
|
|
|
#include "G4AutoLock.hh"
|
|
|
|
namespace
|
|
{
|
|
G4Mutex polyhedronMutex = G4MUTEX_INITIALIZER;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4MultiUnion::G4MultiUnion(const G4String& name)
|
|
: G4VSolid(name)
|
|
{
|
|
SetName(name);
|
|
fSolids.clear();
|
|
fTransformObjs.clear();
|
|
kRadTolerance = G4GeometryTolerance::GetInstance()->GetRadialTolerance();
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4MultiUnion::~G4MultiUnion()
|
|
= default;
|
|
|
|
//______________________________________________________________________________
|
|
void G4MultiUnion::AddNode(G4VSolid& solid, const G4Transform3D& trans)
|
|
{
|
|
fSolids.push_back(&solid);
|
|
fTransformObjs.push_back(trans); // Store a local copy of transformations
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
void G4MultiUnion::AddNode(G4VSolid* solid, const G4Transform3D& trans)
|
|
{
|
|
fSolids.push_back(solid);
|
|
fTransformObjs.push_back(trans); // Store a local copy of transformations
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4VSolid* G4MultiUnion::Clone() const
|
|
{
|
|
return new G4MultiUnion(*this);
|
|
}
|
|
|
|
// Copy constructor
|
|
//______________________________________________________________________________
|
|
G4MultiUnion::G4MultiUnion(const G4MultiUnion& rhs)
|
|
: G4VSolid(rhs), fCubicVolume(rhs.fCubicVolume),
|
|
fSurfaceArea(rhs.fSurfaceArea),
|
|
kRadTolerance(rhs.kRadTolerance), fAccurate(rhs.fAccurate)
|
|
{
|
|
}
|
|
|
|
// Fake default constructor for persistency
|
|
//______________________________________________________________________________
|
|
G4MultiUnion::G4MultiUnion( __void__& a )
|
|
: G4VSolid(a)
|
|
{
|
|
}
|
|
|
|
// Assignment operator
|
|
//______________________________________________________________________________
|
|
G4MultiUnion& G4MultiUnion::operator = (const G4MultiUnion& rhs)
|
|
{
|
|
// Check assignment to self
|
|
//
|
|
if (this == &rhs)
|
|
{
|
|
return *this;
|
|
}
|
|
|
|
// Copy base class data
|
|
//
|
|
G4VSolid::operator=(rhs);
|
|
|
|
return *this;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4double G4MultiUnion::GetCubicVolume()
|
|
{
|
|
if (fCubicVolume == 0.0)
|
|
{
|
|
fCubicVolume = EstimateCubicVolume(1000000, 0.001);
|
|
}
|
|
return fCubicVolume;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4double
|
|
G4MultiUnion::DistanceToInNoVoxels(const G4ThreeVector& aPoint,
|
|
const G4ThreeVector& aDirection) const
|
|
{
|
|
G4ThreeVector direction = aDirection.unit();
|
|
G4ThreeVector localPoint, localDirection;
|
|
G4double minDistance = kInfinity;
|
|
|
|
std::size_t numNodes = fSolids.size();
|
|
for (std::size_t i = 0 ; i < numNodes ; ++i)
|
|
{
|
|
G4VSolid& solid = *fSolids[i];
|
|
const G4Transform3D& transform = fTransformObjs[i];
|
|
|
|
localPoint = GetLocalPoint(transform, aPoint);
|
|
localDirection = GetLocalVector(transform, direction);
|
|
|
|
G4double distance = solid.DistanceToIn(localPoint, localDirection);
|
|
if (minDistance > distance) minDistance = distance;
|
|
}
|
|
return minDistance;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4double G4MultiUnion::DistanceToInCandidates(const G4ThreeVector& aPoint,
|
|
const G4ThreeVector& direction,
|
|
std::vector<G4int>& candidates,
|
|
G4SurfBits& bits) const
|
|
{
|
|
std::size_t candidatesCount = candidates.size();
|
|
G4ThreeVector localPoint, localDirection;
|
|
|
|
G4double minDistance = kInfinity;
|
|
for (std::size_t i = 0 ; i < candidatesCount; ++i)
|
|
{
|
|
G4int candidate = candidates[i];
|
|
G4VSolid& solid = *fSolids[candidate];
|
|
const G4Transform3D& transform = fTransformObjs[candidate];
|
|
|
|
localPoint = GetLocalPoint(transform, aPoint);
|
|
localDirection = GetLocalVector(transform, direction);
|
|
G4double distance = solid.DistanceToIn(localPoint, localDirection);
|
|
if (minDistance > distance) minDistance = distance;
|
|
bits.SetBitNumber(candidate);
|
|
if (minDistance == 0) break;
|
|
}
|
|
return minDistance;
|
|
}
|
|
|
|
// Algorithm note: we have to look also for all other objects in next voxels,
|
|
// if the distance is not shorter ... we have to do it because,
|
|
// for example for objects which starts in first voxel in which they
|
|
// do not collide with direction line, but in second it collides...
|
|
// The idea of crossing voxels would be still applicable,
|
|
// because this way we could exclude from the testing such solids,
|
|
// which were found that obviously are not good candidates, because
|
|
// they would return infinity
|
|
// But if distance is smaller than the shift to next voxel, we can return
|
|
// it immediately
|
|
//______________________________________________________________________________
|
|
G4double G4MultiUnion::DistanceToIn(const G4ThreeVector& aPoint,
|
|
const G4ThreeVector& aDirection) const
|
|
{
|
|
G4double minDistance = kInfinity;
|
|
G4ThreeVector direction = aDirection.unit();
|
|
G4double shift = fVoxels.DistanceToFirst(aPoint, direction);
|
|
if (shift == kInfinity) return shift;
|
|
|
|
G4ThreeVector currentPoint = aPoint;
|
|
if (shift != 0.0) currentPoint += direction * shift;
|
|
|
|
G4SurfBits exclusion(fVoxels.GetBitsPerSlice());
|
|
std::vector<G4int> candidates, curVoxel(3);
|
|
fVoxels.GetVoxel(curVoxel, currentPoint);
|
|
|
|
do
|
|
{
|
|
{
|
|
if (fVoxels.GetCandidatesVoxelArray(curVoxel, candidates, &exclusion) != 0)
|
|
{
|
|
G4double distance = DistanceToInCandidates(aPoint, direction,
|
|
candidates, exclusion);
|
|
if (minDistance > distance) minDistance = distance;
|
|
if (distance < shift) break;
|
|
}
|
|
}
|
|
shift = fVoxels.DistanceToNext(aPoint, direction, curVoxel);
|
|
}
|
|
while (minDistance > shift);
|
|
|
|
return minDistance;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4double G4MultiUnion::DistanceToOutNoVoxels(const G4ThreeVector& aPoint,
|
|
const G4ThreeVector& aDirection,
|
|
G4ThreeVector* aNormal) const
|
|
{
|
|
// Computes distance from a point presumably outside the solid to the solid
|
|
// surface. Ignores first surface if the point is actually inside.
|
|
// Early return infinity in case the safety to any surface is found greater
|
|
// than the proposed step aPstep.
|
|
// The normal vector to the crossed surface is filled only in case the box
|
|
// is crossed, otherwise aNormal->IsNull() is true.
|
|
|
|
// algorithm:
|
|
G4ThreeVector direction = aDirection.unit();
|
|
G4ThreeVector localPoint, localDirection;
|
|
G4int ignoredSolid = -1;
|
|
G4double resultDistToOut = 0;
|
|
G4ThreeVector currentPoint = aPoint;
|
|
|
|
auto numNodes = (G4int)fSolids.size();
|
|
for (auto i = 0; i < numNodes; ++i)
|
|
{
|
|
if (i != ignoredSolid)
|
|
{
|
|
G4VSolid& solid = *fSolids[i];
|
|
const G4Transform3D& transform = fTransformObjs[i];
|
|
localPoint = GetLocalPoint(transform, currentPoint);
|
|
localDirection = GetLocalVector(transform, direction);
|
|
EInside location = solid.Inside(localPoint);
|
|
if (location != EInside::kOutside)
|
|
{
|
|
G4double distance = solid.DistanceToOut(localPoint, localDirection,
|
|
false, nullptr, aNormal);
|
|
if (distance < kInfinity)
|
|
{
|
|
if (resultDistToOut == kInfinity) resultDistToOut = 0;
|
|
if (distance > 0)
|
|
{
|
|
currentPoint = GetGlobalPoint(transform, localPoint
|
|
+ distance*localDirection);
|
|
resultDistToOut += distance;
|
|
ignoredSolid = i; // skip the solid which we have just left
|
|
i = -1; // force the loop to continue from 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return resultDistToOut;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4double G4MultiUnion::DistanceToOut(const G4ThreeVector& aPoint,
|
|
const G4ThreeVector& aDirection,
|
|
const G4bool /* calcNorm */,
|
|
G4bool* /* validNorm */,
|
|
G4ThreeVector* aNormal) const
|
|
{
|
|
return DistanceToOutVoxels(aPoint, aDirection, aNormal);
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4double G4MultiUnion::DistanceToOutVoxels(const G4ThreeVector& aPoint,
|
|
const G4ThreeVector& aDirection,
|
|
G4ThreeVector* aNormal) const
|
|
{
|
|
// Computes distance from a point presumably inside the solid to the solid
|
|
// surface. Ignores first surface along each axis systematically (for points
|
|
// inside or outside. Early returns zero in case the second surface is behind
|
|
// the starting point.
|
|
// o The proposed step is ignored.
|
|
// o The normal vector to the crossed surface is always filled.
|
|
|
|
// In the case the considered point is located inside the G4MultiUnion
|
|
// structure, the treatments are as follows:
|
|
// - investigation of the candidates for the passed point
|
|
// - progressive moving of the point towards the surface, along the
|
|
// passed direction
|
|
// - processing of the normal
|
|
|
|
G4ThreeVector direction = aDirection.unit();
|
|
std::vector<G4int> candidates;
|
|
G4double distance = 0;
|
|
std::size_t numNodes = 2*fSolids.size();
|
|
std::size_t count=0;
|
|
|
|
if (fVoxels.GetCandidatesVoxelArray(aPoint, candidates) != 0)
|
|
{
|
|
// For normal case for which we presume the point is inside
|
|
G4ThreeVector localPoint, localDirection, localNormal;
|
|
G4ThreeVector currentPoint = aPoint;
|
|
G4SurfBits exclusion(fVoxels.GetBitsPerSlice());
|
|
G4bool notOutside;
|
|
G4ThreeVector maxNormal;
|
|
|
|
do
|
|
{
|
|
notOutside = false;
|
|
|
|
G4double maxDistance = -kInfinity;
|
|
G4int maxCandidate = 0;
|
|
G4ThreeVector maxLocalPoint;
|
|
|
|
std::size_t limit = candidates.size();
|
|
for (std::size_t i = 0 ; i < limit ; ++i)
|
|
{
|
|
G4int candidate = candidates[i];
|
|
// ignore the current component (that you just got out of) since
|
|
// numerically the propagated point will be on its surface
|
|
|
|
G4VSolid& solid = *fSolids[candidate];
|
|
const G4Transform3D& transform = fTransformObjs[candidate];
|
|
|
|
// The coordinates of the point are modified so as to fit the
|
|
// intrinsic solid local frame:
|
|
localPoint = GetLocalPoint(transform, currentPoint);
|
|
|
|
// DistanceToOut at least for Trd sometimes return non-zero value
|
|
// even from points that are outside. Therefore, this condition
|
|
// must currently be here, otherwise it would not work.
|
|
// But it means it would be slower.
|
|
|
|
if (solid.Inside(localPoint) != EInside::kOutside)
|
|
{
|
|
notOutside = true;
|
|
|
|
localDirection = GetLocalVector(transform, direction);
|
|
|
|
// propagate with solid.DistanceToOut
|
|
G4double shift = solid.DistanceToOut(localPoint, localDirection,
|
|
false, nullptr, &localNormal);
|
|
if (maxDistance < shift)
|
|
{
|
|
maxDistance = shift;
|
|
maxCandidate = candidate;
|
|
maxNormal = localNormal;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (notOutside)
|
|
{
|
|
const G4Transform3D& transform = fTransformObjs[maxCandidate];
|
|
|
|
// convert from local normal
|
|
if (aNormal != nullptr) *aNormal = GetGlobalVector(transform, maxNormal);
|
|
|
|
distance += maxDistance;
|
|
currentPoint += maxDistance * direction;
|
|
if(maxDistance == 0.) ++count;
|
|
|
|
// the current component will be ignored
|
|
exclusion.SetBitNumber(maxCandidate);
|
|
EInside location = InsideWithExclusion(currentPoint, &exclusion);
|
|
|
|
// perform a Inside
|
|
// it should be excluded current solid from checking
|
|
// we have to collect the maximum distance from all given candidates.
|
|
// such "maximum" candidate should be then used for finding next
|
|
// candidates
|
|
if (location == EInside::kOutside)
|
|
{
|
|
// else return cumulated distances to outside of the traversed
|
|
// components
|
|
break;
|
|
}
|
|
// if inside another component, redo 1 to 3 but add the next
|
|
// DistanceToOut on top of the previous.
|
|
|
|
// and fill the candidates for the corresponding voxel (just
|
|
// exiting current component along direction)
|
|
candidates.clear();
|
|
|
|
fVoxels.GetCandidatesVoxelArray(currentPoint, candidates, &exclusion);
|
|
exclusion.ResetBitNumber(maxCandidate);
|
|
}
|
|
}
|
|
while ((notOutside) && (count < numNodes));
|
|
}
|
|
|
|
return distance;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
EInside G4MultiUnion::InsideWithExclusion(const G4ThreeVector& aPoint,
|
|
G4SurfBits* exclusion) const
|
|
{
|
|
// Classify point location with respect to solid:
|
|
// o eInside - inside the solid
|
|
// o eSurface - close to surface within tolerance
|
|
// o eOutside - outside the solid
|
|
|
|
// Hitherto, it is considered that only parallelepipedic nodes
|
|
// can be added to the container
|
|
|
|
// Implementation using voxelisation techniques:
|
|
// ---------------------------------------------
|
|
|
|
G4ThreeVector localPoint;
|
|
EInside location = EInside::kOutside;
|
|
|
|
std::vector<G4int> candidates;
|
|
std::vector<G4MultiUnionSurface> surfaces;
|
|
|
|
// TODO: test if it works well and if so measure performance
|
|
// TODO: getPointIndex should not be used, instead GetVoxel + GetVoxelsIndex
|
|
// should be used
|
|
// TODO: than pass result to GetVoxel further to GetCandidatesVoxelArray
|
|
// TODO: eventually GetVoxel should be inlined here, early exit if any
|
|
// binary search is -1
|
|
|
|
G4int limit = fVoxels.GetCandidatesVoxelArray(aPoint, candidates, exclusion);
|
|
for (G4int i = 0 ; i < limit ; ++i)
|
|
{
|
|
G4int candidate = candidates[i];
|
|
G4VSolid& solid = *fSolids[candidate];
|
|
const G4Transform3D& transform = fTransformObjs[candidate];
|
|
|
|
// The coordinates of the point are modified so as to fit the intrinsic
|
|
// solid local frame:
|
|
localPoint = GetLocalPoint(transform, aPoint);
|
|
location = solid.Inside(localPoint);
|
|
if (location == EInside::kInside) return EInside::kInside;
|
|
else if (location == EInside::kSurface)
|
|
{
|
|
G4MultiUnionSurface surface;
|
|
surface.point = localPoint;
|
|
surface.solid = &solid;
|
|
surfaces.push_back(surface);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Important comment: When two solids touch each other along a flat
|
|
// surface, the surface points will be considered as kSurface, while points
|
|
// located around will correspond to kInside (cf. G4UnionSolid)
|
|
|
|
std::size_t size = surfaces.size();
|
|
|
|
if (size == 0)
|
|
{
|
|
return EInside::kOutside;
|
|
}
|
|
|
|
for (std::size_t i = 0; i < size - 1; ++i)
|
|
{
|
|
G4MultiUnionSurface& left = surfaces[i];
|
|
for (std::size_t j = i + 1; j < size; ++j)
|
|
{
|
|
G4MultiUnionSurface& right = surfaces[j];
|
|
G4ThreeVector n, n2;
|
|
n = left.solid->SurfaceNormal(left.point);
|
|
n2 = right.solid->SurfaceNormal(right.point);
|
|
if ((n + n2).mag2() < 1000 * kRadTolerance)
|
|
return EInside::kInside;
|
|
}
|
|
}
|
|
|
|
return EInside::kSurface;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
EInside G4MultiUnion::Inside(const G4ThreeVector& aPoint) const
|
|
{
|
|
// Classify point location with respect to solid:
|
|
// o eInside - inside the solid
|
|
// o eSurface - close to surface within tolerance
|
|
// o eOutside - outside the solid
|
|
|
|
// Hitherto, it is considered that only parallelepipedic nodes can be
|
|
// added to the container
|
|
|
|
// Implementation using voxelisation techniques:
|
|
// ---------------------------------------------
|
|
|
|
// return InsideIterator(aPoint);
|
|
|
|
EInside location = InsideWithExclusion(aPoint);
|
|
return location;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
EInside G4MultiUnion::InsideNoVoxels(const G4ThreeVector& aPoint) const
|
|
{
|
|
G4ThreeVector localPoint;
|
|
EInside location = EInside::kOutside;
|
|
G4int countSurface = 0;
|
|
|
|
auto numNodes = (G4int)fSolids.size();
|
|
for (auto i = 0 ; i < numNodes ; ++i)
|
|
{
|
|
G4VSolid& solid = *fSolids[i];
|
|
G4Transform3D transform = GetTransformation(i);
|
|
|
|
// The coordinates of the point are modified so as to fit the
|
|
// intrinsic solid local frame:
|
|
localPoint = GetLocalPoint(transform, aPoint);
|
|
|
|
location = solid.Inside(localPoint);
|
|
|
|
if (location == EInside::kSurface)
|
|
++countSurface;
|
|
|
|
if (location == EInside::kInside) return EInside::kInside;
|
|
}
|
|
if (countSurface != 0) return EInside::kSurface;
|
|
return EInside::kOutside;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
void G4MultiUnion::Extent(EAxis aAxis, G4double& aMin, G4double& aMax) const
|
|
{
|
|
// Determines the bounding box for the considered instance of "UMultipleUnion"
|
|
G4ThreeVector min, max;
|
|
|
|
auto numNodes = (G4int)fSolids.size();
|
|
for (auto i = 0 ; i < numNodes ; ++i)
|
|
{
|
|
G4VSolid& solid = *fSolids[i];
|
|
G4Transform3D transform = GetTransformation(i);
|
|
solid.BoundingLimits(min, max);
|
|
|
|
TransformLimits(min, max, transform);
|
|
|
|
if (i == 0)
|
|
{
|
|
switch (aAxis)
|
|
{
|
|
case kXAxis:
|
|
aMin = min.x();
|
|
aMax = max.x();
|
|
break;
|
|
case kYAxis:
|
|
aMin = min.y();
|
|
aMax = max.y();
|
|
break;
|
|
case kZAxis:
|
|
aMin = min.z();
|
|
aMax = max.z();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Determine the min/max on the considered axis:
|
|
switch (aAxis)
|
|
{
|
|
case kXAxis:
|
|
if (min.x() < aMin)
|
|
aMin = min.x();
|
|
if (max.x() > aMax)
|
|
aMax = max.x();
|
|
break;
|
|
case kYAxis:
|
|
if (min.y() < aMin)
|
|
aMin = min.y();
|
|
if (max.y() > aMax)
|
|
aMax = max.y();
|
|
break;
|
|
case kZAxis:
|
|
if (min.z() < aMin)
|
|
aMin = min.z();
|
|
if (max.z() > aMax)
|
|
aMax = max.z();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
void G4MultiUnion::BoundingLimits(G4ThreeVector& aMin,
|
|
G4ThreeVector& aMax) const
|
|
{
|
|
Extent(kXAxis, aMin[0], aMax[0]);
|
|
Extent(kYAxis, aMin[1], aMax[1]);
|
|
Extent(kZAxis, aMin[2], aMax[2]);
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4bool
|
|
G4MultiUnion::CalculateExtent(const EAxis pAxis,
|
|
const G4VoxelLimits& pVoxelLimit,
|
|
const G4AffineTransform& pTransform,
|
|
G4double& pMin, G4double& pMax) const
|
|
{
|
|
G4ThreeVector bmin, bmax;
|
|
|
|
// Get bounding box
|
|
BoundingLimits(bmin,bmax);
|
|
|
|
// Find extent
|
|
G4BoundingEnvelope bbox(bmin,bmax);
|
|
return bbox.CalculateExtent(pAxis,pVoxelLimit,pTransform,pMin,pMax);
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4ThreeVector G4MultiUnion::SurfaceNormal(const G4ThreeVector& aPoint) const
|
|
{
|
|
// Computes the localNormal on a surface and returns it as a unit vector.
|
|
// Must return a valid vector. (even if the point is not on the surface).
|
|
//
|
|
// On an edge or corner, provide an average localNormal of all facets within
|
|
// tolerance
|
|
// NOTE: the tolerance value used in here is not yet the global surface
|
|
// tolerance - we will have to revise this value - TODO
|
|
|
|
std::vector<G4int> candidates;
|
|
G4ThreeVector localPoint, normal, localNormal;
|
|
G4double safety = kInfinity;
|
|
G4int node = 0;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Important comment: Cases for which the point is located on an edge or
|
|
// on a vertice remain to be treated
|
|
|
|
// determine weather we are in voxel area
|
|
if (fVoxels.GetCandidatesVoxelArray(aPoint, candidates) != 0)
|
|
{
|
|
std::size_t limit = candidates.size();
|
|
for (std::size_t i = 0 ; i < limit ; ++i)
|
|
{
|
|
G4int candidate = candidates[i];
|
|
const G4Transform3D& transform = fTransformObjs[candidate];
|
|
|
|
// The coordinates of the point are modified so as to fit the intrinsic
|
|
// solid local frame:
|
|
localPoint = GetLocalPoint(transform, aPoint);
|
|
G4VSolid& solid = *fSolids[candidate];
|
|
EInside location = solid.Inside(localPoint);
|
|
|
|
if (location == EInside::kSurface)
|
|
{
|
|
// normal case when point is on surface, we pick first solid
|
|
normal = GetGlobalVector(transform, solid.SurfaceNormal(localPoint));
|
|
return normal.unit();
|
|
}
|
|
else
|
|
{
|
|
// collect the smallest safety and remember solid node
|
|
G4double s = (location == EInside::kInside)
|
|
? solid.DistanceToOut(localPoint)
|
|
: solid.DistanceToIn(localPoint);
|
|
if (s < safety)
|
|
{
|
|
safety = s;
|
|
node = candidate;
|
|
}
|
|
}
|
|
}
|
|
// on none of the solids, the point was not on the surface
|
|
G4VSolid& solid = *fSolids[node];
|
|
const G4Transform3D& transform = fTransformObjs[node];
|
|
localPoint = GetLocalPoint(transform, aPoint);
|
|
|
|
normal = GetGlobalVector(transform, solid.SurfaceNormal(localPoint));
|
|
return normal.unit();
|
|
}
|
|
else
|
|
{
|
|
// for the case when point is certainly outside:
|
|
|
|
// find a solid in union with the smallest safety
|
|
node = SafetyFromOutsideNumberNode(aPoint, safety);
|
|
G4VSolid& solid = *fSolids[node];
|
|
|
|
const G4Transform3D& transform = fTransformObjs[node];
|
|
localPoint = GetLocalPoint(transform, aPoint);
|
|
|
|
// evaluate normal for point at this found solid
|
|
// and transform multi-union coordinates
|
|
normal = GetGlobalVector(transform, solid.SurfaceNormal(localPoint));
|
|
|
|
return normal.unit();
|
|
}
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4double G4MultiUnion::DistanceToOut(const G4ThreeVector& point) const
|
|
{
|
|
// Estimates isotropic distance to the surface of the solid. This must
|
|
// be either accurate or an underestimate.
|
|
// Two modes: - default/fast mode, sacrificing accuracy for speed
|
|
// - "precise" mode, requests accurate value if available.
|
|
|
|
std::vector<G4int> candidates;
|
|
G4ThreeVector localPoint;
|
|
G4double safetyMin = kInfinity;
|
|
|
|
// In general, the value return by DistanceToIn(p) will not be the exact
|
|
// but only an undervalue (cf. overlaps)
|
|
fVoxels.GetCandidatesVoxelArray(point, candidates);
|
|
|
|
std::size_t limit = candidates.size();
|
|
for (std::size_t i = 0; i < limit; ++i)
|
|
{
|
|
G4int candidate = candidates[i];
|
|
|
|
// The coordinates of the point are modified so as to fit the intrinsic
|
|
// solid local frame:
|
|
const G4Transform3D& transform = fTransformObjs[candidate];
|
|
localPoint = GetLocalPoint(transform, point);
|
|
G4VSolid& solid = *fSolids[candidate];
|
|
if (solid.Inside(localPoint) == EInside::kInside)
|
|
{
|
|
G4double safety = solid.DistanceToOut(localPoint);
|
|
if (safetyMin > safety) safetyMin = safety;
|
|
}
|
|
}
|
|
if (safetyMin == kInfinity) safetyMin = 0; // we are not inside
|
|
|
|
return safetyMin;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4double G4MultiUnion::DistanceToIn(const G4ThreeVector& point) const
|
|
{
|
|
// Estimates the isotropic safety from a point outside the current solid to
|
|
// any of its surfaces. The algorithm may be accurate or should provide a fast
|
|
// underestimate.
|
|
|
|
if (!fAccurate) { return fVoxels.DistanceToBoundingBox(point); }
|
|
|
|
const std::vector<G4VoxelBox>& boxes = fVoxels.GetBoxes();
|
|
G4double safetyMin = kInfinity;
|
|
G4ThreeVector localPoint;
|
|
|
|
std::size_t numNodes = fSolids.size();
|
|
for (std::size_t j = 0; j < numNodes; ++j)
|
|
{
|
|
G4ThreeVector dxyz;
|
|
if (j > 0)
|
|
{
|
|
const G4ThreeVector& pos = boxes[j].pos;
|
|
const G4ThreeVector& hlen = boxes[j].hlen;
|
|
for (auto i = 0; i <= 2; ++i)
|
|
// distance to middle point - hlength => distance from point to border
|
|
// of x,y,z
|
|
if ((dxyz[i] = std::abs(point[i] - pos[i]) - hlen[i]) > safetyMin)
|
|
continue;
|
|
|
|
G4double d2xyz = 0.;
|
|
for (auto i = 0; i <= 2; ++i)
|
|
if (dxyz[i] > 0) d2xyz += dxyz[i] * dxyz[i];
|
|
|
|
// minimal distance is at least this, but could be even higher. therefore,
|
|
// we can stop if previous was already lower, let us check if it does any
|
|
// chance to be better tha previous values...
|
|
if (d2xyz >= safetyMin * safetyMin)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
const G4Transform3D& transform = fTransformObjs[j];
|
|
localPoint = GetLocalPoint(transform, point);
|
|
G4VSolid& solid = *fSolids[j];
|
|
|
|
G4double safety = solid.DistanceToIn(localPoint);
|
|
if (safety <= 0) return safety;
|
|
// it was detected, that the point is not located outside
|
|
if (safetyMin > safety) safetyMin = safety;
|
|
}
|
|
return safetyMin;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4double G4MultiUnion::GetSurfaceArea()
|
|
{
|
|
if (fSurfaceArea == 0.0)
|
|
{
|
|
fSurfaceArea = EstimateSurfaceArea(1000000, 0.001);
|
|
}
|
|
return fSurfaceArea;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4int G4MultiUnion::GetNumOfConstituents() const
|
|
{
|
|
G4int num = 0;
|
|
for (const auto solid : fSolids) { num += solid->GetNumOfConstituents(); }
|
|
return num;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4bool G4MultiUnion::IsFaceted() const
|
|
{
|
|
for (const auto solid : fSolids) { if (!solid->IsFaceted()) return false; }
|
|
return true;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
void G4MultiUnion::Voxelize()
|
|
{
|
|
fVoxels.Voxelize(fSolids, fTransformObjs);
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4int G4MultiUnion::SafetyFromOutsideNumberNode(const G4ThreeVector& aPoint,
|
|
G4double& safetyMin) const
|
|
{
|
|
// Method returning the closest node from a point located outside a
|
|
// G4MultiUnion.
|
|
// This is used to compute the normal in the case no candidate has been found.
|
|
|
|
const std::vector<G4VoxelBox>& boxes = fVoxels.GetBoxes();
|
|
safetyMin = kInfinity;
|
|
std::size_t safetyNode = 0;
|
|
G4ThreeVector localPoint;
|
|
|
|
std::size_t numNodes = fSolids.size();
|
|
for (std::size_t i = 0; i < numNodes; ++i)
|
|
{
|
|
G4double d2xyz = 0.;
|
|
G4double dxyz0 = std::abs(aPoint.x() - boxes[i].pos.x()) - boxes[i].hlen.x();
|
|
if (dxyz0 > safetyMin) continue;
|
|
G4double dxyz1 = std::abs(aPoint.y() - boxes[i].pos.y()) - boxes[i].hlen.y();
|
|
if (dxyz1 > safetyMin) continue;
|
|
G4double dxyz2 = std::abs(aPoint.z() - boxes[i].pos.z()) - boxes[i].hlen.z();
|
|
if (dxyz2 > safetyMin) continue;
|
|
|
|
if (dxyz0 > 0) d2xyz += dxyz0 * dxyz0;
|
|
if (dxyz1 > 0) d2xyz += dxyz1 * dxyz1;
|
|
if (dxyz2 > 0) d2xyz += dxyz2 * dxyz2;
|
|
if (d2xyz >= safetyMin * safetyMin) continue;
|
|
|
|
G4VSolid& solid = *fSolids[i];
|
|
const G4Transform3D& transform = fTransformObjs[i];
|
|
localPoint = GetLocalPoint(transform, aPoint);
|
|
fAccurate = true;
|
|
G4double safety = solid.DistanceToIn(localPoint);
|
|
fAccurate = false;
|
|
if (safetyMin > safety)
|
|
{
|
|
safetyMin = safety;
|
|
safetyNode = i;
|
|
}
|
|
}
|
|
return (G4int)safetyNode;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
void G4MultiUnion::TransformLimits(G4ThreeVector& min, G4ThreeVector& max,
|
|
const G4Transform3D& transformation) const
|
|
{
|
|
// The goal of this method is to convert the quantities min and max
|
|
// (representing the bounding box of a given solid in its local frame)
|
|
// to the main frame, using "transformation"
|
|
|
|
G4ThreeVector vertices[8] = // Detemination of the vertices thanks to
|
|
{ // the extension of each solid:
|
|
G4ThreeVector(min.x(), min.y(), min.z()), // 1st vertice:
|
|
G4ThreeVector(min.x(), max.y(), min.z()), // 2nd vertice:
|
|
G4ThreeVector(max.x(), max.y(), min.z()),
|
|
G4ThreeVector(max.x(), min.y(), min.z()),
|
|
G4ThreeVector(min.x(), min.y(), max.z()),
|
|
G4ThreeVector(min.x(), max.y(), max.z()),
|
|
G4ThreeVector(max.x(), max.y(), max.z()),
|
|
G4ThreeVector(max.x(), min.y(), max.z())
|
|
};
|
|
|
|
min.set(kInfinity,kInfinity,kInfinity);
|
|
max.set(-kInfinity,-kInfinity,-kInfinity);
|
|
|
|
// Loop on th vertices
|
|
G4int limit = sizeof(vertices) / sizeof(G4ThreeVector);
|
|
for (G4int i = 0 ; i < limit; ++i)
|
|
{
|
|
// From local frame to the global one:
|
|
// Current positions on the three axis:
|
|
G4ThreeVector current = GetGlobalPoint(transformation, vertices[i]);
|
|
|
|
// If need be, replacement of the min & max values:
|
|
if (current.x() > max.x()) max.setX(current.x());
|
|
if (current.x() < min.x()) min.setX(current.x());
|
|
|
|
if (current.y() > max.y()) max.setY(current.y());
|
|
if (current.y() < min.y()) min.setY(current.y());
|
|
|
|
if (current.z() > max.z()) max.setZ(current.z());
|
|
if (current.z() < min.z()) min.setZ(current.z());
|
|
}
|
|
}
|
|
|
|
// Stream object contents to an output stream
|
|
//______________________________________________________________________________
|
|
std::ostream& G4MultiUnion::StreamInfo(std::ostream& os) const
|
|
{
|
|
G4long oldprc = os.precision(16);
|
|
os << "-----------------------------------------------------------\n"
|
|
<< " *** Dump for solid - " << GetName() << " ***\n"
|
|
<< " ===================================================\n"
|
|
<< " Solid type: G4MultiUnion\n"
|
|
<< " Parameters: \n";
|
|
std::size_t numNodes = fSolids.size();
|
|
for (std::size_t i = 0 ; i < numNodes ; ++i)
|
|
{
|
|
G4VSolid& solid = *fSolids[i];
|
|
solid.StreamInfo(os);
|
|
const G4Transform3D& transform = fTransformObjs[i];
|
|
os << " Translation is " << transform.getTranslation() << " \n";
|
|
os << " Rotation is :" << " \n";
|
|
os << " " << transform.getRotation() << "\n";
|
|
}
|
|
os << " \n"
|
|
<< "-----------------------------------------------------------\n";
|
|
os.precision(oldprc);
|
|
|
|
return os;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4ThreeVector G4MultiUnion::GetPointOnSurface() const
|
|
{
|
|
G4ThreeVector point;
|
|
|
|
G4long size = fSolids.size();
|
|
|
|
do
|
|
{
|
|
G4long rnd = G4RandFlat::shootInt(G4long(0), size);
|
|
G4VSolid& solid = *fSolids[rnd];
|
|
point = solid.GetPointOnSurface();
|
|
const G4Transform3D& transform = fTransformObjs[rnd];
|
|
point = GetGlobalPoint(transform, point);
|
|
}
|
|
while (Inside(point) != EInside::kSurface);
|
|
|
|
return point;
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
void
|
|
G4MultiUnion::DescribeYourselfTo ( G4VGraphicsScene& scene ) const
|
|
{
|
|
scene.AddSolid (*this);
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4Polyhedron* G4MultiUnion::CreatePolyhedron() const
|
|
{
|
|
if (G4BooleanSolid::GetExternalBooleanProcessor() == nullptr)
|
|
{
|
|
HepPolyhedronProcessor processor;
|
|
HepPolyhedronProcessor::Operation operation = HepPolyhedronProcessor::UNION;
|
|
|
|
G4VSolid* solidA = GetSolid(0);
|
|
const G4Transform3D transform0 = GetTransformation(0);
|
|
G4DisplacedSolid dispSolidA("placedA", solidA, transform0);
|
|
|
|
auto top = new G4Polyhedron(*dispSolidA.GetPolyhedron());
|
|
|
|
for (G4int i = 1; i < GetNumberOfSolids(); ++i)
|
|
{
|
|
G4VSolid* solidB = GetSolid(i);
|
|
const G4Transform3D transform = GetTransformation(i);
|
|
G4DisplacedSolid dispSolidB("placedB", solidB, transform);
|
|
G4Polyhedron* operand = dispSolidB.GetPolyhedron();
|
|
processor.push_back(operation, *operand);
|
|
}
|
|
|
|
if (processor.execute(*top))
|
|
{
|
|
return top;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return G4BooleanSolid::GetExternalBooleanProcessor()->Process(this);
|
|
}
|
|
}
|
|
|
|
//______________________________________________________________________________
|
|
G4Polyhedron* G4MultiUnion::GetPolyhedron() const
|
|
{
|
|
if (fpPolyhedron == nullptr ||
|
|
fRebuildPolyhedron ||
|
|
fpPolyhedron->GetNumberOfRotationStepsAtTimeOfCreation() !=
|
|
fpPolyhedron->GetNumberOfRotationSteps())
|
|
{
|
|
G4AutoLock l(&polyhedronMutex);
|
|
delete fpPolyhedron;
|
|
fpPolyhedron = CreatePolyhedron();
|
|
fRebuildPolyhedron = false;
|
|
l.unlock();
|
|
}
|
|
return fpPolyhedron;
|
|
}
|