basilisk-engine 0.1.2__py3-none-any.whl → 0.1.4__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 CHANGED
@@ -9,4 +9,5 @@ from .render.shader import Shader
9
9
  from .render.shader_handler import ShaderHandler
10
10
  from .draw import draw
11
11
  from .render.camera import FreeCamera, StaticCamera, FollowCamera, OrbitCamera
12
- from .render.sky import Sky
12
+ from .render.sky import Sky
13
+ from .render.post_process import PostProcess
@@ -2,6 +2,7 @@ import glm
2
2
  from ..generic.abstract_bvh import AbstractAABB as AABB
3
3
  from ..generic.meshes import transform_points, get_aabb_surface_area
4
4
  from ..mesh.mesh import Mesh
5
+ from .narrow.dataclasses import Collision
5
6
 
6
7
  class Collider():
7
8
  node: ...
@@ -35,22 +36,28 @@ class Collider():
35
36
  mesh: Mesh
36
37
  """Reference to the colliding mesh"""
37
38
 
38
- def __init__(self, node, box_mesh: bool=False, static_friction: glm.vec3=0.7, kinetic_friction: glm.vec3=0.3, elasticity: glm.vec3=0.2, collision_group: str=None):
39
+ 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):
39
40
  self.collider_handler = None
40
41
  self.node = node
41
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
42
- self.box_mesh = box_mesh
43
+ self.mesh = collider_mesh
43
44
  self.kinetic_friction = kinetic_friction if elasticity else 0.4
44
45
  self.elasticity = elasticity if elasticity else 0.1
45
46
  self.collision_group = collision_group
46
47
  self.collision_velocity = 0
47
- self.collisions = {}
48
+ self.collisions: list[Collision] = []
48
49
  self.parent = None
49
50
 
50
51
  # lazy update variables TODO change to distinguish between static and nonstatic objects
51
52
  self.needs_obb = True # pos, scale, rot
52
53
  self.needs_half_dimensions = True # scale, rot
53
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))
54
61
 
55
62
  @property
56
63
  def collider_handler(self): return self._collider_handler
@@ -80,5 +87,10 @@ class Collider():
80
87
  def collider_handler(self, value):
81
88
  self._collider_handler = value
82
89
  if not value: return
83
- self.mesh = value.cube if self.box_mesh else self.node.mesh
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)}')
84
96
  value.add(self)
@@ -5,7 +5,7 @@ from .broad.broad_bvh import BroadBVH
5
5
  from .narrow.gjk import collide_gjk
6
6
  from .narrow.epa import get_epa_from_gjk
7
7
  from .narrow.contact_manifold import get_contact_manifold, separate_polytope
8
- from .narrow.dataclasses import SupportPoint, ContactPoint, ContactManifold
8
+ from .narrow.dataclasses import ContactPoint, ContactManifold, Collision
9
9
  from ..nodes.node import Node
10
10
  from ..generic.collisions import get_sat_axes
11
11
  from ..physics.impulse import calculate_collisions
@@ -47,8 +47,7 @@ class ColliderHandler():
47
47
  Resets collider collision values and resolves all collisions in the scene
