quake2ts 0.0.45 → 0.0.47

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 (52) 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 +14 -3
  4. package/apps/viewer/dist/cjs/index.cjs.map +1 -1
  5. package/apps/viewer/dist/esm/index.js +14 -3
  6. package/apps/viewer/dist/esm/index.js.map +1 -1
  7. package/apps/viewer/dist/tsconfig.tsbuildinfo +1 -1
  8. package/package.json +1 -1
  9. package/packages/client/dist/browser/index.global.js.map +1 -1
  10. package/packages/client/dist/cjs/index.cjs.map +1 -1
  11. package/packages/client/dist/esm/index.js.map +1 -1
  12. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  13. package/packages/engine/dist/browser/index.global.js +69 -10
  14. package/packages/engine/dist/browser/index.global.js.map +1 -1
  15. package/packages/engine/dist/cjs/index.cjs +781 -5
  16. package/packages/engine/dist/cjs/index.cjs.map +1 -1
  17. package/packages/engine/dist/esm/index.js +763 -5
  18. package/packages/engine/dist/esm/index.js.map +1 -1
  19. package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
  20. package/packages/engine/dist/types/index.d.ts +2 -0
  21. package/packages/engine/dist/types/index.d.ts.map +1 -1
  22. package/packages/engine/dist/types/render/md3Pipeline.d.ts +79 -0
  23. package/packages/engine/dist/types/render/md3Pipeline.d.ts.map +1 -0
  24. package/packages/engine/dist/types/render/particleSystem.d.ts +105 -0
  25. package/packages/engine/dist/types/render/particleSystem.d.ts.map +1 -0
  26. package/packages/game/dist/browser/index.global.js +1 -1
  27. package/packages/game/dist/browser/index.global.js.map +1 -1
  28. package/packages/game/dist/cjs/index.cjs +176 -3
  29. package/packages/game/dist/cjs/index.cjs.map +1 -1
  30. package/packages/game/dist/esm/index.js +172 -3
  31. package/packages/game/dist/esm/index.js.map +1 -1
  32. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
  33. package/packages/game/dist/types/ai/constants.d.ts +16 -0
  34. package/packages/game/dist/types/ai/constants.d.ts.map +1 -1
  35. package/packages/game/dist/types/ai/index.d.ts +1 -0
  36. package/packages/game/dist/types/ai/index.d.ts.map +1 -1
  37. package/packages/game/dist/types/ai/targeting.d.ts +25 -0
  38. package/packages/game/dist/types/ai/targeting.d.ts.map +1 -0
  39. package/packages/game/dist/types/entities/entity.d.ts +11 -0
  40. package/packages/game/dist/types/entities/entity.d.ts.map +1 -1
  41. package/packages/shared/dist/browser/index.global.js +1 -1
  42. package/packages/shared/dist/browser/index.global.js.map +1 -1
  43. package/packages/shared/dist/cjs/index.cjs +65 -0
  44. package/packages/shared/dist/cjs/index.cjs.map +1 -1
  45. package/packages/shared/dist/esm/index.js +61 -0
  46. package/packages/shared/dist/esm/index.js.map +1 -1
  47. package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
  48. package/packages/shared/dist/types/index.d.ts +1 -0
  49. package/packages/shared/dist/types/index.d.ts.map +1 -1
  50. package/packages/shared/dist/types/math/mat4.d.ts +7 -0
  51. package/packages/shared/dist/types/math/mat4.d.ts.map +1 -0
  52. package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
@@ -153,6 +153,52 @@ function normalizeVec3(a) {
153
153
  }
154
154
  var DEG2RAD_FACTOR = Math.PI / 180;
155
155
  var RAD2DEG_FACTOR = 180 / Math.PI;
156
+ function createMat4Identity() {
157
+ return new Float32Array([
158
+ 1,
159
+ 0,
160
+ 0,
161
+ 0,
162
+ 0,
163
+ 1,
164
+ 0,
165
+ 0,
166
+ 0,
167
+ 0,
168
+ 1,
169
+ 0,
170
+ 0,
171
+ 0,
172
+ 0,
173
+ 1
174
+ ]);
175
+ }
176
+ function transformPointMat4(mat, point) {
177
+ const x = point.x;
178
+ const y = point.y;
179
+ const z = point.z;
180
+ return {
181
+ x: mat[0] * x + mat[4] * y + mat[8] * z + mat[12],
182
+ y: mat[1] * x + mat[5] * y + mat[9] * z + mat[13],
183
+ z: mat[2] * x + mat[6] * y + mat[10] * z + mat[14]
184
+ };
185
+ }
186
+ function mat4FromBasis(origin, axis) {
187
+ const out = createMat4Identity();
188
+ out[0] = axis[0].x;
189
+ out[1] = axis[0].y;
190
+ out[2] = axis[0].z;
191
+ out[4] = axis[1].x;
192
+ out[5] = axis[1].y;
193
+ out[6] = axis[1].z;
194
+ out[8] = axis[2].x;
195
+ out[9] = axis[2].y;
196
+ out[10] = axis[2].z;
197
+ out[12] = origin.x;
198
+ out[13] = origin.y;
199
+ out[14] = origin.z;
200
+ return out;
201
+ }
156
202
  var CONTENTS_SOLID = 1 << 0;
157
203
  var CONTENTS_WINDOW = 1 << 1;
158
204
  var CONTENTS_AUX = 1 << 2;
