summaryrefslogtreecommitdiff
path: root/inc/NCollection_CellFilter.hxx
blob: 6acf0f29c29d7e90541023f464921ad85d903fbb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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
164
165
166
167
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
// File:      NCollection_CellFilter.hxx
// Created:   Sat May 26 06:05:25 2007
// Author:    Andrey BETENEV
// Copyright: Open CASCADE SAS 2007

#ifndef NCollection_CellFilter_HeaderFile
#define NCollection_CellFilter_HeaderFile

#include <Standard_Real.hxx>
#include <NCollection_List.hxx>
#include <NCollection_Map.hxx>
#include <NCollection_DataMap.hxx>
#include <NCollection_BaseAllocator.hxx>

// work-around for obsolete SUN WorkShop 5.3 compiler 
// which does not recognize typename keyword
#if defined(__SUNPRO_CC) && (__SUNPRO_CC <= 0x530) && ! defined(typename)
#define typename
#endif

//! Auxiliary enumeration serving as responce from method Inspect
enum NCollection_CellFilter_Action 
{
  CellFilter_Keep  = 0, //!< Target is needed and should be kept
  CellFilter_Purge = 1  //!< Target is not needed and can be removed from the current cell
};

/**
 * A data structure for sorting geometric objects (called targets) in 
 * n-dimensional space into cells, with associated algorithm for fast checking 
 * of coincidence (overlapping, intersection, etc.) with other objects 
 * (called here bullets).
 *
 * Description
 * 
 * The algorithm is based on hash map, thus it has linear time of initialization 
 * (O(N) where N is number of cells covered by added targets) and constant-time 
 * search for one bullet (more precisely, O(M) where M is number of cells covered 
 * by the bullet).
 *
 * The idea behind the algorithm is to separate each co-ordinate of the space 
 * into equal-size cells. Note that this works well when cell size is 
 * approximately equal to the characteristic size of the involved objects 
 * (targets and bullets; including tolerance eventually used for coincidence 
 * check). 
 *
 * Usage
 *
 * The target objects to be searched are added to the tool by methods Add(); 
 * each target is classified as belonging to some cell(s). The data on cells 
 * (list of targets found in each one) are stored in the hash map with key being 
 * cumulative index of the cell by all co-ordinates.
 * Thus the time needed to find targets in some cell is O(1) * O(number of 
 * targets in the cell).
 *
 * As soon as all the targets are added, the algorithm is ready to check for 
 * coincidence.
 * To find the targets coincident with any given bullet, it checks all the 
 * candidate targets in the cell(s) covered by the bullet object 
 * (methods Inspect()).
 *
 * The methods Add() and Inspect() have two flavours each: one accepts
 * single point identifying one cell, another accept two points specifying
 * the range of cells. It should be noted that normally at least one of these
 * methods is called as for range of cells: either due to objects having non-
 * zero size, or in order to account for the tolerance when objects are points.
 *
 * The set of targets can be modified during the process: new targets can be
 * added by Add(), existing targets can be removed by Remove().
 *
 * Implementation
 *
 * The algorithm is implemented as template class, thus it is capable to 
 * work with objects of any type. The only argument of the template should be 
 * the specific class providing all necessary features required by the 
 * algorithm:
 *
 * - typedef "Target" defining type of target objects.
 *   This type must have copy constructor
 *
 * - typedef "Point" defining type of geometrical points used 
 *
 * - enum Dimension whose value must be dimension of the point
 *
 * - method Coord() returning value of the i-th coordinate of the point:
 *
 *   static Standard_Real Coord (int i, const Point& thePnt);
 *
 *   Note that index i is from 0 to Dimension-1.
 *
 * - method IsEqual() used by Remove() to identify objects to be removed:
 *
 *   Standard_Boolean IsEqual (const Target& theT1, const Target& theT2);
 *
 * - method Inspect() performing necessary actions on the candidate target 
 *   object (usially comparison with the currently checked bullet object):
 *
 *   NCollection_CellFilter_Action Inspect (const Target& theObject);
 *
 *   The returned value can be used to command CellFilter
 *   to remove the inspected item from the current cell; this allows
 *   to exclude the items that has been processed and are not needed any 
 *   more in further search (for better performance).
 *
 *   Note that method Inspect() can be const and/or virtual.
 */

template <class Inspector> 
class NCollection_CellFilter
{  
public:
  typedef typename Inspector::Target Target;
  typedef typename Inspector::Point  Point;

public:

  //! Constructor; initialized by cell size.
  //!
  //! Note: the cell size must be ensured to be greater than 
  //! maximal co-ordinate of the involved points divided by INT_MAX,
  //! in order to avoid integer overflow of cell index.
  //! 
  //! By default cell size is 0, which is invalid; thus if default
  //! constructor is used, the tool must be initialized later with
  //! appropriate cell size by call to Reset()
  NCollection_CellFilter (Standard_Real theCellSize=0,
                          const Handle(NCollection_IncAllocator)& theAlloc=0) 
  {
    Reset (theCellSize, theAlloc);
  }

