basilisk-engine 0.0.9__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of basilisk-engine might be problematic. Click here for more details.

Files changed (61) hide show
  1. basilisk/__init__.py +3 -1
  2. basilisk/collisions/broad/broad_aabb.py +8 -1
  3. basilisk/collisions/broad/broad_bvh.py +38 -2
  4. basilisk/collisions/collider.py +20 -10
  5. basilisk/collisions/collider_handler.py +97 -32
  6. basilisk/collisions/narrow/contact_manifold.py +91 -0
  7. basilisk/collisions/narrow/dataclasses.py +27 -0
  8. basilisk/collisions/narrow/deprecated.py +47 -0
  9. basilisk/collisions/narrow/epa.py +21 -15
  10. basilisk/collisions/narrow/gjk.py +15 -14
  11. basilisk/collisions/narrow/graham_scan.py +25 -0
  12. basilisk/collisions/narrow/helper.py +14 -7
  13. basilisk/collisions/narrow/line_intersections.py +107 -0
  14. basilisk/collisions/narrow/sutherland_hodgman.py +76 -0
  15. basilisk/draw/draw_handler.py +7 -5
  16. basilisk/engine.py +28 -6
  17. basilisk/generic/abstract_custom.py +134 -0
  18. basilisk/generic/collisions.py +47 -2
  19. basilisk/generic/quat.py +84 -65
  20. basilisk/generic/vec3.py +99 -67
  21. basilisk/input/mouse.py +3 -3
  22. basilisk/mesh/cube.py +20 -2
  23. basilisk/mesh/mesh.py +69 -54
  24. basilisk/mesh/mesh_from_data.py +106 -21
  25. basilisk/mesh/narrow_aabb.py +10 -1
  26. basilisk/mesh/narrow_bvh.py +9 -1
  27. basilisk/nodes/node.py +211 -101
  28. basilisk/nodes/node_handler.py +58 -33
  29. basilisk/particles/__init__.py +0 -0
  30. basilisk/particles/particle_handler.py +55 -0
  31. basilisk/particles/particle_renderer.py +87 -0
  32. basilisk/physics/impulse.py +113 -0
  33. basilisk/physics/physics_body.py +10 -2
  34. basilisk/physics/physics_engine.py +2 -3
  35. basilisk/render/batch.py +3 -1
  36. basilisk/render/camera.py +35 -1
  37. basilisk/render/chunk.py +19 -4
  38. basilisk/render/chunk_handler.py +39 -23
  39. basilisk/render/image.py +1 -1
  40. basilisk/render/image_handler.py +17 -14
  41. basilisk/render/light_handler.py +16 -11
  42. basilisk/render/material.py +38 -14
  43. basilisk/render/material_handler.py +31 -18
  44. basilisk/render/shader.py +110 -0
  45. basilisk/render/shader_handler.py +20 -35
  46. basilisk/render/sky.py +8 -5
  47. basilisk/scene.py +116 -33
  48. basilisk/shaders/batch.frag +40 -11
  49. basilisk/shaders/batch.vert +14 -7
  50. basilisk/shaders/geometry.frag +9 -0
  51. basilisk/shaders/geometry.vert +42 -0
  52. basilisk/shaders/normal.frag +60 -0
  53. basilisk/shaders/normal.vert +97 -0
  54. basilisk/shaders/particle.frag +72 -0
  55. basilisk/shaders/particle.vert +85 -0
  56. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/METADATA +5 -5
  57. basilisk_engine-0.1.1.dist-info/RECORD +95 -0
  58. basilisk/shaders/image.png +0 -0
  59. basilisk_engine-0.0.9.dist-info/RECORD +0 -78
  60. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/WHEEL +0 -0
  61. {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/top_level.txt +0 -0
@@ -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')
@@ -0,0 +1,113 @@
1
+ import glm
2
+ from ..nodes.node import Node
3
+
4
+ def calculate_collisions(normal:glm.vec3, node1: Node, node2: Node, contact_points:list[glm.vec3], inv_inertia1:glm.mat3x3, inv_inertia2:glm.mat3x3, center1:glm.vec3, center2:glm.vec3) -> None:
5
+ """
6
+ Resolve the collisions between two objects with multiple contact points
7
+ """
8
+ physics_body1 = node1.physics_body
9
+ physics_body2 = node2.physics_body
10
+ collider1 = node1.collider
11
+ collider2 = node2.collider
12
+
13
+ # determine whether or not the colliders have physics
14
+ has_physics1, has_physics2 = physics_body1 is not None, physics_body2 is not None
15
+
16
+ # get physics data from valid bodies
17
+ if has_physics1: inv_mass1 = 1 / physics_body1.mass
18
+ if has_physics2: inv_mass2 = 1 / physics_body2.mass
19
+
20
+ # gets coefficients
21
+ elasticity = max(collider1.elasticity, collider2.elasticity)
22
+ kinetic = min(collider1.kinetic_friction, collider2.kinetic_friction)
23
+ static = min(collider1.static_friction, collider2.static_friction)
24
+
25
+ # calculate impulses from contact points
26
+ if has_physics1 and has_physics2:
27
+ for contact_point in contact_points:
28
+
29
+ # apply impulse based reduced by total points
30
+ radius1, radius2 = contact_point - center1, contact_point - center2
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)
32
+
33
+ # apply impulses
34
+ apply_impulse(radius1, impulse, inv_inertia1, inv_mass1, node1)
35
+ apply_impulse(radius2, -impulse, inv_inertia2, inv_mass2, node2)
36
+
37
+ elif has_physics1:
38
+ for contact_point in contact_points:
39
+ radius = contact_point - center1
40
+ impulse = calculate_impluse1(node1, inv_mass1, node1.rotational_velocity, radius, inv_inertia1, elasticity, kinetic, static, normal)
41
+
42
+ # apply impulses
43
+ apply_impulse(radius, impulse, inv_inertia1, inv_mass1, node1)
44
+
45
+ else: # only physics body 2
46
+ for contact_point in contact_points:
47
+ radius = contact_point - center2
48
+ impulse = calculate_impluse1(node2, inv_mass2, node2.rotational_velocity, radius, inv_inertia2, elasticity, kinetic, static, normal)
49
+
50
+ # apply impulse
51
+ apply_impulse(radius, impulse, inv_inertia2, inv_mass2, node2)
52
+
53
+ def calculate_impluse1(node: Node, inv_mass, omega, radius, inv_inertia, elasticity, kinetic, static, normal) -> glm.vec3:
54
+ """
55
+ Calculates the impulse from a collision including friction from the impulse
56
+ """
57
+ # determine if mass needs to be calculated TODO determine if this is a good check
58
+ if glm.dot(radius, node.velocity) < 0: return glm.vec3(0, 0, 0)
59
+
60
+ # normal impulse
61
+ relative_velocity = node.velocity + glm.cross(omega, radius)
62
+ relative_normal_velocity = glm.dot(relative_velocity, normal)
63
+
64
+ # calculate denominator
65
+ denominator = inv_mass + glm.dot(normal, glm.cross(inv_inertia * glm.cross(radius, normal), radius))
66
+
67
+ # calculate normal impulse
68
+ normal_impulse_magnitude = -(1 + elasticity) * relative_normal_velocity / denominator
69
+ normal_impulse = normal_impulse_magnitude * normal
70
+
71
+ # friction impulse
72
+ rel_tan_vel = relative_velocity - glm.dot(relative_velocity, normal) * normal
73
+ rel_tan_vel_len = glm.length(rel_tan_vel)
74
+
75
+ if rel_tan_vel_len < 1e-7: friction_impulse = glm.vec3(0, 0, 0) # no friction
76
+ elif rel_tan_vel_len < 1e-2: friction_impulse = -static * glm.length(normal_impulse) * glm.normalize(rel_tan_vel) # static friction
77
+ else: friction_impulse = -kinetic * glm.length(normal_impulse) * glm.normalize(rel_tan_vel) # kinetic friction
78
+
79
+ # return total impulse
80
+ return normal_impulse + friction_impulse
81
+
82
+ def calculate_impulse2(node1: Node, node2: Node, inv_mass1, inv_mass2, omega1, omega2, radius1, radius2, inv_inertia1, inv_inertia2, elasticity, kinetic, static, normal) -> glm.vec3:
83
+ """
84
+ Calculates the impulse from a collision including friction from the impulse
85
+ """
86
+ # normal impulse
87
+ relative_velocity = node1.velocity + glm.cross(omega1, radius1) - (node2.velocity + glm.cross(omega2, radius2))
88
+ relative_normal_velocity = glm.dot(relative_velocity, normal)
89
+ # calculate denominator
90
+ term1 = inv_mass1 + inv_mass2
91
+ term2 = glm.dot(normal, glm.cross(inv_inertia1 * glm.cross(radius1, normal), radius1) + glm.cross(inv_inertia2 * glm.cross(radius2, normal), radius2))
92
+ # calculate normal impulse
93
+ normal_impulse = -(1 + elasticity) * relative_normal_velocity / (term1 + term2) * normal
94
+
95
+ # friction impulse
96
+ rel_tan_vel = relative_velocity - glm.dot(relative_velocity, normal) * normal
97
+ rel_tan_vel_len = glm.length(rel_tan_vel)
98
+ if rel_tan_vel_len < 1e-7: friction_impulse = glm.vec3(0, 0, 0)
99
+ elif rel_tan_vel_len < 1e-2: friction_impulse = -static * glm.length(normal_impulse) * glm.normalize(rel_tan_vel)
100
+ else: friction_impulse = -kinetic * glm.length(normal_impulse) * glm.normalize(rel_tan_vel)
101
+ # return total impulse
102
+ return normal_impulse + friction_impulse
103
+
104
+ def apply_impulse(radius, impulse_signed, inv_inertia, inv_mass, node: Node) -> None:
105
+ """
106
+ Applies the given impulse to the physics body, changing translational and rotational velcoity.
107
+ """
108
+
109
+ # Update linear velocity
110
+ node.velocity += impulse_signed * inv_mass
111
+
112
+ # update rotational velcoity
113
+ node.rotational_velocity += inv_inertia * glm.cross(radius, impulse_signed)
@@ -6,8 +6,8 @@ class PhysicsBody():
6
6
  mass: float
