basilisk-engine 0.1.0__py3-none-any.whl → 0.1.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 (55) hide show
  1. basilisk/collisions/broad/broad_aabb.py +8 -1
  2. basilisk/collisions/broad/broad_bvh.py +8 -1
  3. basilisk/collisions/collider.py +15 -6
  4. basilisk/collisions/collider_handler.py +70 -68
  5. basilisk/collisions/narrow/contact_manifold.py +9 -10
  6. basilisk/collisions/narrow/dataclasses.py +27 -0
  7. basilisk/collisions/narrow/deprecated.py +47 -0
  8. basilisk/collisions/narrow/epa.py +11 -10
  9. basilisk/collisions/narrow/gjk.py +15 -14
  10. basilisk/collisions/narrow/helper.py +8 -7
  11. basilisk/collisions/narrow/sutherland_hodgman.py +52 -0
  12. basilisk/draw/draw_handler.py +5 -3
  13. basilisk/engine.py +14 -5
  14. basilisk/generic/abstract_custom.py +134 -0
  15. basilisk/generic/collisions.py +46 -0
  16. basilisk/generic/quat.py +77 -66
  17. basilisk/generic/vec3.py +91 -67
  18. basilisk/mesh/cube.py +20 -2
  19. basilisk/mesh/mesh.py +69 -54
  20. basilisk/mesh/mesh_from_data.py +106 -21
  21. basilisk/mesh/narrow_aabb.py +10 -1
  22. basilisk/mesh/narrow_bvh.py +9 -1
  23. basilisk/nodes/node.py +169 -30
  24. basilisk/nodes/node_handler.py +51 -30
  25. basilisk/particles/__init__.py +0 -0
  26. basilisk/particles/particle_handler.py +55 -0
  27. basilisk/particles/particle_renderer.py +87 -0
  28. basilisk/physics/impulse.py +7 -13
  29. basilisk/physics/physics_body.py +10 -2
  30. basilisk/physics/physics_engine.py +1 -2
  31. basilisk/render/batch.py +2 -2
  32. basilisk/render/camera.py +5 -0
  33. basilisk/render/chunk.py +19 -4
  34. basilisk/render/chunk_handler.py +33 -26
  35. basilisk/render/image.py +1 -1
  36. basilisk/render/image_handler.py +7 -5
  37. basilisk/render/light_handler.py +16 -11
  38. basilisk/render/material.py +25 -1
  39. basilisk/render/material_handler.py +22 -13
  40. basilisk/render/shader.py +7 -7
  41. basilisk/render/shader_handler.py +13 -12
  42. basilisk/render/sky.py +5 -3
  43. basilisk/scene.py +114 -32
  44. basilisk/shaders/batch.frag +40 -11
  45. basilisk/shaders/batch.vert +14 -7
  46. basilisk/shaders/normal.frag +5 -5
  47. basilisk/shaders/normal.vert +8 -3
  48. basilisk/shaders/particle.frag +72 -0
  49. basilisk/shaders/particle.vert +85 -0
  50. {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.2.dist-info}/METADATA +5 -5
  51. basilisk_engine-0.1.2.dist-info/RECORD +95 -0
  52. basilisk/shaders/image.png +0 -0
  53. basilisk_engine-0.1.0.dist-info/RECORD +0 -88
  54. {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.2.dist-info}/WHEEL +0 -0
  55. {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.2.dist-info}/top_level.txt +0 -0
basilisk/generic/vec3.py CHANGED
@@ -1,87 +1,90 @@
1
1
  import glm
2
2
  import numpy as np
3
+ from .abstract_custom import Custom
3
4
 
4
-
5
- class Vec3():
5
+ class Vec3(Custom):
6
6
  def __init__(self, *args, callback=None):
7
7
  self.callback = callback
8
8
  self.prev_data = glm.vec3(0.0)
9
- self.set_data(*args)
10
-
11
- def normalize(self):
12
- """
13
- Inplace normalizes the vector
14
- """
15
- self.data = glm.normalize(self.data)
16
9
 
17
- def set_data(self, *args):
18
- """
19
- Sets the internal vector inplace
20
- """
21
- # overload constructor TODO nvernest this, definitely possible
22
10
  if len(args) == 1:
11
+
23
12
  if isinstance(args[0], Vec3):
24
13
  self.data = glm.vec3(args[0].data)
14
+ self.prev_data = glm.vec3(args[0].prev_data)
25
15
  self.callback = args[0].callback
