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/nodes/node.py CHANGED
@@ -8,6 +8,7 @@ from ..render.material import Material
8
8
  from ..physics.physics_body import PhysicsBody
9
9
  from ..collisions.collider import Collider
10
10
  from ..render.chunk import Chunk
11
+ from ..render.shader import Shader
11
12
 
12
13
 
13
14
  class Node():
@@ -17,6 +18,12 @@ class Node():
17
18
  """The scale of the node in meters in each direction"""
18
19
  rotation: Quat
19
20
  """The rotation of the node"""
21
+ position_relative: bool
22
+ """The position of this node relative to the parent node"""
23
+ scale_relative: bool
24
+ """The scale of this node relative to the parent node"""
25
+ rotation_relative: bool
26
+ """The rotation of this node relative to the parent node"""
20
27
  forward: glm.vec3
21
28
  """The forward facing vector of the node"""
22
29
  mesh: Mesh
@@ -53,11 +60,16 @@ class Node():
53
60
  """""" # TODO Jonah description
54
61
  children: list
55
62
  """List of nodes that this node is a parent of"""
63
+ shader: Shader
64
+ """Shader that is used to render the node. If none is given, engine default will be used"""
56
65
 
57
- def __init__(self, node_handler,
66
+ def __init__(self,
58
67
  position: glm.vec3=None,
59
68
  scale: glm.vec3=None,
60
69
  rotation: glm.quat=None,
70
+ relative_position: bool=True,
71
+ relative_scale: bool=True,
72
+ relative_rotation: bool=True,
61
73
  forward: glm.vec3=None,
62
74
  mesh: Mesh=None,
63
75
  material: Material=None,
@@ -73,7 +85,8 @@ class Node():
73
85
  collision_group : float=None,
74
86
  name: str='',
75
87
  tags: list[str]=None,
76
- static: bool=True
88
+ static: bool=None,
89
+ shader: Shader=None
77
90
  ) -> None:
78
91
  """
79
92
  Basilisk node object.
@@ -82,32 +95,42 @@ class Node():
82
95
  """
83
96
 
84
97
  # parents
85
- self.node_handler = node_handler
98
+ self.node_handler = None
99
+ self.scene = None
86
100
  self.chunk = None
101
+ self.parent = None
87
102
 
88
103
  # lazy update variables
89
- self.needs_geometric_center = True
90
- self.needs_model_matrix = True
104
+ self.needs_geometric_center = True # pos
105
+ self.needs_model_matrix = True # pos, scale, rot
91
106
 
92
107
  # node data
93
108
  self.internal_position: Vec3 = Vec3(position) if position else Vec3(0, 0, 0)
94
109
  self.internal_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
95
110
  self.internal_rotation: Quat = Quat(rotation) if rotation else Quat(1, 0, 0, 0)
111
+
112
+ # relative transformations
113
+ self.relative_position = glm.vec3(0, 0, 0) if relative_position else None
114
+ self.relative_scale = glm.vec3(0, 0, 0) if relative_scale else None
115
+ self.relative_rotation = glm.quat(1, 0, 0, 0) if relative_rotation else None
116
+
96
117
  self.forward = forward if forward else glm.vec3(1, 0, 0)
97
- self.mesh = mesh if mesh else self.node_handler.scene.engine.cube
98
- self.material = material if material else None # TODO add default base material
118
+ self.mesh = mesh
119
+ self._mtl_list = material if isinstance(material, list) else [material]
120
+ self.material = material if material else None
99
121
  self.velocity = velocity if velocity else glm.vec3(0, 0, 0)
100
122
  self.rotational_velocity = rotational_velocity if rotational_velocity else glm.vec3(0, 0, 0)
101
123
 
102
- # physics body
103
- if physics: self.physics_body: PhysicsBody = self.node_handler.scene.physics_engine.add(mass = mass if mass else 1.0)
124
+ self.static = static if static != None else not(physics or any(self.velocity) or any(self.rotational_velocity))
125
+
126
+ # Physics updates
127
+ if physics: self.physics_body = PhysicsBody(mass = mass if mass else 1.0)
104
128
  elif mass: raise ValueError('Node: cannot have mass if it does not have physics')
105
129
  else: self.physics_body = None
106
130
 
107
131
  # collider
108
132
  if collisions:
109
- if not mesh: raise ValueError('Node: cannot collide if it doezsnt have a mesh')
110
- self.collider: Collider = self.node_handler.scene.collider_handler.add(
133
+ self.collider = Collider(
111
134
  node = self,
112
135
  box_mesh = True if collider == 'box' else False,
113
136
  static_friction = static_friction,
@@ -121,63 +144,105 @@ class Node():
121
144
  elif elasticity: raise ValueError('Node: cannot have elasticity if it does not allow collisions')
122
145
  elif collision_group: raise ValueError('Node: cannot have collider group if it does not allow collisions')
123
146
  else: self.collider = None
124
-
147
+
125
148
  # information and recursion
126
149
  self.name = name
127
150
  self.tags = tags if tags else []
128
- self.static = static and not (self.physics_body or self.velocity or self.rotational_velocity)
151
+
129
152
  self.data_index = 0
130
153
  self.children = []
154
+
155
+ # Shader given by user or none for default
156
+ self.shader = shader
131
157
 
132
158
  # default callback functions for node transform
133
159
  self.previous_position: Vec3 = Vec3(position) if position else Vec3(0, 0, 0)
134
160
  self.previous_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
135
161
  self.previous_rotation: Quat = Quat(rotation) if rotation else Quat(1, 0, 0, 0) # TODO Do these need to be the callback class or can they just be glm?
136
-
137
- self.position_updated = False
138
- self.scale_updated = False
139
- self.rotation_updated = False
140
-
162
+
141
163
  # callback function to be added to the custom Vec3 and Quat classes
142
164
  def position_callback():
143
- print('position')
144
- self.position_updated = True
165
+ self.chunk.node_update_callback(self)
166
+
167
+ # update variables
168
+ self.needs_geometric_center = True
169
+ self.needs_model_matrix = True
170
+ if self.collider:
171
+ self.collider.needs_bvh = True
172
+ self.collider.needs_obb = True
145
173
 
146
174
  def scale_callback():
147
- print('scale')
148
- self.scale_updated = True
175
+ self.chunk.node_update_callback(self)
176
+
177
+ # update variables
178
+ self.needs_model_matrix = True
179
+ if self.collider:
180
+ self.collider.needs_bvh = True
181
+ self.collider.needs_obb = True
182
+ self.collider.needs_half_dimensions = True
149
183
 
150
184
  def rotation_callback():
151
- print('rotation')
152
- self.rotation_updated = True
185
+ self.chunk.node_update_callback(self)
186
+
187
+ # update variables
188
+ self.needs_model_matrix = True
189
+ if self.collider:
190
+ self.collider.needs_bvh = True
191
+ self.collider.needs_obb = True
192
+ self.collider.needs_half_dimensions = True
153
193
 
154
194
  self.internal_position.callback = position_callback
155
195
  self.internal_scale.callback = scale_callback
156
196
  self.internal_rotation.callback = rotation_callback
157
-
197
+
198
+ def init_scene(self, scene):
199
+ """
200
+ Updates the scene of the node
201
+ """
202
+ self.scene = scene
203
+ self.node_handler = scene.node_handler
204
+
205
+ # Update materials
206
+ self.write_materials()
207
+
208
+ # Update the mesh
209
+ self.mesh = self.mesh if self.mesh else self.scene.engine.cube
210
+
211
+ # Update physics and collider
212
+ if self.physics_body: self.physics_body.physics_engine = scene.physics_engine
213
+ if self.collider: self.collider.collider_handler = scene.collider_handler
214
+
158
215
  def update(self, dt: float) -> None:
159
216
  """
160
217
  Updates the node's movement variables based on the delta time
161
218
  """
162
- # update all hard-to-update variables
163
-
164
-
165
- # reset updates
166
- self.position_updated = False
167
- self.scale_updated = False
168
- self.rotation_updated = False
169
-
170
- self.position += dt * self.velocity
171
- self.rotation += 0.5 * dt * self.rotation * glm.quat(0, *self.rotational_velocity)
172
- self.rotation = glm.normalize(self.rotation)
173
- self.scale = self.scale
219
+ # update based on physical properties
220
+ if any(self.velocity): self.position += dt * self.velocity
221
+ if any(self.rotational_velocity): self.rotation = glm.normalize(self.rotation - dt / 2 * self.rotation * glm.quat(0, *self.rotational_velocity))
174
222
 
175
223
  if self.physics_body:
176
224
  self.velocity += self.physics_body.get_delta_velocity(dt)
177
225
  self.rotational_velocity += self.physics_body.get_delta_rotational_velocity(dt)
226
+
227
+ # update children transforms
228
+ for child in self.children: child.sync_data()
178
229
 
179
- def sync_data(self, dt: float) -> None: # TODO only needed for child nodes now
180
- ...
230
+ def sync_data(self) -> None:
231
+ """
232
+ Syncronizes this node with the parent node based on its relative positioning
233
+ """
234
+ # calculate transform matrix with the given input
235
+ transform = glm.mat4x4()
236
+ if self.relative_position: transform = glm.translate(transform, self.parent.position)
237
+ if self.relative_rotation: transform *= glm.transpose(glm.mat4_cast(self.parent.rotation))
238
+ if self.relative_scale: transform = glm.scale(transform, self.parent.scale)
239
+
240
+ # set this node's transforms based on the parent
241
+ self.position = transform * self.relative_position
242
+ self.scale = self.relative_scale * self.parent.scale
243
+ self.rotation = self.relative_rotation * self.parent.rotation
244
+
245
+ for child in self.children: child.sync_data()
181
246
 
182
247
  def get_nodes(self,
183
248
  require_mesh: bool=False,
@@ -203,13 +268,33 @@ class Node():
203
268
 
204
269
  # adds children to nodes list if they match the criteria
205
270
  for node in self.children: nodes.extend(node.get_nodes(require_mesh, require_collider, require_physics_body, filter_material, filter_tags))
206
- return node
271
+ return nodes
207
272
 
208
- def adopt_child(self, node) -> None: # TODO determine the best way for the user to do this through the scene
209
- ...
273
+ # tree functions for managing children
274
+ def add(self, child: ..., relative_position: bool=None, relative_scale: bool=None, relative_rotation: glm.vec3=None) -> None:
275
+ """
276
+ Adopts a node as a child. Relative transforms can be changed, if left bank they will not be chnaged from the current child nodes settings.
277
+ """
278
+ if child in self.children: return
279
+
280
+ # compute relative transformations
281
+ if relative_position or (relative_position is None and child.relative_position): child.relative_position = child.position - self.position
282
+ if relative_scale or (relative_scale is None and child.relative_scale): child.relative_scale = child.scale / self.scale
283
+ if relative_rotation or (relative_rotation is None and child.relative_rotation): child.relative_rotation = child.rotation * glm.inverse(self.rotation)
284
+
285
+ # add as a child to by synchronized and controlled
286
+ if self.node_handler: self.node_handler.add(child)
287
+ child.parent = self
288
+ self.children.append(child)
210
289
 
211
- def add_child(self) -> None: # TODO add node constructor
212
- ...
290
+ def remove(self, child: ...) -> None:
291
+ """
292
+ Removes a child node from this nodes chlid list.
293
+ """
294
+ if child in self.children:
295
+ if self.node_handler: self.node_handler.remove(child)
296
+ child.parent = None
297
+ self.children.remove(child)
213
298
 
214
299
  def apply_force(self, force: glm.vec3, dt: float) -> None:
215
300
  """
@@ -242,16 +327,22 @@ class Node():
242
327
  Transforms the mesh inertia tensor and inverts it
243
328
  """
244
329
  if not (self.mesh and self.physics_body): return None
245
- inertia_tensor = self.mesh.get_inertia_tensor(self.scale)
330
+ inertia_tensor = self.mesh.get_inertia_tensor(self.scale) / 2
246
331
 
247
332
  # mass
248
333
  if self.physics_body: inertia_tensor *= self.physics_body.mass
249
334
 
250
335
  # rotation
251
336
  rotation_matrix = glm.mat3_cast(self.rotation)
252
- inertia_tensor = rotation_matrix * inertia_tensor * glm.transpose(rotation_matrix)
337
+ inertia_tensor = rotation_matrix * inertia_tensor * glm.transpose(rotation_matrix)
253
338
 
254
339
  return glm.inverse(inertia_tensor)
340
+
341
+ def get_vertex(self, index) -> glm.vec3:
342
+ """
343
+ Gets the world space position of a vertex indicated by the index in the mesh
344
+ """
345
+ return glm.vec3(self.model_matrix * glm.vec4(*self.mesh.points[index], 1))
255
346
 
256
347
  def get_data(self) -> np.ndarray:
257
348
  """
@@ -260,15 +351,41 @@ class Node():
260
351
 
261
352
  # Get data from the mesh node
262
353
  mesh_data = self.mesh.data
263
- node_data = np.array([*self.position, *self.rotation, *self.scale, self.material.index])
354
+ node_data = np.array([*self.position, *self.rotation, *self.scale, 0])
355
+
356
+ per_vertex_mtl = isinstance(self.material, list)
357
+
358
+ if not per_vertex_mtl: node_data[-1] = self.material.index
264
359
 
265
360
  # Create an array to hold the node's data
266
361
  data = np.zeros(shape=(mesh_data.shape[0], 25), dtype='f4')
362
+
363
+
267
364
  data[:,:14] = mesh_data
268
365
  data[:,14:] = node_data
269
366
 
367
+ if per_vertex_mtl: data[:,24] = self.material
368
+
270
369
  return data
271
370
 
371
+ def write_materials(self):
372
+ """
373
+ Internal function to write the material list to the material handler and get the material ids
374
+ """
375
+
376
+ if isinstance(self.material, list):
377
+ mtl_index_list = []
378
+ for mtl in self._mtl_list:
379
+ self.node_handler.scene.material_handler.add(mtl)
380
+ mtl_index_list.append(mtl.index)
381
+ mtl_index_list.append(mtl.index)
382
+ mtl_index_list.append(mtl.index)
383
+ self._material = mtl_index_list
384
+
385
+ if isinstance(self.material, type(None)):
386
+ self.material = self.scene.material_handler.base
387
+
388
+
272
389
  def __repr__(self) -> str:
273
390
  """
274
391
  Returns a string representation of the node
@@ -284,7 +401,8 @@ class Node():
284
401
  def rotation(self): return self.internal_rotation.data
285
402
  @property
286
403
  def forward(self): return self._forward
287
- # TODO add property for Mesh
404
+ @property
405
+ def mesh(self): return self._mesh
288
406
  @property
289
407
  def material(self): return self._material
290
408
  @property
@@ -347,21 +465,25 @@ class Node():
347
465
 
348
466
  @position.setter
349
467
  def position(self, value: tuple | list | glm.vec3 | np.ndarray):
350
- if isinstance(value, glm.vec3): self.internal_position.data = glm.vec3(value)
468
+ if isinstance(value, glm.vec3): self.internal_position.data = value
351
469
  elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
352
470
  if len(value) != 3: raise ValueError(f'Node: Invalid number of values for position. Expected 3, got {len(value)}')
353
471
  self.internal_position.data = glm.vec3(value)
354
472
  else: raise TypeError(f'Node: Invalid position value type {type(value)}')
355
- self.update_position()
473
+
474
+ # # compute relative position if all cases passed
475
+ # if self.parent and self.relative_position: self.relative_position = self.internal_position.data - self.parent.position
356
476
 
357
477
  @scale.setter
358
478
  def scale(self, value: tuple | list | glm.vec3 | np.ndarray):
359
- if isinstance(value, glm.vec3): self.internal_scale.data = glm.vec3(value)
479
+ if isinstance(value, glm.vec3): self.internal_scale.data = value
360
480
  elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
361
481
  if len(value) != 3: raise ValueError(f'Node: Invalid number of values for scale. Expected 3, got {len(value)}')
362
482
  self.internal_scale.data = glm.vec3(value)
363
483
  else: raise TypeError(f'Node: Invalid scale value type {type(value)}')
364
- self.update_scale()
484
+
485
+ # # compute relative scale
486
+ # if self.parent and self.relative_scale: self.relative_scale = self.internal_scale.data / self.parent.scale
365
487
 
366
488
  @rotation.setter
367
489
  def rotation(self, value: tuple | list | glm.vec3 | glm.quat | glm.vec4 | np.ndarray):
@@ -371,66 +493,54 @@ class Node():
371
493
  elif len(value) == 4: self.internal_rotation.data = glm.quat(*value)
372
494
  else: raise ValueError(f'Node: Invalid number of values for rotation. Expected 3 or 4, got {len(value)}')
373
495
  else: raise TypeError(f'Node: Invalid rotation value type {type(value)}')
374
- self.update_rotation()
375
-
376
- def update_position(self): # TODO Could these be repeated in a Vec3/Quat Callback function?
377
- """
378
- Updates the rotation
379
- """
380
496
 
381
- thresh = 0.01
382
- cur = self.position
383
- prev = self.previous_position
384
-
385
- if self.chunk and (abs(cur.x - prev.x) > thresh or abs(cur.y - prev.y) > thresh or abs(cur.z - prev.z) > thresh):
386
- self.chunk.node_update_callback(self)
387
- self.previous_position = self.position
388
-
389
- def update_scale(self):
390
- """
391
- Updates the rotation
392
- """
393
-
394
- thresh = 0.01
395
- cur = self.scale
396
- prev = self.previous_scale
397
-
398
- if self.chunk and (abs(cur.x - prev.x) > thresh or abs(cur.y - prev.y) > thresh or abs(cur.z - prev.z) > thresh):
399
- self.chunk.node_update_callback(self)
400
- self.previous_scale = self.scale
401
-
402
- def update_rotation(self):
403
- """
404
- Updates the rotation
405
- """
406
-
407
- thresh = 0.01
408
- cur = self.rotation
409
- prev = self.previous_rotation
410
-
411
- if self.chunk and (abs(cur.x - prev.x) > thresh or abs(cur.y - prev.y) > thresh or abs(cur.z - prev.z) > thresh or abs(cur.w - prev.w) > thresh):
412
- self.chunk.node_update_callback(self)
413
- self.previous_rotation = self.rotation
414
-
497
+ # # compute relative rotation
498
+ # if self.parent and self.relative_rotation: self.relative_rotation = self.rotation * glm.inverse(self.parent.rotation)
415
499
 
416
500
  @forward.setter
417
501
  def forward(self, value: tuple | list | glm.vec3 | np.ndarray):
418
- if isinstance(value, glm.vec3): self._forward = glm.vec3(value)
502
+ if isinstance(value, glm.vec3): self._forward = value
419
503
  elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
420
504
  if len(value) != 3: raise ValueError(f'Node: Invalid number of values for forward. Expected 3, got {len(value)}')
421
505
  self._forward = glm.vec3(value)
422
506
  else: raise TypeError(f'Node: Invalid forward value type {type(value)}')
423
507
 
424
- # TODO add setter for Mesh
508
+ @mesh.setter
509
+ def mesh(self, value: Mesh | None):
510
+ if isinstance(value, Mesh):
511
+ self._mesh = value
512
+ if self.chunk: self.chunk.update()
513
+ elif isinstance(value, type(None)):
514
+ self._mesh = None
515
+ if not self.chunk: return
516
+ self.chunk.remove(self)
517
+ self.chunk.update()
518
+ else: raise TypeError(f'Node: Invalid mesh value type {type(value)}')
425
519
 
426
520
  @material.setter
427
521
  def material(self, value: Material):
428
- if isinstance(value, Material):
522
+ if isinstance(value, list):
523
+ self._mtl_list = value
524
+ if not self.node_handler:
525
+ self._material = value
526
+ else:
527
+ mtl_index_list = []
528
+ for mtl in self._mtl_list:
529
+ self.node_handler.scene.material_handler.add(mtl)
530
+ mtl_index_list.append(mtl.index)
531
+ mtl_index_list.append(mtl.index)
532
+ mtl_index_list.append(mtl.index)
533
+ self._material = mtl_index_list
534
+ elif isinstance(value, Material):
429
535
  self._material = value
430
- self.node_handler.scene.material_handler.add(value)
431
- if not self.chunk: return
432
- self.chunk.node_update_callback(self)
536
+ if self.node_handler: self.node_handler.scene.material_handler.add(value)
537
+ elif isinstance(value, type(None)):
538
+ if self.scene: self._material = self.scene.material_handler.base
539
+ else: self._material = None
540
+
433
541
  else: raise TypeError(f'Node: Invalid material value type {type(value)}')
542
+ if not self.chunk: return
543
+ self.chunk.node_update_callback(self)
434
544
 
435
545
  @velocity.setter
436
546
  def velocity(self, value: tuple | list | glm.vec3 | np.ndarray):
@@ -493,15 +603,15 @@ class Node():
493
603
 
494
604
  @x.setter
495
605
  def x(self, value: int | float):
496
- if isinstance(value, int) or isinstance(value, float): self.internal_position.data.x = value
606
+ if isinstance(value, int) or isinstance(value, float): self.internal_position.x = value
497
607
  else: raise TypeError(f'Node: Invalid positional x value type {type(value)}')
498
608
 
499
609
  @y.setter
500
610
  def y(self, value: int | float):
501
- if isinstance(value, int) or isinstance(value, float): self.internal_position.data.y = value
611
+ if isinstance(value, int) or isinstance(value, float): self.internal_position.y = value
502
612
  else: raise TypeError(f'Node: Invalid positional y value type {type(value)}')
503
613
 
504
614
  @z.setter
505
615
  def z(self, value: int | float):
506
- if isinstance(value, int) or isinstance(value, float): self.internal_position.data.z = value
616
+ if isinstance(value, int) or isinstance(value, float): self.internal_position.z = value
507
617
  else: raise TypeError(f'Node: Invalid positional z value type {type(value)}')
@@ -25,7 +25,9 @@ class NodeHandler():
25
25
  """
26
26
  Updates the nodes and chunks in the scene
27
27
  """
28
- for node in self.nodes: node.update(self.scene.engine.delta_time)
28
+ for node in self.nodes:
29
+ if node.static: continue
30
+ node.update(self.scene.engine.delta_time)
29
31
  self.chunk_handler.update()
30
32
 
31
33
  def render(self):
@@ -35,60 +37,83 @@ class NodeHandler():
35
37
 
36
38
  self.chunk_handler.render()
37
39
 
38
- def add(self,
39
- position: glm.vec3=None,
40
- scale: glm.vec3=None,
41
- rotation: glm.quat=None,
42
- forward: glm.vec3=None,
43
- mesh: Mesh=None,
44
- material: Material=None,
45
- velocity: glm.vec3=None,
46
- rotational_velocity: glm.quat=None,
47
- physics: bool=False,
48
- mass: float=None,
49
- collisions: bool=False,
50
- collider: str=None,
51
- static_friction: float=None,
52
- kinetic_friction: float=None,
53
- elasticity: float=None,
54
- collision_group : float=None,
55
- name: str='',
56
- tags: list[str]=None,
57
- static: bool=True
58
- ) -> Node:
40
+ def add(self, node: Node) -> Node:
59
41
  """
60
42
  Adds a new node to the node handler
61
43
  """
44
+ if node in self.nodes: return
62
45
 
63
- node = Node(self, position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, collider, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static)
64
-
65
- self.nodes.append(node)
66
- self.chunk_handler.add(node)
46
+ for n in node.get_nodes(): # gets all nodes including the node to be added
47
+
48
+ # Update scene Handlers
49
+ self.scene.shader_handler.add(n.shader)
50
+ if not n.material: n.material = self.scene.material_handler.base
51
+ self.scene.material_handler.add(n.material)
52
+
53
+ # Update the node attributes
54
+ n.init_scene(self.scene)
55
+
56
+ # Add the node to internal data
57
+ self.nodes.append(n)
58
+ self.chunk_handler.add(n)
67
59
 
68
60
  return node
61
+
62
+ def node_is(self, node: Node, position: glm.vec3=None, scale: glm.vec3=None, rotation: glm.quat=None, forward: glm.vec3=None, mesh: Mesh=None, material: Material=None, velocity: glm.vec3=None, rotational_velocity: glm.quat=None, physics: bool=None, mass: float=None, collisions: bool=None, static_friction: float=None, kinetic_friction: float=None, elasticity: float=None, collision_group: float=None, name: str=None, tags: list[str]=None,static: bool=None) -> bool:
63
+ """
64
+ Determine if a node meets the requirements given by the parameters. If a parameter is None, then the filter is not applied.
65
+ """
66
+ return all([
67
+ position is None or position == node.position,
68
+ scale is None or scale == node.scale,
69
+ rotation is None or rotation == node.rotation,
70
+ forward is None or forward == node.forward,
71
+ mesh is None or mesh == node.mesh,
72
+ material is None or material == node.material,
73
+ velocity is None or velocity == node.velocity,
74
+ rotational_velocity is None or rotational_velocity == node.rotational_velocity,
75
+ physics is None or bool(node.physics_body) == physics,
76
+ mass is None or (node.physics_body and mass == node.physics_body.mass),
77
+ collisions is None or bool(node.collider) == collisions,
78
+ static_friction is None or (node.collider and node.collider.static_friction == static_friction),
79
+ kinetic_friction is None or (node.collider and node.collider.kinetic_friction == kinetic_friction),
80
+ elasticity is None or (node.collider and node.collider.elasticity == elasticity),
81
+ collision_group is None or (node.collider and node.collider.collision_group == collision_group),
82
+ name is None or node.name == name,
83
+ tags is None or all([tag in node.tags for tag in tags]),
84
+ static is None or node.static == static
85
+ ])
69
86
 
70
- def get(self, name: str) -> Node: # TODO switch to full filter and adapt to search roots
87
+ def get(self, position: glm.vec3=None, scale: glm.vec3=None, rotation: glm.quat=None, forward: glm.vec3=None, mesh: Mesh=None, material: Material=None, velocity: glm.vec3=None, rotational_velocity: glm.quat=None, physics: bool=None, mass: float=None, collisions: bool=None, static_friction: float=None, kinetic_friction: float=None, elasticity: float=None, collision_group: float=None, name: str=None, tags: list[str]=None,static: bool=None) -> Node:
71
88
  """
72
89
  Returns the first node with the given traits
73
90
  """
74
91
  for node in self.nodes:
75
- if node.name == name: return node
92
+ if self.node_is(node, position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static): return node
76
93
  return None
77
94
 
78
- def get_all(self, name: str) -> Node:
95
+ def get_all(self, position: glm.vec3=None, scale: glm.vec3=None, rotation: glm.quat=None, forward: glm.vec3=None, mesh: Mesh=None, material: Material=None, velocity: glm.vec3=None, rotational_velocity: glm.quat=None, physics: bool=None, mass: float=None, collisions: bool=None, static_friction: float=None, kinetic_friction: float=None, elasticity: float=None, collision_group: float=None, name: str=None, tags: list[str]=None,static: bool=None) -> Node:
79
96
  """
80
97
  Returns all nodes with the given traits
81
98
  """
82
99
  nodes = []
83
100
  for node in self.nodes:
84
- if node.name == name: nodes.append(node)
101
+ if self.node_is(node, position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static): nodes.append(node)
85
102
  return nodes
86
103
 
87
104
  def remove(self, node: Node) -> None:
88
105
  """
89
106
  Removes a node and all of its children from their handlers
90
107
  """
108
+
109
+ if node == None: return
110
+
91
111
  # TODO add support for recursive nodes
92
- if node.physics_body: self.scene.physics_engine.remove(node.physics_body)
93
- if node.collider: self.scene.collider_handler.remove(node.collider)
94
- node.node_handler = None
112
+ if node in self.nodes:
113
+ if node.physics_body: self.scene.physics_engine.remove(node.physics_body)
114
+ if node.collider: self.scene.collider_handler.remove(node.collider)
115
+ self.chunk_handler.remove(node)
116
+ self.nodes.remove(node)
117
+ node.node_handler = None
118
+
119
+ for child in node.children: self.remove(child)
File without changes