7
7
  """The mass of the physics body in kg"""
8
8
 
9
- def __init__(self, physics_engine, mass:float=1.0) -> None:
10
- self.physics_engine = physics_engine
9
+ def __init__(self, mass:float=1.0) -> None:
10
+ self.physics_engine = None
11
11
  self.mass = mass
12
12
 
13
13
  def get_delta_velocity(self, dt: float) -> glm.vec3:
@@ -32,5 +32,13 @@ class PhysicsBody():
32
32
  # TODO add torques
33
33
  return dw
34
34
 
35
+ @property
36
+ def physics_engine(self): return self._physics_engine
37
+
38
+ @physics_engine.setter
39
+ def physics_engine(self, value):
40
+ self._physics_engine = value
41
+ if value: value.add(self)
42
+
35
43
  def __repr__(self) -> str:
36
44
  return f'<Physics Body| {self.mass}>'
@@ -15,16 +15,15 @@ class PhysicsEngine():
15
15
 
16
16
  def __init__(self, accelerations: list[glm.vec3] = None, rotational_accelerations: list[glm.vec3] = None, forces: list[glm.vec3] = None, torques: list[glm.vec3] = None) -> None:
17
17
  self.physics_bodies = []
18
- self.accelerations = accelerations if accelerations else [glm.vec3(0, -1, 0)]
18
+ self.accelerations = accelerations if accelerations else [glm.vec3(0, -9.8, 0)]
19
19
  self.rotational_accelerations = rotational_accelerations if rotational_accelerations else []
