basilisk-engine 0.0.9__py3-none-any.whl → 0.1.1__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 (61) hide show
  1. basilisk/__init__.py +3 -1
  2. basilisk/collisions/broad/broad_aabb.py +8 -1
  3. basilisk/collisions/broad/broad_bvh.py +38 -2
  4. basilisk/collisions/collider.py +20 -10
  5. basilisk/collisions/collider_handler.py +97 -32
  6. basilisk/collisions/narrow/contact_manifold.py +91 -0
  7. basilisk/collisions/narrow/dataclasses.py +27 -0
  8. basilisk/collisions/narrow/deprecated.py +47 -0
  9. basilisk/collisions/narrow/epa.py +21 -15
  10. basilisk/collisions/narrow/gjk.py +15 -14
  11. basilisk/collisions/narrow/graham_scan.py +25 -0
  12. basilisk/collisions/narrow/helper.py +14 -7
  13. basilisk/collisions/narrow/line_intersections.py +107 -0
  14. basilisk/collisions/narrow/sutherland_hodgman.py +76 -0
  15. basilisk/draw/draw_handler.py +7 -5
  16. basilisk/engine.py +28 -6
  17. basilisk/generic/abstract_custom.py +134 -0
  18. basilisk/generic/collisions.py +47 -2
  19. basilisk/generic/quat.py +84 -65
  20. basilisk/generic/vec3.py +99 -67
  21. basilisk/input/mouse.py +3 -3
  22. basilisk/mesh/cube.py +20 -2
  23. basilisk/mesh/mesh.py +69 -54
  24. basilisk/mesh/mesh_from_data.py +106 -21
  25. basilisk/mesh/narrow_aabb.py +10 -1
  26. basilisk/mesh/narrow_bvh.py +9 -1
  27. basilisk/nodes/node.py +211 -101
  28. basilisk/nodes/node_handler.py +58 -33
  29. basilisk/particles/__init__.py +0 -0
  30. basilisk/particles/particle_handler.py +55 -0
  31. basilisk/particles/particle_renderer.py +87 -0
  32. basilisk/physics/impulse.py +113 -0
  33. basilisk/physics/physics_body.py +10 -2
  34. basilisk/physics/physics_engine.py +2 -3
  35. basilisk/render/batch.py +3 -1
  36. basilisk/render/camera.py +35 -1
  37. basilisk/render/chunk.py +19 -4
  38. basilisk/render/chunk_handler.py +39 -23
  39. basilisk/render/image.py +1 -1
  40. basilisk/render/image_handler.py +17 -14
  41. basilisk/render/light_handler.py +16 -11
  42. basilisk/render/material.py +38 -14
  43. basilisk/render/material_handler.py +31 -18
  44. basilisk/render/shader.py +110 -0
  45. basilisk/render/shader_handler.py +20 -35
  46. basilisk/render/sky.py +8 -5
  47. basilisk/scene.py +116 -33
  48. basilisk/shaders/batch.frag +40 -11
  49. basilisk/shaders/batch.vert +14 -7
  50. basilisk/shaders/geometry.frag +9 -0
  51. basilisk/shaders/geometry.vert +42 -0
  52. basilisk/shaders/normal.frag +60 -0
  53. basilisk/shaders/normal.vert +97 -0
  54. basilisk/shaders/particle.frag +72 -0
  55. basilisk/shaders/particle.vert +85 -0
  56. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/METADATA +5 -5
  57. basilisk_engine-0.1.1.dist-info/RECORD +95 -0
  58. basilisk/shaders/image.png +0 -0
  59. basilisk_engine-0.0.9.dist-info/RECORD +0 -78
  60. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/WHEEL +0 -0
  61. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/top_level.txt +0 -0
basilisk/generic/quat.py CHANGED
@@ -1,81 +1,86 @@
1
1
  import glm
2
2
  import numpy as np
3
+ from .abstract_custom import Custom
3
4
 
4
5
 
5
- class Quat():
6
+ class Quat(Custom):
6
7
  def __init__(self, *args, callback=None):
7
8
  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)
9
+ self.prev_data = glm.quat(1, 0, 0, 0)
15
10
 
16
- def set_data(self, *args):
17
- """
18
- Sets the internal vector inplace
19
- """
20
- # overload constructor TODO nvernest this, definitely possible
21
11
  if len(args) == 1:
12
+
22
13
  if isinstance(args[0], Quat):
