basilisk-engine 0.0.1__py3-none-any.whl → 0.0.3__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 (62) hide show
  1. basilisk/bsk_assets/__init__.py +0 -0
  2. basilisk/collisions/__init__.py +0 -0
  3. basilisk/collisions/broad/__init__.py +0 -0
  4. basilisk/collisions/broad/broad_aabb.py +96 -0
  5. basilisk/collisions/broad/broad_bvh.py +102 -0
  6. basilisk/collisions/collider.py +75 -0
  7. basilisk/collisions/collider_handler.py +163 -0
  8. basilisk/collisions/narrow/__init__.py +0 -0
  9. basilisk/collisions/narrow/epa.py +86 -0
  10. basilisk/collisions/narrow/gjk.py +66 -0
  11. basilisk/collisions/narrow/helper.py +23 -0
  12. basilisk/draw/__init__.py +0 -0
  13. basilisk/draw/draw.py +101 -0
  14. basilisk/draw/draw_handler.py +208 -0
  15. basilisk/draw/font_renderer.py +28 -0
  16. basilisk/generic/__init__.py +0 -0
  17. basilisk/generic/abstract_bvh.py +16 -0
  18. basilisk/generic/collisions.py +26 -0
  19. basilisk/generic/input_validation.py +28 -0
  20. basilisk/generic/math.py +7 -0
  21. basilisk/generic/matrices.py +34 -0
  22. basilisk/generic/meshes.py +73 -0
  23. basilisk/generic/quat.py +119 -0
  24. basilisk/generic/quat_methods.py +8 -0
  25. basilisk/generic/vec3.py +112 -0
  26. basilisk/input/__init__.py +0 -0
  27. basilisk/input/mouse.py +60 -0
  28. basilisk/mesh/__init__.py +0 -0
  29. basilisk/mesh/built-in/__init__.py +0 -0
  30. basilisk/mesh/cube.py +20 -0
  31. basilisk/mesh/mesh.py +216 -0
  32. basilisk/mesh/mesh_from_data.py +48 -0
  33. basilisk/mesh/model.py +272 -0
  34. basilisk/mesh/narrow_aabb.py +81 -0
  35. basilisk/mesh/narrow_bvh.py +84 -0
  36. basilisk/mesh/narrow_primative.py +24 -0
  37. basilisk/nodes/__init__.py +0 -0
  38. basilisk/nodes/node.py +508 -0
  39. basilisk/nodes/node_handler.py +94 -0
  40. basilisk/physics/__init__.py +0 -0
  41. basilisk/physics/physics_body.py +36 -0
  42. basilisk/physics/physics_engine.py +37 -0
  43. basilisk/render/__init__.py +0 -0
  44. basilisk/render/batch.py +85 -0
  45. basilisk/render/camera.py +166 -0
  46. basilisk/render/chunk.py +85 -0
  47. basilisk/render/chunk_handler.py +139 -0
  48. basilisk/render/frame.py +182 -0
  49. basilisk/render/image.py +76 -0
  50. basilisk/render/image_handler.py +119 -0
  51. basilisk/render/light.py +97 -0
  52. basilisk/render/light_handler.py +54 -0
  53. basilisk/render/material.py +196 -0
  54. basilisk/render/material_handler.py +123 -0
  55. basilisk/render/shader_handler.py +95 -0
  56. basilisk/render/sky.py +118 -0
  57. basilisk/shaders/__init__.py +0 -0
  58. {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.3.dist-info}/METADATA +1 -1
  59. basilisk_engine-0.0.3.dist-info/RECORD +65 -0
  60. basilisk_engine-0.0.1.dist-info/RECORD +0 -8
  61. {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.3.dist-info}/WHEEL +0 -0
  62. {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.3.dist-info}/top_level.txt +0 -0