26
- elif isinstance(args[0], glm.vec3): self.data = glm.vec3(args[0])
16
+
17
+ elif isinstance(args[0], glm.vec3):
18
+ self.data = args[0]
19
+
27
20
  elif isinstance(args[0], tuple) or isinstance(args[0], list) or isinstance(args[0], np.ndarray):
28
- if len(args[0]) != 3: raise ValueError(f'Vec3: Expected 3 values from incoming vector, got {len(args[0])}')
21
+ assert len(args[0]) == 3, f'Vec3: Expected 3 values from incoming vector, got {len(args[0])}'
29
22
  self.data = glm.vec3(args[0])
30
- else: raise ValueError(f'Vec3: Unexpected incoming vector type {args[0]}')
31
- elif len(args) == 3: self.data = glm.vec3(args[0], args[1], args[2])
23
+
24
+ else:
25
+ try: self.data = glm.vec3(args[0])
26
+ except: raise ValueError(f'Vec3: Unexpected incoming vector type {args[0]}')
27
+
28
+ elif len(args) == 3: self.data = glm.vec3(*args)
32
29
  else: raise ValueError(f'Vec3: Expected either 1 vector or 3 numbers, got {len(args)} values')
33
-
34
- # override _= operators
35
- def __iadd__(self, other):
36
- if isinstance(other, glm.vec3): self.data += other
37
- elif isinstance(other, tuple) or isinstance(other, list) or isinstance(other, np.ndarray):
38
- if len(other) != 3: raise ValueError(f'Vec3: Number of added values must be 3, got {len(other)}')
39
- self.data += other
40
- elif isinstance(other, Vec3): self.data += other.data
41
- else: raise ValueError(f'Vec3: Not an accepted type for addition, got {type(other)}')
42
- return self
43
-
44
- def __isub__(self, other):
45
- if isinstance(other, glm.vec3): self.data -= other
30
+
31
+ def apply_function(self, other, func, func_name):
32
+ vec = glm.vec3(self.data)
33
+
34
+ if isinstance(other, (glm.vec3, glm.quat)):
35
+ vec = func(vec, other)
36
+
46
37
  elif isinstance(other, tuple) or isinstance(other, list) or isinstance(other, np.ndarray):
47
- if len(other) != 3: raise ValueError(f'Vec3: Number of added values must be 3, got {len(other)}')
48
- self.data -= other
49
- elif isinstance(other, Vec3): self.data -= other.data
50
- else: raise ValueError(f'Vec3: Not an accepted type for addition, got {type(other)}')
51
- return self
52
-
53
- def __imul__(self, other):
54
- # TODO add checks for number types
55
- self.data *= other
56
- return self
57
-
58
- def __idiv__(self, other):
59
- # TODO add checks for number types
60
- self.data /= other
61
- return self
38
+ assert len(other) == 3, f'Vec3: Number of added values must be 3, got {len(other)}'
39
+ vec = func(vec, other)
40
+
41
+ elif isinstance(other, Custom): # perserve self.callback over other.callback. this should never be done by the user
42
+ vec = func(vec, other.data)
43
+
44
+ else:
45
+ try: vec = func(vec, other)
46
+ except: raise ValueError(f'Vec3: Not an accepted type for {func_name}, got {type(other)}')
47
+ return Vec3(vec)
48
+
49
+ # unary operators
50
+ def __neg__(self):
51
+ return Vec3(-self.data, callback=self.callback)
62
52
 
63
- def __ifloordiv__(self, other):
64
- # TODO add checks for number types
65
- self.data //= other
66
- return self
53
+ def __abs__(self):
54
+ return Vec3(abs(self.data), callback=self.callback)
67
55
 
68
- # override [_] accessor
56
+ # accessor functions
69
57
  def __getitem__(self, index):
70
- if int(index) != index: raise IndexError(f'Vec3: index must be an int, got {type(index)}') # check if index is a float
71
- if index < 0 or index > 2: raise IndexError(f'Vec3: index out of bounds, got {index}')
58
+ assert int(index) == index, f'Vec3: index must be an int, got {type(index)}' # check if index is a float
59
+ assert 0 <= index <= 2, f'Vec3: index out of bounds, got {index}'
72
60
  return self.data[index]
73
61
 
74
62
  def __setitem__(self, index, value):
