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.

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 +1 -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.2.dist-info}/METADATA +1 -1
  59. basilisk_engine-0.0.2.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.2.dist-info}/WHEEL +0 -0
  62. {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,112 @@
1
+ import glm
2
+ import numpy as np
3
+
4
+
5
+ class Vec3():
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], Vec3):
23
+ self.data = glm.vec3(args[0].data)
24
+ self.callback = args[0].callback
25
+ elif isinstance(args[0], glm.vec3): self.data = glm.vec3(args[0])
26
+ elif isinstance(args[0], tuple) or isinstance(args[0], list) or isinstance(args[0], np.ndarray):
27
+ if len(args[0]) != 3: raise ValueError(f'Vec3: Expected 3 values from incoming vector, got {len(args[0])}')
28
+ self.data = glm.vec3(args[0])
29
+ else: raise ValueError(f'Vec3: Unexpected incoming vector type {args[0]}')
30
+ elif len(args) == 3: self.data = glm.vec3(args[0], args[1], args[2])
31
+ else: raise ValueError(f'Vec3: Expected either 1 vector or 3 numbers, got {len(args)} values')
32
+
33
+ # override _= operators
34
+ def __iadd__(self, other):
35
+ if isinstance(other, glm.vec3): self.data += other
36
+ elif isinstance(other, tuple) or isinstance(other, list) or isinstance(other, np.ndarray):
37
+ if len(other) != 3: raise ValueError(f'Vec3: Number of added values must be 3, got {len(other)}')
38
+ self.data += other
39
+ elif isinstance(other, Vec3): self.data += other.data
40
+ else: raise ValueError(f'Vec3: Not an accepted type for addition, got {type(other)}')
41
+ return self
42
+
43
+ def __isub__(self, other):
44
+ if isinstance(other, glm.vec3): self.data -= other
45
+ elif isinstance(other, tuple) or isinstance(other, list) or isinstance(other, np.ndarray):
46
+ if len(other) != 3: raise ValueError(f'Vec3: Number of added values must be 3, got {len(other)}')
47
+ self.data -= other
48
+ elif isinstance(other, Vec3): self.data -= other.data
49
+ else: raise ValueError(f'Vec3: 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'Vec3: index must be an int, got {type(index)}') # check if index is a float
70
+ if index < 0 or index > 2: raise IndexError(f'Vec3: 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'Vec3: index must be an int, got {type(index)}') # check if index is a float
75
+ if index < 0 or index > 2: raise IndexError(f'Vec3: index out of bounds, got {index}')
76
+ try: self.data[index] = value
77
+ except: raise ValueError(f'Vec3: 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 x(self): return self.data.x
89
+ @property
90
+ def y(self): return self.data.y
91
+ @property
92
+ def z(self): return self.data.z
93
+
94
+ @data.setter
95
+ def data(self, value: glm.vec3):
96
+ self._data = value
97
+ if self.callback and all(abs(self.data[i] - value[i]) > 1e-12 for i in range(3)): self.callback()
98
+
99
+ @x.setter
100
+ def x(self, value):
101
+ self.data.x = value
102
+ if self.callback: self.callback()
103
+
104
+ @y.setter
105
+ def y(self, value):
106
+ self.data.y = value
107
+ if self.callback: self.callback()
108
+
109
+ @z.setter
110
+ def z(self, value):
111
+ self.data.z = value
112
+ if self.callback: self.callback()
@@ -0,0 +1 @@
1
+ import
@@ -0,0 +1,60 @@
1
+ import pygame as pg
2
+
3
+
4
+ class Mouse():
5
+ def __init__(self, grab=True):
6
+ self.x, self.y = pg.mouse.get_pos()
7
+ self.buttons = pg.mouse.get_pressed()
8
+ self.previous_buttons = pg.mouse.get_pressed()
9
+ self.grab = grab
10
+
11
+ def update(self, events):
12
+ """
13
+ Updates all mouse state variables.
14
+ Checks for mouse-related events.
15
+ """
16
+
17
+ self.x, self.y = pg.mouse.get_pos()
18
+ self.previous_buttons = self.buttons
19
+ self.buttons = pg.mouse.get_pressed()
20
+
21
+ for event in events:
22
+ if event.type == pg.KEYUP:
23
+ if event.key == pg.K_ESCAPE and self.grab:
24
+ # Unlock mouse
25
+ pg.event.set_grab(False)
26
+ pg.mouse.set_visible(True)
27
+ if event.type == pg.MOUSEBUTTONUP and self.grab:
28
+ # Lock mouse
29
+ pg.event.set_grab(True)
30
+ pg.mouse.set_visible(False)
31
+
32
+ def set_pos(self, x, y):
33
+ """Set the mouse position"""
34
+ pg.mouse.set_pos(x, y)
35
+
36
+ @property
37
+ def click(self): return self.previous_buttons[0] and not self.buttons[0]
38
+ @property
39
+ def middle_click(self): return self.previous_buttons[1] and not self.buttons[1]
40
+ @property
41
+ def right_click(self): return self.previous_buttons[2] and not self.buttons[2]
42
+ @property
43
+ def left_down(self): return self.buttons[0]
44
+ @property
45
+ def middle_down(self): return self.buttons[1]
46
+ @property
47
+ def right_down(self): return self.buttons[2]
48
+
49
+ @property
50
+ def grab(self): return self._grab
51
+
52
+ @grab.setter
53
+ def grab(self, value):
54
+ self._grab = value
55
+ if self._grab:
56
+ pg.event.set_grab(True)
57
+ pg.mouse.set_visible(False)
58
+ else:
59
+ pg.event.set_grab(False)
60
+ pg.mouse.set_visible(True)
File without changes
File without changes
basilisk/mesh/cube.py ADDED
@@ -0,0 +1,20 @@
1
+ import glm
2
+ import os
3
+ from .mesh import Mesh
4
+
5
+
6
+ class Cube(Mesh):
7
+ def __init__(self) -> None:
8
+ # built-in cube mesh with custom functions
9
+ dire = os.path.dirname(__file__)
10
+ path = os.path.join(dire, 'built-in', 'cube.obj')
11
+ super().__init__(path)
12
+
13
+ def get_best_dot(self, vec: glm.vec3) -> glm.vec3:
14
+ """
15
+ Gets the best dot point of a cube
16
+ """
17
+ return glm.vec3([-1 if v < 0 else 1 for v in vec])
18
+
19
+ # create instance of cube mesh to be used by both the user and the package. Needs to be the same cube object for internal comparisons. Do not allow the user to access the Cube class to prevent them from making other Cube objects.
20
+ cube = Cube()
basilisk/mesh/mesh.py ADDED
@@ -0,0 +1,216 @@
1
+ import numpy as np
2
+ import glm
3
+ import os
4
+ # from pyobjloader import load_model
5
+ from .model import load_model
6
+ from .narrow_bvh import NarrowBVH
7
+ from ..generic.matrices import compute_inertia_moment, compute_inertia_product
8
+ from ..generic.meshes import get_extreme_points_np, moller_trumbore
9
+ from .mesh_from_data import from_data
10
+
11
+
12
+ class Mesh():
13
+ data: np.ndarray
14
+ """The mesh vertex data stored as a 4-byte float numpy array. Format will be [position.xyz, uv.xy, normal.xyz, tangent.xyz, bitangent.xyz]"""
15
+ points: np.ndarray
16
+ """All the unique points of the mesh given by the model file"""
17
+ indices: np.ndarray
18
+ """Indices of the triangles corresponding to the points array"""
19
+ bvh: any
20
+ """Data structure allowing the access of closest points more efficiently"""
21
+ volume: float
22
+ """The volume of the unscaled mesh"""
23
+ geometric_center: glm.vec3
24
+ """The geometric center of the mesh"""
25
+ center_of_mass: glm.vec3
26
+ """The center of mass of the mesh calculated from the inertia tensor algorithm"""
27
+ half_dimensions: glm.vec3
28
+ """The aligned half dimensions to the untransformed mesh"""
29
+ bvh: NarrowBVH
30
+ """BVH for accessing triangle intersections with a line"""
31
+
32
+ def __init__(self, data: str | os.PathLike | np.ndarray) -> None:
33
+ """
34
+ Mesh object containing all the data needed to render an object and perform physics/collisions on it
35
+ Args:
36
+ data: str
37
+ path to the .obj file of the model or an array of the mesh data
38
+ """
39
+
40
+ # Verify the path type
41
+ if isinstance(data, str) or isinstance(data, os.PathLike): # Load the model from file
42
+ model = load_model(data, calculate_tangents=True)
43
+ elif isinstance(data, np.ndarray): # Load the model from array of data
44
+ model = from_data(data)
45
+ else: # Invalid data type
46
+ raise TypeError(f'Invalid path type: {type(data)}. Expected a string or os.path')
47
+
48
+ # Get the vertex data
49
+ if len(model.vertex_data[0]) == 8:
50
+ self.data = model.vertex_data.copy()
51
+ else:
52
+ self.data = np.zeros(shape=(len(model.vertex_data), 8))
53
+ self.data[:,:3] = model.vertex_data[:,:3]
54
+ self.data[:,5:] = model.vertex_data[:,3:]
55
+
56
+ # Get tangent data
57
+ if len(model.tangent_data[0]) == 6:
58
+ self.data = np.hstack([self.data, model.tangent_data])
59
+ else:
60
+ tangents = np.zeros(shape=(len(self.data), 6))
61
+ tangents[:,:] += [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]
62
+ self.data = np.hstack([self.data, tangents])
63
+
64
+ # Mesh points and triangles used for physics/collisions
65
+ self.points = model.vertex_points.copy()
66
+ self.indices = model.point_indices.copy()
67
+
68
+ # Model will no longer be used
69
+ del model
70
+
71
+ # generate geometric data
72
+ maximum, minimum = get_extreme_points_np(self.points)
73
+ self.geometric_center = (glm.vec3(maximum) + glm.vec3(minimum)) / 2
74
+ self.half_dimensions = maximum - self.geometric_center
75
+
76
+ # volume and center of mass
77
+ self.volume = 0
78
+ self.center_of_mass = glm.vec3(0.0)
79
+ for triangle in self.indices:
80
+ pts = [glm.vec3(self.points[t]) for t in triangle]
81
+ det_j = glm.dot(pts[0], glm.cross(pts[1], pts[2]))
82
+ tet_volume = det_j / 6
83
+ self.volume += tet_volume
84
+ self.center_of_mass += tet_volume * (pts[0] + pts[1] + pts[2]) / 4
85
+ self.center_of_mass /= self.volume
86
+
87
+ # data structrues
88
+ self.bvh = NarrowBVH(self)
89
+
90
+ def get_inertia_tensor(self, scale: glm.vec3) -> glm.mat3x3:
91
+ """
92
+ Gets the inertia tensor of the mesh with the given scale and mass 1
93
+ """
94
+ # scale variables
95
+ center_of_mass = self.center_of_mass * scale
96
+ volume = self.volume * scale.x * scale.y * scale.z
97
+
98
+ # uses density = 1 to calculate variables, should be the same for mass = 1 since they are only spatial variables
99
+ points = self.points.copy()
100
+ points[:, 0] *= scale.x
101
+ points[:, 1] *= scale.y
102
+ points[:, 2] *= scale.z
103
+
104
+ ia = ib = ic = iap = ibp = icp = 0
105
+ for triangle in self.indices:
106
+ pts = [points[t] for t in triangle]
107
+ det_j = glm.dot(pts[0], glm.cross(pts[1], pts[2]))
108
+
109
+ ia += det_j * (compute_inertia_moment(pts, 1) + compute_inertia_moment(pts, 2))
110
+ ib += det_j * (compute_inertia_moment(pts, 0) + compute_inertia_moment(pts, 2))
111
+ ic += det_j * (compute_inertia_moment(pts, 0) + compute_inertia_moment(pts, 1))
112
+ iap += det_j * compute_inertia_product(pts, 1, 2)
113
+ ibp += det_j * compute_inertia_product(pts, 0, 1)
114
+ icp += det_j * compute_inertia_product(pts, 0, 2)
115
+
116
+ # since tensor was calc with density = 1. we say mass = density / volume = 1 / volume
117
+ ia = ia / volume / 60 - volume * (center_of_mass[1] ** 2 + center_of_mass[2] ** 2)
118
+ ib = ib / volume / 60 - volume * (center_of_mass[0] ** 2 + center_of_mass[2] ** 2)
119
+ ic = ic / volume / 60 - volume * (center_of_mass[0] ** 2 + center_of_mass[1] ** 2)
120
+ iap = iap / volume / 120 - volume * center_of_mass[1] * center_of_mass[2]
121
+ ibp = ibp / volume / 120 - volume * center_of_mass[0] * center_of_mass[1]
122
+ icp = icp / volume / 120 - volume * center_of_mass[0] * center_of_mass[2]
123
+
124
+ return glm.mat3x3(
125
+ ia, -ibp, -icp,
126
+ -ibp, ib, -iap,
127
+ -icp, -iap, ic
128
+ )
129
+
130
+ def get_best_triangle(self, point: glm.vec3, vec: glm.vec3) -> int:
131
+ """
132
+ Gets the triangle with the closest intersection, -1 if no intersection is found
133
+ """
134
+ indices = self.bvh.get_possible_triangles(point, vec)
135
+ best_distance = -1
136
+ best_index = -1
137
+
138
+ point = glm.vec3(point)
139
+ vec = glm.vec3(vec)
140
+
141
+ for triangle in indices:
142
+
143
+ # check if triangle intersects
144
+ intersection = moller_trumbore(point, vec, [self.points[t] for t in self.indices[triangle]])
145
+ if not intersection: continue
146
+
147
+ # check if triangle is on correct side of line
148
+ difference = intersection - self.geometric_center
149
+ if glm.dot(difference, vec) < 0: continue
150
+
151
+ # determine best distance
152
+ distance = glm.length(difference)
153
+ if best_distance < 0 or distance < best_distance:
154
+ best_distance = distance
155
+ best_index = triangle
156
+
157
+ return best_index
158
+
159
+ def get_best_triangle_brute(self, point: glm.vec3, vec: glm.vec3) -> int:
160
+ """
161
+ Gets the triangle with the closest intersection, -1 if no intersection is found. Uses a brute force method
162
+ """
163
+ best_distance = -1
164
+ best_index = -1
165
+
166
+ point = glm.vec3(point)
167
+ vec = glm.vec3(vec)
168
+
169
+ for index, triangle in enumerate(self.indices):
170
+
171
+ # check if triangle intersects
172
+ intersection = moller_trumbore(point, vec, [self.points[t] for t in triangle])
173
+ if not intersection: continue
174
+
175
+ # check if triangle is on correct side of line
176
+ difference = intersection - self.geometric_center
177
+ if glm.dot(difference, vec) < 0: continue
178
+
179
+ # determine best distance
180
+ distance = glm.length(difference)
181
+ if best_distance < 0 or distance < best_distance:
182
+ best_distance = distance
183
+ best_index = index
184
+
185
+ return best_index
186
+
187
+ def get_best_dot(self, vec: glm.vec3) -> glm.vec3:
188
+ """
189
+ Gets the point with the highest normalized dot product to the given vector
190
+ """
191
+ triangle = self.bvh.get_best_dot(vec)
192
+ if triangle == -1: return None
193
+ index = max(self.indices[triangle], key=lambda t: glm.dot(glm.normalize(self.points[t]), vec))
194
+ return glm.vec3(self.points[index])
195
+
196
+ def get_best_dot_old(self, vec):
197
+ best_dot = -1e10
198
+ best = None
199
+ for point in self.points:
200
+ dot = glm.dot(glm.normalize(point), vec)
201
+ if dot > best_dot: best_dot, best = dot, glm.vec3(point)
202
+ return best
203
+
204
+ def __repr__(self) -> str:
205
+ size = (self.data.nbytes + self.points.nbytes + self.indices.nbytes) / 1024 / 1024
206
+ return f'<Basilisk Mesh | {len(self.data)} vertices, {size:.2} mb>'
207
+
208
+ @property
209
+ def top_right(self): return self.bvh.root.top_right
210
+ @property
211
+ def bottom_left(self): return self.bvh.root.bottom_left
212
+ @property
213
+ def aabb_points(self):
214
+ x1, y1, z1 = self.top_right
215
+ x2, y2, z2 = self.bottom_left
216
+ return [glm.vec3(x, y, z) for z in (z1, z2) for y in (y1, y2) for x in (x1, x2)]
@@ -0,0 +1,48 @@
1
+ import numpy as np
2
+
3
+
4
+ def from_data(data: np.ndarray) -> np.ndarray:
5
+ """
6
+ Converts data given to a format compatable with basilisk models
7
+ """
8
+
9
+ shape = data.shape
10
+
11
+ if shape[1] == 3: # Just given position
12
+ pos_norm_data = get_normals(data)
13
+ print(pos_norm_data.shape)
14
+ data = np.zeros(shape=(len(data), 14))
15
+ data[:,:6] = pos_norm_data
16
+ return data
17
+
18
+ elif shape[1] == 6: # Given position and normals, but no UV
19
+ pos_norm_data = data
20
+ data = np.zeros(shape=(len(data), 14))
21
+ data[:][:6] = pos_norm_data
22
+ return data
23
+
24
+ elif shape[1] == 8: # Given position, normals and UV
25
+ ...
26
+
27
+ elif shape[1] == 14: #Given position, normals, UV, bitangents, and tangents, no change needed
28
+ return data
29
+
30
+ raise ValueError(f"Could not find valid format for the given model data of shape {shape}")
31
+
32
+
33
+ def get_normals(positions: np.ndarray) -> np.ndarray:
34
+ """
35
+ Gets the normals for a position array and returns a concatinated array
36
+ """
37
+
38
+ # Create empty array for the normals
39
+ normals = np.zeros(shape=positions.shape)
40
+
41
+ # Loop through each triangle and calculate the normal of the surface
42
+ for tri in range(positions.shape[0] // 3):
43
+ normal = np.cross(positions[tri] - positions[tri + 1], positions[tri] - positions[tri + 2])
44
+ normals[tri ] = normal
45
+ normals[tri + 1] = normal
46
+ normals[tri + 2] = normal
47
+
48
+ return np.hstack([positions, normals])