48
48
  """
49
49
  # reset collision data
50
- for collider in self.colliders: collider.collisions = {}
51
-
50
+ for collider in self.colliders: collider.collisions = []
52
51
 
53
52
  # update BVH
54
53
  for collider in self.colliders:
@@ -122,7 +121,7 @@ class ColliderHandler():
122
121
  # traverse bvh to find aabb aabb collisions
123
122
  colliding = self.bvh.get_collided(collider1)
124
123
  for collider2 in colliding:
125
- if collider1 is collider2: continue
124
+ if collider1 is collider2 or (collider1.collision_group is not None and collider1.collision_group == collider2.collision_group): continue
126
125
  if ((collider1, collider2) if id(collider1) < id(collider2) else (collider2, collider1)) in collisions: continue
127
126
 
128
127
  # run broad collision for specified mesh types
@@ -148,7 +147,7 @@ class ColliderHandler():
148
147
  remove_indices = []
149
148
  for index, vertex in existing.items():
150
149
  if index in incoming_indices: continue
151
- if glm.length2(node.get_vertex(index) - vertex) > 1e-5: remove_indices.append(index) # check to see if point has moved
150
+ if glm.length2(node.collider.get_vertex(index) - vertex) > 1e-5: remove_indices.append(index) # check to see if point has moved
152
151
 
153
152
  # remove unused and moved points
154
153
  for index in remove_indices: del existing[index]
@@ -198,6 +197,10 @@ class ColliderHandler():
198
197
 
199
198
  if glm.dot(vec, node2.position - node1.position) > 0: vec *= -1
200
199
 
200
+ # add collision data to colliders
201
+ collider1.collisions.append(Collision(node2, vec))
202
+ collider2.collisions.append(Collision(node1, -vec))
203
+
201
204
  # apply impulse if a collider has a physic body
202
205
  if node1.physics_body or node2.physics_body:
203
206
 
@@ -205,10 +208,7 @@ class ColliderHandler():
205
208
  points1, points2 = separate_polytope(points1, points2, vec)
206
209
  self.merge_contact_points(vec, collider1, collider2, points1, points2)
207
210
 
208
- # for manifold in self.contact_manifolds.values(): print(list(manifold.contact_points1.values()) + list(manifold.contact_points2.values()))
209
-
210
211
  collider_tuple = (collider1, collider2)
211
- # print(self.contact_manifolds[collider_tuple])
212
212
  manifold = get_contact_manifold(
213
213
  node1.position - vec,
214
214
  vec,
@@ -219,9 +219,6 @@ class ColliderHandler():
219
219
  collision_normal = node1.velocity - node2.velocity
220
220
  collision_normal = vec if glm.length2(collision_normal) < 1e-12 else glm.normalize(collision_normal)
221
221
  calculate_collisions(collision_normal, node1, node2, manifold, node1.get_inverse_inertia(), node2.get_inverse_inertia(), node1.center_of_mass, node2.center_of_mass)
222
-
223
- # for i, point in enumerate(manifold):
224
- # self.scene.add(Node(position = point, scale = (0.1, 0.1, 0.1)))
225
222
 
226
223
  # resolve collision penetration
227
224
  multiplier = 0.5 if not (node1.static or node2.static) else 1
@@ -1,5 +1,6 @@
1
1
  import glm
2
2
  from dataclasses import dataclass
3
+ # from ...nodes.node import Node
3
4
 
4
5
  # frozen because data does not need to be mutable
5
6
  # used in creating polytopes for GJK/EPA
@@ -24,4 +25,9 @@ class ContactPoint():
24
25
  class ContactManifold():
25
26
  normal: glm.vec3
26
27
  contact_points1: dict[int : glm.vec3] # contact point index : collision position
27
- contact_points2: dict[int : glm.vec3]
28
+ contact_points2: dict[int : glm.vec3]
29
+
30
+ @dataclass
31
+ class Collision():
32
+ node: ...
33
+ normal: glm.vec3
@@ -16,8 +16,8 @@ def get_furthest_point(node: Node, dir_vec: glm.vec3) -> glm.vec3:
16
16
  """
17
17
  # determine furthest point by using untransformed mesh
18
18
  node_dir_vec = node.rotation * dir_vec # rotate the world space vector to node space
19
- index = node.mesh.get_best_dot(node_dir_vec)
20
- vertex = node.mesh.points[index]
19
+ index = node.collider.mesh.get_best_dot(node_dir_vec)
20
+ vertex = node.collider.mesh.points[index]
21
21
  vertex = node.model_matrix * glm.vec4(vertex, 1.0)
22
22
 
23
23
  # transform point to world space
@@ -5,6 +5,7 @@ from math import cos, sin, atan2
5
5
  from ..render.image import Image
6
6
  from .font_renderer import FontRenderer
7
7
  from ..render.shader import Shader
8
+ from ..generic.input_validation import validate_color, validate_rect, validate_point
8
9
 
9
10
  class DrawHandler():
