Basic crops system
This commit is contained in:
+164
-100
@@ -6,12 +6,21 @@ import {
|
||||
type BlockRegistry,
|
||||
type RenderLayer,
|
||||
} from "$/common/everything_registry.ts";
|
||||
import { AIR, CHUNK_HEIGHT, CHUNK_SIZE, ID_MASK, type SpriteRegion, TEXTURE_SIZE } from "$/common/constants.ts";
|
||||
import {
|
||||
AIR,
|
||||
CHUNK_AREA,
|
||||
CHUNK_HEIGHT,
|
||||
CHUNK_SIZE,
|
||||
ID_MASK,
|
||||
type SpriteRegion,
|
||||
TEXTURE_SIZE,
|
||||
} from "$/common/constants.ts";
|
||||
import type { Texture } from "../renderer/types.ts";
|
||||
import { type FromChunkWorker, TERRAIN_VERTEX_FLOATS, type ToChunkWorker } from "./chunk_messages.ts";
|
||||
import { generate_raw_chunk, WorldgenSetup } from "$/common/generation.ts";
|
||||
import { load_worldgen } from "$/common/worldgen_loader.ts";
|
||||
import { default_block_value } from "$/common/utils.ts";
|
||||
import { default_block_value, get_state_value } from "$/common/utils.ts";
|
||||
import { bake_model, block_variant, FACE_CORNERS, find_model, type ModelJson } from "$/common/block_models.ts";
|
||||
import {
|
||||
choose_sort_type,
|
||||
FACE_NORMALS,
|
||||
@@ -37,19 +46,7 @@ const FLOATS_PER_QUAD = 4 * TERRAIN_VERTEX_FLOATS;
|
||||
// keeps texture lookups off the sprite's edge
|
||||
const UV_PAD = 0.5;
|
||||
|
||||
// same order as FACE_NORMALS in translucent_sort.ts
|
||||
const FACES = ["top", "bottom", "front", "back", "left", "right"] as const;
|
||||
// each face's corners in drawing order (counter clockwise from outside), as offsets from the block's corner
|
||||
const FACE_CORNERS = [
|
||||
[[0, 1, 1], [1, 1, 1], [1, 1, 0], [0, 1, 0]],
|
||||
[[0, 0, 0], [1, 0, 0], [1, 0, 1], [0, 0, 1]],
|
||||
[[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]],
|
||||
[[1, 0, 0], [0, 0, 0], [0, 1, 0], [1, 1, 0]],
|
||||
[[0, 0, 0], [0, 0, 1], [0, 1, 1], [0, 1, 0]],
|
||||
[[1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]],
|
||||
] as const;
|
||||
// which end of the sprite each corner gets, u then v (0 = start, 1 = end)
|
||||
const CORNER_UVS = [[0, 1], [1, 1], [1, 0], [0, 0]] as const;
|
||||
// same order as FACE_NORMALS in translucent_sort.ts, and FACE_CORNERS and CORNER_UVS in block_models.ts
|
||||
// minecraft's shading by direction, so faces stay apart even in flat light
|
||||
const FACE_SHADE = [1.0, 0.5, 0.8, 0.8, 0.6, 0.6];
|
||||
|
||||
@@ -86,6 +83,24 @@ const block_lets_light_by = new Uint8Array(TABLE_SIZE);
|
||||
const light_tables: LightTables = { opacity: new Uint8Array(TABLE_SIZE), emission: new Uint8Array(TABLE_SIZE) };
|
||||
const region = new LightRegion();
|
||||
|
||||
// a model's quad ready for the mesher: uvs in the atlas, and for each corner how much of each of the face's
|
||||
// four corner lights it gets, so faces smaller than the block are lit like minecraft's
|
||||
interface MeshQuad {
|
||||
positions: number[];
|
||||
uvs: number[];
|
||||
face: number;
|
||||
cull: number;
|
||||
flush: boolean;
|
||||
shade: number;
|
||||
sprite: SpriteRegion;
|
||||
// 4 weights per corner
|
||||
light_weights: number[];
|
||||
}
|
||||
|
||||
let models: Record<string, ModelJson> = {};
|
||||
// by block value, or by numeric id for blocks without variants
|
||||
const baked = new Map<number, MeshQuad[]>();
|
||||
|
||||
let textures_info: TexturesInfo = {};
|
||||
let image: Texture;
|
||||
let worldgen: WorldgenSetup | undefined;
|
||||
@@ -100,6 +115,8 @@ self.onmessage = async (event: MessageEvent<ToChunkWorker>) => {
|
||||
case "init":
|
||||
blocks_registry = message.blocks_registry;
|
||||
block_ids = message.block_ids;
|
||||
models = message.models;
|
||||
baked.clear();
|
||||
build_block_tables();
|
||||
textures_info = message.textures_info;
|
||||
image = message.image as Texture;
|
||||
@@ -115,7 +132,12 @@ self.onmessage = async (event: MessageEvent<ToChunkWorker>) => {
|
||||
case "mesh": {
|
||||
region.fill(message.chunks);
|
||||
region.compute(light_tables);
|
||||
const { solid, cutout, translucent } = make_chunk_mesh(message.chunk_x, message.chunk_z, message.camera);
|
||||
const { solid, cutout, translucent } = make_chunk_mesh(
|
||||
message.chunk_x,
|
||||
message.chunk_z,
|
||||
message.chunks[4]!,
|
||||
message.camera,
|
||||
);
|
||||
post(
|
||||
{
|
||||
type: "meshed",
|
||||
@@ -263,34 +285,19 @@ function should_flip() {
|
||||
return light(0) + light(2) > light(1) + light(3);
|
||||
}
|
||||
|
||||
function push_quad(
|
||||
vertices: Float32Array,
|
||||
i: number,
|
||||
face: number,
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
sprite: SpriteRegion,
|
||||
alpha: number,
|
||||
) {
|
||||
const u0 = (sprite.x * TEXTURE_SIZE + UV_PAD) / image.width;
|
||||
const v0 = (sprite.y * TEXTURE_SIZE + UV_PAD) / image.height;
|
||||
const u1 = ((sprite.x + 1) * TEXTURE_SIZE - UV_PAD) / image.width;
|
||||
const v1 = ((sprite.y + 1) * TEXTURE_SIZE - UV_PAD) / image.height;
|
||||
const shade = FACE_SHADE[face];
|
||||
function push_quad(vertices: Float32Array, i: number, quad: MeshQuad, x: number, y: number, z: number, alpha: number) {
|
||||
const sprite = quad.sprite;
|
||||
// starting from the second corner moves the diagonal, the winding stays the same
|
||||
const first = should_flip() ? 1 : 0;
|
||||
|
||||
for (let k = 0; k < 4; k++) {
|
||||
const corner = (first + k) & 3;
|
||||
const [cx, cy, cz] = FACE_CORNERS[face][corner];
|
||||
const [cu, cv] = CORNER_UVS[corner];
|
||||
const brightness = shade * corner_ao[corner];
|
||||
vertices[i++] = x + cx;
|
||||
vertices[i++] = y + cy;
|
||||
vertices[i++] = z + cz;
|
||||
vertices[i++] = cu ? u1 : u0;
|
||||
vertices[i++] = cv ? v1 : v0;
|
||||
const brightness = quad.shade * corner_ao[corner];
|
||||
vertices[i++] = x + quad.positions[corner * 3];
|
||||
vertices[i++] = y + quad.positions[corner * 3 + 1];
|
||||
vertices[i++] = z + quad.positions[corner * 3 + 2];
|
||||
vertices[i++] = atlas_u(sprite, quad.uvs[corner * 2]);
|
||||
vertices[i++] = atlas_v(sprite, quad.uvs[corner * 2 + 1]);
|
||||
vertices[i++] = brightness;
|
||||
vertices[i++] = brightness;
|
||||
vertices[i++] = brightness;
|
||||
@@ -302,14 +309,111 @@ function push_quad(
|
||||
return i;
|
||||
}
|
||||
|
||||
// pixels of a sprite to atlas coordinates, kept off the sprite's edge
|
||||
function atlas_u(sprite: SpriteRegion, u: number) {
|
||||
return (sprite.x * TEXTURE_SIZE + Math.min(Math.max(u, UV_PAD), TEXTURE_SIZE - UV_PAD)) / image.width;
|
||||
}
|
||||
|
||||
function atlas_v(sprite: SpriteRegion, v: number) {
|
||||
return (sprite.y * TEXTURE_SIZE + Math.min(Math.max(v, UV_PAD), TEXTURE_SIZE - UV_PAD)) / image.height;
|
||||
}
|
||||
|
||||
// the model quads for a block value, baked the first time it's seen
|
||||
function block_quads(value: number): MeshQuad[] {
|
||||
const nid = value & ID_MASK;
|
||||
const block = blocks_registry[nid];
|
||||
const key = block.variants ? value : nid;
|
||||
let quads = baked.get(key);
|
||||
if (quads) return quads;
|
||||
|
||||
let states: Record<string, number> | undefined;
|
||||
if (block.variants && block.states) {
|
||||
states = {};
|
||||
for (const state of block.states) states[state.name] = get_state_value(value, block, state.name)!;
|
||||
}
|
||||
const variant = block_variant(block, states);
|
||||
const model = find_model(variant.model, models) ?? find_model("engine:cube", models)!;
|
||||
|
||||
quads = bake_model(model, variant.textures, variant.y).map((quad) => ({
|
||||
positions: quad.positions,
|
||||
uvs: quad.uvs,
|
||||
face: quad.face,
|
||||
cull: quad.cull,
|
||||
flush: quad.flush,
|
||||
shade: quad.shade ? FACE_SHADE[quad.face] : 1,
|
||||
sprite: textures_info[quad.texture] ?? textures_info["engine:missing"],
|
||||
light_weights: light_weights(quad.face, quad.positions),
|
||||
}));
|
||||
baked.set(key, quads);
|
||||
return quads;
|
||||
}
|
||||
|
||||
// bilinear weights of the face's four corners at each of the quad's corners
|
||||
function light_weights(face: number, positions: number[]) {
|
||||
const axes = [0, 1, 2].filter((axis) => FACE_NORMALS[face][axis] === 0);
|
||||
const weights: number[] = [];
|
||||
for (let k = 0; k < 4; k++) {
|
||||
for (const corner of FACE_CORNERS[face]) {
|
||||
let weight = 1;
|
||||
for (const axis of axes) {
|
||||
const t = Math.min(Math.max(positions[k * 3 + axis], 0), 1);
|
||||
weight *= corner[axis] ? t : 1 - t;
|
||||
}
|
||||
weights.push(weight);
|
||||
}
|
||||
}
|
||||
return weights;
|
||||
}
|
||||
|
||||
// light for a quad smaller than a face, or not on the block's edge at all
|
||||
const face_sky = new Float32Array(4);
|
||||
const face_block = new Float32Array(4);
|
||||
const face_ao = new Float32Array(4);
|
||||
|
||||
function light_quad(quad: MeshQuad, index: number, y: number, face_offsets: number[]) {
|
||||
if (quad.flush) {
|
||||
light_face_corners(quad.face, index + face_offsets[quad.face], y + FACE_NORMALS[quad.face][1]);
|
||||
face_sky.set(corner_sky);
|
||||
face_block.set(corner_block);
|
||||
face_ao.set(corner_ao);
|
||||
const w = quad.light_weights;
|
||||
for (let k = 0; k < 4; k++) {
|
||||
let sky = 0;
|
||||
let block = 0;
|
||||
let ao = 0;
|
||||
for (let c = 0; c < 4; c++) {
|
||||
sky += w[k * 4 + c] * face_sky[c];
|
||||
block += w[k * 4 + c] * face_block[c];
|
||||
ao += w[k * 4 + c] * face_ao[c];
|
||||
}
|
||||
corner_sky[k] = sky;
|
||||
corner_block[k] = block;
|
||||
corner_ao[k] = ao;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// inside the block: its own cell's light, or the cell it faces if the block stops light itself
|
||||
let cell = index;
|
||||
let cell_y = y;
|
||||
if (light_tables.opacity[region_block(region, index, y)] === 15) {
|
||||
cell = index + face_offsets[quad.face];
|
||||
cell_y = y + FACE_NORMALS[quad.face][1];
|
||||
}
|
||||
corner_sky.fill(region_sky(region, cell, cell_y));
|
||||
corner_block.fill(region_block_light(region, cell, cell_y));
|
||||
corner_ao.fill(1);
|
||||
}
|
||||
|
||||
// region has to be filled and lit first
|
||||
function make_chunk_mesh(chunk_x: number, chunk_z: number, camera: number[]) {
|
||||
// values is the middle chunk's blocks with their states, for models that change with them
|
||||
function make_chunk_mesh(chunk_x: number, chunk_z: number, values: Uint32Array, camera: number[]) {
|
||||
const layers = [SOLID, CUTOUT, TRANSLUCENT].map(() => ({ vertices: new Float32Array(4096), floats: 0 }));
|
||||
// for sorting the translucent quads
|
||||
let centers = new Float32Array(256);
|
||||
let faces = new Uint8Array(256);
|
||||
|
||||
// where the neighbor on each face is, same order as FACES
|
||||
// where the neighbor on each face is, same order as FACE_NORMALS
|
||||
const face_offsets = FACE_NORMALS.map(([nx, ny, nz]) => nx + ny * REGION_LAYER + nz * REGION_SIZE);
|
||||
|
||||
for (let y = 0; y < CHUNK_HEIGHT; y++) {
|
||||
@@ -325,73 +429,33 @@ function make_chunk_mesh(chunk_x: number, chunk_z: number, camera: number[]) {
|
||||
const layer = layers[layer_id];
|
||||
const alpha = layer_id === TRANSLUCENT ? block_info.alpha ?? 1 : 1;
|
||||
|
||||
const texture_ids = {
|
||||
top: "engine:missing",
|
||||
bottom: "engine:missing",
|
||||
front: "engine:missing",
|
||||
back: "engine:missing",
|
||||
left: "engine:missing",
|
||||
right: "engine:missing",
|
||||
};
|
||||
|
||||
const textures = block_info.textures;
|
||||
if (!textures) throw new Error(`no textures for ${block_nid}`);
|
||||
|
||||
if (typeof textures === "string") {
|
||||
texture_ids.top = textures;
|
||||
texture_ids.bottom = textures;
|
||||
texture_ids.front = textures;
|
||||
texture_ids.back = textures;
|
||||
texture_ids.left = textures;
|
||||
texture_ids.right = textures;
|
||||
} else if ("top" in textures && "bottom" in textures && "side" in textures) {
|
||||
texture_ids.top = textures.top;
|
||||
texture_ids.bottom = textures.bottom;
|
||||
texture_ids.front = textures.side;
|
||||
texture_ids.back = textures.side;
|
||||
texture_ids.left = textures.side;
|
||||
texture_ids.right = textures.side;
|
||||
} else if ("front" in textures && "side" in textures) {
|
||||
texture_ids.top = textures.side;
|
||||
texture_ids.bottom = textures.side;
|
||||
texture_ids.front = textures.front;
|
||||
texture_ids.back = textures.side;
|
||||
texture_ids.left = textures.side;
|
||||
texture_ids.right = textures.side;
|
||||
}
|
||||
|
||||
const wx = chunk_x * CHUNK_SIZE + x;
|
||||
const wz = chunk_z * CHUNK_SIZE + z;
|
||||
|
||||
for (let face = 0; face < 6; face++) {
|
||||
const front = index + face_offsets[face];
|
||||
const front_y = y + FACE_NORMALS[face][1];
|
||||
if (!show_face(block_nid, region_block(region, front, front_y))) {
|
||||
continue;
|
||||
for (const quad of block_quads(values[y * CHUNK_AREA + z * CHUNK_SIZE + x])) {
|
||||
if (quad.cull >= 0) {
|
||||
const neighbor = region_block(
|
||||
region,
|
||||
index + face_offsets[quad.cull],
|
||||
y + FACE_NORMALS[quad.cull][1],
|
||||
);
|
||||
if (!show_face(block_nid, neighbor)) continue;
|
||||
}
|
||||
|
||||
light_face_corners(face, front, front_y);
|
||||
light_quad(quad, index, y, face_offsets);
|
||||
layer.vertices = ensure_capacity(layer.vertices, layer.floats + FLOATS_PER_QUAD);
|
||||
layer.floats = push_quad(
|
||||
layer.vertices,
|
||||
layer.floats,
|
||||
face,
|
||||
wx,
|
||||
y,
|
||||
wz,
|
||||
textures_info[texture_ids[FACES[face]]],
|
||||
alpha,
|
||||
);
|
||||
layer.floats = push_quad(layer.vertices, layer.floats, quad, wx, y, wz, alpha);
|
||||
|
||||
if (layer_id === TRANSLUCENT) {
|
||||
const quad = layer.floats / FLOATS_PER_QUAD - 1;
|
||||
centers = ensure_capacity(centers, (quad + 1) * 3);
|
||||
faces = ensure_capacity(faces, quad + 1);
|
||||
const [nx, ny, nz] = FACE_NORMALS[face];
|
||||
centers[quad * 3] = wx + 0.5 + nx * 0.5;
|
||||
centers[quad * 3 + 1] = y + 0.5 + ny * 0.5;
|
||||
centers[quad * 3 + 2] = wz + 0.5 + nz * 0.5;
|
||||
faces[quad] = face;
|
||||
// sorting treats every quad as facing along an axis, rotated ones too
|
||||
const q = layer.floats / FLOATS_PER_QUAD - 1;
|
||||
centers = ensure_capacity(centers, (q + 1) * 3);
|
||||
faces = ensure_capacity(faces, q + 1);
|
||||
const p = quad.positions;
|
||||
centers[q * 3] = wx + (p[0] + p[6]) / 2;
|
||||
centers[q * 3 + 1] = y + (p[1] + p[7]) / 2;
|
||||
centers[q * 3 + 2] = wz + (p[2] + p[8]) / 2;
|
||||
faces[q] = quad.face;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user