CSGObjectTest.h 68.4 KB
Newer Older
1
2
3
4
5
6
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
//     NScD Oak Ridge National Laboratory, European Spallation Source
//     & Institut Laue - Langevin
// SPDX - License - Identifier: GPL - 3.0 +
Karl Palmen's avatar
Karl Palmen committed
7
8
#ifndef MANTID_TESTCSGOBJECT__
#define MANTID_TESTCSGOBJECT__
Russell Taylor's avatar
Russell Taylor committed
9

10
#include "MantidGeometry/Objects/CSGObject.h"
11

12
#include "MantidGeometry/Math/Algebra.h"
13
#include "MantidGeometry/Objects/Rules.h"
LamarMoore's avatar
LamarMoore committed
14
#include "MantidGeometry/Objects/ShapeFactory.h"
15
#include "MantidGeometry/Objects/Track.h"
16
#include "MantidGeometry/Rendering/GeometryHandler.h"
LamarMoore's avatar
LamarMoore committed
17
18
19
20
#include "MantidGeometry/Surfaces/Cylinder.h"
#include "MantidGeometry/Surfaces/Plane.h"
#include "MantidGeometry/Surfaces/Sphere.h"
#include "MantidGeometry/Surfaces/SurfaceFactory.h"
21
#include "MantidKernel/Material.h"
22
#include "MantidKernel/MersenneTwister.h"
23
#include "MantidTestHelpers/ComponentCreationHelper.h"
24
#include "MockRNG.h"
25

LamarMoore's avatar
LamarMoore committed
26
#include <cxxtest/TestSuite.h>
27
28

#include "boost/make_shared.hpp"
LamarMoore's avatar
LamarMoore committed
29
#include "boost/shared_ptr.hpp"
30
31
32

#include <Poco/DOM/AutoPtr.h>
#include <Poco/DOM/Document.h>
33

Russell Taylor's avatar
Russell Taylor committed
34
35
using namespace Mantid;
using namespace Geometry;
36
using detail::ShapeInfo;
37
using Mantid::Kernel::V3D;
Russell Taylor's avatar
Russell Taylor committed
38

39
class CSGObjectTest : public CxxTest::TestSuite {
Russell Taylor's avatar
Russell Taylor committed
40
41

public:
42
  void testDefaultObjectHasEmptyMaterial() {
43
    CSGObject obj;
44

45
46
    TSM_ASSERT_DELTA("Expected a zero number density", 0.0,
                     obj.material().numberDensity(), 1e-12);
47
48
  }

49
  void testObjectSetMaterialReplacesExisting() {
50
    using Mantid::Kernel::Material;
51
    CSGObject obj;
52
53
54

    TSM_ASSERT_DELTA("Expected a zero number density", 0.0,
                     obj.material().numberDensity(), 1e-12);
55
56
    obj.setMaterial(
        Material("arm", PhysicalConstants::getNeutronAtom(13), 45.0));
57
58
59
60
    TSM_ASSERT_DELTA("Expected a number density of 45", 45.0,
                     obj.material().numberDensity(), 1e-12);
  }

61
  void testCopyConstructorGivesObjectWithSameAttributes() {
62
    auto original_ptr = ComponentCreationHelper::createSphere(1.0);
63
    auto &original = dynamic_cast<CSGObject &>(*original_ptr);
64
    original.setID("sp-1");
65
    ShapeInfo::GeometryShape objType;
66
    double radius(-1.0), height(-1.0), innerRadius(0.0);
67
    std::vector<V3D> pts;
Lamar Moore's avatar
Lamar Moore committed
68
    auto handler = original.getGeometryHandler();
69
    TS_ASSERT(handler->hasShapeInfo());
70
    original.GetObjectGeom(objType, pts, innerRadius, radius, height);
71
    TS_ASSERT_EQUALS(ShapeInfo::GeometryShape::SPHERE, objType);
72

73
    CSGObject copy(original);
74
    // The copy should be a primitive object with a GeometryHandler
75
    copy.GetObjectGeom(objType, pts, innerRadius, radius, height);
76

Martyn Gigg's avatar
Martyn Gigg committed
77
    TS_ASSERT_EQUALS("sp-1", copy.id());
78
79
80
    auto handlerCopy = copy.getGeometryHandler();
    TS_ASSERT(handlerCopy->hasShapeInfo());
    TS_ASSERT_EQUALS(handler->shapeInfo(), handlerCopy->shapeInfo());
81
    TS_ASSERT_EQUALS(copy.getName(), original.getName());
82
    // Check the string representation is the same
83
84
    TS_ASSERT_EQUALS(copy.str(), original.str());
    TS_ASSERT_EQUALS(copy.getSurfaceIndex(), original.getSurfaceIndex());
85
86
  }

87
  void testAssignmentOperatorGivesObjectWithSameAttributes() {
88
    auto original_ptr = ComponentCreationHelper::createSphere(1.0);
89
    auto &original = dynamic_cast<CSGObject &>(*original_ptr);
90
    original.setID("sp-1");
91
    ShapeInfo::GeometryShape objType;
92
    double radius(-1.0), height(-1.0), innerRadius(0.0);
93
    std::vector<V3D> pts;
Lamar Moore's avatar
Lamar Moore committed
94
    auto handler = original.getGeometryHandler();
95
    TS_ASSERT(handler->hasShapeInfo());
96
    original.GetObjectGeom(objType, pts, innerRadius, radius, height);
97
    TS_ASSERT_EQUALS(ShapeInfo::GeometryShape::SPHERE, objType);
98

99
    CSGObject lhs;  // initialize
100
    lhs = original; // assign
101
    // The copy should be a primitive object with a GluGeometryHandler
102
    lhs.GetObjectGeom(objType, pts, innerRadius, radius, height);
103

Martyn Gigg's avatar
Martyn Gigg committed
104
    TS_ASSERT_EQUALS("sp-1", lhs.id());
105
106
107
108
    TS_ASSERT_EQUALS(ShapeInfo::GeometryShape::SPHERE, objType);
    auto handlerCopy = lhs.getGeometryHandler();
    TS_ASSERT(handlerCopy->hasShapeInfo());
    TS_ASSERT_EQUALS(handlerCopy->shapeInfo(), handler->shapeInfo());
109
  }
Russell Taylor's avatar
Russell Taylor committed
110

111
  void testCreateUnitCube() {
112
    boost::shared_ptr<CSGObject> geom_obj = createUnitCube();
113

114
    TS_ASSERT_EQUALS(geom_obj->str(), "68 1 -2 3 -4 5 -6");
115
116

    double xmin(0.0), xmax(0.0), ymin(0.0), ymax(0.0), zmin(0.0), zmax(0.0);
117
    geom_obj->getBoundingBox(xmax, ymax, zmax, xmin, ymin, zmin);
Russell Taylor's avatar
Russell Taylor committed
118
119
  }

120
121
  void testCloneWithMaterial() {
    using Mantid::Kernel::Material;
mantid-builder's avatar
mantid-builder committed
122
123
    auto testMaterial =
        Material("arm", PhysicalConstants::getNeutronAtom(13), 45.0);
124
    auto geom_obj = createUnitCube();
125
126
127
    std::unique_ptr<IObject> cloned_obj;
    TS_ASSERT_THROWS_NOTHING(
        cloned_obj.reset(geom_obj->cloneWithMaterial(testMaterial)));
128
    TSM_ASSERT_DELTA("Expected a number density of 45", 45.0,
mantid-builder's avatar
mantid-builder committed
129
                     cloned_obj->material().numberDensity(), 1e-12);
130
131
  }

132
  void testIsOnSideCappedCylinder() {
133
    auto geom_obj = createCappedCylinder();
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
    // inside
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, 0)), false); // origin
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 2.9, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, -2.9, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, -2.9)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, 2.9)), false);
    // on the side
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, -3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, -3)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, 3)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(1.2, 0, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(-3.2, 0, 0)), true);

    // on the edges
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(1.2, 3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(1.2, -3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(1.2, 0, -3)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(1.2, 0, 3)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(-3.2, 3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(-3.2, -3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(-3.2, 0, -3)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(-3.2, 0, 3)), true);
    // out side
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 3.1, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, -3.1, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, -3.1)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, 3.1)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(1.3, 0, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(-3.3, 0, 0)), false);
Russell Taylor's avatar
Russell Taylor committed
164
165
  }