  //! Constructor; initialized by cell sizes along each dimension.
  //! Note: the cell size in each dimension must be ensured to be greater than 
  //! maximal co-ordinate of the involved points by this dimension divided by INT_MAX,
  //! in order to avoid integer overflow of cell index.
  NCollection_CellFilter (Standard_Real theCellSize[],
                          const Handle(NCollection_IncAllocator)& theAlloc=0) 
  {
    Reset (theCellSize, theAlloc);
  }

  //! Clear the data structures, set new cell size and allocator
  void Reset (Standard_Real theCellSize, 
              const Handle(NCollection_IncAllocator)& theAlloc=0)
  {
    for (int i=0; i < Inspector::Dimension; i++)
      myCellSize[i] = theCellSize;    
    resetAllocator ( theAlloc );
  }

  //! Clear the data structures and set new cell sizes and allocator
  void Reset (Standard_Real theCellSize[], 
              const Handle(NCollection_IncAllocator)& theAlloc=0)
  {
    for (int i=0; i < Inspector::Dimension; i++)
      myCellSize[i] = theCellSize[i];
    resetAllocator ( theAlloc );
  }
  
  //! Adds a target object for further search at a point (into only one cell)
  void Add (const Target& theTarget, const Point &thePnt)
  {
    Cell aCell (thePnt, myCellSize);
    add (aCell, theTarget);
  }

  //! Adds a target object for further search in the range of cells 
  //! defined by two points (the first point must have all co-ordinates equal or
  //! less than the same co-ordinate of the second point)
  void Add (const Target& theTarget, 
	    const Point &thePntMin, const Point &thePntMax)
  {
    // get cells range by minimal and maximal co-ordinates
    Cell aCellMin (thePntMin, myCellSize);
    Cell aCellMax (thePntMax, myCellSize);
    Cell aCell = aCellMin;
    // add object recursively into all cells in range
    iterateAdd (Inspector::Dimension-1, aCell, aCellMin, aCellMax, theTarget);
  }

  //! Find a target object at a point and remove it from the structures.
  //! For usage of this method "operator ==" should be defined for Target.
  void Remove (const Target& theTarget, const Point &thePnt)
  {
    Cell aCell (thePnt, myCellSize);
    remove (aCell, theTarget);
  }

  //! Find a target object in the range of cells defined by two points and
  //! remove it from the structures
  //! (the first point must have all co-ordinates equal or
  //! less than the same co-ordinate of the second point).
  //! For usage of this method "operator ==" should be defined for Target.
  void Remove (const Target& theTarget, 
               const Point &thePntMin, const Point &thePntMax)
  {
    // get cells range by minimal and maximal co-ordinates
    Cell aCellMin (thePntMin, myCellSize);
    Cell aCellMax (thePntMax, myCellSize);
    Cell aCell = aCellMin;
    // remove object recursively from all cells in range
    iterateRemove (Inspector::Dimension-1, aCell, aCellMin, aCellMax, theTarget);
  }

  //! Inspect all targets in the cell corresponding to the given point
  void Inspect (const Point& thePnt, Inspector &theInspector) 
  {
    Cell aCell (thePnt, myCellSize);
    inspect (aCell, theInspector);
  }

  //! Inspect all targets in the cells range limited by two given points
  //! (the first point must have all co-ordinates equal or
  //! less than the same co-ordinate of the second point)
  void Inspect (const Point& thePntMin, const Point& thePntMax, 
                Inspector &theInspector) 
  {
    // get cells range by minimal and maximal co-ordinates
    Cell aCellMin (thePntMin, myCellSize);
    Cell aCellMax (thePntMax, myCellSize);
    Cell aCell = aCellMin;
    // inspect object recursively into all cells in range
    iterateInspect (Inspector::Dimension-1, aCell, 
                    aCellMin, aCellMax, theInspector);
  }
//Borland needs to have the Cell struct public in order to access it from
//the global functions HashCode and IsEqual ... See below on the declarations of them
#if defined(__SUNPRO_CC) && (__SUNPRO_CC <= 0x530) || defined(__BORLANDC__)
public: // work-around against obsolete SUN WorkShop 5.3 compiler
#else
protected:
#endif
 
  /**
   * Auxiliary class for storing points belonging to the cell as the list
   */
  struct ListNode {
    Target Object;
    ListNode *Next;
  };

