basilisk-engine 0.1.29__py3-none-any.whl → 0.1.30__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 (90) hide show
  1. basilisk/__init__.py +15 -15
  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 +224 -224
  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 +3 -3
  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 +166 -170
  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 +74 -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 +89 -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 +689 -689
  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/camera.py +260 -260
  55. basilisk/render/chunk.py +106 -106
  56. basilisk/render/chunk_handler.py +165 -165
  57. basilisk/render/frame.py +95 -95
  58. basilisk/render/framebuffer.py +191 -182
  59. basilisk/render/image.py +120 -120
  60. basilisk/render/image_handler.py +120 -120
  61. basilisk/render/light.py +96 -96
  62. basilisk/render/light_handler.py +58 -58
  63. basilisk/render/material.py +221 -221
  64. basilisk/render/material_handler.py +133 -133
  65. basilisk/render/post_process.py +139 -139
  66. basilisk/render/shader.py +134 -134
  67. basilisk/render/shader_handler.py +83 -83
  68. basilisk/render/sky.py +120 -120
  69. basilisk/scene.py +290 -280
  70. basilisk/shaders/batch.frag +276 -276
  71. basilisk/shaders/batch.vert +115 -115
  72. basilisk/shaders/crt.frag +31 -31
  73. basilisk/shaders/draw.frag +22 -22
  74. basilisk/shaders/draw.vert +25 -25
  75. basilisk/shaders/filter.frag +22 -22
  76. basilisk/shaders/frame.frag +12 -12
  77. basilisk/shaders/frame.vert +13 -13
  78. basilisk/shaders/geometry.frag +8 -8
  79. basilisk/shaders/geometry.vert +41 -41
  80. basilisk/shaders/normal.frag +59 -59
  81. basilisk/shaders/normal.vert +96 -96
  82. basilisk/shaders/particle.frag +71 -71
  83. basilisk/shaders/particle.vert +84 -84
  84. basilisk/shaders/sky.frag +9 -9
  85. basilisk/shaders/sky.vert +13 -13
  86. {basilisk_engine-0.1.29.dist-info → basilisk_engine-0.1.30.dist-info}/METADATA +38 -45
  87. basilisk_engine-0.1.30.dist-info/RECORD +106 -0
  88. {basilisk_engine-0.1.29.dist-info → basilisk_engine-0.1.30.dist-info}/WHEEL +1 -1
  89. basilisk_engine-0.1.29.dist-info/RECORD +0 -106
  90. {basilisk_engine-0.1.29.dist-info → basilisk_engine-0.1.30.dist-info}/top_level.txt +0 -0
basilisk/scene.py CHANGED
@@ -1,280 +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_handler import ShaderHandler
8
- from .render.material_handler import MaterialHandler
9
- from .render.light_handler import LightHandler
10
- from .render.camera import Camera, FreeCamera
11
- from .nodes.node_handler import NodeHandler
12
- from .physics.physics_engine import PhysicsEngine
13
- from .collisions.collider_handler import ColliderHandler
14
- from .render.sky import Sky
15
- from .render.frame import Frame
16
- from .particles.particle_handler import ParticleHandler
17
- from .nodes.node import Node
18
- from .generic.collisions import moller_trumbore
19
- from .generic.raycast_result import RaycastResult
20
- from .render.post_process import PostProcess
21
- from .render.framebuffer import Framebuffer
22
-
23
- class Scene():
24
- engine: ...=None
25
- """Parent engine of the scene"""
26
- ctx: mgl.Context
27
- """Reference to the engine context"""
28
- camera: Camera=None
29
- """"""
30
- light_handler: LightHandler=None
31
- """"""
32
- physics_engine: PhysicsEngine=None
33
- """"""
34
- node_handler: NodeHandler=None
35
- """"""
36
-
37
- def __init__(self, engine: ...) -> None:
38
- """
39
- Basilisk scene object. Contains all nodes for the scene
40
- """
41
-
42
- self.engine = engine
43
- self.ctx = engine.ctx
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 render: self.render()
73
-
74
- def render(self, render_target: Framebuffer|Frame=None) -> None:
75
- """
76
- Renders all the nodes with meshes in the scene
77
- """
78
-
79
- if render_target:
80
- show = False
81
- else:
82
- render_target = self.engine.frame
83
- show = True
84
-
85
- render_target.use()
86
- self.engine.shader_handler.write(self)
87
- if self.sky: self.sky.render()
88
- self.node_handler.render()
89
- self.particle.render()
90
-
91
-
92
- if self.engine.headless or not show: return
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
-
267
- @camera.setter
268
- def camera(self, value: Camera):
269
- if not value: return
270
- if not isinstance(value, Camera):
271
- raise TypeError(f'Scene: Invalid camera type: {type(value)}. Expected type bsk.Camera')
272
- self._camera = value
273
- self._camera.scene = self
274
-
275
- @sky.setter
276
- def sky(self, value: Sky):
277
- if not isinstance(value, Sky) and not isinstance(value, type(None)):
278
- raise TypeError(f'Scene: Invalid sky type: {type(value)}. Expected type bsk.Sky or None')
279
- self._sky = value
280
- if value: self._sky.write()
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
290
+ value.set_main(self)