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.
@@ -1030,10 +1030,418 @@ function wireFileInput(input, handler) {
1030
1030
  return () => input.removeEventListener("change", onChange);
1031
1031
  }
1032
1032
 
1033
+ // src/assets/bsp.ts
1034
+ var BSP_MAGIC = "IBSP";
1035
+ var BSP_VERSION = 38;
1036
+ var HEADER_LUMPS = 19;
1037
+ var HEADER_SIZE2 = 4 + 4 + HEADER_LUMPS * 8;
1038
+ var BspParseError = class extends Error {
1039
+ };
1040
+ var BspLoader = class {
1041
+ constructor(vfs) {
1042
+ this.vfs = vfs;
1043
+ }
1044
+ async load(path) {
1045
+ const buffer = await this.vfs.readFile(path);
1046
+ const copy = new Uint8Array(buffer.byteLength);
1047
+ copy.set(buffer);
1048
+ return parseBsp(copy.buffer);
1049
+ }
1050
+ };
1051
+ function parseBsp(buffer) {
1052
+ if (buffer.byteLength < HEADER_SIZE2) {
1053
+ throw new BspParseError("BSP too small to contain header");
1054
+ }
1055
+ const view = new DataView(buffer);
1056
+ const magic = String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3));
1057
+ if (magic !== BSP_MAGIC) {
1058
+ throw new BspParseError(`Invalid BSP magic ${magic}`);
1059
+ }
1060
+ const version = view.getInt32(4, true);
1061
+ if (version !== BSP_VERSION) {
1062
+ throw new BspParseError(`Unsupported BSP version ${version}`);
1063
+ }
1064
+ const lumps = /* @__PURE__ */ new Map();
1065
+ for (let i = 0; i < HEADER_LUMPS; i += 1) {
1066
+ const offset = view.getInt32(8 + i * 8, true);
1067
+ const length = view.getInt32(12 + i * 8, true);
1068
+ if (offset < 0 || length < 0 || offset + length > buffer.byteLength) {
1069
+ throw new BspParseError(`Invalid lump bounds for index ${i}`);
1070
+ }
1071
+ lumps.set(i, { offset, length });
1072
+ }
1073
+ const header = { version, lumps };
1074
+ const entities = parseEntities(buffer, lumps.get(0 /* Entities */));
1075
+ const planes = parsePlanes(buffer, lumps.get(1 /* Planes */));
1076
+ const vertices = parseVertices(buffer, lumps.get(2 /* Vertices */));
1077
+ const nodes = parseNodes(buffer, lumps.get(4 /* Nodes */));
1078
+ const texInfo = parseTexInfo(buffer, lumps.get(5 /* TexInfo */));
1079
+ const faces = parseFaces(buffer, lumps.get(6 /* Faces */));
1080
+ const lightMaps = new Uint8Array(buffer, lumps.get(7 /* Lighting */).offset, lumps.get(7 /* Lighting */).length);
1081
+ const lightMapInfo = buildLightMapInfo(faces, lumps.get(7 /* Lighting */));
1082
+ const leafs = parseLeafs(buffer, lumps.get(8 /* Leafs */));
1083
+ const edges = parseEdges(buffer, lumps.get(11 /* Edges */));
1084
+ const surfEdges = parseSurfEdges(buffer, lumps.get(12 /* SurfEdges */));
1085
+ const models = parseModels(buffer, lumps.get(13 /* Models */));
1086
+ const brushes = parseBrushes(buffer, lumps.get(14 /* Brushes */));
1087
+ const brushSides = parseBrushSides(buffer, lumps.get(15 /* BrushSides */));
1088
+ const leafLists = parseLeafLists(buffer, lumps.get(9 /* LeafFaces */), lumps.get(10 /* LeafBrushes */), leafs);
1089
+ const visibility = parseVisibility(buffer, lumps.get(3 /* Visibility */));
1090
+ return {
1091
+ header,
1092
+ entities,
1093
+ planes,
1094
+ vertices,
1095
+ nodes,
1096
+ texInfo,
1097
+ faces,
1098
+ lightMaps,
1099
+ lightMapInfo,
1100
+ leafs,
1101
+ leafLists,
1102
+ edges,
1103
+ surfEdges,
1104
+ models,
1105
+ brushes,
1106
+ brushSides,
1107
+ visibility
1108
+ };
1109
+ }
1110
+ function parseEntities(buffer, info) {
1111
+ const raw = new TextDecoder().decode(new Uint8Array(buffer, info.offset, info.length));
1112
+ const entities = parseEntityString(raw);
1113
+ const worldspawn = entities.find((ent) => ent.classname === "worldspawn");
1114
+ return { raw, entities, worldspawn };
1115
+ }
1116
+ function parseEntityString(text) {
1117
+ const entities = [];
1118
+ const tokenizer = /\{([^}]*)\}/gms;
1119
+ let match;
1120
+ while ((match = tokenizer.exec(text)) !== null) {
1121
+ const entityText = match[1];
1122
+ const properties = {};
1123
+ const kvRegex = /"([^\"]*)"\s+"([^\"]*)"/g;
1124
+ let kv;
1125
+ while ((kv = kvRegex.exec(entityText)) !== null) {
1126
+ properties[kv[1]] = kv[2];
1127
+ }
1128
+ entities.push({ classname: properties.classname, properties });
1129
+ }
1130
+ return entities;
1131
+ }
1132
+ function parsePlanes(buffer, info) {
1133
+ const view = new DataView(buffer, info.offset, info.length);
1134
+ const count = info.length / 20;
1135
+ if (count % 1 !== 0) {
1136
+ throw new BspParseError("Plane lump has invalid length");
1137
+ }
1138
+ const planes = [];
1139
+ for (let i = 0; i < count; i += 1) {
1140
+ const normal = [view.getFloat32(i * 20, true), view.getFloat32(i * 20 + 4, true), view.getFloat32(i * 20 + 8, true)];
1141
+ const dist = view.getFloat32(i * 20 + 12, true);
1142
+ const type = view.getInt32(i * 20 + 16, true);
1143
+ planes.push({ normal, dist, type });
1144
+ }
1145
+ return planes;
1146
+ }
1147
+ function parseVertices(buffer, info) {
1148
+ const view = new DataView(buffer, info.offset, info.length);
1149
+ const count = info.length / 12;
1150
+ if (count % 1 !== 0) {
1151
+ throw new BspParseError("Vertex lump has invalid length");
1152
+ }
1153
+ const vertices = [];
1154
+ for (let i = 0; i < count; i += 1) {
1155
+ vertices.push([
1156
+ view.getFloat32(i * 12, true),
1157
+ view.getFloat32(i * 12 + 4, true),
1158
+ view.getFloat32(i * 12 + 8, true)
1159
+ ]);
1160
+ }
1161
+ return vertices;
1162
+ }
1163
+ function parseNodes(buffer, info) {
1164
+ const view = new DataView(buffer, info.offset, info.length);
1165
+ const entrySize = 36;
1166
+ const count = info.length / entrySize;
1167
+ if (count % 1 !== 0) {
1168
+ throw new BspParseError("Node lump has invalid length");
1169
+ }
1170
+ const nodes = [];
1171
+ for (let i = 0; i < count; i += 1) {
1172
+ const base = i * entrySize;
1173
+ const planeIndex = view.getInt32(base, true);
1174
+ const children = [view.getInt32(base + 4, true), view.getInt32(base + 8, true)];
1175
+ const mins = [view.getInt16(base + 12, true), view.getInt16(base + 14, true), view.getInt16(base + 16, true)];
1176
+ const maxs = [view.getInt16(base + 18, true), view.getInt16(base + 20, true), view.getInt16(base + 22, true)];
1177
+ const firstFace = view.getUint16(base + 24, true);
1178
+ const numFaces = view.getUint16(base + 26, true);
1179
+ nodes.push({ planeIndex, children, mins, maxs, firstFace, numFaces });
1180
+ }
1181
+ return nodes;
1182
+ }
1183
+ function parseTexInfo(buffer, info) {
1184
+ const view = new DataView(buffer, info.offset, info.length);
1185
+ const entrySize = 76;
1186
+ const count = info.length / entrySize;
1187
+ if (count % 1 !== 0) {
1188
+ throw new BspParseError("TexInfo lump has invalid length");
1189
+ }
1190
+ const texInfos = [];
1191
+ for (let i = 0; i < count; i += 1) {
1192
+ const base = i * entrySize;
1193
+ const s = [view.getFloat32(base, true), view.getFloat32(base + 4, true), view.getFloat32(base + 8, true)];
1194
+ const sOffset = view.getFloat32(base + 12, true);
1195
+ const t = [view.getFloat32(base + 16, true), view.getFloat32(base + 20, true), view.getFloat32(base + 24, true)];
1196
+ const tOffset = view.getFloat32(base + 28, true);
1197
+ const flags = view.getInt32(base + 32, true);
1198
+ const value = view.getInt32(base + 36, true);
1199
+ const textureBytes = new Uint8Array(buffer, info.offset + base + 40, 32);
1200
+ const texture = new TextDecoder("utf-8").decode(textureBytes).replace(/\0.*$/, "");
1201
+ const nextTexInfo = view.getInt32(base + 72, true);
1202
+ texInfos.push({ s, sOffset, t, tOffset, flags, value, texture, nextTexInfo });
1203
+ }
1204
+ return texInfos;
1205
+ }
1206
+ function parseFaces(buffer, info) {
1207
+ const view = new DataView(buffer, info.offset, info.length);
1208
+ const entrySize = 20;
1209
+ const count = info.length / entrySize;
1210
+ if (count % 1 !== 0) {
1211
+ throw new BspParseError("Face lump has invalid length");
1212
+ }
1213
+ const faces = [];
1214
+ for (let i = 0; i < count; i += 1) {
1215
+ const base = i * entrySize;
1216
+ const planeIndex = view.getUint16(base, true);
1217
+ const side = view.getInt16(base + 2, true);
1218
+ const firstEdge = view.getInt32(base + 4, true);
1219
+ const numEdges = view.getInt16(base + 8, true);
1220
+ const texInfo = view.getInt16(base + 10, true);
1221
+ const styles = [
1222
+ view.getUint8(base + 12),
1223
+ view.getUint8(base + 13),
1224
+ view.getUint8(base + 14),
1225
+ view.getUint8(base + 15)
1226
+ ];
1227
+ const lightOffset = view.getInt32(base + 16, true);
1228
+ faces.push({ planeIndex, side, firstEdge, numEdges, texInfo, styles, lightOffset });
1229
+ }
1230
+ return faces;
1231
+ }
1232
+ function parseLeafs(buffer, info) {
1233
+ const view = new DataView(buffer, info.offset, info.length);
1234
+ const entrySize = 28;
1235
+ const count = info.length / entrySize;
1236
+ if (count % 1 !== 0) {
1237
+ throw new BspParseError("Leaf lump has invalid length");
1238
+ }
1239
+ const leafs = [];
1240
+ for (let i = 0; i < count; i += 1) {
1241
+ const base = i * entrySize;
1242
+ const contents = view.getInt32(base, true);
1243
+ const cluster = view.getInt16(base + 4, true);
1244
+ const area = view.getInt16(base + 6, true);
1245
+ const mins = [view.getInt16(base + 8, true), view.getInt16(base + 10, true), view.getInt16(base + 12, true)];
1246
+ const maxs = [view.getInt16(base + 14, true), view.getInt16(base + 16, true), view.getInt16(base + 18, true)];
1247
+ const firstLeafFace = view.getUint16(base + 20, true);
1248
+ const numLeafFaces = view.getUint16(base + 22, true);
1249
+ const firstLeafBrush = view.getUint16(base + 24, true);
1250
+ const numLeafBrushes = view.getUint16(base + 26, true);
1251
+ leafs.push({ contents, cluster, area, mins, maxs, firstLeafFace, numLeafFaces, firstLeafBrush, numLeafBrushes });
1252
+ }
1253
+ return leafs;
1254
+ }
1255
+ function parseEdges(buffer, info) {
1256
+ const view = new DataView(buffer, info.offset, info.length);
1257
+ const entrySize = 4;
1258
+ const count = info.length / entrySize;
1259
+ if (count % 1 !== 0) {
1260
+ throw new BspParseError("Edge lump has invalid length");
1261
+ }
1262
+ const edges = [];
1263
+ for (let i = 0; i < count; i += 1) {
1264
+ const base = i * entrySize;
1265
+ edges.push({ vertices: [view.getUint16(base, true), view.getUint16(base + 2, true)] });
1266
+ }
1267
+ return edges;
1268
+ }
1269
+ function parseSurfEdges(buffer, info) {
1270
+ const count = info.length / 4;
1271
+ if (count % 1 !== 0) {
1272
+ throw new BspParseError("SurfEdge lump has invalid length");
1273
+ }
1274
+ const view = new DataView(buffer, info.offset, info.length);
1275
+ const edges = new Int32Array(count);
1276
+ for (let i = 0; i < count; i += 1) {
1277
+ edges[i] = view.getInt32(i * 4, true);
1278
+ }
1279
+ return edges;
1280
+ }
1281
+ function parseModels(buffer, info) {
1282
+ const view = new DataView(buffer, info.offset, info.length);
1283
+ const entrySize = 48;
1284
+ if (info.length % entrySize !== 0) {
1285
+ throw new BspParseError("Model lump has invalid length");
1286
+ }
1287
+ const count = info.length / entrySize;
1288
+ const models = [];
1289
+ for (let i = 0; i < count; i += 1) {
1290
+ const base = i * entrySize;
1291
+ const mins = [view.getFloat32(base, true), view.getFloat32(base + 4, true), view.getFloat32(base + 8, true)];
1292
+ const maxs = [view.getFloat32(base + 12, true), view.getFloat32(base + 16, true), view.getFloat32(base + 20, true)];
1293
+ const origin = [view.getFloat32(base + 24, true), view.getFloat32(base + 28, true), view.getFloat32(base + 32, true)];
1294
+ const headNode = view.getInt32(base + 36, true);
1295
+ const firstFace = view.getInt32(base + 40, true);
1296
+ const numFaces = view.getInt32(base + 44, true);
1297
+ models.push({ mins, maxs, origin, headNode, firstFace, numFaces });
1298
+ }
1299
+ return models;
1300
+ }
1301
+ function parseBrushes(buffer, info) {
1302
+ const view = new DataView(buffer, info.offset, info.length);
1303
+ const entrySize = 12;
1304
+ const count = info.length / entrySize;
1305
+ if (count % 1 !== 0) {
1306
+ throw new BspParseError("Brush lump has invalid length");
1307
+ }
1308
+ const brushes = [];
1309
+ for (let i = 0; i < count; i += 1) {
1310
+ const base = i * entrySize;
1311
+ brushes.push({
1312
+ firstSide: view.getInt32(base, true),
1313
+ numSides: view.getInt32(base + 4, true),
1314
+ contents: view.getInt32(base + 8, true)
1315
+ });
1316
+ }
1317
+ return brushes;
1318
+ }
1319
+ function parseBrushSides(buffer, info) {
1320
+ const view = new DataView(buffer, info.offset, info.length);
1321
+ const entrySize = 8;
1322
+ const count = info.length / entrySize;
1323
+ if (count % 1 !== 0) {
1324
+ throw new BspParseError("Brush side lump has invalid length");
1325
+ }
1326
+ const sides = [];
1327
+ for (let i = 0; i < count; i += 1) {
1328
+ const base = i * entrySize;
1329
+ sides.push({ planeIndex: view.getUint16(base, true), texInfo: view.getInt16(base + 2, true) });
1330
+ }
1331
+ return sides;
1332
+ }
1333
+ function parseLeafLists(buffer, leafFacesInfo, leafBrushesInfo, leafs) {
1334
+ const leafFaces = [];
1335
+ const leafBrushes = [];
1336
+ const maxLeafFaceIndex = leafFacesInfo.length / 2;
1337
+ const maxLeafBrushIndex = leafBrushesInfo.length / 2;
1338
+ const faceView = new DataView(buffer, leafFacesInfo.offset, leafFacesInfo.length);
1339
+ const brushView = new DataView(buffer, leafBrushesInfo.offset, leafBrushesInfo.length);
1340
+ for (const leaf of leafs) {
1341
+ if (leaf.firstLeafFace + leaf.numLeafFaces > maxLeafFaceIndex) {
1342
+ throw new BspParseError("Leaf faces reference data past lump bounds");
1343
+ }
1344
+ if (leaf.firstLeafBrush + leaf.numLeafBrushes > maxLeafBrushIndex) {
1345
+ throw new BspParseError("Leaf brushes reference data past lump bounds");
1346
+ }
1347
+ const faces = [];
1348
+ for (let i = 0; i < leaf.numLeafFaces; i += 1) {
1349
+ faces.push(faceView.getUint16((leaf.firstLeafFace + i) * 2, true));
1350
+ }
1351
+ const brushes = [];
1352
+ for (let i = 0; i < leaf.numLeafBrushes; i += 1) {
1353
+ brushes.push(brushView.getUint16((leaf.firstLeafBrush + i) * 2, true));
1354
+ }
1355
+ leafFaces.push(faces);
1356
+ leafBrushes.push(brushes);
1357
+ }
1358
+ return { leafFaces, leafBrushes };
1359
+ }
1360
+ function parseVisibility(buffer, info) {
1361
+ if (info.length === 0) {
1362
+ return void 0;
1363
+ }
1364
+ if (info.length < 4) {
1365
+ throw new BspParseError("Visibility lump too small");
1366
+ }
1367
+ const view = new DataView(buffer, info.offset, info.length);
1368
+ const numClusters = view.getInt32(0, true);
1369
+ const headerBytes = 4 + numClusters * 8;
1370
+ if (numClusters < 0 || headerBytes > info.length) {
1371
+ throw new BspParseError("Visibility lump truncated");
1372
+ }
1373
+ let cursor = 4;
1374
+ const clusters = [];
1375
+ for (let i = 0; i < numClusters; i += 1) {
1376
+ const pvsOffset = view.getInt32(cursor, true);
1377
+ const phsOffset = view.getInt32(cursor + 4, true);
1378
+ cursor += 8;
1379
+ const absolutePvs = info.offset + pvsOffset;
1380
+ const absolutePhs = info.offset + phsOffset;
1381
+ const lumpEnd = info.offset + info.length;
1382
+ if (pvsOffset < 0 || phsOffset < 0 || absolutePvs >= lumpEnd || absolutePhs >= lumpEnd) {
1383
+ throw new BspParseError("Visibility offsets out of range");
1384
+ }
1385
+ clusters.push({
1386
+ pvs: decompressVis(buffer, absolutePvs, numClusters, info),
1387
+ phs: decompressVis(buffer, absolutePhs, numClusters, info)
1388
+ });
1389
+ }
1390
+ return { numClusters, clusters };
1391
+ }
1392
+ function decompressVis(buffer, offset, numClusters, lump) {
1393
+ const rowBytes = Math.ceil(numClusters / 8);
1394
+ const result = new Uint8Array(rowBytes);
1395
+ const src = new Uint8Array(buffer);
1396
+ let srcIndex = offset;
1397
+ let destIndex = 0;
1398
+ const maxOffset = lump.offset + lump.length;
1399
+ while (destIndex < rowBytes) {
1400
+ if (srcIndex >= maxOffset) {
1401
+ throw new BspParseError("Visibility data truncated");
1402
+ }
1403
+ const value = src[srcIndex++];
1404
+ if (value !== 0) {
1405
+ result[destIndex++] = value;
1406
+ continue;
1407
+ }
1408
+ if (srcIndex >= maxOffset) {
1409
+ throw new BspParseError("Visibility run exceeds lump bounds");
1410
+ }
1411
+ const runLength = src[srcIndex++];
1412
+ for (let i = 0; i < runLength && destIndex < rowBytes; i += 1) {
1413
+ result[destIndex++] = 0;
1414
+ }
1415
+ }
1416
+ return result;
1417
+ }
1418
+ function buildLightMapInfo(faces, lightingLump) {
1419
+ return faces.map((face) => {
1420
+ if (face.lightOffset < 0) {
1421
+ return void 0;
1422
+ }
1423
+ return {
1424
+ offset: face.lightOffset,
1425
+ length: Math.max(0, lightingLump.length - face.lightOffset)
1426
+ };
1427
+ });
1428
+ }
1429
+ function createFaceLightmap(face, lightMaps, info) {
1430
+ if (face.lightOffset < 0 || face.lightOffset >= lightMaps.byteLength) {
1431
+ return void 0;
1432
+ }
1433
+ const available = lightMaps.byteLength - face.lightOffset;
1434
+ const length = Math.min(info?.length ?? available, available);
1435
+ if (length <= 0) {
1436
+ return void 0;
1437
+ }
1438
+ return lightMaps.subarray(face.lightOffset, face.lightOffset + length);
1439
+ }
1440
+
1033
1441
  // src/assets/md2.ts
