@fonsecabarreto/genesis-gl-core 0.1.3 → 0.1.33
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/dist/Core/classes/Material.d.ts +1 -1
- package/dist/Core/classes/Material.js +1 -1
- package/dist/Core/classes/Model.d.ts +2 -2
- package/dist/Core/classes/Renderer.d.ts +4 -2
- package/dist/Core/classes/Renderer.js +4 -4
- package/dist/Core/classes/Scene.d.ts +3 -3
- package/dist/Core/classes/Scene.js +1 -1
- package/dist/Core/classes/Viewport.d.ts +4 -1
- package/dist/Core/classes/Viewport.js +1 -1
- package/dist/Core/index.d.ts +73 -3
- package/dist/Core/index.js +13 -7
- package/dist/Core/utils/load-glb.d.ts +2 -2
- 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/Game/controls/KeyboardInput.d.ts +3 -3
- package/dist/Game/index.d.ts +4 -4
- package/dist/Game/index.js +5 -5
- package/dist/{KeyboardInput-1xOAabI0.d.ts → KeyboardInput-RJ0mbuWM.d.ts} +1 -1
- package/dist/{Material-DhwSRbP2.d.ts → Material-A-7kKes_.d.ts} +2 -0
- package/dist/{Model-BBZHnUp1.d.ts → Model-efZcQkOK.d.ts} +2 -2
- package/dist/{chunk-ZCJ3MJZD.js → chunk-4M3XFPH3.js} +17 -3
- package/dist/chunk-4M3XFPH3.js.map +1 -0
- package/dist/chunk-IOAANJZG.js +159 -0
- package/dist/chunk-IOAANJZG.js.map +1 -0
- package/dist/{chunk-XMW2MS66.js → chunk-WHOIVV44.js} +19 -12
- package/dist/chunk-WHOIVV44.js.map +1 -0
- package/dist/{chunk-3ULETMWF.js → chunk-WKSDPPXS.js} +4 -3
- package/dist/{chunk-3ULETMWF.js.map → chunk-WKSDPPXS.js.map} +1 -1
- package/dist/{chunk-L66K4AZU.js → chunk-XCYJCLHB.js} +3 -1
- package/dist/chunk-XCYJCLHB.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-L66K4AZU.js.map +0 -1
- package/dist/chunk-SUNYSY45.js +0 -81
- package/dist/chunk-SUNYSY45.js.map +0 -1
- package/dist/chunk-XMW2MS66.js.map +0 -1
- package/dist/chunk-ZCJ3MJZD.js.map +0 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Mesh
|
|
3
|
+
} from "./chunk-4M3XFPH3.js";
|
|
4
|
+
import {
|
|
5
|
+
Material
|
|
6
|
+
} from "./chunk-XCYJCLHB.js";
|
|
7
|
+
|
|
8
|
+
// src/Core/controls/KeyboardControl.ts
|
|
9
|
+
var KeyboardControl = class {
|
|
10
|
+
pressedKeys = /* @__PURE__ */ new Set();
|
|
11
|
+
listeners = [];
|
|
12
|
+
keyDownHandler;
|
|
13
|
+
keyUpHandler;
|
|
14
|
+
enable() {
|
|
15
|
+
if (this.keyDownHandler || this.keyUpHandler) return;
|
|
16
|
+
this.keyDownHandler = (e) => {
|
|
17
|
+
const key = this.mapKey(e.key);
|
|
18
|
+
if (!key) return;
|
|
19
|
+
if (!this.pressedKeys.has(key)) {
|
|
20
|
+
this.pressedKeys.add(key);
|
|
21
|
+
this.notify(key, true);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
this.keyUpHandler = (e) => {
|
|
25
|
+
const key = this.mapKey(e.key);
|
|
26
|
+
if (!key) return;
|
|
27
|
+
if (this.pressedKeys.has(key)) {
|
|
28
|
+
this.pressedKeys.delete(key);
|
|
29
|
+
this.notify(key, false);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
window.addEventListener("keydown", this.keyDownHandler);
|
|
33
|
+
window.addEventListener("keyup", this.keyUpHandler);
|
|
34
|
+
}
|
|
35
|
+
disable() {
|
|
36
|
+
if (this.keyDownHandler) {
|
|
37
|
+
window.removeEventListener("keydown", this.keyDownHandler);
|
|
38
|
+
this.keyDownHandler = void 0;
|
|
39
|
+
}
|
|
40
|
+
if (this.keyUpHandler) {
|
|
41
|
+
window.removeEventListener("keyup", this.keyUpHandler);
|
|
42
|
+
this.keyUpHandler = void 0;
|
|
43
|
+
}
|
|
44
|
+
this.pressedKeys.clear();
|
|
45
|
+
}
|
|
46
|
+
onChange(listener) {
|
|
47
|
+
this.listeners.push(listener);
|
|
48
|
+
}
|
|
49
|
+
/** Remove a previously registered listener. */
|
|
50
|
+
removeListener(listener) {
|
|
51
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
52
|
+
}
|
|
53
|
+
notify(key, isPressed) {
|
|
54
|
+
for (const listener of this.listeners) {
|
|
55
|
+
listener(key, isPressed);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
mapKey(key) {
|
|
59
|
+
switch (key) {
|
|
60
|
+
case "w":
|
|
61
|
+
case "ArrowUp":
|
|
62
|
+
return "up";
|
|
63
|
+
case "s":
|
|
64
|
+
case "ArrowDown":
|
|
65
|
+
return "down";
|
|
66
|
+
case "a":
|
|
67
|
+
case "ArrowLeft":
|
|
68
|
+
return "left";
|
|
69
|
+
case "d":
|
|
70
|
+
case "ArrowRight":
|
|
71
|
+
return "right";
|
|
72
|
+
case " ":
|
|
73
|
+
return "jump";
|
|
74
|
+
case "Shift":
|
|
75
|
+
return "roll";
|
|
76
|
+
default:
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
isPressed(key) {
|
|
81
|
+
return this.pressedKeys.has(key);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/Core/utils/create-ground-shadow.ts
|
|
86
|
+
function createRadialGradientTexture(gl, size, color, opacity, falloff) {
|
|
87
|
+
const canvas = document.createElement("canvas");
|
|
88
|
+
canvas.width = size;
|
|
89
|
+
canvas.height = size;
|
|
90
|
+
const ctx = canvas.getContext("2d");
|
|
91
|
+
const cx = size / 2;
|
|
92
|
+
const cy = size / 2;
|
|
93
|
+
const r = size / 2;
|
|
94
|
+
const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
|
|
95
|
+
const [cr, cg, cb] = color.map((v) => Math.round(v * 255));
|
|
96
|
+
const innerStop = Math.max(0, Math.min(1, 1 - falloff));
|
|
97
|
+
grad.addColorStop(0, `rgba(${cr},${cg},${cb},${opacity})`);
|
|
98
|
+
grad.addColorStop(innerStop, `rgba(${cr},${cg},${cb},${opacity * 0.6})`);
|
|
99
|
+
grad.addColorStop(1, `rgba(${cr},${cg},${cb},0)`);
|
|
100
|
+
ctx.fillStyle = grad;
|
|
101
|
+
ctx.fillRect(0, 0, size, size);
|
|
102
|
+
const texture = gl.createTexture();
|
|
103
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
104
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
|
|
105
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
106
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
107
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
108
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
109
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
110
|
+
return texture;
|
|
111
|
+
}
|
|
112
|
+
function createGroundShadow(webglCore, options = {}) {
|
|
113
|
+
const {
|
|
114
|
+
y = 0,
|
|
115
|
+
radius = 1.5,
|
|
116
|
+
opacity = 0.55,
|
|
117
|
+
color = [0, 0, 0],
|
|
118
|
+
falloff = 0.5
|
|
119
|
+
} = options;
|
|
120
|
+
const vertices = new Float32Array([
|
|
121
|
+
-radius,
|
|
122
|
+
y,
|
|
123
|
+
radius,
|
|
124
|
+
radius,
|
|
125
|
+
y,
|
|
126
|
+
radius,
|
|
127
|
+
radius,
|
|
128
|
+
y,
|
|
129
|
+
-radius,
|
|
130
|
+
-radius,
|
|
131
|
+
y,
|
|
132
|
+
-radius
|
|
133
|
+
]);
|
|
134
|
+
const normals = new Float32Array([0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]);
|
|
135
|
+
const texCoords = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
|
|
136
|
+
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
|
|
137
|
+
const gl = webglCore.getRenderingContext();
|
|
138
|
+
const gradientTexture = createRadialGradientTexture(
|
|
139
|
+
gl,
|
|
140
|
+
256,
|
|
141
|
+
color,
|
|
142
|
+
opacity,
|
|
143
|
+
falloff
|
|
144
|
+
);
|
|
145
|
+
const mat = new Material(webglCore, {
|
|
146
|
+
albedoColor: [1, 1, 1, 1],
|
|
147
|
+
unlit: true,
|
|
148
|
+
doubleSided: true,
|
|
149
|
+
transparent: true
|
|
150
|
+
});
|
|
151
|
+
mat.texture = gradientTexture;
|
|
152
|
+
return new Mesh("ground-shadow", vertices, normals, mat, texCoords, indices);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
KeyboardControl,
|
|
157
|
+
createGroundShadow
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=chunk-IOAANJZG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/Core/controls/KeyboardControl.ts","../src/Core/utils/create-ground-shadow.ts"],"sourcesContent":["type ControlKey = 'up' | 'down' | 'left' | 'right' | 'jump' | 'roll';\n\ntype ControlListener = (key: ControlKey, isPressed: boolean) => void;\n\nexport class KeyboardControl {\n private pressedKeys: Set<ControlKey> = new Set();\n private listeners: ControlListener[] = [];\n private keyDownHandler?: (e: KeyboardEvent) => void;\n private keyUpHandler?: (e: KeyboardEvent) => void;\n\n public enable() {\n if (this.keyDownHandler || this.keyUpHandler) return; // prevent duplicates\n\n this.keyDownHandler = (e: KeyboardEvent) => {\n const key = this.mapKey(e.key);\n if (!key) return;\n\n if (!this.pressedKeys.has(key)) {\n this.pressedKeys.add(key);\n this.notify(key, true);\n }\n };\n\n this.keyUpHandler = (e: KeyboardEvent) => {\n const key = this.mapKey(e.key);\n if (!key) return;\n\n if (this.pressedKeys.has(key)) {\n this.pressedKeys.delete(key);\n this.notify(key, false);\n }\n };\n\n window.addEventListener('keydown', this.keyDownHandler);\n window.addEventListener('keyup', this.keyUpHandler);\n }\n\n public disable() {\n if (this.keyDownHandler) {\n window.removeEventListener('keydown', this.keyDownHandler);\n this.keyDownHandler = undefined;\n }\n if (this.keyUpHandler) {\n window.removeEventListener('keyup', this.keyUpHandler);\n this.keyUpHandler = undefined;\n }\n this.pressedKeys.clear();\n }\n\n public onChange(listener: ControlListener) {\n this.listeners.push(listener);\n }\n\n /** Remove a previously registered listener. */\n public removeListener(listener: ControlListener) {\n this.listeners = this.listeners.filter((l) => l !== listener);\n }\n\n private notify(key: ControlKey, isPressed: boolean) {\n for (const listener of this.listeners) {\n listener(key, isPressed);\n }\n }\n\n private mapKey(key: string): ControlKey | null {\n switch (key) {\n case 'w':\n case 'ArrowUp':\n return 'up';\n case 's':\n case 'ArrowDown':\n return 'down';\n case 'a':\n case 'ArrowLeft':\n return 'left';\n case 'd':\n case 'ArrowRight':\n return 'right';\n case ' ':\n return 'jump';\n case 'Shift':\n return 'roll';\n default:\n return null;\n }\n }\n\n public isPressed(key: ControlKey): boolean {\n return this.pressedKeys.has(key);\n }\n}\n","import { Material } from '../classes/Material';\nimport { Mesh } from '../classes/Mesh';\nimport WebGLCore from '../classes/WebGLCore';\n\nexport interface GroundShadowOptions {\n /** World Y position of the shadow plane. Default 0. */\n y?: number;\n /** Half-size of the shadow quad in world units. Default 1.5. */\n radius?: number;\n /** Peak shadow opacity at center, 0–1. Default 0.55. */\n opacity?: number;\n /** Shadow color as [r, g, b]. Default [0, 0, 0]. */\n color?: [number, number, number];\n /** How tight the falloff is. 0 = linear, higher = softer edges. Default 0.5. */\n falloff?: number;\n}\n\n/** Bake a radial gradient into a WebGL texture using an offscreen canvas. */\nfunction createRadialGradientTexture(\n gl: WebGLRenderingContext,\n size: number,\n color: [number, number, number],\n opacity: number,\n falloff: number,\n): WebGLTexture {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d')!;\n\n const cx = size / 2;\n const cy = size / 2;\n const r = size / 2;\n\n const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);\n const [cr, cg, cb] = color.map((v) => Math.round(v * 255));\n\n // Center: full opacity; edge: transparent. Falloff controls the inner stop.\n const innerStop = Math.max(0, Math.min(1, 1 - falloff));\n grad.addColorStop(0, `rgba(${cr},${cg},${cb},${opacity})`);\n grad.addColorStop(innerStop, `rgba(${cr},${cg},${cb},${opacity * 0.6})`);\n grad.addColorStop(1, `rgba(${cr},${cg},${cb},0)`);\n\n ctx.fillStyle = grad;\n ctx.fillRect(0, 0, size, size);\n\n const texture = gl.createTexture()!;\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\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.bindTexture(gl.TEXTURE_2D, null);\n\n return texture;\n}\n\n/**\n * Creates a flat quad lying on the XZ plane with a soft radial-gradient shadow\n * texture. The gradient fades from dark at the center to transparent at the edge,\n * approximating a contact shadow beneath a model.\n */\nexport function createGroundShadow(\n webglCore: WebGLCore,\n options: GroundShadowOptions = {},\n): Mesh {\n const {\n y = 0,\n radius = 1.5,\n opacity = 0.55,\n color = [0, 0, 0],\n falloff = 0.5,\n } = options;\n\n const vertices = new Float32Array([\n -radius,\n y,\n radius,\n radius,\n y,\n radius,\n radius,\n y,\n -radius,\n -radius,\n y,\n -radius,\n ]);\n\n const normals = new Float32Array([0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]);\n\n const texCoords = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);\n\n const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);\n\n const gl = webglCore.getRenderingContext()!;\n const gradientTexture = createRadialGradientTexture(\n gl,\n 256,\n color,\n opacity,\n falloff,\n );\n\n const mat = new Material(webglCore, {\n albedoColor: [1, 1, 1, 1],\n unlit: true,\n doubleSided: true,\n transparent: true,\n });\n mat.texture = gradientTexture;\n\n return new Mesh('ground-shadow', vertices, normals, mat, texCoords, indices);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -13,9 +13,9 @@ var Camera = class {
|
|
|
13
13
|
position = [0, 0, 0];
|
|
14
14
|
// 3D position
|
|
15
15
|
/* Zoom */
|
|
16
|
-
zoom =
|
|
16
|
+
zoom = 1;
|
|
17
17
|
minZoom = 0.5;
|
|
18
|
-
maxZoom =
|
|
18
|
+
maxZoom = 10;
|
|
19
19
|
zoomSpeed = 0.06;
|
|
20
20
|
/* Rotation */
|
|
21
21
|
yaw = 0;
|
|
@@ -121,7 +121,7 @@ var Camera = class {
|
|
|
121
121
|
maxPitch = Math.PI / 2 - 0.01;
|
|
122
122
|
rotate(deltaYaw, deltaPitch) {
|
|
123
123
|
this.yaw += deltaYaw;
|
|
124
|
-
this.pitch
|
|
124
|
+
this.pitch += deltaPitch;
|
|
125
125
|
this.pitch = Math.max(this.minPitch, Math.min(this.maxPitch, this.pitch));
|
|
126
126
|
this._viewDirty = true;
|
|
127
127
|
}
|
|
@@ -265,7 +265,7 @@ var Viewport = class _Viewport {
|
|
|
265
265
|
* GL context is reused for viewport resize instead of
|
|
266
266
|
* requesting a new one.
|
|
267
267
|
*/
|
|
268
|
-
constructor(canvasOrId, width, height, webglCore) {
|
|
268
|
+
constructor(canvasOrId, width, height, webglCore, options = {}) {
|
|
269
269
|
this.width = width;
|
|
270
270
|
this.height = height;
|
|
271
271
|
this.canvas = typeof canvasOrId === "string" ? document.getElementById(canvasOrId) : canvasOrId;
|
|
@@ -275,7 +275,7 @@ var Viewport = class _Viewport {
|
|
|
275
275
|
this.dragControl = new MouseDragControl(this.canvas);
|
|
276
276
|
this.moveControl = new MousePointerLockControl(this.canvas, 0.25);
|
|
277
277
|
this.resizeCanvas();
|
|
278
|
-
this.setupControls();
|
|
278
|
+
this.setupControls(options.pointerLock ?? true);
|
|
279
279
|
window.addEventListener("resize", this.resizeHandler);
|
|
280
280
|
}
|
|
281
281
|
width;
|
|
@@ -290,7 +290,7 @@ var Viewport = class _Viewport {
|
|
|
290
290
|
webglCore = null;
|
|
291
291
|
resizeHandler = () => this.resizeCanvas();
|
|
292
292
|
camera;
|
|
293
|
-
setupControls() {
|
|
293
|
+
setupControls(pointerLock) {
|
|
294
294
|
const zoomInterval = _Viewport.zoomInterval;
|
|
295
295
|
this.wheelControl.onChange((direction) => {
|
|
296
296
|
const now = performance.now();
|
|
@@ -300,11 +300,13 @@ var Viewport = class _Viewport {
|
|
|
300
300
|
this.lastZoomTime = now;
|
|
301
301
|
});
|
|
302
302
|
this.wheelControl.enable();
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
303
|
+
if (pointerLock) {
|
|
304
|
+
this.moveControl.onChange((dx, dy) => {
|
|
305
|
+
const factor = 0.01 * this.camera.zoom;
|
|
306
|
+
this.camera.rotate(-dx * factor, -dy * factor);
|
|
307
|
+
});
|
|
308
|
+
this.moveControl.enable();
|
|
309
|
+
}
|
|
308
310
|
this.canvas.addEventListener("contextmenu", (e) => e.preventDefault());
|
|
309
311
|
}
|
|
310
312
|
isDraggingCamera() {
|
|
@@ -334,6 +336,9 @@ var Viewport = class _Viewport {
|
|
|
334
336
|
getCanvas() {
|
|
335
337
|
return this.canvas;
|
|
336
338
|
}
|
|
339
|
+
getWebGLCore() {
|
|
340
|
+
return this.webglCore;
|
|
341
|
+
}
|
|
337
342
|
getScale() {
|
|
338
343
|
return this.scale;
|
|
339
344
|
}
|
|
@@ -348,6 +353,8 @@ var Viewport = class _Viewport {
|
|
|
348
353
|
|
|
349
354
|
export {
|
|
350
355
|
Camera,
|
|
356
|
+
MouseDragControl,
|
|
357
|
+
MousePointerLockControl,
|
|
351
358
|
Viewport
|
|
352
359
|
};
|
|
353
|
-
//# sourceMappingURL=chunk-
|
|
360
|
+
//# sourceMappingURL=chunk-WHOIVV44.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 = 1;\n public minZoom: number = 0.5;\n public maxZoom: number = 10;\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; // 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 options: { pointerLock?: boolean } = {},\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(options.pointerLock ?? true);\n\n window.addEventListener('resize', this.resizeHandler);\n }\n\n private setupControls(pointerLock: boolean) {\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 if (pointerLock) {\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\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 getWebGLCore(): WebGLCore | null {\n return this.webglCore;\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":[]}
|
|
@@ -98,8 +98,9 @@ var Scene = class _Scene {
|
|
|
98
98
|
this.lights.push(light);
|
|
99
99
|
}
|
|
100
100
|
/** Add a model to the scene with a unique key. */
|
|
101
|
-
add(id, model) {
|
|
102
|
-
this.models.
|
|
101
|
+
add(id, model, prepend = false) {
|
|
102
|
+
if (prepend) this.models.unshift(model);
|
|
103
|
+
else this.models.push(model);
|
|
103
104
|
this.modelsMap.set(id, model);
|
|
104
105
|
}
|
|
105
106
|
/** Remove a model from the scene by key. */
|
|
@@ -141,4 +142,4 @@ export {
|
|
|
141
142
|
Light,
|
|
142
143
|
Scene
|
|
143
144
|
};
|
|
144
|
-
//# sourceMappingURL=chunk-
|
|
145
|
+
//# sourceMappingURL=chunk-WKSDPPXS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/Core/classes/Light.ts","../src/Core/classes/Scene.ts"],"sourcesContent":["import { Vector3 } from '../domain/interfaces/Vectors';\n\n/**\n * Supported light types.\n * - `directional` – parallel rays (sun-like).\n * - `point` – omnidirectional with distance attenuation.\n * - `ambient` – constant contribution everywhere.\n */\nexport type LightType = 'directional' | 'point' | 'ambient';\n\n/**\n * A scene light with type, position, direction, colour, intensity,\n * and attenuation factors (for point lights).\n */\nexport class Light {\n // Attenuation factors are initialized to standard values (no falloff)\n public constant: number = 1.0;\n public linear: number = 0.0;\n public quadratic: number = 0.0;\n\n constructor(\n public type: LightType = 'directional',\n public position: Vector3 = [0, 10, 0], // for point lights\n public direction: Vector3 = [0, -1, -1], // for directional lights\n public color: Vector3 = [1, 1, 1],\n public intensity: number = 1,\n constant: number = 1.0,\n linear: number = 0.0,\n quadratic: number = 0.0,\n ) {\n this.constant = constant;\n this.linear = linear;\n this.quadratic = quadratic;\n }\n\n move(dx: number, dy: number, dz: number) {\n this.position[0] += dx;\n this.position[1] += dy;\n this.position[2] += dz;\n }\n\n rotateY(delta: number) {\n if (this.type === 'directional') {\n const cos = Math.cos(delta);\n const sin = Math.sin(delta);\n const [x, y, z] = this.direction;\n this.direction[0] = x * cos - z * sin;\n this.direction[2] = x * sin + z * cos;\n }\n }\n}\n","import { Light } from './Light';\nimport { Model } from './Model';\nimport WebGLCore from './WebGLCore';\n\nexport class Scene {\n private models: Model[] = [];\n private modelsMap = new Map<string, Model>();\n public lights: Light[] = [];\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n constructor(private webglCore: WebGLCore) {}\n\n // ─────────────────────────────────────────────\n // ░░░ Factory: pre-configured 3-point lighting ░░░\n // ─────────────────────────────────────────────\n\n /**\n * Create a Scene pre-loaded with a classic **3-point lighting** rig\n * (ambient + key + fill + back).\n *\n * Use this for quick prototyping; for production, create a plain Scene and\n * add your own lights.\n */\n static withDefaultLights(webglCore: WebGLCore): Scene {\n const scene = new Scene(webglCore);\n\n // 🌈 AMBIENT LIGHT (Global Base Illumination)\n scene.addLight(\n 'ambient_base',\n new Light('ambient', [0, 0, 0], [0, 0, 0], [0.3, 0.3, 0.35], 0.5),\n );\n\n // 🔑 KEY LIGHT (Main Sun/Primary Source)\n scene.addLight(\n 'directional_key',\n new Light(\n 'directional',\n [5, 10, 5],\n [-0.5, -1.0, -0.5],\n [1.0, 0.95, 0.9],\n 1.2,\n ),\n );\n\n // ☁️ FILL LIGHT (Softens Shadows)\n scene.addLight(\n 'directional_fill',\n new Light(\n 'directional',\n [-10, 5, 10],\n [0.5, -0.2, -0.7],\n [0.8, 0.8, 1.0],\n 0.5,\n ),\n );\n\n // 💫 BACK LIGHT (Rim/Separation Light)\n scene.addLight(\n 'directional_back',\n new Light(\n 'directional',\n [0, 15, -10],\n [0.0, -0.8, 1.0],\n [1.0, 1.0, 1.0],\n 0.7,\n ),\n );\n\n return scene;\n }\n\n /** Register a light source in the scene. */\n public addLight(id: string, light: Light) {\n this.lights.push(light);\n\n /* Debug only */\n /* \n const lightSphere = createSphereMesh(\n this.webglCore,\n 0.25,\n 12,\n 12,\n [1, 1, 0.8],\n );\n\n lightSphere.isCollidable = false;\n lightSphere.translation = light.position;\n this.add(id + '_sphere', lightSphere);\n\n // Debug direction for directional lights\n if (light.type === 'point' || light.type === 'directional') {\n const dirMesh = createLightDirectionMesh(\n this.webglCore,\n light.position,\n light.direction,\n 5, // length of the arrow\n [1, 0, 0], // red arrow\n );\n this.add(id + '_dir', dirMesh);\n } \n */\n }\n\n /** Add a model to the scene with a unique key. */\n add(id: string, model: Model) {\n this.models.push(model);\n this.modelsMap.set(id, model);\n }\n\n /** Remove a model from the scene by key. */\n remove(key: string) {\n const mesh = this.modelsMap.get(key);\n if (!mesh) return;\n this.models = this.models.filter((m) => m !== mesh);\n this.modelsMap.delete(key);\n }\n\n getModels() {\n return this.models;\n }\n\n getLights() {\n return this.lights;\n }\n\n /** Retrieve a model by its key. */\n public getModel(key: string) {\n const mesh = this.modelsMap.get(key);\n return mesh;\n }\n\n public hasMesh(key: string) {\n return this.modelsMap.has(key);\n }\n\n /** Returns all [id, model] pairs — useful for debug/inspector tools. */\n public getEntries(): [string, Model][] {\n return [...this.modelsMap.entries()];\n }\n\n dispose(webglCore: WebGLCore) {\n for (const model of this.models) {\n model.dispose(webglCore);\n }\n this.models.length = 0;\n this.modelsMap.clear();\n this.lights.length = 0;\n }\n}\n"],"mappings":";AAcO,IAAM,QAAN,MAAY;AAAA,EAMjB,YACS,OAAkB,eAClB,WAAoB,CAAC,GAAG,IAAI,CAAC,GAC7B,YAAqB,CAAC,GAAG,IAAI,EAAE,GAC/B,QAAiB,CAAC,GAAG,GAAG,CAAC,GACzB,YAAoB,GAC3B,WAAmB,GACnB,SAAiB,GACjB,YAAoB,GACpB;AARO;AACA;AACA;AACA;AACA;AAKP,SAAK,WAAW;AAChB,SAAK,SAAS;AACd,SAAK,YAAY;AAAA,EACnB;AAAA,EAZS;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EATF,WAAmB;AAAA,EACnB,SAAiB;AAAA,EACjB,YAAoB;AAAA,EAiB3B,KAAK,IAAY,IAAY,IAAY;AACvC,SAAK,SAAS,CAAC,KAAK;AACpB,SAAK,SAAS,CAAC,KAAK;AACpB,SAAK,SAAS,CAAC,KAAK;AAAA,EACtB;AAAA,EAEA,QAAQ,OAAe;AACrB,QAAI,KAAK,SAAS,eAAe;AAC/B,YAAM,MAAM,KAAK,IAAI,KAAK;AAC1B,YAAM,MAAM,KAAK,IAAI,KAAK;AAC1B,YAAM,CAAC,GAAG,GAAG,CAAC,IAAI,KAAK;AACvB,WAAK,UAAU,CAAC,IAAI,IAAI,MAAM,IAAI;AAClC,WAAK,UAAU,CAAC,IAAI,IAAI,MAAM,IAAI;AAAA,IACpC;AAAA,EACF;AACF;;;AC9CO,IAAM,QAAN,MAAM,OAAM;AAAA;AAAA,EAMjB,YAAoB,WAAsB;AAAtB;AAAA,EAAuB;AAAA,EAAvB;AAAA,EALZ,SAAkB,CAAC;AAAA,EACnB,YAAY,oBAAI,IAAmB;AAAA,EACpC,SAAkB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgB1B,OAAO,kBAAkB,WAA6B;AACpD,UAAM,QAAQ,IAAI,OAAM,SAAS;AAGjC,UAAM;AAAA,MACJ;AAAA,MACA,IAAI,MAAM,WAAW,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,GAAG,GAAG;AAAA,IAClE;AAGA,UAAM;AAAA,MACJ;AAAA,MACA,IAAI;AAAA,QACF;AAAA,QACA,CAAC,GAAG,IAAI,CAAC;AAAA,QACT,CAAC,MAAM,IAAM,IAAI;AAAA,QACjB,CAAC,GAAK,MAAM,GAAG;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAGA,UAAM;AAAA,MACJ;AAAA,MACA,IAAI;AAAA,QACF;AAAA,QACA,CAAC,KAAK,GAAG,EAAE;AAAA,QACX,CAAC,KAAK,MAAM,IAAI;AAAA,QAChB,CAAC,KAAK,KAAK,CAAG;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAGA,UAAM;AAAA,MACJ;AAAA,MACA,IAAI;AAAA,QACF;AAAA,QACA,CAAC,GAAG,IAAI,GAAG;AAAA,QACX,CAAC,GAAK,MAAM,CAAG;AAAA,QACf,CAAC,GAAK,GAAK,CAAG;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGO,SAAS,IAAY,OAAc;AACxC,SAAK,OAAO,KAAK,KAAK;AAAA,EA4BxB;AAAA;AAAA,EAGA,IAAI,IAAY,OAAc;
|
|
1
|
+
{"version":3,"sources":["../src/Core/classes/Light.ts","../src/Core/classes/Scene.ts"],"sourcesContent":["import { Vector3 } from '../domain/interfaces/Vectors';\n\n/**\n * Supported light types.\n * - `directional` – parallel rays (sun-like).\n * - `point` – omnidirectional with distance attenuation.\n * - `ambient` – constant contribution everywhere.\n */\nexport type LightType = 'directional' | 'point' | 'ambient';\n\n/**\n * A scene light with type, position, direction, colour, intensity,\n * and attenuation factors (for point lights).\n */\nexport class Light {\n // Attenuation factors are initialized to standard values (no falloff)\n public constant: number = 1.0;\n public linear: number = 0.0;\n public quadratic: number = 0.0;\n\n constructor(\n public type: LightType = 'directional',\n public position: Vector3 = [0, 10, 0], // for point lights\n public direction: Vector3 = [0, -1, -1], // for directional lights\n public color: Vector3 = [1, 1, 1],\n public intensity: number = 1,\n constant: number = 1.0,\n linear: number = 0.0,\n quadratic: number = 0.0,\n ) {\n this.constant = constant;\n this.linear = linear;\n this.quadratic = quadratic;\n }\n\n move(dx: number, dy: number, dz: number) {\n this.position[0] += dx;\n this.position[1] += dy;\n this.position[2] += dz;\n }\n\n rotateY(delta: number) {\n if (this.type === 'directional') {\n const cos = Math.cos(delta);\n const sin = Math.sin(delta);\n const [x, y, z] = this.direction;\n this.direction[0] = x * cos - z * sin;\n this.direction[2] = x * sin + z * cos;\n }\n }\n}\n","import { Light } from './Light';\nimport { Model } from './Model';\nimport WebGLCore from './WebGLCore';\n\nexport class Scene {\n private models: Model[] = [];\n private modelsMap = new Map<string, Model>();\n public lights: Light[] = [];\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n constructor(private webglCore: WebGLCore) {}\n\n // ─────────────────────────────────────────────\n // ░░░ Factory: pre-configured 3-point lighting ░░░\n // ─────────────────────────────────────────────\n\n /**\n * Create a Scene pre-loaded with a classic **3-point lighting** rig\n * (ambient + key + fill + back).\n *\n * Use this for quick prototyping; for production, create a plain Scene and\n * add your own lights.\n */\n static withDefaultLights(webglCore: WebGLCore): Scene {\n const scene = new Scene(webglCore);\n\n // 🌈 AMBIENT LIGHT (Global Base Illumination)\n scene.addLight(\n 'ambient_base',\n new Light('ambient', [0, 0, 0], [0, 0, 0], [0.3, 0.3, 0.35], 0.5),\n );\n\n // 🔑 KEY LIGHT (Main Sun/Primary Source)\n scene.addLight(\n 'directional_key',\n new Light(\n 'directional',\n [5, 10, 5],\n [-0.5, -1.0, -0.5],\n [1.0, 0.95, 0.9],\n 1.2,\n ),\n );\n\n // ☁️ FILL LIGHT (Softens Shadows)\n scene.addLight(\n 'directional_fill',\n new Light(\n 'directional',\n [-10, 5, 10],\n [0.5, -0.2, -0.7],\n [0.8, 0.8, 1.0],\n 0.5,\n ),\n );\n\n // 💫 BACK LIGHT (Rim/Separation Light)\n scene.addLight(\n 'directional_back',\n new Light(\n 'directional',\n [0, 15, -10],\n [0.0, -0.8, 1.0],\n [1.0, 1.0, 1.0],\n 0.7,\n ),\n );\n\n return scene;\n }\n\n /** Register a light source in the scene. */\n public addLight(id: string, light: Light) {\n this.lights.push(light);\n\n /* Debug only */\n /* \n const lightSphere = createSphereMesh(\n this.webglCore,\n 0.25,\n 12,\n 12,\n [1, 1, 0.8],\n );\n\n lightSphere.isCollidable = false;\n lightSphere.translation = light.position;\n this.add(id + '_sphere', lightSphere);\n\n // Debug direction for directional lights\n if (light.type === 'point' || light.type === 'directional') {\n const dirMesh = createLightDirectionMesh(\n this.webglCore,\n light.position,\n light.direction,\n 5, // length of the arrow\n [1, 0, 0], // red arrow\n );\n this.add(id + '_dir', dirMesh);\n } \n */\n }\n\n /** Add a model to the scene with a unique key. */\n add(id: string, model: Model, prepend = false) {\n if (prepend) this.models.unshift(model);\n else this.models.push(model);\n this.modelsMap.set(id, model);\n }\n\n /** Remove a model from the scene by key. */\n remove(key: string) {\n const mesh = this.modelsMap.get(key);\n if (!mesh) return;\n this.models = this.models.filter((m) => m !== mesh);\n this.modelsMap.delete(key);\n }\n\n getModels() {\n return this.models;\n }\n\n getLights() {\n return this.lights;\n }\n\n /** Retrieve a model by its key. */\n public getModel(key: string) {\n const mesh = this.modelsMap.get(key);\n return mesh;\n }\n\n public hasMesh(key: string) {\n return this.modelsMap.has(key);\n }\n\n /** Returns all [id, model] pairs — useful for debug/inspector tools. */\n public getEntries(): [string, Model][] {\n return [...this.modelsMap.entries()];\n }\n\n dispose(webglCore: WebGLCore) {\n for (const model of this.models) {\n model.dispose(webglCore);\n }\n this.models.length = 0;\n this.modelsMap.clear();\n this.lights.length = 0;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -148,6 +148,8 @@ var Material = class _Material {
|
|
|
148
148
|
specular = [0.3, 0.3, 0.3];
|
|
149
149
|
shininess = 64;
|
|
150
150
|
doubleSided = false;
|
|
151
|
+
/** When true, the renderer enables alpha blending for this mesh. */
|
|
152
|
+
transparent = false;
|
|
151
153
|
//others
|
|
152
154
|
ambientColor = [0.1, 0.1, 0.1];
|
|
153
155
|
// Ka
|
|
@@ -299,4 +301,4 @@ export {
|
|
|
299
301
|
MAX_LIGHTS,
|
|
300
302
|
Material
|
|
301
303
|
};
|
|
302
|
-
//# sourceMappingURL=chunk-
|
|
304
|
+
//# sourceMappingURL=chunk-XCYJCLHB.js.map
|
|
@@ -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 /** When true, the renderer enables alpha blending for this mesh. */\n public transparent: 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 // ── Pre-allocated light uniform buffers (reused every frame, zero GC pressure) ──\n private readonly _lightDirs = new Float32Array(MAX_LIGHTS * 3);\n private readonly _lightColors = new Float32Array(MAX_LIGHTS * 3);\n private readonly _lightIntensities = new Float32Array(MAX_LIGHTS);\n private readonly _lightTypes = new Int32Array(MAX_LIGHTS);\n private readonly _lightPositions = new Float32Array(MAX_LIGHTS * 3);\n private readonly _lightConstants = new Float32Array(MAX_LIGHTS);\n private readonly _lightLinears = new Float32Array(MAX_LIGHTS);\n private readonly _lightQuadratics = new Float32Array(MAX_LIGHTS);\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 — fill pre-allocated typed arrays in-place (zero per-frame allocations)\n if (!this.unlit && lights?.length) {\n const count = Math.min(lights.length, MAX_LIGHTS);\n\n if (this.uniformLocations['uLightCount'])\n gl.uniform1i(this.uniformLocations['uLightCount'], count);\n\n for (let i = 0; i < count; i++) {\n const light = lights[i];\n const i3 = i * 3;\n\n let typeInt = 0;\n if (light.type === 'point') typeInt = 1;\n else if (light.type === 'ambient') typeInt = 2;\n this._lightTypes[i] = typeInt;\n\n const dir = light.direction ?? [0, 0, 0];\n this._lightDirs[i3] = dir[0];\n this._lightDirs[i3 + 1] = dir[1];\n this._lightDirs[i3 + 2] = dir[2];\n\n const pos = light.position ?? [0, 0, 0];\n this._lightPositions[i3] = pos[0];\n this._lightPositions[i3 + 1] = pos[1];\n this._lightPositions[i3 + 2] = pos[2];\n\n this._lightColors[i3] = light.color[0];\n this._lightColors[i3 + 1] = light.color[1];\n this._lightColors[i3 + 2] = light.color[2];\n\n this._lightIntensities[i] = light.intensity;\n this._lightConstants[i] = light.constant;\n this._lightLinears[i] = light.linear;\n this._lightQuadratics[i] = light.quadratic;\n }\n\n if (this.uniformLocations['uLightDirection[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightDirection[0]'],\n this._lightDirs,\n );\n if (this.uniformLocations['uLightColor[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightColor[0]'],\n this._lightColors,\n );\n if (this.uniformLocations['uLightIntensity[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightIntensity[0]'],\n this._lightIntensities,\n );\n if (this.uniformLocations['uLightType[0]'])\n gl.uniform1iv(this.uniformLocations['uLightType[0]'], this._lightTypes);\n if (this.uniformLocations['uLightPosition[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightPosition[0]'],\n this._lightPositions,\n );\n if (this.uniformLocations['uLightConstant[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightConstant[0]'],\n this._lightConstants,\n );\n if (this.uniformLocations['uLightLinear[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightLinear[0]'],\n this._lightLinears,\n );\n if (this.uniformLocations['uLightQuadratic[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightQuadratic[0]'],\n this._lightQuadratics,\n );\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
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 // ── Pre-allocated light uniform buffers (reused every frame, zero GC pressure) ──\n private readonly _lightDirs = new Float32Array(MAX_LIGHTS * 3);\n private readonly _lightColors = new Float32Array(MAX_LIGHTS * 3);\n private readonly _lightIntensities = new Float32Array(MAX_LIGHTS);\n private readonly _lightTypes = new Int32Array(MAX_LIGHTS);\n private readonly _lightPositions = new Float32Array(MAX_LIGHTS * 3);\n private readonly _lightConstants = new Float32Array(MAX_LIGHTS);\n private readonly _lightLinears = new Float32Array(MAX_LIGHTS);\n private readonly _lightQuadratics = new Float32Array(MAX_LIGHTS);\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 — fill pre-allocated typed arrays in-place (zero per-frame allocations)\n if (!this.unlit && lights?.length) {\n const count = Math.min(lights.length, MAX_LIGHTS);\n\n if (this.uniformLocations['uLightCount'])\n gl.uniform1i(this.uniformLocations['uLightCount'], count);\n\n for (let i = 0; i < count; i++) {\n const light = lights[i];\n const i3 = i * 3;\n\n let typeInt = 0;\n if (light.type === 'point') typeInt = 1;\n else if (light.type === 'ambient') typeInt = 2;\n this._lightTypes[i] = typeInt;\n\n const dir = light.direction ?? [0, 0, 0];\n this._lightDirs[i3] = dir[0];\n this._lightDirs[i3 + 1] = dir[1];\n this._lightDirs[i3 + 2] = dir[2];\n\n const pos = light.position ?? [0, 0, 0];\n this._lightPositions[i3] = pos[0];\n this._lightPositions[i3 + 1] = pos[1];\n this._lightPositions[i3 + 2] = pos[2];\n\n this._lightColors[i3] = light.color[0];\n this._lightColors[i3 + 1] = light.color[1];\n this._lightColors[i3 + 2] = light.color[2];\n\n this._lightIntensities[i] = light.intensity;\n this._lightConstants[i] = light.constant;\n this._lightLinears[i] = light.linear;\n this._lightQuadratics[i] = light.quadratic;\n }\n\n if (this.uniformLocations['uLightDirection[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightDirection[0]'],\n this._lightDirs,\n );\n if (this.uniformLocations['uLightColor[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightColor[0]'],\n this._lightColors,\n );\n if (this.uniformLocations['uLightIntensity[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightIntensity[0]'],\n this._lightIntensities,\n );\n if (this.uniformLocations['uLightType[0]'])\n gl.uniform1iv(this.uniformLocations['uLightType[0]'], this._lightTypes);\n if (this.uniformLocations['uLightPosition[0]'])\n gl.uniform3fv(\n this.uniformLocations['uLightPosition[0]'],\n this._lightPositions,\n );\n if (this.uniformLocations['uLightConstant[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightConstant[0]'],\n this._lightConstants,\n );\n if (this.uniformLocations['uLightLinear[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightLinear[0]'],\n this._lightLinears,\n );\n if (this.uniformLocations['uLightQuadratic[0]'])\n gl.uniform1fv(\n this.uniformLocations['uLightQuadratic[0]'],\n this._lightQuadratics,\n );\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-SUNYSY45.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
// src/Core/controls/KeyboardControl.ts
|
|
2
|
-
var KeyboardControl = class {
|
|
3
|
-
pressedKeys = /* @__PURE__ */ new Set();
|
|
4
|
-
listeners = [];
|
|
5
|
-
keyDownHandler;
|
|
6
|
-
keyUpHandler;
|
|
7
|
-
enable() {
|
|
8
|
-
if (this.keyDownHandler || this.keyUpHandler) return;
|
|
9
|
-
this.keyDownHandler = (e) => {
|
|
10
|
-
const key = this.mapKey(e.key);
|
|
11
|
-
if (!key) return;
|
|
12
|
-
if (!this.pressedKeys.has(key)) {
|
|
13
|
-
this.pressedKeys.add(key);
|
|
14
|
-
this.notify(key, true);
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
this.keyUpHandler = (e) => {
|
|
18
|
-
const key = this.mapKey(e.key);
|
|
19
|
-
if (!key) return;
|
|
20
|
-
if (this.pressedKeys.has(key)) {
|
|
21
|
-
this.pressedKeys.delete(key);
|
|
22
|
-
this.notify(key, false);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
window.addEventListener("keydown", this.keyDownHandler);
|
|
26
|
-
window.addEventListener("keyup", this.keyUpHandler);
|
|
27
|
-
}
|
|
28
|
-
disable() {
|
|
29
|
-
if (this.keyDownHandler) {
|
|
30
|
-
window.removeEventListener("keydown", this.keyDownHandler);
|
|
31
|
-
this.keyDownHandler = void 0;
|
|
32
|
-
}
|
|
33
|
-
if (this.keyUpHandler) {
|
|
34
|
-
window.removeEventListener("keyup", this.keyUpHandler);
|
|
35
|
-
this.keyUpHandler = void 0;
|
|
36
|
-
}
|
|
37
|
-
this.pressedKeys.clear();
|
|
38
|
-
}
|
|
39
|
-
onChange(listener) {
|
|
40
|
-
this.listeners.push(listener);
|
|
41
|
-
}
|
|
42
|
-
/** Remove a previously registered listener. */
|
|
43
|
-
removeListener(listener) {
|
|
44
|
-
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
45
|
-
}
|
|
46
|
-
notify(key, isPressed) {
|
|
47
|
-
for (const listener of this.listeners) {
|
|
48
|
-
listener(key, isPressed);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
mapKey(key) {
|
|
52
|
-
switch (key) {
|
|
53
|
-
case "w":
|
|
54
|
-
case "ArrowUp":
|
|
55
|
-
return "up";
|
|
56
|
-
case "s":
|
|
57
|
-
case "ArrowDown":
|
|
58
|
-
return "down";
|
|
59
|
-
case "a":
|
|
60
|
-
case "ArrowLeft":
|
|
61
|
-
return "left";
|
|
62
|
-
case "d":
|
|
63
|
-
case "ArrowRight":
|
|
64
|
-
return "right";
|
|
65
|
-
case " ":
|
|
66
|
-
return "jump";
|
|
67
|
-
case "Shift":
|
|
68
|
-
return "roll";
|
|
69
|
-
default:
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
isPressed(key) {
|
|
74
|
-
return this.pressedKeys.has(key);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export {
|
|
79
|
-
KeyboardControl
|
|
80
|
-
};
|
|
81
|
-
//# sourceMappingURL=chunk-SUNYSY45.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/Core/controls/KeyboardControl.ts"],"sourcesContent":["type ControlKey = 'up' | 'down' | 'left' | 'right' | 'jump' | 'roll';\n\ntype ControlListener = (key: ControlKey, isPressed: boolean) => void;\n\nexport class KeyboardControl {\n private pressedKeys: Set<ControlKey> = new Set();\n private listeners: ControlListener[] = [];\n private keyDownHandler?: (e: KeyboardEvent) => void;\n private keyUpHandler?: (e: KeyboardEvent) => void;\n\n public enable() {\n if (this.keyDownHandler || this.keyUpHandler) return; // prevent duplicates\n\n this.keyDownHandler = (e: KeyboardEvent) => {\n const key = this.mapKey(e.key);\n if (!key) return;\n\n if (!this.pressedKeys.has(key)) {\n this.pressedKeys.add(key);\n this.notify(key, true);\n }\n };\n\n this.keyUpHandler = (e: KeyboardEvent) => {\n const key = this.mapKey(e.key);\n if (!key) return;\n\n if (this.pressedKeys.has(key)) {\n this.pressedKeys.delete(key);\n this.notify(key, false);\n }\n };\n\n window.addEventListener('keydown', this.keyDownHandler);\n window.addEventListener('keyup', this.keyUpHandler);\n }\n\n public disable() {\n if (this.keyDownHandler) {\n window.removeEventListener('keydown', this.keyDownHandler);\n this.keyDownHandler = undefined;\n }\n if (this.keyUpHandler) {\n window.removeEventListener('keyup', this.keyUpHandler);\n this.keyUpHandler = undefined;\n }\n this.pressedKeys.clear();\n }\n\n public onChange(listener: ControlListener) {\n this.listeners.push(listener);\n }\n\n /** Remove a previously registered listener. */\n public removeListener(listener: ControlListener) {\n this.listeners = this.listeners.filter((l) => l !== listener);\n }\n\n private notify(key: ControlKey, isPressed: boolean) {\n for (const listener of this.listeners) {\n listener(key, isPressed);\n }\n }\n\n private mapKey(key: string): ControlKey | null {\n switch (key) {\n case 'w':\n case 'ArrowUp':\n return 'up';\n case 's':\n case 'ArrowDown':\n return 'down';\n case 'a':\n case 'ArrowLeft':\n return 'left';\n case 'd':\n case 'ArrowRight':\n return 'right';\n case ' ':\n return 'jump';\n case 'Shift':\n return 'roll';\n default:\n return null;\n }\n }\n\n public isPressed(key: ControlKey): boolean {\n return this.pressedKeys.has(key);\n }\n}\n"],"mappings":";AAIO,IAAM,kBAAN,MAAsB;AAAA,EACnB,cAA+B,oBAAI,IAAI;AAAA,EACvC,YAA+B,CAAC;AAAA,EAChC;AAAA,EACA;AAAA,EAED,SAAS;AACd,QAAI,KAAK,kBAAkB,KAAK,aAAc;AAE9C,SAAK,iBAAiB,CAAC,MAAqB;AAC1C,YAAM,MAAM,KAAK,OAAO,EAAE,GAAG;AAC7B,UAAI,CAAC,IAAK;AAEV,UAAI,CAAC,KAAK,YAAY,IAAI,GAAG,GAAG;AAC9B,aAAK,YAAY,IAAI,GAAG;AACxB,aAAK,OAAO,KAAK,IAAI;AAAA,MACvB;AAAA,IACF;AAEA,SAAK,eAAe,CAAC,MAAqB;AACxC,YAAM,MAAM,KAAK,OAAO,EAAE,GAAG;AAC7B,UAAI,CAAC,IAAK;AAEV,UAAI,KAAK,YAAY,IAAI,GAAG,GAAG;AAC7B,aAAK,YAAY,OAAO,GAAG;AAC3B,aAAK,OAAO,KAAK,KAAK;AAAA,MACxB;AAAA,IACF;AAEA,WAAO,iBAAiB,WAAW,KAAK,cAAc;AACtD,WAAO,iBAAiB,SAAS,KAAK,YAAY;AAAA,EACpD;AAAA,EAEO,UAAU;AACf,QAAI,KAAK,gBAAgB;AACvB,aAAO,oBAAoB,WAAW,KAAK,cAAc;AACzD,WAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,KAAK,cAAc;AACrB,aAAO,oBAAoB,SAAS,KAAK,YAAY;AACrD,WAAK,eAAe;AAAA,IACtB;AACA,SAAK,YAAY,MAAM;AAAA,EACzB;AAAA,EAEO,SAAS,UAA2B;AACzC,SAAK,UAAU,KAAK,QAAQ;AAAA,EAC9B;AAAA;AAAA,EAGO,eAAe,UAA2B;AAC/C,SAAK,YAAY,KAAK,UAAU,OAAO,CAAC,MAAM,MAAM,QAAQ;AAAA,EAC9D;AAAA,EAEQ,OAAO,KAAiB,WAAoB;AAClD,eAAW,YAAY,KAAK,WAAW;AACrC,eAAS,KAAK,SAAS;AAAA,IACzB;AAAA,EACF;AAAA,EAEQ,OAAO,KAAgC;AAC7C,YAAQ,KAAK;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEO,UAAU,KAA0B;AACzC,WAAO,KAAK,YAAY,IAAI,GAAG;AAAA,EACjC;AACF;","names":[]}
|