The following describes the implementation of a box geometry that can serve as a guide to users for developing their own geometry classes.
Our box geometry class will describe a parallelepiped of a user defined size that defines a single region filled with 1 medium.
As our geometry must be derived from EGS_BaseGeometry, we include its header file. We also want to be able to transform the box from being centered about the origin and having its faces parallel to the x-, y- and z-axis to any other location and/or orientation. For that we will employ affine transformations and therefore also include the transformations header file:
As with other geometries, we will be compiling our box class into a shared library. We have to therefore mark the symbols that will be exported from the library. For this purpose we define a macro
EGS_BOX_EXPORT
, which we define like this
#ifdef WIN32
#ifdef BUILD_BOX_DLL
#define EGS_BOX_EXPORT __declspec(dllexport)
#else
#define EGS_BOX_EXPORT __declspec(dllimport)
#endif
#define EGS_BOX_LOCAL
#else
#ifdef HAVE_VISIBILITY
#define EGS_BOX_EXPORT __attribute__ ((visibility ("default")))
#define EGS_BOX_LOCAL __attribute__ ((visibility ("hidden")))
#else
#define EGS_BOX_EXPORT
#define EGS_BOX_LOCAL
#endif
#endif
We are now ready to develop our box geometry class by deriving from
EGS_BaseGeometry:
EGS_Float ax, ay, az;
static string type;
public:
The data members
ax,ay,az
are for the box size, the
type
static data member will be set to "EGS_Box" and used and the
T
data member will point to the affine transformation associated with boxes not located about the origin and/or rotated boxes. We now need a constructor and a destructor for box objects. We provide two different constructors: one for boxes where all sides have the same size (
i.e. cubes)
ax(a), ay(a), az(a), T(0) {
if (t) {
}
nreg = 1;
};
and one for boxes with 3 different sides
EGS_Box(EGS_Float Ax, EGS_Float Ay, EGS_Float Az,
ax(Ax), ay(Ay), az(Az), T(0) {
if (t) {
}
nreg = 1;
};
In the destructor we simply deallocate the memory used by the affine transformation, if there was a transformation defined:
We can now start implementing the required geometry methods
isInside() ,
isWhere() ,
howfar() and
hownear() .
A point is inside a box about the origin if its x-position is between -ax/2
and ax/2
, its y-position between -ay/2
and ay/2
, etc. For a transformed box, the easiest way to determine if the position is inside the box is to apply the inverse transformation to the position and to then use the simple method for boxes about the origin. The isInside
implementation therefore looks like this
if (2*xp.
x + ax < 0 || 2*xp.x - ax > 0) {
return false;
}
if (2*xp.
y + ay < 0 || 2*xp.y - ay > 0) {
return false;
}
if (2*xp.
z + az < 0 || 2*xp.z - az > 0) {
return false;
}
return true;
};
(multiplying the position vector with
*T
from the right is an operation equivalent to transforming
x
with the inverse of
*T
)
The implementation of the isWhere
method is very easy: as we have a single region this function should return 0 if the position is inside and -1 if it is outside:
return isInside(x) ? 0 : -1;
};
The implementation of the howfar
method is also easiest if we first transform the position and direction to a frame where the box is centered about the origin and its sides are parallel to the axis:
The
howfar
logic is different for positions inside (
i.e.
ireg=0
) and outside. For inside points, we must calculate the distances to the x-planes, y-planes and z-planes from the transformed point
xp
along the transformed direction
up
and take the minimum
t1
of the three. If
t1
is less than the intended step
t
, we must set
t
to
t1
and return -1 (
i.e. the particle will exit the box at the end of the step). If
normal
is not
null
, our
howfar
function is being called from the visualization utility and we must also calculate the normal of the plane being intersected to exit the box with the normal pointing towards the position of the particle. The implementation therefore looks like this
When the position is outside, the logic is
- if the particle intersects the top or bottom side, check if the intersection point is between the left-right and east-west sides.
- If yes, this is the position where the trajectory is entering the box => return the required information
- If no, repeat 1-2 for left-right sides and if necessary for the east-west sides.
- If all of the above fails, the particle does not enter the box.
The code is lengthy and not reproduced here.
The next step is the implementation of the hownear
function. As with the other methods, we first inverse transform the position and then calculate the minimum distance to a box about the origin
If the position is inside the boxe (
i.e.
ireg=0
), the minimum distance to a boundary is the minimum distance to any of the 6 faces:
If the position is outside, the algorithm is slightly more difficult and is left to the reader to understand from the implementation:
We reimplement the getType
function to return type
so that the type of our geometry class is known:
We also reimplement the printInfo() function so that users of our geometry class can get a better description of the geometry object:
The actual implementation of
printInfo
is in the
egs_box.cpp file, which includes
egs_box.h
and
egs_input.h:
The latter is necessary to gain access to the definition of the
EGS_Input class that is needed in the
createGeometry
function (see below). The
printInfo
implementation is simple:
void EGS_Box::printInfo() const {
egsInformation(
"=======================================================\n");
}
We also need to declare the static data member type
string EGS_Box::type("EGS_Box");
According to verious sources, compilers can produce better code if text strings are static and local to the library. Hence, we define the various error messages and the key we will be using to define the size of a box object as such:
static char EGS_BOX_LOCAL ebox_message1[] = "createGeometry(box): %s\n";
static char EGS_BOX_LOCAL ebox_message2[] = "null input?";
static char EGS_BOX_LOCAL ebox_message3[] = "wrong/missing 'box size' input?";
static char EGS_BOX_LOCAL ebox_message4[] =
"expecting 1 or 3 float inputs for 'box size'";
static char EGS_BOX_LOCAL ebox_key1[] = "box size";
The remaining task now is to provide a C-style function named createGeometry
for the box shared library that will be used to dynamically create box objects based on the information stored in an EGS_Input object.
Just to be sure, we check that
input
is not
null
and issue a warning and return
null
if it is:
We then check if the input object contains a
box size
property
return 0;
}
vector<EGS_Float> s;
and if it doesn't (indicated by
err
not being zero), issue a warning and return
null:
Next we check if the input contains a definition if a transformation for our box geometry:
Depending on whether there was one or three values to the
box size
key, we use the corresponding constructor to create the new box object, but refuse to create it if there was some other number of values (because,
e.g., the user made a mistake in the input file):
We must now delete the affine transformation pointed to by
t
as the box object has made its own copy in the constructor
else if (s.size() == 3) {
result =
new EGS_Box(s[0],s[1],s[2],t);
}
else {
if (t) {
delete t;
We then use the
setName
and
setMedia
functions inherited from
EGS_BaseGeometry to set the name of the newly created box and fill it with a medium
}
return 0;
}
if (t) {
delete t;
}
to then return the box
Based on this createGeometry
implementation, users will be able to use our box geometry class by having the following input in the input file
:start geometry:
name = some_name
box size = 3.5 1 7
:start transformation:
input defining the transformation
:stop transformation
:start media input:
media = H2O
:stop media input
:stop geometry:
The transformation related input can be of course missing in which case the box will be centered about the origin and has its faces parallel to the x-, y- and z-axis.
If we would have been nice to the users of our box geometry class, we could have provided an easier way for defining the medium of the box by re-implementing the setMedia() function, e.g. something along these lines:
void EGS_Box::setMedia(EGS_Input *inp) {
string medname; int err = inp->getInput("medium",medname);
if( !err ) setMedium(medname);
}
That way, the user could define the medium of the box by simply using
medium = H2O
instead of :start media input: ... :stop media input:
which is necessary for geometries with several regions and potentialy different media and therefore provided as default implementation.
The final step is to provide a Makefile for our geometry DSO. We include the config files containing various definitions for the make system:
include $(EGS_CONFIG)
include $(SPEC_DIR)egspp.spec
include $(SPEC_DIR)egspp_$(my_machine).conf
We then must add the
BUILD_BOX_DLL
macro to the list of defined macros,
DEFS = $(DEF1) -DBUILD_BOX_DLL
set the name of the DSO,
define the source files needed to create the DSO,
and tell the make system that our class depends on the
egs_transformations.h header file,
my_deps = egs_transformations.h
extra_dep = $(addprefix $(DSOLIBS), $(my_deps))
We can then use the generic rules for building the DSO:
include $(SPEC_DIR)egspp_libs.spec
and because our only object file needed to build the library depends on
egs_box.cpp
and
egs_box.h
, we can also use the generic dependence rule
That's all.
Here is the complete source code of the EGS_Box class.
The header file:
#ifndef EGS_BOX_
#define EGS_BOX_
#ifdef WIN32
#ifdef BUILD_BOX_DLL
#define EGS_BOX_EXPORT __declspec(dllexport)
#else
#define EGS_BOX_EXPORT __declspec(dllimport)
#endif
#define EGS_BOX_LOCAL
#else
#ifdef HAVE_VISIBILITY
#define EGS_BOX_EXPORT __attribute__ ((visibility ("default")))
#define EGS_BOX_LOCAL __attribute__ ((visibility ("hidden")))
#else
#define EGS_BOX_EXPORT
#define EGS_BOX_LOCAL
#endif
#endif
EGS_Float ax, ay, az;
static string type;
public:
ax(a), ay(a), az(a), T(0) {
if (t) {
}
nreg = 1;
};
EGS_Box(EGS_Float Ax, EGS_Float Ay, EGS_Float Az,
ax(Ax), ay(Ay), az(Az), T(0) {
if (t) {
}
nreg = 1;
};
if (T) {
delete T;
}
};
if (2*xp.
x + ax < 0 || 2*xp.x - ax > 0) {
return false;
}
if (2*xp.
y + ay < 0 || 2*xp.y - ay > 0) {
return false;
}
if (2*xp.
z + az < 0 || 2*xp.z - az > 0) {
return false;
}
return true;
};
};
};
return t;
};
EGS_Float &t,
int *newmed=0,
EGS_Vector *normal=0) {
if (T) {
xp = x*(*T);
up = u*T->getRotation();
}
if (ireg == 0) {
int inew = 0;
t1 = (ax - 2*xp.x)/(2*up.
x);
}
t1 = -(ax + 2*xp.x)/(2*up.
x);
}
if (t1 < t) {
t = t1;
inew = -1;
}
t1 = (ay - 2*xp.y)/(2*up.
y);
}
t1 = -(ay + 2*xp.y)/(2*up.
y);
}
if (t1 < t) {
t = t1;
inew = -1;
}
t1 = (az - 2*xp.z)/(2*up.
z);
}
t1 = -(az + 2*xp.z)/(2*up.
z);
}
if (t1 < t) {
t = t1;
inew = -1;
}
if (inew < 0) {
if (newmed) {
*newmed = -1;
}
if (normal) {
if (T) {
*normal = T->getRotation()*n;
}
else {
*normal = n;
}
}
}
return inew;
}
if (2*xp.x + ax < 0 && up.x > 0) {
t1 = -(2*xp.x + ax)/(2*up.
x);
}
else if (2*xp.x - ax > 0 && up.
x < 0) {
t1 = -(2*xp.x - ax)/(2*up.
x);
}
if (t1 < t) {
EGS_Float y1 = xp.y + up.
y*t1, z1 = xp.z + up.
z*t1;
if (2*y1 + ay > 0 && 2*y1 - ay < 0 &&
2*z1 + az > 0 && 2*z1 - az < 0) {
t = t1;
if (newmed) {
}
if (normal) {
if (T) {
}
else {
}
}
else {
}
else {
}
}
}
return 0;
}
}
if (2*xp.y + ay < 0 && up.y > 0) {
t1 = -(2*xp.y + ay)/(2*up.
y);
}
else if (2*xp.y - ay > 0 && up.
y < 0) {
t1 = -(2*xp.y - ay)/(2*up.
y);
}
if (t1 < t) {
EGS_Float x1 = xp.x + up.
x*t1, z1 = xp.z + up.
z*t1;
if (2*x1 + ax > 0 && 2*x1 - ax < 0 &&
2*z1 + az > 0 && 2*z1 - az < 0) {
t = t1;
if (newmed) {
}
if (normal) {
if (T) {
}
else {
}
}
else {
}
else {
}
}
}
return 0;
}
}
if (2*xp.z + az < 0 && up.z > 0) {
t1 = -(2*xp.z + az)/(2*up.
z);
}
else if (2*xp.z - az > 0 && up.
z < 0) {
t1 = -(2*xp.z - az)/(2*up.
z);
}
if (t1 < t) {
EGS_Float x1 = xp.x + up.
x*t1, y1 = xp.y + up.
y*t1;
if (2*x1 + ax > 0 && 2*x1 - ax < 0 &&
2*y1 + ay > 0 && 2*y1 - ay < 0) {
t = t1;
if (newmed) {
}
if (normal) {
if (T) {
}
else {
}
}
else {
}
else {
}
}
}
return 0;
}
}
return -1;
};
if (ireg >= 0) {
EGS_Float tmin;
EGS_Float t1 = xp.
x + 0.5*ax, t2 = 0.5*ax - xp.
x;
if (t1 < t2) {
tmin = t1;
}
else {
tmin = t2;
}
if (t1 < tmin) {
tmin = t1;
}
if (t1 < tmin) {
tmin = t1;
}
if (t1 < tmin) {
tmin = t1;
}
if (t1 < tmin) {
tmin = t1;
}
return tmin;
}
EGS_Float s1=0, s2=0;
int nout = 0;
EGS_Float t = -0.5*ax - xp.
x;
s1 += t;
s2 += t*t;
nout++;
}
else if (2*xp.
x - ax > 0) {
EGS_Float t = xp.
x - 0.5*ax;
s1 += t;
s2 += t*t;
nout++;
}
EGS_Float t = -0.5*ay - xp.
y;
s1 += t;
s2 += t*t;
nout++;
}
else if (2*xp.
y - ay > 0) {
EGS_Float t = xp.
y - 0.5*ay;
s1 += t;
s2 += t*t;
nout++;
}
EGS_Float t = -0.5*az - xp.
z;
s1 += t;
s2 += t*t;
nout++;
}
else if (2*xp.
z - az > 0) {
EGS_Float t = xp.
z - 0.5*az;
s1 += t;
s2 += t*t;
nout++;
}
if (nout < 2) {
return s1;
}
return sqrt(s2);
};
return type;
};
};
#endif
The .cpp file:
void EGS_Box::printInfo() const {
egsInformation(
"=======================================================\n");
}
string EGS_Box::type("EGS_Box");
static char EGS_BOX_LOCAL ebox_message1[] = "createGeometry(box): %s\n";
static char EGS_BOX_LOCAL ebox_message2[] = "null input?";
static char EGS_BOX_LOCAL ebox_message3[] = "wrong/missing 'box size' input?";
static char EGS_BOX_LOCAL ebox_message4[] =
"expecting 1 or 3 float inputs for 'box size'";
static char EGS_BOX_LOCAL ebox_key1[] = "box size";
extern "C" {
if (!input) {
return 0;
}
vector<EGS_Float> s;
if (err) {
return 0;
}
if (s.size() == 1) {
}
else if (s.size() == 3) {
result =
new EGS_Box(s[0],s[1],s[2],t);
}
else {
if (t) {
delete t;
}
return 0;
}
if (t) {
delete t;
}
return result;
}
}
The Makefile:
###############################################################################
#
# EGSnrc egs++ makefile to build box geometry
# Copyright (C) 2015 National Research Council Canada
#
# This file is part of EGSnrc.
#
# EGSnrc is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# EGSnrc 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 Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with EGSnrc. If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
#
# Author: Iwan Kawrakow, 2005
#
# Contributors:
#
###############################################################################
include $(EGS_CONFIG)
include $(SPEC_DIR)egspp.spec
include $(SPEC_DIR)egspp_$(my_machine).conf
DEFS = $(DEF1) -DBUILD_BOX_DLL
library = egs_box
lib_files = egs_box
my_deps = egs_transformations.h
extra_dep = $(addprefix $(DSOLIBS), $(my_deps))
include $(SPEC_DIR)egspp_libs.spec
$(make_depend)
void EGS_Box::printInfo() const {
egsInformation(
"=======================================================\n");
}
string EGS_Box::type("EGS_Box");
static char EGS_BOX_LOCAL ebox_message1[] = "createGeometry(box): %s\n";
static char EGS_BOX_LOCAL ebox_message2[] = "null input?";
static char EGS_BOX_LOCAL ebox_message3[] = "wrong/missing 'box size' input?";
static char EGS_BOX_LOCAL ebox_message4[] =
"expecting 1 or 3 float inputs for 'box size'";
static char EGS_BOX_LOCAL ebox_key1[] = "box size";
extern "C" {
if (!input) {
return 0;
}
vector<EGS_Float> s;
if (err) {
return 0;
}
if (s.size() == 1) {
}
else if (s.size() == 3) {
result =
new EGS_Box(s[0],s[1],s[2],t);
}
else {
if (t) {
delete t;
}
return 0;
}
if (t) {
delete t;
}
return result;
}
}