@@ -1614,8 +1660,8 @@ function computeFrameBlend(state) {
1614
1660
  const baseFrame = Math.floor(normalizedPosition);
1615
1661
  const frame = state.sequence.start + baseFrame;
1616
1662
  const nextFrame = baseFrame + 1 >= totalFrames ? loop ? state.sequence.start : state.sequence.end : frame + 1;
1617
- const lerp2 = !loop && baseFrame >= totalFrames - 1 ? 0 : normalizedPosition - baseFrame;
1618
- return { frame, nextFrame, lerp: lerp2 };
1663
+ const lerp3 = !loop && baseFrame >= totalFrames - 1 ? 0 : normalizedPosition - baseFrame;
1664
+ return { frame, nextFrame, lerp: lerp3 };
1619
1665
  }
1620
1666
  function createAnimationState(sequence) {
1621
1667
  return { sequence, time: 0 };
@@ -3889,7 +3935,7 @@ function buildMd2Geometry(model) {
3889
3935
  return { vertices, indices: new Uint16Array(indices) };
3890
3936
  }
3891
3937
  function buildMd2VertexData(model, geometry, blend) {
3892
- const { currentFrame, nextFrame, lerp: lerp2 } = blend;
3938
+ const { currentFrame, nextFrame, lerp: lerp3 } = blend;
3893
3939
  const frameA = model.frames[currentFrame];
3894
3940
  const frameB = model.frames[nextFrame];
3895
3941
  if (!frameA || !frameB) {
@@ -3902,8 +3948,8 @@ function buildMd2VertexData(model, geometry, blend) {
3902
3948
  if (!vA || !vB) {
3903
3949
  throw new Error("MD2 vertex index out of range for frame");
3904
3950
  }
3905
- const position = lerpVec3(vA.position, vB.position, lerp2);
3906
- const normal = normalizeVec32(lerpVec3(vA.normal, vB.normal, lerp2));
3951
+ const position = lerpVec3(vA.position, vB.position, lerp3);
3952
+ const normal = normalizeVec32(lerpVec3(vA.normal, vB.normal, lerp3));
3907
3953
  const base = index * 8;
3908
3954
  data[base] = position.x;
3909
3955
  data[base + 1] = position.y;
@@ -3983,6 +4029,700 @@ var Md2Pipeline = class {
3983
4029
  }
3984
4030
  };
3985
4031
 
4032
+ // src/render/md3Pipeline.ts
4033
+ var DEFAULT_AMBIENT = [0.2, 0.2, 0.2];
4034
+ var DEFAULT_DIRECTION = { x: 0, y: 0, z: 1 };
4035
+ var DEFAULT_DIRECTION_COLOR = [0.8, 0.8, 0.8];
4036
+ function lerp2(a, b, t) {
4037
+ return a + (b - a) * t;
4038
+ }
4039
+ function lerpVec32(a, b, t) {
4040
+ return { x: lerp2(a.x, b.x, t), y: lerp2(a.y, b.y, t), z: lerp2(a.z, b.z, t) };
4041
+ }
4042
+ function clamp012(v) {
4043
+ if (v < 0) return 0;
4044
+ if (v > 1) return 1;
4045
+ return v;
4046
+ }
4047
+ function buildMd3SurfaceGeometry(surface) {
4048
+ const vertices = [];
4049
+ const indices = [];
4050
+ for (const tri of surface.triangles) {
4051
+ const base = vertices.length;
4052
+ const [a, b, c] = tri.indices;
4053
+ const texA = surface.texCoords[a];
4054
+ const texB = surface.texCoords[b];
4055
+ const texC = surface.texCoords[c];
4056
+ if (!texA || !texB || !texC) {
4057
+ throw new Error(`Missing texCoord for triangle in surface ${surface.name}`);
4058
+ }
4059
+ vertices.push(
4060
+ { vertexIndex: a, texCoord: [texA.s, 1 - texA.t] },
4061
+ { vertexIndex: b, texCoord: [texB.s, 1 - texB.t] },
4062
+ { vertexIndex: c, texCoord: [texC.s, 1 - texC.t] }
4063
+ );
4064
+ indices.push(base, base + 1, base + 2);
4065
+ }
4066
+ return { vertices, indices: new Uint16Array(indices) };
4067
+ }
4068
+ function evaluateLighting(normal, position, lighting) {
4069
+ const ambient = lighting?.ambient ?? DEFAULT_AMBIENT;
4070
+ const directional = lighting?.directional ?? { direction: DEFAULT_DIRECTION, color: DEFAULT_DIRECTION_COLOR };
4071
+ const n = normalizeVec3(normal);
4072
+ const l = normalizeVec3(directional.direction);
4073
+ const ndotl = clamp012(n.x * l.x + n.y * l.y + n.z * l.z);
4074
+ let r = ambient[0] + directional.color[0] * ndotl;
4075
+ let g = ambient[1] + directional.color[1] * ndotl;
4076
+ let b = ambient[2] + directional.color[2] * ndotl;
4077
+ if (lighting?.dynamicLights) {
4078
+ const worldPos = lighting.modelMatrix ? transformPointMat4(lighting.modelMatrix, position) : position;
4079
+ for (const light of lighting.dynamicLights) {
4080
+ const dx = worldPos.x - light.origin.x;
4081
+ const dy = worldPos.y - light.origin.y;
4082
+ const dz = worldPos.z - light.origin.z;
4083
+ const distSq = dx * dx + dy * dy + dz * dz;
4084
+ const radiusSq = light.radius * light.radius;
4085
+ if (distSq < radiusSq && radiusSq > 0) {
4086
+ const attenuation = 1 - Math.sqrt(distSq) / light.radius;
4087
+ const amount = clamp012(attenuation * ndotl);
4088
+ r += light.color[0] * amount;
4089
+ g += light.color[1] * amount;
4090
+ b += light.color[2] * amount;
4091
+ }
4092
+ }
4093
+ }
4094
+ return [clamp012(r), clamp012(g), clamp012(b)];
4095
+ }
4096
+ function buildMd3VertexData(surface, geometry, blend, lighting) {
4097
+ const frameA = surface.vertices[blend.currentFrame];
4098
+ const frameB = surface.vertices[blend.nextFrame];
4099
+ if (!frameA || !frameB) {
4100
+ throw new Error("Requested MD3 frames are out of range");
4101
+ }
4102
+ const data = new Float32Array(geometry.vertices.length * 12);
4103
+ geometry.vertices.forEach((vertex, index) => {
4104
+ const vA = frameA[vertex.vertexIndex];
4105
+ const vB = frameB[vertex.vertexIndex];
4106
+ if (!vA || !vB) {
4107
+ throw new Error(`Vertex index ${vertex.vertexIndex} missing for frame`);
4108
+ }
4109
+ const position = lerpVec32(vA.position, vB.position, blend.lerp);
4110
+ const normal = normalizeVec3(lerpVec32(vA.normal, vB.normal, blend.lerp));
4111
+ const color = evaluateLighting(normal, position, lighting);
4112
+ const base = index * 12;
4113
+ data[base] = position.x;
4114
+ data[base + 1] = position.y;
4115
+ data[base + 2] = position.z;
4116
+ data[base + 3] = normal.x;
4117
+ data[base + 4] = normal.y;
4118
+ data[base + 5] = normal.z;
4119
+ data[base + 6] = vertex.texCoord[0];
4120
+ data[base + 7] = vertex.texCoord[1];
4121
+ data[base + 8] = color[0];
4122
+ data[base + 9] = color[1];
4123
+ data[base + 10] = color[2];
4124
+ data[base + 11] = 1;
4125
+ });
4126
+ return data;
4127
+ }
4128
+ function interpolateMd3Tag(model, blend, tagName) {
4129
+ const firstFrameTags = model.tags[0];
4130
+ if (!firstFrameTags) {
4131
+ return null;
4132
+ }
4133
+ const tagIndex = firstFrameTags.findIndex((tag) => tag.name === tagName);
4134
+ if (tagIndex === -1) {
4135
+ return null;
4136
+ }
4137
+ const tagA = model.tags[blend.currentFrame]?.[tagIndex];
4138
+ const tagB = model.tags[blend.nextFrame]?.[tagIndex];
4139
+ if (!tagA || !tagB) {
4140
+ throw new Error(`Tag ${tagName} is missing for one of the interpolated frames`);
4141
+ }
4142
+ const origin = lerpVec32(tagA.origin, tagB.origin, blend.lerp);
4143
+ const axis0 = normalizeVec3(lerpVec32(tagA.axis[0], tagB.axis[0], blend.lerp));
4144
+ const axis1 = normalizeVec3(lerpVec32(tagA.axis[1], tagB.axis[1], blend.lerp));
4145
+ const axis2 = normalizeVec3(lerpVec32(tagA.axis[2], tagB.axis[2], blend.lerp));
4146
+ const corrected0 = axis0;
4147
+ const corrected1 = normalizeVec3({
4148
+ x: axis1.x - corrected0.x * (corrected0.x * axis1.x + corrected0.y * axis1.y + corrected0.z * axis1.z),
4149
+ y: axis1.y - corrected0.y * (corrected0.x * axis1.x + corrected0.y * axis1.y + corrected0.z * axis1.z),
4150
+ z: axis1.z - corrected0.z * (corrected0.x * axis1.x + corrected0.y * axis1.y + corrected0.z * axis1.z)
4151
+ });
4152
+ const corrected2 = normalizeVec3({
4153
+ x: corrected0.y * corrected1.z - corrected0.z * corrected1.y,
4154
+ y: corrected0.z * corrected1.x - corrected0.x * corrected1.z,
4155
+ z: corrected0.x * corrected1.y - corrected0.y * corrected1.x
4156
+ });
4157
+ const axis = [corrected0, corrected1, corrected2];
4158
+ return { origin, axis, matrix: mat4FromBasis(origin, axis) };
4159
+ }
4160
+ var MD3_VERTEX_SHADER = `#version 300 es
4161
+ precision highp float;
4162
+
4163
+ layout(location = 0) in vec3 a_position;
4164
+ layout(location = 1) in vec3 a_normal;
4165
+ layout(location = 2) in vec2 a_texCoord;
4166
+ layout(location = 3) in vec4 a_color;
4167
+
4168
+ uniform mat4 u_modelViewProjection;
4169
+
4170
+ out vec2 v_texCoord;
4171
+ out vec4 v_color;
4172
+
4173
+ void main() {
4174
+ v_texCoord = a_texCoord;
4175
+ v_color = a_color;
4176
+ gl_Position = u_modelViewProjection * vec4(a_position, 1.0);
4177
+ }`;
4178
+ var MD3_FRAGMENT_SHADER = `#version 300 es
4179
+ precision highp float;
4180
+
4181
+ in vec2 v_texCoord;
4182
+ in vec4 v_color;
4183
+
4184
+ uniform sampler2D u_diffuseMap;
4185
+ uniform vec4 u_tint;
4186
+
4187
+ out vec4 o_color;
4188
+
4189
+ void main() {
4190
+ vec4 albedo = texture(u_diffuseMap, v_texCoord) * u_tint;
4191
+ o_color = vec4(albedo.rgb * v_color.rgb, albedo.a * v_color.a);
4192
+ }`;
4193
+ var Md3SurfaceMesh = class {
4194
+ constructor(gl, surface, blend, lighting) {
4195
+ this.gl = gl;
4196
+ this.geometry = buildMd3SurfaceGeometry(surface);
4197
+ this.vertexBuffer = new VertexBuffer(gl, gl.STATIC_DRAW);
4198
+ this.indexBuffer = new IndexBuffer(gl, gl.STATIC_DRAW);
4199
+ this.vertexArray = new VertexArray(gl);
4200
+ this.indexCount = this.geometry.indices.length;
4201
+ this.vertexArray.configureAttributes(
4202
+ [
4203
+ { index: 0, size: 3, type: gl.FLOAT, stride: 48, offset: 0 },
4204
+ { index: 1, size: 3, type: gl.FLOAT, stride: 48, offset: 12 },
4205
+ { index: 2, size: 2, type: gl.FLOAT, stride: 48, offset: 24 },
4206
+ { index: 3, size: 4, type: gl.FLOAT, stride: 48, offset: 32 }
4207
+ ],
4208
+ this.vertexBuffer
4209
+ );
4210
+ this.vertexArray.bind();
4211
+ this.indexBuffer.bind();
4212
+ this.indexBuffer.upload(this.geometry.indices, gl.STATIC_DRAW);
4213
+ this.update(surface, blend, lighting);
4214
+ }
4215
+ update(surface, blend, lighting) {
4216
+ const data = buildMd3VertexData(surface, this.geometry, blend, lighting);
4217
+ this.vertexBuffer.upload(data, this.gl.STATIC_DRAW);
4218
+ }
4219
+ bind() {
4220
+ this.vertexArray.bind();
4221
+ this.indexBuffer.bind();
4222
+ }
4223
+ dispose() {
4224
+ this.vertexBuffer.dispose();
4225
+ this.indexBuffer.dispose();
4226
+ this.vertexArray.dispose();
4227
+ }
4228
+ };
4229
+ var Md3ModelMesh = class {
4230
+ constructor(gl, model, blend, lighting) {
4231
+ this.surfaces = /* @__PURE__ */ new Map();
4232
+ this.gl = gl;
4233
+ this.model = model;
4234
+ this.blend = blend;
4235
+ this.lighting = lighting;
4236
+ model.surfaces.forEach((surface) => {
4237
+ this.surfaces.set(surface.name, new Md3SurfaceMesh(gl, surface, blend, lighting));
4238
+ });
4239
+ }
4240
+ update(blend, lighting) {
4241
+ this.blend = blend;
4242
+ this.lighting = lighting ?? this.lighting;
4243
+ for (const surface of this.model.surfaces) {
4244
+ const mesh = this.surfaces.get(surface.name);
4245
+ mesh?.update(surface, blend, this.lighting);
4246
+ }
4247
+ }
4248
+ dispose() {
4249
+ for (const mesh of this.surfaces.values()) {
4250
+ mesh.dispose();
4251
+ }
4252
+ this.surfaces.clear();
4253
+ }
4254
+ };
4255
+ var Md3Pipeline = class {
4256
+ constructor(gl) {
4257
+ this.gl = gl;
4258
+ this.program = ShaderProgram.create(
4259
+ gl,
4260
+ { vertex: MD3_VERTEX_SHADER, fragment: MD3_FRAGMENT_SHADER },
4261
+ { a_position: 0, a_normal: 1, a_texCoord: 2, a_color: 3 }
4262
+ );
4263
+ this.uniformMvp = this.program.getUniformLocation("u_modelViewProjection");
4264
+ this.uniformTint = this.program.getUniformLocation("u_tint");
4265
+ this.uniformDiffuse = this.program.getUniformLocation("u_diffuseMap");
4266
+ }
4267
+ bind(modelViewProjection, tint = [1, 1, 1, 1], sampler = 0) {
4268
+ this.program.use();
4269
+ this.gl.uniformMatrix4fv(this.uniformMvp, false, modelViewProjection);
4270
+ this.gl.uniform4fv(this.uniformTint, new Float32Array(tint));
4271
+ this.gl.uniform1i(this.uniformDiffuse, sampler);
4272
+ }
4273
+ drawSurface(mesh, material) {
4274
+ const sampler = material?.diffuseSampler ?? 0;
4275
+ const tint = material?.tint ?? [1, 1, 1, 1];
4276
+ this.gl.uniform4fv(this.uniformTint, new Float32Array(tint));
4277
+ this.gl.uniform1i(this.uniformDiffuse, sampler);
4278
+ mesh.bind();
4279
+ this.gl.drawElements(this.gl.TRIANGLES, mesh.indexCount, this.gl.UNSIGNED_SHORT, 0);
4280
+ }
4281
+ dispose() {
4282
+ this.program.dispose();
4283
+ }
4284
+ };
4285
+
4286
+ // src/render/particleSystem.ts
4287
+ var DEFAULT_COLOR = [1, 1, 1, 1];
4288
+ var ParticleSystem = class {
4289
+ // 0 alpha, 1 additive
4290
+ constructor(maxParticles, random = Math.random) {
4291
+ this.maxParticles = maxParticles;
4292
+ this.random = random;
4293
+ this.alive = new Uint8Array(maxParticles);
4294
+ this.positionX = new Float32Array(maxParticles);
4295
+ this.positionY = new Float32Array(maxParticles);
4296
+ this.positionZ = new Float32Array(maxParticles);
4297
+ this.velocityX = new Float32Array(maxParticles);
4298
+ this.velocityY = new Float32Array(maxParticles);
4299
+ this.velocityZ = new Float32Array(maxParticles);
4300
+ this.colorR = new Float32Array(maxParticles);
4301
+ this.colorG = new Float32Array(maxParticles);
4302
+ this.colorB = new Float32Array(maxParticles);
4303
+ this.colorA = new Float32Array(maxParticles);
4304
+ this.size = new Float32Array(maxParticles);
4305
+ this.lifetime = new Float32Array(maxParticles);
4306
+ this.remaining = new Float32Array(maxParticles);
4307
+ this.gravity = new Float32Array(maxParticles);
4308
+ this.damping = new Float32Array(maxParticles);
4309
+ this.bounce = new Float32Array(maxParticles);
4310
+ this.fade = new Uint8Array(maxParticles);
4311
+ this.blendMode = new Uint8Array(maxParticles);
4312
+ }
4313
+ spawn(options) {
4314
+ const index = this.findFreeSlot();
4315
+ if (index === -1) {
4316
+ return null;
4317
+ }
4318
+ const color = options.color ?? DEFAULT_COLOR;
4319
+ const velocity = options.velocity ?? { x: 0, y: 0, z: 0 };
4320
+ this.alive[index] = 1;
4321
+ this.positionX[index] = options.position.x;
4322
+ this.positionY[index] = options.position.y;
4323
+ this.positionZ[index] = options.position.z;
4324
+ this.velocityX[index] = velocity.x;
4325
+ this.velocityY[index] = velocity.y;
4326
+ this.velocityZ[index] = velocity.z;
4327
+ this.colorR[index] = color[0];
4328
+ this.colorG[index] = color[1];
4329
+ this.colorB[index] = color[2];
4330
+ this.colorA[index] = color[3];
4331
+ this.size[index] = options.size ?? 2.5;
4332
+ this.lifetime[index] = options.lifetime;
4333
+ this.remaining[index] = options.lifetime;
4334
+ this.gravity[index] = options.gravity ?? 800;
4335
+ this.damping[index] = options.damping ?? 0;
4336
+ this.bounce[index] = options.bounce ?? 0.25;
4337
+ this.fade[index] = options.fade ? 1 : 0;
4338
+ this.blendMode[index] = options.blendMode === "additive" ? 1 : 0;
4339
+ return index;
4340
+ }
4341
+ update(dt, options = {}) {
4342
+ const floorZ = options.floorZ ?? -Infinity;
4343
+ for (let i = 0; i < this.maxParticles; i += 1) {
4344
+ if (!this.alive[i]) {
4345
+ continue;
4346
+ }
4347
+ this.remaining[i] -= dt;
4348
+ if (this.remaining[i] <= 0) {
4349
+ this.alive[i] = 0;
4350
+ continue;
4351
+ }
4352
+ const damping = Math.max(0, 1 - this.damping[i] * dt);
4353
+ this.velocityX[i] *= damping;
4354
+ this.velocityY[i] *= damping;
4355
+ this.velocityZ[i] = this.velocityZ[i] * damping - this.gravity[i] * dt;
4356
+ this.positionX[i] += this.velocityX[i] * dt;
4357
+ this.positionY[i] += this.velocityY[i] * dt;
4358
+ this.positionZ[i] += this.velocityZ[i] * dt;
4359
+ if (this.positionZ[i] < floorZ) {
4360
+ this.positionZ[i] = floorZ;
4361
+ this.velocityZ[i] = -this.velocityZ[i] * this.bounce[i];
4362
+ this.velocityX[i] *= 0.7;
4363
+ this.velocityY[i] *= 0.7;
4364
+ }
4365
+ }
4366
+ }
4367
+ killAll() {
4368
+ this.alive.fill(0);
4369
+ }
4370
+ aliveCount() {
4371
+ let count = 0;
4372
+ for (let i = 0; i < this.maxParticles; i += 1) {
4373
+ if (this.alive[i]) {
4374
+ count += 1;
4375
+ }
4376
+ }
4377
+ return count;
4378
+ }
4379
+ getState(index) {
4380
+ return {
4381
+ alive: this.alive[index] === 1,
4382
+ position: {
4383
+ x: this.positionX[index],
4384
+ y: this.positionY[index],
4385
+ z: this.positionZ[index]
4386
+ },
4387
+ velocity: {
4388
+ x: this.velocityX[index],
4389
+ y: this.velocityY[index],
4390
+ z: this.velocityZ[index]
4391
+ },
4392
+ remaining: this.remaining[index],
4393
+ color: [this.colorR[index], this.colorG[index], this.colorB[index], this.colorA[index]],
4394
+ size: this.size[index],
4395
+ blendMode: this.blendMode[index] === 1 ? "additive" : "alpha"
4396
+ };
4397
+ }
4398
+ buildMesh(viewRight, viewUp) {
4399
+ const vertices = [];
4400
+ const indices = [];
4401
+ const batches = [];
4402
+ const buildBatch = (mode) => {
4403
+ const startIndex = indices.length;
4404
+ let particleCount = 0;
4405
+ for (let i = 0; i < this.maxParticles; i += 1) {
4406
+ if (!this.alive[i]) {
4407
+ continue;
4408
+ }
4409
+ if ((mode === "additive" ? 1 : 0) !== this.blendMode[i]) {
4410
+ continue;
4411
+ }
4412
+ particleCount += 1;
4413
+ const baseVertex = vertices.length / 9;
4414
+ const size = this.size[i] * 0.5;
4415
+ const fade = this.fade[i] ? Math.max(this.remaining[i] / this.lifetime[i], 0) : 1;
4416
+ const colorScale = this.blendMode[i] === 1 ? 1.2 : 1;
4417
+ const cR = this.colorR[i] * colorScale;
4418
+ const cG = this.colorG[i] * colorScale;
4419
+ const cB = this.colorB[i] * colorScale;
4420
+ const cA = this.colorA[i] * fade;
4421
+ const px = this.positionX[i];
4422
+ const py = this.positionY[i];
4423
+ const pz = this.positionZ[i];
4424
+ const rightX = viewRight.x * size;
4425
+ const rightY = viewRight.y * size;
4426
+ const rightZ = viewRight.z * size;
4427
+ const upX = viewUp.x * size;
4428
+ const upY = viewUp.y * size;
4429
+ const upZ = viewUp.z * size;
4430
+ const corners = [
4431
+ { x: px - rightX - upX, y: py - rightY - upY, z: pz - rightZ - upZ },
4432
+ { x: px + rightX - upX, y: py + rightY - upY, z: pz + rightZ - upZ },
4433
+ { x: px - rightX + upX, y: py - rightY + upY, z: pz - rightZ + upZ },
4434
+ { x: px + rightX + upX, y: py + rightY + upY, z: pz + rightZ + upZ }
4435
+ ];
4436
+ const uvs = [
4437
+ [0, 1],
4438
+ [1, 1],
4439
+ [0, 0],
4440
+ [1, 0]
4441
+ ];
4442
+ corners.forEach((corner, cornerIndex) => {
4443
+ vertices.push(
4444
+ corner.x,
4445
+ corner.y,
4446
+ corner.z,
4447
+ uvs[cornerIndex]?.[0] ?? 0,
4448
+ uvs[cornerIndex]?.[1] ?? 0,
4449
+ cR,
4450
+ cG,
4451
+ cB,
4452
+ cA
4453
+ );
4454
+ });
4455
+ indices.push(baseVertex, baseVertex + 1, baseVertex + 2, baseVertex + 2, baseVertex + 1, baseVertex + 3);
4456
+ }
4457
+ if (particleCount > 0) {
4458
+ batches.push({ blendMode: mode, start: startIndex, count: indices.length - startIndex });
4459
+ }
4460
+ };
4461
+ buildBatch("alpha");
4462
+ buildBatch("additive");
4463
+ return { vertices: new Float32Array(vertices), indices: new Uint16Array(indices), batches };
4464
+ }
4465
+ findFreeSlot() {
4466
+ for (let i = 0; i < this.maxParticles; i += 1) {
4467
+ if (!this.alive[i]) {
4468
+ return i;
4469
+ }
4470
+ }
4471
+ return -1;
4472
+ }
4473
+ };
4474
+ var PARTICLE_VERTEX_SHADER = `#version 300 es
4475
+ precision highp float;
4476
+
4477
+ layout(location = 0) in vec3 a_position;
4478
+ layout(location = 1) in vec2 a_uv;
4479
+ layout(location = 2) in vec4 a_color;
4480
+
4481
+ uniform mat4 u_viewProjection;
4482
+
4483
+ out vec2 v_uv;
4484
+ out vec4 v_color;
4485
+
4486
+ void main() {
4487
+ v_uv = a_uv;
4488
+ v_color = a_color;
4489
+ gl_Position = u_viewProjection * vec4(a_position, 1.0);
4490
+ }`;
4491
+ var PARTICLE_FRAGMENT_SHADER = `#version 300 es
4492
+ precision highp float;
4493
+
4494
+ in vec2 v_uv;
4495
+ in vec4 v_color;
4496
+
4497
+ out vec4 o_color;
4498
+
4499
+ void main() {
4500
+ float dist = distance(v_uv, vec2(0.5));
4501
+ float alpha = v_color.a * (1.0 - smoothstep(0.35, 0.5, dist));
4502
+ o_color = vec4(v_color.rgb, alpha);
4503
+ }`;
4504
+ var ParticleRenderer = class {
4505
+ constructor(gl, system) {
4506
+ this.vertexCapacity = 0;
4507
+ this.indexCapacity = 0;
4508
+ this.gl = gl;
4509
+ this.system = system;
4510
+ this.program = ShaderProgram.create(gl, { vertex: PARTICLE_VERTEX_SHADER, fragment: PARTICLE_FRAGMENT_SHADER });
4511
+ this.vertexBuffer = new VertexBuffer(gl, gl.DYNAMIC_DRAW);
4512
+ this.indexBuffer = new IndexBuffer(gl, gl.DYNAMIC_DRAW);
4513
+ this.vertexArray = new VertexArray(gl);
4514
+ this.vertexArray.configureAttributes(
4515
+ [
4516
+ { index: 0, size: 3, type: gl.FLOAT, stride: 36, offset: 0 },
4517
+ { index: 1, size: 2, type: gl.FLOAT, stride: 36, offset: 12 },
4518
+ { index: 2, size: 4, type: gl.FLOAT, stride: 36, offset: 20 }
4519
+ ],
4520
+ this.vertexBuffer
4521
+ );
4522
+ }
4523
+ render(options) {
4524
+ const mesh = this.system.buildMesh(options.viewRight, options.viewUp);
4525
+ if (mesh.indices.length === 0) {
4526
+ return;
4527
+ }
4528
+ const vertexData = mesh.vertices;
4529
+ if (mesh.vertices.byteLength > this.vertexCapacity) {
4530
+ this.vertexCapacity = mesh.vertices.byteLength;
4531
+ this.vertexBuffer.upload(vertexData, this.gl.DYNAMIC_DRAW);
4532
+ } else {
4533
+ this.vertexBuffer.update(vertexData);
4534
+ }
4535
+ const indexData = mesh.indices;
4536
+ if (mesh.indices.byteLength > this.indexCapacity) {
4537
+ this.indexCapacity = mesh.indices.byteLength;
4538
+ this.indexBuffer.upload(indexData, this.gl.DYNAMIC_DRAW);
4539
+ } else {
4540
+ this.indexBuffer.update(indexData);
4541
+ }
4542
+ this.gl.depthMask(false);
4543
+ this.program.use();
4544
+ const vp = this.program.getUniformLocation("u_viewProjection");
4545
+ this.gl.uniformMatrix4fv(vp, false, options.viewProjection);
4546
+ this.vertexArray.bind();
4547
+ for (const batch of mesh.batches) {
4548
+ if (batch.blendMode === "additive") {
4549
+ this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE);
4550
+ } else {
4551
+ this.gl.blendFuncSeparate(
4552
+ this.gl.SRC_ALPHA,
4553
+ this.gl.ONE_MINUS_SRC_ALPHA,
4554
+ this.gl.ONE,
4555
+ this.gl.ONE_MINUS_SRC_ALPHA
4556
+ );
4557
+ }
4558
+ this.gl.drawElements(this.gl.TRIANGLES, batch.count, this.gl.UNSIGNED_SHORT, batch.start * 2);
4559
+ }
4560
+ this.gl.depthMask(true);
4561
+ }
4562
+ dispose() {
4563
+ this.program.dispose();
4564
+ this.vertexBuffer.dispose();
4565
+ this.indexBuffer.dispose();
4566
+ this.vertexArray.dispose();
4567
+ }
4568
+ };
4569
+ function spawnBulletImpact(context) {
4570
+ const { system, origin, normal = { x: 0, y: 0, z: 1 } } = context;
4571
+ for (let i = 0; i < 12; i += 1) {
4572
+ const speed = 200 + system.random() * 180;
4573
+ const spread = system.random() * 0.35;
4574
+ system.spawn({
4575
+ position: origin,
4576
+ velocity: {
4577
+ x: normal.x * speed + (system.random() - 0.5) * 80,
4578
+ y: normal.y * speed + (system.random() - 0.5) * 80,
4579
+ z: Math.max(normal.z * speed, 120) + spread * 80
4580
+ },
4581
+ color: [1, 0.8, 0.4, 1],
4582
+ size: 2.5,
4583
+ lifetime: 0.45 + system.random() * 0.1,
4584
+ gravity: 600,
4585
+ damping: 2,
4586
+ bounce: 0.45,
4587
+ blendMode: "additive",
4588
+ fade: true
4589
+ });
4590
+ }
4591
+ for (let i = 0; i < 8; i += 1) {
4592
+ system.spawn({
4593
+ position: origin,
4594
+ velocity: { x: (system.random() - 0.5) * 40, y: (system.random() - 0.5) * 40, z: 80 + system.random() * 40 },
4595
+ color: [0.45, 0.45, 0.45, 0.75],
4596
+ size: 6,
4597
+ lifetime: 0.6,
4598
+ gravity: 200,
4599
+ damping: 4,
4600
+ bounce: 0.15,
4601
+ blendMode: "alpha",
4602
+ fade: true
4603
+ });
4604
+ }
4605
+ }
4606
+ function spawnExplosion(context) {
4607
+ const { system, origin } = context;
4608
+ for (let i = 0; i < 40; i += 1) {
4609
+ const theta = system.random() * Math.PI * 2;
4610
+ const phi = Math.acos(2 * system.random() - 1);
4611
+ const speed = 220 + system.random() * 260;
4612
+ const dir = {
4613
+ x: Math.sin(phi) * Math.cos(theta),
4614
+ y: Math.sin(phi) * Math.sin(theta),
4615
+ z: Math.cos(phi)
4616
+ };
4617
+ system.spawn({
4618
+ position: origin,
4619
+ velocity: { x: dir.x * speed, y: dir.y * speed, z: dir.z * speed },
4620
+ color: [1, 0.6, 0.2, 1],
4621
+ size: 5,
4622
+ lifetime: 0.9,
4623
+ gravity: 700,
4624
+ damping: 1,
4625
+ bounce: 0.35,
4626
+ blendMode: "additive",
4627
+ fade: true
4628
+ });
4629
+ }
4630
+ for (let i = 0; i < 16; i += 1) {
4631
+ system.spawn({
4632
+ position: origin,
4633
+ velocity: { x: (system.random() - 0.5) * 30, y: (system.random() - 0.5) * 30, z: 120 + system.random() * 120 },
4634
+ color: [0.25, 0.25, 0.25, 0.9],
4635
+ size: 12,
4636
+ lifetime: 1.2,
4637
+ gravity: 300,
4638
+ damping: 3,
4639
+ blendMode: "alpha",
4640
+ fade: true
4641
+ });
4642
+ }
4643
+ }
4644
+ function spawnBlood(context) {
4645
+ const { system, origin, direction = { x: 0, y: 0, z: 1 } } = context;
4646
+ for (let i = 0; i < 24; i += 1) {
4647
+ const speed = 120 + system.random() * 180;
4648
+ system.spawn({
4649
+ position: origin,
4650
+ velocity: {
4651
+ x: direction.x * speed + (system.random() - 0.5) * 70,
4652
+ y: direction.y * speed + (system.random() - 0.5) * 70,
4653
+ z: direction.z * speed + system.random() * 80
4654
+ },
4655
+ color: [0.6, 0, 0, 0.95],
4656
+ size: 3,
4657
+ lifetime: 0.8,
4658
+ gravity: 900,
4659
+ damping: 1,
4660
+ bounce: 0.2,
4661
+ blendMode: "alpha",
4662
+ fade: true
4663
+ });
4664
+ }
4665
+ }
4666
+ function spawnTeleportFlash(context) {
4667
+ const { system, origin } = context;
4668
+ for (let i = 0; i < 30; i += 1) {
4669
+ const angle = system.random() * Math.PI * 2;
4670
+ const radius = 8 + system.random() * 8;
4671
+ system.spawn({
4672
+ position: origin,
4673
+ velocity: { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius, z: 100 + system.random() * 80 },
4674
+ color: [0.4, 0.6, 1, 0.9],
4675
+ size: 4,
4676
+ lifetime: 0.5,
4677
+ gravity: 300,
4678
+ damping: 2,
4679
+ blendMode: "additive",
4680
+ fade: true
4681
+ });
4682
+ }
4683
+ }
4684
+ function spawnMuzzleFlash(context) {
4685
+ const { system, origin, direction = { x: 1, y: 0, z: 0 } } = context;
4686
+ for (let i = 0; i < 10; i += 1) {
4687
+ const speed = 350 + system.random() * 100;
4688
+ system.spawn({
4689
+ position: origin,
4690
+ velocity: {
4691
+ x: direction.x * speed + (system.random() - 0.5) * 30,
4692
+ y: direction.y * speed + (system.random() - 0.5) * 30,
4693
+ z: direction.z * speed + (system.random() - 0.5) * 30
4694
+ },
4695
+ color: [1, 0.8, 0.3, 1],
4696
+ size: 3,
4697
+ lifetime: 0.25,
4698
+ gravity: 200,
4699
+ damping: 1,
4700
+ blendMode: "additive",
4701
+ fade: true
4702
+ });
4703
+ }
4704
+ }
4705
+ function spawnTrail(context) {
4706
+ const { system, origin, direction = { x: 0, y: 0, z: 0 } } = context;
4707
+ for (let i = 0; i < 6; i += 1) {
4708
+ system.spawn({
4709
+ position: {
4710
+ x: origin.x + direction.x * i * 2,
4711
+ y: origin.y + direction.y * i * 2,
4712
+ z: origin.z + direction.z * i * 2
4713
+ },
4714
+ velocity: { x: (system.random() - 0.5) * 15, y: (system.random() - 0.5) * 15, z: 20 + system.random() * 15 },
4715
+ color: [0.6, 0.6, 0.6, 0.8],
4716
+ size: 2.2,
4717
+ lifetime: 0.6,
4718
+ gravity: 200,
4719
+ damping: 1.5,
4720
+ blendMode: "alpha",
4721
+ fade: true
4722
+ });
4723
+ }
4724
+ }
4725
+
3986
4726
  // src/index.ts
3987
4727
  function createEngine(imports) {
3988
4728
  return {
@@ -4026,19 +4766,28 @@ export {
4026
4766
  MAX_SOUND_CHANNELS,
4027
4767
  MD2_FRAGMENT_SHADER,
4028
4768
  MD2_VERTEX_SHADER,
4769
+ MD3_FRAGMENT_SHADER,
4770
+ MD3_VERTEX_SHADER,
4029
4771
  Md2Loader,
4030
4772
  Md2MeshBuffers,
4031
4773
  Md2ParseError,
4032
4774
  Md2Pipeline,
4033
4775
  Md3Loader,
4776
+ Md3ModelMesh,
4034
4777
  Md3ParseError,
4778
+ Md3Pipeline,
4779
+ Md3SurfaceMesh,
4035
4780
  MusicSystem,
4781
+ PARTICLE_FRAGMENT_SHADER,
4782
+ PARTICLE_VERTEX_SHADER,
4036
4783
  PakArchive,
4037
4784
  PakIndexStore,
4038
4785
  PakIngestionError,
4039
4786
  PakParseError,
4040
4787
  PakValidationError,
4041
4788
  PakValidator,
4789
+ ParticleRenderer,
4790
+ ParticleSystem,
4042
4791
  RERELEASE_KNOWN_PAKS,
4043
4792
  SKYBOX_FRAGMENT_SHADER,
4044
4793
  SKYBOX_VERTEX_SHADER,
@@ -4062,6 +4811,8 @@ export {
4062
4811
  buildBspGeometry,
4063
4812
  buildMd2Geometry,
4064
4813
  buildMd2VertexData,
4814
+ buildMd3SurfaceGeometry,
4815
+ buildMd3VertexData,
4065
4816
  calculateMaxAudibleDistance,
4066
4817
  calculatePakChecksum,
4067
4818
  computeFrameBlend,
@@ -4082,6 +4833,7 @@ export {
4082
4833
  groupMd2Animations,
4083
4834
  ingestPakFiles,
4084
4835
  ingestPaks,
4836
+ interpolateMd3Tag,
4085
4837
  interpolateVec3,
4086
4838
  parseMd2,
4087
4839
  parseMd3,
@@ -4095,6 +4847,12 @@ export {
4095
4847
  removeViewTranslation,
4096
4848
  resolveLightStyles,
4097
4849
  spatializeOrigin,
4850
+ spawnBlood,
4851
+ spawnBulletImpact,
4852
+ spawnExplosion,
4853
+ spawnMuzzleFlash,
4854
+ spawnTeleportFlash,
4855
+ spawnTrail,
4098
4856
  walToRgba,
4099
4857
  wireDropTarget,
4100
4858
  wireFileInput