166
  void testIsValidCappedCylinder() {
167
    auto geom_obj = createCappedCylinder();
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
    // inside
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, 0)), true); // origin
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 2.9, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, -2.9, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, -2.9)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, 2.9)), true);
    // on the side
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, -3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, -3)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, 3)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(1.2, 0, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(-3.2, 0, 0)), true);

    // on the edges
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(1.2, 3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(1.2, -3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(1.2, 0, -3)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(1.2, 0, 3)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(-3.2, 3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(-3.2, -3, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(-3.2, 0, -3)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(-3.2, 0, 3)), true);
    // out side
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 3.1, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, -3.1, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, -3.1)), false);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, 3.1)), false);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(1.3, 0, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(-3.3, 0, 0)), false);
Russell Taylor's avatar
Russell Taylor committed
198
199
  }

200
  void testIsOnSideSphere() {
201
    auto geom_obj = ComponentCreationHelper::createSphere(4.1);
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
    // inside
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, 0)), false); // origin
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 4.0, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, -4.0, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, -4.0)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, 4.0)), false);
    // on the side
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 4.1, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, -4.1, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, -4.1)), true);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, 4.1)), true);

    // out side
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 4.2, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, -4.2, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, -4.2)), false);
    TS_ASSERT_EQUALS(geom_obj->isOnSide(V3D(0, 0, 4.2)), false);
Russell Taylor's avatar
Russell Taylor committed
219
220
  }

221
  void testIsValidSphere() {
222
    auto geom_obj = ComponentCreationHelper::createSphere(4.1);
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
    // inside
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, 0)), true); // origin
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 4.0, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, -4.0, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, -4.0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, 4.0)), true);
    // on the side
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 4.1, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, -4.1, 0)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, -4.1)), true);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, 4.1)), true);

    // out side
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 4.2, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, -4.2, 0)), false);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, -4.2)), false);
    TS_ASSERT_EQUALS(geom_obj->isValid(V3D(0, 0, 4.2)), false);
Russell Taylor's avatar
Russell Taylor committed
240
241
  }

242
  void testCalcValidTypeSphere() {
243
    auto geom_obj = ComponentCreationHelper::createSphere(4.1);
244
    // entry on the normal
Peterson, Peter's avatar
Peterson, Peter committed
245
246
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(-4.1, 0, 0), V3D(1, 0, 0)),
                     TrackDirection::ENTERING);
247
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(-4.1, 0, 0), V3D(-1, 0, 0)),
Peterson, Peter's avatar
Peterson, Peter committed
248
249
250
251
252
253
254
                     TrackDirection::LEAVING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(4.1, 0, 0), V3D(1, 0, 0)),
                     TrackDirection::LEAVING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(4.1, 0, 0), V3D(-1, 0, 0)),
                     TrackDirection::ENTERING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(0, -4.1, 0), V3D(0, 1, 0)),
                     TrackDirection::ENTERING);
255
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(0, -4.1, 0), V3D(0, -1, 0)),
Peterson, Peter's avatar
Peterson, Peter committed
256
257
258
259
260
                     TrackDirection::LEAVING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(0, 4.1, 0), V3D(0, 1, 0)),
                     TrackDirection::LEAVING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(0, 4.1, 0), V3D(0, -1, 0)),
                     TrackDirection::ENTERING);
261
262

    // a glancing blow
