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
basilisk/nodes/node.py ADDED
@@ -0,0 +1,508 @@
1
+ import glm
2
+ import numpy as np
3
+ from ..generic.vec3 import Vec3
4
+ from ..generic.quat import Quat
5
+ from ..generic.matrices import get_model_matrix
6
+ from ..mesh.mesh import Mesh
7
+ from ..mesh.cube import cube
8
+ from ..render.material import Material
9
+ from ..physics.physics_body import PhysicsBody
10
+ from ..collisions.collider import Collider
11
+ from ..render.chunk import Chunk
12
+
13
+
14
+ class Node():
15
+ position: Vec3
16
+ """The position of the node in meters with swizzle xyz"""
17
+ scale: Vec3
18
+ """The scale of the node in meters in each direction"""
19
+ rotation: Quat
20
+ """The rotation of the node"""
21
+ forward: glm.vec3
22
+ """The forward facing vector of the node"""
23
+ mesh: Mesh
24
+ """The mesh of the node stored as a basilisk mesh object"""
25
+ material: Material
26
+ """The mesh of the node stored as a basilisk material object"""
27
+ velocity: glm.vec3
28
+ """The translational velocity of the node"""
29
+ rotational_velocity: glm.vec3
30
+ """The rotational velocity of the node"""
31
+ physics: bool
32
+ """Allows the node's movement to be affected by the physics engine and collisions"""
33
+ mass: float
34
+ """The mass of the node in kg"""
35
+ collisions: bool
36
+ """Gives the node collision with other nodes in the scene"""
37
+ collider: str
38
+ """The collider type of the node. Can be either 'box' or 'mesh'"""
39
+ static_friction: float
40
+ """Determines the friction of the node when still: recommended value 0.0 - 1.0"""
41
+ kinetic_friction: float
42
+ """Determines the friction of the node when moving: recommended value 0.0 - 1.0"""
43
+ elasticity: float
44
+ """Determines how bouncy an object is: recommended value 0.0 - 1.0"""
45
+ collision_group: str
46
+ """Nodes of the same collision group do not collide with each other"""
47
+ name: str
48
+ """The name of the node for reference"""
49
+ tags: list[str]
50
+ """Tags are used to sort nodes into separate groups"""
51
+ static: bool
52
+ """Objects that don't move should be marked as static"""
53
+ chunk: Chunk
54
+ """""" # TODO Jonah description
55
+ children: list
56
+ """List of nodes that this node is a parent of"""
57
+
58
+ def __init__(self, node_handler,
59
+ position: glm.vec3=None,
60
+ scale: glm.vec3=None,
61
+ rotation: glm.quat=None,
62
+ forward: glm.vec3=None,
63
+ mesh: Mesh=None,
64
+ material: Material=None,
65
+ velocity: glm.vec3=None,
66
+ rotational_velocity: glm.vec3=None,
67
+ physics: bool=False,
68
+ mass: float=None,
69
+ collisions: bool=False,
70
+ collider: str=None,
71
+ static_friction: float=None,
72
+ kinetic_friction: float=None,
73
+ elasticity: float=None,
74
+ collision_group : float=None,
75
+ name: str='',
76
+ tags: list[str]=None,
77
+ static: bool=True
78
+ ) -> None:
79
+ """
80
+ Basilisk node object.
81
+ Contains mesh data, translation, material, physics, collider, and descriptive information.
82
+ Base building block for populating a Basilisk scene.
83
+ """
84
+
85
+ # parents
86
+ self.node_handler = node_handler
87
+ self.chunk = None
88
+
89
+ # lazy update variables
90
+ self.needs_geometric_center = True
91
+ self.needs_model_matrix = True
92
+
93
+ # node data
94
+ self.internal_position: Vec3 = Vec3(position) if position else Vec3(0, 0, 0)
95
+ self.internal_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
96
+ self.internal_rotation: Quat = Quat(rotation) if rotation else Quat(1, 0, 0, 0)
97
+ self.forward = forward if forward else glm.vec3(1, 0, 0)
98
+ self.mesh = mesh if mesh else cube
99
+ self.material = material if material else None # TODO add default base material
100
+ self.velocity = velocity if velocity else glm.vec3(0, 0, 0)
101
+ self.rotational_velocity = rotational_velocity if rotational_velocity else glm.vec3(0, 0, 0)
102
+
103
+ # physics body
104
+ if physics: self.physics_body: PhysicsBody = self.node_handler.scene.physics_engine.add(mass = mass if mass else 1.0)
105
+ elif mass: raise ValueError('Node: cannot have mass if it does not have physics')
106
+ else: self.physics_body = None
107
+
108
+ # collider
109
+ if collisions:
110
+ if not mesh: raise ValueError('Node: cannot collide if it doezsnt have a mesh')
111
+ self.collider: Collider = self.node_handler.scene.collider_handler.add(
112
+ node = self,
113
+ box_mesh = True if collider == 'box' else False,
114
+ static_friction = static_friction,
115
+ kinetic_friction = kinetic_friction,
116
+ elasticity = elasticity,
117
+ collision_group = collision_group
118
+ )
119
+ elif collider: raise ValueError('Node: cannot have collider mesh if it does not allow collisions')
120
+ elif static_friction: raise ValueError('Node: cannot have static friction if it does not allow collisions')
121
+ elif kinetic_friction: raise ValueError('Node: cannot have kinetic friction if it does not allow collisions')
122
+ elif elasticity: raise ValueError('Node: cannot have elasticity if it does not allow collisions')
123
+ elif collision_group: raise ValueError('Node: cannot have collider group if it does not allow collisions')
124
+ else: self.collider = None
125
+
126
+ # information and recursion
127
+ self.name = name
128
+ self.tags = tags if tags else []
129
+ self.static = static and not (self.physics_body or self.velocity or self.rotational_velocity)
130
+ self.data_index = 0
131
+ self.children = []
132
+
133
+ # default callback functions for node transform
134
+ self.previous_position: Vec3 = Vec3(position) if position else Vec3(0, 0, 0)
135
+ self.previous_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
136
+ 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?
137
+
138
+ self.position_updated = False
139
+ self.scale_updated = False
140
+ self.rotation_updated = False
141
+
142
+ # callback function to be added to the custom Vec3 and Quat classes
143
+ def position_callback():
144
+ print('position')
145
+ self.position_updated = True
146
+
147
+ def scale_callback():
148
+ print('scale')
149
+ self.scale_updated = True
150
+
151
+ def rotation_callback():
152
+ print('rotation')
153
+ self.rotation_updated = True
154
+
155
+ self.internal_position.callback = position_callback
156
+ self.internal_scale.callback = scale_callback
157
+ self.internal_rotation.callback = rotation_callback
158
+
159
+ def update(self, dt: float) -> None:
160
+ """
161
+ Updates the node's movement variables based on the delta time
162
+ """
163
+ # update all hard-to-update variables
164
+
165
+
166
+ # reset updates
167
+ self.position_updated = False
168
+ self.scale_updated = False
169
+ self.rotation_updated = False
170
+
171
+ self.position += dt * self.velocity
172
+ self.rotation += 0.5 * dt * self.rotation * glm.quat(0, *self.rotational_velocity)
173
+ self.rotation = glm.normalize(self.rotation)
174
+ self.scale = self.scale
175
+
176
+ if self.physics_body:
177
+ self.velocity += self.physics_body.get_delta_velocity(dt)
178
+ self.rotational_velocity += self.physics_body.get_delta_rotational_velocity(dt)
179
+
180
+ def sync_data(self, dt: float) -> None: # TODO only needed for child nodes now
181
+ ...
182
+
183
+ def get_nodes(self,
184
+ require_mesh: bool=False,
185
+ require_collider: bool=False,
186
+ require_physics_body: bool=False,
187
+ filter_material: Material=None,
188
+ filter_tags: list[str]=None
189
+ ) -> list:
190
+ """
191
+ Returns the nodes matching the required filters from this branch of the nodes
192
+ """
193
+ # adds self to nodes list if it matches the criteria
194
+ nodes = []
195
+
196
+ if all([
197
+ (not require_mesh or self.mesh),
198
+ (not require_collider or self.collider),
199
+ (not require_physics_body or self.physics_body),
200
+ (not filter_material or self.material == filter_material),
201
+ (not filter_tags or all([tag in self.tags for tag in filter_tags]))
202
+ ]):
203
+ nodes.append(self)
204
+
205
+ # adds children to nodes list if they match the criteria
206
+ for node in self.children: nodes.extend(node.get_nodes(require_mesh, require_collider, require_physics_body, filter_material, filter_tags))
207
+ return node
208
+
209
+ def adopt_child(self, node) -> None: # TODO determine the best way for the user to do this through the scene
210
+ ...
211
+
212
+ def add_child(self) -> None: # TODO add node constructor
213
+ ...
214
+
215
+ def apply_force(self, force: glm.vec3, dt: float) -> None:
216
+ """
217
+ Applies a force at the center of the node
218
+ """
219
+ self.apply_offset_force(force, glm.vec3(0.0), dt)
220
+
221
+ def apply_offset_force(self, force: glm.vec3, offset: glm.vec3, dt: float) -> None:
222
+ """
223
+ Applies a force at the given offset
224
+ """
225
+ # translation
226
+ assert self.physics_body, 'Node: Cannot apply a force to a node that doesn\'t have a physics body'
227
+ self.velocity = force / self.mass * dt
228
+
229
+ # rotation
230
+ torque = glm.cross(offset, force)
231
+ self.apply_torque(torque, dt)
232
+
233
+ def apply_torque(self, torque: glm.vec3, dt: float) -> None:
234
+ """
235
+ Applies a torque on the node
236
+ """
237
+ assert self.physics_body, 'Node: Cannot apply a torque to a node that doesn\'t have a physics body'
238
+ ...
239
+
240
+ # TODO change geometric variables into properties
241
+ def get_inverse_inertia(self) -> glm.mat3x3:
242
+ """
243
+ Transforms the mesh inertia tensor and inverts it
244
+ """
245
+ if not (self.mesh and self.physics_body): return None
246
+ inertia_tensor = self.mesh.get_inertia_tensor(self.scale)
247
+
248
+ # mass
249
+ if self.physics_body: inertia_tensor *= self.physics_body.mass
250
+
251
+ # rotation
252
+ rotation_matrix = glm.mat3_cast(self.rotation)
253
+ inertia_tensor = rotation_matrix * inertia_tensor * glm.transpose(rotation_matrix)
254
+
255
+ return glm.inverse(inertia_tensor)
256
+
257
+ def get_data(self) -> np.ndarray:
258
+ """
259
+ Gets the node batch data for chunk batching
260
+ """
261
+
262
+ # Get data from the mesh node
263
+ mesh_data = self.mesh.data
264
+ node_data = np.array([*self.position, *self.rotation, *self.scale, self.material.index])
265
+
266
+ # Create an array to hold the node's data
267
+ data = np.zeros(shape=(mesh_data.shape[0], 25), dtype='f4')
268
+ data[:,:14] = mesh_data
269
+ data[:,14:] = node_data
270
+
271
+ return data
272
+
273
+ def __repr__(self) -> str:
274
+ """
275
+ Returns a string representation of the node
276
+ """
277
+
278
+ return f'<Bailisk Node | {self.name}, {self.mesh}, ({self.position})>'
279
+
280
+ @property
281
+ def position(self): return self.internal_position.data
282
+ @property
283
+ def scale(self): return self.internal_scale.data
284
+ @property
285
+ def rotation(self): return self.internal_rotation.data
286
+ @property
287
+ def forward(self): return self._forward
288
+ # TODO add property for Mesh
289
+ @property
290
+ def material(self): return self._material
291
+ @property
292
+ def velocity(self): return self._velocity
293
+ @property
294
+ def rotational_velocity(self): return self._rotational_velocity
295
+ @property
296
+ def mass(self):
297
+ if self.physics_body: return self.physics_body.mass
298
+ raise RuntimeError('Node: Cannot access the mass of a node that has no physics body')
299
+ @property
300
+ def static_friction(self):
301
+ if self.collider: return self.collider.static_friction
302
+ raise RuntimeError('Node: Cannot access the static friction of a node that has no collider')
303
+ @property
304
+ def kinetic_friction(self):
305
+ if self.collider: return self.collider.kinetic_friction
306
+ raise RuntimeError('Node: Cannot access the kinetic friction of a node that has no collider')
307
+ @property
308
+ def elasticity(self):
309
+ if self.collider: return self.collider.elasticity
310
+ raise RuntimeError('Node: Cannot access the elasticity of a node that has no collider')
311
+ @property
312
+ def collision_group(self):
313
+ if self.collider: return self.collider.collision_group
314
+ raise RuntimeError('Node: Cannot access the collision_group of a node that has no collider')
315
+ @property
316
+ def name(self): return self._name
317
+ @property
318
+ def tags(self): return self._tags
319
+ @property
320
+ def x(self): return self.internal_position.data.x
321
+ @property
322
+ def y(self): return self.internal_position.data.y
323
+ @property
324
+ def z(self): return self.internal_position.data.z
325
+
326
+ # TODO add descriptions in the class header
327
+ @property
328
+ def model_matrix(self):
329
+ if self.needs_model_matrix:
330
+ self._model_matrix = get_model_matrix(self.position, self.scale, self.rotation)
331
+ self.needs_model_matrix = False
332
+ return self._model_matrix
333
+ @property
334
+ def geometric_center(self): # assumes the node has a mesh
335
+ # if not self.mesh: raise RuntimeError('Node: Cannot retrieve geometric center if node does not have mesh')
336
+ if self.needs_geometric_center:
337
+ self._geometric_center = self.model_matrix * self.mesh.geometric_center
338
+ self.needs_geometric_center = False
339
+ return self._geometric_center
340
+ @property
341
+ def center_of_mass(self):
342
+ if not self.mesh: raise RuntimeError('Node: Cannot retrieve center of mass if node does not have mesh')
343
+ return self.model_matrix * self.mesh.center_of_mass
344
+ @property
345
+ def volume(self):
346
+ if not self.mesh: raise RuntimeError('Node: Cannot retrieve volume if node does not have mesh')
347
+ return self.mesh.volume * self.scale.x * self.scale.y * self.scale.z
348
+
349
+ @position.setter
350
+ def position(self, value: tuple | list | glm.vec3 | np.ndarray):
351
+ if isinstance(value, glm.vec3): self.internal_position.data = glm.vec3(value)
352
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
353
+ if len(value) != 3: raise ValueError(f'Node: Invalid number of values for position. Expected 3, got {len(value)}')
354
+ self.internal_position.data = glm.vec3(value)
355
+ else: raise TypeError(f'Node: Invalid position value type {type(value)}')
356
+ self.update_position()
357
+
358
+ @scale.setter
359
+ def scale(self, value: tuple | list | glm.vec3 | np.ndarray):
360
+ if isinstance(value, glm.vec3): self.internal_scale.data = glm.vec3(value)
361
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
362
+ if len(value) != 3: raise ValueError(f'Node: Invalid number of values for scale. Expected 3, got {len(value)}')
363
+ self.internal_scale.data = glm.vec3(value)
364
+ else: raise TypeError(f'Node: Invalid scale value type {type(value)}')
365
+ self.update_scale()
366
+
367
+ @rotation.setter
368
+ def rotation(self, value: tuple | list | glm.vec3 | glm.quat | glm.vec4 | np.ndarray):
369
+ if isinstance(value, glm.quat) or isinstance(value, glm.vec4) or isinstance(value, glm.vec3): self.internal_rotation.data = glm.quat(value)
370
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
371
+ if len(value) == 3: self.internal_rotation.data = glm.quat(glm.vec3(*value))
372
+ elif len(value) == 4: self.internal_rotation.data = glm.quat(*value)
373
+ else: raise ValueError(f'Node: Invalid number of values for rotation. Expected 3 or 4, got {len(value)}')
374
+ else: raise TypeError(f'Node: Invalid rotation value type {type(value)}')
375
+ self.update_rotation()
376
+
377
+ def update_position(self): # TODO Could these be repeated in a Vec3/Quat Callback function?
378
+ """
379
+ Updates the rotation
380
+ """
381
+
382
+ thresh = 0.01
383
+ cur = self.position
384
+ prev = self.previous_position
385
+
386
+ if self.chunk and (abs(cur.x - prev.x) > thresh or abs(cur.y - prev.y) > thresh or abs(cur.z - prev.z) > thresh):
387
+ self.chunk.node_update_callback(self)
388
+ self.previous_position = self.position
389
+
390
+ def update_scale(self):
391
+ """
392
+ Updates the rotation
393
+ """
394
+
395
+ thresh = 0.01
396
+ cur = self.scale
397
+ prev = self.previous_scale
398
+
399
+ if self.chunk and (abs(cur.x - prev.x) > thresh or abs(cur.y - prev.y) > thresh or abs(cur.z - prev.z) > thresh):
400
+ self.chunk.node_update_callback(self)
401
+ self.previous_scale = self.scale
402
+
403
+ def update_rotation(self):
404
+ """
405
+ Updates the rotation
406
+ """
407
+
408
+ thresh = 0.01
409
+ cur = self.rotation
410
+ prev = self.previous_rotation
411
+
412
+ 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):
413
+ self.chunk.node_update_callback(self)
414
+ self.previous_rotation = self.rotation
415
+
416
+
417
+ @forward.setter
418
+ def forward(self, value: tuple | list | glm.vec3 | np.ndarray):
419
+ if isinstance(value, glm.vec3): self._forward = glm.vec3(value)
420
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
421
+ if len(value) != 3: raise ValueError(f'Node: Invalid number of values for forward. Expected 3, got {len(value)}')
422
+ self._forward = glm.vec3(value)
423
+ else: raise TypeError(f'Node: Invalid forward value type {type(value)}')
424
+
425
+ # TODO add setter for Mesh
426
+
427
+ @material.setter
428
+ def material(self, value: Material):
429
+ if isinstance(value, Material):
430
+ self._material = value
431
+ self.node_handler.scene.material_handler.add(value)
432
+ if not self.chunk: return
433
+ self.chunk.node_update_callback(self)
434
+ else: raise TypeError(f'Node: Invalid material value type {type(value)}')
435
+
436
+ @velocity.setter
437
+ def velocity(self, value: tuple | list | glm.vec3 | np.ndarray):
438
+ if isinstance(value, glm.vec3): self._velocity = glm.vec3(value)
439
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
440
+ if len(value) != 3: raise ValueError(f'Node: Invalid number of values for velocity. Expected 3, got {len(value)}')
441
+ self._velocity = glm.vec3(value)
442
+ else: raise TypeError(f'Node: Invalid velocity value type {type(value)}')
443
+
444
+ @rotational_velocity.setter
445
+ def rotational_velocity(self, value: tuple | list | glm.vec3 | np.ndarray):
446
+ if isinstance(value, glm.vec3): self._rotational_velocity = glm.vec3(value)
447
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
448
+ if len(value) != 3: raise ValueError(f'Node: Invalid number of values for rotational velocity. Expected 3, got {len(value)}')
449
+ self._rotational_velocity = glm.vec3(value)
450
+ else: raise TypeError(f'Node: Invalid rotational velocity value type {type(value)}')
451
+
452
+ @mass.setter
453
+ def mass(self, value: int | float):
454
+ if not self.physics_body: raise RuntimeError('Node: Cannot set the mass of a node that has no physics body')
455
+ if isinstance(value, int) or isinstance(value, float): self.physics_body.mass = value
456
+ else: raise TypeError(f'Node: Invalid mass value type {type(value)}')
457
+
458
+ @static_friction.setter
459
+ def static_friction(self, value: int | float):
460
+ if not self.collider: raise RuntimeError('Node: Cannot set the static friction of a node that has no physics body')
461
+ if isinstance(value, int) or isinstance(value, float): self.collider.static_friction = value
462
+ else: raise TypeError(f'Node: Invalid static friction value type {type(value)}')
463
+
464
+ @kinetic_friction.setter
465
+ def kinetic_friction(self, value: int | float):
466
+ if not self.collider: raise RuntimeError('Node: Cannot set the kinetic friction of a node that has no physics body')
467
+ if isinstance(value, int) or isinstance(value, float): self.collider.kinetic_friction = value
468
+ else: raise TypeError(f'Node: Invalid kinetic friction value type {type(value)}')
469
+
470
+ @elasticity.setter
471
+ def elasticity(self, value: int | float):
472
+ if not self.collider: raise RuntimeError('Node: Cannot set the elasticity of a node that has no physics body')
473
+ if isinstance(value, int) or isinstance(value, float): self.collider.elasticity = value
474
+ else: raise TypeError(f'Node: Invalid elasticity value type {type(value)}')
475
+
476
+ @collision_group.setter
477
+ def collision_group(self, value: str):
478
+ if not self.collider: raise RuntimeError('Node: Cannot set the collision gruop of a node that has no physics body')
479
+ if isinstance(value, str): self.collider.collision_group = value
480
+ else: raise TypeError(f'Node: Invalid collision group value type {type(value)}')
481
+
482
+ @name.setter
483
+ def name(self, value: str):
484
+ if isinstance(value, str): self._name = value
485
+ else: raise TypeError(f'Node: Invalid name value type {type(value)}')
486
+
487
+ @tags.setter
488
+ def tags(self, value: list[str]):
489
+ if isinstance(value, list) or isinstance(value, tuple):
490
+ for tag in value:
491
+ if not isinstance(tag, str): raise TypeError(f'Node: Invalid tag value in tags list of type {type(tag)}')
492
+ self._tags = value
493
+ else: raise TypeError(f'Node: Invalid tags value type {type(value)}')
494
+
495
+ @x.setter
496
+ def x(self, value: int | float):
497
+ if isinstance(value, int) or isinstance(value, float): self.internal_position.data.x = value
498
+ else: raise TypeError(f'Node: Invalid positional x value type {type(value)}')
499
+
500
+ @y.setter
501
+ def y(self, value: int | float):
502
+ if isinstance(value, int) or isinstance(value, float): self.internal_position.data.y = value
503
+ else: raise TypeError(f'Node: Invalid positional y value type {type(value)}')
504
+
505
+ @z.setter
506
+ def z(self, value: int | float):
507
+ if isinstance(value, int) or isinstance(value, float): self.internal_position.data.z = value
508
+ else: raise TypeError(f'Node: Invalid positional z value type {type(value)}')
@@ -0,0 +1,94 @@
1
+ import glm
2
+ from .node import Node
3
+ from ..render.chunk_handler import ChunkHandler
4
+ from ..mesh.mesh import Mesh
5
+ from ..render.material import Material
6
+
7
+
8
+ class NodeHandler():
9
+ scene: ...
10
+ """Back reference to the scene"""
11
+ nodes: list[Node]
12
+ """The list of root nodes in the scene"""
13
+
14
+ def __init__(self, scene):
15
+ """
16
+ Contains all the nodes in the scene.
17
+ Handles chunking and batching of nodes
18
+ """
19
+
20
+ self.scene = scene
21
+ self.nodes = []
22
+ self.chunk_handler = ChunkHandler(scene)
23
+
24
+ def update(self):
25
+ """
26
+ Updates the nodes and chunks in the scene
27
+ """
28
+ for node in self.nodes: node.update(self.scene.engine.delta_time)
29
+ self.chunk_handler.update()
30
+
31
+ def render(self):
32
+ """
33
+ Updates the node meshes in the scene
34
+ """
35
+
36
+ self.chunk_handler.render()
37
+
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:
59
+ """
60
+ Adds a new node to the node handler
61
+ """
62
+
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)
67
+
68
+ return node
69
+
70
+ def get(self, name: str) -> Node: # TODO switch to full filter and adapt to search roots
71
+ """
72
+ Returns the first node with the given traits
73
+ """
74
+ for node in self.nodes:
75
+ if node.name == name: return node
76
+ return None
77
+
78
+ def get_all(self, name: str) -> Node:
79
+ """
80
+ Returns all nodes with the given traits
81
+ """
82
+ nodes = []
83
+ for node in self.nodes:
84
+ if node.name == name: nodes.append(node)
85
+ return nodes
86
+
87
+ def remove(self, node: Node) -> None:
88
+ """
89
+ Removes a node and all of its children from their handlers
90
+ """
91
+ # 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
File without changes
@@ -0,0 +1,36 @@
1
+ import glm
2
+
3
+ class PhysicsBody():
4
+ physics_engine: ...
5
+ """Back reference to the parent physics engine"""
6
+ mass: float
7
+ """The mass of the physics body in kg"""
8
+
9
+ def __init__(self, physics_engine, mass:float=1.0) -> None:
10
+ self.physics_engine = physics_engine
11
+ self.mass = mass
12
+
13
+ def get_delta_velocity(self, dt: float) -> glm.vec3:
14
+ """
15
+ Returns the chnage in translational velocity from constant accelerations and forces
16
+ """
17
+ if not self.physics_engine: return None
18
+
19
+ dv = glm.vec3(0, 0, 0)
20
+ for acceleration in self.physics_engine.accelerations: dv += acceleration * dt
21
+ for force in self.physics_engine.forces: dv += force / self.mass * dt
22
+ return dv
23
+
24
+ def get_delta_rotational_velocity(self, dt: float) -> glm.quat:
25
+ """
26
+ Returns the delta rotation quaternion from constant accelerations and forces
27
+ """
28
+ if not self.physics_engine: return None
29
+
30
+ dw = glm.vec3(0, 0, 0)
31
+ for rotational_acceleration in self.physics_engine.rotational_accelerations: dw += rotational_acceleration * dt
32
+ # TODO add torques
33
+ return dw
34
+
35
+ def __repr__(self) -> str:
36
+ return f'<Physics Body| {self.mass}>'
@@ -0,0 +1,37 @@
1
+ import glm
2
+ from .physics_body import PhysicsBody
3
+
4
+ class PhysicsEngine():
5
+ physics_bodies: list[PhysicsBody]
6
+ """Contains all the physics bodies controlled by this physics engine"""
7
+ accelerations: list[glm.vec3]
8
+ """Contains constant accelerations to be applied to physics bodies"""
9
+ rotational_accelerations: list[glm.vec3]
10
+ """Contains constant rotational accelerations to be applied to physics bodies"""
11
+ forces: list[glm.vec3]
12
+ """Contains constant forces to be applied to physics bodies"""
13
+ torques: list[glm.vec3]
14
+ """Contains constant rotational accelerations to be applied to physics bodies"""
15
+
16
+ def __init__(self, accelerations: list[glm.vec3] = None, rotational_accelerations: list[glm.vec3] = None, forces: list[glm.vec3] = None, torques: list[glm.vec3] = None) -> None:
17
+ self.physics_bodies = []
18
+ self.accelerations = accelerations if accelerations else [glm.vec3(0, -1, 0)]
19
+ self.rotational_accelerations = rotational_accelerations if rotational_accelerations else []
20
+ self.forces = forces if forces else []
21
+ self.torques = torques if torques else []
22
+
23
+ def add(self, mass: float) -> PhysicsBody:
24
+ """
25
+ Adds a physics body to the physics engine and returns it
26
+ """
27
+ physics_body = PhysicsBody(self, mass)
28
+ self.physics_bodies.append(physics_body)
29
+ return physics_body
30
+
31
+ def remove(self, physics_body: PhysicsBody) -> None:
32
+ """
33
+ Removes the physics body from the physics engine and disconnects it from the engine
34
+ """
35
+ if physics_body in self.physics_bodies:
36
+ self.physics_bodies.remove(physics_body)
37
+ physics_body.physics_engine = None
File without changes