pixospritz-core 0.10.1 → 1.0.1

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.
Files changed (157) hide show
  1. package/README.md +36 -286
  2. package/dist/bundle.js +13 -3
  3. package/dist/bundle.js.map +1 -1
  4. package/dist/style.css +1 -0
  5. package/package.json +43 -44
  6. package/src/components/WebGLView.jsx +318 -0
  7. package/src/css/pixos.css +372 -0
  8. package/src/engine/actions/animate.js +41 -0
  9. package/src/engine/actions/changezone.js +135 -0
  10. package/src/engine/actions/chat.js +109 -0
  11. package/src/engine/actions/dialogue.js +90 -0
  12. package/src/engine/actions/face.js +22 -0
  13. package/src/engine/actions/greeting.js +28 -0
  14. package/src/engine/actions/interact.js +86 -0
  15. package/src/engine/actions/move.js +67 -0
  16. package/src/engine/actions/patrol.js +109 -0
  17. package/src/engine/actions/prompt.js +185 -0
  18. package/src/engine/actions/script.js +42 -0
  19. package/src/engine/core/audio/AudioSystem.js +543 -0
  20. package/src/engine/core/cutscene/PxcPlayer.js +956 -0
  21. package/src/engine/core/cutscene/manager.js +243 -0
  22. package/src/engine/core/database/index.js +75 -0
  23. package/src/engine/core/debug/index.js +371 -0
  24. package/src/engine/core/hud/index.js +765 -0
  25. package/src/engine/core/index.js +540 -0
  26. package/src/engine/core/input/gamepad/Controller.js +71 -0
  27. package/src/engine/core/input/gamepad/ControllerButtons.js +231 -0
  28. package/src/engine/core/input/gamepad/ControllerStick.js +173 -0
  29. package/src/engine/core/input/gamepad/index.js +592 -0
  30. package/src/engine/core/input/keyboard.js +196 -0
  31. package/src/engine/core/input/manager.js +485 -0
  32. package/src/engine/core/input/mouse.js +203 -0
  33. package/src/engine/core/input/touch.js +175 -0
  34. package/src/engine/core/mode/manager.js +199 -0
  35. package/src/engine/core/net/manager.js +535 -0
  36. package/src/engine/core/queue/action.js +83 -0
  37. package/src/engine/core/queue/event.js +82 -0
  38. package/src/engine/core/queue/index.js +44 -0
  39. package/src/engine/core/queue/loadable.js +33 -0
  40. package/src/engine/core/render/CameraEffects.js +494 -0
  41. package/src/engine/core/render/FrustumCuller.js +417 -0
  42. package/src/engine/core/render/LODManager.js +285 -0
  43. package/src/engine/core/render/ParticleManager.js +529 -0
  44. package/src/engine/core/render/TextureAtlas.js +465 -0
  45. package/src/engine/core/render/camera.js +338 -0
  46. package/src/engine/core/render/light.js +197 -0
  47. package/src/engine/core/render/manager.js +1079 -0
  48. package/src/engine/core/render/shaders.js +110 -0
  49. package/src/engine/core/render/skybox.js +342 -0
  50. package/src/engine/core/resource/manager.js +133 -0
  51. package/src/engine/core/resource/object.js +611 -0
  52. package/src/engine/core/resource/texture.js +103 -0
  53. package/src/engine/core/resource/tileset.js +177 -0
  54. package/src/engine/core/scene/avatar.js +215 -0
  55. package/src/engine/core/scene/speech.js +138 -0
  56. package/src/engine/core/scene/sprite.js +702 -0
  57. package/src/engine/core/scene/spritz.js +189 -0
  58. package/src/engine/core/scene/world.js +681 -0
  59. package/src/engine/core/scene/zone.js +1167 -0
  60. package/src/engine/core/store/index.js +110 -0
  61. package/src/engine/dynamic/animatedSprite.js +64 -0
  62. package/src/engine/dynamic/animatedTile.js +98 -0
  63. package/src/engine/dynamic/avatar.js +110 -0
  64. package/src/engine/dynamic/map.js +174 -0
  65. package/src/engine/dynamic/sprite.js +255 -0
  66. package/src/engine/dynamic/spritz.js +119 -0
  67. package/src/engine/events/EventSystem.js +609 -0
  68. package/src/engine/events/camera.js +142 -0
  69. package/src/engine/events/chat.js +75 -0
  70. package/src/engine/events/menu.js +186 -0
  71. package/src/engine/scripting/CallbackManager.js +514 -0
  72. package/src/engine/scripting/PixoScriptInterpreter.js +81 -0
  73. package/src/engine/scripting/PixoScriptLibrary.js +704 -0
  74. package/src/engine/shaders/effects/index.js +450 -0
  75. package/src/engine/shaders/fs.js +222 -0
  76. package/src/engine/shaders/particles/fs.js +41 -0
  77. package/src/engine/shaders/particles/vs.js +61 -0
  78. package/src/engine/shaders/picker/fs.js +34 -0
  79. package/src/engine/shaders/picker/init.js +62 -0
  80. package/src/engine/shaders/picker/vs.js +42 -0
  81. package/src/engine/shaders/pxsl/README.md +250 -0
  82. package/src/engine/shaders/pxsl/index.js +25 -0
  83. package/src/engine/shaders/pxsl/library.js +608 -0
  84. package/src/engine/shaders/pxsl/manager.js +338 -0
  85. package/src/engine/shaders/pxsl/specification.js +363 -0
  86. package/src/engine/shaders/pxsl/transpiler.js +753 -0
  87. package/src/engine/shaders/skybox/cosmic/fs.js +147 -0
  88. package/src/engine/shaders/skybox/cosmic/vs.js +23 -0
  89. package/src/engine/shaders/skybox/matrix/fs.js +127 -0
  90. package/src/engine/shaders/skybox/matrix/vs.js +23 -0
  91. package/src/engine/shaders/skybox/morning/fs.js +109 -0
  92. package/src/engine/shaders/skybox/morning/vs.js +23 -0
  93. package/src/engine/shaders/skybox/neon/fs.js +119 -0
  94. package/src/engine/shaders/skybox/neon/vs.js +23 -0
  95. package/src/engine/shaders/skybox/sky/fs.js +114 -0
  96. package/src/engine/shaders/skybox/sky/vs.js +23 -0
  97. package/src/engine/shaders/skybox/sunset/fs.js +101 -0
  98. package/src/engine/shaders/skybox/sunset/vs.js +23 -0
  99. package/src/engine/shaders/transition/blur/fs.js +42 -0
  100. package/src/engine/shaders/transition/blur/vs.js +26 -0
  101. package/src/engine/shaders/transition/cross/fs.js +36 -0
  102. package/src/engine/shaders/transition/cross/vs.js +26 -0
  103. package/src/engine/shaders/transition/crossBlur/fs.js +41 -0
  104. package/src/engine/shaders/transition/crossBlur/vs.js +25 -0
  105. package/src/engine/shaders/transition/dissolve/fs.js +78 -0
  106. package/src/engine/shaders/transition/dissolve/vs.js +24 -0
  107. package/src/engine/shaders/transition/fade/fs.js +31 -0
  108. package/src/engine/shaders/transition/fade/vs.js +27 -0
  109. package/src/engine/shaders/transition/iris/fs.js +52 -0
  110. package/src/engine/shaders/transition/iris/vs.js +24 -0
  111. package/src/engine/shaders/transition/pixelate/fs.js +44 -0
  112. package/src/engine/shaders/transition/pixelate/vs.js +24 -0
  113. package/src/engine/shaders/transition/slide/fs.js +53 -0
  114. package/src/engine/shaders/transition/slide/vs.js +24 -0
  115. package/src/engine/shaders/transition/swirl/fs.js +39 -0
  116. package/src/engine/shaders/transition/swirl/vs.js +26 -0
  117. package/src/engine/shaders/transition/wipe/fs.js +50 -0
  118. package/src/engine/shaders/transition/wipe/vs.js +24 -0
  119. package/src/engine/shaders/vs.js +60 -0
  120. package/src/engine/utils/CameraController.js +506 -0
  121. package/src/engine/utils/ObjHelper.js +551 -0
  122. package/src/engine/utils/debug-logger.js +110 -0
  123. package/src/engine/utils/enums.js +305 -0
  124. package/src/engine/utils/generator.js +156 -0
  125. package/src/engine/utils/index.js +21 -0
  126. package/src/engine/utils/loaders/ActionLoader.js +77 -0
  127. package/src/engine/utils/loaders/AudioLoader.js +157 -0
  128. package/src/engine/utils/loaders/EventLoader.js +66 -0
  129. package/src/engine/utils/loaders/ObjectLoader.js +67 -0
  130. package/src/engine/utils/loaders/SpriteLoader.js +77 -0
  131. package/src/engine/utils/loaders/TilesetLoader.js +103 -0
  132. package/src/engine/utils/loaders/index.js +21 -0
  133. package/src/engine/utils/math/matrix4.js +367 -0
  134. package/src/engine/utils/math/vector.js +458 -0
  135. package/src/engine/utils/obj/_old_js/index.js +46 -0
  136. package/src/engine/utils/obj/_old_js/layout.js +308 -0
  137. package/src/engine/utils/obj/_old_js/material.js +711 -0
  138. package/src/engine/utils/obj/_old_js/mesh.js +761 -0
  139. package/src/engine/utils/obj/_old_js/utils.js +647 -0
  140. package/src/engine/utils/obj/index.js +24 -0
  141. package/src/engine/utils/obj/js/index.js +277 -0
  142. package/src/engine/utils/obj/js/loader.js +232 -0
  143. package/src/engine/utils/obj/layout.js +246 -0
  144. package/src/engine/utils/obj/material.js +665 -0
  145. package/src/engine/utils/obj/mesh.js +657 -0
  146. package/src/engine/utils/obj/ts/index.ts +72 -0
  147. package/src/engine/utils/obj/ts/layout.ts +265 -0
  148. package/src/engine/utils/obj/ts/material.ts +760 -0
  149. package/src/engine/utils/obj/ts/mesh.ts +785 -0
  150. package/src/engine/utils/obj/ts/utils.ts +501 -0
  151. package/src/engine/utils/obj/utils.js +428 -0
  152. package/src/engine/utils/resources.js +18 -0
  153. package/src/index.jsx +55 -0
  154. package/src/spritz/player.js +18 -0
  155. package/src/spritz/readme.md +18 -0
  156. package/LICENSE +0 -437
  157. package/dist/bundle.js.LICENSE.txt +0 -31
