basilisk-engine 0.1.43__py3-none-any.whl → 0.1.44__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 (98) hide show
  1. basilisk/__init__.py +26 -26
  2. basilisk/audio/sound.py +27 -27
  3. basilisk/bsk_assets/cube.obj +48 -48
  4. basilisk/collisions/broad/broad_aabb.py +102 -102
  5. basilisk/collisions/broad/broad_bvh.py +137 -137
  6. basilisk/collisions/collider.py +95 -95
  7. basilisk/collisions/collider_handler.py +225 -225
  8. basilisk/collisions/narrow/contact_manifold.py +95 -95
  9. basilisk/collisions/narrow/dataclasses.py +34 -34
  10. basilisk/collisions/narrow/deprecated.py +46 -46
  11. basilisk/collisions/narrow/epa.py +91 -91
  12. basilisk/collisions/narrow/gjk.py +66 -66
  13. basilisk/collisions/narrow/graham_scan.py +24 -24
  14. basilisk/collisions/narrow/helper.py +29 -29
  15. basilisk/collisions/narrow/line_intersections.py +106 -106
  16. basilisk/collisions/narrow/sutherland_hodgman.py +75 -75
  17. basilisk/config.py +53 -53
  18. basilisk/draw/draw.py +100 -100
  19. basilisk/draw/draw_handler.py +178 -178
  20. basilisk/draw/font_renderer.py +28 -28
  21. basilisk/engine.py +169 -169
  22. basilisk/generic/abstract_bvh.py +15 -15
  23. basilisk/generic/abstract_custom.py +133 -133
  24. basilisk/generic/collisions.py +70 -70
  25. basilisk/generic/input_validation.py +82 -82
  26. basilisk/generic/math.py +17 -17
  27. basilisk/generic/matrices.py +35 -35
  28. basilisk/generic/meshes.py +72 -72
  29. basilisk/generic/quat.py +142 -142
  30. basilisk/generic/quat_methods.py +7 -7
  31. basilisk/generic/raycast_result.py +26 -26
  32. basilisk/generic/vec3.py +143 -143
  33. basilisk/input/__init__.py +0 -0
  34. basilisk/input/mouse.py +62 -0
  35. basilisk/input/path.py +14 -0
  36. basilisk/input_output/IO_handler.py +91 -91
  37. basilisk/input_output/clock.py +49 -49
  38. basilisk/input_output/keys.py +43 -43
  39. basilisk/input_output/mouse.py +90 -90
  40. basilisk/input_output/path.py +14 -14
  41. basilisk/mesh/cube.py +33 -33
  42. basilisk/mesh/mesh.py +233 -233
  43. basilisk/mesh/mesh_from_data.py +150 -150
  44. basilisk/mesh/model.py +271 -271
  45. basilisk/mesh/narrow_aabb.py +89 -89
  46. basilisk/mesh/narrow_bvh.py +91 -91
  47. basilisk/mesh/narrow_primative.py +23 -23
  48. basilisk/nodes/helper.py +28 -28
  49. basilisk/nodes/node.py +709 -709
  50. basilisk/nodes/node_handler.py +97 -97
  51. basilisk/particles/particle_handler.py +64 -64
  52. basilisk/particles/particle_renderer.py +93 -93
  53. basilisk/physics/impulse.py +112 -112
  54. basilisk/physics/physics_body.py +43 -43
  55. basilisk/physics/physics_engine.py +35 -35
  56. basilisk/render/batch.py +103 -103
  57. basilisk/render/bloom.py +117 -117
  58. basilisk/render/camera.py +260 -260
  59. basilisk/render/chunk.py +113 -113
  60. basilisk/render/chunk_handler.py +167 -167
  61. basilisk/render/frame.py +130 -130
  62. basilisk/render/framebuffer.py +192 -192
  63. basilisk/render/image.py +120 -120
  64. basilisk/render/image_handler.py +120 -120
  65. basilisk/render/light.py +96 -96
  66. basilisk/render/light_handler.py +58 -58
  67. basilisk/render/material.py +232 -232
  68. basilisk/render/material_handler.py +133 -133
  69. basilisk/render/post_process.py +180 -180
  70. basilisk/render/shader.py +135 -135
  71. basilisk/render/shader_handler.py +109 -109
  72. basilisk/render/sky.py +119 -119
  73. basilisk/scene.py +287 -287
  74. basilisk/shaders/batch.frag +288 -293
  75. basilisk/shaders/batch.vert +117 -117
  76. basilisk/shaders/bloom_downsample.frag +23 -23
  77. basilisk/shaders/bloom_frame.frag +25 -0
  78. basilisk/shaders/bloom_upsample.frag +33 -33
  79. basilisk/shaders/crt.frag +34 -34
  80. basilisk/shaders/draw.frag +27 -27
  81. basilisk/shaders/draw.vert +25 -25
  82. basilisk/shaders/filter.frag +22 -22
  83. basilisk/shaders/frame.frag +13 -13
  84. basilisk/shaders/frame.vert +13 -13
  85. basilisk/shaders/frame_hdr.frag +27 -27
  86. basilisk/shaders/geometry.frag +10 -10
  87. basilisk/shaders/geometry.vert +41 -41
  88. basilisk/shaders/normal.frag +62 -62
  89. basilisk/shaders/normal.vert +96 -96
  90. basilisk/shaders/particle.frag +81 -81
  91. basilisk/shaders/particle.vert +86 -86
  92. basilisk/shaders/sky.frag +23 -23
  93. basilisk/shaders/sky.vert +13 -13
  94. {basilisk_engine-0.1.43.dist-info → basilisk_engine-0.1.44.dist-info}/METADATA +89 -89
  95. basilisk_engine-0.1.44.dist-info/RECORD +115 -0
  96. {basilisk_engine-0.1.43.dist-info → basilisk_engine-0.1.44.dist-info}/WHEEL +1 -1
  97. basilisk_engine-0.1.43.dist-info/RECORD +0 -111
  98. {basilisk_engine-0.1.43.dist-info → basilisk_engine-0.1.44.dist-info}/top_level.txt +0 -0
