@fonsecabarreto/genesis-gl-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/Camera-DY_8gx3C.d.ts +45 -0
- package/dist/Core/classes/Material.d.ts +3 -0
- package/dist/Core/classes/Material.js +9 -0
- package/dist/Core/classes/Material.js.map +1 -0
- package/dist/Core/classes/Model.d.ts +5 -0
- package/dist/Core/classes/Model.js +7 -0
- package/dist/Core/classes/Model.js.map +1 -0
- package/dist/Core/classes/Renderer.d.ts +30 -0
- package/dist/Core/classes/Renderer.js +11 -0
- package/dist/Core/classes/Renderer.js.map +1 -0
- package/dist/Core/classes/Scene.d.ts +37 -0
- package/dist/Core/classes/Scene.js +7 -0
- package/dist/Core/classes/Scene.js.map +1 -0
- package/dist/Core/classes/Viewport.d.ts +37 -0
- package/dist/Core/classes/Viewport.js +7 -0
- package/dist/Core/classes/Viewport.js.map +1 -0
- package/dist/Core/domain/interfaces/Vectors.d.ts +4 -0
- package/dist/Core/domain/interfaces/Vectors.js +1 -0
- package/dist/Core/domain/interfaces/Vectors.js.map +1 -0
- package/dist/Core/index.d.ts +10 -0
- package/dist/Core/index.js +51 -0
- package/dist/Core/index.js.map +1 -0
- package/dist/Core/utils/get-overlap.d.ts +3 -0
- package/dist/Core/utils/get-overlap.js +11 -0
- package/dist/Core/utils/get-overlap.js.map +1 -0
- package/dist/Core/utils/load-glb.d.ts +101 -0
- package/dist/Core/utils/load-glb.js +697 -0
- package/dist/Core/utils/load-glb.js.map +1 -0
- package/dist/Core/utils/parse-obj.d.ts +10 -0
- package/dist/Core/utils/parse-obj.js +183 -0
- package/dist/Core/utils/parse-obj.js.map +1 -0
- package/dist/Editor/index.d.ts +364 -0
- package/dist/Editor/index.js +1737 -0
- package/dist/Editor/index.js.map +1 -0
- package/dist/Game/controls/KeyboardInput.d.ts +8 -0
- package/dist/Game/controls/KeyboardInput.js +7 -0
- package/dist/Game/controls/KeyboardInput.js.map +1 -0
- package/dist/Game/index.d.ts +45 -0
- package/dist/Game/index.js +353 -0
- package/dist/Game/index.js.map +1 -0
- package/dist/KeyboardControl-5w7Vm0J0.d.ts +18 -0
- package/dist/KeyboardInput-DTsfj3tE.d.ts +166 -0
- package/dist/Material-BGLkldxv.d.ts +74 -0
- package/dist/Model-CQvDXd-b.d.ts +302 -0
- package/dist/WebGLCore-DR7ZHJB0.d.ts +22 -0
- package/dist/chunk-3ULETMWF.js +144 -0
- package/dist/chunk-3ULETMWF.js.map +1 -0
- package/dist/chunk-5TAAXI6S.js +330 -0
- package/dist/chunk-5TAAXI6S.js.map +1 -0
- package/dist/chunk-6LS6AO5H.js +296 -0
- package/dist/chunk-6LS6AO5H.js.map +1 -0
- package/dist/chunk-JK2HEZAT.js +317 -0
- package/dist/chunk-JK2HEZAT.js.map +1 -0
- package/dist/chunk-P7QOKDLY.js +57 -0
- package/dist/chunk-P7QOKDLY.js.map +1 -0
- package/dist/chunk-QCQVJCSR.js +968 -0
- package/dist/chunk-QCQVJCSR.js.map +1 -0
- package/dist/chunk-SUNYSY45.js +81 -0
- package/dist/chunk-SUNYSY45.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Core/utils/load-texture.ts","../src/Core/utils/parse-hex-to-rgb.ts","../src/Core/classes/Material.ts"],"sourcesContent":["export interface TextureOptions {\n /** When true, use gl.REPEAT wrap mode for tiling textures. */\n repeat?: boolean;\n}\n\nexport async function loadWebGlTexture(\n gl: WebGLRenderingContext,\n url: string,\n options: TextureOptions = {},\n): Promise<WebGLTexture> {\n return new Promise((resolve, reject) => {\n const texture = gl.createTexture();\n if (!texture) return reject(new Error('Failed to create texture'));\n\n gl.bindTexture(gl.TEXTURE_2D, texture);\n\n // Placeholder pixel (gray) until image loads\n const level = 0;\n const internalFormat = gl.RGBA;\n const width = 1;\n const height = 1;\n const border = 0;\n const srcFormat = gl.RGBA;\n const srcType = gl.UNSIGNED_BYTE;\n const pixel = new Uint8Array([128, 128, 128, 255]); // gray\n gl.texImage2D(\n gl.TEXTURE_2D,\n level,\n internalFormat,\n width,\n height,\n border,\n srcFormat,\n srcType,\n pixel,\n );\n\n const image = new Image();\n image.crossOrigin = 'anonymous';\n image.onload = () => {\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(\n gl.TEXTURE_2D,\n level,\n internalFormat,\n srcFormat,\n srcType,\n image,\n );\n\n // Auto mipmaps and filtering\n if (isPowerOf2(image.width) && isPowerOf2(image.height)) {\n gl.generateMipmap(gl.TEXTURE_2D);\n gl.texParameteri(\n gl.TEXTURE_2D,\n gl.TEXTURE_MIN_FILTER,\n gl.LINEAR_MIPMAP_LINEAR,\n );\n // Wrap mode: REPEAT for tiling, otherwise default (REPEAT is GL default for POT)\n if (options.repeat) {\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);\n }\n } else {\n // Non-power-of-2: REPEAT is not supported in WebGL 1\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n }\n\n resolve(texture);\n };\n image.onerror = reject;\n image.src = url;\n });\n}\n\nfunction isPowerOf2(value: number) {\n return (value & (value - 1)) === 0;\n}\n","import { Vector4 } from '../domain/interfaces/Vectors';\n\nexport function parseHexToRgbArray(hex: string): Vector4 {\n // Remove leading #\n if (hex.startsWith('#')) hex = hex.slice(1);\n\n let r = 1,\n g = 1,\n b = 1,\n a = 1;\n\n if (hex.length === 6) {\n r = parseInt(hex.slice(0, 2), 16) / 255;\n g = parseInt(hex.slice(2, 4), 16) / 255;\n b = parseInt(hex.slice(4, 6), 16) / 255;\n a = 1;\n } else if (hex.length === 8) {\n r = parseInt(hex.slice(0, 2), 16) / 255;\n g = parseInt(hex.slice(2, 4), 16) / 255;\n b = parseInt(hex.slice(4, 6), 16) / 255;\n a = parseInt(hex.slice(6, 8), 16) / 255;\n } else {\n throw new Error('Invalid hex color format. Use #RRGGBB or #RRGGBBAA.');\n }\n\n return [r, g, b, a];\n}\n","import { Vector3, Vector4 } from '../domain/interfaces/Vectors';\nimport { loadWebGlTexture, TextureOptions } from '../utils/load-texture';\nimport { parseHexToRgbArray } from '../utils/parse-hex-to-rgb';\nimport { Light } from './Light';\nimport WebGLCore from './WebGLCore';\n\n/** Maximum number of lights supported per draw call (must match the shader). */\nexport const MAX_LIGHTS = 5;\n\n/**\n * Encapsulates GPU material state: shader program reference, uniform/attribute\n * caches, albedo colour, textures, and lighting properties.\n */\nexport class Material {\n // cache\n public program: WebGLProgram;\n public uniformLocations: Record<string, WebGLUniformLocation | null> = {};\n public attribLocations: Record<string, number> = {};\n\n // Attributes\n public albedoColor: Vector4 = [1, 1, 1, 1];\n public unlit: boolean = false;\n public texture?: WebGLTexture;\n public specular: Vector3 = [0.3, 0.3, 0.3];\n public shininess: number = 64;\n public doubleSided: boolean = false;\n //others\n public ambientColor: Vector3 = [0.1, 0.1, 0.1]; // Ka\n public dissolve: number = 1.0; // d or Tr\n public diffuse: Vector3 = [1, 1, 1]; // Kd\n /** Physics friction coefficient [0–1]. 0 = no resistance (ice), 1 = instant stop. */\n public friction: number = 0.3;\n\n constructor(\n private webglCore: WebGLCore,\n options: Partial<Material> = {},\n ) {\n this.program = webglCore.getProgram();\n const { gl } = webglCore;\n\n // Cache uniforms\n const names = [\n 'uColor',\n 'uUnlit',\n 'uModel',\n 'uView',\n 'uProjection',\n 'uLightCount',\n 'uUseTexture',\n 'uTexture',\n // --- LIGHTING UNIFORMS ---\n 'uViewPosition', // Camera position for specular light\n 'uShininess',\n 'uSpecularColor',\n 'uAmbientColor',\n 'uDissolve',\n 'uDiffuseColor',\n 'uLightDirection[0]',\n 'uLightColor[0]',\n 'uLightIntensity[0]',\n 'uLightType[0]',\n 'uLightPosition[0]', // NEW: For Point Lights\n 'uLightConstant[0]', // NEW: Attenuation\n 'uLightLinear[0]', // NEW: Attenuation\n 'uLightQuadratic[0]', // NEW: Attenuation\n // --- SKINNING UNIFORMS ---\n 'uUseSkinning',\n 'uJointMatrices[0]',\n ];\n for (const name of names) {\n this.uniformLocations[name] = gl.getUniformLocation(this.program, name);\n }\n\n // Cache attributes\n const attribs = [\n 'aPosition',\n 'aNormal',\n 'aTexCoord',\n 'aJointIndices',\n 'aJointWeights',\n ];\n for (const name of attribs) {\n this.attribLocations[name] = gl.getAttribLocation(this.program, name);\n }\n\n Object.assign(this, options);\n }\n\n setColorHex(hex: string) {\n this.albedoColor = parseHexToRgbArray(hex);\n }\n\n setColor(rgba: [number, number, number, number]) {\n this.albedoColor = rgba;\n this.dissolve = rgba[3];\n this.diffuse = [rgba[0], rgba[1], rgba[2]];\n }\n\n /**\n * Load an image from URL and create a WebGL texture.\n */\n async loadTexture(url: string, options?: TextureOptions): Promise<void> {\n const { gl } = this.webglCore;\n this.texture = await loadWebGlTexture(gl, url, options);\n }\n\n /**\n * Create an independent copy of this material.\n * Shares the same WebGL program but copies all mutable properties.\n * The texture reference is shared (immutable GPU resource).\n */\n clone(): Material {\n const copy = new Material(this.webglCore);\n copy.albedoColor = [...this.albedoColor];\n copy.specular = [...this.specular];\n copy.ambientColor = [...this.ambientColor];\n copy.diffuse = [...this.diffuse];\n copy.shininess = this.shininess;\n copy.dissolve = this.dissolve;\n copy.unlit = this.unlit;\n copy.doubleSided = this.doubleSided;\n copy.friction = this.friction;\n copy.texture = this.texture; // shared GPU resource\n return copy;\n }\n\n apply(gl: WebGLRenderingContext, lights: Light[], viewPosition?: Vector3) {\n if (this.uniformLocations['uColor'])\n gl.uniform4fv(this.uniformLocations['uColor'], this.albedoColor);\n\n if (this.uniformLocations['uUnlit'])\n gl.uniform1i(this.uniformLocations['uUnlit'], this.unlit ? 1 : 0);\n\n // --- NEW MATERIAL UNIFORMS ---\n if (this.uniformLocations['uShininess'])\n gl.uniform1f(this.uniformLocations['uShininess'], this.shininess);\n\n if (this.uniformLocations['uSpecularColor'])\n gl.uniform3fv(this.uniformLocations['uSpecularColor'], this.specular);\n\n if (this.uniformLocations['uDissolve'])\n gl.uniform1f(this.uniformLocations['uDissolve'], this.dissolve);\n\n if (this.uniformLocations['uAmbientColor'])\n gl.uniform3fv(this.uniformLocations['uAmbientColor'], this.ambientColor);\n\n if (this.uniformLocations['uDiffuseColor'])\n gl.uniform3fv(this.uniformLocations['uDiffuseColor'], this.diffuse);\n\n // --- NEW CAMERA UNIFORM FOR SPECULAR HIGHLIGHTS ---\n if (viewPosition && this.uniformLocations['uViewPosition']) {\n gl.uniform3fv(this.uniformLocations['uViewPosition'], viewPosition);\n }\n\n // Texture\n const hasTexture = !!this.texture;\n if (this.uniformLocations['uUseTexture'])\n gl.uniform1i(this.uniformLocations['uUseTexture'], hasTexture ? 1 : 0);\n\n if (hasTexture && this.texture) {\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.texture);\n if (this.uniformLocations['uTexture'])\n gl.uniform1i(this.uniformLocations['uTexture'], 0);\n }\n\n // Lights (unchanged)\n const MAX = MAX_LIGHTS;\n if (!this.unlit && lights?.length) {\n const count = Math.min(lights.length, MAX);\n\n if (this.uniformLocations['uLightCount'])\n gl.uniform1i(this.uniformLocations['uLightCount'], count);\n\n const dirs: number[] = [];\n const colors: number[] = [];\n const intensities: number[] = [];\n const types: number[] = [];\n const positions: number[] = [];\n const constants: number[] = [];\n const linears: number[] = [];\n const quadratics: number[] = [];\n\n for (let i = 0; i < count; i++) {\n const light = lights[i];\n\n let typeInt = 0;\n if (light.type === 'point') typeInt = 1;\n else if (light.type === 'ambient') typeInt = 2;\n\n types.push(typeInt);\n\n dirs.push(...(light.direction ?? [0, 0, 0]));\n positions.push(...(light.position ?? [0, 0, 0]));\n colors.push(...light.color);\n intensities.push(light.intensity);\n constants.push(light.constant);\n linears.push(light.linear);\n quadratics.push(light.quadratic);\n }\n\n if (this.uniformLocations['uLightDirection[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightDirection[0]'],\n new Float32Array(dirs),\n );\n if (this.uniformLocations['uLightColor[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightColor[0]'],\n new Float32Array(colors),\n );\n if (this.uniformLocations['uLightIntensity[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightIntensity[0]'],\n new Float32Array(intensities),\n );\n if (this.uniformLocations['uLightType[0]'])\n gl.uniform1iv(\n this.uniformLocations['uLightType[0]'],\n new Int32Array(types),\n );\n if (this.uniformLocations['uLightPosition[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightPosition[0]'],\n new Float32Array(positions),\n );\n if (this.uniformLocations['uLightConstant[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightConstant[0]'],\n new Float32Array(constants),\n );\n if (this.uniformLocations['uLightLinear[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightLinear[0]'],\n new Float32Array(linears),\n );\n if (this.uniformLocations['uLightQuadratic[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightQuadratic[0]'],\n new Float32Array(quadratics),\n );\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// src/Core/classes/Model.ts
|
|
2
|
+
import { mat4 as mat42 } from "gl-matrix";
|
|
3
|
+
|
|
4
|
+
// src/Core/domain/value-objects/Collider.ts
|
|
5
|
+
var Collider = class {
|
|
6
|
+
type;
|
|
7
|
+
offset;
|
|
8
|
+
size;
|
|
9
|
+
constructor(type, offset = [0, 0, 0], size = [1, 1, 1]) {
|
|
10
|
+
this.type = type;
|
|
11
|
+
this.offset = offset;
|
|
12
|
+
this.size = size;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/Core/utils/gltf-utils.ts
|
|
17
|
+
import { vec3 } from "gl-matrix";
|
|
18
|
+
function transformPoint(out, m, p) {
|
|
19
|
+
const v = vec3.fromValues(p[0], p[1], p[2]);
|
|
20
|
+
vec3.transformMat4(v, v, m);
|
|
21
|
+
out[0] = v[0];
|
|
22
|
+
out[1] = v[1];
|
|
23
|
+
out[2] = v[2];
|
|
24
|
+
}
|
|
25
|
+
function transformAABB(min, max, m) {
|
|
26
|
+
const corners = [
|
|
27
|
+
[min[0], min[1], min[2]],
|
|
28
|
+
[min[0], min[1], max[2]],
|
|
29
|
+
[min[0], max[1], min[2]],
|
|
30
|
+
[min[0], max[1], max[2]],
|
|
31
|
+
[max[0], min[1], min[2]],
|
|
32
|
+
[max[0], min[1], max[2]],
|
|
33
|
+
[max[0], max[1], min[2]],
|
|
34
|
+
[max[0], max[1], max[2]]
|
|
35
|
+
];
|
|
36
|
+
const outMin = [Infinity, Infinity, Infinity];
|
|
37
|
+
const outMax = [-Infinity, -Infinity, -Infinity];
|
|
38
|
+
const t = [0, 0, 0];
|
|
39
|
+
for (const c of corners) {
|
|
40
|
+
transformPoint(t, m, c);
|
|
41
|
+
for (let i = 0; i < 3; i++) {
|
|
42
|
+
outMin[i] = Math.min(outMin[i], t[i]);
|
|
43
|
+
outMax[i] = Math.max(outMax[i], t[i]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { min: outMin, max: outMax };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/Core/classes/Model.ts
|
|
50
|
+
var Model = class _Model {
|
|
51
|
+
name;
|
|
52
|
+
meshes;
|
|
53
|
+
collider;
|
|
54
|
+
translation;
|
|
55
|
+
rotation;
|
|
56
|
+
scale;
|
|
57
|
+
boundingBox;
|
|
58
|
+
// ── Skeletal animation ──────────────────────────────────────
|
|
59
|
+
/** The skeleton driving skinned meshes (set after GLB load if present). */
|
|
60
|
+
skeleton = null;
|
|
61
|
+
/** Named animation clips available on this model. */
|
|
62
|
+
animations = /* @__PURE__ */ new Map();
|
|
63
|
+
/** The currently playing animation clip (if any). */
|
|
64
|
+
activeAnimation = null;
|
|
65
|
+
_boundsDefinedManually = false;
|
|
66
|
+
_modelMatrix = mat42.create();
|
|
67
|
+
_matrixDirty = true;
|
|
68
|
+
/** When `false` the model is excluded from collision detection. */
|
|
69
|
+
collidable = true;
|
|
70
|
+
constructor(meshes, translation = [0, 0, 0], scale = [1, 1, 1], _name = "model", skeleton = null) {
|
|
71
|
+
this.meshes = meshes;
|
|
72
|
+
this.translation = translation;
|
|
73
|
+
this.scale = scale;
|
|
74
|
+
this.rotation = [0, 0, 0];
|
|
75
|
+
this.collider = new Collider("box");
|
|
76
|
+
this.name = _name;
|
|
77
|
+
this.skeleton = skeleton;
|
|
78
|
+
this.boundingBox = this.computeBoundingBox();
|
|
79
|
+
this.translation[1] -= this.boundingBox.min[1];
|
|
80
|
+
this._matrixDirty = true;
|
|
81
|
+
this.updateBoundingBox();
|
|
82
|
+
}
|
|
83
|
+
// ─────────────────────────────────────────────
|
|
84
|
+
// ░░░ Transform API ░░░
|
|
85
|
+
// ─────────────────────────────────────────────
|
|
86
|
+
/** Set absolute position. Marks matrix dirty and updates bounding box. */
|
|
87
|
+
setTranslation(x, y, z) {
|
|
88
|
+
this.translation = [x, y, z];
|
|
89
|
+
this._matrixDirty = true;
|
|
90
|
+
this.updateBoundingBox();
|
|
91
|
+
}
|
|
92
|
+
/** Set absolute rotation (radians per axis). */
|
|
93
|
+
setRotation(x, y, z) {
|
|
94
|
+
this.rotation = [x, y, z];
|
|
95
|
+
this._matrixDirty = true;
|
|
96
|
+
this.updateBoundingBox();
|
|
97
|
+
}
|
|
98
|
+
/** Set absolute scale. */
|
|
99
|
+
setScale(x, y, z) {
|
|
100
|
+
this.scale = [x, y, z];
|
|
101
|
+
this._matrixDirty = true;
|
|
102
|
+
this.updateBoundingBox();
|
|
103
|
+
}
|
|
104
|
+
/** Translate the model by a delta. */
|
|
105
|
+
move(dx, dy, dz = 0) {
|
|
106
|
+
this.translation[0] += dx;
|
|
107
|
+
this.translation[1] += dy;
|
|
108
|
+
this.translation[2] += dz;
|
|
109
|
+
this._matrixDirty = true;
|
|
110
|
+
this.updateBoundingBox();
|
|
111
|
+
}
|
|
112
|
+
// ─────────────────────────────────────────────
|
|
113
|
+
// ░░░ Matrix Handling ░░░
|
|
114
|
+
// ─────────────────────────────────────────────
|
|
115
|
+
getModelMatrix() {
|
|
116
|
+
if (this._matrixDirty) {
|
|
117
|
+
mat42.identity(this._modelMatrix);
|
|
118
|
+
mat42.translate(this._modelMatrix, this._modelMatrix, this.translation);
|
|
119
|
+
mat42.rotateX(this._modelMatrix, this._modelMatrix, this.rotation[0]);
|
|
120
|
+
mat42.rotateY(this._modelMatrix, this._modelMatrix, this.rotation[1]);
|
|
121
|
+
mat42.rotateZ(this._modelMatrix, this._modelMatrix, this.rotation[2]);
|
|
122
|
+
mat42.scale(this._modelMatrix, this._modelMatrix, this.scale);
|
|
123
|
+
this._matrixDirty = false;
|
|
124
|
+
}
|
|
125
|
+
return this._modelMatrix;
|
|
126
|
+
}
|
|
127
|
+
// ─────────────────────────────────────────────
|
|
128
|
+
// ░░░ Bounding Box & Collider ░░░
|
|
129
|
+
// ─────────────────────────────────────────────
|
|
130
|
+
/**
|
|
131
|
+
* Manually define the collision bounding box relative to the model's
|
|
132
|
+
* current translation.
|
|
133
|
+
*/
|
|
134
|
+
setBounds(xOffset, yOffset, zOffset, width, height, depth) {
|
|
135
|
+
this.boundingBox.min = [
|
|
136
|
+
this.translation[0] + xOffset,
|
|
137
|
+
this.translation[1] + yOffset,
|
|
138
|
+
this.translation[2] + zOffset
|
|
139
|
+
];
|
|
140
|
+
this.boundingBox.max = [
|
|
141
|
+
this.boundingBox.min[0] + width,
|
|
142
|
+
this.boundingBox.min[1] + height,
|
|
143
|
+
this.boundingBox.min[2] + depth
|
|
144
|
+
];
|
|
145
|
+
this._boundsDefinedManually = true;
|
|
146
|
+
}
|
|
147
|
+
computeBoundingBox() {
|
|
148
|
+
if (this.meshes.length === 0) {
|
|
149
|
+
return { min: [...this.translation], max: [...this.translation] };
|
|
150
|
+
}
|
|
151
|
+
const modelMat = this.getModelMatrix();
|
|
152
|
+
let effectiveMat = modelMat;
|
|
153
|
+
if (this.skeleton) {
|
|
154
|
+
effectiveMat = mat42.create();
|
|
155
|
+
mat42.multiply(effectiveMat, modelMat, this.skeleton.rootTransform);
|
|
156
|
+
}
|
|
157
|
+
const min = [Infinity, Infinity, Infinity];
|
|
158
|
+
const max = [-Infinity, -Infinity, -Infinity];
|
|
159
|
+
for (const mesh of this.meshes) {
|
|
160
|
+
if (!mesh.isCollidable) continue;
|
|
161
|
+
mesh.computeBounds();
|
|
162
|
+
const mb = mesh.boundingBox;
|
|
163
|
+
const isDegenerate = mb.min[0] === mb.max[0] && mb.min[1] === mb.max[1] && mb.min[2] === mb.max[2];
|
|
164
|
+
if (isDegenerate) continue;
|
|
165
|
+
const transformed = transformAABB(
|
|
166
|
+
mesh.boundingBox.min,
|
|
167
|
+
mesh.boundingBox.max,
|
|
168
|
+
effectiveMat
|
|
169
|
+
);
|
|
170
|
+
for (let i = 0; i < 3; i++) {
|
|
171
|
+
min[i] = Math.min(min[i], transformed.min[i]);
|
|
172
|
+
max[i] = Math.max(max[i], transformed.max[i]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!isFinite(min[0])) {
|
|
176
|
+
const t = effectiveMat;
|
|
177
|
+
const tx = t[12];
|
|
178
|
+
const ty = t[13];
|
|
179
|
+
const tz = t[14];
|
|
180
|
+
return {
|
|
181
|
+
min: [tx - 0.5, ty, tz - 0.5],
|
|
182
|
+
max: [tx + 0.5, ty + 1, tz + 0.5]
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return { min, max };
|
|
186
|
+
}
|
|
187
|
+
/** Recompute the world-space AABB from mesh data (unless manually set). */
|
|
188
|
+
updateBoundingBox() {
|
|
189
|
+
if (this._boundsDefinedManually) return;
|
|
190
|
+
this.boundingBox = this.computeBoundingBox();
|
|
191
|
+
}
|
|
192
|
+
/** World-space centre of the bounding box. */
|
|
193
|
+
getCenter() {
|
|
194
|
+
const { min, max } = this.boundingBox;
|
|
195
|
+
return [
|
|
196
|
+
(min[0] + max[0]) / 2,
|
|
197
|
+
(min[1] + max[1]) / 2,
|
|
198
|
+
(min[2] + max[2]) / 2
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
/** Extents of the bounding box in each axis. */
|
|
202
|
+
getSize() {
|
|
203
|
+
const { min, max } = this.boundingBox;
|
|
204
|
+
return [max[0] - min[0], max[1] - min[1], max[2] - min[2]];
|
|
205
|
+
}
|
|
206
|
+
/** Test for intersection with another model using their colliders. */
|
|
207
|
+
intersects(other) {
|
|
208
|
+
const a = this.collider;
|
|
209
|
+
const b = other.collider;
|
|
210
|
+
if (a.type !== "box" || b.type !== "box") {
|
|
211
|
+
console.warn(
|
|
212
|
+
`[Model] Collider types '${a.type}' / '${b.type}' not yet implemented \u2014 falling back to AABB.`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return this.intersectAABB(other);
|
|
216
|
+
}
|
|
217
|
+
intersectAABB(other) {
|
|
218
|
+
const a = this.boundingBox;
|
|
219
|
+
const b = other.boundingBox;
|
|
220
|
+
return !(a.max[0] < b.min[0] || a.min[0] > b.max[0] || a.max[1] < b.min[1] || a.min[1] > b.max[1] || a.max[2] < b.min[2] || a.min[2] > b.max[2]);
|
|
221
|
+
}
|
|
222
|
+
// ─────────────────────────────────────────────
|
|
223
|
+
// ░░░ Resource Management ░░░
|
|
224
|
+
// ─────────────────────────────────────────────
|
|
225
|
+
/** Release all GPU resources held by this model's meshes. */
|
|
226
|
+
dispose(glCore) {
|
|
227
|
+
for (const mesh of this.meshes) {
|
|
228
|
+
mesh.dispose?.(glCore);
|
|
229
|
+
}
|
|
230
|
+
this.meshes = [];
|
|
231
|
+
}
|
|
232
|
+
// ─────────────────────────────────────────────
|
|
233
|
+
// ░░░ Cloning ░░░
|
|
234
|
+
// ─────────────────────────────────────────────
|
|
235
|
+
/** Deep-clone the model including all meshes. Materials are shared. */
|
|
236
|
+
clone() {
|
|
237
|
+
const clonedMeshes = this.meshes.map((m) => m.clone ? m.clone() : m);
|
|
238
|
+
const c = new _Model(
|
|
239
|
+
clonedMeshes,
|
|
240
|
+
[...this.translation],
|
|
241
|
+
[...this.scale],
|
|
242
|
+
this.name,
|
|
243
|
+
this.skeleton?.clone() ?? null
|
|
244
|
+
);
|
|
245
|
+
c.rotation = [...this.rotation];
|
|
246
|
+
c.collider = new Collider(this.collider.type);
|
|
247
|
+
c.boundingBox = {
|
|
248
|
+
min: [...this.boundingBox.min],
|
|
249
|
+
max: [...this.boundingBox.max]
|
|
250
|
+
};
|
|
251
|
+
for (const [name, clip] of this.animations) {
|
|
252
|
+
c.animations.set(name, clip.clone());
|
|
253
|
+
}
|
|
254
|
+
return c;
|
|
255
|
+
}
|
|
256
|
+
// ─────────────────────────────────────────────
|
|
257
|
+
// ░░░ Animation ░░░
|
|
258
|
+
// ─────────────────────────────────────────────
|
|
259
|
+
/**
|
|
260
|
+
* Start playing a named animation clip.
|
|
261
|
+
* @param name - Must match a key in {@link animations}.
|
|
262
|
+
* @param loop - Whether the clip should loop (default `true`).
|
|
263
|
+
*/
|
|
264
|
+
playAnimation(name, loop = true) {
|
|
265
|
+
const clip = this.animations.get(name);
|
|
266
|
+
if (!clip) {
|
|
267
|
+
console.warn(`[Model:${this.name}] Animation "${name}" not found`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
this.activeAnimation = clip;
|
|
271
|
+
clip.loop = loop;
|
|
272
|
+
clip.reset();
|
|
273
|
+
clip.play();
|
|
274
|
+
}
|
|
275
|
+
/** Pause the active animation without rewinding. */
|
|
276
|
+
pauseAnimation() {
|
|
277
|
+
this.activeAnimation?.pause();
|
|
278
|
+
}
|
|
279
|
+
/** Stop the active animation and rewind. */
|
|
280
|
+
stopAnimation() {
|
|
281
|
+
if (this.activeAnimation) {
|
|
282
|
+
this.activeAnimation.stop();
|
|
283
|
+
this.activeAnimation = null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/** List the names of all available animations. */
|
|
287
|
+
getAnimationNames() {
|
|
288
|
+
return [...this.animations.keys()];
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Advance animation and recompute joint matrices.
|
|
292
|
+
* Call this every frame from your game loop.
|
|
293
|
+
*
|
|
294
|
+
* @param deltaTime - Elapsed time in seconds since the last frame.
|
|
295
|
+
*/
|
|
296
|
+
update(deltaTime) {
|
|
297
|
+
if (!this.activeAnimation || !this.skeleton) return;
|
|
298
|
+
this.activeAnimation.update(deltaTime);
|
|
299
|
+
const poses = this.activeAnimation.sample();
|
|
300
|
+
const merged = /* @__PURE__ */ new Map();
|
|
301
|
+
for (let i = 0; i < this.skeleton.joints.length; i++) {
|
|
302
|
+
const joint = this.skeleton.joints[i];
|
|
303
|
+
const pose = poses.get(i);
|
|
304
|
+
merged.set(i, {
|
|
305
|
+
t: pose?.t ?? joint.localTranslation,
|
|
306
|
+
r: pose?.r ?? joint.localRotation,
|
|
307
|
+
s: pose?.s ?? joint.localScale
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
this.skeleton.computeJointMatrices(merged);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
export {
|
|
315
|
+
Model
|
|
316
|
+
};
|
|
317
|
+
//# sourceMappingURL=chunk-JK2HEZAT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Core/classes/Model.ts","../src/Core/domain/value-objects/Collider.ts","../src/Core/utils/gltf-utils.ts"],"sourcesContent":["// classes/Model.ts\nimport { mat4 } from 'gl-matrix';\n\nimport { AABB, Collider } from '../domain/value-objects/Collider';\nimport { transformAABB } from '../utils/gltf-utils';\nimport { Vector3 } from '../domain/interfaces/Vectors';\nimport { Mesh } from './Mesh';\nimport { Skeleton } from './Skeleton';\nimport { AnimationClip } from './AnimationClip';\nimport WebGLCore from './WebGLCore';\n\n/**\n * A high-level renderable object composed of one or more {@link Mesh}es.\n * Provides transform, bounding-box, AABB collision, and optional skeletal\n * animation utilities.\n */\nexport class Model {\n public name: string;\n public meshes: Mesh[];\n public collider: Collider;\n public translation: Vector3;\n public rotation: Vector3;\n public scale: Vector3;\n public boundingBox: AABB;\n\n // ── Skeletal animation ──────────────────────────────────────\n /** The skeleton driving skinned meshes (set after GLB load if present). */\n public skeleton: Skeleton | null = null;\n\n /** Named animation clips available on this model. */\n public animations: Map<string, AnimationClip> = new Map();\n\n /** The currently playing animation clip (if any). */\n public activeAnimation: AnimationClip | null = null;\n\n private _boundsDefinedManually = false;\n private _modelMatrix: mat4 = mat4.create();\n private _matrixDirty = true;\n\n /** When `false` the model is excluded from collision detection. */\n public collidable = true;\n\n constructor(\n meshes: Mesh[],\n translation: Vector3 = [0, 0, 0],\n scale: Vector3 = [1, 1, 1],\n _name = 'model',\n skeleton: Skeleton | null = null,\n ) {\n this.meshes = meshes;\n this.translation = translation;\n this.scale = scale;\n this.rotation = [0, 0, 0];\n this.collider = new Collider('box');\n this.name = _name;\n this.skeleton = skeleton;\n this.boundingBox = this.computeBoundingBox();\n\n // Align bottom to y=0 — must dirty the matrix since we mutate translation directly\n this.translation[1] -= this.boundingBox.min[1];\n this._matrixDirty = true;\n this.updateBoundingBox();\n }\n\n // ─────────────────────────────────────────────\n // ░░░ Transform API ░░░\n // ─────────────────────────────────────────────\n /** Set absolute position. Marks matrix dirty and updates bounding box. */\n public setTranslation(x: number, y: number, z: number) {\n this.translation = [x, y, z];\n this._matrixDirty = true;\n this.updateBoundingBox();\n }\n\n /** Set absolute rotation (radians per axis). */\n public setRotation(x: number, y: number, z: number) {\n this.rotation = [x, y, z];\n this._matrixDirty = true;\n this.updateBoundingBox();\n }\n\n /** Set absolute scale. */\n public setScale(x: number, y: number, z: number) {\n this.scale = [x, y, z];\n this._matrixDirty = true;\n this.updateBoundingBox();\n }\n\n /** Translate the model by a delta. */\n public move(dx: number, dy: number, dz: number = 0) {\n this.translation[0] += dx;\n this.translation[1] += dy;\n this.translation[2] += dz;\n this._matrixDirty = true;\n this.updateBoundingBox();\n }\n\n // ─────────────────────────────────────────────\n // ░░░ Matrix Handling ░░░\n // ─────────────────────────────────────────────\n private getModelMatrix(): mat4 {\n if (this._matrixDirty) {\n mat4.identity(this._modelMatrix);\n mat4.translate(this._modelMatrix, this._modelMatrix, this.translation);\n mat4.rotateX(this._modelMatrix, this._modelMatrix, this.rotation[0]);\n mat4.rotateY(this._modelMatrix, this._modelMatrix, this.rotation[1]);\n mat4.rotateZ(this._modelMatrix, this._modelMatrix, this.rotation[2]);\n mat4.scale(this._modelMatrix, this._modelMatrix, this.scale);\n this._matrixDirty = false;\n }\n return this._modelMatrix;\n }\n\n // ─────────────────────────────────────────────\n // ░░░ Bounding Box & Collider ░░░\n // ─────────────────────────────────────────────\n /**\n * Manually define the collision bounding box relative to the model's\n * current translation.\n */\n public setBounds(\n xOffset: number,\n yOffset: number,\n zOffset: number,\n width: number,\n height: number,\n depth: number,\n ) {\n this.boundingBox.min = [\n this.translation[0] + xOffset,\n this.translation[1] + yOffset,\n this.translation[2] + zOffset,\n ];\n this.boundingBox.max = [\n this.boundingBox.min[0] + width,\n this.boundingBox.min[1] + height,\n this.boundingBox.min[2] + depth,\n ];\n this._boundsDefinedManually = true;\n }\n\n private computeBoundingBox(): AABB {\n if (this.meshes.length === 0) {\n return { min: [...this.translation], max: [...this.translation] };\n }\n\n const modelMat = this.getModelMatrix();\n\n // For skinned models the shader applies: uModel * skinMat * vertex.\n // In bind pose skinMat ≈ skeleton.rootTransform (the non-joint ancestor\n // transform that many exporters don't bake into the IBM). We must\n // include it so the bbox matches what the GPU actually renders.\n let effectiveMat = modelMat;\n if (this.skeleton) {\n effectiveMat = mat4.create();\n mat4.multiply(effectiveMat, modelMat, this.skeleton.rootTransform);\n }\n\n const min: Vector3 = [Infinity, Infinity, Infinity];\n const max: Vector3 = [-Infinity, -Infinity, -Infinity];\n\n for (const mesh of this.meshes) {\n if (!mesh.isCollidable) continue;\n\n mesh.computeBounds();\n\n // Skip degenerate meshes (helper objects, IK controls, etc. that have\n // all vertices at the local origin — they would pull the bbox to include\n // the world-space origin even when the model is far away).\n const mb = mesh.boundingBox;\n const isDegenerate =\n mb.min[0] === mb.max[0] &&\n mb.min[1] === mb.max[1] &&\n mb.min[2] === mb.max[2];\n if (isDegenerate) continue;\n\n const transformed = transformAABB(\n mesh.boundingBox.min,\n mesh.boundingBox.max,\n effectiveMat,\n );\n\n for (let i = 0; i < 3; i++) {\n min[i] = Math.min(min[i], transformed.min[i]);\n max[i] = Math.max(max[i], transformed.max[i]);\n }\n }\n\n // No collidable mesh contributed — return a unit box at the model's position\n // to avoid propagating Infinity/NaN into translation.\n if (!isFinite(min[0])) {\n const t = effectiveMat;\n const tx = t[12];\n const ty = t[13];\n const tz = t[14];\n return {\n min: [tx - 0.5, ty, tz - 0.5],\n max: [tx + 0.5, ty + 1, tz + 0.5],\n };\n }\n\n return { min, max };\n }\n\n /** Recompute the world-space AABB from mesh data (unless manually set). */\n public updateBoundingBox() {\n if (this._boundsDefinedManually) return;\n this.boundingBox = this.computeBoundingBox();\n }\n\n /** World-space centre of the bounding box. */\n public getCenter(): Vector3 {\n const { min, max } = this.boundingBox;\n return [\n (min[0] + max[0]) / 2,\n (min[1] + max[1]) / 2,\n (min[2] + max[2]) / 2,\n ];\n }\n\n /** Extents of the bounding box in each axis. */\n public getSize(): Vector3 {\n const { min, max } = this.boundingBox;\n return [max[0] - min[0], max[1] - min[1], max[2] - min[2]];\n }\n\n /** Test for intersection with another model using their colliders. */\n public intersects(other: Model): boolean {\n const a = this.collider;\n const b = other.collider;\n\n if (a.type !== 'box' || b.type !== 'box') {\n console.warn(\n `[Model] Collider types '${a.type}' / '${b.type}' not yet implemented — falling back to AABB.`,\n );\n }\n\n return this.intersectAABB(other);\n }\n\n private intersectAABB(other: Model): boolean {\n const a = this.boundingBox;\n const b = other.boundingBox;\n return !(\n a.max[0] < b.min[0] ||\n a.min[0] > b.max[0] ||\n a.max[1] < b.min[1] ||\n a.min[1] > b.max[1] ||\n a.max[2] < b.min[2] ||\n a.min[2] > b.max[2]\n );\n }\n\n // ─────────────────────────────────────────────\n // ░░░ Resource Management ░░░\n // ─────────────────────────────────────────────\n /** Release all GPU resources held by this model's meshes. */\n public dispose(glCore: WebGLCore) {\n for (const mesh of this.meshes) {\n mesh.dispose?.(glCore);\n }\n this.meshes = [];\n }\n\n // ─────────────────────────────────────────────\n // ░░░ Cloning ░░░\n // ─────────────────────────────────────────────\n /** Deep-clone the model including all meshes. Materials are shared. */\n public clone(): Model {\n const clonedMeshes = this.meshes.map((m) => (m.clone ? m.clone() : m));\n const c = new Model(\n clonedMeshes,\n [...this.translation],\n [...this.scale],\n this.name,\n this.skeleton?.clone() ?? null,\n );\n c.rotation = [...this.rotation];\n c.collider = new Collider(this.collider.type);\n c.boundingBox = {\n min: [...this.boundingBox.min],\n max: [...this.boundingBox.max],\n };\n for (const [name, clip] of this.animations) {\n c.animations.set(name, clip.clone());\n }\n return c;\n }\n\n // ─────────────────────────────────────────────\n // ░░░ Animation ░░░\n // ─────────────────────────────────────────────\n\n /**\n * Start playing a named animation clip.\n * @param name - Must match a key in {@link animations}.\n * @param loop - Whether the clip should loop (default `true`).\n */\n public playAnimation(name: string, loop = true): void {\n const clip = this.animations.get(name);\n if (!clip) {\n console.warn(`[Model:${this.name}] Animation \"${name}\" not found`);\n return;\n }\n this.activeAnimation = clip;\n clip.loop = loop;\n clip.reset();\n clip.play();\n }\n\n /** Pause the active animation without rewinding. */\n public pauseAnimation(): void {\n this.activeAnimation?.pause();\n }\n\n /** Stop the active animation and rewind. */\n public stopAnimation(): void {\n if (this.activeAnimation) {\n this.activeAnimation.stop();\n this.activeAnimation = null;\n }\n }\n\n /** List the names of all available animations. */\n public getAnimationNames(): string[] {\n return [...this.animations.keys()];\n }\n\n /**\n * Advance animation and recompute joint matrices.\n * Call this every frame from your game loop.\n *\n * @param deltaTime - Elapsed time in seconds since the last frame.\n */\n public update(deltaTime: number) {\n if (!this.activeAnimation || !this.skeleton) return;\n\n this.activeAnimation.update(deltaTime);\n const poses = this.activeAnimation.sample();\n\n // Merge with bind-pose defaults\n const merged = new Map<\n number,\n {\n t: import('gl-matrix').vec3;\n r: import('gl-matrix').quat;\n s: import('gl-matrix').vec3;\n }\n >();\n for (let i = 0; i < this.skeleton.joints.length; i++) {\n const joint = this.skeleton.joints[i];\n const pose = poses.get(i);\n merged.set(i, {\n t: pose?.t ?? joint.localTranslation,\n r: pose?.r ?? joint.localRotation,\n s: pose?.s ?? joint.localScale,\n });\n }\n\n this.skeleton.computeJointMatrices(merged);\n }\n}\n","import { Vector3 } from '../interfaces/Vectors';\n\nexport interface AABB {\n min: Vector3;\n max: Vector3;\n}\n\nexport type ColliderType = 'box' | 'sphere' | 'capsule';\nexport class Collider {\n type: ColliderType;\n offset: Vector3;\n size: Vector3;\n\n constructor(\n type: ColliderType,\n offset: Vector3 = [0, 0, 0],\n size: Vector3 = [1, 1, 1],\n ) {\n this.type = type;\n this.offset = offset;\n this.size = size;\n }\n}\n","// utils/gltf-utils.ts\nimport { mat4, vec3 } from 'gl-matrix';\n\n/** Transforma um ponto (vec3) por uma mat4 */\nexport function transformPoint(\n out: [number, number, number],\n m: mat4,\n p: [number, number, number],\n) {\n const v = vec3.fromValues(p[0], p[1], p[2]);\n vec3.transformMat4(v, v, m);\n out[0] = v[0];\n out[1] = v[1];\n out[2] = v[2];\n}\n\n/** Recebe uma AABB local (min,max) e uma mat4 e retorna AABB transformada */\nexport function transformAABB(\n min: [number, number, number],\n max: [number, number, number],\n m: mat4,\n) {\n // calcula os 8 cantos, transforma, e retorna new min/max\n const corners: [number, number, number][] = [\n [min[0], min[1], min[2]],\n [min[0], min[1], max[2]],\n [min[0], max[1], min[2]],\n [min[0], max[1], max[2]],\n [max[0], min[1], min[2]],\n [max[0], min[1], max[2]],\n [max[0], max[1], min[2]],\n [max[0], max[1], max[2]],\n ];\n const outMin: [number, number, number] = [Infinity, Infinity, Infinity];\n const outMax: [number, number, number] = [-Infinity, -Infinity, -Infinity];\n const t: [number, number, number] = [0, 0, 0];\n for (const c of corners) {\n transformPoint(t, m, c);\n for (let i = 0; i < 3; i++) {\n outMin[i] = Math.min(outMin[i], t[i]);\n outMax[i] = Math.max(outMax[i], t[i]);\n }\n }\n return { min: outMin, max: outMax };\n}\n"],"mappings":"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","names":["mat4","mat4"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/Game/controls/KeyboardInput.ts
|
|
2
|
+
var KeyboardInput = class {
|
|
3
|
+
constructor(control, cb) {
|
|
4
|
+
this.control = control;
|
|
5
|
+
this.cb = cb;
|
|
6
|
+
control.enable();
|
|
7
|
+
}
|
|
8
|
+
control;
|
|
9
|
+
cb;
|
|
10
|
+
keysPressed = /* @__PURE__ */ new Set();
|
|
11
|
+
rollRequested = false;
|
|
12
|
+
jumpRequested = false;
|
|
13
|
+
/** Wire up key listeners. Call once after constructing. */
|
|
14
|
+
bind() {
|
|
15
|
+
this.control.onChange((key, isPressed) => {
|
|
16
|
+
if (isPressed) {
|
|
17
|
+
this.keysPressed.add(key);
|
|
18
|
+
if (key === "jump") this.jumpRequested = true;
|
|
19
|
+
if (key === "roll") this.rollRequested = true;
|
|
20
|
+
} else {
|
|
21
|
+
this.keysPressed.delete(key);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Called once per fixed-update from the game loop.
|
|
27
|
+
* Reads currently pressed keys and drives the character.
|
|
28
|
+
*/
|
|
29
|
+
applyInput(character, camera) {
|
|
30
|
+
let fwd = 0;
|
|
31
|
+
let right = 0;
|
|
32
|
+
if (this.keysPressed.has("up")) fwd -= 1;
|
|
33
|
+
if (this.keysPressed.has("down")) fwd += 1;
|
|
34
|
+
if (this.keysPressed.has("left")) right -= 1;
|
|
35
|
+
if (this.keysPressed.has("right")) right += 1;
|
|
36
|
+
character.isInputActive = fwd !== 0 || right !== 0;
|
|
37
|
+
if (!character.isRolling && (fwd !== 0 || right !== 0)) {
|
|
38
|
+
character.moveRelative(fwd, right, camera);
|
|
39
|
+
}
|
|
40
|
+
if (this.jumpRequested) {
|
|
41
|
+
character.jump();
|
|
42
|
+
this.jumpRequested = false;
|
|
43
|
+
}
|
|
44
|
+
if (this.rollRequested) {
|
|
45
|
+
character.roll();
|
|
46
|
+
this.rollRequested = false;
|
|
47
|
+
}
|
|
48
|
+
if (this.keysPressed.size > 0) {
|
|
49
|
+
this.cb?.(character.translation);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
KeyboardInput
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=chunk-P7QOKDLY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Game/controls/KeyboardInput.ts"],"sourcesContent":["import { Camera } from '../../Core/classes';\nimport { KeyboardControl } from '../../Core/controls/KeyboardControl';\nimport { Character } from '../classes/Character';\n\n/**\n * Thin adapter: forwards keyboard state into the Character.\n * Does NOT run its own requestAnimationFrame — the game loop polls\n * {@link applyInput} every fixed-update tick.\n */\nexport class KeyboardInput {\n private keysPressed = new Set<string>();\n private rollRequested = false;\n private jumpRequested = false;\n\n constructor(\n private control: KeyboardControl,\n private cb?: (pos: any) => void,\n ) {\n control.enable();\n }\n\n /** Wire up key listeners. Call once after constructing. */\n bind(): void {\n this.control.onChange((key, isPressed) => {\n if (isPressed) {\n this.keysPressed.add(key);\n // Buffer one-shot actions so a quick press isn't missed between ticks\n if (key === 'jump') this.jumpRequested = true;\n if (key === 'roll') this.rollRequested = true;\n } else {\n this.keysPressed.delete(key);\n }\n });\n }\n\n /**\n * Called once per fixed-update from the game loop.\n * Reads currently pressed keys and drives the character.\n */\n applyInput(character: Character, camera: Camera): void {\n // Always compute directional intent for animation (isInputActive),\n // but only drive movement when not rolling.\n let fwd = 0;\n let right = 0;\n if (this.keysPressed.has('up')) fwd -= 1;\n if (this.keysPressed.has('down')) fwd += 1;\n if (this.keysPressed.has('left')) right -= 1;\n if (this.keysPressed.has('right')) right += 1;\n\n character.isInputActive = fwd !== 0 || right !== 0;\n\n if (!character.isRolling && (fwd !== 0 || right !== 0)) {\n character.moveRelative(fwd, right, camera);\n }\n\n // One-shot actions — consume the buffer\n if (this.jumpRequested) {\n character.jump();\n this.jumpRequested = false;\n }\n if (this.rollRequested) {\n character.roll();\n this.rollRequested = false;\n }\n\n // Network callback\n if (this.keysPressed.size > 0) {\n this.cb?.(character.translation);\n }\n }\n}\n"],"mappings":";AASO,IAAM,gBAAN,MAAoB;AAAA,EAKzB,YACU,SACA,IACR;AAFQ;AACA;AAER,YAAQ,OAAO;AAAA,EACjB;AAAA,EAJU;AAAA,EACA;AAAA,EANF,cAAc,oBAAI,IAAY;AAAA,EAC9B,gBAAgB;AAAA,EAChB,gBAAgB;AAAA;AAAA,EAUxB,OAAa;AACX,SAAK,QAAQ,SAAS,CAAC,KAAK,cAAc;AACxC,UAAI,WAAW;AACb,aAAK,YAAY,IAAI,GAAG;AAExB,YAAI,QAAQ,OAAQ,MAAK,gBAAgB;AACzC,YAAI,QAAQ,OAAQ,MAAK,gBAAgB;AAAA,MAC3C,OAAO;AACL,aAAK,YAAY,OAAO,GAAG;AAAA,MAC7B;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW,WAAsB,QAAsB;AAGrD,QAAI,MAAM;AACV,QAAI,QAAQ;AACZ,QAAI,KAAK,YAAY,IAAI,IAAI,EAAG,QAAO;AACvC,QAAI,KAAK,YAAY,IAAI,MAAM,EAAG,QAAO;AACzC,QAAI,KAAK,YAAY,IAAI,MAAM,EAAG,UAAS;AAC3C,QAAI,KAAK,YAAY,IAAI,OAAO,EAAG,UAAS;AAE5C,cAAU,gBAAgB,QAAQ,KAAK,UAAU;AAEjD,QAAI,CAAC,UAAU,cAAc,QAAQ,KAAK,UAAU,IAAI;AACtD,gBAAU,aAAa,KAAK,OAAO,MAAM;AAAA,IAC3C;AAGA,QAAI,KAAK,eAAe;AACtB,gBAAU,KAAK;AACf,WAAK,gBAAgB;AAAA,IACvB;AACA,QAAI,KAAK,eAAe;AACtB,gBAAU,KAAK;AACf,WAAK,gBAAgB;AAAA,IACvB;AAGA,QAAI,KAAK,YAAY,OAAO,GAAG;AAC7B,WAAK,KAAK,UAAU,WAAW;AAAA,IACjC;AAAA,EACF;AACF;","names":[]}
|