No more ecs
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import type { Client } from "$/client/client.ts";
|
||||
import { begin_mode_3d, end_mode_3d } from "$/client/renderer/mod.ts";
|
||||
import { Hud } from "$/client/gui/hud.ts";
|
||||
import { DebugOverlay } from "$/client/gui/debug_overlay.ts";
|
||||
import { LevelRenderer } from "./level_renderer.ts";
|
||||
|
||||
// draws a frame, like minecraft's GameRenderer: the level from the camera, then the hud and screens on top
|
||||
export class GameRenderer {
|
||||
level_renderer = new LevelRenderer();
|
||||
hud = new Hud();
|
||||
debug_overlay = new DebugOverlay();
|
||||
|
||||
render(client: Client) {
|
||||
const camera = client.camera;
|
||||
camera.setup(client.player);
|
||||
|
||||
begin_mode_3d(camera);
|
||||
this.level_renderer.render_opaque(client.level, camera);
|
||||
this.level_renderer.render_destroy_progress(client.game_mode);
|
||||
this.level_renderer.render_entities(client.level, client.player);
|
||||
this.level_renderer.render_translucent(client.level, camera);
|
||||
end_mode_3d();
|
||||
|
||||
this.hud.render(client);
|
||||
client.screen?.on_render();
|
||||
if (client.debugging) {
|
||||
this.debug_overlay.render(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { TEXTURE_SIZE } from "$/common/constants.ts";
|
||||
import { CHUNK_SIZE, ClientLevel } from "$/client/level/client_level.ts";
|
||||
import { Camera } from "$/client/camera.ts";
|
||||
import { AssetManager } from "$/client/assets.ts";
|
||||
import { get_sprite_region } from "$/client/sprites.ts";
|
||||
import type { MultiPlayerGameMode } from "$/client/game_mode.ts";
|
||||
import type { Entity } from "$/client/entity/entity.ts";
|
||||
import { RemotePlayer } from "$/client/entity/remote_player.ts";
|
||||
import {
|
||||
draw_terrain,
|
||||
flush_batch,
|
||||
push_back_face,
|
||||
push_bottom_face,
|
||||
push_box,
|
||||
push_front_face,
|
||||
push_left_face,
|
||||
push_right_face,
|
||||
push_top_face,
|
||||
set_current_texture,
|
||||
Texture,
|
||||
white_tex,
|
||||
} from "$/client/renderer/mod.ts";
|
||||
|
||||
const BREAKING_FACES = [
|
||||
push_back_face,
|
||||
push_bottom_face,
|
||||
push_front_face,
|
||||
push_left_face,
|
||||
push_right_face,
|
||||
push_top_face,
|
||||
];
|
||||
|
||||
// draws the level, like minecraft's LevelRenderer: terrain in layers, block breaking and entities
|
||||
export class LevelRenderer {
|
||||
// solid and cutout terrain, drawn before entities
|
||||
render_opaque(level: ClientLevel, camera: Camera) {
|
||||
level.request_meshes(camera);
|
||||
level.update_translucent_sorting(camera);
|
||||
|
||||
set_current_texture(level.image.tex);
|
||||
|
||||
for (const chunk of level.chunks.values()) {
|
||||
const mesh = chunk.meshes.solid;
|
||||
if (mesh) {
|
||||
draw_terrain("solid", mesh.vertex_buffer, mesh.quad_count);
|
||||
}
|
||||
}
|
||||
|
||||
for (const chunk of level.chunks.values()) {
|
||||
const mesh = chunk.meshes.cutout;
|
||||
if (mesh) {
|
||||
draw_terrain("cutout", mesh.vertex_buffer, mesh.quad_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// translucent terrain, drawn after entities so they show through water and glass.
|
||||
// chunks go back to front, and each chunk's quads are already sorted back to front
|
||||
render_translucent(level: ClientLevel, camera: Camera) {
|
||||
const distance_sq = (x: number, z: number) => {
|
||||
const dx = (x + 0.5) * CHUNK_SIZE - camera.x;
|
||||
const dz = (z + 0.5) * CHUNK_SIZE - camera.z;
|
||||
return dx * dx + dz * dz;
|
||||
};
|
||||
const chunks = [...level.chunks.values()]
|
||||
.filter((chunk) => chunk.meshes.translucent)
|
||||
.map((chunk) => ({ mesh: chunk.meshes.translucent!, distance: distance_sq(chunk.x, chunk.z) }))
|
||||
.sort((a, b) => b.distance - a.distance);
|
||||
|
||||
set_current_texture(level.image.tex);
|
||||
|
||||
for (const { mesh } of chunks) {
|
||||
draw_terrain("translucent", mesh.vertex_buffer, mesh.quad_count, mesh.index_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// every entity but the one the camera is in
|
||||
render_entities(level: ClientLevel, camera_entity: Entity) {
|
||||
flush_batch();
|
||||
set_current_texture(white_tex!);
|
||||
|
||||
for (const entity of level.entities.values()) {
|
||||
if (entity !== camera_entity && entity instanceof RemotePlayer) {
|
||||
render_player(entity);
|
||||
}
|
||||
}
|
||||
|
||||
flush_batch();
|
||||
}
|
||||
|
||||
// the cracks on the block being broken
|
||||
render_destroy_progress(game_mode: MultiPlayerGameMode) {
|
||||
const block = game_mode.destroy_pos;
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
|
||||
const progress = Math.max(0, Math.min(1, game_mode.destroy_progress / game_mode.destroy_time));
|
||||
const stage = Math.round(progress * 8);
|
||||
if (Number.isNaN(stage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tex = AssetManager.instance.get<Texture>("bworld:textures");
|
||||
const region = get_sprite_region(`engine:break_${stage}`);
|
||||
for (const push_face of BREAKING_FACES) {
|
||||
push_face(
|
||||
tex,
|
||||
block.x,
|
||||
block.y,
|
||||
block.z,
|
||||
region.x * TEXTURE_SIZE,
|
||||
region.y * TEXTURE_SIZE,
|
||||
TEXTURE_SIZE,
|
||||
TEXTURE_SIZE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a box body and head in the player's color
|
||||
function render_player(player: RemotePlayer) {
|
||||
const [r, g, b] = player.color;
|
||||
const { x, y, z } = player;
|
||||
|
||||
// body
|
||||
push_box(x - 0.3, y, z - 0.15, 0.6, 1.3, 0.3, r, g, b);
|
||||
// head
|
||||
push_box(x - 0.25, y + 1.3, z - 0.25, 0.5, 0.5, 0.5, 0.95, 0.8, 0.65);
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
import { SLOT_SIZE, TEXTURE_SIZE } from "$/common/constants.ts";
|
||||
import { get_sprite_region } from "$/client/sprites.ts";
|
||||
import { BlockRegistry, EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
|
||||
import { AssetManager } from "$/client/assets.ts";
|
||||
import { ItemStack } from "$/common/inventory.ts";
|
||||
import { draw_text, draw_texture_region, draw_texture_region_skewed, Texture } from "$/client/renderer/mod.ts";
|
||||
|
||||
export function draw_nine_slice(
|
||||
image: Texture,
|
||||
source_x: number,
|
||||
source_y: number,
|
||||
source_width: number,
|
||||
source_height: number,
|
||||
left: number,
|
||||
right: number,
|
||||
top: number,
|
||||
bottom: number,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
color = [1, 1, 1, 1],
|
||||
) {
|
||||
const center_width = source_width - left - right;
|
||||
const center_height = source_height - top - bottom;
|
||||
|
||||
const dest_center_width = width - left - right;
|
||||
const dest_center_height = height - top - bottom;
|
||||
|
||||
// top left
|
||||
draw_texture_region(
|
||||
image,
|
||||
source_x,
|
||||
source_y,
|
||||
left,
|
||||
top,
|
||||
x,
|
||||
y,
|
||||
left,
|
||||
top,
|
||||
false,
|
||||
false,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
|
||||
// top right
|
||||
draw_texture_region(
|
||||
image,
|
||||
source_x + source_width - right,
|
||||
source_y,
|
||||
right,
|
||||
top,
|
||||
x + width - right,
|
||||
y,
|
||||
right,
|
||||
top,
|
||||
false,
|
||||
false,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
|
||||
// bottom left
|
||||
draw_texture_region(
|
||||
image,
|
||||
source_x,
|
||||
source_y + source_height - bottom,
|
||||
left,
|
||||
bottom,
|
||||
x,
|
||||
y + height - bottom,
|
||||
left,
|
||||
bottom,
|
||||
false,
|
||||
false,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
|
||||
// bottom right
|
||||
draw_texture_region(
|
||||
image,
|
||||
source_x + source_width - right,
|
||||
source_y + source_height - bottom,
|
||||
right,
|
||||
bottom,
|
||||
x + width - right,
|
||||
y + height - bottom,
|
||||
right,
|
||||
bottom,
|
||||
false,
|
||||
false,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
|
||||
// top
|
||||
draw_texture_region(
|
||||
image,
|
||||
source_x + left,
|
||||
source_y,
|
||||
center_width,
|
||||
top,
|
||||
x + left,
|
||||
y,
|
||||
dest_center_width,
|
||||
top,
|
||||
false,
|
||||
false,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
|
||||
// bottom
|
||||
draw_texture_region(
|
||||
image,
|
||||
source_x + left,
|
||||
source_y + source_height - bottom,
|
||||
center_width,
|
||||
bottom,
|
||||
x + left,
|
||||
y + height - bottom,
|
||||
dest_center_width,
|
||||
bottom,
|
||||
false,
|
||||
false,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
|
||||
// left
|
||||
draw_texture_region(
|
||||
image,
|
||||
source_x,
|
||||
source_y + top,
|
||||
left,
|
||||
center_height,
|
||||
x,
|
||||
y + top,
|
||||
left,
|
||||
dest_center_height,
|
||||
false,
|
||||
false,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
|
||||
// right
|
||||
draw_texture_region(
|
||||
image,
|
||||
source_x + source_width - right,
|
||||
source_y + top,
|
||||
right,
|
||||
center_height,
|
||||
x + width - right,
|
||||
y + top,
|
||||
right,
|
||||
dest_center_height,
|
||||
false,
|
||||
false,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
|
||||
// center !
|
||||
draw_texture_region(
|
||||
image,
|
||||
source_x + left,
|
||||
source_y + top,
|
||||
center_width,
|
||||
center_height,
|
||||
x + left,
|
||||
y + top,
|
||||
dest_center_width,
|
||||
dest_center_height,
|
||||
false,
|
||||
false,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
);
|
||||
}
|
||||
|
||||
export function draw_item(item: ItemStack, x: number, y: number) {
|
||||
const item_info = EverythingRegistry.get<ItemRegistry>("items", item.type_id);
|
||||
|
||||
if (!item_info) {
|
||||
throw Error(`Didn't find item registry for '${item.type_id}'`);
|
||||
}
|
||||
|
||||
if (item_info?.block_id) {
|
||||
draw_item_block(item, item_info, x, y);
|
||||
} else {
|
||||
draw_item_item(item, item_info, x, y);
|
||||
}
|
||||
|
||||
if (item.max_amount !== 1) {
|
||||
draw_text(
|
||||
String(item.amount),
|
||||
x + SLOT_SIZE + 2 - 11 * 2,
|
||||
y + SLOT_SIZE + 2 - 11 * 2,
|
||||
1,
|
||||
[63 / 255, 63 / 255, 63 / 255],
|
||||
);
|
||||
draw_text(
|
||||
String(item.amount),
|
||||
x + SLOT_SIZE - 11 * 2,
|
||||
y + SLOT_SIZE - 11 * 2,
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ey its not a bad name
|
||||
function draw_item_item(item: ItemStack, item_info: ItemRegistry, x: number, y: number) {
|
||||
let texture_id = "engine:missing";
|
||||
if (typeof item_info?.texture_id === "string") {
|
||||
texture_id = item_info.texture_id;
|
||||
} else if (typeof item_info?.texture_id === "function") {
|
||||
texture_id = item_info.texture_id(item);
|
||||
}
|
||||
const sprite_region = get_sprite_region(texture_id);
|
||||
draw_texture_region(
|
||||
AssetManager.instance.get("bworld:textures"),
|
||||
sprite_region.x * 16,
|
||||
sprite_region.y * 16,
|
||||
16,
|
||||
16,
|
||||
x + 3,
|
||||
y + 3,
|
||||
SLOT_SIZE - 6,
|
||||
SLOT_SIZE - 6,
|
||||
);
|
||||
}
|
||||
|
||||
function draw_item_block(_item: ItemStack, item_info: ItemRegistry, x: number, y: number) {
|
||||
const block_info = EverythingRegistry.get<BlockRegistry>("blocks", item_info.block_id!);
|
||||
|
||||
if (!block_info) throw new Error(`no textures for ${item_info.block_id}`);
|
||||
|
||||
let front_texture = "engine:missing";
|
||||
let top_texture = "engine:missing";
|
||||
let left_texture = "engine:missing";
|
||||
|
||||
const textures = block_info.textures;
|
||||
|
||||
if (typeof textures === "string") {
|
||||
front_texture = textures;
|
||||
top_texture = textures;
|
||||
left_texture = textures;
|
||||
} else if ("top" in textures && "bottom" in textures && "side" in textures) {
|
||||
top_texture = textures.top;
|
||||
front_texture = textures.side;
|
||||
left_texture = textures.side;
|
||||
} else if ("front" in textures && "side" in textures) {
|
||||
top_texture = textures.side;
|
||||
front_texture = textures.front;
|
||||
left_texture = textures.side;
|
||||
}
|
||||
|
||||
const atlas = AssetManager.instance.get<Texture>("bworld:textures");
|
||||
|
||||
const top = get_sprite_region(top_texture);
|
||||
const front = get_sprite_region(front_texture);
|
||||
const left = get_sprite_region(left_texture);
|
||||
|
||||
const padding = 4;
|
||||
const size = (SLOT_SIZE - padding * 2) / 2;
|
||||
|
||||
const cx = x + SLOT_SIZE / 2;
|
||||
const cy = y + SLOT_SIZE / 2 - 10;
|
||||
|
||||
const half_w = size;
|
||||
const half_h = size / 2;
|
||||
const height = size;
|
||||
|
||||
draw_texture_region_skewed(
|
||||
atlas,
|
||||
top.x * TEXTURE_SIZE,
|
||||
top.y * TEXTURE_SIZE,
|
||||
TEXTURE_SIZE,
|
||||
TEXTURE_SIZE,
|
||||
[
|
||||
{ x: cx, y: cy - half_h },
|
||||
{ x: cx + half_w, y: cy },
|
||||
{ x: cx, y: cy + half_h },
|
||||
{ x: cx - half_w, y: cy },
|
||||
],
|
||||
);
|
||||
draw_texture_region_skewed(
|
||||
atlas,
|
||||
left.x * TEXTURE_SIZE,
|
||||
left.y * TEXTURE_SIZE,
|
||||
TEXTURE_SIZE,
|
||||
TEXTURE_SIZE,
|
||||
[
|
||||
{ x: cx - half_w, y: cy },
|
||||
{ x: cx, y: cy + half_h },
|
||||
{ x: cx, y: cy + half_h + height },
|
||||
{ x: cx - half_w, y: cy + height },
|
||||
],
|
||||
);
|
||||
draw_texture_region_skewed(
|
||||
atlas,
|
||||
front.x * TEXTURE_SIZE,
|
||||
front.y * TEXTURE_SIZE,
|
||||
TEXTURE_SIZE,
|
||||
TEXTURE_SIZE,
|
||||
[
|
||||
{ x: cx + half_w, y: cy },
|
||||
{ x: cx, y: cy + half_h },
|
||||
{ x: cx, y: cy + half_h + height },
|
||||
{ x: cx + half_w, y: cy + height },
|
||||
],
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user