basilisk-engine 0.1.34__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 -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 +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.34.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.34.dist-info/METADATA +0 -45
  97. basilisk_engine-0.1.34.dist-info/RECORD +0 -109
  98. {basilisk_engine-0.1.34.dist-info → basilisk_engine-0.1.36.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,108 @@
1
+ import moderngl as mgl
2
+ import glm
3
+ from .framebuffer import Framebuffer
4
+ from .shader import Shader
5
+
6
+
7
+ class Bloom:
8
+ def __init__(self, frame):
9
+ self.engine = frame.engine
10
+ self.ctx = self.engine.ctx
11
+ self.frame = frame
12
+
13
+ # Load bloom tools
14
+ self.downsample_shader = Shader(self.engine, self.engine.root + '/shaders/frame.vert', self.engine.root + '/shaders/bloom_downsample.frag')
15
+ self.upsample_shader = Shader(self.engine, self.engine.root + '/shaders/frame.vert', self.engine.root + '/shaders/bloom_upsample.frag')
16
+
17
+ self.engine.shader_handler.add(self.downsample_shader)
18
+ self.engine.shader_handler.add(self.upsample_shader)
19
+
20
+ self.downsample_vao = self.ctx.vertex_array(self.downsample_shader.program, [(self.frame.vbo, '3f 2f', 'in_position', 'in_uv')], skip_errors=True)
21
+ self.upsample_vao = self.ctx.vertex_array(self.upsample_shader.program, [(self.frame.vbo, '3f 2f', 'in_position', 'in_uv')], skip_errors=True)
22
+
23
+ self.generate_bloom_buffers()
24
+
25
+ def render(self) -> None:
26
+ """
27
+ GPU downsamples and upsamples the bloom texture to blur it
28
+ """
29
+
30
+ if not self.frame.engine.config.bloom_enabled:
31
+ getattr(self, f'upscale_buffer_0').clear()
32
+ return
33
+
34
+ n = self.engine.config.bloom_quality
35
+
36
+ self.downsample(self.frame.framebuffer.color_attachments[1], getattr(self, f'bloom_buffer_{0}'))
37
+ for i in range(0, n):
38
+ self.downsample(getattr(self, f'bloom_buffer_{i}'), getattr(self, f'bloom_buffer_{i + 1}'))
39
+
40
+ self.upsample(getattr(self, f'bloom_buffer_{n - 1}'), getattr(self, f'bloom_buffer_{n}'), getattr(self, f'upscale_buffer_{n}'))
41
+ for i in range(n - 1, -1, -1):
42
+ self.upsample(getattr(self, f'upscale_buffer_{i + 1}'), getattr(self, f'bloom_buffer_{i}'), getattr(self, f'upscale_buffer_{i}'))
43
+
44
+ def downsample(self, source: Framebuffer, dest: Framebuffer) -> None:
45
+ """
46
+
47
+ """
48
+
49
+ # Bind the source texture to the shader
50
+
51
+ if isinstance(source, Framebuffer): texture = source.texture
52
+ else: texture = source
53
+
54
+ self.downsample_shader.bind(texture, 'screenTexture', 0)
55
+ self.downsample_shader.write('textureSize', glm.ivec2(source.size))
56
+
57
+ # Clear and use the destination fbo
58
+ dest.use()
59
+ dest.clear()
60
+
61
+ # Render using the downsample vao (2x2 box filter)
62
+ self.downsample_vao.render()
63
+
64
+ def upsample(self, low: Framebuffer, high: Framebuffer, dest: Framebuffer) -> None:
65
+ """
66
+
67
+ """
68
+
69
+ # Bind the source texture to the shader
70
+ self.upsample_shader.bind(high.texture, 'highTexture', 0)
71
+ self.upsample_shader.bind(low.texture, 'lowTexture', 1)
72
+ self.upsample_shader.write('textureSize', glm.ivec2(low.size))
73
+
74
+ # Clear and use the destination fbo
75
+ dest.use()
76
+ dest.clear()
77
+
78
+ # Render using the upsample vao (3x3 tent filter)
79
+ self.upsample_vao.render()
80
+
81
+ def generate_bloom_buffers(self) -> None:
82
+ """
83
+ Generates n buffers for down/up sampling
84
+ """
85
+
86
+ n = self.engine.config.bloom_quality
87
+ size = self.frame.framebuffer.size
88
+
89
+ for i in range(100):
90
+ try:
91
+ getattr(self, f'bloom_buffer_{i}').__del__()
92
+ getattr(self, f'upscale_buffer_{i}').__del__()
93
+ except:
94
+ ...
95
+
96
+ for i in range(n + 1):
97
+ fbo = Framebuffer(self.engine, (max(size[0] // (2 ** (i)), 1), max(size[1] // (2 ** (i)), 1)))
98
+ fbo.texture.repeat_x = False
99
+ fbo.texture.repeat_y = False
100
+ setattr(self, f'bloom_buffer_{i}', fbo)
101
+
102
+ fbo = Framebuffer(self.engine, (max(size[0] // (2 ** (i)), 1), max(size[1] // (2 ** (i)), 1)))
103
+ fbo.texture.repeat_x = False
104
+ fbo.texture.repeat_y = False
105
+ setattr(self, f'upscale_buffer_{i}', fbo)
106
+
107
+ @property
108
+ def texture(self): return getattr(self, f'upscale_buffer_0').texture
basilisk/render/camera.py CHANGED
@@ -1,261 +1,261 @@
1
- import pygame as pg
2
- import glm
3
- import numpy as np
4
- from ..generic.vec3 import Vec3
5
- from ..generic.quat import Quat
6
-
7
-
8
- # Camera view constants
9
- FOV = 50 # Degrees
10
- NEAR = 0.1
11
- FAR = 350
12
-
13
- class Camera:
14
- engine: ...
15
- """Back reference to the parent engine"""
16
- scene: ...
17
- """Back reference to the parent scene"""
18
- aspect_ratio: float
19
- """Aspect ratio of the engine window"""
20
- position: glm.vec3
21
- """Location of the camera (maters)"""
22
- speed: float
23
- """The speed that the camera moves in space"""
24
- sensitivity: float
25
- """The speed at which the camera turns"""
26
-
27
- def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0), speed: float=10, sensitivity: float=2) -> None:
28
- """
29
- Camera object to get view and projection matricies. Movement built in
30
- """
31
-
32
- # Back references
33
- self.scene = None
34
- self.engine = None
35
- # transformations
36
- self.rotation = glm.quat(rotation)
37
- self.position = glm.vec3(position)
38
- # fov
39
- self.fov = 50
40
- # The initial aspect ratio of the screen
41
- self.aspect_ratio = 1.0
42
- # View matrix
43
- self.m_view = self.get_view_matrix()
44
- # Projection matrix
45
- self.m_proj = self.get_projection_matrix()
46
- # Movement attributes
47
- self.speed = speed
48
- self.sensitivity = sensitivity
49
-
50
- def update(self) -> None:
51
- """
52
- Updates the camera view matrix
53
- """
54
-
55
- # self.update_camera_vectors()
56
- self.m_view = self.get_view_matrix()
57
-
58
- def use(self):
59
- # Updated aspect ratio of the screen
60
- self.aspect_ratio = self.engine.win_size[0] / self.engine.win_size[1]
61
- # View matrix
62
- self.m_view = self.get_view_matrix()
63
- # Projection matrix
64
- self.m_proj = self.get_projection_matrix()
65
-
66
- def get_view_matrix(self) -> glm.mat4x4:
67
- return glm.lookAt(self.position, self.position + self.forward, self.up)
68
-
69
- def get_projection_matrix(self) -> glm.mat4x4:
70
- return glm.perspective(glm.radians(self.fov), self.aspect_ratio, NEAR, FAR)
71
-
72
- def get_params(self) -> tuple:
73
- return self.engine, self.position, self.yaw, self.pitch
74
-
75
- def look_at(self, other) -> None:
76
- forward = glm.normalize(other.position - self.position)
77
- self.yaw = np.degrees(np.arctan2(forward.z, forward.x))
78
- self.pitch = np.degrees(np.arctan2(forward.y, np.sqrt(forward.x ** 2 + forward.z ** 2)))
79
-
80
- def __repr__(self):
81
- return f'<Basilisk Camera | Position: {self.position}, Direction: {self.forward}>'
82
-
83
- @property
84
- def scene(self): return self._scene
85
- @property
86
- def position(self): return self._position
87
- @property
88
- def rotation(self) -> glm.quat: return self._rotation
89
- @property
90
- def direction(self): return self.rotation * (0, 0, -1)
91
- @property
92
- def forward(self): return self.rotation * (0, 0, -1)
93
- @property
94
- def pitch(self): return glm.pitch(self.rotation)
95
- @property
96
- def yaw(self): return glm.yaw(self.rotation)
97
- @property
98
- def roll(self): return glm.roll(self.rotation)
99
- @property
100
- def UP(self):
101
- up = (self.rotation.x, self.rotation.y, self.rotation.z)
102
- up = (0, 1, 0) # TODO ensure that this works with all up vectors
103
- return glm.normalize(up) if glm.length2(up) > 1e-7 else glm.vec3(0, 1, 0)
104
- @property
105
- def right(self): return glm.normalize(glm.cross(self.forward, self.UP))
106
- @property
107
- def up(self): return glm.normalize(glm.cross(self.right, self.forward))
108
- @property
109
- def horizontal(self): return glm.normalize(glm.cross(self.UP, self.right))
110
- @property
111
- def fov(self): return self._fov
112
-
113
- @scene.setter
114
- def scene(self, value):
115
- if value == None: return
116
- self._scene = value
117
- self.engine = self._scene.engine
118
- self.use()
119
-
120
- @position.setter
121
- def position(self, value: tuple | list | glm.vec3 | np.ndarray | Vec3):
122
- if isinstance(value, glm.vec3): self._position = glm.vec3(value)
123
- elif isinstance(value, Vec3): self._position = glm.vec3(value.data)
124
- elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
125
- if len(value) != 3: raise ValueError(f'Camera: Invalid number of values for position. Expected 3, got {len(value)}')
126
- self._position = glm.vec3(value)
127
- else: raise TypeError(f'Camera: Invalid position value type {type(value)}')
128
-
129
- @rotation.setter
130
- def rotation(self, value):
131
- if isinstance(value, (glm.vec3, glm.quat)): self._rotation = glm.quat(value)
132
- elif isinstance(value, (Vec3, Quat)): self._rotation = glm.quat(value.data)
133
- elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
134
- if not (2 < len(value) < 5): raise ValueError(f'Camera: Invalid number of values for rotation. Expected 3 or 4, got {len(value)}')
135
- self._position = glm.quat(value)
136
- else:
137
- try:
138
- self._rotation = glm.quat(value)
139
- except:
140
- raise TypeError(f'Camera: Invalid rotation value type {type(value)}')
141
-
142
- @direction.setter
143
- def direction(self, value: tuple | list | glm.vec3 | np.ndarray | Vec3):
144
- if isinstance(value, glm.vec3): self.forward = glm.normalize(value)
145
- elif isinstance(value, Vec3): self.forward = glm.normalize(value.data)
146
- elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
147
- if len(value) != 3: raise ValueError(f'Camera: Invalid number of values for direction. Expected 3, got {len(value)}')
148
- self.forward = glm.normalize(value)
149
- else: raise TypeError(f'Camera: Invalid direction value type {type(value)}')
150
-
151
- @forward.setter
152
- def forward(self, value):
153
- self._rotation = glm.quatLookAt(value, self.UP)
154
-
155
- @pitch.setter
156
- def pitch(self, value):
157
- self._rotation = glm.quat((value, self.yaw, self.roll))
158
-
159
- @yaw.setter
160
- def yaw(self, value):
161
- self._rotation = glm.quat((self.pitch, value, self.roll))
162
-
163
- @roll.setter
164
- def roll(self, value):
165
- self._rotation = glm.quat((self.pitch, self.yaw, value))
166
-
167
- @UP.setter
168
- def UP(self, value):
169
- self._rotation = glm.quatLookAt(self.forward, value)
170
-
171
- @fov.setter
172
- def fov(self, value):
173
- self._fov = value
174
- if self.engine: self.use()
175
-
176
-
177
- class FreeCamera(Camera):
178
- def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0)):
179
- super().__init__(position, rotation)
180
-
181
- def update(self) -> None:
182
- """
183
- Updates the camera position and rotaiton based on user input
184
- """
185
-
186
- self.move()
187
- self.rotate()
188
- # self.update_camera_vectors()
189
- self.m_view = self.get_view_matrix()
190
-
191
- def rotate(self) -> None:
192
- """
193
- Rotates the camera based on the amount of mouse movement.
194
- """
195
- rel_x, rel_y = self.engine.mouse.relative
196
-
197
- yaw_rotation = glm.angleAxis(self.sensitivity / 1000 * rel_x, -self.UP)
198
- pitch_rotation = glm.angleAxis(self.sensitivity / 1000 * rel_y, -self.right)
199
- new_rotation = yaw_rotation * pitch_rotation * self.rotation
200
-
201
- v_new = new_rotation * self.UP
202
- pitch_angle = glm.degrees(glm.acos(glm.clamp(glm.dot(v_new, self.UP), -1.0, 1.0)))
203
- self.rotation = new_rotation if pitch_angle < 89 else yaw_rotation * self.rotation
204
-
205
- def move(self) -> None:
206
- """
207
- Checks for button presses and updates vectors accordingly.
208
- """
209
- velocity = (self.speed + self.engine.keys[pg.K_CAPSLOCK] * 10) * self.engine.delta_time
210
- keys = self.engine.keys
211
- if keys[pg.K_w]:
212
- self.position += glm.normalize(glm.vec3(self.forward.x, 0, self.forward.z)) * velocity
213
- if keys[pg.K_s]:
214
- self.position -= glm.normalize(glm.vec3(self.forward.x, 0, self.forward.z)) * velocity
215
- if keys[pg.K_a]:
216
- self.position -= self.right * velocity
217
- if keys[pg.K_d]:
218
- self.position += self.right * velocity
219
- if keys[pg.K_SPACE]:
220
- self.position += self.UP * velocity
221
- if keys[pg.K_LSHIFT]:
222
- self.position -= self.UP * velocity
223
-
224
- class FixedCamera(FreeCamera):
225
- def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0)):
226
- super().__init__(position, rotation)
227
-
228
- def move(self): pass
229
-
230
- class FollowCamera(FreeCamera):
231
- def __init__(self, parent, position=(0, 0, 20), rotation=(1, 0, 0, 0), offset=(0, 0, 0)):
232
- super().__init__(position, rotation)
233
- self.parent = parent
234
- self.offest = glm.vec3(offset)
235
-
236
- def move(self) -> None:
237
- """
238
- Moves the camera to the parent node
239
- """
240
-
241
- self.position = self.parent.position + self.offest
242
-
243
- class OrbitCamera(FreeCamera):
244
- def __init__(self, parent, position=(0, 0, 20), rotation=(1, 0, 0, 0), distance=5, offset=(0, 0)):
245
- self.parent = parent
246
- self.distance = distance
247
- self.offset = glm.vec2(offset)
248
- super().__init__(position, rotation)
249
-
250
- def get_view_matrix(self) -> glm.mat4x4:
251
- return glm.lookAt(self.position, self.parent.position, self.up)
252
-
253
- def move(self) -> None:
254
- """
255
- Moves the camera to the parent node
256
- """
257
- self.position = self.parent.position - glm.normalize(self.forward) * self.distance
258
-
259
- class StaticCamera(Camera):
260
- def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0)):
1
+ import pygame as pg
2
+ import glm
3
+ import numpy as np
4
+ from ..generic.vec3 import Vec3
5
+ from ..generic.quat import Quat
6
+
7
+
8
+ # Camera view constants
9
+ FOV = 50 # Degrees
10
+ NEAR = 0.1
11
+ FAR = 350
12
+
13
+ class Camera:
14
+ engine: ...
15
+ """Back reference to the parent engine"""
16
+ scene: ...
17
+ """Back reference to the parent scene"""
18
+ aspect_ratio: float
19
+ """Aspect ratio of the engine window"""
20
+ position: glm.vec3
21
+ """Location of the camera (maters)"""
22
+ speed: float
23
+ """The speed that the camera moves in space"""
24
+ sensitivity: float
25
+ """The speed at which the camera turns"""
26
+
27
+ def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0), speed: float=10, sensitivity: float=2) -> None:
28
+ """
29
+ Camera object to get view and projection matricies. Movement built in
30
+ """
31
+
32
+ # Back references
33
+ self.scene = None
34
+ self.engine = None
35
+ # transformations
36
+ self.rotation = glm.quat(rotation)
37
+ self.position = glm.vec3(position)
38
+ # fov
39
+ self.fov = 50
40
+ # The initial aspect ratio of the screen
41
+ self.aspect_ratio = 1.0
42
+ # View matrix
43
+ self.m_view = self.get_view_matrix()
44
+ # Projection matrix
45
+ self.m_proj = self.get_projection_matrix()
46
+ # Movement attributes
47
+ self.speed = speed
48
+ self.sensitivity = sensitivity
49
+
50
+ def update(self) -> None:
51
+ """
52
+ Updates the camera view matrix
53
+ """
54
+
55
+ # self.update_camera_vectors()
56
+ self.m_view = self.get_view_matrix()
57
+
58
+ def use(self):
59
+ # Updated aspect ratio of the screen
60
+ self.aspect_ratio = self.engine.win_size[0] / self.engine.win_size[1]
61
+ # View matrix
62
+ self.m_view = self.get_view_matrix()
63
+ # Projection matrix
64
+ self.m_proj = self.get_projection_matrix()
65
+
66
+ def get_view_matrix(self) -> glm.mat4x4:
67
+ return glm.lookAt(self.position, self.position + self.forward, self.up)
68
+
69
+ def get_projection_matrix(self) -> glm.mat4x4:
70
+ return glm.perspective(glm.radians(self.fov), self.aspect_ratio, NEAR, FAR)
71
+
72
+ def get_params(self) -> tuple:
73
+ return self.engine, self.position, self.yaw, self.pitch
74
+
75
+ def look_at(self, other) -> None:
76
+ forward = glm.normalize(other.position - self.position)
77
+ self.yaw = np.degrees(np.arctan2(forward.z, forward.x))
78
+ self.pitch = np.degrees(np.arctan2(forward.y, np.sqrt(forward.x ** 2 + forward.z ** 2)))
79
+
80
+ def __repr__(self):
81
+ return f'<Basilisk Camera | Position: {self.position}, Direction: {self.forward}>'
82
+
83
+ @property
84
+ def scene(self): return self._scene
85
+ @property
86
+ def position(self): return self._position
87
+ @property
88
+ def rotation(self) -> glm.quat: return self._rotation
89
+ @property
90
+ def direction(self): return self.rotation * (0, 0, -1)
91
+ @property
92
+ def forward(self): return self.rotation * (0, 0, -1)
93
+ @property
94
+ def pitch(self): return glm.pitch(self.rotation)
95
+ @property
96
+ def yaw(self): return glm.yaw(self.rotation)
97
+ @property
98
+ def roll(self): return glm.roll(self.rotation)
99
+ @property
100
+ def UP(self):
101
+ up = (self.rotation.x, self.rotation.y, self.rotation.z)
102
+ up = (0, 1, 0) # TODO ensure that this works with all up vectors
103
+ return glm.normalize(up) if glm.length2(up) > 1e-7 else glm.vec3(0, 1, 0)
104
+ @property
105
+ def right(self): return glm.normalize(glm.cross(self.forward, self.UP))
106
+ @property
107
+ def up(self): return glm.normalize(glm.cross(self.right, self.forward))
108
+ @property
109
+ def horizontal(self): return glm.normalize(glm.cross(self.UP, self.right))
110
+ @property
111
+ def fov(self): return self._fov
112
+
113
+ @scene.setter
114
+ def scene(self, value):
115
+ if value == None: return
116
+ self._scene = value
117
+ self.engine = self._scene.engine
118
+ self.use()
119
+
120
+ @position.setter
121
+ def position(self, value: tuple | list | glm.vec3 | np.ndarray | Vec3):
122
+ if isinstance(value, glm.vec3): self._position = glm.vec3(value)
123
+ elif isinstance(value, Vec3): self._position = glm.vec3(value.data)
124
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
125
+ if len(value) != 3: raise ValueError(f'Camera: Invalid number of values for position. Expected 3, got {len(value)}')
126
+ self._position = glm.vec3(value)
127
+ else: raise TypeError(f'Camera: Invalid position value type {type(value)}')
128
+
129
+ @rotation.setter
130
+ def rotation(self, value):
131
+ if isinstance(value, (glm.vec3, glm.quat)): self._rotation = glm.quat(value)
132
+ elif isinstance(value, (Vec3, Quat)): self._rotation = glm.quat(value.data)
133
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
134
+ if not (2 < len(value) < 5): raise ValueError(f'Camera: Invalid number of values for rotation. Expected 3 or 4, got {len(value)}')
135
+ self._position = glm.quat(value)
136
+ else:
137
+ try:
138
+ self._rotation = glm.quat(value)
139
+ except:
140
+ raise TypeError(f'Camera: Invalid rotation value type {type(value)}')
141
+
142
+ @direction.setter
143
+ def direction(self, value: tuple | list | glm.vec3 | np.ndarray | Vec3):
144
+ if isinstance(value, glm.vec3): self.forward = glm.normalize(value)
145
+ elif isinstance(value, Vec3): self.forward = glm.normalize(value.data)
146
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
147
+ if len(value) != 3: raise ValueError(f'Camera: Invalid number of values for direction. Expected 3, got {len(value)}')
148
+ self.forward = glm.normalize(value)
149
+ else: raise TypeError(f'Camera: Invalid direction value type {type(value)}')
150
+
151
+ @forward.setter
152
+ def forward(self, value):
153
+ self._rotation = glm.quatLookAt(value, self.UP)
154
+
155
+ @pitch.setter
156
+ def pitch(self, value):
157
+ self._rotation = glm.quat((value, self.yaw, self.roll))
158
+
159
+ @yaw.setter
160
+ def yaw(self, value):
161
+ self._rotation = glm.quat((self.pitch, value, self.roll))
162
+
163
+ @roll.setter
164
+ def roll(self, value):
165
+ self._rotation = glm.quat((self.pitch, self.yaw, value))
166
+
167
+ @UP.setter
168
+ def UP(self, value):
169
+ self._rotation = glm.quatLookAt(self.forward, value)
170
+
171
+ @fov.setter
172
+ def fov(self, value):
173
+ self._fov = value
174
+ if self.engine: self.use()
175
+
176
+
177
+ class FreeCamera(Camera):
178
+ def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0)):
179
+ super().__init__(position, rotation)
180
+
181
+ def update(self) -> None:
182
+ """
183
+ Updates the camera position and rotaiton based on user input
184
+ """
185
+
186
+ self.move()
187
+ self.rotate()
188
+ # self.update_camera_vectors()
189
+ self.m_view = self.get_view_matrix()
190
+
191
+ def rotate(self) -> None:
192
+ """
193
+ Rotates the camera based on the amount of mouse movement.
194
+ """
195
+ rel_x, rel_y = self.engine.mouse.relative
196
+
197
+ yaw_rotation = glm.angleAxis(self.sensitivity / 1000 * rel_x, -self.UP)
198
+ pitch_rotation = glm.angleAxis(self.sensitivity / 1000 * rel_y, -self.right)
199
+ new_rotation = yaw_rotation * pitch_rotation * self.rotation
200
+
201
+ v_new = new_rotation * self.UP
202
+ pitch_angle = glm.degrees(glm.acos(glm.clamp(glm.dot(v_new, self.UP), -1.0, 1.0)))
203
+ self.rotation = new_rotation if pitch_angle < 89 else yaw_rotation * self.rotation
204
+
205
+ def move(self) -> None:
206
+ """
207
+ Checks for button presses and updates vectors accordingly.
208
+ """
209
+ velocity = (self.speed + self.engine.keys[pg.K_CAPSLOCK] * 10) * self.engine.delta_time
210
+ keys = self.engine.keys
211
+ if keys[pg.K_w]:
212
+ self.position += glm.normalize(glm.vec3(self.forward.x, 0, self.forward.z)) * velocity
213
+ if keys[pg.K_s]:
214
+ self.position -= glm.normalize(glm.vec3(self.forward.x, 0, self.forward.z)) * velocity
215
+ if keys[pg.K_a]:
216
+ self.position -= self.right * velocity
217
+ if keys[pg.K_d]:
218
+ self.position += self.right * velocity
219
+ if keys[pg.K_SPACE]:
220
+ self.position += self.UP * velocity
221
+ if keys[pg.K_LSHIFT]:
222
+ self.position -= self.UP * velocity
223
+
224
+ class FixedCamera(FreeCamera):
225
+ def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0)):
226
+ super().__init__(position, rotation)
227
+
228
+ def move(self): pass
229
+
230
+ class FollowCamera(FreeCamera):
231
+ def __init__(self, parent, position=(0, 0, 20), rotation=(1, 0, 0, 0), offset=(0, 0, 0)):
232
+ super().__init__(position, rotation)
233
+ self.parent = parent
234
+ self.offest = glm.vec3(offset)
235
+
236
+ def move(self) -> None:
237
+ """
238
+ Moves the camera to the parent node
239
+ """
240
+
241
+ self.position = self.parent.position + self.offest
242
+
243
+ class OrbitCamera(FreeCamera):
244
+ def __init__(self, parent, position=(0, 0, 20), rotation=(1, 0, 0, 0), distance=5, offset=(0, 0)):
245
+ self.parent = parent
246
+ self.distance = distance
247
+ self.offset = glm.vec2(offset)
248
+ super().__init__(position, rotation)
249
+
250
+ def get_view_matrix(self) -> glm.mat4x4:
251
+ return glm.lookAt(self.position, self.parent.position, self.up)
252
+
253
+ def move(self) -> None:
254
+ """
255
+ Moves the camera to the parent node
256
+ """
257
+ self.position = self.parent.position - glm.normalize(self.forward) * self.distance
258
+
259
+ class StaticCamera(Camera):
260
+ def __init__(self, position=(0, 0, 20), rotation=(1, 0, 0, 0)):
261
261
  super().__init__(position, rotation)