Peterson, Peter's avatar
Peterson, Peter committed
263
264
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(-4.1, 0, 0), V3D(0, 1, 0)),
                     TrackDirection::INVALID);
265
266
    // not quite on the normal
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(-4.1, 0, 0), V3D(0.5, 0.5, 0)),
Peterson, Peter's avatar
Peterson, Peter committed
267
                     TrackDirection::ENTERING);
268
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(4.1, 0, 0), V3D(0.5, 0.5, 0)),
Peterson, Peter's avatar
Peterson, Peter committed
269
                     TrackDirection::LEAVING);
Russell Taylor's avatar
Russell Taylor committed
270
271
  }

272
  void testGetBoundingBoxForSphere() {
273
    auto geom_obj = ComponentCreationHelper::createSphere(4.1);
274
275
    const double tolerance(1e-10);

276
277
278
279
280
281
282
283
    const BoundingBox &bbox = geom_obj->getBoundingBox();

    TS_ASSERT_DELTA(bbox.xMax(), 4.1, tolerance);
    TS_ASSERT_DELTA(bbox.yMax(), 4.1, tolerance);
    TS_ASSERT_DELTA(bbox.zMax(), 4.1, tolerance);
    TS_ASSERT_DELTA(bbox.xMin(), -4.1, tolerance);
    TS_ASSERT_DELTA(bbox.yMin(), -4.1, tolerance);
    TS_ASSERT_DELTA(bbox.zMin(), -4.1, tolerance);
284
285
  }

286
  void testCalcValidTypeCappedCylinder() {
287
    auto geom_obj = createCappedCylinder();
288
    // entry on the normal
Peterson, Peter's avatar
Peterson, Peter committed
289
290
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(-3.2, 0, 0), V3D(1, 0, 0)),
                     TrackDirection::ENTERING);
291
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(-3.2, 0, 0), V3D(-1, 0, 0)),
Peterson, Peter's avatar
Peterson, Peter committed
292
293
294
295
296
297
298
299
300
301
302
303
304
                     TrackDirection::LEAVING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(1.2, 0, 0), V3D(1, 0, 0)),
                     TrackDirection::LEAVING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(1.2, 0, 0), V3D(-1, 0, 0)),
                     TrackDirection::ENTERING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(0, -3, 0), V3D(0, 1, 0)),
                     TrackDirection::ENTERING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(0, -3, 0), V3D(0, -1, 0)),
                     TrackDirection::LEAVING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(0, 3, 0), V3D(0, 1, 0)),
                     TrackDirection::LEAVING);
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(0, 3, 0), V3D(0, -1, 0)),
                     TrackDirection::ENTERING);
305
306

    // a glancing blow
Peterson, Peter's avatar
Peterson, Peter committed
307
308
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(-3.2, 0, 0), V3D(0, 1, 0)),
                     TrackDirection::INVALID);
309
310
    // not quite on the normal
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(-3.2, 0, 0), V3D(0.5, 0.5, 0)),
Peterson, Peter's avatar
Peterson, Peter committed
311
                     TrackDirection::ENTERING);
312
    TS_ASSERT_EQUALS(geom_obj->calcValidType(V3D(1.2, 0, 0), V3D(0.5, 0.5, 0)),
Peterson, Peter's avatar
Peterson, Peter committed
313
                     TrackDirection::LEAVING);
Russell Taylor's avatar
Russell Taylor committed
314
315
  }

