basilisk-engine 0.1.2__py3-none-any.whl → 0.1.3__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 (82) hide show
  1. basilisk/__init__.py +11 -11
  2. basilisk/bsk_assets/cube.obj +48 -48
  3. basilisk/collisions/broad/broad_aabb.py +102 -102
  4. basilisk/collisions/broad/broad_bvh.py +137 -137
  5. basilisk/collisions/collider.py +95 -83
  6. basilisk/collisions/collider_handler.py +225 -228
  7. basilisk/collisions/narrow/contact_manifold.py +90 -90
  8. basilisk/collisions/narrow/dataclasses.py +33 -27
  9. basilisk/collisions/narrow/deprecated.py +46 -46
  10. basilisk/collisions/narrow/epa.py +91 -91
  11. basilisk/collisions/narrow/gjk.py +66 -66
  12. basilisk/collisions/narrow/graham_scan.py +24 -24
  13. basilisk/collisions/narrow/helper.py +29 -29
  14. basilisk/collisions/narrow/line_intersections.py +106 -106
  15. basilisk/collisions/narrow/sutherland_hodgman.py +75 -75
  16. basilisk/config.py +2 -2
  17. basilisk/draw/draw.py +100 -100
  18. basilisk/draw/draw_handler.py +180 -210
  19. basilisk/draw/font_renderer.py +28 -28
  20. basilisk/engine.py +195 -195
  21. basilisk/generic/abstract_bvh.py +15 -15
  22. basilisk/generic/abstract_custom.py +133 -133
  23. basilisk/generic/collisions.py +70 -70
  24. basilisk/generic/input_validation.py +67 -28
  25. basilisk/generic/math.py +6 -6
  26. basilisk/generic/matrices.py +33 -33
  27. basilisk/generic/meshes.py +72 -72
  28. basilisk/generic/quat.py +137 -137
  29. basilisk/generic/quat_methods.py +7 -7
  30. basilisk/generic/raycast_result.py +24 -0
  31. basilisk/generic/vec3.py +143 -143
  32. basilisk/input/mouse.py +61 -59
  33. basilisk/mesh/cube.py +33 -33
  34. basilisk/mesh/mesh.py +230 -230
  35. basilisk/mesh/mesh_from_data.py +132 -132
  36. basilisk/mesh/model.py +271 -271
  37. basilisk/mesh/narrow_aabb.py +89 -89
  38. basilisk/mesh/narrow_bvh.py +91 -91
  39. basilisk/mesh/narrow_primative.py +23 -23
  40. basilisk/nodes/helper.py +29 -0
  41. basilisk/nodes/node.py +681 -617
  42. basilisk/nodes/node_handler.py +95 -118
  43. basilisk/particles/particle_handler.py +63 -54
  44. basilisk/particles/particle_renderer.py +87 -87
  45. basilisk/physics/impulse.py +112 -112
  46. basilisk/physics/physics_body.py +43 -43
  47. basilisk/physics/physics_engine.py +35 -35
  48. basilisk/render/batch.py +86 -86
  49. basilisk/render/camera.py +204 -199
  50. basilisk/render/chunk.py +99 -99
  51. basilisk/render/chunk_handler.py +154 -154
  52. basilisk/render/frame.py +181 -181
  53. basilisk/render/image.py +75 -75
  54. basilisk/render/image_handler.py +122 -122
  55. basilisk/render/light.py +96 -96
  56. basilisk/render/light_handler.py +58 -58
  57. basilisk/render/material.py +219 -219
  58. basilisk/render/material_handler.py +135 -135
  59. basilisk/render/shader.py +109 -109
  60. basilisk/render/shader_handler.py +79 -79
  61. basilisk/render/sky.py +120 -120
  62. basilisk/scene.py +250 -210
  63. basilisk/shaders/batch.frag +276 -276
  64. basilisk/shaders/batch.vert +115 -115
  65. basilisk/shaders/draw.frag +21 -21
  66. basilisk/shaders/draw.vert +21 -21
  67. basilisk/shaders/filter.frag +22 -22
  68. basilisk/shaders/frame.frag +12 -12
  69. basilisk/shaders/frame.vert +13 -13
  70. basilisk/shaders/geometry.frag +8 -8
  71. basilisk/shaders/geometry.vert +41 -41
  72. basilisk/shaders/normal.frag +59 -59
  73. basilisk/shaders/normal.vert +96 -96
  74. basilisk/shaders/particle.frag +71 -71
  75. basilisk/shaders/particle.vert +84 -84
  76. basilisk/shaders/sky.frag +9 -9
  77. basilisk/shaders/sky.vert +13 -13
  78. {basilisk_engine-0.1.2.dist-info → basilisk_engine-0.1.3.dist-info}/METADATA +45 -38
  79. basilisk_engine-0.1.3.dist-info/RECORD +97 -0
  80. {basilisk_engine-0.1.2.dist-info → basilisk_engine-0.1.3.dist-info}/WHEEL +1 -1
  81. basilisk_engine-0.1.2.dist-info/RECORD +0 -95
  82. {basilisk_engine-0.1.2.dist-info → basilisk_engine-0.1.3.dist-info}/top_level.txt +0 -0
