basilisk-engine 0.0.1__py3-none-any.whl → 0.0.2__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.
- basilisk/bsk_assets/__init__.py +0 -0
- basilisk/collisions/__init__.py +0 -0
- basilisk/collisions/broad/__init__.py +0 -0
- basilisk/collisions/broad/broad_aabb.py +96 -0
- basilisk/collisions/broad/broad_bvh.py +102 -0
- basilisk/collisions/collider.py +75 -0
- basilisk/collisions/collider_handler.py +163 -0
- basilisk/collisions/narrow/__init__.py +0 -0
- basilisk/collisions/narrow/epa.py +86 -0
- basilisk/collisions/narrow/gjk.py +66 -0
- basilisk/collisions/narrow/helper.py +23 -0
- basilisk/draw/__init__.py +0 -0
- basilisk/draw/draw.py +101 -0
- basilisk/draw/draw_handler.py +208 -0
- basilisk/draw/font_renderer.py +28 -0
- basilisk/generic/__init__.py +0 -0
- basilisk/generic/abstract_bvh.py +16 -0
- basilisk/generic/collisions.py +26 -0
- basilisk/generic/input_validation.py +28 -0
- basilisk/generic/math.py +7 -0
- basilisk/generic/matrices.py +34 -0
- basilisk/generic/meshes.py +73 -0
- basilisk/generic/quat.py +119 -0
- basilisk/generic/quat_methods.py +8 -0
- basilisk/generic/vec3.py +112 -0
- basilisk/input/__init__.py +1 -0
- basilisk/input/mouse.py +60 -0
- basilisk/mesh/__init__.py +0 -0
- basilisk/mesh/built-in/__init__.py +0 -0
- basilisk/mesh/cube.py +20 -0
- basilisk/mesh/mesh.py +216 -0
- basilisk/mesh/mesh_from_data.py +48 -0
- basilisk/mesh/model.py +272 -0
- basilisk/mesh/narrow_aabb.py +81 -0
- basilisk/mesh/narrow_bvh.py +84 -0
- basilisk/mesh/narrow_primative.py +24 -0
- basilisk/nodes/__init__.py +0 -0
- basilisk/nodes/node.py +508 -0
- basilisk/nodes/node_handler.py +94 -0
- basilisk/physics/__init__.py +0 -0
- basilisk/physics/physics_body.py +36 -0
- basilisk/physics/physics_engine.py +37 -0
- basilisk/render/__init__.py +0 -0
- basilisk/render/batch.py +85 -0
- basilisk/render/camera.py +166 -0
- basilisk/render/chunk.py +85 -0
- basilisk/render/chunk_handler.py +139 -0
- basilisk/render/frame.py +182 -0
- basilisk/render/image.py +76 -0
- basilisk/render/image_handler.py +119 -0
- basilisk/render/light.py +97 -0
- basilisk/render/light_handler.py +54 -0
- basilisk/render/material.py +196 -0
- basilisk/render/material_handler.py +123 -0
- basilisk/render/shader_handler.py +95 -0
- basilisk/render/sky.py +118 -0
- basilisk/shaders/__init__.py +0 -0
- {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.2.dist-info}/METADATA +1 -1
- basilisk_engine-0.0.2.dist-info/RECORD +65 -0
- basilisk_engine-0.0.1.dist-info/RECORD +0 -8
- {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.2.dist-info}/WHEEL +0 -0
- {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.2.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")
|
basilisk/generic/math.py
ADDED
|
@@ -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
|
basilisk/generic/quat.py
ADDED
|
@@ -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()
|