316
  void testInterceptSurfaceSphereZ() {
317
    std::vector<Link> expectedResults;
318
    std::string S41 = "s 1 1 1 4"; // Sphere at (1,1,1) radius 4
Russell Taylor's avatar
Russell Taylor committed
319
320

    // First create some surfaces
321
322
    std::map<int, boost::shared_ptr<Surface>> SphSurMap;
    SphSurMap[41] = boost::make_shared<Sphere>();
Russell Taylor's avatar
Russell Taylor committed
323
324
325
    SphSurMap[41]->setSurface(S41);
    SphSurMap[41]->setName(41);

326
    // A sphere
327
    std::string ObjSphere = "-41";
Russell Taylor's avatar
Russell Taylor committed
328

mantid-builder's avatar
mantid-builder committed
329
330
    boost::shared_ptr<CSGObject> geom_obj =
        boost::shared_ptr<CSGObject>(new CSGObject);
331
    geom_obj->setObject(41, ObjSphere);
332
    geom_obj->populate(SphSurMap);
Russell Taylor's avatar
Russell Taylor committed
333

334
    Track track(V3D(-1, 1.5, 1), V3D(1, 0, 0));
Russell Taylor's avatar
Russell Taylor committed
335

336
    // format = startPoint, endPoint, total distance so far
Russell Taylor's avatar
Russell Taylor committed
337
    // forward only intercepts means that start point should be track origin
338
    expectedResults.emplace_back(Link(V3D(-1, 1.5, 1),
Nick Draper's avatar
Nick Draper committed
339
340
                                      V3D(sqrt(16 - 0.25) + 1, 1.5, 1.0),
                                      sqrt(15.75) + 2, *geom_obj));
Russell Taylor's avatar
Russell Taylor committed
341

342
    checkTrackIntercept(geom_obj, track, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
343
344
  }

345
  void testInterceptSurfaceSphereY() {
346
    std::vector<Link> expectedResults;
347
    auto geom_obj = ComponentCreationHelper::createSphere(4.1);
348
    Track track(V3D(0, -10, 0), V3D(0, 1, 0));
Russell Taylor's avatar
Russell Taylor committed
349

350
    // format = startPoint, endPoint, total distance so far
351
    expectedResults.emplace_back(
352
        Link(V3D(0, -4.1, 0), V3D(0, 4.1, 0), 14.1, *geom_obj));
Russell Taylor's avatar
Russell Taylor committed
353

354
    checkTrackIntercept(geom_obj, track, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
355
356
  }

357
  void testInterceptSurfaceSphereX() {
358
    std::vector<Link> expectedResults;
359
    auto geom_obj = ComponentCreationHelper::createSphere(4.1);
360
    Track track(V3D(-10, 0, 0), V3D(1, 0, 0));
361

362
    // format = startPoint, endPoint, total distance so far
363
    expectedResults.emplace_back(
364
365
        Link(V3D(-4.1, 0, 0), V3D(4.1, 0, 0), 14.1, *geom_obj));
    checkTrackIntercept(geom_obj, track, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
366
367
  }

368
  void testInterceptSurfaceCappedCylinderY() {
369
    std::vector<Link> expectedResults;
370
    auto geom_obj = createCappedCylinder();
371
    // format = startPoint, endPoint, total distance so far
Nick Draper's avatar
Nick Draper committed
372
373
    expectedResults.emplace_back(
        Link(V3D(0, -3, 0), V3D(0, 3, 0), 13, *geom_obj));
Russell Taylor's avatar
Russell Taylor committed
374

375
376
    Track track(V3D(0, -10, 0), V3D(0, 1, 0));
    checkTrackIntercept(geom_obj, track, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
377
378
  }

379
  void testInterceptSurfaceCappedCylinderX() {
380
    std::vector<Link> expectedResults;
381
    auto geom_obj = createCappedCylinder();
382
    Track track(V3D(-10, 0, 0), V3D(1, 0, 0));
Russell Taylor's avatar
Russell Taylor committed
383

384
    // format = startPoint, endPoint, total distance so far
385
    expectedResults.emplace_back(
386
387
        Link(V3D(-3.2, 0, 0), V3D(1.2, 0, 0), 11.2, *geom_obj));
    checkTrackIntercept(geom_obj, track, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
388
389
  }

390
391
392
  void testInterceptSurfaceCappedCylinderMiss() {
    std::vector<Link>
        expectedResults; // left empty as there are no expected results
393
    auto geom_obj = createCappedCylinder();
394
395
396
    V3D dir(1., 1., 0.);
    dir.normalize();
    Track track(V3D(-10, 0, 0), dir);
Russell Taylor's avatar
Russell Taylor committed
397

398
    checkTrackIntercept(geom_obj, track, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
399
400
  }

401
402
  void checkTrackIntercept(Track &track,
                           const std::vector<Link> &expectedResults) {
403
    size_t index = 0;
mantid-builder's avatar
mantid-builder committed
404
405
    for (Track::LType::const_iterator it = track.cbegin(); it != track.cend();
         ++it) {
406
407
      if (index < expectedResults.size()) {
        TS_ASSERT_DELTA(it->distFromStart, expectedResults[index].distFromStart,
mantid-builder's avatar
mantid-builder committed
408
                        1e-6);
409
        TS_ASSERT_DELTA(it->distInsideObject,
mantid-builder's avatar
mantid-builder committed
410
                        expectedResults[index].distInsideObject, 1e-6);
411
412
413
414
        TS_ASSERT_EQUALS(it->componentID, expectedResults[index].componentID);
        TS_ASSERT_EQUALS(it->entryPoint, expectedResults[index].entryPoint);
        TS_ASSERT_EQUALS(it->exitPoint, expectedResults[index].exitPoint);
      }
Russell Taylor's avatar
Russell Taylor committed
415
416
      ++index;
    }
417
    TS_ASSERT_EQUALS(index, expectedResults.size());
Russell Taylor's avatar
Russell Taylor committed
418
419
  }

420
  void checkTrackIntercept(IObject_sptr obj, Track &track,
421
                           const std::vector<Link> &expectedResults) {
422
    int unitCount = obj->interceptSurface(track);
423
424
    TS_ASSERT_EQUALS(unitCount, expectedResults.size());
    checkTrackIntercept(track, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
425
426
  }

Martyn Gigg's avatar
Martyn Gigg committed
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
  void testDistanceWithIntersectionReturnsResult() {
    auto geom_obj = createCappedCylinder();
    V3D dir(0., 1., 0.);
    dir.normalize();
    Track track(V3D(0, 0, 0), dir);

    TS_ASSERT_DELTA(3.0, geom_obj->distance(track), 1e-08)
  }

  void testDistanceWithoutIntersectionThrows() {
    auto geom_obj = createCappedCylinder();
    V3D dir(-1., 0., 0.);
    dir.normalize();
    Track track(V3D(-10, 0, 0), dir);

442
    TS_ASSERT_THROWS(geom_obj->distance(track), const std::runtime_error &)
Martyn Gigg's avatar
Martyn Gigg committed
443
444
  }

445
  void testTrackTwoIsolatedCubes()
446
447
448
  /**
  Test a track going through an object
  */
Russell Taylor's avatar
Russell Taylor committed
449
  {
450
451
    std::string ObjA = "60001 -60002 60003 -60004 60005 -60006";
    std::string ObjB = "80001 -80002 60003 -60004 60005 -60006";
452

453
    createSurfaces(ObjA);
454
    CSGObject object1 = CSGObject();
455
    object1.setObject(3, ObjA);
Russell Taylor's avatar
Russell Taylor committed
456
457
    object1.populate(SMap);

458
    createSurfaces(ObjB);
459
    CSGObject object2 = CSGObject();
460
    object2.setObject(4, ObjB);
Russell Taylor's avatar
Russell Taylor committed
461
462
    object2.populate(SMap);

463
    Track TL(Kernel::V3D(-5, 0, 0), Kernel::V3D(1, 0, 0));
Russell Taylor's avatar
Russell Taylor committed
464
465

    // CARE: This CANNOT be called twice
466
467
    TS_ASSERT(object1.interceptSurface(TL) != 0);
    TS_ASSERT(object2.interceptSurface(TL) != 0);
Russell Taylor's avatar
Russell Taylor committed
468

469
    std::vector<Link> expectedResults;
470
471
    expectedResults.emplace_back(Link(V3D(-1, 0, 0), V3D(1, 0, 0), 6, object1));
    expectedResults.emplace_back(
472
473
        Link(V3D(4.5, 0, 0), V3D(6.5, 0, 0), 11.5, object2));
    checkTrackIntercept(TL, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
474
475
476
  }

  void testTrackTwoTouchingCubes()
477
478
479
  /**
  Test a track going through an object
  */
Russell Taylor's avatar
Russell Taylor committed
480
  {
481
482
    std::string ObjA = "60001 -60002 60003 -60004 60005 -60006";
    std::string ObjB = "60002 -80002 60003 -60004 60005 -60006";
Russell Taylor's avatar
Russell Taylor committed
483

484
    createSurfaces(ObjA);
485
    CSGObject object1 = CSGObject();
486
    object1.setObject(3, ObjA);
Russell Taylor's avatar
Russell Taylor committed
487
488
    object1.populate(SMap);

489
    createSurfaces(ObjB);
490
    CSGObject object2 = CSGObject();
491
    object2.setObject(4, ObjB);
Russell Taylor's avatar
Russell Taylor committed
492
493
    object2.populate(SMap);

494
    Track TL(Kernel::V3D(-5, 0, 0), Kernel::V3D(1, 0, 0));
Russell Taylor's avatar
Russell Taylor committed
495
496

    // CARE: This CANNOT be called twice
497
498
    TS_ASSERT(object1.interceptSurface(TL) != 0);
    TS_ASSERT(object2.interceptSurface(TL) != 0);
Russell Taylor's avatar
Russell Taylor committed
499

500
    std::vector<Link> expectedResults;
501
502
    expectedResults.emplace_back(Link(V3D(-1, 0, 0), V3D(1, 0, 0), 6, object1));
    expectedResults.emplace_back(
503
        Link(V3D(1, 0, 0), V3D(6.5, 0, 0), 11.5, object2));
Russell Taylor's avatar
Russell Taylor committed
504

505
    checkTrackIntercept(TL, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
506
507
508
  }

  void testTrackCubeWithInternalSphere()
509
510
511
  /**
  Test a track going through an object
  */
Russell Taylor's avatar
Russell Taylor committed
512
  {
513
514
    std::string ObjA = "60001 -60002 60003 -60004 60005 -60006 71";
    std::string ObjB = "-71";
Russell Taylor's avatar
Russell Taylor committed
515

516
    createSurfaces(ObjA);
517
    CSGObject object1 = CSGObject();
518
    object1.setObject(3, ObjA);
Russell Taylor's avatar
Russell Taylor committed
519
520
    object1.populate(SMap);

521
    createSurfaces(ObjB);
522
    CSGObject object2 = CSGObject();
523
    object2.setObject(4, ObjB);
Russell Taylor's avatar
Russell Taylor committed
524
525
    object2.populate(SMap);

526
    Track TL(Kernel::V3D(-5, 0, 0), Kernel::V3D(1, 0, 0));
Russell Taylor's avatar
Russell Taylor committed
527
528

    // CARE: This CANNOT be called twice
529
530
    TS_ASSERT(object1.interceptSurface(TL) != 0);
    TS_ASSERT(object2.interceptSurface(TL) != 0);
Russell Taylor's avatar
Russell Taylor committed
531

532
    std::vector<Link> expectedResults;
533
    expectedResults.emplace_back(
534
        Link(V3D(-1, 0, 0), V3D(-0.8, 0, 0), 4.2, object1));
535
    expectedResults.emplace_back(
536
        Link(V3D(-0.8, 0, 0), V3D(0.8, 0, 0), 5.8, object1));
Nick Draper's avatar
Nick Draper committed
537
538
    expectedResults.emplace_back(
        Link(V3D(0.8, 0, 0), V3D(1, 0, 0), 6, object2));
539
    checkTrackIntercept(TL, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
540
541
542
  }

  void testTrack_CubePlusInternalEdgeTouchSpheres()
543
544
545
  /**
  Test a track going through an object
  */
Russell Taylor's avatar
Russell Taylor committed
546
  {
547
548
    std::string ObjA = "60001 -60002 60003 -60004 60005 -60006 72 73";
    std::string ObjB = "(-72 : -73)";
549

550
    createSurfaces(ObjA);
551
    CSGObject object1 = CSGObject();
552
    object1.setObject(3, ObjA);
Russell Taylor's avatar
Russell Taylor committed
553
554
    object1.populate(SMap);

555
    createSurfaces(ObjB);
556
    CSGObject object2 = CSGObject();
557
    object2.setObject(4, ObjB);
Russell Taylor's avatar
Russell Taylor committed
558
559
    object2.populate(SMap);

560
    Track TL(Kernel::V3D(-5, 0, 0), Kernel::V3D(1, 0, 0));
Russell Taylor's avatar
Russell Taylor committed
561
562

    // CARE: This CANNOT be called twice
563
564
    TS_ASSERT(object1.interceptSurface(TL) != 0);
    TS_ASSERT(object2.interceptSurface(TL) != 0);
Russell Taylor's avatar
Russell Taylor committed
565

566
    std::vector<Link> expectedResults;
567
    expectedResults.emplace_back(
568
        Link(V3D(-1, 0, 0), V3D(-0.4, 0, 0), 4.6, object1));
569
    expectedResults.emplace_back(
570
        Link(V3D(-0.4, 0, 0), V3D(0.2, 0, 0), 5.2, object1));
Nick Draper's avatar
Nick Draper committed
571
572
    expectedResults.emplace_back(
        Link(V3D(0.2, 0, 0), V3D(1, 0, 0), 6, object2));
573
    checkTrackIntercept(TL, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
574
575
576
  }

  void testTrack_CubePlusInternalEdgeTouchSpheresMiss()
577
578
579
  /**
  Test a track missing an object
  */
Russell Taylor's avatar
Russell Taylor committed
580
  {
581
582
    std::string ObjA = "60001 -60002 60003 -60004 60005 -60006 72 73";
    std::string ObjB = "(-72 : -73)";
583

584
    createSurfaces(ObjA);
585
    CSGObject object1 = CSGObject();
586
    object1.setObject(3, ObjA);
Russell Taylor's avatar
Russell Taylor committed
587
588
    object1.populate(SMap);

589
    createSurfaces(ObjB);
590
    CSGObject object2 = CSGObject();
591
    object2.setObject(4, ObjB);
Russell Taylor's avatar
Russell Taylor committed
592
593
    object2.populate(SMap);

594
    Track TL(Kernel::V3D(-5, 0, 0), Kernel::V3D(0, 1, 0));
Russell Taylor's avatar
Russell Taylor committed
595
596

    // CARE: This CANNOT be called twice
597
598
    TS_ASSERT_EQUALS(object1.interceptSurface(TL), 0);
    TS_ASSERT_EQUALS(object2.interceptSurface(TL), 0);
Russell Taylor's avatar
Russell Taylor committed
599

600
601
    std::vector<Link> expectedResults; // left empty as this should miss
    checkTrackIntercept(TL, expectedResults);
Russell Taylor's avatar
Russell Taylor committed
602
603
  }

604
  void testComplementWithTwoPrimitives() {
605
    auto shell_ptr = ComponentCreationHelper::createHollowShell(0.5, 1.0);
606
    auto shell = dynamic_cast<CSGObject *>(shell_ptr.get());
607
608
609
610
611
612
613
614
615
616

    TS_ASSERT_EQUALS(2, shell->getSurfaceIndex().size());

    // Are the rules correct?
    const Rule *headRule = shell->topRule();
    TS_ASSERT_EQUALS("Intersection", headRule->className());
    const Rule *leaf1 = headRule->leaf(0);
    TS_ASSERT_EQUALS("SurfPoint", leaf1->className());
    auto surfPt1 = dynamic_cast<const SurfPoint *>(leaf1);
    TS_ASSERT(surfPt1);
617
    TS_ASSERT_EQUALS(2, surfPt1->getKeyN());
618
619
620
621
622
623
624
625
626
627
    auto outer = dynamic_cast<const Sphere *>(surfPt1->getKey());
    TS_ASSERT(outer);
    TS_ASSERT_DELTA(1.0, outer->getRadius(), 1e-10);

    const Rule *leaf2 = headRule->leaf(1);
    TS_ASSERT_EQUALS("CompGrp", leaf2->className());
    auto compRule = dynamic_cast<const CompGrp *>(leaf2);
    TS_ASSERT(compRule);
    TS_ASSERT_EQUALS("SurfPoint", compRule->leaf(0)->className());
    auto surfPt2 = dynamic_cast<const SurfPoint *>(compRule->leaf(0));
628
    TS_ASSERT_EQUALS(1, surfPt2->getKeyN());
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
    auto inner = dynamic_cast<const Sphere *>(surfPt2->getKey());
    TS_ASSERT(inner);
    TS_ASSERT_DELTA(0.5, inner->getRadius(), 1e-10);

    TS_ASSERT_EQUALS(false, shell->isValid(V3D(0, 0, 0)));

    Track p1(V3D(-2, 0, 0), V3D(1, 0, 0));
    int nsegments = shell->interceptSurface(p1);
    TS_ASSERT_EQUALS(2, nsegments);
    // total traversed distance -> 2*(r2-r1)
    double distanceInside(0.0);
    std::for_each(p1.cbegin(), p1.cend(),
                  [&distanceInside](const Link &segment) {
                    distanceInside += segment.distInsideObject;
                  });
    TS_ASSERT_DELTA(1.0, distanceInside, 1e-10);
  }

Russell Taylor's avatar
Russell Taylor committed
647
  void testFindPointInCube()
648
649
650
  /**
  Test find point in cube
  */
Russell Taylor's avatar
Russell Taylor committed
651
  {
652
    boost::shared_ptr<CSGObject> geom_obj = createUnitCube();
Russell Taylor's avatar
Russell Taylor committed
653
    // initial guess in object
654
    Kernel::V3D pt;
655
656
    TS_ASSERT_EQUALS(geom_obj->getPointInObject(pt), 1);
    TS_ASSERT_EQUALS(pt, V3D(0, 0, 0));
Russell Taylor's avatar
Russell Taylor committed
657
    // initial guess not in object, but on x-axis
658
659
    std::vector<std::string> planes{"px 10",  "px 11",   "py -0.5",
                                    "py 0.5", "pz -0.5", "pz 0.5"};
660
    boost::shared_ptr<CSGObject> B = createCuboid(planes);
661
662
    TS_ASSERT_EQUALS(B->getPointInObject(pt), 1);
    TS_ASSERT_EQUALS(pt, V3D(10, 0, 0));
Russell Taylor's avatar
Russell Taylor committed
663
    // on y axis
664
    planes = {"px -0.5", "px 0.5", "py -22", "py -21", "pz -0.5", "pz 0.5"};
665
    boost::shared_ptr<CSGObject> C = createCuboid(planes);
666
667
    TS_ASSERT_EQUALS(C->getPointInObject(pt), 1);
    TS_ASSERT_EQUALS(pt, V3D(0, -21, 0));
Russell Taylor's avatar
Russell Taylor committed
668
    // not on principle axis, now works using getBoundingBox
669
    planes = {"px 0.5", "px 1.5", "py -22", "py -21", "pz -0.5", "pz 0.5"};
670
    boost::shared_ptr<CSGObject> D = createCuboid(planes);
671
672
673
674
675
676
677
678
679
680
681
682
    TS_ASSERT_EQUALS(D->getPointInObject(pt), 1);
    TS_ASSERT_DELTA(pt.X(), 1.0, 1e-6);
    TS_ASSERT_DELTA(pt.Y(), -21.5, 1e-6);
    TS_ASSERT_DELTA(pt.Z(), 0.0, 1e-6);
    // Test non axis aligned (AA) case - getPointInObject works because the
    // object is on a principle axis
    // However, if not on a principle axis then the getBoundingBox fails to find
    // correct minima (maxima are OK)
    // This is related to use of the complement for -ve surfaces and might be
    // avoided by only using +ve surfaces
    // for defining non-AA objects. However, BoundingBox is poor for non-AA and
    // needs improvement if these are
Russell Taylor's avatar
Russell Taylor committed
683
    // common
LamarMoore's avatar
LamarMoore committed
684
685
    planes = {"p 1 0 0 -0.5",
              "p 1 0 0 0.5",
686
687
688
689
              "p 0 .70710678118 .70710678118 -1.1",
              "p 0 .70710678118 .70710678118 -0.1",
              "p 0 -.70710678118 .70710678118 -0.5",
              "p 0 -.70710678118 .70710678118 0.5"};
690
    boost::shared_ptr<CSGObject> E = createCuboid(planes);
691
692
693
694
    TS_ASSERT_EQUALS(E->getPointInObject(pt), 1);
    TS_ASSERT_DELTA(pt.X(), 0.0, 1e-6);
    TS_ASSERT_DELTA(pt.Y(), -0.1414213562373, 1e-6);
    TS_ASSERT_DELTA(pt.Z(), 0.0, 1e-6);
695
696
697
    // This test used to fail to find a point in object, as object not on a
    // principle axis and getBoundingBox did not give a useful result in this
    // case. Framework has now been updated to support this automatically.
698
    // Object is unit cube located at +-0.5 in x but centred on z=y=-1.606.. and
699
    // rotated 45deg to these two axes
LamarMoore's avatar
LamarMoore committed
700
701
    planes = {"p 1 0 0 -0.5",
              "p 1 0 0 0.5",
702
703
704
705
              "p 0  .70710678118 .70710678118 -2",
              "p 0  .70710678118 .70710678118 -1",
              "p 0 -.70710678118 .70710678118 -0.5",
              "p 0 -.70710678118 .70710678118 0.5"};
706
    boost::shared_ptr<CSGObject> F = createCuboid(planes);
707
    TS_ASSERT_EQUALS(F->getPointInObject(pt), 1); // This now succeeds
708
709
    // Test use of defineBoundingBox to explictly set the bounding box, when the
    // automatic method fails
710
711
712
    F->defineBoundingBox(0.5, -0.5 * M_SQRT1_2, -0.5 * M_SQRT1_2, -0.5,
                         -M_SQRT2 - 0.5 * M_SQRT1_2,
                         -M_SQRT2 - 0.5 * M_SQRT1_2);
713
    TS_ASSERT_EQUALS(F->getPointInObject(pt), 1);
714
    auto S = ComponentCreationHelper::createSphere(4.1);
715
716
    TS_ASSERT_EQUALS(S->getPointInObject(pt), 1);
    TS_ASSERT_EQUALS(pt, V3D(0.0, 0.0, 0));
Russell Taylor's avatar
Russell Taylor committed
717
718
  }

719
720
721
722
723
724
725
726
727
728
729
730
731
  void testGeneratePointInside() {
    using namespace ::testing;

    // Generate "random" sequence
    MockRNG rng;
    Sequence rand;
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(0.55));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(0.65));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(0.70));

    // inner radius=0.5, outer=1. Random sequence set up so as to give point
    // inside hole
    auto shell = ComponentCreationHelper::createHollowShell(0.5, 1.0);
732
    constexpr size_t maxAttempts{1};
733
    V3D point;
734
735
    TS_ASSERT_EQUALS(shell->generatePointInObject(rng, maxAttempts, point),
                     true);
736

737
    constexpr double tolerance{1e-10};
738
739
740
741
742
    TS_ASSERT_DELTA(-1. + 2. * 0.55, point.X(), tolerance);
    TS_ASSERT_DELTA(-1. + 2. * 0.65, point.Y(), tolerance);
    TS_ASSERT_DELTA(-1. + 2. * 0.70, point.Z(), tolerance);
  }

743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
  void testGeneratePointInsideCuboid() {
    using namespace ::testing;

    // Generate "random" sequence
    MockRNG rng;
    Sequence rand;
    constexpr double randX{0.55};
    constexpr double randY{0.65};
    constexpr double randZ{0.70};
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randZ));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randX));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randY));

    constexpr double xLength{0.3};
    constexpr double yLength{0.5};
    constexpr double zLength{0.2};
    auto cuboid =
        ComponentCreationHelper::createCuboid(xLength, yLength, zLength);
    constexpr size_t maxAttempts{0};
    V3D point;
763
764
    TS_ASSERT_EQUALS(cuboid->generatePointInObject(rng, maxAttempts, point),
                     true);
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796

    constexpr double tolerance{1e-10};
    TS_ASSERT_DELTA(xLength - randX * 2. * xLength, point.X(), tolerance);
    TS_ASSERT_DELTA(-yLength + randY * 2. * yLength, point.Y(), tolerance);
    TS_ASSERT_DELTA(-zLength + randZ * 2. * zLength, point.Z(), tolerance);
  }

  void testGeneratePointInsideCylinder() {
    using namespace ::testing;

    // Generate "random" sequence
    MockRNG rng;
    Sequence rand;
    constexpr double randT{0.65};
    constexpr double randR{0.55};
    constexpr double randZ{0.70};
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randT));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randR));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randZ));

    constexpr double radius{0.3};
    constexpr double height{0.5};
    const V3D axis{0., 0., 1.};
    const V3D bottomCentre{
        -1.,
        2.,
        -3.,
    };
    auto cylinder = ComponentCreationHelper::createCappedCylinder(
        radius, height, bottomCentre, axis, "cyl");
    constexpr size_t maxAttempts{0};
    V3D point;
