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