20
20
  self.forces = forces if forces else []
21
21
  self.torques = torques if torques else []
22
22
 
23
- def add(self, mass: float) -> PhysicsBody:
23
+ def add(self, physics_body: PhysicsBody) -> PhysicsBody:
24
24
  """
25
25
  Adds a physics body to the physics engine and returns it
26
26
  """
27
- physics_body = PhysicsBody(self, mass)
28
27
  self.physics_bodies.append(physics_body)
29
28
  return physics_body
30
29
 
basilisk/render/batch.py CHANGED
@@ -23,7 +23,7 @@ class Batch():
23
23
  # Back references
24
24
  self.chunk = chunk
25
25
  self.ctx = chunk.chunk_handler.engine.ctx
26
- self.program = chunk.chunk_handler.program
26
+ self.program = self.chunk.get_program()
27
27
 
28
28
  # Set intial values
29
29
  self.vbo = None
@@ -35,6 +35,8 @@ class Batch():
35
35
  Returns True if batch was successful.
36
36
  """
37
37
 
38
+ self.program = self.chunk.get_program()
39
+
38
40
  # Empty list to contain all vertex data of models in the chunk
39
41
  batch_data = []
40
42
 
basilisk/render/camera.py CHANGED
@@ -85,6 +85,11 @@ class Camera:
85
85
  def get_params(self) -> tuple:
86
86
  return self.engine, self.position, self.yaw, self.pitch
87
87
 
88
+ def look_at(self, other) -> None:
89
+ forward = glm.normalize(other.position - self.position)
90
+ self.yaw = np.degrees(np.arctan2(forward.z, forward.x))
91
+ self.pitch = np.degrees(np.arctan2(forward.y, np.sqrt(forward.x ** 2 + forward.z ** 2)))
92
+
88
93
  def __repr__(self):
89
94
  return f'<Basilisk Camera | Position: {self.position}, Direction: {self.forward}>'
90
95
 
@@ -145,7 +150,7 @@ class FreeCamera(Camera):
145
150
  """
