basilisk-engine 0.0.1__tar.gz → 0.0.2__tar.gz

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 (71) hide show
  1. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/PKG-INFO +1 -1
  2. basilisk_engine-0.0.2/basilisk/bsk_assets/__init__.py +0 -0
  3. basilisk_engine-0.0.2/basilisk/collisions/__init__.py +0 -0
  4. basilisk_engine-0.0.2/basilisk/collisions/broad/__init__.py +0 -0
  5. basilisk_engine-0.0.2/basilisk/collisions/broad/broad_aabb.py +96 -0
  6. basilisk_engine-0.0.2/basilisk/collisions/broad/broad_bvh.py +102 -0
  7. basilisk_engine-0.0.2/basilisk/collisions/collider.py +75 -0
  8. basilisk_engine-0.0.2/basilisk/collisions/collider_handler.py +163 -0
  9. basilisk_engine-0.0.2/basilisk/collisions/narrow/__init__.py +0 -0
  10. basilisk_engine-0.0.2/basilisk/collisions/narrow/epa.py +86 -0
  11. basilisk_engine-0.0.2/basilisk/collisions/narrow/gjk.py +66 -0
  12. basilisk_engine-0.0.2/basilisk/collisions/narrow/helper.py +23 -0
  13. basilisk_engine-0.0.2/basilisk/draw/__init__.py +0 -0
  14. basilisk_engine-0.0.2/basilisk/draw/draw.py +101 -0
  15. basilisk_engine-0.0.2/basilisk/draw/draw_handler.py +208 -0
  16. basilisk_engine-0.0.2/basilisk/draw/font_renderer.py +28 -0
  17. basilisk_engine-0.0.2/basilisk/generic/__init__.py +0 -0
  18. basilisk_engine-0.0.2/basilisk/generic/abstract_bvh.py +16 -0
  19. basilisk_engine-0.0.2/basilisk/generic/collisions.py +26 -0
  20. basilisk_engine-0.0.2/basilisk/generic/input_validation.py +28 -0
  21. basilisk_engine-0.0.2/basilisk/generic/math.py +7 -0
  22. basilisk_engine-0.0.2/basilisk/generic/matrices.py +34 -0
  23. basilisk_engine-0.0.2/basilisk/generic/meshes.py +73 -0
  24. basilisk_engine-0.0.2/basilisk/generic/quat.py +119 -0
  25. basilisk_engine-0.0.2/basilisk/generic/quat_methods.py +8 -0
  26. basilisk_engine-0.0.2/basilisk/generic/vec3.py +112 -0
  27. basilisk_engine-0.0.2/basilisk/input/__init__.py +1 -0
  28. basilisk_engine-0.0.2/basilisk/input/mouse.py +60 -0
  29. basilisk_engine-0.0.2/basilisk/mesh/__init__.py +0 -0
  30. basilisk_engine-0.0.2/basilisk/mesh/built-in/__init__.py +0 -0
  31. basilisk_engine-0.0.2/basilisk/mesh/cube.py +20 -0
  32. basilisk_engine-0.0.2/basilisk/mesh/mesh.py +216 -0
  33. basilisk_engine-0.0.2/basilisk/mesh/mesh_from_data.py +48 -0
  34. basilisk_engine-0.0.2/basilisk/mesh/model.py +272 -0
  35. basilisk_engine-0.0.2/basilisk/mesh/narrow_aabb.py +81 -0
  36. basilisk_engine-0.0.2/basilisk/mesh/narrow_bvh.py +84 -0
  37. basilisk_engine-0.0.2/basilisk/mesh/narrow_primative.py +24 -0
  38. basilisk_engine-0.0.2/basilisk/nodes/__init__.py +0 -0
  39. basilisk_engine-0.0.2/basilisk/nodes/node.py +508 -0
  40. basilisk_engine-0.0.2/basilisk/nodes/node_handler.py +94 -0
  41. basilisk_engine-0.0.2/basilisk/physics/__init__.py +0 -0
  42. basilisk_engine-0.0.2/basilisk/physics/physics_body.py +36 -0
  43. basilisk_engine-0.0.2/basilisk/physics/physics_engine.py +37 -0
  44. basilisk_engine-0.0.2/basilisk/render/__init__.py +0 -0
  45. basilisk_engine-0.0.2/basilisk/render/batch.py +85 -0
  46. basilisk_engine-0.0.2/basilisk/render/camera.py +166 -0
  47. basilisk_engine-0.0.2/basilisk/render/chunk.py +85 -0
  48. basilisk_engine-0.0.2/basilisk/render/chunk_handler.py +139 -0
  49. basilisk_engine-0.0.2/basilisk/render/frame.py +182 -0
  50. basilisk_engine-0.0.2/basilisk/render/image.py +76 -0
  51. basilisk_engine-0.0.2/basilisk/render/image_handler.py +119 -0
  52. basilisk_engine-0.0.2/basilisk/render/light.py +97 -0
  53. basilisk_engine-0.0.2/basilisk/render/light_handler.py +54 -0
  54. basilisk_engine-0.0.2/basilisk/render/material.py +196 -0
  55. basilisk_engine-0.0.2/basilisk/render/material_handler.py +123 -0
  56. basilisk_engine-0.0.2/basilisk/render/shader_handler.py +95 -0
  57. basilisk_engine-0.0.2/basilisk/render/sky.py +118 -0
  58. basilisk_engine-0.0.2/basilisk/shaders/__init__.py +0 -0
  59. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/basilisk_engine.egg-info/PKG-INFO +1 -1
  60. basilisk_engine-0.0.2/basilisk_engine.egg-info/SOURCES.txt +68 -0
  61. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/setup.py +1 -1
  62. basilisk_engine-0.0.1/basilisk_engine.egg-info/SOURCES.txt +0 -11
  63. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/README.md +0 -0
  64. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/basilisk/__init__.py +0 -0
  65. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/basilisk/config.py +0 -0
  66. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/basilisk/engine.py +0 -0
  67. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/basilisk/scene.py +0 -0
  68. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/basilisk_engine.egg-info/dependency_links.txt +0 -0
  69. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/basilisk_engine.egg-info/requires.txt +0 -0
  70. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/basilisk_engine.egg-info/top_level.txt +0 -0
  71. {basilisk_engine-0.0.1 → basilisk_engine-0.0.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: basilisk-engine
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Python 3D Framework
5
5
  Home-page: https://basilisk-website.vercel.app/
6
6
  Author: Name
File without changes
File without changes
@@ -0,0 +1,96 @@
1
+ import glm
2
+ from ...generic.abstract_bvh import AbstractAABB as AABB
3
+ from ...generic.collisions import collide_aabb_aabb
4
+ from ...generic.meshes import get_aabb_surface_area
5
+ from ..collider import Collider
6
+
7
+
8
+ class BroadAABB(AABB):
9
+ a: AABB | Collider
10
+ """The first child of the AABB"""
11
+ b: AABB | Collider
12
+ """The second child of the AABB"""
13
+ top_right: glm.vec3
14
+ """furthest positive vertex of the AABB"""
15
+ bottom_left: glm.vec3
16
+ """furthest negative vertex of the AABB"""
17
+ parent: AABB
18
+ """Back reference to the parent AABB"""
19
+
20
+ def __init__(self, a: AABB | Collider, b: AABB | Collider, parent: AABB) -> None:
21
+ self.a = a
22
+ self.b = b
23
+ self.parent = parent
24
+
25
+ # calculate extreme points
26
+ self.update_points()
27
+
28
+ def update_points(self) -> None:
29
+ """
30
+ Updates the extreme points of the AABB based on the children
31
+ """
32
+ self.top_right = glm.max(self.a.top_right, self.b.top_right)
33
+ self.bottom_left = glm.min(self.a.bottom_left, self.b.bottom_left)
34
+
35
+ def find_sibling(self, collider: Collider, inherited: float) -> tuple[float, AABB | Collider]:
36
+ """
37
+ Determines the best sibling for inserting a collider into the BVH
38
+ """
39
+ # compute estimate sa
40
+ top_right = glm.max(self.top_right, collider.top_right)
41
+ bottom_left = glm.min(self.bottom_left, collider.bottom_left)
42
+ union_area = get_aabb_surface_area(top_right, bottom_left)
43
+
44
+ # compute lowest cost and determine if children are a viable option
45
+ c_best = union_area + inherited
46
+
47
+ delta_surface_area = union_area - self.surface_area
48
+
49
+ c_low = collider.aabb_surface_area + delta_surface_area + inherited
50
+
51
+ # investigate children
52
+ best_sibling = self
53
+ if c_low >= c_best: return c_best, best_sibling
54
+
55
+ for child in (self.a, self.b):
56
+ if isinstance(child, BroadAABB): child_c, child_aabb = child.find_sibling(collider, inherited + delta_surface_area)
57
+ else:
58
+ # compute cost for child
59
+ top_right = glm.max(self.top_right, child.top_right)
60
+ bottom_left = glm.min(self.bottom_left, child.bottom_left)
61
+ union_area = get_aabb_surface_area(top_right, bottom_left)
62
+
63
+ child_c, child_aabb = union_area + inherited, child
64
+
65
+ if child_c < c_best: c_best, best_sibling = child_c, child_aabb
66
+
67
+ return c_best, best_sibling
68
+
69
+ def get_collided(self, collider: Collider) -> list[Collider]:
70
+ """
71
+ Returns which objects may be colliding from the BVH
72
+ """
73
+ if not collide_aabb_aabb(self.top_right, self.bottom_left, collider.top_right, collider.bottom_left): return []
74
+
75
+ # test children
76
+ possible = []
77
+ if isinstance(self.a, BroadAABB): possible.extend(self.a.get_collided(collider))
78
+ elif collide_aabb_aabb(self.a.top_right, self.a.bottom_left, collider.top_right, collider.bottom_left): possible.append(self.a)
79
+ if isinstance(self.b, BroadAABB): possible.extend(self.b.get_collided(collider))
80
+ elif collide_aabb_aabb(self.b.top_right, self.b.bottom_left, collider.top_right, collider.bottom_left): possible.append(self.b)
81
+ return possible
82
+
83
+ def get_all_aabbs(self, layer: int) -> list[tuple[glm.vec3, glm.vec3, int]]: # TODO test function
84
+ """
85
+ Returns all AABBs, their extreme points, and their layer
86
+ """
87
+ aabbs = [(self.top_right, self.bottom_left, layer)]
88
+ if isinstance(self.a, BroadAABB): aabbs += self.a.get_all_aabbs(layer + 1)
89
+ else: aabbs.append((self.a.top_right, self.a.bottom_left, layer + 1))
90
+ if isinstance(self.b, BroadAABB): aabbs += self.b.get_all_aabbs(layer + 1)
91
+ else: aabbs.append((self.b.top_right, self.b.bottom_left, layer + 1))
92
+ return aabbs
93
+
94
+ @property
95
+ def surface_area(self): return get_aabb_surface_area(self.top_right, self.bottom_left)
96
+
@@ -0,0 +1,102 @@
1
+ import glm
2
+ from .broad_aabb import BroadAABB
3
+ from ..collider import Collider
4
+ from ...generic.abstract_bvh import AbstractBVH as BVH
5
+ from ...generic.meshes import get_aabb_surface_area
6
+
7
+ class BroadBVH(BVH):
8
+ root: BroadAABB
9
+ """The root node of the BVH"""
10
+ collider_handler: ...
11
+ """Back reference to the collider ahndler for accessing colliders"""
12
+
13
+ def __init__(self, collider_handler) -> None:
14
+ self.collider_handler = collider_handler
15
+ self.root = None
16
+
17
+ def add(self, collider: Collider) -> None:
18
+ """
19
+ Adds a single collider to the bvh tree
20
+ """
21
+ # test if tree needs to be initiated
22
+ if not self.root:
23
+ self.root = collider # TODO ensure that this is final format for primative
24
+ return
25
+
26
+ # check if root is primative
27
+ if isinstance(self.root, Collider):
28
+ sibling = self.root
29
+ self.root = BroadAABB(sibling, collider, None)
30
+ sibling.parent = collider.parent = self.root
31
+ return
32
+
33
+ # find the best sibling (c_best only used during the recursion)
34
+ c_best, sibling = self.root.find_sibling(collider, 0)
35
+ old_parent = sibling.parent
36
+ new_parent = BroadAABB(sibling, collider, old_parent)
37
+
38
+ # if the sibling was not the root
39
+ if old_parent:
40
+ if old_parent.a == sibling: old_parent.a = new_parent
41
+ else: old_parent.b = new_parent
42
+ else: self.root = new_parent
43
+
44
+ sibling.parent = new_parent
45
+ collider.parent = new_parent
46
+
47
+ # walk back up tree and refit TODO add tree rotations
48
+ aabb = new_parent
49
+ while aabb:
50
+ aabb.update_points()
51
+ self.rotate(aabb)
52
+ aabb = aabb.parent
53
+
54
+ def get_all_aabbs(self) -> list[tuple[glm.vec3, glm.vec3, int]]: # TODO test function
55
+ """
56
+ Returns all AABBs, their extreme points, and their layer
57
+ """
58
+ if isinstance(self.root, BroadAABB): return self.root.get_all_aabbs(0)
59
+ return [(self.root.top_right, self.root.bottom_left, 0)]
60
+
61
+ def remove(self, collider: Collider) -> None: ...
62
+
63
+ def rotate(self, aabb: BroadAABB) -> None:
64
+ """
65
+ Rotates the BVH tree to reduce surface area of internal AABBs
66
+ """
67
+ # determine if rotation is possible
68
+ parent: BroadAABB | None = aabb.parent
69
+ if not parent: return
70
+
71
+ grand = parent.parent
72
+ if not grand: return
73
+
74
+ # determine if swapping
75
+ aunt = grand.b if grand.a == parent else grand.a
76
+ sibling = parent.b if parent.a == aabb else parent.a
77
+
78
+ top_right = glm.max(aunt.top_right, sibling.top_right)
79
+ bottom_left = glm.min(aunt.bottom_left, sibling.bottom_left)
80
+ aunt_sibling_area = get_aabb_surface_area(top_right, bottom_left)
81
+
82
+ if aunt_sibling_area > parent.surface_area: return
83
+
84
+ # rotate tree if necessary
85
+ if grand.a == aunt: grand.a = aabb
86
+ else: grand.b = aabb
87
+
88
+ if parent.a == aabb: parent.a = aunt
89
+ else: parent.b = aunt
90
+
91
+ # reset parents and update points to resize AABBs
92
+ aunt.parent = parent
93
+ aabb.parent = grand
94
+
95
+ parent.update_points()
96
+ grand.update_points()
97
+
98
+ def get_collided(self, collider: Collider) -> list[Collider]:
99
+ """
100
+ Returns which objects may be colliding from the BVH
101
+ """
102
+ return self.root.get_collided(collider)
@@ -0,0 +1,75 @@
1
+ import glm
2
+ from ..generic.abstract_bvh import AbstractAABB as AABB
3
+ from ..generic.meshes import transform_points, get_aabb_surface_area
4
+ from ..mesh.cube import cube
5
+ from ..mesh.mesh import Mesh
6
+
7
+ class Collider():
8
+ node: ...
9
+ """Back reference to the node"""
10
+ collider_handler: ...
11
+ """Back reference to the collider handler"""
12
+ half_dimensions: glm.vec3
13
+ """The axis aligned dimensions of the transformed mesh"""
14
+ static_friction: float = 0.8 # input from node constructor
15
+ """Determines the friction of the node when still: recommended 0 - 1"""
16
+ kinetic_friction: float = 0.3 # input from node constructor
17
+ """Determines the friction of the node when moving: recommended 0 - 1"""
18
+ elasticity: float = 0.1 # input from node constructor
19
+ """Determines how bouncy an object is: recommended 0 - 1"""
20
+ collision_group: str # input from node constructor
21
+ """Nodes of the same collision group do not collide with each other"""
22
+ has_collided: bool
23
+ """Stores whether or not the collider has been collided with in the last frame"""
24
+ collision_velocity: float
25
+ """Stores the highest velocity from a collision on this collider from the last frame"""
26
+ collisions: dict # {node : (normal, velocity, depth)} TODO determine which variables need to be stored
27
+ """Stores data from collisions in the previous frame"""
28
+ top_right: glm.vec3
29
+ """AABB most positive corner"""
30
+ bottom_left: glm.vec3
31
+ """AABB most negative corner"""
32
+ aabb_surface_area: float
33
+ """The surface area of the collider's AABB"""
34
+ parent: AABB
35
+ """Reference to the parent AABB in the broad BVH"""
36
+ mesh: Mesh
37
+ """Reference to the colliding mesh"""
38
+
39
+ 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):
40
+ self.collider_handler = collider_handler
41
+ self.node = node
42
+ self.mesh = cube if box_mesh else self.node.mesh
43
+ self.static_friction = static_friction
44
+ self.kinetic_friction = kinetic_friction
45
+ self.elasticity = elasticity
46
+ self.collision_group = collision_group
47
+ self.collision_velocity = 0
48
+ self.collisions = {}
49
+ self.parent = None
50
+
51
+ # lazy update variables TODO change to distinguish between static and nonstatic objects
52
+ self.needs_obb = True
53
+ self.needs_half_dimensions = True
54
+
55
+ @property
56
+ def has_collided(self): return bool(self.collisions)
57
+ @property
58
+ def half_dimensions(self): # TODO look for optimization
59
+ if self.needs_half_dimensions:
60
+ top_right = glm.max(self.obb_points)
61
+ self._half_dimensions = top_right - self.node.geometric_center
62
+ self.needs_half_dimensions = False
63
+ return self._half_dimensions
64
+ @property
65
+ def bottom_left(self): return self.node.geometric_center - self.half_dimensions
66
+ @property
67
+ def top_right(self): return self.node.geometric_center + self.half_dimensions
68
+ @property
69
+ def aabb_surface_area(self): return get_aabb_surface_area(self.top_right, self.bottom_left)
70
+ @property
71
+ def obb_points(self):
72
+ if self.needs_obb:
73
+ self._obb_points = transform_points(self.mesh.aabb_points, self.node.model_matrix)
74
+ self.needs_obb = False
75
+ return self._obb_points
@@ -0,0 +1,163 @@
1
+ import glm
2
+ from .narrow.gjk import *
3
+ from .collider import Collider
4
+ from .broad.broad_bvh import BroadBVH
5
+ from ..mesh.cube import cube
6
+ from ..generic.collisions import get_sat_axes
7
+ from .narrow.gjk import collide_gjk
8
+ from .narrow.epa import get_epa_from_gjk
9
+
10
+ class ColliderHandler():
11
+ scene: ...
12
+ """Back reference to scene"""
13
+ colliders: list[Collider]
14
+ """Main list of collders contained in the scene"""
15
+ bvh: BroadBVH
16
+ """Broad bottom up BVH containing all colliders in the scene"""
17
+
18
+ def __init__(self, scene) -> None:
19
+ self.scene = scene
20
+ self.colliders = []
21
+ self.bvh = BroadBVH(self)
22
+
23
+ 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:
24
+ """
25
+ Creates a collider and adds it to the collider list
26
+ """
27
+ collider = Collider(self, node, box_mesh, static_friction, kinetic_friction, elasticity, collision_group)
28
+ self.colliders.append(collider)
29
+ self.bvh.add(collider)
30
+ return collider
31
+
32
+ def remove(self, collider: Collider) -> None:
33
+ """
34
+ Removes a collider from the main branch and BVH
35
+ """
36
+ if collider in self.colliders: self.colliders.remove(collider)
37
+ self.bvh.remove(collider)
38
+ collider.collider_handler = None
39
+
40
+ def resolve_collisions(self) -> None:
41
+ """
42
+ Resets collider collision values and resolves all collisions in the scene
43
+ """
44
+ # reset collision data
45
+ for collider in self.colliders: collider.collisions = {}
46
+ # TODO update BVH
47
+ broad_collisions = self.resolve_broad_collisions()
48
+ self.resolve_narrow_collisions(broad_collisions)
49
+
50
+ def collide_obb_obb(self, collider1: Collider, collider2: Collider) -> tuple[glm.vec3, float] | None:
51
+ """
52
+ Finds the minimal penetrating vector for an obb obb collision, return None if not colliding. Uses SAT.
53
+ """
54
+ axes = get_sat_axes(collider1.node.rotation, collider2.node.rotation) # axes are normaized
55
+ points1 = collider1.obb_points # TODO remove once oobb points are lazy updated, switch to just using property
56
+ points2 = collider2.obb_points
57
+
58
+ # test axes
59
+ small_axis = None
60
+ small_overlap = 1e10
61
+ for axis in axes: # TODO add optimization for points on cardinal axis of cuboid
62
+ # "project" points
63
+ proj1 = [glm.dot(p, axis) for p in points1]
64
+ proj2 = [glm.dot(p, axis) for p in points2]
65
+ max1, min1 = max(proj1), min(proj1)
66
+ max2, min2 = max(proj2), min(proj2)
67
+ if max1 < min2 or max2 < min1: return None
68
+
69
+ # if lines are not intersecting
70
+ if max1 > max2 and min1 < min2: overlap = min(max1 - min2, max2 - min1)
71
+ elif max2 > max1 and min2 < min1: overlap = min(max1 - min2, max2 - min1)
72
+ else: overlap = min(max1, max2) - max(min1, min2) # TODO check if works with containment
73
+
74
+ if abs(overlap) > abs(small_overlap): continue
75
+ small_overlap = overlap
76
+ small_axis = axis
77
+
78
+ print(axes.index(small_axis), glm.length(small_axis))
79
+ print('overlap:', small_overlap)
80
+
81
+ return small_axis, small_overlap
82
+
83
+ def collide_obb_obb_decision(self, collider1: Collider, collider2: Collider) -> bool:
84
+ """
85
+ Determines if two obbs are colliding Uses SAT.
86
+ """
87
+ axes = get_sat_axes(collider1.node.rotation, collider2.node.rotation)
88
+ points1 = collider1.obb_points # TODO remove once oobb points are lazy updated, switch to just using property
89
+ points2 = collider2.obb_points
90
+
91
+ # test axes
92
+ for axis in axes: # TODO add optimization for points on cardinal axis of cuboid
93
+ # "project" points
94
+ proj1 = [glm.dot(p, axis) for p in points1]
95
+ proj2 = [glm.dot(p, axis) for p in points2]
96
+ max1, min1 = max(proj1), min(proj1)
97
+ max2, min2 = max(proj2), min(proj2)
98
+ if max1 < min2 or max2 < min1: return False
99
+
100
+ return True
101
+
102
+ def resolve_broad_collisions(self) -> set[tuple[Collider, Collider]]:
103
+ """
104
+ Determines which colliders collide with each other from the BVH
105
+ """
106
+ collisions = set()
107
+ for collider1 in self.colliders:
108
+
109
+ # traverse bvh to find aabb aabb collisions
110
+ colliding = self.bvh.get_collided(collider1)
111
+ for collider2 in colliding:
112
+ if collider1 == collider2: continue
113
+ if (collider1, collider2) in collisions or (collider2, collider1) in collisions: continue # TODO find a secure way for ordering colliders
114
+
115
+ # run broad collision for specified mesh types
116
+ 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
117
+
118
+ collisions.add((collider1, collider2)) # TODO find a secure way for ordering colliders
119
+
120
+ return collisions
121
+
122
+ def resolve_narrow_collisions(self, broad_collisions: list[tuple[Collider, Collider]]) -> None:
123
+ """
124
+ Determines if two colliders are colliding, if so resolves their penetration and applies impulse
125
+ """
126
+ collided = []
127
+
128
+ for collision in broad_collisions: # assumes that broad collisions are unique
129
+ collider1 = collision[0]
130
+ collider2 = collision[1]
131
+ node1: Node = collider1.node
132
+ node2: Node = collider2.node
133
+
134
+ # get peneration data or quit early if no collision is found
135
+ if collider1.mesh == cube and collider2.mesh == cube: # obb-obb collision
136
+
137
+ # run SAT for obb-obb (includes peneration)
138
+ data = self.collide_obb_obb(collider1, collider2)
139
+ if not data: continue
140
+
141
+ vec, distance = data
142
+
143
+ else: # use gjk to determine collisions between non-cuboid meshes
144
+ has_collided, simplex = collide_gjk(node1, node2)
145
+ if not has_collided: continue
146
+
147
+ face, polytope = get_epa_from_gjk(node1, node2, simplex)
148
+ vec, distance = face[1], face[0]
149
+
150
+ if glm.dot(vec, node2.position - node1.position) > 0:
151
+ vec *= -1
152
+
153
+ print('\033[92m', vec, distance, '\033[0m')
154
+
155
+ # resolve collision penetration
156
+ node2.position -= vec * distance
157
+
158
+ collided.append((node1, node2, vec * distance))
159
+
160
+ # TODO add penetration resolution
161
+ # TODO add impulse
162
+
163
+ return collided
@@ -0,0 +1,86 @@
1
+ import glm
2
+ from .helper import get_support_point
3
+ from...nodes.node import Node
4
+
5
+
6
+ # TODO change these to structs when converting to C++
7
+ 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
+
10
+ def get_epa_from_gjk(node1: Node, node2: Node, polytope: polytope_type, epsilon: float=1e-7) -> tuple: # TODO determine the return type of get_epa_from_gjk
11
+ """
12
+ 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
+ # orient faces to point normals counter clockwise
15
+ faces: face_type = []
16
+ for indices in [(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)]: faces = insert_face(polytope, faces, indices)
17
+
18
+ # develope the polytope until the nearest real face has been found, within epsilon
19
+ while True:
20
+ 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:
22
+ faces[0] = list(faces[0])
23
+ faces[0][0] = glm.sqrt(faces[0][0]) # square root distance squared to get real distance
24
+ faces[0][1] = glm.normalize(faces[0][1])
25
+ return faces[0], polytope
26
+ faces, polytope = insert_point(polytope, faces, new_point)
27
+
28
+ def insert_point(polytope: polytope_type, faces: face_type, point: glm.vec3, epsilon: float=1e-7) -> tuple[face_type, polytope_type]:
29
+ """
30
+ Inserts a point into the polytope sorting by distance from the origin
31
+ """
32
+ # determine which faces are facing the new point
33
+ polytope.append(point)
34
+ support_index = len(polytope) - 1
35
+ visible_faces = [
36
+ face for face in faces
37
+ if glm.dot(face[1], polytope[support_index][0]) >= epsilon and # if the normal of a face is pointing towards the added point
38
+ glm.dot(face[1], polytope[support_index][0] - face[2]) >= epsilon # TODO check if this ever occurs
39
+ ]
40
+
41
+ # generate new edges
42
+ edges = []
43
+ for face in visible_faces:
44
+ for p1, p2 in get_face_edges(face):
45
+ if (p2, p1) in edges: edges.remove((p2, p1)) # edges can only be shared by two faces, running opposite to each other.
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]][0] + polytope[indices[1]][0] + polytope[indices[2]][0]) / 3
62
+ distance = glm.length2(center) # TODO face distance is length squared to reduce calculations
63
+ 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
64
+ new_face = (distance, normal, center, *indices)
65
+
66
+ # insert faces into priority queue based on distance from origin
67
+ for i, face in enumerate(faces):
68
+ if face[0] < distance: continue
69
+ faces.insert(i, new_face)
70
+ break
71
+ else: faces.append(new_face)
72
+
73
+ return faces
74
+
75
+ def orient_face(polytope: polytope_type, indices: tuple[int, int, int]) -> tuple[int, int, int]: # TODO finish this
76
+ """
77
+ Orients the face indices to have a counter clockwise normal
78
+ """
79
+ 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])
80
+ return indices
81
+
82
+ def get_face_edges(face: tuple[float, glm.vec3, glm.vec3, int, int, int]) -> list[tuple[int, int]]:
83
+ """
84
+ Permutes a tuple of three unique numbers (a, b, c) into 3 pairs (x, y), preserving order
85
+ """
86
+ return [(face[3], face[4]), (face[4], face[5]), (face[5], face[3])]
@@ -0,0 +1,66 @@
1
+ import glm
2
+ from .helper import get_support_point
3
+ from ...nodes.node import Node
4
+ from ...generic.math import triple_product
5
+
6
+
7
+ def collide_gjk(node1: Node, node2: Node, iterations: int=20) -> tuple: # TODO figure out return data type
8
+ """
9
+ Determines if two convex polyhedra collide, returns the polytope if there is a collision.
10
+ """
11
+ # generate starting values
12
+ dir_vec = node1.position - node2.position
13
+ simplex = [get_support_point(node1, node2, dir_vec)]
14
+ dir_vec = -simplex[0][0] # set direction to point away from starting simplex point
15
+
16
+ for _ in range(iterations):
17
+ # gets support point and checks if its across the origin
18
+ test_point = get_support_point(node1, node2, dir_vec)
19
+ if glm.dot(test_point[0], dir_vec) < -1e-7: return False, simplex
20
+
21
+ # add point and find new direction vector
22
+ simplex.append(test_point)
23
+ check, dir_vec, simplex = handle_simplex(simplex)
24
+
25
+ if check: return True, simplex
26
+ return False, simplex # timeout due to too many checks, usually float errors
27
+
28
+ def handle_simplex(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
29
+ """
30
+ Call proper function based on number of support points
31
+ """
32
+ num = len(simplex) # not using match case to support Python < 3.10
33
+ if num == 2: return handle_simplex_line(simplex)
34
+ if num == 3: return handle_simplex_triangle(simplex)
35
+ return handle_simplex_tetrahedron(simplex) # simplex must be 4 points
36
+
37
+ def handle_simplex_line(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
38
+ """
39
+ Returns the perpendicular vector to the simplex line
40
+ """
41
+ vec_ab = simplex[1][0] - simplex[0][0]
42
+ return False, triple_product(vec_ab, -simplex[0][0], vec_ab), simplex
43
+
44
+ def handle_simplex_triangle(simplex: list) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
45
+ """
46
+ Returns the normal vector of the triangoe pointed towards the origin
47
+ """
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
50
+
51
+ def handle_simplex_tetrahedron(simplex: list, epsilon: float=0) -> tuple[bool, glm.vec3, list[tuple[glm.vec3, glm.vec3, glm.vec3]]]:
52
+ """
53
+ Perform collision check and remove support point if no collision is found
54
+ """
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]
59
+
60
+ 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
+ for normal_vec, index in vectors:
62
+ dot_product = glm.dot(normal_vec, vec_do)
63
+ if dot_product > epsilon:
64
+ simplex.pop(index)
65
+ return False, normal_vec, simplex
66
+ return True, None, simplex
@@ -0,0 +1,23 @@
1
+ import glm
2
+ from ...nodes.node import Node
3
+
4
+
5
+ def get_support_point(node1: Node, node2: Node, dir_vec: glm.vec3) -> tuple[glm.vec3, glm.vec3, glm.vec3]:
6
+ """
7
+ Outputs the best support point to be added to the polytop based on the direction vector.
8
+ """
9
+ vertex1 = get_furthest_point(node1, dir_vec)
10
+ vertex2 = get_furthest_point(node2, -dir_vec)
11
+ return (vertex1 - vertex2, vertex1, vertex2)
12
+
13
+ def get_furthest_point(node: Node, dir_vec: glm.vec3) -> glm.vec3:
14
+ """
15
+ Determines the furthest point in a given direction
16
+ """
17
+ # determine furthest point by using untransformed mesh
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)
20
+ vertex = node.model_matrix * glm.vec4(vertex, 1.0)
21
+
22
+ # transform point to world space
23
+ return glm.vec3(vertex)
File without changes