797
798
    TS_ASSERT_EQUALS(cylinder->generatePointInObject(rng, maxAttempts, point),
                     true);
799
800
801
802
803
804
805
806
807
808
809
    // Global->cylinder local coordinates
    point -= bottomCentre;
    constexpr double tolerance{1e-10};
    const double polarAngle{2. * M_PI * randT};
    const double radialLength{radius * std::sqrt(randR)};
    const double axisLength{height * randZ};
    TS_ASSERT_DELTA(radialLength * std::cos(polarAngle), point.X(), tolerance);
    TS_ASSERT_DELTA(radialLength * std::sin(polarAngle), point.Y(), tolerance);
    TS_ASSERT_DELTA(axisLength, point.Z(), tolerance);
  }

810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
  void testGeneratePointInsideHollowCylinder() {
    using namespace ::testing;

    // Generate "random" sequence
    MockRNG rng;
    Sequence rand;
    constexpr double randT{0.65};
    constexpr double randR{0.55};
    constexpr double randZ{0.70};
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randT));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randR));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randZ));

    constexpr double innerRadius{0.29};
    constexpr double radius{0.3};
    constexpr double height{0.5};
    const V3D axis{0., 0., 1.};
    const V3D bottomCentre{
        -1.,
        2.,
        -3.,
    };
    auto hollowCylinder = ComponentCreationHelper::createHollowCylinder(
        innerRadius, radius, height, bottomCentre, axis, "hol-cyl");
    constexpr size_t maxAttempts{0};
    V3D point;
