@fps-games/vfx 0.3.3 → 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 +20 -0
- package/README.md +5 -5
- package/dist/chunks/{manifestRuntimePackage-CXQFSTo2.js → manifestRuntimePackage-Vgh-cH5X.js} +9 -0
- package/dist/effects/door_open_gold_flakes/index.js +1 -1
- package/dist/effects/lumber_vehicle_gear_spin/index.js +1 -1
- package/dist/effects/lumber_vehicle_upgrade_aura/index.js +1 -1
- package/dist/effects/lumber_vehicle_wood_chips/index.js +1 -1
- package/dist/effects/treasure-attention/index.d.ts +1 -1
- package/dist/effects/tree_chop_dust_burst/index.js +1 -1
- package/dist/effects/weapon-trail/index.d.ts +5 -1
- package/dist/effects/weapon-trail/index.js +130 -77
- package/dist/effects/wood_crusher_sawdust/index.js +1 -1
- package/dist/effects/wood_cutter_sawdust/index.js +1 -1
- package/dist/effects/wood_peel_material_test/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/manifestRuntimePackage.d.ts +1 -1
- package/dist/types.d.ts +6 -3
- package/package.json +2 -2
- package/tooling/vfx-cli.mjs +23 -21
- package/tooling/vfx-package-resolution.mjs +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,26 @@
|
|
|
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
|
+
|
|
27
|
+
## v0.3.4 — 2026-06-30
|
|
28
|
+
|
|
29
|
+
### 改动
|
|
30
|
+
- fix(tooling): use @fps-games package name
|
|
31
|
+
|
|
32
|
+
### 特效
|
|
33
|
+
- 无新增 / 删除(仍 37 个)。
|
|
34
|
+
|
|
15
35
|
## v0.3.3 — 2026-06-30
|
|
16
36
|
|
|
17
37
|
### 改动
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @fps/vfx
|
|
1
|
+
# @fps-games/vfx
|
|
2
2
|
|
|
3
3
|
Babylon.js 共享 VFX 特效库。导入即用 + 项目内调试面板。消费模型 = **模型 C**(runtime/类型来自包,本地 `import.meta.glob` 装配注册表);发布 = **做法A**(dist 提交进 git + tag,消费端 clone 即用,无需 build)。
|
|
4
4
|
|
|
@@ -15,12 +15,12 @@ Babylon.js 共享 VFX 特效库。导入即用 + 项目内调试面板。消费
|
|
|
15
15
|
|
|
16
16
|
```jsonc
|
|
17
17
|
// package.json —— git tag 即版本锁
|
|
18
|
-
"@fps/vfx": "
|
|
18
|
+
"@fps-games/vfx": "^0.3.4"
|
|
19
19
|
```
|
|
20
20
|
```ts
|
|
21
21
|
// src/assets/vfx/index.ts —— 装配注册表(模型 C)
|
|
22
|
-
import { buildRegistry, type VfxRegistry } from '@fps/vfx';
|
|
23
|
-
export * from '@fps/vfx';
|
|
22
|
+
import { buildRegistry, type VfxRegistry } from '@fps-games/vfx';
|
|
23
|
+
export * from '@fps-games/vfx';
|
|
24
24
|
const modules = import.meta.glob('./effects/*/index.ts', { eager: true }) as Record<string, Record<string, unknown>>;
|
|
25
25
|
const params = import.meta.glob('./effects/*/vfx-params.json', { eager: true }) as Record<string, { default: unknown }>;
|
|
26
26
|
export const VFX_REGISTRY: VfxRegistry = buildRegistry({ modules, params });
|
|
@@ -31,7 +31,7 @@ const svc = new EffectPackageService(scene, { registry: VFX_REGISTRY });
|
|
|
31
31
|
svc.playEffectPackage('thruster-flame', {}, { parent: engineSocket, offsetIsLocal: true });
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
> ⚠️ 三个最易漏:① `@fps/vfx` 要加进 vite 构建白名单;② Babylon 要 `resolve.dedupe`;③ glob 只扫本地 `effects/*`,每个要用的特效都得建本地桩(re-export 库内预制即可)。详见 [INTEGRATION.md](./docs/INTEGRATION.md)。
|
|
34
|
+
> ⚠️ 三个最易漏:① `@fps-games/vfx` 要加进 vite 构建白名单;② Babylon 要 `resolve.dedupe`;③ glob 只扫本地 `effects/*`,每个要用的特效都得建本地桩(re-export 库内预制即可)。详见 [INTEGRATION.md](./docs/INTEGRATION.md)。
|
|
35
35
|
|
|
36
36
|
## 开发
|
|
37
37
|
|
package/dist/chunks/{manifestRuntimePackage-CXQFSTo2.js → manifestRuntimePackage-Vgh-cH5X.js}
RENAMED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 = {
|
|
@@ -38,7 +38,7 @@ export interface CreateTreasureAttentionOptions {
|
|
|
38
38
|
renderingGroupId?: number;
|
|
39
39
|
glowLayer?: GlowLayer | null;
|
|
40
40
|
disposeRoot?: boolean;
|
|
41
|
-
/** 经 @fps/vfx 包播放时为 true:root 的 transform 归 service 独占(placement/挂载由 service 管),特效不写 root.position(G6)。 */
|
|
41
|
+
/** 经 @fps-games/vfx 包播放时为 true:root 的 transform 归 service 独占(placement/挂载由 service 管),特效不写 root.position(G6)。 */
|
|
42
42
|
serviceOwnedRoot?: boolean;
|
|
43
43
|
}
|
|
44
44
|
export interface TreasureAttentionHandle extends VfxEffectHandle {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as createManifestRuntimeEffectPackage } from "../../chunks/manifestRuntimePackage-
|
|
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
|
|
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 {
|
|
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,iVBORw0KGgoAAAANSUhEUgAAAP4AAABNCAYAAACGyNmKAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAgAElEQVR4nOy9S49sWXYe9q219z6PiMy8eR91q6rZD/aDXQItyZKrbRiaiIIB/QPNDMMQZPNnsDXyxBNJIxKCBgY1sUcGBwQsAoaHAtgTAxRANLtJkWZXdXVV3Xsz43HO3uvhwT4nMiIy8t681Wx2N5mrcCtPxHnEiXPO9621vrX2DsK+/Yt/FwF0AM6mfw2ADGA1/Rvwb/+54MEe7MF+qS3e8b4CKNNymV4/2IM92N8Qo1vv/It/16ESQg8goIJ+C0Dwb//58Nd6dg/2YA/2MzE+8Z5M/7YAVs+e+PCVizP98L8u+uFv/3b66z29B3uwB/tZ2C2P7+6gf/kv41eufj09f47mvGsOtrkesl8N2QHgAo/kk6utP//gxS7v/95v/mY5PuaDPdiD/WLZSeB/53d+J72nv3L23uMv9TZcL9vUpZxLUjNfjaLmVjakRsWlWCoIkM/X2R4txjITwwUeVTJ4/0c7IvjeRx85vvvdB3HwwR7s52y3c/zvfjf+xuU/OPv7X3n/LKm/23B6f9nGpbOnXMSyk0iWvM2mBhqslFzIi5rl7QDJqpnZRd0Kh06uh2Jb07LN6szXGQAeiOHBHuzna7eA/+Fv/3Z6Tz54+u1n7ZeenPXfOm/S18+79KhJHFRhg2jJRXUQUQPGIprFkEVlNAuluIwqPDrZMGSzkm0sKMWExmKWh6KmjRUuLsWbwgHyYpvtFDHcSiMeiOHBHuyvxG4B/5/+r//X8sl5fO9rjy++8aUny7/7337t4n/82juPvvryeo1B7GqT5fPProf1aP4j4v5HxYqJuoq5FFEr5jKKWhYXsZJFkBU+qmkuwqNAsgmPYB83oyoHbEVKGQbIfYjhtfrCAzE82IPdyw6A7+70T/7V//noVy+fv/eVx/3f/crjxT/47/7Ou/+994uv/nhwXxDRRSQ8bskftUxiwKvVSrejbF+uhzyIXg3FP/98s12PxT8KTfexmKoaVNQ1q2oRl1HVcnERr8Rg8FxUR1XOMzGI+yg5j0ihjDnnYYBk07zZjh7OYpFBy6wvbIv6AzE82IPd36K7715853d+J57jXbhIExhNG0PSou2nwfF/fzrQi+y4FkMBU6Puz1rG8xj5nRCWT5vu7OkZnny1D1/7Lzumiy5iyIqr9Vo2Y9m+XG3KVvFqLP5pVv/x2vCxGtaqrupmuagWdxFxHVVtGEUU7aiOUqQZasSAYrQcVWiUMx9NSrbAeTNkU6ExW5s3cXTrOVNxubxYlM2nCwwUMgB82D3OV9/93ZPE8L2PPqoX4oEYHuxvgZ3s3FMt0R2JGQC5Fwc+LYS/3BZ8ng3FAA6B/nSlnrggUqQk5okJkYDE5E0GvbckfxoRny+786f9Es/O8eQrHX39cRdw1kash2yv1htbj2Vztd6WjfjLodhnRfDxFvyROQadIgVR02IuuagWcxtERLUdRSzrWZvVNOeCrLQYS/YM4lFyHofLVkspRYRGQ1vuIoYP8CU/67blgRge7G+D3QL+q02bvtwniEowA8yAgoCXSP5xLvTxRjFMDX1xZIoERFIkCp4Gp8SExEAsih++IooMJFYkJtRlQtMFX/YJz3ui591ZeKej86fniifJnzxu6JtPuog2Mq63W3u13up6lM1Q7PrlervdSvpM1D4SWv6luEspZuK+I4ZRRMdKDlq0HYtYVtPsTOM4aHY+zyVLNtBgXvJKgjq8bFacC3fyPLJaL7kglcuLhbz4qHVvmlKJoStX3/19v4DIJ1dbB4AHYniwX0Y76fGNhUQ9AMCqPb8QImwVuBLDy2LYOMEA8FBFgohgvBYKVMEdCOBRERmITEiheJzWpeLULBiREjV99MRAehLQtI3Frgm9wFMHulD4E+rpWdfHd9gvnizk4v0njMfJf+1xG8AMrLZZrkaVF5usWfTli20Z1OnjqzF/EpX+YhYe1V1rpAAtJjqqay4uYpqLWBb1sZz7qNZm8zAWLdlAg4qXdy9bK2ZZhUeFl80weL9cDo/eWRqFXrbXl2Wb1T/Al9wQM777+5iJ4fkH39jTF1YTMfzGAzE82M/dbgG/bwK5USQiAoA2EG2NUFIkMaAYMLrDAEDrs8zuoBZOKyMmAgGgwUAEMAG8BQV2BBYEAuJPnMJyhcigyIT0Mnhk4tQFj/2CmkVjTZO8WTRhuYannmjRJG0+BacEb3roI0F42rThaUC4bBa4PEP/9SfOTxp847IhUndcDSIvt2KbYpsXo+SN6KsyjB8X9T+H2mfFxERcxDERg0suxXKtUIzFXMtUqhRFVvNMtNxstCisH1VKGbtHeSzrMpZlKVIkNLEIvDz/lefy8vqVk2nZ5t4/ANywrcTw/jvyybr482V6IIYH+7nYXaPzYIbIcye/E5wY6KP4OkcHYA5g0gXNAb72qUIwvTmRAgFAriRAZOBsIAB8pWACqB8R1pECA4EIcXllIUSKDIpt54GA1ESPRBzblhoK1lzGELqGziX6+QWsK+CeQJHIjYDQQs9A4R1K4VlM4TLi8tkj4CvRnz9p8GsXDWFUx6tB9Tpbfjmoroa8Xqu+XOfyKWX9IVQ+VzerGoNpURIx1bGoZu1EHFlM81hcijajEw3mPqrwKF7GUVjf689KAcooXkZJpWyC2CJmiiKXjrJZv8IQmwwAH5zD7brP+O5/xMX74YEYHuxnareAP6y2sSxi2H8vEMAnx/NM5qgRwGxbqSSwuRnNS+O44wSSfPO+JNB6AFquRHAFDsvRa7SwBoPABAQmcAJiTIgfRQSGhzZS6kJITW/RG6RFRMNAIq/RQ5soBkYHRiSgG8mbBHQMPGsIz2LkJxHdkwb07El/9s3o7z5p8O1lon+0Lo6Xg+ir0ex61GE15vVa5OW4HT6Oju+L21YVKu5ailk2K0XNsriMklTBYzEdh6xm4FGh2XC2KaNmBB+3LYlepFw0lVE2RXQpZVnEomWKrVx2sWy2WwxZCnCGD87P3JJm/Ov/iIv2kBhmUvDf+scyBWoP9mCvtTs9/mxGzA3gLYPIUiUEFYDv2HUG/b6NI3ZFw5LrshQgJqAUEAVgBLQRwAHaCKFhIBt46Q6JREmcCU4YiQmgxGCCMxECEYUYEJgQ2+iB4TEkigwPXaBIhNQFtGiq8NhGRARqiT32AQ2TpyEgXbKxRjqPjuct8A4FftZFftL4+XvL/vxp4+9etvR32kC/scpmV9uir0bRVdb11uzVNtvnZcgfmeufZyUpIpPw6JPw6DJq1RrUfBTv83YUVTwai2pmarZFkY18GIVV+5SLUhlVylBCcVc1a3Lpx3LZPZUX1yt3o/LB+Znj7Bzf/jd/Un7tX3/fB/lUAOCBGB7sLjuJ3sBEIdx4eALQZexV/I9szASk256eCjDsxQIlH+4ntU3fp78QAKFUYsh1P8sgwqaWA4oBicEsBhCQAtGsKVA9z9BEIlRtgYjATfBAVLUFJuwIgshTFz3GWHUGBtJPEkcmb7uA78eAGAjJIxLDExNSExCZ/DwS3u2Z3gkcn3ZNfNqie6/lp09a+sZlSwhEuBrVrkaztdG4yrpdqa9Wg3yuw/CfTeUvioiJkapDc3EtXjWGUaG5mIh7Lo48qotYyiDfqmIsoAykcT24vtu3uSDlrFKKRSm9KhOXwo8KpXN5cb12p1A+eP/cbeT87X/zJ/jwt38kn6xrHvZ8+WMFPsT3PsJEDHgghr8l9lqPTyDqA9FoIIpwaxs6AD+LYT09KfugpwnIM+j3AS9Ho3b16PXIAE1prDoQtT6VRafaokOD1s9aD4YQmednNbCXdZ7aEev/mQGKPAmNlSS4qdolE4gBxC4h0KQxpFCrEU2cyIJRS5SEpo+eMnNaBv9jBpqQEBlomuAhAC23iInx5Czi3UXEs5jCOx0Wj/u4/GbC04uIr13Es39oAK6z23WBXRUvG6L1ZrDVar35zPLwx6L+WTFVUag6VY3BSUZRzVLJQRcYs9k4wlWtyXDeFgtZQCM8jUrIz0LSQk1RU9kqF3fT0OXtI3XfrqIQf1m25WP/4H13W67zt/89KjE8Vn/+4su7PO2BGP7m2WmPT0QEO7jDCcAB6Ac7SOtB2bEOdAD6GfDHYAduA17mKEF2wiAAoEzLPn2cZ2BGdXSCFDemSW2cVvHNqXskJwkMUwPXMIY2lVjmh5ji6EwApwAC6nKomgOHGhEEJkQmCm30+IKQGkZgQhMjIhNiF72NW2qYkF41aGKkLrVoekbbJU0auAV5G4FnHfBeQ7hMCE8aio8adF9dhifnz86/tojn/1UxYKOQdXFZqet1wXYjdrVabX8y5uH7Kvy5wFSMpIhZMRdxrq3Q5kWUcoZLWSxyUR+VYxGjTITNJrs6eLQl55LHskGngiGX4UwMIdOZyWVsZYPPMSBPwqO7WVu+/e+BD3/vR/LJVv35iy/vCY87YtAHYvjlsDfl+ASAmHc9/XSwbhBCAPByfqfQAeCBQ9DfBfa7zPcEbDWAj5INIQZ5JYr5zHYlh+mlOpzNAfBuf/OJHKj+Z9OkgmMBY0L9lCoQCiiwM9fXoakXIzCDY6RAQOgiQmJKzIgpINAGqYsemdDFllIbQkwBbRe9YULXNpQI1LXJGgu0iIQ+ghZnsL6A3zmDXQbwJVF7ycC7PS2WkZ8snp1/vePz/2ZU+NZcNwrbiI/XxYdVtlfrsfwoj+MfjYWzkosoLKuJTFFDNqrRgtM4iGrpm2xEo1qX7Zw2pXhBDONWgmhrueSmrNSNEHJxE6OYKQW9pKZssMVQb6h/cA43yxMxuH6yhT9/AcGHwPf+056/+B/oYe7GXxB7o7h3pw1TaP8SwGaKDk55+X2wvwnowI233we9GxAY8Hn/fWTvI/72Yj3mRAv7BDEdwtnd8w2hOTA1JtTXBAZHdZ2EA8rMhCooUBTnGIBtQUiRGJUEEK3EVyFxCohxxBwxpC4iEKFJEWnB0liKTRPQdME7JKSrQIs2Wvsi8DKSdwlYMlmzBNoMaiL8rIdeGvgiEj8OoGXA4ksLXizOw+OO09cTL/7RoI7BULbiPijG66LbTcbVKuc/WY/+w6woimRZTUVJi3MZTXVUSFGoEo3ilLcZqu65mGfmZluKZIs8jmqqZyGX7LJSN/UweGnMKOXCg1w+dXnRwv2HKh/EeknNzgp+131oUIXHb1Su3RHDAyn8tdpJ4McUQU4E3/PwDQhrTIDeq/alEQjTPSu4DfhjsO/n7wDA6Sa8D3sBBcUb8BNPHn/6XMdNbE9ewe9Ul99kexkBAMBACEdUYTcE4exQQQ1ggzvMHQwnAiCEEJ1BgBZzCgwuUk++zSVsmsRdAIIVZoA35KFhZ4sNt24ceWxD20QipBgoLIK0ffS2BG7a6AnM7SP2bhtDh6Cxo9ATaztSWPbIZwneCoVEQL9EbkfEBcMfOYWziHAZyLvgiy+3adExnrScvs4EH9V9NMgoXtZA3gz2ci32+avV+BfXlv6iiJkCKkaSzbQ4tBjJqI1mYxHTsbiXMUUR96zmGdxsi0hB9HG0Tt99VErRULJ7KZ6k5KysLuXMC7nri7+Ee9PLB4/h9goFv+u4eAT9ZFvvxQMx/GztFvDbVNHnTAEAElMYJvw5UKV9ncAve155HAG2OhP/1u/O2Y/N9lOB12y3ayY6uv8z4J1u7XKnHW9qe+/wCRLY7TclsAaa91ARAxFIFGCCUWBEdhJEi+KsAgJFNC7EDMogClJIAjERbZMUJvIQGbQmhCZ6iDSmNliAU7vpOTJZatkjk3YpUiTkdhmtQ4iJ4T3F0F6HbrGgMRL8gsiSwJcR3giFxMBigdxmxEWAL4lwxuBFoKYjNE8e8aIFvty+0/w9d/hgrqO7joZxNC+D8ItVLi9/8lL+ZEPhsyKk4lB11mymxVhGVc3OZRRWhebiMWeBFvdRXIv3PJhTNsRRg5dnDDUTycll20K8uPapGR8xfOuQ8S/h3kA/eAy3iILfd1xIJYaZFADge/8JD6TwBewA+J/88eP45aeTuEc2V8iw16R3YyLAqMBKCeMIjJP4NoP+FOD3w3d6Q5bhJxrUDDcA5wNF79Db39f7H9sx6G99/hylnCAZA9UZDdRIDE4EN3WfuIIlRsCcEoPgQCNCgRAyEUVGIQMTg7ZKnNiJmCkymK+dA3kI7KlhD8QIiZ1fMLqYLDJ507JFJu9joMCEbsHWI4RuwRIZpd+G1AtTImjfkkWCLwZwJPgiwaKjjqok+GUPvTDingh9QLOMaJ60OGuXzVeaJ83fU4dn9zIaLIOGUVQG5c9/srWrFxv+f7eKUQ0qRCIKzUpanMrUwSRiXrJxKS3KKNDiyAgYdHQR1qzAaO5lk2FFYxl7iG2zGDeFEvRyAdl8DAyh/u7Dh+9Drn6/VpovBIb3cfDgfO87D6Rwyr5Yjp8VgBKgjrEAJjUPL1ZDckIF/Snwzva6dfukoHvFAwYOuglmkW4f6G8C/esCAwO9EfzHoN/z/gfnRQCMqJ4LwVydmMyzgjkyKUULph4JVAxEBAvsFAhUjAACR0YIAUREksykBAIMHGFMhA2LMbGHRAhMFhtGDOxhRUjO1naBAoH6ZczhU3gXuI6eJKBD4L6nEgnou6AJTgnki8zcNrBE8FizL48AFg00Ab5gYEnkS4AWjLZvQntJWP7qskUCfr04PAOycdetet6obQfx63HjH/94CN8fFVm9RgzFTIuzZlXJC5ZRTBWcByEtHbI5srqVsYsFjhGMvFWoLlHKFjIGiAD6nKEmkP4RyvUrGAD7PMMe9dAP/4P7lcAvBPaNb8B+uL25v3/44d/eKsS9gH+yWXdUx3aoD3meQD/bMejdjnp69yycOPopUgh8QyoAAPOTnnc/7P8iXv914J8/zyY9Yo4Awj0eHnMAtZHAoeZEIEMdXEAERHao1QkNiECB3YrBxcGBQMZk2WulITBzJBdyhBhAmUCJKGYDM4EDI0RSFgUH8pgVKbEz4DExUoKHQCUIexvIuhVRx3WIdduTxoE8ELxv2BPDo4NirqMrFwneCFuEo+3JIuAJ8EDkCbAlnJZMWIJoEah5Fjl1Lc67M3wpAv8wAzaYy+jQrSJvRIbB6Gq70T//eBv/P3GUYjBxiJhrcZaspoOYFIcqcxZF2UaoAmKKwXuUIhAj5OcJUhzy1QVyBnQVYH1B0QjFGvLeBniR4U0D+c4fwD/8Dw4M0IkUZvM//LA+sX9TieFO4AdmMN+AcpffF60h/r55qUcqNgFvr/Z+F+Bn2/foxySwv25/GeYwJrCdBugXAfxsbwL9tOwG0D4RnCKhY9sRxp6gaKhaqTtAVCUMclR1FUAk93lEFAEUCW4Ol0oiqgqO7ExEQgQiRojmIkQp1gfXGngZnZiBEN05EGKq/BYjERM5N25tIE8bTAQBD5k8BvJI5A2TRXIkYe/VPBB5zOTLRM5wJCJEAJFhbfS6HjCGI4GMCd4w2QXViKGH8zJy+zRy0xPO+yV9iQk2Omx0l2yetw7ZFBm2xT99ucF/fqXN52NxU4dKjRpkNGgx6KhQA3IxFFGIOLICIg2KOoobChNKXkC+vIAWh1gDWQ9w66H4EewbjyCbDYAA+Sf/D3Bd4B/+Xm0gOREt/FITwy3gL/oebDffpmVQ9psc3+dlE4cqwUuNZYsB4rW7LmBavuNTfW8F7YH9dSQw70fASZQpdrX51wK/nu1pe12Yv6f0AwDNHp+nhiGGn6wQ7I5NNx88fY4biAIcBjhNy7vvM8XZakSR3c1BgeDqE/gNFGqVw9XJqFYaQnSY1mOpOYgcXkAcyE1rQYTEYcZgOIkQiAhRHBoJzFS7HIU8wBEDgYmQakOTp2wAwQPgTSBPSpYSIZA7O7wN8GDkTYC3wT2gRgIRcBbzwPAEsobggWAM8ujugdw6wM+JcEZkPSMuQOF55Kbrcd49wq8CsBGko1oZzWRrGLZi6zz4Zz8pzV9cCbZFYFL5ULNWgE+kUMQgBIwFEEONEnILNUfxAC0FgjPImCFjA7cGMgRYADT/CPjGI+jn1/Cugd5BDAf2hx/CflGJ4Y7OPaXaBF/P2xwIaa8SNqpjFIK8yZ1jD+R3YOo+JFDda102oHr6PfDPQH+Tuv9XfQ9mfTHsAfquVGH/lA0EchAfMpS73/IgFA67lmj6LJqiMYrk+yKDqSPEqfhgjjCRhRmIA0GzgQO5m4JACJEQEkMU5AQPVtuaGE5GBAOcyWFuREQYBR4COTNcAIc6kgIhkLO7E5EHcme4ty15gHskePX48GBwRo0IOoJHcg+ABSKLcA91nbHDW3ZjkAW4B8DPiGzJ8J4Y5xGhcyyeNKFrF3jy9wnfMkCzeRnNZWu+HdU3JfurFzm8/LTwq01BrtUIqGBHDKoCEaBYCykKoQ55dJgIBAuUUWDKEBXYe48gkqGbBCuAWiUGoy3svRV8UEBDrTp85w8OiMGPUgkHfn4RwxcS9xyZcCeSUXW/W1H4iUabg9d0QwL7BOBWt9knCGM6EPnmzONtSno/pdXg4wjMDL9XqlBf32x3SkQ50g3oPqnEG2yOGk6uU8eEeYQINyFQBLQ4IZGLAkQ1AlBzMqtFjBCqxlsUFAKc4AiBnADPo3uI5Az3JpKvxZ1BzoAHco+xphVhHno9cSiTo2cyVvdQicEbgr1wWHBYAjwSjAFLBEsODQRb1IjB+0DUB/TnCW3T+uWvMn01VeVIxyzjKJYHw2Y76rA1vn6h4fqFxNVoEDWIOKwYrHglhylaUAckK6REqDNEW6gYpBgMgDzuYQjQVYHligCzAFOFtZUYsGhhPxnhexGDAzti2D0T31sB/o/hPytieDvgM0+KPgA9yvNlOmfdA7CfigheQwBmVcmqie60Od+Af7d/zYdh1R+BJhZ4U679ujAfuKnnv0nZn23f478u1L8F+tvbEP/V3GQOBJrOhdObxQ6bAE1UQ3yEE9sIyMkRI7loBT9Q0z7VmyKMKxADoAriAIdX+SdEYCgOAjxMYCcHIDO/+fyep1j/XruDUbftmJzcvafaqhUIFgzeEDw4vOWaerxy2McG7wgSCJ4IhZycDdYadEHAIkZfRISOsDxfYtkQniQAU1+oDFnGUX3M4O06Y7sR37yQeP1ZxlArETDRmk4Ug2aDqUHEoOoQdWiJNarQFuI1q9Ii0GcXsKywywBzhb0yeFjCJ2Lw91bwFxm+VeDDCPvOHwAzMeD9w2fmp40Y7gT+XMO/deT4mrKo2aGwd8v2z/0oaHCfavNH6NwngXkfsykmZgJN6v5+hHFXSe8+1+ieoCfGXo4/7XNq35/GU4fTwL2X92f4HOoDAAWcqDveNEPOZgaQOoVIbnqz/76pAbEWWab2TnKiuq3ajTwzn7woEMJ099RBIEwzO+xaPWZXUaS2QKRKoE4E35qjZfKtORomZ4czqsdvCM5ag6YuwIlgscpM3k2yTAsoE/zaamRA0749IFxdh7WANQ5qQ7Q+Ii6B80cdzgKAWAnHFJBcdMzqZaPIg/lmNWL4TNL1q4JcOxzholBx2GhQd2hRqLVQtbqsDDWHPW9hajALUBX4ozPY+wU2FHiJMKvCo7HDYfDNTUXCv/MHAAb4P/ujCrj//dcPn70f/OAHAIBvfetbJx+P09Nrc4gMBoGo4RpJ70Q94LB0d+r1ydT/DXiqHtzBTDCtf2/tO5HC7J7cp9t2opHnFPj3g4a7bPb6d4l086HsngAEDnWAu2yPX+8Kx0+ehzhRrF+UZ09sR0IhKn/SCW9+bHyUdphWjw0AKpUQ3nQMs7kieeJ8521mWQZHf6mSxL6YDABSh094mfTdluvtHWoa4OzAVkENA6ESg18D6AjWeH1KmIDGoVMkYdcELABlh7PDCPDkEAL8jKDklXyYoGfulJg8MCMFipfwkIg6PoMHwvuocreMRUtWl0F9XCu2r3LYfqa8WQlkEh5NtXr+0sDUoAJYibACmARobmEOqLUwc5gHWClwWcC+vIAXh1sDGwL8ssDPCP6b34M/BvzTEfjKr8A/92/iX30LPv9uxjiOaNt258dvAb+Z4jgiIuaaeymmaNbD7d77fAJGbxXi7xlPIN7/e/vgVQWbD+juJ4H8RZT9GiLfP9Sf7U2NP28iCH67+qOb70iHTgCRpnM5efn2Pzbej2AOBEMFhQjoFHRNnh8xTGkD9ojCQHE6F5u8/myzdhuPychrUygdrZsJoJkIbSpwAg4M1bN7okoMDQPB6iajg3oCkYMToWynxsnWoWzw9QR+msmhgh4vAWoASQCxA2uHkLqjliidiGxJUEz9pLGmIAgxoI3gBdA9NWvpHI8iuTtgYiijqI6KvDHkrfj4eabhhcSyKRD12gonPhGDw3QiC2uhRWuB3ADLAsMClmpVyJ42MCf4WQOPV/ALgf8vfwrvtvBRgSfPWv8oA//sjxz/x39B/sVH530he8PzvQvh581n730kdxMR3B3stwGsOJmn3nkq+/sbCDa12hFeSwD7+uO9Wn33lf99241B8EOvz+RQJ0Ty10UW+x7/PkZ3nOsc8ptVgJo4hXtoBDPoVW+AqlM4zwFQc5rThaon3C/yAKqPOSaGrKAU4GOdjOnme+Hmlo4GahjElVxc6noXB6cpgJ2+mfcEbOtXzucAVgYLAYDD2EFiMGaYW92vCTQHvzYoNHAFvgCOKWrAFFEQscFgVLsfnQHEFH2ZEC8IgYHu1wgXXM+9Vhscmh2yVZf1aPLRENYvMsogMHGYAq4KzwrzGsUYATanMApYUfizBTwKTBM8R/gnn8I/38Kg8A9/z3+KYbmzNXCUI/jNXXa3bL49x38nm13UHLrPccoM9N1h3qBo7IP/vkQATKD06oHfJjW/y+PPDybfAfrXmJvTcSnvcH2t498X9DPY9tMAkwNPvvPUs5mAQrx9ziZORIRT6w62m/J9M1C447vrJOHE18zlemxFgeYN99QBKINDVdwPkkUAGBmcDLo2eM9wJYTPALtkQKYQv0xRAzOxB88AAB/fSURBVDmsENFFHVJmsJrzFwY6hY0M6rzK2sx1PWqyadOMNl5oRwrGgIt73d5J62Sy8EhAqqkLtZHikxji18/RUg3ydKo2aHG3rZiss5VXkvK1wLJCJ1IwmsjIp32GKjDahqDxU1jbwH42Hv8k6GfbB/vRs3BXeL8P+v3XRrcn5zi2N4F+P+y3Yya6w+6h0k/HqynHfbSA13DZfmh/a92ex98PyW9tN+X4d3n8/f2Pw3KgksQsrYQAcIDblPOrOSITxGoeDFRAh7gXQdRrSzHA55SAD0J/R3jNdSrVi//UVhxhSTsxllpHdr+5Jgf9oZP3XrgHAGZEghu5y8ngCni0GqIXc4HBGTSr3Ba97oP6tDrX629ZafbWzgSQVqASTVWP+r7TlEZEhkeuvVsdES27EJ93IRDQUm2nUHWyPJUXtwpdKeRlgX68giSDRgF1LaQzHIb6zy96Aupkm0S1iTwRaDf7lQN+6rFJXC9TPrHubexNSekpm2cIu4sA3sbj7475mg484H5i3Wz3/UpHof6dm02deyc/6kjc21f16/sOJ7ol/B3vv2+qTvFEVFFzfbrF8GIV3ABgMoFfK2kw11IfyCly7STcJ5g5Qjg43iQppVB/nqFh7CoC+9acuCSztx8d3NJpyXkkpNYxAkBPCBlAY4dP8gakAMzU3aQCObgLAMv1uA6lESCf6ktOIOe6zuBwnSKCCdiGCdgzyPfWeZjLllzLjCC41mEb/tEamzaA3l+gye42ZpcUCE0kj6jXeRkRLps69wsA9yfwrND/7U/x6scDNJUTY2PbyMT1Z3R26ZjMFHfqyu3u0N7aXe39lB0n12+no90ydhxND1htBvx98/39Q7yuAw/4YiW6U3X+/Yd8X9qYwT3l+AAOm2/0ECGu1eu/zuMDb65CHO8/t1Xcuf1eyK92O0oADnP/fWDPX8GmUH/edw4WiQ5JYD/ff12on/j2o9o6AqgO113SYVTXOspISJd1GhnfOqyZ1mWHgODbYiMAU/A47TsNSqkVgdYg5BW4Xv8aeRXiiOBwKPP02OxVM+Z/06xuU2UYu1+gyupVF5i+a3Dyd3vvnIDi7h0zty1CQ0RM8MgEngikWC0nDgZbFcinI/TjbW2wJ8f9xb0DT58mHpI7Bskwbrrp6t64ndef2NV3iv397RToZ1PUusV9xucfh/xvKOfdy45D09fl+Hd4/LlX/+DNE3Uy12km5BPgnUPoU517x7n9nPfXEL32UilOe/27TOvcBEjT5+qe2m/TREq2N9Ay7FUEDsP/2+AngNLR1z/l7YEJ8NMDNwH+VioXDN1lwDC/jgYZHQLAREym0h/OwIVQj0BVu3Q4mAG3gIjJW2PK7ZeAbwm2rGlEoFpyrF6dKiGY1X4CcO07wNRfEAhUE/RS+sDUU6A2EhoGp9q06uIk4pDRCSuBZINuFLoq0JVAR4FvHLop8BcZ9iJDX2ToFrCS7gP81zmJuNfJ94UPsr8Z3ZFL3MPuyvcVtRf0rY/3ll18x3av0Xo4iEjm4sU+OPcFvh1wJ0T4pJq9jdVwH0R7zUF3RQpmvmv6mcp4bxTzppZdAICYUzz4nKlGP3n5eZ5mqlUUinxDAHRTEjwCv6MWmw9VfWDn7REM3np1PwTgbJq0AwCWuiMDagIGCpCpNkQEeAhoGgcxYEswE8OKoRAhLidln2hqqgEsuitRDesJ8LY+wF6Xp54BIqvailuYiISJ9OWrPCy7SD0o3AAbBExTncWGRkXZCOQzgawL5KqgDAplmpT8SekPdbicXY+182+l8E2BXRd4NtjGYa8Uui2wl37qJ7TyJgANwom4TV83mUmk2gcVDcj+mnD/jmdnFvbmGv5fpd0nzz9+7v8qw3zgTuV/f6DOXKxw9al9lg48/qlQfSaDW3m7gRwOiuS1SoodIF1rErov9NlUgqs5+U0bbwjTbQyTyp8mcY5uAH6s/ute7q5auWyu5++H/rvPNgBUI4vAs1BZ19WS4KHqP4M/K+g81VvXTMdfao2zlgEZ06iRpSLW8Uagx1OuT/UaNXvKu6F6ZCUgtmoF7tYZgZiYAadYwU11dKGIuRIQA8Mcc42/zlHQEOyzq7xtI4c+BWqA0AZQw0SOSiaLi8ZHQ9koyqdb5I2gXGfka0GhaSwCqjLvTNAykY3P6whGBs8GJ65ViFcCfynAegReFPhgtRtyvYbjDLYd4Hh6R+ceE5GoB0ZAojq1/Rycv0U96sSb+w/u0ZF+FoAHbvrp52G7+xErHf3d7QOHvkW4/8YGHveTwh3PZbspj597ke4Q+XwaGrw/PHe3bgr1gVlMuy3W7Yt9rtMdIH+t93+dbrCfJpgedvXt5/PHpTzd2++YCHT6fdWd31GAI6aObqDNRNbXVS3DaQBzAz+fKHkRkBcKJkV8XIsQxgyDg3pHIIAWXOv6dJNOgwAUN2GQwx3MFKZQwAieOibxWlyxBPdIFF5cD9suEC2aSG0K1AakhsHmsOIYm7OmHw15oxg/LcjrjPxKMK4KSuDazhswARvQXFUgRY0oVGvZsAKeYEOu6URqYGMdk+CD1J6GjmEbAb4/1HlxXhbgL68BnZ7h6wGOx8Af/tM6JuTuXv0pxgtUf9nKcUcU3oTDcF/8RI5/T+Np1N1xzf5t8/5jC8DJkXvHot5sp6bTussYb1fzf5PNoN9X46WOvL2rZj979Do5h4P2Gm+8jr4FBfhdRZNTLb63cv8pjCeZwvI4l+1qpBAjoHLTcjvvM7V07Dz+zfHrxY9TDz8zMJUCnWZOArAEiBS+iFUzpwSc5xpFdD3oLAJk4LMAuwRAjthNwvSyenJmAMQ7r46pKklqPoXfdU5pp3o7U2BLBLt+Oaz6SNSnhDYRtwGhYUoTsIfuom9Gw7hV5M8GjCvBcJUxrgpGr7l6AaDuMAZkD9iaFcaog3yYqveegK2pgW8NtpiAvVH4MsC2CqwF/s1LeBbghQH/01fcpxTxYJDXd74HwqO6PA/oqY9X3eat6vjhrnh5v4knUgX/a5X93Wnglue/q2Z/H7OJu9kPQ/L7TMV1rOTfF8z3EQKNbqv5dx1/FvmOphunQL5T760+za8r7QE3611rXq8GcKxXIwSay3vztIUVb0Soc4TSjdB3orln32aghziJeNNrApDCrlxVX0+hOHrSxBNCCX4+8VOXAHZwEqJFPZ51C3gAsMzgs6b20ncC7hgaGlhfleqwCJPgFmpI7kXJAmnU3Zgu59pFpw1R+ex6uFo0ISxS8iYiNIFiYoru0OIYzi76ZjAMW8Xw2YBhJRheZWzXFdgKhwRGmQbiGDGkKAyAcoAMCvU6QEhB0D1g29bgi0oAM7B9q/AjYPsMbGaGqnoIASIyz8UIVdul5T/4wQ/2B+XcgP3EPXv7Bp4YABz9Is69+/VvnRNOgh94vac/1cm3/7pmYoRwTCJ0N/hnMe+eg3R2dp8Reey+u/wHrb50cijujP19YLvW0tluG6u11oNtptExB8eaH/ojm6OEGy1h0gr2vg8fgX6OGEKYBwUBKVVvGblO00VT3ZlA3iiIDUwgLBhoWjImMrj7oiFKBWEZoQTQ0okXbRW+OgGlC+S2VJF/MQ2waVMNKHqGlwREdyc4IsiI3MgIXGcHUiayreLTpTs3zNRGpJYpRkZyRygObS76djBs14rhx1ts14LtqxHrjWA4ArYUhU7AVq+0LAgQCIQJus21Y44B3Ro8EWRBk7p+47F9LcA3L2FZ4JPHtj1gI4TgdwH7z/7sz+4cbfe2djrHZyKy1/i8djd8qpb0Gkwu1m4EPpvHZt4n198D/wziU9PRnGrbPW7rfVM77+vAz6hjs+Ib6vhva8cef/e+k9ecff97HWL3CzQguTjN4T4Feu0lCZFmPaA2nUzb1/eA2NJcinMieNOSTSq2N4ms0/pDYz0RsQHNgpXgtEiEWKqnXjTkkeALo8AEXzSknYKXCxgDoW+hCdDWJh5KMAICog3scJ5aYJlgHeptllfjj1KbqI9MDXtsAsWWKSVGaxXYm2Ub+sGw2Si2n2yxWQs2E7C3DggcxQkagDwBu0zAlgnYBQJlgmwzlBjKgK4U1rXQ1Rp+0UA2Ct+28MUAWwv8m+/Criqw/QSwZ4/tP0tgv8luj86bir7MATHWlt55zLS8TeI+NfPtRrMcT7G1e72vsPkc+82gPvZgt5/g48E7N8uAghBPRA1+/JmYQU+IbncCFcCtyT5uNeUce3v43WLCoapfz60qOftf+66Q3qeW4N23YT6pC06TfMxT/IOZEJo6ZVeMXKfLioQQ6siRENiJ4T2YuyULT1+rI6bIxD0odi0VBih08L4hY5CfGREzwrKBdgJeLmGBgADwokXpFUz1NS1aqKgJEzlbnbknEmmoV8BlNfykb1uL5NxHDg3XMDwxkjlJftw/K47NVrBZKV6sM9ZXGetVwWoCdoGjEJCJdh47T8AuDhQEFCh0dCgKZAJ22QO2TcC2U8BODfyHGf5b3zjUvX8RgP0mO/0TWqE6niZwAupVyg7A7Hb0PddZsh627s65/g7fRwRwUgOYPT/dvDxYfUqgm+PimSSmZbba+XxrNt69WTvIbz7SJpLYD/nvCuNfN9ruLW2n6hPtBT17G+xNoDFHBvOYeY433pyYQBETiHnuB/AU2EPDdV6tQB4T1/7vlhEje0dMdb5QRr9k65nBAPXg0HYkgUCPm0CJ4I+YPBBwliBTUyQFAiIQFj1KJwAzPALUdzDKpuyOSDAuxAyYR9KOAFmNn5+1qbQBKcJDFzgkRkxMjTmV/GjxqDjWW/H1VaH12rCagH29EawDI7ujWK2xFwZK9l0oPgM7u6EEgjihjLmKbBOwy+Sxy/YG2DoBWydgYwL2/hRQ9b7V+QWhehOSHeXYv9B2rxx/NyrhdRs18F2uzzzJwAxMYyNvEcD+8gEB0B3LB2e0twkd/t0tT0SwA/2M2KOSoWOKTuiwAejOgTf++vV3WXWbDneqf0Fzjl/rV5W0dgEP1bn1d1NioQ7PpamXlbmCOzQTsBWIbagTYUbyGNk5MCiQt8ah7YOBQIsYPRD8rE8cCN51kL4L2juHSOC+Jz0roGWEMkALQVguUYIDvdScu+lQAsF5MCOqEgFnUAB5NPeGCShl2zssELRN0RpCagKlyNS5Y8wX3Xk2X41K65Vi9ePsq6uM6+vsV1vFxg0F5JmAkcilKCsxxqIQdoweUJiQs0ECVaCPBbbnscvkscsrhS0DdMqxbQ/Yvgfsgwfrlx3Yb7LXAr/IHZ19MQDttCzAbrZdSkAq2OX6mDszaCqp3TFTT+DDYVFvsvtM62XzEN9AN4A+iNF9F+I7bra5L6BPRQSnwvyJ2+aQfu7Eo8R+86u7wTF5fgoEiqHWmYl2Ig8lVOCDEVqyEKZf2THy0LKnlr1FDEzwpg/exuhtD+84ha4nWWxjbC9ZEsEvmDwxdBHgiUG9oEkELHqUxKDYwNuC2LTInE1i/VlA50AayLFQYs7lZR8Q2sCeCKFPAQ1TDBXYOVsTsvp2MKyuC12vBddX4tfX2V9tCzYOKyDKTi4myE7IXENxcSAz86hKxRV5sBqCE0MYyNygZIMtUHPyCdiyU8XfhRwBezcd6+5pmeSgPWD73yRgv8l++mG5EQRlR4mENFavP4f8MsekuGnPOjal2lKrr9fkAGD3Ax37kcJukM5+3o69CS7uOtgUAbxNzX62OHns3aEOPrd+YpjAzrUHE5GmgtXUNB7YQUBsJmAzOxOBGnIKDCJynrx2CIwYyELDaMbYhUXIzITIhKYLFgncxeDtkq1DDGeeqHsSciLCOcEaBi8vIYmAXhAj1bHfEQBv65DPkIvHAbE/5+slOy8iSb/arhdN431i7mMIDVMTmFp3jLlt2qy+HgzXa8X1J2tcX41yfa30cjVi4wRxRwG8EDA4ITN4LF6B7QgZ0PEG2CHPwJ48dl4NsIsGeQRs28IWA2Qqd4lO4tn//FWImYGZfS53AQ/AfpPd0bkHIjIKkcKtXro+Autye6ckjtIS5pGaZKiDAqdtS4PdT2QffBhQa++YRnAwagYJwAU3U7hKBXw42tnt5j3DsfB2uP3+L+2y+vS5hyA+ZfMxafe/yZtP+1M9f0Lk2vfqWsU4QkAISBCqP0zhtZmGwSGCInlIjKgphT545Ei8DIUZCMRo+oA2hyaeUYkaUmqJm8IIQBOiW3NBuUMIiYGeIndg6y65XDAh1QlwkDZmLRN4ECV36gPpRQLO1q/Wj7og58vGz5uGlk2ITaAuMV2q+Vgcq5wW46C4vs6++vFGr68zXl2Jv1wLNu4QdyvOYc61swMjsY0wZA8hi0kWDcWBzPBMbMLwvFJo13JebYNdGPKYoFuFLXwCdvXYduSxD7LNfWBPYPdfJPHsF91uAT8FJmYiZkYXY8x2c7UPOGC/z3KHZ3EgEvLevEgl1b8NTnfP7e/aBKCEPbBO+6oDMeHg13fn39fb1wx4r449RxERN0OG52loQj7MFXbNO3tRw7zI5Du8MwEITCDCVC4LMcwROzjV2hgBkRZUAAIzhDUk7j0HjSmkiNBGjyFQalu0oUXTR0vLiAh41yWmIhw701QCt020lhFSnaFFWwZIlGLnFtdoE8PborqwXCIDy+u0etIRPx1eri4XMV1cNHbeNLxsOLaRu8TUV2A/5ayeB/HrjeD602u9vs7+6lW2F5viW0cVxNxoZGB0+FgcQswjwNmBcRASc8pKKCYoFH20QgWNl04lr7awrYieX7TjVuELRzkBbPutr+2AvbNhGLzrugeP/TOy+w/LPbl3qL+jF0G7IXAFdbZDAAcEAKC2Z0wmB657h3E0h28jG9DXWRgOJ7/YI4XZCACXCvj91OG4FFYCI6odePK4J/BwZKQKeCbUGpc6uH4XR4yTFg5wG2pXSWJwCAjMCF104pAiJ6QuIMWIiNhEgrdPek8rouZpsMYTtSD0qV6yxACrB+rJgkqI5khDoSap0mbrMRB6lvFZH+hLm83qvSfL7UWXmstlh7MmpiZwnwI9VfOx2DMdxFbZsNoUvf7JVbm6Hu3quvjng9DayQXg0c2LO4oz5SL/f3vnriTXkZzhP7OqzqV7ugcDYEAyMBA3hA0ZegQ5dPkCMvQCegZ5fAa6smTvk8hZQyGXoMRg7GqXwJDo6zlVlZUpo87cMAAXpCJExuL8Tvecvkw7X+U9j2ZiiiAkM46ikkQ4mbfMZjFyJxpzBnHugsX9CKtga9yRlZzNOl7mGIELQwpPYK8S7ADgi0/vdn3NYP+y+mljuVd1pN4Dx8nitg4orPDCgDOcwHCcCG3ecO1TuHke8OMaHdDdcs3ftXfpDeMN4jqtchVWEN3Mf15VG4IHuGG4Yhyu3e87jXG87AhJQa2rCfnA4ODBAFznzXFPxPVM8SctGgvwfTDfe7RtMK8BLQhdU386F6BrYAEgXgAuC3tLcFrgxgyXD9m72gjzTP7w8vHpwj09XR1O16v+Qe+x6s9C63kRHC+L2pjVaBSzKPZykLJ/eUybXSrbbbTvY7EjzMSYI8ikKEeYRYbFRCxQTWo8Simp1G3NiR0nE0ujmKDxuQuc9tGqxW5D3GUrOUfruORYkl2MpzkI7NUe+u1L2JefV6udc0YI4Q7YV674DPavRz8/udd7IHHdjrAAI3oDi2Fgvs74X/nsV3fX7VD3Mb1NmYGg9REA+unnvekBDFYPhKz3B7IB0PSB60em2mtgeYoC3HS9AYUMorY2laxq6cy13pgAZg9mwHcdPBG8a+F6Rx6uuuVto40GWroKd2BgCVjguhhCGWQZ8Eng8lDccYBj2Gkj8enwp82j9cI/e3Cyf3x2QovG96v+vGm964PjruhHj7LadhTlKPpqSGV3+TpudlG2u4jLsZTBDBlMCWZSjCJgkZliEmSAsrKNMkoqjrOKRWKLRJrZd+kYU+k8pc3BSiqmq3MZd7mUderLMZtGn+1iXKQgzfuCbcBfV7nrr13vHMslEBHXLnPDVMtXvevyN1P2LBagdYQI4OTWW/ZMgFSv4FpvHAZXagGAb8qEtxUni91elQhvDgRqcafxhdp6GFw3vTBVq00ATzVxR762oZ44+M7XfegM+ODhew+vLfzSme+YGjgN2tLCV4vdECjUs4S41ADHJUGbY2YClo7o0/SHlw9PmnDxYDGsF03zaNnSquub3rvOMS1FP3G56D6K0ShlN6byx8vXcbsdZLOL8kNUHKddONFgWdUSQBPYJkw0CCzDaIxZihpFYo4kmrlv4zEm7XwfN+OoZyvEvYkMUXXNvRwOos/PSt7GqMWyHvKFffsC+PLzOoQ5g/1h6K3JPZratYPjJt7ypGmMBlzfqa7q7vKzG2c5cnX7rzN1U4i3nwLr1uMmK/jm8ze+cDoM6NgApwko3U3xbiqPuSXb9c4yNMRrM5Yw3eZ5cslXZI7qTSD8wsOPAr/0CASEjqmB16A17vaT5XZZOLCgTTH7DCw80W9yBfvp6WI4uwK7bZsuuN4zdaK/fTiBXUYp342p7C73h+02lc1ukO+j2gA1MaZoallBCdDI5KLBYoEmM4oFSDBKg2YhpUjiMnccvXIassr+OOrZY477KDLETtdc5LDZ6fOzXrbxoMWyfv2y2BeffXoVX09grzGOY53L9jPYH6L+4r3zrhiPe6hZ0vufybgumreuAg/cstyZ6rUpqL/tEcDdGlarSTLk9ur/w6yZpikJ3HvjRzfJde46uNo1Bm5a8wS4lYOrkFswQjvF146qK97W18hTLQCEk+n1lOBjKd4pTkLST+W714+a4J8+6ON5CzxcdVg1bdsH1zumRvS3ZxVsvQV23G6TbPZDvhzVRlPLYEqmJKqSwC6y0WimsYAq2FISwGkosYINzdxR9OZSEi2bw76sH3bxWEoZSqdrJ3I4Jr1Yt6khtSKmX79MbwE73ALb48WLF/b8+fMZ7FnXeq8YfyzAmEEW5e3vv72Mo73ZfskVeMPSAyIVbF+/ggCg1DlL6j0xnDIAkpauLLdrGnVEcCuHYARHQBOAtq0WfGG1EaUL4DDB7af3cKnPWweUAeJdBb5pND2TYXfusvtkQeV85e2s8e60DeEG7ItlFt2PUo5RdDsm2b3ajdvdIJvdmC+jlngDtolWyxyJMCYpAkYyoyiiSQzJjBJxjgTOzIjeugp2Hst60Y3HRGXIna5PRA7HohfrkArBStPoN5f5HtjOOaSUZrBn/Wy9F/hFgKG53+Ry50LjQKFadcpce+kawNQzE4HhlJf+2mJT09bJrdLUnnFPcADaFrbQCnQfwJ6BpgECAd0S0gzwnurfnmuLezjWop5j4MQDS9L48dLkSSt4esLxQQP3ILDrPPeOeSG6oCxlP4oeo5TdmGX7X/thux3iZj+WV0PKkdhlgJLBRFUTiCIRV7BtAttiEqFkTIkU0Rwl7yjt91paR3F/HHT9oBmPScoQO10jyiGKXlwsUhFYOTb6zWEGe9b/v96e3KvjleA34nlug/AevkbpoSbQvK9JM+fh+pAJAEfvuGNiC4WXxI4dHIH7vVPPQLcGewKvEswz0DbVOi/rfc4pTAm0jlD8lJTHEXB7UOtRVqzxo8bkvBU86VnPe+gE9qJabEpZLI5C+yiyG4+y/TrKdhfTdjeWywo2JSlQMowgRIASEcakJmYUIZbugi3RXEheJe0HLW3XxP0xTWDnMuRO+2HIqahdXCBFeWSFvX5zKPbFZ89nsGf9qvSjFr/xromK6+RY6xrXLU7hpvkW1zfZIXhHAPctOcehhYMnoM/QpofvW0h3gGsbIPSgdoHSjpPbfQpqEuQabAM6D6ycxidBy5O22F8Ce8yyi0fZvoh5uxvTdhfL5TGWbLBa1oImBx6uwc4mxhqLaDJBEtNk6hOxRHNIXuk9wR6t8FG/OUT74rPP3gl227Yz2LN+dXrHPH5dPzjdJRsstYz14KQhTzBtG2JmNHBh5euhcNJBPAF9RO0dR12wGAjOOxQ3rUhuR2Dl9PgkaPmoVfvkIaVHDfE6kO8D90TUSEGSQsModLgC++so2+0YJ7BzrjE2J9WSHfNgxrXclU3MSiymKRcVEEZTJGJEU9y44l0T98dBjyRl1TfjkIv2A89gz/og9KNZfXZEjmGrHvo3Brd0AUMXqGsgzQR426O0Ec6O9QBYP8a42pTh4ybbxwsujxZE7wYb+zHJ7njIu+9S2WyHuD2M+ftjLvfABtyNxTaLxTSlIpkIsahEx5RMkVLRooVS2zVxP6S7YMt9sGOM9i//MIM968PSPfC7lgtRycFxQjEigE4VzjewxyO0eYR0+mrcPurJf7yg8tECsg5oToPzXeAFEfXyCZmUsBul3AF7E9PmeEw//F/AHnMRGOf7YMs12Nsh6/PHY74C+3+2g335+efXDSrMbM45xBhnsGd9kLoD/t/+/SAdnmXvbDTSg8Limbf8T+f6umV0zTPXMaHLTxpNovtUym5IWsGOZbtJFexRNJaiBcyJjCJI411XvIKtqrFA808HO/5ksIG5QWXWrCvds/gqr6Pyg40q/XlM+u2ft8N/jGLjkGR3GNPl5RB/iKkkYkqqlk0p3wdb3wDbIpOPsJJNJUVxb4mx3w/s6WcagBnsWbN+pvztkbRSSv7nf/v3iId47dvw3y9ebuWPr4//ueh8Y4ClbNlMY8oqFWz8KNib/bFEV3TVN+N2PJa1eUnFbDsMM9izZv2CumPxnXP2j7/7XcT4d6/5zITRbHZjXBWwyzkjFy1vgn32sI1jknIb7IsLSVFGw12wb89jz2DPmvUL6l43nlndP/Gvv/99+/2fmvbhSeuCY8pFLYvaTVZc7OXxqNt4Xe66PbpzZ4jvCmwAM9izZv0KdA/8r776Cufn59T3PUIIpHWXPl1NcE2awZ41a9asWbNmzZo161et/wXHz8OvsqLhjQAAAABJRU5ErkJggg==";
|
|
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:
|
|
23
|
+
trailLength: 32,
|
|
22
24
|
worldLength: 4,
|
|
23
25
|
width: 1.01,
|
|
24
26
|
coreWidth: 0.4,
|
|
25
|
-
tailFadeStrength: 0
|
|
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: "
|
|
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: "
|
|
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
|
|
160
|
-
const
|
|
161
|
-
const
|
|
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
|
|
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 *
|
|
270
|
+
mountPos.x + c * orbitRadius,
|
|
228
271
|
mountPos.y + params.height,
|
|
229
|
-
mountPos.z + s *
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
287
|
-
const
|
|
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) >
|
|
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
|
-
|
|
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
|
|
383
|
+
outerMat?.setFloat("uTime", time);
|
|
330
384
|
coreMat?.setFloat("uTime", time);
|
|
331
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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",
|
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.
|
|
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": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"demo:treasure-attention": "vite demo/treasure-attention --host 127.0.0.1",
|
|
46
46
|
"release": "node scripts/release.mjs"
|
|
47
47
|
},
|
|
48
|
-
"//": "分发=git tag + 提交 dist(做法A):发版前 pnpm build → 提交 dist/ → git tag。消费端 import '@fps/vfx' 解析到 dist;本地开发可用 FPS_VFX_LIBRARY_REPO 切回源码 alias。",
|
|
48
|
+
"//": "分发=git tag + 提交 dist(做法A):发版前 pnpm build → 提交 dist/ → git tag。消费端 import '@fps-games/vfx' 解析到 dist;本地开发可用 FPS_VFX_LIBRARY_REPO 切回源码 alias。",
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@babylonjs/core": "^8.56.2",
|
|
51
51
|
"@babylonjs/loaders": "^8.56.2",
|
package/tooling/vfx-cli.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @fps/vfx CLI:把库特效以「re-export 桩 + 本地 vfx-params.json」装进项目(plan §6.2/§16 混合分发 模型C),
|
|
2
|
+
// @fps-games/vfx CLI:把库特效以「re-export 桩 + 本地 vfx-params.json」装进项目(plan §6.2/§16 混合分发 模型C),
|
|
3
3
|
// 并管理版本升级(issue #4)。消费方典型调用:
|
|
4
|
-
// node node_modules/@fps/vfx/tooling/vfx-cli.mjs <cmd> --project .
|
|
4
|
+
// node node_modules/@fps-games/vfx/tooling/vfx-cli.mjs <cmd> --project .
|
|
5
5
|
//
|
|
6
6
|
// list 列出库里可用特效
|
|
7
7
|
// add <id> [--project <dir>] [--force] 装一个特效(桩 + vfx-params.json + .vfx-source.json)
|
|
8
|
-
// outdated [--project <dir>] 对比项目钉的 @fps/vfx 版本 / 各特效 provenance vs 最新
|
|
8
|
+
// outdated [--project <dir>] 对比项目钉的 @fps-games/vfx 版本 / 各特效 provenance vs 最新
|
|
9
9
|
// update [<id>] [--project <dir>] [--to vX.Y.Z]
|
|
10
|
-
// 升级:改 package.json 的 @fps/vfx git-tag ref + 重同步桩
|
|
10
|
+
// 升级:改 package.json 的 @fps-games/vfx git-tag ref + 重同步桩
|
|
11
11
|
// + 不动 vfx-params.json + 打印 CHANGELOG 区间(然后 pnpm install)
|
|
12
12
|
import { readdirSync, statSync, existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
|
|
13
13
|
import { execFileSync } from 'node:child_process';
|
|
@@ -16,6 +16,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
16
16
|
|
|
17
17
|
const LIB_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
18
|
const LIB_EFFECTS = join(LIB_ROOT, 'packages', 'effects');
|
|
19
|
+
const PACKAGE_NAME = '@fps-games/vfx';
|
|
19
20
|
const LIB_VERSION = (() => { try { return JSON.parse(readFileSync(join(LIB_ROOT, 'package.json'), 'utf8')).version; } catch { return '0.0.0'; } })();
|
|
20
21
|
const LIB_INDEX = (() => {
|
|
21
22
|
try { return JSON.parse(readFileSync(join(LIB_ROOT, 'index.json'), 'utf8')); } catch { return null; }
|
|
@@ -45,11 +46,11 @@ function hasLibEffect(id) {
|
|
|
45
46
|
return libEffectIds().includes(id) || existsSync(join(LIB_EFFECTS, id, 'vfx-params.json'));
|
|
46
47
|
}
|
|
47
48
|
function stub(id) {
|
|
48
|
-
return `//
|
|
49
|
+
return `// ${PACKAGE_NAME} re-export 桩(代码/资源在包内;本地仅 vfx-params.json + 此桩)。\nexport * from '${PACKAGE_NAME}/effects/${id}';\n`;
|
|
49
50
|
}
|
|
50
51
|
function effectsDestDir() { return join(projectRoot, 'src', 'assets', 'vfx', 'effects'); }
|
|
51
52
|
function provenance(id, version) {
|
|
52
|
-
return JSON.stringify({ id, source:
|
|
53
|
+
return JSON.stringify({ id, source: PACKAGE_NAME, version }, null, 2) + '\n';
|
|
53
54
|
}
|
|
54
55
|
function normVer(v) { return String(v ?? '').replace(/^v/, ''); }
|
|
55
56
|
function semverCmp(a, b) {
|
|
@@ -58,17 +59,17 @@ function semverCmp(a, b) {
|
|
|
58
59
|
return 0;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
// 读项目 package.json 里的
|
|
62
|
+
// 读项目 package.json 里的 VFX 依赖(原始文本 + 解析值)。
|
|
62
63
|
function readProjectVfxDep() {
|
|
63
64
|
const p = join(projectRoot, 'package.json');
|
|
64
65
|
if (!existsSync(p)) return null;
|
|
65
66
|
const raw = readFileSync(p, 'utf8');
|
|
66
67
|
const json = JSON.parse(raw);
|
|
67
68
|
for (const field of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
69
|
+
const deps = json[field] ?? {};
|
|
70
|
+
if (deps[PACKAGE_NAME]) return { path: p, raw, json, field, packageName: PACKAGE_NAME, value: deps[PACKAGE_NAME] };
|
|
70
71
|
}
|
|
71
|
-
return { path: p, raw, json, field: null, value: null };
|
|
72
|
+
return { path: p, raw, json, field: null, packageName: null, value: null };
|
|
72
73
|
}
|
|
73
74
|
function pinFromValue(value) { const m = String(value ?? '').match(/#v?(\d+\.\d+\.\d+)/); return m ? m[1] : null; }
|
|
74
75
|
function repoUrlFromValue(value) { return String(value ?? '').replace(/#.*$/, ''); }
|
|
@@ -150,7 +151,7 @@ function outdated() {
|
|
|
150
151
|
const latestSource = remote ? '远端最新 tag' : `本库版本(远端不可达,回退)`;
|
|
151
152
|
|
|
152
153
|
console.log(`项目:${projectRoot}`);
|
|
153
|
-
console.log(
|
|
154
|
+
console.log(`${dep.packageName ?? PACKAGE_NAME} 钉版本:${pin ? `v${pin}` : '(未钉 / 非 git-tag)'} → 最新:v${latest}(${latestSource})`);
|
|
154
155
|
const depBehind = pin && semverCmp(pin, latest) < 0;
|
|
155
156
|
console.log(depBehind ? `⬆ 依赖落后:可升级到 v${latest}(\`vfx update --to v${latest}\` + pnpm install)` : `✓ 依赖已是最新(或无法判定)。`);
|
|
156
157
|
|
|
@@ -162,7 +163,7 @@ function outdated() {
|
|
|
162
163
|
const tag = v == null ? '无 provenance(早期批量安装)' : (semverCmp(v, latest) < 0 ? `v${v} ⬆ 落后` : `v${v} ✓`);
|
|
163
164
|
console.log(` - ${e.id} [${tag}]`);
|
|
164
165
|
}
|
|
165
|
-
console.log(`\n注:模型C 下特效代码/资源在
|
|
166
|
+
console.log(`\n注:模型C 下特效代码/资源在 ${PACKAGE_NAME} 包内,真实版本由上面的依赖钉版本决定;`);
|
|
166
167
|
console.log(` .vfx-source.json 仅作 add 时的来源记录,落后不影响运行(随依赖升级即同步)。`);
|
|
167
168
|
} else {
|
|
168
169
|
console.log(`\n(项目 ${effectsDestDir()} 下暂无已装特效)`);
|
|
@@ -178,7 +179,7 @@ function update(onlyId) {
|
|
|
178
179
|
const toFlag = flagVal('--to');
|
|
179
180
|
const target = normVer(toFlag ?? latestRemoteTag(repoUrl) ?? LIB_VERSION);
|
|
180
181
|
|
|
181
|
-
// 1) 改 package.json 的
|
|
182
|
+
// 1) 改 package.json 的 VFX git-tag ref(保留原 url 与格式,仅换 #ref)。
|
|
182
183
|
const isGitDep = /(^git\+|:\/\/|github\.com|\.git)/.test(dep.value ?? '');
|
|
183
184
|
if (dep.field && dep.value && (/#/.test(dep.value) || isGitDep)) {
|
|
184
185
|
let newValue;
|
|
@@ -186,21 +187,22 @@ function update(onlyId) {
|
|
|
186
187
|
else newValue = `${dep.value}#v${target}`;
|
|
187
188
|
if (newValue !== dep.value) {
|
|
188
189
|
// 原文本就地替换,避免重排整份 package.json;用替换函数避免 newValue 里的 $ 被当替换记号。
|
|
189
|
-
const
|
|
190
|
+
const depName = dep.packageName ?? PACKAGE_NAME;
|
|
191
|
+
const depValRe = new RegExp(`("${depName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:\\s*")${dep.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(")`);
|
|
190
192
|
const newRaw = dep.raw.replace(depValRe, (_m, p1, p2) => `${p1}${newValue}${p2}`);
|
|
191
193
|
if (newRaw !== dep.raw) {
|
|
192
194
|
writeFileSync(dep.path, newRaw);
|
|
193
|
-
console.log(`✓ package.json
|
|
195
|
+
console.log(`✓ package.json:${depName} ${oldPin ? `#v${oldPin}` : dep.value} → ${PACKAGE_NAME}#v${target}`);
|
|
194
196
|
} else {
|
|
195
|
-
console.warn(`! 未能就地替换
|
|
197
|
+
console.warn(`! 未能就地替换 ${depName} 依赖文本,请手动把 ref 改成 ${PACKAGE_NAME}#v${target}。`);
|
|
196
198
|
}
|
|
197
199
|
} else {
|
|
198
|
-
console.log(`= package.json
|
|
200
|
+
console.log(`= package.json:${dep.packageName ?? PACKAGE_NAME} 已是 #v${target}。`);
|
|
199
201
|
}
|
|
200
202
|
} else if (dep.field && dep.value) {
|
|
201
|
-
console.warn(`!
|
|
203
|
+
console.warn(`! ${dep.packageName ?? PACKAGE_NAME} 依赖 "${dep.value}" 不是 git-tag 形式,跳过 ref 升级(仅重同步桩)。如需固定版本,请手动改成 git+ssh://…#v${target}。`);
|
|
202
204
|
} else {
|
|
203
|
-
console.warn(`! 项目 package.json 未声明
|
|
205
|
+
console.warn(`! 项目 package.json 未声明 ${PACKAGE_NAME} 依赖,跳过 ref 升级(仅同步桩)。`);
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
// 2) 重同步桩 + bump .vfx-source.json(永不动 vfx-params.json)。
|
|
@@ -219,12 +221,12 @@ function update(onlyId) {
|
|
|
219
221
|
const range = changelogRange(oldPin, target);
|
|
220
222
|
if (range) { console.log(`\n── CHANGELOG ${oldPin ? `v${oldPin} → ` : ''}v${target} ──\n${range}\n`); }
|
|
221
223
|
|
|
222
|
-
console.log(`\n下一步:\`pnpm install\`(拉取
|
|
224
|
+
console.log(`\n下一步:\`pnpm install\`(拉取 ${PACKAGE_NAME}#v${target} 新代码/资源)。`);
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
// ── dispatch ──────────────────────────────────────────────────────────────────
|
|
226
228
|
if (cmd === 'list') {
|
|
227
|
-
console.log(
|
|
229
|
+
console.log(`${PACKAGE_NAME} 可用特效(v${LIB_VERSION}):`);
|
|
228
230
|
for (const id of libEffectIds().sort()) console.log(` - ${id}`);
|
|
229
231
|
} else if (cmd === 'add') {
|
|
230
232
|
if (!positional[0]) { console.error('用法: add <id> [--project <dir>] [--force]'); process.exit(1); }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// @fps/vfx 的 Vite 接入助手(plan §18.3:克隆 editor 蓝图,简化为单包)。
|
|
1
|
+
// @fps-games/vfx 的 Vite 接入助手(plan §18.3:克隆 editor 蓝图,简化为单包)。
|
|
2
2
|
// 消费方 vite.config:
|
|
3
|
-
// import { createVfxLibrarySourceAlias, VFX_BABYLON_DEDUPE } from '@fps/vfx/vite'(或本文件路径)
|
|
3
|
+
// import { createVfxLibrarySourceAlias, VFX_BABYLON_DEDUPE } from '@fps-games/vfx/vite'(或本文件路径)
|
|
4
4
|
// resolve: { alias: [...createVfxLibrarySourceAlias(process.env.FPS_VFX_LIBRARY_REPO)], dedupe: [...VFX_BABYLON_DEDUPE] }
|
|
5
5
|
import { resolve } from 'node:path';
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ import { resolve } from 'node:path';
|
|
|
8
8
|
export const VFX_BABYLON_DEDUPE = ['@babylonjs/core', '@babylonjs/loaders'];
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* 源码模式:把 `@fps/vfx` / `@fps/vfx/effects/<id>` 别名到本地库源(供 dev / SDK 联调)。
|
|
11
|
+
* 源码模式:把 `@fps-games/vfx` / `@fps-games/vfx/effects/<id>` 别名到本地库源(供 dev / SDK 联调)。
|
|
12
12
|
* @param {string} libRepoRoot fps-vfx-library 仓库根(通常来自 process.env.FPS_VFX_LIBRARY_REPO)
|
|
13
13
|
* @returns vite alias 数组(给 resolve.alias)
|
|
14
14
|
*/
|
|
@@ -18,11 +18,11 @@ export function createVfxLibrarySourceAlias(libRepoRoot) {
|
|
|
18
18
|
}
|
|
19
19
|
const pkgs = resolve(libRepoRoot, 'packages');
|
|
20
20
|
return [
|
|
21
|
-
// 子路径:@fps/vfx/effects/<id> → packages/effects/<id>/index.ts
|
|
22
|
-
{ find: /^@fps\/vfx\/effects\/(.+)$/, replacement: resolve(pkgs, 'effects') + '/$1/index.ts' },
|
|
23
|
-
// 根入口:@fps/vfx → packages/index.ts
|
|
24
|
-
{ find: /^@fps\/vfx$/, replacement: resolve(pkgs, 'index.ts') },
|
|
21
|
+
// 子路径:@fps-games/vfx/effects/<id> → packages/effects/<id>/index.ts
|
|
22
|
+
{ find: /^@fps-games\/vfx\/effects\/(.+)$/, replacement: resolve(pkgs, 'effects') + '/$1/index.ts' },
|
|
23
|
+
// 根入口:@fps-games/vfx → packages/index.ts
|
|
24
|
+
{ find: /^@fps-games\/vfx$/, replacement: resolve(pkgs, 'index.ts') },
|
|
25
25
|
// vite 助手子路径(本文件)
|
|
26
|
-
{ find: /^@fps\/vfx\/vite$/, replacement: resolve(libRepoRoot, 'tooling', 'vfx-package-resolution.mjs') },
|
|
26
|
+
{ find: /^@fps-games\/vfx\/vite$/, replacement: resolve(libRepoRoot, 'tooling', 'vfx-package-resolution.mjs') },
|
|
27
27
|
];
|
|
28
28
|
}
|