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.
@@ -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 HEADER_SIZE2 = 17 * 4;
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 < HEADER_SIZE2 || offset + length > buffer.byteLength) {
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 < HEADER_SIZE2) {
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 HEADER_SIZE3 = 12;
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 < HEADER_SIZE3) {
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 = HEADER_SIZE3;
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,