836
837
    TS_ASSERT_EQUALS(
        hollowCylinder->generatePointInObject(rng, maxAttempts, point), true);
838
839
840
841
842
843
844
845
846
847
848
849
850
    // Global->cylinder local coordinates
    point -= bottomCentre;
    constexpr double tolerance{1e-10};
    const double polarAngle{2. * M_PI * randT};
    const double c1 = std::pow(innerRadius, 2);
    const double c2 = std::pow(radius, 2);
    const double radialLength{std::sqrt(c1 + (c2 - c1) * randR)};
    const double axisLength{height * randZ};
    TS_ASSERT_DELTA(radialLength * std::cos(polarAngle), point.X(), tolerance);
    TS_ASSERT_DELTA(radialLength * std::sin(polarAngle), point.Y(), tolerance);
    TS_ASSERT_DELTA(axisLength, point.Z(), tolerance);
  }

851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
  void testGeneratePointInsideSphere() {
    using namespace ::testing;

    // Generate "random" sequence
    MockRNG rng;
    Sequence rand;
    constexpr double randT{0.65};
    constexpr double randF{0.55};
    constexpr double randR{0.70};
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randT));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randF));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randR));

    constexpr double radius{0.23};
    auto sphere = ComponentCreationHelper::createSphere(radius);
    constexpr size_t maxAttempts{0};
    V3D point;
