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/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
- collisions: bool
42
+ collision: bool
42
43
  """Gives the node collision with other nodes in the scene"""
43
- collider: str
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
- """""" # TODO Jonah description
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
- collisions: bool=False,
81
- collider: str=None,
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 : float=None,
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.static = static if static != None else not(physics or any(self.velocity) or any(self.rotational_velocity))
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 collisions:
133
+ if collision:
133
134
  self.collider = Collider(
134
135
  node = self,
135
- box_mesh = True if collider == 'box' else False,
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 collider: raise ValueError('Node: cannot have collider mesh if it does not allow collisions')
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.node_update_callback(self)
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.node_update_callback(self)
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.node_update_callback(self)
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 get_nodes(self,
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
- Returns the nodes matching the required filters from this branch of the nodes
248
+ Creates a deep copy of this node and returns it. The new node is not added to the scene.
256
249
  """
257
- # adds self to nodes list if it matches the criteria
258
- nodes = []
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
- if all([
261
- (not require_mesh or self.mesh),
262
- (not require_collider or self.collider),
263
- (not require_physics_body or self.physics_body),
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 = force / self.mass * dt
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
- inertia_tensor = self.mesh.get_inertia_tensor(self.scale) / 2
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
+
@@ -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
- for node in self.nodes:
29
- if node.static: continue
30
- node.update(self.scene.engine.delta_time)
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.get_nodes(): # gets all nodes including the node to be added
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 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
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 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)
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=(0, 0, 0), material: Material=None, scale=1.0, velocity=(0, 3, 0), acceleration=(0, -10, 0)) -> bool:
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 PIL import Image
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
- program: mgl.Program=None
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.load_program()
30
- self.set_textures()
31
- self.set_renderer()
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
- # self.apply_postprocess('filter')
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.color_attachments[0].use(location=0)
56
+ self.shader.program['screenTexture'] = 0
57
+ self.framebuffer.texture.use(location=0)
46
58
  self.vao.render()
47
59
 
48
- def use(self):
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 save(self, destination: str=None):
69
+ def add_post_process(self, post_process: PostProcess) -> PostProcess:
57
70
  """
58
- Saves the frame as an image to the given file destination
71
+ Add a post process to the frames post process stack
59
72
  """
60
73
 
61
- path = destination if destination else 'screenshot'
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 load_program(self) -> None:
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
- # Release any existing memory in case of a resize
124
- if self.frame_texture: self.frame_texture.release()
125
- if self.depth_texture: self.depth_texture.release()
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
- Sets the vertex data and vao for the frame
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
- # Vertex and index info for the frame
152
- verticies = [[-1, -1, 0], [ 1, -1, 0], [ 1, 1, 0], [-1, 1, 0]]
153
- indicies = [(3, 0, 1), (2, 3, 1)]
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.program: self.program.release()
175
- if self.vbo: self.vbo.release()
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()