basilisk-engine 0.1.38__py3-none-any.whl → 0.1.39__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.

Files changed (97) hide show
  1. basilisk/__init__.py +26 -26
  2. basilisk/audio/sound.py +27 -27
  3. basilisk/bsk_assets/cube.obj +48 -48
  4. basilisk/collisions/broad/broad_aabb.py +102 -102
  5. basilisk/collisions/broad/broad_bvh.py +137 -137
  6. basilisk/collisions/collider.py +95 -95
  7. basilisk/collisions/collider_handler.py +226 -226
  8. basilisk/collisions/narrow/contact_manifold.py +95 -95
  9. basilisk/collisions/narrow/dataclasses.py +34 -34
  10. basilisk/collisions/narrow/deprecated.py +46 -46
  11. basilisk/collisions/narrow/epa.py +91 -91
  12. basilisk/collisions/narrow/gjk.py +66 -66
  13. basilisk/collisions/narrow/graham_scan.py +24 -24
  14. basilisk/collisions/narrow/helper.py +29 -29
  15. basilisk/collisions/narrow/line_intersections.py +106 -106
  16. basilisk/collisions/narrow/sutherland_hodgman.py +75 -75
  17. basilisk/config.py +53 -53
  18. basilisk/draw/draw.py +100 -100
  19. basilisk/draw/draw_handler.py +178 -178
  20. basilisk/draw/font_renderer.py +28 -28
  21. basilisk/engine.py +165 -165
  22. basilisk/generic/abstract_bvh.py +15 -15
  23. basilisk/generic/abstract_custom.py +133 -133
  24. basilisk/generic/collisions.py +70 -70
  25. basilisk/generic/input_validation.py +82 -82
  26. basilisk/generic/math.py +18 -7
  27. basilisk/generic/matrices.py +35 -35
  28. basilisk/generic/meshes.py +72 -72
  29. basilisk/generic/quat.py +142 -142
  30. basilisk/generic/quat_methods.py +7 -7
  31. basilisk/generic/raycast_result.py +26 -26
  32. basilisk/generic/vec3.py +143 -143
  33. basilisk/input/__init__.py +0 -0
  34. basilisk/input/mouse.py +62 -0
  35. basilisk/input/path.py +14 -0
  36. basilisk/input_output/IO_handler.py +91 -91
  37. basilisk/input_output/clock.py +49 -49
  38. basilisk/input_output/keys.py +43 -43
  39. basilisk/input_output/mouse.py +90 -90
  40. basilisk/input_output/path.py +14 -14
  41. basilisk/mesh/cube.py +33 -33
  42. basilisk/mesh/mesh.py +233 -233
  43. basilisk/mesh/mesh_from_data.py +150 -150
  44. basilisk/mesh/model.py +271 -271
  45. basilisk/mesh/narrow_aabb.py +89 -89
  46. basilisk/mesh/narrow_bvh.py +91 -91
  47. basilisk/mesh/narrow_primative.py +23 -23
  48. basilisk/nodes/helper.py +28 -28
  49. basilisk/nodes/node.py +709 -704
  50. basilisk/nodes/node_handler.py +97 -97
  51. basilisk/particles/particle_handler.py +64 -64
  52. basilisk/particles/particle_renderer.py +93 -93
  53. basilisk/physics/impulse.py +112 -112
  54. basilisk/physics/physics_body.py +43 -43
  55. basilisk/physics/physics_engine.py +35 -35
  56. basilisk/render/batch.py +103 -103
  57. basilisk/render/bloom.py +107 -107
  58. basilisk/render/camera.py +260 -260
  59. basilisk/render/chunk.py +108 -108
  60. basilisk/render/chunk_handler.py +167 -167
  61. basilisk/render/frame.py +110 -110
  62. basilisk/render/framebuffer.py +202 -202
  63. basilisk/render/image.py +120 -120
  64. basilisk/render/image_handler.py +120 -120
  65. basilisk/render/light.py +96 -96
  66. basilisk/render/light_handler.py +58 -58
  67. basilisk/render/material.py +232 -232
  68. basilisk/render/material_handler.py +133 -133
  69. basilisk/render/post_process.py +146 -146
  70. basilisk/render/shader.py +134 -134
  71. basilisk/render/shader_handler.py +85 -85
  72. basilisk/render/sky.py +120 -120
  73. basilisk/scene.py +290 -290
  74. basilisk/shaders/batch.frag +289 -289
  75. basilisk/shaders/batch.vert +117 -117
  76. basilisk/shaders/bloom_downsample.frag +42 -42
  77. basilisk/shaders/bloom_frame.frag +24 -24
  78. basilisk/shaders/bloom_upsample.frag +33 -33
  79. basilisk/shaders/crt.frag +31 -31
  80. basilisk/shaders/draw.frag +25 -25
  81. basilisk/shaders/draw.vert +25 -25
  82. basilisk/shaders/filter.frag +22 -22
  83. basilisk/shaders/frame.frag +12 -12
  84. basilisk/shaders/frame.vert +13 -13
  85. basilisk/shaders/geometry.frag +10 -10
  86. basilisk/shaders/geometry.vert +41 -41
  87. basilisk/shaders/normal.frag +62 -62
  88. basilisk/shaders/normal.vert +96 -96
  89. basilisk/shaders/particle.frag +76 -76
  90. basilisk/shaders/particle.vert +86 -86
  91. basilisk/shaders/sky.frag +23 -23
  92. basilisk/shaders/sky.vert +13 -13
  93. {basilisk_engine-0.1.38.dist-info → basilisk_engine-0.1.39.dist-info}/METADATA +89 -89
  94. basilisk_engine-0.1.39.dist-info/RECORD +114 -0
  95. {basilisk_engine-0.1.38.dist-info → basilisk_engine-0.1.39.dist-info}/WHEEL +1 -1
  96. basilisk_engine-0.1.38.dist-info/RECORD +0 -111
  97. {basilisk_engine-0.1.38.dist-info → basilisk_engine-0.1.39.dist-info}/top_level.txt +0 -0
