├── README.md ├── build ├── hexprism.cpp ├── images ├── giants_causeway.jpg ├── hexagon_house.jpg └── hexagonalprism.jpg └── output.txt /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | bounding volume | accepts | seconds 3 | ------------------------------------------ 4 | AABB | 29355 | 8.3969 5 | HexPrism | 28622 | 6.9214 6 | ``` 7 | 8 | Coordinate Space 9 | ---------------- 10 | 11 | For the purposes of this paper, XY define the horizontal plane, and positive Z points upwards at the sky. 12 | 13 | Hex Prism Bounding Volume 14 | ------------------------------- 15 | 16 | The [Axis-Aligned Bounding Octahedron (AABO)](http://www.github.com/bryanmcnett/aabo) is better than AABB for objects 17 | distributed fairly throughout 3D space, but most videogames have a distribution of objects that is fair in XY but not 18 | in Z. For games that are strictly 2D, axis-aligned bounding hexagons are best, but most 3D games take place on some 19 | kind of almost-2D terrain with mild verticality in Z. For this majority of games, a different data structure is a better 20 | fit than AABB or AABO. 21 | 22 | Hex Prism 23 | --------- 24 | 25 | We are talking here specifically about hexagonal prisms aligned to the Z axis, like the houses in this image. 26 | 27 | ![Hex Prism Houses](images/hexagon_house.jpg) 28 | 29 | This sort of bounding volume can be as tall and skinny as you like in Z, and also in three directions in the XY 30 | plane, for a total of four long-and-skinny directions, which is more than the three of an AABB. 31 | 32 | Whatever shape is well-bounded by an AABB, is better-bounded by a hex prism, which can even *be* an AABB if you 33 | set two of its axes to X and Y. Despite the fact that a hex prism has 33% more planes, an AABB spends 33% more 34 | energy in trivial rejection, because a hex prism almost always rejects after reading three values from memory, 35 | instead of an AABB's four. 36 | 37 | ![Hexagonal Prism](images/hexagonalprism.jpg) 38 | 39 | Like AABO, a hex prism has eight sides. Unlike AABO, it is not made of opposing tetrahedra. Instead, it is made of 40 | opposing triangular prisms whose caps are coplanar in the XY plane. Each has five sides, but we do not store a total 41 | of ten sides, because the triangular prisms share caps. Unlike with tetrahedra, it is not efficient to test 42 | opposing pairs of triangular prisms in turn, because the caps would be tested more than once. 43 | 44 | There are two better ways to look at the hex prism, than as opposing five-sided convex polyhedra: 45 | 46 | 1. It is an axis-aligned bounding hexagon in XY, plus an unrelated interval in Z. This leads to the most efficient 47 | implementation, as a hexagon-hexagon check is most likely to exclude the vastest majority of objects in a mostly-2D world. 48 | However, the three values of a triangle don't pack well into SIMD registers unless you write intrinsics. 49 | 50 | 2. Ⓐ a triangular prism that ascends from the depths to some specific point in Z, matched to Ⓑ an opposing triangular 51 | prism that descends from the sky to some specific point in Z. These are not closed shapes, but Ⓐ can play the role of 52 | terrain and Ⓑ can play the role of a thing that rests on the terrain. As each of these things has four values - three 53 | for the triangle and one for Z - it fits nicely into SIMD registers and cache, even when intrinsics aren't used. 54 | However, efficiency is lost when Z values are mixed with XY in memory. 55 | 56 | The Naive Implementation 57 | ------------------------ 58 | 59 | This implementation is fairly easy to auto-vectorize or represent with naive SIMD (a "float4 class") 60 | 61 | ``` 62 | struct UpTriangularPrism 63 | { 64 | float minA, minB, minC, minZ; 65 | }; 66 | 67 | struct DownTriangularPrism 68 | { 69 | float maxA, maxB, maxC, maxZ; 70 | }; 71 | 72 | bool Intersects(UpTriangularPrism u, DownTriangularPrism d) 73 | { 74 | return u.minA <= d.maxA 75 | && u.minB <= d.maxB 76 | && u.minC <= d.maxC 77 | && u.minZ <= d.maxZ; 78 | } 79 | ``` 80 | 81 | The intersection of an UpPrism and DownPrism is a HexagonalPrism: 82 | 83 | ``` 84 | struct HexagonalPrisms 85 | { 86 | UpTriangularPrism *up; // Triangular prism points up in Y, extends from specific Z to positive infinity 87 | DownTriangularPrism *down; // Triangular prism points down in Y, extends from specific Z to negative infinity 88 | }; 89 | 90 | bool Intersects(HexagonalPrisms world, int index, HexagonalPrism query) 91 | { 92 | return Intersects(query.up, world.down[index]) // query comes down from sky, world comes up to meet it 93 | && Intersects(world.up[index], query.down); // world comes down from sky, query comes up to meet it 94 | } 95 | ``` 96 | 97 | 98 | The novel bit here is in the order of the tests in the Intersection function. Let's look at it closely: 99 | 100 | ``` 101 | bool Intersects(HexagonalPrisms world, int index, HexagonalPrism query) 102 | { 103 | return Intersects(query.up, world.down[index]) // query comes down from sky, world comes up to meet it 104 | && Intersects(world.up[index], query.down); // world comes down from sky, query comes up to meet it 105 | } 106 | ``` 107 | 108 | The first test approximates the world object as a triangular prism that extends from some point underfoot, 109 | downwards to infinity. This describes terrain features well. Most of the time, terrain is assumed 110 | to extend downwards to infinity, in concept. The query object is approximated as a triangular prism that 111 | extends from infinitely high in the sky, downwards to its "feet." It's quite unlikely that there is anything 112 | in the sky above a player at any given moment in time, so this is also a good match. 113 | 114 | Therefore, in the case that a world object is far enough away in XY for the up and down XY triangles to not 115 | intersect, we pay for only four checks instead of an AABB's six. And, in the case where a world object *is* 116 | close enough to intersect in XY, we still pay for only four checks, as long as the query is above the terrain: 117 | flying or jumping or falling or ragdolling, or simply above it for some other reason. 118 | 119 | Only when the object is close to the query in XY, *and* when the query's "feet" are below the top of the 120 | object, do we pay for eight checks. 121 | 122 | The cost is therefore roughly the same as the XY of an AABB - four comparisons. But there are six planes perpendicular 123 | to XY, which is a tighter fit than an AABB's four planes, for when the query and world object are close enough in 124 | XY to merit it. 125 | 126 | And, intersection tests between airborne objects and terrain can fail without consulting any more than the same four 127 | comparisons. 128 | 129 | ![Giant's Causeway in Northern Ireland](images/giants_causeway.jpg) 130 | 131 | A More Efficient Implementation 132 | ------------------------------- 133 | 134 | Up to now, we have been able to introduce new bounding volumes using the conventional notation of the C struct 135 | containing floats, and see performance pretty close to what the geometry indicates, plus pretty good autovectorization 136 | on contemporary processors. But to get the most efficiency from Hexagonal Prisms, we can no longer do this. 137 | 138 | This is because a triangle has three values, which is a poor match for naive SIMD. 139 | 140 | The more efficient implementation of Hexagonal Prisms uses SIMD intrinsics, and looks more like this: 141 | 142 | ``` 143 | struct UpTriangle 144 | { 145 | floatN minA, minB, minC; 146 | }; 147 | 148 | struct DownTriangle 149 | { 150 | floatN maxA, maxB, maxC; 151 | }; 152 | 153 | struct ZInterval 154 | { 155 | floatN minZ, maxZ; 156 | }; 157 | 158 | struct HexagonalPrism 159 | { 160 | UpTriangle up; 161 | DownTriangle down; 162 | ZInterval z; 163 | }; 164 | 165 | struct HexagonalPrisms 166 | { 167 | UpTriangle *up; 168 | DownTriangle *down; 169 | ZInterval *z; 170 | }; 171 | 172 | int Intersects(UpTriangle up, DownTriangle down) 173 | { 174 | return less_equals(up.minA, down.maxA) 175 | & less_equals(up.minB, down.maxB) 176 | & less_equals(up.minC, down.maxC); 177 | } 178 | 179 | int Intersects(HexagonalPrisms world, int index, HexagonalPrism query) 180 | { 181 | int mask = 0; 182 | if(mask = Intersects(query.up, world.down[index])) // query up triangle intersects world down triangle 183 | { 184 | mask &= Intersects(world.up[index], query.down)) // world up triangle intersects query down triangle 185 | mask &= less_equals(query.z.minZ, world.z[index].maxZ)) // query's bottom intersects world's top 186 | mask &= less_equals(world.z[index].minZ, query.z.maxZ)) // world's bottom intersects query's top 187 | } 188 | return mask; 189 | } 190 | ``` 191 | 192 | This is more efficient because it reads only three values into memory for the vast majority of objects: maxA, maxB, and 193 | maxC. The remaining five values are read only in the unlikely event that the initial triangle check passes. 194 | 195 | We could attempt to never read Z into memory, unless the initial hexagon check passes. But making decisions (branching) 196 | itself consumes energy, and the first decision we made - the decision to reject objects or investigate further, based on 197 | the results of a triangle test - has a much higher payoff than any subsequent decision can. 198 | 199 | If you know your query has a far smaller DownTriangle than an UpTriangle (if, for example, it is roughly 200 | DownTriangle-shaped) you can reverse the initial triangle test like so, to get better initial culling: 201 | 202 | ``` 203 | int Intersects(HexagonalPrisms world, int index, HexagonalPrism query) 204 | { 205 | int mask = 0; 206 | if(mask = Intersects(world.up[index], query.down)) // world up triangle intersects query down triangle 207 | { 208 | mask &= Intersects(query.up, world.down[index])) // query up triangle intersects world down triangle 209 | mask &= less_equals(query.z.minZ, world.z[index].maxZ)) // query's bottom intersects world's top 210 | mask &= less_equals(world.z[index].minZ, query.z.maxZ)) // world's bottom intersects query's top 211 | } 212 | return mask; 213 | } 214 | ``` 215 | 216 | The Pragmatic Axes 217 | ------------------ 218 | 219 | As with [Axis-Aligned Bounding Octahedron (AABO)](http://www.github.com/bryanmcnett/aabo) it is possible to use the 220 | elegant axes ABC that point at the vertices of an equilateral triangle, or the pragmatic axes {A,B,C} = {X, Y, -(X+Y)}. 221 | 222 | If you use these pragmatic axes, you can convert a pre-existing AABB into a Hexagonal Prism with exactly the same 223 | box shape it always had, and which needs to compare only 5 values per hexagon, instead of 6: 224 | 225 | ``` 226 | UpTriangle up = {minX, minY, -(maxX+maxY)} 227 | DownTriangle down = {maxX, maxY, -(minX+minY)} 228 | ``` 229 | 230 | You still have the benefit of needing to read only 3 values from memory per object most of the time, which is 231 | nicer than an AABB's 4. This is reminiscent of the [7-Sided AABB](http://www.github.com/bryanmcnett/aabo) and I guess 232 | you could call it a 5-Sided AABB. 233 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | g++ hexprism.cpp -Ofast -march=native 2 | -------------------------------------------------------------------------------- /hexprism.cpp: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct Clock 9 | { 10 | const clock_t m_start; 11 | Clock() : m_start(clock()) 12 | { 13 | } 14 | float seconds() const 15 | { 16 | const clock_t end = clock(); 17 | const float seconds = ((float)(end - m_start)) / CLOCKS_PER_SEC; 18 | return seconds; 19 | } 20 | }; 21 | 22 | #define VECTOR __m128 23 | #define MASK __m128 24 | #define SIZE 4 25 | 26 | union UNION 27 | { 28 | VECTOR v; 29 | float f[SIZE]; 30 | }; 31 | 32 | float get(VECTOR v, int index) 33 | { 34 | UNION u; 35 | u.v = v; 36 | return u.f[index]; 37 | } 38 | 39 | VECTOR broadcast(VECTOR v, float value) 40 | { 41 | UNION u; 42 | u.v = v; 43 | for(int i = 0; i < SIZE; ++i) 44 | u.f[i] = value; 45 | return u.v; 46 | } 47 | 48 | VECTOR broadcast_index(VECTOR v, int index) 49 | { 50 | return broadcast(v, get(v, index)); 51 | } 52 | 53 | void set(VECTOR& v, int index, float value) 54 | { 55 | UNION u; 56 | u.v = v; 57 | u.f[index] = value; 58 | v = u.v; 59 | } 60 | 61 | uint32_t cmple_ps(float a, float b) { return a <= b ? 0xFFFFFFFF : 0; } 62 | uint32_t and_ps(uint32_t a, uint32_t b) { return a & b; } 63 | uint32_t movemask_ps(uint32_t a) { return (a >> 31) & 1; } 64 | 65 | __m128 cmple_ps(__m128 a, __m128 b) { return _mm_cmple_ps(a,b); } 66 | __m128 and_ps(__m128 a, __m128 b) { return _mm_and_ps(a,b); } 67 | uint32_t movemask_ps(__m128 a) { return _mm_movemask_ps(a); } 68 | 69 | #if 0 70 | __m256 cmple_ps(__m256 a, __m256 b) { return _mm256_cmp_ps(a,b,_CMP_LE_OQ); } 71 | __m256 and_ps(__m256 a, __m256 b) { return _mm256_and_ps(a,b); } 72 | uint32_t movemask_ps(__m256 a) { return _mm256_movemask_ps(a); } 73 | #endif 74 | 75 | struct Slab 76 | { 77 | VECTOR mini, maxi; 78 | }; 79 | 80 | struct TwoSlab 81 | { 82 | Slab x, y; 83 | }; 84 | 85 | struct AABB 86 | { 87 | TwoSlab xy; 88 | Slab z; 89 | }; 90 | 91 | struct AABBs 92 | { 93 | TwoSlab *xy; 94 | Slab *z; 95 | }; 96 | 97 | MASK Intersects(const TwoSlab a, const TwoSlab b) 98 | { 99 | MASK mask = cmple_ps(a.x.mini, b.x.maxi); 100 | mask = and_ps(mask, cmple_ps(b.x.mini, a.x.maxi)); 101 | mask = and_ps(mask, cmple_ps(a.y.mini, b.y.maxi)); 102 | mask = and_ps(mask, cmple_ps(b.y.mini, a.y.maxi)); 103 | return mask; 104 | } 105 | 106 | int Intersects(const AABBs world, const int index, const AABB query) 107 | { 108 | MASK mask = Intersects(world.xy[index], query.xy); // 4 half-spaces 109 | if(movemask_ps(mask) == 0) 110 | return 0; 111 | mask = and_ps(mask, cmple_ps(query.z.mini, world.z[index].maxi)); // 1 half-space 112 | mask = and_ps(mask, cmple_ps(world.z[index].mini, query.z.maxi)); // 1 half-space 113 | return movemask_ps(mask); 114 | }; 115 | 116 | struct TriangleUp 117 | { 118 | VECTOR minA, minB, minC; 119 | }; 120 | 121 | struct TriangleDown 122 | { 123 | VECTOR maxA, maxB, maxC; 124 | }; 125 | 126 | MASK Intersects(const TriangleUp up, const TriangleDown down) 127 | { 128 | MASK mask = cmple_ps(up.minA, down.maxA); 129 | mask = and_ps(mask, cmple_ps(up.minB, down.maxB)); 130 | mask = and_ps(mask, cmple_ps(up.minC, down.maxC)); 131 | return mask; 132 | } 133 | 134 | struct HexPrism 135 | { 136 | TriangleUp up; 137 | TriangleDown down; 138 | Slab z; 139 | }; 140 | 141 | struct HexPrisms 142 | { 143 | TriangleUp *up; 144 | TriangleDown *down; 145 | Slab *z; 146 | }; 147 | 148 | int Intersects(const HexPrisms world, const int index, const HexPrism query) 149 | { 150 | MASK mask = Intersects(world.up[index], query.down); // 3 half-spaces 151 | if(movemask_ps(mask) == 0) 152 | return 0; 153 | mask = and_ps(mask, Intersects(query.up, world.down[index])); // 3 half-spaces 154 | mask = and_ps(mask, cmple_ps(query.z.mini, world.z[index].maxi)); // 1 half-space 155 | mask = and_ps(mask, cmple_ps(world.z[index].mini, query.z.maxi)); // 1 half-space 156 | return movemask_ps(mask); 157 | }; 158 | 159 | struct float3 160 | { 161 | float x,y,z; 162 | }; 163 | 164 | float3 operator+(const float3 a, const float3 b) 165 | { 166 | float3 c = {a.x+b.x, a.y+b.y, a.z+b.z}; 167 | return c; 168 | } 169 | 170 | float dot(const float3 a, const float3 b) 171 | { 172 | return a.x*b.x + a.y*b.y + a.z*b.z; 173 | } 174 | 175 | float length(const float3 a) 176 | { 177 | return sqrtf(dot(a,a)); 178 | } 179 | 180 | float3 min(const float3 a, const float3 b) 181 | { 182 | float3 c = {std::min(a.x,b.x), std::min(a.y,b.y), std::min(a.z,b.z)}; 183 | return c; 184 | } 185 | 186 | float3 max(const float3 a, const float3 b) 187 | { 188 | float3 c = {std::max(a.x,b.x), std::max(a.y,b.y), std::max(a.z,b.z)}; 189 | return c; 190 | } 191 | 192 | struct float4 193 | { 194 | float a,b,c,d; 195 | }; 196 | 197 | float4 min(const float4 a, const float4 b) 198 | { 199 | float4 c = {std::min(a.a,b.a), std::min(a.b,b.b), std::min(a.c,b.c), std::min(a.d,b.d)}; 200 | return c; 201 | } 202 | 203 | float4 max(const float4 a, const float4 b) 204 | { 205 | float4 c = {std::max(a.a,b.a), std::max(a.b,b.b), std::max(a.c,b.c), std::max(a.d,b.d)}; 206 | return c; 207 | } 208 | 209 | float random(float lo, float hi) 210 | { 211 | const int grain = 10000; 212 | const float t = (rand() % grain) * 1.f/(grain-1); 213 | return lo + (hi - lo) * t; 214 | } 215 | 216 | struct Mesh 217 | { 218 | std::vector m_point; 219 | void Generate(int points, float radius) 220 | { 221 | m_point.resize(points); 222 | for(int p = 0; p < points; ++p) 223 | { 224 | do 225 | { 226 | m_point[p].x = random(-radius, radius) * 0.25f; 227 | m_point[p].y = random(-radius, radius) * 0.25f; 228 | m_point[p].z = random(-radius, radius); // mostly taller than wide, like in a game 229 | } while(length(m_point[p]) > radius); 230 | } 231 | } 232 | }; 233 | 234 | struct Object 235 | { 236 | Mesh *m_mesh; 237 | float3 m_position; 238 | void CalculateAABB(AABBs* aabb, int index) const 239 | { 240 | float3 mini, maxi; 241 | const float3 xyz = m_position + m_mesh->m_point[0]; 242 | mini = maxi = xyz; 243 | for(int p = 1; p < m_mesh->m_point.size(); ++p) 244 | { 245 | const float3 xyz = m_position + m_mesh->m_point[p]; 246 | mini = min(mini, xyz); 247 | maxi = max(maxi, xyz); 248 | } 249 | const int vector = index / SIZE; 250 | const int element = index % SIZE; 251 | set(aabb->xy[vector].x.mini, element, mini.x); 252 | set(aabb->xy[vector].x.maxi, element, maxi.x); 253 | set(aabb->xy[vector].y.mini, element, mini.y); 254 | set(aabb->xy[vector].y.maxi, element, maxi.y); 255 | set(aabb->z[vector].mini, element, mini.z); 256 | set(aabb->z[vector].maxi, element, maxi.z); 257 | } 258 | void CalculateHexPrism(HexPrisms* hexPrism, int index) const 259 | { 260 | const float3 xyz = m_position + m_mesh->m_point[0]; 261 | float4 abcd, mini, maxi; 262 | abcd.a = xyz.x; 263 | abcd.b = xyz.y; 264 | abcd.c = -(xyz.x + xyz.y); 265 | abcd.d = xyz.z; 266 | mini = maxi = abcd; 267 | for(int p = 1; p < m_mesh->m_point.size(); ++p) 268 | { 269 | const float3 xyz = m_position + m_mesh->m_point[p]; 270 | abcd.a = xyz.x; 271 | abcd.b = xyz.y; 272 | abcd.c = -(xyz.x + xyz.y); 273 | abcd.d = xyz.z; 274 | mini = min(mini, abcd); 275 | maxi = max(maxi, abcd); 276 | } 277 | const int vector = index / SIZE; 278 | const int element = index % SIZE; 279 | set(hexPrism->up[vector].minA, element, mini.a); 280 | set(hexPrism->up[vector].minB, element, mini.b); 281 | set(hexPrism->up[vector].minC, element, mini.c); 282 | set(hexPrism->down[vector].maxA, element, maxi.a); 283 | set(hexPrism->down[vector].maxB, element, maxi.b); 284 | set(hexPrism->down[vector].maxC, element, maxi.c); 285 | set(hexPrism->z[vector].mini, element, mini.d); 286 | set(hexPrism->z[vector].maxi, element, maxi.d); 287 | }; 288 | }; 289 | 290 | int main(int argc, char* argv[]) 291 | { 292 | const int kMeshes = 100; 293 | Mesh mesh[kMeshes]; 294 | for(int m = 0; m < kMeshes; ++m) 295 | mesh[m].Generate(50, 1.f); 296 | 297 | const int kTests = 500; 298 | 299 | const int kVectors = 10000000; 300 | const int kObjects = kVectors / SIZE; 301 | Object* objects = new Object[kObjects]; 302 | for(int o = 0; o < kObjects; ++o) 303 | { 304 | objects[o].m_mesh = &mesh[rand() % kMeshes]; 305 | objects[o].m_position.x = random(-100.f, 100.f); 306 | objects[o].m_position.y = random(-100.f, 100.f); 307 | objects[o].m_position.z = random( -1.f, 1.f); // mostly wider than flat, like in a game 308 | } 309 | 310 | AABBs aabbs; 311 | aabbs.xy = new TwoSlab[kVectors]; 312 | aabbs.z = new Slab[kVectors]; 313 | for(int a = 0; a < kObjects; ++a) 314 | objects[a].CalculateAABB(&aabbs, a); 315 | 316 | HexPrisms hexprisms; 317 | hexprisms.up = new TriangleUp[kVectors]; 318 | hexprisms.down = new TriangleDown[kVectors]; 319 | hexprisms.z = new Slab[kVectors]; 320 | for(int a = 0; a < kObjects; ++a) 321 | objects[a].CalculateHexPrism(&hexprisms, a); 322 | 323 | const char *title = "%22s | %7s | %7s\n"; 324 | 325 | printf(title, "bounding volume", "accepts", "seconds"); 326 | printf("------------------------------------------\n"); 327 | 328 | const char *format = "%22s | %7d | %3.4f\n"; 329 | 330 | { 331 | const Clock clock; 332 | int intersections = 0; 333 | for(int test = 0; test < kTests; ++test) 334 | { 335 | AABB query; 336 | query.xy.x.mini = broadcast_index(aabbs.xy[test].x.mini, 0); 337 | query.xy.x.maxi = broadcast_index(aabbs.xy[test].x.maxi, 0); 338 | query.xy.y.mini = broadcast_index(aabbs.xy[test].y.mini, 0); 339 | query.xy.y.maxi = broadcast_index(aabbs.xy[test].y.maxi, 0); 340 | query.z.mini = broadcast_index(aabbs.z[test].mini, 0); 341 | query.z.maxi = broadcast_index(aabbs.z[test].maxi, 0); 342 | for(int v = 0; v < kVectors; ++v) 343 | if(const int mask = Intersects(aabbs, v, query)) 344 | intersections += __builtin_popcount(mask); 345 | } 346 | const float seconds = clock.seconds(); 347 | 348 | printf(format, "AABB", intersections, seconds); 349 | } 350 | 351 | { 352 | const Clock clock; 353 | int intersections = 0; 354 | for(int test = 0; test < kTests; ++test) 355 | { 356 | HexPrism query; 357 | query.up.minA = broadcast_index(hexprisms.up[test].minA, 0); 358 | query.up.minB = broadcast_index(hexprisms.up[test].minB, 0); 359 | query.up.minC = broadcast_index(hexprisms.up[test].minC, 0); 360 | query.down.maxA = broadcast_index(hexprisms.down[test].maxA, 0); 361 | query.down.maxB = broadcast_index(hexprisms.down[test].maxB, 0); 362 | query.down.maxC = broadcast_index(hexprisms.down[test].maxC, 0); 363 | query.z.mini = broadcast_index(hexprisms.z[test].mini, 0); 364 | query.z.maxi = broadcast_index(hexprisms.z[test].maxi, 0); 365 | for(int v = 0; v < kVectors; ++v) 366 | if(const int mask = Intersects(hexprisms, v, query)) 367 | intersections += __builtin_popcount(mask); 368 | } 369 | const float seconds = clock.seconds(); 370 | 371 | printf(format, "HexPrism", intersections, seconds); 372 | } 373 | 374 | return 0; 375 | } 376 | -------------------------------------------------------------------------------- /images/giants_causeway.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryanmcnett/hexprism/eb18ce15314ed4f9b0ad6da48f67bfc15cb53be8/images/giants_causeway.jpg -------------------------------------------------------------------------------- /images/hexagon_house.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryanmcnett/hexprism/eb18ce15314ed4f9b0ad6da48f67bfc15cb53be8/images/hexagon_house.jpg -------------------------------------------------------------------------------- /images/hexagonalprism.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryanmcnett/hexprism/eb18ce15314ed4f9b0ad6da48f67bfc15cb53be8/images/hexagonalprism.jpg -------------------------------------------------------------------------------- /output.txt: -------------------------------------------------------------------------------- 1 | bounding volume | accepts | seconds 2 | ------------------------------------------ 3 | AABB | 29355 | 8.3969 4 | HexPrism | 28622 | 6.9214 5 | --------------------------------------------------------------------------------