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.
- package/README.md +36 -286
- package/dist/bundle.js +13 -3
- package/dist/bundle.js.map +1 -1
- package/dist/style.css +1 -0
- package/package.json +43 -44
- package/src/components/WebGLView.jsx +318 -0
- package/src/css/pixos.css +372 -0
- package/src/engine/actions/animate.js +41 -0
- package/src/engine/actions/changezone.js +135 -0
- package/src/engine/actions/chat.js +109 -0
- package/src/engine/actions/dialogue.js +90 -0
- package/src/engine/actions/face.js +22 -0
- package/src/engine/actions/greeting.js +28 -0
- package/src/engine/actions/interact.js +86 -0
- package/src/engine/actions/move.js +67 -0
- package/src/engine/actions/patrol.js +109 -0
- package/src/engine/actions/prompt.js +185 -0
- package/src/engine/actions/script.js +42 -0
- package/src/engine/core/audio/AudioSystem.js +543 -0
- package/src/engine/core/cutscene/PxcPlayer.js +956 -0
- package/src/engine/core/cutscene/manager.js +243 -0
- package/src/engine/core/database/index.js +75 -0
- package/src/engine/core/debug/index.js +371 -0
- package/src/engine/core/hud/index.js +765 -0
- package/src/engine/core/index.js +540 -0
- package/src/engine/core/input/gamepad/Controller.js +71 -0
- package/src/engine/core/input/gamepad/ControllerButtons.js +231 -0
- package/src/engine/core/input/gamepad/ControllerStick.js +173 -0
- package/src/engine/core/input/gamepad/index.js +592 -0
- package/src/engine/core/input/keyboard.js +196 -0
- package/src/engine/core/input/manager.js +485 -0
- package/src/engine/core/input/mouse.js +203 -0
- package/src/engine/core/input/touch.js +175 -0
- package/src/engine/core/mode/manager.js +199 -0
- package/src/engine/core/net/manager.js +535 -0
- package/src/engine/core/queue/action.js +83 -0
- package/src/engine/core/queue/event.js +82 -0
- package/src/engine/core/queue/index.js +44 -0
- package/src/engine/core/queue/loadable.js +33 -0
- package/src/engine/core/render/CameraEffects.js +494 -0
- package/src/engine/core/render/FrustumCuller.js +417 -0
- package/src/engine/core/render/LODManager.js +285 -0
- package/src/engine/core/render/ParticleManager.js +529 -0
- package/src/engine/core/render/TextureAtlas.js +465 -0
- package/src/engine/core/render/camera.js +338 -0
- package/src/engine/core/render/light.js +197 -0
- package/src/engine/core/render/manager.js +1079 -0
- package/src/engine/core/render/shaders.js +110 -0
- package/src/engine/core/render/skybox.js +342 -0
- package/src/engine/core/resource/manager.js +133 -0
- package/src/engine/core/resource/object.js +611 -0
- package/src/engine/core/resource/texture.js +103 -0
- package/src/engine/core/resource/tileset.js +177 -0
- package/src/engine/core/scene/avatar.js +215 -0
- package/src/engine/core/scene/speech.js +138 -0
- package/src/engine/core/scene/sprite.js +702 -0
- package/src/engine/core/scene/spritz.js +189 -0
- package/src/engine/core/scene/world.js +681 -0
- package/src/engine/core/scene/zone.js +1167 -0
- package/src/engine/core/store/index.js +110 -0
- package/src/engine/dynamic/animatedSprite.js +64 -0
- package/src/engine/dynamic/animatedTile.js +98 -0
- package/src/engine/dynamic/avatar.js +110 -0
- package/src/engine/dynamic/map.js +174 -0
- package/src/engine/dynamic/sprite.js +255 -0
- package/src/engine/dynamic/spritz.js +119 -0
- package/src/engine/events/EventSystem.js +609 -0
- package/src/engine/events/camera.js +142 -0
- package/src/engine/events/chat.js +75 -0
- package/src/engine/events/menu.js +186 -0
- package/src/engine/scripting/CallbackManager.js +514 -0
- package/src/engine/scripting/PixoScriptInterpreter.js +81 -0
- package/src/engine/scripting/PixoScriptLibrary.js +704 -0
- package/src/engine/shaders/effects/index.js +450 -0
- package/src/engine/shaders/fs.js +222 -0
- package/src/engine/shaders/particles/fs.js +41 -0
- package/src/engine/shaders/particles/vs.js +61 -0
- package/src/engine/shaders/picker/fs.js +34 -0
- package/src/engine/shaders/picker/init.js +62 -0
- package/src/engine/shaders/picker/vs.js +42 -0
- package/src/engine/shaders/pxsl/README.md +250 -0
- package/src/engine/shaders/pxsl/index.js +25 -0
- package/src/engine/shaders/pxsl/library.js +608 -0
- package/src/engine/shaders/pxsl/manager.js +338 -0
- package/src/engine/shaders/pxsl/specification.js +363 -0
- package/src/engine/shaders/pxsl/transpiler.js +753 -0
- package/src/engine/shaders/skybox/cosmic/fs.js +147 -0
- package/src/engine/shaders/skybox/cosmic/vs.js +23 -0
- package/src/engine/shaders/skybox/matrix/fs.js +127 -0
- package/src/engine/shaders/skybox/matrix/vs.js +23 -0
- package/src/engine/shaders/skybox/morning/fs.js +109 -0
- package/src/engine/shaders/skybox/morning/vs.js +23 -0
- package/src/engine/shaders/skybox/neon/fs.js +119 -0
- package/src/engine/shaders/skybox/neon/vs.js +23 -0
- package/src/engine/shaders/skybox/sky/fs.js +114 -0
- package/src/engine/shaders/skybox/sky/vs.js +23 -0
- package/src/engine/shaders/skybox/sunset/fs.js +101 -0
- package/src/engine/shaders/skybox/sunset/vs.js +23 -0
- package/src/engine/shaders/transition/blur/fs.js +42 -0
- package/src/engine/shaders/transition/blur/vs.js +26 -0
- package/src/engine/shaders/transition/cross/fs.js +36 -0
- package/src/engine/shaders/transition/cross/vs.js +26 -0
- package/src/engine/shaders/transition/crossBlur/fs.js +41 -0
- package/src/engine/shaders/transition/crossBlur/vs.js +25 -0
- package/src/engine/shaders/transition/dissolve/fs.js +78 -0
- package/src/engine/shaders/transition/dissolve/vs.js +24 -0
- package/src/engine/shaders/transition/fade/fs.js +31 -0
- package/src/engine/shaders/transition/fade/vs.js +27 -0
- package/src/engine/shaders/transition/iris/fs.js +52 -0
- package/src/engine/shaders/transition/iris/vs.js +24 -0
- package/src/engine/shaders/transition/pixelate/fs.js +44 -0
- package/src/engine/shaders/transition/pixelate/vs.js +24 -0
- package/src/engine/shaders/transition/slide/fs.js +53 -0
- package/src/engine/shaders/transition/slide/vs.js +24 -0
- package/src/engine/shaders/transition/swirl/fs.js +39 -0
- package/src/engine/shaders/transition/swirl/vs.js +26 -0
- package/src/engine/shaders/transition/wipe/fs.js +50 -0
- package/src/engine/shaders/transition/wipe/vs.js +24 -0
- package/src/engine/shaders/vs.js +60 -0
- package/src/engine/utils/CameraController.js +506 -0
- package/src/engine/utils/ObjHelper.js +551 -0
- package/src/engine/utils/debug-logger.js +110 -0
- package/src/engine/utils/enums.js +305 -0
- package/src/engine/utils/generator.js +156 -0
- package/src/engine/utils/index.js +21 -0
- package/src/engine/utils/loaders/ActionLoader.js +77 -0
- package/src/engine/utils/loaders/AudioLoader.js +157 -0
- package/src/engine/utils/loaders/EventLoader.js +66 -0
- package/src/engine/utils/loaders/ObjectLoader.js +67 -0
- package/src/engine/utils/loaders/SpriteLoader.js +77 -0
- package/src/engine/utils/loaders/TilesetLoader.js +103 -0
- package/src/engine/utils/loaders/index.js +21 -0
- package/src/engine/utils/math/matrix4.js +367 -0
- package/src/engine/utils/math/vector.js +458 -0
- package/src/engine/utils/obj/_old_js/index.js +46 -0
- package/src/engine/utils/obj/_old_js/layout.js +308 -0
- package/src/engine/utils/obj/_old_js/material.js +711 -0
- package/src/engine/utils/obj/_old_js/mesh.js +761 -0
- package/src/engine/utils/obj/_old_js/utils.js +647 -0
- package/src/engine/utils/obj/index.js +24 -0
- package/src/engine/utils/obj/js/index.js +277 -0
- package/src/engine/utils/obj/js/loader.js +232 -0
- package/src/engine/utils/obj/layout.js +246 -0
- package/src/engine/utils/obj/material.js +665 -0
- package/src/engine/utils/obj/mesh.js +657 -0
- package/src/engine/utils/obj/ts/index.ts +72 -0
- package/src/engine/utils/obj/ts/layout.ts +265 -0
- package/src/engine/utils/obj/ts/material.ts +760 -0
- package/src/engine/utils/obj/ts/mesh.ts +785 -0
- package/src/engine/utils/obj/ts/utils.ts +501 -0
- package/src/engine/utils/obj/utils.js +428 -0
- package/src/engine/utils/resources.js +18 -0
- package/src/index.jsx +55 -0
- package/src/spritz/player.js +18 -0
- package/src/spritz/readme.md +18 -0
- package/LICENSE +0 -437
- 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
|
+
};
|