23
14
  self.data = glm.quat(args[0].data)
15
+ self.prev_data = glm.quat(args[0].prev_data)
24
16
  self.callback = args[0].callback
25
- elif isinstance(args[0], glm.quat): self.data = glm.quat(args[0])
17
+
18
+ elif isinstance(args[0], glm.quat):
19
+ self.data = args[0]
20
+
26
21
  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])}')
22
+ assert 2 < len(args[0]) < 5, f'Quat: Expected 3 or 4 values from incoming vector, got {len(args[0])}'
28
23
  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')
24
+
25
+ else:
26
+ try: self.data = glm.quat(args[0])
27
+ except: raise ValueError(f'Quat: Unexpected incoming quaternion type {args[0]}')
28
+
29
+ elif 2 < len(args) < 5: self.data = glm.quat(*args)
30
+ else: raise ValueError(f'Quat: Expected either a vector, quaternion, or 3 or 4 numbers, got {len(args)} values')
31
+
32
+ def apply_function(self, other, func, func_name):
33
+ quat = glm.quat(self.data)
34
+
35
+ if isinstance(other, (glm.vec3, glm.quat)):
36
+ quat = func(quat, other)
37
+
38
+ elif isinstance(other, (tuple, list, np.ndarray)):
39
+ assert 2 < len(other) < 5, f'Quat: Expected 3 or 4 values from incoming vector, got {len(other)}'
40
+ quat = func(quat, other)
41
+
42
+ elif isinstance(other, Custom):
43
+ quat = func(quat, other.data)
44
+
45
+ else:
46
+ try: quat = func(quat, other)
47
+ except: raise ValueError(f'Quat: Not an accepted type for {func_name}, got {type(other)}')
48
+ return Quat(quat)
32
49
 
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
50
+ # unary operators
51
+ def __neg__(self):
52
+ return Quat(-self.data, callback=self.callback)
51
53
 
52
- def __imul__(self, other):
53
- # TODO add checks for number types
54
- self.data *= other
55
- return self
54
+ def __abs__(self):
55
+ return Quat(abs(self.data), callback=self.callback)
56
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
57
+ # accessor functions
68
58
  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}')
59
+ assert int(index) == index, f'Quat: index must be an int, got {type(index)}' # check if index is a float
60
+ assert 0 <= index <= 3, f'Quat: index out of bounds, got {index}'
71
61
  return self.data[index]
72
62
 
73
63
  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}')
64
+ assert int(index) == index, f'Quat: index must be an int, got {type(index)}' # check if index is a float
65
+ assert 0 <= index <= 3, f'Quat: index out of bounds, got {index}'
76
66
  try: self.data[index] = value
77
67
  except: raise ValueError(f'Quat: Invalid element type, got {type(value)}')
78
68
 
69
+ def __delitem__(self, index): # cannot delete an index from a quaternion, so we set the value to zero instead
70
+ assert int(index) == index, f'Quat: index must be an int, got {type(index)}' # check if index is a float
71
+ assert 0 <= index <= 3, f'Quat: index out of bounds, got {index}'
72
+ self.data[index] = 0
73
+
74
+ def __len__(self):
75
+ return 4
76
+
77
+ def __iter__(self):
78
+ return iter(self.data)
79
+
80
+ def __contains__(self, item):
81
+ return item in self.data
82
+
83
+ # override str operators
79
84
  def __repr__(self):
80
85
  return str(self.data)
81
86
 
@@ -94,26 +99,40 @@ class Quat():
94
99
  def z(self): return self.data.z
95
100
 
96
101
  @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()
102
+ def data(self, value: glm.vec3 | glm.quat):
103
+ self._data = glm.quat(value)
104
+ cur = self._data
105
+ prev = self.prev_data
106
+ thresh = 1e-6
107
+
108
+ if self.callback and (abs(cur.w - prev.w) > thresh or abs(cur.x - prev.x) > thresh or abs(cur.y - prev.y) > thresh or abs(cur.z - prev.z) > thresh):
109
+ self.prev_data = glm.quat(self._data)
110
+ self.callback()
100
111
 
101
112
  @w.setter
102
113
  def w(self, value):
103
114
  self.data.w = value
104
- if self.callback: self.callback()
115
+ if self.callback and abs(value - self.prev_data.w) > 1e-6:
116
+ self.prev_data.w = value
117
+ self.callback()
105
118
 
106
119
  @x.setter
