@fps-games/vfx 0.3.2 → 0.3.3
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 +8 -0
- package/package.json +5 -4
- package/tooling/vfx-cli.mjs +8 -1
- package/docs/AUTHORING_EFFECTS.md +0 -297
- package/docs/INTEGRATION.md +0 -194
- package/docs/VFX_LIBRARY_ARCHITECTURE_chatgpt.md +0 -1580
- package/docs/VFX_LIBRARY_EXECUTION_PLAN.md +0 -229
- package/docs/VFX_LIBRARY_PLAN.md +0 -546
- package/docs/hit-flash-red-method.md +0 -155
- package/docs/treasure_attention_fx_babylonjs_guide.md +0 -901
- package/docs/vfx-ab/RESULTS.md +0 -35
- package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/after.png +0 -0
- package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/before.png +0 -0
- package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/after.png +0 -0
- package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/before.png +0 -0
- package/docs/vfx-integration_chatgpt.md +0 -462
- package/docs/vfx-placement-design.md +0 -164
- package/docs/weapon-trail-visualization.html +0 -241
- package/packages/EffectPackageService.ts +0 -346
- package/packages/SceneVfxService.ts +0 -96
- package/packages/build.mesh.yellow_build_upgrade_indicator/assets/runtime/simpleMeshEffect.v1.js +0 -333
- package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.frag.glsl +0 -25
- package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.vert.glsl +0 -11
- package/packages/build.mesh.yellow_build_upgrade_indicator/assets/textures/effect_jianbian.png +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/effect.json +0 -927
- package/packages/build.mesh.yellow_build_upgrade_indicator/media/contact-sheet.png +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.gif +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.mp4 +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.webm +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/media/thumbnail.png +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/metadata/description.json +0 -197
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-effect.original.json +0 -895
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-normalized-vfx-to-babylon-report.json +0 -241
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-overrides.json +0 -542
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-tuning-report.json +0 -69
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/asset-map.json +0 -125
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/lineage.json +0 -44
- package/packages/build.particle.golden_upgrade_starburst/assets/runtime/particleEmitter.v1.js +0 -210
- package/packages/build.particle.golden_upgrade_starburst/assets/textures/effect_guangdian.png +0 -0
- package/packages/build.particle.golden_upgrade_starburst/effect.json +0 -880
- package/packages/build.particle.golden_upgrade_starburst/media/contact-sheet.png +0 -0
- package/packages/build.particle.golden_upgrade_starburst/media/preview.gif +0 -0
- package/packages/build.particle.golden_upgrade_starburst/media/preview.mp4 +0 -0
- package/packages/build.particle.golden_upgrade_starburst/media/preview.webm +0 -0
- package/packages/build.particle.golden_upgrade_starburst/media/thumbnail.png +0 -0
- package/packages/build.particle.golden_upgrade_starburst/metadata/description.json +0 -161
- package/packages/build.particle.golden_upgrade_starburst/provenance/03-effect.original.json +0 -762
- package/packages/build.particle.golden_upgrade_starburst/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
- package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-overrides.json +0 -436
- package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-tuning-report.json +0 -40
- package/packages/build.particle.golden_upgrade_starburst/provenance/asset-map.json +0 -115
- package/packages/build.particle.golden_upgrade_starburst/provenance/lineage.json +0 -40
- package/packages/build.particle.orange_upgrade_transform_sparks/assets/runtime/particleEmitter.v1.js +0 -210
- package/packages/build.particle.orange_upgrade_transform_sparks/assets/textures/b5b27ab1-e740-4398-b407-848fc2b2c897.png +0 -0
- package/packages/build.particle.orange_upgrade_transform_sparks/effect.json +0 -887
- package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.gif +0 -0
- package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.mp4 +0 -0
- package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.webm +0 -0
- package/packages/build.particle.orange_upgrade_transform_sparks/media/thumbnail.png +0 -0
- package/packages/build.particle.orange_upgrade_transform_sparks/metadata/description.json +0 -179
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-effect.original.json +0 -859
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-overrides.json +0 -478
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-tuning-report.json +0 -40
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/asset-map.json +0 -114
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/lineage.json +0 -41
- package/packages/build.particle.wood_chip_dust_burst/assets/runtime/particleEmitter.v1.js +0 -210
- package/packages/build.particle.wood_chip_dust_burst/assets/textures/effect_yanwu4.png +0 -0
- package/packages/build.particle.wood_chip_dust_burst/assets/textures/suipian3.png +0 -0
- package/packages/build.particle.wood_chip_dust_burst/effect.json +0 -1400
- package/packages/build.particle.wood_chip_dust_burst/media/contact-sheet.png +0 -0
- package/packages/build.particle.wood_chip_dust_burst/media/preview.gif +0 -0
- package/packages/build.particle.wood_chip_dust_burst/media/preview.mp4 +0 -0
- package/packages/build.particle.wood_chip_dust_burst/media/preview.webm +0 -0
- package/packages/build.particle.wood_chip_dust_burst/media/thumbnail.png +0 -0
- package/packages/build.particle.wood_chip_dust_burst/metadata/description.json +0 -173
- package/packages/build.particle.wood_chip_dust_burst/provenance/03-effect.original.json +0 -1277
- package/packages/build.particle.wood_chip_dust_burst/provenance/03-normalized-vfx-to-babylon-report.json +0 -329
- package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-overrides.json +0 -749
- package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-tuning-report.json +0 -60
- package/packages/build.particle.wood_chip_dust_burst/provenance/asset-map.json +0 -146
- package/packages/build.particle.wood_chip_dust_burst/provenance/lineage.json +0 -69
- package/packages/effects/building-upgrade/effect.json +0 -6
- package/packages/effects/building-upgrade/index.ts +0 -387
- package/packages/effects/character-entrance/effect.json +0 -6
- package/packages/effects/character-entrance/index.ts +0 -308
- package/packages/effects/coin-burst/index.ts +0 -48
- package/packages/effects/comet-trail-flame/assets/textures/trail_5x4.webp +0 -0
- package/packages/effects/comet-trail-flame/index.ts +0 -107
- package/packages/effects/conveyor-upgrade/effect.json +0 -6
- package/packages/effects/conveyor-upgrade/index.ts +0 -401
- package/packages/effects/debris-smoke/assets/models/rock_debris.glb +0 -0
- package/packages/effects/debris-smoke/assets/textures/smoke22.webp +0 -0
- package/packages/effects/debris-smoke/index.ts +0 -102
- package/packages/effects/door_open_gold_flakes/assets/runtime/goldFlakes.v1.js +0 -397
- package/packages/effects/door_open_gold_flakes/door_open_gold_flakes.json +0 -498
- package/packages/effects/door_open_gold_flakes/index.ts +0 -11
- package/packages/effects/electric-arc/assets/textures/spark_4x4.webp +0 -0
- package/packages/effects/electric-arc/index.ts +0 -63
- package/packages/effects/energy-shield/effect.json +0 -7
- package/packages/effects/energy-shield/index.ts +0 -432
- package/packages/effects/explosion/assets/textures/baozha29.webp +0 -0
- package/packages/effects/explosion/assets/textures/glow01.webp +0 -0
- package/packages/effects/explosion/assets/textures/lizi01.webp +0 -0
- package/packages/effects/explosion/assets/textures/xulie_lizibaozha02_5x5.webp +0 -0
- package/packages/effects/explosion/index.ts +0 -115
- package/packages/effects/ground-tile-upgrade/effect.json +0 -6
- package/packages/effects/ground-tile-upgrade/index.ts +0 -410
- package/packages/effects/hit-flash/index.ts +0 -77
- package/packages/effects/laser-beam/assets/textures/laser_glow.webp +0 -0
- package/packages/effects/laser-beam/assets/textures/laser_noise.webp +0 -0
- package/packages/effects/laser-beam/index.ts +0 -6
- package/packages/effects/laser-beam-factory.ts +0 -146
- package/packages/effects/lumber-cart-suction/effect.json +0 -6
- package/packages/effects/lumber-cart-suction/index.ts +0 -399
- package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb +0 -0
- package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/.active +0 -1
- package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/1781589063890_upload.glb +0 -0
- package/packages/effects/lumber-debris-scatter/assets/runtime/solidParticleScatter.v1.js +0 -236
- package/packages/effects/lumber-debris-scatter/effect.json +0 -228
- package/packages/effects/lumber-debris-scatter/index.ts +0 -780
- package/packages/effects/lumber_vehicle_gear_spin/assets/runtime/gearSpin.v1.js +0 -202
- package/packages/effects/lumber_vehicle_gear_spin/index.ts +0 -11
- package/packages/effects/lumber_vehicle_gear_spin/lumber_vehicle_gear_spin.json +0 -338
- package/packages/effects/lumber_vehicle_upgrade_aura/assets/runtime/upgradeAura.v1.js +0 -353
- package/packages/effects/lumber_vehicle_upgrade_aura/index.ts +0 -13
- package/packages/effects/lumber_vehicle_upgrade_aura/lumber_vehicle_upgrade_aura.json +0 -365
- package/packages/effects/lumber_vehicle_wood_chips/assets/runtime/solidParticleScatter.v1.js +0 -310
- package/packages/effects/lumber_vehicle_wood_chips/index.ts +0 -11
- package/packages/effects/lumber_vehicle_wood_chips/lumber_vehicle_wood_chips.json +0 -335
- package/packages/effects/monster-hit/index.ts +0 -129
- package/packages/effects/player-upgrade/effect.json +0 -6
- package/packages/effects/player-upgrade/index.ts +0 -640
- package/packages/effects/pulse-laser-volley/assets/textures/bolt_guangshu.webp +0 -0
- package/packages/effects/pulse-laser-volley/index.ts +0 -215
- package/packages/effects/resource-collect-sparkles/effect.json +0 -6
- package/packages/effects/resource-collect-sparkles/index.ts +0 -254
- package/packages/effects/run-footstep-dust/assets/runtime/runFootstepDust.v1.js +0 -215
- package/packages/effects/run-footstep-dust/effect.json +0 -183
- package/packages/effects/run-footstep-dust/index.ts +0 -377
- package/packages/effects/space-shared.ts +0 -120
- package/packages/effects/thruster-flame/index.ts +0 -306
- package/packages/effects/treasure-attention/effect.json +0 -7
- package/packages/effects/treasure-attention/index.ts +0 -630
- package/packages/effects/tree-chop-dust/assets/textures/tree_chop_dust_atlas.png +0 -0
- package/packages/effects/tree-chop-dust/effect.json +0 -8
- package/packages/effects/tree-chop-dust/index.ts +0 -202
- package/packages/effects/tree-leaf-chop-burst/assets/runtime/particleEmitter.v1.js +0 -303
- package/packages/effects/tree-leaf-chop-burst/assets/textures/tree_leaf_chop_burst_leaf_atlas.png +0 -0
- package/packages/effects/tree-leaf-chop-burst/effect.json +0 -498
- package/packages/effects/tree-leaf-chop-burst/index.ts +0 -294
- package/packages/effects/tree_chop_dust_burst/assets/runtime/particleEmitter.v1.js +0 -318
- package/packages/effects/tree_chop_dust_burst/assets/textures/tree_chop_dust_burst_dust.png +0 -0
- package/packages/effects/tree_chop_dust_burst/index.ts +0 -13
- package/packages/effects/tree_chop_dust_burst/tree_chop_dust_burst.json +0 -412
- package/packages/effects/ufo-thread-flame/assets/textures/flame_flow.webp +0 -0
- package/packages/effects/ufo-thread-flame/assets/textures/flame_spike.webp +0 -0
- package/packages/effects/ufo-thread-flame/index.ts +0 -153
- package/packages/effects/vehicle-exhaust-smoke/assets/textures/vehicle_exhaust_smoke_atlas.png +0 -0
- package/packages/effects/vehicle-exhaust-smoke/effect.json +0 -8
- package/packages/effects/vehicle-exhaust-smoke/index.ts +0 -298
- package/packages/effects/vehicle-upgrade/effect.json +0 -6
- package/packages/effects/vehicle-upgrade/index.ts +0 -404
- package/packages/effects/weapon-trail/assets/textures/trail_wedge.png +0 -0
- package/packages/effects/weapon-trail/index.ts +0 -317
- package/packages/effects/wood-crusher-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
- package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
- package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
- package/packages/effects/wood-crusher-sawdust/index.ts +0 -43
- package/packages/effects/wood-crusher-sawdust/wood_crusher_sawdust.json +0 -592
- package/packages/effects/wood-cutter-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
- package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
- package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
- package/packages/effects/wood-cutter-sawdust/index.ts +0 -43
- package/packages/effects/wood-cutter-sawdust/wood_cutter_sawdust.json +0 -592
- package/packages/effects/wood-sawdust-effect-factory.ts +0 -194
- package/packages/effects/wood_crusher_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
- package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
- package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
- package/packages/effects/wood_crusher_sawdust/index.ts +0 -29
- package/packages/effects/wood_crusher_sawdust/wood_crusher_sawdust.json +0 -592
- package/packages/effects/wood_cutter_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
- package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
- package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
- package/packages/effects/wood_cutter_sawdust/index.ts +0 -29
- package/packages/effects/wood_cutter_sawdust/wood_cutter_sawdust.json +0 -592
- package/packages/effects/wood_peel_material_test/assets/runtime/woodPeelMaterialTest.v1.js +0 -196
- package/packages/effects/wood_peel_material_test/index.ts +0 -11
- package/packages/effects/wood_peel_material_test/wood_peel_material_test.json +0 -136
- package/packages/env.particle.forest_edge_fog/assets/runtime/particleEmitter.v1.js +0 -210
- package/packages/env.particle.forest_edge_fog/assets/textures/f12a23c4-b924-4322-a260-3d982428f1e8.png +0 -0
- package/packages/env.particle.forest_edge_fog/assets/textures/yuan.png +0 -0
- package/packages/env.particle.forest_edge_fog/effect.json +0 -854
- package/packages/env.particle.forest_edge_fog/media/preview.gif +0 -0
- package/packages/env.particle.forest_edge_fog/media/preview.mp4 +0 -0
- package/packages/env.particle.forest_edge_fog/media/preview.webm +0 -0
- package/packages/env.particle.forest_edge_fog/media/thumbnail.png +0 -0
- package/packages/env.particle.forest_edge_fog/metadata/description.json +0 -148
- package/packages/env.particle.forest_edge_fog/provenance/03-effect.original.json +0 -827
- package/packages/env.particle.forest_edge_fog/provenance/03-normalized-vfx-to-babylon-report.json +0 -196
- package/packages/env.particle.forest_edge_fog/provenance/04-preview-overrides.json +0 -432
- package/packages/env.particle.forest_edge_fog/provenance/04-preview-tuning-report.json +0 -28
- package/packages/env.particle.forest_edge_fog/provenance/asset-map.json +0 -124
- package/packages/env.particle.forest_edge_fog/provenance/lineage.json +0 -41
- package/packages/generation.ts +0 -14
- package/packages/index.ts +0 -14
- package/packages/legacy-particle-runtime.ts +0 -113
- package/packages/manifestRuntimePackage.ts +0 -519
- package/packages/registry.ts +0 -85
- package/packages/rendering.ts +0 -13
- package/packages/resource.particle.viscous_oil_suction/assets/runtime/top_disk_suction/particleEmitter.v1.js +0 -424
- package/packages/resource.particle.viscous_oil_suction/assets/textures/Glow ad.png +0 -0
- package/packages/resource.particle.viscous_oil_suction/effect.json +0 -1254
- package/packages/resource.particle.viscous_oil_suction/media/preview.gif +0 -0
- package/packages/resource.particle.viscous_oil_suction/media/preview.mp4 +0 -0
- package/packages/resource.particle.viscous_oil_suction/media/preview.webm +0 -0
- package/packages/resource.particle.viscous_oil_suction/media/thumbnail.png +0 -0
- package/packages/resource.particle.viscous_oil_suction/metadata/description.json +0 -122
- package/packages/resource.particle.viscous_oil_suction/provenance/03-effect.original.json +0 -1051
- package/packages/resource.particle.viscous_oil_suction/provenance/03-normalized-vfx-to-babylon-report.json +0 -200
- package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-overrides.json +0 -581
- package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-tuning-report.json +0 -28
- package/packages/resource.particle.viscous_oil_suction/provenance/asset-map.json +0 -111
- package/packages/resource.particle.viscous_oil_suction/provenance/lineage.json +0 -45
- package/packages/types.ts +0 -195
- package/tooling/check-dist-runtime.mjs +0 -166
- package/tooling/generate-index.mjs +0 -55
- package/tooling/test-guardrails.mjs +0 -157
- package/tooling/validate-boundary.mjs +0 -92
- package/tooling/validate-effects.mjs +0 -304
- package/tooling/validate.mjs +0 -53
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
// 武器挥砍拖尾(移植自 axe_and_bear OrbitingAxe 的 ribbon 拖尾, 自定义着色器 + trail_wedge 贴图)。
|
|
2
|
-
// 库版:每帧采样「挂载点 root 实时世界坐标 + 可选轨道偏移」,按弧长重采样成固定点数,世界空间重建 ribbon。
|
|
3
|
-
// 双层(细白亮芯 + 宽柔光晕)+ GlowLayer 辉光 + 能量流动高光带 + 刃头火花。持续型(默认无限循环)。
|
|
4
|
-
import { Mesh } from '@babylonjs/core/Meshes/mesh';
|
|
5
|
-
import { VertexData } from '@babylonjs/core/Meshes/mesh.vertexData';
|
|
6
|
-
import { VertexBuffer } from '@babylonjs/core/Buffers/buffer';
|
|
7
|
-
import { ShaderMaterial } from '@babylonjs/core/Materials/shaderMaterial';
|
|
8
|
-
import { Effect } from '@babylonjs/core/Materials/effect';
|
|
9
|
-
import { Texture } from '@babylonjs/core/Materials/Textures/texture';
|
|
10
|
-
import { Color3, Color4 } from '@babylonjs/core/Maths/math.color';
|
|
11
|
-
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
|
|
12
|
-
import { Constants } from '@babylonjs/core/Engines/constants';
|
|
13
|
-
import { GlowLayer } from '@babylonjs/core/Layers/glowLayer';
|
|
14
|
-
import { ParticleSystem } from '@babylonjs/core/Particles/particleSystem';
|
|
15
|
-
import type { Scene } from '@babylonjs/core/scene';
|
|
16
|
-
import type { VfxColorValue, VfxEffectPackage, VfxParamValues } from '../../types';
|
|
17
|
-
import { LIFETIME_DEBUG_PARAMS, LIFETIME_DEFAULTS, scheduleLifetime, makeFlareTexture } from '../space-shared';
|
|
18
|
-
import trailTextureUrl from './assets/textures/trail_wedge.png?url';
|
|
19
|
-
|
|
20
|
-
interface WeaponTrailParams extends VfxParamValues {
|
|
21
|
-
lifetimeMode: string; lifetime: number;
|
|
22
|
-
orientation: string;
|
|
23
|
-
orbitRadius: number; orbitSpeed: number; height: number;
|
|
24
|
-
trailLength: number; worldLength: number; width: number; coreWidth: number;
|
|
25
|
-
tailFadeStrength: number; tailFadeExponent: number;
|
|
26
|
-
opacity: number; saturation: number; contrast: number;
|
|
27
|
-
bright: number; bloom: number; blendMode: string; flowSpeed: number; sparkRate: number;
|
|
28
|
-
tint: VfxColorValue;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const DEFAULT_PARAMS: WeaponTrailParams = {
|
|
32
|
-
...LIFETIME_DEFAULTS,
|
|
33
|
-
orientation: 'horizontal',
|
|
34
|
-
orbitRadius: 0, orbitSpeed: 7, height: 0, // 默认 0:真实跟随挂点(orbit>0 是演示用的幻影圆周轨道)
|
|
35
|
-
trailLength: 16, worldLength: 4.0, width: 1.01, coreWidth: 0.4,
|
|
36
|
-
tailFadeStrength: 0.58, tailFadeExponent: 1.35,
|
|
37
|
-
opacity: 1.35, saturation: 1.45, contrast: 1.32,
|
|
38
|
-
bright: 1.35, bloom: 0.9, blendMode: 'add', flowSpeed: 5, sparkRate: 20,
|
|
39
|
-
tint: { r: 0.4, g: 0.85, b: 1 }, // 青色能量,配新武器发光风
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const TRAIL_VERTEX = `precision highp float;
|
|
43
|
-
attribute vec3 position;
|
|
44
|
-
attribute vec2 uv;
|
|
45
|
-
attribute vec4 color;
|
|
46
|
-
uniform mat4 worldViewProjection;
|
|
47
|
-
varying vec2 vUv;
|
|
48
|
-
varying vec4 vColor;
|
|
49
|
-
void main(){ gl_Position = worldViewProjection * vec4(position, 1.0); vUv = uv; vColor = color; }`;
|
|
50
|
-
|
|
51
|
-
const TRAIL_FRAGMENT = `precision highp float;
|
|
52
|
-
varying vec2 vUv;
|
|
53
|
-
varying vec4 vColor;
|
|
54
|
-
uniform sampler2D mainTex;
|
|
55
|
-
uniform vec3 uTint;
|
|
56
|
-
uniform float uSaturation;
|
|
57
|
-
uniform float uContrast;
|
|
58
|
-
uniform float uAlphaScale;
|
|
59
|
-
uniform float uAlphaPower;
|
|
60
|
-
uniform float uBright;
|
|
61
|
-
uniform float uTime;
|
|
62
|
-
uniform float uFlowSpeed;
|
|
63
|
-
void main(){
|
|
64
|
-
vec4 base = texture2D(mainTex, vUv);
|
|
65
|
-
vec3 color = base.rgb * uTint;
|
|
66
|
-
float luminance = dot(color, vec3(0.299, 0.587, 0.114));
|
|
67
|
-
color = mix(vec3(luminance), color, uSaturation);
|
|
68
|
-
color = (color - 0.5) * uContrast + 0.5;
|
|
69
|
-
color = clamp(color, 0.0, 1.0) * uBright; // HDR 亮度 → 过曝白核,辉光更强
|
|
70
|
-
// 能量流动:一条高光带沿长度(头→尾)滚动
|
|
71
|
-
float phase = fract(vUv.x * 2.0 - uTime * uFlowSpeed);
|
|
72
|
-
float band = smoothstep(0.0, 0.12, phase) * (1.0 - smoothstep(0.12, 0.4, phase));
|
|
73
|
-
color *= 1.0 + band * 0.6 * step(0.0001, uFlowSpeed);
|
|
74
|
-
float alpha = pow(clamp(base.a, 0.0, 1.0), uAlphaPower) * vColor.a * uAlphaScale;
|
|
75
|
-
gl_FragColor = vec4(color, clamp(alpha, 0.0, 1.0));
|
|
76
|
-
}`;
|
|
77
|
-
|
|
78
|
-
let shadersRegistered = false;
|
|
79
|
-
function registerShaders(): void {
|
|
80
|
-
if (shadersRegistered) return;
|
|
81
|
-
Effect.ShadersStore['vfxWeaponTrailVertexShader'] = TRAIL_VERTEX;
|
|
82
|
-
Effect.ShadersStore['vfxWeaponTrailFragmentShader'] = TRAIL_FRAGMENT;
|
|
83
|
-
shadersRegistered = true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface Sample { position: Vector3; widthAxis: Vector3; }
|
|
87
|
-
function sample(): Sample { return { position: new Vector3(), widthAxis: new Vector3(0, 1, 0) }; }
|
|
88
|
-
function copySample(src: Sample, dst: Sample): void { dst.position.copyFrom(src.position); dst.widthAxis.copyFrom(src.widthAxis); }
|
|
89
|
-
function lerpSample(a: Sample, b: Sample, t: number, dst: Sample): void {
|
|
90
|
-
Vector3.LerpToRef(a.position, b.position, t, dst.position);
|
|
91
|
-
Vector3.LerpToRef(a.widthAxis, b.widthAxis, t, dst.widthAxis);
|
|
92
|
-
if (dst.widthAxis.lengthSquared() > 1e-6) dst.widthAxis.normalize();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export const weaponTrailEffectPackage: VfxEffectPackage<WeaponTrailParams> = {
|
|
96
|
-
id: 'weapon-trail',
|
|
97
|
-
nameZh: '武器拖尾',
|
|
98
|
-
placement: 'socket',
|
|
99
|
-
geometrySpace: 'world',
|
|
100
|
-
debugAnchor: 'player',
|
|
101
|
-
defaultParams: DEFAULT_PARAMS,
|
|
102
|
-
debugParams: [
|
|
103
|
-
...LIFETIME_DEBUG_PARAMS,
|
|
104
|
-
{ key: 'orientation', label: '拖尾朝向', kind: 'enum', options: [
|
|
105
|
-
{ value: 'horizontal', label: '水平面' }, { value: 'vertical', label: '竖直' },
|
|
106
|
-
] },
|
|
107
|
-
{ key: 'orbitRadius', label: '环绕半径', kind: 'scale', min: 0, max: 5, step: 0.05 },
|
|
108
|
-
{ key: 'orbitSpeed', label: '环绕速度', kind: 'number', min: 0, max: 20, step: 0.1 },
|
|
109
|
-
{ key: 'height', label: '高度', kind: 'number', min: -2, max: 3, step: 0.05 },
|
|
110
|
-
{ key: 'trailLength', label: '拖尾段数', kind: 'number', min: 4, max: 48, step: 1 },
|
|
111
|
-
{ key: 'worldLength', label: '拖尾长度', kind: 'scale', min: 0.5, max: 12, step: 0.1 },
|
|
112
|
-
{ key: 'width', label: '拖尾宽度', kind: 'scale', min: 0.1, max: 4, step: 0.01 },
|
|
113
|
-
{ key: 'coreWidth', label: '亮芯宽度比', kind: 'number', min: 0, max: 1, step: 0.01 },
|
|
114
|
-
{ key: 'tailFadeStrength', label: '尾部淡出强度', kind: 'number', min: 0, max: 1, step: 0.01 },
|
|
115
|
-
{ key: 'tailFadeExponent', label: '尾部淡出曲线', kind: 'number', min: 0.2, max: 4, step: 0.05 },
|
|
116
|
-
{ key: 'opacity', label: '透明度', kind: 'opacity', min: 0, max: 3, step: 0.01 },
|
|
117
|
-
{ key: 'saturation', label: '饱和度', kind: 'number', min: 0, max: 3, step: 0.01 },
|
|
118
|
-
{ key: 'contrast', label: '对比度', kind: 'number', min: 0, max: 3, step: 0.01 },
|
|
119
|
-
{ key: 'bright', label: '亮度(HDR)', kind: 'number', min: 0.2, max: 4, step: 0.05 },
|
|
120
|
-
{ key: 'bloom', label: '辉光强度', kind: 'number', min: 0, max: 3, step: 0.05 },
|
|
121
|
-
{ key: 'blendMode', label: '混合模式', kind: 'enum', options: [
|
|
122
|
-
{ value: 'add', label: '加色(能量光)' }, { value: 'combine', label: '正常(实心刃)' },
|
|
123
|
-
] },
|
|
124
|
-
{ key: 'flowSpeed', label: '能量流动速度', kind: 'number', min: 0, max: 4, step: 0.05 },
|
|
125
|
-
{ key: 'sparkRate', label: '刃头火花/秒', kind: 'number', min: 0, max: 80, step: 1 },
|
|
126
|
-
{ key: 'tint', label: '颜色', kind: 'color' },
|
|
127
|
-
],
|
|
128
|
-
|
|
129
|
-
create({ scene, root, position, params, renderingGroupId }) {
|
|
130
|
-
root.position.copyFrom(position);
|
|
131
|
-
const rgid = renderingGroupId ?? 0;
|
|
132
|
-
registerShaders();
|
|
133
|
-
const blend = params.blendMode === 'combine' ? Constants.ALPHA_COMBINE : Constants.ALPHA_ADD;
|
|
134
|
-
|
|
135
|
-
const tex = new Texture(trailTextureUrl, scene, true, false);
|
|
136
|
-
tex.hasAlpha = true; tex.wrapU = Texture.CLAMP_ADDRESSMODE; tex.wrapV = Texture.CLAMP_ADDRESSMODE;
|
|
137
|
-
|
|
138
|
-
function makeMat(name: string, tint: Color3, bright: number): ShaderMaterial {
|
|
139
|
-
const m = new ShaderMaterial(name, scene, { vertex: 'vfxWeaponTrail', fragment: 'vfxWeaponTrail' }, {
|
|
140
|
-
attributes: ['position', 'uv', 'color'],
|
|
141
|
-
uniforms: ['worldViewProjection', 'uTint', 'uSaturation', 'uContrast', 'uAlphaScale', 'uAlphaPower', 'uBright', 'uTime', 'uFlowSpeed'],
|
|
142
|
-
samplers: ['mainTex'], needAlphaBlending: true,
|
|
143
|
-
});
|
|
144
|
-
m.setTexture('mainTex', tex);
|
|
145
|
-
m.setColor3('uTint', tint);
|
|
146
|
-
m.setFloat('uSaturation', params.saturation);
|
|
147
|
-
m.setFloat('uContrast', params.contrast);
|
|
148
|
-
m.setFloat('uAlphaScale', params.opacity);
|
|
149
|
-
m.setFloat('uAlphaPower', 0.82);
|
|
150
|
-
m.setFloat('uBright', bright);
|
|
151
|
-
m.setFloat('uTime', 0);
|
|
152
|
-
m.setFloat('uFlowSpeed', params.flowSpeed);
|
|
153
|
-
m.alphaMode = blend; m.backFaceCulling = false; m.disableDepthWrite = true; m.fogEnabled = false;
|
|
154
|
-
return m;
|
|
155
|
-
}
|
|
156
|
-
const tintC = new Color3(params.tint.r, params.tint.g, params.tint.b);
|
|
157
|
-
const coreC = Color3.Lerp(tintC, new Color3(1, 1, 1), 0.75); // 亮芯偏白
|
|
158
|
-
const outerMat = makeMat('vfx_weaponTrail_outerMat', tintC, params.bright);
|
|
159
|
-
const hasCore = params.coreWidth > 0.001;
|
|
160
|
-
const coreMat = hasCore ? makeMat('vfx_weaponTrail_coreMat', coreC, params.bright * 1.6) : null;
|
|
161
|
-
|
|
162
|
-
function makeMesh(name: string, mat: ShaderMaterial, ai: number): Mesh {
|
|
163
|
-
const m = new Mesh(name, scene);
|
|
164
|
-
m.material = mat; m.isPickable = false; m.alwaysSelectAsActiveMesh = true;
|
|
165
|
-
m.useVertexColors = true; m.hasVertexAlpha = true; m.renderingGroupId = rgid; m.alphaIndex = ai;
|
|
166
|
-
return m;
|
|
167
|
-
}
|
|
168
|
-
const outer = makeMesh('vfx_weaponTrail_outer', outerMat, 10);
|
|
169
|
-
const core = hasCore ? makeMesh('vfx_weaponTrail_core', coreMat!, 11) : null;
|
|
170
|
-
|
|
171
|
-
// 辉光:作用域仅限本拖尾的 GlowLayer,用拖尾自身 shader 输出做辉光源
|
|
172
|
-
let glow: GlowLayer | null = null;
|
|
173
|
-
if (params.bloom > 0) {
|
|
174
|
-
glow = new GlowLayer('vfx_weaponTrail_glow', scene, { blurKernelSize: 48 });
|
|
175
|
-
glow.intensity = params.bloom;
|
|
176
|
-
glow.addIncludedOnlyMesh(outer); glow.referenceMeshToUseItsOwnMaterial(outer);
|
|
177
|
-
if (core) { glow.addIncludedOnlyMesh(core); glow.referenceMeshToUseItsOwnMaterial(core); }
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// 刃头能量火花:加色径向辉光粒子,发射点跟随拖尾头部
|
|
181
|
-
let sparkPs: ParticleSystem | null = null;
|
|
182
|
-
const sparkEmitter = new Vector3();
|
|
183
|
-
if (params.sparkRate > 0) {
|
|
184
|
-
const c255 = `${Math.round(params.tint.r * 255)},${Math.round(params.tint.g * 255)},${Math.round(params.tint.b * 255)}`;
|
|
185
|
-
const flare = makeFlareTexture(scene, 'vfx_weaponTrail_sparkTex', '255,255,255', c255);
|
|
186
|
-
sparkPs = new ParticleSystem('vfx_weaponTrail_sparks', 160, scene);
|
|
187
|
-
sparkPs.renderingGroupId = rgid;
|
|
188
|
-
sparkPs.particleTexture = flare;
|
|
189
|
-
sparkPs.emitter = sparkEmitter;
|
|
190
|
-
sparkPs.minSize = 0.07; sparkPs.maxSize = 0.26;
|
|
191
|
-
sparkPs.minLifeTime = 0.12; sparkPs.maxLifeTime = 0.4;
|
|
192
|
-
sparkPs.emitRate = params.sparkRate;
|
|
193
|
-
sparkPs.blendMode = ParticleSystem.BLENDMODE_ADD;
|
|
194
|
-
sparkPs.minEmitPower = 0.6; sparkPs.maxEmitPower = 2.4;
|
|
195
|
-
sparkPs.direction1 = new Vector3(-1, -0.6, -1); sparkPs.direction2 = new Vector3(1, 0.6, 1);
|
|
196
|
-
sparkPs.gravity = new Vector3(0, -3, 0);
|
|
197
|
-
sparkPs.color1 = new Color4(params.tint.r, params.tint.g, params.tint.b, 1);
|
|
198
|
-
sparkPs.color2 = new Color4(1, 1, 1, 1);
|
|
199
|
-
sparkPs.colorDead = new Color4(params.tint.r, params.tint.g, params.tint.b, 0);
|
|
200
|
-
sparkPs.start();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const targetCount = Math.max(2, Math.round(params.trailLength));
|
|
204
|
-
const raw: Sample[] = [];
|
|
205
|
-
const points: Sample[] = [];
|
|
206
|
-
for (let i = 0; i < 96; i++) raw.push(sample());
|
|
207
|
-
for (let i = 0; i < targetCount; i++) points.push(sample());
|
|
208
|
-
let rawCount = 0, angle = 0, time = 0;
|
|
209
|
-
let outerInit = false, coreInit = false;
|
|
210
|
-
const cur = sample();
|
|
211
|
-
const mountPos = new Vector3();
|
|
212
|
-
const lastWidthAxis = new Vector3(0, 0, 1);
|
|
213
|
-
const tmpDir = new Vector3();
|
|
214
|
-
const UP = new Vector3(0, 1, 0);
|
|
215
|
-
const positions: number[] = [], uvs: number[] = [], colors: number[] = [], indices: number[] = [];
|
|
216
|
-
|
|
217
|
-
function sampleEmitter(out: Sample): void {
|
|
218
|
-
const c = Math.cos(angle), s = Math.sin(angle);
|
|
219
|
-
// 跟随挂载点:每帧读 root 实时世界坐标(宿主移动/挂骨骼即跟随),叠加可选轨道偏移
|
|
220
|
-
out.position.set(
|
|
221
|
-
mountPos.x + c * params.orbitRadius,
|
|
222
|
-
mountPos.y + params.height,
|
|
223
|
-
mountPos.z + s * params.orbitRadius,
|
|
224
|
-
);
|
|
225
|
-
if (params.orientation === 'vertical') { out.widthAxis.set(0, 1, 0); return; }
|
|
226
|
-
// 水平:宽度轴 = 上 × 运动方向(水平且垂直于真实路径)→ 轨道时=径向,跟随时垂直于挥动方向
|
|
227
|
-
if (rawCount > 0) {
|
|
228
|
-
tmpDir.set(out.position.x - raw[0].position.x, out.position.y - raw[0].position.y, out.position.z - raw[0].position.z);
|
|
229
|
-
if (tmpDir.lengthSquared() > 1e-8) {
|
|
230
|
-
Vector3.CrossToRef(UP, tmpDir, out.widthAxis);
|
|
231
|
-
if (out.widthAxis.lengthSquared() > 1e-8) { out.widthAxis.normalize(); lastWidthAxis.copyFrom(out.widthAxis); return; }
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
out.widthAxis.copyFrom(lastWidthAxis); // 静止/首帧:沿用上次朝向,带子不塌
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function resample(): void {
|
|
238
|
-
if (rawCount <= 0) return;
|
|
239
|
-
const target = params.worldLength;
|
|
240
|
-
let seg = 0, distBefore = 0;
|
|
241
|
-
for (let i = 0; i < targetCount; i++) {
|
|
242
|
-
const t = points[i];
|
|
243
|
-
if (i === 0 || rawCount === 1) { copySample(raw[0], t); continue; }
|
|
244
|
-
const targetDist = target * (i / (targetCount - 1));
|
|
245
|
-
let found = false;
|
|
246
|
-
while (seg < rawCount - 1) {
|
|
247
|
-
const l = raw[seg], r = raw[seg + 1];
|
|
248
|
-
const segLen = Vector3.Distance(l.position, r.position);
|
|
249
|
-
if (segLen <= 1e-5) { seg++; continue; }
|
|
250
|
-
if (distBefore + segLen >= targetDist) { lerpSample(l, r, Math.min(1, Math.max(0, (targetDist - distBefore) / segLen)), t); found = true; break; }
|
|
251
|
-
distBefore += segLen; seg++;
|
|
252
|
-
}
|
|
253
|
-
if (!found) copySample(raw[rawCount - 1], t);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// 从 points 重建 ribbon 几何(世界空间),halfWidth 决定层宽
|
|
258
|
-
function buildRibbon(mesh: Mesh, halfWidth: number, wasInit: boolean): boolean {
|
|
259
|
-
positions.length = 0; uvs.length = 0; colors.length = 0; indices.length = 0;
|
|
260
|
-
for (let i = 0; i < targetCount; i++) {
|
|
261
|
-
const s = points[i];
|
|
262
|
-
const t = i / (targetCount - 1);
|
|
263
|
-
const u = t * 0.97 + 0.015;
|
|
264
|
-
const alpha = Math.max(0, 1 - Math.pow(t, params.tailFadeExponent) * params.tailFadeStrength);
|
|
265
|
-
const ox = s.widthAxis.x * halfWidth, oy = s.widthAxis.y * halfWidth, oz = s.widthAxis.z * halfWidth;
|
|
266
|
-
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);
|
|
267
|
-
uvs.push(u, 0, u, 1);
|
|
268
|
-
colors.push(1, 1, 1, alpha, 1, 1, 1, alpha);
|
|
269
|
-
if (i < targetCount - 1) { const b = i * 2; indices.push(b, b + 1, b + 2, b + 1, b + 3, b + 2); }
|
|
270
|
-
}
|
|
271
|
-
if (!wasInit) {
|
|
272
|
-
const vd = new VertexData(); vd.positions = positions; vd.indices = indices; vd.uvs = uvs; vd.colors = colors; vd.hasVertexAlpha = true;
|
|
273
|
-
vd.applyToMesh(mesh, true); mesh.refreshBoundingInfo(true);
|
|
274
|
-
return true;
|
|
275
|
-
}
|
|
276
|
-
mesh.updateVerticesData(VertexBuffer.PositionKind, positions, true, false);
|
|
277
|
-
mesh.updateVerticesData(VertexBuffer.UVKind, uvs, true, false);
|
|
278
|
-
mesh.updateVerticesData(VertexBuffer.ColorKind, colors, true, false);
|
|
279
|
-
return true;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const observer = scene.onBeforeRenderObservable.add(() => {
|
|
283
|
-
const dt = Math.min(scene.getEngine().getDeltaTime() / 1000, 0.05);
|
|
284
|
-
time += dt; angle += params.orbitSpeed * dt;
|
|
285
|
-
root.computeWorldMatrix(true);
|
|
286
|
-
mountPos.copyFrom(root.getAbsolutePosition());
|
|
287
|
-
sampleEmitter(cur);
|
|
288
|
-
if (rawCount === 0 || Vector3.Distance(cur.position, raw[0].position) > 0.0005) {
|
|
289
|
-
for (let i = Math.min(rawCount, raw.length - 1); i > 0; i--) copySample(raw[i - 1], raw[i]);
|
|
290
|
-
copySample(cur, raw[0]);
|
|
291
|
-
rawCount = Math.min(rawCount + 1, raw.length);
|
|
292
|
-
}
|
|
293
|
-
resample();
|
|
294
|
-
const halfW = params.width * 0.5;
|
|
295
|
-
outerInit = buildRibbon(outer, halfW, outerInit);
|
|
296
|
-
if (core) coreInit = buildRibbon(core, halfW * params.coreWidth, coreInit);
|
|
297
|
-
outerMat.setFloat('uTime', time);
|
|
298
|
-
coreMat?.setFloat('uTime', time);
|
|
299
|
-
if (sparkPs) sparkEmitter.copyFrom(points[0].position); // 火花发射点 = 拖尾头部(刃)
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
let disposed = false;
|
|
303
|
-
let ltTimer = 0;
|
|
304
|
-
const dispose = (): void => {
|
|
305
|
-
if (disposed) return; disposed = true;
|
|
306
|
-
if (ltTimer) window.clearTimeout(ltTimer);
|
|
307
|
-
scene.onBeforeRenderObservable.remove(observer);
|
|
308
|
-
sparkPs?.stop(); sparkPs?.dispose();
|
|
309
|
-
glow?.dispose();
|
|
310
|
-
outer.dispose(); outerMat.dispose();
|
|
311
|
-
core?.dispose(); coreMat?.dispose();
|
|
312
|
-
tex.dispose(); root.dispose();
|
|
313
|
-
};
|
|
314
|
-
ltTimer = scheduleLifetime(params, dispose);
|
|
315
|
-
return { root, dispose };
|
|
316
|
-
},
|
|
317
|
-
};
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
export const runtimeId = "particleEmitter/v1";
|
|
2
|
-
|
|
3
|
-
export const runtimeContract = {
|
|
4
|
-
runtimeId,
|
|
5
|
-
kind: "particleEmitter",
|
|
6
|
-
emitterFields: {
|
|
7
|
-
capacity: "Maximum Babylon particle capacity.",
|
|
8
|
-
particleTexturePath: "Package-relative particle texture path used to load the Babylon ParticleSystem.particleTexture.",
|
|
9
|
-
blendMode: "Babylon particle blend mode label: ADD, STANDARD, MULTIPLY, or ONEONE.",
|
|
10
|
-
shape: "Emitter shape definition generated by the package build stage.",
|
|
11
|
-
spriteSheet: "Optional Babylon particle spritesheet settings for atlas-based particle textures.",
|
|
12
|
-
effectScale: "Spatial scale for the whole effect; scales emitter volume, particle size, velocity, and gravity together.",
|
|
13
|
-
renderColor: "Converted particle color inputs: one base color plus RGB/alpha intensity before writing Babylon color1/color2/colorDead.",
|
|
14
|
-
alphaKeys: "Color alpha gradients sampled over normalized particle lifetime.",
|
|
15
|
-
sizeKeys: "Size gradients sampled over normalized particle lifetime.",
|
|
16
|
-
},
|
|
17
|
-
runtimeBindings: {
|
|
18
|
-
"ParticleSystem.particleTexture": "Texture loaded from emitter.particleTexturePath through urlForPackagePath.",
|
|
19
|
-
"ParticleSystem.blendMode": "Mapped from emitter.blendMode.",
|
|
20
|
-
"ParticleSystem.manualEmitCount": "emitter.manualEmitCount when emitRate is 0.",
|
|
21
|
-
"ParticleSystem.emitRate": "emitter.emitRate for sustained emitters.",
|
|
22
|
-
"ParticleSystem.shape": "Mapped from emitter.shape.type and shape parameters, then scaled by emitter.effectScale.",
|
|
23
|
-
"ParticleSystem.spriteSheet": "Applied from emitter.spriteSheet when enabled.",
|
|
24
|
-
"ParticleSystem.colorGradient": "emitter.alphaKeys applied to the final ParticleSystem.color1.",
|
|
25
|
-
"ParticleSystem.sizeGradient": "emitter.sizeKeys multiplied by emitter.maxSize and emitter.effectScale.",
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const BABYLON_PARTICLE_UPDATE_SPEED_UNIT = 0.01;
|
|
30
|
-
|
|
31
|
-
function num(value, fallback = 0) {
|
|
32
|
-
const next = Number(value);
|
|
33
|
-
return Number.isFinite(next) ? next : fallback;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function readColor(BABYLON, value, fallback = new BABYLON.Color4(1, 1, 1, 1)) {
|
|
37
|
-
if (!value) return fallback;
|
|
38
|
-
return new BABYLON.Color4(num(value.r, fallback.r), num(value.g, fallback.g), num(value.b, fallback.b), num(value.a, fallback.a));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function applyRenderColorIntensity(BABYLON, color, renderColor = {}) {
|
|
42
|
-
const colorIntensity = Math.max(0, num(renderColor.colorIntensity, 1));
|
|
43
|
-
const alphaIntensity = Math.max(0, num(renderColor.alphaIntensity, colorIntensity));
|
|
44
|
-
return new BABYLON.Color4(
|
|
45
|
-
color.r * colorIntensity,
|
|
46
|
-
color.g * colorIntensity,
|
|
47
|
-
color.b * colorIntensity,
|
|
48
|
-
Math.min(1, color.a * alphaIntensity)
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function particleColors(BABYLON, emitter) {
|
|
53
|
-
const renderColor = emitter.renderColor;
|
|
54
|
-
const baseColor = readColor(BABYLON, renderColor?.color, new BABYLON.Color4(1, 1, 1, 1));
|
|
55
|
-
const color1 = applyRenderColorIntensity(BABYLON, baseColor, renderColor);
|
|
56
|
-
return {
|
|
57
|
-
color1,
|
|
58
|
-
color2: color1,
|
|
59
|
-
colorDead: new BABYLON.Color4(color1.r, color1.g, color1.b, 0),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function readVec(BABYLON, value, fallback = new BABYLON.Vector3(0, 0, 0)) {
|
|
64
|
-
if (!value) return fallback;
|
|
65
|
-
return new BABYLON.Vector3(num(value.x, fallback.x), num(value.y, fallback.y), num(value.z, fallback.z));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function blendMode(BABYLON, value) {
|
|
69
|
-
if (value === "STANDARD") return BABYLON.ParticleSystem.BLENDMODE_STANDARD;
|
|
70
|
-
if (value === "MULTIPLY") return BABYLON.ParticleSystem.BLENDMODE_MULTIPLY;
|
|
71
|
-
if (value === "ONEONE") return BABYLON.ParticleSystem.BLENDMODE_ONEONE;
|
|
72
|
-
return BABYLON.ParticleSystem.BLENDMODE_ADD;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function billboardMode(BABYLON, value) {
|
|
76
|
-
if (!value || BABYLON.ParticleSystem[value] === undefined) return undefined;
|
|
77
|
-
return BABYLON.ParticleSystem[value];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function effectScale(emitter) {
|
|
81
|
-
return Math.max(0.0001, num(emitter.effectScale, 1));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function applyRenderOrder(system, emitter) {
|
|
85
|
-
system.renderingGroupId = Math.max(0, Math.round(num(emitter.renderingGroupId, 2)));
|
|
86
|
-
system.alphaIndex = num(emitter.alphaIndex, 100);
|
|
87
|
-
if ("forceDepthWrite" in system) {
|
|
88
|
-
system.forceDepthWrite = false;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function isDeterministic(emitter) {
|
|
93
|
-
return emitter?.deterministic !== false && emitter?.randomize !== true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function midpoint(min, max) {
|
|
97
|
-
return (min + max) * 0.5;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function fract(value) {
|
|
101
|
-
return value - Math.floor(value);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function sequence01(index, axis) {
|
|
105
|
-
if (index === 0) return 0.5;
|
|
106
|
-
const strides = [0.618033988749895, 0.414213562373095, 0.732050807568877, 0.324717957244746, 0.754877666246693, 0.569840290998053];
|
|
107
|
-
return fract(0.5 + index * strides[axis % strides.length] + axis * 0.173205080756888);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function lerp(min, max, t) {
|
|
111
|
-
return min + (max - min) * t;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function applyDeterministicBoxEmitter(BABYLON, system, direction1, direction2, boxMin, boxMax) {
|
|
115
|
-
system.minEmitBox = boxMin;
|
|
116
|
-
system.maxEmitBox = boxMax;
|
|
117
|
-
system.direction1 = direction1;
|
|
118
|
-
system.direction2 = direction2;
|
|
119
|
-
|
|
120
|
-
let positionIndex = 0;
|
|
121
|
-
let directionIndex = 0;
|
|
122
|
-
|
|
123
|
-
system.startPositionFunction = (worldMatrix, positionToUpdate, _particle, isLocal) => {
|
|
124
|
-
const index = positionIndex;
|
|
125
|
-
positionIndex += 1;
|
|
126
|
-
const x = lerp(boxMin.x, boxMax.x, sequence01(index, 0));
|
|
127
|
-
const y = lerp(boxMin.y, boxMax.y, sequence01(index, 1));
|
|
128
|
-
const z = lerp(boxMin.z, boxMax.z, sequence01(index, 2));
|
|
129
|
-
if (isLocal) positionToUpdate.set(x, y, z);
|
|
130
|
-
else BABYLON.Vector3.TransformCoordinatesFromFloatsToRef(x, y, z, worldMatrix, positionToUpdate);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
system.startDirectionFunction = (worldMatrix, directionToUpdate, _particle, isLocal) => {
|
|
134
|
-
const index = directionIndex;
|
|
135
|
-
directionIndex += 1;
|
|
136
|
-
const x = lerp(direction1.x, direction2.x, sequence01(index, 3));
|
|
137
|
-
const y = lerp(direction1.y, direction2.y, sequence01(index, 4));
|
|
138
|
-
const z = lerp(direction1.z, direction2.z, sequence01(index, 5));
|
|
139
|
-
if (isLocal) directionToUpdate.set(x, y, z);
|
|
140
|
-
else BABYLON.Vector3.TransformNormalFromFloatsToRef(x, y, z, worldMatrix, directionToUpdate);
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function applyCocosConeEmitFunctions(BABYLON, system, radius, angle, radiusRange, scale) {
|
|
145
|
-
const safeRadius = Math.max(0, radius * scale);
|
|
146
|
-
const innerSq = (safeRadius * Math.max(0, 1 - radiusRange)) ** 2;
|
|
147
|
-
const outerSq = safeRadius * safeRadius;
|
|
148
|
-
const halfAngle = Math.min(Math.PI, Math.max(0, angle));
|
|
149
|
-
const cosAngle = Math.cos(halfAngle);
|
|
150
|
-
|
|
151
|
-
system.startPositionFunction = (worldMatrix, positionToUpdate, _particle, isLocal) => {
|
|
152
|
-
const s = Math.random() * Math.PI * 2;
|
|
153
|
-
const r2 = innerSq + Math.random() * (outerSq - innerSq);
|
|
154
|
-
const r = Math.sqrt(Math.max(0, r2));
|
|
155
|
-
const lx = r * Math.cos(s);
|
|
156
|
-
const lz = r * Math.sin(s);
|
|
157
|
-
if (isLocal) positionToUpdate.set(lx, 0, lz);
|
|
158
|
-
else BABYLON.Vector3.TransformCoordinatesFromFloatsToRef(lx, 0, lz, worldMatrix, positionToUpdate);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
system.startDirectionFunction = (worldMatrix, directionToUpdate, _particle, isLocal) => {
|
|
162
|
-
const phi = Math.random() * Math.PI * 2;
|
|
163
|
-
const cosTheta = 1 - Math.random() * (1 - cosAngle);
|
|
164
|
-
const sinTheta = Math.sqrt(Math.max(0, 1 - cosTheta * cosTheta));
|
|
165
|
-
const dx = sinTheta * Math.cos(phi);
|
|
166
|
-
const dy = cosTheta;
|
|
167
|
-
const dz = sinTheta * Math.sin(phi);
|
|
168
|
-
if (isLocal) directionToUpdate.set(dx, dy, dz);
|
|
169
|
-
else BABYLON.Vector3.TransformNormalFromFloatsToRef(dx, dy, dz, worldMatrix, directionToUpdate);
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function applyEmitterShape(BABYLON, system, emitter) {
|
|
174
|
-
const scale = effectScale(emitter);
|
|
175
|
-
const shape = emitter.shape || {};
|
|
176
|
-
const type = shape.type;
|
|
177
|
-
const radius = num(shape.radius, 1);
|
|
178
|
-
const radiusRange = Math.min(1, Math.max(0, num(shape.radiusRange, 1)));
|
|
179
|
-
const angle = num(shape.angle, Math.PI / 4);
|
|
180
|
-
const direction1 = readVec(BABYLON, shape.direction1, new BABYLON.Vector3(0, 1, 0));
|
|
181
|
-
const direction2 = readVec(BABYLON, shape.direction2, new BABYLON.Vector3(0, 1, 0));
|
|
182
|
-
const boxMin = readVec(BABYLON, shape.minEmitBox, new BABYLON.Vector3(-radius * 0.5, -radius * 0.5, -radius * 0.5)).scale(scale);
|
|
183
|
-
const boxMax = readVec(BABYLON, shape.maxEmitBox, new BABYLON.Vector3(radius * 0.5, radius * 0.5, radius * 0.5)).scale(scale);
|
|
184
|
-
|
|
185
|
-
if (type === "Box") {
|
|
186
|
-
if (isDeterministic(emitter)) applyDeterministicBoxEmitter(BABYLON, system, direction1, direction2, boxMin, boxMax);
|
|
187
|
-
else system.createBoxEmitter(direction1, direction2, boxMin, boxMax);
|
|
188
|
-
} else if (type === "SphereDirected") {
|
|
189
|
-
system.createDirectedSphereEmitter(radius * scale, direction1, direction2);
|
|
190
|
-
} else if (type === "Sphere") {
|
|
191
|
-
system.createSphereEmitter(radius * scale, radiusRange);
|
|
192
|
-
} else if (type === "Hemisphere") {
|
|
193
|
-
system.createHemisphericEmitter(radius * scale, radiusRange);
|
|
194
|
-
} else if (type === "Cone") {
|
|
195
|
-
const dirSpread = direction1.subtract(direction2).lengthSquared();
|
|
196
|
-
if (dirSpread < 1e-4) {
|
|
197
|
-
system.createConeEmitter(Math.max(0.001, radius * scale), angle);
|
|
198
|
-
applyCocosConeEmitFunctions(BABYLON, system, radius, angle, radiusRange, scale);
|
|
199
|
-
} else {
|
|
200
|
-
system.createDirectedConeEmitter(Math.max(0.001, radius * scale), angle, direction1, direction2);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function applySpriteSheet(system, emitter) {
|
|
206
|
-
const spriteSheet = emitter.spriteSheet || {};
|
|
207
|
-
if (!spriteSheet.enabled) return;
|
|
208
|
-
const deterministic = isDeterministic(emitter);
|
|
209
|
-
|
|
210
|
-
system.isAnimationSheetEnabled = true;
|
|
211
|
-
system.spriteCellWidth = Math.max(1, Math.round(num(spriteSheet.cellWidth, 256)));
|
|
212
|
-
system.spriteCellHeight = Math.max(1, Math.round(num(spriteSheet.cellHeight, 256)));
|
|
213
|
-
system.startSpriteCellID = Math.max(0, Math.round(num(spriteSheet.startCellID, 0)));
|
|
214
|
-
system.endSpriteCellID = Math.max(system.startSpriteCellID, Math.round(num(spriteSheet.endCellID, system.startSpriteCellID)));
|
|
215
|
-
system.spriteCellChangeSpeed = Math.max(0, num(spriteSheet.cellChangeSpeed, 0));
|
|
216
|
-
system.spriteRandomStartCell = deterministic ? spriteSheet.randomStartCell === true && emitter.randomize === true : spriteSheet.randomStartCell !== false;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function addGradients(BABYLON, system, emitter, color1) {
|
|
220
|
-
const scale = effectScale(emitter);
|
|
221
|
-
for (const key of emitter.alphaKeys || []) {
|
|
222
|
-
const time = Math.min(1, Math.max(0, num(key.time, 0)));
|
|
223
|
-
const alpha = Math.min(1, Math.max(0, num(key.alpha, 1)));
|
|
224
|
-
system.addColorGradient?.(time, new BABYLON.Color4(color1.r, color1.g, color1.b, Math.min(1, color1.a * alpha)));
|
|
225
|
-
}
|
|
226
|
-
for (const key of emitter.sizeKeys || []) {
|
|
227
|
-
const time = Math.min(1, Math.max(0, num(key.time, 0)));
|
|
228
|
-
const size = Math.max(0.001, num(key.size, 1) * num(emitter.maxSize, 1) * scale);
|
|
229
|
-
system.addSizeGradient?.(time, size);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function playbackSpeed(emitter) {
|
|
234
|
-
return Math.max(0.001, num(emitter.updateSpeed, BABYLON_PARTICLE_UPDATE_SPEED_UNIT) / BABYLON_PARTICLE_UPDATE_SPEED_UNIT);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function runtimeDuration(emitter) {
|
|
238
|
-
return Math.max(0.1, num(emitter.runtime?.stopAfterSeconds, 3) / playbackSpeed(emitter));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export async function createParticleEmitterRuntime({ BABYLON, scene, emitter, index = 0, urlForPackagePath, shouldStart = false }) {
|
|
242
|
-
if (!BABYLON) throw new Error("createParticleEmitterRuntime requires BABYLON.");
|
|
243
|
-
if (!scene) throw new Error("createParticleEmitterRuntime requires scene.");
|
|
244
|
-
if (!emitter) throw new Error("createParticleEmitterRuntime requires emitter.");
|
|
245
|
-
|
|
246
|
-
const scale = effectScale(emitter);
|
|
247
|
-
const deterministic = isDeterministic(emitter);
|
|
248
|
-
const capacity = Math.max(1, Math.round(num(emitter.capacity, 100)));
|
|
249
|
-
const system = new BABYLON.ParticleSystem(`particle-emitter-${emitter.name || index}`, capacity, scene);
|
|
250
|
-
applyRenderOrder(system, emitter);
|
|
251
|
-
const textureUrl = urlForPackagePath ? urlForPackagePath(emitter.particleTexturePath) : emitter.particleTexturePath;
|
|
252
|
-
system.particleTexture = new BABYLON.Texture(textureUrl, scene);
|
|
253
|
-
system.particleTexture.hasAlpha = true;
|
|
254
|
-
system.emitter = readVec(BABYLON, emitter.origin, BABYLON.Vector3.Zero());
|
|
255
|
-
system.blendMode = blendMode(BABYLON, emitter.blendMode);
|
|
256
|
-
system.isLocal = emitter.isLocal === true;
|
|
257
|
-
const minSize = Math.max(0.001, num(emitter.minSize, 1) * scale);
|
|
258
|
-
const maxSize = Math.max(minSize, num(emitter.maxSize, minSize) * scale);
|
|
259
|
-
system.minSize = deterministic ? midpoint(minSize, maxSize) : minSize;
|
|
260
|
-
system.maxSize = deterministic ? system.minSize : maxSize;
|
|
261
|
-
const minScaleX = Math.max(0.001, num(emitter.minScaleX, 1));
|
|
262
|
-
const maxScaleX = Math.max(minScaleX, num(emitter.maxScaleX, minScaleX));
|
|
263
|
-
system.minScaleX = deterministic ? midpoint(minScaleX, maxScaleX) : minScaleX;
|
|
264
|
-
system.maxScaleX = deterministic ? system.minScaleX : maxScaleX;
|
|
265
|
-
const minScaleY = Math.max(0.001, num(emitter.minScaleY, 1));
|
|
266
|
-
const maxScaleY = Math.max(minScaleY, num(emitter.maxScaleY, minScaleY));
|
|
267
|
-
system.minScaleY = deterministic ? midpoint(minScaleY, maxScaleY) : minScaleY;
|
|
268
|
-
system.maxScaleY = deterministic ? system.minScaleY : maxScaleY;
|
|
269
|
-
const minLifeTime = Math.max(0.02, num(emitter.minLifeTime, 0.55));
|
|
270
|
-
const maxLifeTime = Math.max(minLifeTime, num(emitter.maxLifeTime, minLifeTime));
|
|
271
|
-
system.minLifeTime = deterministic ? midpoint(minLifeTime, maxLifeTime) : minLifeTime;
|
|
272
|
-
system.maxLifeTime = deterministic ? system.minLifeTime : maxLifeTime;
|
|
273
|
-
const minEmitPower = Math.max(0, num(emitter.minEmitPower, 1) * scale);
|
|
274
|
-
const maxEmitPower = Math.max(minEmitPower, num(emitter.maxEmitPower, minEmitPower) * scale);
|
|
275
|
-
system.minEmitPower = deterministic ? midpoint(minEmitPower, maxEmitPower) : minEmitPower;
|
|
276
|
-
system.maxEmitPower = deterministic ? system.minEmitPower : maxEmitPower;
|
|
277
|
-
system.updateSpeed = Math.max(0.00001, num(emitter.updateSpeed, BABYLON_PARTICLE_UPDATE_SPEED_UNIT));
|
|
278
|
-
system.gravity = readVec(BABYLON, emitter.gravity, new BABYLON.Vector3(0, -6, 0)).scale(scale);
|
|
279
|
-
system.emitRate = Math.max(0, num(emitter.emitRate, 0));
|
|
280
|
-
if (system.emitRate <= 0) system.manualEmitCount = Math.max(0, Math.round(num(emitter.manualEmitCount, 0)));
|
|
281
|
-
|
|
282
|
-
const colors = particleColors(BABYLON, emitter);
|
|
283
|
-
system.color1 = colors.color1;
|
|
284
|
-
system.color2 = colors.color2;
|
|
285
|
-
system.colorDead = colors.colorDead;
|
|
286
|
-
const minInitialRotation = num(emitter.minInitialRotation, 0);
|
|
287
|
-
const maxInitialRotation = Math.max(minInitialRotation, num(emitter.maxInitialRotation, minInitialRotation));
|
|
288
|
-
system.minInitialRotation = deterministic ? midpoint(minInitialRotation, maxInitialRotation) : minInitialRotation;
|
|
289
|
-
system.maxInitialRotation = deterministic ? system.minInitialRotation : maxInitialRotation;
|
|
290
|
-
const minAngularSpeed = num(emitter.minAngularSpeed, 0);
|
|
291
|
-
const maxAngularSpeed = Math.max(minAngularSpeed, num(emitter.maxAngularSpeed, minAngularSpeed));
|
|
292
|
-
system.minAngularSpeed = deterministic ? midpoint(minAngularSpeed, maxAngularSpeed) : minAngularSpeed;
|
|
293
|
-
system.maxAngularSpeed = deterministic ? system.minAngularSpeed : maxAngularSpeed;
|
|
294
|
-
|
|
295
|
-
const nextBillboardMode = billboardMode(BABYLON, emitter.billboardMode);
|
|
296
|
-
if (nextBillboardMode !== undefined) system.billboardMode = nextBillboardMode;
|
|
297
|
-
|
|
298
|
-
applyEmitterShape(BABYLON, system, emitter);
|
|
299
|
-
applySpriteSheet(system, emitter);
|
|
300
|
-
addGradients(BABYLON, system, emitter, system.color1);
|
|
301
|
-
|
|
302
|
-
const preview = {
|
|
303
|
-
start: () => system.start(),
|
|
304
|
-
stop: () => system.stop(),
|
|
305
|
-
dispose: () => system.dispose(),
|
|
306
|
-
isStarted: () => system.isStarted(),
|
|
307
|
-
duration: runtimeDuration(emitter),
|
|
308
|
-
system,
|
|
309
|
-
};
|
|
310
|
-
if (shouldStart) preview.start();
|
|
311
|
-
return preview;
|
|
312
|
-
}
|
|
Binary file
|
package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_fiber.png
DELETED
|
Binary file
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import manifest from './wood_crusher_sawdust.json';
|
|
2
|
-
import runtimeUrl from './assets/runtime/particleEmitter.v1.js?url';
|
|
3
|
-
import dustTextureUrl from './assets/textures/wood_crusher_sawdust_dust.png?url';
|
|
4
|
-
import fiberTextureUrl from './assets/textures/wood_crusher_sawdust_fiber.png?url';
|
|
5
|
-
import { createWoodSawdustEffectPackage, type WoodSawdustParams } from '../wood-sawdust-effect-factory';
|
|
6
|
-
|
|
7
|
-
const DEFAULT_PARAMS: WoodSawdustParams = {
|
|
8
|
-
lifetime: 0.96,
|
|
9
|
-
scale: 1.61,
|
|
10
|
-
initialHeight: 0.35,
|
|
11
|
-
dustCount: 9,
|
|
12
|
-
fragmentCount: 6,
|
|
13
|
-
dustColor: { r: 0.8196078431372549, g: 0.6784313725490196, b: 0.45098039215686275 },
|
|
14
|
-
fragmentColor: { r: 0.7490196078431373, g: 0.4588235294117647, b: 0.2196078431372549 },
|
|
15
|
-
dustSize: 1,
|
|
16
|
-
fragmentSize: 1.77,
|
|
17
|
-
dustLifetime: 0.78,
|
|
18
|
-
fragmentLifetime: 0.91,
|
|
19
|
-
dustSpeed: 0.72,
|
|
20
|
-
fragmentSpeed: 1.63,
|
|
21
|
-
fragmentSpread: 2.4,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const woodCrusherSawdustEffectPackage = createWoodSawdustEffectPackage({
|
|
25
|
-
id: 'wood-crusher-sawdust',
|
|
26
|
-
nameZh: '粉碎机木粉',
|
|
27
|
-
manifest,
|
|
28
|
-
runtimeUrl,
|
|
29
|
-
assetUrls: {
|
|
30
|
-
'assets/textures/wood_crusher_sawdust_dust.png': dustTextureUrl,
|
|
31
|
-
'assets/textures/wood_crusher_sawdust_fiber.png': fiberTextureUrl,
|
|
32
|
-
},
|
|
33
|
-
sourceEmitters: manifest.emitters,
|
|
34
|
-
dustEmitterName: 'wood_crusher_sawdust_dust',
|
|
35
|
-
fragmentEmitterName: 'wood_crusher_sawdust_fiber',
|
|
36
|
-
fragmentLabel: '细碎屑',
|
|
37
|
-
fragmentCountLabel: '细碎屑数量',
|
|
38
|
-
fragmentSizeLabel: '细碎屑大小',
|
|
39
|
-
fragmentColorLabel: '细碎屑颜色',
|
|
40
|
-
fragmentSpeedLabel: '细碎屑飞溅速度',
|
|
41
|
-
fragmentSpreadLabel: '细碎屑炸开幅度',
|
|
42
|
-
defaultParams: DEFAULT_PARAMS,
|
|
43
|
-
});
|