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