basilisk-engine 0.1.14__py3-none-any.whl → 0.1.16__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.
- basilisk/__init__.py +14 -14
- basilisk/audio/sound.py +27 -27
- basilisk/bsk_assets/cube.obj +48 -48
- basilisk/collisions/broad/broad_aabb.py +102 -102
- basilisk/collisions/broad/broad_bvh.py +137 -137
- basilisk/collisions/collider.py +95 -95
- basilisk/collisions/collider_handler.py +224 -224
- basilisk/collisions/narrow/contact_manifold.py +95 -95
- basilisk/collisions/narrow/dataclasses.py +34 -34
- basilisk/collisions/narrow/deprecated.py +46 -46
- basilisk/collisions/narrow/epa.py +91 -91
- basilisk/collisions/narrow/gjk.py +66 -66
- basilisk/collisions/narrow/graham_scan.py +24 -24
- basilisk/collisions/narrow/helper.py +29 -29
- basilisk/collisions/narrow/line_intersections.py +106 -106
- basilisk/collisions/narrow/sutherland_hodgman.py +75 -75
- basilisk/config.py +2 -2
- basilisk/draw/draw.py +100 -100
- basilisk/draw/draw_handler.py +179 -179
- basilisk/draw/font_renderer.py +28 -28
- basilisk/engine.py +206 -206
- basilisk/generic/abstract_bvh.py +15 -15
- basilisk/generic/abstract_custom.py +133 -133
- basilisk/generic/collisions.py +72 -72
- basilisk/generic/input_validation.py +66 -66
- basilisk/generic/math.py +6 -6
- basilisk/generic/matrices.py +35 -35
- basilisk/generic/meshes.py +72 -72
- basilisk/generic/quat.py +142 -142
- basilisk/generic/quat_methods.py +7 -7
- basilisk/generic/raycast_result.py +26 -26
- basilisk/generic/vec3.py +143 -143
- basilisk/input/mouse.py +61 -61
- basilisk/input/path.py +14 -14
- basilisk/mesh/cube.py +33 -33
- basilisk/mesh/mesh.py +230 -230
- basilisk/mesh/mesh_from_data.py +130 -130
- basilisk/mesh/model.py +271 -271
- basilisk/mesh/narrow_aabb.py +89 -89
- basilisk/mesh/narrow_bvh.py +91 -91
- basilisk/mesh/narrow_primative.py +23 -23
- basilisk/nodes/helper.py +28 -28
- basilisk/nodes/node.py +684 -684
- basilisk/nodes/node_handler.py +95 -95
- basilisk/particles/particle_handler.py +63 -63
- basilisk/particles/particle_renderer.py +87 -87
- basilisk/physics/impulse.py +112 -112
- basilisk/physics/physics_body.py +43 -43
- basilisk/physics/physics_engine.py +35 -35
- basilisk/render/batch.py +105 -105
- basilisk/render/camera.py +211 -211
- basilisk/render/chunk.py +106 -106
- basilisk/render/chunk_handler.py +165 -165
- basilisk/render/frame.py +101 -101
- basilisk/render/framebuffer.py +130 -130
- basilisk/render/image.py +87 -87
- basilisk/render/image_handler.py +122 -122
- basilisk/render/light.py +96 -96
- basilisk/render/light_handler.py +58 -58
- basilisk/render/material.py +219 -219
- basilisk/render/material_handler.py +135 -135
- basilisk/render/post_process.py +132 -132
- basilisk/render/shader.py +110 -110
- basilisk/render/shader_handler.py +80 -79
- basilisk/render/sky.py +120 -120
- basilisk/scene.py +276 -276
- basilisk/shaders/batch.frag +276 -276
- basilisk/shaders/batch.vert +115 -115
- basilisk/shaders/crt.frag +31 -31
- basilisk/shaders/draw.frag +21 -21
- basilisk/shaders/draw.vert +21 -21
- basilisk/shaders/filter.frag +22 -22
- basilisk/shaders/frame.frag +12 -12
- basilisk/shaders/frame.vert +13 -13
- basilisk/shaders/geometry.frag +8 -8
- basilisk/shaders/geometry.vert +41 -41
- basilisk/shaders/normal.frag +59 -59
- basilisk/shaders/normal.vert +96 -96
- basilisk/shaders/particle.frag +71 -71
- basilisk/shaders/particle.vert +84 -84
- basilisk/shaders/sky.frag +9 -9
- basilisk/shaders/sky.vert +13 -13
- {basilisk_engine-0.1.14.dist-info → basilisk_engine-0.1.16.dist-info}/METADATA +38 -45
- basilisk_engine-0.1.16.dist-info/RECORD +103 -0
- {basilisk_engine-0.1.14.dist-info → basilisk_engine-0.1.16.dist-info}/WHEEL +1 -1
- basilisk_engine-0.1.14.dist-info/RECORD +0 -103
- {basilisk_engine-0.1.14.dist-info → basilisk_engine-0.1.16.dist-info}/top_level.txt +0 -0
basilisk/collisions/collider.py
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
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.mesh import Mesh
|
|
5
|
-
from .narrow.dataclasses import Collision
|
|
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, node, collider_mesh: str|Mesh=None, static_friction: glm.vec3=0.7, kinetic_friction: glm.vec3=0.3, elasticity: glm.vec3=0.2, collision_group: str=None):
|
|
40
|
-
self.collider_handler = None
|
|
41
|
-
self.node = node
|
|
42
|
-
self.static_friction = static_friction if elasticity else 0.8 # added checks to prevent floats being set to None. Also done for kinetic and elasticity
|
|
43
|
-
self.mesh = collider_mesh
|
|
44
|
-
self.kinetic_friction = kinetic_friction if elasticity else 0.4
|
|
45
|
-
self.elasticity = elasticity if elasticity else 0.1
|
|
46
|
-
self.collision_group = collision_group
|
|
47
|
-
self.collision_velocity = 0
|
|
48
|
-
self.collisions: list[Collision] = []
|
|
49
|
-
self.parent = None
|
|
50
|
-
|
|
51
|
-
# lazy update variables TODO change to distinguish between static and nonstatic objects
|
|
52
|
-
self.needs_obb = True # pos, scale, rot
|
|
53
|
-
self.needs_half_dimensions = True # scale, rot
|
|
54
|
-
self.needs_bvh = True # pos, scale, rot
|
|
55
|
-
|
|
56
|
-
def get_vertex(self, index: int) -> glm.vec3:
|
|
57
|
-
"""
|
|
58
|
-
Gets the world space position of a vertex indicated by the index in the mesh
|
|
59
|
-
"""
|
|
60
|
-
return glm.vec3(self.node.model_matrix * glm.vec4(*self.mesh.points[index], 1))
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def collider_handler(self): return self._collider_handler
|
|
64
|
-
@property
|
|
65
|
-
def has_collided(self): return bool(self.collisions)
|
|
66
|
-
@property
|
|
67
|
-
def half_dimensions(self): # TODO look for optimization
|
|
68
|
-
if self.needs_half_dimensions:
|
|
69
|
-
top_right = glm.max(self.obb_points)
|
|
70
|
-
self._half_dimensions = top_right - self.node.geometric_center
|
|
71
|
-
self.needs_half_dimensions = False
|
|
72
|
-
return self._half_dimensions
|
|
73
|
-
@property
|
|
74
|
-
def bottom_left(self): return self.node.geometric_center - self.half_dimensions
|
|
75
|
-
@property
|
|
76
|
-
def top_right(self): return self.node.geometric_center + self.half_dimensions
|
|
77
|
-
@property
|
|
78
|
-
def aabb_surface_area(self): return get_aabb_surface_area(self.top_right, self.bottom_left)
|
|
79
|
-
@property
|
|
80
|
-
def obb_points(self):
|
|
81
|
-
if self.needs_obb:
|
|
82
|
-
self._obb_points = transform_points(self.mesh.aabb_points, self.node.model_matrix)
|
|
83
|
-
self.needs_obb = False
|
|
84
|
-
return self._obb_points
|
|
85
|
-
|
|
86
|
-
@collider_handler.setter
|
|
87
|
-
def collider_handler(self, value):
|
|
88
|
-
self._collider_handler = value
|
|
89
|
-
if not value: return
|
|
90
|
-
if self.mesh is None: self.mesh = self.node.mesh
|
|
91
|
-
elif isinstance(self.mesh, Mesh): ...
|
|
92
|
-
elif isinstance(self.mesh, str):
|
|
93
|
-
if self.mesh =='box': self.mesh = value.cube
|
|
94
|
-
else: raise ValueError(f'Incorrect built-in mesh type {self.mesh}')
|
|
95
|
-
else: raise ValueError(f'Unkown type for mesh, got {type(self.mesh)}')
|
|
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.mesh import Mesh
|
|
5
|
+
from .narrow.dataclasses import Collision
|
|
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, node, collider_mesh: str|Mesh=None, static_friction: glm.vec3=0.7, kinetic_friction: glm.vec3=0.3, elasticity: glm.vec3=0.2, collision_group: str=None):
|
|
40
|
+
self.collider_handler = None
|
|
41
|
+
self.node = node
|
|
42
|
+
self.static_friction = static_friction if elasticity else 0.8 # added checks to prevent floats being set to None. Also done for kinetic and elasticity
|
|
43
|
+
self.mesh = collider_mesh
|
|
44
|
+
self.kinetic_friction = kinetic_friction if elasticity else 0.4
|
|
45
|
+
self.elasticity = elasticity if elasticity else 0.1
|
|
46
|
+
self.collision_group = collision_group
|
|
47
|
+
self.collision_velocity = 0
|
|
48
|
+
self.collisions: list[Collision] = []
|
|
49
|
+
self.parent = None
|
|
50
|
+
|
|
51
|
+
# lazy update variables TODO change to distinguish between static and nonstatic objects
|
|
52
|
+
self.needs_obb = True # pos, scale, rot
|
|
53
|
+
self.needs_half_dimensions = True # scale, rot
|
|
54
|
+
self.needs_bvh = True # pos, scale, rot
|
|
55
|
+
|
|
56
|
+
def get_vertex(self, index: int) -> glm.vec3:
|
|
57
|
+
"""
|
|
58
|
+
Gets the world space position of a vertex indicated by the index in the mesh
|
|
59
|
+
"""
|
|
60
|
+
return glm.vec3(self.node.model_matrix * glm.vec4(*self.mesh.points[index], 1))
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def collider_handler(self): return self._collider_handler
|
|
64
|
+
@property
|
|
65
|
+
def has_collided(self): return bool(self.collisions)
|
|
66
|
+
@property
|
|
67
|
+
def half_dimensions(self): # TODO look for optimization
|
|
68
|
+
if self.needs_half_dimensions:
|
|
69
|
+
top_right = glm.max(self.obb_points)
|
|
70
|
+
self._half_dimensions = top_right - self.node.geometric_center
|
|
71
|
+
self.needs_half_dimensions = False
|
|
72
|
+
return self._half_dimensions
|
|
73
|
+
@property
|
|
74
|
+
def bottom_left(self): return self.node.geometric_center - self.half_dimensions
|
|
75
|
+
@property
|
|
76
|
+
def top_right(self): return self.node.geometric_center + self.half_dimensions
|
|
77
|
+
@property
|
|
78
|
+
def aabb_surface_area(self): return get_aabb_surface_area(self.top_right, self.bottom_left)
|
|
79
|
+
@property
|
|
80
|
+
def obb_points(self):
|
|
81
|
+
if self.needs_obb:
|
|
82
|
+
self._obb_points = transform_points(self.mesh.aabb_points, self.node.model_matrix)
|
|
83
|
+
self.needs_obb = False
|
|
84
|
+
return self._obb_points
|
|
85
|
+
|
|
86
|
+
@collider_handler.setter
|
|
87
|
+
def collider_handler(self, value):
|
|
88
|
+
self._collider_handler = value
|
|
89
|
+
if not value: return
|
|
90
|
+
if self.mesh is None: self.mesh = self.node.mesh
|
|
91
|
+
elif isinstance(self.mesh, Mesh): ...
|
|
92
|
+
elif isinstance(self.mesh, str):
|
|
93
|
+
if self.mesh =='box': self.mesh = value.cube
|
|
94
|
+
else: raise ValueError(f'Incorrect built-in mesh type {self.mesh}')
|
|
95
|
+
else: raise ValueError(f'Unkown type for mesh, got {type(self.mesh)}')
|
|
96
96
|
value.add(self)
|
|
@@ -1,225 +1,225 @@
|
|
|
1
|
-
import glm
|
|
2
|
-
|
|
3
|
-
from .collider import Collider
|
|
4
|
-
from .broad.broad_bvh import BroadBVH
|
|
5
|
-
from .narrow.gjk import collide_gjk
|
|
6
|
-
from .narrow.epa import get_epa_from_gjk
|
|
7
|
-
from .narrow.contact_manifold import get_contact_manifold, separate_polytope
|
|
8
|
-
from .narrow.dataclasses import ContactPoint, ContactManifold, Collision
|
|
9
|
-
from ..nodes.node import Node
|
|
10
|
-
from ..generic.collisions import get_sat_axes
|
|
11
|
-
from ..physics.impulse import calculate_collisions
|
|
12
|
-
|
|
13
|
-
class ColliderHandler():
|
|
14
|
-
scene: ...
|
|
15
|
-
"""Back reference to scene"""
|
|
16
|
-
colliders: list[Collider]
|
|
17
|
-
"""Main list of collders contained in the scene"""
|
|
18
|
-
bvh: BroadBVH
|
|
19
|
-
"""Broad bottom up BVH containing all colliders in the scene"""
|
|
20
|
-
|
|
21
|
-
def __init__(self, scene) -> None:
|
|
22
|
-
self.scene = scene
|
|
23
|
-
self.cube = self.scene.engine.cube
|
|
24
|
-
self.colliders = []
|
|
25
|
-
self.polytope_data = {}
|
|
26
|
-
self.contact_manifolds: dict[tuple[Collider, Collider] : ContactManifold] = {}
|
|
27
|
-
self.bvh = BroadBVH(self)
|
|
28
|
-
|
|
29
|
-
def add(self, collider: Collider) -> Collider:
|
|
30
|
-
"""
|
|
31
|
-
Creates a collider and adds it to the collider list
|
|
32
|
-
"""
|
|
33
|
-
self.colliders.append(collider)
|
|
34
|
-
self.bvh.add(collider)
|
|
35
|
-
return collider
|
|
36
|
-
|
|
37
|
-
def remove(self, collider: Collider) -> None:
|
|
38
|
-
"""
|
|
39
|
-
Removes a collider from the main branch and BVH
|
|
40
|
-
"""
|
|
41
|
-
if collider in self.colliders: self.colliders.remove(collider)
|
|
42
|
-
self.bvh.remove(collider)
|
|
43
|
-
collider.collider_handler = None
|
|
44
|
-
|
|
45
|
-
def resolve_collisions(self) -> None:
|
|
46
|
-
"""
|
|
47
|
-
Resets collider collision values and resolves all collisions in the scene
|
|
48
|
-
"""
|
|
49
|
-
# reset collision data
|
|
50
|
-
for collider in self.colliders: collider.collisions = []
|
|
51
|
-
|
|
52
|
-
# update BVH
|
|
53
|
-
for collider in self.colliders:
|
|
54
|
-
if collider.needs_bvh:
|
|
55
|
-
self.bvh.remove(collider)
|
|
56
|
-
self.bvh.add(collider)
|
|
57
|
-
collider.needs_bvh = False
|
|
58
|
-
|
|
59
|
-
# resolve collisions
|
|
60
|
-
broad_collisions = self.resolve_broad_collisions()
|
|
61
|
-
self.resolve_narrow_collisions(broad_collisions)
|
|
62
|
-
|
|
63
|
-
def collide_obb_obb(self, collider1: Collider, collider2: Collider) -> tuple[glm.vec3, float] | None:
|
|
64
|
-
"""
|
|
65
|
-
Finds the minimal penetrating vector for an obb obb collision, return None if not colliding. Uses SAT.
|
|
66
|
-
"""
|
|
67
|
-
axes = get_sat_axes(collider1.node.rotation, collider2.node.rotation) # axes are normaized
|
|
68
|
-
points1 = collider1.obb_points # TODO remove once oobb points are lazy updated, switch to just using property
|
|
69
|
-
points2 = collider2.obb_points
|
|
70
|
-
|
|
71
|
-
# test axes
|
|
72
|
-
small_axis = None
|
|
73
|
-
small_overlap = 1e10
|
|
74
|
-
small_index = 0
|
|
75
|
-
for i, axis in enumerate(axes): # TODO add optimization for points on cardinal axis of cuboid
|
|
76
|
-
# "project" points
|
|
77
|
-
proj1 = [glm.dot(p, axis) for p in points1]
|
|
78
|
-
proj2 = [glm.dot(p, axis) for p in points2]
|
|
79
|
-
max1, min1 = max(proj1), min(proj1)
|
|
80
|
-
max2, min2 = max(proj2), min(proj2)
|
|
81
|
-
if max1 < min2 or max2 < min1: return None
|
|
82
|
-
|
|
83
|
-
# if lines are not intersecting
|
|
84
|
-
if max1 > max2 and min1 < min2: overlap = min(max1 - min2, max2 - min1)
|
|
85
|
-
elif max2 > max1 and min2 < min1: overlap = min(max2 - min1, max1 - min2)
|
|
86
|
-
else: overlap = min(max1, max2) - max(min1, min2) # TODO check if works with containment
|
|
87
|
-
|
|
88
|
-
if abs(overlap) > abs(small_overlap): continue
|
|
89
|
-
small_overlap = overlap
|
|
90
|
-
small_axis = axis
|
|
91
|
-
small_index = i
|
|
92
|
-
|
|
93
|
-
return small_axis, small_overlap, small_index
|
|
94
|
-
|
|
95
|
-
def collide_obb_obb_decision(self, collider1: Collider, collider2: Collider) -> bool:
|
|
96
|
-
"""
|
|
97
|
-
Determines if two obbs are colliding Uses SAT.
|
|
98
|
-
"""
|
|
99
|
-
axes = get_sat_axes(collider1.node.rotation, collider2.node.rotation)
|
|
100
|
-
points1 = collider1.obb_points # TODO remove once oobb points are lazy updated, switch to just using property
|
|
101
|
-
points2 = collider2.obb_points
|
|
102
|
-
|
|
103
|
-
# test axes
|
|
104
|
-
for axis in axes: # TODO add optimization for points on cardinal axis of cuboid
|
|
105
|
-
# "project" points
|
|
106
|
-
proj1 = [glm.dot(p, axis) for p in points1]
|
|
107
|
-
proj2 = [glm.dot(p, axis) for p in points2]
|
|
108
|
-
max1, min1 = max(proj1), min(proj1)
|
|
109
|
-
max2, min2 = max(proj2), min(proj2)
|
|
110
|
-
if max1 < min2 or max2 < min1: return False
|
|
111
|
-
|
|
112
|
-
return True
|
|
113
|
-
|
|
114
|
-
def resolve_broad_collisions(self) -> set[tuple[Collider, Collider]]:
|
|
115
|
-
"""
|
|
116
|
-
Determines which colliders collide with each other from the BVH
|
|
117
|
-
"""
|
|
118
|
-
collisions = set()
|
|
119
|
-
for collider1 in self.colliders:
|
|
120
|
-
if collider1.node.static: continue
|
|
121
|
-
# traverse bvh to find aabb aabb collisions
|
|
122
|
-
colliding = self.bvh.get_collided(collider1)
|
|
123
|
-
for collider2 in colliding:
|
|
124
|
-
if collider1 is collider2 or (collider1.collision_group is not None and collider1.collision_group == collider2.collision_group): continue
|
|
125
|
-
if ((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1)) in collisions: continue
|
|
126
|
-
|
|
127
|
-
# run broad collision for specified mesh types
|
|
128
|
-
if max(len(collider1.mesh.points), len(collider2.mesh.points)) > 250 and not self.collide_obb_obb_decision(collider1, collider2): continue # contains at least one "large" mesh TODO write heuristic algorithm for determining large meshes
|
|
129
|
-
collisions.add((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1))
|
|
130
|
-
|
|
131
|
-
return collisions
|
|
132
|
-
|
|
133
|
-
def merge_contact_points(self, vec: glm.vec3, collider1: Collider, collider2: Collider, points1: list[ContactPoint], points2: list[ContactPoint]) -> None:
|
|
134
|
-
"""
|
|
135
|
-
|
|
136
|
-
"""
|
|
137
|
-
def merge_points(node: Node, existing: dict[int, glm.vec3], incoming: list[ContactPoint]) -> dict[int, glm.vec3]:
|
|
138
|
-
incoming_indices = set()
|
|
139
|
-
|
|
140
|
-
# add incoming points
|
|
141
|
-
for point in incoming:
|
|
142
|
-
incoming_indices.add(point.index)
|
|
143
|
-
if point.index not in existing or glm.length2(point.vertex - existing[point.index]) > 1e-5: existing[point.index] = glm.vec3(point.vertex)
|
|
144
|
-
|
|
145
|
-
# remove changed stored points
|
|
146
|
-
remove_indices = []
|
|
147
|
-
for index, vertex in existing.items():
|
|
148
|
-
if index in incoming_indices: continue
|
|
149
|
-
if glm.length2(node.collider.get_vertex(index) - vertex) > 1e-5: remove_indices.append(index) # check to see if point has moved
|
|
150
|
-
|
|
151
|
-
# remove unused and moved points
|
|
152
|
-
for index in remove_indices: del existing[index]
|
|
153
|
-
return existing
|
|
154
|
-
|
|
155
|
-
# check if collision is logged, if not create a new one
|
|
156
|
-
collider_tuple = (collider1, collider2)
|
|
157
|
-
if collider_tuple not in self.contact_manifolds or glm.length2(self.contact_manifolds[collider_tuple].normal - vec) > 1e-7: self.contact_manifolds[collider_tuple] = ContactManifold(vec, dict(), dict())
|
|
158
|
-
|
|
159
|
-
# add contact point from current collision and check overlap
|
|
160
|
-
self.contact_manifolds[collider_tuple].contact_points1 = merge_points(collider1.node, self.contact_manifolds[collider_tuple].contact_points1, points1)
|
|
161
|
-
self.contact_manifolds[collider_tuple].contact_points2 = merge_points(collider2.node, self.contact_manifolds[collider_tuple].contact_points2, points2)
|
|
162
|
-
|
|
163
|
-
def resolve_narrow_collisions(self, broad_collisions: list[tuple[Collider, Collider]]) -> None:
|
|
164
|
-
"""
|
|
165
|
-
Determines if two colliders are colliding, if so resolves their penetration and applies impulse
|
|
166
|
-
"""
|
|
167
|
-
for collision in broad_collisions: # assumes that broad collisions are unique
|
|
168
|
-
collider1 = collision[0]
|
|
169
|
-
collider2 = collision[1]
|
|
170
|
-
node1: Node = collider1.node
|
|
171
|
-
node2: Node = collider2.node
|
|
172
|
-
|
|
173
|
-
# get peneration data or quit early if no collision is found
|
|
174
|
-
if collider1.mesh == self.cube and collider2.mesh == self.cube: # obb-obb collision
|
|
175
|
-
|
|
176
|
-
# run SAT for obb-obb (includes peneration)
|
|
177
|
-
data = self.collide_obb_obb(collider1, collider2)
|
|
178
|
-
if not data: continue
|
|
179
|
-
|
|
180
|
-
vec, distance, index = data
|
|
181
|
-
|
|
182
|
-
# TODO replace with own contact algorithm
|
|
183
|
-
points1 = [ContactPoint(index, vertex) for index, vertex in enumerate(collider1.obb_points)]
|
|
184
|
-
points2 = [ContactPoint(index, vertex) for index, vertex in enumerate(collider2.obb_points)]
|
|
185
|
-
|
|
186
|
-
else: # use gjk to determine collisions between non-cuboid meshes
|
|
187
|
-
has_collided, simplex = collide_gjk(node1, node2)
|
|
188
|
-
if not has_collided: continue
|
|
189
|
-
|
|
190
|
-
face, polytope = get_epa_from_gjk(node1, node2, simplex)
|
|
191
|
-
vec, distance = face[1], face[0]
|
|
192
|
-
|
|
193
|
-
# TODO replace with own contact algorithm
|
|
194
|
-
points1 = [ContactPoint(p.index1, p.vertex1) for p in polytope]
|
|
195
|
-
points2 = [ContactPoint(p.index2, p.vertex2) for p in polytope]
|
|
196
|
-
|
|
197
|
-
if glm.dot(vec, node2.position.data - node1.position.data) > 0: vec *= -1
|
|
198
|
-
|
|
199
|
-
# add collision data to colliders
|
|
200
|
-
collider1.collisions.append(Collision(node2, vec))
|
|
201
|
-
collider2.collisions.append(Collision(node1, -vec))
|
|
202
|
-
|
|
203
|
-
# apply impulse if a collider has a physic body
|
|
204
|
-
if node1.physics_body or node2.physics_body:
|
|
205
|
-
|
|
206
|
-
# determine the contact points from the collision
|
|
207
|
-
points1, points2 = separate_polytope(points1, points2, vec)
|
|
208
|
-
self.merge_contact_points(vec, collider1, collider2, points1, points2)
|
|
209
|
-
|
|
210
|
-
collider_tuple = (collider1, collider2)
|
|
211
|
-
manifold = get_contact_manifold(
|
|
212
|
-
node1.position.data - vec,
|
|
213
|
-
vec,
|
|
214
|
-
self.contact_manifolds[collider_tuple].contact_points1.values(),
|
|
215
|
-
self.contact_manifolds[collider_tuple].contact_points2.values()
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
collision_normal = node1.velocity - node2.velocity
|
|
219
|
-
collision_normal = vec if glm.length2(collision_normal) < 1e-12 else glm.normalize(collision_normal)
|
|
220
|
-
calculate_collisions(collision_normal, node1, node2, manifold, node1.get_inverse_inertia(), node2.get_inverse_inertia(), node1.center_of_mass, node2.center_of_mass)
|
|
221
|
-
|
|
222
|
-
# resolve collision penetration
|
|
223
|
-
multiplier = 0.5 if not (node1.static or node2.static) else 1
|
|
224
|
-
if not node1.static: node1.position += multiplier * vec * distance
|
|
1
|
+
import glm
|
|
2
|
+
|
|
3
|
+
from .collider import Collider
|
|
4
|
+
from .broad.broad_bvh import BroadBVH
|
|
5
|
+
from .narrow.gjk import collide_gjk
|
|
6
|
+
from .narrow.epa import get_epa_from_gjk
|
|
7
|
+
from .narrow.contact_manifold import get_contact_manifold, separate_polytope
|
|
8
|
+
from .narrow.dataclasses import ContactPoint, ContactManifold, Collision
|
|
9
|
+
from ..nodes.node import Node
|
|
10
|
+
from ..generic.collisions import get_sat_axes
|
|
11
|
+
from ..physics.impulse import calculate_collisions
|
|
12
|
+
|
|
13
|
+
class ColliderHandler():
|
|
14
|
+
scene: ...
|
|
15
|
+
"""Back reference to scene"""
|
|
16
|
+
colliders: list[Collider]
|
|
17
|
+
"""Main list of collders contained in the scene"""
|
|
18
|
+
bvh: BroadBVH
|
|
19
|
+
"""Broad bottom up BVH containing all colliders in the scene"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, scene) -> None:
|
|
22
|
+
self.scene = scene
|
|
23
|
+
self.cube = self.scene.engine.cube
|
|
24
|
+
self.colliders = []
|
|
25
|
+
self.polytope_data = {}
|
|
26
|
+
self.contact_manifolds: dict[tuple[Collider, Collider] : ContactManifold] = {}
|
|
27
|
+
self.bvh = BroadBVH(self)
|
|
28
|
+
|
|
29
|
+
def add(self, collider: Collider) -> Collider:
|
|
30
|
+
"""
|
|
31
|
+
Creates a collider and adds it to the collider list
|
|
32
|
+
"""
|
|
33
|
+
self.colliders.append(collider)
|
|
34
|
+
self.bvh.add(collider)
|
|
35
|
+
return collider
|
|
36
|
+
|
|
37
|
+
def remove(self, collider: Collider) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Removes a collider from the main branch and BVH
|
|
40
|
+
"""
|
|
41
|
+
if collider in self.colliders: self.colliders.remove(collider)
|
|
42
|
+
self.bvh.remove(collider)
|
|
43
|
+
collider.collider_handler = None
|
|
44
|
+
|
|
45
|
+
def resolve_collisions(self) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Resets collider collision values and resolves all collisions in the scene
|
|
48
|
+
"""
|
|
49
|
+
# reset collision data
|
|
50
|
+
for collider in self.colliders: collider.collisions = []
|
|
51
|
+
|
|
52
|
+
# update BVH
|
|
53
|
+
for collider in self.colliders:
|
|
54
|
+
if collider.needs_bvh:
|
|
55
|
+
self.bvh.remove(collider)
|
|
56
|
+
self.bvh.add(collider)
|
|
57
|
+
collider.needs_bvh = False
|
|
58
|
+
|
|
59
|
+
# resolve collisions
|
|
60
|
+
broad_collisions = self.resolve_broad_collisions()
|
|
61
|
+
self.resolve_narrow_collisions(broad_collisions)
|
|
62
|
+
|
|
63
|
+
def collide_obb_obb(self, collider1: Collider, collider2: Collider) -> tuple[glm.vec3, float] | None:
|
|
64
|
+
"""
|
|
65
|
+
Finds the minimal penetrating vector for an obb obb collision, return None if not colliding. Uses SAT.
|
|
66
|
+
"""
|
|
67
|
+
axes = get_sat_axes(collider1.node.rotation, collider2.node.rotation) # axes are normaized
|
|
68
|
+
points1 = collider1.obb_points # TODO remove once oobb points are lazy updated, switch to just using property
|
|
69
|
+
points2 = collider2.obb_points
|
|
70
|
+
|
|
71
|
+
# test axes
|
|
72
|
+
small_axis = None
|
|
73
|
+
small_overlap = 1e10
|
|
74
|
+
small_index = 0
|
|
75
|
+
for i, axis in enumerate(axes): # TODO add optimization for points on cardinal axis of cuboid
|
|
76
|
+
# "project" points
|
|
77
|
+
proj1 = [glm.dot(p, axis) for p in points1]
|
|
78
|
+
proj2 = [glm.dot(p, axis) for p in points2]
|
|
79
|
+
max1, min1 = max(proj1), min(proj1)
|
|
80
|
+
max2, min2 = max(proj2), min(proj2)
|
|
81
|
+
if max1 < min2 or max2 < min1: return None
|
|
82
|
+
|
|
83
|
+
# if lines are not intersecting
|
|
84
|
+
if max1 > max2 and min1 < min2: overlap = min(max1 - min2, max2 - min1)
|
|
85
|
+
elif max2 > max1 and min2 < min1: overlap = min(max2 - min1, max1 - min2)
|
|
86
|
+
else: overlap = min(max1, max2) - max(min1, min2) # TODO check if works with containment
|
|
87
|
+
|
|
88
|
+
if abs(overlap) > abs(small_overlap): continue
|
|
89
|
+
small_overlap = overlap
|
|
90
|
+
small_axis = axis
|
|
91
|
+
small_index = i
|
|
92
|
+
|
|
93
|
+
return small_axis, small_overlap, small_index
|
|
94
|
+
|
|
95
|
+
def collide_obb_obb_decision(self, collider1: Collider, collider2: Collider) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
Determines if two obbs are colliding Uses SAT.
|
|
98
|
+
"""
|
|
99
|
+
axes = get_sat_axes(collider1.node.rotation, collider2.node.rotation)
|
|
100
|
+
points1 = collider1.obb_points # TODO remove once oobb points are lazy updated, switch to just using property
|
|
101
|
+
points2 = collider2.obb_points
|
|
102
|
+
|
|
103
|
+
# test axes
|
|
104
|
+
for axis in axes: # TODO add optimization for points on cardinal axis of cuboid
|
|
105
|
+
# "project" points
|
|
106
|
+
proj1 = [glm.dot(p, axis) for p in points1]
|
|
107
|
+
proj2 = [glm.dot(p, axis) for p in points2]
|
|
108
|
+
max1, min1 = max(proj1), min(proj1)
|
|
109
|
+
max2, min2 = max(proj2), min(proj2)
|
|
110
|
+
if max1 < min2 or max2 < min1: return False
|
|
111
|
+
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
def resolve_broad_collisions(self) -> set[tuple[Collider, Collider]]:
|
|
115
|
+
"""
|
|
116
|
+
Determines which colliders collide with each other from the BVH
|
|
117
|
+
"""
|
|
118
|
+
collisions = set()
|
|
119
|
+
for collider1 in self.colliders:
|
|
120
|
+
if collider1.node.static: continue
|
|
121
|
+
# traverse bvh to find aabb aabb collisions
|
|
122
|
+
colliding = self.bvh.get_collided(collider1)
|
|
123
|
+
for collider2 in colliding:
|
|
124
|
+
if collider1 is collider2 or (collider1.collision_group is not None and collider1.collision_group == collider2.collision_group): continue
|
|
125
|
+
if ((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1)) in collisions: continue
|
|
126
|
+
|
|
127
|
+
# run broad collision for specified mesh types
|
|
128
|
+
if max(len(collider1.mesh.points), len(collider2.mesh.points)) > 250 and not self.collide_obb_obb_decision(collider1, collider2): continue # contains at least one "large" mesh TODO write heuristic algorithm for determining large meshes
|
|
129
|
+
collisions.add((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1))
|
|
130
|
+
|
|
131
|
+
return collisions
|
|
132
|
+
|
|
133
|
+
def merge_contact_points(self, vec: glm.vec3, collider1: Collider, collider2: Collider, points1: list[ContactPoint], points2: list[ContactPoint]) -> None:
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
def merge_points(node: Node, existing: dict[int, glm.vec3], incoming: list[ContactPoint]) -> dict[int, glm.vec3]:
|
|
138
|
+
incoming_indices = set()
|
|
139
|
+
|
|
140
|
+
# add incoming points
|
|
141
|
+
for point in incoming:
|
|
142
|
+
incoming_indices.add(point.index)
|
|
143
|
+
if point.index not in existing or glm.length2(point.vertex - existing[point.index]) > 1e-5: existing[point.index] = glm.vec3(point.vertex)
|
|
144
|
+
|
|
145
|
+
# remove changed stored points
|
|
146
|
+
remove_indices = []
|
|
147
|
+
for index, vertex in existing.items():
|
|
148
|
+
if index in incoming_indices: continue
|
|
149
|
+
if glm.length2(node.collider.get_vertex(index) - vertex) > 1e-5: remove_indices.append(index) # check to see if point has moved
|
|
150
|
+
|
|
151
|
+
# remove unused and moved points
|
|
152
|
+
for index in remove_indices: del existing[index]
|
|
153
|
+
return existing
|
|
154
|
+
|
|
155
|
+
# check if collision is logged, if not create a new one
|
|
156
|
+
collider_tuple = (collider1, collider2)
|
|
157
|
+
if collider_tuple not in self.contact_manifolds or glm.length2(self.contact_manifolds[collider_tuple].normal - vec) > 1e-7: self.contact_manifolds[collider_tuple] = ContactManifold(vec, dict(), dict())
|
|
158
|
+
|
|
159
|
+
# add contact point from current collision and check overlap
|
|
160
|
+
self.contact_manifolds[collider_tuple].contact_points1 = merge_points(collider1.node, self.contact_manifolds[collider_tuple].contact_points1, points1)
|
|
161
|
+
self.contact_manifolds[collider_tuple].contact_points2 = merge_points(collider2.node, self.contact_manifolds[collider_tuple].contact_points2, points2)
|
|
162
|
+
|
|
163
|
+
def resolve_narrow_collisions(self, broad_collisions: list[tuple[Collider, Collider]]) -> None:
|
|
164
|
+
"""
|
|
165
|
+
Determines if two colliders are colliding, if so resolves their penetration and applies impulse
|
|
166
|
+
"""
|
|
167
|
+
for collision in broad_collisions: # assumes that broad collisions are unique
|
|
168
|
+
collider1 = collision[0]
|
|
169
|
+
collider2 = collision[1]
|
|
170
|
+
node1: Node = collider1.node
|
|
171
|
+
node2: Node = collider2.node
|
|
172
|
+
|
|
173
|
+
# get peneration data or quit early if no collision is found
|
|
174
|
+
if collider1.mesh == self.cube and collider2.mesh == self.cube: # obb-obb collision
|
|
175
|
+
|
|
176
|
+
# run SAT for obb-obb (includes peneration)
|
|
177
|
+
data = self.collide_obb_obb(collider1, collider2)
|
|
178
|
+
if not data: continue
|
|
179
|
+
|
|
180
|
+
vec, distance, index = data
|
|
181
|
+
|
|
182
|
+
# TODO replace with own contact algorithm
|
|
183
|
+
points1 = [ContactPoint(index, vertex) for index, vertex in enumerate(collider1.obb_points)]
|
|
184
|
+
points2 = [ContactPoint(index, vertex) for index, vertex in enumerate(collider2.obb_points)]
|
|
185
|
+
|
|
186
|
+
else: # use gjk to determine collisions between non-cuboid meshes
|
|
187
|
+
has_collided, simplex = collide_gjk(node1, node2)
|
|
188
|
+
if not has_collided: continue
|
|
189
|
+
|
|
190
|
+
face, polytope = get_epa_from_gjk(node1, node2, simplex)
|
|
191
|
+
vec, distance = face[1], face[0]
|
|
192
|
+
|
|
193
|
+
# TODO replace with own contact algorithm
|
|
194
|
+
points1 = [ContactPoint(p.index1, p.vertex1) for p in polytope]
|
|
195
|
+
points2 = [ContactPoint(p.index2, p.vertex2) for p in polytope]
|
|
196
|
+
|
|
197
|
+
if glm.dot(vec, node2.position.data - node1.position.data) > 0: vec *= -1
|
|
198
|
+
|
|
199
|
+
# add collision data to colliders
|
|
200
|
+
collider1.collisions.append(Collision(node2, vec))
|
|
201
|
+
collider2.collisions.append(Collision(node1, -vec))
|
|
202
|
+
|
|
203
|
+
# apply impulse if a collider has a physic body
|
|
204
|
+
if node1.physics_body or node2.physics_body:
|
|
205
|
+
|
|
206
|
+
# determine the contact points from the collision
|
|
207
|
+
points1, points2 = separate_polytope(points1, points2, vec)
|
|
208
|
+
self.merge_contact_points(vec, collider1, collider2, points1, points2)
|
|
209
|
+
|
|
210
|
+
collider_tuple = (collider1, collider2)
|
|
211
|
+
manifold = get_contact_manifold(
|
|
212
|
+
node1.position.data - vec,
|
|
213
|
+
vec,
|
|
214
|
+
self.contact_manifolds[collider_tuple].contact_points1.values(),
|
|
215
|
+
self.contact_manifolds[collider_tuple].contact_points2.values()
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
collision_normal = node1.velocity - node2.velocity
|
|
219
|
+
collision_normal = vec if glm.length2(collision_normal) < 1e-12 else glm.normalize(collision_normal)
|
|
220
|
+
calculate_collisions(collision_normal, node1, node2, manifold, node1.get_inverse_inertia(), node2.get_inverse_inertia(), node1.center_of_mass, node2.center_of_mass)
|
|
221
|
+
|
|
222
|
+
# resolve collision penetration
|
|
223
|
+
multiplier = 0.5 if not (node1.static or node2.static) else 1
|
|
224
|
+
if not node1.static: node1.position += multiplier * vec * distance
|
|
225
225
|
if not node2.static: node2.position -= multiplier * vec * distance
|