basilisk-engine 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of basilisk-engine might be problematic. Click here for more details.
- basilisk/collisions/broad/broad_aabb.py +8 -1
- basilisk/collisions/broad/broad_bvh.py +8 -1
- basilisk/collisions/collider.py +15 -6
- basilisk/collisions/collider_handler.py +70 -68
- basilisk/collisions/narrow/contact_manifold.py +9 -10
- basilisk/collisions/narrow/dataclasses.py +27 -0
- basilisk/collisions/narrow/deprecated.py +47 -0
- basilisk/collisions/narrow/epa.py +11 -10
- basilisk/collisions/narrow/gjk.py +15 -14
- basilisk/collisions/narrow/helper.py +8 -7
- basilisk/collisions/narrow/sutherland_hodgman.py +52 -0
- basilisk/draw/draw_handler.py +5 -3
- basilisk/engine.py +14 -5
- basilisk/generic/abstract_custom.py +134 -0
- basilisk/generic/collisions.py +46 -0
- basilisk/generic/quat.py +77 -66
- basilisk/generic/vec3.py +91 -67
- basilisk/mesh/cube.py +20 -2
- basilisk/mesh/mesh.py +69 -54
- basilisk/mesh/mesh_from_data.py +106 -21
- basilisk/mesh/narrow_aabb.py +10 -1
- basilisk/mesh/narrow_bvh.py +9 -1
- basilisk/nodes/node.py +169 -30
- basilisk/nodes/node_handler.py +51 -30
- basilisk/particles/__init__.py +0 -0
- basilisk/particles/particle_handler.py +55 -0
- basilisk/particles/particle_renderer.py +87 -0
- basilisk/physics/impulse.py +7 -13
- basilisk/physics/physics_body.py +10 -2
- basilisk/physics/physics_engine.py +1 -2
- basilisk/render/batch.py +2 -2
- basilisk/render/camera.py +5 -0
- basilisk/render/chunk.py +19 -4
- basilisk/render/chunk_handler.py +33 -26
- basilisk/render/image.py +1 -1
- basilisk/render/image_handler.py +4 -3
- basilisk/render/light_handler.py +16 -11
- basilisk/render/material.py +25 -1
- basilisk/render/material_handler.py +22 -13
- basilisk/render/shader.py +7 -7
- basilisk/render/shader_handler.py +13 -12
- basilisk/render/sky.py +5 -3
- basilisk/scene.py +114 -32
- basilisk/shaders/batch.frag +40 -11
- basilisk/shaders/batch.vert +14 -7
- basilisk/shaders/normal.frag +5 -5
- basilisk/shaders/normal.vert +8 -3
- basilisk/shaders/particle.frag +72 -0
- basilisk/shaders/particle.vert +85 -0
- {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.1.dist-info}/METADATA +5 -5
- basilisk_engine-0.1.1.dist-info/RECORD +95 -0
- basilisk/shaders/image.png +0 -0
- basilisk_engine-0.1.0.dist-info/RECORD +0 -88
- {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.1.dist-info}/WHEEL +0 -0
- {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import glm
|
|
2
2
|
from ...generic.abstract_bvh import AbstractAABB as AABB
|
|
3
|
-
from ...generic.collisions import collide_aabb_aabb
|
|
3
|
+
from ...generic.collisions import collide_aabb_aabb, collide_aabb_line
|
|
4
4
|
from ...generic.meshes import get_aabb_surface_area
|
|
5
5
|
from ..collider import Collider
|
|
6
6
|
|
|
@@ -79,6 +79,13 @@ class BroadAABB(AABB):
|
|
|
79
79
|
if isinstance(self.b, BroadAABB): possible.extend(self.b.get_collided(collider))
|
|
80
80
|
elif collide_aabb_aabb(self.b.top_right, self.b.bottom_left, collider.top_right, collider.bottom_left): possible.append(self.b)
|
|
81
81
|
return possible
|
|
82
|
+
|
|
83
|
+
def get_line_collided(self, position: glm.vec3, forward: glm.vec3) -> list[Collider]:
|
|
84
|
+
"""
|
|
85
|
+
Returns the colliders that may intersect with the given line
|
|
86
|
+
"""
|
|
87
|
+
if not collide_aabb_line(self.top_right, self.bottom_left, position, forward): return []
|
|
88
|
+
return (self.a.get_line_collided(position, forward) if isinstance(self.a, BroadAABB) else [self.a]) + (self.b.get_line_collided(position, forward) if isinstance(self.b, BroadAABB) else [self.b])
|
|
82
89
|
|
|
83
90
|
def get_all_aabbs(self, layer: int) -> list[tuple[glm.vec3, glm.vec3, int]]: # TODO test function
|
|
84
91
|
"""
|
|
@@ -128,4 +128,11 @@ class BroadBVH(BVH):
|
|
|
128
128
|
Returns which objects may be colliding from the BVH
|
|
129
129
|
"""
|
|
130
130
|
if isinstance(self.root, BroadAABB): return self.root.get_collided(collider)
|
|
131
|
-
else: return []
|
|
131
|
+
else: return [] # if there is only one collider in the scene then there is nothing to collide with
|
|
132
|
+
|
|
133
|
+
def get_line_collided(self, position: glm.vec3, forward: glm.vec3) -> list[Collider]:
|
|
134
|
+
"""
|
|
135
|
+
Returns the colliders that may intersect with the given line
|
|
136
|
+
"""
|
|
137
|
+
if isinstance(self.root, BroadAABB): return self.root.get_line_collided(position, forward)
|
|
138
|
+
return [self.root]
|
basilisk/collisions/collider.py
CHANGED
|
@@ -35,11 +35,11 @@ class Collider():
|
|
|
35
35
|
mesh: Mesh
|
|
36
36
|
"""Reference to the colliding mesh"""
|
|
37
37
|
|
|
38
|
-
def __init__(self,
|
|
39
|
-
self.collider_handler =
|
|
38
|
+
def __init__(self, node, box_mesh: bool=False, static_friction: glm.vec3=0.7, kinetic_friction: glm.vec3=0.3, elasticity: glm.vec3=0.2, collision_group: str=None):
|
|
39
|
+
self.collider_handler = None
|
|
40
40
|
self.node = node
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
41
|
+
self.static_friction = static_friction if elasticity else 0.8 # added checks to prevent floats being set to None. Also done for kinetic and elasticity
|
|
42
|
+
self.box_mesh = box_mesh
|
|
43
43
|
self.kinetic_friction = kinetic_friction if elasticity else 0.4
|
|
44
44
|
self.elasticity = elasticity if elasticity else 0.1
|
|
45
45
|
self.collision_group = collision_group
|
|
@@ -51,7 +51,9 @@ class Collider():
|
|
|
51
51
|
self.needs_obb = True # pos, scale, rot
|
|
52
52
|
self.needs_half_dimensions = True # scale, rot
|
|
53
53
|
self.needs_bvh = True # pos, scale, rot
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def collider_handler(self): return self._collider_handler
|
|
55
57
|
@property
|
|
56
58
|
def has_collided(self): return bool(self.collisions)
|
|
57
59
|
@property
|
|
@@ -72,4 +74,11 @@ class Collider():
|
|
|
72
74
|
if self.needs_obb:
|
|
73
75
|
self._obb_points = transform_points(self.mesh.aabb_points, self.node.model_matrix)
|
|
74
76
|
self.needs_obb = False
|
|
75
|
-
return self._obb_points
|
|
77
|
+
return self._obb_points
|
|
78
|
+
|
|
79
|
+
@collider_handler.setter
|
|
80
|
+
def collider_handler(self, value):
|
|
81
|
+
self._collider_handler = value
|
|
82
|
+
if not value: return
|
|
83
|
+
self.mesh = value.cube if self.box_mesh else self.node.mesh
|
|
84
|
+
value.add(self)
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import glm
|
|
2
2
|
|
|
3
|
-
from basilisk.collisions.narrow.graham_scan import graham_scan
|
|
4
|
-
from basilisk.collisions.narrow.sutherland_hodgman import sutherland_hodgman
|
|
5
3
|
from .collider import Collider
|
|
6
4
|
from .broad.broad_bvh import BroadBVH
|
|
7
5
|
from .narrow.gjk import collide_gjk
|
|
8
6
|
from .narrow.epa import get_epa_from_gjk
|
|
9
|
-
from .narrow.contact_manifold import get_contact_manifold,
|
|
10
|
-
from .narrow.
|
|
7
|
+
from .narrow.contact_manifold import get_contact_manifold, separate_polytope
|
|
8
|
+
from .narrow.dataclasses import SupportPoint, ContactPoint, ContactManifold
|
|
11
9
|
from ..nodes.node import Node
|
|
12
10
|
from ..generic.collisions import get_sat_axes
|
|
13
11
|
from ..physics.impulse import calculate_collisions
|
|
@@ -24,13 +22,14 @@ class ColliderHandler():
|
|
|
24
22
|
self.scene = scene
|
|
25
23
|
self.cube = self.scene.engine.cube
|
|
26
24
|
self.colliders = []
|
|
25
|
+
self.polytope_data = {}
|
|
26
|
+
self.contact_manifolds: dict[tuple[Collider, Collider] : ContactManifold] = {}
|
|
27
27
|
self.bvh = BroadBVH(self)
|
|
28
28
|
|
|
29
|
-
def add(self,
|
|
29
|
+
def add(self, collider: Collider) -> Collider:
|
|
30
30
|
"""
|
|
31
31
|
Creates a collider and adds it to the collider list
|
|
32
32
|
"""
|
|
33
|
-
collider = Collider(self, node, box_mesh, static_friction, kinetic_friction, elasticity, collision_group)
|
|
34
33
|
self.colliders.append(collider)
|
|
35
34
|
self.bvh.add(collider)
|
|
36
35
|
return collider
|
|
@@ -49,11 +48,14 @@ class ColliderHandler():
|
|
|
49
48
|
"""
|
|
50
49
|
# reset collision data
|
|
51
50
|
for collider in self.colliders: collider.collisions = {}
|
|
51
|
+
|
|
52
|
+
|
|
52
53
|
# update BVH
|
|
53
54
|
for collider in self.colliders:
|
|
54
55
|
if collider.needs_bvh:
|
|
55
56
|
self.bvh.remove(collider)
|
|
56
57
|
self.bvh.add(collider)
|
|
58
|
+
collider.needs_bvh = False
|
|
57
59
|
|
|
58
60
|
# resolve collisions
|
|
59
61
|
broad_collisions = self.resolve_broad_collisions()
|
|
@@ -91,54 +93,6 @@ class ColliderHandler():
|
|
|
91
93
|
|
|
92
94
|
return small_axis, small_overlap, small_index
|
|
93
95
|
|
|
94
|
-
def sat_manifold(self, points1: list[glm.vec3], points2: list[glm.vec3], axis: glm.vec3, plane_point: glm.vec3, digit: int) -> list[glm.vec3]:
|
|
95
|
-
"""
|
|
96
|
-
Returns the contact manifold from an SAT OBB OBB collision
|
|
97
|
-
"""
|
|
98
|
-
def get_test_points(contact_plane_normal:glm.vec3, points:list[glm.vec3], count: int):
|
|
99
|
-
test_points = [(glm.dot(contact_plane_normal, p), p) for p in points]
|
|
100
|
-
test_points.sort(key=lambda p: p[0])
|
|
101
|
-
return [p[1] for p in test_points[:count]]
|
|
102
|
-
|
|
103
|
-
def get_test_points_unknown(contact_plane_normal:glm.vec3, points:list[glm.vec3]):
|
|
104
|
-
test_points = [(glm.dot(contact_plane_normal, p), p) for p in points]
|
|
105
|
-
test_points.sort(key=lambda p: p[0])
|
|
106
|
-
if test_points[2][0] - test_points[0][0] > 1e-3: return [p[1] for p in test_points[:2]]
|
|
107
|
-
else: return [p[1] for p in test_points[:4]]
|
|
108
|
-
|
|
109
|
-
if digit < 6: # there must be at least one face in the collision
|
|
110
|
-
reference, incident = (get_test_points(-axis, points1, 4), get_test_points_unknown(axis, points2)) if digit < 3 else (get_test_points(axis, points2, 4), get_test_points_unknown(-axis, points1))
|
|
111
|
-
|
|
112
|
-
# project vertices onto the 2d plane
|
|
113
|
-
reference = project_points(plane_point, axis, reference)
|
|
114
|
-
incident = project_points(plane_point, axis, incident)
|
|
115
|
-
|
|
116
|
-
# convert points to 2d for intersection algorithms
|
|
117
|
-
reference, u1, v1 = points_to_2d(plane_point, axis, reference)
|
|
118
|
-
incident, u2, v2 = points_to_2d(plane_point, axis, incident, u1, v1) #TODO precalc orthogonal basis for 2d conversion
|
|
119
|
-
|
|
120
|
-
# convert arbitrary points to polygon
|
|
121
|
-
reference = graham_scan(reference)
|
|
122
|
-
if len(incident) == 4: incident = graham_scan(incident)
|
|
123
|
-
|
|
124
|
-
# run clipping algorithms
|
|
125
|
-
manifold = []
|
|
126
|
-
if len(incident) == 2: manifold = line_poly_intersect(incident, reference)
|
|
127
|
-
else: manifold = sutherland_hodgman(reference, incident)
|
|
128
|
-
|
|
129
|
-
# # fall back if manifold fails to develope
|
|
130
|
-
assert len(manifold), 'sat did not generate points'
|
|
131
|
-
|
|
132
|
-
# # convert inertsection algorithm output to 3d
|
|
133
|
-
return points_to_3d(u1, v1, plane_point, manifold)
|
|
134
|
-
|
|
135
|
-
else: # there is an edge edge collision
|
|
136
|
-
|
|
137
|
-
points1 = get_test_points(-axis, points1, 2)
|
|
138
|
-
points2 = get_test_points(axis, points2, 2)
|
|
139
|
-
|
|
140
|
-
return closest_two_lines(*points1, *points2)
|
|
141
|
-
|
|
142
96
|
def collide_obb_obb_decision(self, collider1: Collider, collider2: Collider) -> bool:
|
|
143
97
|
"""
|
|
144
98
|
Determines if two obbs are colliding Uses SAT.
|
|
@@ -164,20 +118,50 @@ class ColliderHandler():
|
|
|
164
118
|
"""
|
|
165
119
|
collisions = set()
|
|
166
120
|
for collider1 in self.colliders:
|
|
167
|
-
|
|
121
|
+
if collider1.node.static: continue
|
|
168
122
|
# traverse bvh to find aabb aabb collisions
|
|
169
123
|
colliding = self.bvh.get_collided(collider1)
|
|
170
124
|
for collider2 in colliding:
|
|
171
|
-
if collider1
|
|
172
|
-
if (collider1, collider2)
|
|
125
|
+
if collider1 is collider2: continue
|
|
126
|
+
if ((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1)) in collisions: continue
|
|
173
127
|
|
|
174
128
|
# run broad collision for specified mesh types
|
|
175
129
|
if max(len(collider1.mesh.points), len(collider2.mesh.points)) > 250 and not self.collide_obb_obb_decision(collider1, collider2): continue # contains at least one "large" mesh TODO write heuristic algorithm for determining large meshes
|
|
176
|
-
|
|
177
|
-
collisions.add((collider1, collider2)) # TODO find a secure way for ordering colliders
|
|
130
|
+
collisions.add((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1))
|
|
178
131
|
|
|
179
132
|
return collisions
|
|
180
133
|
|
|
134
|
+
def merge_contact_points(self, vec: glm.vec3, collider1: Collider, collider2: Collider, points1: list[ContactPoint], points2: list[ContactPoint]) -> None:
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
def merge_points(node: Node, existing: dict[int, glm.vec3], incoming: list[ContactPoint]) -> dict[int, glm.vec3]:
|
|
139
|
+
incoming_indices = set()
|
|
140
|
+
|
|
141
|
+
# add incoming points
|
|
142
|
+
for point in incoming:
|
|
143
|
+
incoming_indices.add(point.index)
|
|
144
|
+
if point.index not in existing or glm.length2(point.vertex - existing[point.index]) > 1e-5: existing[point.index] = glm.vec3(point.vertex)
|
|
145
|
+
# if glm.length2(point.vertex - existing[point.index]) != 0: print(point.vertex - existing[point.index])
|
|
146
|
+
|
|
147
|
+
# remove changed stored points
|
|
148
|
+
remove_indices = []
|
|
149
|
+
for index, vertex in existing.items():
|
|
150
|
+
if index in incoming_indices: continue
|
|
151
|
+
if glm.length2(node.get_vertex(index) - vertex) > 1e-5: remove_indices.append(index) # check to see if point has moved
|
|
152
|
+
|
|
153
|
+
# remove unused and moved points
|
|
154
|
+
for index in remove_indices: del existing[index]
|
|
155
|
+
return existing
|
|
156
|
+
|
|
157
|
+
# check if collision is logged, if not create a new one
|
|
158
|
+
collider_tuple = (collider1, collider2)
|
|
159
|
+
if collider_tuple not in self.contact_manifolds or glm.length2(self.contact_manifolds[collider_tuple].normal - vec) > 1e-7: self.contact_manifolds[collider_tuple] = ContactManifold(vec, dict(), dict())
|
|
160
|
+
|
|
161
|
+
# add contact point from current collision and check overlap
|
|
162
|
+
self.contact_manifolds[collider_tuple].contact_points1 = merge_points(collider1.node, self.contact_manifolds[collider_tuple].contact_points1, points1)
|
|
163
|
+
self.contact_manifolds[collider_tuple].contact_points2 = merge_points(collider2.node, self.contact_manifolds[collider_tuple].contact_points2, points2)
|
|
164
|
+
|
|
181
165
|
def resolve_narrow_collisions(self, broad_collisions: list[tuple[Collider, Collider]]) -> None:
|
|
182
166
|
"""
|
|
183
167
|
Determines if two colliders are colliding, if so resolves their penetration and applies impulse
|
|
@@ -198,8 +182,8 @@ class ColliderHandler():
|
|
|
198
182
|
vec, distance, index = data
|
|
199
183
|
|
|
200
184
|
# TODO replace with own contact algorithm
|
|
201
|
-
points1 = collider1.obb_points
|
|
202
|
-
points2 = collider2.obb_points
|
|
185
|
+
points1 = [ContactPoint(index, vertex) for index, vertex in enumerate(collider1.obb_points)]
|
|
186
|
+
points2 = [ContactPoint(index, vertex) for index, vertex in enumerate(collider2.obb_points)]
|
|
203
187
|
|
|
204
188
|
else: # use gjk to determine collisions between non-cuboid meshes
|
|
205
189
|
has_collided, simplex = collide_gjk(node1, node2)
|
|
@@ -209,17 +193,35 @@ class ColliderHandler():
|
|
|
209
193
|
vec, distance = face[1], face[0]
|
|
210
194
|
|
|
211
195
|
# TODO replace with own contact algorithm
|
|
212
|
-
points1 = [p
|
|
213
|
-
points2 = [p
|
|
196
|
+
points1 = [ContactPoint(p.index1, p.vertex1) for p in polytope]
|
|
197
|
+
points2 = [ContactPoint(p.index2, p.vertex2) for p in polytope]
|
|
214
198
|
|
|
215
199
|
if glm.dot(vec, node2.position - node1.position) > 0: vec *= -1
|
|
216
200
|
|
|
201
|
+
# apply impulse if a collider has a physic body
|
|
217
202
|
if node1.physics_body or node2.physics_body:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
203
|
+
|
|
204
|
+
# determine the contact points from the collision
|
|
205
|
+
points1, points2 = separate_polytope(points1, points2, vec)
|
|
206
|
+
self.merge_contact_points(vec, collider1, collider2, points1, points2)
|
|
207
|
+
|
|
208
|
+
# for manifold in self.contact_manifolds.values(): print(list(manifold.contact_points1.values()) + list(manifold.contact_points2.values()))
|
|
209
|
+
|
|
210
|
+
collider_tuple = (collider1, collider2)
|
|
211
|
+
# print(self.contact_manifolds[collider_tuple])
|
|
212
|
+
manifold = get_contact_manifold(
|
|
213
|
+
node1.position - vec,
|
|
214
|
+
vec,
|
|
215
|
+
self.contact_manifolds[collider_tuple].contact_points1.values(),
|
|
216
|
+
self.contact_manifolds[collider_tuple].contact_points2.values()
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
collision_normal = node1.velocity - node2.velocity
|
|
220
|
+
collision_normal = vec if glm.length2(collision_normal) < 1e-12 else glm.normalize(collision_normal)
|
|
221
|
+
calculate_collisions(collision_normal, node1, node2, manifold, node1.get_inverse_inertia(), node2.get_inverse_inertia(), node1.center_of_mass, node2.center_of_mass)
|
|
222
|
+
|
|
223
|
+
# for i, point in enumerate(manifold):
|
|
224
|
+
# self.scene.add(Node(position = point, scale = (0.1, 0.1, 0.1)))
|
|
223
225
|
|
|
224
226
|
# resolve collision penetration
|
|
225
227
|
multiplier = 0.5 if not (node1.static or node2.static) else 1
|
|
@@ -3,15 +3,13 @@ from random import randint
|
|
|
3
3
|
from .line_intersections import line_line_intersect, line_poly_intersect
|
|
4
4
|
from .graham_scan import graham_scan
|
|
5
5
|
from .sutherland_hodgman import sutherland_hodgman
|
|
6
|
+
from .dataclasses import ContactPoint
|
|
6
7
|
|
|
7
8
|
# sutherland hodgman clipping algorithm
|
|
8
9
|
def get_contact_manifold(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, points1:list[glm.vec3], points2:list[glm.vec3]) -> list[glm.vec3]:
|
|
9
10
|
"""
|
|
10
11
|
computes the contact manifold for a collision between two nearby polyhedra
|
|
11
12
|
"""
|
|
12
|
-
# determine the contact points from the collision
|
|
13
|
-
points1, points2 = separate_polytope(points1, points2, contact_plane_normal)
|
|
14
|
-
|
|
15
13
|
if len(points1) == 0 or len(points2) == 0: return []
|
|
16
14
|
|
|
17
15
|
# project vertices onto the 2d plane
|
|
@@ -41,22 +39,23 @@ def get_contact_manifold(contact_plane_point:glm.vec3, contact_plane_normal:glm.
|
|
|
41
39
|
|
|
42
40
|
# fall back if manifold fails to develope
|
|
43
41
|
if len(manifold) == 0: return []
|
|
42
|
+
|
|
44
43
|
# convert inertsection algorithm output to 3d
|
|
45
44
|
return points_to_3d(u1, v1, contact_plane_point, manifold)
|
|
46
45
|
|
|
47
|
-
def separate_polytope(points1: list[
|
|
46
|
+
def separate_polytope(points1: list[ContactPoint], points2: list[ContactPoint], contact_plane_normal, epsilon: float=1e-5) -> tuple[list[ContactPoint], list[ContactPoint]]:
|
|
48
47
|
"""
|
|
49
48
|
Determines the potential contact manifold points of each shape based on their position along the penetrating axis
|
|
50
49
|
"""
|
|
51
|
-
proj1 = [(glm.dot(point, contact_plane_normal), point) for point in points1]
|
|
52
|
-
proj2 = [(glm.dot(point, contact_plane_normal), point) for point in points2]
|
|
50
|
+
proj1 = [(glm.dot(point.vertex, contact_plane_normal), point) for point in points1]
|
|
51
|
+
proj2 = [(glm.dot(point.vertex, contact_plane_normal), point) for point in points2]
|
|
53
52
|
|
|
54
53
|
# min1 and max2 should be past the collising points of node2 and node1 respectively
|
|
55
|
-
min1 = min(proj1, key=lambda
|
|
56
|
-
max2 = max(proj2, key=lambda
|
|
54
|
+
min1 = min(proj1, key=lambda proj: proj[0])[0]
|
|
55
|
+
max2 = max(proj2, key=lambda proj: proj[0])[0]
|
|
57
56
|
|
|
58
|
-
proj1 = filter(lambda proj: proj
|
|
59
|
-
proj2 = filter(lambda proj: proj
|
|
57
|
+
proj1 = filter(lambda proj: proj[0] <= max2 + epsilon, proj1)
|
|
58
|
+
proj2 = filter(lambda proj: proj[0] + epsilon >= min1, proj2)
|
|
60
59
|
|
|
61
60
|
return [point[1] for point in proj1], [point[1] for point in proj2]
|
|
62
61
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import glm
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
# frozen because data does not need to be mutable
|
|
5
|
+
# used in creating polytopes for GJK/EPA
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class SupportPoint():
|
|
8
|
+
support_point: glm.vec3
|
|
9
|
+
|
|
10
|
+
index1: int # index of the vertex in the mesh
|
|
11
|
+
vertex1: glm.vec3 # world space location of the vertex at collision
|
|
12
|
+
|
|
13
|
+
index2: int
|
|
14
|
+
vertex2: glm.vec3
|
|
15
|
+
|
|
16
|
+
# used for generating contact points for the contact manifold
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class ContactPoint():
|
|
19
|
+
index: int
|
|
20
|
+
vertex: glm.vec3
|
|
21
|
+
|
|
22
|
+
# contact manifold object used in the contact handler list
|
|
23
|
+
@dataclass
|
|
24
|
+
class ContactManifold():
|
|
25
|
+
normal: glm.vec3
|
|
26
|
+
contact_points1: dict[int : glm.vec3] # contact point index : collision position
|
|
27
|
+
contact_points2: dict[int : glm.vec3]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# def sat_manifold(self, points1: list[glm.vec3], points2: list[glm.vec3], axis: glm.vec3, plane_point: glm.vec3, digit: int) -> list[glm.vec3]:
|
|
2
|
+
# """
|
|
3
|
+
# Returns the contact manifold from an SAT OBB OBB collision
|
|
4
|
+
# """
|
|
5
|
+
# def get_test_points(contact_plane_normal:glm.vec3, points:list[glm.vec3], count: int):
|
|
6
|
+
# test_points = [(glm.dot(contact_plane_normal, p), p) for p in points]
|
|
7
|
+
# test_points.sort(key=lambda p: p[0])
|
|
8
|
+
# return [p[1] for p in test_points[:count]]
|
|
9
|
+
|
|
10
|
+
# def get_test_points_unknown(contact_plane_normal:glm.vec3, points:list[glm.vec3]):
|
|
11
|
+
# test_points = [(glm.dot(contact_plane_normal, p), p) for p in points]
|
|
12
|
+
# test_points.sort(key=lambda p: p[0])
|
|
13
|
+
# if test_points[2][0] - test_points[0][0] > 1e-3: return [p[1] for p in test_points[:2]]
|
|
14
|
+
# else: return [p[1] for p in test_points[:4]]
|
|
15
|
+
|
|
16
|
+
# if digit < 6: # there must be at least one face in the collision
|
|
17
|
+
# reference, incident = (get_test_points(-axis, points1, 4), get_test_points_unknown(axis, points2)) if digit < 3 else (get_test_points(axis, points2, 4), get_test_points_unknown(-axis, points1))
|
|
18
|
+
|
|
19
|
+
# # project vertices onto the 2d plane
|
|
20
|
+
# reference = project_points(plane_point, axis, reference)
|
|
21
|
+
# incident = project_points(plane_point, axis, incident)
|
|
22
|
+
|
|
23
|
+
# # convert points to 2d for intersection algorithms
|
|
24
|
+
# reference, u1, v1 = points_to_2d(plane_point, axis, reference)
|
|
25
|
+
# incident, u2, v2 = points_to_2d(plane_point, axis, incident, u1, v1)
|
|
26
|
+
|
|
27
|
+
# # convert arbitrary points to polygon
|
|
28
|
+
# reference = graham_scan(reference)
|
|
29
|
+
# if len(incident) == 4: incident = graham_scan(incident)
|
|
30
|
+
|
|
31
|
+
# # run clipping algorithms
|
|
32
|
+
# manifold = []
|
|
33
|
+
# if len(incident) == 2: manifold = line_poly_intersect(incident, reference)
|
|
34
|
+
# else: manifold = sutherland_hodgman(reference, incident)
|
|
35
|
+
|
|
36
|
+
# # # fall back if manifold fails to develope
|
|
37
|
+
# assert len(manifold), 'sat did not generate points'
|
|
38
|
+
|
|
39
|
+
# # # convert inertsection algorithm output to 3d
|
|
40
|
+
# return points_to_3d(u1, v1, plane_point, manifold)
|
|
41
|
+
|
|
42
|
+
# else: # there is an edge edge collision
|
|
43
|
+
|
|
44
|
+
# points1 = get_test_points(-axis, points1, 2)
|
|
45
|
+
# points2 = get_test_points(axis, points2, 2)
|
|
46
|
+
|
|
47
|
+
# return closest_two_lines(*points1, *points2)
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import glm
|
|
2
2
|
from .helper import get_support_point
|
|
3
|
+
from .dataclasses import SupportPoint
|
|
3
4
|
from...nodes.node import Node
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
# TODO change these to structs when converting to C++
|
|
7
8
|
face_type = list[tuple[float, glm.vec3, glm.vec3, int, int, int]] # distance, normal, center, index 1, index 2, index 3
|
|
8
|
-
polytope_type = list[
|
|
9
|
+
polytope_type = list[SupportPoint] # polytope vertex, node1 vertex, node2 vertex
|
|
9
10
|
|
|
10
|
-
def get_epa_from_gjk(node1: Node, node2: Node, polytope: polytope_type, epsilon: float=0) -> tuple: # TODO determine the return type of get_epa_from_gjk and if epsilon is good value
|
|
11
|
+
def get_epa_from_gjk(node1: Node, node2: Node, polytope: polytope_type, epsilon: float=0) -> tuple[face_type, polytope_type]: # TODO determine the return type of get_epa_from_gjk and if epsilon is good value
|
|
11
12
|
"""
|
|
12
13
|
Determines the peneration vector from a collision using EPA. The returned face normal is normalized but the rest are not guarunteed to be.
|
|
13
14
|
"""
|
|
@@ -18,7 +19,7 @@ def get_epa_from_gjk(node1: Node, node2: Node, polytope: polytope_type, epsilon:
|
|
|
18
19
|
# develope the polytope until the nearest real face has been found, within epsilon
|
|
19
20
|
while True:
|
|
20
21
|
new_point = get_support_point(node1, node2, faces[0][1])
|
|
21
|
-
if new_point in polytope or glm.length(new_point
|
|
22
|
+
if new_point in polytope or glm.length(new_point.support_point) - faces[0][0] < epsilon: return faces[0], polytope
|
|
22
23
|
faces, polytope = insert_point(polytope, faces, new_point)
|
|
23
24
|
|
|
24
25
|
def insert_point(polytope: polytope_type, faces: face_type, point: glm.vec3, epsilon: float=0) -> tuple[face_type, polytope_type]:
|
|
@@ -30,8 +31,8 @@ def insert_point(polytope: polytope_type, faces: face_type, point: glm.vec3, eps
|
|
|
30
31
|
support_index = len(polytope) - 1
|
|
31
32
|
visible_faces = [
|
|
32
33
|
face for face in faces
|
|
33
|
-
if glm.dot(face[1], polytope[support_index]
|
|
34
|
-
glm.dot(face[1], polytope[support_index]
|
|
34
|
+
if glm.dot(face[1], polytope[support_index].support_point) >= epsilon and # if the normal of a face is pointing towards the added point
|
|
35
|
+
glm.dot(face[1], polytope[support_index].support_point - face[2]) >= epsilon # TODO check if this ever occurs
|
|
35
36
|
]
|
|
36
37
|
|
|
37
38
|
# generate new edges
|
|
@@ -57,15 +58,15 @@ def insert_face(polytope: polytope_type, faces: face_type, indices: tuple[int, i
|
|
|
57
58
|
"""
|
|
58
59
|
Inserts a face into the face priority queue based on the indices given in the polytope
|
|
59
60
|
"""
|
|
60
|
-
center = (polytope[indices[0]]
|
|
61
|
-
normal = glm.cross(polytope[indices[1]]
|
|
61
|
+
center = (polytope[indices[0]].support_point + polytope[indices[1]].support_point + polytope[indices[2]].support_point) / 3
|
|
62
|
+
normal = glm.cross(polytope[indices[1]].support_point - polytope[indices[0]].support_point, polytope[indices[2]].support_point - polytope[indices[0]].support_point) # closest face normal will be normalized once returned to avoid square roots and division
|
|
62
63
|
if glm.dot(center, normal) < 0:
|
|
63
64
|
normal *= -1
|
|
64
65
|
indices = (indices[2], indices[1], indices[0])
|
|
65
66
|
|
|
66
67
|
# TODO solve cases where face may contain origin
|
|
67
68
|
normal = glm.normalize(normal)
|
|
68
|
-
distance = abs(glm.dot(polytope[indices[0]]
|
|
69
|
+
distance = abs(glm.dot(polytope[indices[0]].support_point, normal))
|
|
69
70
|
new_face = (distance, normal, center, *indices)
|
|
70
71
|
|
|
71
72
|
# insert faces into priority queue based on distance from origin
|
|
@@ -77,11 +78,11 @@ def insert_face(polytope: polytope_type, faces: face_type, indices: tuple[int, i
|
|
|
77
78
|
|
|
78
79
|
return faces
|
|
79
80
|
|
|
80
|
-
def orient_face(polytope: polytope_type, indices: tuple[int, int, int]) -> tuple[int, int, int]:
|
|
81
|
+
def orient_face(polytope: polytope_type, indices: tuple[int, int, int]) -> tuple[int, int, int]:
|
|
81
82
|
"""
|
|
82
83
|
Orients the face indices to have a counter clockwise normal
|
|
83
84
|
"""
|
|
84
|
-
if glm.dot(glm.cross(polytope[indices[1]]
|
|
85
|
+
if glm.dot(glm.cross(polytope[indices[1]].support_point, polytope[indices[2]].support_point), polytope[indices[0]].support_point) < 0: return (indices[2], indices[1], indices[0])
|
|
85
86
|
return indices
|
|
86
87
|
|
|
87
88
|
def get_face_edges(face: tuple[float, glm.vec3, glm.vec3, int, int, int]) -> list[tuple[int, int]]:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import glm
|
|
2
2
|
from .helper import get_support_point
|
|
3
|
+
from .dataclasses import SupportPoint
|
|
3
4
|
from ...nodes.node import Node
|
|
4
5
|
from ...generic.math import triple_product
|
|
5
6
|
|
|
@@ -11,12 +12,12 @@ def collide_gjk(node1: Node, node2: Node, iterations: int=20) -> tuple: # TODO f
|
|
|
11
12
|
# generate starting values
|
|
12
13
|
dir_vec = node1.position - node2.position
|
|
13
14
|
simplex = [get_support_point(node1, node2, dir_vec)]
|
|
14
|
-
dir_vec = -simplex[0]
|
|
15
|
+
dir_vec = -simplex[0].support_point # set direction to point away from starting simplex point
|
|
15
16
|
|
|
16
17
|
for _ in range(iterations):
|
|
17
18
|
# gets support point and checks if its across the origin
|
|
18
19
|
test_point = get_support_point(node1, node2, dir_vec)
|
|
19
|
-
if glm.dot(test_point
|
|
20
|
+
if glm.dot(test_point.support_point, dir_vec) < -1e-7: return False, simplex
|
|
20
21
|
|
|
21
22
|
# add point and find new direction vector
|
|
22
23
|
simplex.append(test_point)
|
|
@@ -25,7 +26,7 @@ def collide_gjk(node1: Node, node2: Node, iterations: int=20) -> tuple: # TODO f
|
|
|
25
26
|
if check: return True, simplex
|
|
26
27
|
return False, simplex # timeout due to too many checks, usually float errors
|
|
27
28
|
|
|
28
|
-
def handle_simplex(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
|
|
29
|
+
def handle_simplex(simplex: list[SupportPoint]) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
|
|
29
30
|
"""
|
|
30
31
|
Call proper function based on number of support points
|
|
31
32
|
"""
|
|
@@ -34,28 +35,28 @@ def handle_simplex(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3,
|
|
|
34
35
|
if num == 3: return handle_simplex_triangle(simplex)
|
|
35
36
|
return handle_simplex_tetrahedron(simplex) # simplex must be 4 points
|
|
36
37
|
|
|
37
|
-
def handle_simplex_line(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
|
|
38
|
+
def handle_simplex_line(simplex: list[SupportPoint]) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
|
|
38
39
|
"""
|
|
39
40
|
Returns the perpendicular vector to the simplex line
|
|
40
41
|
"""
|
|
41
|
-
vec_ab = simplex[1]
|
|
42
|
-
return False, triple_product(vec_ab, -simplex[0]
|
|
42
|
+
vec_ab = simplex[1].support_point - simplex[0].support_point
|
|
43
|
+
return False, triple_product(vec_ab, -simplex[0].support_point, vec_ab), simplex
|
|
43
44
|
|
|
44
|
-
def handle_simplex_triangle(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
|
|
45
|
+
def handle_simplex_triangle(simplex: list[SupportPoint]) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
|
|
45
46
|
"""
|
|
46
47
|
Returns the normal vector of the triangoe pointed towards the origin
|
|
47
48
|
"""
|
|
48
|
-
dir_vec = glm.cross(simplex[1]
|
|
49
|
-
return False, -dir_vec if glm.dot(dir_vec, -simplex[0]
|
|
49
|
+
dir_vec = glm.cross(simplex[1].support_point - simplex[0].support_point, simplex[2].support_point - simplex[0].support_point)
|
|
50
|
+
return False, -dir_vec if glm.dot(dir_vec, -simplex[0].support_point) < 0 else dir_vec, simplex
|
|
50
51
|
|
|
51
|
-
def handle_simplex_tetrahedron(simplex: list, epsilon: float=0) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
|
|
52
|
+
def handle_simplex_tetrahedron(simplex: list[SupportPoint], epsilon: float=0) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
|
|
52
53
|
"""
|
|
53
54
|
Perform collision check and remove support point if no collision is found
|
|
54
55
|
"""
|
|
55
|
-
vec_da = simplex[3]
|
|
56
|
-
vec_db = simplex[3]
|
|
57
|
-
vec_dc = simplex[3]
|
|
58
|
-
vec_do = -simplex[3]
|
|
56
|
+
vec_da = simplex[3].support_point - simplex[0].support_point
|
|
57
|
+
vec_db = simplex[3].support_point - simplex[1].support_point
|
|
58
|
+
vec_dc = simplex[3].support_point - simplex[2].support_point
|
|
59
|
+
vec_do = -simplex[3].support_point
|
|
59
60
|
|
|
60
61
|
vectors = [(glm.cross(vec_da, vec_db), 2), (glm.cross(vec_dc, vec_da), 1), (glm.cross(vec_db, vec_dc), 0)] # TODO determine if this is the best way to do this
|
|
61
62
|
for normal_vec, index in vectors:
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import glm
|
|
2
2
|
from ...nodes.node import Node
|
|
3
|
+
from .dataclasses import SupportPoint
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
def get_support_point(node1: Node, node2: Node, dir_vec: glm.vec3) -> tuple[glm.vec3, glm.vec3, glm.vec3]:
|
|
5
|
+
def get_support_point(node1: Node, node2: Node, dir_vec: glm.vec3) -> SupportPoint:
|
|
6
6
|
"""
|
|
7
7
|
Outputs the best support point to be added to the polytop based on the direction vector.
|
|
8
8
|
"""
|
|
9
|
-
vertex1 = get_furthest_point(node1, dir_vec)
|
|
10
|
-
vertex2 = get_furthest_point(node2, -dir_vec)
|
|
11
|
-
return (vertex1 - vertex2, vertex1, vertex2)
|
|
9
|
+
vertex1, index1 = get_furthest_point(node1, dir_vec)
|
|
10
|
+
vertex2, index2 = get_furthest_point(node2, -dir_vec)
|
|
11
|
+
return SupportPoint(vertex1 - vertex2, index1, vertex1, index2, vertex2)
|
|
12
12
|
|
|
13
13
|
def get_furthest_point(node: Node, dir_vec: glm.vec3) -> glm.vec3:
|
|
14
14
|
"""
|
|
@@ -16,11 +16,12 @@ def get_furthest_point(node: Node, dir_vec: glm.vec3) -> glm.vec3:
|
|
|
16
16
|
"""
|
|
17
17
|
# determine furthest point by using untransformed mesh
|
|
18
18
|
node_dir_vec = node.rotation * dir_vec # rotate the world space vector to node space
|
|
19
|
-
|
|
19
|
+
index = node.mesh.get_best_dot(node_dir_vec)
|
|
20
|
+
vertex = node.mesh.points[index]
|
|
20
21
|
vertex = node.model_matrix * glm.vec4(vertex, 1.0)
|
|
21
22
|
|
|
22
23
|
# transform point to world space
|
|
23
|
-
return glm.vec3(vertex)
|
|
24
|
+
return glm.vec3(vertex), index
|
|
24
25
|
|
|
25
26
|
def is_ccw_turn(a:glm.vec2, b:glm.vec2, c:glm.vec2) -> bool:
|
|
26
27
|
"""
|
|
@@ -22,3 +22,55 @@ def sutherland_hodgman(subject:list[glm.vec2], clip:list[glm.vec2]) -> list[glm.
|
|
|
22
22
|
output_poly += line_line_intersect([edge_end, edge_start], [prev_point, curr_point])
|
|
23
23
|
|
|
24
24
|
return output_poly
|
|
25
|
+
|
|
26
|
+
# def get_intersect(one: glm.vec2, two: glm.vec2, thr: glm.vec2, fou: glm.vec2) -> glm.vec2:
|
|
27
|
+
# """
|
|
28
|
+
# Gets the intersection point between two lines
|
|
29
|
+
# """
|
|
30
|
+
# deno = (one.x - two.x) * (thr.y - fou.y) - (one.y - two.y) * (thr.x - fou.x)
|
|
31
|
+
# if deno == 0: # TODO determine if this happens
|
|
32
|
+
# print('sutherland-hodgman line intersection had zero denominator')
|
|
33
|
+
# return None
|
|
34
|
+
# x_num = (one.x * two.y - one.y * two.x) * (thr.x - fou.x) - (one.x - two.x) * (thr.x * fou.y - thr.y * fou.x)
|
|
35
|
+
# y_num = (one.x * two.y - one.y * two.x) * (thr.y - fou.y) - (one.y - two.y) * (thr.x * fou.y - thr.y * fou.x)
|
|
36
|
+
# return glm.vec2(x_num / deno, y_num / deno)
|
|
37
|
+
|
|
38
|
+
# def clip(poly: list[glm.vec2], one: glm.vec2, two: glm.vec2) -> list[glm.vec2]:
|
|
39
|
+
# """
|
|
40
|
+
# Clip all edges of polygon with one of the clipping edges
|
|
41
|
+
# """
|
|
42
|
+
# num_points = len(poly)
|
|
43
|
+
# new_points = []
|
|
44
|
+
|
|
45
|
+
# for i in range(num_points):
|
|
46
|
+
# k = (i + 1) % num_points
|
|
47
|
+
# veci = poly[i]
|
|
48
|
+
# veck = poly[k]
|
|
49
|
+
|
|
50
|
+
# posi = (two.x - one.x) * (veci.y - one.y) - (two.y - one.y) * (veci.x - one.x)
|
|
51
|
+
# posk = (two.x - one.x) * (veck.y - one.y) - (two.y - one.y) * (veck.x - one.x)
|
|
52
|
+
|
|
53
|
+
# if posi < 0 and posk < 0: new_points.append(veck)
|
|
54
|
+
# elif posi >= 0 and posk < 0:
|
|
55
|
+
|
|
56
|
+
# new_points.append(get_intersect(one, two, veci, veck))
|
|
57
|
+
# new_points.append(veck)
|
|
58
|
+
|
|
59
|
+
# elif posi < 0 and posk >= 0:
|
|
60
|
+
|
|
61
|
+
# new_points.append(get_intersect(one, two, veci, veck))
|
|
62
|
+
|
|
63
|
+
# return new_points
|
|
64
|
+
|
|
65
|
+
# def sutherland_hodgman(subj_poly:list[glm.vec2], clip_poly:list[glm.vec2]) -> list[glm.vec2]:
|
|
66
|
+
# """
|
|
67
|
+
# Determines the clipped polygon vertices from ccw oriented polygons.
|
|
68
|
+
# """
|
|
69
|
+
# num_clip = len(clip_poly)
|
|
70
|
+
|
|
71
|
+
# for i in range(num_clip):
|
|
72
|
+
# k = (i + 1) % num_clip
|
|
73
|
+
|
|
74
|
+
# subj_poly = clip(subj_poly, clip_poly[i], clip_poly[k])
|
|
75
|
+
|
|
76
|
+
# return subj_poly
|