146
151
  Checks for button presses and updates vectors accordingly.
147
152
  """
148
- velocity = SPEED * self.engine.delta_time
153
+ velocity = (SPEED + self.engine.keys[pg.K_CAPSLOCK] * 10) * self.engine.delta_time
149
154
  keys = self.engine.keys
150
155
  if keys[pg.K_w]:
151
156
  self.position += glm.normalize(glm.vec3(self.forward.x, 0, self.forward.z)) * velocity
@@ -161,6 +166,35 @@ class FreeCamera(Camera):
161
166
  self.position -= self.UP * velocity
162
167
 
163
168
 
169
+ class FollowCamera(FreeCamera):
170
+ def __init__(self, parent, position=(0, 0, 20), yaw=-90, pitch=0, offset=(0, 0, 0)):
171
+ super().__init__(position, yaw, pitch)
172
+ self.parent = parent
173
+ self.offest = glm.vec3(offset)
174
+
175
+ def move(self) -> None:
176
+ """
177
+ Moves the camera to the parent node
178
+ """
179
+
180
+ self.position = self.parent.position + self.offest
181
+
182
+ class OrbitCamera(FreeCamera):
183
+ def __init__(self, parent, position=(0, 0, 20), yaw=-90, pitch=0, distance=5):
184
+ self.parent = parent
185
+ self.distance = distance
186
+ super().__init__(position, yaw, pitch)
187
+
188
+ def get_view_matrix(self) -> glm.mat4x4:
189
+ return glm.lookAt(self.position, self.parent.position, self.up)
190
+
191
+ def move(self) -> None:
192
+ """
193
+ Moves the camera to the parent node
194
+ """
195
+
196
+ self.position = self.parent.position - glm.normalize(self.forward) * self.distance
197
+
164
198
  class StaticCamera(Camera):
165
199
  def __init__(self, position=(0, 0, 20), yaw=-90, pitch=0):
166
200
  super().__init__(position, yaw, pitch)
basilisk/render/chunk.py CHANGED
@@ -4,7 +4,7 @@ from .batch import Batch
4
4
  class Chunk():
5
5
  chunk_handler: ...
6
6
  """Back refrence to the parent chunk handler"""
7
- chunk_key: tuple
7
+ position: tuple
8
8
  """The position of the chunk. Used as a key in the chunk handler"""
9
9
  batch: Batch
10
10
  """Batched mesh of the chunk"""
@@ -13,7 +13,7 @@ class Chunk():
13
13
  static: bool
14
14
  """Type of node that the chunk recognizes"""
15
15
 
16
- def __init__(self, chunk_handler, chunk_key: tuple, static: bool) -> None:
16
+ def __init__(self, chunk_handler, position: tuple, static: bool, shader=None) -> None:
17
17
  """
