basilisk-engine 0.1.43__py3-none-any.whl → 0.1.45__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 +181 -178
  20. basilisk/draw/font_renderer.py +28 -28
  21. basilisk/engine.py +168 -169
  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/__init__.py +0 -0
  34. basilisk/input/mouse.py +62 -0
  35. basilisk/input/path.py +14 -0
  36. basilisk/input_output/IO_handler.py +91 -91
  37. basilisk/input_output/clock.py +49 -49
  38. basilisk/input_output/keys.py +43 -43
  39. basilisk/input_output/mouse.py +90 -90
  40. basilisk/input_output/path.py +14 -14
  41. basilisk/mesh/cube.py +33 -33
  42. basilisk/mesh/mesh.py +233 -233
  43. basilisk/mesh/mesh_from_data.py +150 -150
  44. basilisk/mesh/model.py +271 -271
  45. basilisk/mesh/narrow_aabb.py +89 -89
  46. basilisk/mesh/narrow_bvh.py +91 -91
  47. basilisk/mesh/narrow_primative.py +23 -23
  48. basilisk/nodes/helper.py +28 -28
  49. basilisk/nodes/node.py +709 -709
  50. basilisk/nodes/node_handler.py +97 -97
  51. basilisk/particles/particle_handler.py +64 -64
  52. basilisk/particles/particle_renderer.py +93 -93
  53. basilisk/physics/impulse.py +112 -112
  54. basilisk/physics/physics_body.py +43 -43
  55. basilisk/physics/physics_engine.py +35 -35
  56. basilisk/render/batch.py +103 -103
  57. basilisk/render/bloom.py +117 -117
  58. basilisk/render/camera.py +260 -260
  59. basilisk/render/chunk.py +113 -113
  60. basilisk/render/chunk_handler.py +167 -167
  61. basilisk/render/frame.py +130 -130
  62. basilisk/render/framebuffer.py +192 -192
  63. basilisk/render/image.py +120 -120
  64. basilisk/render/image_handler.py +120 -120
  65. basilisk/render/light.py +96 -96
  66. basilisk/render/light_handler.py +58 -58
  67. basilisk/render/material.py +232 -232
  68. basilisk/render/material_handler.py +133 -133
  69. basilisk/render/post_process.py +180 -180
  70. basilisk/render/shader.py +135 -135
  71. basilisk/render/shader_handler.py +109 -109
  72. basilisk/render/sky.py +119 -119
  73. basilisk/scene.py +287 -287
  74. basilisk/shaders/batch.frag +291 -293
  75. basilisk/shaders/batch.vert +117 -117
  76. basilisk/shaders/bloom_downsample.frag +23 -23
  77. basilisk/shaders/bloom_frame.frag +25 -0
  78. basilisk/shaders/bloom_upsample.frag +33 -33
  79. basilisk/shaders/crt.frag +34 -34
  80. basilisk/shaders/draw.frag +27 -27
  81. basilisk/shaders/draw.vert +25 -25
  82. basilisk/shaders/filter.frag +22 -22
  83. basilisk/shaders/frame.frag +13 -13
  84. basilisk/shaders/frame.vert +13 -13
  85. basilisk/shaders/frame_hdr.frag +27 -27
  86. basilisk/shaders/geometry.frag +10 -10
  87. basilisk/shaders/geometry.vert +41 -41
  88. basilisk/shaders/normal.frag +62 -62
  89. basilisk/shaders/normal.vert +96 -96
  90. basilisk/shaders/particle.frag +81 -81
  91. basilisk/shaders/particle.vert +86 -86
  92. basilisk/shaders/sky.frag +23 -23
  93. basilisk/shaders/sky.vert +13 -13
  94. {basilisk_engine-0.1.43.dist-info → basilisk_engine-0.1.45.dist-info}/METADATA +89 -89
  95. basilisk_engine-0.1.45.dist-info/RECORD +115 -0
  96. {basilisk_engine-0.1.43.dist-info → basilisk_engine-0.1.45.dist-info}/WHEEL +1 -1
  97. basilisk_engine-0.1.43.dist-info/RECORD +0 -111
  98. {basilisk_engine-0.1.43.dist-info → basilisk_engine-0.1.45.dist-info}/top_level.txt +0 -0
basilisk/scene.py CHANGED
@@ -1,288 +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(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)
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)
288
288
  if self.light_handler: self.light_handler.write(value)