basilisk-engine 0.1.42__py3-none-any.whl → 0.1.43__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 +53 -53
  18. basilisk/draw/draw.py +100 -100
  19. basilisk/draw/draw_handler.py +178 -178
  20. basilisk/draw/font_renderer.py +28 -28
  21. basilisk/engine.py +169 -171
  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 +97 -97
  48. basilisk/particles/particle_handler.py +64 -64
  49. basilisk/particles/particle_renderer.py +93 -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 +118 -124
  55. basilisk/render/camera.py +260 -260
  56. basilisk/render/chunk.py +113 -108
  57. basilisk/render/chunk_handler.py +167 -167
  58. basilisk/render/frame.py +130 -128
  59. basilisk/render/framebuffer.py +192 -212
  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 -232
  65. basilisk/render/material_handler.py +133 -133
  66. basilisk/render/post_process.py +180 -180
  67. basilisk/render/shader.py +135 -134
  68. basilisk/render/shader_handler.py +109 -94
  69. basilisk/render/sky.py +119 -121
  70. basilisk/scene.py +287 -288
  71. basilisk/shaders/batch.frag +293 -293
  72. basilisk/shaders/batch.vert +117 -117
  73. basilisk/shaders/bloom_downsample.frag +23 -42
  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.42.dist-info → basilisk_engine-0.1.43.dist-info}/METADATA +89 -89
  91. basilisk_engine-0.1.43.dist-info/RECORD +111 -0
  92. {basilisk_engine-0.1.42.dist-info → basilisk_engine-0.1.43.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/shaders/bloom_frame.frag +0 -25
  97. basilisk_engine-0.1.42.dist-info/RECORD +0 -115
  98. {basilisk_engine-0.1.42.dist-info → basilisk_engine-0.1.43.dist-info}/top_level.txt +0 -0
basilisk/scene.py CHANGED
@@ -1,289 +1,288 @@
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)
51
- self.frame = Frame(self.engine)
52
-
53
-
54
- def update(self, render: bool=True, nodes: bool=True, particles: bool=True, collisions: bool=True) -> None:
55
- """
56
- Updates the physics and in the scene
57
- """
58
-
59
- # Call the internal engine update (for IO and time)
60
- self.engine._update()
61
-
62
- # Check that the engine is still running
63
- if not self.engine.running: return
64
-
65
- # Update based on the given parameters
66
- if nodes: self.node_handler.update()
67
- if particles: self.particle.update()
68
-
69
- # Update the camera
70
- self.camera.update()
71
- if self.engine.event_resize: self.camera.use()
72
-
73
- 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
74
- self.collider_handler.resolve_collisions()
75
-
76
- # Render by default to the scene frame
77
- if render: self.render()
78
-
79
- def render(self, target=None) -> None:
80
- """
81
- Renders all the nodes with meshes in the scene
82
- """
83
-
84
- target.use() if target else self.frame.use(); self.frame.clear()
85
- self.engine.shader_handler.write(self)
86
- self.particle.render()
87
- self.node_handler.render()
88
- if self.sky: self.sky.render()
89
-
90
- if target: return
91
- # This will show the frame to screen on engine.update()
92
- self.frame.scene_render(target)
93
- self.engine.frames.append(self.frame)
94
-
95
-
96
-
97
- def add(self, *objects: Node | None) -> None | Node | list:
98
- """
99
- Adds the given object(s) to the scene. Can pass in any scene objects:
100
- Argument overloads:
101
- object: Node - Adds the given node to the scene.
102
- """
103
-
104
- # List of all return values for the added objects
105
- returns = []
106
-
107
- # Loop through all objects passed in
108
- for bsk_object in objects:
109
-
110
- # Considered well defined behavior to add None
111
- if isinstance(bsk_object, type(None)):
112
- continue
113
-
114
- # Add a node to the scene
115
- elif isinstance(bsk_object, Node):
116
- returns.append(self.node_handler.add(bsk_object)); continue
117
-
118
- # Add a node to the scene
119
- elif isinstance(bsk_object, PostProcess):
120
- returns.append(self.engine.frame.add_post_process(bsk_object)); continue
121
-
122
-
123
- # Recived incompatable type
124
- else:
125
- raise ValueError(f'scene.add: Incompatable object add type {type(bsk_object)}')
126
-
127
- # Return based on what the user passed in
128
- if not returns: return None
129
- if len(returns) == 1: return returns[0]
130
- return returns
131
-
132
- def remove(self, *objects: Node | None) -> None | Node | list:
133
- """
134
- Removes the given baskilsk object from the scene
135
- """
136
-
137
- # List of all return values for the added objects
138
- returns = []
139
-
140
- # Loop through all objects passed in
141
- for bsk_object in objects:
142
-
143
- # Considered well defined behavior to remove None
144
- if isinstance(bsk_object, type(None)):
145
- continue
146
-
147
- # Remove a node from the scene
148
- elif isinstance(bsk_object, Node):
149
- returns.append(self.node_handler.remove(bsk_object)); continue
150
-
151
- # Recived incompatable type
152
- else:
153
- raise ValueError(f'scene.remove: Incompatable object remove type {type(bsk_object)}')
154
-
155
- # Return based on what the user passed in
156
- if not returns: return None
157
- if len(returns) == 1: return returns[0]
158
- return returns
159
-
160
- def set_engine(self, engine: any) -> None:
161
- """
162
- Sets the back references to the engine and creates handlers with the context
163
- """
164
-
165
- if not self.engine:
166
- self.engine = engine
167
- self.ctx = engine.ctx
168
- self.init_handlers()
169
- else:
170
- self.engine = engine
171
- self.ctx = engine.ctx
172
-
173
- 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:
174
- """
175
- Ray cast from any posiiton and forward vector and returns a RaycastResult eith the nearest node.
176
- If no position or forward is given, uses the scene camera's current position and forward
177
- """
178
- if not position: position = self.camera.position
179
- if not forward: forward = self.camera.forward
180
- forward = glm.normalize(forward)
181
-
182
- # if we are filtering for collisions, use the broad BVH to improve performance
183
- if has_collisions:
184
- colliders = self.collider_handler.bvh.get_line_collided(position, forward)
185
- nodes = [collider.node for collider in colliders]
186
-
187
- def is_valid(node: Node) -> bool:
188
- return all([
189
- has_collisions is None or bool(node.collider) == has_collisions,
190
- has_physics is None or bool(node.physics_body) == has_physics,
191
- all(tag in node.tags for tag in tags)
192
- ])
193
-
194
- nodes: list[Node] = list(filter(lambda node: is_valid(node), nodes))
195
-
196
- # if we are not filtering for collisions, filter nodes and
197
- else: nodes = self.node_handler.get_all(collisions=has_collisions, physics=has_physics, tags=tags)
198
-
199
- # determine closest node
200
- best_distance, best_point, best_node, best_triangle = max_distance, None, None, None
201
- position_two = position + forward
202
- for node in nodes:
203
-
204
- inv_mat = glm.inverse(node.model_matrix)
205
- relative_position = inv_mat * position
206
- relative_forward = glm.normalize(inv_mat * position_two - relative_position)
207
-
208
- triangles = [node.mesh.indices[i] for i in node.mesh.get_line_collided(relative_position, relative_forward)]
209
-
210
- for triangle in triangles:
211
- intersection = moller_trumbore(relative_position, relative_forward, [node.mesh.points[i] for i in triangle])
212
- if not intersection: continue
213
- intersection = node.model_matrix * intersection
214
- distance = glm.length(intersection - position)
215
- if distance < best_distance:
216
- best_distance = distance
217
- best_point = intersection
218
- best_node = node
219
- best_triangle = triangle
220
-
221
- if not best_node: return RaycastResult(best_node, best_point, None)
222
-
223
- points = [best_node.model_matrix * best_node.mesh.points[t] for t in best_triangle]
224
- normal = glm.normalize(glm.cross(points[1] - points[0], points[2] - points[0]))
225
-
226
- return RaycastResult(best_node, best_point, normal)
227
-
228
- 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:
229
- """
230
- Ray casts from the mouse position with respect to the camera. Returns the nearest node that was clicked, if none was clicked, returns None.
231
- """
232
- # derive forward vector from mouse click position
233
- position = glm.vec2(position)
234
- inv_proj, inv_view = glm.inverse(self.camera.m_proj), glm.inverse(self.camera.m_view)
235
- ndc = glm.vec4(2 * position[0] / self.engine.win_size[0] - 1, 1 - 2 * position[1] / self.engine.win_size[1], 1, 1)
236
- point = inv_proj * ndc
237
- point /= point.w
238
- forward = glm.normalize(glm.vec3(inv_view * glm.vec4(point.x, point.y, point.z, 0)))
239
-
240
- return self.raycast(
241
- position=self.camera.position,
242
- forward=forward,
243
- max_distance=max_distance,
244
- has_collisions=has_collisions,
245
- has_physics=has_pshyics,
246
- tags=tags
247
- )
248
-
249
- 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:
250
- """
251
- Returns the first node with the given traits
252
- """
253
- 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)
254
-
255
- 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]:
256
- """
257
- Returns all nodes with the given traits
258
- """
259
- 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)
260
-
261
- @property
262
- def camera(self): return self._camera
263
- @property
264
- def sky(self): return self._sky
265
- @property
266
- def nodes(self): return self.node_handler.nodes
267
- @property
268
- def shader(self): return self._shader
269
-
270
- @camera.setter
271
- def camera(self, value: Camera):
272
- if not value: return
273
- if not isinstance(value, Camera):
274
- raise TypeError(f'Scene: Invalid camera type: {type(value)}. Expected type bsk.Camera')
275
- self._camera = value
276
- self._camera.scene = self
277
-
278
- @sky.setter
279
- def sky(self, value: Sky):
280
- if not isinstance(value, Sky) and not isinstance(value, type(None)):
281
- raise TypeError(f'Scene: Invalid sky type: {type(value)}. Expected type bsk.Sky or None')
282
- self._sky = value
283
- if value: self._sky.write()
284
-
285
- @shader.setter
286
- def shader(self, value):
287
- self._shader = value
288
- value.set_main(self)
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)
51
+ self.frame = Frame(self.engine)
52
+
53
+
54
+ def update(self, render: bool=True, nodes: bool=True, particles: bool=True, collisions: bool=True) -> None:
55
+ """
56
+ Updates the physics and in the scene
57
+ """
58
+
59
+ # Call the internal engine update (for IO and time)
60
+ self.engine._update()
61
+
62
+ # Check that the engine is still running
63
+ if not self.engine.running: return
64
+
65
+ # Update based on the given parameters
66
+ if nodes: self.node_handler.update()
67
+ if particles: self.particle.update()
68
+
69
+ # Update the camera
70
+ self.camera.update()
71
+ if self.engine.event_resize: self.camera.use()
72
+
73
+ 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
74
+ self.collider_handler.resolve_collisions()
75
+
76
+ # Render by default to the scene frame
77
+ if render: self.render()
78
+
79
+ def render(self, target=None) -> None:
80
+ """
81
+ Renders all the nodes with meshes in the scene
82
+ """
83
+
84
+ target.use() if target else self.frame.use(); self.frame.clear()
85
+ self.engine.shader_handler.write(self)
86
+ self.particle.render()
87
+ self.node_handler.render()
88
+ if self.sky: self.sky.render()
89
+
90
+ if target: return
91
+ # This will show the frame to screen on engine.update()
92
+ self.frame.scene_render(self.ctx.screen)
93
+
94
+
95
+
96
+ def add(self, *objects: Node | None) -> None | Node | list:
97
+ """
98
+ Adds the given object(s) to the scene. Can pass in any scene objects:
99
+ Argument overloads:
100
+ object: Node - Adds the given node to the scene.
101
+ """
102
+
103
+ # List of all return values for the added objects
104
+ returns = []
105
+
106
+ # Loop through all objects passed in
107
+ for bsk_object in objects:
108
+
109
+ # Considered well defined behavior to add None
110
+ if isinstance(bsk_object, type(None)):
111
+ continue
112
+
113
+ # Add a node to the scene
114
+ elif isinstance(bsk_object, Node):
115
+ returns.append(self.node_handler.add(bsk_object)); continue
116
+
117
+ # Add a node to the scene
118
+ elif isinstance(bsk_object, PostProcess):
119
+ returns.append(self.engine.frame.add_post_process(bsk_object)); continue
120
+
121
+
122
+ # Recived incompatable type
123
+ else:
124
+ raise ValueError(f'scene.add: Incompatable object add type {type(bsk_object)}')
125
+
126
+ # Return based on what the user passed in
127
+ if not returns: return None
128
+ if len(returns) == 1: return returns[0]
129
+ return returns
130
+
131
+ def remove(self, *objects: Node | None) -> None | Node | list:
132
+ """
133
+ Removes the given baskilsk object from the scene
134
+ """
135
+
136
+ # List of all return values for the added objects
137
+ returns = []
138
+
139
+ # Loop through all objects passed in
140
+ for bsk_object in objects:
141
+
142
+ # Considered well defined behavior to remove None
143
+ if isinstance(bsk_object, type(None)):
144
+ continue
145
+
146
+ # Remove a node from the scene
147
+ elif isinstance(bsk_object, Node):
148
+ returns.append(self.node_handler.remove(bsk_object)); continue
149
+
150
+ # Recived incompatable type
151
+ else:
152
+ raise ValueError(f'scene.remove: Incompatable object remove type {type(bsk_object)}')
153
+
154
+ # Return based on what the user passed in
155
+ if not returns: return None
156
+ if len(returns) == 1: return returns[0]
157
+ return returns
158
+
159
+ def set_engine(self, engine: any) -> None:
160
+ """
161
+ Sets the back references to the engine and creates handlers with the context
162
+ """
163
+
164
+ if not self.engine:
165
+ self.engine = engine
166
+ self.ctx = engine.ctx
167
+ self.init_handlers()
168
+ else:
169
+ self.engine = engine
170
+ self.ctx = engine.ctx
171
+
172
+ 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:
173
+ """
174
+ Ray cast from any posiiton and forward vector and returns a RaycastResult eith the nearest node.
175
+ If no position or forward is given, uses the scene camera's current position and forward
176
+ """
177
+ if not position: position = self.camera.position
178
+ if not forward: forward = self.camera.forward
179
+ forward = glm.normalize(forward)
180
+
181
+ # if we are filtering for collisions, use the broad BVH to improve performance
182
+ if has_collisions:
183
+ colliders = self.collider_handler.bvh.get_line_collided(position, forward)
184
+ nodes = [collider.node for collider in colliders]
185
+
186
+ def is_valid(node: Node) -> bool:
187
+ return all([
188
+ has_collisions is None or bool(node.collider) == has_collisions,
189
+ has_physics is None or bool(node.physics_body) == has_physics,
190
+ all(tag in node.tags for tag in tags)
191
+ ])
192
+
193
+ nodes: list[Node] = list(filter(lambda node: is_valid(node), nodes))
194
+
195
+ # if we are not filtering for collisions, filter nodes and
196
+ else: nodes = self.node_handler.get_all(collisions=has_collisions, physics=has_physics, tags=tags)
197
+
198
+ # determine closest node
199
+ best_distance, best_point, best_node, best_triangle = max_distance, None, None, None
200
+ position_two = position + forward
201
+ for node in nodes:
202
+
203
+ inv_mat = glm.inverse(node.model_matrix)
204
+ relative_position = inv_mat * position
205
+ relative_forward = glm.normalize(inv_mat * position_two - relative_position)
206
+
207
+ triangles = [node.mesh.indices[i] for i in node.mesh.get_line_collided(relative_position, relative_forward)]
208
+
209
+ for triangle in triangles:
210
+ intersection = moller_trumbore(relative_position, relative_forward, [node.mesh.points[i] for i in triangle])
211
+ if not intersection: continue
212
+ intersection = node.model_matrix * intersection
213
+ distance = glm.length(intersection - position)
214
+ if distance < best_distance:
215
+ best_distance = distance
216
+ best_point = intersection
217
+ best_node = node
218
+ best_triangle = triangle
219
+
220
+ if not best_node: return RaycastResult(best_node, best_point, None)
221
+
222
+ points = [best_node.model_matrix * best_node.mesh.points[t] for t in best_triangle]
223
+ normal = glm.normalize(glm.cross(points[1] - points[0], points[2] - points[0]))
224
+
225
+ return RaycastResult(best_node, best_point, normal)
226
+
227
+ 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:
228
+ """
229
+ Ray casts from the mouse position with respect to the camera. Returns the nearest node that was clicked, if none was clicked, returns None.
230
+ """
231
+ # derive forward vector from mouse click position
232
+ position = glm.vec2(position)
233
+ inv_proj, inv_view = glm.inverse(self.camera.m_proj), glm.inverse(self.camera.m_view)
234
+ ndc = glm.vec4(2 * position[0] / self.engine.win_size[0] - 1, 1 - 2 * position[1] / self.engine.win_size[1], 1, 1)
235
+ point = inv_proj * ndc
236
+ point /= point.w
237
+ forward = glm.normalize(glm.vec3(inv_view * glm.vec4(point.x, point.y, point.z, 0)))
238
+
239
+ return self.raycast(
240
+ position=self.camera.position,
241
+ forward=forward,
242
+ max_distance=max_distance,
243
+ has_collisions=has_collisions,
244
+ has_physics=has_pshyics,
245
+ tags=tags
246
+ )
247
+
248
+ 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:
249
+ """
250
+ Returns the first node with the given traits
251
+ """
252
+ 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)
253
+
254
+ 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]:
255
+ """
256
+ Returns all nodes with the given traits
257
+ """
258
+ 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)
259
+
260
+ @property
261
+ def camera(self): return self._camera
262
+ @property
263
+ def sky(self): return self._sky
264
+ @property
265
+ def nodes(self): return self.node_handler.nodes
266
+ @property
267
+ def shader(self): return self._shader
268
+
269
+ @camera.setter
270
+ def camera(self, value: Camera):
271
+ if not value: return
272
+ if not isinstance(value, Camera):
273
+ raise TypeError(f'Scene: Invalid camera type: {type(value)}. Expected type bsk.Camera')
274
+ self._camera = value
275
+ self._camera.scene = self
276
+
277
+ @sky.setter
278
+ def sky(self, value: Sky):
279
+ if not isinstance(value, Sky) and not isinstance(value, type(None)):
280
+ raise TypeError(f'Scene: Invalid sky type: {type(value)}. Expected type bsk.Sky or None')
281
+ self._sky = value
282
+ if value: self._sky.write()
283
+
284
+ @shader.setter
285
+ def shader(self, value):
286
+ self._shader = value
287
+ value.set_main(self)
289
288
  if self.light_handler: self.light_handler.write(value)