basilisk-engine 0.0.1__tar.gz → 0.0.3__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.
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/PKG-INFO +1 -1
- basilisk_engine-0.0.3/basilisk/bsk_assets/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/collisions/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/collisions/broad/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/collisions/broad/broad_aabb.py +96 -0
- basilisk_engine-0.0.3/basilisk/collisions/broad/broad_bvh.py +102 -0
- basilisk_engine-0.0.3/basilisk/collisions/collider.py +75 -0
- basilisk_engine-0.0.3/basilisk/collisions/collider_handler.py +163 -0
- basilisk_engine-0.0.3/basilisk/collisions/narrow/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/collisions/narrow/epa.py +86 -0
- basilisk_engine-0.0.3/basilisk/collisions/narrow/gjk.py +66 -0
- basilisk_engine-0.0.3/basilisk/collisions/narrow/helper.py +23 -0
- basilisk_engine-0.0.3/basilisk/draw/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/draw/draw.py +101 -0
- basilisk_engine-0.0.3/basilisk/draw/draw_handler.py +208 -0
- basilisk_engine-0.0.3/basilisk/draw/font_renderer.py +28 -0
- basilisk_engine-0.0.3/basilisk/generic/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/generic/abstract_bvh.py +16 -0
- basilisk_engine-0.0.3/basilisk/generic/collisions.py +26 -0
- basilisk_engine-0.0.3/basilisk/generic/input_validation.py +28 -0
- basilisk_engine-0.0.3/basilisk/generic/math.py +7 -0
- basilisk_engine-0.0.3/basilisk/generic/matrices.py +34 -0
- basilisk_engine-0.0.3/basilisk/generic/meshes.py +73 -0
- basilisk_engine-0.0.3/basilisk/generic/quat.py +119 -0
- basilisk_engine-0.0.3/basilisk/generic/quat_methods.py +8 -0
- basilisk_engine-0.0.3/basilisk/generic/vec3.py +112 -0
- basilisk_engine-0.0.3/basilisk/input/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/input/mouse.py +60 -0
- basilisk_engine-0.0.3/basilisk/mesh/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/mesh/built-in/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/mesh/cube.py +20 -0
- basilisk_engine-0.0.3/basilisk/mesh/mesh.py +216 -0
- basilisk_engine-0.0.3/basilisk/mesh/mesh_from_data.py +48 -0
- basilisk_engine-0.0.3/basilisk/mesh/model.py +272 -0
- basilisk_engine-0.0.3/basilisk/mesh/narrow_aabb.py +81 -0
- basilisk_engine-0.0.3/basilisk/mesh/narrow_bvh.py +84 -0
- basilisk_engine-0.0.3/basilisk/mesh/narrow_primative.py +24 -0
- basilisk_engine-0.0.3/basilisk/nodes/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/nodes/node.py +508 -0
- basilisk_engine-0.0.3/basilisk/nodes/node_handler.py +94 -0
- basilisk_engine-0.0.3/basilisk/physics/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/physics/physics_body.py +36 -0
- basilisk_engine-0.0.3/basilisk/physics/physics_engine.py +37 -0
- basilisk_engine-0.0.3/basilisk/render/__init__.py +0 -0
- basilisk_engine-0.0.3/basilisk/render/batch.py +85 -0
- basilisk_engine-0.0.3/basilisk/render/camera.py +166 -0
- basilisk_engine-0.0.3/basilisk/render/chunk.py +85 -0
- basilisk_engine-0.0.3/basilisk/render/chunk_handler.py +139 -0
- basilisk_engine-0.0.3/basilisk/render/frame.py +182 -0
- basilisk_engine-0.0.3/basilisk/render/image.py +76 -0
- basilisk_engine-0.0.3/basilisk/render/image_handler.py +119 -0
- basilisk_engine-0.0.3/basilisk/render/light.py +97 -0
- basilisk_engine-0.0.3/basilisk/render/light_handler.py +54 -0
- basilisk_engine-0.0.3/basilisk/render/material.py +196 -0
- basilisk_engine-0.0.3/basilisk/render/material_handler.py +123 -0
- basilisk_engine-0.0.3/basilisk/render/shader_handler.py +95 -0
- basilisk_engine-0.0.3/basilisk/render/sky.py +118 -0
- basilisk_engine-0.0.3/basilisk/shaders/__init__.py +0 -0
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/basilisk_engine.egg-info/PKG-INFO +1 -1
- basilisk_engine-0.0.3/basilisk_engine.egg-info/SOURCES.txt +68 -0
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/setup.py +1 -1
- basilisk_engine-0.0.1/basilisk_engine.egg-info/SOURCES.txt +0 -11
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/README.md +0 -0
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/basilisk/__init__.py +0 -0
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/basilisk/config.py +0 -0
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/basilisk/engine.py +0 -0
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/basilisk/scene.py +0 -0
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/basilisk_engine.egg-info/dependency_links.txt +0 -0
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/basilisk_engine.egg-info/requires.txt +0 -0
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/basilisk_engine.egg-info/top_level.txt +0 -0
- {basilisk_engine-0.0.1 → basilisk_engine-0.0.3}/setup.cfg +0 -0
|
File without changes
|
|
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
|
|
File without changes
|
|
@@ -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
|