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.
- basilisk/__init__.py +3 -1
- basilisk/collisions/broad/broad_aabb.py +8 -1
- basilisk/collisions/broad/broad_bvh.py +38 -2
- basilisk/collisions/collider.py +20 -10
- basilisk/collisions/collider_handler.py +97 -32
- basilisk/collisions/narrow/contact_manifold.py +91 -0
- basilisk/collisions/narrow/dataclasses.py +27 -0
- basilisk/collisions/narrow/deprecated.py +47 -0
- basilisk/collisions/narrow/epa.py +21 -15
- basilisk/collisions/narrow/gjk.py +15 -14
- basilisk/collisions/narrow/graham_scan.py +25 -0
- basilisk/collisions/narrow/helper.py +14 -7
- basilisk/collisions/narrow/line_intersections.py +107 -0
- basilisk/collisions/narrow/sutherland_hodgman.py +76 -0
- basilisk/draw/draw_handler.py +7 -5
- basilisk/engine.py +28 -6
- basilisk/generic/abstract_custom.py +134 -0
- basilisk/generic/collisions.py +47 -2
- basilisk/generic/quat.py +84 -65
- basilisk/generic/vec3.py +99 -67
- basilisk/input/mouse.py +3 -3
- basilisk/mesh/cube.py +20 -2
- basilisk/mesh/mesh.py +69 -54
- basilisk/mesh/mesh_from_data.py +106 -21
- basilisk/mesh/narrow_aabb.py +10 -1
- basilisk/mesh/narrow_bvh.py +9 -1
- basilisk/nodes/node.py +211 -101
- basilisk/nodes/node_handler.py +58 -33
- basilisk/particles/__init__.py +0 -0
- basilisk/particles/particle_handler.py +55 -0
- basilisk/particles/particle_renderer.py +87 -0
- basilisk/physics/impulse.py +113 -0
- basilisk/physics/physics_body.py +10 -2
- basilisk/physics/physics_engine.py +2 -3
- basilisk/render/batch.py +3 -1
- basilisk/render/camera.py +35 -1
- basilisk/render/chunk.py +19 -4
- basilisk/render/chunk_handler.py +39 -23
- basilisk/render/image.py +1 -1
- basilisk/render/image_handler.py +17 -14
- basilisk/render/light_handler.py +16 -11
- basilisk/render/material.py +38 -14
- basilisk/render/material_handler.py +31 -18
- basilisk/render/shader.py +110 -0
- basilisk/render/shader_handler.py +20 -35
- basilisk/render/sky.py +8 -5
- basilisk/scene.py +116 -33
- basilisk/shaders/batch.frag +40 -11
- basilisk/shaders/batch.vert +14 -7
- basilisk/shaders/geometry.frag +9 -0
- basilisk/shaders/geometry.vert +42 -0
- basilisk/shaders/normal.frag +60 -0
- basilisk/shaders/normal.vert +97 -0
- basilisk/shaders/particle.frag +72 -0
- basilisk/shaders/particle.vert +85 -0
- {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/METADATA +5 -5
- basilisk_engine-0.1.1.dist-info/RECORD +95 -0
- basilisk/shaders/image.png +0 -0
- basilisk_engine-0.0.9.dist-info/RECORD +0 -78
- {basilisk_engine-0.0.9.dist-info → basilisk_engine-0.1.1.dist-info}/WHEEL +0 -0
- {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)
|
basilisk/physics/physics_body.py
CHANGED
|
@@ -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,
|
|
10
|
-
self.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, -
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
"""
|
basilisk/render/chunk_handler.py
CHANGED
|
@@ -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:
|
|
19
|
-
"""
|
|
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.
|
|
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.
|
|
35
|
-
self.
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
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(
|
|
71
|
+
removes.append(chunk)
|
|
70
72
|
|
|
71
73
|
# Remove any empty chunks
|
|
72
|
-
for
|
|
73
|
-
|
|
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
|
|
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.
|
|
90
|
-
|
|
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.
|
|
107
|
+
self.shader_groups[shader][node.static][chunk_key].add(node)
|
|
94
108
|
|
|
95
109
|
# Update the chunk
|
|
96
|
-
self.updated_chunks
|
|
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
basilisk/render/image_handler.py
CHANGED
|
@@ -3,7 +3,7 @@ import glm
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
texture_sizes = (
|
|
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(
|
|
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,
|
|
80
|
+
def write(self, regenerate=False) -> None:
|
|
81
81
|
"""
|
|
82
|
-
Writes all texture arrays to
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
"""
|
basilisk/render/light_handler.py
CHANGED
|
@@ -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.
|
|
41
|
+
# if not program: program = self.engine.shader.program
|
|
42
42
|
|
|
43
|
-
|
|
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
|
-
|
|
48
|
+
if directional and self.directional_lights and 'numDirLights' in self.engine.shader.uniforms:
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
...
|