868
869
    TS_ASSERT_EQUALS(sphere->generatePointInObject(rng, maxAttempts, point),
                     true);
870
871
872
873
874
875
876
877
878
879
880
881
    // Global->cylinder local coordinates
    constexpr double tolerance{1e-10};
    const double azimuthalAngle{2. * M_PI * randT};
    const double polarAngle{std::acos(2. * randF - 1.)};
    const double r{radius * randR};
    TS_ASSERT_DELTA(r * std::cos(azimuthalAngle) * std::sin(polarAngle),
                    point.X(), tolerance);
    TS_ASSERT_DELTA(r * std::sin(azimuthalAngle) * std::sin(polarAngle),
                    point.Y(), tolerance);
    TS_ASSERT_DELTA(r * std::cos(polarAngle), point.Z(), tolerance);
  }

882
883
884
885
886
887
888
889
890
891
892
893
894
  void testGeneratePointInsideRespectsMaxAttempts() {
    using namespace ::testing;

    // Generate "random" sequence
    MockRNG rng;
    Sequence rand;
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(0.1));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(0.2));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(0.3));

    // inner radius=0.5, outer=1. Random sequence set up so as to give point
    // inside hole
    auto shell = ComponentCreationHelper::createHollowShell(0.5, 1.0);
895
    constexpr size_t maxAttempts{1};