75
- if int(index) != index: raise IndexError(f'Vec3: index must be an int, got {type(index)}') # check if index is a float
76
- if index < 0 or index > 2: raise IndexError(f'Vec3: index out of bounds, got {index}')
63
+ assert int(index) == index, f'Vec3: index must be an int, got {type(index)}' # check if index is a float
64
+ assert 0 <= index <= 2, f'Vec3: index out of bounds, got {index}'
77
65
  try: self.data[index] = value
78
66
  except: raise ValueError(f'Vec3: Invalid element type, got {type(value)}')
79
67
 
80
- def __repr__(self):
81
- return str(self.data)
82
-
68
+ def __delitem__(self, index): # index in a vec cannot be deleted, so we default to zero
69
+ assert int(index) == index, f'Vec3: index must be an int, got {type(index)}' # check if index is a float
70
+ assert 0 <= index <= 2, f'Vec3: index out of bounds, got {index}'
71
+ self.data[index] = 0
72
+
73
+ def __len__(self):
74
+ return 3
75
+
83
76
  def __iter__(self):
84
- return iter((self.x, self.y, self.z))
77
+ return iter(self.data)
78
+
79
+ def __contains__(self, item):
80
+ return item in self.data
81
+
82
+ # override str operators
83
+ def __repr__(self):
84
+ return 'bsk ' + str(self.data)
85
+
86
+ def __str__(self):
87
+ return 'bsk ' + str(self.data)
85
88
 
86
89
  @property
87
90
  def data(self): return self._data
@@ -94,8 +97,7 @@ class Vec3():
94
97
 
95
98
  @data.setter
96
99
  def data(self, value: glm.vec3):
97
- self._data = value
98
-
100
+ self._data = glm.vec3(value)
99
101
  cur = self._data
100
102
  prev = self.prev_data
101
103
  thresh = 1e-6
@@ -106,15 +108,37 @@ class Vec3():
106
108
 
107
109
  @x.setter
108
110
  def x(self, value):
109
- self.data.x = value
110
- if self.callback: self.callback()
111
+ self._data.x = value
112
+ if self.callback and abs(value - self.prev_data.x) > 1e-6:
113
+ self.prev_data.x = value
114
+ self.callback()
111
115
 
112
116
  @y.setter
113
117
  def y(self, value):
114
- self.data.y = value
115
- if self.callback: self.callback()
118
+ self._data.y = value
119
+ if self.callback and abs(value - self.prev_data.y) > 1e-6:
120
+ self.prev_data.y = value
121
+ self.callback()
116
122
 
117
123
  @z.setter
118
124
  def z(self, value):
119
- self.data.z = value
120
- if self.callback: self.callback()
125
+ self._data.z = value
126
+ if self.callback and abs(value - self.prev_data.z) > 1e-6:
127
+ self.prev_data.z = value
128
+ self.callback()
129
+
130
+ class Node():
131
+
132
+ def __init__(self, pos):
133
+
134
+ def callback(): print('calling back')
135
+ self.pos_callback = callback
136
+ self._pos = Vec3(pos, callback=self.pos_callback)
137
+
138
+ @property
139
+ def pos(self): return self._pos
140
+
141
+ @pos.setter
142
+ def pos(self, value):
143
+ if isinstance(value, Vec3): self._pos.data = value.data
144
+ else: self._pos.data = value
basilisk/mesh/cube.py CHANGED
@@ -8,9 +8,27 @@ class Cube(Mesh):
8
8
  # built-in cube mesh with custom functions
9
9
  path = engine.root + '/bsk_assets/cube.obj'
10
10
  super().__init__(path)
11
+
12
+ self.dot_indices = [0 for _ in range(8)]
13
+ for i, point in enumerate(self.points):
14
+ index = 0
15
+ if point[0] > 0: index += 4
16
+ if point[1] > 0: index += 2
17
+ if point[2] > 0: index += 1
18
+ self.dot_indices[index] = i
11
19
 
12
- def get_best_dot(self, vec: glm.vec3) -> glm.vec3:
20
+ def get_best_dot(self, vec: glm.vec3) -> int:
13
21
  """
14
22
  Gets the best dot point of a cube
15
23
  """
16
- return glm.vec3([-1 if v < 0 else 1 for v in vec])
24
+ index = 0
25
+ if vec[0] > 0: index += 4
26
+ if vec[1] > 0: index += 2
27
+ if vec[2] > 0: index += 1
28
+ return self.dot_indices[index]
29
+
30
+ def get_line_collided(self, position: glm.vec3, forward: glm.vec3) -> list[int]:
31
+ """
32
+ Returns all the faces on the cube since the AABB degenerates on the cube mesh
33
+ """
34
+ return [i for i in range(12)]
basilisk/mesh/mesh.py CHANGED
@@ -7,6 +7,7 @@ from .narrow_bvh import NarrowBVH
7
7
  from ..generic.matrices import compute_inertia_moment, compute_inertia_product
