@adatechnology/game-vfx-three 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +51 -2
- package/dist/index.d.ts +184 -0
- package/dist/index.global.js +9027 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +1614 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anderson Fernandes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @adatechnology/game-vfx-three
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The three.js renderer of [`@adatechnology/game-vfx`](../game-vfx). It draws a `VfxWorld` with the shaders of the Explosion Lab and lends the host an
|
|
4
|
+
`EffectSink` (`@adatechnology/game-contracts`) so modules ask for effects by id without bringing particles of their own.
|
|
5
|
+
|
|
6
|
+
- **Puffs**: one icosahedron instance each, faceted, with the five-band posterised heat ramp, the noise dissolve and the banded light of the Lab
|
|
7
|
+
(shader verbatim; the only change is the r160 `colorspace_fragment` chunk). **Shrapnel** streaks, additive **halos**, the **shock** shell and the dust
|
|
8
|
+
ring on the ground, glowing **debris** (two instanced meshes, rocks and plates), the **blast lights** and **fire lights** as point lights.
|
|
9
|
+
- **Marks**: scorch, dirt, rim rocks and blood as meshes. A scorch or dirt mesh conforms to the ground as it was when the mark was made, so craters
|
|
10
|
+
and marks agree. `createGroundPatch(THREE, { field })` is the Lab's 60 m patch as a mesh that follows a `GroundField`.
|
|
11
|
+
- **Screen**: `overlay` puts the Lab's white flash (`mix-blend-mode: screen`) and the Workshop's white, red edge, sides and gas tint inside your container.
|
|
12
|
+
`applyCamera()` shakes the camera for the render that follows and answers the function that puts it back.
|
|
13
|
+
- **Workshop layers**: what `world.play` fills is drawn too, made when the first recipe plays: the two sprite systems with the page's soft-point shader, streaks,
|
|
14
|
+
500 instanced debris boxes, the decals (wet and dry), the expanding rings and the flash and fire lights. The Workshop's screen state joins the overlay (the
|
|
15
|
+
stronger value of each field). The lens blobs and the post-processing the page paints on a 2D canvas are not drawn yet.
|
|
16
|
+
- **Bench module**: `createVfxTestModule({ locale })` (id `vfx`, group `effects`) for `game-test-room`: pick an explosion or a recipe (listed by team), its variant,
|
|
17
|
+
replay or auto replay, distance, LOD band, quality and six layers (`show_puffs`, `show_sparks`, `show_glows`, `show_debris`, `show_shock`, `show_sprites`),
|
|
18
|
+
with the sheet of the effect, a timeline whose playhead follows the effect's clock, and the counters against the budget (particles 400, debris 40, lights 6).
|
|
19
|
+
It plays through `host.effects`, or into a renderer of its own when the host lends none.
|
|
20
|
+
- Lights use the raw colours of the Lab and are multiplied by `lightScale` (default `Math.PI`: r155+ lights are physical, the Lab's r128 ones were not).
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import * as THREE from 'three'
|
|
24
|
+
import { createVfxRenderer } from '@adatechnology/game-vfx-three'
|
|
25
|
+
|
|
26
|
+
const vfx = createVfxRenderer(THREE, { scene, camera, overlay: container, vfx: { profile: 'high', seed: 1 } })
|
|
27
|
+
vfx.play({ effect: 'm67', point: [0, 0.1, 0], seed: 7 }) // EffectSink: a handle, or null for an unknown id
|
|
28
|
+
|
|
29
|
+
// every frame: steps the world, redraws, then render with the shake around it
|
|
30
|
+
vfx.update(dt)
|
|
31
|
+
const restore = vfx.applyCamera()
|
|
32
|
+
gl.render(scene, camera)
|
|
33
|
+
restore()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Pass a `VfxWorld` as `vfx` to share one world between renderers (the test room does): the renderer that owns the loop calls `update`, nobody else.
|
|
37
|
+
`configure({ lod_band: 'auto' | 'near' | 'middle' | 'far' })` forces the scale, `configure({ quality: 'high' | 'medium' | 'low' })` thins the recipes and `configure({ layer_puffs: false })` hides a layer (`puffs`, `sparks`, `glows`, `debris`, `shock`, `sprites`). In Node (tests) pass `createCanvas` so the marks can draw their textures.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
pnpm add @adatechnology/game-vfx-three three
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Browser without a bundler (the global is `GameVfxThree`; load three.js first):
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<script src="https://unpkg.com/@adatechnology/game-vfx-three/dist/index.global.js"></script>
|
|
49
|
+
<script>
|
|
50
|
+
const vfx = GameVfxThree.createVfxRenderer(THREE, { scene, camera })
|
|
51
|
+
</script>
|
|
52
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as THREE_NS from 'three';
|
|
2
|
+
import { EffectSink, TestableModule } from '@adatechnology/game-contracts';
|
|
3
|
+
import { VfxWorld, ScreenState, VfxOptions, VfxProfileName, EffectDef, ExplosionDef, GroundField, SpriteKindSystem, MarkEvent } from '@adatechnology/game-vfx';
|
|
4
|
+
|
|
5
|
+
/** The only three.js members the renderer needs at runtime. Pass your own `THREE` instance (the whole namespace works). */
|
|
6
|
+
type ThreeLib = Pick<typeof THREE_NS, 'AdditiveBlending' | 'BufferAttribute' | 'BufferGeometry' | 'BoxGeometry' | 'CanvasTexture' | 'CircleGeometry' | 'Color' | 'DodecahedronGeometry' | 'DynamicDrawUsage' | 'Euler' | 'Float32BufferAttribute' | 'Group' | 'IcosahedronGeometry' | 'InstancedBufferAttribute' | 'InstancedBufferGeometry' | 'InstancedMesh' | 'LineBasicMaterial' | 'LineSegments' | 'LinearSRGBColorSpace' | 'Matrix4' | 'Mesh' | 'MeshBasicMaterial' | 'MeshStandardMaterial' | 'NormalBlending' | 'PlaneGeometry' | 'Points' | 'PointLight' | 'Quaternion' | 'RepeatWrapping' | 'RingGeometry' | 'SRGBColorSpace' | 'DoubleSide' | 'ShaderMaterial' | 'SphereGeometry' | 'Vector3'>;
|
|
7
|
+
/** A canvas the marks draw their textures on. A browser has one; a test supplies a fake. */
|
|
8
|
+
interface CanvasLike {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
getContext(kind: '2d'): CanvasContextLike | null;
|
|
12
|
+
}
|
|
13
|
+
/** The drawing calls the scorch and dirt textures use. */
|
|
14
|
+
interface CanvasContextLike {
|
|
15
|
+
fillStyle: unknown;
|
|
16
|
+
strokeStyle: unknown;
|
|
17
|
+
lineWidth: number;
|
|
18
|
+
globalAlpha: number;
|
|
19
|
+
clearRect(x: number, y: number, w: number, h: number): void;
|
|
20
|
+
fillRect(x: number, y: number, w: number, h: number): void;
|
|
21
|
+
beginPath(): void;
|
|
22
|
+
moveTo(x: number, y: number): void;
|
|
23
|
+
lineTo(x: number, y: number): void;
|
|
24
|
+
stroke(): void;
|
|
25
|
+
arc(x: number, y: number, radius: number, start: number, end: number): void;
|
|
26
|
+
fill(): void;
|
|
27
|
+
strokeRect(x: number, y: number, w: number, h: number): void;
|
|
28
|
+
createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): {
|
|
29
|
+
addColorStop(offset: number, color: string): void;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
interface VfxRendererOptions {
|
|
33
|
+
/** Where the effects live: everything is added under one group. */
|
|
34
|
+
scene: THREE_NS.Object3D;
|
|
35
|
+
camera: THREE_NS.Camera;
|
|
36
|
+
/** A shared world (the test room lends one to every renderer) or the options of the world to create. */
|
|
37
|
+
vfx?: VfxWorld | VfxOptions;
|
|
38
|
+
/** Pool sizes when the world is created here (`vfx` given as options without a profile). Default `medium`. */
|
|
39
|
+
quality?: VfxProfileName;
|
|
40
|
+
/** Where the screen flash goes (the room's container). Without it nothing is drawn on the screen. */
|
|
41
|
+
overlay?: HTMLElement;
|
|
42
|
+
/** Skips the flash and the camera shake. Default: what the browser says (`prefers-reduced-motion`). */
|
|
43
|
+
reducedMotion?: boolean;
|
|
44
|
+
/** Canvas factory for the textures of the marks. Default `document.createElement('canvas')`. */
|
|
45
|
+
createCanvas?: () => CanvasLike;
|
|
46
|
+
/** Direction towards the light the puffs are shaded with. Default the Lab's moon. */
|
|
47
|
+
lightDirection?: readonly [number, number, number];
|
|
48
|
+
/** Pixels of point size per metre at a distance of one metre for the sprite kinds; roughly `viewportHeight / (2 * tan(fov / 2))`. Default 700. */
|
|
49
|
+
pointScale?: number;
|
|
50
|
+
/** Multiplies the blast and fire lights. Default `Math.PI`: three.js r155+ lights are physical, the Lab's were not (r128). */
|
|
51
|
+
lightScale?: number;
|
|
52
|
+
}
|
|
53
|
+
/** The world's counters flattened (`lastBlast` becomes `last_puffs`, `last_sparks`, `last_chunks`, `last_lights`), plus `drawCalls`. */
|
|
54
|
+
type VfxRendererStats = Readonly<Record<string, number>>;
|
|
55
|
+
interface VfxRenderer extends EffectSink {
|
|
56
|
+
readonly world: VfxWorld;
|
|
57
|
+
/** Steps the world, then redraws everything from it. Call once per frame (a shared world is stepped here, nowhere else). */
|
|
58
|
+
update(dt: number): void;
|
|
59
|
+
/** Shakes the camera for the render that follows (the Lab's L1092) and answers the function that puts it back. */
|
|
60
|
+
applyCamera(): () => void;
|
|
61
|
+
stats(): VfxRendererStats;
|
|
62
|
+
/** The screen state the overlay draws (shake, flash...). */
|
|
63
|
+
screen(): Readonly<ScreenState>;
|
|
64
|
+
dispose(): void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** `configure({ layer_<id>: false })` hides a layer of the renderer: `puffs`, `sparks`, `glows`, `debris`, `shock`, `sprites`. */
|
|
68
|
+
declare const VFX_LAYERS: readonly ["puffs", "sparks", "glows", "debris", "shock", "sprites"];
|
|
69
|
+
/** Draws a `VfxWorld`: the Lab's shaders, debris, shock, lights, marks and the screen overlay, and plays effects asked of it by id (`EffectSink`). */
|
|
70
|
+
declare function createVfxRenderer(THREE: ThreeLib, options: VfxRendererOptions): VfxRenderer;
|
|
71
|
+
|
|
72
|
+
interface VfxTestModuleOptions {
|
|
73
|
+
readonly locale?: string;
|
|
74
|
+
/** The recipes the bench lists (default `GAME_EFFECTS`) and the explosions (default `EXPLOSIONS`). */
|
|
75
|
+
readonly effects?: Readonly<Record<string, EffectDef>>;
|
|
76
|
+
readonly explosions?: Readonly<Record<string, ExplosionDef>>;
|
|
77
|
+
/** Seconds between two replays while `auto_repeat` is on. */
|
|
78
|
+
readonly repeatSeconds?: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The effects bench: pick an explosion or a recipe (listed by team), its variant, the distance, the quality and the layers, replay it or let it repeat,
|
|
82
|
+
* and watch the particle and light counters against the budget. It plays through `host.effects` (the room's world) and, in a host without one, draws
|
|
83
|
+
* into a renderer of its own.
|
|
84
|
+
*/
|
|
85
|
+
declare function createVfxTestModule(options?: VfxTestModuleOptions): TestableModule;
|
|
86
|
+
|
|
87
|
+
/** The words of the effects bench, per locale (the effects' own texts live in `@adatechnology/game-vfx`). */
|
|
88
|
+
interface BenchText {
|
|
89
|
+
readonly module: string;
|
|
90
|
+
readonly controls: Readonly<Record<'effect' | 'variant' | 'replay' | 'auto_repeat' | 'distance' | 'lod_band' | 'quality', string>>;
|
|
91
|
+
readonly lod: Readonly<Record<'auto' | 'near' | 'middle' | 'far', string>>;
|
|
92
|
+
readonly quality: Readonly<Record<'high' | 'medium' | 'low', string>>;
|
|
93
|
+
readonly layers: Readonly<Record<'puffs' | 'sparks' | 'glows' | 'debris' | 'shock' | 'sprites', string>>;
|
|
94
|
+
readonly metrics: Readonly<Record<'puffs' | 'sparks' | 'debris' | 'lights' | 'budget_particles' | 'lod' | 'phase' | 'blast_time', string>>;
|
|
95
|
+
readonly sheet: Readonly<Record<'explosion' | 'lethal' | 'injury' | 'radius' | 'fireballs' | 'particles' | 'scale', string>>;
|
|
96
|
+
readonly none: string;
|
|
97
|
+
}
|
|
98
|
+
declare const benchTexts: Readonly<Record<string, BenchText>>;
|
|
99
|
+
declare const benchText: (locale: string) => BenchText;
|
|
100
|
+
|
|
101
|
+
interface GroundPatchOptions {
|
|
102
|
+
/** The height field the craters dig; the mesh follows it. */
|
|
103
|
+
readonly field: GroundField;
|
|
104
|
+
/** What the patch is made of (default a plain flat-shaded grey standard material). */
|
|
105
|
+
readonly material?: THREE_NS.Material;
|
|
106
|
+
/** Texture coordinates repeat every this many metres (the Lab's 17). */
|
|
107
|
+
readonly textureMetres?: number;
|
|
108
|
+
}
|
|
109
|
+
interface GroundPatch {
|
|
110
|
+
readonly mesh: THREE_NS.Mesh;
|
|
111
|
+
/** Redraws the vertices when the field changed since the last call. Returns whether it did. */
|
|
112
|
+
sync(): boolean;
|
|
113
|
+
dispose(): void;
|
|
114
|
+
}
|
|
115
|
+
/** The Lab's 60 m deformable patch with 33 cm cells (L60-83), as a mesh that follows a `GroundField`. */
|
|
116
|
+
declare function createGroundPatch(THREE: ThreeLib, options: GroundPatchOptions): GroundPatch;
|
|
117
|
+
|
|
118
|
+
interface ScreenOverlay {
|
|
119
|
+
update(state: Readonly<ScreenState>): void;
|
|
120
|
+
dispose(): void;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The screen effects as overlays inside the host's container: the Lab's white flash (L1087, `mix-blend-mode: screen`) and the Workshop's white,
|
|
124
|
+
* red edge, the two sides and the gas tint (W356-366), with the same colours. Nothing is drawn while the state is at rest.
|
|
125
|
+
*/
|
|
126
|
+
declare function createScreenOverlay(container: HTMLElement): ScreenOverlay;
|
|
127
|
+
|
|
128
|
+
interface SpriteLayer {
|
|
129
|
+
readonly additive: THREE_NS.Points;
|
|
130
|
+
readonly normal: THREE_NS.Points;
|
|
131
|
+
/** Tells the GPU what changed and answers how many sprites are drawn. */
|
|
132
|
+
sync(): number;
|
|
133
|
+
dispose(): void;
|
|
134
|
+
}
|
|
135
|
+
/** Draws the game's sprite kinds (debris, sparks, smoke, steam, drips...) as two soft point layers, additive and normal. */
|
|
136
|
+
declare function createSpriteLayer(THREE: ThreeLib, system: SpriteKindSystem, pointScale: number): SpriteLayer;
|
|
137
|
+
|
|
138
|
+
/** The soft round point sprite of the game's sprite kinds (moved verbatim from the destruction renderer's particles). */
|
|
139
|
+
declare const SPRITE_VERTEX = "\nuniform float uScale;\nattribute float size;\nattribute float alpha;\nattribute vec3 pcolor;\nvarying float vAlpha;\nvarying vec3 vColor;\nvoid main() {\n vAlpha = alpha;\n vColor = pcolor;\n vec4 mv = modelViewMatrix * vec4(position, 1.0);\n gl_PointSize = min(size * uScale / max(0.1, -mv.z), 256.0);\n gl_Position = projectionMatrix * mv;\n}";
|
|
140
|
+
declare const SPRITE_FRAGMENT = "\nvarying float vAlpha;\nvarying vec3 vColor;\nvoid main() {\n float d = length(gl_PointCoord - vec2(0.5)) * 2.0;\n float a = (1.0 - smoothstep(0.35, 1.0, d)) * vAlpha;\n if (a < 0.004) discard;\n gl_FragColor = vec4(vColor, a);\n}";
|
|
141
|
+
|
|
142
|
+
interface MarkLayer {
|
|
143
|
+
readonly group: THREE_NS.Group;
|
|
144
|
+
/** The shared rock and plate shapes, so the debris and the rim rocks use the same geometry. */
|
|
145
|
+
handle(event: MarkEvent): void;
|
|
146
|
+
update(dt: number): void;
|
|
147
|
+
/** The meshes in the group right now. */
|
|
148
|
+
count(): number;
|
|
149
|
+
dispose(): void;
|
|
150
|
+
}
|
|
151
|
+
interface MarkLayerOptions {
|
|
152
|
+
readonly ground: GroundField | undefined;
|
|
153
|
+
readonly createCanvas: (() => CanvasLike) | undefined;
|
|
154
|
+
readonly rockGeometry: THREE_NS.BufferGeometry;
|
|
155
|
+
readonly plateGeometry: THREE_NS.BufferGeometry;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The scorch, dirt, rim rocks and blood of the Lab as meshes (L443-476). A scorch or dirt mesh follows the ground as it was the moment the mark was
|
|
159
|
+
* made (`conform`), so the renderer builds it from the mark event, not later.
|
|
160
|
+
*/
|
|
161
|
+
declare function createMarkLayer(THREE: ThreeLib, options: MarkLayerOptions): MarkLayer;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The puff shader of the Lab (L130-192), verbatim: a faceted icosahedron per puff, a five-band posterised heat ramp, a noise dissolve and banded light.
|
|
165
|
+
* The only change is the r160 colour-space chunk: `encodings_fragment` (r128) is `colorspace_fragment` now.
|
|
166
|
+
*/
|
|
167
|
+
declare const PUFF_VERTEX = "\nfloat h3(vec3 p){p=fract(p*.3183099+.1);p*=17.0;return fract(p.x*p.y*p.z*(p.x+p.y+p.z));}\nfloat vn(vec3 x){vec3 i=floor(x),f=fract(x);f=f*f*(3.-2.*f);\n return mix(mix(mix(h3(i),h3(i+vec3(1,0,0)),f.x),mix(h3(i+vec3(0,1,0)),h3(i+vec3(1,1,0)),f.x),f.y),\n mix(mix(h3(i+vec3(0,0,1)),h3(i+vec3(1,0,1)),f.x),mix(h3(i+vec3(0,1,1)),h3(i+vec3(1,1,1)),f.x),f.y),f.z);}\n\nattribute vec4 aP;attribute vec4 aD;attribute vec3 aC;\nvarying vec3 vWP;varying vec3 vL;varying vec4 vD;varying vec3 vC;\nvoid main(){\n vec3 p=position;\n float s=aD.z;\n float n=vn(p*1.6+vec3(s*11.0,s*7.0,s*3.0));\n p*=0.78+0.5*n;\n float a=s*6.2831;float c=cos(a),si=sin(a);p.xz=mat2(c,-si,si,c)*p.xz;\n vL=p;\n vec3 wp=aP.xyz+p*aP.w;\n vWP=wp;vD=aD;vC=aC;\n gl_Position=projectionMatrix*viewMatrix*vec4(wp,1.0);\n}";
|
|
168
|
+
declare const PUFF_FRAGMENT = "\nfloat h3(vec3 p){p=fract(p*.3183099+.1);p*=17.0;return fract(p.x*p.y*p.z*(p.x+p.y+p.z));}\nfloat vn(vec3 x){vec3 i=floor(x),f=fract(x);f=f*f*(3.-2.*f);\n return mix(mix(mix(h3(i),h3(i+vec3(1,0,0)),f.x),mix(h3(i+vec3(0,1,0)),h3(i+vec3(1,1,0)),f.x),f.y),\n mix(mix(h3(i+vec3(0,0,1)),h3(i+vec3(1,0,1)),f.x),mix(h3(i+vec3(0,1,1)),h3(i+vec3(1,1,1)),f.x),f.y),f.z);}\n\nuniform vec3 uLight;uniform vec3 uSky;uniform vec3 uGround;uniform vec3 uGlowPos;uniform vec3 uGlowCol;uniform float uGlowI;\nvarying vec3 vWP;varying vec3 vL;varying vec4 vD;varying vec3 vC;\nvec3 L(vec3 c){return pow(c,vec3(2.2));}\nvec3 ramp(float h){\n vec3 c0=L(vec3(.36,.07,.03)),c1=L(vec3(.76,.21,.1)),c2=L(vec3(1.,.54,.16)),c3=L(vec3(1.,.83,.42)),c4=L(vec3(1.,.96,.84));\n if(h<.25)return mix(c0,c1,h/.25);\n if(h<.5)return mix(c1,c2,(h-.25)/.25);\n if(h<.8)return mix(c2,c3,(h-.5)/.3);\n return mix(c3,c4,clamp((h-.8)/.4,0.,1.));\n}\nvoid main(){\n vec3 N=normalize(cross(dFdx(vWP),dFdy(vWP)));\n float age=vD.x,heat=vD.y,kind=vD.w;\n float n=vn(vL*2.4+vD.z*13.0);\n float diss=smoothstep(.5,1.,age);\n if(n<diss*1.08-.04)discard;\n float lam=max(dot(N,uLight),0.);\n float band=lam>.55?1.:lam>.15?.68:.42;\n vec3 col=vC*(vec3(.5,.56,.7)*band*1.05+vec3(.2,.19,.18)+uGround*(.4-.4*N.y));\n vec3 tg=uGlowPos-vWP;float dg=length(tg)+.001;\n float gi=uGlowI/(1.+dg*dg*.05)*(.3+.7*max(dot(N,tg/dg),0.));\n col+=uGlowCol*gi*vC*1.6;\n float h=heat*(.72+.56*n)+heat*(-N.y)*.18;\n if(h>.05){\n float hq=floor(h*5.+.5)/5.;\n vec3 fc=ramp(clamp(hq,0.,1.2))*(1.1+3.1*hq);\n col=mix(col,fc,smoothstep(.04,.2,h));\n }\n if(kind>2.5&&kind<3.5){col+=vec3(.02,.025,.03);}\n gl_FragColor=vec4(col,1.);\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n}";
|
|
169
|
+
|
|
170
|
+
/** The streak shader of shrapnel, sparks and embers (Lab L255-275), verbatim except for the r160 colour-space chunk. */
|
|
171
|
+
declare const SPARK_VERTEX = "\nattribute vec3 aPos;attribute vec3 aVel;attribute vec4 aE;varying vec2 vU;varying vec4 vE;\nvoid main(){\n float sp=length(aVel)+1e-4;vec3 d=aVel/sp;\n vec3 tc=normalize(cameraPosition-aPos);\n vec3 sd=normalize(cross(d,tc)+vec3(1e-5));\n float len=max(aE.y*2.5,min(sp*aE.z,2.2));\n vec3 wp=aPos+sd*position.x*aE.y-d*position.y*len;\n vU=position.xy;vE=aE;\n gl_Position=projectionMatrix*viewMatrix*vec4(wp,1.);\n}";
|
|
172
|
+
declare const SPARK_FRAGMENT = "\nvarying vec2 vU;varying vec4 vE;\nvoid main(){\n float age=vE.x,k=vE.w;\n float w=1.-abs(vU.x)*2.;w=w*w;\n float tail=1.-vU.y;\n vec3 c;\n if(k<.5){c=mix(vec3(4.,3.3,2.2),vec3(2.4,.7,.12),smoothstep(0.,.55,age));c=mix(c,vec3(.5,.06,.02),smoothstep(.55,1.,age));}\n else if(k<1.5){c=mix(vec3(1.6,1.6,1.6),vec3(.35,.35,.36),smoothstep(0.,.4,age));}\n else if(k<2.5){c=mix(vec3(5.,5.,6.),vec3(1.,1.3,2.),smoothstep(0.,.5,age));}\n else if(k<3.5){c=vec3(.9,1.2,1.4)*(1.-age);}\n else {c=mix(vec3(3.2,1.4,.3),vec3(1.2,.25,.05),age);}\n float a=w*(.35+.65*tail)*(1.-smoothstep(.75,1.,age));\n gl_FragColor=vec4(c*a,1.);\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n}";
|
|
173
|
+
|
|
174
|
+
/** The additive halo (Lab L297-304), verbatim except for the r160 colour-space chunk. */
|
|
175
|
+
declare const GLOW_VERTEX = "attribute vec4 aP;attribute vec4 aC;varying vec2 vU;varying vec4 vC;\nvoid main(){vec3 R=vec3(viewMatrix[0][0],viewMatrix[1][0],viewMatrix[2][0]);vec3 Up=vec3(viewMatrix[0][1],viewMatrix[1][1],viewMatrix[2][1]);\nvec3 wp=aP.xyz+(R*position.x+Up*position.y)*aP.w;vU=position.xy;vC=aC;gl_Position=projectionMatrix*viewMatrix*vec4(wp,1.);}";
|
|
176
|
+
declare const GLOW_FRAGMENT = "varying vec2 vU;varying vec4 vC;void main(){float r=dot(vU,vU);if(r>1.)discard;float f=exp(-r*4.2)-.015;gl_FragColor=vec4(vC.rgb*vC.a*max(f,0.),1.);\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n}";
|
|
177
|
+
|
|
178
|
+
/** The shock shell (fresnel edge, Lab L322-325) and the dust ring on the ground (L326-333), verbatim except for the r160 colour-space chunk. */
|
|
179
|
+
declare const SHELL_VERTEX = "varying vec3 vN;varying vec3 vV;void main(){vec4 wp=modelMatrix*vec4(position,1.);vN=normalize(mat3(modelMatrix)*normal);vV=normalize(cameraPosition-wp.xyz);gl_Position=projectionMatrix*viewMatrix*wp;}";
|
|
180
|
+
declare const SHELL_FRAGMENT = "uniform float uA;varying vec3 vN;varying vec3 vV;void main(){float f=1.-abs(dot(vN,vV));f=pow(f,5.);gl_FragColor=vec4(vec3(1.,.93,.82)*f*uA*.45,1.);\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n}";
|
|
181
|
+
declare const RING_VERTEX = "varying vec2 vU;void main(){vU=position.xz;gl_Position=projectionMatrix*viewMatrix*modelMatrix*vec4(position,1.);}";
|
|
182
|
+
declare const RING_FRAGMENT = "uniform float uA;uniform float uW;uniform vec3 uC;varying vec2 vU;\n float h(vec2 p){return fract(sin(dot(p,vec2(12.9898,78.233)))*43758.5453);}\n void main(){float r=length(vU);float b=smoothstep(1.,1.-uW,r)*smoothstep(1.-uW*3.5,1.-uW,r);float a=atan(vU.y,vU.x);float n=.7+.3*h(vec2(floor(a*14.),1.));gl_FragColor=vec4(uC,b*uA*n);\n #include <tonemapping_fragment>\n #include <colorspace_fragment>\n}";
|
|
183
|
+
|
|
184
|
+
export { type BenchText, type CanvasContextLike, type CanvasLike, GLOW_FRAGMENT, GLOW_VERTEX, type GroundPatch, type GroundPatchOptions, type MarkLayer, type MarkLayerOptions, PUFF_FRAGMENT, PUFF_VERTEX, RING_FRAGMENT, RING_VERTEX, SHELL_FRAGMENT, SHELL_VERTEX, SPARK_FRAGMENT, SPARK_VERTEX, SPRITE_FRAGMENT, SPRITE_VERTEX, type ScreenOverlay, type SpriteLayer, type ThreeLib, VFX_LAYERS, type VfxRenderer, type VfxRendererOptions, type VfxRendererStats, type VfxTestModuleOptions, benchText, benchTexts, createGroundPatch, createMarkLayer, createScreenOverlay, createSpriteLayer, createVfxRenderer, createVfxTestModule };
|