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.
- 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 +14 -3
- package/apps/viewer/dist/cjs/index.cjs.map +1 -1
- package/apps/viewer/dist/esm/index.js +14 -3
- 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 +1 -1
- package/packages/game/dist/browser/index.global.js.map +1 -1
- package/packages/game/dist/cjs/index.cjs +176 -3
- package/packages/game/dist/cjs/index.cjs.map +1 -1
- package/packages/game/dist/esm/index.js +172 -3
- package/packages/game/dist/esm/index.js.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/game/dist/types/ai/constants.d.ts +16 -0
- package/packages/game/dist/types/ai/constants.d.ts.map +1 -1
- package/packages/game/dist/types/ai/index.d.ts +1 -0
- package/packages/game/dist/types/ai/index.d.ts.map +1 -1
- package/packages/game/dist/types/ai/targeting.d.ts +25 -0
- package/packages/game/dist/types/ai/targeting.d.ts.map +1 -0
- package/packages/game/dist/types/entities/entity.d.ts +11 -0
- package/packages/game/dist/types/entities/entity.d.ts.map +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
|
@@ -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
|
|
1618
|
-
return { frame, nextFrame, lerp:
|
|
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:
|
|
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,
|
|
3906
|
-
const normal = normalizeVec32(lerpVec3(vA.normal, vB.normal,
|
|
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
|