1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3
4 #include "Geom.h"
5
6 #include "BVHTree.h"
7 #include "CollisionContact.h"
8 #include "CollisionSpace.h"
9 #include "GeomTree.h"
10
11 #include <float.h>
12
13 static const unsigned int MAX_CONTACTS = 8;
14
Geom(const GeomTree * geomtree,const matrix4x4d & m,const vector3d & pos,void * data)15 Geom::Geom(const GeomTree *geomtree, const matrix4x4d &m, const vector3d &pos, void *data) :
16 m_orient(m),
17 m_pos(pos),
18 m_geomtree(geomtree),
19 m_data(data),
20 m_group(0),
21 m_mailboxIndex(0),
22 m_active(true)
23 {
24 m_orient.SetTranslate(pos);
25 m_invOrient = m_orient.Inverse();
26 }
27
28 /*matrix4x4d Geom::GetRotation() const
29 {
30 PROFILE_SCOPED()
31 matrix4x4d m = GetTransform();
32 m[12] = 0; m[13] = 0; m[14] = 0;
33 return m;
34 }*/
35
MoveTo(const matrix4x4d & m)36 void Geom::MoveTo(const matrix4x4d &m)
37 {
38 m_orient = m;
39 m_pos = m_orient.GetTranslate();
40 m_invOrient = m.Inverse();
41 }
42
MoveTo(const matrix4x4d & m,const vector3d & pos)43 void Geom::MoveTo(const matrix4x4d &m, const vector3d &pos)
44 {
45 m_orient = m;
46 m_pos = pos;
47 m_orient.SetTranslate(pos);
48 m_invOrient = m_orient.Inverse();
49 }
50
CollideSphere(Sphere & sphere,void (* callback)(CollisionContact *)) const51 void Geom::CollideSphere(Sphere &sphere, void (*callback)(CollisionContact *)) const
52 {
53 PROFILE_SCOPED()
54 /* if the geom is actually within the sphere, create a contact so
55 * that we can't fall into spheres forever and ever */
56 vector3d v = GetPosition() - sphere.pos;
57 CollisionContact contact;
58 const double len = v.Length();
59 if (len < sphere.radius) {
60 contact.pos = GetPosition();
61 contact.normal = (1.0 / len) * v;
62 contact.depth = sphere.radius - len;
63 contact.triIdx = 0;
64 contact.userData1 = this->m_data;
65 contact.userData2 = sphere.userData;
66 contact.geomFlag = 0;
67 callback(&contact);
68 return;
69 }
70 }
71
72 /*
73 * This geom has moved, causing a possible collision with geom b.
74 * Collide meshes to see.
75 */
Collide(Geom * b,void (* callback)(CollisionContact *)) const76 void Geom::Collide(Geom *b, void (*callback)(CollisionContact *)) const
77 {
78 PROFILE_SCOPED()
79 int max_contacts = MAX_CONTACTS;
80 matrix4x4d transTo;
81 //unsigned int t = SDL_GetTicks();
82 /* Collide this geom's edges against tri-mesh of geom b */
83 transTo = b->m_invOrient * m_orient;
84 this->CollideEdgesWithTrisOf(max_contacts, b, transTo, callback);
85
86 /* Collide b's edges against this geom's tri-mesh */
87 if (max_contacts > 0) {
88 transTo = m_invOrient * b->m_orient;
89 b->CollideEdgesWithTrisOf(max_contacts, this, transTo, callback);
90 }
91
92 // t = SDL_GetTicks() - t;
93 // int numEdges = GetGeomTree()->GetNumEdges() + b->GetGeomTree()->GetNumEdges();
94 // Output("%d 'rays' in %dms (%f rps)\n", numEdges, t, 1000.0*numEdges / (double)t);
95 }
96
rotatedAabbIsectsNormalOne(Aabb & a,const matrix4x4d & transA,Aabb & b)97 static bool rotatedAabbIsectsNormalOne(Aabb &a, const matrix4x4d &transA, Aabb &b)
98 {
99 Aabb arot;
100 vector3d p[8];
101 p[0] = transA * vector3d(a.min.x, a.min.y, a.min.z);
102 p[1] = transA * vector3d(a.min.x, a.min.y, a.max.z);
103 p[2] = transA * vector3d(a.min.x, a.max.y, a.min.z);
104 p[3] = transA * vector3d(a.min.x, a.max.y, a.max.z);
105 p[4] = transA * vector3d(a.max.x, a.min.y, a.min.z);
106 p[5] = transA * vector3d(a.max.x, a.min.y, a.max.z);
107 p[6] = transA * vector3d(a.max.x, a.max.y, a.min.z);
108 p[7] = transA * vector3d(a.max.x, a.max.y, a.max.z);
109 arot.min = arot.max = p[0];
110 for (int i = 1; i < 8; i++)
111 arot.Update(p[i]);
112 return b.Intersects(arot);
113 }
114
115 /*
116 * Intersect this Geom's edge BVH tree with geom b's triangle BVH tree.
117 * Generate collision contacts.
118 */
CollideEdgesWithTrisOf(int & maxContacts,const Geom * b,const matrix4x4d & transTo,void (* callback)(CollisionContact *)) const119 void Geom::CollideEdgesWithTrisOf(int &maxContacts, const Geom *b, const matrix4x4d &transTo, void (*callback)(CollisionContact *)) const
120 {
121 PROFILE_SCOPED()
122 struct stackobj {
123 BVHNode *edgeNode;
124 BVHNode *triNode;
125 } stack[32];
126 int stackpos = 0;
127
128 stack[0].edgeNode = GetGeomTree()->GetEdgeTree()->GetRoot();
129 stack[0].triNode = b->GetGeomTree()->GetTriTree()->GetRoot();
130
131 while ((stackpos >= 0) && (maxContacts > 0)) {
132 BVHNode *edgeNode = stack[stackpos].edgeNode;
133 BVHNode *triNode = stack[stackpos].triNode;
134 stackpos--;
135
136 // does the edgeNode (with its aabb described in 6 planes transformed and rotated to
137 // b's coordinates) intersect with one or other of b's child nodes?
138 if (triNode->triIndicesStart || edgeNode->triIndicesStart) {
139 // reached triangle leaf node or edge leaf node.
140 // Intersect all edges under edgeNode with this leaf
141 CollideEdgesTris(maxContacts, edgeNode, transTo, b, triNode, callback);
142 } else {
143 BVHNode *left = triNode->kids[0];
144 BVHNode *right = triNode->kids[1];
145 bool edgeNodeIsectsLeftChild = rotatedAabbIsectsNormalOne(edgeNode->aabb, transTo, left->aabb);
146 bool edgeNodeIsectsRightChild = rotatedAabbIsectsNormalOne(edgeNode->aabb, transTo, right->aabb);
147 //edgeNodeIsectsRightChild = edgeNodeIsectsLeftChild = true;
148 if (edgeNodeIsectsRightChild) {
149 if (edgeNodeIsectsLeftChild) {
150 // isects both. split edgeNode and try again
151 ++stackpos;
152 stack[stackpos].edgeNode = edgeNode->kids[0];
153 stack[stackpos].triNode = triNode;
154 ++stackpos;
155 stack[stackpos].edgeNode = edgeNode->kids[1];
156 stack[stackpos].triNode = triNode;
157 } else {
158 // hits only right child. go down into that
159 // side with same edge node
160 ++stackpos;
161 stack[stackpos].edgeNode = edgeNode;
162 stack[stackpos].triNode = triNode->kids[1];
163 }
164 } else if (edgeNodeIsectsLeftChild) {
165 // hits only left child
166 ++stackpos;
167 stack[stackpos].edgeNode = edgeNode;
168 stack[stackpos].triNode = triNode->kids[0];
169 } else {
170 // hits none
171 }
172 }
173 }
174 }
175
176 /*
177 * Collide one edgeNode (all edges below it) of this Geom with the triangle
178 * BVH of another geom (b), starting from btriNode.
179 */
CollideEdgesTris(int & maxContacts,const BVHNode * edgeNode,const matrix4x4d & transToB,const Geom * b,const BVHNode * btriNode,void (* callback)(CollisionContact *)) const180 void Geom::CollideEdgesTris(int &maxContacts, const BVHNode *edgeNode, const matrix4x4d &transToB,
181 const Geom *b, const BVHNode *btriNode, void (*callback)(CollisionContact *)) const
182 {
183 PROFILE_SCOPED()
184 if (maxContacts <= 0) return;
185 if (edgeNode->triIndicesStart) {
186 const GeomTree::Edge *edges = this->GetGeomTree()->GetEdges();
187 int numContacts = 0;
188 vector3f dir;
189 isect_t isect;
190 const std::vector<vector3f> &rVertices = GetGeomTree()->GetVertices();
191 for (int i = 0; i < edgeNode->numTris; i++) {
192 const int vtxNum = edges[edgeNode->triIndicesStart[i]].v1i;
193 const vector3d v1 = transToB * vector3d(rVertices[vtxNum]);
194 const vector3f _from(float(v1.x), float(v1.y), float(v1.z));
195
196 vector3d _dir(
197 double(edges[edgeNode->triIndicesStart[i]].dir.x),
198 double(edges[edgeNode->triIndicesStart[i]].dir.y),
199 double(edges[edgeNode->triIndicesStart[i]].dir.z));
200 _dir = transToB.ApplyRotationOnly(_dir);
201 dir = vector3f(&_dir.x);
202 isect.dist = edges[edgeNode->triIndicesStart[i]].len;
203 isect.triIdx = -1;
204
205 b->GetGeomTree()->TraceRay(btriNode, _from, dir, &isect);
206
207 if (isect.triIdx == -1) continue;
208 numContacts++;
209 const double depth = edges[edgeNode->triIndicesStart[i]].len - isect.dist;
210 // in world coords
211 CollisionContact contact;
212 contact.pos = b->GetTransform() * (v1 + vector3d(&dir.x) * double(isect.dist));
213 vector3f n = b->m_geomtree->GetTriNormal(isect.triIdx);
214 contact.normal = vector3d(n.x, n.y, n.z);
215 contact.normal = b->GetTransform().ApplyRotationOnly(contact.normal);
216 contact.distance = isect.dist;
217
218 contact.depth = depth;
219 contact.triIdx = isect.triIdx;
220 contact.userData1 = m_data;
221 contact.userData2 = b->m_data;
222 // contact geomFlag is bitwise OR of triangle's and edge's flags
223 contact.geomFlag = b->m_geomtree->GetTriFlag(isect.triIdx) |
224 edges[edgeNode->triIndicesStart[i]].triFlag;
225 callback(&contact);
226 if (--maxContacts <= 0) return;
227 }
228 } else {
229 CollideEdgesTris(maxContacts, edgeNode->kids[0], transToB, b, btriNode, callback);
230 CollideEdgesTris(maxContacts, edgeNode->kids[1], transToB, b, btriNode, callback);
231 }
232 }
233