10
11
  engine: ...
@@ -73,7 +74,7 @@ class DrawHandler():
73
74
  Draws a rect to the screen
74
75
  """
75
76
 
76
- color = validate_color(color)
77
+ color = validate_color('draw', 'color', color)
77
78
  rect = validate_rect(rect)
78
79
 
79
80
  p1 = (rect[0] , rect[1] )
@@ -106,8 +107,8 @@ class DrawHandler():
106
107
  """
107
108
 
108
109
  if not outer_color: outer_color = color
109
- color = validate_color(color)
110
- outer_color = validate_color(outer_color)
110
+ color = validate_color('draw', 'color', color)
111
+ outer_color = validate_color('draw', 'color', outer_color)
111
112
  p1 = validate_point(center)
112
113
 
113
114
  v1 = (*p1, *color, 0)
@@ -134,7 +135,7 @@ class DrawHandler():
134
135
  Size of the line on either side. pixels
135
136
  """
136
137
 
137
- color = validate_color(color)
138
+ color = validate_color('draw', 'color', color)
138
139
 
139
140
  p1 = glm.vec2(validate_point(p1))
140
141
  p2 = glm.vec2(validate_point(p2))
@@ -176,35 +177,4 @@ class DrawHandler():
176
177
  """
177
178
 
178
179
  if self.vbo: self.vbo.release()
179
- if self.vao: self.vao.release()
180
-
181
-
182
- def validate_color(color):
183
- if not (isinstance(color, tuple) or isinstance(color, list) or isinstance(color, np.ndarray)):
184
- raise TypeError(f'Invalid color type: {type(color)}. Expected a tuple, list, or numpy array')
185
- if len(color) == 4:
186
- color = (color[0] / 255.0, color[1] / 255.0, color[2] / 255.0, color[3] / 255.0)
187
- elif len(color) == 3:
188
- color = (color[0] / 255.0, color[1] / 255.0, color[2] / 255.0, 1.0)
189
- else:
190
- raise TypeError(f'Invalid number of color values. Expected 3 or 4 values, got {len(color)}')
191
- return color
192
-
193
- def validate_rect(rect):
194
- if not (isinstance(rect, tuple) or isinstance(rect, list) or isinstance(rect, np.ndarray)):
195
- raise TypeError(f'Invalid rect type: {type(rect)}. Expected a tuple, list, or numpy array')
196
- if len(rect) != 4:
197
- raise TypeError(f'Invalid number of rect values. Expected 4 values, got {len(rect)}')
198
- return list(rect)
199
-
200
- def validate_point(point):
201
- if not (isinstance(point, tuple) or isinstance(point, list) or isinstance(point, np.ndarray)):
202
- raise TypeError(f'Invalid rect type: {type(point)}. Expected a tuple, list, or numpy array')
203
- if len(point) != 2:
204
- raise TypeError(f'Invalid number of rect values. Expected 2 values, got {len(point)}')
205
- return list(point)
206
-
207
- def validate_image(image):
208
- if not (isinstance(image, Image)):
209
- raise TypeError(f'Invalid imgae type: {type(image)}. Expected a bask.Image.')
210
- return image
180
+ if self.vao: self.vao.release()
basilisk/engine.py CHANGED
@@ -1,5 +1,8 @@
1
1
  import os
2
2
  from sys import platform
3
+ import sys
4
+ import glcontext
5
+ from .input.path import get_root
3
6
  os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"
4
7
  import pygame as pg
5
8
  import moderngl as mgl
@@ -80,7 +83,8 @@ class Engine():
80
83
  # Global attributes referenced by the handlers
81
84
  self.headless = headless
82
85
  self.set_configurations()
83
- self.root = os.path.dirname(__file__)
86
+ # self.root = get_root()
87
+ self.root = getattr(sys, '_MEIPASS', os.getcwd()) + '/basilisk'
84
88
  self.cube = Cube(self)
85
89
 
86
90
  # Update the icon
@@ -134,7 +138,7 @@ class Engine():
134
138
  self.win_size = (event.w, event.h)