@@ -1,91 +1,91 @@
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
-
8
- # sutherland hodgman clipping algorithm
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]:
10
- """
11
- computes the contact manifold for a collision between two nearby polyhedra
12
- """
13
- if len(points1) == 0 or len(points2) == 0: return []
14
-
15
- # project vertices onto the 2d plane
16
- points1 = project_points(contact_plane_point, contact_plane_normal, points1)
17
- points2 = project_points(contact_plane_point, contact_plane_normal, points2)
18
-
19
- # check if collsion was on a vertex
20
- if len(points1) == 1: return points1
21
- if len(points2) == 1: return points2
22
-
23
- # convert points to 2d for intersection algorithms
24
- points1, u1, v1 = points_to_2d(contact_plane_point, contact_plane_normal, points1)
25
- points2, u2, v2 = points_to_2d(contact_plane_point, contact_plane_normal, points2, u1, v1) #TODO precalc orthogonal basis for 2d conversion
26
-
27
- # convert arbitrary points to polygon
28
- if len(points1) > 2: points1 = graham_scan(points1)
29
- if len(points2) > 2: points2 = graham_scan(points2)
30
-
31
- # run clipping algorithms
32
- manifold = []
33
- is_line1, is_line2 = len(points1) == 2, len(points2) == 2
34
- if is_line1 and is_line2: manifold = line_line_intersect(points1, points2)
35
- else:
36
- if is_line1: manifold = line_poly_intersect(points1, points2)
37
- elif is_line2: manifold = line_poly_intersect(points2, points1)
38
- else: manifold = sutherland_hodgman(points1, points2)
39
-
40
- # fall back if manifold fails to develope
41
- if len(manifold) == 0: return []
42
-
43
- # convert inertsection algorithm output to 3d
44
- return points_to_3d(u1, v1, contact_plane_point, manifold)
45
-
46
- def separate_polytope(points1: list[ContactPoint], points2: list[ContactPoint], contact_plane_normal, epsilon: float=1e-5) -> tuple[list[ContactPoint], list[ContactPoint]]:
47
- """
48
- Determines the potential contact manifold points of each shape based on their position along the penetrating axis
49
- """
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]
52
-
53
- # min1 and max2 should be past the collising points of node2 and node1 respectively
54
- min1 = min(proj1, key=lambda proj: proj[0])[0]
55
- max2 = max(proj2, key=lambda proj: proj[0])[0]
56
-
57
- proj1 = filter(lambda proj: proj[0] <= max2 + epsilon, proj1)
58
- proj2 = filter(lambda proj: proj[0] + epsilon >= min1, proj2)
59
-
60
- return [point[1] for point in proj1], [point[1] for point in proj2]
61
-
62
- def distance_to_plane(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, point:glm.vec3) -> float:
63
- """gets the smallest distance a point is from a plane"""
64
- return glm.dot(point - contact_plane_point, contact_plane_normal) #TODO check this formula
65
-
66
- def project_points(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, points:list[glm.vec3]) -> list[glm.vec3]:
67
- """gets the projected positions of the given points onto the given plane"""
68
- return [point - glm.dot(point - contact_plane_point, contact_plane_normal) * contact_plane_normal for point in points]
69
-
70
- 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]:
71
- """converts a list of points on a plane to their 2d representation"""
72
- # generate a new basis
73
- k = get_noncolinear_vector(contact_plane_normal)
74
- u = u if u else glm.normalize(glm.cross(contact_plane_normal, k))
75
- v = v if v else glm.cross(contact_plane_normal, u)
76
-
77
- # convert points to new basis
78
- return [glm.vec2(glm.dot(vec := point - contact_plane_point, u), glm.dot(vec, v)) for point in points], u, v
79
-
80
- def points_to_3d(u:glm.vec3, v:glm.vec3, contact_plane_point:glm.vec3, points:list[glm.vec2]) -> list[glm.vec3]:
81
- """converts a list of points on a plane to their 3d representation"""
82
- return [contact_plane_point + point.x * u + point.y * v for point in points]
83
-
84
- # vector math
85
- def get_noncolinear_vector(vector:glm.vec3) -> glm.vec3:
86
- """generates a non colinear vector based on the given vector"""
87
- test_vector = (1, 1, 1)
88
- while glm.cross(test_vector, vector) == (0, 0, 0):
89
- val = randint(0, 7) # 000 to 111
90
- 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
+
8
+ # sutherland hodgman clipping algorithm
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]:
10
+ """
11
+ computes the contact manifold for a collision between two nearby polyhedra
12
+ """
13
+ if len(points1) == 0 or len(points2) == 0: return []
14
+
15
+ # project vertices onto the 2d plane
16
+ points1 = project_points(contact_plane_point, contact_plane_normal, points1)
17
+ points2 = project_points(contact_plane_point, contact_plane_normal, points2)
18
+
19
+ # check if collsion was on a vertex
20
+ if len(points1) == 1: return points1
21
+ if len(points2) == 1: return points2
22
+
23
+ # convert points to 2d for intersection algorithms
24
+ points1, u1, v1 = points_to_2d(contact_plane_point, contact_plane_normal, points1)
25
+ points2, u2, v2 = points_to_2d(contact_plane_point, contact_plane_normal, points2, u1, v1) #TODO precalc orthogonal basis for 2d conversion
26
+
27
+ # convert arbitrary points to polygon
28
+ if len(points1) > 2: points1 = graham_scan(points1)
29
+ if len(points2) > 2: points2 = graham_scan(points2)
30
+
31
+ # run clipping algorithms
32
+ manifold = []
33
+ is_line1, is_line2 = len(points1) == 2, len(points2) == 2
34
+ if is_line1 and is_line2: manifold = line_line_intersect(points1, points2)
35
+ else:
36
+ if is_line1: manifold = line_poly_intersect(points1, points2)
37
+ elif is_line2: manifold = line_poly_intersect(points2, points1)
38
+ else: manifold = sutherland_hodgman(points1, points2)
39
+
40
+ # fall back if manifold fails to develope
41
+ if len(manifold) == 0: return []
42
+
43
+ # convert inertsection algorithm output to 3d
44
+ return points_to_3d(u1, v1, contact_plane_point, manifold)
45
+
46
+ def separate_polytope(points1: list[ContactPoint], points2: list[ContactPoint], contact_plane_normal, epsilon: float=1e-5) -> tuple[list[ContactPoint], list[ContactPoint]]:
47
+ """
48
+ Determines the potential contact manifold points of each shape based on their position along the penetrating axis
49
+ """
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]
52
+
53
+ # min1 and max2 should be past the collising points of node2 and node1 respectively
54
+ min1 = min(proj1, key=lambda proj: proj[0])[0]
55
+ max2 = max(proj2, key=lambda proj: proj[0])[0]
56
+
57
+ proj1 = filter(lambda proj: proj[0] <= max2 + epsilon, proj1)
58
+ proj2 = filter(lambda proj: proj[0] + epsilon >= min1, proj2)
59
+
60
+ return [point[1] for point in proj1], [point[1] for point in proj2]
61
+
62
+ def distance_to_plane(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, point:glm.vec3) -> float:
63
+ """gets the smallest distance a point is from a plane"""
64
+ return glm.dot(point - contact_plane_point, contact_plane_normal) #TODO check this formula
65
+
66
+ def project_points(contact_plane_point:glm.vec3, contact_plane_normal:glm.vec3, points:list[glm.vec3]) -> list[glm.vec3]:
67
+ """gets the projected positions of the given points onto the given plane"""
68
+ return [point - glm.dot(point - contact_plane_point, contact_plane_normal) * contact_plane_normal for point in points]
69
+
70
+ 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]:
71
+ """converts a list of points on a plane to their 2d representation"""
72
+ # generate a new basis
73
+ k = get_noncolinear_vector(contact_plane_normal)
74
+ u = u if u else glm.normalize(glm.cross(contact_plane_normal, k))
75
+ v = v if v else glm.cross(contact_plane_normal, u)
76
+
77
+ # convert points to new basis
78
+ return [glm.vec2(glm.dot(vec := point - contact_plane_point, u), glm.dot(vec, v)) for point in points], u, v
79
+
80
+ def points_to_3d(u:glm.vec3, v:glm.vec3, contact_plane_point:glm.vec3, points:list[glm.vec2]) -> list[glm.vec3]:
81
+ """converts a list of points on a plane to their 3d representation"""
82
+ return [contact_plane_point + point.x * u + point.y * v for point in points]
83
+
84
+ # vector math
85
+ def get_noncolinear_vector(vector:glm.vec3) -> glm.vec3:
86
+ """generates a non colinear vector based on the given vector"""
87
+ test_vector = (1, 1, 1)
88
+ while glm.cross(test_vector, vector) == (0, 0, 0):
89
+ val = randint(0, 7) # 000 to 111
90
+ test_vector = (val & 1, val & 2, val & 4) # one random for three digits
91
91
  return test_vector
