basilisk-engine 0.0.1__py3-none-any.whl → 0.0.3__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/bsk_assets/__init__.py +0 -0
- basilisk/collisions/__init__.py +0 -0
- basilisk/collisions/broad/__init__.py +0 -0
- basilisk/collisions/broad/broad_aabb.py +96 -0
- basilisk/collisions/broad/broad_bvh.py +102 -0
- basilisk/collisions/collider.py +75 -0
- basilisk/collisions/collider_handler.py +163 -0
- basilisk/collisions/narrow/__init__.py +0 -0
- basilisk/collisions/narrow/epa.py +86 -0
- basilisk/collisions/narrow/gjk.py +66 -0
- basilisk/collisions/narrow/helper.py +23 -0
- basilisk/draw/__init__.py +0 -0
- basilisk/draw/draw.py +101 -0
- basilisk/draw/draw_handler.py +208 -0
- basilisk/draw/font_renderer.py +28 -0
- basilisk/generic/__init__.py +0 -0
- basilisk/generic/abstract_bvh.py +16 -0
- basilisk/generic/collisions.py +26 -0
- basilisk/generic/input_validation.py +28 -0
- basilisk/generic/math.py +7 -0
- basilisk/generic/matrices.py +34 -0
- basilisk/generic/meshes.py +73 -0
- basilisk/generic/quat.py +119 -0
- basilisk/generic/quat_methods.py +8 -0
- basilisk/generic/vec3.py +112 -0
- basilisk/input/__init__.py +0 -0
- basilisk/input/mouse.py +60 -0
- basilisk/mesh/__init__.py +0 -0
- basilisk/mesh/built-in/__init__.py +0 -0
- basilisk/mesh/cube.py +20 -0
- basilisk/mesh/mesh.py +216 -0
- basilisk/mesh/mesh_from_data.py +48 -0
- basilisk/mesh/model.py +272 -0
- basilisk/mesh/narrow_aabb.py +81 -0
- basilisk/mesh/narrow_bvh.py +84 -0
- basilisk/mesh/narrow_primative.py +24 -0
- basilisk/nodes/__init__.py +0 -0
- basilisk/nodes/node.py +508 -0
- basilisk/nodes/node_handler.py +94 -0
- basilisk/physics/__init__.py +0 -0
- basilisk/physics/physics_body.py +36 -0
- basilisk/physics/physics_engine.py +37 -0
- basilisk/render/__init__.py +0 -0
- basilisk/render/batch.py +85 -0
- basilisk/render/camera.py +166 -0
- basilisk/render/chunk.py +85 -0
- basilisk/render/chunk_handler.py +139 -0
- basilisk/render/frame.py +182 -0
- basilisk/render/image.py +76 -0
- basilisk/render/image_handler.py +119 -0
- basilisk/render/light.py +97 -0
- basilisk/render/light_handler.py +54 -0
- basilisk/render/material.py +196 -0
- basilisk/render/material_handler.py +123 -0
- basilisk/render/shader_handler.py +95 -0
- basilisk/render/sky.py +118 -0
- basilisk/shaders/__init__.py +0 -0
- {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.3.dist-info}/METADATA +1 -1
- basilisk_engine-0.0.3.dist-info/RECORD +65 -0
- basilisk_engine-0.0.1.dist-info/RECORD +0 -8
- {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.3.dist-info}/WHEEL +0 -0
- {basilisk_engine-0.0.1.dist-info → basilisk_engine-0.0.3.dist-info}/top_level.txt +0 -0
basilisk/render/batch.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import moderngl as mgl
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Batch():
|
|
6
|
+
chunk: ...
|
|
7
|
+
"""Reference to the parent chunk of the batch"""
|
|
8
|
+
ctx: mgl.Context
|
|
9
|
+
"""Reference to the context of the parent engine"""
|
|
10
|
+
program: mgl.Program
|
|
11
|
+
"""Reference to the program used by batches"""
|
|
12
|
+
vao: mgl.VertexArray
|
|
13
|
+
"""The vertex array of the batch. Used for rendering"""
|
|
14
|
+
vbo: mgl.Buffer
|
|
15
|
+
"""Buffer containing all the batch data"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, chunk) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Basilik batch object
|
|
20
|
+
Contains all the data for a chunk batch to be stored and rendered
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Back references
|
|
24
|
+
self.chunk = chunk
|
|
25
|
+
self.ctx = chunk.chunk_handler.engine.ctx
|
|
26
|
+
self.program = chunk.chunk_handler.program
|
|
27
|
+
|
|
28
|
+
# Set intial values
|
|
29
|
+
self.vbo = None
|
|
30
|
+
self.vao = None
|
|
31
|
+
|
|
32
|
+
def batch(self) -> bool:
|
|
33
|
+
"""
|
|
34
|
+
Batches all the node meshes in the chunks bounds.
|
|
35
|
+
Returns True if batch was successful.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# Empty list to contain all vertex data of models in the chunk
|
|
39
|
+
batch_data = []
|
|
40
|
+
|
|
41
|
+
# Loop through each node in the chunk, adding the nodes's mesh to batch_data
|
|
42
|
+
index = 0
|
|
43
|
+
for node in self.chunk.nodes:
|
|
44
|
+
# Check that the node should be used
|
|
45
|
+
if not node.mesh: continue
|
|
46
|
+
if node.static != self.chunk.static: continue
|
|
47
|
+
|
|
48
|
+
# Get the data from the node
|
|
49
|
+
node_data = node.get_data()
|
|
50
|
+
# Update the index
|
|
51
|
+
node.data_index = index
|
|
52
|
+
index += len(node_data)
|
|
53
|
+
# Add to the chunk mesh
|
|
54
|
+
batch_data.append(node_data)
|
|
55
|
+
|
|
56
|
+
# Combine all meshes into a single array
|
|
57
|
+
if len(batch_data) > 1: batch_data = np.vstack(batch_data)
|
|
58
|
+
else: batch_data = np.array(batch_data, dtype='f4')
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# If there are no verticies, delete the chunk
|
|
62
|
+
if len(batch_data) == 0: return False
|
|
63
|
+
|
|
64
|
+
if self.vbo: self.vbo.release()
|
|
65
|
+
if self.vao: self.vao.release()
|
|
66
|
+
|
|
67
|
+
# Create the vbo and the vao from mesh data
|
|
68
|
+
self.vbo = self.ctx.buffer(batch_data)
|
|
69
|
+
self.vao = self.ctx.vertex_array(self.program, [(self.vbo,
|
|
70
|
+
'3f 2f 3f 3f 3f 3f 4f 3f 1f',
|
|
71
|
+
*['in_position', 'in_uv', 'in_normal', 'in_tangent', 'in_bitangent', 'obj_position', 'obj_rotation', 'obj_scale', 'obj_material'])],
|
|
72
|
+
skip_errors=True)
|
|
73
|
+
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
def __repr__(self) -> str:
|
|
77
|
+
return f'<Basilisk Batch | {self.chunk.chunk_key}, {self.vbo.size / 1024 / 1024} mb>'
|
|
78
|
+
|
|
79
|
+
def __del__(self) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Deallocates the mesh vbo and vao
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
if self.vbo: self.vbo.release()
|
|
85
|
+
if self.vao: self.vao.release()
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import pygame as pg
|
|
2
|
+
import glm
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
# Camera view constants
|
|
6
|
+
FOV = 50 # Degrees
|
|
7
|
+
NEAR = 0.1
|
|
8
|
+
FAR = 350
|
|
9
|
+
|
|
10
|
+
# Camera movement constants
|
|
11
|
+
SPEED = 10
|
|
12
|
+
SENSITIVITY = 0.15
|
|
13
|
+
|
|
14
|
+
class Camera:
|
|
15
|
+
engine: ...
|
|
16
|
+
"""Back reference to the parent engine"""
|
|
17
|
+
scene: ...
|
|
18
|
+
"""Back reference to the parent scene"""
|
|
19
|
+
aspect_ratio: float
|
|
20
|
+
"""Aspect ratio of the engine window"""
|
|
21
|
+
position: glm.vec3
|
|
22
|
+
"""Location of the camera (maters)"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, position=(0, 0, 20), yaw=-90, pitch=0) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Camera object to get view and projection matricies. Movement built in
|
|
27
|
+
"""
|
|
28
|
+
# Back references
|
|
29
|
+
self.scene = None
|
|
30
|
+
self.engine = None
|
|
31
|
+
# The initial aspect ratio of the screen
|
|
32
|
+
self.aspect_ratio = 1.0
|
|
33
|
+
# Position
|
|
34
|
+
self.position = glm.vec3(position)
|
|
35
|
+
# k vector for vertical movement
|
|
36
|
+
self.UP = glm.vec3(0, 1, 0)
|
|
37
|
+
# Movement vectors
|
|
38
|
+
self.up = glm.vec3(0, 1, 0)
|
|
39
|
+
self.right = glm.vec3(1, 0, 0)
|
|
40
|
+
self.forward = glm.vec3(0, 0, -1)
|
|
41
|
+
# Look directions in degrees
|
|
42
|
+
self.yaw = yaw
|
|
43
|
+
self.pitch = pitch
|
|
44
|
+
# View matrix
|
|
45
|
+
self.m_view = self.get_view_matrix()
|
|
46
|
+
# Projection matrix
|
|
47
|
+
self.m_proj = self.get_projection_matrix()
|
|
48
|
+
|
|
49
|
+
def update(self) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Updates the camera view matrix
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
self.update_camera_vectors()
|
|
55
|
+
self.m_view = self.get_view_matrix()
|
|
56
|
+
|
|
57
|
+
def update_camera_vectors(self) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Computes the forward vector based on the pitch and yaw. Computes horizontal and vertical vectors with cross product.
|
|
60
|
+
"""
|
|
61
|
+
yaw, pitch = glm.radians(self.yaw), glm.radians(self.pitch)
|
|
62
|
+
|
|
63
|
+
self.forward.x = glm.cos(yaw) * glm.cos(pitch)
|
|
64
|
+
self.forward.y = glm.sin(pitch)
|
|
65
|
+
self.forward.z = glm.sin(yaw) * glm.cos(pitch)
|
|
66
|
+
|
|
67
|
+
self.forward = glm.normalize(self.forward)
|
|
68
|
+
self.right = glm.normalize(glm.cross(self.forward, self.UP))
|
|
69
|
+
self.up = glm.normalize(glm.cross(self.right, self.forward))
|
|
70
|
+
|
|
71
|
+
def use(self):
|
|
72
|
+
# Updated aspect ratio of the screen
|
|
73
|
+
self.aspect_ratio = self.engine.win_size[0] / self.engine.win_size[1]
|
|
74
|
+
# View matrix
|
|
75
|
+
self.m_view = self.get_view_matrix()
|
|
76
|
+
# Projection matrix
|
|
77
|
+
self.m_proj = self.get_projection_matrix()
|
|
78
|
+
|
|
79
|
+
def get_view_matrix(self) -> glm.mat4x4:
|
|
80
|
+
return glm.lookAt(self.position, self.position + self.forward, self.up)
|
|
81
|
+
|
|
82
|
+
def get_projection_matrix(self) -> glm.mat4x4:
|
|
83
|
+
return glm.perspective(glm.radians(FOV), self.aspect_ratio, NEAR, FAR)
|
|
84
|
+
|
|
85
|
+
def get_params(self) -> tuple:
|
|
86
|
+
return self.engine, self.position, self.yaw, self.pitch
|
|
87
|
+
|
|
88
|
+
def __repr__(self):
|
|
89
|
+
return f'<Basilisk Camera | Position: {self.position}, Direction: {self.forward}>'
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def scene(self): return self._scene
|
|
93
|
+
@property
|
|
94
|
+
def position(self): return self._position
|
|
95
|
+
@property
|
|
96
|
+
def direction(self): return self.forward
|
|
97
|
+
|
|
98
|
+
@scene.setter
|
|
99
|
+
def scene(self, value):
|
|
100
|
+
if value == None: return
|
|
101
|
+
self._scene = value
|
|
102
|
+
self.engine = self._scene.engine
|
|
103
|
+
self.use()
|
|
104
|
+
@position.setter
|
|
105
|
+
def position(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
106
|
+
if isinstance(value, glm.vec3): self._position = glm.vec3(value)
|
|
107
|
+
elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
|
|
108
|
+
if len(value) != 3: raise ValueError(f'Camera: Invalid number of values for position. Expected 3, got {len(value)}')
|
|
109
|
+
self._position = glm.vec3(value)
|
|
110
|
+
else: raise TypeError(f'Camera: Invalid position value type {type(value)}')
|
|
111
|
+
@direction.setter
|
|
112
|
+
def direction(self, value: tuple | list | glm.vec3 | np.ndarray):
|
|
113
|
+
if isinstance(value, glm.vec3): self.direction = glm.normalize(glm.vec3(value))
|
|
114
|
+
elif isinstance(value, tuple) or isinstance(value, list) or isinstance(value, np.ndarray):
|
|
115
|
+
if len(value) != 3: raise ValueError(f'Camera: Invalid number of values for direction. Expected 3, got {len(value)}')
|
|
116
|
+
self.forward = glm.normalize(glm.vec3(value))
|
|
117
|
+
else: raise TypeError(f'Camera: Invalid direction value type {type(value)}')
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class FreeCamera(Camera):
|
|
121
|
+
def __init__(self, position=(0, 0, 20), yaw=-90, pitch=0):
|
|
122
|
+
super().__init__(position, yaw, pitch)
|
|
123
|
+
|
|
124
|
+
def update(self) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Updates the camera position and rotaiton based on user input
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
self.move()
|
|
130
|
+
self.rotate()
|
|
131
|
+
self.update_camera_vectors()
|
|
132
|
+
self.m_view = self.get_view_matrix()
|
|
133
|
+
|
|
134
|
+
def rotate(self) -> None:
|
|
135
|
+
"""
|
|
136
|
+
Rotates the camera based on the amount of mouse movement.
|
|
137
|
+
"""
|
|
138
|
+
rel_x, rel_y = pg.mouse.get_rel()
|
|
139
|
+
self.yaw += rel_x * SENSITIVITY
|
|
140
|
+
self.pitch -= rel_y * SENSITIVITY
|
|
141
|
+
self.yaw = self.yaw % 360
|
|
142
|
+
self.pitch = max(-89, min(89, self.pitch))
|
|
143
|
+
|
|
144
|
+
def move(self) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Checks for button presses and updates vectors accordingly.
|
|
147
|
+
"""
|
|
148
|
+
velocity = SPEED * self.engine.delta_time
|
|
149
|
+
keys = self.engine.keys
|
|
150
|
+
if keys[pg.K_w]:
|
|
151
|
+
self.position += glm.normalize(glm.vec3(self.forward.x, 0, self.forward.z)) * velocity
|
|
152
|
+
if keys[pg.K_s]:
|
|
153
|
+
self.position -= glm.normalize(glm.vec3(self.forward.x, 0, self.forward.z)) * velocity
|
|
154
|
+
if keys[pg.K_a]:
|
|
155
|
+
self.position -= self.right * velocity
|
|
156
|
+
if keys[pg.K_d]:
|
|
157
|
+
self.position += self.right * velocity
|
|
158
|
+
if keys[pg.K_SPACE]:
|
|
159
|
+
self.position += self.UP * velocity
|
|
160
|
+
if keys[pg.K_LSHIFT]:
|
|
161
|
+
self.position -= self.UP * velocity
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class StaticCamera(Camera):
|
|
165
|
+
def __init__(self, position=(0, 0, 20), yaw=-90, pitch=0):
|
|
166
|
+
super().__init__(position, yaw, pitch)
|
basilisk/render/chunk.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from .batch import Batch
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Chunk():
|
|
5
|
+
chunk_handler: ...
|
|
6
|
+
"""Back refrence to the parent chunk handler"""
|
|
7
|
+
chunk_key: tuple
|
|
8
|
+
"""The position of the chunk. Used as a key in the chunk handler"""
|
|
9
|
+
batch: Batch
|
|
10
|
+
"""Batched mesh of the chunk"""
|
|
11
|
+
nodes: set
|
|
12
|
+
"""Set conaining references to all nodes in the chunk"""
|
|
13
|
+
static: bool
|
|
14
|
+
"""Type of node that the chunk recognizes"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, chunk_handler, chunk_key: tuple, static: bool) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Basilisk chunk object.
|
|
19
|
+
Contains references to all nodes in the chunk.
|
|
20
|
+
Handles batching for its own nodes
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Back references
|
|
24
|
+
self.chunk_handler = chunk_handler
|
|
25
|
+
self.chunk_key = chunk_key
|
|
26
|
+
|
|
27
|
+
self.static = static
|
|
28
|
+
|
|
29
|
+
# Create empty batch
|
|
30
|
+
self.batch = Batch(self)
|
|
31
|
+
|
|
32
|
+
# Create empty set for chunk's nodes
|
|
33
|
+
self.nodes = set()
|
|
34
|
+
|
|
35
|
+
def render(self) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Renders the chunk mesh
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
if self.batch.vao: self.batch.vao.render()
|
|
41
|
+
|
|
42
|
+
def update(self) -> bool:
|
|
43
|
+
"""
|
|
44
|
+
Batches all the node meshes in the chunk
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
# Check if there are no nodes in the chunk
|
|
48
|
+
if not self.nodes: return False
|
|
49
|
+
# Batch the chunk nodes, return success bit
|
|
50
|
+
return self.batch.batch()
|
|
51
|
+
|
|
52
|
+
def node_update_callback(self, node):
|
|
53
|
+
if not self.batch.vbo: return
|
|
54
|
+
|
|
55
|
+
data = node.get_data()
|
|
56
|
+
self.batch.vbo.write(data, node.data_index * 25 * 4)
|
|
57
|
+
|
|
58
|
+
def add(self, node):
|
|
59
|
+
"""
|
|
60
|
+
Adds an existing node to the chunk. Updates the node's chunk reference
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
self.nodes.add(node)
|
|
64
|
+
node.chunk = self
|
|
65
|
+
|
|
66
|
+
return node
|
|
67
|
+
|
|
68
|
+
def remove(self, node):
|
|
69
|
+
"""
|
|
70
|
+
Removes a node from the chunk
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
self.nodes.remove(node)
|
|
74
|
+
|
|
75
|
+
return node
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
return f'<Basilisk Chunk | {self.chunk_key}, {len(self.nodes)} nodes, {'static' if self.static else 'dynamic'}>'
|
|
79
|
+
|
|
80
|
+
def __del__(self) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Deletes the batch if this chunk is deleted
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
del self.batch
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import moderngl as mgl
|
|
3
|
+
from .chunk import Chunk
|
|
4
|
+
from ..nodes.node import Node
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChunkHandler():
|
|
8
|
+
engine: ...
|
|
9
|
+
"""Back reference to the parent engine"""
|
|
10
|
+
scene: ...
|
|
11
|
+
"""Back reference to the parent scene"""
|
|
12
|
+
ctx: mgl.Context
|
|
13
|
+
"""Back reference to the parent context"""
|
|
14
|
+
program: mgl.Program
|
|
15
|
+
"""Reference to the shader program used by batches"""
|
|
16
|
+
chunks: list[dict]
|
|
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"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, scene) -> None:
|
|
22
|
+
# Reference to the scene hadlers and variables
|
|
23
|
+
"""
|
|
24
|
+
Handles all the chunking of all the nodes in the scene
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# Back references
|
|
28
|
+
self.scene = scene
|
|
29
|
+
self.engine = scene.engine
|
|
30
|
+
self.ctx = scene.engine.ctx
|
|
31
|
+
self.program = scene.shader_handler.programs['batch']
|
|
32
|
+
|
|
33
|
+
# List for the dynamic and static chunk dictionaries | [dyanmic: dict, static: dict]
|
|
34
|
+
self.chunks = [{} , {} ]
|
|
35
|
+
self.updated_chunks = [set(), set()]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def render(self) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Renders all the chunk batches in the camera's range
|
|
41
|
+
Includes some view culling, but not frustum culling.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# Gets a rectanglur prism of chunks in the cameras view
|
|
45
|
+
render_range_x, render_range_y, render_range_z = self.get_render_range()
|
|
46
|
+
|
|
47
|
+
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
|
+
# 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()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def update(self) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Updates all the chunks that have been updated since the last frame.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# Loop through the set of updated chunk keys and update the chunk
|
|
62
|
+
removes = []
|
|
63
|
+
|
|
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]:
|
|
68
|
+
if chunk.update(): continue
|
|
69
|
+
removes.append((1, chunk))
|
|
70
|
+
|
|
71
|
+
# 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]]
|
|
75
|
+
|
|
76
|
+
# Clears the set of updated chunks so that they are not updated unless they are updated again
|
|
77
|
+
self.updated_chunks = [set(), set()]
|
|
78
|
+
|
|
79
|
+
def add(self, node: Node) -> Node:
|
|
80
|
+
"""
|
|
81
|
+
Adds an existing node to its chunk. Updates the node's chunk reference
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
# The key of the chunk the node will be added to
|
|
85
|
+
chunk_size = self.engine.config.chunk_size
|
|
86
|
+
chunk_key = (int(node.x // chunk_size), int(node.y // chunk_size), int(node.z // chunk_size))
|
|
87
|
+
|
|
88
|
+
# 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)
|
|
91
|
+
|
|
92
|
+
# Add the node to the chunk
|
|
93
|
+
self.chunks[node.static][chunk_key].add(node)
|
|
94
|
+
|
|
95
|
+
# Update the chunk
|
|
96
|
+
self.updated_chunks[node.static].add(self.chunks[node.static][chunk_key])
|
|
97
|
+
|
|
98
|
+
return Node
|
|
99
|
+
|
|
100
|
+
def remove(self, node) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Removes a node from the its chunk
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
# Remove the node
|
|
106
|
+
chunk = node.chunk
|
|
107
|
+
chunk.remove(node)
|
|
108
|
+
node.chunk = None
|
|
109
|
+
|
|
110
|
+
# Update the chunk
|
|
111
|
+
self.updated_chunks.add(chunk)
|
|
112
|
+
|
|
113
|
+
def get_render_range(self) -> tuple:
|
|
114
|
+
"""
|
|
115
|
+
Returns a rectangluar prism of chunks that are in the camera's view.
|
|
116
|
+
Tuple return is in form ((x1, x2), (y1, y2), (z1, z2))
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
cam_position = self.scene.camera.position # glm.vec3(x, y, z)
|
|
120
|
+
fov = 40 # The range in which a direction will not be culled
|
|
121
|
+
|
|
122
|
+
# Default to a cube of chunks around the camera extending view_distance chunks in each direction
|
|
123
|
+
chunk_size = self.engine.config.chunk_size
|
|
124
|
+
render_distance = self.engine.config.render_distance
|
|
125
|
+
render_range_x = [int(cam_position.x // chunk_size - render_distance), int(cam_position.x // chunk_size + render_distance + 1)]
|
|
126
|
+
render_range_y = [int(cam_position.y // chunk_size - render_distance), int(cam_position.y // chunk_size + render_distance + 1)]
|
|
127
|
+
render_range_z = [int(cam_position.z // chunk_size - render_distance), int(cam_position.z // chunk_size + render_distance + 1)]
|
|
128
|
+
|
|
129
|
+
# Remove chunks that the camera is facing away from
|
|
130
|
+
render_range_x[1] -= render_distance * (180 - fov < self.scene.camera.yaw < 180 + fov) - 1
|
|
131
|
+
render_range_x[0] += render_distance * (-fov < self.scene.camera.yaw < fov or self.scene.camera.yaw > 360 - fov) - 1
|
|
132
|
+
|
|
133
|
+
render_range_y[0] += render_distance * (self.scene.camera.pitch > 25) - 1
|
|
134
|
+
render_range_y[1] -= render_distance * (self.scene.camera.pitch < -25) - 1
|
|
135
|
+
|
|
136
|
+
render_range_z[1] -= render_distance * (270 - fov < self.scene.camera.yaw < 270 + fov) - 1
|
|
137
|
+
render_range_z[0] += render_distance * (90 - fov < self.scene.camera.yaw < 90 + fov) - 1
|
|
138
|
+
|
|
139
|
+
return (render_range_x, render_range_y, render_range_z)
|
basilisk/render/frame.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import moderngl as mgl
|
|
3
|
+
from PIL import Image
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Frame:
|
|
7
|
+
program: mgl.Program=None
|
|
8
|
+
vbo: mgl.Buffer=None
|
|
9
|
+
vao: mgl.VertexArray=None
|
|
10
|
+
frame_texture: mgl.Texture=None
|
|
11
|
+
depth_texture: mgl.Texture=None
|
|
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
|
+
|
|
18
|
+
def __init__(self, scene) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Basilisk render destination.
|
|
21
|
+
Can be used to render to the screen or for headless rendering
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
self.scene = scene
|
|
25
|
+
self.engine = scene.engine
|
|
26
|
+
self.ctx = scene.ctx
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
self.load_program()
|
|
30
|
+
self.set_textures()
|
|
31
|
+
self.set_renderer()
|
|
32
|
+
|
|
33
|
+
self.postprocess = {}
|
|
34
|
+
self.load_post_shader('frame', 'filter')
|
|
35
|
+
|
|
36
|
+
def render(self):
|
|
37
|
+
"""
|
|
38
|
+
Renders the current frame to the screen
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# self.apply_postprocess('filter')
|
|
42
|
+
|
|
43
|
+
self.ctx.screen.use()
|
|
44
|
+
self.program['screenTexture'] = 0
|
|
45
|
+
self.framebuffer.color_attachments[0].use(location=0)
|
|
46
|
+
self.vao.render()
|
|
47
|
+
|
|
48
|
+
def use(self):
|
|
49
|
+
"""
|
|
50
|
+
Uses the frame as a render target
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
self.framebuffer.use()
|
|
54
|
+
self.framebuffer.clear()
|
|
55
|
+
|
|
56
|
+
def save(self, destination: str=None):
|
|
57
|
+
"""
|
|
58
|
+
Saves the frame as an image to the given file destination
|
|
59
|
+
"""
|
|
60
|
+
|
|
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
|
+
|
|
67
|
+
|
|
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
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
# Read the shaders
|
|
91
|
+
with open(f'basilisk/shaders/{vert}.vert') as file:
|
|
92
|
+
vertex_shader = file.read()
|
|
93
|
+
with open(f'basilisk/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
|
|
121
|
+
"""
|
|
122
|
+
|
|
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:
|
|
143
|
+
"""
|
|
144
|
+
Sets the vertex data and vao for the frame
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
# Release any existing memory
|
|
148
|
+
if self.vbo: self.vbo.release()
|
|
149
|
+
if self.vao: self.vao.release()
|
|
150
|
+
|
|
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')
|
|
162
|
+
|
|
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
|
+
def __del__(self) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Releases memory used by the frame
|
|
172
|
+
"""
|
|
173
|
+
|
|
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()
|