18
18
  Basilisk chunk object.
19
19
  Contains references to all nodes in the chunk.
@@ -22,9 +22,11 @@ class Chunk():
22
22
 
23
23
  # Back references
24
24
  self.chunk_handler = chunk_handler
25
- self.chunk_key = chunk_key
26
25
 
26
+ # Chunk Attrbiutes
27
+ self.position = position
27
28
  self.static = static
29
+ self.shader = shader
28
30
 
29
31
  # Create empty batch
30
32
  self.batch = Batch(self)
@@ -70,12 +72,25 @@ class Chunk():
70
72
  Removes a node from the chunk
71
73
  """
72
74
 
75
+ if node == None: return
76
+
73
77
  self.nodes.remove(node)
78
+ if self.batch and self.batch.vbo: self.batch.vbo.clear()
74
79
 
75
80
  return node
76
81
 
82
+ def get_program(self):
83
+ """
84
+ Gets the program of the chunks nodes' shader
85
+ """
86
+
87
+ shader = self.shader
88
+
89
+ if shader: return shader.program
90
+ return self.chunk_handler.engine.shader.program
91
+
77
92
  def __repr__(self) -> str:
78
- return f'<Basilisk Chunk | {self.chunk_key}, {len(self.nodes)} nodes, {'static' if self.static else 'dynamic'}>'
93
+ return f'<Basilisk Chunk | {self.position}, {len(self.nodes)} nodes, {"static" if self.static else "dynamic"}>'
79
94
 
80
95
  def __del__(self) -> None:
81
96
  """
@@ -15,8 +15,8 @@ class ChunkHandler():
15
15
  """Reference to the shader program used by batches"""
16
16
  chunks: list[dict]
17
17
  """List containing two dictionaries for dynamic and static chunks repsectivly"""
18
- updated_chunks: list[set]
19
- """List containing two dictionaries for recently updated dynamic and static chunks repsectivly"""
18
+ updated_chunks: set
19
+ """Set containing recently updated chunks"""
20
20
 
21
21
  def __init__(self, scene) -> None:
22
22
  # Reference to the scene hadlers and variables
@@ -28,11 +28,12 @@ class ChunkHandler():
28
28
  self.scene = scene
29
29
  self.engine = scene.engine
30
30
  self.ctx = scene.engine.ctx
31
- self.program = scene.shader_handler.programs['batch']
31
+ self.program = scene.engine.shader.program
32
32
 
33
33
  # List for the dynamic and static chunk dictionaries | [dyanmic: dict, static: dict]
34
- self.chunks = [{} , {} ]
35
- self.updated_chunks = [set(), set()]
34
+ self.shader_groups = {None : ({}, {})}
35
+ # self.chunks = [{}, {}]
36
+ self.updated_chunks = set()
36
37
 
37
38
 
38
39
  def render(self) -> None:
@@ -47,10 +48,12 @@ class ChunkHandler():
47
48
  chunk_keys = [(x, y, z) for x in range(*render_range_x) for y in range(*render_range_y) for z in range(*render_range_z)]
48
49
 
49
50
  # Loop through all chunks in view and render
50
- for chunk in chunk_keys:
51
- # Render the chunk if it exists
52
- if chunk in self.chunks[0]: self.chunks[0][chunk].render()
53
- if chunk in self.chunks[1]: self.chunks[1][chunk].render()
51
+ for shader, group in self.shader_groups.items():
52
+ if shader == None: shader = self.engine.shader
53
+ for chunk in chunk_keys:
54
+ # Render the chunk if it exists
55
+ if chunk in group[0]: group[0][chunk].render()
56
+ if chunk in group[1]: group[1][chunk].render()
54
57
 
55
58
 
