basilisk-engine 0.1.51__py3-none-any.whl → 0.1.53__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of basilisk-engine might be problematic. Click here for more details.

Files changed (94) hide show
  1. basilisk/__init__.py +27 -27
  2. basilisk/audio/sound.py +40 -40
  3. basilisk/bsk_assets/cube.obj +48 -48
  4. basilisk/collisions/broad/broad_aabb.py +102 -102
  5. basilisk/collisions/broad/broad_bvh.py +137 -137
  6. basilisk/collisions/collider.py +95 -95
  7. basilisk/collisions/collider_handler.py +225 -225
  8. basilisk/collisions/narrow/contact_manifold.py +95 -95
  9. basilisk/collisions/narrow/dataclasses.py +34 -34
  10. basilisk/collisions/narrow/deprecated.py +46 -46
  11. basilisk/collisions/narrow/epa.py +91 -91
  12. basilisk/collisions/narrow/gjk.py +66 -66
  13. basilisk/collisions/narrow/graham_scan.py +24 -24
  14. basilisk/collisions/narrow/helper.py +29 -29
  15. basilisk/collisions/narrow/line_intersections.py +106 -106
  16. basilisk/collisions/narrow/sutherland_hodgman.py +75 -75
  17. basilisk/config.py +53 -53
  18. basilisk/draw/draw.py +100 -100
  19. basilisk/draw/draw_handler.py +181 -181
  20. basilisk/draw/font_renderer.py +28 -28
  21. basilisk/engine.py +168 -168
  22. basilisk/generic/abstract_bvh.py +15 -15
  23. basilisk/generic/abstract_custom.py +133 -133
  24. basilisk/generic/collisions.py +70 -70
  25. basilisk/generic/input_validation.py +82 -82
  26. basilisk/generic/math.py +17 -17
  27. basilisk/generic/matrices.py +35 -35
  28. basilisk/generic/meshes.py +72 -72
  29. basilisk/generic/quat.py +142 -142
  30. basilisk/generic/quat_methods.py +7 -7
  31. basilisk/generic/raycast_result.py +26 -26
  32. basilisk/generic/vec3.py +143 -143
  33. basilisk/input_output/IO_handler.py +91 -91
  34. basilisk/input_output/clock.py +49 -49
  35. basilisk/input_output/keys.py +43 -43
  36. basilisk/input_output/mouse.py +90 -90
  37. basilisk/input_output/path.py +14 -14
  38. basilisk/mesh/cube.py +33 -33
  39. basilisk/mesh/mesh.py +233 -233
  40. basilisk/mesh/mesh_from_data.py +150 -150
  41. basilisk/mesh/model.py +271 -271
  42. basilisk/mesh/narrow_aabb.py +89 -89
  43. basilisk/mesh/narrow_bvh.py +91 -91
  44. basilisk/mesh/narrow_primative.py +23 -23
  45. basilisk/nodes/helper.py +28 -28
  46. basilisk/nodes/node.py +709 -709
  47. basilisk/nodes/node_handler.py +107 -98
  48. basilisk/particles/particle_handler.py +69 -65
  49. basilisk/particles/particle_renderer.py +92 -93
  50. basilisk/physics/impulse.py +112 -112
  51. basilisk/physics/physics_body.py +43 -43
  52. basilisk/physics/physics_engine.py +35 -35
  53. basilisk/render/batch.py +103 -103
  54. basilisk/render/bloom.py +117 -117
  55. basilisk/render/camera.py +260 -260
  56. basilisk/render/chunk.py +113 -113
  57. basilisk/render/chunk_handler.py +167 -167
  58. basilisk/render/frame.py +130 -130
  59. basilisk/render/framebuffer.py +192 -192
  60. basilisk/render/image.py +128 -128
  61. basilisk/render/image_handler.py +120 -120
  62. basilisk/render/light.py +96 -96
  63. basilisk/render/light_handler.py +58 -58
  64. basilisk/render/material.py +232 -232
  65. basilisk/render/material_handler.py +133 -133
  66. basilisk/render/post_process.py +180 -180
  67. basilisk/render/shader.py +135 -135
  68. basilisk/render/shader_handler.py +109 -109
  69. basilisk/render/sky.py +119 -119
  70. basilisk/scene.py +295 -291
  71. basilisk/shaders/batch.frag +291 -291
  72. basilisk/shaders/batch.vert +117 -117
  73. basilisk/shaders/bloom_downsample.frag +23 -23
  74. basilisk/shaders/bloom_upsample.frag +33 -33
  75. basilisk/shaders/crt.frag +34 -34
  76. basilisk/shaders/draw.frag +27 -27
  77. basilisk/shaders/draw.vert +25 -25
  78. basilisk/shaders/filter.frag +22 -22
  79. basilisk/shaders/frame.frag +13 -13
  80. basilisk/shaders/frame.vert +13 -13
  81. basilisk/shaders/frame_hdr.frag +27 -27
  82. basilisk/shaders/geometry.frag +10 -10
  83. basilisk/shaders/geometry.vert +41 -41
  84. basilisk/shaders/normal.frag +62 -62
  85. basilisk/shaders/normal.vert +96 -96
  86. basilisk/shaders/particle.frag +81 -81
  87. basilisk/shaders/particle.vert +86 -86
  88. basilisk/shaders/sky.frag +23 -23
  89. basilisk/shaders/sky.vert +13 -13
  90. {basilisk_engine-0.1.51.dist-info → basilisk_engine-0.1.53.dist-info}/METADATA +82 -89
  91. basilisk_engine-0.1.53.dist-info/RECORD +110 -0
  92. {basilisk_engine-0.1.51.dist-info → basilisk_engine-0.1.53.dist-info}/WHEEL +1 -1
  93. basilisk_engine-0.1.51.dist-info/RECORD +0 -110
  94. {basilisk_engine-0.1.51.dist-info → basilisk_engine-0.1.53.dist-info}/top_level.txt +0 -0
