basilisk-engine 0.1.35__py3-none-any.whl → 0.1.36__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 (98) hide show
  1. basilisk/__init__.py +26 -26
  2. basilisk/audio/sound.py +27 -27
  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 +54 -4
  18. basilisk/draw/draw.py +100 -100
  19. basilisk/draw/draw_handler.py +175 -175
  20. basilisk/draw/font_renderer.py +28 -28
  21. basilisk/engine.py +165 -165
  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 -74
  26. basilisk/generic/math.py +6 -6
  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 -89
  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 +704 -695
  47. basilisk/nodes/node_handler.py +97 -97
  48. basilisk/particles/particle_handler.py +64 -64
  49. basilisk/particles/particle_renderer.py +92 -92
  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 +108 -0
  55. basilisk/render/camera.py +260 -260
  56. basilisk/render/chunk.py +108 -106
  57. basilisk/render/chunk_handler.py +167 -165
  58. basilisk/render/frame.py +107 -95
  59. basilisk/render/framebuffer.py +203 -192
  60. basilisk/render/image.py +120 -120
  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 -221
  65. basilisk/render/material_handler.py +133 -133
  66. basilisk/render/post_process.py +139 -139
  67. basilisk/render/shader.py +134 -134
  68. basilisk/render/shader_handler.py +85 -83
  69. basilisk/render/sky.py +120 -120
  70. basilisk/scene.py +289 -289
  71. basilisk/shaders/batch.frag +289 -276
  72. basilisk/shaders/batch.vert +117 -115
  73. basilisk/shaders/bloom_downsample.frag +43 -0
  74. basilisk/shaders/bloom_frame.frag +25 -0
  75. basilisk/shaders/bloom_upsample.frag +34 -0
  76. basilisk/shaders/crt.frag +31 -31
  77. basilisk/shaders/draw.frag +25 -22
  78. basilisk/shaders/draw.vert +25 -25
  79. basilisk/shaders/filter.frag +22 -22
  80. basilisk/shaders/frame.frag +12 -12
  81. basilisk/shaders/frame.vert +13 -13
  82. basilisk/shaders/geometry.frag +8 -8
  83. basilisk/shaders/geometry.vert +41 -41
  84. basilisk/shaders/normal.frag +59 -59
  85. basilisk/shaders/normal.vert +96 -96
  86. basilisk/shaders/particle.frag +71 -71
  87. basilisk/shaders/particle.vert +84 -84
  88. basilisk/shaders/sky.frag +23 -9
  89. basilisk/shaders/sky.vert +13 -13
  90. basilisk_engine-0.1.36.dist-info/METADATA +89 -0
  91. basilisk_engine-0.1.36.dist-info/RECORD +110 -0
  92. {basilisk_engine-0.1.35.dist-info → basilisk_engine-0.1.36.dist-info}/WHEEL +1 -1
  93. basilisk/input/__init__.py +0 -0
  94. basilisk/input/mouse.py +0 -62
  95. basilisk/input/path.py +0 -14
  96. basilisk_engine-0.1.35.dist-info/METADATA +0 -45
  97. basilisk_engine-0.1.35.dist-info/RECORD +0 -109
  98. {basilisk_engine-0.1.35.dist-info → basilisk_engine-0.1.36.dist-info}/top_level.txt +0 -0
basilisk/scene.py CHANGED
@@ -1,290 +1,290 @@
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.shader = shader if shader else engine.shader
44
- self.camera = FreeCamera()
45
- self.light_handler = LightHandler(self)
46
- self.physics_engine = PhysicsEngine()
47
- self.node_handler = NodeHandler(self)
48
- self.particle = ParticleHandler(self)
49
- self.collider_handler = ColliderHandler(self)
50
- self.sky = Sky(self.engine)
51
-
52
-
53
- def update(self, render: bool=True, nodes: bool=True, particles: bool=True, collisions: bool=True) -> None:
54
- """
55
- Updates the physics and in the scene
56
- """
57
-
58
- # Check that the engine is still running
59
- self.engine._update()
60
- if not self.engine.running: return
61
-
62
- # Update based on the given parameters
63
- if nodes: self.node_handler.update()
64
- if particles: self.particle.update()
65
- if self.engine.event_resize: self.camera.use()
66
- self.camera.update()
67
-
68
- 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
69
- self.collider_handler.resolve_collisions()
70
-
71
- # Render by default to the engine frame
72
- if not render: return
73
-
74
- # Check if the user is giving a destination
75
- if not isinstance(render, bool): self.render(renders)
76
- else: self.render()
77
-
78
- def render(self, render_target: Framebuffer|Frame=None) -> None:
79
- """
80
- Renders all the nodes with meshes in the scene
81
- """
82
-
83
- if render_target:
84
- show = False
85
- else:
86
- render_target = self.engine.frame
87
- show = True
88
-
89
- render_target.use()
90
- self.engine.shader_handler.write(self)
91
- if self.sky: self.sky.render()
92
- self.node_handler.render()
93
- self.particle.render()
94
-
95
-
96
- if self.engine.headless or not show: return
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
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.shader = shader if shader else engine.shader
44
+ self.camera = FreeCamera()
45
+ self.light_handler = LightHandler(self)
46
+ self.physics_engine = PhysicsEngine()
47
+ self.node_handler = NodeHandler(self)
48
+ self.particle = ParticleHandler(self)
49
+ self.collider_handler = ColliderHandler(self)
50
+ self.sky = Sky(self.engine)
51
+
52
+
53
+ def update(self, render: bool=True, nodes: bool=True, particles: bool=True, collisions: bool=True) -> None:
54
+ """
55
+ Updates the physics and in the scene
56
+ """
57
+
58
+ # Check that the engine is still running
59
+ self.engine._update()
60
+ if not self.engine.running: return
61
+
62
+ # Update based on the given parameters
63
+ if nodes: self.node_handler.update()
64
+ if particles: self.particle.update()
65
+ if self.engine.event_resize: self.camera.use()
66
+ self.camera.update()
67
+
68
+ 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
69
+ self.collider_handler.resolve_collisions()
70
+
71
+ # Render by default to the engine frame
72
+ if not render: return
73
+
74
+ # Check if the user is giving a destination
75
+ if not isinstance(render, bool): self.render(renders)
76
+ else: self.render()
77
+
78
+ def render(self, render_target: Framebuffer|Frame=None) -> None:
79
+ """
80
+ Renders all the nodes with meshes in the scene
81
+ """
82
+
83
+ if render_target:
84
+ show = False
85
+ else:
86
+ render_target = self.engine.frame
87
+ show = True
88
+
89
+ render_target.use()
90
+ self.engine.shader_handler.write(self)
91
+ self.node_handler.render()
92
+ self.particle.render()
93
+ if self.sky: self.sky.render()
94
+
95
+
96
+ if self.engine.headless or not show: return
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
290
  value.set_main(self)