56
59
  def update(self) -> None:
@@ -58,23 +61,29 @@ class ChunkHandler():
58
61
  Updates all the chunks that have been updated since the last frame.
59
62
  """
60
63
 
64
+ self.program = self.scene.engine.shader.program
65
+
61
66
  # Loop through the set of updated chunk keys and update the chunk
62
67
  removes = []
63
68
 
64
- for chunk in self.updated_chunks[0]:
65
- if chunk.update(): continue
66
- removes.append((0, chunk))
67
- for chunk in self.updated_chunks[1]:
69
+ for chunk in self.updated_chunks:
68
70
  if chunk.update(): continue
69
- removes.append((1, chunk))
71
+ removes.append(chunk)
70
72
 
71
73
  # Remove any empty chunks
72
- for chunk_tuple in removes:
73
- if chunk_tuple[1] not in self.chunks[chunk_tuple[0]]: continue
74
- del self.chunks[chunk_tuple[0]][chunk_tuple[1]]
74
+ for chunk in removes:
75
+ del self.shader_groups[chunk.shader][chunk.static][chunk.position]
75
76
 
76
77
  # Clears the set of updated chunks so that they are not updated unless they are updated again
77
- self.updated_chunks = [set(), set()]
78
+ self.updated_chunks.clear()
79
+
80
+ def update_all(self):
81
+ self.program = self.scene.engine.shader.program
82
+ for shader in self.shader_groups.values():
83
+ for chunk in shader[0].values():
84
+ self.updated_chunks.add(chunk)
85
+ for chunk in shader[1].values():
86
+ self.updated_chunks.add(chunk)
78
87
 
79
88
  def add(self, node: Node) -> Node:
80
89
  """
@@ -84,24 +93,31 @@ class ChunkHandler():
84
93
  # The key of the chunk the node will be added to
85
94
  chunk_size = self.engine.config.chunk_size
