@fonsecabarreto/genesis-gl-core 0.1.0 → 0.1.2
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/README.md +19 -2
- package/dist/{Camera-DY_8gx3C.d.ts → Camera-CJVYy9fH.d.ts} +13 -2
- package/dist/Core/classes/Material.d.ts +1 -1
- package/dist/Core/classes/Material.js +1 -1
- package/dist/Core/classes/Model.d.ts +3 -3
- package/dist/Core/classes/Model.js +1 -1
- package/dist/Core/classes/Renderer.d.ts +11 -5
- package/dist/Core/classes/Renderer.js +4 -4
- package/dist/Core/classes/Scene.d.ts +2 -2
- package/dist/Core/classes/Viewport.d.ts +1 -1
- package/dist/Core/classes/Viewport.js +1 -1
- package/dist/Core/index.d.ts +4 -4
- package/dist/Core/index.js +4 -4
- package/dist/Core/utils/load-glb.d.ts +3 -3
- package/dist/Core/utils/load-glb.js +4 -4
- package/dist/Core/utils/parse-obj.d.ts +2 -2
- package/dist/Core/utils/parse-obj.js +4 -4
- package/dist/Editor/index.d.ts +126 -15
- package/dist/Editor/index.js +471 -74
- package/dist/Editor/index.js.map +1 -1
- package/dist/Game/controls/KeyboardInput.d.ts +4 -4
- package/dist/Game/index.d.ts +308 -7
- package/dist/Game/index.js +470 -24
- package/dist/Game/index.js.map +1 -1
- package/dist/{KeyboardInput-DTsfj3tE.d.ts → KeyboardInput-1xOAabI0.d.ts} +95 -14
- package/dist/{Material-BGLkldxv.d.ts → Material-DhwSRbP2.d.ts} +8 -0
- package/dist/{Model-CQvDXd-b.d.ts → Model-BBZHnUp1.d.ts} +24 -8
- package/dist/{chunk-6LS6AO5H.js → chunk-L66K4AZU.js} +36 -30
- package/dist/chunk-L66K4AZU.js.map +1 -0
- package/dist/{chunk-JK2HEZAT.js → chunk-QOAQVTAB.js} +26 -22
- package/dist/chunk-QOAQVTAB.js.map +1 -0
- package/dist/{chunk-5TAAXI6S.js → chunk-XMW2MS66.js} +39 -16
- package/dist/chunk-XMW2MS66.js.map +1 -0
- package/dist/{chunk-QCQVJCSR.js → chunk-ZCJ3MJZD.js} +103 -67
- package/dist/chunk-ZCJ3MJZD.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-5TAAXI6S.js.map +0 -1
- package/dist/chunk-6LS6AO5H.js.map +0 -1
- package/dist/chunk-JK2HEZAT.js.map +0 -1
- package/dist/chunk-QCQVJCSR.js.map +0 -1
|
@@ -54,7 +54,6 @@ var Model = class _Model {
|
|
|
54
54
|
translation;
|
|
55
55
|
rotation;
|
|
56
56
|
scale;
|
|
57
|
-
boundingBox;
|
|
58
57
|
// ── Skeletal animation ──────────────────────────────────────
|
|
59
58
|
/** The skeleton driving skinned meshes (set after GLB load if present). */
|
|
60
59
|
skeleton = null;
|
|
@@ -62,9 +61,24 @@ var Model = class _Model {
|
|
|
62
61
|
animations = /* @__PURE__ */ new Map();
|
|
63
62
|
/** The currently playing animation clip (if any). */
|
|
64
63
|
activeAnimation = null;
|
|
64
|
+
_boundingBox = { min: [0, 0, 0], max: [0, 0, 0] };
|
|
65
|
+
_bboxDirty = false;
|
|
65
66
|
_boundsDefinedManually = false;
|
|
66
67
|
_modelMatrix = mat42.create();
|
|
67
68
|
_matrixDirty = true;
|
|
69
|
+
_poseMap = /* @__PURE__ */ new Map();
|
|
70
|
+
/** World-space AABB, lazily recomputed after any transform change. */
|
|
71
|
+
get boundingBox() {
|
|
72
|
+
if (this._bboxDirty && !this._boundsDefinedManually) {
|
|
73
|
+
this._boundingBox = this.computeBoundingBox();
|
|
74
|
+
this._bboxDirty = false;
|
|
75
|
+
}
|
|
76
|
+
return this._boundingBox;
|
|
77
|
+
}
|
|
78
|
+
set boundingBox(value) {
|
|
79
|
+
this._boundingBox = value;
|
|
80
|
+
this._bboxDirty = false;
|
|
81
|
+
}
|
|
68
82
|
/** When `false` the model is excluded from collision detection. */
|
|
69
83
|
collidable = true;
|
|
70
84
|
constructor(meshes, translation = [0, 0, 0], scale = [1, 1, 1], _name = "model", skeleton = null) {
|
|
@@ -75,31 +89,31 @@ var Model = class _Model {
|
|
|
75
89
|
this.collider = new Collider("box");
|
|
76
90
|
this.name = _name;
|
|
77
91
|
this.skeleton = skeleton;
|
|
78
|
-
this.
|
|
79
|
-
this.translation[1] -= this.
|
|
92
|
+
this._boundingBox = this.computeBoundingBox();
|
|
93
|
+
this.translation[1] -= this._boundingBox.min[1];
|
|
80
94
|
this._matrixDirty = true;
|
|
81
|
-
this.
|
|
95
|
+
this._bboxDirty = true;
|
|
82
96
|
}
|
|
83
97
|
// ─────────────────────────────────────────────
|
|
84
98
|
// ░░░ Transform API ░░░
|
|
85
99
|
// ─────────────────────────────────────────────
|
|
86
|
-
/** Set absolute position. Marks matrix
|
|
100
|
+
/** Set absolute position. Marks matrix and bounding box dirty. */
|
|
87
101
|
setTranslation(x, y, z) {
|
|
88
102
|
this.translation = [x, y, z];
|
|
89
103
|
this._matrixDirty = true;
|
|
90
|
-
this.
|
|
104
|
+
this._bboxDirty = true;
|
|
91
105
|
}
|
|
92
106
|
/** Set absolute rotation (radians per axis). */
|
|
93
107
|
setRotation(x, y, z) {
|
|
94
108
|
this.rotation = [x, y, z];
|
|
95
109
|
this._matrixDirty = true;
|
|
96
|
-
this.
|
|
110
|
+
this._bboxDirty = true;
|
|
97
111
|
}
|
|
98
112
|
/** Set absolute scale. */
|
|
99
113
|
setScale(x, y, z) {
|
|
100
114
|
this.scale = [x, y, z];
|
|
101
115
|
this._matrixDirty = true;
|
|
102
|
-
this.
|
|
116
|
+
this._bboxDirty = true;
|
|
103
117
|
}
|
|
104
118
|
/** Translate the model by a delta. */
|
|
105
119
|
move(dx, dy, dz = 0) {
|
|
@@ -107,7 +121,7 @@ var Model = class _Model {
|
|
|
107
121
|
this.translation[1] += dy;
|
|
108
122
|
this.translation[2] += dz;
|
|
109
123
|
this._matrixDirty = true;
|
|
110
|
-
this.
|
|
124
|
+
this._bboxDirty = true;
|
|
111
125
|
}
|
|
112
126
|
// ─────────────────────────────────────────────
|
|
113
127
|
// ░░░ Matrix Handling ░░░
|
|
@@ -296,22 +310,12 @@ var Model = class _Model {
|
|
|
296
310
|
update(deltaTime) {
|
|
297
311
|
if (!this.activeAnimation || !this.skeleton) return;
|
|
298
312
|
this.activeAnimation.update(deltaTime);
|
|
299
|
-
|
|
300
|
-
|
|
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);
|
|
313
|
+
this.activeAnimation.sample(this._poseMap);
|
|
314
|
+
this.skeleton.computeJointMatrices(this._poseMap);
|
|
311
315
|
}
|
|
312
316
|
};
|
|
313
317
|
|
|
314
318
|
export {
|
|
315
319
|
Model
|
|
316
320
|
};
|
|
317
|
-
//# sourceMappingURL=chunk-
|
|
321
|
+
//# sourceMappingURL=chunk-QOAQVTAB.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, JointPose } 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\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 _boundingBox: AABB = { min: [0, 0, 0], max: [0, 0, 0] };\n private _bboxDirty = false;\n private _boundsDefinedManually = false;\n private _modelMatrix: mat4 = mat4.create();\n private _matrixDirty = true;\n private readonly _poseMap = new Map<number, JointPose>();\n\n /** World-space AABB, lazily recomputed after any transform change. */\n get boundingBox(): AABB {\n if (this._bboxDirty && !this._boundsDefinedManually) {\n this._boundingBox = this.computeBoundingBox();\n this._bboxDirty = false;\n }\n return this._boundingBox;\n }\n\n set boundingBox(value: AABB) {\n this._boundingBox = value;\n this._bboxDirty = false;\n }\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._bboxDirty = true;\n }\n\n // ─────────────────────────────────────────────\n // ░░░ Transform API ░░░\n // ─────────────────────────────────────────────\n /** Set absolute position. Marks matrix and bounding box dirty. */\n public setTranslation(x: number, y: number, z: number) {\n this.translation = [x, y, z];\n this._matrixDirty = true;\n this._bboxDirty = true;\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._bboxDirty = true;\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._bboxDirty = true;\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._bboxDirty = true;\n }\n\n // ─────────────────────────────────────────────\n // ░░░ Matrix Handling ░░░\n // ─────────────────────────────────────────────\n public 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 // Reuse _poseMap to avoid a new Map allocation every frame.\n // Skeleton.computeJointMatrices already falls back to bind-pose for any\n // joint not present in the map, so no manual merging is needed.\n this.activeAnimation.sample(this._poseMap);\n this.skeleton.computeJointMatrices(this._poseMap);\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"]}
|
|
@@ -27,15 +27,32 @@ var Camera = class {
|
|
|
27
27
|
/* Render Utils */
|
|
28
28
|
lastViewMatrix;
|
|
29
29
|
lastProjectionMatrix;
|
|
30
|
+
// ── Cached matrices (rebuilt only when camera state changes) ──
|
|
31
|
+
_view = mat4.create();
|
|
32
|
+
_proj = mat4.create();
|
|
33
|
+
_mvp = mat4.create();
|
|
34
|
+
_viewDirty = true;
|
|
35
|
+
_projDirty = true;
|
|
30
36
|
/** Translate the camera by a delta. */
|
|
31
37
|
move(dx, dy, dz = 0) {
|
|
32
38
|
this.position[0] += dx;
|
|
33
39
|
this.position[1] += dy;
|
|
34
40
|
this.position[2] += dz;
|
|
41
|
+
this._viewDirty = true;
|
|
35
42
|
}
|
|
36
43
|
/** Clamp-set the zoom level. */
|
|
37
44
|
setZoom(zoom) {
|
|
38
45
|
this.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, zoom));
|
|
46
|
+
this._viewDirty = true;
|
|
47
|
+
this._projDirty = true;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Mark the view matrix as dirty so it is rebuilt on the next
|
|
51
|
+
* {@link getViewMatrix} call. Call this after directly mutating
|
|
52
|
+
* {@link target} or {@link position} (e.g. from a follow-camera update).
|
|
53
|
+
*/
|
|
54
|
+
invalidate() {
|
|
55
|
+
this._viewDirty = true;
|
|
39
56
|
}
|
|
40
57
|
/** Zoom by a signed delta (positive = zoom in). */
|
|
41
58
|
zoomBy(delta) {
|
|
@@ -56,6 +73,7 @@ var Camera = class {
|
|
|
56
73
|
setViewport(width, height) {
|
|
57
74
|
this.viewportWidth = width;
|
|
58
75
|
this.viewportHeight = height;
|
|
76
|
+
this._projDirty = true;
|
|
59
77
|
}
|
|
60
78
|
/** Compute the eye position from yaw/pitch/distance to target. */
|
|
61
79
|
getComputedPosition() {
|
|
@@ -65,27 +83,31 @@ var Camera = class {
|
|
|
65
83
|
const camZ = this.target[2] + radius * Math.cos(this.pitch) * Math.cos(this.yaw);
|
|
66
84
|
return vec3.fromValues(camX, camY, camZ);
|
|
67
85
|
}
|
|
68
|
-
/** Build the view matrix (lookAt). */
|
|
86
|
+
/** Build the view matrix (lookAt). Returns a cached matrix rebuilt only when the camera moves. */
|
|
69
87
|
getViewMatrix() {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
88
|
+
if (this._viewDirty) {
|
|
89
|
+
const eye = this.getComputedPosition();
|
|
90
|
+
mat4.lookAt(this._view, eye, this.target, vec3.fromValues(0, 1, 0));
|
|
91
|
+
this._viewDirty = false;
|
|
92
|
+
this.lastViewMatrix = this._view;
|
|
93
|
+
}
|
|
94
|
+
return this._view;
|
|
75
95
|
}
|
|
76
|
-
/** Build the perspective projection matrix. */
|
|
96
|
+
/** Build the perspective projection matrix. Returns a cached matrix rebuilt only when viewport/zoom changes. */
|
|
77
97
|
getProjectionMatrix() {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
98
|
+
if (this._projDirty) {
|
|
99
|
+
const aspect = this.viewportWidth / this.viewportHeight;
|
|
100
|
+
const fov = Math.PI / 4 / this.zoom;
|
|
101
|
+
mat4.perspective(this._proj, fov, aspect, this.near, this.far);
|
|
102
|
+
this._projDirty = false;
|
|
103
|
+
this.lastProjectionMatrix = this._proj;
|
|
104
|
+
}
|
|
105
|
+
return this._proj;
|
|
83
106
|
}
|
|
84
107
|
worldToNDC3D(x, y, z = 0) {
|
|
85
108
|
const world = vec4.fromValues(x, y, z, 1);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
vec4.transformMat4(world, world, mvp);
|
|
109
|
+
mat4.multiply(this._mvp, this.getProjectionMatrix(), this.getViewMatrix());
|
|
110
|
+
vec4.transformMat4(world, world, this._mvp);
|
|
89
111
|
return [world[0] / world[3], world[1] / world[3], world[2] / world[3]];
|
|
90
112
|
}
|
|
91
113
|
worldScale3D(scale) {
|
|
@@ -101,6 +123,7 @@ var Camera = class {
|
|
|
101
123
|
this.yaw += deltaYaw;
|
|
102
124
|
this.pitch -= deltaPitch;
|
|
103
125
|
this.pitch = Math.max(this.minPitch, Math.min(this.maxPitch, this.pitch));
|
|
126
|
+
this._viewDirty = true;
|
|
104
127
|
}
|
|
105
128
|
};
|
|
106
129
|
|
|
@@ -327,4 +350,4 @@ export {
|
|
|
327
350
|
Camera,
|
|
328
351
|
Viewport
|
|
329
352
|
};
|
|
330
|
-
//# sourceMappingURL=chunk-
|
|
353
|
+
//# sourceMappingURL=chunk-XMW2MS66.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Core/classes/Camera.ts","../src/Core/controls/Mouse/MouseWheelControl.ts","../src/Core/controls/Mouse/MouseDragControl.ts","../src/Core/controls/Mouse/MousePointerLockControl.ts","../src/Core/classes/Viewport.ts"],"sourcesContent":["import { mat4, vec3, vec4 } from 'gl-matrix';\nimport { Vector3 } from '../domain/interfaces/Vectors';\n\n/**\n * Perspective camera with orbit (yaw/pitch), zoom, and viewport controls.\n */\nexport class Camera {\n public target: Vector3 = [0, 0, 0]; // camera looks at this\n\n /* Translation */\n public position: Vector3 = [0, 0, 0]; // 3D position\n\n /* Zoom */\n public zoom: number = 0.89;\n public minZoom: number = 0.5;\n public maxZoom: number = 1;\n public zoomSpeed: number = 0.06;\n\n /* Rotation */\n public yaw: number = 0; // horizontal rotation\n public pitch: number = 0.5; // vertical rotation\n\n public near = 0.01;\n public far = 1000.0;\n\n /* Render Utils */\n public lastViewMatrix?: Float32Array;\n public lastProjectionMatrix?: Float32Array;\n\n // ── Cached matrices (rebuilt only when camera state changes) ──\n private readonly _view: mat4 = mat4.create();\n private readonly _proj: mat4 = mat4.create();\n private readonly _mvp: mat4 = mat4.create();\n private _viewDirty = true;\n private _projDirty = true;\n\n constructor(\n public viewportWidth: number,\n public viewportHeight: number,\n ) {}\n\n /** Translate the camera by a delta. */\n move(dx: number, dy: number, dz: number = 0) {\n this.position[0] += dx;\n this.position[1] += dy;\n this.position[2] += dz;\n this._viewDirty = true;\n }\n\n /** Clamp-set the zoom level. */\n setZoom(zoom: number) {\n this.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, zoom));\n this._viewDirty = true;\n this._projDirty = true;\n }\n\n /**\n * Mark the view matrix as dirty so it is rebuilt on the next\n * {@link getViewMatrix} call. Call this after directly mutating\n * {@link target} or {@link position} (e.g. from a follow-camera update).\n */\n invalidate(): void {\n this._viewDirty = true;\n }\n\n /** Zoom by a signed delta (positive = zoom in). */\n zoomBy(delta: number) {\n const factor = Math.exp(delta * this.zoomSpeed);\n this.setZoom(this.zoom * factor);\n }\n\n worldToNDC(x: number, y: number): [number, number] {\n const ndcX = ((x - this.position[0]) / this.viewportWidth) * 2 * this.zoom;\n const ndcY = ((y - this.position[1]) / this.viewportHeight) * 2 * this.zoom;\n return [ndcX, ndcY];\n }\n\n worldScale(scale: [number, number]): [number, number] {\n return [\n (scale[0] / this.viewportWidth) * 2 * this.zoom,\n (scale[1] / this.viewportHeight) * 2 * this.zoom,\n ];\n }\n\n setViewport(width: number, height: number) {\n this.viewportWidth = width;\n this.viewportHeight = height;\n this._projDirty = true;\n }\n\n /** Compute the eye position from yaw/pitch/distance to target. */\n getComputedPosition(): vec3 {\n const radius = vec3.distance(this.position, this.target);\n\n const camX =\n this.target[0] + radius * Math.cos(this.pitch) * Math.sin(this.yaw);\n const camY = this.target[1] + radius * Math.sin(this.pitch);\n const camZ =\n this.target[2] + radius * Math.cos(this.pitch) * Math.cos(this.yaw);\n\n return vec3.fromValues(camX, camY, camZ);\n }\n\n /** Build the view matrix (lookAt). Returns a cached matrix rebuilt only when the camera moves. */\n getViewMatrix(): mat4 {\n if (this._viewDirty) {\n const eye = this.getComputedPosition();\n mat4.lookAt(this._view, eye, this.target, vec3.fromValues(0, 1, 0));\n this._viewDirty = false;\n this.lastViewMatrix = this._view as unknown as Float32Array;\n }\n return this._view;\n }\n\n /** Build the perspective projection matrix. Returns a cached matrix rebuilt only when viewport/zoom changes. */\n getProjectionMatrix(): mat4 {\n if (this._projDirty) {\n const aspect = this.viewportWidth / this.viewportHeight;\n const fov = Math.PI / 4 / this.zoom;\n mat4.perspective(this._proj, fov, aspect, this.near, this.far);\n this._projDirty = false;\n this.lastProjectionMatrix = this._proj as unknown as Float32Array;\n }\n return this._proj;\n }\n\n worldToNDC3D(x: number, y: number, z: number = 0): [number, number, number] {\n const world = vec4.fromValues(x, y, z, 1.0);\n mat4.multiply(this._mvp, this.getProjectionMatrix(), this.getViewMatrix());\n vec4.transformMat4(world, world, this._mvp);\n return [world[0] / world[3], world[1] / world[3], world[2] / world[3]];\n }\n\n worldScale3D(scale: [number, number, number]): [number, number, number] {\n return [\n (scale[0] / this.viewportWidth) * 2 * this.zoom,\n (scale[1] / this.viewportHeight) * 2 * this.zoom,\n (scale[2] / ((this.viewportWidth + this.viewportHeight) / 2)) *\n 2 *\n this.zoom,\n ];\n }\n\n public minPitch: number = -Math.PI / 2 + 1.49;\n public maxPitch: number = Math.PI / 2 - 0.01;\n\n rotate(deltaYaw: number, deltaPitch: number) {\n this.yaw += deltaYaw;\n\n this.pitch -= deltaPitch; // ← add deltaPitch here\n this.pitch = Math.max(this.minPitch, Math.min(this.maxPitch, this.pitch));\n this._viewDirty = true;\n }\n}\n","import { IMouseControl } from './IMouseControl';\n\nexport type WheelDirection = 'up' | 'down';\nexport type WheelListener = (direction: WheelDirection, delta: number) => void;\n\nexport class MouseWheelControl implements IMouseControl {\n private listeners: WheelListener[] = [];\n private wheelHandler?: (e: WheelEvent) => void;\n\n public enable() {\n if (this.wheelHandler) return;\n\n this.wheelHandler = (e: WheelEvent) => {\n const direction: WheelDirection = e.deltaY < 0 ? 'up' : 'down';\n this.notify(direction, e.deltaY);\n };\n\n window.addEventListener('wheel', this.wheelHandler, { passive: true });\n }\n\n public disable() {\n if (this.wheelHandler) {\n window.removeEventListener('wheel', this.wheelHandler);\n this.wheelHandler = undefined;\n }\n }\n\n public onChange(listener: WheelListener) {\n this.listeners.push(listener);\n }\n\n /** Remove a previously registered listener. */\n public removeListener(listener: WheelListener) {\n this.listeners = this.listeners.filter((l) => l !== listener);\n }\n\n private notify(direction: WheelDirection, delta: number) {\n for (const listener of this.listeners) listener(direction, delta);\n }\n}\n","import { IMouseControl } from './IMouseControl';\n\nexport type DragListener = (dx: number, dy: number, button: number) => void;\n\nexport class MouseDragControl implements IMouseControl {\n private listeners: DragListener[] = [];\n public isDragging = false;\n\n private dragButton: number = 0; // 0 = left, 2 = right\n private lastX = 0;\n private lastY = 0;\n\n constructor(private element: HTMLElement) {}\n\n enable() {\n this.element.addEventListener('mousedown', this.onMouseDown);\n window.addEventListener('mouseup', this.onMouseUp);\n window.addEventListener('mousemove', this.onMouseMove);\n }\n\n disable() {\n this.element.removeEventListener('mousedown', this.onMouseDown);\n window.removeEventListener('mouseup', this.onMouseUp);\n window.removeEventListener('mousemove', this.onMouseMove);\n }\n\n onChange(listener: DragListener) {\n this.listeners.push(listener);\n }\n\n /** Remove a previously registered listener. */\n removeListener(listener: DragListener) {\n this.listeners = this.listeners.filter((l) => l !== listener);\n }\n\n private onMouseDown = (e: MouseEvent) => {\n this.isDragging = true;\n this.dragButton = e.button;\n\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n };\n\n private onMouseUp = () => {\n this.isDragging = false;\n };\n\n private onMouseMove = (e: MouseEvent) => {\n if (!this.isDragging) return;\n\n const dx = e.clientX - this.lastX;\n const dy = e.clientY - this.lastY;\n\n this.lastX = e.clientX;\n this.lastY = e.clientY;\n\n for (const listener of this.listeners) listener(dx, dy, this.dragButton);\n };\n}\n","import { IMouseControl } from './IMouseControl';\n\nexport type MoveListener = (dx: number, dy: number) => void;\n\n/**\n * Captures mouse movement using the Pointer Lock API.\n * Ideal for camera-like controls where the cursor should not hit screen edges.\n */\nexport class MousePointerLockControl implements IMouseControl {\n private listeners: MoveListener[] = [];\n private isEnabled = false;\n private isPointerLocked = false;\n\n constructor(\n private element: HTMLElement,\n private sensitivity = 1,\n ) {}\n\n enable() {\n if (this.isEnabled) return;\n this.isEnabled = true;\n\n // Must be initiated by user click\n this.element.addEventListener('click', this.requestPointerLock);\n document.addEventListener('pointerlockchange', this.onPointerLockChange);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n disable() {\n if (!this.isEnabled) return;\n this.isEnabled = false;\n\n this.element.removeEventListener('click', this.requestPointerLock);\n document.removeEventListener('pointerlockchange', this.onPointerLockChange);\n document.removeEventListener('mousemove', this.onMouseMove);\n\n if (this.isPointerLocked) document.exitPointerLock();\n }\n\n onChange(listener: MoveListener) {\n this.listeners.push(listener);\n }\n\n /** Remove a previously registered listener. */\n removeListener(listener: MoveListener) {\n this.listeners = this.listeners.filter((l) => l !== listener);\n }\n\n setSensitivity(value: number) {\n this.sensitivity = value;\n }\n\n private requestPointerLock = () => {\n this.element.requestPointerLock();\n };\n\n private onPointerLockChange = () => {\n this.isPointerLocked = document.pointerLockElement === this.element;\n };\n\n private onMouseMove = (e: MouseEvent) => {\n if (!this.isPointerLocked) return;\n\n const dx = e.movementX * this.sensitivity;\n const dy = e.movementY * this.sensitivity;\n\n for (const listener of this.listeners) listener(dx, dy);\n };\n}\n","import { Camera } from './Camera';\nimport {\n MouseWheelControl,\n WheelDirection,\n} from '../controls/Mouse/MouseWheelControl';\nimport { MouseDragControl } from '../controls/Mouse/MouseDragControl';\nimport { MousePointerLockControl } from '../controls/Mouse/MousePointerLockControl';\nimport WebGLCore from './WebGLCore';\n\nexport class Viewport {\n private static zoomInterval = 25;\n\n private wheelControl = new MouseWheelControl();\n private dragControl: MouseDragControl;\n private moveControl: MousePointerLockControl;\n\n private lastZoomTime: number = 0;\n private canvas: HTMLCanvasElement;\n private scale: number = 1;\n private webglCore: WebGLCore | null = null;\n private resizeHandler = () => this.resizeCanvas();\n public camera: Camera;\n\n /**\n * @param canvasOrId - An HTMLCanvasElement or the `id` attribute of one.\n * @param width - Logical viewport width.\n * @param height - Logical viewport height.\n * @param webglCore - Optional WebGLCore instance. When provided, the existing\n * GL context is reused for viewport resize instead of\n * requesting a new one.\n */\n constructor(\n canvasOrId: string | HTMLCanvasElement,\n public width: number,\n public height: number,\n webglCore?: WebGLCore,\n ) {\n this.canvas =\n typeof canvasOrId === 'string'\n ? (document.getElementById(canvasOrId) as HTMLCanvasElement)\n : canvasOrId;\n if (!this.canvas) throw new Error('Canvas not found');\n\n this.webglCore = webglCore ?? null;\n this.camera = new Camera(width, height);\n this.dragControl = new MouseDragControl(this.canvas);\n this.moveControl = new MousePointerLockControl(this.canvas, 0.25);\n\n this.resizeCanvas();\n this.setupControls();\n\n window.addEventListener('resize', this.resizeHandler);\n }\n\n private setupControls() {\n const zoomInterval = Viewport.zoomInterval;\n\n // Camera Zoom\n this.wheelControl.onChange((direction: WheelDirection) => {\n const now = performance.now();\n if (now - this.lastZoomTime < zoomInterval) return;\n\n const zoomDelta = direction === 'up' ? 1 : -1;\n this.camera.zoomBy(zoomDelta);\n\n this.lastZoomTime = now;\n });\n this.wheelControl.enable();\n\n // Drag / pan / rotate\n /* this.dragControl.onChange((dx, dy, button) => {\n const factor = 0.01 * this.camera.zoom;\n\n if (button === 2) {\n // Right click = orbit\n this.camera.rotate(-dx * factor, -dy * factor);\n } else if (button === 0) {\n this.camera.target[0] -= (dx * factor) / this.scale;\n this.camera.target[1] += (dy * factor) / this.scale;\n }\n\n });\n this.dragControl.enable(); */\n\n this.moveControl.onChange((dx, dy) => {\n const factor = 0.01 * this.camera.zoom;\n this.camera.rotate(-dx * factor, -dy * factor);\n });\n this.moveControl.enable();\n\n this.canvas.addEventListener('contextmenu', (e) => e.preventDefault());\n }\n\n public isDraggingCamera(): boolean {\n return this.dragControl.isDragging;\n }\n\n private resizeCanvas() {\n const screenWidth = window.innerWidth;\n const screenHeight = window.innerHeight;\n\n const aspectViewport = this.width / this.height;\n const aspectScreen = screenWidth / screenHeight;\n\n let canvasWidth: number, canvasHeight: number;\n\n if (aspectScreen > aspectViewport) {\n canvasHeight = screenHeight;\n canvasWidth = canvasHeight * aspectViewport;\n } else {\n canvasWidth = screenWidth;\n canvasHeight = canvasWidth / aspectViewport;\n }\n\n this.canvas.width = canvasWidth;\n this.canvas.height = canvasHeight;\n\n this.scale = canvasWidth / this.width;\n\n // Reuse the existing GL context when available, otherwise fall back.\n const gl = this.webglCore\n ? this.webglCore.getRenderingContext()\n : (this.canvas.getContext('webgl2') ?? this.canvas.getContext('webgl'));\n\n if (!gl) throw new Error('WebGL not supported');\n gl.viewport(0, 0, canvasWidth, canvasHeight);\n\n this.camera.setViewport(canvasWidth, canvasHeight);\n }\n\n getCanvas(): HTMLCanvasElement {\n return this.canvas;\n }\n\n getScale(): number {\n return this.scale;\n }\n\n /** Release event listeners. Call when the viewport is no longer needed. */\n dispose() {\n window.removeEventListener('resize', this.resizeHandler);\n this.wheelControl.disable();\n this.dragControl.disable();\n this.moveControl.disable();\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Material
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-L66K4AZU.js";
|
|
4
4
|
|
|
5
5
|
// src/Core/classes/Renderer.ts
|
|
6
6
|
import { mat4 as mat42 } from "gl-matrix";
|
|
@@ -444,6 +444,11 @@ var Skeleton = class _Skeleton {
|
|
|
444
444
|
* `global * inverseBindMatrix` evaluates to identity in the bind pose.
|
|
445
445
|
*/
|
|
446
446
|
rootTransform;
|
|
447
|
+
// ── Pre-allocated scratch buffers ──
|
|
448
|
+
/** Per-joint global transform matrices, reused each frame (allocated once in constructor). */
|
|
449
|
+
_globals;
|
|
450
|
+
_local = mat4.create();
|
|
451
|
+
_final = mat4.create();
|
|
447
452
|
constructor(joints, rootTransform) {
|
|
448
453
|
if (joints.length > MAX_JOINTS) {
|
|
449
454
|
console.warn(
|
|
@@ -453,6 +458,7 @@ var Skeleton = class _Skeleton {
|
|
|
453
458
|
this.joints = joints;
|
|
454
459
|
this.jointMatrices = new Float32Array(joints.length * 16);
|
|
455
460
|
this.rootTransform = rootTransform ?? mat4.create();
|
|
461
|
+
this._globals = Array.from({ length: joints.length }, () => mat4.create());
|
|
456
462
|
}
|
|
457
463
|
/** Number of joints in the skeleton. */
|
|
458
464
|
get jointCount() {
|
|
@@ -468,8 +474,9 @@ var Skeleton = class _Skeleton {
|
|
|
468
474
|
* node indices).
|
|
469
475
|
*/
|
|
470
476
|
computeJointMatrices(animatedPoses) {
|
|
471
|
-
const
|
|
472
|
-
const
|
|
477
|
+
const local = this._local;
|
|
478
|
+
const final = this._final;
|
|
479
|
+
const globals = this._globals;
|
|
473
480
|
for (let i = 0; i < this.joints.length; i++) {
|
|
474
481
|
const joint = this.joints[i];
|
|
475
482
|
const pose = animatedPoses?.get(i);
|
|
@@ -478,13 +485,10 @@ var Skeleton = class _Skeleton {
|
|
|
478
485
|
const s = pose?.s ?? joint.localScale;
|
|
479
486
|
mat4.fromRotationTranslationScale(local, r, t, s);
|
|
480
487
|
if (joint.parentIndex >= 0) {
|
|
481
|
-
globals[i] = mat4.create();
|
|
482
488
|
mat4.multiply(globals[i], globals[joint.parentIndex], local);
|
|
483
489
|
} else {
|
|
484
|
-
globals[i] = mat4.create();
|
|
485
490
|
mat4.multiply(globals[i], this.rootTransform, local);
|
|
486
491
|
}
|
|
487
|
-
const final = mat4.create();
|
|
488
492
|
mat4.multiply(final, globals[i], joint.inverseBindMatrix);
|
|
489
493
|
this.jointMatrices.set(final, i * 16);
|
|
490
494
|
}
|
|
@@ -508,6 +512,12 @@ var Skeleton = class _Skeleton {
|
|
|
508
512
|
|
|
509
513
|
// src/Core/classes/AnimationClip.ts
|
|
510
514
|
import { quat as quat2, vec3 as vec32 } from "gl-matrix";
|
|
515
|
+
var _sv0Vec3 = vec32.create();
|
|
516
|
+
var _sv1Vec3 = vec32.create();
|
|
517
|
+
var _sOutVec3 = vec32.create();
|
|
518
|
+
var _sv0Quat = quat2.create();
|
|
519
|
+
var _sv1Quat = quat2.create();
|
|
520
|
+
var _sOutQuat = quat2.create();
|
|
511
521
|
var AnimationClip = class _AnimationClip {
|
|
512
522
|
/** Human-readable name (e.g. "Walk", "Idle"). */
|
|
513
523
|
name;
|
|
@@ -523,6 +533,11 @@ var AnimationClip = class _AnimationClip {
|
|
|
523
533
|
speed = 1;
|
|
524
534
|
/** Whether the clip is currently advancing. */
|
|
525
535
|
playing = false;
|
|
536
|
+
/**
|
|
537
|
+
* Per-joint pose objects reused across frames to avoid per-frame allocations.
|
|
538
|
+
* The vec3/quat inside each entry are also pre-allocated on first use.
|
|
539
|
+
*/
|
|
540
|
+
_posePool = /* @__PURE__ */ new Map();
|
|
526
541
|
constructor(name, channels, duration) {
|
|
527
542
|
this.name = name;
|
|
528
543
|
this.channels = channels;
|
|
@@ -565,26 +580,34 @@ var AnimationClip = class _AnimationClip {
|
|
|
565
580
|
}
|
|
566
581
|
}
|
|
567
582
|
/**
|
|
568
|
-
* Sample every channel at the current playhead and
|
|
569
|
-
*
|
|
583
|
+
* Sample every channel at the current playhead and write per-joint local
|
|
584
|
+
* transforms into `out` (or a newly created map if none is provided).
|
|
585
|
+
*
|
|
586
|
+
* Pass a persistent `Map` to avoid allocating a new one every frame.
|
|
570
587
|
*/
|
|
571
|
-
sample() {
|
|
572
|
-
const result = /* @__PURE__ */ new Map();
|
|
588
|
+
sample(out) {
|
|
589
|
+
const result = out ?? /* @__PURE__ */ new Map();
|
|
590
|
+
if (out) out.clear();
|
|
573
591
|
for (const channel of this.channels) {
|
|
574
|
-
|
|
575
|
-
|
|
592
|
+
let entry = this._posePool.get(channel.jointIndex);
|
|
593
|
+
if (!entry) {
|
|
594
|
+
entry = {};
|
|
595
|
+
this._posePool.set(channel.jointIndex, entry);
|
|
576
596
|
}
|
|
577
|
-
|
|
597
|
+
result.set(channel.jointIndex, entry);
|
|
578
598
|
const value = sampleChannel(channel, this.currentTime);
|
|
579
599
|
switch (channel.path) {
|
|
580
600
|
case "translation":
|
|
581
|
-
entry.t =
|
|
601
|
+
if (!entry.t) entry.t = vec32.create();
|
|
602
|
+
vec32.copy(entry.t, value);
|
|
582
603
|
break;
|
|
583
604
|
case "rotation":
|
|
584
|
-
entry.r =
|
|
605
|
+
if (!entry.r) entry.r = quat2.create();
|
|
606
|
+
quat2.copy(entry.r, value);
|
|
585
607
|
break;
|
|
586
608
|
case "scale":
|
|
587
|
-
entry.s =
|
|
609
|
+
if (!entry.s) entry.s = vec32.create();
|
|
610
|
+
vec32.copy(entry.s, value);
|
|
588
611
|
break;
|
|
589
612
|
}
|
|
590
613
|
}
|
|
@@ -601,30 +624,30 @@ var AnimationClip = class _AnimationClip {
|
|
|
601
624
|
return copy;
|
|
602
625
|
}
|
|
603
626
|
};
|
|
604
|
-
function
|
|
627
|
+
function readValueInto(values, index, numComponents, out) {
|
|
605
628
|
const off = index * numComponents;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
values[off + 2],
|
|
611
|
-
values[off + 3]
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
return vec32.fromValues(values[off], values[off + 1], values[off + 2]);
|
|
629
|
+
out[0] = values[off];
|
|
630
|
+
out[1] = values[off + 1];
|
|
631
|
+
out[2] = values[off + 2];
|
|
632
|
+
if (numComponents === 4) out[3] = values[off + 3];
|
|
615
633
|
}
|
|
616
634
|
function sampleChannel(channel, time) {
|
|
617
635
|
const { sampler, path } = channel;
|
|
618
636
|
const { times, values, interpolation } = sampler;
|
|
619
637
|
const numComponents = path === "rotation" ? 4 : 3;
|
|
638
|
+
const outScratch = numComponents === 4 ? _sOutQuat : _sOutVec3;
|
|
620
639
|
if (times.length === 0) {
|
|
621
|
-
|
|
640
|
+
if (numComponents === 4) quat2.identity(outScratch);
|
|
641
|
+
else vec32.set(outScratch, 0, 0, 0);
|
|
642
|
+
return outScratch;
|
|
622
643
|
}
|
|
623
644
|
if (time <= times[0]) {
|
|
624
|
-
|
|
645
|
+
readValueInto(values, 0, numComponents, outScratch);
|
|
646
|
+
return outScratch;
|
|
625
647
|
}
|
|
626
648
|
if (time >= times[times.length - 1]) {
|
|
627
|
-
|
|
649
|
+
readValueInto(values, times.length - 1, numComponents, outScratch);
|
|
650
|
+
return outScratch;
|
|
628
651
|
}
|
|
629
652
|
let lo = 0;
|
|
630
653
|
let hi = times.length - 1;
|
|
@@ -639,19 +662,20 @@ function sampleChannel(channel, time) {
|
|
|
639
662
|
const t0 = times[lo];
|
|
640
663
|
const t1 = times[hi];
|
|
641
664
|
const factor = (time - t0) / (t1 - t0);
|
|
642
|
-
const
|
|
643
|
-
const
|
|
665
|
+
const v0Scratch = numComponents === 4 ? _sv0Quat : _sv0Vec3;
|
|
666
|
+
const v1Scratch = numComponents === 4 ? _sv1Quat : _sv1Vec3;
|
|
667
|
+
readValueInto(values, lo, numComponents, v0Scratch);
|
|
668
|
+
readValueInto(values, hi, numComponents, v1Scratch);
|
|
644
669
|
if (interpolation === "STEP") {
|
|
645
|
-
|
|
670
|
+
readValueInto(values, lo, numComponents, outScratch);
|
|
671
|
+
return outScratch;
|
|
646
672
|
}
|
|
647
673
|
if (path === "rotation") {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
return out2;
|
|
674
|
+
quat2.slerp(outScratch, v0Scratch, v1Scratch, factor);
|
|
675
|
+
return outScratch;
|
|
651
676
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
return out;
|
|
677
|
+
vec32.lerp(outScratch, v0Scratch, v1Scratch, factor);
|
|
678
|
+
return outScratch;
|
|
655
679
|
}
|
|
656
680
|
|
|
657
681
|
// src/Core/utils/create-wire-box.ts
|
|
@@ -736,19 +760,13 @@ function createWireBox(min, max, material) {
|
|
|
736
760
|
}
|
|
737
761
|
|
|
738
762
|
// src/Core/classes/Renderer.ts
|
|
739
|
-
var computeModelMatrix = (model) => {
|
|
740
|
-
const m = mat42.create();
|
|
741
|
-
mat42.translate(m, m, model.translation);
|
|
742
|
-
mat42.rotateX(m, m, model.rotation[0]);
|
|
743
|
-
mat42.rotateY(m, m, model.rotation[1]);
|
|
744
|
-
mat42.rotateZ(m, m, model.rotation[2]);
|
|
745
|
-
mat42.scale(m, m, model.scale);
|
|
746
|
-
return m;
|
|
747
|
-
};
|
|
748
763
|
var Renderer = class {
|
|
749
764
|
constructor(webglCore, viewport) {
|
|
750
765
|
this.webglCore = webglCore;
|
|
751
766
|
this.viewport = viewport;
|
|
767
|
+
const gl = webglCore.getRenderingContext();
|
|
768
|
+
this._isWebGL2 = gl instanceof WebGL2RenderingContext;
|
|
769
|
+
if (gl) gl.enable(gl.DEPTH_TEST);
|
|
752
770
|
}
|
|
753
771
|
webglCore;
|
|
754
772
|
viewport;
|
|
@@ -757,6 +775,13 @@ var Renderer = class {
|
|
|
757
775
|
// Cached debug materials to avoid per-frame GPU allocations
|
|
758
776
|
_modelHitboxMaterial = null;
|
|
759
777
|
_meshHitboxMaterial = null;
|
|
778
|
+
// Cache wire-box debug meshes: recreated only when the bounding box changes.
|
|
779
|
+
_modelWireBoxCache = /* @__PURE__ */ new WeakMap();
|
|
780
|
+
_meshWireBoxCache = /* @__PURE__ */ new WeakMap();
|
|
781
|
+
/** Pre-allocated identity matrix for world-space hitbox draws. */
|
|
782
|
+
_identity = mat42.create();
|
|
783
|
+
/** Cached `gl instanceof WebGL2RenderingContext` — computed once at construction. */
|
|
784
|
+
_isWebGL2;
|
|
760
785
|
/** Lazily-created material for model-level hitboxes (red). */
|
|
761
786
|
get modelHitboxMaterial() {
|
|
762
787
|
if (!this._modelHitboxMaterial) {
|
|
@@ -780,7 +805,6 @@ var Renderer = class {
|
|
|
780
805
|
if (!gl) return;
|
|
781
806
|
gl.clearColor(...color);
|
|
782
807
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
783
|
-
gl.enable(gl.DEPTH_TEST);
|
|
784
808
|
}
|
|
785
809
|
render(scene) {
|
|
786
810
|
const gl = this.webglCore.getRenderingContext();
|
|
@@ -792,9 +816,9 @@ var Renderer = class {
|
|
|
792
816
|
this.clearFrame();
|
|
793
817
|
const viewMatrix = camera.getViewMatrix();
|
|
794
818
|
const projMatrix = camera.getProjectionMatrix();
|
|
795
|
-
|
|
819
|
+
let activeProgram = null;
|
|
796
820
|
for (const model of models) {
|
|
797
|
-
this.drawModel(
|
|
821
|
+
activeProgram = this.drawModel(
|
|
798
822
|
gl,
|
|
799
823
|
model,
|
|
800
824
|
lights,
|
|
@@ -806,7 +830,7 @@ var Renderer = class {
|
|
|
806
830
|
}
|
|
807
831
|
}
|
|
808
832
|
drawModel(gl, model, lights, viewPosition, viewMatrix, projMatrix, activeProgram) {
|
|
809
|
-
const modelMatrix =
|
|
833
|
+
const modelMatrix = model.getModelMatrix();
|
|
810
834
|
const hasSkeleton = model.skeleton !== null;
|
|
811
835
|
for (const mesh of model.meshes) {
|
|
812
836
|
activeProgram = this.drawMesh(
|
|
@@ -830,8 +854,9 @@ var Renderer = class {
|
|
|
830
854
|
}
|
|
831
855
|
}
|
|
832
856
|
if (this.debug) {
|
|
833
|
-
this.drawHitBox(model,
|
|
857
|
+
this.drawHitBox(model, activeProgram);
|
|
834
858
|
}
|
|
859
|
+
return activeProgram;
|
|
835
860
|
}
|
|
836
861
|
drawMesh(mesh, lights, viewPosition, modelMatrix, viewMatrix, projMatrix, activeProgram, skinnedModel = null) {
|
|
837
862
|
const gl = this.webglCore.getRenderingContext();
|
|
@@ -847,7 +872,7 @@ var Renderer = class {
|
|
|
847
872
|
if (uniformLocations["uProjection"])
|
|
848
873
|
gl.uniformMatrix4fv(uniformLocations["uProjection"], false, projMatrix);
|
|
849
874
|
}
|
|
850
|
-
const isWebGL2 =
|
|
875
|
+
const isWebGL2 = this._isWebGL2;
|
|
851
876
|
if (isWebGL2 && mesh.buffers?.vao) {
|
|
852
877
|
gl.bindVertexArray(
|
|
853
878
|
mesh.buffers.vao
|
|
@@ -913,33 +938,44 @@ var Renderer = class {
|
|
|
913
938
|
}
|
|
914
939
|
return activeProgram;
|
|
915
940
|
}
|
|
916
|
-
drawHitBox(model,
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
)
|
|
922
|
-
|
|
941
|
+
drawHitBox(model, activeProgram) {
|
|
942
|
+
const bbox = model.boundingBox;
|
|
943
|
+
const key = `${bbox.min[0]},${bbox.min[1]},${bbox.min[2]},${bbox.max[0]},${bbox.max[1]},${bbox.max[2]}`;
|
|
944
|
+
const cached = this._modelWireBoxCache.get(model);
|
|
945
|
+
let hitboxMesh;
|
|
946
|
+
if (!cached || cached.key !== key) {
|
|
947
|
+
hitboxMesh = createWireBox(bbox.min, bbox.max, this.modelHitboxMaterial);
|
|
948
|
+
hitboxMesh.isCollidable = false;
|
|
949
|
+
this._modelWireBoxCache.set(model, { mesh: hitboxMesh, key });
|
|
950
|
+
} else {
|
|
951
|
+
hitboxMesh = cached.mesh;
|
|
952
|
+
}
|
|
923
953
|
const camera = this.viewport.camera;
|
|
924
954
|
const viewMatrix = camera.getViewMatrix();
|
|
925
955
|
const projMatrix = camera.getProjectionMatrix();
|
|
926
|
-
const identity = mat42.create();
|
|
927
956
|
this.drawMesh(
|
|
928
957
|
hitboxMesh,
|
|
929
958
|
[],
|
|
930
959
|
camera.position,
|
|
931
|
-
|
|
960
|
+
this._identity,
|
|
932
961
|
viewMatrix,
|
|
933
962
|
projMatrix,
|
|
934
963
|
activeProgram
|
|
935
964
|
);
|
|
936
965
|
}
|
|
937
966
|
drawHitBoxForMesh(mesh, modelMatrix, viewMatrix, projMatrix, activeProgram) {
|
|
938
|
-
|
|
939
|
-
if (!gl || !mesh.boundingBox) return;
|
|
967
|
+
if (!mesh.boundingBox) return;
|
|
940
968
|
const { min, max } = mesh.boundingBox;
|
|
941
|
-
const
|
|
942
|
-
|
|
969
|
+
const key = `${min[0]},${min[1]},${min[2]},${max[0]},${max[1]},${max[2]}`;
|
|
970
|
+
const cached = this._meshWireBoxCache.get(mesh);
|
|
971
|
+
let wireBox;
|
|
972
|
+
if (!cached || cached.key !== key) {
|
|
973
|
+
wireBox = createWireBox(min, max, this.meshHitboxMaterial);
|
|
974
|
+
wireBox.isCollidable = false;
|
|
975
|
+
this._meshWireBoxCache.set(mesh, { mesh: wireBox, key });
|
|
976
|
+
} else {
|
|
977
|
+
wireBox = cached.mesh;
|
|
978
|
+
}
|
|
943
979
|
const camera = this.viewport.camera;
|
|
944
980
|
this.drawMesh(
|
|
945
981
|
wireBox,
|
|
@@ -965,4 +1001,4 @@ export {
|
|
|
965
1001
|
Skeleton,
|
|
966
1002
|
AnimationClip
|
|
967
1003
|
};
|
|
968
|
-
//# sourceMappingURL=chunk-
|
|
1004
|
+
//# sourceMappingURL=chunk-ZCJ3MJZD.js.map
|