107
120
  def x(self, value):
108
- self.data.x = value
109
- if self.callback: self.callback()
121
+ self._data.x = value
122
+ if self.callback and abs(value - self.prev_data.x) > 1e-6:
123
+ self.prev_data.x = value
124
+ self.callback()
110
125
 
111
126
  @y.setter
112
127
  def y(self, value):
113
- self.data.y = value
114
- if self.callback: self.callback()
128
+ self._data.y = value
129
+ if self.callback and abs(value - self.prev_data.y) > 1e-6:
130
+ self.prev_data.y = value
131
+ self.callback()
115
132
 
116
133
  @z.setter
117
134
  def z(self, value):
118
- self.data.z = value
119
- if self.callback: self.callback()
135
+ self._data.z = value
136
+ if self.callback and abs(value - self.prev_data.z) > 1e-6:
137
+ self.prev_data.z = value
138
+ self.callback()
basilisk/generic/vec3.py CHANGED
@@ -1,86 +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
- self.set_data(*args)
9
-
10
- def normalize(self):
11
- """
12
- Inplace normalizes the vector
13
- """
14
- self.data = glm.normalize(self.data)
8
+ self.prev_data = glm.vec3(0.0)
15
9
 
16
- def set_data(self, *args):
17
- """
18
- Sets the internal vector inplace
19
- """
20
- # overload constructor TODO nvernest this, definitely possible
21
10
  if len(args) == 1:
11
+
22
12
  if isinstance(args[0], Vec3):
23
13
  self.data = glm.vec3(args[0].data)
14
+ self.prev_data = glm.vec3(args[0].prev_data)
24
15
  self.callback = args[0].callback
25
- 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
+
26
20
  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])}')
21
+ assert len(args[0]) == 3, f'Vec3: Expected 3 values from incoming vector, got {len(args[0])}'
28
22
  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])
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)
31
29
  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
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
+
45
37
  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
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)
61
52
 
62
- def __ifloordiv__(self, other):
63
- # TODO add checks for number types
64
- self.data //= other
65
- return self
53
+ def __abs__(self):
54
+ return Vec3(abs(self.data), callback=self.callback)
66
55
 
67
- # override [_] accessor
56
+ # accessor functions
68
57
  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}')
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}'
71
60
  return self.data[index]
72
61
 
73
62
  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}')
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}'
76
65
  try: self.data[index] = value
77
66
  except: raise ValueError(f'Vec3: Invalid element type, got {type(value)}')
78
67
 
79
- def __repr__(self):
80
- return str(self.data)
81
-
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
+
82
76
  def __iter__(self):
83
- 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)
84
88
 
85
89
  @property
86
90
  def data(self): return self._data
@@ -93,20 +97,48 @@ class Vec3():
93
97
 
94
98
  @data.setter
95
99
  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()
100
+ self._data = glm.vec3(value)
101
+ cur = self._data
102
+ prev = self.prev_data
103
+ thresh = 1e-6
104
+
105
+ if self.callback and (abs(cur.x - prev.x) > thresh or abs(cur.y - prev.y) > thresh or abs(cur.z - prev.z) > thresh):
106
+ self.prev_data = glm.vec3(self._data)
107
+ self.callback()
98
108
 
99
109
  @x.setter
100
110
  def x(self, value):
101
- self.data.x = value
102
- 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()
103
115
 
104
116
  @y.setter
105
117
  def y(self, value):
106
- self.data.y = value
107
- 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()
108
122
 
109
123
  @z.setter
110
124
  def z(self, value):
111
- self.data.z = value
112
- 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/input/mouse.py CHANGED
@@ -34,11 +34,11 @@ class Mouse():
34
34
  pg.mouse.set_pos(x, y)
35
35
 
36
36
  @property
37
- def click(self): return self.previous_buttons[0] and not self.buttons[0]
37
+ def click(self): return self.buttons[0] and not self.previous_buttons[0]
38
38
  @property
39
- def middle_click(self): return self.previous_buttons[1] and not self.buttons[1]
39
+ def middle_click(self): return self.buttons[1] and not self.previous_buttons[1]
40
40
  @property
41
- def right_click(self): return self.previous_buttons[2] and not self.buttons[2]
41
+ def right_click(self): return self.buttons[2] and not self.previous_buttons[2]
42
42
  @property
43
43
  def left_down(self): return self.buttons[0]
44
44
  @property
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