Allolib  1.0
C++ Components For Interactive Multimedia
al_HashSpace.hpp
1 #ifndef INCLUDE_AL_HASHSPACE_HPP
2 #define INCLUDE_AL_HASHSPACE_HPP
3 
4 #include <algorithm>
5 #include <vector>
6 
7 #include "al/math/al_Vec.hpp"
8 
9 /* Allocore --
10  Multimedia / virtual environment application class library
11 
12  Copyright (C) 2009. AlloSphere Research Group, Media Arts & Technology, UCSB.
13  Copyright (C) 2012. The Regents of the University of California.
14  All rights reserved.
15 
16  Redistribution and use in source and binary forms, with or without
17  modification, are permitted provided that the following conditions are met:
18 
19  Redistributions of source code must retain the above copyright notice,
20  this list of conditions and the following disclaimer.
21 
22  Redistributions in binary form must reproduce the above copyright
23  notice, this list of conditions and the following disclaimer in the
24  documentation and/or other materials provided with the distribution.
25 
26  Neither the name of the University of California nor the names of its
27  contributors may be used to endorse or promote products derived from
28  this software without specific prior written permission.
29 
30  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
31  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
34  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
35  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
36  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
37  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
38  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
40  POSSIBILITY OF SUCH DAMAGE.
41 
42 
43  File description:
44  HashSpace is a way to detect object collisions using a voxel grid
45  The grid has a given resolution (no. voxel cells per side)
46 
47  It is optimized for densely packed points and querying for nearest neighbors
48  within given radii (results will be roughly sorted by distance).
49 
50  TODO: non-toroidal options
51  TODO: have query() automatically (insertion) sort results by distance
52  (perhaps use std::set instead of vector?)
53 
54  File author(s):
55  Wesley Smith, 2010, wesley.hoke@gmail.com
56  Graham Wakefield, 2011, grrrwaaa@gmail.com
57 
58  Inspired by this paper:
59  http://nicolas.brodu.numerimoire.net/common/recherche/publications/QuerySphereIndexing.pdf
60 */
61 
62 namespace al {
63 
64 #include <climits>
65 
76 class HashSpace {
77 public:
79  struct Object {
80  Object() : hash(invalidHash()), next(nullptr), prev(nullptr), userdata(0) {}
81 
82  Vec3d pos;
83  uint32_t hash;
84  Object *next, *prev;
85  union {
86  uint32_t id;
87  void *userdata;
88  };
89  };
90 
92  struct Voxel {
93  Voxel() : mObjects(NULL) {}
94  Voxel(const Voxel &cpy) : mObjects(NULL) {}
95 
97  inline void add(Object *o);
98 
100  inline void remove(Object *o);
101 
104  };
105 
121  struct Query {
122  struct Result {
123  HashSpace::Object *object;
124  double distanceSquared;
125 
126  static bool compare(const Result &x, const Result &y) {
127  return x.distanceSquared > y.distanceSquared;
128  }
129 
130  Result() : object(0), distanceSquared(0) {}
131  Result(const Result &cpy)
132  : object(cpy.object), distanceSquared(cpy.distanceSquared) {}
133  };
134 
135  typedef std::vector<Result> Results;
136  typedef Results::iterator Iterator;
137 
142  Query(uint32_t maxResults = 128) : mMaxResults(maxResults) {
143  mObjects.reserve(maxResults);
144  }
145 
159  int operator()(const HashSpace &space, const Vec3d center, double maxRadius,
160  double minRadius = 0.);
161  int operator()(const HashSpace &space, const Object *obj, double maxRadius,
162  double minRadius = 0.);
163 
172  int operator()(const HashSpace &space, Vec3d center);
173  int operator()(const HashSpace &space, const Object *obj);
174 
178  Object *nearest(const HashSpace &space, const Object *obj);
179 
181  unsigned size() const { return mObjects.size(); }
183  Object *operator[](unsigned i) const { return mObjects[i].object; }
184  double distanceSquared(unsigned i) const {
185  return mObjects[i].distanceSquared;
186  }
187  double distance(unsigned i) const { return sqrt(distanceSquared(i)); }
188 
195  mObjects.clear();
196  return *this;
197  }
198 
200  Query &maxResults(uint32_t i) {
201  mMaxResults = i;
202  return *this;
203  }
205  uint32_t maxResults() const { return mMaxResults; }
206 
208  Iterator begin() { return mObjects.begin(); }
209  Iterator end() { return mObjects.end(); }
210  Results &results() { return mObjects; }
211 
212  protected:
213  uint32_t mMaxResults;
214  Results mObjects;
215  };
216 
229  HashSpace(uint32_t resolution = 5, uint32_t numObjects = 0);
230 
231  ~HashSpace();
232 
234  uint32_t dim() const { return mDim; }
236  uint32_t maxRadius() const { return mDimHalf; }
237 
239  void numObjects(int numObjects);
240  uint32_t numObjects() const { return mObjects.size(); }
241 
243  Object &object(uint32_t i) { return mObjects[i]; }
244 
246  HashSpace &move(uint32_t objectId, double x, double y, double z) {
247  return move(objectId, Vec3d(x, y, z));
248  }
249  template <typename T> HashSpace &move(uint32_t objectId, Vec<3, T> pos);
250 
253  HashSpace &remove(uint32_t objectId);
254 
256  double wrap(double x) const { return wrap(x, dim()); }
257  template <typename T> Vec<3, T> wrap(Vec<3, T> v) const {
258  return Vec<3, T>(wrap(v.x), wrap(v.y), wrap(v.z));
259  }
260 
264  double wrapRelative(double x) const { return wrap(x, maxRadius()); }
265  template <typename T> Vec<3, T> wrapRelative(Vec<3, T> v) const {
266  return wrap(v + maxRadius()) - maxRadius();
267  }
268 
270  static uint32_t invalidHash() { return UINT_MAX; }
271 
272 protected:
273  // integer distance squared
274  uint32_t distanceSquared(double a1, double a2, double a3) const;
275 
276  // convert x,y,z in range [0..DIM) to unsigned hash:
277  // this is also the valid mVoxels index for the corresponding voxel:
278  inline uint32_t hash(unsigned x, unsigned y, unsigned z) const {
279  return hashx(x) + hashy(y) + hashz(z);
280  }
281  template <typename T> inline uint32_t hash(Vec<3, T> v) const {
282  return hash(v[0], v[1], v[2]);
283  }
284  // generate hash offset by an already generated hash:
285  inline uint32_t hash(uint32_t x, uint32_t y, uint32_t z,
286  uint32_t offset) const {
287  return hashx(unhashx(offset) + x) + hashy(unhashy(offset) + y) +
288  hashz(unhashz(offset) + z);
289  }
290  template <typename T>
291  inline uint32_t hash(Vec<3, T> v, uint32_t offset) const {
292  return hash(v[0], v[1], v[2], offset);
293  }
294 
295  inline uint32_t hashx(uint32_t v) const { return v & mWrap; }
296  inline uint32_t hashy(uint32_t v) const { return (v & mWrap) << mShift; }
297  inline uint32_t hashz(uint32_t v) const { return (v & mWrap) << mShift2; }
298 
299  // convert unsigned hash to x,y,z in range [0..mDim):
300  inline Vec3i unhash(uint32_t h) const {
301  return Vec3i(unhashx(h), unhashy(h), unhashz(h));
302  }
303 
304  inline uint32_t unhashx(uint32_t h) const { return (h)&mWrap; }
305  inline uint32_t unhashy(uint32_t h) const { return (h >> mShift) & mWrap; }
306  inline uint32_t unhashz(uint32_t h) const { return (h >> mShift2) & mWrap; }
307 
308  // safe floating-point wrapping
309  static double wrap(double x, double mod);
310  static double wrap(double x, double lo, double hi);
311 
312  uint32_t mShift, mShift2, mDim, mDim2, mDim3, mWrap, mWrap3;
313  int mDimHalf; // the valid maximum radius for queries
314  uint32_t mMaxD2, mMaxHalfD2;
315 
317  std::vector<Object> mObjects;
318 
320  std::vector<Voxel> mVoxels;
321 
323  std::vector<uint32_t> mVoxelIndices;
325  std::vector<uint32_t> mDistanceToVoxelIndices;
326  std::vector<uint32_t> mVoxelIndicesToDistance;
327 };
328 
329 // this is definitely not thread-safe.
331  if (mObjects) {
332  // add to tail:
333  Object *last = mObjects->prev;
334  last->next = o;
335  o->prev = last;
336  o->next = mObjects;
337  mObjects->prev = o;
338  } else {
339  // unique:
340  mObjects = o->prev = o->next = o;
341  }
342 }
343 
344 // this is definitely not thread-safe.
346  if (o == o->prev) { // voxel only has 1 item
347  mObjects = NULL;
348  } else {
349  Object *prev = o->prev;
350  Object *next = o->next;
351  prev->next = next;
352  next->prev = prev;
353  // update head pointer?
354  if (mObjects == o) {
355  mObjects = next;
356  }
357  }
358  // leave the object clean:
359  o->prev = o->next = NULL;
360 }
361 
362 inline int HashSpace::Query ::operator()(const HashSpace &space, Vec3d center) {
363  return (*this)(space, center, space.maxRadius());
364 }
365 
366 inline int HashSpace::Query ::operator()(const HashSpace &space,
367  const Object *obj) {
368  return (*this)(space, obj, space.maxRadius());
369 }
370 
371 // the maximum permissible value of radius is mDimHalf
372 // if int(inner^2) == int(outer^2), only 1 shell will be queried.
373 // TODO: non-toroidal version.
374 inline int HashSpace::Query ::operator()(const HashSpace &space, Vec3d center,
375  double maxRadius, double minRadius) {
376  unsigned nres = 0;
377  double minr2 = minRadius * minRadius;
378  double maxr2 = maxRadius * maxRadius;
379  uint32_t iminr2 = std::max(uint32_t(0), uint32_t(minRadius * minRadius));
380  uint32_t imaxr2 = std::min(space.mMaxHalfD2,
381  uint32_t(1 + (maxRadius + 1) * (maxRadius + 1)));
382  if (iminr2 < imaxr2) {
383  uint32_t cellstart = space.mDistanceToVoxelIndices[iminr2];
384  uint32_t cellend = space.mDistanceToVoxelIndices[imaxr2];
385  for (uint32_t i = cellstart; i < cellend; i++) {
386  uint32_t index = space.hash(center, space.mVoxelIndices[i]);
387  const Voxel &voxel = space.mVoxels[index];
388  // now add any objects in this voxel to the result...
389  Object *head = voxel.mObjects;
390  if (head) {
391  Object *o = head;
392  do {
393  // final check - float version:
394  Vec3d rel = space.wrapRelative(o->pos - center);
395  double d2 = rel.magSqr();
396  if (d2 >= minr2 && d2 <= maxr2) {
397  Result r;
398  r.object = o;
399  r.distanceSquared = d2;
400  mObjects.push_back(r);
401  nres++;
402  }
403  o = o->next;
404  } while (o != head && nres < mMaxResults);
405  }
406  if (nres == mMaxResults) {
407  break;
408  }
409  }
410  }
411  // std::sort(mObjects.begin(), mObjects.end(), Result::compare);
412  return nres;
413 }
414 
415 // the maximum permissible value of radius is mDimHalf
416 // if int(inner^2) == int(outer^2), only 1 shell will be queried.
417 // TODO: non-toroidal version.
418 inline int HashSpace::Query ::operator()(const HashSpace &space,
419  const HashSpace::Object *obj,
420  double maxRadius, double minRadius) {
421  unsigned nres = 0;
422  const Vec3d &center = obj->pos;
423  double minr2 = minRadius * minRadius;
424  double maxr2 = maxRadius * maxRadius;
425  uint32_t iminr2 = std::max(uint32_t(0), uint32_t(minRadius * minRadius));
426  uint32_t imaxr2 = std::min(space.mMaxHalfD2,
427  uint32_t(1 + (maxRadius + 1) * (maxRadius + 1)));
428  if (iminr2 < imaxr2) {
429  uint32_t cellstart = space.mDistanceToVoxelIndices[iminr2];
430  uint32_t cellend = space.mDistanceToVoxelIndices[imaxr2];
431  for (uint32_t i = cellstart; i < cellend; i++) {
432  uint32_t index = space.hash(center, space.mVoxelIndices[i]);
433  const Voxel &voxel = space.mVoxels[index];
434  // now add any objects in this voxel to the result...
435  Object *head = voxel.mObjects;
436  if (head) {
437  Object *o = head;
438  do {
439  if (o != obj) {
440  // final check - float version:
441  Vec3d rel = space.wrapRelative(o->pos - center);
442  double d2 = rel.magSqr();
443  if (d2 >= minr2 && d2 <= maxr2) {
444  // here we could insert-sort based on distance...
445  Result r;
446  r.object = o;
447  r.distanceSquared = d2;
448  mObjects.push_back(r);
449  nres++;
450  }
451  }
452  o = o->next;
453  } while (o != head && nres < mMaxResults);
454  }
455  if (nres == mMaxResults)
456  break;
457  }
458  }
459  // std::sort(mObjects.begin(), mObjects.end(), Result::compare);
460  return nres;
461 }
462 
463 // of the matches, return the best:
465  const Object *src) {
466  clear();
467  const Vec3d &center = src->pos;
468  uint32_t results = (*this)(space, src, space.mMaxHalfD2);
469 
470  Object *result = 0;
471  double rd2 = space.mMaxHalfD2;
472  for (uint32_t i = 0; i < results; i++) {
473  Object *o = mObjects[i].object;
474  Vec3d rel = space.wrapRelative(o->pos - center);
475  double d2 = rel.magSqr();
476  if (d2 < rd2) {
477  rd2 = d2;
478  result = o;
479  }
480  }
481  return result;
482 }
483 
484 inline void HashSpace ::numObjects(int numObjects) {
485  mObjects.clear();
486  mObjects.resize(numObjects);
487  // clear all voxels:
488  for (unsigned i = 0; i < mVoxels.size(); i++) {
489  mVoxels[i].mObjects = 0;
490  }
491 }
492 
493 template <typename T>
494 inline HashSpace &HashSpace ::move(uint32_t objectId, Vec<3, T> pos) {
495  Object &o = mObjects[objectId];
496  o.pos.set(wrap(pos));
497  uint32_t newhash = hash(o.pos);
498  if (newhash != o.hash) {
499  if (o.hash != invalidHash())
500  mVoxels[o.hash].remove(&o);
501  o.hash = newhash;
502  mVoxels[newhash].add(&o);
503  }
504  return *this;
505 }
506 
507 inline HashSpace &HashSpace ::remove(uint32_t objectId) {
508  Object &o = mObjects[objectId];
509  if (o.hash != invalidHash())
510  mVoxels[o.hash].remove(&o);
511  o.hash = invalidHash();
512  return *this;
513 }
514 
515 // integer distance squared
516 inline uint32_t HashSpace ::distanceSquared(double x, double y,
517  double z) const {
518  return x * x + y * y + z * z;
519 }
520 
521 // wrapping floating point numbers is surprisingly complex
522 // because of rounding errors, behavior when near zero
523 // and other oddities
524 inline double HashSpace ::wrap(double x, double mod) {
525  if (mod) {
526  if (x > mod) {
527  // shift down
528  if (x > (mod * 2.)) {
529  // multiple wraps:
530  double div = x / mod;
531  // get fract:
532  double divl = (long)div;
533  double fract = div - (double)divl;
534  return fract * mod;
535  } else {
536  // single wrap:
537  return x - mod;
538  }
539  } else if (x < 0.) {
540  // negative x, shift up
541  if (x < -mod) {
542  // multiple wraps:
543  double div = x / mod;
544  // get fract:
545  double divl = (long)div;
546  double fract = div - (double)divl;
547  double x1 = fract * mod;
548  return (x1 < 0.) ? x1 + mod : x1;
549  } else {
550  // single wrap:
551  return x + mod;
552  }
553  } else {
554  return x;
555  }
556  } else {
557  return 0.; // avoid divide by zero
558  }
559 }
560 
561 inline double HashSpace ::wrap(double x, double lo, double hi) {
562  return lo + wrap(x - lo, hi - lo);
563 }
564 
565 } // namespace al
566 
567 #endif
The HashSpace class.
std::vector< Voxel > mVoxels
the array of voxels (indexed by hashed location)
std::vector< uint32_t > mDistanceToVoxelIndices
a baked array mapping distance to mVoxelIndices offsets
uint32_t maxRadius() const
the maximum valid radius to query (half the dimension):
HashSpace & remove(uint32_t objectId)
std::vector< Object > mObjects
the array of objects
Object & object(uint32_t i)
get the object at a given index:
std::vector< uint32_t > mVoxelIndices
a baked array of voxel indices sorted by distance
uint32_t dim() const
the dimension of the space per axis:
double wrapRelative(double x) const
double wrap(double x) const
wrap an absolute position within the space:
static uint32_t invalidHash()
an invalid voxel index used to indicate non-membership
HashSpace(uint32_t resolution=5, uint32_t numObjects=0)
HashSpace & move(uint32_t objectId, double x, double y, double z)
set the position of an object:
T magSqr() const
Returns magnitude squared.
Definition: al_Vec.hpp:418
T min(const T &v1, const T &v2, const T &v3)
T max(const T &v1, const T &v2, const T &v3)
Definition: al_App.hpp:23
Vec< 3, double > Vec3d
double 3-vector
Definition: al_Vec.hpp:60
Vec< 3, int > Vec3i
integer 3-vector
Definition: al_Vec.hpp:61
container for registered spatial elements
uint32_t hash
which voxel ID it belongs to (or invalidHash())
uint32_t id
< a way to attach user-defined payloads:
Object * prev
neighbors in the same voxel
Object * operator[](unsigned i) const
get each result:
uint32_t maxResults() const
get the maximum number of desired results
Query(uint32_t maxResults=128)
Iterator begin()
std::vector interface:
Object * nearest(const HashSpace &space, const Object *obj)
unsigned size() const
get number of results:
int operator()(const HashSpace &space, const Vec3d center, double maxRadius, double minRadius=0.)
Query & maxResults(uint32_t i)
set the maximum number of desired results
each Voxel contains a linked list of Objects
void add(Object *o)
definitely not thread-safe.
void remove(Object *o)
definitely not thread-safe.
Object * mObjects
the linked list of objects in this voxel