86
95
  chunk_key = (int(node.x // chunk_size), int(node.y // chunk_size), int(node.z // chunk_size))
96
+ shader = node.shader
97
+
98
+ if shader not in self.shader_groups:
99
+ self.shader_groups[shader] = ({}, {})
87
100
 
88
101
  # Ensure that the chunk exists
89
- if chunk_key not in self.chunks[node.static]:
90
- self.chunks[node.static][chunk_key] = Chunk(self, chunk_key, node.static)
102
+ if chunk_key not in self.shader_groups[shader][node.static]:
103
+ chunk = Chunk(self, chunk_key, node.static, shader)
104
+ self.shader_groups[shader][node.static][chunk_key] = chunk
91
105
 
92
106
  # Add the node to the chunk
93
- self.chunks[node.static][chunk_key].add(node)
107
+ self.shader_groups[shader][node.static][chunk_key].add(node)
94
108
 
95
109
  # Update the chunk
96
- self.updated_chunks[node.static].add(self.chunks[node.static][chunk_key])
110
+ self.updated_chunks.add(self.shader_groups[shader][node.static][chunk_key])
97
111
 
98
112
  return Node
99
113
 
100
- def remove(self, node) -> None:
114
+ def remove(self, node: Node) -> None:
101
115
  """
102
116
  Removes a node from the its chunk
103
117
  """
104
118
 
119
+ if node == None: return
120
+
105
121
  # Remove the node
106
122
  chunk = node.chunk
107
123
  chunk.remove(node)
basilisk/render/image.py CHANGED
@@ -7,7 +7,7 @@ import pygame as pg
7
7
  from PIL import Image as PIL_Image
8
8
 
9
9
 
10
- texture_sizes = (128, 256, 512, 1024, 2048)
10
+ texture_sizes = (8, 64, 512, 1024, 2048)
11
11
 
12
12
 
13
13
  class Image():
@@ -3,7 +3,7 @@ import glm
3
3
  import numpy as np
4
4
 
5
5
 
6
- texture_sizes = (128, 256, 512, 1024, 2048)
6
+ texture_sizes = (8, 64, 512, 1024, 2048)
7
7
 
8
8
 
9
9
  class ImageHandler():
@@ -43,8 +43,7 @@ class ImageHandler():
43
43
  if image in self.images: return
44
44
 
45
45
  self.images.append(image)
46
- self.write(self.scene.shader_handler.programs['batch'])
47
- self.write(self.scene.shader_handler.programs['draw'])
46
+ self.write(regenerate=True)
48
47
 
49
48
  def generate_texture_array(self) -> None:
50
49
  """
@@ -74,23 +73,27 @@ class ImageHandler():
74
73
  self.texture_arrays[size] = self.ctx.texture_array(size=dim, components=4, data=array_data)
75
74
  # Texture OpenGl settings
76
75
  self.texture_arrays[size].build_mipmaps()
77
- self.texture_arrays[size].filter = (mgl.LINEAR_MIPMAP_LINEAR, mgl.LINEAR)
76
+ if size > 32: self.texture_arrays[size].filter = (mgl.LINEAR_MIPMAP_LINEAR, mgl.LINEAR)
77
+ else: self.texture_arrays[size].filter = (mgl.NEAREST, mgl.NEAREST)
78
78
  self.texture_arrays[size].anisotropy = 32.0
79
79
 
80
- def write(self, shader_program: mgl.Program) -> None:
80
+ def write(self, regenerate=False) -> None:
81
81
  """
82
- Writes all texture arrays to the given shader program
83
- Args:
84
- shader_program: mgl.Program:
85
- Destination of the texture array write
82
+ Writes all texture arrays to shaders that use images
86
83
  """
87
84
 
88
- self.generate_texture_array()
85
+ if regenerate: self.generate_texture_array()
86
+
87
+ if not self.texture_arrays: return
88
+
89
+ for shader in self.engine.scene.shader_handler.shaders:
90
+ if 'textureArrays[5]' not in shader.uniforms: continue
89
91
 
90
- for i, size in enumerate(texture_sizes):
91
- if not size in self.texture_arrays: continue
92
- shader_program[f'textureArrays[{i}].array'] = i + 3
93
- self.texture_arrays[size].use(location=i+3)
92
+ for i, size in enumerate(texture_sizes):
93
+ if not size in self.texture_arrays: continue
94
+ if not self.texture_arrays[size]: continue
95
+ shader.program[f'textureArrays[{i}].array'] = i + 3
96
+ self.texture_arrays[size].use(location=i+3)
94
97
 
95
98
  def get(self, identifier: str | int) -> any:
96
99
  """
@@ -38,17 +38,22 @@ class LightHandler():
38
38
  Writes all the lights in a scene to the given shader program
39
39
  """
40
40
 
41
- if not program: program = self.scene.shader_handler.programs['batch']
41
+ # if not program: program = self.engine.shader.program
42
42
 
43
- if directional and self.directional_lights:
43
+ for shader in self.scene.shader_handler.shaders:
44
+ if 'numDirLights' not in shader.uniforms: continue
45
+
46
+ program = shader.program
44
47
 
45
- program['numDirLights'].write(glm.int32(len(self.directional_lights)))
48
+ if directional and self.directional_lights and 'numDirLights' in self.engine.shader.uniforms:
46
49
 
47
- for i, light in enumerate(self.directional_lights):
48
- program[f'dirLights[{i}].direction'].write(light.direction)
49
- program[f'dirLights[{i}].intensity'].write(glm.float32(light.intensity))
50
- program[f'dirLights[{i}].color' ].write(light.color / 255.0)
51
- program[f'dirLights[{i}].ambient' ].write(glm.float32(light.ambient))
52
-
53
- if point:
54
- ...
50
+ program['numDirLights'].write(glm.int32(len(self.directional_lights)))
51
+
52
+ for i, light in enumerate(self.directional_lights):
53
+ program[f'dirLights[{i}].direction'].write(light.direction)
54
+ program[f'dirLights[{i}].intensity'].write(glm.float32(light.intensity))
55
+ program[f'dirLights[{i}].color' ].write(light.color / 255.0)
56
+ program[f'dirLights[{i}].ambient' ].write(glm.float32(light.ambient))
57
+
58
+ if point:
59
+ ...