896
897
898
    V3D point;
    TS_ASSERT_EQUALS(shell->generatePointInObject(rng, maxAttempts, point),
                     false);
899
900
901
902
903
904
905
906
  }

  void testGeneratePointInsideRespectsActiveRegion() {
    using namespace ::testing;

    // Generate "random" sequence.
    MockRNG rng;
    Sequence rand;
907
908
909
910
911
912
913
914
915
    constexpr double randX{0.92};
    constexpr double randY{0.14};
    constexpr double randZ{0.83};
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randX));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randY));
    EXPECT_CALL(rng, nextValue()).InSequence(rand).WillOnce(Return(randZ));

    constexpr double halfWidth{0.75};
    auto ball = ComponentCreationHelper::createCuboid(halfWidth);
916
917
    // Create a thin infinite rectangular region to restrict point generation
    BoundingBox activeRegion(0.1, 0.1, 0.1, -0.1, -0.1, -0.1);
918
    constexpr size_t maxAttempts{1};
919
    V3D point;
920
921
922
    TS_ASSERT_EQUALS(
        ball->generatePointInObject(rng, activeRegion, maxAttempts, point),
        true);
923
924
925
926
927
    // We should get the point generated from the second 'random' triplet.
    constexpr double tolerance{1e-10};
    TS_ASSERT_DELTA(-0.1 + randX * 0.2, point.X(), tolerance)
    TS_ASSERT_DELTA(-0.1 + randY * 0.2, point.Y(), tolerance)
    TS_ASSERT_DELTA(-0.1 + randZ * 0.2, point.Z(), tolerance)
928
929
  }