@@ -1,27 +1,33 @@
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]
1
+ import glm
2
+ from dataclasses import dataclass
3
+ # from ...nodes.node import Node
4
+
5
+ # frozen because data does not need to be mutable
6
+ # used in creating polytopes for GJK/EPA
7
+ @dataclass(frozen=True)
8
+ class SupportPoint():
9
+ support_point: glm.vec3
10
+
11
+ index1: int # index of the vertex in the mesh
12
+ vertex1: glm.vec3 # world space location of the vertex at collision
13
+
14
+ index2: int
15
+ vertex2: glm.vec3
16
+
17
+ # used for generating contact points for the contact manifold
18
+ @dataclass(frozen=True)
19
+ class ContactPoint():
20
+ index: int
21
+ vertex: glm.vec3
22
+
23
+ # contact manifold object used in the contact handler list
24
+ @dataclass
25
+ class ContactManifold():
26
+ normal: glm.vec3
27
+ contact_points1: dict[int : glm.vec3] # contact point index : collision position
28
+ contact_points2: dict[int : glm.vec3]
29
+
30
+ @dataclass
31
+ class Collision():
32
+ node: ...
33
+ normal: glm.vec3
@@ -1,47 +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
-
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
47
  # return closest_two_lines(*points1, *points2)
@@ -1,92 +1,92 @@
1
- import glm
2
- from .helper import get_support_point
3
- from .dataclasses import SupportPoint
4
- from...nodes.node import Node
5
-
6
-
7
- # TODO change these to structs when converting to C++
8
- face_type = list[tuple[float, glm.vec3, glm.vec3, int, int, int]] # distance, normal, center, index 1, index 2, index 3
9
- polytope_type = list[SupportPoint] # polytope vertex, node1 vertex, node2 vertex
10
-
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
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.
14
- """
15
- # orient faces to point normals counter clockwise
16
- faces: face_type = []
17
- for indices in [(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)]: faces = insert_face(polytope, faces, indices)
18
-
19
- # develope the polytope until the nearest real face has been found, within epsilon
20
- while True:
21
- new_point = get_support_point(node1, node2, faces[0][1])
22
- if new_point in polytope or glm.length(new_point.support_point) - faces[0][0] < epsilon: return faces[0], polytope
23
- faces, polytope = insert_point(polytope, faces, new_point)
24
-
25
- def insert_point(polytope: polytope_type, faces: face_type, point: glm.vec3, epsilon: float=0) -> tuple[face_type, polytope_type]:
26
- """
27
- Inserts a point into the polytope sorting by distance from the origin
28
- """
29
- # determine which faces are facing the new point
30
- polytope.append(point)
31
- support_index = len(polytope) - 1
32
- visible_faces = [
33
- face for face in faces
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
36
- ]
37
-
38
- # generate new edges
39
- edges = []
40
- for face in visible_faces:
41
- for p1, p2 in get_face_edges(face):
42
- if (p2, p1) in edges: edges.remove((p2, p1)) # edges can only be shared by two faces, running opposite to each other.
43
- elif (p1, p2) in edges: # TODO remove this
44
- edges.remove((p1, p2))
45
- # print('not reversed')
46
- else: edges.append((p1, p2))
47
-
48
- # remove visible faces
49
- for face in sorted(visible_faces, reverse = True): faces.remove(face)
50
-
51
- # add new faces
52
- new_face_indices = [orient_face(polytope, (edge[0], edge[1], support_index)) for edge in edges] # edge[0], edge[1] is already ccw
53
- for indices in new_face_indices: faces = insert_face(polytope, faces, indices)
54
-
55
- return faces, polytope
56
-
57
- def insert_face(polytope: polytope_type, faces: face_type, indices: tuple[int, int, int]) -> face_type:
58
- """
59
- Inserts a face into the face priority queue based on the indices given in the polytope
60
- """
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
63
- if glm.dot(center, normal) < 0:
64
- normal *= -1
65
- indices = (indices[2], indices[1], indices[0])
66
-
67
- # TODO solve cases where face may contain origin
68
- normal = glm.normalize(normal)
69
- distance = abs(glm.dot(polytope[indices[0]].support_point, normal))
70
- new_face = (distance, normal, center, *indices)
71
-
72
- # insert faces into priority queue based on distance from origin
73
- for i, face in enumerate(faces):
74
- if face[0] < distance: continue
75
- faces.insert(i, new_face)
76
- break
77
- else: faces.append(new_face)
78
-
79
- return faces
80
-
81
- def orient_face(polytope: polytope_type, indices: tuple[int, int, int]) -> tuple[int, int, int]:
82
- """
83
- Orients the face indices to have a counter clockwise normal
84
- """
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])
86
- return indices
87
-
88
- def get_face_edges(face: tuple[float, glm.vec3, glm.vec3, int, int, int]) -> list[tuple[int, int]]:
89
- """
90
- Permutes a tuple of three unique numbers (a, b, c) into 3 pairs (x, y), preserving order
91
- """
1
+ import glm
2
+ from .helper import get_support_point
3
+ from .dataclasses import SupportPoint
4
+ from...nodes.node import Node
5
+
6
+
7
+ # TODO change these to structs when converting to C++
8
+ face_type = list[tuple[float, glm.vec3, glm.vec3, int, int, int]] # distance, normal, center, index 1, index 2, index 3
9
+ polytope_type = list[SupportPoint] # polytope vertex, node1 vertex, node2 vertex
10
+
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
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.
14
+ """
15
+ # orient faces to point normals counter clockwise
16
+ faces: face_type = []
17
+ for indices in [(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)]: faces = insert_face(polytope, faces, indices)
18
+
19
+ # develope the polytope until the nearest real face has been found, within epsilon
20
+ while True:
21
+ new_point = get_support_point(node1, node2, faces[0][1])
22
+ if new_point in polytope or glm.length(new_point.support_point) - faces[0][0] < epsilon: return faces[0], polytope
23
+ faces, polytope = insert_point(polytope, faces, new_point)
24
+
25
+ def insert_point(polytope: polytope_type, faces: face_type, point: glm.vec3, epsilon: float=0) -> tuple[face_type, polytope_type]:
26
+ """
27
+ Inserts a point into the polytope sorting by distance from the origin
28
+ """
29
+ # determine which faces are facing the new point
30
+ polytope.append(point)
31
+ support_index = len(polytope) - 1
32
+ visible_faces = [
33
+ face for face in faces
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
36
+ ]
37
+
38
+ # generate new edges
39
+ edges = []
40
+ for face in visible_faces:
41
+ for p1, p2 in get_face_edges(face):
42
+ if (p2, p1) in edges: edges.remove((p2, p1)) # edges can only be shared by two faces, running opposite to each other.
43
+ elif (p1, p2) in edges: # TODO remove this
44
+ edges.remove((p1, p2))
45
+ # print('not reversed')
46
+ else: edges.append((p1, p2))
47
+
48
+ # remove visible faces
49
+ for face in sorted(visible_faces, reverse = True): faces.remove(face)
50
+
51
+ # add new faces
52
+ new_face_indices = [orient_face(polytope, (edge[0], edge[1], support_index)) for edge in edges] # edge[0], edge[1] is already ccw
53
+ for indices in new_face_indices: faces = insert_face(polytope, faces, indices)
54
+
55
+ return faces, polytope
56
+
57
+ def insert_face(polytope: polytope_type, faces: face_type, indices: tuple[int, int, int]) -> face_type:
58
+ """
59
+ Inserts a face into the face priority queue based on the indices given in the polytope
60
+ """
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
63
+ if glm.dot(center, normal) < 0:
64
+ normal *= -1
65
+ indices = (indices[2], indices[1], indices[0])
66
+
67
+ # TODO solve cases where face may contain origin
68
+ normal = glm.normalize(normal)
69
+ distance = abs(glm.dot(polytope[indices[0]].support_point, normal))
70
+ new_face = (distance, normal, center, *indices)
71
+
72
+ # insert faces into priority queue based on distance from origin
73
+ for i, face in enumerate(faces):
74
+ if face[0] < distance: continue
75
+ faces.insert(i, new_face)
76
+ break
77
+ else: faces.append(new_face)
78
+
79
+ return faces
80
+
81
+ def orient_face(polytope: polytope_type, indices: tuple[int, int, int]) -> tuple[int, int, int]:
82
+ """
83
+ Orients the face indices to have a counter clockwise normal
84
+ """
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])
86
+ return indices
87
+
88
+ def get_face_edges(face: tuple[float, glm.vec3, glm.vec3, int, int, int]) -> list[tuple[int, int]]:
89
+ """
90
+ Permutes a tuple of three unique numbers (a, b, c) into 3 pairs (x, y), preserving order
91
+ """
92
92
  return [(face[3], face[4]), (face[4], face[5]), (face[5], face[3])]