basilisk-engine 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of basilisk-engine might be problematic. Click here for more details.
- basilisk/collisions/broad/broad_aabb.py +8 -1
- basilisk/collisions/broad/broad_bvh.py +8 -1
- basilisk/collisions/collider.py +15 -6
- basilisk/collisions/collider_handler.py +70 -68
- basilisk/collisions/narrow/contact_manifold.py +9 -10
- basilisk/collisions/narrow/dataclasses.py +27 -0
- basilisk/collisions/narrow/deprecated.py +47 -0
- basilisk/collisions/narrow/epa.py +11 -10
- basilisk/collisions/narrow/gjk.py +15 -14
- basilisk/collisions/narrow/helper.py +8 -7
- basilisk/collisions/narrow/sutherland_hodgman.py +52 -0
- basilisk/draw/draw_handler.py +5 -3
- basilisk/engine.py +14 -5
- basilisk/generic/abstract_custom.py +134 -0
- basilisk/generic/collisions.py +46 -0
- basilisk/generic/quat.py +77 -66
- basilisk/generic/vec3.py +91 -67
- basilisk/mesh/cube.py +20 -2
- basilisk/mesh/mesh.py +69 -54
- basilisk/mesh/mesh_from_data.py +106 -21
- basilisk/mesh/narrow_aabb.py +10 -1
- basilisk/mesh/narrow_bvh.py +9 -1
- basilisk/nodes/node.py +169 -30
- basilisk/nodes/node_handler.py +51 -30
- basilisk/particles/__init__.py +0 -0
- basilisk/particles/particle_handler.py +55 -0
- basilisk/particles/particle_renderer.py +87 -0
- basilisk/physics/impulse.py +7 -13
- basilisk/physics/physics_body.py +10 -2
- basilisk/physics/physics_engine.py +1 -2
- basilisk/render/batch.py +2 -2
- basilisk/render/camera.py +5 -0
- basilisk/render/chunk.py +19 -4
- basilisk/render/chunk_handler.py +33 -26
- basilisk/render/image.py +1 -1
- basilisk/render/image_handler.py +7 -5
- basilisk/render/light_handler.py +16 -11
- basilisk/render/material.py +25 -1
- basilisk/render/material_handler.py +22 -13
- basilisk/render/shader.py +7 -7
- basilisk/render/shader_handler.py +13 -12
- basilisk/render/sky.py +5 -3
- basilisk/scene.py +114 -32
- basilisk/shaders/batch.frag +40 -11
- basilisk/shaders/batch.vert +14 -7
- basilisk/shaders/normal.frag +5 -5
- basilisk/shaders/normal.vert +8 -3
- basilisk/shaders/particle.frag +72 -0
- basilisk/shaders/particle.vert +85 -0
- {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.2.dist-info}/METADATA +5 -5
- basilisk_engine-0.1.2.dist-info/RECORD +95 -0
- basilisk/shaders/image.png +0 -0
- basilisk_engine-0.1.0.dist-info/RECORD +0 -88
- {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.2.dist-info}/WHEEL +0 -0
- {basilisk_engine-0.1.0.dist-info → basilisk_engine-0.1.2.dist-info}/top_level.txt +0 -0
basilisk/nodes/node.py
CHANGED
|
@@ -18,6 +18,12 @@ class Node():
|
|
|
18
18
|
"""The scale of the node in meters in each direction"""
|
|
19
19
|
rotation: Quat
|
|
20
20
|
"""The rotation of the node"""
|
|
21
|
+
position_relative: bool
|
|
22
|
+
"""The position of this node relative to the parent node"""
|
|
23
|
+
scale_relative: bool
|
|
24
|
+
"""The scale of this node relative to the parent node"""
|
|
25
|
+
rotation_relative: bool
|
|
26
|
+
"""The rotation of this node relative to the parent node"""
|
|
21
27
|
forward: glm.vec3
|
|
22
28
|
"""The forward facing vector of the node"""
|
|
23
29
|
mesh: Mesh
|
|
@@ -57,10 +63,13 @@ class Node():
|
|
|
57
63
|
shader: Shader
|
|
58
64
|
"""Shader that is used to render the node. If none is given, engine default will be used"""
|
|
59
65
|
|
|
60
|
-
def __init__(self,
|
|
66
|
+
def __init__(self,
|
|
61
67
|
position: glm.vec3=None,
|
|
62
68
|
scale: glm.vec3=None,
|
|
63
69
|
rotation: glm.quat=None,
|
|
70
|
+
relative_position: bool=True,
|
|
71
|
+
relative_scale: bool=True,
|
|
72
|
+
relative_rotation: bool=True,
|
|
64
73
|
forward: glm.vec3=None,
|
|
65
74
|
mesh: Mesh=None,
|
|
66
75
|
material: Material=None,
|
|
@@ -86,8 +95,10 @@ class Node():
|
|
|
86
95
|
"""
|
|
87
96
|
|
|
88
97
|
# parents
|
|
89
|
-
self.node_handler =
|
|
98
|
+
self.node_handler = None
|
|
99
|
+
self.scene = None
|
|
90
100
|
self.chunk = None
|
|
101
|
+
self.parent = None
|
|
91
102
|
|
|
92
103
|
# lazy update variables
|
|
93
104
|
self.needs_geometric_center = True # pos
|
|
@@ -98,21 +109,28 @@ class Node():
|
|
|
98
109
|
self.internal_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
|
|
99
110
|
self.internal_rotation: Quat = Quat(rotation) if rotation else Quat(1, 0, 0, 0)
|
|
100
111
|
|
|
112
|
+
# relative transformations
|
|
113
|
+
self.relative_position = glm.vec3(0, 0, 0) if relative_position else None
|
|
114
|
+
self.relative_scale = glm.vec3(0, 0, 0) if relative_scale else None
|
|
115
|
+
self.relative_rotation = glm.quat(1, 0, 0, 0) if relative_rotation else None
|
|
116
|
+
|
|
101
117
|
self.forward = forward if forward else glm.vec3(1, 0, 0)
|
|
102
|
-
self.mesh = mesh
|
|
103
|
-
self.
|
|
118
|
+
self.mesh = mesh
|
|
119
|
+
self._mtl_list = material if isinstance(material, list) else [material]
|
|
120
|
+
self.material = material if material else None
|
|
104
121
|
self.velocity = velocity if velocity else glm.vec3(0, 0, 0)
|
|
105
122
|
self.rotational_velocity = rotational_velocity if rotational_velocity else glm.vec3(0, 0, 0)
|
|
106
123
|
|
|
107
|
-
|
|
108
|
-
|
|
124
|
+
self.static = static if static != None else not(physics or any(self.velocity) or any(self.rotational_velocity))
|
|
125
|
+
|
|
126
|
+
# Physics updates
|
|
127
|
+
if physics: self.physics_body = PhysicsBody(mass = mass if mass else 1.0)
|
|
109
128
|
elif mass: raise ValueError('Node: cannot have mass if it does not have physics')
|
|
110
129
|
else: self.physics_body = None
|
|
111
130
|
|
|
112
131
|
# collider
|
|
113
132
|
if collisions:
|
|
114
|
-
|
|
115
|
-
self.collider: Collider = self.node_handler.scene.collider_handler.add(
|
|
133
|
+
self.collider = Collider(
|
|
116
134
|
node = self,
|
|
117
135
|
box_mesh = True if collider == 'box' else False,
|
|
118
136
|
static_friction = static_friction,
|
|
@@ -126,13 +144,11 @@ class Node():
|
|
|
126
144
|
elif elasticity: raise ValueError('Node: cannot have elasticity if it does not allow collisions')
|
|
127
145
|
elif collision_group: raise ValueError('Node: cannot have collider group if it does not allow collisions')
|
|
128
146
|
else: self.collider = None
|
|
129
|
-
|
|
147
|
+
|
|
130
148
|
# information and recursion
|
|
131
149
|
self.name = name
|
|
132
150
|
self.tags = tags if tags else []
|
|
133
|
-
|
|
134
|
-
self.static = not(physics or any(self.velocity) or any(self.rotational_velocity))
|
|
135
|
-
else: self.static = static
|
|
151
|
+
|
|
136
152
|
self.data_index = 0
|
|
137
153
|
self.children = []
|
|
138
154
|
|
|
@@ -143,7 +159,7 @@ class Node():
|
|
|
143
159
|
self.previous_position: Vec3 = Vec3(position) if position else Vec3(0, 0, 0)
|
|
144
160
|
self.previous_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
|
|
145
161
|
self.previous_rotation: Quat = Quat(rotation) if rotation else Quat(1, 0, 0, 0) # TODO Do these need to be the callback class or can they just be glm?
|
|
146
|
-
|
|
162
|
+
|
|
147
163
|
# callback function to be added to the custom Vec3 and Quat classes
|
|
148
164
|
def position_callback():
|
|
149
165
|
self.chunk.node_update_callback(self)
|
|
@@ -178,20 +194,55 @@ class Node():
|
|
|
178
194
|
self.internal_position.callback = position_callback
|
|
179
195
|
self.internal_scale.callback = scale_callback
|
|
180
196
|
self.internal_rotation.callback = rotation_callback
|
|
181
|
-
|
|
197
|
+
|
|
198
|
+
def init_scene(self, scene):
|
|
199
|
+
"""
|
|
200
|
+
Updates the scene of the node
|
|
201
|
+
"""
|
|
202
|
+
self.scene = scene
|
|
203
|
+
self.node_handler = scene.node_handler
|
|
204
|
+
|
|
205
|
+
# Update materials
|
|
206
|
+
self.write_materials()
|
|
207
|
+
|
|
208
|
+
# Update the mesh
|
|
209
|
+
self.mesh = self.mesh if self.mesh else self.scene.engine.cube
|
|
210
|
+
|
|
211
|
+
# Update physics and collider
|
|
212
|
+
if self.physics_body: self.physics_body.physics_engine = scene.physics_engine
|
|
213
|
+
if self.collider: self.collider.collider_handler = scene.collider_handler
|
|
214
|
+
|
|
182
215
|
def update(self, dt: float) -> None:
|
|
183
216
|
"""
|
|
184
217
|
Updates the node's movement variables based on the delta time
|
|
185
218
|
"""
|
|
219
|
+
# update based on physical properties
|
|
186
220
|
if any(self.velocity): self.position += dt * self.velocity
|
|
187
|
-
if any(self.rotational_velocity): self.rotation = glm.normalize(self.rotation -
|
|
221
|
+
if any(self.rotational_velocity): self.rotation = glm.normalize(self.rotation - dt / 2 * self.rotation * glm.quat(0, *self.rotational_velocity))
|
|
188
222
|
|
|
189
223
|
if self.physics_body:
|
|
190
224
|
self.velocity += self.physics_body.get_delta_velocity(dt)
|
|
191
225
|
self.rotational_velocity += self.physics_body.get_delta_rotational_velocity(dt)
|
|
226
|
+
|
|
227
|
+
# update children transforms
|
|
228
|
+
for child in self.children: child.sync_data()
|
|
192
229
|
|
|
193
|
-
def sync_data(self
|
|
194
|
-
|
|
230
|
+
def sync_data(self) -> None:
|
|
231
|
+
"""
|
|
232
|
+
Syncronizes this node with the parent node based on its relative positioning
|
|
233
|
+
"""
|
|
234
|
+
# calculate transform matrix with the given input
|
|
235
|
+
transform = glm.mat4x4()
|
|
236
|
+
if self.relative_position: transform = glm.translate(transform, self.parent.position)
|
|
237
|
+
if self.relative_rotation: transform *= glm.transpose(glm.mat4_cast(self.parent.rotation))
|
|
238
|
+
if self.relative_scale: transform = glm.scale(transform, self.parent.scale)
|
|
239
|
+
|
|
240
|
+
# set this node's transforms based on the parent
|
|
241
|
+
self.position = transform * self.relative_position
|
|
242
|
+
self.scale = self.relative_scale * self.parent.scale
|
|
243
|
+
self.rotation = self.relative_rotation * self.parent.rotation
|
|
244
|
+
|
|
245
|
+
for child in self.children: child.sync_data()
|
|
195
246
|
|
|
196
247
|
def get_nodes(self,
|
|
197
248
|
require_mesh: bool=False,
|
|
@@ -217,13 +268,33 @@ class Node():
|
|
|
217
268
|
|
|
218
269
|
# adds children to nodes list if they match the criteria
|
|
219
270
|
for node in self.children: nodes.extend(node.get_nodes(require_mesh, require_collider, require_physics_body, filter_material, filter_tags))
|
|
220
|
-
return
|
|
271
|
+
return nodes
|
|
221
272
|
|
|
222
|
-
|
|
223
|
-
|
|
273
|
+
# tree functions for managing children
|
|
274
|
+
def add(self, child: ..., relative_position: bool=None, relative_scale: bool=None, relative_rotation: glm.vec3=None) -> None:
|
|
275
|
+
"""
|
|
276
|
+
Adopts a node as a child. Relative transforms can be changed, if left bank they will not be chnaged from the current child nodes settings.
|
|
277
|
+
"""
|
|
278
|
+
if child in self.children: return
|
|
279
|
+
|
|
280
|
+
# compute relative transformations
|
|
281
|
+
if relative_position or (relative_position is None and child.relative_position): child.relative_position = child.position - self.position
|
|
282
|
+
if relative_scale or (relative_scale is None and child.relative_scale): child.relative_scale = child.scale / self.scale
|
|
283
|
+
if relative_rotation or (relative_rotation is None and child.relative_rotation): child.relative_rotation = child.rotation * glm.inverse(self.rotation)
|
|
224
284
|
|
|
225
|
-
|
|
226
|
-
|
|
285
|
+
# add as a child to by synchronized and controlled
|
|
286
|
+
if self.node_handler: self.node_handler.add(child)
|
|
287
|
+
child.parent = self
|
|
288
|
+
self.children.append(child)
|
|
289
|
+
|
|
290
|
+
def remove(self, child: ...) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Removes a child node from this nodes chlid list.
|
|
293
|
+
"""
|
|
294
|
+
if child in self.children:
|
|
295
|
+
if self.node_handler: self.node_handler.remove(child)
|
|
296
|
+
child.parent = None
|
|
297
|
+
self.children.remove(child)
|
|
227
298
|
|
|
228
299
|
def apply_force(self, force: glm.vec3, dt: float) -> None:
|
|
229
300
|
"""
|
|
@@ -256,7 +327,7 @@ class Node():
|
|
|
256
327
|
Transforms the mesh inertia tensor and inverts it
|
|
257
328
|
"""
|
|
258
329
|
if not (self.mesh and self.physics_body): return None
|
|
259
|
-
inertia_tensor = self.mesh.get_inertia_tensor(self.scale)
|
|
330
|
+
inertia_tensor = self.mesh.get_inertia_tensor(self.scale) / 2
|
|
260
331
|
|
|
261
332
|
# mass
|
|
262
333
|
if self.physics_body: inertia_tensor *= self.physics_body.mass
|
|
@@ -266,6 +337,12 @@ class Node():
|
|
|
266
337
|
inertia_tensor = rotation_matrix * inertia_tensor * glm.transpose(rotation_matrix)
|
|
267
338
|
|
|
268
339
|
return glm.inverse(inertia_tensor)
|
|
340
|
+
|
|
341
|
+
def get_vertex(self, index) -> glm.vec3:
|
|
342
|
+
"""
|
|
343
|
+
Gets the world space position of a vertex indicated by the index in the mesh
|
|
344
|
+
"""
|
|
345
|
+
return glm.vec3(self.model_matrix * glm.vec4(*self.mesh.points[index], 1))
|
|
269
346
|
|
|
270
347
|
def get_data(self) -> np.ndarray:
|
|
271
348
|
"""
|
|
@@ -274,15 +351,41 @@ class Node():
|
|
|
274
351
|
|
|
275
352
|
# Get data from the mesh node
|
|
276
353
|
mesh_data = self.mesh.data
|
|
277
|
-
node_data = np.array([*self.position, *self.rotation, *self.scale,
|
|
354
|
+
node_data = np.array([*self.position, *self.rotation, *self.scale, 0])
|
|
355
|
+
|
|
356
|
+
per_vertex_mtl = isinstance(self.material, list)
|
|
357
|
+
|
|
358
|
+
if not per_vertex_mtl: node_data[-1] = self.material.index
|
|
278
359
|
|
|
279
360
|
# Create an array to hold the node's data
|
|
280
361
|
data = np.zeros(shape=(mesh_data.shape[0], 25), dtype='f4')
|
|
362
|
+
|
|
363
|
+
|
|
281
364
|
data[:,:14] = mesh_data
|
|
282
365
|
data[:,14:] = node_data
|
|
283
366
|
|
|
367
|
+
if per_vertex_mtl: data[:,24] = self.material
|
|
368
|
+
|
|
284
369
|
return data
|
|
285
370
|
|
|
371
|
+
def write_materials(self):
|
|
372
|
+
"""
|
|
373
|
+
Internal function to write the material list to the material handler and get the material ids
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
if isinstance(self.material, list):
|
|
377
|
+
mtl_index_list = []
|
|
378
|
+
for mtl in self._mtl_list:
|
|
379
|
+
self.node_handler.scene.material_handler.add(mtl)
|
|
380
|
+
mtl_index_list.append(mtl.index)
|
|
381
|
+
mtl_index_list.append(mtl.index)
|
|
382
|
+
mtl_index_list.append(mtl.index)
|
|
383
|
+
self._material = mtl_index_list
|
|
384
|
+
|
|
385
|
+
if isinstance(self.material, type(None)):
|
|
386
|
+
self.material = self.scene.material_handler.base
|
|
387
|
+
|
|
388
|
+
|
|
286
389
|
def __repr__(self) -> str:
|
|
287
390
|
"""
|
|
288
391
|
Returns a string representation of the node
|
|
@@ -298,7 +401,8 @@ class Node():
|
|
|
298
401
|
def rotation(self): return self.internal_rotation.data
|
|
299
402
|
@property
|
|
300
403
|
def forward(self): return self._forward
|
|
301
|
-
|
|
404
|
+
@property
|
|
405
|
+
def mesh(self): return self._mesh
|
|
302
406
|
@property
|
|
303
407
|
def material(self): return self._material
|
|
304
408
|
@property
|
|
@@ -366,6 +470,9 @@ class Node():
|
|
|
366
470
|
if len(value) != 3: raise ValueError(f'Node: Invalid number of values for position. Expected 3, got {len(value)}')
|
|
367
471
|
self.internal_position.data = glm.vec3(value)
|
|
368
472
|
else: raise TypeError(f'Node: Invalid position value type {type(value)}')
|
|
473
|
+
|
|
474
|
+
# # compute relative position if all cases passed
|
|
475
|
+
# if self.parent and self.relative_position: self.relative_position = self.internal_position.data - self.parent.position
|
|
369
476
|
|
|
370
477
|
@scale.setter
|
|
371
478
|
def scale(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
@@ -374,6 +481,9 @@ class Node():
|
|
|
374
481
|
if len(value) != 3: raise ValueError(f'Node: Invalid number of values for scale. Expected 3, got {len(value)}')
|
|
375
482
|
self.internal_scale.data = glm.vec3(value)
|
|
376
483
|
else: raise TypeError(f'Node: Invalid scale value type {type(value)}')
|
|
484
|
+
|
|
485
|
+
# # compute relative scale
|
|
486
|
+
# if self.parent and self.relative_scale: self.relative_scale = self.internal_scale.data / self.parent.scale
|
|
377
487
|
|
|
378
488
|
@rotation.setter
|
|
379
489
|
def rotation(self, value: tuple | list | glm.vec3 | glm.quat | glm.vec4 | np.ndarray):
|
|
@@ -383,6 +493,9 @@ class Node():
|
|
|
383
493
|
elif len(value) == 4: self.internal_rotation.data = glm.quat(*value)
|
|
384
494
|
else: raise ValueError(f'Node: Invalid number of values for rotation. Expected 3 or 4, got {len(value)}')
|
|
385
495
|
else: raise TypeError(f'Node: Invalid rotation value type {type(value)}')
|
|
496
|
+
|
|
497
|
+
# # compute relative rotation
|
|
498
|
+
# if self.parent and self.relative_rotation: self.relative_rotation = self.rotation * glm.inverse(self.parent.rotation)
|
|
386
499
|
|
|
387
500
|
@forward.setter
|
|
388
501
|
def forward(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
@@ -392,16 +505,42 @@ class Node():
|
|
|
392
505
|
self._forward = glm.vec3(value)
|
|
393
506
|
else: raise TypeError(f'Node: Invalid forward value type {type(value)}')
|
|
394
507
|
|
|
395
|
-
|
|
508
|
+
@mesh.setter
|
|
509
|
+
def mesh(self, value: Mesh | None):
|
|
510
|
+
if isinstance(value, Mesh):
|
|
511
|
+
self._mesh = value
|
|
512
|
+
if self.chunk: self.chunk.update()
|
|
513
|
+
elif isinstance(value, type(None)):
|
|
514
|
+
self._mesh = None
|
|
515
|
+
if not self.chunk: return
|
|
516
|
+
self.chunk.remove(self)
|
|
517
|
+
self.chunk.update()
|
|
518
|
+
else: raise TypeError(f'Node: Invalid mesh value type {type(value)}')
|
|
396
519
|
|
|
397
520
|
@material.setter
|
|
398
521
|
def material(self, value: Material):
|
|
399
|
-
if isinstance(value,
|
|
522
|
+
if isinstance(value, list):
|
|
523
|
+
self._mtl_list = value
|
|
524
|
+
if not self.node_handler:
|
|
525
|
+
self._material = value
|
|
526
|
+
else:
|
|
527
|
+
mtl_index_list = []
|
|
528
|
+
for mtl in self._mtl_list:
|
|
529
|
+
self.node_handler.scene.material_handler.add(mtl)
|
|
530
|
+
mtl_index_list.append(mtl.index)
|
|
531
|
+
mtl_index_list.append(mtl.index)
|
|
532
|
+
mtl_index_list.append(mtl.index)
|
|
533
|
+
self._material = mtl_index_list
|
|
534
|
+
elif isinstance(value, Material):
|
|
400
535
|
self._material = value
|
|
401
|
-
self.node_handler.scene.material_handler.add(value)
|
|
402
|
-
|
|
403
|
-
self.
|
|
536
|
+
if self.node_handler: self.node_handler.scene.material_handler.add(value)
|
|
537
|
+
elif isinstance(value, type(None)):
|
|
538
|
+
if self.scene: self._material = self.scene.material_handler.base
|
|
539
|
+
else: self._material = None
|
|
540
|
+
|
|
404
541
|
else: raise TypeError(f'Node: Invalid material value type {type(value)}')
|
|
542
|
+
if not self.chunk: return
|
|
543
|
+
self.chunk.node_update_callback(self)
|
|
405
544
|
|
|
406
545
|
@velocity.setter
|
|
407
546
|
def velocity(self, value: tuple | list | glm.vec3 | np.ndarray):
|
basilisk/nodes/node_handler.py
CHANGED
|
@@ -37,62 +37,83 @@ class NodeHandler():
|
|
|
37
37
|
|
|
38
38
|
self.chunk_handler.render()
|
|
39
39
|
|
|
40
|
-
def add(self,
|
|
41
|
-
position: glm.vec3=None,
|
|
42
|
-
scale: glm.vec3=None,
|
|
43
|
-
rotation: glm.quat=None,
|
|
44
|
-
forward: glm.vec3=None,
|
|
45
|
-
mesh: Mesh=None,
|
|
46
|
-
material: Material=None,
|
|
47
|
-
velocity: glm.vec3=None,
|
|
48
|
-
rotational_velocity: glm.quat=None,
|
|
49
|
-
physics: bool=False,
|
|
50
|
-
mass: float=None,
|
|
51
|
-
collisions: bool=False,
|
|
52
|
-
collider: str=None,
|
|
53
|
-
static_friction: float=None,
|
|
54
|
-
kinetic_friction: float=None,
|
|
55
|
-
elasticity: float=None,
|
|
56
|
-
collision_group : float=None,
|
|
57
|
-
name: str='',
|
|
58
|
-
tags: list[str]=None,
|
|
59
|
-
static: bool=True
|
|
60
|
-
) -> Node:
|
|
40
|
+
def add(self, node: Node) -> Node:
|
|
61
41
|
"""
|
|
62
42
|
Adds a new node to the node handler
|
|
63
43
|
"""
|
|
44
|
+
if node in self.nodes: return
|
|
64
45
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
46
|
+
for n in node.get_nodes(): # gets all nodes including the node to be added
|
|
47
|
+
|
|
48
|
+
# Update scene Handlers
|
|
49
|
+
self.scene.shader_handler.add(n.shader)
|
|
50
|
+
if not n.material: n.material = self.scene.material_handler.base
|
|
51
|
+
self.scene.material_handler.add(n.material)
|
|
52
|
+
|
|
53
|
+
# Update the node attributes
|
|
54
|
+
n.init_scene(self.scene)
|
|
55
|
+
|
|
56
|
+
# Add the node to internal data
|
|
57
|
+
self.nodes.append(n)
|
|
58
|
+
self.chunk_handler.add(n)
|
|
69
59
|
|
|
70
60
|
return node
|
|
61
|
+
|
|
62
|
+
def node_is(self, node: Node, position: glm.vec3=None, scale: glm.vec3=None, rotation: glm.quat=None, forward: glm.vec3=None, mesh: Mesh=None, material: Material=None, velocity: glm.vec3=None, rotational_velocity: glm.quat=None, physics: bool=None, mass: float=None, collisions: bool=None, static_friction: float=None, kinetic_friction: float=None, elasticity: float=None, collision_group: float=None, name: str=None, tags: list[str]=None,static: bool=None) -> bool:
|
|
63
|
+
"""
|
|
64
|
+
Determine if a node meets the requirements given by the parameters. If a parameter is None, then the filter is not applied.
|
|
65
|
+
"""
|
|
66
|
+
return all([
|
|
67
|
+
position is None or position == node.position,
|
|
68
|
+
scale is None or scale == node.scale,
|
|
69
|
+
rotation is None or rotation == node.rotation,
|
|
70
|
+
forward is None or forward == node.forward,
|
|
71
|
+
mesh is None or mesh == node.mesh,
|
|
72
|
+
material is None or material == node.material,
|
|
73
|
+
velocity is None or velocity == node.velocity,
|
|
74
|
+
rotational_velocity is None or rotational_velocity == node.rotational_velocity,
|
|
75
|
+
physics is None or bool(node.physics_body) == physics,
|
|
76
|
+
mass is None or (node.physics_body and mass == node.physics_body.mass),
|
|
77
|
+
collisions is None or bool(node.collider) == collisions,
|
|
78
|
+
static_friction is None or (node.collider and node.collider.static_friction == static_friction),
|
|
79
|
+
kinetic_friction is None or (node.collider and node.collider.kinetic_friction == kinetic_friction),
|
|
80
|
+
elasticity is None or (node.collider and node.collider.elasticity == elasticity),
|
|
81
|
+
collision_group is None or (node.collider and node.collider.collision_group == collision_group),
|
|
82
|
+
name is None or node.name == name,
|
|
83
|
+
tags is None or all([tag in node.tags for tag in tags]),
|
|
84
|
+
static is None or node.static == static
|
|
85
|
+
])
|
|
71
86
|
|
|
72
|
-
def get(self,
|
|
87
|
+
def get(self, position: glm.vec3=None, scale: glm.vec3=None, rotation: glm.quat=None, forward: glm.vec3=None, mesh: Mesh=None, material: Material=None, velocity: glm.vec3=None, rotational_velocity: glm.quat=None, physics: bool=None, mass: float=None, collisions: bool=None, static_friction: float=None, kinetic_friction: float=None, elasticity: float=None, collision_group: float=None, name: str=None, tags: list[str]=None,static: bool=None) -> Node:
|
|
73
88
|
"""
|
|
74
89
|
Returns the first node with the given traits
|
|
75
90
|
"""
|
|
76
91
|
for node in self.nodes:
|
|
77
|
-
if node
|
|
92
|
+
if self.node_is(node, position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static): return node
|
|
78
93
|
return None
|
|
79
94
|
|
|
80
|
-
def get_all(self, name: str) -> Node:
|
|
95
|
+
def get_all(self, position: glm.vec3=None, scale: glm.vec3=None, rotation: glm.quat=None, forward: glm.vec3=None, mesh: Mesh=None, material: Material=None, velocity: glm.vec3=None, rotational_velocity: glm.quat=None, physics: bool=None, mass: float=None, collisions: bool=None, static_friction: float=None, kinetic_friction: float=None, elasticity: float=None, collision_group: float=None, name: str=None, tags: list[str]=None,static: bool=None) -> Node:
|
|
81
96
|
"""
|
|
82
97
|
Returns all nodes with the given traits
|
|
83
98
|
"""
|
|
84
99
|
nodes = []
|
|
85
100
|
for node in self.nodes:
|
|
86
|
-
if node
|
|
101
|
+
if self.node_is(node, position, scale, rotation, forward, mesh, material, velocity, rotational_velocity, physics, mass, collisions, static_friction, kinetic_friction, elasticity, collision_group, name, tags, static): nodes.append(node)
|
|
87
102
|
return nodes
|
|
88
103
|
|
|
89
104
|
def remove(self, node: Node) -> None:
|
|
90
105
|
"""
|
|
91
106
|
Removes a node and all of its children from their handlers
|
|
92
107
|
"""
|
|
108
|
+
|
|
109
|
+
if node == None: return
|
|
110
|
+
|
|
93
111
|
# TODO add support for recursive nodes
|
|
94
112
|
if node in self.nodes:
|
|
95
113
|
if node.physics_body: self.scene.physics_engine.remove(node.physics_body)
|
|
96
114
|
if node.collider: self.scene.collider_handler.remove(node.collider)
|
|
115
|
+
self.chunk_handler.remove(node)
|
|
116
|
+
self.nodes.remove(node)
|
|
97
117
|
node.node_handler = None
|
|
98
|
-
|
|
118
|
+
|
|
119
|
+
for child in node.children: self.remove(child)
|
|
File without changes
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from .particle_renderer import ParticleRenderer
|
|
2
|
+
from ..mesh.mesh import Mesh
|
|
3
|
+
from ..render.material import Material
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ParticleHandler:
|
|
7
|
+
def __init__(self, scene):
|
|
8
|
+
"""
|
|
9
|
+
A handler for all particles in a scene
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
self.scene = scene
|
|
13
|
+
self.cube = Mesh(scene.engine.root + '/bsk_assets/cube.obj')
|
|
14
|
+
self.particle_renderers = {self.cube : ParticleRenderer(scene, self.cube)}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def add(self, mesh: Mesh=None, life=1.0, position=(0, 0, 0), material: Material=None, scale=1.0, velocity=(0, 3, 0), acceleration=(0, -10, 0)) -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Add a new particle to the scene
|
|
20
|
+
Args:
|
|
21
|
+
mesh: Mesh
|
|
22
|
+
The basilisk mesh of the particle
|
|
23
|
+
life: float
|
|
24
|
+
The duration of the particle in seconds
|
|
25
|
+
position: tuple (x, y, z)
|
|
26
|
+
The initial position of the particle
|
|
27
|
+
color: tuple (r, g, b) (components out of 255)
|
|
28
|
+
The color of the particle
|
|
29
|
+
scale: float
|
|
30
|
+
The overall scale factor of the particle
|
|
31
|
+
velocity: tuple (x, y, z)
|
|
32
|
+
The inital velocity of the particle as a vector
|
|
33
|
+
acceleration: tuple (x, y, z)
|
|
34
|
+
The permanent acceleration of the particle as a vector
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Get the mesh and make a new particle renderer if the mesh is new
|
|
38
|
+
if mesh == None: mesh = self.cube
|
|
39
|
+
elif not isinstance(mesh, Mesh): raise ValueError(f'particle_handler.add: invlaid mesh type for particle: {type(mesh)}')
|
|
40
|
+
if mesh not in self.particle_renderers: self.particle_renderers[mesh] = ParticleRenderer(self.scene, mesh)
|
|
41
|
+
|
|
42
|
+
# Get material ID
|
|
43
|
+
if material == None: material_index = 0
|
|
44
|
+
elif isinstance(material, Material):
|
|
45
|
+
self.scene.material_handler.add(material)
|
|
46
|
+
material_index = material.index
|
|
47
|
+
else: raise ValueError(f'particle_handler.add: Invalid particle material type: {type(material)}')
|
|
48
|
+
|
|
49
|
+
# Add the particle to the renderer
|
|
50
|
+
self.particle_renderers[mesh].add(life, position, material_index, scale, velocity, acceleration)
|
|
51
|
+
|
|
52
|
+
def render(self) -> None:
|
|
53
|
+
for renderer in self.particle_renderers.values(): renderer.render()
|
|
54
|
+
def update(self) -> None:
|
|
55
|
+
for renderer in self.particle_renderers.values(): renderer.update()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from ..render.shader import Shader
|
|
3
|
+
from ..mesh.mesh import Mesh
|
|
4
|
+
from ..render.material import Material
|
|
5
|
+
from numba import njit
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@njit
|
|
9
|
+
def update_particle_matrix(particle_instances, dt):
|
|
10
|
+
particle_instances[:,6:9] += particle_instances[:,9:12] * dt
|
|
11
|
+
particle_instances[:,:3] += particle_instances[:,6:9] * dt
|
|
12
|
+
particle_instances[:,5] -= dt/3
|
|
13
|
+
return particle_instances
|
|
14
|
+
|
|
15
|
+
@njit
|
|
16
|
+
def get_alive(particles):
|
|
17
|
+
return particles[particles[:, 5] >= 0]
|
|
18
|
+
|
|
19
|
+
update_particle_matrix(np.zeros(shape=(2, 12), dtype='f4'), 1)
|
|
20
|
+
get_alive(np.zeros(shape=(2, 12), dtype='f4'))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ParticleRenderer:
|
|
24
|
+
def __init__(self, scene: ..., mesh: Mesh) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Handels and renders the particles of a single mesh type
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
self.scene = scene
|
|
30
|
+
self.ctx = scene.ctx
|
|
31
|
+
root = scene.engine.root
|
|
32
|
+
self.shader = Shader(scene.engine, vert=root + '/shaders/particle.vert', frag=root + '/shaders/particle.frag')
|
|
33
|
+
scene.shader_handler.add(self.shader)
|
|
34
|
+
|
|
35
|
+
self.particle_cube_size = 25
|
|
36
|
+
|
|
37
|
+
self.particle_instances = np.zeros(shape=(1, 12), dtype='f4')
|
|
38
|
+
self.instance_buffer = self.ctx.buffer(reserve=(12 * 3) * (self.particle_cube_size ** 3))
|
|
39
|
+
|
|
40
|
+
self.vao = self.ctx.vertex_array( self.shader.program,
|
|
41
|
+
[(self.ctx.buffer(mesh.data), '3f 2f 3f 3f 3f', *['in_position', 'in_uv', 'in_normal', 'in_tangent', 'in_bitangent']),
|
|
42
|
+
(self.instance_buffer, '3f 1f 1f 1f /i', 'in_instance_pos', 'in_instance_mtl', 'scale', 'life')],
|
|
43
|
+
skip_errors=True)
|
|
44
|
+
|
|
45
|
+
def render(self) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Renders the alive particles in the scene
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Get the current particles
|
|
51
|
+
alive_particles = get_alive(self.particle_instances)
|
|
52
|
+
n = len(alive_particles)
|
|
53
|
+
|
|
54
|
+
# Write and render
|
|
55
|
+
self.instance_buffer.write(np.array(alive_particles[:,:6], order='C'))
|
|
56
|
+
self.vao.render(instances=n)
|
|
57
|
+
|
|
58
|
+
def update(self) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Updates the particle positions based on their given properties
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
self.particle_instances = get_alive(self.particle_instances)
|
|
64
|
+
self.particle_instances = update_particle_matrix(self.particle_instances, self.scene.engine.delta_time)
|
|
65
|
+
|
|
66
|
+
def add(self, life=1.0, position=(0, 0, 0), material: int=0, scale=1.0, velocity=(0, 3, 0), acceleration=(0, -10, 0)) -> bool:
|
|
67
|
+
"""
|
|
68
|
+
Add a new particle to the scene
|
|
69
|
+
Args:
|
|
70
|
+
life: float
|
|
71
|
+
The duration of the particle in seconds
|
|
72
|
+
position: tuple (x, y, z)
|
|
73
|
+
The initial position of the particle
|
|
74
|
+
color: tuple (r, g, b) (components out of 255)
|
|
75
|
+
The color of the particle
|
|
76
|
+
scale: float
|
|
77
|
+
The overall scale factor of the particle
|
|
78
|
+
velocity: tuple (x, y, z)
|
|
79
|
+
The inital velocity of the particle as a vector
|
|
80
|
+
acceleration: tuple (x, y, z)
|
|
81
|
+
The permanent acceleration of the particle as a vector
|
|
82
|
+
"""
|
|
83
|
+
# Check if there is already the max number of particles
|
|
84
|
+
if len(self.particle_instances) >= (self.particle_cube_size ** 3): return False
|
|
85
|
+
# Create and add the particle to the scene
|
|
86
|
+
new_particle = np.array([*position, material, scale, life, *velocity, *acceleration])
|
|
87
|
+
self.particle_instances = np.vstack([new_particle, self.particle_instances], dtype='f4')
|
basilisk/physics/impulse.py
CHANGED
|
@@ -21,9 +21,6 @@ def calculate_collisions(normal:glm.vec3, node1: Node, node2: Node, contact_poin
|
|
|
21
21
|
elasticity = max(collider1.elasticity, collider2.elasticity)
|
|
22
22
|
kinetic = min(collider1.kinetic_friction, collider2.kinetic_friction)
|
|
23
23
|
static = min(collider1.static_friction, collider2.static_friction)
|
|
24
|
-
num_points = len(contact_points)
|
|
25
|
-
|
|
26
|
-
total_impulse = glm.vec3(0, 0, 0)
|
|
27
24
|
|
|
28
25
|
# calculate impulses from contact points
|
|
29
26
|
if has_physics1 and has_physics2:
|
|
@@ -32,29 +29,26 @@ def calculate_collisions(normal:glm.vec3, node1: Node, node2: Node, contact_poin
|
|
|
32
29
|
# apply impulse based reduced by total points
|
|
33
30
|
radius1, radius2 = contact_point - center1, contact_point - center2
|
|
34
31
|
impulse = calculate_impulse2(node1, node2, inv_mass1, inv_mass2, node1.rotational_velocity, node2.rotational_velocity, radius1, radius2, inv_inertia1, inv_inertia2, elasticity, kinetic, static, normal)
|
|
35
|
-
total_impulse += impulse / num_points
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
# apply impulses
|
|
34
|
+
apply_impulse(radius1, impulse, inv_inertia1, inv_mass1, node1)
|
|
35
|
+
apply_impulse(radius2, -impulse, inv_inertia2, inv_mass2, node2)
|
|
40
36
|
|
|
41
37
|
elif has_physics1:
|
|
42
38
|
for contact_point in contact_points:
|
|
43
39
|
radius = contact_point - center1
|
|
44
40
|
impulse = calculate_impluse1(node1, inv_mass1, node1.rotational_velocity, radius, inv_inertia1, elasticity, kinetic, static, normal)
|
|
45
|
-
total_impulse += impulse / num_points
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
# apply impulses
|
|
43
|
+
apply_impulse(radius, impulse, inv_inertia1, inv_mass1, node1)
|
|
49
44
|
|
|
50
45
|
else: # only physics body 2
|
|
51
46
|
for contact_point in contact_points:
|
|
52
47
|
radius = contact_point - center2
|
|
53
48
|
impulse = calculate_impluse1(node2, inv_mass2, node2.rotational_velocity, radius, inv_inertia2, elasticity, kinetic, static, normal)
|
|
54
|
-
total_impulse += impulse / num_points
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
# apply impulse
|
|
51
|
+
apply_impulse(radius, impulse, inv_inertia2, inv_mass2, node2)
|
|
58
52
|
|
|
59
53
|
def calculate_impluse1(node: Node, inv_mass, omega, radius, inv_inertia, elasticity, kinetic, static, normal) -> glm.vec3:
|
|
60
54
|
"""
|