basilisk-engine 0.1.2__py3-none-any.whl → 0.1.4__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/__init__.py +2 -1
- basilisk/collisions/collider.py +16 -4
- basilisk/collisions/collider_handler.py +8 -11
- basilisk/collisions/narrow/dataclasses.py +7 -1
- basilisk/collisions/narrow/helper.py +2 -2
- basilisk/draw/draw_handler.py +6 -36
- basilisk/engine.py +6 -2
- basilisk/generic/input_validation.py +41 -2
- basilisk/generic/raycast_result.py +24 -0
- basilisk/input/mouse.py +2 -0
- basilisk/input/path.py +14 -0
- basilisk/mesh/mesh_from_data.py +0 -2
- basilisk/nodes/helper.py +29 -0
- basilisk/nodes/node.py +109 -45
- basilisk/nodes/node_handler.py +9 -32
- basilisk/particles/particle_handler.py +11 -2
- basilisk/render/camera.py +6 -1
- basilisk/render/frame.py +48 -131
- basilisk/render/framebuffer.py +106 -0
- basilisk/render/image.py +22 -10
- basilisk/render/post_process.py +133 -0
- basilisk/scene.py +76 -30
- basilisk/shaders/crt.frag +32 -0
- {basilisk_engine-0.1.2.dist-info → basilisk_engine-0.1.4.dist-info}/METADATA +1 -1
- {basilisk_engine-0.1.2.dist-info → basilisk_engine-0.1.4.dist-info}/RECORD +27 -21
- {basilisk_engine-0.1.2.dist-info → basilisk_engine-0.1.4.dist-info}/WHEEL +0 -0
- {basilisk_engine-0.1.2.dist-info → basilisk_engine-0.1.4.dist-info}/top_level.txt +0 -0
basilisk/nodes/node.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import glm
|
|
2
2
|
import numpy as np
|
|
3
|
+
from .helper import node_is
|
|
3
4
|
from ..generic.vec3 import Vec3
|
|
4
5
|
from ..generic.quat import Quat
|
|
5
6
|
from ..generic.matrices import get_model_matrix
|
|
@@ -38,9 +39,9 @@ class Node():
|
|
|
38
39
|
"""Allows the node's movement to be affected by the physics engine and collisions"""
|
|
39
40
|
mass: float
|
|
40
41
|
"""The mass of the node in kg"""
|
|
41
|
-
|
|
42
|
+
collision: bool
|
|
42
43
|
"""Gives the node collision with other nodes in the scene"""
|
|
43
|
-
|
|
44
|
+
collider_mesh: str
|
|
44
45
|
"""The collider type of the node. Can be either 'box' or 'mesh'"""
|
|
45
46
|
static_friction: float
|
|
46
47
|
"""Determines the friction of the node when still: recommended value 0.0 - 1.0"""
|
|
@@ -57,7 +58,7 @@ class Node():
|
|
|
57
58
|
static: bool
|
|
58
59
|
"""Objects that don't move should be marked as static"""
|
|
59
60
|
chunk: Chunk
|
|
60
|
-
"""
|
|
61
|
+
"""The parent chunk of the node. Used for callbacks to update chunk meshes"""
|
|
61
62
|
children: list
|
|
62
63
|
"""List of nodes that this node is a parent of"""
|
|
63
64
|
shader: Shader
|
|
@@ -77,12 +78,12 @@ class Node():
|
|
|
77
78
|
rotational_velocity: glm.vec3=None,
|
|
78
79
|
physics: bool=False,
|
|
79
80
|
mass: float=None,
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
collision: bool=False,
|
|
82
|
+
collider_mesh: str|Mesh=None,
|
|
82
83
|
static_friction: float=None,
|
|
83
84
|
kinetic_friction: float=None,
|
|
84
85
|
elasticity: float=None,
|
|
85
|
-
collision_group
|
|
86
|
+
collision_group: float=None,
|
|
86
87
|
name: str='',
|
|
87
88
|
tags: list[str]=None,
|
|
88
89
|
static: bool=None,
|
|
@@ -121,7 +122,7 @@ class Node():
|
|
|
121
122
|
self.velocity = velocity if velocity else glm.vec3(0, 0, 0)
|
|
122
123
|
self.rotational_velocity = rotational_velocity if rotational_velocity else glm.vec3(0, 0, 0)
|
|
123
124
|
|
|
124
|
-
self.
|
|
125
|
+
self._static = static
|
|
125
126
|
|
|
126
127
|
# Physics updates
|
|
127
128
|
if physics: self.physics_body = PhysicsBody(mass = mass if mass else 1.0)
|
|
@@ -129,16 +130,16 @@ class Node():
|
|
|
129
130
|
else: self.physics_body = None
|
|
130
131
|
|
|
131
132
|
# collider
|
|
132
|
-
if
|
|
133
|
+
if collision:
|
|
133
134
|
self.collider = Collider(
|
|
134
135
|
node = self,
|
|
135
|
-
|
|
136
|
+
collider_mesh = collider_mesh,
|
|
136
137
|
static_friction = static_friction,
|
|
137
138
|
kinetic_friction = kinetic_friction,
|
|
138
139
|
elasticity = elasticity,
|
|
139
140
|
collision_group = collision_group
|
|
140
141
|
)
|
|
141
|
-
elif
|
|
142
|
+
elif collider_mesh: raise ValueError('Node: cannot have collider mesh if it does not allow collisions')
|
|
142
143
|
elif static_friction: raise ValueError('Node: cannot have static friction if it does not allow collisions')
|
|
143
144
|
elif kinetic_friction: raise ValueError('Node: cannot have kinetic friction if it does not allow collisions')
|
|
144
145
|
elif elasticity: raise ValueError('Node: cannot have elasticity if it does not allow collisions')
|
|
@@ -154,15 +155,11 @@ class Node():
|
|
|
154
155
|
|
|
155
156
|
# Shader given by user or none for default
|
|
156
157
|
self.shader = shader
|
|
157
|
-
|
|
158
|
-
# default callback functions for node transform
|
|
159
|
-
self.previous_position: Vec3 = Vec3(position) if position else Vec3(0, 0, 0)
|
|
160
|
-
self.previous_scale : Vec3 = Vec3(scale) if scale else Vec3(1, 1, 1)
|
|
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?
|
|
162
158
|
|
|
163
159
|
# callback function to be added to the custom Vec3 and Quat classes
|
|
164
160
|
def position_callback():
|
|
165
|
-
self.chunk
|
|
161
|
+
if self.chunk:
|
|
162
|
+
self.chunk.node_update_callback(self)
|
|
166
163
|
|
|
167
164
|
# update variables
|
|
168
165
|
self.needs_geometric_center = True
|
|
@@ -172,7 +169,8 @@ class Node():
|
|
|
172
169
|
self.collider.needs_obb = True
|
|
173
170
|
|
|
174
171
|
def scale_callback():
|
|
175
|
-
self.chunk
|
|
172
|
+
if self.chunk:
|
|
173
|
+
self.chunk.node_update_callback(self)
|
|
176
174
|
|
|
177
175
|
# update variables
|
|
178
176
|
self.needs_model_matrix = True
|
|
@@ -182,7 +180,8 @@ class Node():
|
|
|
182
180
|
self.collider.needs_half_dimensions = True
|
|
183
181
|
|
|
184
182
|
def rotation_callback():
|
|
185
|
-
self.chunk
|
|
183
|
+
if self.chunk:
|
|
184
|
+
self.chunk.node_update_callback(self)
|
|
186
185
|
|
|
187
186
|
# update variables
|
|
188
187
|
self.needs_model_matrix = True
|
|
@@ -244,31 +243,42 @@ class Node():
|
|
|
244
243
|
|
|
245
244
|
for child in self.children: child.sync_data()
|
|
246
245
|
|
|
247
|
-
def
|
|
248
|
-
require_mesh: bool=False,
|
|
249
|
-
require_collider: bool=False,
|
|
250
|
-
require_physics_body: bool=False,
|
|
251
|
-
filter_material: Material=None,
|
|
252
|
-
filter_tags: list[str]=None
|
|
253
|
-
) -> list:
|
|
246
|
+
def deep_copy(self) -> ...:
|
|
254
247
|
"""
|
|
255
|
-
|
|
248
|
+
Creates a deep copy of this node and returns it. The new node is not added to the scene.
|
|
256
249
|
"""
|
|
257
|
-
|
|
258
|
-
|
|
250
|
+
|
|
251
|
+
copy = Node(
|
|
252
|
+
position = self.position,
|
|
253
|
+
scale = self.scale,
|
|
254
|
+
rotation = self.rotation,
|
|
255
|
+
relative_position = bool(self.relative_position),
|
|
256
|
+
relative_scale = bool(self.relative_scale),
|
|
257
|
+
relative_rotation = bool(self.relative_rotation),
|
|
258
|
+
forward = glm.vec3(self.forward),
|
|
259
|
+
mesh = self.mesh,
|
|
260
|
+
material = self.material,
|
|
261
|
+
velocity = glm.vec3(self.velocity),
|
|
262
|
+
rotational_velocity = glm.vec3(self.rotational_velocity),
|
|
263
|
+
physics = bool(self.physics_body),
|
|
264
|
+
mass = self.mass if self.physics_body else None,
|
|
265
|
+
collision = bool(self.collider),
|
|
266
|
+
static_friction = self.static_friction if self.collider else None,
|
|
267
|
+
kinetic_friction = self.kinetic_friction if self.collider else None,
|
|
268
|
+
elasticity = self.elasticity if self.collider else None,
|
|
269
|
+
collision_group = self.collision_group if self.collider else None,
|
|
270
|
+
name = self.name,
|
|
271
|
+
tags = [tag for tag in self.tags], # deep copy tags list
|
|
272
|
+
static = self.static,
|
|
273
|
+
shader = self.shader
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return copy
|
|
259
277
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
(not filter_material or self.material == filter_material),
|
|
265
|
-
(not filter_tags or all([tag in self.tags for tag in filter_tags]))
|
|
266
|
-
]):
|
|
267
|
-
nodes.append(self)
|
|
268
|
-
|
|
269
|
-
# adds children to nodes list if they match the criteria
|
|
270
|
-
for node in self.children: nodes.extend(node.get_nodes(require_mesh, require_collider, require_physics_body, filter_material, filter_tags))
|
|
271
|
-
return nodes
|
|
278
|
+
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:
|
|
279
|
+
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 []
|
|
280
|
+
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)
|
|
281
|
+
return nodes
|
|
272
282
|
|
|
273
283
|
# tree functions for managing children
|
|
274
284
|
def add(self, child: ..., relative_position: bool=None, relative_scale: bool=None, relative_rotation: glm.vec3=None) -> None:
|
|
@@ -308,7 +318,7 @@ class Node():
|
|
|
308
318
|
"""
|
|
309
319
|
# translation
|
|
310
320
|
assert self.physics_body, 'Node: Cannot apply a force to a node that doesn\'t have a physics body'
|
|
311
|
-
self.velocity
|
|
321
|
+
self.velocity += force / self.mass * dt
|
|
312
322
|
|
|
313
323
|
# rotation
|
|
314
324
|
torque = glm.cross(offset, force)
|
|
@@ -326,8 +336,9 @@ class Node():
|
|
|
326
336
|
"""
|
|
327
337
|
Transforms the mesh inertia tensor and inverts it
|
|
328
338
|
"""
|
|
329
|
-
if not (self.mesh and self.physics_body): return None
|
|
330
|
-
|
|
339
|
+
if not ((self.mesh or (self.collider and self.collider.mesh)) and self.physics_body): return None
|
|
340
|
+
mesh = self.collider.mesh if self.collider else self.mesh
|
|
341
|
+
inertia_tensor = mesh.get_inertia_tensor(self.scale) / 2
|
|
331
342
|
|
|
332
343
|
# mass
|
|
333
344
|
if self.physics_body: inertia_tensor *= self.physics_body.mass
|
|
@@ -434,6 +445,9 @@ class Node():
|
|
|
434
445
|
@property
|
|
435
446
|
def tags(self): return self._tags
|
|
436
447
|
@property
|
|
448
|
+
def static(self):
|
|
449
|
+
return self._static if self._static is not None else not(self.physics or any(self.velocity) or any(self.rotational_velocity))
|
|
450
|
+
@property
|
|
437
451
|
def x(self): return self.internal_position.data.x
|
|
438
452
|
@property
|
|
439
453
|
def y(self): return self.internal_position.data.y
|
|
@@ -463,6 +477,18 @@ class Node():
|
|
|
463
477
|
if not self.mesh: raise RuntimeError('Node: Cannot retrieve volume if node does not have mesh')
|
|
464
478
|
return self.mesh.volume * self.scale.x * self.scale.y * self.scale.z
|
|
465
479
|
|
|
480
|
+
@property
|
|
481
|
+
def physics(self):
|
|
482
|
+
return bool(self.physics_body)
|
|
483
|
+
@property
|
|
484
|
+
def collision(self):
|
|
485
|
+
return bool(self.collider)
|
|
486
|
+
|
|
487
|
+
@property
|
|
488
|
+
def collisions(self):
|
|
489
|
+
assert self.collision, 'Node: Cannot access collision data without collisions enabled on Node'
|
|
490
|
+
return self.collider.collisions
|
|
491
|
+
|
|
466
492
|
@position.setter
|
|
467
493
|
def position(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
468
494
|
if isinstance(value, glm.vec3): self.internal_position.data = value
|
|
@@ -585,7 +611,7 @@ class Node():
|
|
|
585
611
|
@collision_group.setter
|
|
586
612
|
def collision_group(self, value: str):
|
|
587
613
|
if not self.collider: raise RuntimeError('Node: Cannot set the collision gruop of a node that has no physics body')
|
|
588
|
-
if isinstance(value, str): self.collider.collision_group = value
|
|
614
|
+
if isinstance(value, (str, type(None))): self.collider.collision_group = value
|
|
589
615
|
else: raise TypeError(f'Node: Invalid collision group value type {type(value)}')
|
|
590
616
|
|
|
591
617
|
@name.setter
|
|
@@ -600,6 +626,10 @@ class Node():
|
|
|
600
626
|
if not isinstance(tag, str): raise TypeError(f'Node: Invalid tag value in tags list of type {type(tag)}')
|
|
601
627
|
self._tags = value
|
|
602
628
|
else: raise TypeError(f'Node: Invalid tags value type {type(value)}')
|
|
629
|
+
|
|
630
|
+
@static.setter
|
|
631
|
+
def static(self, value: bool):
|
|
632
|
+
self._static = value
|
|
603
633
|
|
|
604
634
|
@x.setter
|
|
605
635
|
def x(self, value: int | float):
|
|
@@ -614,4 +644,38 @@ class Node():
|
|
|
614
644
|
@z.setter
|
|
615
645
|
def z(self, value: int | float):
|
|
616
646
|
if isinstance(value, int) or isinstance(value, float): self.internal_position.z = value
|
|
617
|
-
else: raise TypeError(f'Node: Invalid positional z value type {type(value)}')
|
|
647
|
+
else: raise TypeError(f'Node: Invalid positional z value type {type(value)}')
|
|
648
|
+
|
|
649
|
+
@physics.setter
|
|
650
|
+
def physics(self, value: bool | PhysicsBody):
|
|
651
|
+
if not value and self.physics: # remove physics body from self and scene
|
|
652
|
+
if self.node_handler: self.node_handler.scene.physics_engine.remove(self.physics_body)
|
|
653
|
+
self.physics_body = None
|
|
654
|
+
elif isinstance(value, PhysicsBody): # deep copy physics body
|
|
655
|
+
if self.physics:
|
|
656
|
+
self.mass = value.mass
|
|
657
|
+
else:
|
|
658
|
+
self.physics_body = PhysicsBody(value.mass)
|
|
659
|
+
if self.node_handler: self.physics_body.physics_engine = self.node_handler.scene.physics_engine
|
|
660
|
+
elif not self.physics:
|
|
661
|
+
self.physics_body = PhysicsBody(mass = 1)
|
|
662
|
+
if self.node_handler: self.physics_body.physics_engine = self.node_handler.scene.physics_engine
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
@collision.setter
|
|
666
|
+
def collision(self, value: bool | PhysicsBody):
|
|
667
|
+
if not value and self.collision:
|
|
668
|
+
if self.node_handler: self.node_handler.scene.collider_handler.remove(self.collider)
|
|
669
|
+
self.collider = None
|
|
670
|
+
elif isinstance(value, Collider):
|
|
671
|
+
if self.collision:
|
|
672
|
+
self.kinetic_friction = value.kinetic_friction
|
|
673
|
+
self.elasticity = value.elasticity
|
|
674
|
+
self.static_friction = value.static_friction
|
|
675
|
+
else:
|
|
676
|
+
self.collider = Collider(self, value.mesh, value.static_friction, value.kinetic_friction, value.elasticity, value.collision_group)
|
|
677
|
+
if self.node_handler: self.collider.collider_handler = self.node_handler.scene.collider_handler
|
|
678
|
+
elif not self.collider:
|
|
679
|
+
self.collider = Collider(self)
|
|
680
|
+
if self.node_handler: self.collider.collider_handler = self.node_handler.scene.collider_handler
|
|
681
|
+
|
basilisk/nodes/node_handler.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import glm
|
|
2
2
|
from .node import Node
|
|
3
|
+
from .helper import node_is
|
|
3
4
|
from ..render.chunk_handler import ChunkHandler
|
|
4
5
|
from ..mesh.mesh import Mesh
|
|
5
6
|
from ..render.material import Material
|
|
@@ -25,9 +26,10 @@ class NodeHandler():
|
|
|
25
26
|
"""
|
|
26
27
|
Updates the nodes and chunks in the scene
|
|
27
28
|
"""
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
node
|
|
29
|
+
dt = self.scene.engine.delta_time
|
|
30
|
+
if dt < 0.5:
|
|
31
|
+
for node in self.nodes:
|
|
32
|
+
if not node.static: node.update(dt)
|
|
31
33
|
self.chunk_handler.update()
|
|
32
34
|
|
|
33
35
|
def render(self):
|
|
@@ -43,7 +45,7 @@ class NodeHandler():
|
|
|
43
45
|
"""
|
|
44
46
|
if node in self.nodes: return
|
|
45
47
|
|
|
46
|
-
for n in node.
|
|
48
|
+
for n in node.get_all(): # gets all nodes including the node to be added
|
|
47
49
|
|
|
48
50
|
# Update scene Handlers
|
|
49
51
|
self.scene.shader_handler.add(n.shader)
|
|
@@ -58,47 +60,22 @@ class NodeHandler():
|
|
|
58
60
|
self.chunk_handler.add(n)
|
|
59
61
|
|
|
60
62
|
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
|
-
])
|
|
86
63
|
|
|
87
64
|
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:
|
|
88
65
|
"""
|
|
89
66
|
Returns the first node with the given traits
|
|
90
67
|
"""
|
|
91
68
|
for node in self.nodes:
|
|
92
|
-
if
|
|
69
|
+
if 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
|
|
93
70
|
return None
|
|
94
71
|
|
|
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:
|
|
72
|
+
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[Node]:
|
|
96
73
|
"""
|
|
97
74
|
Returns all nodes with the given traits
|
|
98
75
|
"""
|
|
99
76
|
nodes = []
|
|
100
77
|
for node in self.nodes:
|
|
101
|
-
if
|
|
78
|
+
if 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)
|
|
102
79
|
return nodes
|
|
103
80
|
|
|
104
81
|
def remove(self, node: Node) -> None:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from .particle_renderer import ParticleRenderer
|
|
2
2
|
from ..mesh.mesh import Mesh
|
|
3
3
|
from ..render.material import Material
|
|
4
|
-
|
|
4
|
+
from ..generic.input_validation import validate_tuple3, validate_float
|
|
5
5
|
|
|
6
6
|
class ParticleHandler:
|
|
7
7
|
def __init__(self, scene):
|
|
@@ -14,7 +14,7 @@ class ParticleHandler:
|
|
|
14
14
|
self.particle_renderers = {self.cube : ParticleRenderer(scene, self.cube)}
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def add(self, mesh: Mesh=None, life=1.0, position=
|
|
17
|
+
def add(self, mesh: Mesh=None, life: float=1.0, position: tuple|float=0, material: Material=None, scale: float=1.0, velocity: tuple|float=0, acceleration: tuple|float=0) -> bool:
|
|
18
18
|
"""
|
|
19
19
|
Add a new particle to the scene
|
|
20
20
|
Args:
|
|
@@ -46,6 +46,15 @@ class ParticleHandler:
|
|
|
46
46
|
material_index = material.index
|
|
47
47
|
else: raise ValueError(f'particle_handler.add: Invalid particle material type: {type(material)}')
|
|
48
48
|
|
|
49
|
+
# Validate the 3-component vectors
|
|
50
|
+
position = validate_tuple3('particle', 'add', position)
|
|
51
|
+
velocity = validate_tuple3('particle', 'add', velocity)
|
|
52
|
+
acceleration = validate_tuple3('particle', 'add', acceleration)
|
|
53
|
+
|
|
54
|
+
# Validate float inputs
|
|
55
|
+
life = validate_float('particle', 'add', life)
|
|
56
|
+
scale = validate_float('particle', 'add', scale)
|
|
57
|
+
|
|
49
58
|
# Add the particle to the renderer
|
|
50
59
|
self.particle_renderers[mesh].add(life, position, material_index, scale, velocity, acceleration)
|
|
51
60
|
|
basilisk/render/camera.py
CHANGED
|
@@ -99,6 +99,8 @@ class Camera:
|
|
|
99
99
|
def position(self): return self._position
|
|
100
100
|
@property
|
|
101
101
|
def direction(self): return self.forward
|
|
102
|
+
@property
|
|
103
|
+
def horizontal(self): return glm.normalize(self.forward.xz)
|
|
102
104
|
|
|
103
105
|
@scene.setter
|
|
104
106
|
def scene(self, value):
|
|
@@ -106,6 +108,7 @@ class Camera:
|
|
|
106
108
|
self._scene = value
|
|
107
109
|
self.engine = self._scene.engine
|
|
108
110
|
self.use()
|
|
111
|
+
|
|
109
112
|
@position.setter
|
|
110
113
|
def position(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
111
114
|
if isinstance(value, glm.vec3): self._position = glm.vec3(value)
|
|
@@ -113,6 +116,7 @@ class Camera:
|
|
|
113
116
|
if len(value) != 3: raise ValueError(f'Camera: Invalid number of values for position. Expected 3, got {len(value)}')
|
|
114
117
|
self._position = glm.vec3(value)
|
|
115
118
|
else: raise TypeError(f'Camera: Invalid position value type {type(value)}')
|
|
119
|
+
|
|
116
120
|
@direction.setter
|
|
117
121
|
def direction(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
118
122
|
if isinstance(value, glm.vec3): self.direction = glm.normalize(glm.vec3(value))
|
|
@@ -180,9 +184,10 @@ class FollowCamera(FreeCamera):
|
|
|
180
184
|
self.position = self.parent.position + self.offest
|
|
181
185
|
|
|
182
186
|
class OrbitCamera(FreeCamera):
|
|
183
|
-
def __init__(self, parent, position=(0, 0, 20), yaw=-90, pitch=0, distance=5):
|
|
187
|
+
def __init__(self, parent, position=(0, 0, 20), yaw=-90, pitch=0, distance=5, offset=(0, 0)):
|
|
184
188
|
self.parent = parent
|
|
185
189
|
self.distance = distance
|
|
190
|
+
self.offset = glm.vec2(offset)
|
|
186
191
|
super().__init__(position, yaw, pitch)
|
|
187
192
|
|
|
188
193
|
def get_view_matrix(self) -> glm.mat4x4:
|
basilisk/render/frame.py
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import moderngl as mgl
|
|
3
|
-
from
|
|
3
|
+
from .shader import Shader
|
|
4
|
+
from .framebuffer import Framebuffer
|
|
4
5
|
|
|
6
|
+
from .post_process import PostProcess
|
|
5
7
|
|
|
6
8
|
class Frame:
|
|
7
|
-
|
|
9
|
+
shader: Shader=None
|
|
8
10
|
vbo: mgl.Buffer=None
|
|
9
11
|
vao: mgl.VertexArray=None
|
|
10
|
-
frame_texture: mgl.Texture=None
|
|
11
|
-
depth_texture: mgl.Texture=None
|
|
12
12
|
framebuffer: mgl.Framebuffer=None
|
|
13
|
-
pingpong_frame_texture: mgl.Texture=None
|
|
14
|
-
pingpong_depth_texture: mgl.Texture=None
|
|
15
|
-
pingpong_framebuffer: mgl.Framebuffer=None
|
|
16
|
-
postprocess: dict=None
|
|
17
13
|
|
|
18
|
-
def __init__(self, scene) -> None:
|
|
14
|
+
def __init__(self, scene, resolution=1, filter=(mgl.LINEAR, mgl.LINEAR)) -> None:
|
|
19
15
|
"""
|
|
20
16
|
Basilisk render destination.
|
|
21
17
|
Can be used to render to the screen or for headless rendering
|
|
@@ -25,27 +21,44 @@ class Frame:
|
|
|
25
21
|
self.engine = scene.engine
|
|
26
22
|
self.ctx = scene.ctx
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
self.
|
|
30
|
-
self.
|
|
31
|
-
self.
|
|
24
|
+
# Load framebuffer
|
|
25
|
+
self.resolution = resolution
|
|
26
|
+
self.filter = filter
|
|
27
|
+
size = tuple(map(lambda x: int(x * self.resolution), self.engine.win_size))
|
|
28
|
+
self.framebuffer = Framebuffer(self.engine, size=size, filter=self.filter)
|
|
29
|
+
self.ping_pong_buffer = Framebuffer(self.engine, size=size, filter=self.filter)
|
|
30
|
+
|
|
31
|
+
# Load Shaders
|
|
32
|
+
self.shader = Shader(self.engine, self.engine.root + '/shaders/frame.vert', self.engine.root + '/shaders/frame.frag')
|
|
33
|
+
self.scene.shader_handler.add(self.shader)
|
|
34
|
+
|
|
35
|
+
# Load VAO
|
|
36
|
+
self.vbo = self.ctx.buffer(np.array([[-1, -1, 0, 0, 0], [1, -1, 0, 1, 0], [1, 1, 0, 1, 1], [-1, 1, 0, 0, 1], [-1, -1, 0, 0, 0], [1, 1, 0, 1, 1]], dtype='f4'))
|
|
37
|
+
self.vao = self.ctx.vertex_array(self.shader.program, [(self.vbo, '3f 2f', 'in_position', 'in_uv')], skip_errors=True)
|
|
38
|
+
|
|
39
|
+
# TEMP TESTING
|
|
40
|
+
self.post_processes = []
|
|
32
41
|
|
|
33
|
-
self.postprocess = {}
|
|
34
|
-
self.load_post_shader('frame', 'filter')
|
|
35
42
|
|
|
36
|
-
def render(self):
|
|
43
|
+
def render(self) -> None:
|
|
37
44
|
"""
|
|
38
45
|
Renders the current frame to the screen
|
|
39
46
|
"""
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
for process in self.post_processes:
|
|
49
|
+
process.apply(self.framebuffer, self.ping_pong_buffer)
|
|
50
|
+
|
|
51
|
+
temp = self.framebuffer
|
|
52
|
+
self.framebuffer = self.ping_pong_buffer
|
|
53
|
+
self.ping_pong_buffer = temp
|
|
42
54
|
|
|
43
55
|
self.ctx.screen.use()
|
|
44
|
-
self.program['screenTexture'] = 0
|
|
45
|
-
self.framebuffer.
|
|
56
|
+
self.shader.program['screenTexture'] = 0
|
|
57
|
+
self.framebuffer.texture.use(location=0)
|
|
46
58
|
self.vao.render()
|
|
47
59
|
|
|
48
|
-
|
|
60
|
+
|
|
61
|
+
def use(self) -> None:
|
|
49
62
|
"""
|
|
50
63
|
Uses the frame as a render target
|
|
51
64
|
"""
|
|
@@ -53,130 +66,34 @@ class Frame:
|
|
|
53
66
|
self.framebuffer.use()
|
|
54
67
|
self.framebuffer.clear()
|
|
55
68
|
|
|
56
|
-
def
|
|
69
|
+
def add_post_process(self, post_process: PostProcess) -> PostProcess:
|
|
57
70
|
"""
|
|
58
|
-
|
|
71
|
+
Add a post process to the frames post process stack
|
|
59
72
|
"""
|
|
60
73
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
data = self.framebuffer.read(components=3, alignment=1)
|
|
64
|
-
img = Image.frombytes('RGB', self.framebuffer.size, data).transpose(Image.FLIP_TOP_BOTTOM)
|
|
65
|
-
img.save(f'{path}.png')
|
|
66
|
-
|
|
74
|
+
self.post_processes.append(post_process)
|
|
75
|
+
return post_process
|
|
67
76
|
|
|
68
|
-
def
|
|
69
|
-
"""
|
|
70
|
-
Loads the frame shaders
|
|
71
|
-
"""
|
|
72
|
-
|
|
73
|
-
# Release any existing memory
|
|
74
|
-
if self.program: self.program.release()
|
|
75
|
-
|
|
76
|
-
# Read the shaders
|
|
77
|
-
with open(self.engine.root + '/shaders/frame.vert') as file:
|
|
78
|
-
vertex_shader = file.read()
|
|
79
|
-
with open(self.engine.root + '/shaders/frame.frag') as file:
|
|
80
|
-
fragment_shader = file.read()
|
|
81
|
-
|
|
82
|
-
# Create the program
|
|
83
|
-
self.program = self.ctx.program(vertex_shader=vertex_shader, fragment_shader=fragment_shader)
|
|
84
|
-
|
|
85
|
-
def load_post_shader(self, vert: str, frag: str) -> None:
|
|
86
|
-
"""
|
|
87
|
-
Loads a post processing shader
|
|
77
|
+
def save(self, destination: str=None) -> None:
|
|
88
78
|
"""
|
|
89
|
-
|
|
90
|
-
# Read the shaders
|
|
91
|
-
with open(self.engine.root + f'/shaders/{vert}.vert') as file:
|
|
92
|
-
vertex_shader = file.read()
|
|
93
|
-
with open(self.engine.root + f'/shaders/{frag}.frag') as file:
|
|
94
|
-
fragment_shader = file.read()
|
|
95
|
-
|
|
96
|
-
# Create the program
|
|
97
|
-
program = self.ctx.program(vertex_shader=vertex_shader, fragment_shader=fragment_shader)
|
|
98
|
-
self.postprocess[frag] = self.ctx.vertex_array(program, [(self.vbo, '3f 2f', 'in_position', 'in_uv')], skip_errors=True)
|
|
99
|
-
|
|
100
|
-
def apply_postprocess(self, shader: str):
|
|
101
|
-
self.pingpong_framebuffer.use()
|
|
102
|
-
self.pingpong_framebuffer.clear()
|
|
103
|
-
self.postprocess[shader].program['screenTexture'] = 0
|
|
104
|
-
self.framebuffer.color_attachments[0].use(location=0)
|
|
105
|
-
self.postprocess[shader].render()
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
temp = self.framebuffer
|
|
109
|
-
self.framebuffer = self.pingpong_framebuffer
|
|
110
|
-
self.pingpong_framebuffer = temp
|
|
111
|
-
|
|
112
|
-
# self.use()
|
|
113
|
-
# self.postprocess[shader].program['screenTexture'] = 0
|
|
114
|
-
# self.pingpong_frame_texture.use(location=0)
|
|
115
|
-
# self.vao.render()
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def set_textures(self, viewport: tuple=None) -> None:
|
|
119
|
-
"""
|
|
120
|
-
Sets the framebuffer textures
|
|
79
|
+
Saves the frame as an image to the given file destination
|
|
121
80
|
"""
|
|
122
81
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if self.framebuffer: self.framebuffer.release()
|
|
127
|
-
if self.pingpong_frame_texture: self.pingpong_frame_texture.release()
|
|
128
|
-
if self.pingpong_depth_texture: self.pingpong_depth_texture.release()
|
|
129
|
-
if self.pingpong_framebuffer: self.pingpong_framebuffer.release()
|
|
130
|
-
|
|
131
|
-
# Get the size from the engine window if the not specified by the function call
|
|
132
|
-
size = viewport if viewport else self.engine.win_size
|
|
133
|
-
|
|
134
|
-
# Create textures and frame buffer object
|
|
135
|
-
self.frame_texture = self.ctx.texture(size, components=4)
|
|
136
|
-
self.depth_texture = self.ctx.depth_texture(size)
|
|
137
|
-
self.framebuffer = self.ctx.framebuffer([self.frame_texture], self.depth_texture)
|
|
138
|
-
self.pingpong_frame_texture = self.ctx.texture(size, components=4)
|
|
139
|
-
self.pingpong_depth_texture = self.ctx.depth_texture(size)
|
|
140
|
-
self.pingpong_framebuffer = self.ctx.framebuffer([self.pingpong_frame_texture], self.pingpong_depth_texture)
|
|
141
|
-
|
|
142
|
-
def set_renderer(self) -> None:
|
|
82
|
+
self.framebuffer.save(destination)
|
|
83
|
+
|
|
84
|
+
def resize(self, size: tuple[int]=None) -> None:
|
|
143
85
|
"""
|
|
144
|
-
|
|
86
|
+
Resize the frame to the given size. None for window size
|
|
145
87
|
"""
|
|
146
|
-
|
|
147
|
-
# Release any existing memory
|
|
148
|
-
if self.vbo: self.vbo.release()
|
|
149
|
-
if self.vao: self.vao.release()
|
|
150
88
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
uv_verticies = [(0, 0), (1, 0), (1, 1), (0, 1)]
|
|
155
|
-
uv_indicies = [(3, 0, 1),(2, 3, 1)]
|
|
156
|
-
|
|
157
|
-
# Format the data
|
|
158
|
-
vertex_data = [verticies[ind] for triangle in indicies for ind in triangle]
|
|
159
|
-
vertex_data = np.array(vertex_data, dtype='f4')
|
|
160
|
-
uv_data = [uv_verticies[ind] for triangle in uv_indicies for ind in triangle]
|
|
161
|
-
uv_data = np.array(uv_data, dtype='f4')
|
|
89
|
+
size = tuple(map(lambda x: int(x * self.resolution), self.engine.win_size))
|
|
90
|
+
self.framebuffer.resize(size)
|
|
91
|
+
self.ping_pong_buffer.resize(size)
|
|
162
92
|
|
|
163
|
-
vertex_data = np.hstack([vertex_data, uv_data])
|
|
164
|
-
|
|
165
|
-
# Create moderngl objects
|
|
166
|
-
self.vbo = self.ctx.buffer(vertex_data)
|
|
167
|
-
self.vao = self.ctx.vertex_array(self.program, [(self.vbo, '3f 2f', 'in_position', 'in_uv')], skip_errors=True)
|
|
168
|
-
|
|
169
93
|
def __del__(self) -> None:
|
|
170
94
|
"""
|
|
171
95
|
Releases memory used by the frame
|
|
172
96
|
"""
|
|
173
97
|
|
|
174
|
-
if self.
|
|
175
|
-
if self.
|
|
176
|
-
if self.vao: self.vao.release()
|
|
177
|
-
if self.frame_texture: self.frame_texture.release()
|
|
178
|
-
if self.depth_texture: self.depth_texture.release()
|
|
179
|
-
if self.framebuffer: self.framebuffer.release()
|
|
180
|
-
if self.pingpong_frame_texture: self.frame_texture.release()
|
|
181
|
-
if self.pingpong_depth_texture: self.depth_texture.release()
|
|
182
|
-
if self.pingpong_framebuffer: self.framebuffer.release()
|
|
98
|
+
if self.vbo: self.vbo.release()
|
|
99
|
+
if self.vao: self.vao.release()
|