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.

Files changed (55) hide show
  1. basilisk/collisions/broad/broad_aabb.py +8 -1
  2. basilisk/collisions/broad/broad_bvh.py +8 -1
  3. basilisk/collisions/collider.py +15 -6
  4. basilisk/collisions/collider_handler.py +70 -68
  5. basilisk/collisions/narrow/contact_manifold.py +9 -10
  6. basilisk/collisions/narrow/dataclasses.py +27 -0
  7. basilisk/collisions/narrow/deprecated.py +47 -0
  8. basilisk/collisions/narrow/epa.py +11 -10
  9. basilisk/collisions/narrow/gjk.py +15 -14
  10. basilisk/collisions/narrow/helper.py +8 -7
  11. basilisk/collisions/narrow/sutherland_hodgman.py +52 -0
  12. basilisk/draw/draw_handler.py +5 -3
  13. basilisk/engine.py +14 -5
  14. basilisk/generic/abstract_custom.py +134 -0
  15. basilisk/generic/collisions.py +46 -0
  16. basilisk/generic/quat.py +77 -66
  17. basilisk/generic/vec3.py +91 -67
  18. basilisk/mesh/cube.py +20 -2
  19. basilisk/mesh/mesh.py +69 -54
  20. basilisk/mesh/mesh_from_data.py +106 -21
  21. basilisk/mesh/narrow_aabb.py +10 -1
  22. basilisk/mesh/narrow_bvh.py +9 -1
  23. basilisk/nodes/node.py +169 -30
  24. basilisk/nodes/node_handler.py +51 -30
  25. basilisk/particles/__init__.py +0 -0
  26. basilisk/particles/particle_handler.py +55 -0
  27. basilisk/particles/particle_renderer.py +87 -0
  28. basilisk/physics/impulse.py +7 -13
  29. basilisk/physics/physics_body.py +10 -2
  30. basilisk/physics/physics_engine.py +1 -2
  31. basilisk/render/batch.py +2 -2
  32. basilisk/render/camera.py +5 -0
  33. basilisk/render/chunk.py +19 -4
  34. basilisk/render/chunk_handler.py +33 -26
  35. basilisk/render/image.py +1 -1
  36. basilisk/render/image_handler.py +4 -3
  37. basilisk/render/light_handler.py +16 -11
  38. basilisk/render/material.py +25 -1
  39. basilisk/render/material_handler.py +22 -13
  40. basilisk/render/shader.py +7 -7
  41. basilisk/render/shader_handler.py +13 -12
  42. basilisk/render/sky.py +5 -3
  43. basilisk/scene.py +114 -32
  44. basilisk/shaders/batch.frag +40 -11
  45. basilisk/shaders/batch.vert +14 -7
  46. basilisk/shaders/normal.frag +5 -5
  47. basilisk/shaders/normal.vert +8 -3
  48. basilisk/shaders/particle.frag +72 -0
  49. basilisk/shaders/particle.vert +85 -0
  50. {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.1.dist-info}/METADATA +5 -5
  51. basilisk_engine-0.1.1.dist-info/RECORD +95 -0
  52. basilisk/shaders/image.png +0 -0
  53. basilisk_engine-0.1.0.dist-info/RECORD +0 -88
  54. {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.1.dist-info}/WHEEL +0 -0
  55. {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]
@@ -35,11 +35,11 @@ class Collider():
35
35
  mesh: Mesh
36
36
  """Reference to the colliding mesh"""
37
37
 
38
- def __init__(self, collider_handler, node, box_mesh: bool=False, static_friction: glm.vec3=0.7, kinetic_friction: glm.vec3=0.3, elasticity: glm.vec3=0.1, collision_group: str=None):
39
- self.collider_handler = 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.mesh = self.collider_handler.cube if box_mesh else self.node.mesh
42
- self.static_friction = static_friction if elasticity else 0.8
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, points_to_2d, points_to_3d, project_points
10
- from .narrow.line_intersections import closest_two_lines, line_poly_intersect
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, node, box_mesh: bool=False, static_friction: glm.vec3=0.7, kinetic_friction: glm.vec3=0.3, elasticity: glm.vec3=0.1, collision_group: str=None) -> Collider:
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 == collider2: continue
172
- if (collider1, collider2) in collisions or (collider2, collider1) in collisions: continue # TODO find a secure way for ordering colliders
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[1] for p in polytope]
213
- points2 = [p[2] for p in polytope]
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
- manifold = get_contact_manifold(node1.position - vec, vec, points1, points2)
219
- if len(manifold) == 0:
220
- print('manifold failed to generate')
221
- continue
222
- calculate_collisions(vec, node1, node2, manifold, node1.get_inverse_inertia(), node2.get_inverse_inertia(), node1.center_of_mass, node2.center_of_mass)
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[glm.vec3], points2: list[glm.vec3], contact_plane_normal) -> list[glm.vec3]:
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 point: point[0])
56
- max2 = max(proj2, key=lambda point: point[0])
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 < max2, proj1)
59
- proj2 = filter(lambda proj: proj > min1, proj2)
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[tuple[glm.vec3, glm.vec3, glm.vec3]] # polytope vertex, node1 vertex, node2 vertex
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[0]) - faces[0][0] < epsilon: return faces[0], polytope
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][0]) >= epsilon and # if the normal of a face is pointing towards the added point
34
- glm.dot(face[1], polytope[support_index][0] - face[2]) >= epsilon # TODO check if this ever occurs
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]][0] + polytope[indices[1]][0] + polytope[indices[2]][0]) / 3
61
- normal = glm.cross(polytope[indices[1]][0] - polytope[indices[0]][0], polytope[indices[2]][0] - polytope[indices[0]][0]) # closest face normal will be normalized once returned to avoid square roots and division
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]][0], normal))
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]: # TODO finish this
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]][0], polytope[indices[2]][0]), polytope[indices[0]][0]) < 0: return (indices[2], indices[1], indices[0])
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][0] # set direction to point away from starting simplex point
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[0], dir_vec) < -1e-7: return False, simplex
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][0] - simplex[0][0]
42
- return False, triple_product(vec_ab, -simplex[0][0], vec_ab), simplex
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][0] - simplex[0][0], simplex[2][0] - simplex[0][0])
49
- return False, -dir_vec if glm.dot(dir_vec, -simplex[0][0]) < 0 else dir_vec, simplex
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][0] - simplex[0][0]
56
- vec_db = simplex[3][0] - simplex[1][0]
57
- vec_dc = simplex[3][0] - simplex[2][0]
58
- vec_do = -simplex[3][0]
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
- vertex = node.mesh.get_best_dot(node_dir_vec)
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