1 /*
2 Copyright (c) 2011-2014 Timur Gafarov 
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 module dmech.bvh;
30 
31 import std.array;
32 import std.math;
33 
34 import dlib.core.compound;
35 import dlib.math.utils;
36 import dlib.math.vector;
37 import dlib.geometry.aabb;
38 import dlib.geometry.sphere;
39 import dlib.geometry.ray;
40 
41 /*
42  * Bounding Volume Hierarchy implementation
43  */
44 
45 // Returns the axis that has the largest length
46 Axis boxGetMainAxis(AABB box)
47 {
48     float xl = box.size.x;
49     float yl = box.size.y;
50     float zl = box.size.z;
51          
52     if (xl < yl)
53     {
54         if (yl < zl)
55            return Axis.z;
56         return Axis.y;
57     }
58     else if (xl < zl)
59         return Axis.z;
60     return Axis.x;        
61 }
62 
63 struct SplitPlane
64 {
65     public:
66     float split;
67     Axis axis;
68     
69     this(float s, Axis ax)
70     {
71         split = s;
72         axis = ax;
73     }
74 }
75 
76 SplitPlane boxGetSplitPlaneForAxis(AABB box, Axis a)
77 {
78     return SplitPlane(box.center[a], a);
79 }
80 
81 Compound!(AABB, AABB) boxSplitWithPlane(AABB box, SplitPlane sp)
82 {
83     Vector3f minLP = box.pmin;
84     Vector3f maxLP = box.pmax;
85     maxLP[sp.axis] = sp.split;
86     
87     Vector3f minRP = box.pmin;
88     Vector3f maxRP = box.pmax;
89     minRP[sp.axis] = sp.split;
90 
91     AABB leftB = boxFromMinMaxPoints(minLP, maxLP);
92     AABB rightB = boxFromMinMaxPoints(minRP, maxRP);
93 
94     return compound(leftB, rightB);
95 }
96 
97 AABB enclosingAABB(T)(T[] objects)
98 {
99     Vector3f pmin = objects[0].boundingBox.pmin;
100     Vector3f pmax = pmin;
101     
102     void adjustMinPoint(Vector3f p)
103     {    
104         if (p.x < pmin.x) pmin.x = p.x;
105         if (p.y < pmin.y) pmin.y = p.y;
106         if (p.z < pmin.z) pmin.z = p.z;
107     }
108     
109     void adjustMaxPoint(Vector3f p)
110     {
111         if (p.x > pmax.x) pmax.x = p.x;
112         if (p.y > pmax.y) pmax.y = p.y;
113         if (p.z > pmax.z) pmax.z = p.z;
114     }
115 
116     foreach(ref obj; objects)
117     {
118         adjustMinPoint(obj.boundingBox.pmin);
119         adjustMaxPoint(obj.boundingBox.pmax);
120     }
121     
122     return boxFromMinMaxPoints(pmin, pmax);
123 }
124 
125 class BVHNode(T)
126 {
127     T[] objects;
128     AABB aabb;
129     BVHNode[2] child;
130     uint userData;
131 
132     this(T[] objs)
133     {
134         objects = objs;
135         aabb = enclosingAABB(objects);
136     }
137 }
138 
139 void traverseBySphere(T)(BVHNode!T node, ref Sphere sphere, void delegate(ref T) func)
140 {
141     Vector3f cn;
142     float pd;
143     if (node.aabb.intersectsSphere(sphere, cn, pd))
144     {
145         if (node.child[0] !is null)
146             node.child[0].traverseBySphere(sphere, func);
147         if (node.child[1] !is null)
148             node.child[1].traverseBySphere(sphere, func);
149 
150         foreach(ref obj; node.objects)
151             func(obj);
152     }
153 }
154 
155 void traverse(T)(BVHNode!T node, void delegate(BVHNode!T) func)
156 {
157     if (node.child[0] !is null)
158         node.child[0].traverse(func);
159     if (node.child[1] !is null)
160         node.child[1].traverse(func);
161 
162     func(node);
163 }
164 
165 void traverseByRay(T)(BVHNode!T node, Ray ray, void delegate(ref T) func)
166 {
167     float it = 0.0f;
168     if (node.aabb.intersectsSegment(ray.p0, ray.p1, it))
169     {
170         if (node.child[0] !is null)
171             node.child[0].traverseByRay(ray, func);
172         if (node.child[1] !is null)
173             node.child[1].traverseByRay(ray, func);
174 
175         foreach(ref obj; node.objects)
176             func(obj);
177     }
178 }
179 
180 // TODO:
181 // - support multithreading (2 children = 2 threads)
182 // - add ESC (Early Split Clipping)
183 enum Heuristic
184 {
185     HMA, // Half Main Axis
186     SAH, // Surface Area Heuristic
187     //ESC  // Early Split Clipping
188 }
189 
190 class BVHTree(T)
191 {
192     BVHNode!T root;
193 
194     this(T[] objects, 
195          uint maxObjectsPerNode = 8,
196          Heuristic splitHeuristic = Heuristic.SAH)
197     {
198         root = construct(objects, maxObjectsPerNode, splitHeuristic);
199     }
200 
201     BVHNode!T construct(
202          T[] objects, 
203          uint maxObjectsPerNode,
204          Heuristic splitHeuristic)
205     {
206         AABB box = enclosingAABB(objects);
207         
208         SplitPlane sp;
209         if (splitHeuristic == Heuristic.HMA)
210             sp = getHalfMainAxisSplitPlane(objects, box);
211         else if (splitHeuristic == Heuristic.SAH)
212             sp = getSAHSplitPlane(objects, box);
213         else
214             assert(0, "BVH: unsupported split heuristic");
215             
216         auto boxes = boxSplitWithPlane(box, sp);
217 
218         T[] leftObjects;
219         T[] rightObjects;
220     
221         foreach(obj; objects)
222         {
223             if (boxes[0].intersectsAABB(obj.boundingBox))
224                 leftObjects ~= obj;
225             else if (boxes[1].intersectsAABB(obj.boundingBox))
226                 rightObjects ~= obj;
227         }
228     
229         BVHNode!T node = new BVHNode!T(objects);
230 
231         if (objects.length <= maxObjectsPerNode)
232             return node;
233         
234         if (leftObjects.length > 0 || rightObjects.length > 0)
235             node.objects = [];
236 
237         if (leftObjects.length > 0)
238             node.child[0] = construct(leftObjects, maxObjectsPerNode, splitHeuristic);
239         else
240             node.child[0] = null;
241     
242         if (rightObjects.length > 0)
243             node.child[1] = construct(rightObjects, maxObjectsPerNode, splitHeuristic);
244         else
245             node.child[1] = null;
246 
247         return node;    
248     }
249 
250     SplitPlane getHalfMainAxisSplitPlane(ref T[] objects, ref AABB box)
251     {
252         Axis axis = boxGetMainAxis(box);
253         return boxGetSplitPlaneForAxis(box, axis);
254     }
255 
256     SplitPlane getSAHSplitPlane(ref T[] objects, ref AABB box)
257     {
258         Axis axis = boxGetMainAxis(box);
259         
260         float minAlongSplitPlane = box.pmin[axis];
261         float maxAlongSplitPlane = box.pmax[axis];
262         
263         float bestSAHCost = float.nan;
264         float bestSplitPoint = float.nan;
265 
266         int iterations = 12;
267         foreach (i; 0..iterations)
268         {
269             float valueOfSplit = minAlongSplitPlane + 
270                                ((maxAlongSplitPlane - minAlongSplitPlane) / (iterations + 1.0f) * (i + 1.0f));
271 
272             SplitPlane SAHSplitPlane = SplitPlane(valueOfSplit, axis);
273             auto boxes = boxSplitWithPlane(box, SAHSplitPlane);
274 
275             uint leftObjectsLength = 0;
276             uint rightObjectsLength = 0;
277 
278             foreach(obj; objects)
279             {
280                 if (boxes[0].intersectsAABB(obj.boundingBox))
281                     leftObjectsLength++;
282                 else if (boxes[1].intersectsAABB(obj.boundingBox))
283                     rightObjectsLength++;
284             }
285 
286             if (leftObjectsLength > 0 && rightObjectsLength > 0)
287             {
288                 float SAHCost = getSAHCost(boxes[0], leftObjectsLength, 
289                                            boxes[1], rightObjectsLength, box);
290 
291                 if (bestSAHCost.isNaN || SAHCost < bestSAHCost)
292                 {
293                     bestSAHCost = SAHCost;
294                     bestSplitPoint = valueOfSplit;
295                 }
296             }
297         }
298         
299         return SplitPlane(bestSplitPoint, axis);
300     }
301 
302     float getSAHCost(AABB leftBox, uint numLeftObjects, 
303                      AABB rightBox, uint numRightObjects,
304                      AABB parentBox)
305     {
306         return getSurfaceArea(leftBox) / getSurfaceArea(parentBox) * numLeftObjects
307              + getSurfaceArea(rightBox) / getSurfaceArea(parentBox) * numRightObjects;
308     }
309 
310     float getSurfaceArea(AABB bbox)
311     {
312         float width = bbox.pmax.x - bbox.pmin.x;
313         float height = bbox.pmax.y - bbox.pmin.y;
314         float depth = bbox.pmax.z - bbox.pmin.z;
315         return 2.0f * (width * height + width * depth + height * depth);
316     }
317 }
318