1034
1442
  var MD2_MAGIC = 844121161;
1035
1443
  var MD2_VERSION = 8;
1036
- var HEADER_SIZE2 = 17 * 4;
1444
+ var HEADER_SIZE3 = 17 * 4;
1037
1445
  var MD2_NORMALS = [
1038
1446
  { x: -0.525731, y: 0, z: 0.850651 },
1039
1447
  { x: -0.442863, y: 0.238856, z: 0.864188 },
@@ -1222,12 +1630,12 @@ function readCString2(view, offset, maxLength) {
1222
1630
  }
1223
1631
  function validateSection(buffer, offset, length, label) {
1224
1632
  if (length === 0) return;
1225
- if (offset < HEADER_SIZE2 || offset + length > buffer.byteLength) {
1633
+ if (offset < HEADER_SIZE3 || offset + length > buffer.byteLength) {
1226
1634
  throw new Md2ParseError(`${label} section is out of bounds`);
1227
1635
  }
1228
1636
  }
1229
1637
  function parseHeader(buffer) {
1230
- if (buffer.byteLength < HEADER_SIZE2) {
1638
+ if (buffer.byteLength < HEADER_SIZE3) {
1231
1639
  throw new Md2ParseError("MD2 buffer too small to contain header");
1232
1640
  }
1233
1641
  const view = new DataView(buffer);
@@ -1641,7 +2049,7 @@ var Md3Loader = class {
1641
2049
  var IDSPRITEHEADER = 1229214514;
1642
2050
  var SPRITE_VERSION = 2;
1643
2051
  var MAX_SKINNAME = 64;
1644
- var HEADER_SIZE3 = 12;
2052
+ var HEADER_SIZE4 = 12;
1645
2053
  var SpriteParseError = class extends Error {
1646
2054
  };
1647
2055
  function readCString3(view, offset, maxLength) {
@@ -1654,7 +2062,7 @@ function readCString3(view, offset, maxLength) {
1654
2062
  return String.fromCharCode(...chars);
1655
2063
  }
1656
2064
  function parseSprite(buffer) {
1657
- if (buffer.byteLength < HEADER_SIZE3) {
2065
+ if (buffer.byteLength < HEADER_SIZE4) {
1658
2066
  throw new SpriteParseError("Sprite buffer too small to contain header");
1659
2067
  }
1660
2068
  const view = new DataView(buffer);
@@ -1669,7 +2077,7 @@ function parseSprite(buffer) {
1669
2077
  }
1670
2078
  const frames = [];
1671
2079
  const frameSize = 16 + MAX_SKINNAME;
1672
- let offset = HEADER_SIZE3;
2080
+ let offset = HEADER_SIZE4;
1673
2081
  for (let i = 0; i < numFrames; i += 1) {
1674
2082
  if (offset + frameSize > buffer.byteLength) {
1675
2083
  throw new SpriteParseError("Sprite frame data exceeds buffer length");
@@ -3335,6 +3743,70 @@ function buildVertexData(surface, placement) {
3335
3743
  }
3336
3744
  return interleaved;
3337
3745
  }
3746
+ function createBspSurfaces(map) {
3747
+ const results = [];
3748
+ for (let faceIndex = 0; faceIndex < map.faces.length; faceIndex++) {
3749
+ const face = map.faces[faceIndex];
3750
+ if (face.texInfo < 0) continue;
3751
+ const texInfo = map.texInfo[face.texInfo];
3752
+ const vertices = [];
3753
+ const textureCoords = [];
3754
+ const lightmapCoords = [];
3755
+ for (let i = 0; i < face.numEdges; i++) {
3756
+ const edgeIndex = map.surfEdges[face.firstEdge + i];
3757
+ const edge = map.edges[Math.abs(edgeIndex)];
3758
+ const vIndex = edgeIndex >= 0 ? edge.vertices[0] : edge.vertices[1];
3759
+ const vertex = map.vertices[vIndex];
3760
+ vertices.push(vertex[0], vertex[1], vertex[2]);
3761
+ const s = vertex[0] * texInfo.s[0] + vertex[1] * texInfo.s[1] + vertex[2] * texInfo.s[2] + texInfo.sOffset;
3762
+ const t = vertex[0] * texInfo.t[0] + vertex[1] * texInfo.t[1] + vertex[2] * texInfo.t[2] + texInfo.tOffset;
3763
+ textureCoords.push(s, t);
3764
+ lightmapCoords.push(s, t);
3765
+ }
3766
+ const indices = [];
3767
+ const vertexCount = vertices.length / 3;
3768
+ for (let i = 1; i < vertexCount - 1; i++) {
3769
+ indices.push(0, i, i + 1);
3770
+ }
3771
+ let lightmapData;
3772
+ const lightmapInfo = map.lightMapInfo[faceIndex];
3773
+ if (lightmapInfo) {
3774
+ let minS = Infinity, maxS = -Infinity, minT = Infinity, maxT = -Infinity;
3775
+ for (let k = 0; k < textureCoords.length; k += 2) {
3776
+ const s = textureCoords[k];
3777
+ const t = textureCoords[k + 1];
3778
+ if (s < minS) minS = s;
3779
+ if (s > maxS) maxS = s;
3780
+ if (t < minT) minT = t;
3781
+ if (t > maxT) maxT = t;
3782
+ }
3783
+ const floorMinS = Math.floor(minS / 16);
3784
+ const floorMinT = Math.floor(minT / 16);
3785
+ const lmWidth = Math.ceil(maxS / 16) - floorMinS + 1;
3786
+ const lmHeight = Math.ceil(maxT / 16) - floorMinT + 1;
3787
+ const samples = createFaceLightmap(face, map.lightMaps, lightmapInfo);
3788
+ if (samples) {
3789
+ if (samples.length === lmWidth * lmHeight * 3) {
3790
+ lightmapData = { width: lmWidth, height: lmHeight, samples };
3791
+ for (let k = 0; k < lightmapCoords.length; k += 2) {
3792
+ lightmapCoords[k] = textureCoords[k] / 16 - floorMinS + 0.5;
3793
+ lightmapCoords[k + 1] = textureCoords[k + 1] / 16 - floorMinT + 0.5;
3794
+ }
3795
+ }
3796
+ }
3797
+ }
3798
+ results.push({
3799
+ vertices: new Float32Array(vertices),
3800
+ textureCoords: new Float32Array(textureCoords),
3801
+ lightmapCoords: new Float32Array(lightmapCoords),
3802
+ indices: new Uint16Array(indices),
3803
+ texture: texInfo.texture,
3804
+ surfaceFlags: texInfo.flags,
3805
+ lightmap: lightmapData
3806
+ });
3807
+ }
3808
+ return results;
3809
+ }
3338
3810
  function buildBspGeometry(gl, surfaces, options = {}) {
3339
3811
  const resolved = {
3340
3812
  atlasSize: options.atlasSize ?? 1024,
@@ -5013,6 +5485,8 @@ export {
5013
5485
  BSP_SURFACE_FRAGMENT_SOURCE,
5014
5486
  BSP_SURFACE_VERTEX_SOURCE,
5015
5487
  BSP_VERTEX_LAYOUT,
5488
+ BspLoader,
5489
+ BspParseError,
5016
5490
  BspSurfacePipeline,
5017
5491
  Camera,
5018
5492
  ConfigStringRegistry,
@@ -5082,8 +5556,10 @@ export {
5082
5556
  computeSkyScroll,
5083
5557
  createAnimationState,
5084
5558
  createAudioGraph,
5559
+ createBspSurfaces,
5085
5560
  createEngine,
5086
5561
  createEngineRuntime,
5562
+ createFaceLightmap,
5087
5563
  createInitialChannels,
5088
5564
  createProgramFromSources,
5089
5565
  createWebGLContext,
@@ -5098,6 +5574,7 @@ export {
5098
5574
  ingestPaks,
5099
5575
  interpolateMd3Tag,
5100
5576
  interpolateVec3,
5577
+ parseBsp,
5101
5578
  parseMd2,
5102
5579
  parseMd3,
5103
5580
  parsePcx,