quake2ts 0.0.72 → 0.0.73
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 +8 -5
- package/package.json +1 -1
- package/packages/client/dist/browser/index.global.js +1 -1
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +3 -1
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +3 -1
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/browser/index.global.js +14 -14
- package/packages/engine/dist/browser/index.global.js.map +1 -1
- package/packages/engine/dist/cjs/index.cjs +488 -6
- package/packages/engine/dist/cjs/index.cjs.map +1 -1
- package/packages/engine/dist/esm/index.js +483 -6
- package/packages/engine/dist/esm/index.js.map +1 -1
- package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/types/index.d.ts +2 -1
- package/packages/engine/dist/types/index.d.ts.map +1 -1
- package/packages/engine/dist/types/render/bsp.d.ts +14 -0
- package/packages/engine/dist/types/render/bsp.d.ts.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
|
@@ -36,6 +36,8 @@ __export(index_exports, {
|
|
|
36
36
|
BSP_SURFACE_FRAGMENT_SOURCE: () => BSP_SURFACE_FRAGMENT_SOURCE,
|
|
37
37
|
BSP_SURFACE_VERTEX_SOURCE: () => BSP_SURFACE_VERTEX_SOURCE,
|
|
38
38
|
BSP_VERTEX_LAYOUT: () => BSP_VERTEX_LAYOUT,
|
|
39
|
+
BspLoader: () => BspLoader,
|
|
40
|
+
BspParseError: () => BspParseError,
|
|
39
41
|
BspSurfacePipeline: () => BspSurfacePipeline,
|
|
40
42
|
Camera: () => Camera,
|
|
41
43
|
ConfigStringRegistry: () => ConfigStringRegistry,
|
|
@@ -105,8 +107,10 @@ __export(index_exports, {
|
|
|
105
107
|
computeSkyScroll: () => computeSkyScroll,
|
|
106
108
|
createAnimationState: () => createAnimationState,
|
|
107
109
|
createAudioGraph: () => createAudioGraph,
|
|
110
|
+
createBspSurfaces: () => createBspSurfaces,
|
|
108
111
|
createEngine: () => createEngine,
|
|
109
112
|
createEngineRuntime: () => createEngineRuntime,
|
|
113
|
+
createFaceLightmap: () => createFaceLightmap,
|
|
110
114
|
createInitialChannels: () => createInitialChannels,
|
|
111
115
|
createProgramFromSources: () => createProgramFromSources,
|
|
112
116
|
createWebGLContext: () => createWebGLContext,
|
|
@@ -121,6 +125,7 @@ __export(index_exports, {
|
|
|
121
125
|
ingestPaks: () => ingestPaks,
|
|
122
126
|
interpolateMd3Tag: () => interpolateMd3Tag,
|
|
123
127
|
interpolateVec3: () => interpolateVec3,
|
|
128
|
+
parseBsp: () => parseBsp,
|
|
124
129
|
parseMd2: () => parseMd2,
|
|
125
130
|
parseMd3: () => parseMd3,
|
|
126
131
|
parsePcx: () => parsePcx,
|
|
@@ -1178,10 +1183,418 @@ function wireFileInput(input, handler) {
|
|
|
1178
1183
|
return () => input.removeEventListener("change", onChange);
|
|
1179
1184
|
}
|
|
1180
1185
|
|
|
1186
|
+
// src/assets/bsp.ts
|
|
1187
|
+
var BSP_MAGIC = "IBSP";
|
|
1188
|
+
var BSP_VERSION = 38;
|
|
1189
|
+
var HEADER_LUMPS = 19;
|
|
1190
|
+
var HEADER_SIZE2 = 4 + 4 + HEADER_LUMPS * 8;
|
|
1191
|
+
var BspParseError = class extends Error {
|
|
1192
|
+
};
|
|
1193
|
+
var BspLoader = class {
|
|
1194
|
+
constructor(vfs) {
|
|
1195
|
+
this.vfs = vfs;
|
|
1196
|
+
}
|
|
1197
|
+
async load(path) {
|
|
1198
|
+
const buffer = await this.vfs.readFile(path);
|
|
1199
|
+
const copy = new Uint8Array(buffer.byteLength);
|
|
1200
|
+
copy.set(buffer);
|
|
1201
|
+
return parseBsp(copy.buffer);
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
function parseBsp(buffer) {
|
|
1205
|
+
if (buffer.byteLength < HEADER_SIZE2) {
|
|
1206
|
+
throw new BspParseError("BSP too small to contain header");
|
|
1207
|
+
}
|
|
1208
|
+
const view = new DataView(buffer);
|
|
1209
|
+
const magic = String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3));
|
|
1210
|
+
if (magic !== BSP_MAGIC) {
|
|
1211
|
+
throw new BspParseError(`Invalid BSP magic ${magic}`);
|
|
1212
|
+
}
|
|
1213
|
+
const version = view.getInt32(4, true);
|
|
1214
|
+
if (version !== BSP_VERSION) {
|
|
1215
|
+
throw new BspParseError(`Unsupported BSP version ${version}`);
|
|
1216
|
+
}
|
|
1217
|
+
const lumps = /* @__PURE__ */ new Map();
|
|
1218
|
+
for (let i = 0; i < HEADER_LUMPS; i += 1) {
|
|
1219
|
+
const offset = view.getInt32(8 + i * 8, true);
|
|
1220
|
+
const length = view.getInt32(12 + i * 8, true);
|
|
1221
|
+
if (offset < 0 || length < 0 || offset + length > buffer.byteLength) {
|
|
1222
|
+
throw new BspParseError(`Invalid lump bounds for index ${i}`);
|
|
1223
|
+
}
|
|
1224
|
+
lumps.set(i, { offset, length });
|
|
1225
|
+
}
|
|
1226
|
+
const header = { version, lumps };
|
|
1227
|
+
const entities = parseEntities(buffer, lumps.get(0 /* Entities */));
|
|
1228
|
+
const planes = parsePlanes(buffer, lumps.get(1 /* Planes */));
|
|
1229
|
+
const vertices = parseVertices(buffer, lumps.get(2 /* Vertices */));
|
|
1230
|
+
const nodes = parseNodes(buffer, lumps.get(4 /* Nodes */));
|
|
1231
|
+
const texInfo = parseTexInfo(buffer, lumps.get(5 /* TexInfo */));
|
|
1232
|
+
const faces = parseFaces(buffer, lumps.get(6 /* Faces */));
|
|
1233
|
+
const lightMaps = new Uint8Array(buffer, lumps.get(7 /* Lighting */).offset, lumps.get(7 /* Lighting */).length);
|
|
1234
|
+
const lightMapInfo = buildLightMapInfo(faces, lumps.get(7 /* Lighting */));
|
|
1235
|
+
const leafs = parseLeafs(buffer, lumps.get(8 /* Leafs */));
|
|
1236
|
+
const edges = parseEdges(buffer, lumps.get(11 /* Edges */));
|
|
1237
|
+
const surfEdges = parseSurfEdges(buffer, lumps.get(12 /* SurfEdges */));
|
|
1238
|
+
const models = parseModels(buffer, lumps.get(13 /* Models */));
|
|
1239
|
+
const brushes = parseBrushes(buffer, lumps.get(14 /* Brushes */));
|
|
1240
|
+
const brushSides = parseBrushSides(buffer, lumps.get(15 /* BrushSides */));
|
|
1241
|
+
const leafLists = parseLeafLists(buffer, lumps.get(9 /* LeafFaces */), lumps.get(10 /* LeafBrushes */), leafs);
|
|
1242
|
+
const visibility = parseVisibility(buffer, lumps.get(3 /* Visibility */));
|
|
1243
|
+
return {
|
|
1244
|
+
header,
|
|
1245
|
+
entities,
|
|
1246
|
+
planes,
|
|
1247
|
+
vertices,
|
|
1248
|
+
nodes,
|
|
1249
|
+
texInfo,
|
|
1250
|
+
faces,
|
|
1251
|
+
lightMaps,
|
|
1252
|
+
lightMapInfo,
|
|
1253
|
+
leafs,
|
|
1254
|
+
leafLists,
|
|
1255
|
+
edges,
|
|
1256
|
+
surfEdges,
|
|
1257
|
+
models,
|
|
1258
|
+
brushes,
|
|
1259
|
+
brushSides,
|
|
1260
|
+
visibility
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
function parseEntities(buffer, info) {
|
|
1264
|
+
const raw = new TextDecoder().decode(new Uint8Array(buffer, info.offset, info.length));
|
|
1265
|
+
const entities = parseEntityString(raw);
|
|
1266
|
+
const worldspawn = entities.find((ent) => ent.classname === "worldspawn");
|
|
1267
|
+
return { raw, entities, worldspawn };
|
|
1268
|
+
}
|
|
1269
|
+
function parseEntityString(text) {
|
|
1270
|
+
const entities = [];
|
|
1271
|
+
const tokenizer = /\{([^}]*)\}/gms;
|
|
1272
|
+
let match;
|
|
1273
|
+
while ((match = tokenizer.exec(text)) !== null) {
|
|
1274
|
+
const entityText = match[1];
|
|
1275
|
+
const properties = {};
|
|
1276
|
+
const kvRegex = /"([^\"]*)"\s+"([^\"]*)"/g;
|
|
1277
|
+
let kv;
|
|
1278
|
+
while ((kv = kvRegex.exec(entityText)) !== null) {
|
|
1279
|
+
properties[kv[1]] = kv[2];
|
|
1280
|
+
}
|
|
1281
|
+
entities.push({ classname: properties.classname, properties });
|
|
1282
|
+
}
|
|
1283
|
+
return entities;
|
|
1284
|
+
}
|
|
1285
|
+
function parsePlanes(buffer, info) {
|
|
1286
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1287
|
+
const count = info.length / 20;
|
|
1288
|
+
if (count % 1 !== 0) {
|
|
1289
|
+
throw new BspParseError("Plane lump has invalid length");
|
|
1290
|
+
}
|
|
1291
|
+
const planes = [];
|
|
1292
|
+
for (let i = 0; i < count; i += 1) {
|
|
1293
|
+
const normal = [view.getFloat32(i * 20, true), view.getFloat32(i * 20 + 4, true), view.getFloat32(i * 20 + 8, true)];
|
|
1294
|
+
const dist = view.getFloat32(i * 20 + 12, true);
|
|
1295
|
+
const type = view.getInt32(i * 20 + 16, true);
|
|
1296
|
+
planes.push({ normal, dist, type });
|
|
1297
|
+
}
|
|
1298
|
+
return planes;
|
|
1299
|
+
}
|
|
1300
|
+
function parseVertices(buffer, info) {
|
|
1301
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1302
|
+
const count = info.length / 12;
|
|
1303
|
+
if (count % 1 !== 0) {
|
|
1304
|
+
throw new BspParseError("Vertex lump has invalid length");
|
|
1305
|
+
}
|
|
1306
|
+
const vertices = [];
|
|
1307
|
+
for (let i = 0; i < count; i += 1) {
|
|
1308
|
+
vertices.push([
|
|
1309
|
+
view.getFloat32(i * 12, true),
|
|
1310
|
+
view.getFloat32(i * 12 + 4, true),
|
|
1311
|
+
view.getFloat32(i * 12 + 8, true)
|
|
1312
|
+
]);
|
|
1313
|
+
}
|
|
1314
|
+
return vertices;
|
|
1315
|
+
}
|
|
1316
|
+
function parseNodes(buffer, info) {
|
|
1317
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1318
|
+
const entrySize = 36;
|
|
1319
|
+
const count = info.length / entrySize;
|
|
1320
|
+
if (count % 1 !== 0) {
|
|
1321
|
+
throw new BspParseError("Node lump has invalid length");
|
|
1322
|
+
}
|
|
1323
|
+
const nodes = [];
|
|
1324
|
+
for (let i = 0; i < count; i += 1) {
|
|
1325
|
+
const base = i * entrySize;
|
|
1326
|
+
const planeIndex = view.getInt32(base, true);
|
|
1327
|
+
const children = [view.getInt32(base + 4, true), view.getInt32(base + 8, true)];
|
|
1328
|
+
const mins = [view.getInt16(base + 12, true), view.getInt16(base + 14, true), view.getInt16(base + 16, true)];
|
|
1329
|
+
const maxs = [view.getInt16(base + 18, true), view.getInt16(base + 20, true), view.getInt16(base + 22, true)];
|
|
1330
|
+
const firstFace = view.getUint16(base + 24, true);
|
|
1331
|
+
const numFaces = view.getUint16(base + 26, true);
|
|
1332
|
+
nodes.push({ planeIndex, children, mins, maxs, firstFace, numFaces });
|
|
1333
|
+
}
|
|
1334
|
+
return nodes;
|
|
1335
|
+
}
|
|
1336
|
+
function parseTexInfo(buffer, info) {
|
|
1337
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1338
|
+
const entrySize = 76;
|
|
1339
|
+
const count = info.length / entrySize;
|
|
1340
|
+
if (count % 1 !== 0) {
|
|
1341
|
+
throw new BspParseError("TexInfo lump has invalid length");
|
|
1342
|
+
}
|
|
1343
|
+
const texInfos = [];
|
|
1344
|
+
for (let i = 0; i < count; i += 1) {
|
|
1345
|
+
const base = i * entrySize;
|
|
1346
|
+
const s = [view.getFloat32(base, true), view.getFloat32(base + 4, true), view.getFloat32(base + 8, true)];
|
|
1347
|
+
const sOffset = view.getFloat32(base + 12, true);
|
|
1348
|
+
const t = [view.getFloat32(base + 16, true), view.getFloat32(base + 20, true), view.getFloat32(base + 24, true)];
|
|
1349
|
+
const tOffset = view.getFloat32(base + 28, true);
|
|
1350
|
+
const flags = view.getInt32(base + 32, true);
|
|
1351
|
+
const value = view.getInt32(base + 36, true);
|
|
1352
|
+
const textureBytes = new Uint8Array(buffer, info.offset + base + 40, 32);
|
|
1353
|
+
const texture = new TextDecoder("utf-8").decode(textureBytes).replace(/\0.*$/, "");
|
|
1354
|
+
const nextTexInfo = view.getInt32(base + 72, true);
|
|
1355
|
+
texInfos.push({ s, sOffset, t, tOffset, flags, value, texture, nextTexInfo });
|
|
1356
|
+
}
|
|
1357
|
+
return texInfos;
|
|
1358
|
+
}
|
|
1359
|
+
function parseFaces(buffer, info) {
|
|
1360
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1361
|
+
const entrySize = 20;
|
|
1362
|
+
const count = info.length / entrySize;
|
|
1363
|
+
if (count % 1 !== 0) {
|
|
1364
|
+
throw new BspParseError("Face lump has invalid length");
|
|
1365
|
+
}
|
|
1366
|
+
const faces = [];
|
|
1367
|
+
for (let i = 0; i < count; i += 1) {
|
|
1368
|
+
const base = i * entrySize;
|
|
1369
|
+
const planeIndex = view.getUint16(base, true);
|
|
1370
|
+
const side = view.getInt16(base + 2, true);
|
|
1371
|
+
const firstEdge = view.getInt32(base + 4, true);
|
|
1372
|
+
const numEdges = view.getInt16(base + 8, true);
|
|
1373
|
+
const texInfo = view.getInt16(base + 10, true);
|
|
1374
|
+
const styles = [
|
|
1375
|
+
view.getUint8(base + 12),
|
|
1376
|
+
view.getUint8(base + 13),
|
|
1377
|
+
view.getUint8(base + 14),
|
|
1378
|
+
view.getUint8(base + 15)
|
|
1379
|
+
];
|
|
1380
|
+
const lightOffset = view.getInt32(base + 16, true);
|
|
1381
|
+
faces.push({ planeIndex, side, firstEdge, numEdges, texInfo, styles, lightOffset });
|
|
1382
|
+
}
|
|
1383
|
+
return faces;
|
|
1384
|
+
}
|
|
1385
|
+
function parseLeafs(buffer, info) {
|
|
1386
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1387
|
+
const entrySize = 28;
|
|
1388
|
+
const count = info.length / entrySize;
|
|
1389
|
+
if (count % 1 !== 0) {
|
|
1390
|
+
throw new BspParseError("Leaf lump has invalid length");
|
|
1391
|
+
}
|
|
1392
|
+
const leafs = [];
|
|
1393
|
+
for (let i = 0; i < count; i += 1) {
|
|
1394
|
+
const base = i * entrySize;
|
|
1395
|
+
const contents = view.getInt32(base, true);
|
|
1396
|
+
const cluster = view.getInt16(base + 4, true);
|
|
1397
|
+
const area = view.getInt16(base + 6, true);
|
|
1398
|
+
const mins = [view.getInt16(base + 8, true), view.getInt16(base + 10, true), view.getInt16(base + 12, true)];
|
|
1399
|
+
const maxs = [view.getInt16(base + 14, true), view.getInt16(base + 16, true), view.getInt16(base + 18, true)];
|
|
1400
|
+
const firstLeafFace = view.getUint16(base + 20, true);
|
|
1401
|
+
const numLeafFaces = view.getUint16(base + 22, true);
|
|
1402
|
+
const firstLeafBrush = view.getUint16(base + 24, true);
|
|
1403
|
+
const numLeafBrushes = view.getUint16(base + 26, true);
|
|
1404
|
+
leafs.push({ contents, cluster, area, mins, maxs, firstLeafFace, numLeafFaces, firstLeafBrush, numLeafBrushes });
|
|
1405
|
+
}
|
|
1406
|
+
return leafs;
|
|
1407
|
+
}
|
|
1408
|
+
function parseEdges(buffer, info) {
|
|
1409
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1410
|
+
const entrySize = 4;
|
|
1411
|
+
const count = info.length / entrySize;
|
|
1412
|
+
if (count % 1 !== 0) {
|
|
1413
|
+
throw new BspParseError("Edge lump has invalid length");
|
|
1414
|
+
}
|
|
1415
|
+
const edges = [];
|
|
1416
|
+
for (let i = 0; i < count; i += 1) {
|
|
1417
|
+
const base = i * entrySize;
|
|
1418
|
+
edges.push({ vertices: [view.getUint16(base, true), view.getUint16(base + 2, true)] });
|
|
1419
|
+
}
|
|
1420
|
+
return edges;
|
|
1421
|
+
}
|
|
1422
|
+
function parseSurfEdges(buffer, info) {
|
|
1423
|
+
const count = info.length / 4;
|
|
1424
|
+
if (count % 1 !== 0) {
|
|
1425
|
+
throw new BspParseError("SurfEdge lump has invalid length");
|
|
1426
|
+
}
|
|
1427
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1428
|
+
const edges = new Int32Array(count);
|
|
1429
|
+
for (let i = 0; i < count; i += 1) {
|
|
1430
|
+
edges[i] = view.getInt32(i * 4, true);
|
|
1431
|
+
}
|
|
1432
|
+
return edges;
|
|
1433
|
+
}
|
|
1434
|
+
function parseModels(buffer, info) {
|
|
1435
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1436
|
+
const entrySize = 48;
|
|
1437
|
+
if (info.length % entrySize !== 0) {
|
|
1438
|
+
throw new BspParseError("Model lump has invalid length");
|
|
1439
|
+
}
|
|
1440
|
+
const count = info.length / entrySize;
|
|
1441
|
+
const models = [];
|
|
1442
|
+
for (let i = 0; i < count; i += 1) {
|
|
1443
|
+
const base = i * entrySize;
|
|
1444
|
+
const mins = [view.getFloat32(base, true), view.getFloat32(base + 4, true), view.getFloat32(base + 8, true)];
|
|
1445
|
+
const maxs = [view.getFloat32(base + 12, true), view.getFloat32(base + 16, true), view.getFloat32(base + 20, true)];
|
|
1446
|
+
const origin = [view.getFloat32(base + 24, true), view.getFloat32(base + 28, true), view.getFloat32(base + 32, true)];
|
|
1447
|
+
const headNode = view.getInt32(base + 36, true);
|
|
1448
|
+
const firstFace = view.getInt32(base + 40, true);
|
|
1449
|
+
const numFaces = view.getInt32(base + 44, true);
|
|
1450
|
+
models.push({ mins, maxs, origin, headNode, firstFace, numFaces });
|
|
1451
|
+
}
|
|
1452
|
+
return models;
|
|
1453
|
+
}
|
|
1454
|
+
function parseBrushes(buffer, info) {
|
|
1455
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1456
|
+
const entrySize = 12;
|
|
1457
|
+
const count = info.length / entrySize;
|
|
1458
|
+
if (count % 1 !== 0) {
|
|
1459
|
+
throw new BspParseError("Brush lump has invalid length");
|
|
1460
|
+
}
|
|
1461
|
+
const brushes = [];
|
|
1462
|
+
for (let i = 0; i < count; i += 1) {
|
|
1463
|
+
const base = i * entrySize;
|
|
1464
|
+
brushes.push({
|
|
1465
|
+
firstSide: view.getInt32(base, true),
|
|
1466
|
+
numSides: view.getInt32(base + 4, true),
|
|
1467
|
+
contents: view.getInt32(base + 8, true)
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
return brushes;
|
|
1471
|
+
}
|
|
1472
|
+
function parseBrushSides(buffer, info) {
|
|
1473
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1474
|
+
const entrySize = 8;
|
|
1475
|
+
const count = info.length / entrySize;
|
|
1476
|
+
if (count % 1 !== 0) {
|
|
1477
|
+
throw new BspParseError("Brush side lump has invalid length");
|
|
1478
|
+
}
|
|
1479
|
+
const sides = [];
|
|
1480
|
+
for (let i = 0; i < count; i += 1) {
|
|
1481
|
+
const base = i * entrySize;
|
|
1482
|
+
sides.push({ planeIndex: view.getUint16(base, true), texInfo: view.getInt16(base + 2, true) });
|
|
1483
|
+
}
|
|
1484
|
+
return sides;
|
|
1485
|
+
}
|
|
1486
|
+
function parseLeafLists(buffer, leafFacesInfo, leafBrushesInfo, leafs) {
|
|
1487
|
+
const leafFaces = [];
|
|
1488
|
+
const leafBrushes = [];
|
|
1489
|
+
const maxLeafFaceIndex = leafFacesInfo.length / 2;
|
|
1490
|
+
const maxLeafBrushIndex = leafBrushesInfo.length / 2;
|
|
1491
|
+
const faceView = new DataView(buffer, leafFacesInfo.offset, leafFacesInfo.length);
|
|
1492
|
+
const brushView = new DataView(buffer, leafBrushesInfo.offset, leafBrushesInfo.length);
|
|
1493
|
+
for (const leaf of leafs) {
|
|
1494
|
+
if (leaf.firstLeafFace + leaf.numLeafFaces > maxLeafFaceIndex) {
|
|
1495
|
+
throw new BspParseError("Leaf faces reference data past lump bounds");
|
|
1496
|
+
}
|
|
1497
|
+
if (leaf.firstLeafBrush + leaf.numLeafBrushes > maxLeafBrushIndex) {
|
|
1498
|
+
throw new BspParseError("Leaf brushes reference data past lump bounds");
|
|
1499
|
+
}
|
|
1500
|
+
const faces = [];
|
|
1501
|
+
for (let i = 0; i < leaf.numLeafFaces; i += 1) {
|
|
1502
|
+
faces.push(faceView.getUint16((leaf.firstLeafFace + i) * 2, true));
|
|
1503
|
+
}
|
|
1504
|
+
const brushes = [];
|
|
1505
|
+
for (let i = 0; i < leaf.numLeafBrushes; i += 1) {
|
|
1506
|
+
brushes.push(brushView.getUint16((leaf.firstLeafBrush + i) * 2, true));
|
|
1507
|
+
}
|
|
1508
|
+
leafFaces.push(faces);
|
|
1509
|
+
leafBrushes.push(brushes);
|
|
1510
|
+
}
|
|
1511
|
+
return { leafFaces, leafBrushes };
|
|
1512
|
+
}
|
|
1513
|
+
function parseVisibility(buffer, info) {
|
|
1514
|
+
if (info.length === 0) {
|
|
1515
|
+
return void 0;
|
|
1516
|
+
}
|
|
1517
|
+
if (info.length < 4) {
|
|
1518
|
+
throw new BspParseError("Visibility lump too small");
|
|
1519
|
+
}
|
|
1520
|
+
const view = new DataView(buffer, info.offset, info.length);
|
|
1521
|
+
const numClusters = view.getInt32(0, true);
|
|
1522
|
+
const headerBytes = 4 + numClusters * 8;
|
|
1523
|
+
if (numClusters < 0 || headerBytes > info.length) {
|
|
1524
|
+
throw new BspParseError("Visibility lump truncated");
|
|
1525
|
+
}
|
|
1526
|
+
let cursor = 4;
|
|
1527
|
+
const clusters = [];
|
|
1528
|
+
for (let i = 0; i < numClusters; i += 1) {
|
|
1529
|
+
const pvsOffset = view.getInt32(cursor, true);
|
|
1530
|
+
const phsOffset = view.getInt32(cursor + 4, true);
|
|
1531
|
+
cursor += 8;
|
|
1532
|
+
const absolutePvs = info.offset + pvsOffset;
|
|
1533
|
+
const absolutePhs = info.offset + phsOffset;
|
|
1534
|
+
const lumpEnd = info.offset + info.length;
|
|
1535
|
+
if (pvsOffset < 0 || phsOffset < 0 || absolutePvs >= lumpEnd || absolutePhs >= lumpEnd) {
|
|
1536
|
+
throw new BspParseError("Visibility offsets out of range");
|
|
1537
|
+
}
|
|
1538
|
+
clusters.push({
|
|
1539
|
+
pvs: decompressVis(buffer, absolutePvs, numClusters, info),
|
|
1540
|
+
phs: decompressVis(buffer, absolutePhs, numClusters, info)
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
return { numClusters, clusters };
|
|
1544
|
+
}
|
|
1545
|
+
function decompressVis(buffer, offset, numClusters, lump) {
|
|
1546
|
+
const rowBytes = Math.ceil(numClusters / 8);
|
|
1547
|
+
const result = new Uint8Array(rowBytes);
|
|
1548
|
+
const src = new Uint8Array(buffer);
|
|
1549
|
+
let srcIndex = offset;
|
|
1550
|
+
let destIndex = 0;
|
|
1551
|
+
const maxOffset = lump.offset + lump.length;
|
|
1552
|
+
while (destIndex < rowBytes) {
|
|
1553
|
+
if (srcIndex >= maxOffset) {
|
|
1554
|
+
throw new BspParseError("Visibility data truncated");
|
|
1555
|
+
}
|
|
1556
|
+
const value = src[srcIndex++];
|
|
1557
|
+
if (value !== 0) {
|
|
1558
|
+
result[destIndex++] = value;
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
if (srcIndex >= maxOffset) {
|
|
1562
|
+
throw new BspParseError("Visibility run exceeds lump bounds");
|
|
1563
|
+
}
|
|
1564
|
+
const runLength = src[srcIndex++];
|
|
1565
|
+
for (let i = 0; i < runLength && destIndex < rowBytes; i += 1) {
|
|
1566
|
+
result[destIndex++] = 0;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
return result;
|
|
1570
|
+
}
|
|
1571
|
+
function buildLightMapInfo(faces, lightingLump) {
|
|
1572
|
+
return faces.map((face) => {
|
|
1573
|
+
if (face.lightOffset < 0) {
|
|
1574
|
+
return void 0;
|
|
1575
|
+
}
|
|
1576
|
+
return {
|
|
1577
|
+
offset: face.lightOffset,
|
|
1578
|
+
length: Math.max(0, lightingLump.length - face.lightOffset)
|
|
1579
|
+
};
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
function createFaceLightmap(face, lightMaps, info) {
|
|
1583
|
+
if (face.lightOffset < 0 || face.lightOffset >= lightMaps.byteLength) {
|
|
1584
|
+
return void 0;
|
|
1585
|
+
}
|
|
1586
|
+
const available = lightMaps.byteLength - face.lightOffset;
|
|
1587
|
+
const length = Math.min(info?.length ?? available, available);
|
|
1588
|
+
if (length <= 0) {
|
|
1589
|
+
return void 0;
|
|
1590
|
+
}
|
|
1591
|
+
return lightMaps.subarray(face.lightOffset, face.lightOffset + length);
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1181
1594
|
// src/assets/md2.ts
|
|
1182
1595
|
var MD2_MAGIC = 844121161;
|
|
1183
1596
|
var MD2_VERSION = 8;
|
|
1184
|
-
var
|
|
1597
|
+
var HEADER_SIZE3 = 17 * 4;
|
|
1185
1598
|
var MD2_NORMALS = [
|
|
1186
1599
|
{ x: -0.525731, y: 0, z: 0.850651 },
|
|
1187
1600
|
{ x: -0.442863, y: 0.238856, z: 0.864188 },
|
|
@@ -1370,12 +1783,12 @@ function readCString2(view, offset, maxLength) {
|
|
|
1370
1783
|
}
|
|
1371
1784
|
function validateSection(buffer, offset, length, label) {
|
|
1372
1785
|
if (length === 0) return;
|
|
1373
|
-
if (offset <
|
|
1786
|
+
if (offset < HEADER_SIZE3 || offset + length > buffer.byteLength) {
|
|
1374
1787
|
throw new Md2ParseError(`${label} section is out of bounds`);
|
|
1375
1788
|
}
|
|
1376
1789
|
}
|
|
1377
1790
|
function parseHeader(buffer) {
|
|
1378
|
-
if (buffer.byteLength <
|
|
1791
|
+
if (buffer.byteLength < HEADER_SIZE3) {
|
|
1379
1792
|
throw new Md2ParseError("MD2 buffer too small to contain header");
|
|
1380
1793
|
}
|
|
1381
1794
|
const view = new DataView(buffer);
|
|
@@ -1789,7 +2202,7 @@ var Md3Loader = class {
|
|
|
1789
2202
|
var IDSPRITEHEADER = 1229214514;
|
|
1790
2203
|
var SPRITE_VERSION = 2;
|
|
1791
2204
|
var MAX_SKINNAME = 64;
|
|
1792
|
-
var
|
|
2205
|
+
var HEADER_SIZE4 = 12;
|
|
1793
2206
|
var SpriteParseError = class extends Error {
|
|
1794
2207
|
};
|
|
1795
2208
|
function readCString3(view, offset, maxLength) {
|
|
@@ -1802,7 +2215,7 @@ function readCString3(view, offset, maxLength) {
|
|
|
1802
2215
|
return String.fromCharCode(...chars);
|
|
1803
2216
|
}
|
|
1804
2217
|
function parseSprite(buffer) {
|
|
1805
|
-
if (buffer.byteLength <
|
|
2218
|
+
if (buffer.byteLength < HEADER_SIZE4) {
|
|
1806
2219
|
throw new SpriteParseError("Sprite buffer too small to contain header");
|
|
1807
2220
|
}
|
|
1808
2221
|
const view = new DataView(buffer);
|
|
@@ -1817,7 +2230,7 @@ function parseSprite(buffer) {
|
|
|
1817
2230
|
}
|
|
1818
2231
|
const frames = [];
|
|
1819
2232
|
const frameSize = 16 + MAX_SKINNAME;
|
|
1820
|
-
let offset =
|
|
2233
|
+
let offset = HEADER_SIZE4;
|
|
1821
2234
|
for (let i = 0; i < numFrames; i += 1) {
|
|
1822
2235
|
if (offset + frameSize > buffer.byteLength) {
|
|
1823
2236
|
throw new SpriteParseError("Sprite frame data exceeds buffer length");
|
|
@@ -3483,6 +3896,70 @@ function buildVertexData(surface, placement) {
|
|
|
3483
3896
|
}
|
|
3484
3897
|
return interleaved;
|
|
3485
3898
|
}
|
|
3899
|
+
function createBspSurfaces(map) {
|
|
3900
|
+
const results = [];
|
|
3901
|
+
for (let faceIndex = 0; faceIndex < map.faces.length; faceIndex++) {
|
|
3902
|
+
const face = map.faces[faceIndex];
|
|
3903
|
+
if (face.texInfo < 0) continue;
|
|
3904
|
+
const texInfo = map.texInfo[face.texInfo];
|
|
3905
|
+
const vertices = [];
|
|
3906
|
+
const textureCoords = [];
|
|
3907
|
+
const lightmapCoords = [];
|
|
3908
|
+
for (let i = 0; i < face.numEdges; i++) {
|
|
3909
|
+
const edgeIndex = map.surfEdges[face.firstEdge + i];
|
|
3910
|
+
const edge = map.edges[Math.abs(edgeIndex)];
|
|
3911
|
+
const vIndex = edgeIndex >= 0 ? edge.vertices[0] : edge.vertices[1];
|
|
3912
|
+
const vertex = map.vertices[vIndex];
|
|
3913
|
+
vertices.push(vertex[0], vertex[1], vertex[2]);
|
|
3914
|
+
const s = vertex[0] * texInfo.s[0] + vertex[1] * texInfo.s[1] + vertex[2] * texInfo.s[2] + texInfo.sOffset;
|
|
3915
|
+
const t = vertex[0] * texInfo.t[0] + vertex[1] * texInfo.t[1] + vertex[2] * texInfo.t[2] + texInfo.tOffset;
|
|
3916
|
+
textureCoords.push(s, t);
|
|
3917
|
+
lightmapCoords.push(s, t);
|
|
3918
|
+
}
|
|
3919
|
+
const indices = [];
|
|
3920
|
+
const vertexCount = vertices.length / 3;
|
|
3921
|
+
for (let i = 1; i < vertexCount - 1; i++) {
|
|
3922
|
+
indices.push(0, i, i + 1);
|
|
3923
|
+
}
|
|
3924
|
+
let lightmapData;
|
|
3925
|
+
const lightmapInfo = map.lightMapInfo[faceIndex];
|
|
3926
|
+
if (lightmapInfo) {
|
|
3927
|
+
let minS = Infinity, maxS = -Infinity, minT = Infinity, maxT = -Infinity;
|
|
3928
|
+
for (let k = 0; k < textureCoords.length; k += 2) {
|
|
3929
|
+
const s = textureCoords[k];
|
|
3930
|
+
const t = textureCoords[k + 1];
|
|
3931
|
+
if (s < minS) minS = s;
|
|
3932
|
+
if (s > maxS) maxS = s;
|
|
3933
|
+
if (t < minT) minT = t;
|
|
3934
|
+
if (t > maxT) maxT = t;
|
|
3935
|
+
}
|
|
3936
|
+
const floorMinS = Math.floor(minS / 16);
|
|
3937
|
+
const floorMinT = Math.floor(minT / 16);
|
|
3938
|
+
const lmWidth = Math.ceil(maxS / 16) - floorMinS + 1;
|
|
3939
|
+
const lmHeight = Math.ceil(maxT / 16) - floorMinT + 1;
|
|
3940
|
+
const samples = createFaceLightmap(face, map.lightMaps, lightmapInfo);
|
|
3941
|
+
if (samples) {
|
|
3942
|
+
if (samples.length === lmWidth * lmHeight * 3) {
|
|
3943
|
+
lightmapData = { width: lmWidth, height: lmHeight, samples };
|
|
3944
|
+
for (let k = 0; k < lightmapCoords.length; k += 2) {
|
|
3945
|
+
lightmapCoords[k] = textureCoords[k] / 16 - floorMinS + 0.5;
|
|
3946
|
+
lightmapCoords[k + 1] = textureCoords[k + 1] / 16 - floorMinT + 0.5;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
results.push({
|
|
3952
|
+
vertices: new Float32Array(vertices),
|
|
3953
|
+
textureCoords: new Float32Array(textureCoords),
|
|
3954
|
+
lightmapCoords: new Float32Array(lightmapCoords),
|
|
3955
|
+
indices: new Uint16Array(indices),
|
|
3956
|
+
texture: texInfo.texture,
|
|
3957
|
+
surfaceFlags: texInfo.flags,
|
|
3958
|
+
lightmap: lightmapData
|
|
3959
|
+
});
|
|
3960
|
+
}
|
|
3961
|
+
return results;
|
|
3962
|
+
}
|
|
3486
3963
|
function buildBspGeometry(gl, surfaces, options = {}) {
|
|
3487
3964
|
const resolved = {
|
|
3488
3965
|
atlasSize: options.atlasSize ?? 1024,
|
|
@@ -5162,6 +5639,8 @@ function createEngine(imports) {
|
|
|
5162
5639
|
BSP_SURFACE_FRAGMENT_SOURCE,
|
|
5163
5640
|
BSP_SURFACE_VERTEX_SOURCE,
|
|
5164
5641
|
BSP_VERTEX_LAYOUT,
|
|
5642
|
+
BspLoader,
|
|
5643
|
+
BspParseError,
|
|
5165
5644
|
BspSurfacePipeline,
|
|
5166
5645
|
Camera,
|
|
5167
5646
|
ConfigStringRegistry,
|
|
@@ -5231,8 +5710,10 @@ function createEngine(imports) {
|
|
|
5231
5710
|
computeSkyScroll,
|
|
5232
5711
|
createAnimationState,
|
|
5233
5712
|
createAudioGraph,
|
|
5713
|
+
createBspSurfaces,
|
|
5234
5714
|
createEngine,
|
|
5235
5715
|
createEngineRuntime,
|
|
5716
|
+
createFaceLightmap,
|
|
5236
5717
|
createInitialChannels,
|
|
5237
5718
|
createProgramFromSources,
|
|
5238
5719
|
createWebGLContext,
|
|
@@ -5247,6 +5728,7 @@ function createEngine(imports) {
|
|
|
5247
5728
|
ingestPaks,
|
|
5248
5729
|
interpolateMd3Tag,
|
|
5249
5730
|
interpolateVec3,
|
|
5731
|
+
parseBsp,
|
|
5250
5732
|
parseMd2,
|
|
5251
5733
|
parseMd3,
|
|
5252
5734
|
parsePcx,
|