@@ -0,0 +1,551 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ /**
15
+ * ObjHelper - A clean, modern OBJ/MTL parser and loader
16
+ *
17
+ * Based on the patterns from ObjModelViewer.jsx, this helper provides:
18
+ * - Simple OBJ parsing with face triangulation
19
+ * - MTL material parsing with texture support
20
+ * - Automatic face normal calculation when vertex normals are missing
21
+ * - Per-mesh material assignment
22
+ * - WebGL buffer initialization
23
+ *
24
+ * @example
25
+ * const helper = new ObjHelper(gl);
26
+ * const meshes = helper.parseOBJ(objText);
27
+ * const materials = helper.parseMTL(mtlText);
28
+ * helper.assignMaterials(meshes, materials);
29
+ * await helper.loadTextures(meshes, textureMap);
30
+ * helper.initBuffers(meshes);
31
+ */
32
+
33
+ /**
34
+ * Simple vec3 math utilities
35
+ */
36
+ const vec3 = {
37
+ sub: (a, b, out = [0, 0, 0]) => {
38
+ out[0] = a[0] - b[0];
39
+ out[1] = a[1] - b[1];
40
+ out[2] = a[2] - b[2];
41
+ return out;
42
+ },
43
+ cross: (a, b, out = [0, 0, 0]) => {
44
+ const ax = a[0], ay = a[1], az = a[2];
45
+ const bx = b[0], by = b[1], bz = b[2];
46
+ out[0] = ay * bz - az * by;
47
+ out[1] = az * bx - ax * bz;
48
+ out[2] = ax * by - ay * bx;
49
+ return out;
50
+ },
51
+ length: (v) => Math.hypot(v[0], v[1], v[2]),
52
+ normalize: (v, out = [0, 0, 0]) => {
53
+ const len = vec3.length(v);
54
+ if (len === 0) return out;
55
+ out[0] = v[0] / len;
56
+ out[1] = v[1] / len;
57
+ out[2] = v[2] / len;
58
+ return out;
59
+ },
60
+ };
61
+
62
+ /**
63
+ * Parsed mesh structure
64
+ * @typedef {Object} ParsedMesh
65
+ * @property {number[]} positions - Flat array of vertex positions (x,y,z,...)
66
+ * @property {number[]} normals - Flat array of vertex normals (nx,ny,nz,...)
67
+ * @property {number[]} uvs - Flat array of texture coordinates (u,v,...)
68
+ * @property {string} material - Material name from usemtl directive
69
+ * @property {Object} [materialProps] - Material properties after assignment
70
+ * @property {WebGLTexture} [texture] - Loaded texture after texture loading
71
+ * @property {boolean} [hasTexture] - Whether mesh has a texture
72
+ * @property {WebGLVertexArrayObject} [vao] - Vertex array object after buffer init
73
+ * @property {number} [count] - Number of vertices for drawArrays
74
+ * @property {WebGLBuffer} [vertexBuffer] - Position buffer (for legacy compatibility)
75
+ * @property {WebGLBuffer} [normalBuffer] - Normal buffer (for legacy compatibility)
76
+ * @property {WebGLBuffer} [textureBuffer] - UV buffer (for legacy compatibility)
77
+ */
78
+
79
+ /**
80
+ * Parsed material structure
81
+ * @typedef {Object} ParsedMaterial
82
+ * @property {number[]} Ka - Ambient color [r,g,b]
83
+ * @property {number[]} Kd - Diffuse color [r,g,b]
84
+ * @property {number[]} Ks - Specular color [r,g,b]
85
+ * @property {number} Ns - Specular exponent
86
+ * @property {string} [map_Kd] - Diffuse texture filename
87
+ */
88
+
89
+ export default class ObjHelper {
90
+ /**
91
+ * @param {WebGL2RenderingContext} gl - WebGL context
92
+ */
93
+ constructor(gl) {
94
+ this.gl = gl;
95
+ }
96
+
97
+ /**
98
+ * Parse OBJ file text into mesh arrays
99
+ * Supports:
100
+ * - v (vertex positions)
101
+ * - vt (texture coordinates)
102
+ * - vn (vertex normals)
103
+ * - f (faces with triangulation)
104
+ * - usemtl (material assignment)
105
+ *
106
+ * @param {string} text - OBJ file content
107
+ * @returns {ParsedMesh[]} Array of parsed meshes, one per material group
108
+ */
109
+ parseOBJ(text) {
110
+ const lines = text.split(/\r?\n/);
111
+
112
+ // Global vertex data (1-indexed in OBJ format)
113
+ const positions = [];
114
+ const uvs = [];
115
+ const normals = [];
116
+
117
+ // Meshes split by material
118
+ const meshes = [];
119
+ let currentMesh = { positions: [], uvs: [], normals: [], material: 'default' };
120
+ let currentMaterial = 'default';
121
+
122
+ for (let line of lines) {
123
+ line = line.trim();
124
+ if (!line || line[0] === '#') continue;
125
+ const parts = line.split(/\s+/);
126
+
127
+ switch (parts[0]) {
128
+ case 'v':
129
+ // Vertex position
130
+ positions.push(
131
+ parseFloat(parts[1]),
132
+ parseFloat(parts[2]),
133
+ parseFloat(parts[3])
134
+ );
135
+ break;
136
+
137
+ case 'vt':
138
+ // Texture coordinate (flip Y for OpenGL convention)
139
+ uvs.push(
140
+ parseFloat(parts[1]),
141
+ 1.0 - parseFloat(parts[2])
142
+ );
143
+ break;
144
+
145
+ case 'vn':
146
+ // Vertex normal
147
+ normals.push(
148
+ parseFloat(parts[1]),
149
+ parseFloat(parts[2]),
150
+ parseFloat(parts[3])
151
+ );
152
+ break;
153
+
154
+ case 'f':
155
+ // Face - triangulate if more than 3 vertices
156
+ const faceVerts = parts.slice(1).map(v => {
157
+ const idx = v.split('/');
158
+ return {
159
+ v: idx[0] ? parseInt(idx[0]) : null,
160
+ vt: idx[1] && idx[1] !== '' ? parseInt(idx[1]) : null,
161
+ vn: idx[2] && idx[2] !== '' ? parseInt(idx[2]) : null,
162
+ };
163
+ });
164
+
165
+ // Triangulate face (fan triangulation)
166
+ for (let i = 1; i < faceVerts.length - 1; i++) {
167
+ const fv = [faceVerts[0], faceVerts[i], faceVerts[i + 1]];
168
+
169
+ // Get positions for this triangle
170
+ const triPos = fv.map(f => {
171
+ if (!f.v) return [0, 0, 0];
172
+ const idx = f.v > 0 ? f.v - 1 : positions.length / 3 + f.v;
173
+ const off = idx * 3;
174
+ return [positions[off], positions[off + 1], positions[off + 2]];
175
+ });
176
+
177
+ // Get UVs for this triangle
178
+ const triUv = fv.map(f => {
179
+ if (!f.vt) return [0, 0];
180
+ const idx = f.vt > 0 ? f.vt - 1 : uvs.length / 2 + f.vt;
181
+ const off = idx * 2;
182
+ return [uvs[off], uvs[off + 1]];
183
+ });
184
+
185
+ // Calculate face normal if vertex normals are missing
186
+ const useFaceNormal = fv[0].vn == null && fv[1].vn == null && fv[2].vn == null;
187
+ let faceNormal = [0, 0, 0];
188
+ if (useFaceNormal) {
189
+ const e1 = vec3.sub(triPos[1], triPos[0]);
190
+ const e2 = vec3.sub(triPos[2], triPos[0]);
191
+ vec3.cross(e1, e2, faceNormal);
192
+ vec3.normalize(faceNormal, faceNormal);
193
+ }
194
+
195
+ // Add each vertex of the triangle
196
+ for (let k = 0; k < 3; k++) {
197
+ currentMesh.positions.push(triPos[k][0], triPos[k][1], triPos[k][2]);
198
+ currentMesh.uvs.push(triUv[k][0], triUv[k][1]);
199
+
200
+ if (fv[k].vn != null) {
201
+ const idx = fv[k].vn > 0 ? fv[k].vn - 1 : normals.length / 3 + fv[k].vn;
202
+ const off = idx * 3;
203
+ currentMesh.normals.push(normals[off], normals[off + 1], normals[off + 2]);
204
+ } else {
205
+ currentMesh.normals.push(faceNormal[0], faceNormal[1], faceNormal[2]);
206
+ }
207
+ }
208
+ }
209
+ break;
210
+
211
+ case 'usemtl':
212
+ // Start new mesh for new material
213
+ if (currentMesh.positions.length > 0) {
214
+ meshes.push(currentMesh);
215
+ }
216
+ currentMaterial = parts[1];
217
+ currentMesh = { positions: [], uvs: [], normals: [], material: currentMaterial };
218
+ break;
219
+ }
220
+ }
221
+
222
+ // Don't forget the last mesh
223
+ if (currentMesh.positions.length > 0) {
224
+ meshes.push(currentMesh);
225
+ }
226
+
227
+ return meshes;
228
+ }
229
+
230
+ /**
231
+ * Parse MTL file text into material definitions
232
+ * Supports:
233
+ * - newmtl (new material)
234
+ * - Ka, Kd, Ks (ambient, diffuse, specular colors)
235
+ * - Ns (specular exponent)
236
+ * - map_Kd (diffuse texture map)
237
+ *
238
+ * @param {string} text - MTL file content
239
+ * @returns {Object.<string, ParsedMaterial>} Material definitions keyed by name
240
+ */
241
+ parseMTL(text) {
242
+ const materials = {};
243
+ let current = null;
244
+ const lines = text.split(/\r?\n/);
245
+
246
+ for (let line of lines) {
247
+ line = line.trim();
248
+ if (!line || line.startsWith('#')) continue;
249
+ const parts = line.split(/\s+/);
250
+
251
+ switch (parts[0]) {
252
+ case 'newmtl':
253
+ current = parts[1];
254
+ materials[current] = {
255
+ Ka: [1, 1, 1],
256
+ Kd: [0.8, 0.8, 0.8],
257
+ Ks: [1, 1, 1],
258
+ Ns: 50
259
+ };
260
+ break;
261
+
262
+ case 'Ka':
263
+ case 'Kd':
264
+ case 'Ks':
265
+ if (current) {
266
+ const vals = parts.slice(1).map(parseFloat);
267
+ materials[current][parts[0]] = vals.length === 1
268
+ ? [vals[0], vals[0], vals[0]]
269
+ : vals;
270
+ }
271
+ break;
272
+
273
+ case 'Ns':
274
+ if (current) {
275
+ materials[current].Ns = parseFloat(parts[1]);
276
+ }
277
+ break;
278
+
279
+ case 'map_Kd':
280
+ if (current) {
281
+ // Handle paths with spaces
282
+ materials[current].map_Kd = parts.slice(1).join(' ');
283
+ }
284
+ break;
285
+ }
286
+ }
287
+
288
+ return materials;
289
+ }
290
+
291
+ /**
292
+ * Assign material properties to meshes
293
+ * @param {ParsedMesh[]} meshes - Parsed meshes
294
+ * @param {Object.<string, ParsedMaterial>} materials - Parsed materials
295
+ * @param {Object.<string, WebGLTexture>} [textures={}] - Pre-loaded textures keyed by filename
296
+ */
297
+ assignMaterials(meshes, materials, textures = {}) {
298
+ const defaultMaterial = {
299
+ Ka: [0.25, 0.25, 0.3],
300
+ Kd: [0.75, 0.75, 0.75],
301
+ Ks: [1, 1, 1],
302
+ Ns: 50
303
+ };
304
+
305
+ meshes.forEach(mesh => {
306
+ mesh.materialProps = materials[mesh.material] || defaultMaterial;
307
+
308
+ // Look for texture
309
+ let tex = null;
310
+ if (mesh.materialProps.map_Kd) {
311
+ // Extract filename from path
312
+ const texName = mesh.materialProps.map_Kd.split('/').pop();
313
+ tex = textures[texName] || null;
314
+ }
315
+ mesh.texture = tex;
316
+ mesh.hasTexture = !!tex;
317
+ });
318
+ }
319
+
320
+ /**
321
+ * Load a texture from an image source
322
+ * @param {string|HTMLImageElement|Blob} source - Image URL, Image element, or Blob
323
+ * @returns {Promise<WebGLTexture>} Loaded texture
324
+ */
325
+ async loadTexture(source) {
326
+ const gl = this.gl;
327
+
328
+ return new Promise((resolve, reject) => {
329
+ const img = new Image();
330
+
331
+ img.onload = () => {
332
+ const tex = gl.createTexture();
333
+ gl.bindTexture(gl.TEXTURE_2D, tex);
334
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
335
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
336
+
337
+ // Power of 2 textures get mipmaps
338
+ if ((img.width & (img.width - 1)) === 0 && (img.height & (img.height - 1)) === 0) {
339
+ gl.generateMipmap(gl.TEXTURE_2D);
340
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
341
+ } else {
342
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
343
+ }
344
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
345
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
346
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
347
+
348
+ // Clean up blob URL if used
349
+ if (typeof source !== 'string' && !(source instanceof HTMLImageElement)) {
350
+ URL.revokeObjectURL(img.src);
351
+ }
352
+
353
+ resolve(tex);
354
+ };
355
+
356
+ img.onerror = () => {
357
+ if (typeof source !== 'string' && !(source instanceof HTMLImageElement)) {
358
+ URL.revokeObjectURL(img.src);
359
+ }
360
+ reject(new Error(`Failed to load texture`));
361
+ };
362
+
363
+ // Set source
364
+ if (typeof source === 'string') {
365
+ img.src = source;
366
+ } else if (source instanceof HTMLImageElement) {
367
+ // Already an image
368
+ img.src = source.src;
369
+ } else if (source instanceof Blob) {
370
+ img.src = URL.createObjectURL(source);
371
+ }
372
+ });
373
+ }
374
+
375
+ /**
376
+ * Create a 1x1 placeholder texture
377
+ * @param {number[]} color - RGBA color [r,g,b,a] 0-255
378
+ * @returns {WebGLTexture} Placeholder texture
379
+ */
380
+ createPlaceholderTexture(color = [128, 128, 128, 255]) {
381
+ const gl = this.gl;
382
+ const tex = gl.createTexture();
383
+ gl.bindTexture(gl.TEXTURE_2D, tex);
384
+ gl.texImage2D(
385
+ gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0,
386
+ gl.RGBA, gl.UNSIGNED_BYTE,
387
+ new Uint8Array(color)
388
+ );
389
+ return tex;
390
+ }
391
+
392
+ /**
393
+ * Initialize WebGL buffers for meshes
394
+ * Creates VAO with position, normal, and UV buffers
395
+ * Also creates legacy-compatible separate buffer references
396
+ *
397
+ * @param {ParsedMesh[]} meshes - Parsed meshes to initialize
398
+ */
399
+ initBuffers(meshes) {
400
+ const gl = this.gl;
401
+
402
+ meshes.forEach(mesh => {
403
+ // Create VAO for modern WebGL2 approach
404
+ const vao = gl.createVertexArray();
405
+ gl.bindVertexArray(vao);
406
+
407
+ // Position buffer (attribute 0)
408
+ const posBuf = gl.createBuffer();
409
+ gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
410
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh.positions), gl.STATIC_DRAW);
411
+ gl.enableVertexAttribArray(0);
412
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
413
+
414
+ // Normal buffer (attribute 1)
415
+ const normBuf = gl.createBuffer();
416
+ gl.bindBuffer(gl.ARRAY_BUFFER, normBuf);
417
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh.normals), gl.STATIC_DRAW);
418
+ gl.enableVertexAttribArray(1);
419
+ gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
420
+
421
+ // UV buffer (attribute 2)
422
+ const uvBuf = gl.createBuffer();
423
+ gl.bindBuffer(gl.ARRAY_BUFFER, uvBuf);
424
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh.uvs), gl.STATIC_DRAW);
425
+ gl.enableVertexAttribArray(2);
426
+ gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 0, 0);
427
+
428
+ // Store references
429
+ mesh.vao = vao;
430
+ mesh.count = mesh.positions.length / 3;
431
+
432
+ // Legacy compatibility - store buffer references for existing engine code
433
+ mesh.vertexBuffer = posBuf;
434
+ mesh.vertexBuffer.numItems = mesh.count;
435
+ mesh.normalBuffer = normBuf;
436
+ mesh.normalBuffer.numItems = mesh.count;
437
+ mesh.textureBuffer = uvBuf;
438
+ mesh.textureBuffer.numItems = mesh.count;
439
+
440
+ // Unbind VAO
441
+ gl.bindVertexArray(null);
442
+ });
443
+ }
444
+
445
+ /**
446
+ * Initialize buffers in legacy format for compatibility with existing engine
447
+ * This creates buffers compatible with the existing OBJ library interface
448
+ *
449
+ * @param {Object} mesh - Legacy mesh object with vertices, vertexNormals, textures arrays
450
+ * @returns {Object} Mesh with initialized buffers
451
+ */
452
+ initLegacyBuffers(mesh) {
453
+ const gl = this.gl;
454
+
455
+ // Vertex positions
456
+ mesh.vertexBuffer = gl.createBuffer();
457
+ gl.bindBuffer(gl.ARRAY_BUFFER, mesh.vertexBuffer);
458
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh.vertices), gl.STATIC_DRAW);
459
+ mesh.vertexBuffer.itemSize = 3;
460
+ mesh.vertexBuffer.numItems = mesh.vertices.length / 3;
461
+
462
+ // Vertex normals
463
+ if (mesh.vertexNormals && mesh.vertexNormals.length > 0) {
464
+ mesh.normalBuffer = gl.createBuffer();
465
+ gl.bindBuffer(gl.ARRAY_BUFFER, mesh.normalBuffer);
466
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh.vertexNormals), gl.STATIC_DRAW);
467
+ mesh.normalBuffer.itemSize = 3;
468
+ mesh.normalBuffer.numItems = mesh.vertexNormals.length / 3;
469
+ }
470
+
471
+ // Texture coordinates
472
+ if (mesh.textures && mesh.textures.length > 0) {
473
+ mesh.textureBuffer = gl.createBuffer();
474
+ gl.bindBuffer(gl.ARRAY_BUFFER, mesh.textureBuffer);
475
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh.textures), gl.STATIC_DRAW);
476
+ mesh.textureBuffer.itemSize = 2;
477
+ mesh.textureBuffer.numItems = mesh.textures.length / 2;
478
+ }
479
+
480
+ // Indices
481
+ if (mesh.indices && mesh.indices.length > 0) {
482
+ mesh.indexBuffer = gl.createBuffer();
483
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, mesh.indexBuffer);
484
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(mesh.indices), gl.STATIC_DRAW);
485
+ mesh.indexBuffer.itemSize = 1;
486
+ mesh.indexBuffer.numItems = mesh.indices.length;
487
+ }
488
+
489
+ return mesh;
490
+ }
491
+
492
+ /**
493
+ * Calculate bounding box for a mesh
494
+ * @param {ParsedMesh|{vertices: number[]}} mesh - Mesh with positions or vertices
495
+ * @returns {{min: number[], max: number[], size: number[], center: number[]}}
496
+ */
497
+ calculateBounds(mesh) {
498
+ const positions = mesh.positions || mesh.vertices;
499
+ if (!positions || positions.length === 0) {
500
+ return {
501
+ min: [0, 0, 0],
502
+ max: [0, 0, 0],
503
+ size: [0, 0, 0],
504
+ center: [0, 0, 0]
505
+ };
506
+ }
507
+
508
+ let minX = Infinity, maxX = -Infinity;
509
+ let minY = Infinity, maxY = -Infinity;
510
+ let minZ = Infinity, maxZ = -Infinity;
511
+
512
+ for (let i = 0; i < positions.length; i += 3) {
513
+ const x = positions[i];
514
+ const y = positions[i + 1];
515
+ const z = positions[i + 2];
516
+
517
+ if (x < minX) minX = x;
518
+ if (x > maxX) maxX = x;
519
+ if (y < minY) minY = y;
520
+ if (y > maxY) maxY = y;
521
+ if (z < minZ) minZ = z;
522
+ if (z > maxZ) maxZ = z;
523
+ }
524
+
525
+ return {
526
+ min: [minX, minY, minZ],
527
+ max: [maxX, maxY, maxZ],
528
+ size: [maxX - minX, maxY - minY, maxZ - minZ],
529
+ center: [(minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2]
530
+ };
531
+ }
532
+
533
+ /**
534
+ * Clean up WebGL resources for meshes
535
+ * @param {ParsedMesh[]} meshes - Meshes to clean up
536
+ */
537
+ deleteMeshBuffers(meshes) {
538
+ const gl = this.gl;
539
+
540
+ meshes.forEach(mesh => {
541
+ if (mesh.vao) gl.deleteVertexArray(mesh.vao);
542
+ if (mesh.vertexBuffer) gl.deleteBuffer(mesh.vertexBuffer);
543
+ if (mesh.normalBuffer) gl.deleteBuffer(mesh.normalBuffer);
544
+ if (mesh.textureBuffer) gl.deleteBuffer(mesh.textureBuffer);
545
+ if (mesh.texture) gl.deleteTexture(mesh.texture);
546
+ });
547
+ }
548
+ }
549
+
550
+ // Export vec3 utilities for external use
551
+ export { vec3 };
@@ -0,0 +1,110 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ /**
15
+ * Debug Logger - Centralized logging utility for the core engine
16
+ *
17
+ * Usage:
18
+ * import { debug, debugWarn, debugError } from '../utils/debug-logger.js';
19
+ * debug('ZoneLoader', 'Loading zone:', zoneName);
20
+ * debugWarn('Sprite', 'Missing texture:', texturePath);
21
+ * debugError('Engine', 'Critical failure:', error);
22
+ */
23
+
24
+ /* eslint-disable no-console */
25
+
26
+ // Check for debug mode via global variable or environment
27
+ const isDebugMode = () => {
28
+ if (typeof window !== 'undefined') {
29
+ return window.PIXOS_DEBUG === true ||
30
+ (typeof localStorage !== 'undefined' && localStorage.getItem('pixos_debug') === 'true');
31
+ }
32
+ return typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'development';
33
+ };
34
+
35
+ // Cache the debug state
36
+ let _debugEnabled = isDebugMode();
37
+
38
+ /**
39
+ * Enable/disable debug logging at runtime
40
+ * @param {boolean} enabled
41
+ */
42
+ export function setDebugEnabled(enabled) {
43
+ _debugEnabled = enabled;
44
+ if (typeof window !== 'undefined') {
45
+ window.PIXOS_DEBUG = enabled;
46
+ if (typeof localStorage !== 'undefined') {
47
+ if (enabled) {
48
+ localStorage.setItem('pixos_debug', 'true');
49
+ } else {
50
+ localStorage.removeItem('pixos_debug');
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Check if debug mode is enabled
58
+ * @returns {boolean}
59
+ */
60
+ export function isDebugEnabled() {
61
+ return _debugEnabled;
62
+ }
63
+
64
+ /**
65
+ * Log a debug message with component prefix
66
+ * @param {string} component - Component name (e.g., 'Zone', 'Sprite')
67
+ * @param {...any} args - Log arguments
68
+ */
69
+ export function debug(component, ...args) {
70
+ if (_debugEnabled) {
71
+ console.log(`[${component}]`, ...args);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Log a warning with component prefix
77
+ * @param {string} component - Component name
78
+ * @param {...any} args - Log arguments
79
+ */
80
+ export function debugWarn(component, ...args) {
81
+ if (_debugEnabled) {
82
+ console.warn(`[${component}]`, ...args);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Log an error (always shown, regardless of debug mode)
88
+ * @param {string} component - Component name
89
+ * @param {...any} args - Log arguments
90
+ */
91
+ export function debugError(component, ...args) {
92
+ console.error(`[${component}]`, ...args);
93
+ }
94
+
95
+ // Expose to window for runtime debugging
96
+ if (typeof window !== 'undefined') {
97
+ window.pixosDebug = {
98
+ enable: () => setDebugEnabled(true),
99
+ disable: () => setDebugEnabled(false),
100
+ isEnabled: isDebugEnabled,
101
+ };
102
+ }
103
+
104
+ export default {
105
+ debug,
106
+ debugWarn,
107
+ debugError,
108
+ setDebugEnabled,
109
+ isDebugEnabled,
110
+ };