@quake2ts/test-utils 0.0.838 → 0.0.839
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/dist/index.cjs +622 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +137 -11
- package/dist/index.d.ts +137 -11
- package/dist/index.js +606 -0
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/engine/builders/bspBuilder.ts +278 -0
- package/src/engine/builders/md2Builder.ts +156 -0
- package/src/engine/builders/md3Builder.ts +149 -0
- package/src/engine/builders/pakBuilder.ts +50 -0
- package/src/engine/builders/pcxBuilder.ts +51 -0
- package/src/engine/builders/visual-testing.ts +144 -0
- package/src/engine/builders/walBuilder.ts +40 -0
- package/src/engine/builders/wavBuilder.ts +49 -0
- package/src/index.ts +7 -0
- package/src/shared/math.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -4910,6 +4910,602 @@ function expectNoDoubleTransform(renderer) {
|
|
|
4910
4910
|
expect2(warnings).toHaveLength(0);
|
|
4911
4911
|
}
|
|
4912
4912
|
|
|
4913
|
+
// src/engine/builders/bspBuilder.ts
|
|
4914
|
+
import { BspLump } from "@quake2ts/engine";
|
|
4915
|
+
var HEADER_LUMPS = 19;
|
|
4916
|
+
var HEADER_SIZE = 4 + 4 + HEADER_LUMPS * 8;
|
|
4917
|
+
function allocBuffer(size) {
|
|
4918
|
+
return new DataView(new ArrayBuffer(size));
|
|
4919
|
+
}
|
|
4920
|
+
function writeVec3(view, offset, vec) {
|
|
4921
|
+
view.setFloat32(offset, vec[0], true);
|
|
4922
|
+
view.setFloat32(offset + 4, vec[1], true);
|
|
4923
|
+
view.setFloat32(offset + 8, vec[2], true);
|
|
4924
|
+
}
|
|
4925
|
+
function encodePlanes(planes) {
|
|
4926
|
+
const view = allocBuffer(planes.length * 20);
|
|
4927
|
+
planes.forEach((plane, index) => {
|
|
4928
|
+
const base = index * 20;
|
|
4929
|
+
writeVec3(view, base, plane.normal);
|
|
4930
|
+
view.setFloat32(base + 12, plane.dist, true);
|
|
4931
|
+
view.setInt32(base + 16, plane.type, true);
|
|
4932
|
+
});
|
|
4933
|
+
return new Uint8Array(view.buffer);
|
|
4934
|
+
}
|
|
4935
|
+
function encodeVertices(vertices) {
|
|
4936
|
+
const view = allocBuffer(vertices.length * 12);
|
|
4937
|
+
vertices.forEach((vertex, index) => writeVec3(view, index * 12, vertex));
|
|
4938
|
+
return new Uint8Array(view.buffer);
|
|
4939
|
+
}
|
|
4940
|
+
function encodeNodes(nodes) {
|
|
4941
|
+
const view = allocBuffer(nodes.length * 28);
|
|
4942
|
+
nodes.forEach((node, index) => {
|
|
4943
|
+
const base = index * 28;
|
|
4944
|
+
view.setInt32(base, node.planeIndex, true);
|
|
4945
|
+
view.setInt32(base + 4, node.children[0], true);
|
|
4946
|
+
view.setInt32(base + 8, node.children[1], true);
|
|
4947
|
+
view.setInt16(base + 12, node.mins[0], true);
|
|
4948
|
+
view.setInt16(base + 14, node.mins[1], true);
|
|
4949
|
+
view.setInt16(base + 16, node.mins[2], true);
|
|
4950
|
+
view.setInt16(base + 18, node.maxs[0], true);
|
|
4951
|
+
view.setInt16(base + 20, node.maxs[1], true);
|
|
4952
|
+
view.setInt16(base + 22, node.maxs[2], true);
|
|
4953
|
+
view.setUint16(base + 24, node.firstFace, true);
|
|
4954
|
+
view.setUint16(base + 26, node.numFaces, true);
|
|
4955
|
+
});
|
|
4956
|
+
return new Uint8Array(view.buffer);
|
|
4957
|
+
}
|
|
4958
|
+
function encodeTexInfo(texInfos) {
|
|
4959
|
+
const view = allocBuffer(texInfos.length * 76);
|
|
4960
|
+
texInfos.forEach((tex, index) => {
|
|
4961
|
+
const base = index * 76;
|
|
4962
|
+
writeVec3(view, base, tex.s);
|
|
4963
|
+
view.setFloat32(base + 12, tex.sOffset, true);
|
|
4964
|
+
writeVec3(view, base + 16, tex.t);
|
|
4965
|
+
view.setFloat32(base + 28, tex.tOffset, true);
|
|
4966
|
+
view.setInt32(base + 32, tex.flags, true);
|
|
4967
|
+
view.setInt32(base + 36, tex.value, true);
|
|
4968
|
+
const textureBytes = new TextEncoder().encode(tex.texture);
|
|
4969
|
+
new Uint8Array(view.buffer).set(textureBytes.slice(0, 32), base + 40);
|
|
4970
|
+
view.setInt32(base + 72, tex.nextTexInfo, true);
|
|
4971
|
+
});
|
|
4972
|
+
return new Uint8Array(view.buffer);
|
|
4973
|
+
}
|
|
4974
|
+
function encodeFaces(faces) {
|
|
4975
|
+
const view = allocBuffer(faces.length * 20);
|
|
4976
|
+
faces.forEach((face, index) => {
|
|
4977
|
+
const base = index * 20;
|
|
4978
|
+
view.setUint16(base, face.planeIndex, true);
|
|
4979
|
+
view.setInt16(base + 2, face.side, true);
|
|
4980
|
+
view.setInt32(base + 4, face.firstEdge, true);
|
|
4981
|
+
view.setInt16(base + 8, face.numEdges, true);
|
|
4982
|
+
view.setInt16(base + 10, face.texInfo, true);
|
|
4983
|
+
face.styles.forEach((style, sIndex) => view.setUint8(base + 12 + sIndex, style));
|
|
4984
|
+
view.setInt32(base + 16, face.lightOffset, true);
|
|
4985
|
+
});
|
|
4986
|
+
return new Uint8Array(view.buffer);
|
|
4987
|
+
}
|
|
4988
|
+
function encodeLeafs(leafs) {
|
|
4989
|
+
const view = allocBuffer(leafs.length * 28);
|
|
4990
|
+
leafs.forEach((leaf, index) => {
|
|
4991
|
+
const base = index * 28;
|
|
4992
|
+
view.setInt32(base, leaf.contents, true);
|
|
4993
|
+
view.setInt16(base + 4, leaf.cluster, true);
|
|
4994
|
+
view.setInt16(base + 6, leaf.area, true);
|
|
4995
|
+
view.setInt16(base + 8, leaf.mins[0], true);
|
|
4996
|
+
view.setInt16(base + 10, leaf.mins[1], true);
|
|
4997
|
+
view.setInt16(base + 12, leaf.mins[2], true);
|
|
4998
|
+
view.setInt16(base + 14, leaf.maxs[0], true);
|
|
4999
|
+
view.setInt16(base + 16, leaf.maxs[1], true);
|
|
5000
|
+
view.setInt16(base + 18, leaf.maxs[2], true);
|
|
5001
|
+
view.setUint16(base + 20, leaf.firstLeafFace, true);
|
|
5002
|
+
view.setUint16(base + 22, leaf.numLeafFaces, true);
|
|
5003
|
+
view.setUint16(base + 24, leaf.firstLeafBrush, true);
|
|
5004
|
+
view.setUint16(base + 26, leaf.numLeafBrushes, true);
|
|
5005
|
+
});
|
|
5006
|
+
return new Uint8Array(view.buffer);
|
|
5007
|
+
}
|
|
5008
|
+
function encodeEdges(edges) {
|
|
5009
|
+
const view = allocBuffer(edges.length * 4);
|
|
5010
|
+
edges.forEach((edge, index) => {
|
|
5011
|
+
const base = index * 4;
|
|
5012
|
+
view.setUint16(base, edge[0], true);
|
|
5013
|
+
view.setUint16(base + 2, edge[1], true);
|
|
5014
|
+
});
|
|
5015
|
+
return new Uint8Array(view.buffer);
|
|
5016
|
+
}
|
|
5017
|
+
function encodeModels(models) {
|
|
5018
|
+
const entrySize = 48;
|
|
5019
|
+
const view = allocBuffer(models.length * entrySize);
|
|
5020
|
+
models.forEach((model, index) => {
|
|
5021
|
+
const base = index * entrySize;
|
|
5022
|
+
writeVec3(view, base, model.mins);
|
|
5023
|
+
writeVec3(view, base + 12, model.maxs);
|
|
5024
|
+
writeVec3(view, base + 24, model.origin);
|
|
5025
|
+
view.setInt32(base + 36, model.headNode, true);
|
|
5026
|
+
view.setInt32(base + 40, model.firstFace, true);
|
|
5027
|
+
view.setInt32(base + 44, model.numFaces, true);
|
|
5028
|
+
});
|
|
5029
|
+
return new Uint8Array(view.buffer);
|
|
5030
|
+
}
|
|
5031
|
+
function encodeBrushes(brushes) {
|
|
5032
|
+
const view = allocBuffer(brushes.length * 12);
|
|
5033
|
+
brushes.forEach((brush, index) => {
|
|
5034
|
+
const base = index * 12;
|
|
5035
|
+
view.setInt32(base, brush.firstSide, true);
|
|
5036
|
+
view.setInt32(base + 4, brush.numSides, true);
|
|
5037
|
+
view.setInt32(base + 8, brush.contents, true);
|
|
5038
|
+
});
|
|
5039
|
+
return new Uint8Array(view.buffer);
|
|
5040
|
+
}
|
|
5041
|
+
function encodeBrushSides(sides) {
|
|
5042
|
+
const view = allocBuffer(sides.length * 4);
|
|
5043
|
+
sides.forEach((side, index) => {
|
|
5044
|
+
const base = index * 4;
|
|
5045
|
+
view.setUint16(base, side.planeIndex, true);
|
|
5046
|
+
view.setInt16(base + 2, side.texInfo, true);
|
|
5047
|
+
});
|
|
5048
|
+
return new Uint8Array(view.buffer);
|
|
5049
|
+
}
|
|
5050
|
+
function encodeVisibility(numClusters, pvsRows, phsRows) {
|
|
5051
|
+
const headerBytes = 4 + numClusters * 8;
|
|
5052
|
+
const header = allocBuffer(headerBytes);
|
|
5053
|
+
header.setInt32(0, numClusters, true);
|
|
5054
|
+
let cursor = headerBytes;
|
|
5055
|
+
const payloads = [];
|
|
5056
|
+
for (let i = 0; i < numClusters; i += 1) {
|
|
5057
|
+
const pvs = pvsRows[i];
|
|
5058
|
+
const phs = phsRows?.[i] ?? pvs;
|
|
5059
|
+
const pvsOffset = cursor - 0;
|
|
5060
|
+
const phsOffset = cursor + pvs.byteLength - 0;
|
|
5061
|
+
payloads.push(pvs, phs);
|
|
5062
|
+
header.setInt32(4 + i * 8, pvsOffset, true);
|
|
5063
|
+
header.setInt32(8 + i * 8, phsOffset, true);
|
|
5064
|
+
cursor += pvs.byteLength + phs.byteLength;
|
|
5065
|
+
}
|
|
5066
|
+
const result = new Uint8Array(cursor);
|
|
5067
|
+
result.set(new Uint8Array(header.buffer), 0);
|
|
5068
|
+
let payloadCursor = headerBytes;
|
|
5069
|
+
for (const payload of payloads) {
|
|
5070
|
+
result.set(payload, payloadCursor);
|
|
5071
|
+
payloadCursor += payload.byteLength;
|
|
5072
|
+
}
|
|
5073
|
+
return result;
|
|
5074
|
+
}
|
|
5075
|
+
function buildTestBsp(options) {
|
|
5076
|
+
const lumps = {};
|
|
5077
|
+
lumps[BspLump.Entities] = new TextEncoder().encode(options.entities ?? '{"classname" "worldspawn"}\n');
|
|
5078
|
+
lumps[BspLump.Planes] = options.planes ? encodePlanes(options.planes) : new Uint8Array();
|
|
5079
|
+
lumps[BspLump.Vertices] = options.vertices ? encodeVertices(options.vertices) : new Uint8Array();
|
|
5080
|
+
lumps[BspLump.Visibility] = options.visibility ?? new Uint8Array();
|
|
5081
|
+
lumps[BspLump.Nodes] = options.nodes ? encodeNodes(options.nodes) : new Uint8Array();
|
|
5082
|
+
lumps[BspLump.TexInfo] = options.texInfo ? encodeTexInfo(options.texInfo) : new Uint8Array();
|
|
5083
|
+
lumps[BspLump.Faces] = options.faces ? encodeFaces(options.faces) : new Uint8Array();
|
|
5084
|
+
lumps[BspLump.Lighting] = options.lighting ?? new Uint8Array();
|
|
5085
|
+
lumps[BspLump.Leafs] = options.leafs ? encodeLeafs(options.leafs) : new Uint8Array();
|
|
5086
|
+
lumps[BspLump.LeafFaces] = options.leafFaces ? new Uint8Array(options.leafFaces.buffer) : new Uint8Array();
|
|
5087
|
+
lumps[BspLump.LeafBrushes] = options.leafBrushes ? new Uint8Array(options.leafBrushes.buffer) : new Uint8Array();
|
|
5088
|
+
lumps[BspLump.Edges] = options.edges ? encodeEdges(options.edges) : new Uint8Array();
|
|
5089
|
+
lumps[BspLump.SurfEdges] = options.surfEdges ? new Uint8Array(options.surfEdges.buffer) : new Uint8Array();
|
|
5090
|
+
lumps[BspLump.Models] = options.models ? encodeModels(options.models) : new Uint8Array();
|
|
5091
|
+
lumps[BspLump.Brushes] = options.brushes ? encodeBrushes(options.brushes) : new Uint8Array();
|
|
5092
|
+
lumps[BspLump.BrushSides] = options.brushSides ? encodeBrushSides(options.brushSides) : new Uint8Array();
|
|
5093
|
+
lumps[BspLump.Areas] = options.areas ?? new Uint8Array();
|
|
5094
|
+
lumps[BspLump.AreaPortals] = options.areaPortals ?? new Uint8Array();
|
|
5095
|
+
let cursor = HEADER_SIZE;
|
|
5096
|
+
const ordered = [];
|
|
5097
|
+
for (let i = 0; i < HEADER_LUMPS; i += 1) {
|
|
5098
|
+
const data = lumps[i] ?? new Uint8Array();
|
|
5099
|
+
const info = { offset: cursor, length: data.byteLength };
|
|
5100
|
+
ordered.push({ info, data });
|
|
5101
|
+
cursor += data.byteLength;
|
|
5102
|
+
}
|
|
5103
|
+
const buffer = new ArrayBuffer(cursor);
|
|
5104
|
+
const header = new DataView(buffer);
|
|
5105
|
+
header.setUint8(0, 73);
|
|
5106
|
+
header.setUint8(1, 66);
|
|
5107
|
+
header.setUint8(2, 83);
|
|
5108
|
+
header.setUint8(3, 80);
|
|
5109
|
+
header.setInt32(4, 38, true);
|
|
5110
|
+
ordered.forEach((entry, index) => {
|
|
5111
|
+
header.setInt32(8 + index * 8, entry.info.offset, true);
|
|
5112
|
+
header.setInt32(12 + index * 8, entry.info.length, true);
|
|
5113
|
+
});
|
|
5114
|
+
const body = new Uint8Array(buffer);
|
|
5115
|
+
ordered.forEach((entry) => body.set(entry.data, entry.info.offset));
|
|
5116
|
+
return buffer;
|
|
5117
|
+
}
|
|
5118
|
+
function runLengthVisRow(values) {
|
|
5119
|
+
return new Uint8Array(values);
|
|
5120
|
+
}
|
|
5121
|
+
function encodedVisForClusters(numClusters, rows) {
|
|
5122
|
+
const rowBytes = Math.ceil(numClusters / 8);
|
|
5123
|
+
const encodedRows = rows.map((row) => new Uint8Array(row));
|
|
5124
|
+
return encodeVisibility(numClusters, encodedRows);
|
|
5125
|
+
}
|
|
5126
|
+
|
|
5127
|
+
// src/engine/builders/md2Builder.ts
|
|
5128
|
+
function allocBuffer2(size) {
|
|
5129
|
+
return new DataView(new ArrayBuffer(size));
|
|
5130
|
+
}
|
|
5131
|
+
function writeCString(view, offset, text, max) {
|
|
5132
|
+
const bytes = new TextEncoder().encode(text);
|
|
5133
|
+
const length = Math.min(bytes.length, max - 1);
|
|
5134
|
+
new Uint8Array(view.buffer, view.byteOffset + offset, length).set(bytes.slice(0, length));
|
|
5135
|
+
view.setUint8(offset + length, 0);
|
|
5136
|
+
}
|
|
5137
|
+
function encodeFrames(frames, numVertices) {
|
|
5138
|
+
const frameSize = 40 + numVertices * 4;
|
|
5139
|
+
const view = allocBuffer2(frames.length * frameSize);
|
|
5140
|
+
frames.forEach((frame, frameIndex) => {
|
|
5141
|
+
const base = frameIndex * frameSize;
|
|
5142
|
+
const scale = frame.scale ?? { x: 1, y: 1, z: 1 };
|
|
5143
|
+
const translate = frame.translate ?? { x: 0, y: 0, z: 0 };
|
|
5144
|
+
view.setFloat32(base, scale.x, true);
|
|
5145
|
+
view.setFloat32(base + 4, scale.y, true);
|
|
5146
|
+
view.setFloat32(base + 8, scale.z, true);
|
|
5147
|
+
view.setFloat32(base + 12, translate.x, true);
|
|
5148
|
+
view.setFloat32(base + 16, translate.y, true);
|
|
5149
|
+
view.setFloat32(base + 20, translate.z, true);
|
|
5150
|
+
writeCString(view, base + 24, frame.name, 16);
|
|
5151
|
+
frame.vertices.forEach((vertex, index) => {
|
|
5152
|
+
const offset = base + 40 + index * 4;
|
|
5153
|
+
view.setUint8(offset, Math.round((vertex.position.x - translate.x) / scale.x));
|
|
5154
|
+
view.setUint8(offset + 1, Math.round((vertex.position.y - translate.y) / scale.y));
|
|
5155
|
+
view.setUint8(offset + 2, Math.round((vertex.position.z - translate.z) / scale.z));
|
|
5156
|
+
view.setUint8(offset + 3, vertex.normalIndex);
|
|
5157
|
+
});
|
|
5158
|
+
});
|
|
5159
|
+
return new Uint8Array(view.buffer);
|
|
5160
|
+
}
|
|
5161
|
+
function encodeGlCommands(commands) {
|
|
5162
|
+
const bytes = [];
|
|
5163
|
+
(commands ?? []).forEach((command) => {
|
|
5164
|
+
const count = command.vertices.length * (command.mode === "strip" ? 1 : -1);
|
|
5165
|
+
bytes.push(count);
|
|
5166
|
+
command.vertices.forEach((vertex) => {
|
|
5167
|
+
const floatView = new DataView(new ArrayBuffer(4));
|
|
5168
|
+
floatView.setFloat32(0, vertex.s, true);
|
|
5169
|
+
bytes.push(floatView.getInt32(0, true));
|
|
5170
|
+
floatView.setFloat32(0, vertex.t, true);
|
|
5171
|
+
bytes.push(floatView.getInt32(0, true));
|
|
5172
|
+
bytes.push(vertex.vertexIndex);
|
|
5173
|
+
});
|
|
5174
|
+
});
|
|
5175
|
+
bytes.push(0);
|
|
5176
|
+
const data = new Uint8Array(bytes.length * 4);
|
|
5177
|
+
const view = new DataView(data.buffer);
|
|
5178
|
+
bytes.forEach((value, index) => view.setInt32(index * 4, value, true));
|
|
5179
|
+
return { data, count: bytes.length };
|
|
5180
|
+
}
|
|
5181
|
+
function buildMd2(options) {
|
|
5182
|
+
const numVertices = options.frames[0]?.vertices.length ?? 0;
|
|
5183
|
+
const frameSize = 40 + numVertices * 4;
|
|
5184
|
+
const skins = options.skins ?? [];
|
|
5185
|
+
const { data: glData, count: glCount } = encodeGlCommands(options.glCommands);
|
|
5186
|
+
const headerSize = 68;
|
|
5187
|
+
const skinsSize = skins.length * 64;
|
|
5188
|
+
const texCoordSize = options.texCoords.length * 4;
|
|
5189
|
+
const triangleSize = options.triangles.length * 12;
|
|
5190
|
+
const frameBlockSize = options.frames.length * frameSize;
|
|
5191
|
+
const glSize = glData.length;
|
|
5192
|
+
const offsetSkins = headerSize;
|
|
5193
|
+
const offsetTexCoords = offsetSkins + skinsSize;
|
|
5194
|
+
const offsetTriangles = offsetTexCoords + texCoordSize;
|
|
5195
|
+
const offsetFrames = offsetTriangles + triangleSize;
|
|
5196
|
+
const offsetGlCommands = offsetFrames + frameBlockSize;
|
|
5197
|
+
const offsetEnd = offsetGlCommands + glSize;
|
|
5198
|
+
const view = allocBuffer2(offsetEnd);
|
|
5199
|
+
view.setInt32(0, 844121161, true);
|
|
5200
|
+
view.setInt32(4, 8, true);
|
|
5201
|
+
view.setInt32(8, options.skinWidth ?? 64, true);
|
|
5202
|
+
view.setInt32(12, options.skinHeight ?? 64, true);
|
|
5203
|
+
view.setInt32(16, frameSize, true);
|
|
5204
|
+
view.setInt32(20, skins.length, true);
|
|
5205
|
+
view.setInt32(24, numVertices, true);
|
|
5206
|
+
view.setInt32(28, options.texCoords.length, true);
|
|
5207
|
+
view.setInt32(32, options.triangles.length, true);
|
|
5208
|
+
view.setInt32(36, glCount, true);
|
|
5209
|
+
view.setInt32(40, options.frames.length, true);
|
|
5210
|
+
view.setInt32(44, offsetSkins, true);
|
|
5211
|
+
view.setInt32(48, offsetTexCoords, true);
|
|
5212
|
+
view.setInt32(52, offsetTriangles, true);
|
|
5213
|
+
view.setInt32(56, offsetFrames, true);
|
|
5214
|
+
view.setInt32(60, offsetGlCommands, true);
|
|
5215
|
+
view.setInt32(64, offsetEnd, true);
|
|
5216
|
+
skins.forEach((skin, index) => writeCString(view, offsetSkins + index * 64, skin, 64));
|
|
5217
|
+
options.texCoords.forEach((coord, index) => {
|
|
5218
|
+
const base = offsetTexCoords + index * 4;
|
|
5219
|
+
view.setInt16(base, coord.s, true);
|
|
5220
|
+
view.setInt16(base + 2, coord.t, true);
|
|
5221
|
+
});
|
|
5222
|
+
options.triangles.forEach((tri, index) => {
|
|
5223
|
+
const base = offsetTriangles + index * 12;
|
|
5224
|
+
view.setUint16(base, tri.vertexIndices[0], true);
|
|
5225
|
+
view.setUint16(base + 2, tri.vertexIndices[1], true);
|
|
5226
|
+
view.setUint16(base + 4, tri.vertexIndices[2], true);
|
|
5227
|
+
view.setUint16(base + 6, tri.texCoordIndices[0], true);
|
|
5228
|
+
view.setUint16(base + 8, tri.texCoordIndices[1], true);
|
|
5229
|
+
view.setUint16(base + 10, tri.texCoordIndices[2], true);
|
|
5230
|
+
});
|
|
5231
|
+
new Uint8Array(view.buffer, offsetFrames, frameBlockSize).set(encodeFrames(options.frames, numVertices));
|
|
5232
|
+
new Uint8Array(view.buffer, offsetGlCommands, glSize).set(glData);
|
|
5233
|
+
return view.buffer;
|
|
5234
|
+
}
|
|
5235
|
+
|
|
5236
|
+
// src/engine/builders/md3Builder.ts
|
|
5237
|
+
var HEADER_SIZE2 = 108;
|
|
5238
|
+
var FRAME_SIZE = 56;
|
|
5239
|
+
var TAG_SIZE = 112;
|
|
5240
|
+
var SURFACE_HEADER_SIZE = 108;
|
|
5241
|
+
function writeString(target, offset, text, length) {
|
|
5242
|
+
const encoder = new TextEncoder();
|
|
5243
|
+
const encoded = encoder.encode(text);
|
|
5244
|
+
const bytes = new Uint8Array(target.buffer, target.byteOffset + offset, length);
|
|
5245
|
+
bytes.fill(0);
|
|
5246
|
+
bytes.set(encoded.subarray(0, length));
|
|
5247
|
+
}
|
|
5248
|
+
function buildSurface(buffer, offset, surface, numFrames) {
|
|
5249
|
+
const view = new DataView(buffer);
|
|
5250
|
+
writeString(view, offset + 4, surface.name, 64);
|
|
5251
|
+
view.setInt32(offset, 860898377, true);
|
|
5252
|
+
view.setInt32(offset + 72, numFrames, true);
|
|
5253
|
+
view.setInt32(offset + 76, surface.shaders?.length ?? 0, true);
|
|
5254
|
+
view.setInt32(offset + 80, surface.vertices[0]?.length ?? 0, true);
|
|
5255
|
+
view.setInt32(offset + 84, surface.triangles.length, true);
|
|
5256
|
+
let cursor = SURFACE_HEADER_SIZE;
|
|
5257
|
+
view.setInt32(offset + 88, cursor, true);
|
|
5258
|
+
for (const tri of surface.triangles) {
|
|
5259
|
+
view.setInt32(offset + cursor, tri[0], true);
|
|
5260
|
+
view.setInt32(offset + cursor + 4, tri[1], true);
|
|
5261
|
+
view.setInt32(offset + cursor + 8, tri[2], true);
|
|
5262
|
+
cursor += 12;
|
|
5263
|
+
}
|
|
5264
|
+
view.setInt32(offset + 92, cursor, true);
|
|
5265
|
+
for (const shader of surface.shaders ?? []) {
|
|
5266
|
+
writeString(view, offset + cursor, shader.name, 64);
|
|
5267
|
+
view.setInt32(offset + cursor + 64, shader.index, true);
|
|
5268
|
+
cursor += 68;
|
|
5269
|
+
}
|
|
5270
|
+
view.setInt32(offset + 96, cursor, true);
|
|
5271
|
+
for (const tex of surface.texCoords) {
|
|
5272
|
+
view.setFloat32(offset + cursor, tex.s, true);
|
|
5273
|
+
view.setFloat32(offset + cursor + 4, tex.t, true);
|
|
5274
|
+
cursor += 8;
|
|
5275
|
+
}
|
|
5276
|
+
view.setInt32(offset + 100, cursor, true);
|
|
5277
|
+
for (const frame of surface.vertices) {
|
|
5278
|
+
for (const vertex of frame) {
|
|
5279
|
+
view.setInt16(offset + cursor, Math.round(vertex.position.x * 64), true);
|
|
5280
|
+
view.setInt16(offset + cursor + 2, Math.round(vertex.position.y * 64), true);
|
|
5281
|
+
view.setInt16(offset + cursor + 4, Math.round(vertex.position.z * 64), true);
|
|
5282
|
+
view.setUint16(offset + cursor + 6, vertex.latLng, true);
|
|
5283
|
+
cursor += 8;
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
5286
|
+
view.setInt32(offset + 104, cursor, true);
|
|
5287
|
+
return cursor;
|
|
5288
|
+
}
|
|
5289
|
+
function buildMd3(options) {
|
|
5290
|
+
const numFrames = options.frames.length;
|
|
5291
|
+
const numTags = options.tags?.length ?? 0;
|
|
5292
|
+
const numSurfaces = options.surfaces.length;
|
|
5293
|
+
let size = HEADER_SIZE2;
|
|
5294
|
+
size += numFrames * FRAME_SIZE;
|
|
5295
|
+
size += numFrames * numTags * TAG_SIZE;
|
|
5296
|
+
for (const surface of options.surfaces) {
|
|
5297
|
+
const verts = surface.vertices[0]?.length ?? 0;
|
|
5298
|
+
const triangles = surface.triangles.length;
|
|
5299
|
+
const shaders = surface.shaders?.length ?? 0;
|
|
5300
|
+
const texCoordBytes = surface.texCoords.length * 8;
|
|
5301
|
+
const surfaceSize = SURFACE_HEADER_SIZE + triangles * 12 + shaders * 68 + texCoordBytes + verts * 8 * numFrames;
|
|
5302
|
+
size += surfaceSize;
|
|
5303
|
+
}
|
|
5304
|
+
const buffer = new ArrayBuffer(size);
|
|
5305
|
+
const view = new DataView(buffer);
|
|
5306
|
+
writeString(view, 8, options.name ?? "builder", 64);
|
|
5307
|
+
view.setInt32(0, 860898377, true);
|
|
5308
|
+
view.setInt32(4, 15, true);
|
|
5309
|
+
view.setInt32(72, 0, true);
|
|
5310
|
+
view.setInt32(76, numFrames, true);
|
|
5311
|
+
view.setInt32(80, numTags, true);
|
|
5312
|
+
view.setInt32(84, numSurfaces, true);
|
|
5313
|
+
view.setInt32(88, 0, true);
|
|
5314
|
+
let offset = HEADER_SIZE2;
|
|
5315
|
+
view.setInt32(92, offset, true);
|
|
5316
|
+
for (const frame of options.frames) {
|
|
5317
|
+
view.setFloat32(offset, frame.min.x, true);
|
|
5318
|
+
view.setFloat32(offset + 4, frame.min.y, true);
|
|
5319
|
+
view.setFloat32(offset + 8, frame.min.z, true);
|
|
5320
|
+
view.setFloat32(offset + 12, frame.max.x, true);
|
|
5321
|
+
view.setFloat32(offset + 16, frame.max.y, true);
|
|
5322
|
+
view.setFloat32(offset + 20, frame.max.z, true);
|
|
5323
|
+
view.setFloat32(offset + 24, frame.origin.x, true);
|
|
5324
|
+
view.setFloat32(offset + 28, frame.origin.y, true);
|
|
5325
|
+
view.setFloat32(offset + 32, frame.origin.z, true);
|
|
5326
|
+
view.setFloat32(offset + 36, frame.radius, true);
|
|
5327
|
+
writeString(view, offset + 40, frame.name, 16);
|
|
5328
|
+
offset += FRAME_SIZE;
|
|
5329
|
+
}
|
|
5330
|
+
view.setInt32(96, offset, true);
|
|
5331
|
+
for (let frame = 0; frame < numFrames; frame += 1) {
|
|
5332
|
+
for (const tag of options.tags ?? []) {
|
|
5333
|
+
writeString(view, offset, tag.name, 64);
|
|
5334
|
+
view.setFloat32(offset + 64, tag.origin.x, true);
|
|
5335
|
+
view.setFloat32(offset + 68, tag.origin.y, true);
|
|
5336
|
+
view.setFloat32(offset + 72, tag.origin.z, true);
|
|
5337
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
5338
|
+
const v = tag.axis[axis];
|
|
5339
|
+
view.setFloat32(offset + 76 + axis * 12, v.x, true);
|
|
5340
|
+
view.setFloat32(offset + 80 + axis * 12, v.y, true);
|
|
5341
|
+
view.setFloat32(offset + 84 + axis * 12, v.z, true);
|
|
5342
|
+
}
|
|
5343
|
+
offset += TAG_SIZE;
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
5346
|
+
view.setInt32(100, offset, true);
|
|
5347
|
+
for (let i = 0; i < options.surfaces.length; i += 1) {
|
|
5348
|
+
const written = buildSurface(buffer, offset, options.surfaces[i], numFrames);
|
|
5349
|
+
offset += written;
|
|
5350
|
+
}
|
|
5351
|
+
view.setInt32(104, offset, true);
|
|
5352
|
+
return buffer;
|
|
5353
|
+
}
|
|
5354
|
+
|
|
5355
|
+
// src/engine/builders/pakBuilder.ts
|
|
5356
|
+
var HEADER_SIZE3 = 12;
|
|
5357
|
+
var DIRECTORY_ENTRY_SIZE = 64;
|
|
5358
|
+
function buildPak(entries) {
|
|
5359
|
+
let offset = HEADER_SIZE3;
|
|
5360
|
+
const fileBuffers = [];
|
|
5361
|
+
const directory = new Uint8Array(entries.length * DIRECTORY_ENTRY_SIZE);
|
|
5362
|
+
const dirView = new DataView(directory.buffer);
|
|
5363
|
+
entries.forEach((entry, index) => {
|
|
5364
|
+
const data = entry.data;
|
|
5365
|
+
fileBuffers.push(data);
|
|
5366
|
+
const nameBytes = new TextEncoder().encode(entry.path);
|
|
5367
|
+
directory.set(nameBytes.slice(0, 56), index * DIRECTORY_ENTRY_SIZE);
|
|
5368
|
+
dirView.setInt32(index * DIRECTORY_ENTRY_SIZE + 56, offset, true);
|
|
5369
|
+
dirView.setInt32(index * DIRECTORY_ENTRY_SIZE + 60, data.byteLength, true);
|
|
5370
|
+
offset += data.byteLength;
|
|
5371
|
+
});
|
|
5372
|
+
const directoryOffset = offset;
|
|
5373
|
+
const directoryLength = directory.byteLength;
|
|
5374
|
+
const totalSize = HEADER_SIZE3 + fileBuffers.reduce((sum, buf) => sum + buf.byteLength, 0) + directoryLength;
|
|
5375
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
5376
|
+
const view = new DataView(buffer);
|
|
5377
|
+
const writer = new Uint8Array(buffer);
|
|
5378
|
+
writer.set([80, 65, 67, 75]);
|
|
5379
|
+
view.setInt32(4, directoryOffset, true);
|
|
5380
|
+
view.setInt32(8, directoryLength, true);
|
|
5381
|
+
let cursor = HEADER_SIZE3;
|
|
5382
|
+
for (const data of fileBuffers) {
|
|
5383
|
+
writer.set(data, cursor);
|
|
5384
|
+
cursor += data.byteLength;
|
|
5385
|
+
}
|
|
5386
|
+
writer.set(directory, directoryOffset);
|
|
5387
|
+
return buffer;
|
|
5388
|
+
}
|
|
5389
|
+
function textData(text) {
|
|
5390
|
+
return new TextEncoder().encode(text);
|
|
5391
|
+
}
|
|
5392
|
+
|
|
5393
|
+
// src/engine/builders/pcxBuilder.ts
|
|
5394
|
+
function buildPcx(options) {
|
|
5395
|
+
const { width, height } = options;
|
|
5396
|
+
const headerSize = 128;
|
|
5397
|
+
const paletteSize = 769;
|
|
5398
|
+
const encodedPixels = [];
|
|
5399
|
+
for (const value of options.pixels) {
|
|
5400
|
+
if (value >= 192) {
|
|
5401
|
+
encodedPixels.push(193, value);
|
|
5402
|
+
} else {
|
|
5403
|
+
encodedPixels.push(value);
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
const imageSize = encodedPixels.length;
|
|
5407
|
+
const buffer = new ArrayBuffer(headerSize + imageSize + paletteSize);
|
|
5408
|
+
const view = new DataView(buffer);
|
|
5409
|
+
view.setUint8(0, 10);
|
|
5410
|
+
view.setUint8(1, 5);
|
|
5411
|
+
view.setUint8(2, 1);
|
|
5412
|
+
view.setUint8(3, 8);
|
|
5413
|
+
view.setUint16(4, 0, true);
|
|
5414
|
+
view.setUint16(6, 0, true);
|
|
5415
|
+
view.setUint16(8, width - 1, true);
|
|
5416
|
+
view.setUint16(10, height - 1, true);
|
|
5417
|
+
view.setUint16(66, width, true);
|
|
5418
|
+
const encoded = new Uint8Array(buffer, headerSize, imageSize);
|
|
5419
|
+
encoded.set(encodedPixels);
|
|
5420
|
+
const paletteMarkerOffset = headerSize + imageSize;
|
|
5421
|
+
view.setUint8(paletteMarkerOffset, 12);
|
|
5422
|
+
const palette = new Uint8Array(buffer, paletteMarkerOffset + 1, 768);
|
|
5423
|
+
palette.fill(0);
|
|
5424
|
+
if (options.palette) {
|
|
5425
|
+
palette.set(options.palette.subarray(0, 768));
|
|
5426
|
+
} else {
|
|
5427
|
+
for (let i = 0; i < 256; i += 1) {
|
|
5428
|
+
palette[i * 3] = i;
|
|
5429
|
+
palette[i * 3 + 1] = 255 - i;
|
|
5430
|
+
palette[i * 3 + 2] = i;
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
return buffer;
|
|
5434
|
+
}
|
|
5435
|
+
|
|
5436
|
+
// src/engine/builders/walBuilder.ts
|
|
5437
|
+
function buildWal(options) {
|
|
5438
|
+
const { width, height } = options;
|
|
5439
|
+
const headerSize = 100;
|
|
5440
|
+
const mipSizes = [
|
|
5441
|
+
width * height,
|
|
5442
|
+
Math.max(1, (width >> 1) * (height >> 1)),
|
|
5443
|
+
Math.max(1, (width >> 2) * (height >> 2)),
|
|
5444
|
+
Math.max(1, (width >> 3) * (height >> 3))
|
|
5445
|
+
];
|
|
5446
|
+
const totalSize = headerSize + mipSizes.reduce((a, b) => a + b, 0);
|
|
5447
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
5448
|
+
const view = new DataView(buffer);
|
|
5449
|
+
const encoder = new TextEncoder();
|
|
5450
|
+
new Uint8Array(buffer, 0, 32).set(encoder.encode(options.name));
|
|
5451
|
+
view.setInt32(32, width, true);
|
|
5452
|
+
view.setInt32(36, height, true);
|
|
5453
|
+
let offset = headerSize;
|
|
5454
|
+
mipSizes.forEach((size, index) => {
|
|
5455
|
+
view.setInt32(40 + index * 4, offset, true);
|
|
5456
|
+
const data = new Uint8Array(buffer, offset, size);
|
|
5457
|
+
for (let i = 0; i < size; i += 1) {
|
|
5458
|
+
data[i] = (i + index) % 256;
|
|
5459
|
+
}
|
|
5460
|
+
offset += size;
|
|
5461
|
+
});
|
|
5462
|
+
new Uint8Array(buffer, 56, 32).set(encoder.encode(options.name + "_anim"));
|
|
5463
|
+
view.setInt32(88, 0, true);
|
|
5464
|
+
view.setInt32(92, 0, true);
|
|
5465
|
+
view.setInt32(96, 0, true);
|
|
5466
|
+
return buffer;
|
|
5467
|
+
}
|
|
5468
|
+
|
|
5469
|
+
// src/engine/builders/wavBuilder.ts
|
|
5470
|
+
function buildWav(options) {
|
|
5471
|
+
const bitsPerSample = options.bitsPerSample ?? 16;
|
|
5472
|
+
const bytesPerSample = bitsPerSample / 8;
|
|
5473
|
+
const frameCount = options.samples.length / options.channels;
|
|
5474
|
+
const dataSize = frameCount * options.channels * bytesPerSample;
|
|
5475
|
+
const buffer = new ArrayBuffer(44 + dataSize);
|
|
5476
|
+
const view = new DataView(buffer);
|
|
5477
|
+
const writeString2 = (offset2, text) => {
|
|
5478
|
+
new Uint8Array(buffer, offset2, text.length).set(new TextEncoder().encode(text));
|
|
5479
|
+
};
|
|
5480
|
+
writeString2(0, "RIFF");
|
|
5481
|
+
view.setUint32(4, 36 + dataSize, true);
|
|
5482
|
+
writeString2(8, "WAVE");
|
|
5483
|
+
writeString2(12, "fmt ");
|
|
5484
|
+
view.setUint32(16, 16, true);
|
|
5485
|
+
view.setUint16(20, 1, true);
|
|
5486
|
+
view.setUint16(22, options.channels, true);
|
|
5487
|
+
view.setUint32(24, options.sampleRate, true);
|
|
5488
|
+
view.setUint32(28, options.sampleRate * options.channels * bytesPerSample, true);
|
|
5489
|
+
view.setUint16(32, options.channels * bytesPerSample, true);
|
|
5490
|
+
view.setUint16(34, bitsPerSample, true);
|
|
5491
|
+
writeString2(36, "data");
|
|
5492
|
+
view.setUint32(40, dataSize, true);
|
|
5493
|
+
let offset = 44;
|
|
5494
|
+
for (let i = 0; i < options.samples.length; i += 1) {
|
|
5495
|
+
const sample = options.samples[i];
|
|
5496
|
+
if (bitsPerSample === 8) {
|
|
5497
|
+
view.setUint8(offset, Math.max(0, Math.min(255, Math.round(sample * 128 + 128))));
|
|
5498
|
+
offset += 1;
|
|
5499
|
+
} else if (bitsPerSample === 16) {
|
|
5500
|
+
view.setInt16(offset, Math.round(sample * 32767), true);
|
|
5501
|
+
offset += 2;
|
|
5502
|
+
} else {
|
|
5503
|
+
throw new Error("Unsupported bit depth for builder");
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
return buffer;
|
|
5507
|
+
}
|
|
5508
|
+
|
|
4913
5509
|
// src/client/helpers/view.ts
|
|
4914
5510
|
import { vec3 } from "gl-matrix";
|
|
4915
5511
|
import { Camera } from "@quake2ts/engine";
|
|
@@ -5587,6 +6183,13 @@ export {
|
|
|
5587
6183
|
ShaderProgram,
|
|
5588
6184
|
VertexBuffer,
|
|
5589
6185
|
ZERO_VEC3,
|
|
6186
|
+
buildMd2,
|
|
6187
|
+
buildMd3,
|
|
6188
|
+
buildPak,
|
|
6189
|
+
buildPcx,
|
|
6190
|
+
buildTestBsp,
|
|
6191
|
+
buildWal,
|
|
6192
|
+
buildWav,
|
|
5590
6193
|
captureAudioEvents,
|
|
5591
6194
|
captureCanvasDrawCalls,
|
|
5592
6195
|
captureFramebufferAsPNG,
|
|
@@ -5770,6 +6373,7 @@ export {
|
|
|
5770
6373
|
createVisualTestScenario,
|
|
5771
6374
|
createWebGLPlaywrightSetup,
|
|
5772
6375
|
createWebGLRenderTestSetup,
|
|
6376
|
+
encodedVisForClusters,
|
|
5773
6377
|
expectAnimationSnapshot,
|
|
5774
6378
|
expectNoDoubleTransform,
|
|
5775
6379
|
expectRendererCalls,
|
|
@@ -5800,6 +6404,7 @@ export {
|
|
|
5800
6404
|
renderAndExpectSnapshot,
|
|
5801
6405
|
restoreSaveGameSnapshot,
|
|
5802
6406
|
runComputeAndReadback,
|
|
6407
|
+
runLengthVisRow,
|
|
5803
6408
|
savePNG,
|
|
5804
6409
|
serializeUserInfo,
|
|
5805
6410
|
setupBrowserEnvironment,
|
|
@@ -5836,6 +6441,7 @@ export {
|
|
|
5836
6441
|
testPipelineRendering,
|
|
5837
6442
|
testWebGLAnimation,
|
|
5838
6443
|
testWebGLRenderer,
|
|
6444
|
+
textData,
|
|
5839
6445
|
throttleBandwidth,
|
|
5840
6446
|
verifySmoothing,
|
|
5841
6447
|
verifySnapshotConsistency,
|