1 module sbylib.collision.broad.aabbset;
2 
3 import sbylib.collision.bounds.aabb;
4 import sbylib.collision.shape.shape;
5 import sbylib.collision.narrow.detect : CollisionResult;
6 import std.typecons : Nullable;
7 import std.variant : Algebraic, visit;
8 
9 class AABBSet {
10 
11     auto collisionDetected(MyType : CollisionShape, YourType : CollisionShape)(YourType shape) {
12         return CollisionDetectNotification!(MyType, YourType)(this, shape);
13     }
14 
15     private alias Tree = Algebraic!(Node, Leaf);
16     private AABB visitAABB(Tree tree) {
17         return tree.visit!(
18                 (Node node) => node.getAABB(),
19                 (Leaf leaf) => leaf.getAABB());
20     }
21 
22     private Tree root; 
23     private this(ShapePair[] shapeList) {
24         this.root = buildTree(shapeList);
25     }
26 
27     private Tree buildTree(ShapePair[] shapeList) 
28         in (shapeList.length > 0)
29     {
30         import std.algorithm : map, reduce, sort, minElement, maxElement;
31         import std.array : array;
32         import std.typecons : tuple;
33         import std.range : enumerate;
34         import sbylib.math : mostDispersionBasis, dot, vec3;
35 
36         if (shapeList.length is 1)
37             return Tree(new Leaf(shapeList[0]));
38 
39         auto shapeAABBList = shapeList.map!(s => tuple(s, s.getAABB())).array;
40         auto centerList = shapeAABBList.map!(s => s[1].center).array;
41         auto basisList = mostDispersionBasis(centerList);
42         auto lengthList = basisList.array.map!(basis =>
43                 centerList.map!(c => dot(c, basis)).maxElement
44               - centerList.map!(c => dot(c, basis)).minElement).array;
45         vec3 basis = basisList[lengthList.enumerate.reduce!((a,b) => a.value > b.value ? a : b).index];
46         shapeList = shapeAABBList
47             .sort!((a,b) => dot(a[1].center, basis) < dot(b[1].center, basis))
48             .map!(s => s[0])
49             .array;
50         return Tree(new Node([buildTree(shapeList[0..$/2]), buildTree(shapeList[$/2..$])]));
51     }
52 
53     void detect
54         (MyType : CollisionShape, YourType : CollisionShape)
55         (YourType shape, CollisionDetectCallback!(MyType, YourType) callback) 
56     {
57         root.visit!(
58             (Node node) => node.detect!(MyType, YourType)(shape, callback),
59             (Leaf leaf) => leaf.detect!(MyType, YourType)(shape, callback));
60     }
61 
62     private alias CollisionDetectCallback(MyType, YourType)
63         = void delegate(MyType, YourType, CollisionResult!(MyType, YourType));
64 
65     private struct ShapePair {
66         import std.variant : Variant;
67 
68         CollisionShape shape;
69         Variant variant;
70         alias shape this;
71 
72         this(Shape : CollisionShape)(Shape shape) {
73             this.shape = shape;
74             this.variant = shape;
75         }
76     }
77 
78     struct Builder {
79         ShapePair[] shapeList;
80 
81         void add(Shape : CollisionShape)(Shape shape) {
82             this.shapeList ~= ShapePair(shape); 
83         }
84 
85         auto build() {
86             return new AABBSet(shapeList);
87         }
88     }
89 
90     private class Node {
91         Tree[2] children;
92         Nullable!AABB aabb;
93         this(Tree[2] children) { this.children = children; }
94         AABB getAABB() { 
95             if(aabb.isNull)
96                 aabb = AABB.unite(visitAABB(children[0]), visitAABB(children[1])); 
97             return aabb.get();
98         }
99         
100         void detect(MyType : CollisionShape, YourType : CollisionShape)
101             (YourType shape, CollisionDetectCallback!(MyType, YourType) callback) {
102             if (intersect(this.getAABB(), shape.getAABB()) is false) return;
103             static foreach (i; 0..2) {
104                 children[i].visit!(
105                     (Node node) => node.detect!(MyType, YourType)(shape, callback),
106                     (Leaf leaf) => leaf.detect!(MyType, YourType)(shape, callback));
107             }
108         }
109     }
110 
111     private class Leaf {
112         ShapePair shape;
113         this(ShapePair shape) { this.shape = shape; }
114         AABB getAABB() { return shape.getAABB(); }
115 
116         void detect(MyType : CollisionShape, YourType : CollisionShape)
117             (YourType shape, CollisionDetectCallback!(MyType, YourType) callback) {
118             import sbylib.collision.narrow : detect;
119 
120             if (this.shape is shape) return;
121             if (intersect(this.getAABB(), shape.getAABB()) is false) return;
122 
123             if (auto s1 = this.shape.variant.peek!MyType) {
124                 auto info = detect(*s1, shape);
125                 if (info.isNull) return;
126                 callback(*s1, shape, info.get());
127             }
128         }
129     }
130 }
131 
132 private struct CollisionDetectNotification(MyType : CollisionShape, YourType : CollisionShape) {
133     AABBSet set;
134     YourType shape;
135 }
136 
137 auto when(MyType : CollisionShape, YourType : CollisionShape)(CollisionDetectNotification!(MyType, YourType) notification) {
138     import sbylib.graphics : Event, when, Frame, then, until;
139     import std.meta : AliasSeq;
140 
141     alias Args = AliasSeq!(MyType, YourType, CollisionResult!(MyType, YourType));
142 
143     auto result = new Event!(Args);
144 
145     when(Frame).then({
146         notification.set.detect!(MyType, YourType)(notification.shape, (Args args) => result.fire(args));
147     });
148     return result;
149 }