  /**
   * Auxilary structure representing a cell in the space. 
   * Cells are stored in the map, each cell contains list of objects 
   * that belong to that cell.
   */
  struct Cell
  {
  public:
    //! Empty constructor -- required only for NCollection_Map,
    //! therefore does not initialize index (avoid cycle)
    Cell () : Objects(0) {}

    //! Constructor; computes cell indices
    Cell (const Point& thePnt, const Standard_Real theCellSize[])
      : Objects(0)
    {
      for (int i=0; i < Inspector::Dimension; i++)
      {
          Standard_Real val = (Standard_Real)(Inspector::Coord(i, thePnt) / theCellSize[i]);
          //If the value of index is greater than
          //INT_MAX it is decreased correspondingly for the value of INT_MAX. If the value
          //of index is less than INT_MIN it is increased correspondingly for the absolute
          //value of INT_MIN.
          index[i] = long((val > INT_MAX - 1) ? fmod(val, (Standard_Real) INT_MAX) 
                                               : (val < INT_MIN + 1) ? fmod(val, (Standard_Real) INT_MIN)
                                                                     : val);
      }
    }

    //! Copy constructor: ensure that list is not deleted twice
    Cell (const Cell& theOther) { (*this) = theOther; }

    //! Assignment operator: ensure that list is not deleted twice
    void operator = (const Cell& theOther)
    {
      for (int i=0; i < Inspector::Dimension; i++)
        index[i] = theOther.index[i];
      Objects = theOther.Objects;
      ((Cell&)theOther).Objects = 0;
    }

    //! Destructor; calls destructors for targets contained in the list
    ~Cell ()
    {
      for ( ListNode* aNode = Objects; aNode; aNode = aNode->Next )
        aNode->Object.~Target();
      // note that list nodes need not to be freed, since IncAllocator is used
      Objects = 0;
    }

    //! Compare cell with other one
    Standard_Boolean IsEqual (const Cell& theOther) const
    {
      for (int i=0; i < Inspector::Dimension; i++) 
        if ( index[i] != theOther.index[i] ) return Standard_False;
      return Standard_True;
    }

    //! Compute hash code
    Standard_Integer HashCode (const Standard_Integer theUpper) const
    {
      // number of bits per each dimension in the hash code
      const Standard_Size aShiftBits = (BITS(long)-1) / Inspector::Dimension;
      long aCode=0;
      for (int i=0; i < Inspector::Dimension; i++)
        aCode = ( aCode << aShiftBits ) ^ index[i];
      return (unsigned)aCode % theUpper;
    }

  public:
    long index[Inspector::Dimension];
    ListNode *Objects;
  };

  // definition of global functions is needed for map
#ifdef __BORLANDC__
  //It seems that Borland compiler does not support template friends ...
  //If one wants to use the below global functions must instantiate them outside the
  //class body.See MeshAlgo_CellFilter.hxx file
  template <class CellContainer>
  friend Standard_Integer HashCode (const CellContainer::Cell &aCell, const Standard_Integer theUpper);
  template <class CellContainer>
  friend Standard_Boolean IsEqual (const CellContainer::Cell &aCell1, const CellContainer::Cell &aCell2);
#else
  friend Standard_Integer HashCode (const Cell &aCell, const Standard_Integer theUpper)
  { return aCell.HashCode(theUpper); }
  friend Standard_Boolean IsEqual (const Cell &aCell1, const Cell &aCell2)
  { return aCell1.IsEqual(aCell2); }
#endif

protected:

  //! Reset allocator to the new one
  void resetAllocator (const Handle(NCollection_IncAllocator)& theAlloc)
  {
    if ( theAlloc.IsNull() )
      myAllocator = new NCollection_IncAllocator;
    else 
      myAllocator = theAlloc;
    myCells.Clear ( myAllocator );
  }
  
  //! Add a new target object into the specified cell
  void add (const Cell& theCell, const Target& theTarget)
  {
    // add a new cell or get reference to existing one
    Cell& aMapCell = (Cell&)myCells.Added (theCell);

    // create a new list node and add it to the beginning of the list
    ListNode* aNode = (ListNode*)myAllocator->Allocate(sizeof(ListNode));
    new (&aNode->Object) Target (theTarget);
    aNode->Next = aMapCell.Objects;
    aMapCell.Objects = aNode;
  }

  //! Internal addition function, performing iteration for adjacent cells
  //! by one dimension; called recursively to cover all dimensions
  void iterateAdd (int idim, Cell &theCell, 
		   const Cell& theCellMin, const Cell& theCellMax, 
                   const Target& theTarget)
  {
    int start = theCellMin.index[idim];
    int end   = theCellMax.index[idim];
    for (int i=start; i <= end; i++) {
      theCell.index[idim] = i;
      if ( idim ) // recurse
        iterateAdd (idim-1, theCell, theCellMin, theCellMax, theTarget);
      else // add to this cell
        add (theCell, theTarget);
    }
  }
  