8
8
  from ..generic.meshes import get_extreme_points_np, moller_trumbore
9
9
  from .mesh_from_data import from_data
10
+ import time
10
11
 
11
12
 
12
13
  class Mesh():
@@ -40,31 +41,44 @@ class Mesh():
40
41
  # Verify the path type
41
42
  if isinstance(data, str) or isinstance(data, os.PathLike): # Load the model from file
42
43
  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)
44
+
45
+ # Get the vertex data
46
+ if len(model.vertex_data[0]) == 8:
47
+ self.data = model.vertex_data.copy()
48
+ else:
49
+ self.data = np.zeros(shape=(len(model.vertex_data), 8))
50
+ self.data[:,:3] = model.vertex_data[:,:3]
51
+ self.data[:,5:] = model.vertex_data[:,3:]
52
+
53
+ # Get tangent data
54
+ if len(model.tangent_data[0]) == 6:
55
+ self.data = np.hstack([self.data, model.tangent_data])
56
+ else:
57
+ tangents = np.zeros(shape=(len(self.data), 6))
58
+ tangents[:,:] += [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]
59
+ self.data = np.hstack([self.data, tangents])
60
+
61
+ elif isinstance(data, np.ndarray):
62
+ model = from_data(data)
63
+ self.data = model.vertex_data
64
+
45
65
  else: # Invalid data type
46
66
  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
67
 
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
68
  # Mesh points and triangles used for physics/collisions
65
69
  self.points = model.vertex_points.copy()
66
70
  self.indices = model.point_indices.copy()
67
71
 
72
+ self.hash = hash(str(self.data))
73
+
74
+ # generate edges from faces
75
+ edges = [set() for _ in range(len(self.points))]
76
+ for face in self.indices:
77
+
78
+ # add points to the dictionary since each point on a face is adjacent to the others
79
+ for i in range(3): edges[face[i]].update([int(face[(i + 1) % 3]), int(face[(i + 2) % 3])])
80
+ self.edges = [tuple(adjacent) for adjacent in edges]
81
+
68
82
  # Model will no longer be used
69
83
  del model
70
84
 
@@ -127,6 +141,12 @@ class Mesh():
127
141
  -icp, -iap, ic
128
142
  )
129
143
 
144
+ # return glm.mat3x3(
145
+ # ic, -iap, -icp,
146
+ # -iap, ib, -ibp,
147
+ # -ibp, -iap, ia
148
+ # )
149
+
130
150
  def get_best_triangle(self, point: glm.vec3, vec: glm.vec3) -> int:
131
151
  """
132
152
  Gets the triangle with the closest intersection, -1 if no intersection is found
@@ -155,51 +175,43 @@ class Mesh():
155
175
  best_index = triangle
156
176
 
157
177
  return best_index
178
+
179
+ def get_best_dot(self, vec: glm.vec3) -> int:
180
+ """
181
+ Gets the point with the highest normalized dot product to the given vector
182
+ """
183
+ triangle = self.bvh.get_best_dot(vec)
184
+ if triangle == -1: return None
185
+ index = max(self.indices[triangle], key=lambda t: glm.dot(glm.normalize(self.points[t]), vec))
186
+ return index
158
187
 
159
- def get_best_triangle_brute(self, point: glm.vec3, vec: glm.vec3) -> int:
188
+ def get_best_dot_hill_climbing(self, vec: glm.vec3) -> int:
160
189
  """
161
- Gets the triangle with the closest intersection, -1 if no intersection is found. Uses a brute force method
190
+ Gets the point with the highest dot product to the given vector using a hill climbing algorithm. This function is only effective for convex models.
162
191
  """
163
- best_distance = -1
164
- best_index = -1
165
-
166
- point = glm.vec3(point)
167
- vec = glm.vec3(vec)
192
+ best_index = 0
193
+ best_dot = glm.dot(self.points[best_index], vec)
168
194
 
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
195
+ while True:
174
196
 
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
197
+ best_changed = False
198
+ for index in self.edges[best_index]:
184
199
 
185
- return best_index
200
+ dot = glm.dot(self.points[index], vec)
201
+ if dot > best_dot:
202
+ best_dot = dot
203
+ best_changed = True
204
+ best_index = index
205
+
206
+ if not best_changed: break
186
207
 
187
- def get_best_dot(self, vec: glm.vec3) -> glm.vec3:
208
+ return best_index
209
+
210
+ def get_line_collided(self, position: glm.vec3, forward: glm.vec3) -> list[tuple[int, int, int]]:
188
211
  """
