@fps-games/vfx 0.3.4 → 0.3.5

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/CHANGELOG.md CHANGED
@@ -12,6 +12,18 @@
12
12
 
13
13
  <!-- release.mjs 在此行下方插入新版本区块 -->
14
14
 
15
+ ## v0.3.5 — 2026-07-01
16
+
17
+ ### 改动
18
+ - fix(weapon-trail): 拖尾按实际历史弧长映射,消除运动采样不足导致的截断
19
+ - feat(weapon-trail): refine layered trail controls
20
+ - feat(weapon-trail): 默认关闭刃头火花 (sparkRate 20→0)
21
+ - feat(weapon-trail): 软边加色外晕 ribbon 替代 GlowLayer 的柔光晕
22
+ - perf(weapon-trail): 去掉 per-instance GlowLayer,bloom 折进 additive 亮度 (#11 L-D)
23
+
24
+ ### 特效
25
+ - 无新增 / 删除(仍 37 个)。
26
+
15
27
  ## v0.3.4 — 2026-06-30
16
28
 
17
29
  ### 改动
@@ -294,6 +294,14 @@ function buildDebugParams(manifest) {
294
294
  });
295
295
  continue;
296
296
  }
297
+ if (param.type === "boolean") {
298
+ result.push({
299
+ key: param.key,
300
+ label,
301
+ kind: "boolean"
302
+ });
303
+ continue;
304
+ }
297
305
  if (param.type === "enum" && Array.isArray(param.options)) {
298
306
  result.push({
299
307
  key: param.key,
@@ -311,6 +319,7 @@ function buildDebugParams(manifest) {
311
319
  function isVfxParamValue(value) {
312
320
  if (typeof value === "number") return Number.isFinite(value);
313
321
  if (typeof value === "string") return true;
322
+ if (typeof value === "boolean") return true;
314
323
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
315
324
  const color = value;
316
325
  return typeof color.r === "number" && Number.isFinite(color.r) && typeof color.g === "number" && Number.isFinite(color.g) && typeof color.b === "number" && Number.isFinite(color.b);
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.door_open_gold_flakes";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.lumber_vehicle_gear_spin";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.lumber_vehicle_upgrade_aura";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.lumber_vehicle_wood_chips";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.tree_chop_dust_burst";
4
4
  const name = {
@@ -2,7 +2,11 @@ import { VfxColorValue, VfxEffectPackage, VfxParamValues } from '../../types';
2
2
  interface WeaponTrailParams extends VfxParamValues {
3
3
  lifetimeMode: string;
4
4
  lifetime: number;
5
+ playbackMode: string;
5
6
  orientation: string;
7
+ showGlow: boolean;
8
+ showOuter: boolean;
9
+ showCore: boolean;
6
10
  orbitRadius: number;
7
11
  orbitSpeed: number;
8
12
  height: number;
@@ -12,6 +16,7 @@ interface WeaponTrailParams extends VfxParamValues {
12
16
  coreWidth: number;
13
17
  tailFadeStrength: number;
14
18
  tailFadeExponent: number;
19
+ edgeSoftness: number;
15
20
  opacity: number;
16
21
  saturation: number;
17
22
  contrast: number;
@@ -19,7 +24,6 @@ interface WeaponTrailParams extends VfxParamValues {
19
24
  bloom: number;
20
25
  blendMode: string;
21
26
  flowSpeed: number;
22
- sparkRate: number;
23
27
  tint: VfxColorValue;
24
28
  }
25
29
  export declare const weaponTrailEffectPackage: VfxEffectPackage<WeaponTrailParams>;
@@ -4,26 +4,29 @@ import { VertexBuffer } from "@babylonjs/core/Buffers/buffer";
4
4
  import { ShaderMaterial } from "@babylonjs/core/Materials/shaderMaterial";
5
5
  import { Effect } from "@babylonjs/core/Materials/effect";
6
6
  import { Texture } from "@babylonjs/core/Materials/Textures/texture";
7
- import { Color3, Color4 } from "@babylonjs/core/Maths/math.color";
7
+ import { Color3 } from "@babylonjs/core/Maths/math.color";
8
8
  import { Vector3 } from "@babylonjs/core/Maths/math.vector";
9
9
  import { Constants } from "@babylonjs/core/Engines/constants";
10
- import { GlowLayer } from "@babylonjs/core/Layers/glowLayer";
11
- import { ParticleSystem } from "@babylonjs/core/Particles/particleSystem";
12
- import { L as LIFETIME_DEBUG_PARAMS, a as LIFETIME_DEFAULTS, b as makeFlareTexture, s as scheduleLifetime } from "../../chunks/space-shared-BXV1hc0k.js";
10
+ import { L as LIFETIME_DEBUG_PARAMS, a as LIFETIME_DEFAULTS, s as scheduleLifetime } from "../../chunks/space-shared-BXV1hc0k.js";
13
11
  const trailTextureUrl = "data:image/png;base64,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";
14
12
  const DEFAULT_PARAMS = {
15
13
  ...LIFETIME_DEFAULTS,
14
+ playbackMode: "follow",
16
15
  orientation: "horizontal",
16
+ showGlow: true,
17
+ showOuter: true,
18
+ showCore: true,
17
19
  orbitRadius: 0,
18
20
  orbitSpeed: 7,
19
21
  height: 0,
20
22
  // 默认 0:真实跟随挂点(orbit>0 是演示用的幻影圆周轨道)
21
- trailLength: 16,
23
+ trailLength: 32,
22
24
  worldLength: 4,
23
25
  width: 1.01,
24
26
  coreWidth: 0.4,
25
- tailFadeStrength: 0.58,
27
+ tailFadeStrength: 0,
26
28
  tailFadeExponent: 1.35,
29
+ edgeSoftness: 0.16,
27
30
  opacity: 1.35,
28
31
  saturation: 1.45,
29
32
  contrast: 1.32,
@@ -31,10 +34,12 @@ const DEFAULT_PARAMS = {
31
34
  bloom: 0.9,
32
35
  blendMode: "add",
33
36
  flowSpeed: 5,
34
- sparkRate: 20,
35
37
  tint: { r: 0.4, g: 0.85, b: 1 }
36
38
  // 青色能量,配新武器发光风
37
39
  };
40
+ const TEXTURE_U_MIN = 0;
41
+ const TEXTURE_U_MAX = 1;
42
+ const HISTORY_DISTANCE_MARGIN = 1.4;
38
43
  const TRAIL_VERTEX = `precision highp float;
39
44
  attribute vec3 position;
40
45
  attribute vec2 uv;
@@ -53,6 +58,7 @@ uniform float uContrast;
53
58
  uniform float uAlphaScale;
54
59
  uniform float uAlphaPower;
55
60
  uniform float uBright;
61
+ uniform float uEdgeSoftness;
56
62
  uniform float uTime;
57
63
  uniform float uFlowSpeed;
58
64
  void main(){
@@ -67,6 +73,8 @@ void main(){
67
73
  float band = smoothstep(0.0, 0.12, phase) * (1.0 - smoothstep(0.12, 0.4, phase));
68
74
  color *= 1.0 + band * 0.6 * step(0.0001, uFlowSpeed);
69
75
  float alpha = pow(clamp(base.a, 0.0, 1.0), uAlphaPower) * vColor.a * uAlphaScale;
76
+ float edge = min(vUv.y, 1.0 - vUv.y);
77
+ alpha *= mix(1.0, smoothstep(0.0, max(0.0001, uEdgeSoftness), edge), step(0.0001, uEdgeSoftness));
70
78
  gl_FragColor = vec4(color, clamp(alpha, 0.0, 1.0));
71
79
  }`;
72
80
  let shadersRegistered = false;
@@ -97,22 +105,26 @@ const weaponTrailEffectPackage = {
97
105
  defaultParams: DEFAULT_PARAMS,
98
106
  debugParams: [
99
107
  ...LIFETIME_DEBUG_PARAMS,
108
+ { key: "playbackMode", label: "播放方式", kind: "enum", options: [
109
+ { value: "follow", label: "跟随挂点" },
110
+ { value: "orbit", label: "环绕预览" }
111
+ ] },
100
112
  { key: "orientation", label: "拖尾朝向", kind: "enum", options: [
101
113
  { value: "horizontal", label: "水平面" },
102
114
  { value: "vertical", label: "竖直" }
103
115
  ] },
116
+ { key: "showGlow", label: "显示外晕层", kind: "boolean" },
117
+ { key: "showOuter", label: "显示主带层", kind: "boolean" },
118
+ { key: "showCore", label: "显示亮芯层", kind: "boolean" },
104
119
  { key: "orbitRadius", label: "环绕半径", kind: "scale", min: 0, max: 5, step: 0.05 },
105
120
  { key: "orbitSpeed", label: "环绕速度", kind: "number", min: 0, max: 20, step: 0.1 },
106
- { key: "height", label: "高度", kind: "number", min: -2, max: 3, step: 0.05 },
107
- { key: "trailLength", label: "拖尾段数", kind: "number", min: 4, max: 48, step: 1 },
121
+ { key: "trailLength", label: "拖尾段数", kind: "number", min: 4, max: 72, step: 1 },
108
122
  { key: "worldLength", label: "拖尾长度", kind: "scale", min: 0.5, max: 12, step: 0.1 },
109
123
  { key: "width", label: "拖尾宽度", kind: "scale", min: 0.1, max: 4, step: 0.01 },
110
124
  { key: "coreWidth", label: "亮芯宽度比", kind: "number", min: 0, max: 1, step: 0.01 },
111
125
  { key: "tailFadeStrength", label: "尾部淡出强度", kind: "number", min: 0, max: 1, step: 0.01 },
112
126
  { key: "tailFadeExponent", label: "尾部淡出曲线", kind: "number", min: 0.2, max: 4, step: 0.05 },
113
- { key: "opacity", label: "透明度", kind: "opacity", min: 0, max: 3, step: 0.01 },
114
- { key: "saturation", label: "饱和度", kind: "number", min: 0, max: 3, step: 0.01 },
115
- { key: "contrast", label: "对比度", kind: "number", min: 0, max: 3, step: 0.01 },
127
+ { key: "edgeSoftness", label: "横向软边", kind: "number", min: 0, max: 0.45, step: 0.01 },
116
128
  { key: "bright", label: "亮度(HDR)", kind: "number", min: 0.2, max: 4, step: 0.05 },
117
129
  { key: "bloom", label: "辉光强度", kind: "number", min: 0, max: 3, step: 0.05 },
118
130
  { key: "blendMode", label: "混合模式", kind: "enum", options: [
@@ -120,7 +132,6 @@ const weaponTrailEffectPackage = {
120
132
  { value: "combine", label: "正常(实心刃)" }
121
133
  ] },
122
134
  { key: "flowSpeed", label: "能量流动速度", kind: "number", min: 0, max: 4, step: 0.05 },
123
- { key: "sparkRate", label: "刃头火花/秒", kind: "number", min: 0, max: 80, step: 1 },
124
135
  { key: "tint", label: "颜色", kind: "color" }
125
136
  ],
126
137
  create({ scene, root, position, params, renderingGroupId }) {
@@ -135,7 +146,7 @@ const weaponTrailEffectPackage = {
135
146
  function makeMat(name, tint, bright) {
136
147
  const m = new ShaderMaterial(name, scene, { vertex: "vfxWeaponTrail", fragment: "vfxWeaponTrail" }, {
137
148
  attributes: ["position", "uv", "color"],
138
- uniforms: ["worldViewProjection", "uTint", "uSaturation", "uContrast", "uAlphaScale", "uAlphaPower", "uBright", "uTime", "uFlowSpeed"],
149
+ uniforms: ["worldViewProjection", "uTint", "uSaturation", "uContrast", "uAlphaScale", "uAlphaPower", "uBright", "uEdgeSoftness", "uTime", "uFlowSpeed"],
139
150
  samplers: ["mainTex"],
140
151
  needAlphaBlending: true
141
152
  });
@@ -146,6 +157,7 @@ const weaponTrailEffectPackage = {
146
157
  m.setFloat("uAlphaScale", params.opacity);
147
158
  m.setFloat("uAlphaPower", 0.82);
148
159
  m.setFloat("uBright", bright);
160
+ m.setFloat("uEdgeSoftness", params.edgeSoftness);
149
161
  m.setFloat("uTime", 0);
150
162
  m.setFloat("uFlowSpeed", params.flowSpeed);
151
163
  m.alphaMode = blend;
@@ -156,9 +168,14 @@ const weaponTrailEffectPackage = {
156
168
  }
157
169
  const tintC = new Color3(params.tint.r, params.tint.g, params.tint.b);
158
170
  const coreC = Color3.Lerp(tintC, new Color3(1, 1, 1), 0.75);
159
- const outerMat = makeMat("vfx_weaponTrail_outerMat", tintC, params.bright);
160
- const hasCore = params.coreWidth > 1e-3;
161
- const coreMat = hasCore ? makeMat("vfx_weaponTrail_coreMat", coreC, params.bright * 1.6) : null;
171
+ const bloomBoost = 1 + Math.max(0, params.bloom) * 0.25;
172
+ const hasOuter = params.showOuter !== false;
173
+ const hasCore = params.showCore !== false && params.coreWidth > 1e-3;
174
+ const hasGlow = params.showGlow !== false && params.bloom > 1e-3;
175
+ const outerMat = hasOuter ? makeMat("vfx_weaponTrail_outerMat", tintC, params.bright * bloomBoost) : null;
176
+ const coreMat = hasCore ? makeMat("vfx_weaponTrail_coreMat", coreC, params.bright * 1.6 * bloomBoost) : null;
177
+ const glowMat = hasGlow ? makeMat("vfx_weaponTrail_glowMat", tintC, params.bright) : null;
178
+ if (glowMat) glowMat.setFloat("uAlphaScale", params.opacity * 0.42 * Math.min(2.2, params.bloom));
162
179
  function makeMesh(name, mat, ai) {
163
180
  const m = new Mesh(name, scene);
164
181
  m.material = mat;
@@ -170,63 +187,89 @@ const weaponTrailEffectPackage = {
170
187
  m.alphaIndex = ai;
171
188
  return m;
172
189
  }
173
- const outer = makeMesh("vfx_weaponTrail_outer", outerMat, 10);
190
+ const glow = hasGlow ? makeMesh("vfx_weaponTrail_glow", glowMat, 9) : null;
191
+ const outer = hasOuter ? makeMesh("vfx_weaponTrail_outer", outerMat, 10) : null;
174
192
  const core = hasCore ? makeMesh("vfx_weaponTrail_core", coreMat, 11) : null;
175
- let glow = null;
176
- if (params.bloom > 0) {
177
- glow = new GlowLayer("vfx_weaponTrail_glow", scene, { blurKernelSize: 48 });
178
- glow.intensity = params.bloom;
179
- glow.addIncludedOnlyMesh(outer);
180
- glow.referenceMeshToUseItsOwnMaterial(outer);
181
- if (core) {
182
- glow.addIncludedOnlyMesh(core);
183
- glow.referenceMeshToUseItsOwnMaterial(core);
184
- }
185
- }
186
- let sparkPs = null;
187
- const sparkEmitter = new Vector3();
188
- if (params.sparkRate > 0) {
189
- const c255 = `${Math.round(params.tint.r * 255)},${Math.round(params.tint.g * 255)},${Math.round(params.tint.b * 255)}`;
190
- const flare = makeFlareTexture(scene, "vfx_weaponTrail_sparkTex", "255,255,255", c255);
191
- sparkPs = new ParticleSystem("vfx_weaponTrail_sparks", 160, scene);
192
- sparkPs.renderingGroupId = rgid;
193
- sparkPs.particleTexture = flare;
194
- sparkPs.emitter = sparkEmitter;
195
- sparkPs.minSize = 0.07;
196
- sparkPs.maxSize = 0.26;
197
- sparkPs.minLifeTime = 0.12;
198
- sparkPs.maxLifeTime = 0.4;
199
- sparkPs.emitRate = params.sparkRate;
200
- sparkPs.blendMode = ParticleSystem.BLENDMODE_ADD;
201
- sparkPs.minEmitPower = 0.6;
202
- sparkPs.maxEmitPower = 2.4;
203
- sparkPs.direction1 = new Vector3(-1, -0.6, -1);
204
- sparkPs.direction2 = new Vector3(1, 0.6, 1);
205
- sparkPs.gravity = new Vector3(0, -3, 0);
206
- sparkPs.color1 = new Color4(params.tint.r, params.tint.g, params.tint.b, 1);
207
- sparkPs.color2 = new Color4(1, 1, 1, 1);
208
- sparkPs.colorDead = new Color4(params.tint.r, params.tint.g, params.tint.b, 0);
209
- sparkPs.start();
210
- }
211
193
  const targetCount = Math.max(2, Math.round(params.trailLength));
212
194
  const raw = [];
213
195
  const points = [];
214
- for (let i = 0; i < 96; i++) raw.push(sample());
215
196
  for (let i = 0; i < targetCount; i++) points.push(sample());
197
+ const pointPathT = [];
216
198
  let rawCount = 0, angle = 0, time = 0;
217
- let outerInit = false, coreInit = false;
199
+ let outerInit = false, coreInit = false, glowInit = false;
218
200
  const cur = sample();
219
201
  const mountPos = new Vector3();
220
202
  const lastWidthAxis = new Vector3(0, 0, 1);
221
203
  const tmpDir = new Vector3();
204
+ const tailDir = new Vector3(1, 0, 0);
222
205
  const UP = new Vector3(0, 1, 0);
223
206
  const positions = [], uvs = [], colors = [], indices = [];
207
+ function resolvePlaybackMode() {
208
+ if (params.playbackMode === "follow" || params.playbackMode === "orbit") return params.playbackMode;
209
+ return params.orbitRadius > 0 ? "orbit" : "follow";
210
+ }
211
+ function sampleMinDistance() {
212
+ return Math.max(3e-3, Math.min(0.04, params.worldLength / Math.max(16, targetCount * 3)));
213
+ }
214
+ function ensureRawCapacity(length) {
215
+ while (raw.length < length) raw.push(sample());
216
+ }
217
+ function resolveTailDirection() {
218
+ if (rawCount >= 2) {
219
+ const tail = raw[rawCount - 1].position;
220
+ const newer = raw[rawCount - 2].position;
221
+ tailDir.set(tail.x - newer.x, tail.y - newer.y, tail.z - newer.z);
222
+ if (tailDir.lengthSquared() > 1e-8) {
223
+ tailDir.normalize();
224
+ return tailDir;
225
+ }
226
+ }
227
+ const axis = rawCount > 0 ? raw[rawCount - 1].widthAxis : lastWidthAxis;
228
+ Vector3.CrossToRef(UP, axis, tailDir);
229
+ if (tailDir.lengthSquared() > 1e-8) {
230
+ tailDir.normalize();
231
+ return tailDir;
232
+ }
233
+ tailDir.set(1, 0, 0);
234
+ return tailDir;
235
+ }
236
+ function extrapolateTailSample(targetDist, availableDist, out) {
237
+ const tail = raw[rawCount - 1];
238
+ copySample(tail, out);
239
+ const extra = Math.max(0, targetDist - availableDist);
240
+ const dir = resolveTailDirection();
241
+ out.position.x += dir.x * extra;
242
+ out.position.y += dir.y * extra;
243
+ out.position.z += dir.z * extra;
244
+ }
245
+ function trimRawHistory() {
246
+ const maxDistance = Math.max(1, params.worldLength * HISTORY_DISTANCE_MARGIN + params.width * 2);
247
+ let total = 0;
248
+ let keep = rawCount;
249
+ for (let i = 0; i < rawCount - 1; i++) {
250
+ total += Vector3.Distance(raw[i].position, raw[i + 1].position);
251
+ if (total >= maxDistance) {
252
+ keep = i + 2;
253
+ break;
254
+ }
255
+ }
256
+ rawCount = Math.min(rawCount, keep);
257
+ }
258
+ function insertRawSample(src) {
259
+ ensureRawCapacity(rawCount + 1);
260
+ for (let i = rawCount; i > 0; i--) copySample(raw[i - 1], raw[i]);
261
+ copySample(src, raw[0]);
262
+ rawCount += 1;
263
+ trimRawHistory();
264
+ }
224
265
  function sampleEmitter(out) {
266
+ const playbackMode = resolvePlaybackMode();
267
+ const orbitRadius = playbackMode === "orbit" ? params.orbitRadius : 0;
225
268
  const c = Math.cos(angle), s = Math.sin(angle);
226
269
  out.position.set(
227
- mountPos.x + c * params.orbitRadius,
270
+ mountPos.x + c * orbitRadius,
228
271
  mountPos.y + params.height,
229
- mountPos.z + s * params.orbitRadius
272
+ mountPos.z + s * orbitRadius
230
273
  );
231
274
  if (params.orientation === "vertical") {
232
275
  out.widthAxis.set(0, 1, 0);
@@ -245,17 +288,29 @@ const weaponTrailEffectPackage = {
245
288
  }
246
289
  out.widthAxis.copyFrom(lastWidthAxis);
247
290
  }
291
+ function historyArcLength() {
292
+ let total = 0;
293
+ for (let i = 0; i < rawCount - 1; i++) total += Vector3.Distance(raw[i].position, raw[i + 1].position);
294
+ return total;
295
+ }
248
296
  function resample() {
249
297
  if (rawCount <= 0) return;
250
- const target = params.worldLength;
298
+ const historyLen = historyArcLength();
299
+ const target = Math.max(1e-3, Math.min(params.worldLength, historyLen));
251
300
  let seg = 0, distBefore = 0;
252
301
  for (let i = 0; i < targetCount; i++) {
253
302
  const t = points[i];
254
- if (i === 0 || rawCount === 1) {
303
+ const normalizedDist = i / (targetCount - 1);
304
+ const targetDist = target * normalizedDist;
305
+ pointPathT[i] = normalizedDist;
306
+ if (i === 0) {
255
307
  copySample(raw[0], t);
256
308
  continue;
257
309
  }
258
- const targetDist = target * (i / (targetCount - 1));
310
+ if (rawCount === 1) {
311
+ extrapolateTailSample(targetDist, 0, t);
312
+ continue;
313
+ }
259
314
  let found = false;
260
315
  while (seg < rawCount - 1) {
261
316
  const l = raw[seg], r = raw[seg + 1];
@@ -272,7 +327,7 @@ const weaponTrailEffectPackage = {
272
327
  distBefore += segLen;
273
328
  seg++;
274
329
  }
275
- if (!found) copySample(raw[rawCount - 1], t);
330
+ if (!found) extrapolateTailSample(targetDist, distBefore, t);
276
331
  }
277
332
  }
278
333
  function buildRibbon(mesh, halfWidth, wasInit) {
@@ -283,8 +338,10 @@ const weaponTrailEffectPackage = {
283
338
  for (let i = 0; i < targetCount; i++) {
284
339
  const s = points[i];
285
340
  const t = i / (targetCount - 1);
286
- const u = t * 0.97 + 0.015;
287
- const alpha = Math.max(0, 1 - Math.pow(t, params.tailFadeExponent) * params.tailFadeStrength);
341
+ const pathT = pointPathT[i] ?? t;
342
+ const u = TEXTURE_U_MIN + (TEXTURE_U_MAX - TEXTURE_U_MIN) * pathT;
343
+ const tailAlpha = Math.max(0, 1 - Math.pow(pathT, params.tailFadeExponent) * params.tailFadeStrength);
344
+ const alpha = tailAlpha;
288
345
  const ox = s.widthAxis.x * halfWidth, oy = s.widthAxis.y * halfWidth, oz = s.widthAxis.z * halfWidth;
289
346
  positions.push(s.position.x + ox, s.position.y + oy, s.position.z + oz, s.position.x - ox, s.position.y - oy, s.position.z - oz);
290
347
  uvs.push(u, 0, u, 1);
@@ -313,22 +370,19 @@ const weaponTrailEffectPackage = {
313
370
  const observer = scene.onBeforeRenderObservable.add(() => {
314
371
  const dt = Math.min(scene.getEngine().getDeltaTime() / 1e3, 0.05);
315
372
  time += dt;
316
- angle += params.orbitSpeed * dt;
373
+ if (resolvePlaybackMode() === "orbit") angle += params.orbitSpeed * dt;
317
374
  root.computeWorldMatrix(true);
318
375
  mountPos.copyFrom(root.getAbsolutePosition());
319
376
  sampleEmitter(cur);
320
- if (rawCount === 0 || Vector3.Distance(cur.position, raw[0].position) > 5e-4) {
321
- for (let i = Math.min(rawCount, raw.length - 1); i > 0; i--) copySample(raw[i - 1], raw[i]);
322
- copySample(cur, raw[0]);
323
- rawCount = Math.min(rawCount + 1, raw.length);
324
- }
377
+ if (rawCount === 0 || Vector3.Distance(cur.position, raw[0].position) > sampleMinDistance()) insertRawSample(cur);
325
378
  resample();
326
379
  const halfW = params.width * 0.5;
327
- outerInit = buildRibbon(outer, halfW, outerInit);
380
+ if (glow) glowInit = buildRibbon(glow, halfW * 2.2, glowInit);
381
+ if (outer) outerInit = buildRibbon(outer, halfW, outerInit);
328
382
  if (core) coreInit = buildRibbon(core, halfW * params.coreWidth, coreInit);
329
- outerMat.setFloat("uTime", time);
383
+ outerMat?.setFloat("uTime", time);
330
384
  coreMat?.setFloat("uTime", time);
331
- if (sparkPs) sparkEmitter.copyFrom(points[0].position);
385
+ glowMat?.setFloat("uTime", time);
332
386
  });
333
387
  let disposed = false;
334
388
  let ltTimer = 0;
@@ -337,11 +391,10 @@ const weaponTrailEffectPackage = {
337
391
  disposed = true;
338
392
  if (ltTimer) window.clearTimeout(ltTimer);
339
393
  scene.onBeforeRenderObservable.remove(observer);
340
- sparkPs?.stop();
341
- sparkPs?.dispose();
342
394
  glow?.dispose();
343
- outer.dispose();
344
- outerMat.dispose();
395
+ glowMat?.dispose();
396
+ outer?.dispose();
397
+ outerMat?.dispose();
345
398
  core?.dispose();
346
399
  coreMat?.dispose();
347
400
  tex.dispose();
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.wood_crusher_sawdust";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.particle.wood_cutter_sawdust";
4
4
  const name = {
@@ -1,4 +1,4 @@
1
- import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-CXQFSTo2.js";
1
+ import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-Vgh-cH5X.js";
2
2
  const schemaVersion = "vfx-effect-package/1.0";
3
3
  const id = "build.material.wood_peel_material_test";
4
4
  const name = {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { V, c, a } from "./chunks/rendering-CV3nhygX.js";
2
2
  import { Vector3 } from "@babylonjs/core/Maths/math.vector";
3
3
  import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
4
- import { c as c2, c as c3 } from "./chunks/manifestRuntimePackage-CXQFSTo2.js";
4
+ import { c as c2, c as c3 } from "./chunks/manifestRuntimePackage-Vgh-cH5X.js";
5
5
  import { c as c4, f, a as a2, s } from "./chunks/legacy-particle-runtime-CbJYVxdd.js";
6
6
  const VFX_GENERATION_PARAM_KEYS = {
7
7
  offsetX: "__generation.offsetX",
@@ -1,5 +1,5 @@
1
1
  import { VfxEffectPackage } from './types';
2
- type LegacyTunableParamType = 'number' | 'color' | 'enum' | string;
2
+ type LegacyTunableParamType = 'number' | 'boolean' | 'color' | 'enum' | string;
3
3
  interface LegacyTunableTarget {
4
4
  emitter: string;
5
5
  property: string;
package/dist/types.d.ts CHANGED
@@ -22,14 +22,14 @@ export interface VfxResolvedInputs {
22
22
  target?: AbstractMesh;
23
23
  scalars?: Record<string, () => number>;
24
24
  }
25
- export type VfxParamValue = number | string | VfxColorValue;
25
+ export type VfxParamValue = number | string | boolean | VfxColorValue;
26
26
  export type VfxParamValues = Record<string, VfxParamValue>;
27
27
  export interface VfxColorValue {
28
28
  r: number;
29
29
  g: number;
30
30
  b: number;
31
31
  }
32
- export type VfxParamKind = 'number' | 'color' | 'enum' | 'opacity' | 'lifetime' | 'scale';
32
+ export type VfxParamKind = 'number' | 'boolean' | 'color' | 'enum' | 'opacity' | 'lifetime' | 'scale';
33
33
  export interface VfxParamDefinitionBase {
34
34
  key: string;
35
35
  label: string;
@@ -45,6 +45,9 @@ export interface VfxNumberParamDefinition extends VfxParamDefinitionBase {
45
45
  export interface VfxColorParamDefinition extends VfxParamDefinitionBase {
46
46
  kind: 'color';
47
47
  }
48
+ export interface VfxBooleanParamDefinition extends VfxParamDefinitionBase {
49
+ kind: 'boolean';
50
+ }
48
51
  export interface VfxEnumParamOption {
49
52
  value: string;
50
53
  label: string;
@@ -53,7 +56,7 @@ export interface VfxEnumParamDefinition extends VfxParamDefinitionBase {
53
56
  kind: 'enum';
54
57
  options: VfxEnumParamOption[];
55
58
  }
56
- export type VfxParamDefinition = VfxNumberParamDefinition | VfxColorParamDefinition | VfxEnumParamDefinition;
59
+ export type VfxParamDefinition = VfxNumberParamDefinition | VfxColorParamDefinition | VfxBooleanParamDefinition | VfxEnumParamDefinition;
57
60
  export interface VfxEffectPackageContext<PParams extends VfxParamValues = VfxParamValues> {
58
61
  scene: Scene;
59
62
  root: TransformNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fps-games/vfx",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "description": "Shared VFX library for Playable projects: engine-injected effect packages + runtime. Project-pure (no imports of host project code).",
6
6
  "publishConfig": {