-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathBVH.cs
232 lines (199 loc) · 7.52 KB
/
BVH.cs
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
// Copyright(C) David W. Jeske, 2014, and released to the public domain.
//
// Dynamic BVH (Bounding Volume Hierarchy) using incremental refit and tree-rotations
//
// initial BVH build based on: Bounding Volume Hierarchies (BVH) – A brief tutorial on what they are and how to implement them
// http://www.3dmuve.com/3dmblog/?p=182
//
// Dynamic Updates based on: "Fast, Effective BVH Updates for Animated Scenes" (Kopta, Ize, Spjut, Brunvand, David, Kensler)
// https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Util/ssBVH/docs/BVH_fast_effective_updates_for_animated_scenes.pdf
//
// see also: Space Partitioning: Octree vs. BVH
// http://thomasdiewald.com/blog/?p=1488
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
// TODO: handle merge/split when LEAF_OBJ_MAX > 1 and objects move
// TODO: add sphere traversal
namespace DataStructures
{
public enum Axis
{
X, Y, Z,
}
public delegate bool NodeTraversalTest(Bounds box);
public class BVHHelper
{
public static NodeTraversalTest RadialNodeTraversalTest(Vector3 center, float radius)
{
return (Bounds bounds) =>
{
//find the closest point inside the bounds
//Then get the difference between the point and the circle center
float deltaX = center.x - Mathf.Max(bounds.min.x, Mathf.Min(center.x, bounds.max.x));
float deltaY = center.y - Mathf.Max(bounds.min.y, Mathf.Min(center.y, bounds.max.y));
float deltaZ = center.z - Mathf.Max(bounds.min.z, Mathf.Min(center.z, bounds.max.z));
//sqr magnitude < sqr radius = inside bounds!
return (deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) < (radius * radius);
};
}
}
public class BVH<T>
{
private Material _debugRenderMaterial = null;
public BVHNode<T> rootBVH;
public IBVHNodeAdapter<T> nAda;
public readonly int LEAF_OBJ_MAX;
public int nodeCount = 0;
public int maxDepth = 0;
public HashSet<BVHNode<T>> refitNodes = new HashSet<BVHNode<T>>();
// internal functional traversal...
private void _traverse(BVHNode<T> curNode, NodeTraversalTest hitTest, List<BVHNode<T>> hitlist)
{
if (curNode == null) { return; }
if (hitTest(curNode.Box))
{
hitlist.Add(curNode);
_traverse(curNode.Left, hitTest, hitlist);
_traverse(curNode.Right, hitTest, hitlist);
}
}
// public interface to traversal..
public List<BVHNode<T>> Traverse(NodeTraversalTest hitTest)
{
var hits = new List<BVHNode<T>>();
this._traverse(rootBVH, hitTest, hits);
return hits;
}
/*
public List<BVHNode<T> Traverse(Ray ray)
{
float tnear = 0f, tfar = 0f;
return Traverse(box => OpenTKHelper.intersectRayAABox1(ray, box, ref tnear, ref tfar));
}
public List<BVHNode<T>> Traverse(Bounds volume)
{
return Traverse(box => box.IntersectsAABB(volume));
}
*/
/// <summary>
/// Call this to batch-optimize any object-changes notified through
/// ssBVHNode.refit_ObjectChanged(..). For example, in a game-loop,
/// call this once per frame.
/// </summary>
public void Optimize()
{
if (LEAF_OBJ_MAX != 1)
{
throw new Exception("In order to use optimize, you must set LEAF_OBJ_MAX=1");
}
while (refitNodes.Count > 0)
{
int maxdepth = refitNodes.Max(n => n.Depth);
var sweepNodes = refitNodes.Where(n => n.Depth == maxdepth).ToList();
sweepNodes.ForEach(n => refitNodes.Remove(n));
sweepNodes.ForEach(n => n.TryRotate(this));
}
}
public void Add(T newOb)
{
Bounds box = BoundsFromSphere(nAda.GetObjectPos(newOb), nAda.GetRadius(newOb));
float boxSAH = BVHNode<T>.SA(ref box);
rootBVH.Add(nAda, newOb, ref box, boxSAH);
}
/// <summary>
/// Call this when you wish to update an object. This does not update straight away, but marks it for update when Optimize() is called
/// </summary>
/// <param name="toUpdate"></param>
public void MarkForUpdate(T toUpdate)
{
nAda.OnPositionOrSizeChanged(toUpdate);
}
//Modified from https://github.com/jeske/SimpleScene/blob/master/SimpleScene/Core/SSAABB.cs
public static Bounds BoundsFromSphere(Vector3 pos, float radius)
{
Bounds bounds = new Bounds
{
min = new Vector3(pos.x - radius, pos.y - radius, pos.z - radius),
max = new Vector3(pos.x + radius, pos.y + radius, pos.z + radius)
};
return bounds;
}
public void Remove(T newObj)
{
var leaf = nAda.GetLeaf(newObj);
leaf.Remove(nAda, newObj);
}
public int CountBVHNodes()
{
return rootBVH.CountBVHNodes();
}
/// <summary>
/// initializes a BVH with a given nodeAdaptor, and object list.
/// </summary>
/// <param name="nodeAdaptor"></param>
/// <param name="objects"></param>
/// <param name="LEAF_OBJ_MAX">WARNING! currently this must be 1 to use dynamic BVH updates</param>
public BVH(IBVHNodeAdapter<T> nodeAdaptor, List<T> objects, int LEAF_OBJ_MAX = 1)
{
this.LEAF_OBJ_MAX = LEAF_OBJ_MAX;
nodeAdaptor.BVH = this;
this.nAda = nodeAdaptor;
if (objects.Count > 0)
{
rootBVH = new BVHNode<T>(this, objects);
}
else
{
rootBVH = new BVHNode<T>(this);
rootBVH.GObjects = new List<T>(); // it's a leaf, so give it an empty object list
}
}
//Bounds rendering ( for debugging )
//=======\/=======\/=======\/=======\/=======\/=======\/=======\/=======\/=======\/=======\/=======\/=======\/
private static readonly List<Vector3> vertices = new List<Vector3>
{
new Vector3 (-0.5f, -0.5f, -0.5f), new Vector3(0.5f, -0.5f, -0.5f), new Vector3(0.5f, 0.5f, -0.5f), new Vector3(-0.5f, 0.5f, -0.5f),
new Vector3 (-0.5f, -0.5f, 0.5f), new Vector3(0.5f, -0.5f, 0.5f), new Vector3(0.5f, 0.5f, 0.5f), new Vector3(-0.5f, 0.5f, 0.5f),
};
private static readonly int[] indices =
{
0, 1, 1, 2, 2, 3, 3, 0, // face1
4, 5, 5, 6, 6, 7, 7, 4, // face2
0, 4, 1, 5, 2, 6, 3, 7 // interconnects
};
public void GetAllNodeMatriciesRecursive(BVHNode<T> n, ref List<Matrix4x4> matricies, int depth)
{
//rotate not required since AABB
Matrix4x4 matrix = Matrix4x4.Translate(n.Box.center) * Matrix4x4.Scale(n.Box.size);
matricies.Add(matrix);
if (n.Right != null) GetAllNodeMatriciesRecursive(n.Right, ref matricies, depth + 1);
if (n.Left != null) GetAllNodeMatriciesRecursive(n.Left, ref matricies, depth + 1);
}
public void RenderDebug()
{
if (!SystemInfo.supportsInstancing)
{
Debug.LogError("[BVH] Cannot render BVH. Mesh instancing not supported by system");
}
else
{
List<Matrix4x4> matricies = new List<Matrix4x4>();
GetAllNodeMatriciesRecursive(rootBVH, ref matricies, 0);
Mesh mesh = new Mesh();
mesh.SetVertices(vertices);
mesh.SetIndices(indices, MeshTopology.Lines, 0);
if(_debugRenderMaterial == null)
{
_debugRenderMaterial = new Material(Shader.Find("Standard"))
{
enableInstancing = true
};
}
Graphics.DrawMeshInstanced(mesh, 0, _debugRenderMaterial, matricies);
}
}
}
}