Phase 13: real non-cube block models via blockstate/model JSON resolution
Adds worker/src/models.rs (parent-chain blockstate/model resolution, texture-variable substitution reusing Phase 12's atlas keys), routes non-cube blocks through new per-element mesh emission in mesh.rs while leaving full-cube blocks on the existing cube mesher, and threads a per-server ModelContext (vanilla worker-wide + modded per-job) through main.rs. Modded model JSON is stored in a new block_models Postgres table and read alongside the existing (previously write-only) block_registry table. Fixes a pre-existing face-culling bug as a side effect of excluding non-cube voxels from the cube mesher's input. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
This commit is contained in:
@@ -48,6 +48,51 @@ pub(crate) fn texture_atlas() -> Option<&'static crate::atlas::TextureAtlas> {
|
||||
TEXTURE_ATLAS.get()
|
||||
}
|
||||
|
||||
/// Same `OnceLock`-once-at-startup pattern as `TEXTURE_PALETTE`/`TEXTURE_ATLAS`, for the Phase 13
|
||||
/// vanilla model registry (see `models.rs`). Worker-wide, not per-server — the same simplification
|
||||
/// tier already accepted for the texture palette/atlas. `None` until `main.rs` successfully builds
|
||||
/// one; `mesh.rs` simply skips non-cube resolution entirely when this is `None`.
|
||||
static VANILLA_MODEL_REGISTRY: OnceLock<crate::models::ModelRegistry> = OnceLock::new();
|
||||
|
||||
pub fn set_vanilla_model_registry(registry: crate::models::ModelRegistry) {
|
||||
let _ = VANILLA_MODEL_REGISTRY.set(registry);
|
||||
}
|
||||
|
||||
pub(crate) fn vanilla_model_registry() -> Option<&'static crate::models::ModelRegistry> {
|
||||
VANILLA_MODEL_REGISTRY.get()
|
||||
}
|
||||
|
||||
/// Bundles everything `mesh.rs` needs to resolve one voxel's real (possibly non-cube) shape:
|
||||
/// the worker-wide vanilla registry plus one job's per-server modded registry/id-map, both
|
||||
/// optional (a server with no modded model data yet, or a worker that never built the vanilla
|
||||
/// registry, simply resolves nothing — mesh_section falls back to its pre-Phase-13 behavior).
|
||||
/// Built fresh per chunk-job in `main.rs` (the modded half is genuinely per-server; the vanilla
|
||||
/// half is just a borrow of the static above) rather than stored globally, since it's cheap and
|
||||
/// keeps `mesh_section` ignorant of *where* the data came from.
|
||||
#[derive(Default)]
|
||||
pub struct ModelContext<'a> {
|
||||
pub vanilla: Option<&'a crate::models::ModelRegistry>,
|
||||
pub modded: Option<&'a crate::models::ModelRegistry>,
|
||||
pub modded_id_to_name: std::collections::HashMap<u16, String>,
|
||||
}
|
||||
|
||||
impl<'a> ModelContext<'a> {
|
||||
pub fn resolve(&self, block_id: u16, meta: u8) -> Option<crate::models::ResolvedModel> {
|
||||
crate::models::resolve_for_block(self.vanilla, self.modded, &self.modded_id_to_name, block_id, meta)
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a `ModelContext` for one chunk-job, combining the worker-wide vanilla registry (read
|
||||
/// from the `OnceLock` above — kept private to this module, `main.rs` never reads it directly) with
|
||||
/// the caller-supplied per-server modded registry/id-map (see `main.rs`'s `fetch_chunk_data`,
|
||||
/// which queries `block_registry`/`block_models` for the job's `server_id`).
|
||||
pub fn model_context(
|
||||
modded: Option<&crate::models::ModelRegistry>,
|
||||
modded_id_to_name: std::collections::HashMap<u16, String>,
|
||||
) -> ModelContext<'_> {
|
||||
ModelContext { vanilla: vanilla_model_registry(), modded, modded_id_to_name }
|
||||
}
|
||||
|
||||
/// One rendered column within a chunk, in chunk-local coordinates (0..16).
|
||||
pub struct ColumnPixel {
|
||||
pub local_x: u8,
|
||||
|
||||
Reference in New Issue
Block a user