quake2ts 0.0.44 → 0.0.46

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.
Files changed (39) hide show
  1. package/apps/viewer/dist/browser/index.global.js +1 -1
  2. package/apps/viewer/dist/browser/index.global.js.map +1 -1
  3. package/apps/viewer/dist/cjs/index.cjs.map +1 -1
  4. package/apps/viewer/dist/esm/index.js.map +1 -1
  5. package/apps/viewer/dist/tsconfig.tsbuildinfo +1 -1
  6. package/package.json +1 -1
  7. package/packages/client/dist/browser/index.global.js.map +1 -1
  8. package/packages/client/dist/cjs/index.cjs.map +1 -1
  9. package/packages/client/dist/esm/index.js.map +1 -1
  10. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  11. package/packages/engine/dist/browser/index.global.js +69 -10
  12. package/packages/engine/dist/browser/index.global.js.map +1 -1
  13. package/packages/engine/dist/cjs/index.cjs +781 -5
  14. package/packages/engine/dist/cjs/index.cjs.map +1 -1
  15. package/packages/engine/dist/esm/index.js +763 -5
  16. package/packages/engine/dist/esm/index.js.map +1 -1
  17. package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
  18. package/packages/engine/dist/types/index.d.ts +2 -0
  19. package/packages/engine/dist/types/index.d.ts.map +1 -1
  20. package/packages/engine/dist/types/render/md3Pipeline.d.ts +79 -0
  21. package/packages/engine/dist/types/render/md3Pipeline.d.ts.map +1 -0
  22. package/packages/engine/dist/types/render/particleSystem.d.ts +105 -0
  23. package/packages/engine/dist/types/render/particleSystem.d.ts.map +1 -0
  24. package/packages/game/dist/browser/index.global.js.map +1 -1
  25. package/packages/game/dist/cjs/index.cjs.map +1 -1
  26. package/packages/game/dist/esm/index.js.map +1 -1
  27. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
  28. package/packages/shared/dist/browser/index.global.js +1 -1
  29. package/packages/shared/dist/browser/index.global.js.map +1 -1
  30. package/packages/shared/dist/cjs/index.cjs +65 -0
  31. package/packages/shared/dist/cjs/index.cjs.map +1 -1
  32. package/packages/shared/dist/esm/index.js +61 -0
  33. package/packages/shared/dist/esm/index.js.map +1 -1
  34. package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
  35. package/packages/shared/dist/types/index.d.ts +1 -0
  36. package/packages/shared/dist/types/index.d.ts.map +1 -1
  37. package/packages/shared/dist/types/math/mat4.d.ts +7 -0
  38. package/packages/shared/dist/types/math/mat4.d.ts.map +1 -0
  39. package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