135
139
  self.ctx.viewport = (0, 0, event.w, event.h)
136
140
  self.scene.camera.use()
137
- self.scene.frame.set_textures()
141
+ self.scene.frame.resize()
138
142
 
139
143
 
140
144
  # Update the scene if possible
@@ -15,14 +15,53 @@ def validate_glm_vec3(module: str, name: str, value: tuple | list | glm.vec3 | n
15
15
  if isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
16
16
  if len(value) != 3: raise ValueError(f"{module}: Invalid number of values for {name}. Expected 3 values, got {len(value)} values")
17
17
  return glm.vec3(value)
18
- elif isinstance(value, glm.vec3):
18
+ elif isinstance(value, glm.vec3) or isinstance(value, int) or isinstance(value, float):
19
19
  return glm.vec3(value)
20
20
  else:
21
21
  raise TypeError(f"{module}: Invalid {name} value type {type(value)}. Expected glm.vec3")
22
22
 
23
+ def validate_tuple3(module: str, name: str, value: tuple | list | glm.vec3 | np.ndarray) -> tuple:
24
+ if isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
25
+ if len(value) != 3: raise ValueError(f"{module}: Invalid number of values for {name}. Expected 3 values, got {len(value)} values")
26
+ return tuple(value)
27
+ elif isinstance(value, glm.vec3):
28
+ return (value.x, value.y, value.z)
29
+ if isinstance(value, int) or isinstance(value, float):
30
+ return (value, value, value)
31
+ else:
32
+ raise TypeError(f"{module}: Invalid {name} value type {type(value)}. Expected tuple of size 3")
33
+
23
34
  def validate_image(module: str, name: str, value: Image | None) -> Image | None:
24
35
  """Accepts none as a value for no image"""
25
36
  if isinstance(value, Image) or isinstance(value, type(None)):
26
37
  return value
27
38
  else:
28
- raise TypeError(f"{module}: Invalid {name} value type {type(value)}. Expected bsk.Image or None")
39
+ raise TypeError(f"{module}: Invalid {name} value type {type(value)}. Expected bsk.Image or None")
40
+
41
+ def validate_color(module: str, name: str, value: tuple | list | np.ndarray | int | float | None) -> tuple:
42
+ if isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
43
+ if len(value) == 4:
44
+ return tuple(map(lambda x: x / 255, value))
45
+ elif len(value) == 3:
46
+ return (*tuple(map(lambda x: x / 255, value)), 1.0)
47
+ else:
48
+ raise TypeError(f"{module}: Invalid number of values for {name}. Expected 3 or 4 values, got {len(value)} values")
49
+ elif isinstance(value, int) or isinstance(value, float):
50
+ v = value / 255
51
+ return (v, v, v, 1.0)
52
+ else:
53
+ raise TypeError(f"{module}: Invalid {name} value type {type(value)}. Expected tuple of size 3 or 4")
54
+
55
+ def validate_rect(rect) -> tuple:
56
+ if not (isinstance(rect, tuple) or isinstance(rect, list) or isinstance(rect, np.ndarray)):
57
+ raise TypeError(f'Invalid rect type: {type(rect)}. Expected a tuple, list, or numpy array')
58
+ if len(rect) != 4:
59
+ raise TypeError(f'Invalid number of rect values. Expected 4 values, got {len(rect)}')
60
+ return list(rect)
61
+
62
+ def validate_point(point) -> tuple:
63
+ if not (isinstance(point, tuple) or isinstance(point, list) or isinstance(point, np.ndarray)):
64
+ raise TypeError(f'Invalid rect type: {type(point)}. Expected a tuple, list, or numpy array')
65
+ if len(point) != 2:
66
+ raise TypeError(f'Invalid number of rect values. Expected 2 values, got {len(point)}')
67
+ return list(point)
@@ -0,0 +1,24 @@
1
+ import glm
2
+ from ..nodes.node import Node
3
+
4
+
5
+ class RaycastResult:
6
+ node: Node | None
7
+ """The node that the raycast hit. Is None if no object was hit"""
8
+ position: glm.vec3
9
+ """The node that the raycast hit"""
10
+
11
+ def __init__(self, node: Node | None, position: glm.vec3):
12
+ """
13
+ Container for returning raycast results.
14
+ Contains the node hit and the global position the raycast hit at.
15
+ """
16
+
17
+ self.node = node
18
+ self.position = position
19
+
20
+ def __bool__(self):
21
+ return bool(self.node)
22
+
23
+ def __repr__(self):
24
+ return f'<Raycast | Node: {self.node}, Position: {self.position}>'
basilisk/input/mouse.py CHANGED
@@ -36,6 +36,8 @@ class Mouse():
36
36
  @property
37
37
  def click(self): return self.buttons[0] and not self.previous_buttons[0]
38
38
  @property
39
+ def left_click(self): return self.buttons[0] and not self.previous_buttons[0]
40
+ @property
39
41
  def middle_click(self): return self.buttons[1] and not self.previous_buttons[1]
40
42
  @property
41
43
  def right_click(self): return self.buttons[2] and not self.previous_buttons[2]
basilisk/input/path.py ADDED
@@ -0,0 +1,14 @@
1
+ import os
2
+ import sys
3
+
4
+
5
+ # Credit for function: https://stackoverflow.com/questions/7674790/bundling-data-files-with-pyinstaller-onefile
6
+ def get_root():
7
+ """ Get absolute path to resource, works for dev and for PyInstaller """
8
+ try:
9
+ # PyInstaller creates a temp folder and stores path in _MEIPASS
10
+ base_path = sys._MEIPASS
11
+ except Exception:
12
+ base_path = os.path.abspath(".")
13
+
14
+ return base_path + '/basilisk'
@@ -63,8 +63,6 @@ def from_data(data: np.ndarray) -> Model:
63
63
  model.vertex_data[:,8:14] = tangents
64
64
  model.vertex_points, model.point_indices = get_points_and_indices(positions)
65
65
 
66
- print(model.vertex_points, model.point_indices)
67
-
68
66
  return model
69
67
 
70
68
 
@@ -0,0 +1,29 @@
1
+ import glm
2
+ # from .node import Node
3
+ from ..mesh.mesh import Mesh
4
+ from ..render.material import Material
5
+
6
+ def node_is(node, position: glm.vec3=None, scale: glm.vec3=None, rotation: glm.quat=None, forward: glm.vec3=None, mesh: Mesh=None, material: Material=None, velocity: glm.vec3=None, rotational_velocity: glm.quat=None, physics: bool=None, mass: float=None, collisions: bool=None, static_friction: float=None, kinetic_friction: float=None, elasticity: float=None, collision_group: float=None, name: str=None, tags: list[str]=None,static: bool=None) -> bool:
7
+ """
8
+ Determine if a node meets the requirements given by the parameters. If a parameter is None, then the filter is not applied.
9
+ """
10
+ return all([
11
+ position is None or position == node.position,
12
+ scale is None or scale == node.scale,
13
+ rotation is None or rotation == node.rotation,
14
+ forward is None or forward == node.forward,
15
+ mesh is None or mesh == node.mesh,
16
+ material is None or material == node.material,
17
+ velocity is None or velocity == node.velocity,
18
+ rotational_velocity is None or rotational_velocity == node.rotational_velocity,
19
+ physics is None or bool(node.physics_body) == physics,
20
+ mass is None or (node.physics_body and mass == node.physics_body.mass),
21
+ collisions is None or bool(node.collider) == collisions,
22
+ static_friction is None or (node.collider and node.collider.static_friction == static_friction),
23
+ kinetic_friction is None or (node.collider and node.collider.kinetic_friction == kinetic_friction),
24
+ elasticity is None or (node.collider and node.collider.elasticity == elasticity),
25
+ collision_group is None or (node.collider and node.collider.collision_group == collision_group),
26
+ name is None or node.name == name,
27
+ tags is None or all([tag in node.tags for tag in tags]),
28
+ static is None or node.static == static
29
+ ])