189
- Gets the point with the highest normalized dot product to the given vector
212
+ Determines which triangles are intersecting with the given line segment. Returns the indices of the triangle contained in the mesh points list
190
213
  """
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
214
+ return self.bvh.get_line_collided(position, forward)
203
215
 
204
216
  def __repr__(self) -> str:
205
217
  size = (self.data.nbytes + self.points.nbytes + self.indices.nbytes) / 1024 / 1024
@@ -214,3 +226,6 @@ class Mesh():
214
226
  x1, y1, z1 = self.top_right
215
227
  x2, y2, z2 = self.bottom_left
216
228
  return [glm.vec3(x, y, z) for z in (z1, z2) for y in (y1, y2) for x in (x1, x2)]
229
+
230
+ def __hash__(self):
231
+ return self.hash
@@ -1,48 +1,133 @@
1
1
  import numpy as np
2
+ from .model import Model
3
+ import glm
2
4
 
3
-
4
- def from_data(data: np.ndarray) -> np.ndarray:
5
+ def from_data(data: np.ndarray) -> Model:
5
6
  """
6
7
  Converts data given to a format compatable with basilisk models
7
8
  """
8
9
 
10
+ # Create an empty model
11
+ model = Model()
12
+ # Get the shape of the given data
13
+
14
+ # Get the shape of the given data and check for a valid shape
9
15
  shape = data.shape
16
+ if len(shape) == 2: pass
17
+ elif len(shape) == 3: data = np.reshape(data, (shape[0] * 3, shape[1] * shape[2] // 3)); shape = data.shape
18
+ else: raise ValueError(f"Could not find valid format for the given model data of shape {shape}")
19
+
20
+ # Data to be retraived/generated
21
+ positions = None
22
+ uvs = None
23
+ normals = None
24
+ tangents = None
10
25
 
11
26
  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
27
+ positions = data[:,:]
28
+ uvs = get_uvs(positions)
29
+ normals = get_normals(positions)
30
+ tangents = get_tangents(normals)
31
+
32
+ elif shape[1] == 5: # Given position and uv, but no normals
33
+ positions = data[:,:3]
34
+ uvs = data[:,3:5]
35
+ normals = get_normals(positions)
36
+ tangents = get_tangents(normals)
17
37
 
18
38
  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
39
+ positions = data[:,:3]
40
+ uvs = get_uvs(positions)
41
+ normals = data[:,3:6]
42
+ tangents = get_tangents(normals)
23
43
 
24
44
  elif shape[1] == 8: # Given position, normals and UV
25
- ...
45
+ positions = data[:,:3]
46
+ uvs = data[:,3:5]
47
+ normals = data[:,5:8]
48
+ tangents = get_tangents(normals)
26
49
 
27
50
  elif shape[1] == 14: #Given position, normals, UV, bitangents, and tangents, no change needed
28
- return data
51
+ positions = data[:,:3]
52
+ uvs = data[:,3:5]
53
+ normals = data[:,5:8]
54
+ tangents = data[:,8:14]
55
+
56
+ else:
57
+ raise ValueError(f"Could not find valid format for the given model data of shape {shape}")
58
+
59
+ model.vertex_data = np.zeros(shape=(shape[0], 14))
60
+ model.vertex_data[:,:3] = positions
61
+ model.vertex_data[:,3:5] = uvs
62
+ model.vertex_data[:,5:8] = normals
63
+ model.vertex_data[:,8:14] = tangents
64
+ model.vertex_points, model.point_indices = get_points_and_indices(positions)
29
65
 
30
- raise ValueError(f"Could not find valid format for the given model data of shape {shape}")
66
+ print(model.vertex_points, model.point_indices)
67
+
68
+ return model
31
69
 
32
70
 
33
71
  def get_normals(positions: np.ndarray) -> np.ndarray:
34
72
  """
