added a shitty ecs that I'm confident is way worse than just looping … (#4)
* added a shitty ecs that I'm confident is way worse than just looping objects lol * mader a rendering system based on ecs
This commit is contained in:
@@ -58,11 +58,11 @@ const connectWorker = (worker: Worker) => {
|
|||||||
|
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"pointermove",
|
"pointermove",
|
||||||
eventPassers[EventTypes.pointermove]
|
eventPassers[EventTypes.pointermove],
|
||||||
);
|
);
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"pointerdown",
|
"pointerdown",
|
||||||
eventPassers[EventTypes.pointerdown]
|
eventPassers[EventTypes.pointerdown],
|
||||||
);
|
);
|
||||||
document.addEventListener("pointerup", eventPassers[EventTypes.pointerup]);
|
document.addEventListener("pointerup", eventPassers[EventTypes.pointerup]);
|
||||||
//
|
//
|
||||||
@@ -72,15 +72,15 @@ const connectWorker = (worker: Worker) => {
|
|||||||
const disconnectWorker = () => {
|
const disconnectWorker = () => {
|
||||||
document.removeEventListener(
|
document.removeEventListener(
|
||||||
"pointermove",
|
"pointermove",
|
||||||
eventPassers[EventTypes.pointermove]
|
eventPassers[EventTypes.pointermove],
|
||||||
);
|
);
|
||||||
document.removeEventListener(
|
document.removeEventListener(
|
||||||
"pointerdown",
|
"pointerdown",
|
||||||
eventPassers[EventTypes.pointerdown]
|
eventPassers[EventTypes.pointerdown],
|
||||||
);
|
);
|
||||||
document.removeEventListener(
|
document.removeEventListener(
|
||||||
"pointerup",
|
"pointerup",
|
||||||
eventPassers[EventTypes.pointerup]
|
eventPassers[EventTypes.pointerup],
|
||||||
);
|
);
|
||||||
//
|
//
|
||||||
document.removeEventListener("keydown", eventPassers[EventTypes.keydown]);
|
document.removeEventListener("keydown", eventPassers[EventTypes.keydown]);
|
||||||
@@ -97,11 +97,6 @@ const attachCanvas = (worker: Worker, canvas: HTMLCanvasElement | string) => {
|
|||||||
throw new Error("Fatal: Canvas Not Found!");
|
throw new Error("Fatal: Canvas Not Found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// set canvas dimensions
|
|
||||||
// if this is not done, the canvas passed to the web worker
|
|
||||||
// will have the default size of 300x150
|
|
||||||
// this will cause the viewport to be set to the default size
|
|
||||||
// and eveything will be messed up
|
|
||||||
canvas.width = canvas.clientWidth;
|
canvas.width = canvas.clientWidth;
|
||||||
canvas.height = canvas.clientHeight;
|
canvas.height = canvas.clientHeight;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { Attribute, draw, ProgramData } from "../gl";
|
import { Engine } from "../renderer/engine";
|
||||||
import { cubeVertices } from "../gl/vertices";
|
|
||||||
import { MessageType } from "./mainThread";
|
import { MessageType } from "./mainThread";
|
||||||
|
|
||||||
|
const cubeVertices = [
|
||||||
|
1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1,
|
||||||
|
-1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1,
|
||||||
|
1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1,
|
||||||
|
-1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1,
|
||||||
|
1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1,
|
||||||
|
];
|
||||||
|
|
||||||
const handleConnection = (msg: MessageEvent<any>) => {
|
const handleConnection = (msg: MessageEvent<any>) => {
|
||||||
const { data } = msg;
|
const { data } = msg;
|
||||||
|
|
||||||
@@ -12,19 +19,19 @@ const handleConnection = (msg: MessageEvent<any>) => {
|
|||||||
case MessageType.attachCanvas:
|
case MessageType.attachCanvas:
|
||||||
const canvas = data[1];
|
const canvas = data[1];
|
||||||
|
|
||||||
const ctxWorker = canvas.getContext("webgl2");
|
const engine = new Engine(canvas);
|
||||||
var p: ProgramData<Attribute<Float32Array>> = {
|
const scene = engine.createScene();
|
||||||
vertexShaderSource: "/shaders/triangle/vertex.glsl",
|
scene.addMesh("box", cubeVertices);
|
||||||
fragmentShaderSource: "/shaders/triangle/frag.glsl",
|
scene.addCamera("cam", true);
|
||||||
attributes: [
|
|
||||||
{
|
const raf = () => {
|
||||||
name: "position",
|
requestAnimationFrame(() => {
|
||||||
data: new Float32Array(cubeVertices),
|
scene.runSystems();
|
||||||
},
|
raf();
|
||||||
],
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
requestAnimationFrame(() => draw(ctxWorker, p));
|
raf();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
type ComponentMap<CT> = {
|
||||||
|
uid: (number | null)[];
|
||||||
|
} & {
|
||||||
|
[K in keyof CT]?: Array<CT[K] | null>;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace ECS {
|
||||||
|
export type System<ComponentsType extends Record<string | number, any>> = (
|
||||||
|
arg: ComponentsType & { uid: (number | null)[] },
|
||||||
|
) => Partial<ComponentsType> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ECS<ComponentsType extends Record<string | number, any>> {
|
||||||
|
private components: ComponentMap<ComponentsType> = { uid: [] };
|
||||||
|
private componentDefaults: Record<
|
||||||
|
string,
|
||||||
|
null | ComponentsType[keyof ComponentsType]
|
||||||
|
> = { uid: null };
|
||||||
|
private systems: Record<string, ECS.System<ComponentsType>> = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System
|
||||||
|
*/
|
||||||
|
runSystems() {
|
||||||
|
const limit = this.components.uid.length;
|
||||||
|
for (let i = 0; i < limit; i++) {
|
||||||
|
const obj = {} as Record<string, unknown>;
|
||||||
|
|
||||||
|
for (const key in this.components) {
|
||||||
|
obj[key] = this.components[key]![i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const systems = Object.values(this.systems);
|
||||||
|
for (let j = 0; j < systems.length; j++) {
|
||||||
|
const res = systems[j](
|
||||||
|
obj as ComponentsType & { uid: (number | null)[] },
|
||||||
|
);
|
||||||
|
if (!res) continue;
|
||||||
|
|
||||||
|
for (const key in res) {
|
||||||
|
if (!this.components[key]) continue;
|
||||||
|
this.components[key][i] = res[key]!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addSystem(system: ECS.System<ComponentsType>) {
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
this.systems[id] = system;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSystem(id: string) {
|
||||||
|
delete this.systems[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
getById(uid: number) {
|
||||||
|
if (this.components.uid.length <= uid) return {};
|
||||||
|
|
||||||
|
const obj = {} as Record<string, unknown>;
|
||||||
|
|
||||||
|
for (const key in this.components) {
|
||||||
|
obj[key] = this.components[key]![uid];
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity
|
||||||
|
*/
|
||||||
|
addEntity(components: ComponentsType) {
|
||||||
|
for (const key in this.components) {
|
||||||
|
if (key === "uid") {
|
||||||
|
this.components[key].push(this.components[key].length);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.components[key]!.push(
|
||||||
|
components[key] ?? this.componentDefaults[key],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.components.uid.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component
|
||||||
|
*/
|
||||||
|
addComponent(
|
||||||
|
name: keyof ComponentsType,
|
||||||
|
fillValue = null as null | ComponentsType[keyof ComponentsType],
|
||||||
|
defaultValue = null as null | ComponentsType[keyof ComponentsType],
|
||||||
|
) {
|
||||||
|
if (this.components[name]) throw new Error("Component already exists");
|
||||||
|
|
||||||
|
this.components[name] = new Array(this.components.uid.length).fill(
|
||||||
|
fillValue,
|
||||||
|
) as ComponentMap<ComponentsType>[keyof ComponentsType];
|
||||||
|
|
||||||
|
this.componentDefaults[name as string] = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeComponent(name: keyof ComponentsType) {
|
||||||
|
delete this.components[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ECS };
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import { mat4 } from "gl-matrix";
|
|
||||||
import { cubeVertices } from "./vertices";
|
|
||||||
|
|
||||||
type AttributeDataTypes = Float32Array | Int32Array;
|
|
||||||
|
|
||||||
export interface Attribute<T> {
|
|
||||||
name: string;
|
|
||||||
data: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Dimensions {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProgramData<T extends Attribute<AttributeDataTypes>> {
|
|
||||||
vertexShaderSource: string;
|
|
||||||
fragmentShaderSource: string;
|
|
||||||
attributes: T[];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchShader(path: string) {
|
|
||||||
const response = await fetch(path);
|
|
||||||
return response.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createShader(
|
|
||||||
gl: WebGL2RenderingContext,
|
|
||||||
type: number,
|
|
||||||
sourceFile: string
|
|
||||||
) {
|
|
||||||
const source = await fetchShader(sourceFile);
|
|
||||||
|
|
||||||
var shader = gl.createShader(type);
|
|
||||||
if (!shader) return;
|
|
||||||
|
|
||||||
gl.shaderSource(shader, source);
|
|
||||||
gl.compileShader(shader);
|
|
||||||
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
|
||||||
if (!success) {
|
|
||||||
console.error(gl.getShaderInfoLog(shader)); // eslint-disable-line
|
|
||||||
gl.deleteShader(shader);
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setupProgram<T extends Attribute<AttributeDataTypes>>(
|
|
||||||
gl: WebGL2RenderingContext,
|
|
||||||
programData: ProgramData<T>
|
|
||||||
) {
|
|
||||||
var program = gl.createProgram();
|
|
||||||
if (!program) return;
|
|
||||||
|
|
||||||
var vertexShader = await createShader(
|
|
||||||
gl,
|
|
||||||
gl.VERTEX_SHADER,
|
|
||||||
programData.vertexShaderSource
|
|
||||||
);
|
|
||||||
if (!vertexShader) return;
|
|
||||||
|
|
||||||
var fragmentShader = await createShader(
|
|
||||||
gl,
|
|
||||||
gl.FRAGMENT_SHADER,
|
|
||||||
programData.fragmentShaderSource
|
|
||||||
);
|
|
||||||
if (!fragmentShader) return;
|
|
||||||
|
|
||||||
gl.attachShader(program, vertexShader);
|
|
||||||
gl.attachShader(program, fragmentShader);
|
|
||||||
gl.linkProgram(program);
|
|
||||||
|
|
||||||
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
|
|
||||||
if (!success) {
|
|
||||||
console.error(gl.getProgramInfoLog(program));
|
|
||||||
gl.deleteProgram(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
return program;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupAttribute<T extends Attribute<AttributeDataTypes>>(
|
|
||||||
gl: WebGL2RenderingContext,
|
|
||||||
program: WebGLProgram,
|
|
||||||
attribute: T
|
|
||||||
) {
|
|
||||||
const location = gl.getAttribLocation(program, attribute.name);
|
|
||||||
if (location === -1) return;
|
|
||||||
|
|
||||||
// setup a buffer for the attribute data
|
|
||||||
const buffer = gl.createBuffer();
|
|
||||||
if (!buffer) return;
|
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, attribute.data, gl.STATIC_DRAW);
|
|
||||||
|
|
||||||
const vao = gl.createVertexArray();
|
|
||||||
if (!vao) return;
|
|
||||||
|
|
||||||
gl.bindVertexArray(vao);
|
|
||||||
|
|
||||||
gl.enableVertexAttribArray(location);
|
|
||||||
gl.vertexAttribPointer(location, 3, gl.FLOAT, false, 5 * 4, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function draw<T extends Attribute<AttributeDataTypes>>(
|
|
||||||
gl: WebGL2RenderingContext,
|
|
||||||
programData: ProgramData<T>
|
|
||||||
) {
|
|
||||||
gl.enable(gl.DEPTH_TEST);
|
|
||||||
|
|
||||||
const program = await setupProgram(gl, programData);
|
|
||||||
if (!program) {
|
|
||||||
console.error("failed to setup program");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.useProgram(program);
|
|
||||||
|
|
||||||
for (const attribute of programData.attributes) {
|
|
||||||
setupAttribute(gl, program, attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
let view = mat4.create();
|
|
||||||
view = mat4.translate(view, view, [0, 0, -5]);
|
|
||||||
|
|
||||||
let model = mat4.create();
|
|
||||||
const angle = Date.now() * 0.001;
|
|
||||||
model = mat4.rotate(model, model, angle, [0.5, 1, 0]);
|
|
||||||
|
|
||||||
const projection = mat4.create();
|
|
||||||
mat4.perspective(
|
|
||||||
projection,
|
|
||||||
45,
|
|
||||||
gl.canvas.width / gl.canvas.height,
|
|
||||||
0.1,
|
|
||||||
100.0
|
|
||||||
);
|
|
||||||
|
|
||||||
const viewLoc = gl.getUniformLocation(program, "view");
|
|
||||||
const modelLoc = gl.getUniformLocation(program, "model");
|
|
||||||
const projectionLoc = gl.getUniformLocation(program, "projection");
|
|
||||||
|
|
||||||
gl.uniformMatrix4fv(viewLoc, false, view);
|
|
||||||
gl.uniformMatrix4fv(modelLoc, false, model);
|
|
||||||
gl.uniformMatrix4fv(projectionLoc, false, projection);
|
|
||||||
|
|
||||||
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
|
||||||
|
|
||||||
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 36);
|
|
||||||
|
|
||||||
requestAnimationFrame(() => draw(gl, programData));
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
export const cubeVertices = [
|
|
||||||
-0.5, -0.5, -0.5, 0.0, 0.0, 0.5, -0.5, -0.5, 1.0, 0.0, 0.5, 0.5, -0.5, 1.0,
|
|
||||||
1.0, 0.5, 0.5, -0.5, 1.0, 1.0, -0.5, 0.5, -0.5, 0.0, 1.0, -0.5, -0.5, -0.5,
|
|
||||||
0.0, 0.0,
|
|
||||||
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 0.0, 0.5, -0.5, 0.5, 1.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0,
|
|
||||||
0.5, 0.5, 0.5, 1.0, 1.0, -0.5, 0.5, 0.5, 0.0, 1.0, -0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
|
|
||||||
-0.5, 0.5, 0.5, 1.0, 0.0, -0.5, 0.5, -0.5, 1.0, 1.0, -0.5, -0.5, -0.5, 0.0,
|
|
||||||
1.0, -0.5, -0.5, -0.5, 0.0, 1.0, -0.5, -0.5, 0.5, 0.0, 0.0, -0.5, 0.5, 0.5,
|
|
||||||
1.0, 0.0,
|
|
||||||
|
|
||||||
0.5, 0.5, 0.5, 1.0, 0.0, 0.5, 0.5, -0.5, 1.0, 1.0, 0.5, -0.5, -0.5, 0.0, 1.0,
|
|
||||||
0.5, -0.5, -0.5, 0.0, 1.0, 0.5, -0.5, 0.5, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 0.0,
|
|
||||||
|
|
||||||
-0.5, -0.5, -0.5, 0.0, 1.0, 0.5, -0.5, -0.5, 1.0, 1.0, 0.5, -0.5, 0.5, 1.0,
|
|
||||||
0.0, 0.5, -0.5, 0.5, 1.0, 0.0, -0.5, -0.5, 0.5, 0.0, 0.0, -0.5, -0.5, -0.5,
|
|
||||||
0.0, 1.0,
|
|
||||||
|
|
||||||
-0.5, 0.5, -0.5, 0.0, 1.0, 0.5, 0.5, -0.5, 1.0, 1.0, 0.5, 0.5, 0.5, 1.0, 0.0,
|
|
||||||
0.5, 0.5, 0.5, 1.0, 0.0, -0.5, 0.5, 0.5, 0.0, 0.0, -0.5, 0.5, -0.5, 0.0, 1.0,
|
|
||||||
];
|
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite-plugin-glsl/ext" />
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Scene } from "./scene";
|
||||||
|
|
||||||
|
class Engine {
|
||||||
|
canvas: HTMLCanvasElement;
|
||||||
|
ctx: WebGL2RenderingContext;
|
||||||
|
scenes = [] as Scene[];
|
||||||
|
|
||||||
|
constructor(canvas: string | HTMLCanvasElement) {
|
||||||
|
/** get canvas by id */
|
||||||
|
if (typeof canvas === "string") {
|
||||||
|
canvas = document.getElementById(canvas)! as HTMLCanvasElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canvas) {
|
||||||
|
throw new Error("canvas not found");
|
||||||
|
}
|
||||||
|
this.canvas = canvas;
|
||||||
|
this.ctx = canvas.getContext("webgl2")!;
|
||||||
|
if (!this.ctx) {
|
||||||
|
throw new Error("can't get webgl2 context");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createScene() {
|
||||||
|
const scene = new Scene(this.canvas, this.ctx);
|
||||||
|
this.scenes.push(scene);
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Engine };
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { mat4 } from "gl-matrix";
|
||||||
|
|
||||||
|
export const compileShader = (
|
||||||
|
gl: WebGL2RenderingContext,
|
||||||
|
type: number,
|
||||||
|
source: string,
|
||||||
|
) => {
|
||||||
|
const shader = gl.createShader(type);
|
||||||
|
if (!shader) {
|
||||||
|
throw new Error(`couldn't make the shader ${source}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.shaderSource(shader, source);
|
||||||
|
gl.compileShader(shader);
|
||||||
|
|
||||||
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||||
|
throw new Error(
|
||||||
|
`Error in ${source}: ${gl.getShaderInfoLog(shader) ?? "no error returned"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createProgram = (
|
||||||
|
gl: WebGL2RenderingContext,
|
||||||
|
vs: string,
|
||||||
|
fs: string,
|
||||||
|
): WebGLProgram => {
|
||||||
|
const program = gl.createProgram()!;
|
||||||
|
|
||||||
|
const vShader = compileShader(gl, gl.VERTEX_SHADER, vs);
|
||||||
|
const fShader = compileShader(gl, gl.FRAGMENT_SHADER, fs);
|
||||||
|
|
||||||
|
gl.attachShader(program, vShader);
|
||||||
|
gl.attachShader(program, fShader);
|
||||||
|
|
||||||
|
gl.linkProgram(program);
|
||||||
|
|
||||||
|
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||||
|
throw new Error(gl.getProgramInfoLog(program) || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return program;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AttributeCollection = {
|
||||||
|
name: string;
|
||||||
|
data: Float32Array;
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setupVAO = (
|
||||||
|
gl: WebGL2RenderingContext,
|
||||||
|
program: WebGLProgram,
|
||||||
|
attributeCollections: AttributeCollection[],
|
||||||
|
) => {
|
||||||
|
const vao = gl.createVertexArray()!;
|
||||||
|
gl.bindVertexArray(vao);
|
||||||
|
|
||||||
|
for (const attr of attributeCollections) {
|
||||||
|
const loc = gl.getAttribLocation(program, attr.name);
|
||||||
|
if (loc >= 0) {
|
||||||
|
const buf = gl.createBuffer()!;
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, attr.data, gl.STATIC_DRAW);
|
||||||
|
gl.enableVertexAttribArray(loc);
|
||||||
|
gl.vertexAttribPointer(loc, attr.size, gl.FLOAT, false, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.bindVertexArray(null);
|
||||||
|
return vao;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateVAO = (
|
||||||
|
gl: WebGL2RenderingContext,
|
||||||
|
program: WebGLProgram,
|
||||||
|
vao: WebGLVertexArrayObject,
|
||||||
|
attributeCollections: AttributeCollection[],
|
||||||
|
) => {
|
||||||
|
gl.bindVertexArray(vao);
|
||||||
|
|
||||||
|
for (const attr of attributeCollections) {
|
||||||
|
const loc = gl.getAttribLocation(program, attr.name);
|
||||||
|
if (loc >= 0) {
|
||||||
|
const buf = gl.createBuffer()!;
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, attr.data, gl.STATIC_DRAW);
|
||||||
|
gl.enableVertexAttribArray(loc);
|
||||||
|
gl.vertexAttribPointer(loc, attr.size, gl.FLOAT, false, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.bindVertexArray(null);
|
||||||
|
return vao;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setMat4 = (
|
||||||
|
gl: WebGL2RenderingContext,
|
||||||
|
program: WebGLProgram,
|
||||||
|
name: string,
|
||||||
|
mat: mat4,
|
||||||
|
) => {
|
||||||
|
const loc = gl.getUniformLocation(program, name);
|
||||||
|
if (loc) gl.uniformMatrix4fv(loc, false, mat);
|
||||||
|
};
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import { mat4, vec3 } from "gl-matrix";
|
||||||
|
import { ECS } from "../ecs/ecs";
|
||||||
|
import { createProgram, setMat4, setupVAO, updateVAO } from "./gl";
|
||||||
|
import defaultVertexSrc from "../shaders/triangle/vertex.glsl";
|
||||||
|
import defaultFragSrc from "../shaders/triangle/frag.glsl";
|
||||||
|
|
||||||
|
class Scene {
|
||||||
|
canvas: HTMLCanvasElement;
|
||||||
|
gl: WebGL2RenderingContext;
|
||||||
|
ecs: ECS<Record<string | number, any>>;
|
||||||
|
activeCamera: number;
|
||||||
|
|
||||||
|
constructor(canvas: HTMLCanvasElement, ctx: WebGL2RenderingContext) {
|
||||||
|
this.canvas = canvas;
|
||||||
|
this.gl = ctx;
|
||||||
|
this.activeCamera = -1;
|
||||||
|
|
||||||
|
/** make ecs */
|
||||||
|
this.ecs = new ECS();
|
||||||
|
this.initECS();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initECS() {
|
||||||
|
const projection = mat4.create();
|
||||||
|
mat4.perspective(
|
||||||
|
projection,
|
||||||
|
45,
|
||||||
|
this.canvas.width / this.canvas.height,
|
||||||
|
0.1,
|
||||||
|
100.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.ecs.addComponent("isMesh");
|
||||||
|
this.ecs.addComponent("isCamera");
|
||||||
|
this.ecs.addComponent("isMaterial");
|
||||||
|
this.ecs.addComponent("program");
|
||||||
|
this.ecs.addComponent("vao");
|
||||||
|
this.ecs.addComponent("name", "deafult", "default");
|
||||||
|
this.ecs.addComponent("attr_pos", [], []);
|
||||||
|
this.ecs.addComponent("attr_normals", [], []);
|
||||||
|
this.ecs.addComponent("u_model_matrix", mat4.create(), mat4.create());
|
||||||
|
this.ecs.addComponent("u_projection_matrix", projection, projection);
|
||||||
|
|
||||||
|
const defaultMaterial = this.addMaterial(
|
||||||
|
"default-material",
|
||||||
|
defaultVertexSrc,
|
||||||
|
defaultFragSrc,
|
||||||
|
);
|
||||||
|
this.ecs.addComponent("applied_material", defaultMaterial, defaultMaterial);
|
||||||
|
|
||||||
|
this.ecs.addSystem((data) => {
|
||||||
|
this.render(data);
|
||||||
|
|
||||||
|
const model = mat4.create();
|
||||||
|
const angle = Date.now() * 0.001;
|
||||||
|
mat4.translate(model, model, vec3.fromValues(0, 0, -5));
|
||||||
|
mat4.rotate(model, model, angle, [0.5, 1, 0]);
|
||||||
|
|
||||||
|
if (data["isMesh"]) {
|
||||||
|
return {
|
||||||
|
u_model_matrix: model,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
runSystems() {
|
||||||
|
this.ecs.runSystems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private render(data: any) {
|
||||||
|
if (this.activeCamera === -1) return;
|
||||||
|
const camera = this.ecs.getById(this.activeCamera);
|
||||||
|
|
||||||
|
const view = mat4.create();
|
||||||
|
mat4.invert(view, camera["u_model_matrix"] as mat4);
|
||||||
|
|
||||||
|
const projection = camera["u_projection_matrix"] as mat4;
|
||||||
|
|
||||||
|
const gl = this.gl;
|
||||||
|
gl.enable(gl.DEPTH_TEST);
|
||||||
|
|
||||||
|
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||||
|
|
||||||
|
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
||||||
|
// gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
if (data["isMesh"]) {
|
||||||
|
const { program, vao } = this.ecs.getById(data["applied_material"]);
|
||||||
|
|
||||||
|
const attrCollection = [
|
||||||
|
{
|
||||||
|
name: "attr_pos",
|
||||||
|
data: data["attr_pos"],
|
||||||
|
size: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "attr_normals",
|
||||||
|
data: data["attr_normals"],
|
||||||
|
size: 3,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
updateVAO(gl, program as any, vao as any, attrCollection);
|
||||||
|
gl.useProgram(program!);
|
||||||
|
|
||||||
|
const model = data["u_model_matrix"];
|
||||||
|
setMat4(gl, program as any, "model", model);
|
||||||
|
setMat4(gl, program as any, "view", view);
|
||||||
|
setMat4(gl, program as any, "projection", projection);
|
||||||
|
|
||||||
|
gl.bindVertexArray(vao as any);
|
||||||
|
const vertexCount = data["attr_pos"].length / 3;
|
||||||
|
gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
|
||||||
|
console.log(vertexCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.bindVertexArray(null);
|
||||||
|
gl.useProgram(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
addMaterial(name: string, vs: string, fs: string) {
|
||||||
|
const program = createProgram(this.gl, vs, fs);
|
||||||
|
const vao = setupVAO(this.gl, program, []);
|
||||||
|
return this.ecs.addEntity({
|
||||||
|
name,
|
||||||
|
isMaterial: true,
|
||||||
|
program,
|
||||||
|
vao,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addCamera(name: string, setActive = true) {
|
||||||
|
const uid = this.ecs.addEntity({
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
if (setActive) this.activeCamera = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
addMesh(name: string, positions: number[], normals?: number[]) {
|
||||||
|
const numVerts = Math.floor(positions.length / 3);
|
||||||
|
const numTris = Math.floor(numVerts / 3);
|
||||||
|
positions.length = numTris * 9;
|
||||||
|
|
||||||
|
if (!normals) {
|
||||||
|
normals = [];
|
||||||
|
const a = vec3.create();
|
||||||
|
const b = vec3.create();
|
||||||
|
const c = vec3.create();
|
||||||
|
const e1 = vec3.create();
|
||||||
|
const e2 = vec3.create();
|
||||||
|
|
||||||
|
for (let i = 0; i < numTris; i++) {
|
||||||
|
const base = i * 9;
|
||||||
|
|
||||||
|
vec3.set(
|
||||||
|
a,
|
||||||
|
positions[base + 0],
|
||||||
|
positions[base + 1],
|
||||||
|
positions[base + 2],
|
||||||
|
);
|
||||||
|
vec3.set(
|
||||||
|
b,
|
||||||
|
positions[base + 3],
|
||||||
|
positions[base + 4],
|
||||||
|
positions[base + 5],
|
||||||
|
);
|
||||||
|
vec3.set(
|
||||||
|
c,
|
||||||
|
positions[base + 6],
|
||||||
|
positions[base + 7],
|
||||||
|
positions[base + 8],
|
||||||
|
);
|
||||||
|
|
||||||
|
vec3.subtract(e1, b, a);
|
||||||
|
vec3.subtract(e2, c, a);
|
||||||
|
vec3.cross(e1, e2, e1);
|
||||||
|
vec3.normalize(e1, e1);
|
||||||
|
|
||||||
|
normals.push(e1[0], e1[1], e1[2]);
|
||||||
|
normals.push(e1[0], e1[1], e1[2]);
|
||||||
|
normals.push(e1[0], e1[1], e1[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ecs.addEntity({
|
||||||
|
name,
|
||||||
|
attr_pos: new Float32Array(positions),
|
||||||
|
attr_normals: new Float32Array(normals),
|
||||||
|
isMesh: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Scene };
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
in vec3 vNormal;
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 normal = normalize(vNormal);
|
||||||
|
vec3 lightDir = normalize(vec3(0.0, 0.0, 1.0));
|
||||||
|
float diff = max(dot(normal, lightDir), 0.0);
|
||||||
|
|
||||||
|
vec3 diffuseColor = vec3(1.0, 0.5, 0.3) * diff;
|
||||||
|
|
||||||
|
fragColor = vec4(diffuseColor, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
in vec3 attr_pos;
|
||||||
|
in vec3 attr_normals;
|
||||||
|
|
||||||
|
uniform mat4 model;
|
||||||
|
uniform mat4 view;
|
||||||
|
uniform mat4 projection;
|
||||||
|
|
||||||
|
out vec3 vNormal;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
mat3 normalMatrix = transpose(inverse(mat3(model)));
|
||||||
|
|
||||||
|
vNormal = normalize(normalMatrix * attr_normals);
|
||||||
|
|
||||||
|
gl_Position = projection * view * model * vec4(attr_pos, 1.0);
|
||||||
|
}
|
||||||
+2
-1
@@ -10,7 +10,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.3.4"
|
"vite": "^5.3.4",
|
||||||
|
"vite-plugin-glsl": "^1.4.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gl-matrix": "^3.4.3",
|
"gl-matrix": "^3.4.3",
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
#version 300 es
|
|
||||||
|
|
||||||
// fragment shaders don't have a default precision so we need
|
|
||||||
// to pick one. highp is a good default. It means "high precision"
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
// we need to declare an output for the fragment shader
|
|
||||||
out vec4 outColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// Just set the output to a constant redish-purple
|
|
||||||
outColor = vec4(1, 0, 0.5, 1);
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#version 300 es
|
|
||||||
|
|
||||||
// an attribute is an input (in) to a vertex shader.
|
|
||||||
// It will receive data from a buffer
|
|
||||||
in vec3 position;
|
|
||||||
|
|
||||||
// uniforms
|
|
||||||
uniform mat4 model;
|
|
||||||
uniform mat4 view;
|
|
||||||
uniform mat4 projection;
|
|
||||||
|
|
||||||
// all shaders have a main function
|
|
||||||
void main() {
|
|
||||||
|
|
||||||
// gl_Position is a special variable a vertex shader
|
|
||||||
// is responsible for setting
|
|
||||||
gl_Position = projection * view * model * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
import "./style.css";
|
import "./style.css";
|
||||||
import { attachCanvas, connectWorker } from "../core/connection/mainThread.ts";
|
import { attachCanvas, connectWorker } from "../core/connection/mainThread.ts";
|
||||||
import MainSceneWorker from "../examples/starterScene?worker";
|
import MainSceneWorker from "../examples/starterScene?worker";
|
||||||
|
import { ECS } from "../core/ecs/ecs";
|
||||||
|
|
||||||
const mainSceneWorker = new MainSceneWorker();
|
const mainSceneWorker = new MainSceneWorker();
|
||||||
connectWorker(mainSceneWorker);
|
connectWorker(mainSceneWorker);
|
||||||
attachCanvas(mainSceneWorker, "rendering-canvas");
|
attachCanvas(mainSceneWorker, "rendering-canvas");
|
||||||
|
|
||||||
|
(window as any).ecs = new ECS();
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import glsl from "vite-plugin-glsl";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [glsl({})],
|
||||||
|
});
|
||||||
@@ -117,6 +117,15 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
|
||||||
integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
|
integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
|
||||||
|
|
||||||
|
"@rollup/pluginutils@^5.1.4":
|
||||||
|
version "5.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a"
|
||||||
|
integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/estree" "^1.0.0"
|
||||||
|
estree-walker "^2.0.2"
|
||||||
|
picomatch "^4.0.2"
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi@4.19.2":
|
"@rollup/rollup-android-arm-eabi@4.19.2":
|
||||||
version "4.19.2"
|
version "4.19.2"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.2.tgz#6b991cb44bf69e50163528ea85bed545330ba821"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.2.tgz#6b991cb44bf69e50163528ea85bed545330ba821"
|
||||||
@@ -202,6 +211,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz"
|
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz"
|
||||||
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||||
|
|
||||||
|
"@types/estree@^1.0.0":
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8"
|
||||||
|
integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
|
||||||
|
|
||||||
esbuild@^0.21.3:
|
esbuild@^0.21.3:
|
||||||
version "0.21.5"
|
version "0.21.5"
|
||||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz"
|
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz"
|
||||||
@@ -231,6 +245,11 @@ esbuild@^0.21.3:
|
|||||||
"@esbuild/win32-ia32" "0.21.5"
|
"@esbuild/win32-ia32" "0.21.5"
|
||||||
"@esbuild/win32-x64" "0.21.5"
|
"@esbuild/win32-x64" "0.21.5"
|
||||||
|
|
||||||
|
estree-walker@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||||
|
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||||
|
|
||||||
fsevents@~2.3.2, fsevents@~2.3.3:
|
fsevents@~2.3.2, fsevents@~2.3.3:
|
||||||
version "2.3.3"
|
version "2.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||||
@@ -251,6 +270,11 @@ picocolors@^1.0.1:
|
|||||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz"
|
||||||
integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
|
integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
|
||||||
|
|
||||||
|
picomatch@^4.0.2:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
|
||||||
|
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
|
||||||
|
|
||||||
postcss@^8.4.39:
|
postcss@^8.4.39:
|
||||||
version "8.4.40"
|
version "8.4.40"
|
||||||
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz"
|
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz"
|
||||||
@@ -300,6 +324,13 @@ typescript@^5.2.2:
|
|||||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz"
|
resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz"
|
||||||
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
|
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
|
||||||
|
|
||||||
|
vite-plugin-glsl@^1.4.1:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite-plugin-glsl/-/vite-plugin-glsl-1.4.1.tgz#9468538799c539cbd56016e3502c2c65ba06d344"
|
||||||
|
integrity sha512-LkfLZeHdyl0drA71GmCCfcTeLuXyn/DYwDfjzQrupvNYVwdFRvb7nkCnCm0o9LZRKxS4/8r7E3KNM7KQjWrWWQ==
|
||||||
|
dependencies:
|
||||||
|
"@rollup/pluginutils" "^5.1.4"
|
||||||
|
|
||||||
vite@^5.3.4:
|
vite@^5.3.4:
|
||||||
version "5.3.5"
|
version "5.3.5"
|
||||||
resolved "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz"
|
resolved "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user