  //! Remove the target object from the specified cell
  void remove (const Cell& theCell, const Target& theTarget)
  {
    // check if any objects are recorded in that cell
    if ( ! myCells.Contains (theCell) ) 
      return;

    // iterate by objects in the cell and check each
    Cell& aMapCell = (Cell&)myCells.Added (theCell);
    ListNode* aNode = aMapCell.Objects;
    ListNode* aPrev = NULL;
    while (aNode)
    {
      ListNode* aNext = aNode->Next;
      if (Inspector::IsEqual (aNode->Object, theTarget))
      {
        aNode->Object.~Target();
        (aPrev ? aPrev->Next : aMapCell.Objects) = aNext;
        // note that aNode itself need not to be freed, since IncAllocator is used
      }
      else
        aPrev = aNode;
      aNode = aNext;
    }
  }

  //! Internal removal function, performing iteration for adjacent cells
  //! by one dimension; called recursively to cover all dimensions
  void iterateRemove (int idim, Cell &theCell, 
                      const Cell& theCellMin, const Cell& theCellMax, 
                      const Target& theTarget)
  {
    int start = theCellMin.index[idim];
    int end   = theCellMax.index[idim];
    for (int i=start; i <= end; i++) {
      theCell.index[idim] = i;
      if ( idim ) // recurse
        iterateRemove (idim-1, theCell, theCellMin, theCellMax, theTarget);
      else // remove from this cell
        remove (theCell, theTarget);
    }
  }

  //! Inspect the target objects in the specified cell.
  void inspect (const Cell& theCell, Inspector& theInspector) 
  {
    // check if any objects are recorded in that cell
    if ( ! myCells.Contains (theCell) ) 
      return;

    // iterate by objects in the cell and check each
    Cell& aMapCell = (Cell&)myCells.Added (theCell);
    ListNode* aNode = aMapCell.Objects;
    ListNode* aPrev = NULL;
    while(aNode) {
      ListNode* aNext = aNode->Next;
      NCollection_CellFilter_Action anAction = 
        theInspector.Inspect (aNode->Object);
      // delete items requested to be purged
      if ( anAction == CellFilter_Purge ) {
        aNode->Object.~Target();
        (aPrev ? aPrev->Next : aMapCell.Objects) = aNext;
        // note that aNode itself need not to be freed, since IncAllocator is used
      }
      else
        aPrev = aNode;
      aNode = aNext;      
    }
  }

  //! Inspect the target objects in the specified range of the cells
  void iterateInspect (int idim, Cell &theCell, 
	               const Cell& theCellMin, const Cell& theCellMax, 
                       Inspector& theInspector) 
  {
    int start = theCellMin.index[idim];
    int end   = theCellMax.index[idim];
    for (int i=start; i <= end; i++) {
      theCell.index[idim] = i;
      if ( idim ) // recurse
        iterateInspect (idim-1, theCell, theCellMin, theCellMax, theInspector);
      else // inspect this cell
        inspect (theCell, theInspector);
    }
  }

protected:
  Handle(NCollection_BaseAllocator) myAllocator;
  NCollection_Map<Cell>             myCells;
  Standard_Real                     myCellSize [Inspector::Dimension];
};

/**
 * Base class defining part of the Inspector interface 
 * for CellFilter algorithm, working with gp_XYZ points in 3d space
 */

class gp_XYZ;
struct NCollection_CellFilter_InspectorXYZ
{
  //! Points dimension
  enum { Dimension = 3 };

  //! Points type
  typedef gp_XYZ Point;

  //! Access to co-ordinate
  static Standard_Real Coord (int i, const Point &thePnt) { return thePnt.Coord(i+1); }
  
  //! Auxiliary method to shift point by each coordinate on given value;
  //! useful for preparing a points range for Inspect with tolerance
  Point Shift (const Point& thePnt, Standard_Real theTol) const 
  { return Point (thePnt.X() + theTol, thePnt.Y() + theTol, thePnt.Z() + theTol); }
};

/**
 * Base class defining part of the Inspector interface 
 * for CellFilter algorithm, working with gp_XY points in 2d space
 */

class gp_XY;
struct NCollection_CellFilter_InspectorXY
{
  //! Points dimension
  enum { Dimension = 2 };

  //! Points type
  typedef gp_XY Point;

  //! Access to co-ordinate
  static Standard_Real Coord (int i, const Point &thePnt) { return thePnt.Coord(i+1); }

  //! Auxiliary method to shift point by each coordinate on given value;
  //! useful for preparing a points range for Inspect with tolerance
  Point Shift (const Point& thePnt, Standard_Real theTol) const 
  { return Point (thePnt.X() + theTol, thePnt.Y() + theTol); }
};

#endif