basilisk/scene.py CHANGED
@@ -1,292 +1,296 @@
1
- import moderngl as mgl
2
- import glm
3
- import pygame as pg
4
-
5
- from .mesh.mesh import Mesh
6
- from .render.material import Material
7
- from .render.shader import Shader
8
- from .render.light_handler import LightHandler
9
- from .render.camera import Camera, FreeCamera
10
- from .nodes.node_handler import NodeHandler
11
- from .physics.physics_engine import PhysicsEngine
12
- from .collisions.collider_handler import ColliderHandler
13
- from .render.sky import Sky
14
- from .render.frame import Frame
15
- from .particles.particle_handler import ParticleHandler
16
- from .nodes.node import Node
17
- from .generic.collisions import moller_trumbore
18
- from .generic.raycast_result import RaycastResult
19
- from .render.post_process import PostProcess
20
- from .render.framebuffer import Framebuffer
21
-
22
- class Scene():
23
- engine: ...=None
24
- """Parent engine of the scene"""
25
- ctx: mgl.Context
26
- """Reference to the engine context"""
27
- camera: Camera=None
28
- """"""
29
- light_handler: LightHandler=None
30
- """"""
31
- physics_engine: PhysicsEngine=None
32
- """"""
33
- node_handler: NodeHandler=None
34
- """"""
35
-
36
- def __init__(self, engine: ..., shader: Shader=None) -> None:
37
- """
38
- Basilisk scene object. Contains all nodes for the scene
39
- """
40
-
41
- self.engine = engine
42
- self.ctx = engine.ctx
43
- self.sky = None
44
- self.shader = shader if shader else engine.shader
45
- self.camera = FreeCamera()
46
- self.light_handler = LightHandler(self)
47
- self.physics_engine = PhysicsEngine()
48
- self.node_handler = NodeHandler(self)
49
- self.particle = ParticleHandler(self)
50
- self.collider_handler = ColliderHandler(self)
51
- self.sky = Sky(self)
52
- self.frame = Frame(self.engine)
53
-
54
-
55
- def update(self, render: bool=True, nodes: bool=True, particles: bool=True, collisions: bool=True) -> None:
56
- """
57
- Updates the physics and in the scene
58
- """
59
-
60
- # Call the internal engine update (for IO and time)
61
- self.engine._update()
62
-
63
- # Check that the engine is still running
64
- if not self.engine.running: return
65
-
66
- # Update based on the given parameters
67
- if nodes: self.node_handler.update()
68
- if particles: self.particle.update()
69
-
70
- # Update the camera
71
- self.camera.update()
72
- if self.engine.event_resize: self.camera.use()
73
-
74
- if collisions and self.engine.delta_time < 0.5: # TODO this will cause physics to slow down when on low frame rate, this is probabl;y acceptable
75
- self.collider_handler.resolve_collisions()
76
-
77
- # Render by default to the scene frame
78
- if render: self.render(self.engine.frame.output_buffer)
79
-
80
- def render(self, target=None) -> None:
81
- """
82
- Renders all the nodes with meshes in the scene
83
- """
84
-
85
- # target.use() if target else self.frame.use(); self.frame.clear()
86
- self.frame.use()
87
- self.frame.clear()
88
- self.engine.shader_handler.write(self)
89
- self.particle.render()
90
- self.node_handler.render()
91
- if self.sky: self.sky.render()
92
-
93
- self.frame.scene_render(target)
94
- # This will show the frame to screen on engine.update()
95
- # self.frame.scene_render(self.ctx.screen)
96
-
97
-
98
-
99
- def add(self, *objects: Node | None) -> None | Node | list:
100
- """
101
- Adds the given object(s) to the scene. Can pass in any scene objects:
102
- Argument overloads:
103
- object: Node - Adds the given node to the scene.
104
- """
105
-
106
- # List of all return values for the added objects
107
- returns = []
108
-
109
- # Loop through all objects passed in
110
- for bsk_object in objects:
111
-
112
- # Considered well defined behavior to add None
113
- if isinstance(bsk_object, type(None)):
114
- continue
115
-
116
- # Add a node to the scene
117
- elif isinstance(bsk_object, Node):
118
- returns.append(self.node_handler.add(bsk_object)); continue
119
-
120
- # Add a node to the scene
121
- elif isinstance(bsk_object, PostProcess):
122
- returns.append(self.engine.frame.add_post_process(bsk_object)); continue
123
-
124
-
125
- # Recived incompatable type
126
- else:
127
- raise ValueError(f'scene.add: Incompatable object add type {type(bsk_object)}')
128
-
129
- # Return based on what the user passed in
130
- if not returns: return None
131
- if len(returns) == 1: return returns[0]
132
- return returns
133
-
134
- def remove(self, *objects: Node | None) -> None | Node | list:
135
- """
136
- Removes the given baskilsk object from the scene
137
- """
138
-
139
- # List of all return values for the added objects
140
- returns = []
141
-
142
- # Loop through all objects passed in
143
- for bsk_object in objects:
144
-
145
- # Considered well defined behavior to remove None
146
- if isinstance(bsk_object, type(None)):
147
- continue
148
-
149
- # Remove a node from the scene
150
- elif isinstance(bsk_object, Node):
151
- returns.append(self.node_handler.remove(bsk_object)); continue
152
-
153
- # Recived incompatable type
154
- else:
155
- raise ValueError(f'scene.remove: Incompatable object remove type {type(bsk_object)}')
156
-
157
- # Return based on what the user passed in
158
- if not returns: return None
159
- if len(returns) == 1: return returns[0]
160
- return returns
161
-
162
- def set_engine(self, engine: any) -> None:
163
- """
164
- Sets the back references to the engine and creates handlers with the context
165
- """
166
-
167
- if not self.engine:
168
- self.engine = engine
169
- self.ctx = engine.ctx
170
- self.init_handlers()
171
- else:
172
- self.engine = engine
173
- self.ctx = engine.ctx
174
-
175
- def raycast(self, position: glm.vec3=None, forward: glm.vec3=None, max_distance: float=1e5, has_collisions: bool=None, has_physics: bool=None, tags: list[str]=[]) -> RaycastResult:
176
- """
177
- Ray cast from any posiiton and forward vector and returns a RaycastResult eith the nearest node.
178
- If no position or forward is given, uses the scene camera's current position and forward
179
- """
180
- if not position: position = self.camera.position
181
- if not forward: forward = self.camera.forward
182
- forward = glm.normalize(forward)
183
-
184
- # if we are filtering for collisions, use the broad BVH to improve performance
185
- if has_collisions:
186
- colliders = self.collider_handler.bvh.get_line_collided(position, forward)
187
- nodes = [collider.node for collider in colliders]
188
-
189
- def is_valid(node: Node) -> bool:
190
- return all([
191
- has_collisions is None or bool(node.collider) == has_collisions,
192
- has_physics is None or bool(node.physics_body) == has_physics,
193
- all(tag in node.tags for tag in tags)
194
- ])
195
-
196
- nodes: list[Node] = list(filter(lambda node: is_valid(node), nodes))
197
-
198
- # if we are not filtering for collisions, filter nodes and
199
- else: nodes = self.node_handler.get_all(collisions=has_collisions, physics=has_physics, tags=tags)
200
-
201
- # determine closest node
202
- best_distance, best_point, best_node, best_triangle = max_distance, None, None, None
203
- position_two = position + forward
204
- for node in nodes:
205
-
206
- inv_mat = glm.inverse(node.model_matrix)
207
- relative_position = inv_mat * position
208
- relative_forward = glm.normalize(inv_mat * position_two - relative_position)
209
-
210
- triangles = [node.mesh.indices[i] for i in node.mesh.get_line_collided(relative_position, relative_forward)]
211
-
212
- for triangle in triangles:
213
- intersection = moller_trumbore(relative_position, relative_forward, [node.mesh.points[i] for i in triangle])
214
- if not intersection: continue
215
- intersection = node.model_matrix * intersection
216
- distance = glm.length(intersection - position)
217
- if distance < best_distance:
218
- best_distance = distance
219
- best_point = intersection
220
- best_node = node
221
- best_triangle = triangle
222
-
223
- if not best_node: return RaycastResult(best_node, best_point, None)
224
-
225
- points = [best_node.model_matrix * best_node.mesh.points[t] for t in best_triangle]
226
- normal = glm.normalize(glm.cross(points[1] - points[0], points[2] - points[0]))
227
-
228
- return RaycastResult(best_node, best_point, normal)
229
-
230
- def raycast_mouse(self, position: tuple[int, int] | glm.vec2, max_distance: float=1e5, has_collisions: bool=None, has_pshyics: bool=None, tags: list[str]=[]) -> RaycastResult:
231
- """
232
- Ray casts from the mouse position with respect to the camera. Returns the nearest node that was clicked, if none was clicked, returns None.
233
- """
234
- # derive forward vector from mouse click position
235
- position = glm.vec2(position)
236
- inv_proj, inv_view = glm.inverse(self.camera.m_proj), glm.inverse(self.camera.m_view)
237
- ndc = glm.vec4(2 * position[0] / self.engine.win_size[0] - 1, 1 - 2 * position[1] / self.engine.win_size[1], 1, 1)
238
- point = inv_proj * ndc
239
- point /= point.w
240
- forward = glm.normalize(glm.vec3(inv_view * glm.vec4(point.x, point.y, point.z, 0)))
241
-
242
- return self.raycast(
243
- position=self.camera.position,
244
- forward=forward,
245
- max_distance=max_distance,
246
- has_collisions=has_collisions,
247
- has_physics=has_pshyics,
248
- tags=tags
249
- )
250
-
251
- def get(self, 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) -> Node:
252
- """
253
- Returns the first node with the given traits
254
- """
255
- self.node_handler.get(position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static)
256
-
257
- def get_all(self, 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) -> list[Node]:
258
- """
259
- Returns all nodes with the given traits
260
- """
261
- self.node_handler.get_all(position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static)
262
-
263
- @property
264
- def camera(self): return self._camera
265
- @property
266
- def sky(self): return self._sky
267
- @property
268
- def nodes(self): return self.node_handler.nodes
269
- @property
270
- def shader(self): return self._shader
271
-
272
- @camera.setter
273
- def camera(self, value: Camera):
274
- if not value: return
275
- if not isinstance(value, Camera):
276
- raise TypeError(f'Scene: Invalid camera type: {type(value)}. Expected type bsk.Camera')
277
- self._camera = value
278
- self._camera.scene = self
279
-
280
- @sky.setter
281
- def sky(self, value: Sky):
282
- if not isinstance(value, Sky) and not isinstance(value, type(None)):
283
- raise TypeError(f'Scene: Invalid sky type: {type(value)}. Expected type bsk.Sky or None')
284
- self._sky = value
285
- if value: self._sky.write()
286
-
287
- @shader.setter
288
- def shader(self, value):
289
- self._shader = value
290
- value.set_main(self)
291
- if self.light_handler: self.light_handler.write(value)
1
+ import moderngl as mgl
2
+ import glm
3
+ import pygame as pg
4
+
5
+ from .mesh.mesh import Mesh
6
+ from .render.material import Material
7
+ from .render.shader import Shader
8
+ from .render.light_handler import LightHandler
9
+ from .render.camera import Camera, FreeCamera
10
+ from .nodes.node_handler import NodeHandler
11
+ from .physics.physics_engine import PhysicsEngine
12
+ from .collisions.collider_handler import ColliderHandler
13
+ from .render.sky import Sky
14
+ from .render.frame import Frame
15
+ from .particles.particle_handler import ParticleHandler
16
+ from .nodes.node import Node
17
+ from .generic.collisions import moller_trumbore
18
+ from .generic.raycast_result import RaycastResult
19
+ from .render.post_process import PostProcess
20
+ from .render.framebuffer import Framebuffer
21
+
22
+ class Scene():
23
+ engine: ...=None
24
+ """Parent engine of the scene"""
25
+ ctx: mgl.Context
26
+ """Reference to the engine context"""
27
+ camera: Camera=None
28
+ """"""
29
+ light_handler: LightHandler=None
30
+ """"""
31
+ physics_engine: PhysicsEngine=None
32
+ """"""
33
+ node_handler: NodeHandler=None
34
+ """"""
35
+
36
+ def __init__(self, engine: ..., shader: Shader=None) -> None:
37
+ """
38
+ Basilisk scene object. Contains all nodes for the scene
39
+ """
40
+
41
+ self.engine = engine
42
+ self.ctx = engine.ctx
43
+ self.sky = None
44
+ self.shader = shader if shader else engine.shader
45
+ self.camera = FreeCamera()
46
+ self.light_handler = LightHandler(self)
47
+ self.physics_engine = PhysicsEngine()
48
+ self.node_handler = NodeHandler(self)
49
+ self.particle = ParticleHandler(self)
50
+ self.collider_handler = ColliderHandler(self)
51
+ self.sky = Sky(self)
52
+ self.frame = Frame(self.engine)
53
+
54
+
55
+ def update(self, render: bool=True, nodes: bool=True, particles: bool=True, collisions: bool=True) -> None:
56
+ """
57
+ Updates the physics and in the scene
58
+ """
59
+
60
+ # Call the internal engine update (for IO and time)
61
+ self.engine._update()
62
+
63
+ # Check that the engine is still running
64
+ if not self.engine.running: return
65
+
66
+ # Update based on the given parameters
67
+ if nodes: self.node_handler.update()
68
+ if particles: self.particle.update()
69
+
70
+ # Update the camera
71
+ self.camera.update()
72
+ if self.engine.event_resize: self.camera.use()
73
+
74
+ if collisions and self.engine.delta_time < 0.5: # TODO this will cause physics to slow down when on low frame rate, this is probabl;y acceptable
75
+ self.collider_handler.resolve_collisions()
76
+
77
+ # Render by default to the scene frame
78
+ if render: self.render(self.engine.frame.output_buffer)
79
+
80
+ def render(self, target=None) -> None:
81
+ """
82
+ Renders all the nodes with meshes in the scene
83
+ """
84
+
85
+ # target.use() if target else self.frame.use(); self.frame.clear()
86
+ self.frame.use()
87
+ self.frame.clear()
88
+ self.engine.shader_handler.write(self)
89
+ self.particle.render()
90
+ self.node_handler.render()
91
+ if self.sky: self.sky.render()
92
+
93
+ self.frame.scene_render(target)
94
+ # This will show the frame to screen on engine.update()
95
+ # self.frame.scene_render(self.ctx.screen)
96
+
97
+
98
+
99
+ def add(self, *objects: Node | None) -> None | Node | list:
100
+ """
101
+ Adds the given object(s) to the scene. Can pass in any scene objects:
102
+ Argument overloads:
103
+ object: Node - Adds the given node to the scene.
104
+ """
105
+
106
+ # List of all return values for the added objects
107
+ returns = []
108
+
109
+ # Loop through all objects passed in
110
+ for bsk_object in objects:
111
+
112
+ # Considered well defined behavior to add None
113
+ if isinstance(bsk_object, type(None)):
114
+ continue
115
+
116
+ # Add a node to the scene
117
+ elif isinstance(bsk_object, Node):
118
+ returns.append(self.node_handler.add(bsk_object)); continue
119
+
120
+ # Add a node to the scene
121
+ elif isinstance(bsk_object, PostProcess):
122
+ returns.append(self.engine.frame.add_post_process(bsk_object)); continue
123
+
124
+
125
+ # Recived incompatable type
126
+ else:
127
+ raise ValueError(f'scene.add: Incompatable object add type {type(bsk_object)}')
128
+
129
+ # Return based on what the user passed in
130
+ if not returns: return None
131
+ if len(returns) == 1: return returns[0]
132
+ return returns
133
+
134
+ def remove(self, *objects: Node | None) -> None | Node | list:
135
+ """
136
+ Removes the given baskilsk object from the scene
137
+ """
138
+
139
+ # List of all return values for the added objects
140
+ returns = []
141
+
142
+ # Loop through all objects passed in
143
+ for bsk_object in objects:
144
+
145
+ # Considered well defined behavior to remove None
146
+ if isinstance(bsk_object, type(None)):
147
+ continue
148
+
149
+ # Remove a node from the scene
150
+ elif isinstance(bsk_object, Node):
151
+ returns.append(self.node_handler.remove(bsk_object)); continue
152
+
153
+ # Recived incompatable type
154
+ else:
155
+ raise ValueError(f'scene.remove: Incompatable object remove type {type(bsk_object)}')
156
+
157
+ # Return based on what the user passed in
158
+ if not returns: return None
159
+ if len(returns) == 1: return returns[0]
160
+ return returns
161
+
162
+ def clear(self) -> None:
163
+ self.node_handler.clear()
164
+ self.particle.clear()
165
+
166
+ def set_engine(self, engine: any) -> None:
167
+ """
168
+ Sets the back references to the engine and creates handlers with the context
169
+ """
170
+
171
+ if not self.engine:
172
+ self.engine = engine
173
+ self.ctx = engine.ctx
174
+ self.init_handlers()
175
+ else:
176
+ self.engine = engine
177
+ self.ctx = engine.ctx
178
+
179
+ def raycast(self, position: glm.vec3=None, forward: glm.vec3=None, max_distance: float=1e5, has_collisions: bool=None, has_physics: bool=None, tags: list[str]=[]) -> RaycastResult:
180
+ """
181
+ Ray cast from any posiiton and forward vector and returns a RaycastResult eith the nearest node.
182
+ If no position or forward is given, uses the scene camera's current position and forward
183
+ """
184
+ if not position: position = self.camera.position
185
+ if not forward: forward = self.camera.forward
186
+ forward = glm.normalize(forward)
187
+
188
+ # if we are filtering for collisions, use the broad BVH to improve performance
189
+ if has_collisions:
190
+ colliders = self.collider_handler.bvh.get_line_collided(position, forward)
191
+ nodes = [collider.node for collider in colliders]
192
+
193
+ def is_valid(node: Node) -> bool:
194
+ return all([
195
+ has_collisions is None or bool(node.collider) == has_collisions,
196
+ has_physics is None or bool(node.physics_body) == has_physics,
197
+ all(tag in node.tags for tag in tags)
198
+ ])
199
+
200
+ nodes: list[Node] = list(filter(lambda node: is_valid(node), nodes))
201
+
202
+ # if we are not filtering for collisions, filter nodes and
203
+ else: nodes = self.node_handler.get_all(collisions=has_collisions, physics=has_physics, tags=tags)
204
+
205
+ # determine closest node
206
+ best_distance, best_point, best_node, best_triangle = max_distance, None, None, None
207
+ position_two = position + forward
208
+ for node in nodes:
209
+
210
+ inv_mat = glm.inverse(node.model_matrix)
211
+ relative_position = inv_mat * position
212
+ relative_forward = glm.normalize(inv_mat * position_two - relative_position)
213
+
214
+ triangles = [node.mesh.indices[i] for i in node.mesh.get_line_collided(relative_position, relative_forward)]
215
+
216
+ for triangle in triangles:
217
+ intersection = moller_trumbore(relative_position, relative_forward, [node.mesh.points[i] for i in triangle])
218
+ if not intersection: continue
219
+ intersection = node.model_matrix * intersection
220
+ distance = glm.length(intersection - position)
221
+ if distance < best_distance:
222
+ best_distance = distance
223
+ best_point = intersection
224
+ best_node = node
225
+ best_triangle = triangle
226
+
227
+ if not best_node: return RaycastResult(best_node, best_point, None)
228
+
229
+ points = [best_node.model_matrix * best_node.mesh.points[t] for t in best_triangle]
230
+ normal = glm.normalize(glm.cross(points[1] - points[0], points[2] - points[0]))
231
+
232
+ return RaycastResult(best_node, best_point, normal)
233
+
234
+ def raycast_mouse(self, position: tuple[int, int] | glm.vec2, max_distance: float=1e5, has_collisions: bool=None, has_pshyics: bool=None, tags: list[str]=[]) -> RaycastResult:
235
+ """
236
+ Ray casts from the mouse position with respect to the camera. Returns the nearest node that was clicked, if none was clicked, returns None.
237
+ """
238
+ # derive forward vector from mouse click position
239
+ position = glm.vec2(position)
240
+ inv_proj, inv_view = glm.inverse(self.camera.m_proj), glm.inverse(self.camera.m_view)
241
+ ndc = glm.vec4(2 * position[0] / self.engine.win_size[0] - 1, 1 - 2 * position[1] / self.engine.win_size[1], 1, 1)
242
+ point = inv_proj * ndc
243
+ point /= point.w
244
+ forward = glm.normalize(glm.vec3(inv_view * glm.vec4(point.x, point.y, point.z, 0)))
245
+
246
+ return self.raycast(
247
+ position=self.camera.position,
248
+ forward=forward,
249
+ max_distance=max_distance,
250
+ has_collisions=has_collisions,
251
+ has_physics=has_pshyics,
252
+ tags=tags
253
+ )
254
+
255
+ def get(self, 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) -> Node:
256
+ """
257
+ Returns the first node with the given traits
258
+ """
259
+ self.node_handler.get(position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static)
260
+
261
+ def get_all(self, 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) -> list[Node]:
262
+ """
263
+ Returns all nodes with the given traits
264
+ """
265
+ self.node_handler.get_all(position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static)
266
+
267
+ @property
268
+ def camera(self): return self._camera
269
+ @property
270
+ def sky(self): return self._sky
271
+ @property
272
+ def nodes(self): return self.node_handler.nodes
273
+ @property
274
+ def shader(self): return self._shader
275
+
276
+ @camera.setter
277
+ def camera(self, value: Camera):
278
+ if not value: return
279
+ if not isinstance(value, Camera):
280
+ raise TypeError(f'Scene: Invalid camera type: {type(value)}. Expected type bsk.Camera')
281
+ self._camera = value
282
+ self._camera.scene = self
283
+
284
+ @sky.setter
285
+ def sky(self, value: Sky):
286
+ if not isinstance(value, Sky) and not isinstance(value, type(None)):
287
+ raise TypeError(f'Scene: Invalid sky type: {type(value)}. Expected type bsk.Sky or None')
288
+ self._sky = value
289
+ if value: self._sky.write()
290
+
291
+ @shader.setter
292
+ def shader(self, value):
293
+ self._shader = value
294
+ value.set_main(self)
295
+ if self.light_handler: self.light_handler.write(value)
292
296
  if self.sky: self.sky.write()