basilisk/nodes/node.py CHANGED
@@ -1,710 +1,710 @@
1
- import glm
2
- import inspect # TODO testing import
3
- import numpy as np
4
- from .helper import node_is
5
- from ..generic.vec3 import Vec3
6
- from ..generic.quat import Quat
7
- from ..generic.matrices import get_model_matrix
8
- from ..generic.math import relative_transforms
9
- from ..mesh.mesh import Mesh
10
- from ..render.material import Material
11
- from ..physics.physics_body import PhysicsBody
12
- from ..collisions.collider import Collider
13
- from ..render.chunk import Chunk
14
- from ..render.shader import Shader
15
-
16
-
17
- class Node():
18
- position: Vec3
19
- """The position of the node in meters with swizzle xyz"""
20
- scale: Vec3
21
- """The scale of the node in meters in each direction"""
22
- rotation: Quat
23
- """The rotation of the node"""
24
- relative_position: bool
25
- """The position of this node relative to the parent node"""
26
- relative_scale: bool
27
- """The scale of this node relative to the parent node"""
28
- relative_rotation: bool
29
- """The rotation of this node relative to the parent node"""
30
- forward: glm.vec3
31
- """The forward facing vector of the node"""
32
- mesh: Mesh
33
- """The mesh of the node stored as a basilisk mesh object"""
34
- material: Material
35
- """The mesh of the node stored as a basilisk material object"""
36
- velocity: glm.vec3
37
- """The translational velocity of the node"""
38
- rotational_velocity: glm.vec3
39
- """The rotational velocity of the node"""
40
- physics: bool
41
- """Allows the node's movement to be affected by the physics engine and collisions"""
42
- mass: float
43
- """The mass of the node in kg"""
44
- collision: bool
45
- """Gives the node collision with other nodes in the scene"""
46
- collider_mesh: str
47
- """The collider type of the node. Can be either 'box' or 'mesh'"""
48
- static_friction: float
49
- """Determines the friction of the node when still: recommended value 0.0 - 1.0"""
50
- kinetic_friction: float
51
- """Determines the friction of the node when moving: recommended value 0.0 - 1.0"""
52
- elasticity: float
53
- """Determines how bouncy an object is: recommended value 0.0 - 1.0"""
54
- collision_group: str
55
- """Nodes of the same collision group do not collide with each other"""
56
- name: str
57
- """The name of the node for reference"""
58
- tags: list[str]
59
- """Tags are used to sort nodes into separate groups"""
60
- static: bool
61
- """Objects that don't move should be marked as static"""
62
- chunk: Chunk
63
- """The parent chunk of the node. Used for callbacks to update chunk meshes"""
64
- children: list
65
- """List of nodes that this node is a parent of"""
66
- shader: Shader
67
- """Shader that is used to render the node. If none is given, engine default will be used"""
68
-
69
- def __init__(self,
70
- position: glm.vec3=None,
71
- scale: glm.vec3=None,
72
- rotation: glm.quat=None,
73
- relative_position: bool=True,
74
- relative_scale: bool=True,
75
- relative_rotation: bool=True,
76
- forward: glm.vec3=None,
77
- mesh: Mesh=None,
78
- material: Material=None,
79
- velocity: glm.vec3=None,
80
- rotational_velocity: glm.vec3=None,
81
- physics: bool=False,
82
- mass: float=None,
83
- collision: bool=False,
84
- collider_mesh: str|Mesh=None,
85
- static_friction: float=None,
86
- kinetic_friction: float=None,
87
- elasticity: float=None,
88
- collision_group: float=None,
89
- name: str='',
90
- tags: list[str]=None,
91
- static: bool=None,
92
- shader: Shader=None
93
- ) -> None:
94
- """
95
- Basilisk node object.
96
- Contains mesh data, translation, material, physics, collider, and descriptive information.
97
- Base building block for populating a Basilisk scene.
98
- """
99
-
100
- # parents
101
- self.node_handler = None
102
- self.scene = None
103
- self.engine = None
104
- self.chunk = None
105
- self.parent = None
106
-
107
- # lazy update variables
108
- self.needs_geometric_center = True # pos
109
- self.needs_model_matrix = True # pos, scale, rot
110
-
111
- # node data
112
- self.internal_position: Vec3 = Vec3(position) if position else Vec3(0, 0, 0)
113
- self.internal_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
114
- self.internal_rotation: Quat = Quat(rotation) if rotation else Quat(1, 0, 0, 0)
115
-
116
- # relative transformations
117
- self.relative_position = glm.vec3(0, 0, 0) if relative_position else None
118
- self.relative_scale = glm.vec3(0, 0, 0) if relative_scale else None
119
- self.relative_rotation = glm.quat(1, 0, 0, 0) if relative_rotation else None
120
-
121
- self.forward = forward if forward else glm.vec3(1, 0, 0)
122
- self.mesh = mesh
123
- self._mtl_list = material if isinstance(material, list) else [material]
124
- self.material = material if material else None
125
- self.velocity = velocity if velocity else glm.vec3(0, 0, 0)
126
- self.rotational_velocity = rotational_velocity if rotational_velocity else glm.vec3(0, 0, 0)
127
-
128
- self._static = static
129
-
130
- # Physics updates
131
- if physics: self.physics_body = PhysicsBody(mass = mass if mass else 1.0)
132
- elif mass: raise ValueError('Node: cannot have mass if it does not have physics')
133
- else: self.physics_body = None
134
-
135
- # collider
136
- if collision:
137
- self.collider = Collider(
138
- node = self,
139
- collider_mesh = collider_mesh,
140
- static_friction = static_friction,
141
- kinetic_friction = kinetic_friction,
142
- elasticity = elasticity,
143
- collision_group = collision_group
144
- )
145
- elif collider_mesh: raise ValueError('Node: cannot have collider mesh if it does not allow collisions')
146
- elif static_friction: raise ValueError('Node: cannot have static friction if it does not allow collisions')
147
- elif kinetic_friction: raise ValueError('Node: cannot have kinetic friction if it does not allow collisions')
148
- elif elasticity: raise ValueError('Node: cannot have elasticity if it does not allow collisions')
149
- elif collision_group: raise ValueError('Node: cannot have collider group if it does not allow collisions')
150
- else: self.collider = None
151
-
152
- # information and recursion
153
- self.name = name
154
- self.tags = tags if tags else []
155
-
156
- self.data_index = 0
157
- self.children = []
158
-
159
- # Shader given by user or none for default
160
- self.shader = shader
161
-
162
- # callback function to be added to the custom Vec3 and Quat classes
163
- def position_callback():
164
-
165
- if self.chunk:
166
-
167
- chunk_size = self.scene.engine.config.chunk_size
168
- chunk_pos = self.position // chunk_size
169
-
170
- if self.chunk.position[0] == chunk_pos.x and self.chunk.position[1] == chunk_pos.y and self.chunk.position[2] == chunk_pos.z:
171
- self.chunk.node_update_callback(self)
172
- else:
173
- self.chunk.remove(self)
174
- self.chunk.chunk_handler.add(self)
175
-
176
- # update variables
177
- self.needs_geometric_center = True
178
- self.needs_model_matrix = True
179
- if self.collider:
180
- self.collider.needs_bvh = True
181
- self.collider.needs_obb = True
182
-
183
- def scale_callback():
184
- if self.chunk:
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
193
-
194
- def rotation_callback():
195
- if self.chunk:
196
- self.chunk.node_update_callback(self)
197
-
198
- # update variables
199
- self.needs_model_matrix = True
200
- if self.collider:
201
- self.collider.needs_bvh = True
202
- self.collider.needs_obb = True
203
- self.collider.needs_half_dimensions = True
204
-
205
- self.internal_position.callback = position_callback
206
- self.internal_scale.callback = scale_callback
207
- self.internal_rotation.callback = rotation_callback
208
-
209
- def init_scene(self, scene: ...) -> None:
210
- """
211
- Updates the scene of the node
212
- """
213
- self.scene = scene
214
- self.engine = scene.engine
215
- self.node_handler = scene.node_handler
216
-
217
- # Update materials
218
- self.write_materials()
219
-
220
- # Update the mesh
221
- self.mesh = self.mesh if self.mesh else self.engine.cube
222
-
223
- # Update physics and collider
224
- if self.physics_body: self.physics_body.physics_engine = scene.physics_engine
225
- if self.collider: self.collider.collider_handler = scene.collider_handler
226
-
227
- def update(self, dt: float) -> None:
228
- """
229
- Updates the node's movement variables based on the delta time
230
- """
231
- # update based on physical properties
232
- if any(self.velocity): self.position += dt * self.velocity # NOTE this should be an external setter, do not change to self.position.data
233
- if any(self.rotational_velocity): self.rotation = glm.normalize(self.rotation.data - dt / 2 * self.rotation.data * glm.quat(0, *self.rotational_velocity)) # NOTE see translational velocity note
234
-
235
- if self.physics_body:
236
- self.velocity += self.physics_body.get_delta_velocity(dt)
237
- self.rotational_velocity += self.physics_body.get_delta_rotational_velocity(dt)
238
-
239
- # update children transforms
240
- for child in self.children: child.sync_data()
241
-
242
- def sync_data(self) -> None:
243
- """
244
- Syncronizes this node with the parent node based on its relative positioning
245
- """
246
- # calculate transform matrix with the given input
247
- transform = glm.mat4x4()
248
- if self.relative_position: transform = glm.translate(transform, self.parent.position.data)
249
- if self.relative_rotation: transform *= glm.transpose(glm.mat4_cast(self.parent.rotation.data))
250
- if self.relative_scale: transform = glm.scale(transform, self.parent.scale.data)
251
-
252
- # set this node's transforms based on the parent
253
- if self.relative_position: self.position.data = transform * self.relative_position
254
- if self.relative_scale: self.scale.data = self.relative_scale * self.parent.scale.data
255
- if self.relative_rotation: self.rotation.data = self.relative_rotation * self.parent.rotation.data
256
-
257
- for child in self.children: child.sync_data()
258
-
259
- def deep_copy(self) -> ...:
260
- """
261
- Creates a deep copy of this node and returns it. The new node is not added to the scene.
262
- """
263
-
264
- copy = Node(
265
- position = self.position,
266
- scale = self.scale,
267
- rotation = self.rotation,
268
- relative_position = bool(self.relative_position),
269
- relative_scale = bool(self.relative_scale),
270
- relative_rotation = bool(self.relative_rotation),
271
- forward = glm.vec3(self.forward),
272
- mesh = self.mesh,
273
- material = self.material,
274
- velocity = glm.vec3(self.velocity),
275
- rotational_velocity = glm.vec3(self.rotational_velocity),
276
- physics = bool(self.physics_body),
277
- mass = self.mass if self.physics_body else None,
278
- collision = bool(self.collider),
279
- static_friction = self.static_friction if self.collider else None,
280
- kinetic_friction = self.kinetic_friction if self.collider else None,
281
- elasticity = self.elasticity if self.collider else None,
282
- collision_group = self.collision_group if self.collider else None,
283
- name = self.name,
284
- tags = [tag for tag in self.tags], # deep copy tags list
285
- static = self.static,
286
- shader = self.shader
287
- )
288
-
289
- return copy
290
-
291
- 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) -> list:
292
- nodes = [self] if node_is(self, position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static) else []
293
- for node in self.children: nodes += node.get_all(position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static)
294
- return nodes
295
-
296
- # tree functions for managing children
297
- def add(self, child: ..., relative_position: bool=None, relative_scale: bool=None, relative_rotation: glm.vec3=None) -> None:
298
- """
299
- 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.
300
- """
301
- if child in self.children or child is self: return
302
- assert isinstance(child, Node), 'Nodes can only accept other Nodes as children.'
303
-
304
- position, scale, rotation = relative_transforms(self, child)
305
-
306
- # compute relative transformations
307
- if relative_position or (relative_position is None and child.relative_position): child.relative_position = position
308
- if relative_scale or (relative_scale is None and child.relative_scale): child.relative_scale = scale
309
- if relative_rotation or (relative_rotation is None and child.relative_rotation): child.relative_rotation = rotation
310
-
311
- # add as a child to by synchronized and controlled
312
- if self.node_handler: self.node_handler.add(child)
313
- child.parent = self
314
- self.children.append(child)
315
-
316
- def remove(self, child: ...) -> None:
317
- """
318
- Removes a child node from this nodes chlid list.
319
- """
320
- if child in self.children:
321
- if self.node_handler: self.node_handler.remove(child)
322
- child.parent = None
323
- self.children.remove(child)
324
-
325
- def apply_force(self, force: glm.vec3, dt: float) -> None:
326
- """
327
- Applies a force at the center of the node
328
- """
329
- self.apply_offset_force(force, glm.vec3(0.0), dt)
330
-
331
- def apply_offset_force(self, force: glm.vec3, offset: glm.vec3, dt: float) -> None:
332
- """
333
- Applies a force at the given offset
334
- """
335
- # translation
336
- assert self.physics_body, 'Node: Cannot apply a force to a node that doesn\'t have a physics body'
337
- self.velocity += force / self.mass * dt
338
-
339
- # rotation
340
- torque = glm.cross(offset, force)
341
- self.apply_torque(torque, dt)
342
-
343
- def apply_torque(self, torque: glm.vec3, dt: float) -> None:
344
- """
345
- Applies a torque on the node
346
- """
347
- assert self.physics_body, 'Node: Cannot apply a torque to a node that doesn\'t have a physics body'
348
- ...
349
-
350
- # TODO change geometric variables into properties
351
- def get_inverse_inertia(self) -> glm.mat3x3:
352
- """
353
- Transforms the mesh inertia tensor and inverts it
354
- """
355
- if not ((self.mesh or (self.collider and self.collider.mesh)) and self.physics_body): return None
356
- mesh = self.collider.mesh if self.collider else self.mesh
357
- inertia_tensor = mesh.get_inertia_tensor(self.scale) / 2
358
-
359
- # mass
360
- if self.physics_body: inertia_tensor *= self.physics_body.mass
361
-
362
- # rotation
363
- rotation_matrix = glm.mat3_cast(self.rotation.data)
364
- inertia_tensor = rotation_matrix * inertia_tensor * glm.transpose(rotation_matrix)
365
-
366
- return glm.inverse(inertia_tensor)
367
-
368
- def get_vertex(self, index) -> glm.vec3:
369
- """
370
- Gets the world space position of a vertex indicated by the index in the mesh
371
- """
372
- return glm.vec3(self.model_matrix * glm.vec4(*self.mesh.points[index], 1))
373
-
374
- def get_data(self) -> np.ndarray:
375
- """
376
- Gets the node batch data for chunk batching
377
- """
378
-
379
- # Get data from the mesh node
380
- mesh_data = self.mesh.data
381
- node_data = np.array([*self.position, *self.rotation, *self.scale, 0])
382
-
383
- per_vertex_mtl = isinstance(self.material, list)
384
-
385
- if not per_vertex_mtl: node_data[-1] = self.material.index
386
-
387
- # Create an array to hold the node's data
388
- width = 25 if not self.mesh.custom else 11 + mesh_data.shape[1]
389
- data = np.zeros(shape=(mesh_data.shape[0], width), dtype='f4')
390
-
391
-
392
- data[:,:mesh_data.shape[1]] = mesh_data
393
- data[:,mesh_data.shape[1]:] = node_data
394
-
395
- if per_vertex_mtl: data[:,-1] = self.material
396
-
397
- if self.shader and not self.mesh.custom: data = np.take(data, self.shader.attribute_indices, axis=1)
398
-
399
- return data
400
-
401
- def write_materials(self):
402
- """
403
- Internal function to write the material list to the material handler and get the material ids
404
- """
405
-
406
- if isinstance(self.material, list):
407
- mtl_index_list = []
408
- for mtl in self._mtl_list:
409
- self.engine.material_handler.add(mtl)
410
- mtl_index_list.append(mtl.index)
411
- mtl_index_list.append(mtl.index)
412
- mtl_index_list.append(mtl.index)
413
- self._material = mtl_index_list
414
-
415
- if isinstance(self.material, type(None)):
416
- self.material = self.engine.material_handler.base
417
-
418
-
419
- def __repr__(self) -> str:
420
- """
421
- Returns a string representation of the node
422
- """
423
-
424
- return f'<Bailisk Node | {self.name}, {self.mesh}, ({self.position})>'
425
-
426
- @property
427
- def position(self): return self.internal_position
428
- @property
429
- def scale(self): return self.internal_scale
430
- @property
431
- def rotation(self): return self.internal_rotation
432
- @property
433
- def forward(self): return self._forward
434
- @property
435
- def mesh(self): return self._mesh
436
- @property
437
- def material(self): return self._material
438
- @property
439
- def velocity(self): return self._velocity
440
- @property
441
- def rotational_velocity(self): return self._rotational_velocity
442
- @property
443
- def mass(self):
444
- if self.physics_body: return self.physics_body.mass
445
- raise RuntimeError('Node: Cannot access the mass of a node that has no physics body')
446
- @property
447
- def static_friction(self):
448
- if self.collider: return self.collider.static_friction
449
- raise RuntimeError('Node: Cannot access the static friction of a node that has no collider')
450
- @property
451
- def kinetic_friction(self):
452
- if self.collider: return self.collider.kinetic_friction
453
- raise RuntimeError('Node: Cannot access the kinetic friction of a node that has no collider')
454
- @property
455
- def elasticity(self):
456
- if self.collider: return self.collider.elasticity
457
- raise RuntimeError('Node: Cannot access the elasticity of a node that has no collider')
458
- @property
459
- def collision_group(self):
460
- if self.collider: return self.collider.collision_group
461
- raise RuntimeError('Node: Cannot access the collision_group of a node that has no collider')
462
- @property
463
- def name(self): return self._name
464
- @property
465
- def tags(self): return self._tags
466
- @property
467
- def static(self):
468
- return self._static if self._static is not None else not(self.physics or any(self.velocity) or any(self.rotational_velocity) or (self.parent and not self.parent.static))
469
- @property
470
- def x(self): return self.internal_position.data.x
471
- @property
472
- def y(self): return self.internal_position.data.y
473
- @property
474
- def z(self): return self.internal_position.data.z
475
-
476
- # TODO add descriptions in the class header
477
- @property
478
- def model_matrix(self):
479
- if self.needs_model_matrix:
480
- self._model_matrix = get_model_matrix(self.position, self.scale, self.rotation)
481
- self.needs_model_matrix = False
482
- return self._model_matrix
483
- @property
484
- def geometric_center(self): # assumes the node has a mesh
485
- # if not self.mesh: raise RuntimeError('Node: Cannot retrieve geometric center if node does not have mesh')
486
- if self.needs_geometric_center:
487
- self._geometric_center = self.model_matrix * self.mesh.geometric_center
488
- self.needs_geometric_center = False
489
- return self._geometric_center
490
- @property
491
- def center_of_mass(self):
492
- if not self.mesh: raise RuntimeError('Node: Cannot retrieve center of mass if node does not have mesh')
493
- return self.model_matrix * self.mesh.center_of_mass
494
- @property
495
- def volume(self):
496
- if not self.mesh: raise RuntimeError('Node: Cannot retrieve volume if node does not have mesh')
497
- return self.mesh.volume * self.scale.x * self.scale.y * self.scale.z
498
-
499
- @property
500
- def physics(self):
501
- return bool(self.physics_body)
502
- @property
503
- def collision(self):
504
- return bool(self.collider)
505
-
506
- @property
507
- def collisions(self):
508
- assert self.collision, 'Node: Cannot access collision data without collisions enabled on Node'
509
- return self.collider.collisions
510
-
511
- @position.setter
512
- def position(self, value: tuple | list | glm.vec3 | np.ndarray):
513
- if isinstance(value, glm.vec3): self.internal_position.data = value
514
- elif isinstance(value, Vec3): self.internal_position.data = value.data
515
- elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
516
- if len(value) != 3: raise ValueError(f'Node: Invalid number of values for position. Expected 3, got {len(value)}')
517
- self.internal_position.data = glm.vec3(value)
518
- else: raise TypeError(f'Node: Invalid position value type {type(value)}')
519
-
520
- # recompute relative transforms when user sets transform
521
- if not self.parent or not self.relative_position: return
522
- position, scale, rotation = relative_transforms(self.parent, self)
523
- self.relative_position = position
524
-
525
- @scale.setter
526
- def scale(self, value: tuple | list | glm.vec3 | np.ndarray):
527
- if isinstance(value, glm.vec3): self.internal_scale.data = value
528
- elif isinstance(value, Vec3): self.internal_scale.data = value.data
529
- elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
530
- if len(value) != 3: raise ValueError(f'Node: Invalid number of values for scale. Expected 3, got {len(value)}')
531
- self.internal_scale.data = glm.vec3(value)
532
- else: raise TypeError(f'Node: Invalid scale value type {type(value)}')
533
-
534
- # recompute relative transforms when user sets transform
535
- if not self.parent or not self.relative_scale: return
536
- position, scale, rotation = relative_transforms(self.parent, self)
537
- self.relative_scale = scale
538
-
539
- @rotation.setter
540
- def rotation(self, value: tuple | list | glm.vec3 | glm.quat | glm.vec4 | np.ndarray):
541
- if isinstance(value, glm.quat) or isinstance(value, glm.vec4) or isinstance(value, glm.vec3): self.internal_rotation.data = glm.quat(value)
542
- elif isinstance(value, Quat): self.internal_rotation.data = value.data
543
- elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
544
- if len(value) == 3: self.internal_rotation.data = glm.quat(glm.vec3(*value))
545
- elif len(value) == 4: self.internal_rotation.data = glm.quat(*value)
546
- else: raise ValueError(f'Node: Invalid number of values for rotation. Expected 3 or 4, got {len(value)}')
547
- else: raise TypeError(f'Node: Invalid rotation value type {type(value)}')
548
-
549
- # recompute relative transforms when user sets transform
550
- if not self.parent or not self.relative_rotation: return
551
- position, scale, rotation = relative_transforms(self.parent, self)
552
- self.relative_rotation = rotation
553
-
554
- @forward.setter
555
- def forward(self, value: tuple | list | glm.vec3 | np.ndarray):
556
- if isinstance(value, glm.vec3): self._forward = value
557
- elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
558
- if len(value) != 3: raise ValueError(f'Node: Invalid number of values for forward. Expected 3, got {len(value)}')
559
- self._forward = glm.vec3(value)
560
- else: raise TypeError(f'Node: Invalid forward value type {type(value)}')
561
-
562
- @mesh.setter
563
- def mesh(self, value: Mesh | None):
564
- if isinstance(value, Mesh):
565
- self._mesh = value
566
- if self.chunk: self.chunk.update()
567
- elif isinstance(value, type(None)):
568
- self._mesh = None
569
- if not self.chunk: return
570
- self.chunk.remove(self)
571
- self.chunk.update()
572
- else: raise TypeError(f'Node: Invalid mesh value type {type(value)}')
573
-
574
- @material.setter
575
- def material(self, value: Material):
576
- if isinstance(value, list):
577
- self._mtl_list = value
578
- if not self.node_handler:
579
- self._material = value
580
- else:
581
- mtl_index_list = []
582
- for mtl in self._mtl_list:
583
- self.engine.material_handler.add(mtl)
584
- mtl_index_list.append(mtl.index)
585
- mtl_index_list.append(mtl.index)
586
- mtl_index_list.append(mtl.index)
587
- self._material = mtl_index_list
588
- elif isinstance(value, Material):
589
- self._material = value
590
- if self.node_handler: self.engine.material_handler.add(value)
591
- elif isinstance(value, type(None)):
592
- if self.engine: self._material = self.engine.material_handler.base
593
- else: self._material = None
594
-
595
- else: raise TypeError(f'Node: Invalid material value type {type(value)}')
596
- if not self.chunk: return
597
- self.chunk.node_update_callback(self)
598
-
599
- @velocity.setter
600
- def velocity(self, value: tuple | list | glm.vec3 | np.ndarray | Vec3):
601
- if isinstance(value, glm.vec3): self._velocity = glm.vec3(value)
602
- elif isinstance(value, Vec3): self._velocity = glm.vec3(value.data)
603
- elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
604
- if len(value) != 3: raise ValueError(f'Node: Invalid number of values for velocity. Expected 3, got {len(value)}')
605
- self._velocity = glm.vec3(value)
606
- else: raise TypeError(f'Node: Invalid velocity value type {type(value)}')
607
-
608
- @rotational_velocity.setter
609
- def rotational_velocity(self, value: tuple | list | glm.vec3 | np.ndarray | Vec3):
610
- if isinstance(value, glm.vec3): self._rotational_velocity = glm.vec3(value)
611
- elif isinstance(value, Vec3): self._rotational_velocity = glm.vec3(value.data)
612
- elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
613
- if len(value) != 3: raise ValueError(f'Node: Invalid number of values for rotational velocity. Expected 3, got {len(value)}')
614
- self._rotational_velocity = glm.vec3(value)
615
- else: raise TypeError(f'Node: Invalid rotational velocity value type {type(value)}')
616
-
617
- @mass.setter
618
- def mass(self, value: int | float):
619
- if not self.physics_body: raise RuntimeError('Node: Cannot set the mass of a node that has no physics body')
620
- if isinstance(value, int) or isinstance(value, float): self.physics_body.mass = value
621
- else: raise TypeError(f'Node: Invalid mass value type {type(value)}')
622
-
623
- @static_friction.setter
624
- def static_friction(self, value: int | float):
625
- if not self.collider: raise RuntimeError('Node: Cannot set the static friction of a node that has no physics body')
626
- if isinstance(value, int) or isinstance(value, float): self.collider.static_friction = value
627
- else: raise TypeError(f'Node: Invalid static friction value type {type(value)}')
628
-
629
- @kinetic_friction.setter
630
- def kinetic_friction(self, value: int | float):
631
- if not self.collider: raise RuntimeError('Node: Cannot set the kinetic friction of a node that has no physics body')
632
- if isinstance(value, int) or isinstance(value, float): self.collider.kinetic_friction = value
633
- else: raise TypeError(f'Node: Invalid kinetic friction value type {type(value)}')
634
-
635
- @elasticity.setter
636
- def elasticity(self, value: int | float):
637
- if not self.collider: raise RuntimeError('Node: Cannot set the elasticity of a node that has no physics body')
638
- if isinstance(value, int) or isinstance(value, float): self.collider.elasticity = value
639
- else: raise TypeError(f'Node: Invalid elasticity value type {type(value)}')
640
-
641
- @collision_group.setter
642
- def collision_group(self, value: str):
643
- if not self.collider: raise RuntimeError('Node: Cannot set the collision gruop of a node that has no physics body')
644
- if isinstance(value, (str, type(None))): self.collider.collision_group = value
645
- else: raise TypeError(f'Node: Invalid collision group value type {type(value)}')
646
-
647
- @name.setter
648
- def name(self, value: str):
649
- if isinstance(value, str): self._name = value
650
- else: raise TypeError(f'Node: Invalid name value type {type(value)}')
651
-
652
- @tags.setter
653
- def tags(self, value: list[str]):
654
- if isinstance(value, list) or isinstance(value, tuple):
655
- for tag in value:
656
- if not isinstance(tag, str): raise TypeError(f'Node: Invalid tag value in tags list of type {type(tag)}')
657
- self._tags = value
658
- else: raise TypeError(f'Node: Invalid tags value type {type(value)}')
659
-
660
- @static.setter
661
- def static(self, value: bool):
662
- self._static = value
663
-
664
- @x.setter
665
- def x(self, value: int | float):
666
- if isinstance(value, int) or isinstance(value, float): self.internal_position.x = value
667
- else: raise TypeError(f'Node: Invalid positional x value type {type(value)}')
668
-
669
- @y.setter
670
- def y(self, value: int | float):
671
- if isinstance(value, int) or isinstance(value, float): self.internal_position.y = value
672
- else: raise TypeError(f'Node: Invalid positional y value type {type(value)}')
673
-
674
- @z.setter
675
- def z(self, value: int | float):
676
- if isinstance(value, int) or isinstance(value, float): self.internal_position.z = value
677
- else: raise TypeError(f'Node: Invalid positional z value type {type(value)}')
678
-
679
- @physics.setter
680
- def physics(self, value: bool | PhysicsBody):
681
- if not value and self.physics: # remove physics body from self and scene
682
- if self.node_handler: self.node_handler.scene.physics_engine.remove(self.physics_body)
683
- self.physics_body = None
684
- elif isinstance(value, PhysicsBody): # deep copy physics body
685
- if self.physics:
686
- self.mass = value.mass
687
- else:
688
- self.physics_body = PhysicsBody(value.mass)
689
- if self.node_handler: self.physics_body.physics_engine = self.node_handler.scene.physics_engine
690
- elif not self.physics:
691
- self.physics_body = PhysicsBody(mass = 1)
692
- if self.node_handler: self.physics_body.physics_engine = self.node_handler.scene.physics_engine
693
-
694
-
695
- @collision.setter
696
- def collision(self, value: bool | PhysicsBody):
697
- if not value and self.collision:
698
- if self.node_handler: self.node_handler.scene.collider_handler.remove(self.collider)
699
- self.collider = None
700
- elif isinstance(value, Collider):
701
- if self.collision:
702
- self.kinetic_friction = value.kinetic_friction
703
- self.elasticity = value.elasticity
704
- self.static_friction = value.static_friction
705
- else:
706
- self.collider = Collider(self, value.mesh, value.static_friction, value.kinetic_friction, value.elasticity, value.collision_group)
707
- if self.node_handler: self.collider.collider_handler = self.node_handler.scene.collider_handler
708
- elif not self.collider:
709
- self.collider = Collider(self)
1
+ import glm
2
+ import inspect # TODO testing import
3
+ import numpy as np
4
+ from .helper import node_is
5
+ from ..generic.vec3 import Vec3
6
+ from ..generic.quat import Quat
7
+ from ..generic.matrices import get_model_matrix
8
+ from ..generic.math import relative_transforms
9
+ from ..mesh.mesh import Mesh
10
+ from ..render.material import Material
11
+ from ..physics.physics_body import PhysicsBody
12
+ from ..collisions.collider import Collider
13
+ from ..render.chunk import Chunk
14
+ from ..render.shader import Shader
15
+
16
+
17
+ class Node():
18
+ position: Vec3
19
+ """The position of the node in meters with swizzle xyz"""
20
+ scale: Vec3
21
+ """The scale of the node in meters in each direction"""
22
+ rotation: Quat
23
+ """The rotation of the node"""
24
+ relative_position: bool
25
+ """The position of this node relative to the parent node"""
26
+ relative_scale: bool
27
+ """The scale of this node relative to the parent node"""
28
+ relative_rotation: bool
29
+ """The rotation of this node relative to the parent node"""
30
+ forward: glm.vec3
31
+ """The forward facing vector of the node"""
32
+ mesh: Mesh
33
+ """The mesh of the node stored as a basilisk mesh object"""
34
+ material: Material
35
+ """The mesh of the node stored as a basilisk material object"""
36
+ velocity: glm.vec3
37
+ """The translational velocity of the node"""
38
+ rotational_velocity: glm.vec3
39
+ """The rotational velocity of the node"""
40
+ physics: bool
41
+ """Allows the node's movement to be affected by the physics engine and collisions"""
42
+ mass: float
43
+ """The mass of the node in kg"""
44
+ collision: bool
45
+ """Gives the node collision with other nodes in the scene"""
46
+ collider_mesh: str
47
+ """The collider type of the node. Can be either 'box' or 'mesh'"""
48
+ static_friction: float
49
+ """Determines the friction of the node when still: recommended value 0.0 - 1.0"""
50
+ kinetic_friction: float
51
+ """Determines the friction of the node when moving: recommended value 0.0 - 1.0"""
52
+ elasticity: float
53
+ """Determines how bouncy an object is: recommended value 0.0 - 1.0"""
54
+ collision_group: str
55
+ """Nodes of the same collision group do not collide with each other"""
56
+ name: str
57
+ """The name of the node for reference"""
58
+ tags: list[str]
59
+ """Tags are used to sort nodes into separate groups"""
60
+ static: bool
61
+ """Objects that don't move should be marked as static"""
62
+ chunk: Chunk
63
+ """The parent chunk of the node. Used for callbacks to update chunk meshes"""
64
+ children: list
65
+ """List of nodes that this node is a parent of"""
66
+ shader: Shader
67
+ """Shader that is used to render the node. If none is given, engine default will be used"""
68
+
69
+ def __init__(self,
70
+ position: glm.vec3=None,
71
+ scale: glm.vec3=None,
72
+ rotation: glm.quat=None,
73
+ relative_position: bool=True,
74
+ relative_scale: bool=True,
75
+ relative_rotation: bool=True,
76
+ forward: glm.vec3=None,
77
+ mesh: Mesh=None,
78
+ material: Material=None,
79
+ velocity: glm.vec3=None,
80
+ rotational_velocity: glm.vec3=None,
81
+ physics: bool=False,
82
+ mass: float=None,
83
+ collision: bool=False,
84
+ collider_mesh: str|Mesh=None,
85
+ static_friction: float=None,
86
+ kinetic_friction: float=None,
87
+ elasticity: float=None,
88
+ collision_group: float=None,
89
+ name: str='',
90
+ tags: list[str]=None,
91
+ static: bool=None,
92
+ shader: Shader=None
93
+ ) -> None:
94
+ """
95
+ Basilisk node object.
96
+ Contains mesh data, translation, material, physics, collider, and descriptive information.
97
+ Base building block for populating a Basilisk scene.
98
+ """
99
+
100
+ # parents
101
+ self.node_handler = None
102
+ self.scene = None
103
+ self.engine = None
104
+ self.chunk = None
105
+ self.parent = None
106
+
107
+ # lazy update variables
108
+ self.needs_geometric_center = True # pos
109
+ self.needs_model_matrix = True # pos, scale, rot
110
+
111
+ # node data
112
+ self.internal_position: Vec3 = Vec3(position) if position else Vec3(0, 0, 0)
113
+ self.internal_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
114
+ self.internal_rotation: Quat = Quat(rotation) if rotation else Quat(1, 0, 0, 0)
115
+
116
+ # relative transformations
117
+ self.relative_position = glm.vec3(0, 0, 0) if relative_position else None
118
+ self.relative_scale = glm.vec3(0, 0, 0) if relative_scale else None
119
+ self.relative_rotation = glm.quat(1, 0, 0, 0) if relative_rotation else None
120
+
121
+ self.forward = forward if forward else glm.vec3(1, 0, 0)
122
+ self.mesh = mesh
123
+ self._mtl_list = material if isinstance(material, list) else [material]
124
+ self.material = material if material else None
125
+ self.velocity = velocity if velocity else glm.vec3(0, 0, 0)
126
+ self.rotational_velocity = rotational_velocity if rotational_velocity else glm.vec3(0, 0, 0)
127
+
128
+ self._static = static
129
+
130
+ # Physics updates
131
+ if physics: self.physics_body = PhysicsBody(mass = mass if mass else 1.0)
132
+ elif mass: raise ValueError('Node: cannot have mass if it does not have physics')
133
+ else: self.physics_body = None
134
+
135
+ # collider
136
+ if collision:
137
+ self.collider = Collider(
138
+ node = self,
139
+ collider_mesh = collider_mesh,
140
+ static_friction = static_friction,
141
+ kinetic_friction = kinetic_friction,
142
+ elasticity = elasticity,
143
+ collision_group = collision_group
144
+ )
145
+ elif collider_mesh: raise ValueError('Node: cannot have collider mesh if it does not allow collisions')
146
+ elif static_friction: raise ValueError('Node: cannot have static friction if it does not allow collisions')
147
+ elif kinetic_friction: raise ValueError('Node: cannot have kinetic friction if it does not allow collisions')
148
+ elif elasticity: raise ValueError('Node: cannot have elasticity if it does not allow collisions')
149
+ elif collision_group: raise ValueError('Node: cannot have collider group if it does not allow collisions')
150
+ else: self.collider = None
151
+
152
+ # information and recursion
153
+ self.name = name
154
+ self.tags = tags if tags else []
155
+
156
+ self.data_index = 0
157
+ self.children = []
158
+
159
+ # Shader given by user or none for default
160
+ self.shader = shader
161
+
162
+ # callback function to be added to the custom Vec3 and Quat classes
163
+ def position_callback():
164
+
165
+ if self.chunk:
166
+
167
+ chunk_size = self.scene.engine.config.chunk_size
168
+ chunk_pos = self.position // chunk_size
169
+
170
+ if self.chunk.position[0] == chunk_pos.x and self.chunk.position[1] == chunk_pos.y and self.chunk.position[2] == chunk_pos.z:
171
+ self.chunk.node_update_callback(self)
172
+ else:
173
+ self.chunk.remove(self)
174
+ self.chunk.chunk_handler.add(self)
175
+
176
+ # update variables
177
+ self.needs_geometric_center = True
178
+ self.needs_model_matrix = True
179
+ if self.collider:
180
+ self.collider.needs_bvh = True
181
+ self.collider.needs_obb = True
182
+
183
+ def scale_callback():
184
+ if self.chunk:
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
193
+
194
+ def rotation_callback():
195
+ if self.chunk:
196
+ self.chunk.node_update_callback(self)
197
+
198
+ # update variables
199
+ self.needs_model_matrix = True
200
+ if self.collider:
201
+ self.collider.needs_bvh = True
202
+ self.collider.needs_obb = True
203
+ self.collider.needs_half_dimensions = True
204
+
205
+ self.internal_position.callback = position_callback
206
+ self.internal_scale.callback = scale_callback
207
+ self.internal_rotation.callback = rotation_callback
208
+
209
+ def init_scene(self, scene: ...) -> None:
210
+ """
211
+ Updates the scene of the node
212
+ """
213
+ self.scene = scene
214
+ self.engine = scene.engine
215
+ self.node_handler = scene.node_handler
216
+
217
+ # Update materials
218
+ self.write_materials()
219
+
220
+ # Update the mesh
221
+ self.mesh = self.mesh if self.mesh else self.engine.cube
222
+
223
+ # Update physics and collider
224
+ if self.physics_body: self.physics_body.physics_engine = scene.physics_engine
225
+ if self.collider: self.collider.collider_handler = scene.collider_handler
226
+
227
+ def update(self, dt: float) -> None:
228
+ """
229
+ Updates the node's movement variables based on the delta time
230
+ """
231
+ # update based on physical properties
232
+ if any(self.velocity): self.position += dt * self.velocity # NOTE this should be an external setter, do not change to self.position.data
233
+ if any(self.rotational_velocity): self.rotation = glm.normalize(self.rotation.data - dt / 2 * self.rotation.data * glm.quat(0, *self.rotational_velocity)) # NOTE see translational velocity note
234
+
235
+ if self.physics_body:
236
+ self.velocity += self.physics_body.get_delta_velocity(dt)
237
+ self.rotational_velocity += self.physics_body.get_delta_rotational_velocity(dt)
238
+
239
+ # update children transforms
240
+ for child in self.children: child.sync_data()
241
+
242
+ def sync_data(self) -> None:
243
+ """
244
+ Syncronizes this node with the parent node based on its relative positioning
245
+ """
246
+ # calculate transform matrix with the given input
247
+ transform = glm.mat4x4()
248
+ if self.relative_position: transform = glm.translate(transform, self.parent.position.data)
249
+ if self.relative_rotation: transform *= glm.transpose(glm.mat4_cast(self.parent.rotation.data))
250
+ if self.relative_scale: transform = glm.scale(transform, self.parent.scale.data)
251
+
252
+ # set this node's transforms based on the parent
253
+ if self.relative_position: self.position.data = transform * self.relative_position
254
+ if self.relative_scale: self.scale.data = self.relative_scale * self.parent.scale.data
255
+ if self.relative_rotation: self.rotation.data = self.relative_rotation * self.parent.rotation.data
256
+
257
+ for child in self.children: child.sync_data()
258
+
259
+ def deep_copy(self) -> ...:
260
+ """
261
+ Creates a deep copy of this node and returns it. The new node is not added to the scene.
262
+ """
263
+
264
+ copy = Node(
265
+ position = self.position,
266
+ scale = self.scale,
267
+ rotation = self.rotation,
268
+ relative_position = bool(self.relative_position),
269
+ relative_scale = bool(self.relative_scale),
270
+ relative_rotation = bool(self.relative_rotation),
271
+ forward = glm.vec3(self.forward),
272
+ mesh = self.mesh,
273
+ material = self.material,
274
+ velocity = glm.vec3(self.velocity),
275
+ rotational_velocity = glm.vec3(self.rotational_velocity),
276
+ physics = bool(self.physics_body),
277
+ mass = self.mass if self.physics_body else None,
278
+ collision = bool(self.collider),
279
+ static_friction = self.static_friction if self.collider else None,
280
+ kinetic_friction = self.kinetic_friction if self.collider else None,
281
+ elasticity = self.elasticity if self.collider else None,
282
+ collision_group = self.collision_group if self.collider else None,
283
+ name = self.name,
284
+ tags = [tag for tag in self.tags], # deep copy tags list
285
+ static = self.static,
286
+ shader = self.shader
287
+ )
288
+
289
+ return copy
290
+
291
+ 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) -> list:
292
+ nodes = [self] if node_is(self, position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static) else []
293
+ for node in self.children: nodes += node.get_all(position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static)
294
+ return nodes
295
+
296
+ # tree functions for managing children
297
+ def add(self, child: ..., relative_position: bool=None, relative_scale: bool=None, relative_rotation: glm.vec3=None) -> None:
298
+ """
299
+ 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.
300
+ """
301
+ if child in self.children or child is self: return
302
+ assert isinstance(child, Node), 'Nodes can only accept other Nodes as children.'
303
+
304
+ position, scale, rotation = relative_transforms(self, child)
305
+
306
+ # compute relative transformations
307
+ if relative_position or (relative_position is None and child.relative_position): child.relative_position = position
308
+ if relative_scale or (relative_scale is None and child.relative_scale): child.relative_scale = scale
309
+ if relative_rotation or (relative_rotation is None and child.relative_rotation): child.relative_rotation = rotation
310
+
311
+ # add as a child to by synchronized and controlled
312
+ if self.node_handler: self.node_handler.add(child)
313
+ child.parent = self
314
+ self.children.append(child)
315
+
316
+ def remove(self, child: ...) -> None:
317
+ """
318
+ Removes a child node from this nodes chlid list.
319
+ """
320
+ if child in self.children:
321
+ if self.node_handler: self.node_handler.remove(child)
322
+ child.parent = None
323
+ self.children.remove(child)
324
+
325
+ def apply_force(self, force: glm.vec3, dt: float) -> None:
326
+ """
327
+ Applies a force at the center of the node
328
+ """
329
+ self.apply_offset_force(force, glm.vec3(0.0), dt)
330
+
331
+ def apply_offset_force(self, force: glm.vec3, offset: glm.vec3, dt: float) -> None:
332
+ """
333
+ Applies a force at the given offset
334
+ """
335
+ # translation
336
+ assert self.physics_body, 'Node: Cannot apply a force to a node that doesn\'t have a physics body'
337
+ self.velocity += force / self.mass * dt
338
+
339
+ # rotation
340
+ torque = glm.cross(offset, force)
341
+ self.apply_torque(torque, dt)
342
+
343
+ def apply_torque(self, torque: glm.vec3, dt: float) -> None:
344
+ """
345
+ Applies a torque on the node
346
+ """
347
+ assert self.physics_body, 'Node: Cannot apply a torque to a node that doesn\'t have a physics body'
348
+ ...
349
+
350
+ # TODO change geometric variables into properties
351
+ def get_inverse_inertia(self) -> glm.mat3x3:
352
+ """
353
+ Transforms the mesh inertia tensor and inverts it
354
+ """
355
+ if not ((self.mesh or (self.collider and self.collider.mesh)) and self.physics_body): return None
356
+ mesh = self.collider.mesh if self.collider else self.mesh
357
+ inertia_tensor = mesh.get_inertia_tensor(self.scale) / 2
358
+
359
+ # mass
360
+ if self.physics_body: inertia_tensor *= self.physics_body.mass
361
+
362
+ # rotation
363
+ rotation_matrix = glm.mat3_cast(self.rotation.data)
364
+ inertia_tensor = rotation_matrix * inertia_tensor * glm.transpose(rotation_matrix)
365
+
366
+ return glm.inverse(inertia_tensor)
367
+
368
+ def get_vertex(self, index) -> glm.vec3:
369
+ """
370
+ Gets the world space position of a vertex indicated by the index in the mesh
371
+ """
372
+ return glm.vec3(self.model_matrix * glm.vec4(*self.mesh.points[index], 1))
373
+
374
+ def get_data(self) -> np.ndarray:
375
+ """
376
+ Gets the node batch data for chunk batching
377
+ """
378
+
379
+ # Get data from the mesh node
380
+ mesh_data = self.mesh.data
381
+ node_data = np.array([*self.position, *self.rotation, *self.scale, 0])
382
+
383
+ per_vertex_mtl = isinstance(self.material, list)
384
+
385
+ if not per_vertex_mtl: node_data[-1] = self.material.index
386
+
387
+ # Create an array to hold the node's data
388
+ width = 25 if not self.mesh.custom else 11 + mesh_data.shape[1]
389
+ data = np.zeros(shape=(mesh_data.shape[0], width), dtype='f4')
390
+
391
+
392
+ data[:,:mesh_data.shape[1]] = mesh_data
393
+ data[:,mesh_data.shape[1]:] = node_data
394
+
395
+ if per_vertex_mtl: data[:,-1] = self.material
396
+
397
+ if self.shader and not self.mesh.custom: data = np.take(data, self.shader.attribute_indices, axis=1)
398
+
399
+ return data
400
+
401
+ def write_materials(self):
402
+ """
403
+ Internal function to write the material list to the material handler and get the material ids
404
+ """
405
+
406
+ if isinstance(self.material, list):
407
+ mtl_index_list = []
408
+ for mtl in self._mtl_list:
409
+ self.engine.material_handler.add(mtl)
410
+ mtl_index_list.append(mtl.index)
411
+ mtl_index_list.append(mtl.index)
412
+ mtl_index_list.append(mtl.index)
413
+ self._material = mtl_index_list
414
+
415
+ if isinstance(self.material, type(None)):
416
+ self.material = self.engine.material_handler.base
417
+
418
+
419
+ def __repr__(self) -> str:
420
+ """
421
+ Returns a string representation of the node
422
+ """
423
+
424
+ return f'<Bailisk Node | {self.name}, {self.mesh}, ({self.position})>'
425
+
426
+ @property
427
+ def position(self): return self.internal_position
428
+ @property
429
+ def scale(self): return self.internal_scale
430
+ @property
431
+ def rotation(self): return self.internal_rotation
432
+ @property
433
+ def forward(self): return self._forward
434
+ @property
435
+ def mesh(self): return self._mesh
436
+ @property
437
+ def material(self): return self._material
438
+ @property
439
+ def velocity(self): return self._velocity
440
+ @property
441
+ def rotational_velocity(self): return self._rotational_velocity
442
+ @property
443
+ def mass(self):
444
+ if self.physics_body: return self.physics_body.mass
445
+ raise RuntimeError('Node: Cannot access the mass of a node that has no physics body')
446
+ @property
447
+ def static_friction(self):
448
+ if self.collider: return self.collider.static_friction
449
+ raise RuntimeError('Node: Cannot access the static friction of a node that has no collider')
450
+ @property
451
+ def kinetic_friction(self):
452
+ if self.collider: return self.collider.kinetic_friction
453
+ raise RuntimeError('Node: Cannot access the kinetic friction of a node that has no collider')
454
+ @property
455
+ def elasticity(self):
456
+ if self.collider: return self.collider.elasticity
457
+ raise RuntimeError('Node: Cannot access the elasticity of a node that has no collider')
458
+ @property
459
+ def collision_group(self):
460
+ if self.collider: return self.collider.collision_group
461
+ raise RuntimeError('Node: Cannot access the collision_group of a node that has no collider')
462
+ @property
463
+ def name(self): return self._name
464
+ @property
465
+ def tags(self): return self._tags
466
+ @property
467
+ def static(self):
468
+ return self._static if self._static is not None else not(self.physics or any(self.velocity) or any(self.rotational_velocity) or (self.parent and not self.parent.static))
469
+ @property
470
+ def x(self): return self.internal_position.data.x
471
+ @property
472
+ def y(self): return self.internal_position.data.y
473
+ @property
474
+ def z(self): return self.internal_position.data.z
475
+
476
+ # TODO add descriptions in the class header
477
+ @property
478
+ def model_matrix(self):
479
+ if self.needs_model_matrix:
480
+ self._model_matrix = get_model_matrix(self.position, self.scale, self.rotation)
481
+ self.needs_model_matrix = False
482
+ return self._model_matrix
483
+ @property
484
+ def geometric_center(self): # assumes the node has a mesh
485
+ # if not self.mesh: raise RuntimeError('Node: Cannot retrieve geometric center if node does not have mesh')
486
+ if self.needs_geometric_center:
487
+ self._geometric_center = self.model_matrix * self.mesh.geometric_center
488
+ self.needs_geometric_center = False
489
+ return self._geometric_center
490
+ @property
491
+ def center_of_mass(self):
492
+ if not self.mesh: raise RuntimeError('Node: Cannot retrieve center of mass if node does not have mesh')
493
+ return self.model_matrix * self.mesh.center_of_mass
494
+ @property
495
+ def volume(self):
496
+ if not self.mesh: raise RuntimeError('Node: Cannot retrieve volume if node does not have mesh')
497
+ return self.mesh.volume * self.scale.x * self.scale.y * self.scale.z
498
+
499
+ @property
500
+ def physics(self):
501
+ return bool(self.physics_body)
502
+ @property
503
+ def collision(self):
504
+ return bool(self.collider)
505
+
506
+ @property
507
+ def collisions(self):
508
+ assert self.collision, 'Node: Cannot access collision data without collisions enabled on Node'
509
+ return self.collider.collisions
510
+
511
+ @position.setter
512
+ def position(self, value: tuple | list | glm.vec3 | np.ndarray):
513
+ if isinstance(value, glm.vec3): self.internal_position.data = value
514
+ elif isinstance(value, Vec3): self.internal_position.data = value.data
515
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
516
+ if len(value) != 3: raise ValueError(f'Node: Invalid number of values for position. Expected 3, got {len(value)}')
517
+ self.internal_position.data = glm.vec3(value)
518
+ else: raise TypeError(f'Node: Invalid position value type {type(value)}')
519
+
520
+ # recompute relative transforms when user sets transform
521
+ if not self.parent or not self.relative_position: return
522
+ position, scale, rotation = relative_transforms(self.parent, self)
523
+ self.relative_position = position
524
+
525
+ @scale.setter
526
+ def scale(self, value: tuple | list | glm.vec3 | np.ndarray):
527
+ if isinstance(value, glm.vec3): self.internal_scale.data = value
528
+ elif isinstance(value, Vec3): self.internal_scale.data = value.data
529
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
530
+ if len(value) != 3: raise ValueError(f'Node: Invalid number of values for scale. Expected 3, got {len(value)}')
531
+ self.internal_scale.data = glm.vec3(value)
532
+ else: raise TypeError(f'Node: Invalid scale value type {type(value)}')
533
+
534
+ # recompute relative transforms when user sets transform
535
+ if not self.parent or not self.relative_scale: return
536
+ position, scale, rotation = relative_transforms(self.parent, self)
537
+ self.relative_scale = scale
538
+
539
+ @rotation.setter
540
+ def rotation(self, value: tuple | list | glm.vec3 | glm.quat | glm.vec4 | np.ndarray):
541
+ if isinstance(value, glm.quat) or isinstance(value, glm.vec4) or isinstance(value, glm.vec3): self.internal_rotation.data = glm.quat(value)
542
+ elif isinstance(value, Quat): self.internal_rotation.data = value.data
543
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
544
+ if len(value) == 3: self.internal_rotation.data = glm.quat(glm.vec3(*value))
545
+ elif len(value) == 4: self.internal_rotation.data = glm.quat(*value)
546
+ else: raise ValueError(f'Node: Invalid number of values for rotation. Expected 3 or 4, got {len(value)}')
547
+ else: raise TypeError(f'Node: Invalid rotation value type {type(value)}')
548
+
549
+ # recompute relative transforms when user sets transform
550
+ if not self.parent or not self.relative_rotation: return
551
+ position, scale, rotation = relative_transforms(self.parent, self)
552
+ self.relative_rotation = rotation
553
+
554
+ @forward.setter
555
+ def forward(self, value: tuple | list | glm.vec3 | np.ndarray):
556
+ if isinstance(value, glm.vec3): self._forward = value
557
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
558
+ if len(value) != 3: raise ValueError(f'Node: Invalid number of values for forward. Expected 3, got {len(value)}')
559
+ self._forward = glm.vec3(value)
560
+ else: raise TypeError(f'Node: Invalid forward value type {type(value)}')
561
+
562
+ @mesh.setter
563
+ def mesh(self, value: Mesh | None):
564
+ if isinstance(value, Mesh):
565
+ self._mesh = value
566
+ if self.chunk: self.chunk.update()
567
+ elif isinstance(value, type(None)):
568
+ self._mesh = None
569
+ if not self.chunk: return
570
+ self.chunk.remove(self)
571
+ self.chunk.update()
572
+ else: raise TypeError(f'Node: Invalid mesh value type {type(value)}')
573
+
574
+ @material.setter
575
+ def material(self, value: Material):
576
+ if isinstance(value, list):
577
+ self._mtl_list = value
578
+ if not self.node_handler:
579
+ self._material = value
580
+ else:
581
+ mtl_index_list = []
582
+ for mtl in self._mtl_list:
583
+ self.engine.material_handler.add(mtl)
584
+ mtl_index_list.append(mtl.index)
585
+ mtl_index_list.append(mtl.index)
586
+ mtl_index_list.append(mtl.index)
587
+ self._material = mtl_index_list
588
+ elif isinstance(value, Material):
589
+ self._material = value
590
+ if self.node_handler: self.engine.material_handler.add(value)
591
+ elif isinstance(value, type(None)):
592
+ if self.engine: self._material = self.engine.material_handler.base
593
+ else: self._material = None
594
+
595
+ else: raise TypeError(f'Node: Invalid material value type {type(value)}')
596
+ if not self.chunk: return
597
+ self.chunk.node_update_callback(self)
598
+
599
+ @velocity.setter
600
+ def velocity(self, value: tuple | list | glm.vec3 | np.ndarray | Vec3):
601
+ if isinstance(value, glm.vec3): self._velocity = glm.vec3(value)
602
+ elif isinstance(value, Vec3): self._velocity = glm.vec3(value.data)
603
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
604
+ if len(value) != 3: raise ValueError(f'Node: Invalid number of values for velocity. Expected 3, got {len(value)}')
605
+ self._velocity = glm.vec3(value)
606
+ else: raise TypeError(f'Node: Invalid velocity value type {type(value)}')
607
+
608
+ @rotational_velocity.setter
609
+ def rotational_velocity(self, value: tuple | list | glm.vec3 | np.ndarray | Vec3):
610
+ if isinstance(value, glm.vec3): self._rotational_velocity = glm.vec3(value)
611
+ elif isinstance(value, Vec3): self._rotational_velocity = glm.vec3(value.data)
612
+ elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
613
+ if len(value) != 3: raise ValueError(f'Node: Invalid number of values for rotational velocity. Expected 3, got {len(value)}')
614
+ self._rotational_velocity = glm.vec3(value)
615
+ else: raise TypeError(f'Node: Invalid rotational velocity value type {type(value)}')
616
+
617
+ @mass.setter
618
+ def mass(self, value: int | float):
619
+ if not self.physics_body: raise RuntimeError('Node: Cannot set the mass of a node that has no physics body')
620
+ if isinstance(value, int) or isinstance(value, float): self.physics_body.mass = value
621
+ else: raise TypeError(f'Node: Invalid mass value type {type(value)}')
622
+
623
+ @static_friction.setter
624
+ def static_friction(self, value: int | float):
625
+ if not self.collider: raise RuntimeError('Node: Cannot set the static friction of a node that has no physics body')
626
+ if isinstance(value, int) or isinstance(value, float): self.collider.static_friction = value
627
+ else: raise TypeError(f'Node: Invalid static friction value type {type(value)}')
628
+
629
+ @kinetic_friction.setter
630
+ def kinetic_friction(self, value: int | float):
631
+ if not self.collider: raise RuntimeError('Node: Cannot set the kinetic friction of a node that has no physics body')
632
+ if isinstance(value, int) or isinstance(value, float): self.collider.kinetic_friction = value
633
+ else: raise TypeError(f'Node: Invalid kinetic friction value type {type(value)}')
634
+
635
+ @elasticity.setter
636
+ def elasticity(self, value: int | float):
637
+ if not self.collider: raise RuntimeError('Node: Cannot set the elasticity of a node that has no physics body')
638
+ if isinstance(value, int) or isinstance(value, float): self.collider.elasticity = value
639
+ else: raise TypeError(f'Node: Invalid elasticity value type {type(value)}')
640
+
641
+ @collision_group.setter
642
+ def collision_group(self, value: str):
643
+ if not self.collider: raise RuntimeError('Node: Cannot set the collision gruop of a node that has no physics body')
644
+ if isinstance(value, (str, type(None))): self.collider.collision_group = value
645
+ else: raise TypeError(f'Node: Invalid collision group value type {type(value)}')
646
+
647
+ @name.setter
648
+ def name(self, value: str):
649
+ if isinstance(value, str): self._name = value
650
+ else: raise TypeError(f'Node: Invalid name value type {type(value)}')
651
+
652
+ @tags.setter
653
+ def tags(self, value: list[str]):
654
+ if isinstance(value, list) or isinstance(value, tuple):
655
+ for tag in value:
656
+ if not isinstance(tag, str): raise TypeError(f'Node: Invalid tag value in tags list of type {type(tag)}')
657
+ self._tags = value
658
+ else: raise TypeError(f'Node: Invalid tags value type {type(value)}')
659
+
660
+ @static.setter
661
+ def static(self, value: bool):
662
+ self._static = value
663
+
664
+ @x.setter
665
+ def x(self, value: int | float):
666
+ if isinstance(value, int) or isinstance(value, float): self.internal_position.x = value
667
+ else: raise TypeError(f'Node: Invalid positional x value type {type(value)}')
668
+
669
+ @y.setter
670
+ def y(self, value: int | float):
671
+ if isinstance(value, int) or isinstance(value, float): self.internal_position.y = value
672
+ else: raise TypeError(f'Node: Invalid positional y value type {type(value)}')
673
+
674
+ @z.setter
675
+ def z(self, value: int | float):
676
+ if isinstance(value, int) or isinstance(value, float): self.internal_position.z = value
677
+ else: raise TypeError(f'Node: Invalid positional z value type {type(value)}')
678
+
679
+ @physics.setter
680
+ def physics(self, value: bool | PhysicsBody):
681
+ if not value and self.physics: # remove physics body from self and scene
682
+ if self.node_handler: self.node_handler.scene.physics_engine.remove(self.physics_body)
683
+ self.physics_body = None
684
+ elif isinstance(value, PhysicsBody): # deep copy physics body
685
+ if self.physics:
686
+ self.mass = value.mass
687
+ else:
688
+ self.physics_body = PhysicsBody(value.mass)
689
+ if self.node_handler: self.physics_body.physics_engine = self.node_handler.scene.physics_engine
690
+ elif not self.physics:
691
+ self.physics_body = PhysicsBody(mass = 1)
692
+ if self.node_handler: self.physics_body.physics_engine = self.node_handler.scene.physics_engine
693
+
694
+
695
+ @collision.setter
696
+ def collision(self, value: bool | PhysicsBody):
697
+ if not value and self.collision:
698
+ if self.node_handler: self.node_handler.scene.collider_handler.remove(self.collider)
699
+ self.collider = None
700
+ elif isinstance(value, Collider):
701
+ if self.collision:
702
+ self.kinetic_friction = value.kinetic_friction
703
+ self.elasticity = value.elasticity
704
+ self.static_friction = value.static_friction
705
+ else:
706
+ self.collider = Collider(self, value.mesh, value.static_friction, value.kinetic_friction, value.elasticity, value.collision_group)
707
+ if self.node_handler: self.collider.collider_handler = self.node_handler.scene.collider_handler
708
+ elif not self.collider:
709
+ self.collider = Collider(self)
710
710
  if self.node_handler: self.collider.collider_handler = self.node_handler.scene.collider_handler