@@ -1,226 +1,226 @@
1
- import glm
2
-
3
- from .collider import Collider
4
- from .broad.broad_bvh import BroadBVH
5
- from .narrow.gjk import collide_gjk
6
- from .narrow.epa import get_epa_from_gjk
7
- from .narrow.contact_manifold import get_contact_manifold, separate_polytope
8
- from .narrow.dataclasses import ContactPoint, ContactManifold, Collision
9
- from ..nodes.node import Node
10
- from ..generic.collisions import get_sat_axes
11
- from ..physics.impulse import calculate_collisions
12
-
13
- class ColliderHandler():
14
- scene: ...
15
- """Back reference to scene"""
16
- colliders: list[Collider]
17
- """Main list of collders contained in the scene"""
18
- bvh: BroadBVH
19
- """Broad bottom up BVH containing all colliders in the scene"""
20
-
21
- def __init__(self, scene) -> None:
22
- self.scene = scene
23
- self.cube = self.scene.engine.cube
24
- self.colliders = []
25
- self.polytope_data = {}
26
- self.contact_manifolds: dict[tuple[Collider, Collider] : ContactManifold] = {}
27
- self.bvh = BroadBVH(self)
28
-
29
- def add(self, collider: Collider) -> Collider:
30
- """
31
- Creates a collider and adds it to the collider list
32
- """
33
- self.colliders.append(collider)
34
- self.bvh.add(collider)
35
- return collider
36
-
37
- def remove(self, collider: Collider) -> None:
38
- """
39
- Removes a collider from the main branch and BVH
40
- """
41
- if collider in self.colliders: self.colliders.remove(collider)
42
- self.bvh.remove(collider)
43
- collider.collider_handler = None
44
-
45
- def resolve_collisions(self) -> None:
46
- """
47
- Resets collider collision values and resolves all collisions in the scene
48
- """
49
- # reset collision data
50
- for collider in self.colliders: collider.collisions = []
51
-
52
- # update BVH
53
- for collider in self.colliders:
54
- if collider.needs_bvh:
55
- self.bvh.remove(collider)
56
- self.bvh.add(collider)
57
- collider.needs_bvh = False
58
-
59
- # resolve collisions
60
- broad_collisions = self.resolve_broad_collisions()
61
- self.resolve_narrow_collisions(broad_collisions)
62
-
63
- def collide_obb_obb(self, collider1: Collider, collider2: Collider) -> tuple[glm.vec3, float] | None:
64
- """
65
- Finds the minimal penetrating vector for an obb obb collision, return None if not colliding. Uses SAT.
66
- """
67
- axes = get_sat_axes(collider1.node.rotation, collider2.node.rotation) # axes are normaized
68
- points1 = collider1.obb_points # TODO remove once oobb points are lazy updated, switch to just using property
69
- points2 = collider2.obb_points
70
-
71
- # test axes
72
- small_axis = None
73
- small_overlap = 1e10
74
- small_index = 0
75
- for i, axis in enumerate(axes): # TODO add optimization for points on cardinal axis of cuboid
76
- # "project" points
77
- proj1 = [glm.dot(p, axis) for p in points1]
78
- proj2 = [glm.dot(p, axis) for p in points2]
79
- max1, min1 = max(proj1), min(proj1)
80
- max2, min2 = max(proj2), min(proj2)
81
- if max1 < min2 or max2 < min1: return None
82
-
83
- # if lines are not intersecting
84
- if max1 > max2 and min1 < min2: overlap = min(max1 - min2, max2 - min1)
85
- elif max2 > max1 and min2 < min1: overlap = min(max2 - min1, max1 - min2)
86
- else: overlap = min(max1, max2) - max(min1, min2) # TODO check if works with containment
87
-
88
- if abs(overlap) > abs(small_overlap): continue
89
- small_overlap = overlap
90
- small_axis = axis
91
- small_index = i
92
-
93
- return small_axis, small_overlap, small_index
94
-
95
- def collide_obb_obb_decision(self, collider1: Collider, collider2: Collider) -> bool:
96
- """
97
- Determines if two obbs are colliding Uses SAT.
98
- """
99
- axes = get_sat_axes(collider1.node.rotation, collider2.node.rotation)
100
- points1 = collider1.obb_points # TODO remove once oobb points are lazy updated, switch to just using property
101
- points2 = collider2.obb_points
102
-
103
- # test axes
104
- for axis in axes: # TODO add optimization for points on cardinal axis of cuboid
105
- # "project" points
106
- proj1 = [glm.dot(p, axis) for p in points1]
107
- proj2 = [glm.dot(p, axis) for p in points2]
108
- max1, min1 = max(proj1), min(proj1)
109
- max2, min2 = max(proj2), min(proj2)
110
- if max1 < min2 or max2 < min1: return False
111
-
112
- return True
113
-
114
- def resolve_broad_collisions(self) -> set[tuple[Collider, Collider]]:
115
- """
116
- Determines which colliders collide with each other from the BVH
117
- """
118
- collisions = set()
119
- for collider1 in self.colliders:
120
- if collider1.node.static: continue
121
- # traverse bvh to find aabb aabb collisions
122
- colliding = self.bvh.get_collided(collider1)
123
- for collider2 in colliding:
124
- if collider1 is collider2 or (collider1.collision_group is not None and collider1.collision_group == collider2.collision_group): continue
125
- if ((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1)) in collisions: continue
126
-
127
- # run broad collision for specified mesh types
128
- 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
129
- collisions.add((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1))
130
-
131
- return collisions
132
-
133
- def merge_contact_points(self, vec: glm.vec3, collider1: Collider, collider2: Collider, points1: list[ContactPoint], points2: list[ContactPoint]) -> None:
134
- """
135
-
136
- """
137
- def merge_points(node: Node, existing: dict[int, glm.vec3], incoming: list[ContactPoint]) -> dict[int, glm.vec3]:
138
- incoming_indices = set()
139
-
140
- # add incoming points
141
- for point in incoming:
142
- incoming_indices.add(point.index)
143
- if point.index not in existing or glm.length2(point.vertex - existing[point.index]) > 1e-5: existing[point.index] = glm.vec3(point.vertex)
144
-
145
- # remove changed stored points
146
- remove_indices = []
147
- for index, vertex in existing.items():
148
- if index in incoming_indices: continue
149
- if glm.length2(node.collider.get_vertex(index) - vertex) > 1e-5: remove_indices.append(index) # check to see if point has moved
150
-
151
- # remove unused and moved points
152
- for index in remove_indices: del existing[index]
153
- return existing
154
-
155
- # check if collision is logged, if not create a new one
156
- collider_tuple = (collider1, collider2)
157
- 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())
158
-
159
- # add contact point from current collision and check overlap
160
- self.contact_manifolds[collider_tuple].contact_points1 = merge_points(collider1.node, self.contact_manifolds[collider_tuple].contact_points1, points1)
161
- self.contact_manifolds[collider_tuple].contact_points2 = merge_points(collider2.node, self.contact_manifolds[collider_tuple].contact_points2, points2)
162
-
163
- def resolve_narrow_collisions(self, broad_collisions: list[tuple[Collider, Collider]]) -> None:
164
- """
165
- Determines if two colliders are colliding, if so resolves their penetration and applies impulse
166
- """
167
- for collision in broad_collisions: # assumes that broad collisions are unique
168
- collider1 = collision[0]
169
- collider2 = collision[1]
170
- node1: Node = collider1.node
171
- node2: Node = collider2.node
172
-
173
- # get peneration data or quit early if no collision is found
174
- if collider1.mesh == self.cube and collider2.mesh == self.cube: # obb-obb collision
175
-
176
- # run SAT for obb-obb (includes peneration)
177
- data = self.collide_obb_obb(collider1, collider2)
178
- if not data: continue
179
-
180
- vec, distance, index = data
181
-
182
- # TODO replace with own contact algorithm
183
- points1 = [ContactPoint(index, vertex) for index, vertex in enumerate(collider1.obb_points)]
184
- points2 = [ContactPoint(index, vertex) for index, vertex in enumerate(collider2.obb_points)]
185
-
186
- else: # use gjk to determine collisions between non-cuboid meshes
187
- has_collided, simplex = collide_gjk(node1, node2)
188
- if not has_collided: continue
189
-
190
- faces, polytope = get_epa_from_gjk(node1, node2, simplex)
191
- face = faces[0]
192
- vec, distance = face[1], face[0]
193
-
194
- # TODO replace with own contact algorithm
195
- points1 = [ContactPoint(p.index1, p.vertex1) for p in polytope]
196
- points2 = [ContactPoint(p.index2, p.vertex2) for p in polytope]
197
-
198
- if glm.dot(vec, node2.position.data - node1.position.data) > 0: vec *= -1
199
-
200
- # add collision data to colliders
201
- collider1.collisions.append(Collision(node2, vec))
202
- collider2.collisions.append(Collision(node1, -vec))
203
-
204
- # apply impulse if a collider has a physic body
205
- if node1.physics_body or node2.physics_body:
206
-
207
- # determine the contact points from the collision
208
- points1, points2 = separate_polytope(points1, points2, vec)
209
- self.merge_contact_points(vec, collider1, collider2, points1, points2)
210
-
211
- collider_tuple = (collider1, collider2)
212
- manifold = get_contact_manifold(
213
- node1.position.data - 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
- # resolve collision penetration
224
- multiplier = 0.5 if not (node1.static or node2.static) else 1
225
- if not node1.static: node1.position += multiplier * vec * distance
226
- if not node2.static: node2.position -= multiplier * vec * distance
1
+ import glm
2
+
3
+ from .collider import Collider
4
+ from .broad.broad_bvh import BroadBVH
5
+ from .narrow.gjk import collide_gjk
6
+ from .narrow.epa import get_epa_from_gjk
7
+ from .narrow.contact_manifold import get_contact_manifold, separate_polytope
8
+ from .narrow.dataclasses import ContactPoint, ContactManifold, Collision
9
+ from ..nodes.node import Node
10
+ from ..generic.collisions import get_sat_axes
11
+ from ..physics.impulse import calculate_collisions
12
+
13
+ class ColliderHandler():
14
+ scene: ...
15
+ """Back reference to scene"""
16
+ colliders: list[Collider]
17
+ """Main list of collders contained in the scene"""
18
+ bvh: BroadBVH
19
+ """Broad bottom up BVH containing all colliders in the scene"""
20
+
21
+ def __init__(self, scene) -> None:
22
+ self.scene = scene
23
+ self.cube = self.scene.engine.cube
24
+ self.colliders = []
25
+ self.polytope_data = {}
26
+ self.contact_manifolds: dict[tuple[Collider, Collider] : ContactManifold] = {}
27
+ self.bvh = BroadBVH(self)
28
+
29
+ def add(self, collider: Collider) -> Collider:
30
+ """
31
+ Creates a collider and adds it to the collider list
32
+ """
33
+ self.colliders.append(collider)
34
+ self.bvh.add(collider)
35
+ return collider
36
+
37
+ def remove(self, collider: Collider) -> None:
38
+ """
39
+ Removes a collider from the main branch and BVH
40
+ """
41
+ if collider in self.colliders: self.colliders.remove(collider)
42
+ self.bvh.remove(collider)
43
+ collider.collider_handler = None
44
+
45
+ def resolve_collisions(self) -> None:
46
+ """
47
+ Resets collider collision values and resolves all collisions in the scene
48
+ """
49
+ # reset collision data
50
+ for collider in self.colliders: collider.collisions = []
51
+
52
+ # update BVH
53
+ for collider in self.colliders:
54
+ if collider.needs_bvh:
55
+ self.bvh.remove(collider)
56
+ self.bvh.add(collider)
57
+ collider.needs_bvh = False
58
+
59
+ # resolve collisions
60
+ broad_collisions = self.resolve_broad_collisions()
61
+ self.resolve_narrow_collisions(broad_collisions)
62
+
63
+ def collide_obb_obb(self, collider1: Collider, collider2: Collider) -> tuple[glm.vec3, float] | None:
64
+ """
65
+ Finds the minimal penetrating vector for an obb obb collision, return None if not colliding. Uses SAT.
66
+ """
67
+ axes = get_sat_axes(collider1.node.rotation, collider2.node.rotation) # axes are normaized
68
+ points1 = collider1.obb_points # TODO remove once oobb points are lazy updated, switch to just using property
69
+ points2 = collider2.obb_points
70
+
71
+ # test axes
72
+ small_axis = None
73
+ small_overlap = 1e10
74
+ small_index = 0
75
+ for i, axis in enumerate(axes): # TODO add optimization for points on cardinal axis of cuboid
76
+ # "project" points
77
+ proj1 = [glm.dot(p, axis) for p in points1]
78
+ proj2 = [glm.dot(p, axis) for p in points2]
79
+ max1, min1 = max(proj1), min(proj1)
80
+ max2, min2 = max(proj2), min(proj2)
81
+ if max1 < min2 or max2 < min1: return None
82
+
83
+ # if lines are not intersecting
84
+ if max1 > max2 and min1 < min2: overlap = min(max1 - min2, max2 - min1)
85
+ elif max2 > max1 and min2 < min1: overlap = min(max2 - min1, max1 - min2)
86
+ else: overlap = min(max1, max2) - max(min1, min2) # TODO check if works with containment
87
+
88
+ if abs(overlap) > abs(small_overlap): continue
89
+ small_overlap = overlap
90
+ small_axis = axis
91
+ small_index = i
92
+
93
+ return small_axis, small_overlap, small_index
94
+
95
+ def collide_obb_obb_decision(self, collider1: Collider, collider2: Collider) -> bool:
96
+ """
97
+ Determines if two obbs are colliding Uses SAT.
98
+ """
99
+ axes = get_sat_axes(collider1.node.rotation, collider2.node.rotation)
100
+ points1 = collider1.obb_points # TODO remove once oobb points are lazy updated, switch to just using property
101
+ points2 = collider2.obb_points
102
+
103
+ # test axes
104
+ for axis in axes: # TODO add optimization for points on cardinal axis of cuboid
105
+ # "project" points
106
+ proj1 = [glm.dot(p, axis) for p in points1]
107
+ proj2 = [glm.dot(p, axis) for p in points2]
108
+ max1, min1 = max(proj1), min(proj1)
109
+ max2, min2 = max(proj2), min(proj2)
110
+ if max1 < min2 or max2 < min1: return False
111
+
112
+ return True
113
+
114
+ def resolve_broad_collisions(self) -> set[tuple[Collider, Collider]]:
115
+ """
116
+ Determines which colliders collide with each other from the BVH
117
+ """
118
+ collisions = set()
119
+ for collider1 in self.colliders:
120
+ if collider1.node.static: continue
121
+ # traverse bvh to find aabb aabb collisions
122
+ colliding = self.bvh.get_collided(collider1)
123
+ for collider2 in colliding:
124
+ if collider1 is collider2 or (collider1.collision_group is not None and collider1.collision_group == collider2.collision_group): continue
125
+ if ((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1)) in collisions: continue
126
+
127
+ # run broad collision for specified mesh types
128
+ 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
129
+ collisions.add((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1))
130
+
131
+ return collisions
132
+
133
+ def merge_contact_points(self, vec: glm.vec3, collider1: Collider, collider2: Collider, points1: list[ContactPoint], points2: list[ContactPoint]) -> None:
134
+ """
135
+
136
+ """
137
+ def merge_points(node: Node, existing: dict[int, glm.vec3], incoming: list[ContactPoint]) -> dict[int, glm.vec3]:
138
+ incoming_indices = set()
139
+
140
+ # add incoming points
141
+ for point in incoming:
142
+ incoming_indices.add(point.index)
143
+ if point.index not in existing or glm.length2(point.vertex - existing[point.index]) > 1e-5: existing[point.index] = glm.vec3(point.vertex)
144
+
145
+ # remove changed stored points
146
+ remove_indices = []
147
+ for index, vertex in existing.items():
148
+ if index in incoming_indices: continue
149
+ if glm.length2(node.collider.get_vertex(index) - vertex) > 1e-5: remove_indices.append(index) # check to see if point has moved
150
+
151
+ # remove unused and moved points
152
+ for index in remove_indices: del existing[index]
153
+ return existing
154
+
155
+ # check if collision is logged, if not create a new one
156
+ collider_tuple = (collider1, collider2)
157
+ 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())
158
+
159
+ # add contact point from current collision and check overlap
160
+ self.contact_manifolds[collider_tuple].contact_points1 = merge_points(collider1.node, self.contact_manifolds[collider_tuple].contact_points1, points1)
161
+ self.contact_manifolds[collider_tuple].contact_points2 = merge_points(collider2.node, self.contact_manifolds[collider_tuple].contact_points2, points2)
162
+
163
+ def resolve_narrow_collisions(self, broad_collisions: list[tuple[Collider, Collider]]) -> None:
164
+ """
165
+ Determines if two colliders are colliding, if so resolves their penetration and applies impulse
166
+ """
167
+ for collision in broad_collisions: # assumes that broad collisions are unique
168
+ collider1 = collision[0]
169
+ collider2 = collision[1]
170
+ node1: Node = collider1.node
171
+ node2: Node = collider2.node
172
+
173
+ # get peneration data or quit early if no collision is found
174
+ if collider1.mesh == self.cube and collider2.mesh == self.cube: # obb-obb collision
175
+
176
+ # run SAT for obb-obb (includes peneration)
177
+ data = self.collide_obb_obb(collider1, collider2)
178
+ if not data: continue
179
+
180
+ vec, distance, index = data
181
+
182
+ # TODO replace with own contact algorithm
183
+ points1 = [ContactPoint(index, vertex) for index, vertex in enumerate(collider1.obb_points)]
184
+ points2 = [ContactPoint(index, vertex) for index, vertex in enumerate(collider2.obb_points)]
185
+
186
+ else: # use gjk to determine collisions between non-cuboid meshes
187
+ has_collided, simplex = collide_gjk(node1, node2)
188
+ if not has_collided: continue
189
+
190
+ faces, polytope = get_epa_from_gjk(node1, node2, simplex)
191
+ face = faces[0]
192
+ vec, distance = face[1], face[0]
193
+
194
+ # TODO replace with own contact algorithm
195
+ points1 = [ContactPoint(p.index1, p.vertex1) for p in polytope]
196
+ points2 = [ContactPoint(p.index2, p.vertex2) for p in polytope]
197
+
198
+ if glm.dot(vec, node2.position.data - node1.position.data) > 0: vec *= -1
199
+
200
+ # add collision data to colliders
201
+ collider1.collisions.append(Collision(node2, vec))
202
+ collider2.collisions.append(Collision(node1, -vec))
203
+
204
+ # apply impulse if a collider has a physic body
205
+ if node1.physics_body or node2.physics_body:
206
+
207
+ # determine the contact points from the collision
208
+ points1, points2 = separate_polytope(points1, points2, vec)
209
+ self.merge_contact_points(vec, collider1, collider2, points1, points2)
210
+
211
+ collider_tuple = (collider1, collider2)
212
+ manifold = get_contact_manifold(
213
+ node1.position.data - 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
+ # resolve collision penetration
224
+ multiplier = 0.5 if not (node1.static or node2.static) else 1
225
+ if not node1.static: node1.position.data += multiplier * vec * distance
226
+ if not node2.static: node2.position.data -= multiplier * vec * distance
@@ -1,96 +1,96 @@
1
- import glm
2
- from random import randint
3
- from .line_intersections import line_line_intersect, line_poly_intersect
4
- from .graham_scan import graham_scan
5
- from .sutherland_hodgman import sutherland_hodgman
6
- from .dataclasses import ContactPoint
7
- from ...generic.vec3 import Vec3
8
- from ...generic.quat import Quat
9
-
10
-
11
-
12
- # sutherland hodgman clipping algorithm
13
- 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]:
14
- """
15
- computes the contact manifold for a collision between two nearby polyhedra
16
- """
17
- if len(points1) == 0 or len(points2) == 0: return []
18
-
19
- # project vertices onto the 2d plane
20
- points1 = project_points(contact_plane_point, contact_plane_normal, points1)
21
- points2 = project_points(contact_plane_point, contact_plane_normal, points2)
22
-
23
- # check if collsion was on a vertex
24
- if len(points1) == 1: return points1
25
- if len(points2) == 1: return points2
26
-
27
- # convert points to 2d for intersection algorithms
28
- points1, u1, v1 = points_to_2d(contact_plane_point, contact_plane_normal, points1)
29
- points2, u2, v2 = points_to_2d(contact_plane_point, contact_plane_normal, points2, u1, v1) #TODO precalc orthogonal basis for 2d conversion
30
-
31
- # convert arbitrary points to polygon
32
- if len(points1) > 2: points1 = graham_scan(points1)
33
- if len(points2) > 2: points2 = graham_scan(points2)
34
-
35
- # run clipping algorithms
36
- manifold = []
37
- is_line1, is_line2 = len(points1) == 2, len(points2) == 2
38
- if is_line1 and is_line2: manifold = line_line_intersect(points1, points2)
39
- else:
40
- if is_line1: manifold = line_poly_intersect(points1, points2)
41
- elif is_line2: manifold = line_poly_intersect(points2, points1)
42
- else: manifold = sutherland_hodgman(points1, points2)
43
-
44
- # fall back if manifold fails to develope
45
- if len(manifold) == 0: return []
46
-
47
- # convert inertsection algorithm output to 3d
48
- return points_to_3d(u1, v1, contact_plane_point, manifold)
49
-
50
- def separate_polytope(points1: list[ContactPoint], points2: list[ContactPoint], contact_plane_normal, epsilon: float=1e-5) -> tuple[list[ContactPoint], list[ContactPoint]]:
51
- """
52
- Determines the potential contact manifold points of each shape based on their position along the penetrating axis
53
- """
54
-
55
- proj1 = [(glm.dot(point.vertex, contact_plane_normal), point) for point in points1]
56
- proj2 = [(glm.dot(point.vertex, contact_plane_normal), point) for point in points2]
57
-
58
- # min1 and max2 should be past the collising points of node2 and node1 respectively
59
- min1 = min(proj1, key=lambda proj: proj[0])[0]
60
- max2 = max(proj2, key=lambda proj: proj[0])[0]
61
-
62
- proj1 = filter(lambda proj: proj[0] <= max2 + epsilon, proj1)
63
- proj2 = filter(lambda proj: proj[0] + epsilon >= min1, proj2)
64
-
65
- return [point[1] for point in proj1], [point[1] for point in proj2]
66
-
67
- def distance_to_plane(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, point:glm.vec3) -> float:
68
- """gets the smallest distance a point is from a plane"""
69
- return glm.dot(point - contact_plane_point, contact_plane_normal) #TODO check this formula
70
-
71
- def project_points(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, points:list[Vec3]) -> list[glm.vec3]:
72
- """gets the projected positions of the given points onto the given plane"""
73
- return [point - glm.dot(point - contact_plane_point, contact_plane_normal) * contact_plane_normal for point in points]
74
-
75
- def points_to_2d(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, points:list[glm.vec3], u = None, v = None) -> tuple[list[glm.vec2], glm.vec3, glm.vec3]:
76
- """converts a list of points on a plane to their 2d representation"""
77
- # generate a new basis
78
- k = get_noncolinear_vector(contact_plane_normal)
79
- u = u if u else glm.normalize(glm.cross(contact_plane_normal, k))
80
- v = v if v else glm.cross(contact_plane_normal, u)
81
-
82
- # convert points to new basis
83
- return [glm.vec2(glm.dot(vec := point - contact_plane_point, u), glm.dot(vec, v)) for point in points], u, v
84
-
85
- def points_to_3d(u:glm.vec3, v:glm.vec3, contact_plane_point:glm.vec3, points:list[glm.vec2]) -> list[glm.vec3]:
86
- """converts a list of points on a plane to their 3d representation"""
87
- return [contact_plane_point + point.x * u + point.y * v for point in points]
88
-
89
- # vector math
90
- def get_noncolinear_vector(vector:glm.vec3) -> glm.vec3:
91
- """generates a non colinear vector based on the given vector"""
92
- test_vector = (1, 1, 1)
93
- while glm.cross(test_vector, vector) == (0, 0, 0):
94
- val = randint(0, 7) # 000 to 111
95
- test_vector = (val & 1, val & 2, val & 4) # one random for three digits
1
+ import glm
2
+ from random import randint
3
+ from .line_intersections import line_line_intersect, line_poly_intersect
4
+ from .graham_scan import graham_scan
5
+ from .sutherland_hodgman import sutherland_hodgman
6
+ from .dataclasses import ContactPoint
7
+ from ...generic.vec3 import Vec3
8
+ from ...generic.quat import Quat
9
+
10
+
11
+
12
+ # sutherland hodgman clipping algorithm
13
+ 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]:
14
+ """
15
+ computes the contact manifold for a collision between two nearby polyhedra
16
+ """
17
+ if len(points1) == 0 or len(points2) == 0: return []
18
+
19
+ # project vertices onto the 2d plane
20
+ points1 = project_points(contact_plane_point, contact_plane_normal, points1)
21
+ points2 = project_points(contact_plane_point, contact_plane_normal, points2)
22
+
23
+ # check if collsion was on a vertex
24
+ if len(points1) == 1: return points1
25
+ if len(points2) == 1: return points2
26
+
27
+ # convert points to 2d for intersection algorithms
28
+ points1, u1, v1 = points_to_2d(contact_plane_point, contact_plane_normal, points1)
29
+ points2, u2, v2 = points_to_2d(contact_plane_point, contact_plane_normal, points2, u1, v1) #TODO precalc orthogonal basis for 2d conversion
30
+
31
+ # convert arbitrary points to polygon
32
+ if len(points1) > 2: points1 = graham_scan(points1)
33
+ if len(points2) > 2: points2 = graham_scan(points2)
34
+
35
+ # run clipping algorithms
36
+ manifold = []
37
+ is_line1, is_line2 = len(points1) == 2, len(points2) == 2
38
+ if is_line1 and is_line2: manifold = line_line_intersect(points1, points2)
39
+ else:
40
+ if is_line1: manifold = line_poly_intersect(points1, points2)
41
+ elif is_line2: manifold = line_poly_intersect(points2, points1)
42
+ else: manifold = sutherland_hodgman(points1, points2)
43
+
44
+ # fall back if manifold fails to develope
45
+ if len(manifold) == 0: return []
46
+
47
+ # convert inertsection algorithm output to 3d
48
+ return points_to_3d(u1, v1, contact_plane_point, manifold)
49
+
50
+ def separate_polytope(points1: list[ContactPoint], points2: list[ContactPoint], contact_plane_normal, epsilon: float=1e-5) -> tuple[list[ContactPoint], list[ContactPoint]]:
51
+ """
52
+ Determines the potential contact manifold points of each shape based on their position along the penetrating axis
53
+ """
54
+
55
+ proj1 = [(glm.dot(point.vertex, contact_plane_normal), point) for point in points1]
56
+ proj2 = [(glm.dot(point.vertex, contact_plane_normal), point) for point in points2]
57
+
58
+ # min1 and max2 should be past the collising points of node2 and node1 respectively
59
+ min1 = min(proj1, key=lambda proj: proj[0])[0]
60
+ max2 = max(proj2, key=lambda proj: proj[0])[0]
61
+
62
+ proj1 = filter(lambda proj: proj[0] <= max2 + epsilon, proj1)
63
+ proj2 = filter(lambda proj: proj[0] + epsilon >= min1, proj2)
64
+
65
+ return [point[1] for point in proj1], [point[1] for point in proj2]
66
+
67
+ def distance_to_plane(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, point:glm.vec3) -> float:
68
+ """gets the smallest distance a point is from a plane"""
69
+ return glm.dot(point - contact_plane_point, contact_plane_normal) #TODO check this formula
70
+
71
+ def project_points(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, points:list[Vec3]) -> list[glm.vec3]:
72
+ """gets the projected positions of the given points onto the given plane"""
73
+ return [point - glm.dot(point - contact_plane_point, contact_plane_normal) * contact_plane_normal for point in points]
74
+
75
+ def points_to_2d(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, points:list[glm.vec3], u = None, v = None) -> tuple[list[glm.vec2], glm.vec3, glm.vec3]:
76
+ """converts a list of points on a plane to their 2d representation"""
77
+ # generate a new basis
78
+ k = get_noncolinear_vector(contact_plane_normal)
79
+ u = u if u else glm.normalize(glm.cross(contact_plane_normal, k))
80
+ v = v if v else glm.cross(contact_plane_normal, u)
81
+
82
+ # convert points to new basis
83
+ return [glm.vec2(glm.dot(vec := point - contact_plane_point, u), glm.dot(vec, v)) for point in points], u, v
84
+
85
+ def points_to_3d(u:glm.vec3, v:glm.vec3, contact_plane_point:glm.vec3, points:list[glm.vec2]) -> list[glm.vec3]:
86
+ """converts a list of points on a plane to their 3d representation"""
87
+ return [contact_plane_point + point.x * u + point.y * v for point in points]
88
+
89
+ # vector math
90
+ def get_noncolinear_vector(vector:glm.vec3) -> glm.vec3:
91
+ """generates a non colinear vector based on the given vector"""
92
+ test_vector = (1, 1, 1)
93
+ while glm.cross(test_vector, vector) == (0, 0, 0):
94
+ val = randint(0, 7) # 000 to 111
95
+ test_vector = (val & 1, val & 2, val & 4) # one random for three digits
96
96
  return test_vector