35
- Gets the normals for a position array and returns a concatinated array
73
+ Gets the normals from the position data
74
+ Returns a numpy array
36
75
  """
37
76
 
38
77
  # Create empty array for the normals
39
- normals = np.zeros(shape=positions.shape)
78
+ normals = np.zeros(shape=positions.shape, dtype='f4')
40
79
 
41
80
  # Loop through each triangle and calculate the normal of the surface
42
81
  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
82
+ v1 = glm.vec3(positions[tri * 3]) - glm.vec3(positions[tri * 3 + 1])
83
+ v2 = glm.vec3(positions[tri * 3]) - glm.vec3(positions[tri * 3 + 2])
84
+ normal = glm.normalize(glm.cross(v1, v2))
85
+ normals[tri * 3 ] = list(normal.xyz)
86
+ normals[tri * 3 + 1] = list(normal.xyz)
87
+ normals[tri * 3 + 2] = list(normal.xyz)
88
+
89
+ return normals
90
+
91
+ def get_uvs(positions: np.ndarray) -> np.ndarray:
92
+ """
93
+ Gets the uvs from the position array.
94
+ Currently assigns each triangle arbitrarily, since there is no standard
95
+ """
96
+ uvs = np.array([*[[0, 0], [0, 1], [1, 0]] * (positions.shape[0]//3)])
97
+ return uvs
98
+
99
+ def get_tangents(normals: np.array):
100
+ """
101
+ Gets the uvs from the normal array.
102
+ Currently just fills with arbitrary data, since there is no standard
103
+ """
104
+
105
+ # Get linearly independent vectors
106
+ tangent = np.cross(normals, [1, 1, 0])
107
+ bitangent = np.cross(normals, tangent)
108
+
109
+ # Combine to a single array
110
+ all_tangents = np.hstack([tangent, bitangent], dtype='f4')
111
+
112
+ return all_tangents
113
+
114
+
115
+ def get_points_and_indices(positions: np.ndarray) -> tuple[np.ndarray]:
116
+ """
117
+ Gets the unique points and indices from the position data.
118
+ Returns a tuple of numpy arrays: (points, indices)
119
+ """
120
+
121
+ points = {}
122
+ indices = [[] for i in range(len(positions) // 3)]
123
+
124
+ for i, point in enumerate(positions):
125
+ point = tuple(point)
126
+ if point not in points: points[point] = []
127
+ points[point].append(i // 3)
128
+
129
+ for i, index_mapping in enumerate(points.values()):
130
+ for triangle in index_mapping:
131
+ indices[triangle].append(i)
47
132
 
48
- return np.hstack([positions, normals])
133
+ return np.array(list(points.keys()), dtype='f4'), np.array(indices, dtype='i')
@@ -1,4 +1,6 @@
1
1
  import glm
2
+
3
+ from basilisk.generic.collisions import collide_aabb_line
2
4
  from .narrow_primative import NarrowPrimative
3
5
  from ..generic.abstract_bvh import AbstractAABB as AABB
4
6
  from ..generic.meshes import get_aabb_line_collision
@@ -78,4 +80,11 @@ class NarrowAABB(AABB):
78
80
  aabbs += self.b.get_all_aabbs(layer + 1)
79
81
  else: aabbs.append((self.b.top_right, self.b.bottom_left, layer + 1))
80
82
 
81
- return aabbs
83
+ return aabbs
84
+
85
+ def get_line_collided(self, position: glm.vec3, forward: glm.vec3) -> list[int]:
86
+ """
87
+ Returns the colliders that may intersect with the given line
88
+ """
89
+ if not collide_aabb_line(self.top_right, self.bottom_left, position, forward): return []
90
+ return (self.a.get_line_collided(position, forward) if isinstance(self.a, NarrowAABB) else [self.a.index]) + (self.b.get_line_collided(position, forward) if isinstance(self.b, NarrowAABB) else [self.b.index])
@@ -81,4 +81,12 @@ class NarrowBVH(BVH):
81
81
  Returns all AABBs, their extreme points, and their layer
82
82
  """
83
83
  if isinstance(self.root, NarrowAABB): return self.root.get_all_aabbs(0)
84
- return [(self.root.top_right, self.root.bottom_left, 0)]
84
+ return [(self.root.top_right, self.root.bottom_left, 0)]
85
+
86
+ def get_line_collided(self, position: glm.vec3, forward: glm.vec3) -> list[tuple[int, int, int]]:
87
+ """
88
+ Determines which triangles are intersecting with the given line segment. Returns the indices of the triangle contained in the mesh points list
89
+ """
90
+ if isinstance(self.root, NarrowAABB): return self.root.get_line_collided(position, forward)
91
+ return self.root.index
92
+