├── examples
├── with-node
│ ├── .npmignore
│ ├── package.json
│ ├── index.html
│ ├── src
│ │ ├── index.js
│ │ └── index.ts
│ ├── style.css
│ ├── package-lock.json
│ └── tsconfig.json
└── with-browser
│ ├── demo.js
│ └── index.html
├── assets
├── img
│ ├── demo.png
│ ├── raindrop.png
│ ├── 84765992_p0.jpg
│ └── 87747832_p0.jpg
└── css
│ └── style.css
├── .gitmodules
├── bundle
└── index.d.ts
├── .npmignore
├── dist
├── utils.d.ts
├── utils.d.ts.map
├── spawner.d.ts.map
├── blur.d.ts.map
├── index.d.ts.map
├── random.d.ts.map
├── spawner.d.ts
├── random.d.ts
├── index.d.ts
├── blur.d.ts
├── raindrop.d.ts.map
├── raindrop.d.ts
├── simulator.d.ts.map
├── renderer.d.ts.map
├── simulator.d.ts
└── renderer.d.ts
├── src
├── utils.ts
├── shader
│ ├── droplet.glsl
│ ├── 2d-frag.glsl
│ ├── raindrop-frag.glsl
│ ├── bg-mist.glsl
│ ├── erase.glsl
│ ├── 2d-vert.glsl
│ ├── blur.glsl
│ ├── raindrop-vert.glsl
│ ├── droplet-vert.glsl
│ └── compose.glsl
├── loader.d.ts
├── spawner.ts
├── random.ts
├── blur.ts
├── index.ts
├── raindrop.ts
├── simulator.ts
└── renderer.ts
├── .github
└── workflows
│ ├── build.yml
│ └── npm-publish.yml
├── .vscode
└── launch.json
├── package.json
├── LICENSE
├── index.html
├── .gitignore
├── tsconfig.json
└── README.md
/examples/with-node/.npmignore:
--------------------------------------------------------------------------------
1 | dist/
--------------------------------------------------------------------------------
/assets/img/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SardineFish/raindrop-fx/HEAD/assets/img/demo.png
--------------------------------------------------------------------------------
/assets/img/raindrop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SardineFish/raindrop-fx/HEAD/assets/img/raindrop.png
--------------------------------------------------------------------------------
/assets/img/84765992_p0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SardineFish/raindrop-fx/HEAD/assets/img/84765992_p0.jpg
--------------------------------------------------------------------------------
/assets/img/87747832_p0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SardineFish/raindrop-fx/HEAD/assets/img/87747832_p0.jpg
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "zogra-renderer"]
2 | path = zogra-renderer
3 | url = https://github.com/SardineFish/zogra-renderer.git
4 |
--------------------------------------------------------------------------------
/bundle/index.d.ts:
--------------------------------------------------------------------------------
1 | import RaindropFXClass from "../dist";
2 |
3 | declare global
4 | {
5 | const RaindropFX: typeof RaindropFXClass;
6 | }
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /.vscode
2 | node_modules
3 | /zogra-renderer
4 | /assets/img/84765992_p0.jpg
5 | /assets/img/87747832_p0.jpg
6 | /assets/img/demo.png
--------------------------------------------------------------------------------
/dist/utils.d.ts:
--------------------------------------------------------------------------------
1 | export interface Time {
2 | dt: number;
3 | total: number;
4 | }
5 | export declare function clamp(x: number, l: number, h: number): number;
6 | export declare function lerp(a: number, b: number, t: number): number;
7 | //# sourceMappingURL=utils.d.ts.map
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export interface Time
2 | {
3 | dt: number,
4 | total: number,
5 | }
6 |
7 | export function clamp(x: number, l: number, h: number)
8 | {
9 | return Math.min(Math.max(x, l), h);
10 | }
11 |
12 | export function lerp(a: number, b: number, t: number)
13 | {
14 | return a + (b - a) * t;
15 | }
--------------------------------------------------------------------------------
/dist/utils.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IAEjB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,UAGpD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,UAGnD"}
--------------------------------------------------------------------------------
/src/shader/droplet.glsl:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | precision mediump float;
3 |
4 | in vec2 vUV;
5 |
6 | uniform sampler2D uMainTex;
7 | uniform vec4 uColor;
8 |
9 | out vec4 fragColor;
10 |
11 | void main()
12 | {
13 | vec4 color = texture(uMainTex, vUV.xy).rgba;
14 | color.rgb *= color.a;
15 | fragColor = vec4(color.rg, 0.0, color.a);
16 | }
--------------------------------------------------------------------------------
/src/shader/2d-frag.glsl:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | precision mediump float;
3 |
4 | in vec4 vColor;
5 | in vec4 vPos;
6 | in vec2 vUV;
7 |
8 | uniform sampler2D uMainTex;
9 | uniform vec4 uColor;
10 |
11 | out vec4 fragColor;
12 |
13 | void main()
14 | {
15 | vec4 color = texture(uMainTex, vUV.xy).rgba;
16 | color.rgba = color * uColor;
17 | fragColor = color.rgba;
18 | }
--------------------------------------------------------------------------------
/src/loader.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.png' {
2 | const contents: Uint8Array
3 | export = contents
4 | }
5 | declare module '*.jpg' {
6 | const contents: Uint8Array
7 | export = contents
8 | }
9 | declare module '*.fbx' {
10 | const contents: string
11 | export = contents
12 | }
13 | declare module '*.glsl' {
14 | const contents: string
15 | export = contents
16 | }
17 |
--------------------------------------------------------------------------------
/src/shader/raindrop-frag.glsl:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | precision mediump float;
3 |
4 | in vec4 vColor;
5 | in vec4 vPos;
6 | in vec2 vUV;
7 | in float vSize;
8 |
9 | uniform sampler2D uMainTex;
10 | uniform float uSize;
11 |
12 | out vec4 fragColor;
13 |
14 | void main()
15 | {
16 | vec4 color = texture(uMainTex, vUV.xy).rgba;
17 |
18 | fragColor = vec4(color.rg * color.a, vSize * color.a, color.a);
19 | }
--------------------------------------------------------------------------------
/src/shader/bg-mist.glsl:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | precision mediump float;
3 |
4 | in vec2 vUV;
5 |
6 | uniform sampler2D uMainTex;
7 | uniform sampler2D uMistTex;
8 | uniform vec4 uMistColor;
9 |
10 | out vec4 fragColor;
11 |
12 | void main()
13 | {
14 | vec4 color = texture(uMainTex, vUV.xy).rgba;
15 | color.rgb += vec3(uMistColor);
16 | color.a = texture(uMistTex, vUV.xy).r * uMistColor.a;
17 | fragColor = color.rgba;
18 | }
--------------------------------------------------------------------------------
/src/shader/erase.glsl:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | precision mediump float;
3 |
4 | in vec4 vColor;
5 | in vec4 vPos;
6 | in vec2 vUV;
7 |
8 | uniform sampler2D uMainTex;
9 | uniform vec4 uColor;
10 | uniform vec2 uEraserSmooth;
11 |
12 | out vec4 fragColor;
13 |
14 | void main()
15 | {
16 | vec4 color = texture(uMainTex, vUV.xy).rgba;
17 | color.a = smoothstep(uEraserSmooth.x, uEraserSmooth.y, color.a);
18 | fragColor = color.rgba;
19 | }
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build & Test
2 | on:
3 | push:
4 | branches: [ master ]
5 | pull_request:
6 | branches: [ master ]
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-node@v2
14 | with:
15 | node-version: '15.x'
16 | registry-url: 'https://registry.npmjs.org'
17 | - run: npm install
18 | - run: npm test
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Attach to Chrome",
9 | "port": 9222,
10 | "request": "attach",
11 | "type": "pwa-chrome",
12 | "webRoot": "${workspaceFolder}"
13 | }
14 |
15 | ]
16 | }
--------------------------------------------------------------------------------
/examples/with-node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "raindrop-fx-example",
3 | "version": "1.0.0",
4 | "description": "Example of raindrop-fx using with nodejs.",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "npx esbuild --bundle --outdir=./dist --platform=browser ./src/index.js",
8 | "test": "npm run build && npx tsc"
9 | },
10 | "author": "SardineFish",
11 | "license": "MIT",
12 | "dependencies": {
13 | "raindrop-fx": "file:///../.."
14 | },
15 | "devDependencies": {
16 | "esbuild": "^0.9.5"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/with-node/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Raindrop Effect
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/with-node/src/index.js:
--------------------------------------------------------------------------------
1 | const RaindropFX = require("raindrop-fx");
2 |
3 | const canvas = document.querySelector("#canvas");
4 | const rect = canvas.getBoundingClientRect();
5 | canvas.width = rect.width;
6 | canvas.height = rect.height;
7 |
8 | const raindropFx = new RaindropFX({
9 | canvas: canvas,
10 | background: "../../assets/img/84765992_p0.jpg",
11 | });
12 |
13 | window.onresize = () =>
14 | {
15 | const rect = canvas.getBoundingClientRect();
16 | raindropFx.resize(rect.width, rect.height);
17 | }
18 |
19 | raindropFx.start();
--------------------------------------------------------------------------------
/examples/with-browser/demo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {import("../../bundle/index")}
3 | */
4 |
5 | const canvas = document.querySelector("#canvas");
6 | const rect = canvas.getBoundingClientRect();
7 | canvas.width = rect.width;
8 | canvas.height = rect.height;
9 |
10 | const raindropFx = new RaindropFX({
11 | canvas: canvas,
12 | background: "../../assets/img/84765992_p0.jpg",
13 | });
14 |
15 | window.onresize = () =>
16 | {
17 | const rect = canvas.getBoundingClientRect();
18 | raindropFx.resize(rect.width, rect.height);
19 | }
20 |
21 | raindropFx.start();
--------------------------------------------------------------------------------
/examples/with-node/src/index.ts:
--------------------------------------------------------------------------------
1 | import RaindropFX from "raindrop-fx";
2 |
3 | const canvas = document.querySelector("#canvas") as HTMLCanvasElement;
4 | const rect = canvas.getBoundingClientRect();
5 | canvas.width = rect.width;
6 | canvas.height = rect.height;
7 |
8 | const raindropFx = new RaindropFX({
9 | canvas: canvas,
10 | background: "../../assets/img/84765992_p0.jpg",
11 | });
12 |
13 | window.onresize = () =>
14 | {
15 | const rect = canvas.getBoundingClientRect();
16 | raindropFx.resize(rect.width, rect.height);
17 | }
18 |
19 | raindropFx.start();
--------------------------------------------------------------------------------
/dist/spawner.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"spawner.d.ts","sourceRoot":"","sources":["../src/spawner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAElE,qBAAa,OAAO;IAIhB,WAAW,SAAK;IAChB,SAAS,SAAK;IAEd,OAAO,CAAC,SAAS,CAAoB;gBAEzB,SAAS,EAAE,iBAAiB,EAAE,OAAO,EAAE,gBAAgB;IAMnE,IAAI,QAAQ,qBAAkD;IAC9D,IAAI,IAAI,qBAA8C;IACtD,IAAI,SAAS,SAA6C;IAE1D,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAKvB,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;IAe/B,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,SAAI;CAI7C"}
--------------------------------------------------------------------------------
/dist/blur.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"blur.d.ts","sourceRoot":"","sources":["../src/blur.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,aAAa,EAAsB,OAAO,EAAoC,IAAI,EAAY,aAAa,EAAiB,MAAM,gBAAgB,CAAC;;AAItM,cAAM,YAAa,SAAQ,iBAA0C;IAGjE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAQ;IAG/B,WAAW,EAAE,IAAI,CAAc;IAG/B,YAAY,EAAE,MAAM,CAAK;CAC5B;AAED,qBAAa,YAAY;IAErB,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,EAAE,aAAa,EAAE,CAAM;IAC5B,YAAY,eAAsB;gBAEtB,QAAQ,EAAE,aAAa;IAKnC,IAAI,CAAC,OAAO,EAAE,OAAO;IAYrB,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,GAAE,MAAU,EAAE,MAAM,gBAAgB;IAcpE,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM;IAwB5C,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,gBAAgB;CAuB1D"}
--------------------------------------------------------------------------------
/examples/with-node/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 |
4 | background-position: 0px 0px,
5 | 16px 16px;
6 | background-size: 32px 32px;
7 | background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee 100%),
8 | linear-gradient(45deg, #eee 25%, white 25%, white 75%, #eee 75%, #eee 100%);
9 | }
10 |
11 | #root {
12 | position: absolute;
13 | width: 100vw;
14 | height: 100vh;
15 | display: flex;
16 | justify-content: center;
17 | align-items: center;
18 | }
19 |
20 | #canvas {
21 | width: 100%;
22 | height: 100%;
23 | }
--------------------------------------------------------------------------------
/examples/with-browser/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Raindrop Effect
8 |
9 |
10 |
11 |
12 |
13 |
14 |
Loading...
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/dist/index.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,WAAW,EAAQ,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGlE,UAAU,OAAQ,SAAQ,gBAAgB,EAAE,aAAa;CAExD;AAED,cAAM,UAAU;IAEL,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,SAAS,EAAE,iBAAiB,CAAC;IAEpC,OAAO,CAAC,UAAU,CAAK;gBAEX,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG;QAAC,MAAM,EAAE,iBAAiB,CAAA;KAAC;IAsD7D,KAAK;IAsBX,IAAI;IAKJ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAQ9B,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW;IAMpD,OAAO;IAMP,OAAO,CAAC,MAAM;CAOjB;AAED,SAAS,UAAU,CAAC"}
--------------------------------------------------------------------------------
/dist/random.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"random.d.ts","sourceRoot":"","sources":["../src/random.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,IAAI,EAAE,IAAI,EAAiB,MAAM,gBAAgB,CAAC;AAE1E,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAEpE,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,CAAC,CAAC;CACb;AAED,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAYlF;AACD,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAG7C;AAQD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,UAG/C;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAIhD;AAED;;GAEG;AACH,wBAAgB,MAAM,WAGrB;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,UAGnD"}
--------------------------------------------------------------------------------
/dist/spawner.d.ts:
--------------------------------------------------------------------------------
1 | import { Rect, vec2 } from "zogra-renderer";
2 | import { RainDrop } from "./raindrop";
3 | import { RaindropSimulator, SimulatorOptions } from "./simulator";
4 | export declare class Spawner {
5 | currentTime: number;
6 | nextSpawn: number;
7 | private simulator;
8 | constructor(simulator: RaindropSimulator, options: SimulatorOptions);
9 | get interval(): [number, number];
10 | get size(): [number, number];
11 | get spawnRect(): Rect;
12 | update(dt: number): this;
13 | trySpawn(): Iterable;
14 | spawn(pos: vec2, size: number, density?: number): RainDrop;
15 | }
16 | //# sourceMappingURL=spawner.d.ts.map
--------------------------------------------------------------------------------
/src/shader/2d-vert.glsl:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | precision mediump float;
3 |
4 | in vec3 aPos;
5 | in vec4 aColor;
6 | in vec2 aUV;
7 | in vec3 aNormal;
8 |
9 | uniform mat4 uTransformM;
10 | uniform mat4 uTransformVP;
11 | uniform mat4 uTransformMVP;
12 | uniform mat4 uTransformM_IT;
13 |
14 | out vec4 vColor;
15 | out vec4 vPos;
16 | out vec2 vUV;
17 | out vec3 vNormal;
18 | out vec3 vWorldPos;
19 |
20 | void main()
21 | {
22 | gl_Position = uTransformMVP * vec4(aPos, 1);
23 | vPos = gl_Position;
24 | vColor = aColor;
25 | vUV = aUV;
26 | vNormal = (uTransformM_IT * vec4(aNormal, 0)).xyz;
27 | vWorldPos = (uTransformM * vec4(aPos, 1)).xyz;
28 |
29 | }
--------------------------------------------------------------------------------
/src/shader/blur.glsl:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | precision mediump float;
3 |
4 | in vec4 vColor;
5 | in vec4 vPos;
6 | in vec2 vUV;
7 |
8 | uniform sampler2D uMainTex;
9 | uniform vec4 uTexSize; // (w, h, 1/w, 1/h)
10 | uniform float uSampleOffset;
11 |
12 | out vec4 fragColor;
13 |
14 | void main()
15 | {
16 | vec2 delta = vec2(-uSampleOffset, uSampleOffset);
17 | vec4 color =
18 | texture(uMainTex, clamp(vUV.xy + uTexSize.zw * delta.xx, vec2(0), vec2(1)))
19 | + texture(uMainTex, clamp(vUV.xy + uTexSize.zw * delta.yx, vec2(0), vec2(1)))
20 | + texture(uMainTex, clamp(vUV.xy + uTexSize.zw * delta.yy, vec2(0), vec2(1)))
21 | + texture(uMainTex, clamp(vUV.xy + uTexSize.zw * delta.xy, vec2(0), vec2(1)));
22 |
23 | color /= vec4(4.0);
24 |
25 | fragColor = color.rgba;
26 | }
--------------------------------------------------------------------------------
/assets/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 |
4 | background-position: 0px 0px,
5 | 16px 16px;
6 | background-size: 32px 32px;
7 | background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee 100%),
8 | linear-gradient(45deg, #eee 25%, white 25%, white 75%, #eee 75%, #eee 100%);
9 | }
10 |
11 | #root {
12 | position: absolute;
13 | width: 100vw;
14 | height: 100vh;
15 | display: flex;
16 | justify-content: center;
17 | align-items: center;
18 | }
19 |
20 | #canvas {
21 | width: 100%;
22 | height: 100%;
23 | }
24 |
25 | #loading {
26 | position: absolute;
27 | z-index: -1;
28 | font-family: 'Segoe UI', Roboto, Tahoma, Geneva, Verdana, sans-serif;
29 | font-size: 2em;
30 | color: gray;
31 | }
--------------------------------------------------------------------------------
/dist/random.d.ts:
--------------------------------------------------------------------------------
1 | import { Rect, vec2 } from "zogra-renderer";
2 | export interface JitterOption {
3 | base: T;
4 | jitter: T;
5 | }
6 | export declare function randomJittered(option: JitterOption): T;
7 | export declare function randomInRect(rect: Rect): vec2;
8 | /**
9 | * Generate noise in [-1, 1]
10 | * @param xy Should be non zero integer
11 | * @param seed Non zero integer
12 | */
13 | export declare function goldNoise(xy: vec2, seed: number): number;
14 | export declare function tentNoise(t: number, seed: number): void;
15 | /**
16 | * Get random value in (-1, 1)
17 | */
18 | export declare function random(): number;
19 | export declare function randomRange(min: number, max: number): number;
20 | //# sourceMappingURL=random.d.ts.map
--------------------------------------------------------------------------------
/src/shader/raindrop-vert.glsl:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | precision mediump float;
3 |
4 | in vec3 aPos;
5 | in vec4 aColor;
6 | in vec2 aUV;
7 | in vec3 aNormal;
8 |
9 | in float aSize;
10 | in mat4 aModelMatrix;
11 |
12 | uniform mat4 uTransformM;
13 | uniform mat4 uTransformVP;
14 | uniform mat4 uTransformMVP;
15 | uniform mat4 uTransformM_IT;
16 |
17 | out vec4 vColor;
18 | out vec4 vPos;
19 | out vec2 vUV;
20 | out vec3 vNormal;
21 | out vec3 vWorldPos;
22 | out float vSize;
23 |
24 | void main()
25 | {
26 | mat4 mvp = uTransformVP * aModelMatrix;
27 | gl_Position = mvp * vec4(aPos, 1);
28 | vPos = gl_Position;
29 | vColor = aColor;
30 | vUV = aUV;
31 | vNormal = (uTransformM_IT * vec4(aNormal, 0)).xyz;
32 | vWorldPos = (uTransformM * vec4(aPos, 1)).xyz;
33 | vSize = aSize;
34 | }
--------------------------------------------------------------------------------
/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | import { TextureData } from "zogra-renderer";
2 | import { RaindropRenderer, RenderOptions } from "./renderer";
3 | import { RaindropSimulator, SimulatorOptions } from "./simulator";
4 | interface Options extends SimulatorOptions, RenderOptions {
5 | }
6 | declare class RaindropFX {
7 | options: Options;
8 | renderer: RaindropRenderer;
9 | simulator: RaindropSimulator;
10 | private animHandle;
11 | constructor(options: Partial & {
12 | canvas: HTMLCanvasElement;
13 | });
14 | start(): Promise;
15 | stop(): void;
16 | resize(width: number, height: number): void;
17 | setBackground(background: string | TextureData): Promise;
18 | destroy(): void;
19 | private update;
20 | }
21 | export = RaindropFX;
22 | //# sourceMappingURL=index.d.ts.map
--------------------------------------------------------------------------------
/dist/blur.d.ts:
--------------------------------------------------------------------------------
1 | import { RenderTexture, Texture, vec4, ZograRenderer } from "zogra-renderer";
2 | declare const MaterialBlur_base: new (gl?: WebGL2RenderingContext | undefined) => import("zogra-renderer").Material;
3 | declare class MaterialBlur extends MaterialBlur_base {
4 | texture: Texture | null;
5 | textureSize: vec4;
6 | sampleOffset: number;
7 | }
8 | export declare class BlurRenderer {
9 | renderer: ZograRenderer;
10 | steps: RenderTexture[];
11 | mateiralBlur: MaterialBlur;
12 | constructor(renderer: ZograRenderer);
13 | init(texture: Texture): void;
14 | blur(texture: Texture, iteration?: number, output?: RenderTexture): RenderTexture;
15 | downSample(input: Texture, iteration: number): void;
16 | upSample(iteration: number, finalOutput?: RenderTexture): RenderTexture;
17 | }
18 | export {};
19 | //# sourceMappingURL=blur.d.ts.map
--------------------------------------------------------------------------------
/dist/raindrop.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"raindrop.d.ts","sourceRoot":"","sources":["../src/raindrop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,IAAI,EAAW,MAAM,gBAAgB,CAAC;AAE/D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAQ,IAAI,EAAE,MAAM,SAAS,CAAC;AAErC,qBAAa,QAAQ;IAEjB,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,MAAM,CAAK;IACpB,QAAQ,EAAE,IAAI,CAAe;IAC7B,MAAM,EAAE,IAAI,CAAC;IACb,SAAS,UAAS;IAClB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,iBAAiB,CAAS;IAElC,OAAO,CAAC,cAAc,CAAK;gBAEf,SAAS,EAAE,iBAAiB,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,SAAI;IAc9E,IAAI,IAAI,IACI,MAAM,CADe;IACjC,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,EAOjB;IAED,IAAI,IAAI,IAAI,IAAI,CAGf;IAED,IAAI,aAAa,WAGhB;IAED,IAAI,OAAO,2CAAoC;IAE/C,cAAc,CAAC,IAAI,EAAE,IAAI;IA+BzB,KAAK;IAgBL,YAAY;IAOZ,KAAK,CAAC,MAAM,EAAE,QAAQ;CAQzB"}
--------------------------------------------------------------------------------
/dist/raindrop.d.ts:
--------------------------------------------------------------------------------
1 | import { vec2 } from "zogra-renderer";
2 | import { CollisionGrid, RaindropSimulator } from "./simulator";
3 | import { Time } from "./utils";
4 | export declare class RainDrop {
5 | pos: vec2;
6 | density: number;
7 | velocity: vec2;
8 | spread: vec2;
9 | destroied: boolean;
10 | parent?: RainDrop;
11 | grid?: CollisionGrid;
12 | gridIdx?: number;
13 | private _mass;
14 | private _size;
15 | private simulator;
16 | private resistance;
17 | private shifting;
18 | private lastTrailPos;
19 | private nextTrailDistance;
20 | private nextRandomTime;
21 | constructor(simulator: RaindropSimulator, pos: vec2, size: number, density?: number);
22 | get mass(): number;
23 | set mass(m: number);
24 | get size(): vec2;
25 | get mergeDistance(): number;
26 | get options(): import("./simulator").SimulatorOptions;
27 | updateRaindrop(time: Time): void;
28 | split(): void;
29 | randomMotion(): void;
30 | merge(target: RainDrop): void;
31 | }
32 | //# sourceMappingURL=raindrop.d.ts.map
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "raindrop-fx",
3 | "version": "1.0.8",
4 | "description": "Rain drop effect with WebGL",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "build": "npm run build:ts && npm run build:node && npm run build:browser",
8 | "build:watch": "node ./build/build-browser.js --dev -w",
9 | "build:browser": "node ./build/build-browser.js",
10 | "build:node": "node ./build/build-node.js",
11 | "build:ts": "npx tsc",
12 | "start": "npx esbuild --serve=0.0.0.0:8000 --servedir=./",
13 | "test": "npm run build && npm run test:examples",
14 | "test:examples": "cd examples/with-node && npm i && npm test"
15 | },
16 | "keywords": [
17 | "webgl",
18 | "raindrop",
19 | "rain",
20 | "visual",
21 | "effect",
22 | "vfx"
23 | ],
24 | "author": "SardineFish",
25 | "license": "MIT",
26 | "dependencies": {
27 | "zogra-renderer": "^1.3.5"
28 | },
29 | "devDependencies": {
30 | "esbuild": "^0.8.50",
31 | "typescript": "^4.1.5"
32 | },
33 | "repository": {
34 | "type": "git",
35 | "url": "git+https://github.com/SardineFish/raindrop-fx.git"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 SardineFish
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.
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Raindrop Effect
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
Loading...
17 |
18 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/spawner.ts:
--------------------------------------------------------------------------------
1 | import { Rect, vec2 } from "zogra-renderer";
2 | import { RainDrop } from "./raindrop";
3 | import { randomJittered, JitterOption, randomInRect, randomRange } from "./random";
4 | import { RaindropSimulator, SimulatorOptions } from "./simulator";
5 |
6 | export class Spawner
7 | {
8 |
9 |
10 | currentTime = 0;
11 | nextSpawn = 0;
12 |
13 | private simulator: RaindropSimulator;
14 |
15 | constructor(simulator: RaindropSimulator, options: SimulatorOptions)
16 | {
17 |
18 | this.simulator = simulator;
19 | }
20 |
21 | get interval() { return this.simulator.options.spawnInterval }
22 | get size() { return this.simulator.options.spawnSize }
23 | get spawnRect() { return this.simulator.options.viewport }
24 |
25 | update(dt: number): this
26 | {
27 | this.currentTime += dt;
28 | return this;
29 | }
30 | *trySpawn(): Iterable
31 | {
32 | while (this.currentTime >= this.nextSpawn)
33 | {
34 | const size = randomRange(...this.size);
35 | const pos = randomInRect(this.spawnRect);
36 | this.nextSpawn += randomRange(...this.interval);
37 | yield new RainDrop(this.simulator, pos, size);
38 | }
39 | if (this.currentTime >= this.nextSpawn)
40 | {
41 |
42 | }
43 | return undefined;
44 | }
45 | spawn(pos: vec2, size: number, density = 1)
46 | {
47 | return new RainDrop(this.simulator, pos, size, density);
48 | }
49 | }
--------------------------------------------------------------------------------
/src/shader/droplet-vert.glsl:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | precision mediump float;
3 |
4 | in vec3 aPos;
5 | in vec2 aUV;
6 |
7 | uniform mat4 uTransformVP;
8 |
9 | uniform float uSeed;
10 | uniform vec4 uSpawnRect; // (xmin, ymin, xsize, ysize)
11 | uniform vec2 uSizeRange;
12 |
13 | out vec2 vUV;
14 |
15 | // Gold Noise ©2015 dcerisano@standard3d.com
16 | // - based on the Golden Ratio
17 | // - uniform normalized distribution
18 | // - fastest static noise generator function (also runs at low precision)
19 | // Ref: https://www.shadertoy.com/view/ltB3zD
20 | const float PHI = 1.61803398874989484820459; // Φ = Golden Ratio
21 |
22 | float gold_noise(in vec2 xy, in float seed)
23 | {
24 | return fract(tan(distance(xy*PHI, xy)*seed)*xy.x);
25 | }
26 |
27 | vec2 lerp(vec2 a, vec2 b, vec2 t)
28 | {
29 | return a + (b - a) * t;
30 | }
31 |
32 | void main()
33 | {
34 | int id = gl_InstanceID + 1;
35 | vec2 pos = uSpawnRect.xy + uSpawnRect.zw * vec2(
36 | gold_noise(vec2(1, id), uSeed + 1.0),
37 | gold_noise(vec2(id, 1), uSeed + 2.0));
38 |
39 | vec2 size = vec2(
40 | gold_noise(vec2(1, id), uSeed + 3.0),
41 | gold_noise(vec2(id, 1), uSeed + 4.0));
42 | size = lerp(vec2(uSizeRange.x), vec2(uSizeRange.y), size);
43 |
44 | mat4 model = mat4(size.x, 0.0, 0.0, 0.0,
45 | 0.0, size.x, 0.0, 0.0,
46 | 0.0, 0.0, 1, 0.0,
47 | pos.x, pos.y, 0.0, 1.0);
48 | mat4 mvp = uTransformVP * model;
49 | gl_Position = mvp * vec4(aPos, 1);
50 | vUV = aUV;
51 | }
--------------------------------------------------------------------------------
/dist/simulator.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"simulator.d.ts","sourceRoot":"","sources":["../src/simulator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,IAAI,EAAQ,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,MAAM,WAAW,gBAAgB;IAE7B,QAAQ,EAAE,IAAI,CAAC;IACf;;OAEG;IACH,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;OAEG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;OAEG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;OAEG;IACH,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,aAAc,SAAQ,KAAK,CAAC,QAAQ,CAAC;IAE9C,iBAAiB;IACjB,IAAI,CAAC,GAAG,IAAI,EAAE,QAAQ,EAAE;IAIxB,GAAG,CAAC,QAAQ,EAAE,QAAQ;IAMtB,MAAM,CAAC,QAAQ,EAAE,QAAQ;CAQ5B;AAED,qBAAa,iBAAiB;IAE1B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,QAAQ,EAAE,CAAM;IAC3B,IAAI,EAAE,aAAa,EAAE,CAAM;gBACf,OAAO,EAAE,gBAAgB;IAQrC,IAAI,QAAQ,WAA6C;IAEzD,MAAM;IAaN,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAUnC,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;IAInC,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAMnD,GAAG,CAAC,QAAQ,EAAE,QAAQ;IAYtB,MAAM,CAAC,IAAI,EAAE,IAAI;IAwBjB,cAAc,CAAC,IAAI,EAAE,IAAI;IA4BzB,eAAe;CAgDlB"}
--------------------------------------------------------------------------------
/examples/with-node/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "raindrop-fx-example",
3 | "version": "1.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "raindrop-fx-example",
9 | "version": "1.0.0",
10 | "license": "MIT",
11 | "dependencies": {
12 | "raindrop-fx": "file:///../.."
13 | },
14 | "devDependencies": {
15 | "esbuild": "^0.9.5"
16 | }
17 | },
18 | "../..": {
19 | "version": "1.0.1",
20 | "license": "MIT",
21 | "dependencies": {
22 | "@sardinefish/zogra-renderer": "^1.2.0"
23 | },
24 | "devDependencies": {
25 | "esbuild": "^0.8.50",
26 | "typescript": "^4.1.5"
27 | }
28 | },
29 | "node_modules/esbuild": {
30 | "version": "0.9.5",
31 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.9.5.tgz",
32 | "integrity": "sha512-ZBOtZX9HxgSdwkAroVifEUgylWqbOjqbu1i1ohRvhoaYt6aj81dxbizODx419gwDAupqut44ehFVwUq7WRN/OA==",
33 | "dev": true,
34 | "hasInstallScript": true,
35 | "bin": {
36 | "esbuild": "bin/esbuild"
37 | }
38 | },
39 | "node_modules/raindrop-fx": {
40 | "resolved": "../..",
41 | "link": true
42 | }
43 | },
44 | "dependencies": {
45 | "esbuild": {
46 | "version": "0.9.5",
47 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.9.5.tgz",
48 | "integrity": "sha512-ZBOtZX9HxgSdwkAroVifEUgylWqbOjqbu1i1ohRvhoaYt6aj81dxbizODx419gwDAupqut44ehFVwUq7WRN/OA==",
49 | "dev": true
50 | },
51 | "raindrop-fx": {
52 | "version": "file:../..",
53 | "requires": {
54 | "@sardinefish/zogra-renderer": "^1.2.0",
55 | "esbuild": "^0.8.50",
56 | "typescript": "^4.1.5"
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/random.ts:
--------------------------------------------------------------------------------
1 | import { distance, mul, Rect, vec2, vec3, Vector2 } from "zogra-renderer";
2 |
3 | export interface JitterOption
4 | {
5 | base: T;
6 | jitter: T;
7 | }
8 |
9 | export function randomJittered(option: JitterOption): T
10 | {
11 | if (typeof (option.base) === "number")
12 | return option.base + (option.jitter as number) * (Math.random() * 2 - 1) as T;
13 | else
14 | {
15 | let opt = option as JitterOption<[number, number]>;
16 | return vec2(
17 | opt.base[0] + (opt.jitter[0]) * (Math.random() * 2 - 1),
18 | opt.base[1] + (opt.jitter[1]) * (Math.random() * 2 - 1)
19 | ) as T;
20 | }
21 | }
22 | export function randomInRect(rect: Rect): vec2
23 | {
24 | return vec2(Math.random(), Math.random()).mul(rect.size).plus(rect.min);
25 | }
26 |
27 | // https://www.shadertoy.com/view/ltB3zD
28 | const PHI = 1.61803398874989484820459; // Φ = Golden Ratio
29 | function fract(x: number)
30 | {
31 | return x - Math.floor(x);
32 | }
33 | /**
34 | * Generate noise in [-1, 1]
35 | * @param xy Should be non zero integer
36 | * @param seed Non zero integer
37 | */
38 | export function goldNoise(xy: vec2, seed: number)
39 | {
40 | return fract(Math.tan(distance(mul(xy, vec2(PHI)), xy) * seed) * xy.x);
41 | }
42 |
43 | export function tentNoise(t: number, seed: number)
44 | {
45 | let frac = fract(t);
46 | let grid = Math.floor(t += seed);
47 | }
48 |
49 | /**
50 | * Get random value in (-1, 1)
51 | */
52 | export function random()
53 | {
54 | return Math.random() * 2 - 1;
55 | }
56 |
57 | export function randomRange(min: number, max: number)
58 | {
59 | return Math.random() * (max - min) + min;
60 | }
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish to npm
2 | on:
3 | push:
4 | tags:
5 | - "v*"
6 | jobs:
7 | release:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: actions/setup-node@v2
12 | with:
13 | node-version: '15.x'
14 | registry-url: 'https://registry.npmjs.org'
15 | - run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
16 | - run: npm install
17 | - run: npm run build
18 | - run: zip -j raindrop-fx README.md LICENSE bundle/index.js
19 | - name: Create Release
20 | id: create_release
21 | uses: actions/create-release@v1
22 | env:
23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24 | with:
25 | tag_name: ${{ github.ref }}
26 | release_name: Release ${{ github.ref }}
27 | draft: false
28 | prerelease: false
29 | - name: Upload Release Asset
30 | id: upload-release-asset
31 | uses: actions/upload-release-asset@v1
32 | env:
33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 | with:
35 | upload_url: ${{ steps.create_release.outputs.upload_url }}
36 | asset_path: ./raindrop-fx.zip
37 | asset_name: raindrop-fx-${{ env.RELEASE_VERSION }}.zip
38 | asset_content_type: application/zip
39 |
40 | publish:
41 | runs-on: ubuntu-latest
42 | steps:
43 | - uses: actions/checkout@v2
44 | - uses: actions/setup-node@v2
45 | with:
46 | node-version: '15.x'
47 | registry-url: 'https://registry.npmjs.org'
48 | - run: npm install
49 | - run: npm run build
50 | - run: npm publish
51 | env:
52 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
--------------------------------------------------------------------------------
/dist/renderer.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OACA,EAEkG,WAAW,EAC5C,aAAa,EAC7E,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAWtC,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAmH/B,MAAM,WAAW,aAAa;IAE1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,WAAW,GAAG,MAAM,CAAC;IACjC;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;OAEG;IACH,kBAAkB,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClD;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B;;;;;;;OAOG;IACH,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,eAAe,EAAE,UAAU,GAAG,QAAQ,CAAA;IACtC;;;;;;;OAOG;IACH,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD;;;OAGG;IACH,oBAAoB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD;;;OAGG;IACH,yBAAyB,EAAE,MAAM,CAAC;IAClC;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAE7B;AAED,qBAAa,gBAAgB;IAEzB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,aAAa,CAAC;IAEvB,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,kBAAkB,CAA0B;IACpD,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,kBAAkB,CAAgB;IAC1C,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAe;IAEnC,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,aAAa,CAA0B;IAC/C,OAAO,CAAC,YAAY,CAAyB;IAC7C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,gBAAgB,CAA+B;IAEvD,OAAO,CAAC,gBAAgB,CAAO;IAC/B,OAAO,CAAC,IAAI,CAAO;IAEnB,OAAO,CAAC,cAAc,CAAmD;gBAI7D,OAAO,EAAE,aAAa;IAmB5B,UAAU;IAaV,gBAAgB;IAmCtB,MAAM;IAmBN,MAAM,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI;IA0BxC,OAAO,CAAC,cAAc;IAgCtB,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,aAAa;IAwCrB,OAAO,CAAC,WAAW;CAStB"}
--------------------------------------------------------------------------------
/src/shader/compose.glsl:
--------------------------------------------------------------------------------
1 | #version 300 es
2 | precision mediump float;
3 |
4 | in vec4 vColor;
5 | in vec4 vPos;
6 | in vec2 vUV;
7 |
8 | uniform sampler2D uMainTex;
9 | uniform vec4 uBackgroundSize; // (x, y, 1/x, 1/y)
10 | uniform sampler2D uRaindropTex;
11 | uniform sampler2D uDropletTex;
12 | uniform sampler2D uMistTex;
13 | uniform vec4 uColor;
14 | uniform vec2 uSmoothRaindrop;
15 | uniform vec2 uRefractParams; // (refractBase, refractScale)
16 | uniform vec4 uLightPos;
17 | uniform vec4 uDiffuseColor; // (color.rgb, shadowOffset)
18 | uniform vec4 uSpecularParams; // (color.rgb, exponent)
19 | uniform float uBump;
20 |
21 | out vec4 fragColor;
22 |
23 | void main()
24 | {
25 | // vec3 lightPos = vec3(0.5, 1, 1);
26 |
27 | vec4 raindrop = texture(uRaindropTex, vUV.xy).rgba;
28 | vec4 droplet = texture(uDropletTex, vUV.xy).rgba;
29 | float mist = texture(uMistTex, vUV.xy).r;
30 |
31 | vec4 compose = vec4(raindrop.rgb + droplet.rgb - vec3(2.0) * raindrop.rgb * droplet.rgb, max(droplet.a, raindrop.a));
32 |
33 | float mask = smoothstep(uSmoothRaindrop.x, uSmoothRaindrop.y, compose.a);
34 |
35 | vec2 uv = vUV.xy + -(compose.xy - vec2(0.5)) * vec2(compose.b * uRefractParams.y + uRefractParams.x);
36 | vec3 normal = normalize(vec3((compose.xy - vec2(0.5)) * vec2(2), 1.0));
37 |
38 | // vec3 lightDir = lightPos - vec3(vUV, 0);
39 | vec3 lightDir = uLightPos.xyz - uLightPos.w * vec3(vUV.xy, 0.0);
40 | vec3 viewDir = vec3(0, 0, 1);
41 | vec3 halfDir = normalize(lightDir + viewDir);
42 | float lambertian = clamp(dot(normalize(lightDir), normal), 0.0, 1.0);
43 | float blinnPhon = pow(max(dot(normal, halfDir), 0.0), uSpecularParams.a);
44 |
45 |
46 | // offset = pow(offset, vec2(2));
47 | vec4 color = texture(uMainTex, uv.xy).rgba;
48 | vec3 diffuse = vec3((lambertian - uDiffuseColor.a) * uDiffuseColor.rgb);
49 |
50 | color.rgb += vec3((lambertian - uDiffuseColor.a) * uDiffuseColor.rgb);
51 | color.rgb += vec3(blinnPhon) * uSpecularParams.rgb;
52 |
53 |
54 | // fragColor = vec4(mask, mask, mask, 1);
55 | // color = color * vec3(uColor);
56 |
57 | fragColor = vec4(color.rgb, mask);// vec4(color.rgb, mask);
58 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # Snowpack dependency directory (https://snowpack.dev/)
45 | web_modules/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 | .parcel-cache
78 |
79 | # Next.js build output
80 | .next
81 | out
82 |
83 | # Nuxt.js build / generate output
84 | .nuxt
85 | # dist
86 |
87 | # Gatsby files
88 | .cache/
89 | # Comment in the public line in if your project uses Gatsby and not Next.js
90 | # https://nextjs.org/blog/next-9-1#public-directory-support
91 | # public
92 |
93 | # vuepress build output
94 | .vuepress/dist
95 |
96 | # Serverless directories
97 | .serverless/
98 |
99 | # FuseBox cache
100 | .fusebox/
101 |
102 | # DynamoDB Local files
103 | .dynamodb/
104 |
105 | # TernJS port file
106 | .tern-port
107 |
108 | # Stores VSCode versions used for testing VSCode extensions
109 | .vscode-test
110 |
111 | # yarn v2
112 | .yarn/cache
113 | .yarn/unplugged
114 | .yarn/build-state.yml
115 | .yarn/install-state.gz
116 | .pnp.*
117 |
--------------------------------------------------------------------------------
/dist/simulator.d.ts:
--------------------------------------------------------------------------------
1 | import { Rect } from "zogra-renderer";
2 | import { RainDrop } from "./raindrop";
3 | import { Spawner } from "./spawner";
4 | import { Time } from "./utils";
5 | export interface SimulatorOptions {
6 | viewport: Rect;
7 | /**
8 | * Time range between two raindrop spwan.
9 | */
10 | spawnInterval: [number, number];
11 | /**
12 | * Random size range when spawn a new raindrop
13 | */
14 | spawnSize: [number, number];
15 | /**
16 | * Maximal amount of spawned raindrops.
17 | * Recommend less than 2000
18 | */
19 | spawnLimit: number;
20 | /**
21 | * Recommend in range (0..1), other value should be ok.
22 | */
23 | slipRate: number;
24 | /**
25 | * Describe how often a raindrop change its motion state
26 | */
27 | motionInterval: [number, number];
28 | /**
29 | * Random velociy x relative to velocity y. Recommend in range (0..0.1)
30 | */
31 | xShifting: [number, number];
32 | /**
33 | * Relative size for collision check. Recommend in range (0.6..1.2)
34 | */
35 | colliderSize: number;
36 | /**
37 | * Mass density of the slipping trail raindrop.
38 | * Recommend in range (0.1..0.3)
39 | *
40 | * A large value cause a raindrop quickly lose its size during slipping.
41 | */
42 | trailDropDensity: number;
43 | /**
44 | * Random size range of slipping trail drop. Recommend in range (0.3..0.5)
45 | */
46 | trailDropSize: [number, number];
47 | /**
48 | * Random distance range between two slipping trail drop. Recommend in range (20..40)
49 | */
50 | trailDistance: [number, number];
51 | /**
52 | * Vertical spread of a new spawned slipping trail drop. Recommend in range (0.4..0.6)
53 | */
54 | trailSpread: number;
55 | /**
56 | * Spread rate when a new spawned raindrop hit the screen. Recommend in range (0.4..0.7)
57 | */
58 | initialSpread: number;
59 | /**
60 | * Spread shrink rate per seconds. Recommend in range (0.01..0.02)
61 | */
62 | shrinkRate: number;
63 | /**
64 | * Spread rate by velocity Y. Recommend in range (0.2..0.4)
65 | *
66 | * Raindrop with higher fall speed looks more narrow.
67 | */
68 | velocitySpread: number;
69 | /**
70 | * Mass lose per second. Recommend in range (10..30)
71 | */
72 | evaporate: number;
73 | /**
74 | * Gravity acceleration in pixels/s. Recommend 2400
75 | */
76 | gravity: number;
77 | }
78 | export declare class CollisionGrid extends Array {
79 | /**@deprecated */
80 | push(...item: RainDrop[]): number;
81 | add(raindrop: RainDrop): void;
82 | delete(raindrop: RainDrop): void;
83 | }
84 | export declare class RaindropSimulator {
85 | options: SimulatorOptions;
86 | spawner: Spawner;
87 | raindrops: RainDrop[];
88 | grid: CollisionGrid[];
89 | constructor(options: SimulatorOptions);
90 | get gridSize(): number;
91 | resize(): void;
92 | gridAt(gridX: number, gridY: number): CollisionGrid | undefined;
93 | gridAtWorldPos(x: number, y: number): CollisionGrid | undefined;
94 | worldToGrid(x: number, y: number): [number, number];
95 | add(raindrop: RainDrop): void;
96 | update(time: Time): void;
97 | raindropUpdate(time: Time): void;
98 | collisionUpdate(): void;
99 | }
100 | //# sourceMappingURL=simulator.d.ts.map
--------------------------------------------------------------------------------
/src/blur.ts:
--------------------------------------------------------------------------------
1 | import { div, FilterMode, MaterialFromShader, mul, RenderTexture, Shader, shaderProp, Texture, Texture2D, TextureResizing, vec2, vec4, WrapMode, ZograRenderer, TextureFormat } from "zogra-renderer";
2 | import vert from "./shader/2d-vert.glsl";
3 | import frag from "./shader/blur.glsl";
4 |
5 | class MaterialBlur extends MaterialFromShader(new Shader(vert, frag))
6 | {
7 | @shaderProp("uMainTex", "tex2d")
8 | texture: Texture | null = null;
9 |
10 | @shaderProp("uTexSize", "vec4")
11 | textureSize: vec4 = vec4.one();
12 |
13 | @shaderProp("uSampleOffset", "float")
14 | sampleOffset: number = 1;
15 | }
16 |
17 | export class BlurRenderer
18 | {
19 | renderer: ZograRenderer;
20 | steps: RenderTexture[] = [];
21 | mateiralBlur = new MaterialBlur();
22 |
23 | constructor(renderer: ZograRenderer)
24 | {
25 | this.renderer = renderer;
26 | }
27 |
28 | init(texture: Texture)
29 | {
30 | if (!this.steps[0])
31 | {
32 | this.steps[0] = new RenderTexture(texture.width, texture.height, false, texture.format, texture.filterMode);
33 | this.steps[0].wrapMode = WrapMode.Clamp;
34 | this.steps[0].updateParameters();
35 | }
36 | if (this.steps[0].width !== texture.width || this.steps[0].height !== texture.height)
37 | this.steps[0].resize(texture.width, texture.height, TextureResizing.Discard);
38 | }
39 |
40 | blur(texture: Texture, iteration: number = 4, output = this.steps[0])
41 | {
42 | if (!this.steps[0])
43 | this.steps[0] = new RenderTexture(texture.width, texture.height, false, texture.format, texture.filterMode);
44 | output = output || this.steps[0];
45 |
46 | if (this.steps[0].width !== texture.width || this.steps[0].height !== texture.height)
47 | this.steps[0].resize(texture.width, texture.height, TextureResizing.Discard);
48 |
49 | this.downSample(texture, iteration);
50 |
51 | return this.upSample(iteration, output);
52 | }
53 |
54 | downSample(input: Texture, iteration: number)
55 | {
56 | for (let i = 1; i <= iteration; i++)
57 | {
58 | const downSize = vec2.floor(div(input.size, vec2(2)));
59 | if (!this.steps[i])
60 | {
61 | this.steps[i] = new RenderTexture(downSize.x, downSize.y, false, TextureFormat.RGBA, FilterMode.Linear);
62 | this.steps[i].wrapMode = WrapMode.Clamp;
63 | this.steps[i].updateParameters();
64 | }
65 |
66 | const output = this.steps[i];
67 | if (output.width !== downSize.x || output.height !== downSize.y)
68 | output.resize(downSize.x, downSize.y, TextureResizing.Discard);
69 |
70 | this.mateiralBlur.texture = input;
71 | this.mateiralBlur.textureSize = vec4(input.width, input.height, 1 / input.width, 1 / input.height);
72 | this.mateiralBlur.sampleOffset = 1;
73 | this.renderer.blit(input, output, this.mateiralBlur);
74 | input = output;
75 | }
76 | }
77 |
78 | upSample(iteration: number, finalOutput = this.steps[0])
79 | {
80 | let input = this.steps[iteration];
81 | for (let i = iteration - 1; i >= 0; i--)
82 | {
83 | const upSize = mul(input.size, vec2(2));
84 | if (!this.steps[i])
85 | {
86 | this.steps[i] = new RenderTexture(upSize.x, upSize.y, false, TextureFormat.RGBA, FilterMode.Linear);
87 | this.steps[i].wrapMode = WrapMode.Clamp;
88 | this.steps[i].updateParameters();
89 | }
90 |
91 | const output = i === 0 ? finalOutput : this.steps[i];
92 |
93 | this.mateiralBlur.texture = input;
94 | this.mateiralBlur.textureSize = vec4(input.width, input.height, 1 / input.width, 1 / input.height);
95 | this.mateiralBlur.sampleOffset = 1;
96 | this.renderer.blit(input, output, this.mateiralBlur);
97 | input = output;
98 | }
99 | return input;
100 | }
101 | }
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Rect, TextureData, vec2 } from "zogra-renderer";
2 | import { RaindropRenderer, RenderOptions } from "./renderer";
3 | import { RaindropSimulator, SimulatorOptions } from "./simulator";
4 | import { Time } from "./utils";
5 |
6 | interface Options extends SimulatorOptions, RenderOptions
7 | {
8 | }
9 |
10 | class RaindropFX
11 | {
12 | public options: Options;
13 | public renderer: RaindropRenderer;
14 | public simulator: RaindropSimulator;
15 |
16 | private animHandle = 0;
17 |
18 | constructor(options: Partial & {canvas: HTMLCanvasElement})
19 | {
20 | const canvas = options.canvas;
21 | const defaultOptions: Options = {
22 | // Simulator options
23 | spawnInterval: [0.1, 0.1],
24 | spawnSize: [60, 100],
25 | spawnLimit: 2000,
26 | viewport: new Rect(vec2.zero(), vec2(canvas.width, canvas.height)),
27 | canvas: canvas,
28 | width: canvas.width,
29 | height: canvas.height,
30 | background: "",
31 | gravity: 2400,
32 | slipRate: 0,
33 | motionInterval: [0.1, 0.4],
34 | colliderSize: 1,
35 | trailDropDensity: 0.2,
36 | trailDistance: [20, 30],
37 | trailDropSize: [0.3, 0.5],
38 | trailSpread: 0.6,
39 | initialSpread: 0.5,
40 | shrinkRate: 0.01,
41 | velocitySpread: 0.3,
42 | evaporate: 10,
43 | xShifting: [0, 0.1],
44 |
45 | // Rendering options
46 | backgroundBlurSteps: 3,
47 | backgroundWrapMode: "clamp",
48 | mist: true,
49 | mistColor: [0.01, 0.01, 0.01, 1],
50 | mistBlurStep: 4,
51 | mistTime: 10,
52 | dropletsPerSeconds: 500,
53 | dropletSize: [10, 30],
54 | smoothRaindrop: [0.96, 0.99],
55 | refractBase: 0.4,
56 | refractScale: 0.6,
57 | raindropCompose: "smoother",
58 | raindropLightPos: [-1, 1, 2, 0],
59 | raindropDiffuseLight: [0.2, 0.2, 0.2],
60 | raindropShadowOffset: 0.8,
61 | raindropEraserSize: [0.93, 1.0],
62 | raindropSpecularLight: [0, 0, 0],
63 | raindropSpecularShininess: 256,
64 | raindropLightBump: 1,
65 | };
66 | this.options = { ...defaultOptions, ...options };
67 |
68 | this.simulator = new RaindropSimulator(this.options);
69 | this.renderer = new RaindropRenderer(this.options);
70 | }
71 |
72 | async start()
73 | {
74 | await this.renderer.loadAssets();
75 |
76 | let lastFrameTime = 0;
77 | const update = (delay: number) =>
78 | {
79 | const dt = (delay - lastFrameTime) / 1000;
80 | lastFrameTime = delay;
81 | const time =