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.
- package/apps/viewer/dist/browser/index.global.js +1 -1
- package/apps/viewer/dist/browser/index.global.js.map +1 -1
- package/apps/viewer/dist/cjs/index.cjs.map +1 -1
- package/apps/viewer/dist/esm/index.js.map +1 -1
- package/apps/viewer/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/browser/index.global.js +69 -10
- package/packages/engine/dist/browser/index.global.js.map +1 -1
- package/packages/engine/dist/cjs/index.cjs +781 -5
- package/packages/engine/dist/cjs/index.cjs.map +1 -1
- package/packages/engine/dist/esm/index.js +763 -5
- package/packages/engine/dist/esm/index.js.map +1 -1
- package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/types/index.d.ts +2 -0
- package/packages/engine/dist/types/index.d.ts.map +1 -1
- package/packages/engine/dist/types/render/md3Pipeline.d.ts +79 -0
- package/packages/engine/dist/types/render/md3Pipeline.d.ts.map +1 -0
- package/packages/engine/dist/types/render/particleSystem.d.ts +105 -0
- package/packages/engine/dist/types/render/particleSystem.d.ts.map +1 -0
- package/packages/game/dist/browser/index.global.js.map +1 -1
- package/packages/game/dist/cjs/index.cjs.map +1 -1
- package/packages/game/dist/esm/index.js.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/shared/dist/browser/index.global.js +1 -1
- package/packages/shared/dist/browser/index.global.js.map +1 -1
- package/packages/shared/dist/cjs/index.cjs +65 -0
- package/packages/shared/dist/cjs/index.cjs.map +1 -1
- package/packages/shared/dist/esm/index.js +61 -0
- package/packages/shared/dist/esm/index.js.map +1 -1
- package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/shared/dist/types/index.d.ts +1 -0
- package/packages/shared/dist/types/index.d.ts.map +1 -1
- package/packages/shared/dist/types/math/mat4.d.ts +7 -0
- package/packages/shared/dist/types/math/mat4.d.ts.map +1 -0
- 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
|
|
1744
|
-
return { frame, nextFrame, lerp:
|
|
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:
|
|
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,
|
|
4032
|
-
const normal = normalizeVec32(lerpVec3(vA.normal, vB.normal,
|
|
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
|