Files
bworld/client/renderer/core.ts
T
2026-03-11 16:55:14 -03:00

529 lines
11 KiB
TypeScript

import { Camera } from "../components/camera.ts";
import { mat4_mul, mat4_perspective, mat4_view } from "./math.ts";
const MAX_SPRITES = 10000;
const VERTS_PER_SPRITE = 6;
const FLOATS_PER_VERT = 9;
export let gl: WebGLRenderingContext;
export let canvas: HTMLCanvasElement;
let program: WebGLProgram;
let buffer: WebGLBuffer;
// const default_camera = new Camera();
// let camera: Camera = default_camera;
//
// let camera_pos_loc: WebGLUniformLocation | null;
// let camera_zoom_loc: WebGLUniformLocation | null;
// let camera_rot_loc: WebGLUniformLocation | null;
// let camera_offset_loc: WebGLUniformLocation | null;
export const projection = new Float32Array(16);
export const view = new Float32Array(16);
export const mvp_matrix = new Float32Array(16);
const vertex_data = new Float32Array(MAX_SPRITES * VERTS_PER_SPRITE * FLOATS_PER_VERT);
let vert_index = 0;
let pos_loc: GLint;
let uv_loc: GLint;
let resolution_loc: WebGLUniformLocation | null;
let texture_loc: WebGLUniformLocation | null;
let mvp_loc: WebGLUniformLocation | null;
let col_diffuse_loc: WebGLUniformLocation | null;
let current_texture: WebGLTexture | null = null;
export let white_tex: WebGLTexture | null = null;
export const camera = {
x: 3,
y: 3,
z: 3,
pitch: 0,
yaw: 0,
roll: 0,
fov: Math.PI / 3,
near: 0.1,
far: 1000,
};
let mode3d = false;
let active_camera = camera;
const vertex_src = `#version 300 es
precision mediump float;
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec4 vertexColor;
out vec2 fragTexCoord;
out vec4 fragColor;
uniform mat4 mvp;
void main() {
fragTexCoord = vertexTexCoord;
fragColor = vertexColor;
gl_Position = mvp*vec4(vertexPosition, 1.0);
}
`;
const fragment_src = `#version 300 es
precision mediump float;
in vec2 fragTexCoord;
in vec4 fragColor;
out vec4 finalColor;
uniform sampler2D texture0;
uniform vec4 colDiffuse;
void main() {
vec4 texelColor = texture(texture0, fragTexCoord);
finalColor = texelColor*colDiffuse*fragColor;
}
`;
export function init_window(canvas_element: HTMLCanvasElement) {
canvas = canvas_element;
canvas.width = 1800;
canvas.height = 900;
const ctx = canvas.getContext("webgl2");
if (!ctx) {
throw new Error("WebGL2 not supported");
}
gl = ctx;
program = create_program(vertex_src, fragment_src);
buffer = gl.createBuffer()!;
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
const stride = FLOATS_PER_VERT * 4;
pos_loc = gl.getAttribLocation(program, "vertexPosition");
gl.enableVertexAttribArray(pos_loc);
gl.vertexAttribPointer(pos_loc, 3, gl.FLOAT, false, stride, 0);
uv_loc = gl.getAttribLocation(program, "vertexTexCoord");
gl.enableVertexAttribArray(uv_loc);
gl.vertexAttribPointer(uv_loc, 2, gl.FLOAT, false, stride, 12);
const color_loc = gl.getAttribLocation(program, "vertexColor");
gl.enableVertexAttribArray(color_loc);
gl.vertexAttribPointer(color_loc, 4, gl.FLOAT, false, stride, 20);
mvp_loc = gl.getUniformLocation(program, "mvp");
texture_loc = gl.getUniformLocation(program, "texture0");
col_diffuse_loc = gl.getUniformLocation(program, "colDiffuse");
gl.useProgram(program);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.disable(gl.CULL_FACE);
create_white_texture();
update_mvp(canvas.width, canvas.height);
}
export function begin_drawing() {
update_2d_mvp();
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
vert_index = 0;
}
export function end_drawing() {
flush_batch();
}
export const ortho_matrix = new Float32Array(16);
export function update_2d_mvp() {
const w = canvas.width;
const h = canvas.height;
const l = 0, r = w, t = 0, b = h, n = -1, f = 1;
ortho_matrix[0] = 2 / (r - l);
ortho_matrix[1] = 0;
ortho_matrix[2] = 0;
ortho_matrix[3] = 0;
ortho_matrix[4] = 0;
ortho_matrix[5] = 2 / (t - b);
ortho_matrix[6] = 0;
ortho_matrix[7] = 0;
ortho_matrix[8] = 0;
ortho_matrix[9] = 0;
ortho_matrix[10] = -2 / (f - n);
ortho_matrix[11] = 0;
ortho_matrix[12] = -(r + l) / (r - l);
ortho_matrix[13] = -(t + b) / (t - b);
ortho_matrix[14] = -(f + n) / (f - n);
ortho_matrix[15] = 1;
}
export function begin_mode_2d(new_camera: Camera) {
mode3d = false;
gl.disable(gl.DEPTH_TEST);
vert_index = 0;
update_2d_mvp();
gl.uniformMatrix4fv(mvp_loc, false, ortho_matrix);
}
export function end_mode_2d() {
flush_batch();
// camera = default_camera;
}
export function begin_clip(x: number, y: number, width: number, height: number) {
gl.enable(gl.SCISSOR_TEST);
const flipped_y = canvas.height - (y + height);
gl.scissor(x, flipped_y, width, height);
}
export function end_clip() {
gl.disable(gl.SCISSOR_TEST);
}
export function clear_background(r: number, g: number, b: number, a = 1) {
gl.clearColor(r, g, b, a);
gl.clear(gl.COLOR_BUFFER_BIT);
}
export function flush_batch() {
if (vert_index === 0 || !current_texture) {
return;
}
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertex_data.subarray(0, vert_index), gl.STREAM_DRAW);
if (mode3d) {
gl.uniformMatrix4fv(mvp_loc, false, mvp_matrix);
} else {
gl.uniformMatrix4fv(mvp_loc, false, ortho_matrix);
}
gl.uniform4f(col_diffuse_loc, 1, 1, 1, 1);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, current_texture);
gl.uniform1i(texture_loc, 0);
gl.drawArrays(gl.TRIANGLES, 0, vert_index / FLOATS_PER_VERT);
vert_index = 0;
}
export function push_quad(
dx: number,
dy: number,
dw: number,
dh: number,
u0: number,
v0: number,
u1: number,
v1: number,
r = 1,
g = 1,
b = 1,
a = 1,
) {
const x2 = dx + dw;
const y2 = dy + dh;
let i = vert_index;
// triangle 1
vertex_data[i++] = dx;
vertex_data[i++] = dy;
vertex_data[i++] = 0;
vertex_data[i++] = u0;
vertex_data[i++] = v0;
vertex_data[i++] = r;
vertex_data[i++] = g;
vertex_data[i++] = b;
vertex_data[i++] = a;
vertex_data[i++] = x2;
vertex_data[i++] = dy;
vertex_data[i++] = 0;
vertex_data[i++] = u1;
vertex_data[i++] = v0;
vertex_data[i++] = r;
vertex_data[i++] = g;
vertex_data[i++] = b;
vertex_data[i++] = a;
vertex_data[i++] = dx;
vertex_data[i++] = y2;
vertex_data[i++] = 0;
vertex_data[i++] = u0;
vertex_data[i++] = v1;
vertex_data[i++] = r;
vertex_data[i++] = g;
vertex_data[i++] = b;
vertex_data[i++] = a;
// triangle 2
vertex_data[i++] = x2;
vertex_data[i++] = dy;
vertex_data[i++] = 0;
vertex_data[i++] = u1;
vertex_data[i++] = v0;
vertex_data[i++] = r;
vertex_data[i++] = g;
vertex_data[i++] = b;
vertex_data[i++] = a;
vertex_data[i++] = x2;
vertex_data[i++] = y2;
vertex_data[i++] = 0;
vertex_data[i++] = u1;
vertex_data[i++] = v1;
vertex_data[i++] = r;
vertex_data[i++] = g;
vertex_data[i++] = b;
vertex_data[i++] = a;
vertex_data[i++] = dx;
vertex_data[i++] = y2;
vertex_data[i++] = 0;
vertex_data[i++] = u0;
vertex_data[i++] = v1;
vertex_data[i++] = r;
vertex_data[i++] = g;
vertex_data[i++] = b;
vertex_data[i++] = a;
vert_index = i;
}
export function resize_canvas() {
const width = self.innerWidth;
const height = self.innerHeight;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
canvas.style.width = canvas.width + "px";
canvas.style.height = canvas.height + "px";
gl.viewport(0, 0, width, height);
gl.useProgram(program);
// gl.uniform2f(resolution_loc, width, height);
update_mvp(canvas.width, canvas.height);
}
}
export function get_current_texture(): WebGLTexture | null {
return current_texture;
}
export function set_current_texture(texture: WebGLTexture) {
current_texture = texture;
}
function compile_shader(type: number, src: string) {
const shader = gl.createShader(type)!;
gl.shaderSource(shader, src);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
throw new Error("Shader compile failed");
}
return shader;
}
function create_program(vs_src: string, fs_src: string) {
const vs = compile_shader(gl.VERTEX_SHADER, vs_src);
const fs = compile_shader(gl.FRAGMENT_SHADER, fs_src);
const program = gl.createProgram()!;
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
throw new Error("Program link failed");
}
return program;
}
function create_white_texture() {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
const white_pixel = new Uint8Array([255, 255, 255, 255]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, white_pixel);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
white_tex = tex;
}
function update_mvp(width: number, height: number) {
const l = 0;
const r = width;
const b = height;
const t = 0;
const n = -1;
const f = 1;
mvp_matrix[0] = 2 / (r - l);
mvp_matrix[1] = 0;
mvp_matrix[2] = 0;
mvp_matrix[3] = 0;
mvp_matrix[4] = 0;
mvp_matrix[5] = 2 / (t - b);
mvp_matrix[6] = 0;
mvp_matrix[7] = 0;
mvp_matrix[8] = 0;
mvp_matrix[9] = 0;
mvp_matrix[10] = -2 / (f - n);
mvp_matrix[11] = 0;
mvp_matrix[12] = -(r + l) / (r - l);
mvp_matrix[13] = -(t + b) / (t - b);
mvp_matrix[14] = -(f + n) / (f - n);
mvp_matrix[15] = 1;
}
export function push_cube(
x: number,
y: number,
z: number,
w: number,
h: number,
d: number,
r = 1,
g = 1,
b = 1,
a = 1,
) {
set_current_texture(white_tex!);
const x2 = x + w;
const y2 = y + h;
const z2 = z + d;
const u0 = 0, v0 = 0;
const u1 = 1, v1 = 1;
let i = vert_index;
function v(px: number, py: number, pz: number, u: number, vv: number) {
vertex_data[i++] = px;
vertex_data[i++] = py;
vertex_data[i++] = pz;
vertex_data[i++] = u;
vertex_data[i++] = vv;
vertex_data[i++] = r;
vertex_data[i++] = g;
vertex_data[i++] = b;
vertex_data[i++] = a;
}
v(x, y, z2, u0, v0);
v(x2, y, z2, u1, v0);
v(x, y2, z2, u0, v1);
v(x2, y, z2, u1, v0);
v(x2, y2, z2, u1, v1);
v(x, y2, z2, u0, v1);
v(x2, y, z, u0, v0);
v(x, y, z, u1, v0);
v(x2, y2, z, u0, v1);
v(x, y, z, u1, v0);
v(x, y2, z, u1, v1);
v(x2, y2, z, u0, v1);
v(x, y, z, u0, v0);
v(x, y, z2, u1, v0);
v(x, y2, z, u0, v1);
v(x, y, z2, u1, v0);
v(x, y2, z2, u1, v1);
v(x, y2, z, u0, v1);
v(x2, y, z2, u0, v0);
v(x2, y, z, u1, v0);
v(x2, y2, z2, u0, v1);
v(x2, y, z, u1, v0);
v(x2, y2, z, u1, v1);
v(x2, y2, z2, u0, v1);
v(x, y2, z2, u0, v0);
v(x2, y2, z2, u1, v0);
v(x, y2, z, u0, v1);
v(x2, y2, z2, u1, v0);
v(x2, y2, z, u1, v1);
v(x, y2, z, u0, v1);
v(x, y, z, u0, v0);
v(x2, y, z, u1, v0);
v(x, y, z2, u0, v1);
v(x2, y, z, u1, v0);
v(x2, y, z2, u1, v1);
v(x, y, z2, u0, v1);
vert_index = i;
}
export function begin_mode_3d() {
flush_batch();
mode3d = true;
active_camera = camera;
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
vert_index = 0;
update_camera();
}
export function end_mode_3d() {
if (!mode3d) {
return;
}
flush_batch();
mode3d = false;
gl.disable(gl.DEPTH_TEST);
}
export function update_camera() {
mat4_perspective(
projection,
camera.fov,
canvas.width / canvas.height,
camera.near,
camera.far,
);
mat4_view(view);
mat4_mul(mvp_matrix, projection, view);
}