"""
Procedural cloud mesh generation and greedy meshing.
This module manages the volumetric cloud layer by generating 2D noise-based
density maps and using a specialized 2D greedy meshing algorithm to compile
optimized, low-polygon chunks of clouds that scroll across the sky.
"""
from typing import Any, Tuple
import numpy as np
from numba import njit, prange
from numpy.typing import NDArray
import noise
from meshes.base_mesh import BaseMesh
from noise import noise2
from profiler import global_profiler
from settings import CHUNK_AREA, CHUNK_SIZE, CLOUD_HEIGHT, WORLD_AREA, WORLD_DEPTH, WORLD_WIDTH
[docs]
class CloudMesh(BaseMesh):
"""
Generates the geometry for the procedural 3D cloud layer.
Utilizes simplex noise to map cloud density and a 2D greedy meshing algorithm
to create an optimized, low-polygon mesh of cloud blocks.
Args:
app (Any): The main application instance providing the ModernGL context.
"""
@global_profiler.profile_func('CloudMesh_Init')
def __init__(self, app: Any) -> None:
"""
Initializes the cloud mesh, setting up the shader program and vertex buffer
configuration required to render the volumetric clouds.
"""
super().__init__()
self.app: Any = app
self.ctx: Any = self.app.ctx
self.program: Any = self.app.shader_program.clouds
self.vbo_format: str = '3u2'
self.attrs: Tuple[str, ...] = ('in_position',)
self.vao: Any = self.get_vao()
[docs]
@global_profiler.profile_func('CloudMesh_GetVertexData')
def get_vertex_data(self) -> NDArray[np.uint16]:
"""
Coordinates the generation of the raw cloud density data and subsequently
constructs the optimized 3D mesh vertex data required for rendering.
"""
cloud_data: Any = np.zeros(WORLD_AREA * CHUNK_SIZE**2, dtype='uint8')
self.gen_clouds(cloud_data, noise.perm)
return self.build_mesh(cloud_data) # type: ignore[no-any-return]
[docs]
@staticmethod
@njit(cache=True, fastmath=True, parallel=True, nogil=True)
def gen_clouds(cloud_data: Any, perm_array: Any) -> None:
"""
Populates a 2D density grid using multi-octave simplex noise to procedurally
determine the exact locations where clouds should form in the sky.
"""
for x in prange(WORLD_WIDTH * CHUNK_SIZE):
for z in range(WORLD_DEPTH * CHUNK_SIZE):
if noise2(0.13 * x, 0.13 * z, perm_array) < 0.2:
continue
cloud_data[x + WORLD_WIDTH * CHUNK_SIZE * z] = 1
[docs]
@staticmethod
@njit(cache=True, fastmath=True, nogil=True)
def build_mesh(cloud_data: Any) -> NDArray[np.uint16]:
"""
A specialized 2D greedy meshing algorithm that scans the generated cloud density grid.
It mathematically combines adjacent, identical cloud blocks into massive single
polygonal faces, drastically reducing the total number of vertices sent to the GPU.
"""
mesh = np.empty(WORLD_AREA * CHUNK_AREA * 6 * 3, dtype='uint16')
index = 0
width = WORLD_WIDTH * CHUNK_SIZE
depth = WORLD_DEPTH * CHUNK_SIZE
y = CLOUD_HEIGHT
visited = set()
for z in range(depth):
for x in range(width):
idx = x + width * z
if not cloud_data[idx] or idx in visited:
continue
# find number of continuous quads along x
x_count = 1
idx = (x + x_count) + width * z
while x + x_count < width and cloud_data[idx] and idx not in visited:
x_count += 1
idx = (x + x_count) + width * z
# find the number of continuous quads along z for each x
z_count_list = []
for ix in range(x_count):
z_count = 1
idx = (x + ix) + width * (z + z_count)
while (z + z_count) < depth and cloud_data[idx] and idx not in visited:
z_count += 1
idx = (x + ix) + width * (z + z_count)
z_count_list.append(z_count)
# find min count z to form a large quad
z_count = min(z_count_list) if z_count_list else 1
# mark all unit quads of the large quad as visited
for ix in range(x_count):
for iz in range(z_count):
visited.add((x + ix) + width * (z + iz))
v0 = x, y, z
v1 = x + x_count, y, z + z_count
v2 = x + x_count, y, z
v3 = x, y, z + z_count
for vertex in (v0, v1, v2, v0, v3, v1):
for attr in vertex:
mesh[index] = attr
index += 1
mesh = mesh[: index + 1]
return mesh