@@ -49,19 +49,28 @@ __export(index_exports, {
49
49
  MAX_SOUND_CHANNELS: () => MAX_SOUND_CHANNELS,
50
50
  MD2_FRAGMENT_SHADER: () => MD2_FRAGMENT_SHADER,
51
51
  MD2_VERTEX_SHADER: () => MD2_VERTEX_SHADER,
52
+ MD3_FRAGMENT_SHADER: () => MD3_FRAGMENT_SHADER,
53
+ MD3_VERTEX_SHADER: () => MD3_VERTEX_SHADER,
52
54
  Md2Loader: () => Md2Loader,
53
55
  Md2MeshBuffers: () => Md2MeshBuffers,
54
56
  Md2ParseError: () => Md2ParseError,
55
57
  Md2Pipeline: () => Md2Pipeline,
56
58
  Md3Loader: () => Md3Loader,
59
+ Md3ModelMesh: () => Md3ModelMesh,
57
60
  Md3ParseError: () => Md3ParseError,
61
+ Md3Pipeline: () => Md3Pipeline,
62
+ Md3SurfaceMesh: () => Md3SurfaceMesh,
58
63
  MusicSystem: () => MusicSystem,
64
+ PARTICLE_FRAGMENT_SHADER: () => PARTICLE_FRAGMENT_SHADER,
65
+ PARTICLE_VERTEX_SHADER: () => PARTICLE_VERTEX_SHADER,
59
66
  PakArchive: () => PakArchive,
60
67
  PakIndexStore: () => PakIndexStore,
61
68
  PakIngestionError: () => PakIngestionError,
62
69
  PakParseError: () => PakParseError,
63
70
  PakValidationError: () => PakValidationError,
64
71
  PakValidator: () => PakValidator,
72
+ ParticleRenderer: () => ParticleRenderer,
73
+ ParticleSystem: () => ParticleSystem,
65
74
  RERELEASE_KNOWN_PAKS: () => RERELEASE_KNOWN_PAKS,
66
75
  SKYBOX_FRAGMENT_SHADER: () => SKYBOX_FRAGMENT_SHADER,
67
76
  SKYBOX_VERTEX_SHADER: () => SKYBOX_VERTEX_SHADER,
@@ -85,6 +94,8 @@ __export(index_exports, {
85
94
  buildBspGeometry: () => buildBspGeometry,
86
95
  buildMd2Geometry: () => buildMd2Geometry,
87
96
  buildMd2VertexData: () => buildMd2VertexData,
97
+ buildMd3SurfaceGeometry: () => buildMd3SurfaceGeometry,
98
+ buildMd3VertexData: () => buildMd3VertexData,
88
99
  calculateMaxAudibleDistance: () => calculateMaxAudibleDistance,
89
100
  calculatePakChecksum: () => calculatePakChecksum,
90
101
  computeFrameBlend: () => computeFrameBlend,
@@ -105,6 +116,7 @@ __export(index_exports, {
105
116
  groupMd2Animations: () => groupMd2Animations,
106
117
  ingestPakFiles: () => ingestPakFiles,
107
118
  ingestPaks: () => ingestPaks,
119
+ interpolateMd3Tag: () => interpolateMd3Tag,
108
120
  interpolateVec3: () => interpolateVec3,
109
121
  parseMd2: () => parseMd2,
110
122
  parseMd3: () => parseMd3,
@@ -118,6 +130,12 @@ __export(index_exports, {
118
130
  removeViewTranslation: () => removeViewTranslation,
119
131
  resolveLightStyles: () => resolveLightStyles,
120
132
  spatializeOrigin: () => spatializeOrigin,
133
+ spawnBlood: () => spawnBlood,
134
+ spawnBulletImpact: () => spawnBulletImpact,
135
+ spawnExplosion: () => spawnExplosion,
136
+ spawnMuzzleFlash: () => spawnMuzzleFlash,
137
+ spawnTeleportFlash: () => spawnTeleportFlash,
138
+ spawnTrail: () => spawnTrail,
121
139
  walToRgba: () => walToRgba,
122
140
  wireDropTarget: () => wireDropTarget,
123
141
  wireFileInput: () => wireFileInput
@@ -279,6 +297,52 @@ function normalizeVec3(a) {
279
297
  }
280
298
  var DEG2RAD_FACTOR = Math.PI / 180;
281
299
  var RAD2DEG_FACTOR = 180 / Math.PI;
300
+ function createMat4Identity() {
301
+ return new Float32Array([
302
+ 1,
303
+ 0,
304
+ 0,
305
+ 0,
306
+ 0,
307
+ 1,
308
+ 0,
309
+ 0,
310
+ 0,
311
+ 0,
312
+ 1,
313
+ 0,
314
+ 0,
315
+ 0,
316
+ 0,
317
+ 1
318
+ ]);
319
+ }
320
+ function transformPointMat4(mat, point) {
321
+ const x = point.x;
322
+ const y = point.y;
323
+ const z = point.z;
324
+ return {
325
+ x: mat[0] * x + mat[4] * y + mat[8] * z + mat[12],
326
+ y: mat[1] * x + mat[5] * y + mat[9] * z + mat[13],
327
+ z: mat[2] * x + mat[6] * y + mat[10] * z + mat[14]
328
+ };
329
+ }
330
+ function mat4FromBasis(origin, axis) {
331
+ const out = createMat4Identity();
332
+ out[0] = axis[0].x;
333
+ out[1] = axis[0].y;
334
+ out[2] = axis[0].z;
335
+ out[4] = axis[1].x;
336
+ out[5] = axis[1].y;
337
+ out[6] = axis[1].z;
338
+ out[8] = axis[2].x;
339
+ out[9] = axis[2].y;
340
+ out[10] = axis[2].z;
341
+ out[12] = origin.x;
342
+ out[13] = origin.y;
343
+ out[14] = origin.z;
344
+ return out;
345
+ }
282
346
  var CONTENTS_SOLID = 1 << 0;
283
347
  var CONTENTS_WINDOW = 1 << 1;
284
348
  var CONTENTS_AUX = 1 << 2;
@@ -1740,8 +1804,8 @@ function computeFrameBlend(state) {
1740
1804
  const baseFrame = Math.floor(normalizedPosition);
1741
1805
  const frame = state.sequence.start + baseFrame;
1742
1806
  const nextFrame = baseFrame + 1 >= totalFrames ? loop ? state.sequence.start : state.sequence.end : frame + 1;
1743
- const lerp2 = !loop && baseFrame >= totalFrames - 1 ? 0 : normalizedPosition - baseFrame;
1744
- return { frame, nextFrame, lerp: lerp2 };
1807
+ const lerp3 = !loop && baseFrame >= totalFrames - 1 ? 0 : normalizedPosition - baseFrame;
1808
+ return { frame, nextFrame, lerp: lerp3 };
1745
1809
  }
1746
1810
  function createAnimationState(sequence) {
1747
1811
  return { sequence, time: 0 };
@@ -4015,7 +4079,7 @@ function buildMd2Geometry(model) {
4015
4079
  return { vertices, indices: new Uint16Array(indices) };
4016
4080
  }
4017
4081
  function buildMd2VertexData(model, geometry, blend) {
4018
- const { currentFrame, nextFrame, lerp: lerp2 } = blend;
4082
+ const { currentFrame, nextFrame, lerp: lerp3 } = blend;
4019
4083
  const frameA = model.frames[currentFrame];
4020
4084
  const frameB = model.frames[nextFrame];
4021
4085
  if (!frameA || !frameB) {
@@ -4028,8 +4092,8 @@ function buildMd2VertexData(model, geometry, blend) {
4028
4092
  if (!vA || !vB) {
4029
4093
  throw new Error("MD2 vertex index out of range for frame");
4030
4094
  }
4031
- const position = lerpVec3(vA.position, vB.position, lerp2);
4032
- const normal = normalizeVec32(lerpVec3(vA.normal, vB.normal, lerp2));
4095
+ const position = lerpVec3(vA.position, vB.position, lerp3);
4096
+ const normal = normalizeVec32(lerpVec3(vA.normal, vB.normal, lerp3));
4033
4097
  const base = index * 8;
4034
4098
  data[base] = position.x;
4035
4099
  data[base + 1] = position.y;
@@ -4109,6 +4173,700 @@ var Md2Pipeline = class {
4109
4173
  }
4110
4174
  };
4111
4175
 
4176
+ // src/render/md3Pipeline.ts
4177
+ var DEFAULT_AMBIENT = [0.2, 0.2, 0.2];
4178
+ var DEFAULT_DIRECTION = { x: 0, y: 0, z: 1 };
4179
+ var DEFAULT_DIRECTION_COLOR = [0.8, 0.8, 0.8];
4180
+ function lerp2(a, b, t) {
4181
+ return a + (b - a) * t;
4182
+ }
4183
+ function lerpVec32(a, b, t) {
4184
+ return { x: lerp2(a.x, b.x, t), y: lerp2(a.y, b.y, t), z: lerp2(a.z, b.z, t) };
4185
+ }
4186
+ function clamp012(v) {
4187
+ if (v < 0) return 0;
4188
+ if (v > 1) return 1;
4189
+ return v;
4190
+ }
4191
+ function buildMd3SurfaceGeometry(surface) {
4192
+ const vertices = [];
4193
+ const indices = [];
4194
+ for (const tri of surface.triangles) {
4195
+ const base = vertices.length;
4196
+ const [a, b, c] = tri.indices;
4197
+ const texA = surface.texCoords[a];
4198
+ const texB = surface.texCoords[b];
4199
+ const texC = surface.texCoords[c];
4200
+ if (!texA || !texB || !texC) {
4201
+ throw new Error(`Missing texCoord for triangle in surface ${surface.name}`);
4202
+ }
4203
+ vertices.push(
4204
+ { vertexIndex: a, texCoord: [texA.s, 1 - texA.t] },
4205
+ { vertexIndex: b, texCoord: [texB.s, 1 - texB.t] },
4206
+ { vertexIndex: c, texCoord: [texC.s, 1 - texC.t] }
4207
+ );
4208
+ indices.push(base, base + 1, base + 2);
4209
+ }
4210
+ return { vertices, indices: new Uint16Array(indices) };
4211
+ }
4212
+ function evaluateLighting(normal, position, lighting) {
4213
+ const ambient = lighting?.ambient ?? DEFAULT_AMBIENT;
4214
+ const directional = lighting?.directional ?? { direction: DEFAULT_DIRECTION, color: DEFAULT_DIRECTION_COLOR };
4215
+ const n = normalizeVec3(normal);
4216
+ const l = normalizeVec3(directional.direction);
4217
+ const ndotl = clamp012(n.x * l.x + n.y * l.y + n.z * l.z);
4218
+ let r = ambient[0] + directional.color[0] * ndotl;
4219
+ let g = ambient[1] + directional.color[1] * ndotl;
4220
+ let b = ambient[2] + directional.color[2] * ndotl;
4221
+ if (lighting?.dynamicLights) {
4222
+ const worldPos = lighting.modelMatrix ? transformPointMat4(lighting.modelMatrix, position) : position;
4223
+ for (const light of lighting.dynamicLights) {
4224
+ const dx = worldPos.x - light.origin.x;
4225
+ const dy = worldPos.y - light.origin.y;
4226
+ const dz = worldPos.z - light.origin.z;
4227
+ const distSq = dx * dx + dy * dy + dz * dz;
4228
+ const radiusSq = light.radius * light.radius;
4229
+ if (distSq < radiusSq && radiusSq > 0) {
4230
+ const attenuation = 1 - Math.sqrt(distSq) / light.radius;
4231
+ const amount = clamp012(attenuation * ndotl);
4232
+ r += light.color[0] * amount;
4233
+ g += light.color[1] * amount;
4234
+ b += light.color[2] * amount;
4235
+ }
4236
+ }
4237
+ }
4238
+ return [clamp012(r), clamp012(g), clamp012(b)];
4239
+ }
4240
+ function buildMd3VertexData(surface, geometry, blend, lighting) {
4241
+ const frameA = surface.vertices[blend.currentFrame];
4242
+ const frameB = surface.vertices[blend.nextFrame];
4243
+ if (!frameA || !frameB) {
4244
+ throw new Error("Requested MD3 frames are out of range");
4245
+ }
4246
+ const data = new Float32Array(geometry.vertices.length * 12);
4247
+ geometry.vertices.forEach((vertex, index) => {
4248
+ const vA = frameA[vertex.vertexIndex];
4249
+ const vB = frameB[vertex.vertexIndex];
4250
+ if (!vA || !vB) {
4251
+ throw new Error(`Vertex index ${vertex.vertexIndex} missing for frame`);
4252
+ }
4253
+ const position = lerpVec32(vA.position, vB.position, blend.lerp);
4254
+ const normal = normalizeVec3(lerpVec32(vA.normal, vB.normal, blend.lerp));
4255
+ const color = evaluateLighting(normal, position, lighting);
4256
+ const base = index * 12;
4257
+ data[base] = position.x;
4258
+ data[base + 1] = position.y;
4259
+ data[base + 2] = position.z;
4260
+ data[base + 3] = normal.x;
4261
+ data[base + 4] = normal.y;
4262
+ data[base + 5] = normal.z;
4263
+ data[base + 6] = vertex.texCoord[0];
4264
+ data[base + 7] = vertex.texCoord[1];
4265
+ data[base + 8] = color[0];
4266
+ data[base + 9] = color[1];
4267
+ data[base + 10] = color[2];
4268
+ data[base + 11] = 1;
4269
+ });
4270
+ return data;
4271
+ }
4272
+ function interpolateMd3Tag(model, blend, tagName) {
4273
+ const firstFrameTags = model.tags[0];
4274
+ if (!firstFrameTags) {
4275
+ return null;
4276
+ }
4277
+ const tagIndex = firstFrameTags.findIndex((tag) => tag.name === tagName);
4278
+ if (tagIndex === -1) {
4279
+ return null;
4280
+ }
4281
+ const tagA = model.tags[blend.currentFrame]?.[tagIndex];
4282
+ const tagB = model.tags[blend.nextFrame]?.[tagIndex];
4283
+ if (!tagA || !tagB) {
4284
+ throw new Error(`Tag ${tagName} is missing for one of the interpolated frames`);
4285
+ }
4286
+ const origin = lerpVec32(tagA.origin, tagB.origin, blend.lerp);
4287
+ const axis0 = normalizeVec3(lerpVec32(tagA.axis[0], tagB.axis[0], blend.lerp));
4288
+ const axis1 = normalizeVec3(lerpVec32(tagA.axis[1], tagB.axis[1], blend.lerp));
4289
+ const axis2 = normalizeVec3(lerpVec32(tagA.axis[2], tagB.axis[2], blend.lerp));
4290
+ const corrected0 = axis0;
4291
+ const corrected1 = normalizeVec3({
4292
+ x: axis1.x - corrected0.x * (corrected0.x * axis1.x + corrected0.y * axis1.y + corrected0.z * axis1.z),
4293
+ y: axis1.y - corrected0.y * (corrected0.x * axis1.x + corrected0.y * axis1.y + corrected0.z * axis1.z),
4294
+ z: axis1.z - corrected0.z * (corrected0.x * axis1.x + corrected0.y * axis1.y + corrected0.z * axis1.z)
4295
+ });
4296
+ const corrected2 = normalizeVec3({
4297
+ x: corrected0.y * corrected1.z - corrected0.z * corrected1.y,
4298
+ y: corrected0.z * corrected1.x - corrected0.x * corrected1.z,
4299
+ z: corrected0.x * corrected1.y - corrected0.y * corrected1.x
4300
+ });
4301
+ const axis = [corrected0, corrected1, corrected2];
4302
+ return { origin, axis, matrix: mat4FromBasis(origin, axis) };
4303
+ }
4304
+ var MD3_VERTEX_SHADER = `#version 300 es
4305
+ precision highp float;
4306
+
4307
+ layout(location = 0) in vec3 a_position;
4308
+ layout(location = 1) in vec3 a_normal;
4309
+ layout(location = 2) in vec2 a_texCoord;
4310
+ layout(location = 3) in vec4 a_color;
4311
+
4312
+ uniform mat4 u_modelViewProjection;
4313
+
4314
+ out vec2 v_texCoord;
4315
+ out vec4 v_color;
4316
+
4317
+ void main() {
4318
+ v_texCoord = a_texCoord;
4319
+ v_color = a_color;
4320
+ gl_Position = u_modelViewProjection * vec4(a_position, 1.0);
4321
+ }`;
4322
+ var MD3_FRAGMENT_SHADER = `#version 300 es
4323
+ precision highp float;
4324
+
4325
+ in vec2 v_texCoord;
4326
+ in vec4 v_color;
4327
+
4328
+ uniform sampler2D u_diffuseMap;
4329
+ uniform vec4 u_tint;
4330
+
4331
+ out vec4 o_color;
4332
+
4333
+ void main() {
4334
+ vec4 albedo = texture(u_diffuseMap, v_texCoord) * u_tint;
4335
+ o_color = vec4(albedo.rgb * v_color.rgb, albedo.a * v_color.a);
4336
+ }`;
4337
+ var Md3SurfaceMesh = class {
4338
+ constructor(gl, surface, blend, lighting) {
4339
+ this.gl = gl;
4340
+ this.geometry = buildMd3SurfaceGeometry(surface);
4341
+ this.vertexBuffer = new VertexBuffer(gl, gl.STATIC_DRAW);
4342
+ this.indexBuffer = new IndexBuffer(gl, gl.STATIC_DRAW);
4343
+ this.vertexArray = new VertexArray(gl);
4344
+ this.indexCount = this.geometry.indices.length;
4345
+ this.vertexArray.configureAttributes(
4346
+ [
4347
+ { index: 0, size: 3, type: gl.FLOAT, stride: 48, offset: 0 },
4348
+ { index: 1, size: 3, type: gl.FLOAT, stride: 48, offset: 12 },
4349
+ { index: 2, size: 2, type: gl.FLOAT, stride: 48, offset: 24 },
4350
+ { index: 3, size: 4, type: gl.FLOAT, stride: 48, offset: 32 }
4351
+ ],
4352
+ this.vertexBuffer
4353
+ );
4354
+ this.vertexArray.bind();
4355
+ this.indexBuffer.bind();
4356
+ this.indexBuffer.upload(this.geometry.indices, gl.STATIC_DRAW);
4357
+ this.update(surface, blend, lighting);
4358
+ }
4359
+ update(surface, blend, lighting) {
4360
+ const data = buildMd3VertexData(surface, this.geometry, blend, lighting);
4361
+ this.vertexBuffer.upload(data, this.gl.STATIC_DRAW);
4362
+ }
4363
+ bind() {
4364
+ this.vertexArray.bind();
4365
+ this.indexBuffer.bind();
4366
+ }
4367
+ dispose() {
4368
+ this.vertexBuffer.dispose();
4369
+ this.indexBuffer.dispose();
4370
+ this.vertexArray.dispose();
4371
+ }
4372
+ };
4373
+ var Md3ModelMesh = class {
4374
+ constructor(gl, model, blend, lighting) {
4375
+ this.surfaces = /* @__PURE__ */ new Map();
4376
+ this.gl = gl;
4377
+ this.model = model;
4378
+ this.blend = blend;
4379
+ this.lighting = lighting;
4380
+ model.surfaces.forEach((surface) => {
4381
+ this.surfaces.set(surface.name, new Md3SurfaceMesh(gl, surface, blend, lighting));
4382
+ });
4383
+ }
4384
+ update(blend, lighting) {
4385
+ this.blend = blend;
4386
+ this.lighting = lighting ?? this.lighting;
4387
+ for (const surface of this.model.surfaces) {
4388
+ const mesh = this.surfaces.get(surface.name);
4389
+ mesh?.update(surface, blend, this.lighting);
4390
+ }
4391
+ }
4392
+ dispose() {
4393
+ for (const mesh of this.surfaces.values()) {
4394
+ mesh.dispose();
4395
+ }
4396
+ this.surfaces.clear();
4397
+ }
4398
+ };
4399
+ var Md3Pipeline = class {
4400
+ constructor(gl) {
4401
+ this.gl = gl;
4402
+ this.program = ShaderProgram.create(
4403
+ gl,
4404
+ { vertex: MD3_VERTEX_SHADER, fragment: MD3_FRAGMENT_SHADER },
4405
+ { a_position: 0, a_normal: 1, a_texCoord: 2, a_color: 3 }
4406
+ );
4407
+ this.uniformMvp = this.program.getUniformLocation("u_modelViewProjection");
4408
+ this.uniformTint = this.program.getUniformLocation("u_tint");
4409
+ this.uniformDiffuse = this.program.getUniformLocation("u_diffuseMap");
4410
+ }
4411
+ bind(modelViewProjection, tint = [1, 1, 1, 1], sampler = 0) {
4412
+ this.program.use();
4413
+ this.gl.uniformMatrix4fv(this.uniformMvp, false, modelViewProjection);
4414
+ this.gl.uniform4fv(this.uniformTint, new Float32Array(tint));
4415
+ this.gl.uniform1i(this.uniformDiffuse, sampler);
4416
+ }
4417
+ drawSurface(mesh, material) {
4418
+ const sampler = material?.diffuseSampler ?? 0;
4419
+ const tint = material?.tint ?? [1, 1, 1, 1];
4420
+ this.gl.uniform4fv(this.uniformTint, new Float32Array(tint));
4421
+ this.gl.uniform1i(this.uniformDiffuse, sampler);
4422
+ mesh.bind();
4423
+ this.gl.drawElements(this.gl.TRIANGLES, mesh.indexCount, this.gl.UNSIGNED_SHORT, 0);
4424
+ }
4425
+ dispose() {
4426
+ this.program.dispose();
4427
+ }
4428
+ };
4429
+
4430
+ // src/render/particleSystem.ts
4431
+ var DEFAULT_COLOR = [1, 1, 1, 1];
4432
+ var ParticleSystem = class {
4433
+ // 0 alpha, 1 additive
4434
+ constructor(maxParticles, random = Math.random) {
4435
+ this.maxParticles = maxParticles;
4436
+ this.random = random;
4437
+ this.alive = new Uint8Array(maxParticles);
4438
+ this.positionX = new Float32Array(maxParticles);
4439
+ this.positionY = new Float32Array(maxParticles);
4440
+ this.positionZ = new Float32Array(maxParticles);
4441
+ this.velocityX = new Float32Array(maxParticles);
4442
+ this.velocityY = new Float32Array(maxParticles);
4443
+ this.velocityZ = new Float32Array(maxParticles);
4444
+ this.colorR = new Float32Array(maxParticles);
4445
+ this.colorG = new Float32Array(maxParticles);
4446
+ this.colorB = new Float32Array(maxParticles);
4447
+ this.colorA = new Float32Array(maxParticles);
4448
+ this.size = new Float32Array(maxParticles);
4449
+ this.lifetime = new Float32Array(maxParticles);
4450
+ this.remaining = new Float32Array(maxParticles);
4451
+ this.gravity = new Float32Array(maxParticles);
4452
+ this.damping = new Float32Array(maxParticles);
4453
+ this.bounce = new Float32Array(maxParticles);
4454
+ this.fade = new Uint8Array(maxParticles);
4455
+ this.blendMode = new Uint8Array(maxParticles);
4456
+ }
4457
+ spawn(options) {
4458
+ const index = this.findFreeSlot();
4459
+ if (index === -1) {
4460
+ return null;
4461
+ }
4462
+ const color = options.color ?? DEFAULT_COLOR;
4463
+ const velocity = options.velocity ?? { x: 0, y: 0, z: 0 };
4464
+ this.alive[index] = 1;
4465
+ this.positionX[index] = options.position.x;
4466
+ this.positionY[index] = options.position.y;
4467
+ this.positionZ[index] = options.position.z;
4468
+ this.velocityX[index] = velocity.x;
4469
+ this.velocityY[index] = velocity.y;
4470
+ this.velocityZ[index] = velocity.z;
4471
+ this.colorR[index] = color[0];
4472
+ this.colorG[index] = color[1];
4473
+ this.colorB[index] = color[2];
4474
+ this.colorA[index] = color[3];
4475
+ this.size[index] = options.size ?? 2.5;
4476
+ this.lifetime[index] = options.lifetime;
4477
+ this.remaining[index] = options.lifetime;
4478
+ this.gravity[index] = options.gravity ?? 800;
4479
+ this.damping[index] = options.damping ?? 0;
4480
+ this.bounce[index] = options.bounce ?? 0.25;
4481
+ this.fade[index] = options.fade ? 1 : 0;
4482
+ this.blendMode[index] = options.blendMode === "additive" ? 1 : 0;
4483
+ return index;
4484
+ }
4485
+ update(dt, options = {}) {
4486
+ const floorZ = options.floorZ ?? -Infinity;
4487
+ for (let i = 0; i < this.maxParticles; i += 1) {
4488
+ if (!this.alive[i]) {
4489
+ continue;
4490
+ }
4491
+ this.remaining[i] -= dt;
4492
+ if (this.remaining[i] <= 0) {
4493
+ this.alive[i] = 0;
4494
+ continue;
4495
+ }
4496
+ const damping = Math.max(0, 1 - this.damping[i] * dt);
4497
+ this.velocityX[i] *= damping;
4498
+ this.velocityY[i] *= damping;
4499
+ this.velocityZ[i] = this.velocityZ[i] * damping - this.gravity[i] * dt;
4500
+ this.positionX[i] += this.velocityX[i] * dt;
4501
+ this.positionY[i] += this.velocityY[i] * dt;
4502
+ this.positionZ[i] += this.velocityZ[i] * dt;
4503
+ if (this.positionZ[i] < floorZ) {
4504
+ this.positionZ[i] = floorZ;
4505
+ this.velocityZ[i] = -this.velocityZ[i] * this.bounce[i];
4506
+ this.velocityX[i] *= 0.7;
4507
+ this.velocityY[i] *= 0.7;
4508
+ }
4509
+ }
4510
+ }
4511
+ killAll() {
4512
+ this.alive.fill(0);
4513
+ }
4514
+ aliveCount() {
4515
+ let count = 0;
4516
+ for (let i = 0; i < this.maxParticles; i += 1) {
4517
+ if (this.alive[i]) {
4518
+ count += 1;
4519
+ }
4520
+ }
4521
+ return count;
4522
+ }
4523
+ getState(index) {
4524
+ return {
4525
+ alive: this.alive[index] === 1,
4526
+ position: {
4527
+ x: this.positionX[index],
4528
+ y: this.positionY[index],
4529
+ z: this.positionZ[index]
4530
+ },
4531
+ velocity: {
4532
+ x: this.velocityX[index],
4533
+ y: this.velocityY[index],
4534
+ z: this.velocityZ[index]
4535
+ },
4536
+ remaining: this.remaining[index],
4537
+ color: [this.colorR[index], this.colorG[index], this.colorB[index], this.colorA[index]],
4538
+ size: this.size[index],
4539
+ blendMode: this.blendMode[index] === 1 ? "additive" : "alpha"
4540
+ };
4541
+ }
4542
+ buildMesh(viewRight, viewUp) {
4543
+ const vertices = [];
4544
+ const indices = [];
4545
+ const batches = [];
4546
+ const buildBatch = (mode) => {
4547
+ const startIndex = indices.length;
4548
+ let particleCount = 0;
4549
+ for (let i = 0; i < this.maxParticles; i += 1) {
4550
+ if (!this.alive[i]) {
4551
+ continue;
4552
+ }
4553
+ if ((mode === "additive" ? 1 : 0) !== this.blendMode[i]) {
4554
+ continue;
4555
+ }
4556
+ particleCount += 1;
4557
+ const baseVertex = vertices.length / 9;
4558
+ const size = this.size[i] * 0.5;
4559
+ const fade = this.fade[i] ? Math.max(this.remaining[i] / this.lifetime[i], 0) : 1;
4560
+ const colorScale = this.blendMode[i] === 1 ? 1.2 : 1;
4561
+ const cR = this.colorR[i] * colorScale;
4562
+ const cG = this.colorG[i] * colorScale;
4563
+ const cB = this.colorB[i] * colorScale;
4564
+ const cA = this.colorA[i] * fade;
4565
+ const px = this.positionX[i];
4566
+ const py = this.positionY[i];
4567
+ const pz = this.positionZ[i];
4568
+ const rightX = viewRight.x * size;
4569
+ const rightY = viewRight.y * size;
4570
+ const rightZ = viewRight.z * size;
4571
+ const upX = viewUp.x * size;
4572
+ const upY = viewUp.y * size;
4573
+ const upZ = viewUp.z * size;
4574
+ const corners = [
4575
+ { x: px - rightX - upX, y: py - rightY - upY, z: pz - rightZ - upZ },
4576
+ { x: px + rightX - upX, y: py + rightY - upY, z: pz + rightZ - upZ },
4577
+ { x: px - rightX + upX, y: py - rightY + upY, z: pz - rightZ + upZ },
4578
+ { x: px + rightX + upX, y: py + rightY + upY, z: pz + rightZ + upZ }
4579
+ ];
4580
+ const uvs = [
4581
+ [0, 1],
4582
+ [1, 1],
4583
+ [0, 0],
4584
+ [1, 0]
4585
+ ];
4586
+ corners.forEach((corner, cornerIndex) => {
4587
+ vertices.push(
4588
+ corner.x,
4589
+ corner.y,
4590
+ corner.z,
4591
+ uvs[cornerIndex]?.[0] ?? 0,
4592
+ uvs[cornerIndex]?.[1] ?? 0,
4593
+ cR,
4594
+ cG,
4595
+ cB,
4596
+ cA
4597
+ );
4598
+ });
4599
+ indices.push(baseVertex, baseVertex + 1, baseVertex + 2, baseVertex + 2, baseVertex + 1, baseVertex + 3);
4600
+ }
4601
+ if (particleCount > 0) {
4602
+ batches.push({ blendMode: mode, start: startIndex, count: indices.length - startIndex });
4603
+ }
4604
+ };
4605
+ buildBatch("alpha");
4606
+ buildBatch("additive");
4607
+ return { vertices: new Float32Array(vertices), indices: new Uint16Array(indices), batches };
4608
+ }
4609
+ findFreeSlot() {
4610
+ for (let i = 0; i < this.maxParticles; i += 1) {
4611
+ if (!this.alive[i]) {
4612
+ return i;
4613
+ }
4614
+ }
4615
+ return -1;
4616
+ }
4617
+ };
4618
+ var PARTICLE_VERTEX_SHADER = `#version 300 es
4619
+ precision highp float;
4620
+
4621
+ layout(location = 0) in vec3 a_position;
4622
+ layout(location = 1) in vec2 a_uv;
4623
+ layout(location = 2) in vec4 a_color;
4624
+
4625
+ uniform mat4 u_viewProjection;
4626
+
4627
+ out vec2 v_uv;
4628
+ out vec4 v_color;
4629
+
4630
+ void main() {
4631
+ v_uv = a_uv;
4632
+ v_color = a_color;
4633
+ gl_Position = u_viewProjection * vec4(a_position, 1.0);
4634
+ }`;
4635
+ var PARTICLE_FRAGMENT_SHADER = `#version 300 es
4636
+ precision highp float;
4637
+
4638
+ in vec2 v_uv;
4639
+ in vec4 v_color;
4640
+
4641
+ out vec4 o_color;
4642
+
4643
+ void main() {
4644
+ float dist = distance(v_uv, vec2(0.5));
4645
+ float alpha = v_color.a * (1.0 - smoothstep(0.35, 0.5, dist));
4646
+ o_color = vec4(v_color.rgb, alpha);
4647
+ }`;
4648
+ var ParticleRenderer = class {
4649
+ constructor(gl, system) {
4650
+ this.vertexCapacity = 0;
4651
+ this.indexCapacity = 0;
4652
+ this.gl = gl;
4653
+ this.system = system;
4654
+ this.program = ShaderProgram.create(gl, { vertex: PARTICLE_VERTEX_SHADER, fragment: PARTICLE_FRAGMENT_SHADER });
4655
+ this.vertexBuffer = new VertexBuffer(gl, gl.DYNAMIC_DRAW);
4656
+ this.indexBuffer = new IndexBuffer(gl, gl.DYNAMIC_DRAW);
4657
+ this.vertexArray = new VertexArray(gl);
4658
+ this.vertexArray.configureAttributes(
4659
+ [
4660
+ { index: 0, size: 3, type: gl.FLOAT, stride: 36, offset: 0 },
4661
+ { index: 1, size: 2, type: gl.FLOAT, stride: 36, offset: 12 },
4662
+ { index: 2, size: 4, type: gl.FLOAT, stride: 36, offset: 20 }
4663
+ ],
4664
+ this.vertexBuffer
4665
+ );
4666
+ }
4667
+ render(options) {
4668
+ const mesh = this.system.buildMesh(options.viewRight, options.viewUp);
4669
+ if (mesh.indices.length === 0) {
4670
+ return;
4671
+ }
4672
+ const vertexData = mesh.vertices;
4673
+ if (mesh.vertices.byteLength > this.vertexCapacity) {
4674
+ this.vertexCapacity = mesh.vertices.byteLength;
4675
+ this.vertexBuffer.upload(vertexData, this.gl.DYNAMIC_DRAW);
4676
+ } else {
4677
+ this.vertexBuffer.update(vertexData);
4678
+ }
4679
+ const indexData = mesh.indices;
4680
+ if (mesh.indices.byteLength > this.indexCapacity) {
4681
+ this.indexCapacity = mesh.indices.byteLength;
4682
+ this.indexBuffer.upload(indexData, this.gl.DYNAMIC_DRAW);
4683
+ } else {
4684
+ this.indexBuffer.update(indexData);
4685
+ }
4686
+ this.gl.depthMask(false);
4687
+ this.program.use();
4688
+ const vp = this.program.getUniformLocation("u_viewProjection");
4689
+ this.gl.uniformMatrix4fv(vp, false, options.viewProjection);
4690
+ this.vertexArray.bind();
4691
+ for (const batch of mesh.batches) {
4692
+ if (batch.blendMode === "additive") {
4693
+ this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE);
4694
+ } else {
4695
+ this.gl.blendFuncSeparate(
4696
+ this.gl.SRC_ALPHA,
4697
+ this.gl.ONE_MINUS_SRC_ALPHA,
4698
+ this.gl.ONE,
4699
+ this.gl.ONE_MINUS_SRC_ALPHA
4700
+ );
4701
+ }
4702
+ this.gl.drawElements(this.gl.TRIANGLES, batch.count, this.gl.UNSIGNED_SHORT, batch.start * 2);
4703
+ }
4704
+ this.gl.depthMask(true);
4705
+ }
4706
+ dispose() {
4707
+ this.program.dispose();
4708
+ this.vertexBuffer.dispose();
4709
+ this.indexBuffer.dispose();
4710
+ this.vertexArray.dispose();
4711
+ }
4712
+ };
4713
+ function spawnBulletImpact(context) {
4714
+ const { system, origin, normal = { x: 0, y: 0, z: 1 } } = context;
4715
+ for (let i = 0; i < 12; i += 1) {
4716
+ const speed = 200 + system.random() * 180;
4717
+ const spread = system.random() * 0.35;
4718
+ system.spawn({
4719
+ position: origin,
4720
+ velocity: {
4721
+ x: normal.x * speed + (system.random() - 0.5) * 80,
4722
+ y: normal.y * speed + (system.random() - 0.5) * 80,
4723
+ z: Math.max(normal.z * speed, 120) + spread * 80
4724
+ },
4725
+ color: [1, 0.8, 0.4, 1],
4726
+ size: 2.5,
4727
+ lifetime: 0.45 + system.random() * 0.1,
4728
+ gravity: 600,
4729
+ damping: 2,
4730
+ bounce: 0.45,
4731
+ blendMode: "additive",
4732
+ fade: true
4733
+ });
4734
+ }
4735
+ for (let i = 0; i < 8; i += 1) {
4736
+ system.spawn({
4737
+ position: origin,
4738
+ velocity: { x: (system.random() - 0.5) * 40, y: (system.random() - 0.5) * 40, z: 80 + system.random() * 40 },
4739
+ color: [0.45, 0.45, 0.45, 0.75],
4740
+ size: 6,
4741
+ lifetime: 0.6,
4742
+ gravity: 200,
4743
+ damping: 4,
4744
+ bounce: 0.15,
4745
+ blendMode: "alpha",
4746
+ fade: true
4747
+ });
4748
+ }
4749
+ }
4750
+ function spawnExplosion(context) {
4751
+ const { system, origin } = context;
4752
+ for (let i = 0; i < 40; i += 1) {
4753
+ const theta = system.random() * Math.PI * 2;
4754
+ const phi = Math.acos(2 * system.random() - 1);
4755
+ const speed = 220 + system.random() * 260;
4756
+ const dir = {
4757
+ x: Math.sin(phi) * Math.cos(theta),
4758
+ y: Math.sin(phi) * Math.sin(theta),
4759
+ z: Math.cos(phi)
4760
+ };
4761
+ system.spawn({
4762
+ position: origin,
4763
+ velocity: { x: dir.x * speed, y: dir.y * speed, z: dir.z * speed },
4764
+ color: [1, 0.6, 0.2, 1],
4765
+ size: 5,
4766
+ lifetime: 0.9,
4767
+ gravity: 700,
4768
+ damping: 1,
4769
+ bounce: 0.35,
4770
+ blendMode: "additive",
4771
+ fade: true
4772
+ });
4773
+ }
4774
+ for (let i = 0; i < 16; i += 1) {
4775
+ system.spawn({
4776
+ position: origin,
4777
+ velocity: { x: (system.random() - 0.5) * 30, y: (system.random() - 0.5) * 30, z: 120 + system.random() * 120 },
4778
+ color: [0.25, 0.25, 0.25, 0.9],
4779
+ size: 12,
4780
+ lifetime: 1.2,
4781
+ gravity: 300,
4782
+ damping: 3,
4783
+ blendMode: "alpha",
4784
+ fade: true
4785
+ });
4786
+ }
4787
+ }
4788
+ function spawnBlood(context) {
4789
+ const { system, origin, direction = { x: 0, y: 0, z: 1 } } = context;
4790
+ for (let i = 0; i < 24; i += 1) {
4791
+ const speed = 120 + system.random() * 180;
4792
+ system.spawn({
4793
+ position: origin,
4794
+ velocity: {
4795
+ x: direction.x * speed + (system.random() - 0.5) * 70,
4796
+ y: direction.y * speed + (system.random() - 0.5) * 70,
4797
+ z: direction.z * speed + system.random() * 80
4798
+ },
4799
+ color: [0.6, 0, 0, 0.95],
4800
+ size: 3,
4801
+ lifetime: 0.8,
4802
+ gravity: 900,
4803
+ damping: 1,
4804
+ bounce: 0.2,
4805
+ blendMode: "alpha",
4806
+ fade: true
4807
+ });
4808
+ }
4809
+ }
4810
+ function spawnTeleportFlash(context) {
4811
+ const { system, origin } = context;
4812
+ for (let i = 0; i < 30; i += 1) {
4813
+ const angle = system.random() * Math.PI * 2;
4814
+ const radius = 8 + system.random() * 8;
4815
+ system.spawn({
4816
+ position: origin,
4817
+ velocity: { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius, z: 100 + system.random() * 80 },
4818
+ color: [0.4, 0.6, 1, 0.9],
4819
+ size: 4,
4820
+ lifetime: 0.5,
4821
+ gravity: 300,
4822
+ damping: 2,
4823
+ blendMode: "additive",
4824
+ fade: true
4825
+ });
4826
+ }
4827
+ }
4828
+ function spawnMuzzleFlash(context) {
4829
+ const { system, origin, direction = { x: 1, y: 0, z: 0 } } = context;
4830
+ for (let i = 0; i < 10; i += 1) {
4831
+ const speed = 350 + system.random() * 100;
4832
+ system.spawn({
4833
+ position: origin,
4834
+ velocity: {
4835
+ x: direction.x * speed + (system.random() - 0.5) * 30,
4836
+ y: direction.y * speed + (system.random() - 0.5) * 30,
4837
+ z: direction.z * speed + (system.random() - 0.5) * 30
4838
+ },
4839
+ color: [1, 0.8, 0.3, 1],
4840
+ size: 3,
4841
+ lifetime: 0.25,
4842
+ gravity: 200,
4843
+ damping: 1,
4844
+ blendMode: "additive",
4845
+ fade: true
4846
+ });
4847
+ }
4848
+ }
4849
+ function spawnTrail(context) {
4850
+ const { system, origin, direction = { x: 0, y: 0, z: 0 } } = context;
4851
+ for (let i = 0; i < 6; i += 1) {
4852
+ system.spawn({
4853
+ position: {
4854
+ x: origin.x + direction.x * i * 2,
4855
+ y: origin.y + direction.y * i * 2,
4856
+ z: origin.z + direction.z * i * 2
4857
+ },
4858
+ velocity: { x: (system.random() - 0.5) * 15, y: (system.random() - 0.5) * 15, z: 20 + system.random() * 15 },
4859
+ color: [0.6, 0.6, 0.6, 0.8],
4860
+ size: 2.2,
4861
+ lifetime: 0.6,
4862
+ gravity: 200,
4863
+ damping: 1.5,
4864
+ blendMode: "alpha",
4865
+ fade: true
4866
+ });
4867
+ }
4868
+ }
4869
+
4112
4870
  // src/index.ts
4113
4871
  function createEngine(imports) {
4114
4872
  return {
@@ -4153,19 +4911,28 @@ function createEngine(imports) {
4153
4911
  MAX_SOUND_CHANNELS,
4154
4912
  MD2_FRAGMENT_SHADER,
4155
4913
  MD2_VERTEX_SHADER,
4914
+ MD3_FRAGMENT_SHADER,
4915
+ MD3_VERTEX_SHADER,
4156
4916
  Md2Loader,
4157
4917
  Md2MeshBuffers,
4158
4918
  Md2ParseError,
4159
4919
  Md2Pipeline,
4160
4920
  Md3Loader,
4921
+ Md3ModelMesh,
4161
4922
  Md3ParseError,
4923
+ Md3Pipeline,
4924
+ Md3SurfaceMesh,
4162
4925
  MusicSystem,
4926
+ PARTICLE_FRAGMENT_SHADER,
4927
+ PARTICLE_VERTEX_SHADER,
4163
4928
  PakArchive,
4164
4929
  PakIndexStore,
4165
4930
  PakIngestionError,
4166
4931
  PakParseError,
4167
4932
  PakValidationError,
4168
4933
  PakValidator,
4934
+ ParticleRenderer,
4935
+ ParticleSystem,
4169
4936
  RERELEASE_KNOWN_PAKS,
4170
4937
  SKYBOX_FRAGMENT_SHADER,
4171
4938
  SKYBOX_VERTEX_SHADER,
@@ -4189,6 +4956,8 @@ function createEngine(imports) {
4189
4956
  buildBspGeometry,
4190
4957
  buildMd2Geometry,
4191
4958
  buildMd2VertexData,
4959
+ buildMd3SurfaceGeometry,
4960
+ buildMd3VertexData,
4192
4961
  calculateMaxAudibleDistance,
4193
4962
  calculatePakChecksum,
4194
4963
  computeFrameBlend,
@@ -4209,6 +4978,7 @@ function createEngine(imports) {
4209
4978
  groupMd2Animations,
4210
4979
  ingestPakFiles,
4211
4980
  ingestPaks,
4981
+ interpolateMd3Tag,
4212
4982
  interpolateVec3,
4213
4983
  parseMd2,
4214
4984
  parseMd3,
@@ -4222,6 +4992,12 @@ function createEngine(imports) {
4222
4992
  removeViewTranslation,
4223
4993
  resolveLightStyles,
4224
4994
  spatializeOrigin,
4995
+ spawnBlood,
4996
+ spawnBulletImpact,
4997
+ spawnExplosion,
4998
+ spawnMuzzleFlash,
4999
+ spawnTeleportFlash,
5000
+ spawnTrail,
4225
5001
  walToRgba,
4226
5002
  wireDropTarget,
4227
5003
  wireFileInput