basilisk/draw/draw.py ADDED
@@ -0,0 +1,101 @@
1
+ from ..engine import Engine
2
+ from ..render.image import Image
3
+
4
+ def rect(engine: Engine, color: tuple, rect: tuple) -> None:
5
+ """
6
+ Draws a rectagle to the screen
7
+ Args:
8
+ engine: bsk.Engine
9
+ The destination engine for the rectangle
10
+ color: tuple(r, g, b) | tuple(r, g, b, a)
11
+ The color value of the rectangle, with int components in range [0, 255]
12
+ rect: tuple(x, y, w, h)
13
+ The screen position and size of the rectangle given in pixels
14
+ """
15
+
16
+ # Get the draw handler from the engine
17
+ draw_handler = engine.scene.draw_handler
18
+ if not draw_handler: return
19
+
20
+ # Draw the rect
21
+ draw_handler.draw_rect(color, rect)
22
+
23
+
24
+ def circle(engine: Engine, color: tuple, center: tuple, radius: int, resolution: int=20, outer_color: tuple=None) -> None:
25
+ """
26
+ Draws a rect between centered on x, y with width and height
27
+ Args:
28
+ color: tuple(r, g, b) | tuple(r, g, b, a)
29
+ The color value of the circle, with int components in range [0, 255]
30
+ center: tuple (x: float, y: float)
31
+ Center of the circle, given in pixels
32
+ radius: float
33
+ Radius of the circle, given in pixels
34
+ resolution: float
35
+ The number of triangles used to approximate the circle
36
+ """
37
+
38
+ # Get the draw handler from the engine
39
+ draw_handler = engine.scene.draw_handler
40
+ if not draw_handler: return
41
+
42
+ # Draw the circle
43
+ draw_handler.draw_circle(color, center, radius, resolution, outer_color)
44
+
45
+ def line(engine: Engine, color: tuple, p1: tuple, p2: tuple, thickness: int=1) -> None:
46
+ """
47
+ Draws a line between two points
48
+ Args:
49
+ color: tuple=(r, g, b) | tuple=(r, g, b, a)
50
+ Color of the line
51
+ p1: tuple=((x1, y1), (x2, y2))
52
+ Starting point of the line. Given in pixels
53
+ p1: tuple=((x1, y1), (x2, y2))
54
+ Starting point of the line. Given in pixels
55
+ thickness: int
56
+ Size of the line on either side. pixels
57
+ """
58
+
59
+ # Get the draw handler from the engine
60
+ draw_handler = engine.scene.draw_handler
61
+ if not draw_handler: return
62
+
63
+ # Draw the line
64
+ draw_handler.draw_line(color, p1, p2, thickness)
65
+
66
+ def blit(engine: Engine, image: Image, rect: tuple):
67
+ """
68
+ Blits a basilisk image to the engine screen.
69
+ Args:
70
+ image: bsk.Image
71
+ The image to display on the screen
72
+ rect: tuple(x, y, w, h)
73
+ The screen position and size of the image given in pixels
74
+ """
75
+
76
+ # Get the draw handler from the engine
77
+ draw_handler = engine.scene.draw_handler
78
+ if not draw_handler: return
79
+
80
+ engine.scene.material_handler.image_handler.add(image)
81
+
82
+ # Blit the image
83
+ draw_handler.blit(image, rect)
84
+
85
+ def text(engine: Engine, text: str, position: tuple, scale: float=1.0):
86
+ """
87
+ Renders text do the screen
88
+ USE SPARINGLY, INEFFICIENT IMPLAMENTATION
89
+ """
90
+
91
+ font_renderer = engine.scene.draw_handler.font_renderer
92
+
93
+ # Render the text if it has not been cached
94
+ if text not in font_renderer.text_renders:
95
+ surf = font_renderer.render(text).convert_alpha()
96
+ text_image = Image(surf)
97
+ font_renderer.text_renders[text] = (text_image, surf.get_rect())
98
+
99
+ # Blit the text image
100
+ img, rect = font_renderer.text_renders[text]
101
+ blit(engine, img, (position[0] - rect[2] * scale / 2, position[1] - rect[3] * scale / 2, rect[2] * scale, rect[3] * scale))
@@ -0,0 +1,208 @@
1
+ import moderngl as mgl
2
+ import numpy as np
3
+ import glm
4
+ from math import cos, sin, atan2
5
+ from ..render.image import Image
6
+ from .font_renderer import FontRenderer
7
+
8
+ class DrawHandler():
9
+ engine: ...
10
+ """Back reference to the parent engine"""
11
+ scene: ...
12
+ """Back reference to the parent scene"""
13
+ ctx: mgl.Context
14
+ """Back reference to the parent context"""
15
+ program: mgl.Program
16
+ """2D draw program"""
17
+ draw_data: list[float]
18
+ """Temporary buffer for user draw calls"""
19
+ vbo: mgl.Buffer
20
+ """Buffer for all 2D draws"""
21
+ vao: mgl.VertexArray
22
+ """VAO for rendering all 2D draw calls"""
23
+
24
+ def __init__(self, scene) -> None:
25
+ # Back references
26
+ self.scene = scene
27
+ self.engine = scene.engine
28
+ self.ctx = scene.engine.ctx
29
+
30
+ # Get the program
31
+ self.program = self.scene.shader_handler.programs['draw']
32
+
33
+ # Initialize draw data as blank
34
+ self.draw_data = []
35
+ self.vbo = None
36
+ self.vao = None
37
+
38
+ self.font_renderer = FontRenderer(self.engine.root)
39
+
40
+ def render(self) -> None:
41
+ """
42
+ Renders all draw calls from the user since the last frame
43
+ """
44
+
45
+ if not self.draw_data: return
46
+
47
+ # Reverse the draw order, and convert to C-like array
48
+ self.draw_data.reverse()
49
+ data = np.array(self.draw_data, dtype='f4')
50
+ ratio = np.array([2 / self.engine.win_size[0], 2 / self.engine.win_size[1]])
51
+ data[:,:2] = data[:,:2] * ratio - 1
52
+
53
+ # Create buffer and VAO
54
+ self.vbo = self.ctx.buffer(data)
55
+ self.vao = self.ctx.vertex_array(self.program, [(self.vbo, '2f 4f 1i', *['in_position', 'in_color', 'in_uses_image'])], skip_errors=True)
56
+
57
+ # Render the VAO
58
+ self.ctx.enable(mgl.BLEND)
59
+ self.ctx.blend_equation = mgl.ADDITIVE_BLENDING
60
+ self.vao.render()
61
+
62
+ # Clera the draw data
63
+ self.vbo.release()
64
+ self.vao.release()
65
+ self.vbo = None
66
+ self.vao = None
67
+ self.draw_data.clear()
68
+
69
+ def draw_rect(self, color: tuple, rect: tuple) -> None:
70
+ """
71
+ Draws a rect to the screen
72
+ """
73
+
74
+ color = validate_color(color)
75
+ rect = validate_rect(rect)
76
+
77
+ p1 = (rect[0] , rect[1] )
78
+ p2 = (rect[0] , rect[1] + rect[3])
79
+ p3 = (rect[0] + rect[2], rect[1] )
80
+ p4 = (rect[0] + rect[2], rect[1] + rect[3])
81
+
82
+ v1 = (*p1, *color, 0)
83
+ v2 = (*p2, *color, 0)
84
+ v3 = (*p3, *color, 0)
85
+ v4 = (*p4, *color, 0)
86
+
87
+ self.draw_data.extend([
88
+ v1, v3, v2,
89
+ v2, v3, v4
90
+ ])
91
+
92
+ def draw_circle(self, color: tuple, center: tuple, radius: int, resolution: int=20, outer_color: tuple=None) -> None:
93
+ """
94
+ Draws a rect between centered on x, y with width and height
95
+ Args:
96
+ color: tuple(r, g, b) | tuple(r, g, b, a)
97
+ The color value of the circle, with int components in range [0, 255]
98
+ center: tuple (x: float, y: float)
99
+ Center of the circle, given in pixels
100
+ radius: float
101
+ Radius of the circle, given in pixels
102
+ resolution: float
103
+ The number of triangles used to approximate the circle
104
+ """
105
+
106
+ if not outer_color: outer_color = color
107
+ color = validate_color(color)
108
+ outer_color = validate_color(outer_color)
109
+ p1 = validate_point(center)
110
+
111
+ v1 = (*p1, *color, 0)
112
+ theta = 0
113
+ delta_theta = (2 * 3.1415) / resolution
114
+
115
+ for triangle in range(resolution):
116
+ v2 = (center[0] + radius * cos(theta), center[1] + radius * sin(theta), *outer_color, 0)
117
+ theta += delta_theta
118
+ v3 = (center[0] + radius * cos(theta), center[1] + radius * sin(theta), *outer_color, 0)
119
+ self.draw_data.extend([v1, v2, v3])
120
+
121
+ def draw_line(self, color: tuple, p1: tuple, p2: tuple, thickness: int=1):
122
+ """
123
+ Draws a line between two points
124
+ Args:
125
+ color: tuple=(r, g, b) | tuple=(r, g, b, a)
126
+ Color of the line
127
+ p1: tuple=((x1, y1), (x2, y2))
128
+ Starting point of the line. Given in pixels
129
+ p1: tuple=((x1, y1), (x2, y2))
130
+ Starting point of the line. Given in pixels
131
+ thickness: int
132
+ Size of the line on either side. pixels
133
+ """
134
+
135
+ color = validate_color(color)
136
+
137
+ p1 = glm.vec2(validate_point(p1))
138
+ p2 = glm.vec2(validate_point(p2))
139
+
140
+ thickness /= 2
141
+
142
+ unit = glm.normalize(p1 - p2) * thickness
143
+ theta = atan2(*unit)
144
+ perp_vector = glm.vec2(cos(-theta), sin(-theta)) * thickness
145
+
146
+ v1 = (*(p1 - perp_vector), *color, 0)
147
+ v2 = (*(p1 + perp_vector), *color, 0)
148
+ v3 = (*(p2 - perp_vector), *color, 0)
149
+ v4 = (*(p2 + perp_vector), *color, 0)
150
+
151
+ self.draw_data.extend([v1, v3, v4, v2, v1, v4])
152
+
153
+ def blit(self, image: Image, rect: tuple):
154
+ rect = validate_rect(rect)
155
+
156
+ p1 = (rect[0] , rect[1] )
157
+ p2 = (rect[0] , rect[1] + rect[3])
158
+ p3 = (rect[0] + rect[2], rect[1] )
159
+ p4 = (rect[0] + rect[2], rect[1] + rect[3])
160
+
161
+ v1 = (*p1, *image.index, 0, 0, 1)
162
+ v2 = (*p2, *image.index, 0, 1, 1)
163
+ v3 = (*p3, *image.index, 1, 0, 1)
164
+ v4 = (*p4, *image.index, 1, 1, 1)
165
+
166
+ self.draw_data.extend([
167
+ v1, v3, v2,
168
+ v2, v3, v4
169
+ ])
170
+
171
+ def __del__(self) -> None:
172
+ """
173
+ Releases any allocated data
174
+ """
175
+
176
+ if self.vbo: self.vbo.release()
177
+ if self.vao: self.vao.release()
178
+
179
+
180
+ def validate_color(color):
181
+ if not (isinstance(color, tuple) or isinstance(color, list) or isinstance(color, np.ndarray)):
182
+ raise TypeError(f'Invalid color type: {type(color)}. Expected a tuple, list, or numpy array')
183
+ if len(color) == 4:
184
+ color = (color[0] / 255.0, color[1] / 255.0, color[2] / 255.0, color[3] / 255.0)
185
+ elif len(color) == 3:
186
+ color = (color[0] / 255.0, color[1] / 255.0, color[2] / 255.0, 1.0)
187
+ else:
188
+ raise TypeError(f'Invalid number of color values. Expected 3 or 4 values, got {len(color)}')
189
+ return color
190
+
191
+ def validate_rect(rect):
192
+ if not (isinstance(rect, tuple) or isinstance(rect, list) or isinstance(rect, np.ndarray)):
193
+ raise TypeError(f'Invalid rect type: {type(rect)}. Expected a tuple, list, or numpy array')
194
+ if len(rect) != 4:
195
+ raise TypeError(f'Invalid number of rect values. Expected 4 values, got {len(rect)}')
196
+ return list(rect)
197
+
198
+ def validate_point(point):
199
+ if not (isinstance(point, tuple) or isinstance(point, list) or isinstance(point, np.ndarray)):
200
+ raise TypeError(f'Invalid rect type: {type(point)}. Expected a tuple, list, or numpy array')
201
+ if len(point) != 2:
202
+ raise TypeError(f'Invalid number of rect values. Expected 2 values, got {len(point)}')
203
+ return list(point)
204
+
205
+ def validate_image(image):
206
+ if not (isinstance(image, Image)):
207
+ raise TypeError(f'Invalid imgae type: {type(image)}. Expected a bask.Image.')
208
+ return image
@@ -0,0 +1,28 @@
1
+ import pygame as pg
2
+
3
+ class FontRenderer():
4
+ def __init__(self, root):
5
+ pg.font.init()
6
+ self.font = pg.font.Font(root + '/draw/Roboto-Regular.ttf', 48)
7
+ self.text_renders = {}
8
+
9
+ def render(self, text, color=(255, 255, 255), bold=False, underline=False, italic=False):
10
+ '''
11
+ Renders any font which has been loaded to the class instance.
12
+ Args:
13
+ text::str
14
+ Text to be rendered
15
+ color::(int, int, int) =(255, 255, 255)
16
+ The RGB value of the text
17
+ bold::bool (=False)
18
+ Specifies if the text should be rendered in bolded font
19
+ underline::bool (=False)
20
+ Specifies if the text should be underlined in bolded font
21
+ italic::bool (=False)
22
+ Specifies if the text should be rendered in italicized font
23
+ '''
24
+ self.font.set_bold(bold)
25
+ self.font.set_underline(underline)
26
+ self.font.set_italic(italic)
27
+
28
+ return self.font.render(text, True, color, (0, 0, 0, 0))
File without changes
@@ -0,0 +1,16 @@
1
+ import glm
2
+
3
+
4
+ class AbstractAABB():
5
+ top_right: glm.vec3
6
+ """The furthest positive corner of the AABB"""
7
+ bottom_left: glm.vec3
8
+ """The furthest negative corner of the AABB"""
9
+ a: ...
10
+ """Binary child 1"""
11
+ b: ...
12
+ """Binary child 2"""
13
+
14
+ class AbstractBVH():
15
+ root: AbstractAABB
16
+ """Root aabb used for the start of all collisions"""
@@ -0,0 +1,26 @@
1
+ import glm
2
+
3
+
4
+ def collide_aabb_aabb(top_right1: glm.vec3, bottom_left1: glm.vec3, top_right2: glm.vec3, bottom_left2: glm.vec3, epsilon:float=1e-7) -> bool:
5
+ """
6
+ Determines if two aabbs are colliding
7
+ """
8
+ return all(bottom_left1[i] <= top_right2[i] + epsilon and epsilon + top_right1[i] >= bottom_left2[i] for i in range(3))
9
+
10
+ def get_sat_axes(rotation1: glm.quat, rotation2: glm.quat) -> list[glm.vec3]:
11
+ """
12
+ Gets the axes for SAT from obb rotation matrices
13
+ """
14
+ axes = []
15
+ axes.extend(glm.transpose(glm.mat3_cast(rotation1)))
16
+ axes.extend(glm.transpose(glm.mat3_cast(rotation2)))
17
+ # axes.extend(glm.mat3_cast(rotation1))
18
+ # axes.extend(glm.mat3_cast(rotation2))
19
+
20
+ # crossed roots
21
+ for i in range(0, 3):
22
+ for j in range(3, 6):
23
+ cross = glm.cross(axes[i], axes[j])
24
+ axes.append(glm.normalize(cross))
25
+
26
+ return axes
@@ -0,0 +1,28 @@
1
+ import numpy as np
2
+ import glm
3
+ from ..render.image import Image
4
+
5
+
6
+ def validate_float(module: str, name: str, value: float | int | glm.float32) -> float:
7
+ if isinstance(value, float) or isinstance(value, int):
8
+ return float(value)
9
+ elif isinstance(value, glm.float32):
10
+ return float(value.value)
11
+ else:
12
+ raise TypeError(f"{module}: Invalid {name} value type {type(value)}. Expected float.")
13
+
14
+ def validate_glm_vec3(module: str, name: str, value: tuple | list | glm.vec3 | np.ndarray) -> glm.vec3:
15
+ if isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
16
+ if len(value) != 3: raise ValueError(f"{module}: Invalid number of values for {name}. Expected 3 values, got {len(value)} values")
17
+ return glm.vec3(value)
18
+ elif isinstance(value, glm.vec3):
19
+ return glm.vec3(value)
20
+ else:
21
+ raise TypeError(f"{module}: Invalid {name} value type {type(value)}. Expected glm.vec3")
22
+
23
+ def validate_image(module: str, name: str, value: Image | None) -> Image | None:
24
+ """Accepts none as a value for no image"""
25
+ if isinstance(value, Image) or isinstance(value, type(None)):
26
+ return value
27
+ else:
28
+ raise TypeError(f"{module}: Invalid {name} value type {type(value)}. Expected bsk.Image or None")
@@ -0,0 +1,7 @@
1
+ import glm
2
+
3
+ def triple_product(vector1, vector2, vector3) -> glm.vec3:
4
+ """
5
+ Computes (1 x 2) x 3
6
+ """
7
+ return glm.cross(glm.cross(vector1, vector2), vector3)
@@ -0,0 +1,34 @@
1
+ import glm
2
+
3
+
4
+ # transform matrices
5
+ def get_model_matrix(position: glm.vec3, scale: glm.vec3, rotation: glm.quat) -> glm.mat4x4:
6
+ """
7
+ Gets projection matrix from object data
8
+ """
9
+ translation_matrix = glm.translate(glm.mat4(1.0), position)
10
+ rotation_matrix = glm.mat4_cast(rotation)
11
+ scale_matrix = glm.scale(glm.mat4(1.0), scale)
12
+ model_matrix = translation_matrix * glm.transpose(rotation_matrix) * scale_matrix
13
+ return model_matrix
14
+
15
+ def get_scale_matrix(scale: glm.vec3) -> glm.mat3x3:
16
+ """
17
+ Gets the scaling matrix from a scale vector
18
+ """
19
+ return glm.mat3x3(
20
+ scale.x, 0, 0,
21
+ 0, scale.y, 0,
22
+ 0, 0, scale.z
23
+ )
24
+
25
+ # inertia tensors
26
+ def compute_inertia_moment(t:list[glm.vec3], i:int) -> float:
27
+ return t[0][i] ** 2 + t[1][i] * t[2][i] + \
28
+ t[1][i] ** 2 + t[0][i] * t[2][i] + \
29
+ t[2][i] ** 2 + t[0][i] * t[1][i]
30
+
31
+ def compute_inertia_product(t:list[glm.vec3], i:int, j:int) -> float:
32
+ return 2 * t[0][i] * t[0][j] + t[1][i] * t[2][j] + t[2][i] * t[1][j] + \
33
+ 2 * t[1][i] * t[1][j] + t[0][i] * t[2][j] + t[2][i] * t[0][j] + \
34
+ 2 * t[2][i] * t[2][j] + t[0][i] * t[1][j] + t[1][i] * t[0][j]
@@ -0,0 +1,73 @@
1
+ import glm
2
+ import numpy as np
3
+
4
+
5
+ # transformations
6
+ def transform_points(points: np.ndarray, model_matrix: glm.mat4x4) -> list[glm.vec3]:
7
+ """
8
+ Transforms the mesh points to world space
9
+ """
10
+ return [model_matrix * pt for pt in points]
11
+
12
+ # bvh
13
+ def get_aabb_surface_area(top_right: glm.vec3, bottom_left: glm.vec3) -> float:
14
+ """
15
+ Returns the surface area of the AABB
16
+ """
17
+ diagonal = top_right - bottom_left
18
+ return 2 * (diagonal.x * diagonal.y + diagonal.y * diagonal.z + diagonal.x * diagonal.z)
19
+
20
+ def get_extreme_points_np(points: np.ndarray) -> tuple[glm.vec3, glm.vec3]:
21
+ """
22
+ Returns the top right and bottom left points of the aabb encapsulating the points
23
+ """
24
+ top_right = glm.vec3(-1e10)
25
+ bottom_left = glm.vec3(1e10)
26
+ for pt in points:
27
+ for i in range(3):
28
+ if top_right[i] < pt[i]: top_right[i] = pt[i]
29
+ if bottom_left[i] > pt[i]: bottom_left[i] = pt[i]
30
+ return top_right, bottom_left
31
+
32
+ def get_aabb_line_collision(top_right:glm.vec3, bottom_left:glm.vec3, point:glm.vec3, vec:glm.vec3) -> bool:
33
+ """
34
+ Determines if a line has collided with an aabb
35
+ """
36
+ tmin, tmax = -1e10, 1e10
37
+ for i in range(3):
38
+ if vec[i] != 0:
39
+ inv_dir = 1.0 / vec[i]
40
+ t1 = (bottom_left[i] - point[i]) * inv_dir
41
+ t2 = (top_right[i] - point[i]) * inv_dir
42
+ t1, t2 = min(t1, t2), max(t1, t2)
43
+ tmin = max(tmin, t1)
44
+ tmax = min(tmax, t2)
45
+ if tmin > tmax: return False
46
+ elif point[i] < bottom_left[i] or point[i] > top_right[i]: return False
47
+ return tmax >= 0 and tmin <= 1
48
+
49
+ def moller_trumbore(point:glm.vec3, vec:glm.vec3, triangle:list[glm.vec3], epsilon:float=1e-7) -> glm.vec3 | None:
50
+ """
51
+ Determines where a line intersects with a triangle and where that intersection occurred
52
+ """
53
+ edge1, edge2 = triangle[1] - triangle[0], triangle[2] - triangle[0]
54
+ ray_cross = glm.cross(vec, edge2)
55
+ det = glm.dot(edge1, ray_cross)
56
+
57
+ # if the ray is parallel to the triangle
58
+ if abs(det) < epsilon: return None
59
+
60
+ inv_det = 1 / det
61
+ s = point - triangle[0]
62
+ u = glm.dot(s, ray_cross) * inv_det
63
+
64
+ if (u < 0 and abs(u) > epsilon) or (u > 1 and abs(u - 1) > epsilon): return None
65
+
66
+ s_cross = glm.cross(s, edge1)
67
+ v = glm.dot(vec, s_cross) * inv_det
68
+
69
+ if (v < 0 and abs(v) > epsilon) or (u + v > 1 and abs(u + v - 1) > epsilon): return None
70
+
71
+ t = glm.dot(edge2, s_cross) * inv_det
72
+ if t > epsilon: return point + vec * t
73
+ return None
@@ -0,0 +1,119 @@
1
+ import glm
2
+ import numpy as np
3
+
4
+
5
+ class Quat():
6
+ def __init__(self, *args, callback=None):
7
+ self.callback = callback
8
+ self.set_data(*args)
9
+
10
+ def normalize(self):
11
+ """
12
+ Inplace normalizes the vector
13
+ """
14
+ self.data = glm.normalize(self.data)
15
+
16
+ def set_data(self, *args):
17
+ """
18
+ Sets the internal vector inplace
19
+ """
20
+ # overload constructor TODO nvernest this, definitely possible
21
+ if len(args) == 1:
22
+ if isinstance(args[0], Quat):
23
+ self.data = glm.quat(args[0].data)
24
+ self.callback = args[0].callback
25
+ elif isinstance(args[0], glm.quat): self.data = glm.quat(args[0])
26
+ elif isinstance(args[0], tuple) or isinstance(args[0], list) or isinstance(args[0], np.ndarray):
27
+ if len(args[0]) != 4 and len(args[0]) != 3: raise ValueError(f'Quat: Expected 3 or 4 values from incoming vector, got {len(args[0])}')
28
+ self.data = glm.quat(args[0])
29
+ else: raise ValueError(f'Quat: Unexpected incoming vector type {args[0]}')
30
+ elif len(args) == 4: self.data = glm.quat(*args)
31
+ else: raise ValueError(f'Quat: Expected either 1 vector or 4 numbers, got {len(args)} values')
32
+
33
+ # override _= operators
34
+ def __iadd__(self, other):
35
+ if isinstance(other, glm.quat): self.data += other
36
+ elif isinstance(other, tuple) or isinstance(other, list) or isinstance(other, np.ndarray):
37
+ if len(other) != 4 and len(other) != 3: raise ValueError(f'Quat: Number of added values must be 3 or 4, got {len(other)}')
38
+ self.data += other
39
+ elif isinstance(other, Quat): self.data += other.data
40
+ else: raise ValueError(f'Quat: Not an accepted type for addition, got {type(other)}')
41
+ return self
42
+
43
+ def __isub__(self, other):
44
+ if isinstance(other, glm.quat): self.data -= other
45
+ elif isinstance(other, tuple) or isinstance(other, list) or isinstance(other, np.ndarray):
46
+ if len(other) != 4 and len(other) != 3: raise ValueError(f'Quat: Number of added values must be 3 or 4, got {len(other)}')
47
+ self.data -= other
48
+ elif isinstance(other, Quat): self.data -= other.data
49
+ else: raise ValueError(f'Quat: Not an accepted type for addition, got {type(other)}')
50
+ return self
51
+
52
+ def __imul__(self, other):
53
+ # TODO add checks for number types
54
+ self.data *= other
55
+ return self
56
+
57
+ def __idiv__(self, other):
58
+ # TODO add checks for number types
59
+ self.data /= other
60
+ return self
61
+
62
+ def __ifloordiv__(self, other):
63
+ # TODO add checks for number types
64
+ self.data //= other
65
+ return self
66
+
67
+ # override [_] accessor
68
+ def __getitem__(self, index):
69
+ if int(index) != index: raise IndexError(f'Quat: index must be an int, got {type(index)}') # check if index is a float
70
+ if index < 0 or index > 3: raise IndexError(f'Quat: index out of bounds, got {index}')
71
+ return self.data[index]
72
+
73
+ def __setitem__(self, index, value):
74
+ if int(index) != index: raise IndexError(f'Quat: index must be an int, got {type(index)}') # check if index is a float
75
+ if index < 0 or index > 3: raise IndexError(f'Quat: index out of bounds, got {index}')
76
+ try: self.data[index] = value
77
+ except: raise ValueError(f'Quat: Invalid element type, got {type(value)}')
78
+
79
+ def __repr__(self):
80
+ return str(self.data)
81
+
82
+ def __iter__(self):
83
+ return iter((self.x, self.y, self.z))
84
+
85
+ @property
86
+ def data(self): return self._data
87
+ @property
88
+ def w(self): return self.data.w
89
+ @property
90
+ def x(self): return self.data.x
91
+ @property
92
+ def y(self): return self.data.y
93
+ @property
94
+ def z(self): return self.data.z
95
+
96
+ @data.setter
97
+ def data(self, value: glm.quat):
98
+ self._data = value
99
+ if self.callback and all(abs(self.data[i] - value[i]) > 1e-12 for i in range(4)): self.callback()
100
+
101
+ @w.setter
102
+ def w(self, value):
103
+ self.data.w = value
104
+ if self.callback: self.callback()
105
+
106
+ @x.setter
107
+ def x(self, value):
108
+ self.data.x = value
109
+ if self.callback: self.callback()
110
+
111
+ @y.setter
112
+ def y(self, value):
113
+ self.data.y = value
114
+ if self.callback: self.callback()
115
+
116
+ @z.setter
117
+ def z(self, value):
118
+ self.data.z = value
119
+ if self.callback: self.callback()
@@ -0,0 +1,8 @@
1
+ import glm
2
+
3
+
4
+ def rotate_vec_by_quat(vec: glm.vec3, quat: glm.quat) -> glm.vec3:
5
+ """
6
+ Rotates a vector by a quaternion. Probably just dont use this, just a reminder of how glm works with quaternions
7
+ """
8
+ return vec * quat