A hackable template for creating small and fast browser games.

Add Template3D

+815
+15
Template3D/blueprints/blu_camera.ts
··· 1 + import {camera} from "../components/com_camera.js"; 2 + import {Game} from "../game.js"; 3 + import {Blueprint} from "./blu_common"; 4 + 5 + export function blueprint_camera(game: Game) { 6 + return <Blueprint>{ 7 + Rotation: [0, 1, 0, 0], 8 + Children: [ 9 + { 10 + Rotation: [0, 1, 0, 0], 11 + Using: [camera(game.ViewportWidth / game.ViewportHeight, 1, 0.1, 1000)], 12 + }, 13 + ], 14 + }; 15 + }
+20
Template3D/blueprints/blu_common.ts
··· 1 + import {Quat, Rad, Vec2, Vec3} from "../../common"; 2 + import {Entity, Game} from "../game"; 3 + 4 + type Mixin = (game: Game, entity: Entity) => void; 5 + 6 + export interface Blueprint { 7 + Translation?: Vec3; 8 + Rotation?: Quat; 9 + Scale?: Vec3; 10 + Using?: Array<Mixin>; 11 + Children?: Array<Blueprint>; 12 + } 13 + 14 + export interface Blueprint2D { 15 + Translation?: Vec2; 16 + Rotation?: Rad; 17 + Scale?: Vec2; 18 + Using?: Array<Mixin>; 19 + Children?: Array<Blueprint2D>; 20 + }
+21
Template3D/components/com_camera.ts
··· 1 + import {Mat4} from "../../common/index.js"; 2 + import {create, perspective} from "../../common/mat4.js"; 3 + import {Entity, Game} from "../game.js"; 4 + import {Has} from "./com_index.js"; 5 + 6 + export interface Camera { 7 + Projection: Mat4; 8 + View: Mat4; 9 + PV: Mat4; 10 + } 11 + 12 + export function camera(aspect: number, fovy: number, near: number, far: number) { 13 + return (game: Game, entity: Entity) => { 14 + game.World.Mask[entity] |= Has.Camera; 15 + game.World.Camera[entity] = <Camera>{ 16 + Projection: perspective(create(), fovy, aspect, near, far), 17 + View: create(), 18 + PV: create(), 19 + }; 20 + }; 21 + }
+13
Template3D/components/com_index.ts
··· 1 + const enum Component { 2 + Camera, 3 + Light, 4 + Render, 5 + Transform, 6 + } 7 + 8 + export const enum Has { 9 + Camera = 1 << Component.Camera, 10 + Light = 1 << Component.Light, 11 + Render = 1 << Component.Render, 12 + Transform = 1 << Component.Transform, 13 + }
+20
Template3D/components/com_light.ts
··· 1 + import {Vec3} from "../../common/index.js"; 2 + import {Entity, Game} from "../game.js"; 3 + import {Has} from "./com_index.js"; 4 + 5 + export interface Light { 6 + EntityId: Entity; 7 + Color: Vec3; 8 + Intensity: number; 9 + } 10 + 11 + export function light(color: Vec3 = [1, 1, 1], range: number = 1) { 12 + return (game: Game, EntityId: Entity) => { 13 + game.World.Mask[EntityId] |= Has.Light; 14 + game.World.Light[EntityId] = <Light>{ 15 + EntityId, 16 + Color: color, 17 + Intensity: range ** 2, 18 + }; 19 + }; 20 + }
+7
Template3D/components/com_render.ts
··· 1 + import {RenderShaded} from "./com_render_shaded.js"; 2 + 3 + export type Render = RenderShaded; 4 + 5 + export const enum RenderKind { 6 + Shaded, 7 + }
+75
Template3D/components/com_render_shaded.ts
··· 1 + import {Vec4} from "../../common/index.js"; 2 + import { 3 + GL_ARRAY_BUFFER, 4 + GL_ELEMENT_ARRAY_BUFFER, 5 + GL_FLOAT, 6 + GL_STATIC_DRAW, 7 + } from "../../common/webgl.js"; 8 + import {Entity, Game} from "../game.js"; 9 + import {Material, Shape} from "../materials/mat_common.js"; 10 + import {Has} from "./com_index.js"; 11 + import {RenderKind} from "./com_render.js"; 12 + 13 + export interface RenderShaded { 14 + readonly Kind: RenderKind.Shaded; 15 + readonly Material: Material; 16 + readonly VAO: WebGLVertexArrayObject; 17 + readonly Count: number; 18 + Color: Vec4; 19 + } 20 + 21 + let vaos: WeakMap<Shape, WebGLVertexArrayObject> = new WeakMap(); 22 + 23 + export function render_shaded(Material: Material, shape: Shape, color: Vec4) { 24 + return (game: Game, entity: Entity) => { 25 + if (!vaos.has(shape)) { 26 + // We only need to create the VAO once. 27 + vaos.set(shape, buffer(game.GL, shape)!); 28 + } 29 + 30 + game.World.Mask[entity] |= Has.Render; 31 + game.World.Render[entity] = <RenderShaded>{ 32 + Kind: RenderKind.Shaded, 33 + Material, 34 + VAO: vaos.get(shape), 35 + Count: shape.Indices.length, 36 + Color: color, 37 + }; 38 + }; 39 + } 40 + 41 + export const enum ShadedAttribute { 42 + Position = 1, 43 + Normal = 2, 44 + } 45 + 46 + export const enum ShadedUniform { 47 + PV, 48 + World, 49 + Self, 50 + Color, 51 + LightCount, 52 + LightPositions, 53 + LightDetails, 54 + } 55 + 56 + function buffer(gl: WebGL2RenderingContext, shape: Shape) { 57 + let vao = gl.createVertexArray(); 58 + gl.bindVertexArray(vao); 59 + 60 + gl.bindBuffer(GL_ARRAY_BUFFER, gl.createBuffer()); 61 + gl.bufferData(GL_ARRAY_BUFFER, shape.Vertices, GL_STATIC_DRAW); 62 + gl.enableVertexAttribArray(ShadedAttribute.Position); 63 + gl.vertexAttribPointer(ShadedAttribute.Position, 3, GL_FLOAT, false, 0, 0); 64 + 65 + gl.bindBuffer(GL_ARRAY_BUFFER, gl.createBuffer()); 66 + gl.bufferData(GL_ARRAY_BUFFER, shape.Normals, GL_STATIC_DRAW); 67 + gl.enableVertexAttribArray(ShadedAttribute.Normal); 68 + gl.vertexAttribPointer(ShadedAttribute.Normal, 3, GL_FLOAT, false, 0, 0); 69 + 70 + gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, gl.createBuffer()); 71 + gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, shape.Indices, GL_STATIC_DRAW); 72 + 73 + gl.bindVertexArray(null); 74 + return vao; 75 + }
+66
Template3D/components/com_transform.ts
··· 1 + import {Mat4, Quat, Vec3} from "../../common/index.js"; 2 + import {create} from "../../common/mat4.js"; 3 + import {Entity, Game} from "../game.js"; 4 + import {World} from "../world.js"; 5 + import {Has} from "./com_index.js"; 6 + 7 + export interface Transform { 8 + /** Absolute matrix relative to the world. */ 9 + World: Mat4; 10 + /** World to self matrix. */ 11 + Self: Mat4; 12 + /** Local translation relative to the parent. */ 13 + Translation: Vec3; 14 + /** Local rotation relative to the parent. */ 15 + Rotation: Quat; 16 + /** Local scale relative to the parent. */ 17 + Scale: Vec3; 18 + /** This Transform's entity id. */ 19 + readonly EntityId: Entity; 20 + Parent?: Transform; 21 + Children: Array<Transform>; 22 + Dirty: boolean; 23 + } 24 + 25 + export function transform( 26 + Translation: Vec3 = [0, 0, 0], 27 + Rotation: Quat = [0, 0, 0, 1], 28 + Scale: Vec3 = [1, 1, 1] 29 + ) { 30 + return (game: Game, EntityId: Entity) => { 31 + game.World.Mask[EntityId] |= Has.Transform; 32 + game.World.Transform[EntityId] = <Transform>{ 33 + EntityId, 34 + World: create(), 35 + Self: create(), 36 + Translation, 37 + Rotation, 38 + Scale, 39 + Children: [], 40 + Dirty: true, 41 + }; 42 + }; 43 + } 44 + 45 + /** 46 + * Get all component instances of a given type from the current entity and all 47 + * its children. 48 + * 49 + * @param world World object which stores the component data. 50 + * @param transform The transform to traverse. 51 + * @param component Component data array. 52 + * @param mask Component mask to look for. 53 + */ 54 + export function* components_of_type<T>( 55 + world: World, 56 + transform: Transform, 57 + component: Array<T>, 58 + mask: Has 59 + ): IterableIterator<T> { 60 + if (world.Mask[transform.EntityId] & mask) { 61 + yield component[transform.EntityId]; 62 + } 63 + for (let child of transform.Children) { 64 + yield* components_of_type(world, child, component, mask); 65 + } 66 + }
+157
Template3D/game.ts
··· 1 + import {GL_CULL_FACE, GL_CW, GL_DEPTH_TEST} from "../common/webgl.js"; 2 + import {Blueprint} from "./blueprints/blu_common.js"; 3 + import {Camera} from "./components/com_camera.js"; 4 + import {Has} from "./components/com_index.js"; 5 + import {Light} from "./components/com_light.js"; 6 + import {transform} from "./components/com_transform.js"; 7 + import {Material} from "./materials/mat_common.js"; 8 + import {mat_gouraud} from "./materials/mat_gouraud.js"; 9 + import {sys_camera} from "./systems/sys_camera.js"; 10 + import {sys_framerate} from "./systems/sys_framerate.js"; 11 + import {sys_light} from "./systems/sys_light.js"; 12 + import {sys_performance} from "./systems/sys_performance.js"; 13 + import {sys_render} from "./systems/sys_render.js"; 14 + import {sys_transform} from "./systems/sys_transform.js"; 15 + import {World} from "./world.js"; 16 + 17 + const MAX_ENTITIES = 10000; 18 + 19 + export type Entity = number; 20 + 21 + export interface InputState { 22 + [k: string]: number; 23 + mouse_x: number; 24 + mouse_y: number; 25 + } 26 + 27 + export interface InputEvent { 28 + [k: string]: number; 29 + mouse_x: number; 30 + mouse_y: number; 31 + wheel_y: number; 32 + } 33 + 34 + export class Game { 35 + public World = new World(); 36 + 37 + public ViewportWidth = window.innerWidth; 38 + public ViewportHeight = window.innerHeight; 39 + public UI = document.querySelector("main")!; 40 + public GL: WebGL2RenderingContext; 41 + public InputState: InputState = {mouse_x: 0, mouse_y: 0}; 42 + public InputEvent: InputEvent = {mouse_x: 0, mouse_y: 0, wheel_y: 0}; 43 + 44 + public MaterialGouraud: Material; 45 + 46 + public Cameras: Array<Camera> = []; 47 + public Lights: Array<Light> = []; 48 + public Palette: Array<number> = []; 49 + private RAF: number = 0; 50 + 51 + constructor() { 52 + document.addEventListener("visibilitychange", () => 53 + document.hidden ? this.Stop() : this.Start() 54 + ); 55 + 56 + window.addEventListener("keydown", evt => (this.InputState[evt.code] = 1)); 57 + window.addEventListener("keyup", evt => (this.InputState[evt.code] = 0)); 58 + this.UI.addEventListener("contextmenu", evt => evt.preventDefault()); 59 + this.UI.addEventListener("mousedown", evt => { 60 + this.InputState[`mouse_${evt.button}`] = 1; 61 + this.InputEvent[`mouse_${evt.button}_down`] = 1; 62 + }); 63 + this.UI.addEventListener("mouseup", evt => { 64 + this.InputState[`mouse_${evt.button}`] = 0; 65 + this.InputEvent[`mouse_${evt.button}_up`] = 1; 66 + }); 67 + this.UI.addEventListener("mousemove", evt => { 68 + this.InputState.mouse_x = evt.offsetX; 69 + this.InputState.mouse_y = evt.offsetY; 70 + this.InputEvent.mouse_x = evt.movementX; 71 + this.InputEvent.mouse_y = evt.movementY; 72 + }); 73 + this.UI.addEventListener("wheel", evt => { 74 + this.InputEvent.wheel_y = evt.deltaY; 75 + }); 76 + this.UI.addEventListener("click", () => this.UI.requestPointerLock()); 77 + 78 + let canvas3d = document.querySelector("canvas")!; 79 + canvas3d.width = this.ViewportWidth; 80 + canvas3d.height = this.ViewportHeight; 81 + this.GL = canvas3d.getContext("webgl2")!; 82 + this.GL.enable(GL_DEPTH_TEST); 83 + this.GL.enable(GL_CULL_FACE); 84 + this.GL.frontFace(GL_CW); 85 + 86 + this.MaterialGouraud = mat_gouraud(this.GL); 87 + } 88 + 89 + Update(delta: number) { 90 + let now = performance.now(); 91 + sys_transform(this, delta); 92 + sys_camera(this, delta); 93 + sys_light(this, delta); 94 + sys_render(this, delta); 95 + sys_framerate(this, delta); 96 + sys_performance(this, performance.now() - now, document.querySelector("#frame")); 97 + } 98 + 99 + Start() { 100 + let last = performance.now(); 101 + let tick = (now: number) => { 102 + let delta = (now - last) / 1000; 103 + this.Update(delta); 104 + 105 + // Reset all input events for the next frame. 106 + for (let name in this.InputEvent) { 107 + this.InputEvent[name] = 0; 108 + } 109 + 110 + last = now; 111 + this.RAF = requestAnimationFrame(tick); 112 + }; 113 + 114 + this.Stop(); 115 + tick(last); 116 + } 117 + 118 + Stop() { 119 + cancelAnimationFrame(this.RAF); 120 + } 121 + 122 + CreateEntity(mask: number = 0) { 123 + for (let i = 0; i < MAX_ENTITIES; i++) { 124 + if (!this.World.Mask[i]) { 125 + this.World.Mask[i] = mask; 126 + return i; 127 + } 128 + } 129 + throw new Error("No more entities available."); 130 + } 131 + 132 + Add({Translation, Rotation, Scale, Using = [], Children = []}: Blueprint) { 133 + let entity = this.CreateEntity(); 134 + transform(Translation, Rotation, Scale)(this, entity); 135 + for (let mixin of Using) { 136 + mixin(this, entity); 137 + } 138 + let entity_transform = this.World.Transform[entity]; 139 + for (let subtree of Children) { 140 + let child = this.Add(subtree); 141 + let child_transform = this.World.Transform[child]; 142 + child_transform.Parent = entity_transform; 143 + entity_transform.Children.push(child_transform); 144 + } 145 + return entity; 146 + } 147 + 148 + Destroy(entity: Entity) { 149 + let mask = this.World.Mask[entity]; 150 + if (mask & Has.Transform) { 151 + for (let child of this.World.Transform[entity].Children) { 152 + this.Destroy(child.EntityId); 153 + } 154 + } 155 + this.World.Mask[entity] = 0; 156 + } 157 + }
+70
Template3D/index.html
··· 1 + <!DOCTYPE html> 2 + <meta charset="utf8" /> 3 + <meta name="viewport" content="width=device-width, user-scalable=0" /> 4 + <title>Template3D</title> 5 + <style> 6 + html, 7 + body { 8 + overflow: hidden; 9 + margin: 0; 10 + } 11 + canvas, 12 + main { 13 + position: absolute; 14 + top: 0; 15 + right: 0; 16 + bottom: 0; 17 + left: 0; 18 + user-select: none; 19 + } 20 + #debug { 21 + display: flex; 22 + flex-flow: column nowrap; 23 + align-items: flex-end; 24 + position: absolute; 25 + top: 0; 26 + right: 0; 27 + color: #fff; 28 + font-family: monospace; 29 + } 30 + #debug button { 31 + cursor: pointer; 32 + padding: 0; 33 + background: none; 34 + border: none; 35 + font: inherit; 36 + color: inherit; 37 + } 38 + #debug div { 39 + width: max-content; 40 + background: #000; 41 + color: #fff; 42 + } 43 + </style> 44 + <body> 45 + <canvas></canvas> 46 + <main></main> 47 + 48 + <div id="debug"> 49 + <div>frame: <span id="frame"></span></div> 50 + <div>tick: <span id="tick"></span></div> 51 + <div>fps: <span id="fps"></span></div> 52 + <div><button onclick="toggle(this)">pause</button></div> 53 + </div> 54 + <script type="module"> 55 + import "./index.js"; 56 + 57 + let running = true; 58 + window.toggle = function toggle(button) { 59 + if (running) { 60 + running = false; 61 + window.game.Stop(); 62 + button.textContent = "resume"; 63 + } else { 64 + running = true; 65 + window.game.Start(); 66 + button.textContent = "pause"; 67 + } 68 + }; 69 + </script> 70 + </body>
+9
Template3D/index.ts
··· 1 + import {Game} from "./game.js"; 2 + import {scene_stage} from "./scenes/sce_stage.js"; 3 + 4 + let game = new Game(); 5 + scene_stage(game); 6 + game.Start(); 7 + 8 + // @ts-ignore 9 + window.game = game;
+43
Template3D/materials/mat_common.ts
··· 1 + import { 2 + GL_COMPILE_STATUS, 3 + GL_FRAGMENT_SHADER, 4 + GL_LINK_STATUS, 5 + GL_VERTEX_SHADER, 6 + } from "../../common/webgl.js"; 7 + 8 + export interface Shape { 9 + Vertices: Float32Array; 10 + Indices: Uint16Array; 11 + Normals: Float32Array; 12 + } 13 + 14 + export interface Material { 15 + Mode: GLint; 16 + Program: WebGLProgram; 17 + Uniforms: Array<WebGLUniformLocation>; 18 + } 19 + 20 + export function link(gl: WebGL2RenderingContext, vertex: string, fragment: string) { 21 + let program = gl.createProgram()!; 22 + gl.attachShader(program, compile(gl, GL_VERTEX_SHADER, vertex)); 23 + gl.attachShader(program, compile(gl, GL_FRAGMENT_SHADER, fragment)); 24 + gl.linkProgram(program); 25 + 26 + if (!gl.getProgramParameter(program, GL_LINK_STATUS)) { 27 + throw new Error(gl.getProgramInfoLog(program)!); 28 + } 29 + 30 + return program; 31 + } 32 + 33 + function compile(gl: WebGL2RenderingContext, type: GLint, source: string) { 34 + let shader = gl.createShader(type)!; 35 + gl.shaderSource(shader, source); 36 + gl.compileShader(shader); 37 + 38 + if (!gl.getShaderParameter(shader, GL_COMPILE_STATUS)) { 39 + throw new Error(gl.getShaderInfoLog(shader)!); 40 + } 41 + 42 + return shader; 43 + }
+72
Template3D/materials/mat_gouraud.ts
··· 1 + import {GL_TRIANGLES} from "../../common/webgl.js"; 2 + import {ShadedAttribute} from "../components/com_render_shaded.js"; 3 + import {link, Material} from "./mat_common.js"; 4 + 5 + let vertex = `#version 300 es 6 + uniform mat4 pv; 7 + uniform mat4 world; 8 + uniform mat4 self; 9 + uniform vec4 color; 10 + uniform int light_count; 11 + uniform vec3 light_positions[10]; 12 + uniform vec4 light_details[10]; 13 + 14 + layout(location=${ShadedAttribute.Position}) in vec3 position; 15 + layout(location=${ShadedAttribute.Normal}) in vec3 normal; 16 + out vec4 vert_color; 17 + 18 + void main() { 19 + vec4 world_pos = world * vec4(position, 1.0); 20 + vec3 world_normal = normalize((vec4(normal, 1.0) * self).xyz); 21 + gl_Position = pv * world_pos; 22 + 23 + vec3 rgb = vec3(0.0, 0.0, 0.0); 24 + for (int i = 0; i < light_count; i++) { 25 + vec3 light_dir = light_positions[i] - world_pos.xyz ; 26 + vec3 light_normal = normalize(light_dir); 27 + float light_dist = length(light_dir); 28 + 29 + float diffuse_factor = max(dot(world_normal, light_normal), 0.0); 30 + float distance_factor = light_dist * light_dist; 31 + float intensity_factor = light_details[i].a; 32 + 33 + rgb += color.rgb * light_details[i].rgb * diffuse_factor 34 + * intensity_factor / distance_factor; 35 + } 36 + 37 + vert_color = vec4(rgb, 1.0); 38 + } 39 + `; 40 + 41 + let fragment = `#version 300 es 42 + precision mediump float; 43 + 44 + in vec4 vert_color; 45 + out vec4 frag_color; 46 + 47 + void main() { 48 + frag_color = vert_color; 49 + } 50 + `; 51 + 52 + export function mat_gouraud(gl: WebGL2RenderingContext) { 53 + let material = <Material>{ 54 + Mode: GL_TRIANGLES, 55 + Program: link(gl, vertex, fragment), 56 + Uniforms: [], 57 + }; 58 + 59 + for (let name of [ 60 + "pv", 61 + "world", 62 + "self", 63 + "color", 64 + "light_count", 65 + "light_positions", 66 + "light_details", 67 + ]) { 68 + material.Uniforms.push(gl.getUniformLocation(material.Program, name)!); 69 + } 70 + 71 + return material; 72 + }
+38
Template3D/scenes/sce_stage.ts
··· 1 + import {Cube} from "../../shapes/Cube.js"; 2 + import {blueprint_camera} from "../blueprints/blu_camera.js"; 3 + import {light} from "../components/com_light.js"; 4 + import {render_shaded} from "../components/com_render_shaded.js"; 5 + import {Game} from "../game.js"; 6 + import {World} from "../world.js"; 7 + 8 + export function scene_stage(game: Game) { 9 + game.World = new World(); 10 + game.Cameras = []; 11 + game.Lights = []; 12 + game.GL.clearColor(1, 0.3, 0.3, 1); 13 + 14 + // Camera. 15 + game.Add({ 16 + Translation: [1, 2, 5], 17 + ...blueprint_camera(game), 18 + }); 19 + 20 + // Light. 21 + game.Add({ 22 + Translation: [2, 3, 5], 23 + Using: [light([1, 1, 1], 5)], 24 + }); 25 + 26 + // Ground. 27 + game.Add({ 28 + Translation: [0, 0, 0], 29 + Scale: [10, 1, 10], 30 + Using: [render_shaded(game.MaterialGouraud, Cube, [1, 1, 0.3, 1])], 31 + }); 32 + 33 + // Box. 34 + game.Add({ 35 + Translation: [0, 1, 0], 36 + Using: [render_shaded(game.MaterialGouraud, Cube, [1, 1, 0.3, 1])], 37 + }); 38 + }
+22
Template3D/systems/sys_camera.ts
··· 1 + import {invert, multiply} from "../../common/mat4.js"; 2 + import {Has} from "../components/com_index.js"; 3 + import {Entity, Game} from "../game.js"; 4 + 5 + const QUERY = Has.Transform | Has.Camera; 6 + 7 + export function sys_camera(game: Game, delta: number) { 8 + game.Cameras = []; 9 + for (let i = 0; i < game.World.Mask.length; i++) { 10 + if ((game.World.Mask[i] & QUERY) === QUERY) { 11 + update(game, i); 12 + } 13 + } 14 + } 15 + 16 + function update(game: Game, entity: Entity) { 17 + let transform = game.World.Transform[entity]; 18 + let camera = game.World.Camera[entity]; 19 + game.Cameras.push(camera); 20 + invert(camera.View, transform.World); 21 + multiply(camera.PV, camera.Projection, camera.View); 22 + }
+13
Template3D/systems/sys_framerate.ts
··· 1 + import {Game} from "../game.js"; 2 + 3 + let tick_span = document.getElementById("tick"); 4 + let fps_span = document.getElementById("fps"); 5 + 6 + export function sys_framerate(game: Game, delta: number) { 7 + if (tick_span) { 8 + tick_span.textContent = (delta * 1000).toFixed(1); 9 + } 10 + if (fps_span) { 11 + fps_span.textContent = (1 / delta).toFixed(); 12 + } 13 + }
+18
Template3D/systems/sys_light.ts
··· 1 + import {Has} from "../components/com_index.js"; 2 + import {Entity, Game} from "../game.js"; 3 + 4 + const QUERY = Has.Transform | Has.Light; 5 + 6 + export function sys_light(game: Game, delta: number) { 7 + game.Lights = []; 8 + 9 + for (let i = 0; i < game.World.Mask.length; i++) { 10 + if ((game.World.Mask[i] & QUERY) === QUERY) { 11 + update(game, i); 12 + } 13 + } 14 + } 15 + 16 + function update(game: Game, entity: Entity) { 17 + game.Lights.push(game.World.Light[entity]); 18 + }
+7
Template3D/systems/sys_performance.ts
··· 1 + import {Game} from "../game.js"; 2 + 3 + export function sys_performance(game: Game, delta: number, target: HTMLElement | null) { 4 + if (target) { 5 + target.textContent = delta.toFixed(1); 6 + } 7 + }
+74
Template3D/systems/sys_render.ts
··· 1 + import {get_translation} from "../../common/mat4.js"; 2 + import {GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_UNSIGNED_SHORT} from "../../common/webgl.js"; 3 + import {Has} from "../components/com_index.js"; 4 + import {RenderKind} from "../components/com_render.js"; 5 + import {RenderShaded, ShadedUniform} from "../components/com_render_shaded.js"; 6 + import {Transform} from "../components/com_transform.js"; 7 + import {Game} from "../game.js"; 8 + 9 + const QUERY = Has.Transform | Has.Render; 10 + 11 + export function sys_render(game: Game, delta: number) { 12 + game.GL.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 13 + 14 + let light_positions: Array<number> = []; 15 + let light_details: Array<number> = []; 16 + 17 + for (let i = 0; i < game.Lights.length; i++) { 18 + let light = game.Lights[i]; 19 + let transform = game.World.Transform[light.EntityId]; 20 + let position = get_translation([0, 0, 0], transform.World); 21 + light_positions.push(...position); 22 + light_details.push(...light.Color, light.Intensity); 23 + } 24 + 25 + // Keep track of the current material to minimize switching. 26 + let current_material = null; 27 + 28 + for (let i = 0; i < game.World.Mask.length; i++) { 29 + if ((game.World.Mask[i] & QUERY) === QUERY) { 30 + let transform = game.World.Transform[i]; 31 + let render = game.World.Render[i]; 32 + 33 + if (render.Material !== current_material) { 34 + current_material = render.Material; 35 + 36 + game.GL.useProgram(current_material.Program); 37 + // XXX Uniforms[0] should always be PV. 38 + game.GL.uniformMatrix4fv(current_material.Uniforms[0], false, game.Cameras[0].PV); 39 + 40 + switch (render.Kind) { 41 + case RenderKind.Shaded: 42 + game.GL.uniform1i( 43 + current_material.Uniforms[ShadedUniform.LightCount], 44 + game.Lights.length 45 + ); 46 + game.GL.uniform3fv( 47 + current_material.Uniforms[ShadedUniform.LightPositions], 48 + light_positions 49 + ); 50 + game.GL.uniform4fv( 51 + current_material.Uniforms[ShadedUniform.LightDetails], 52 + light_details 53 + ); 54 + break; 55 + } 56 + } 57 + 58 + switch (render.Kind) { 59 + case RenderKind.Shaded: 60 + draw_shaded(game, transform, render); 61 + break; 62 + } 63 + } 64 + } 65 + } 66 + 67 + function draw_shaded(game: Game, transform: Transform, render: RenderShaded) { 68 + game.GL.uniformMatrix4fv(render.Material.Uniforms[ShadedUniform.World], false, transform.World); 69 + game.GL.uniformMatrix4fv(render.Material.Uniforms[ShadedUniform.Self], false, transform.Self); 70 + game.GL.uniform4fv(render.Material.Uniforms[ShadedUniform.Color], render.Color); 71 + game.GL.bindVertexArray(render.VAO); 72 + game.GL.drawElements(render.Material.Mode, render.Count, GL_UNSIGNED_SHORT, 0); 73 + game.GL.bindVertexArray(null); 74 + }
+41
Template3D/systems/sys_transform.ts
··· 1 + import {from_rotation_translation_scale, invert, multiply} from "../../common/mat4.js"; 2 + import {Has} from "../components/com_index.js"; 3 + import {Transform} from "../components/com_transform.js"; 4 + import {Game} from "../game.js"; 5 + 6 + const QUERY = Has.Transform; 7 + 8 + export function sys_transform(game: Game, delta: number) { 9 + for (let i = 0; i < game.World.Mask.length; i++) { 10 + if ((game.World.Mask[i] & QUERY) === QUERY) { 11 + update(game.World.Transform[i]); 12 + } 13 + } 14 + } 15 + 16 + function update(transform: Transform) { 17 + if (transform.Dirty) { 18 + transform.Dirty = false; 19 + set_children_as_dirty(transform); 20 + 21 + from_rotation_translation_scale( 22 + transform.World, 23 + transform.Rotation, 24 + transform.Translation, 25 + transform.Scale 26 + ); 27 + 28 + if (transform.Parent) { 29 + multiply(transform.World, transform.Parent.World, transform.World); 30 + } 31 + 32 + invert(transform.Self, transform.World); 33 + } 34 + } 35 + 36 + function set_children_as_dirty(transform: Transform) { 37 + for (let child of transform.Children) { 38 + child.Dirty = true; 39 + set_children_as_dirty(child); 40 + } 41 + }
+14
Template3D/world.ts
··· 1 + import {Camera} from "./components/com_camera.js"; 2 + import {Light} from "./components/com_light.js"; 3 + import {Render} from "./components/com_render.js"; 4 + import {Transform} from "./components/com_transform.js"; 5 + 6 + export class World { 7 + // Component flags 8 + Mask: Array<number> = []; 9 + // Component data 10 + Camera: Array<Camera> = []; 11 + Light: Array<Light> = []; 12 + Render: Array<Render> = []; 13 + Transform: Array<Transform> = []; 14 + }
favicon.ico

This is a binary file and will not be displayed.