@toriistudio/shader-ui 0.0.1
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.md +21 -0
- package/README.md +39 -0
- package/dist/index.d.mts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +566 -0
- package/dist/index.mjs +532 -0
- package/package.json +103 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Torii Studio
|
|
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
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# π¨ Shader UI
|
|
2
|
+
|
|
3
|
+
Shader components FTW!
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## π Installation
|
|
8
|
+
|
|
9
|
+
Install the package and its peer dependencies:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @toriistudio/shader-ui
|
|
13
|
+
# or
|
|
14
|
+
yarn add @toriistudio/shader-ui
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## π License
|
|
18
|
+
|
|
19
|
+
MIT
|
|
20
|
+
|
|
21
|
+
## π€ Contributing
|
|
22
|
+
|
|
23
|
+
We welcome contributions!
|
|
24
|
+
|
|
25
|
+
If you'd like to improve the playground, add new features, or fix bugs:
|
|
26
|
+
|
|
27
|
+
1. **Fork** this repository
|
|
28
|
+
2. **Clone** your fork: `git clone https://github.com/your-username/shader-ui`
|
|
29
|
+
3. **Install** dependencies: `yarn` or `npm install`
|
|
30
|
+
4. Make your changes in a branch: `git checkout -b my-new-feature`
|
|
31
|
+
5. **Push** your branch and open a pull request
|
|
32
|
+
|
|
33
|
+
Before submitting a PR:
|
|
34
|
+
|
|
35
|
+
- Run `yarn build` to ensure everything compiles
|
|
36
|
+
- Make sure the playground runs without errors (`yalc push` or `npm link` for local testing)
|
|
37
|
+
- Keep the code style clean and consistent
|
|
38
|
+
|
|
39
|
+
Weβre excited to see what youβll build π¨π
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
|
|
4
|
+
type ShaderUniforms = {
|
|
5
|
+
uIterations: number;
|
|
6
|
+
uAmplitude: number;
|
|
7
|
+
uFreq: number;
|
|
8
|
+
};
|
|
9
|
+
type ShaderArtProps = {
|
|
10
|
+
uniforms: ShaderUniforms;
|
|
11
|
+
} & Omit<ComponentProps<"div">, "ref" | "children" | "color" | "width" | "height"> & {
|
|
12
|
+
width?: string | number;
|
|
13
|
+
height?: string | number;
|
|
14
|
+
};
|
|
15
|
+
declare function ShaderArt({ uniforms, className, style, width, height, ...divProps }: ShaderArtProps): react_jsx_runtime.JSX.Element;
|
|
16
|
+
|
|
17
|
+
type FractalFlowerProps = {
|
|
18
|
+
timeScale: number;
|
|
19
|
+
petalRadius: number;
|
|
20
|
+
scale: number;
|
|
21
|
+
intensity: number;
|
|
22
|
+
morphCycle: number;
|
|
23
|
+
color: string;
|
|
24
|
+
} & Omit<ComponentProps<"div">, "ref" | "children" | "color" | "width" | "height"> & {
|
|
25
|
+
width?: string | number;
|
|
26
|
+
height?: string | number;
|
|
27
|
+
};
|
|
28
|
+
declare function FractalFlower({ timeScale, petalRadius, scale, intensity, morphCycle, color, className, style, width, height, ...divProps }: FractalFlowerProps): react_jsx_runtime.JSX.Element;
|
|
29
|
+
|
|
30
|
+
type OranoParticlesUniforms = {
|
|
31
|
+
color: string;
|
|
32
|
+
alpha: number;
|
|
33
|
+
wind: number;
|
|
34
|
+
baseSize: number;
|
|
35
|
+
distanceOffset: number;
|
|
36
|
+
distanceStrength: number;
|
|
37
|
+
particleCount: number;
|
|
38
|
+
};
|
|
39
|
+
type OranoParticlesProps = OranoParticlesUniforms & Omit<ComponentProps<"div">, keyof OranoParticlesUniforms | "ref" | "width" | "height"> & {
|
|
40
|
+
width?: string | number;
|
|
41
|
+
height?: string | number;
|
|
42
|
+
};
|
|
43
|
+
declare function OranoParticles({ className, style, width, height, color, alpha, wind, baseSize, distanceOffset, distanceStrength, particleCount, ...divProps }: OranoParticlesProps): react_jsx_runtime.JSX.Element;
|
|
44
|
+
|
|
45
|
+
export { FractalFlower, OranoParticles, ShaderArt };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
|
|
4
|
+
type ShaderUniforms = {
|
|
5
|
+
uIterations: number;
|
|
6
|
+
uAmplitude: number;
|
|
7
|
+
uFreq: number;
|
|
8
|
+
};
|
|
9
|
+
type ShaderArtProps = {
|
|
10
|
+
uniforms: ShaderUniforms;
|
|
11
|
+
} & Omit<ComponentProps<"div">, "ref" | "children" | "color" | "width" | "height"> & {
|
|
12
|
+
width?: string | number;
|
|
13
|
+
height?: string | number;
|
|
14
|
+
};
|
|
15
|
+
declare function ShaderArt({ uniforms, className, style, width, height, ...divProps }: ShaderArtProps): react_jsx_runtime.JSX.Element;
|
|
16
|
+
|
|
17
|
+
type FractalFlowerProps = {
|
|
18
|
+
timeScale: number;
|
|
19
|
+
petalRadius: number;
|
|
20
|
+
scale: number;
|
|
21
|
+
intensity: number;
|
|
22
|
+
morphCycle: number;
|
|
23
|
+
color: string;
|
|
24
|
+
} & Omit<ComponentProps<"div">, "ref" | "children" | "color" | "width" | "height"> & {
|
|
25
|
+
width?: string | number;
|
|
26
|
+
height?: string | number;
|
|
27
|
+
};
|
|
28
|
+
declare function FractalFlower({ timeScale, petalRadius, scale, intensity, morphCycle, color, className, style, width, height, ...divProps }: FractalFlowerProps): react_jsx_runtime.JSX.Element;
|
|
29
|
+
|
|
30
|
+
type OranoParticlesUniforms = {
|
|
31
|
+
color: string;
|
|
32
|
+
alpha: number;
|
|
33
|
+
wind: number;
|
|
34
|
+
baseSize: number;
|
|
35
|
+
distanceOffset: number;
|
|
36
|
+
distanceStrength: number;
|
|
37
|
+
particleCount: number;
|
|
38
|
+
};
|
|
39
|
+
type OranoParticlesProps = OranoParticlesUniforms & Omit<ComponentProps<"div">, keyof OranoParticlesUniforms | "ref" | "width" | "height"> & {
|
|
40
|
+
width?: string | number;
|
|
41
|
+
height?: string | number;
|
|
42
|
+
};
|
|
43
|
+
declare function OranoParticles({ className, style, width, height, color, alpha, wind, baseSize, distanceOffset, distanceStrength, particleCount, ...divProps }: OranoParticlesProps): react_jsx_runtime.JSX.Element;
|
|
44
|
+
|
|
45
|
+
export { FractalFlower, OranoParticles, ShaderArt };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
FractalFlower: () => FractalFlower,
|
|
34
|
+
OranoParticles: () => OranoParticles,
|
|
35
|
+
ShaderArt: () => ShaderArt
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(src_exports);
|
|
38
|
+
|
|
39
|
+
// src/components/ShaderArt.tsx
|
|
40
|
+
var import_react2 = require("react");
|
|
41
|
+
var THREE2 = __toESM(require("three"));
|
|
42
|
+
|
|
43
|
+
// src/hooks/useScene.ts
|
|
44
|
+
var import_react = require("react");
|
|
45
|
+
var THREE = __toESM(require("three"));
|
|
46
|
+
var EMPTY_DEPS = [];
|
|
47
|
+
function useScene({
|
|
48
|
+
renderer: rendererOptions,
|
|
49
|
+
camera: cameraOptions,
|
|
50
|
+
pixelRatio,
|
|
51
|
+
onCreate,
|
|
52
|
+
onRender,
|
|
53
|
+
onResize,
|
|
54
|
+
deps
|
|
55
|
+
} = {}) {
|
|
56
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
57
|
+
const contextRef = (0, import_react.useRef)(null);
|
|
58
|
+
const onCreateRef = (0, import_react.useRef)(onCreate);
|
|
59
|
+
const onRenderRef = (0, import_react.useRef)(onRender);
|
|
60
|
+
const onResizeRef = (0, import_react.useRef)(onResize);
|
|
61
|
+
(0, import_react.useEffect)(() => {
|
|
62
|
+
onCreateRef.current = onCreate;
|
|
63
|
+
}, [onCreate]);
|
|
64
|
+
(0, import_react.useEffect)(() => {
|
|
65
|
+
onRenderRef.current = onRender;
|
|
66
|
+
}, [onRender]);
|
|
67
|
+
(0, import_react.useEffect)(() => {
|
|
68
|
+
onResizeRef.current = onResize;
|
|
69
|
+
}, [onResize]);
|
|
70
|
+
const effectDeps = deps ?? EMPTY_DEPS;
|
|
71
|
+
(0, import_react.useEffect)(() => {
|
|
72
|
+
const container = containerRef.current;
|
|
73
|
+
if (!container) return;
|
|
74
|
+
const resolvedPixelRatio = pixelRatio ?? (typeof window !== "undefined" ? Math.min(window.devicePixelRatio, 2) : 1);
|
|
75
|
+
const renderer = new THREE.WebGLRenderer({
|
|
76
|
+
alpha: true,
|
|
77
|
+
antialias: true,
|
|
78
|
+
...rendererOptions
|
|
79
|
+
});
|
|
80
|
+
renderer.setPixelRatio(resolvedPixelRatio);
|
|
81
|
+
renderer.setSize(container.clientWidth, container.clientHeight, false);
|
|
82
|
+
renderer.setClearColor(0, 0);
|
|
83
|
+
renderer.domElement.style.width = "100%";
|
|
84
|
+
renderer.domElement.style.height = "100%";
|
|
85
|
+
container.appendChild(renderer.domElement);
|
|
86
|
+
const scene = new THREE.Scene();
|
|
87
|
+
const camera = new THREE.PerspectiveCamera(
|
|
88
|
+
cameraOptions?.fov ?? 55,
|
|
89
|
+
container.clientWidth / Math.max(1, container.clientHeight),
|
|
90
|
+
cameraOptions?.near ?? 0.1,
|
|
91
|
+
cameraOptions?.far ?? 500
|
|
92
|
+
);
|
|
93
|
+
const [x = 0, y = 0, z = 15] = cameraOptions?.position ?? [];
|
|
94
|
+
camera.position.set(x, y, z);
|
|
95
|
+
const clock = new THREE.Clock();
|
|
96
|
+
const context = { renderer, scene, camera, clock };
|
|
97
|
+
contextRef.current = context;
|
|
98
|
+
const teardownCreate = onCreateRef.current?.(context);
|
|
99
|
+
let animationFrameId = 0;
|
|
100
|
+
const renderLoop = () => {
|
|
101
|
+
onRenderRef.current?.(context);
|
|
102
|
+
renderer.render(scene, camera);
|
|
103
|
+
animationFrameId = requestAnimationFrame(renderLoop);
|
|
104
|
+
};
|
|
105
|
+
animationFrameId = requestAnimationFrame(renderLoop);
|
|
106
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
107
|
+
const width = container.clientWidth;
|
|
108
|
+
const height = container.clientHeight;
|
|
109
|
+
renderer.setSize(width, height, false);
|
|
110
|
+
camera.aspect = width / Math.max(1, height);
|
|
111
|
+
camera.updateProjectionMatrix();
|
|
112
|
+
onResizeRef.current?.(context, { width, height });
|
|
113
|
+
});
|
|
114
|
+
resizeObserver.observe(container);
|
|
115
|
+
return () => {
|
|
116
|
+
teardownCreate?.();
|
|
117
|
+
cancelAnimationFrame(animationFrameId);
|
|
118
|
+
resizeObserver.disconnect();
|
|
119
|
+
scene.clear();
|
|
120
|
+
renderer.dispose();
|
|
121
|
+
if (renderer.domElement.parentNode === container) {
|
|
122
|
+
container.removeChild(renderer.domElement);
|
|
123
|
+
}
|
|
124
|
+
contextRef.current = null;
|
|
125
|
+
};
|
|
126
|
+
}, effectDeps);
|
|
127
|
+
return { containerRef, contextRef };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/shaders/shader-art/fragment.glsl
|
|
131
|
+
var fragment_default = "precision highp float;\n\nvarying vec2 vUv;\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uIterations;\nuniform float uAmplitude;\nuniform float uFreq;\n\nvec3 palette(float t) {\n vec3 a = vec3(0.5, 0.5, 0.5);\n vec3 b = vec3(0.5, 0.5, 0.5);\n vec3 c = vec3(1.0, 1.0, 1.0);\n vec3 d = vec3(0.263, 0.416, 0.557);\n return a + b * cos(6.28318 * (c * t + d));\n}\n\nvoid main() {\n vec2 fragCoord = vec2(vUv.x * iResolution.x, vUv.y * iResolution.y);\n vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;\n vec2 uv0 = uv;\n vec3 finalColor = vec3(0.0);\n const int MAX_ITERATIONS = 8;\n for (int idx = 0; idx < MAX_ITERATIONS; idx++) {\n float i = float(idx);\n if (i >= uIterations) {\n break;\n }\n uv = fract(uv * uAmplitude) - 0.5;\n float d = length(uv) * exp(-length(uv0));\n vec3 col = palette(length(uv0) + i * 0.4 + iTime * uFreq);\n d = sin(d * 8.0 + iTime) / 8.0;\n d = abs(d);\n d = pow(0.01 / max(d, 1e-4), 1.9);\n finalColor += col * d;\n }\n gl_FragColor = vec4(finalColor, 1.0);\n}";
|
|
132
|
+
|
|
133
|
+
// src/shaders/shader-art/vertex.glsl
|
|
134
|
+
var vertex_default = "varying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}";
|
|
135
|
+
|
|
136
|
+
// src/components/ShaderArt.tsx
|
|
137
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
138
|
+
function ShaderArt({
|
|
139
|
+
uniforms,
|
|
140
|
+
className,
|
|
141
|
+
style,
|
|
142
|
+
width,
|
|
143
|
+
height,
|
|
144
|
+
...divProps
|
|
145
|
+
}) {
|
|
146
|
+
const shaderUniformsRef = (0, import_react2.useRef)({
|
|
147
|
+
iTime: { value: 0 },
|
|
148
|
+
iResolution: { value: new THREE2.Vector3(1, 1, 1) },
|
|
149
|
+
uIterations: { value: Math.max(1, uniforms.uIterations) },
|
|
150
|
+
uAmplitude: { value: uniforms.uAmplitude },
|
|
151
|
+
uFreq: { value: uniforms.uFreq }
|
|
152
|
+
});
|
|
153
|
+
const assetsRef = (0, import_react2.useRef)(null);
|
|
154
|
+
const handleCreate = (0, import_react2.useCallback)(({ scene }) => {
|
|
155
|
+
const geometry = new THREE2.PlaneGeometry(2, 2);
|
|
156
|
+
const material = new THREE2.ShaderMaterial({
|
|
157
|
+
vertexShader: vertex_default,
|
|
158
|
+
fragmentShader: fragment_default,
|
|
159
|
+
uniforms: shaderUniformsRef.current
|
|
160
|
+
});
|
|
161
|
+
const mesh = new THREE2.Mesh(geometry, material);
|
|
162
|
+
scene.add(mesh);
|
|
163
|
+
assetsRef.current = { mesh, geometry, material };
|
|
164
|
+
return () => {
|
|
165
|
+
scene.remove(mesh);
|
|
166
|
+
geometry.dispose();
|
|
167
|
+
material.dispose();
|
|
168
|
+
assetsRef.current = null;
|
|
169
|
+
};
|
|
170
|
+
}, []);
|
|
171
|
+
const handleRender = (0, import_react2.useCallback)((context) => {
|
|
172
|
+
shaderUniformsRef.current.iTime.value = context.clock.getElapsedTime();
|
|
173
|
+
const canvas = context.renderer.domElement;
|
|
174
|
+
shaderUniformsRef.current.iResolution.value.set(
|
|
175
|
+
canvas.width,
|
|
176
|
+
canvas.height,
|
|
177
|
+
1
|
|
178
|
+
);
|
|
179
|
+
}, []);
|
|
180
|
+
const { containerRef } = useScene({
|
|
181
|
+
onCreate: handleCreate,
|
|
182
|
+
onRender: handleRender
|
|
183
|
+
});
|
|
184
|
+
(0, import_react2.useEffect)(() => {
|
|
185
|
+
shaderUniformsRef.current.uIterations.value = Math.max(
|
|
186
|
+
1,
|
|
187
|
+
uniforms.uIterations
|
|
188
|
+
);
|
|
189
|
+
shaderUniformsRef.current.uAmplitude.value = uniforms.uAmplitude;
|
|
190
|
+
shaderUniformsRef.current.uFreq.value = uniforms.uFreq;
|
|
191
|
+
}, [uniforms.uAmplitude, uniforms.uFreq, uniforms.uIterations]);
|
|
192
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
193
|
+
"div",
|
|
194
|
+
{
|
|
195
|
+
ref: containerRef,
|
|
196
|
+
className,
|
|
197
|
+
style: {
|
|
198
|
+
width: width ?? "100%",
|
|
199
|
+
height: height ?? "100%",
|
|
200
|
+
...style
|
|
201
|
+
},
|
|
202
|
+
...divProps
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/components/FractalFlower.tsx
|
|
208
|
+
var import_react3 = require("react");
|
|
209
|
+
var THREE3 = __toESM(require("three"));
|
|
210
|
+
|
|
211
|
+
// src/shaders/fractal-flower/fragment.glsl
|
|
212
|
+
var fragment_default2 = "precision highp float;\n\nuniform float uIntensity;\nuniform float uExposure;\nuniform float uPetalRadius;\nuniform float uMorph;\n\nvarying vec3 vColor;\nvarying float vWeight;\n\nvoid main() {\n vec2 uv = gl_PointCoord - vec2(0.5);\n float sigma = max(0.3, uPetalRadius * 10.0);\n float dist2 = dot(uv, uv);\n float alpha = exp(-dist2 / (sigma * sigma));\n\n if (alpha <= 0.01) {\n discard;\n }\n\n float weight = 0.6 + 0.4 * vWeight;\n float falloff = pow(alpha, 0.8);\n float morphBoost = mix(1.0, 1.35, uMorph);\n vec3 color = clamp(vColor * weight * falloff * (uIntensity * morphBoost), 0.0, 1.0);\n\n float outAlpha = clamp(alpha * mix(0.6, 0.85, uMorph), 0.0, 1.0);\n gl_FragColor = vec4(color, outAlpha);\n}\n";
|
|
213
|
+
|
|
214
|
+
// src/shaders/fractal-flower/vertex.glsl
|
|
215
|
+
var vertex_default2 = "precision highp float;\n\nattribute float aK;\nattribute float aE;\nattribute float aRotation;\n\nuniform float uTime;\nuniform float uTimeScale;\nuniform float uSpread;\nuniform float uPointSize;\nuniform float uMorph;\nuniform vec3 uColor;\n\nvarying vec3 vColor;\nvarying float vWeight;\n\nmat2 rotation(float angle) {\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nvoid main() {\n float time = uTime * uTimeScale;\n float d = 7.0 * cos(length(vec2(aK, aE)) / 3.0 + time * 0.5);\n\n vec2 basePoint = vec2(\n aK * 4.0 + d * aK * sin(d + aE / 9.0 + time),\n aE * 2.0 - d * 9.0 - d * 9.0 * cos(d + time)\n );\n\n vec2 altPoint = vec2(\n aK * 6.0 + sin(time * 1.3 + aE * 2.0) * (42.0 + 12.0 * sin(aE * 1.15)),\n aE * 3.4 - d * 12.5 + 28.0 * cos(time * 0.75 + aK * 0.35)\n );\n\n vec2 morphed = mix(basePoint, altPoint, uMorph);\n float swirl = aRotation + uMorph * (0.8 + 0.25 * sin(time * 0.4 + aE));\n vec2 rotated = rotation(swirl) * morphed;\n\n float spreadMix = mix(uSpread, uSpread * 0.65, uMorph);\n vec2 scaled = rotated / spreadMix;\n vec3 modelPosition = vec3(scaled, 0.0);\n\n float radial = length(morphed) / (spreadMix * 0.9);\n vWeight = clamp(1.0 - radial * radial, 0.0, 1.0);\n\n vColor = uColor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(modelPosition, 1.0);\n gl_PointSize = uPointSize * mix(1.0, 1.35, uMorph);\n}\n";
|
|
216
|
+
|
|
217
|
+
// src/components/FractalFlower.tsx
|
|
218
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
219
|
+
var BASE_POINT_COUNT = 2e4;
|
|
220
|
+
var ROTATIONS = [
|
|
221
|
+
0,
|
|
222
|
+
Math.PI / 3,
|
|
223
|
+
2 * Math.PI / 3,
|
|
224
|
+
Math.PI,
|
|
225
|
+
4 * Math.PI / 3,
|
|
226
|
+
5 * Math.PI / 3
|
|
227
|
+
];
|
|
228
|
+
var ROTATION_COUNT = ROTATIONS.length;
|
|
229
|
+
var TOTAL_POINTS = ROTATION_COUNT * BASE_POINT_COUNT;
|
|
230
|
+
var BASE_SPREAD = 200;
|
|
231
|
+
var MORPH_SPEED = 0.6;
|
|
232
|
+
var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
233
|
+
function FractalFlower({
|
|
234
|
+
timeScale,
|
|
235
|
+
petalRadius,
|
|
236
|
+
scale,
|
|
237
|
+
intensity,
|
|
238
|
+
morphCycle,
|
|
239
|
+
color,
|
|
240
|
+
className,
|
|
241
|
+
style,
|
|
242
|
+
width,
|
|
243
|
+
height,
|
|
244
|
+
...divProps
|
|
245
|
+
}) {
|
|
246
|
+
const attributes = (0, import_react3.useMemo)(() => {
|
|
247
|
+
const positions = new Float32Array(TOTAL_POINTS * 3);
|
|
248
|
+
const kValues = new Float32Array(TOTAL_POINTS);
|
|
249
|
+
const eValues = new Float32Array(TOTAL_POINTS);
|
|
250
|
+
const rotations = new Float32Array(TOTAL_POINTS);
|
|
251
|
+
for (let copy = 0; copy < ROTATION_COUNT; copy++) {
|
|
252
|
+
const angle = ROTATIONS[copy];
|
|
253
|
+
for (let i = 0; i < BASE_POINT_COUNT; i++) {
|
|
254
|
+
const index = copy * BASE_POINT_COUNT + i;
|
|
255
|
+
const k = i % 25 - 12;
|
|
256
|
+
const e = i / 800;
|
|
257
|
+
positions[index * 3] = 0;
|
|
258
|
+
positions[index * 3 + 1] = 0;
|
|
259
|
+
positions[index * 3 + 2] = 0;
|
|
260
|
+
kValues[index] = k;
|
|
261
|
+
eValues[index] = e;
|
|
262
|
+
rotations[index] = angle;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
positions,
|
|
267
|
+
kValues,
|
|
268
|
+
eValues,
|
|
269
|
+
rotations
|
|
270
|
+
};
|
|
271
|
+
}, []);
|
|
272
|
+
const uniformsRef = (0, import_react3.useRef)({
|
|
273
|
+
uTime: { value: 0 },
|
|
274
|
+
uResolution: { value: new THREE3.Vector2(1, 1) },
|
|
275
|
+
uTimeScale: { value: 0.78539816339 },
|
|
276
|
+
uPetalRadius: { value: 0.02 },
|
|
277
|
+
uIntensity: { value: 0.25 },
|
|
278
|
+
uExposure: { value: 5 },
|
|
279
|
+
uSpread: { value: BASE_SPREAD / Math.max(scale, 1e-4) },
|
|
280
|
+
uPointSize: { value: 2.5 },
|
|
281
|
+
uColor: { value: new THREE3.Color(color) },
|
|
282
|
+
uMorph: { value: 0 }
|
|
283
|
+
});
|
|
284
|
+
const morphRef = (0, import_react3.useRef)({
|
|
285
|
+
progress: uniformsRef.current.uMorph.value,
|
|
286
|
+
target: 0,
|
|
287
|
+
cycle: morphCycle
|
|
288
|
+
});
|
|
289
|
+
const assetsRef = (0, import_react3.useRef)(null);
|
|
290
|
+
const handleCreate = (0, import_react3.useCallback)(
|
|
291
|
+
({ scene }) => {
|
|
292
|
+
const geometry = new THREE3.BufferGeometry();
|
|
293
|
+
geometry.setAttribute(
|
|
294
|
+
"position",
|
|
295
|
+
new THREE3.BufferAttribute(attributes.positions, 3)
|
|
296
|
+
);
|
|
297
|
+
geometry.setAttribute(
|
|
298
|
+
"aK",
|
|
299
|
+
new THREE3.BufferAttribute(attributes.kValues, 1)
|
|
300
|
+
);
|
|
301
|
+
geometry.setAttribute(
|
|
302
|
+
"aE",
|
|
303
|
+
new THREE3.BufferAttribute(attributes.eValues, 1)
|
|
304
|
+
);
|
|
305
|
+
geometry.setAttribute(
|
|
306
|
+
"aRotation",
|
|
307
|
+
new THREE3.BufferAttribute(attributes.rotations, 1)
|
|
308
|
+
);
|
|
309
|
+
const uniformValues = uniformsRef.current;
|
|
310
|
+
const material = new THREE3.ShaderMaterial({
|
|
311
|
+
vertexShader: vertex_default2,
|
|
312
|
+
fragmentShader: fragment_default2,
|
|
313
|
+
uniforms: uniformValues,
|
|
314
|
+
blending: THREE3.AdditiveBlending,
|
|
315
|
+
depthWrite: false,
|
|
316
|
+
transparent: true
|
|
317
|
+
});
|
|
318
|
+
const points = new THREE3.Points(geometry, material);
|
|
319
|
+
points.frustumCulled = false;
|
|
320
|
+
scene.add(points);
|
|
321
|
+
assetsRef.current = { points, geometry, material, uniforms: uniformValues };
|
|
322
|
+
return () => {
|
|
323
|
+
scene.remove(points);
|
|
324
|
+
geometry.dispose();
|
|
325
|
+
material.dispose();
|
|
326
|
+
assetsRef.current = null;
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
[attributes.eValues, attributes.kValues, attributes.positions, attributes.rotations]
|
|
330
|
+
);
|
|
331
|
+
const handleRender = (0, import_react3.useCallback)(
|
|
332
|
+
(context) => {
|
|
333
|
+
const assets = assetsRef.current;
|
|
334
|
+
if (!assets) return;
|
|
335
|
+
const uniforms = assets.uniforms;
|
|
336
|
+
uniforms.uTime.value = context.clock.getElapsedTime();
|
|
337
|
+
const canvas = context.renderer.domElement;
|
|
338
|
+
uniforms.uResolution.value.set(canvas.width, canvas.height);
|
|
339
|
+
const basePointSize = Math.max(
|
|
340
|
+
2,
|
|
341
|
+
canvas.height / 400 * (uniforms.uPetalRadius.value * 32)
|
|
342
|
+
);
|
|
343
|
+
const pointSize = Math.min(basePointSize, 6);
|
|
344
|
+
uniforms.uPointSize.value = pointSize;
|
|
345
|
+
const morph = morphRef.current;
|
|
346
|
+
if (Math.abs(morph.target - morph.progress) > 1e-3) {
|
|
347
|
+
const delta = context.clock.getDelta();
|
|
348
|
+
const direction = Math.sign(morph.target - morph.progress);
|
|
349
|
+
morph.progress = clamp(
|
|
350
|
+
morph.progress + direction * delta * MORPH_SPEED,
|
|
351
|
+
0,
|
|
352
|
+
1
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
uniforms.uMorph.value = morph.progress;
|
|
356
|
+
},
|
|
357
|
+
[]
|
|
358
|
+
);
|
|
359
|
+
const { containerRef } = useScene({
|
|
360
|
+
onCreate: handleCreate,
|
|
361
|
+
onRender: handleRender
|
|
362
|
+
});
|
|
363
|
+
(0, import_react3.useEffect)(() => {
|
|
364
|
+
const uniforms = uniformsRef.current;
|
|
365
|
+
uniforms.uTimeScale.value = timeScale;
|
|
366
|
+
uniforms.uPetalRadius.value = petalRadius;
|
|
367
|
+
uniforms.uSpread.value = BASE_SPREAD / Math.max(scale, 1e-4);
|
|
368
|
+
uniforms.uIntensity.value = intensity;
|
|
369
|
+
uniforms.uColor.value.set(color);
|
|
370
|
+
uniforms.uMorph.value = morphRef.current.progress;
|
|
371
|
+
}, [color, intensity, petalRadius, scale, timeScale]);
|
|
372
|
+
(0, import_react3.useEffect)(() => {
|
|
373
|
+
if (morphRef.current.cycle === morphCycle) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
morphRef.current.cycle = morphCycle;
|
|
377
|
+
morphRef.current.target = morphRef.current.target < 0.5 ? 1 : 0;
|
|
378
|
+
}, [morphCycle]);
|
|
379
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
380
|
+
"div",
|
|
381
|
+
{
|
|
382
|
+
ref: containerRef,
|
|
383
|
+
className,
|
|
384
|
+
style: {
|
|
385
|
+
width: width ?? "100%",
|
|
386
|
+
height: height ?? "100%",
|
|
387
|
+
...style
|
|
388
|
+
},
|
|
389
|
+
...divProps
|
|
390
|
+
}
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/components/OranoParticles.tsx
|
|
395
|
+
var import_react4 = require("react");
|
|
396
|
+
var THREE4 = __toESM(require("three"));
|
|
397
|
+
|
|
398
|
+
// src/shaders/orano-particles/fragment.glsl
|
|
399
|
+
var fragment_default3 = "uniform vec3 uColor;\nuniform float uAlpha;\n\nvarying float vAlpha;\n\nvoid main() {\n gl_FragColor = vec4(uColor, vAlpha * uAlpha);\n}\n";
|
|
400
|
+
|
|
401
|
+
// src/shaders/orano-particles/vertex.glsl
|
|
402
|
+
var vertex_default3 = "#define M_PI 3.1415926535897932384626433832795\n\nuniform float uTime;\nuniform float uWind;\nuniform float uBaseSize;\nuniform float uDistanceOffset;\nuniform float uDistanceStrength;\n\nvarying float vAlpha;\n\nhighp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, M_PI);\n\n return fract(sin(sn) * c);\n}\n\nvoid main() {\n vec4 modelPosition = modelMatrix * vec4(position, 1.0);\n\n modelPosition.x = mod(\n modelPosition.x + uTime * ((uWind * 0.5) + (uWind * 0.5) * random(modelPosition.yz)),\n 20.0\n ) -\n 10.0;\n\n modelPosition.y =\n modelPosition.y + sin(modelPosition.x) * 0.3 +\n modelPosition.y + sin(modelPosition.x * 0.357) * 0.3;\n\n vec4 viewPosition = viewMatrix * modelPosition;\n float cameraDistance = distance(vec4(0.0), viewPosition);\n\n float sizeFalloff =\n clamp((cameraDistance - uDistanceOffset) * uDistanceStrength, 0.0, uBaseSize);\n\n gl_PointSize = uBaseSize - sizeFalloff;\n gl_Position = projectionMatrix * viewPosition;\n\n vAlpha = 1.0 - clamp(cameraDistance * 0.5 / 12.0, 0.0, 1.0);\n}\n";
|
|
403
|
+
|
|
404
|
+
// src/components/OranoParticles.tsx
|
|
405
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
406
|
+
var X_RANGE = 20;
|
|
407
|
+
var Y_RANGE = { min: -8, max: 2 };
|
|
408
|
+
var Z_RANGE = { min: -5, max: 215 };
|
|
409
|
+
function createParticleGeometry(count) {
|
|
410
|
+
const geometry = new THREE4.BufferGeometry();
|
|
411
|
+
const positions = new Float32Array(count * 3);
|
|
412
|
+
for (let i = 0; i < count; i += 1) {
|
|
413
|
+
const x = (Math.random() - 0.5) * X_RANGE;
|
|
414
|
+
const y = Math.random() * (Y_RANGE.max - Y_RANGE.min) + Y_RANGE.min;
|
|
415
|
+
const z = Math.random() * (Z_RANGE.max - Z_RANGE.min) + Z_RANGE.min;
|
|
416
|
+
positions[i * 3] = x;
|
|
417
|
+
positions[i * 3 + 1] = y;
|
|
418
|
+
positions[i * 3 + 2] = z;
|
|
419
|
+
}
|
|
420
|
+
geometry.setAttribute("position", new THREE4.BufferAttribute(positions, 3));
|
|
421
|
+
geometry.computeBoundingSphere();
|
|
422
|
+
return geometry;
|
|
423
|
+
}
|
|
424
|
+
function buildUniforms({
|
|
425
|
+
color,
|
|
426
|
+
alpha,
|
|
427
|
+
wind,
|
|
428
|
+
baseSize,
|
|
429
|
+
distanceOffset,
|
|
430
|
+
distanceStrength
|
|
431
|
+
}) {
|
|
432
|
+
return {
|
|
433
|
+
uColor: { value: new THREE4.Color(color) },
|
|
434
|
+
uAlpha: { value: alpha },
|
|
435
|
+
uTime: { value: 0 },
|
|
436
|
+
uWind: { value: wind },
|
|
437
|
+
uBaseSize: { value: baseSize },
|
|
438
|
+
uDistanceOffset: { value: distanceOffset },
|
|
439
|
+
uDistanceStrength: { value: distanceStrength }
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function OranoParticles({
|
|
443
|
+
className,
|
|
444
|
+
style,
|
|
445
|
+
width,
|
|
446
|
+
height,
|
|
447
|
+
color,
|
|
448
|
+
alpha,
|
|
449
|
+
wind,
|
|
450
|
+
baseSize,
|
|
451
|
+
distanceOffset,
|
|
452
|
+
distanceStrength,
|
|
453
|
+
particleCount,
|
|
454
|
+
...divProps
|
|
455
|
+
}) {
|
|
456
|
+
const particlesRef = (0, import_react4.useRef)(null);
|
|
457
|
+
const initialUniformsRef = (0, import_react4.useRef)({
|
|
458
|
+
color,
|
|
459
|
+
alpha,
|
|
460
|
+
wind,
|
|
461
|
+
baseSize,
|
|
462
|
+
distanceOffset,
|
|
463
|
+
distanceStrength,
|
|
464
|
+
particleCount
|
|
465
|
+
});
|
|
466
|
+
initialUniformsRef.current = {
|
|
467
|
+
color,
|
|
468
|
+
alpha,
|
|
469
|
+
wind,
|
|
470
|
+
baseSize,
|
|
471
|
+
distanceOffset,
|
|
472
|
+
distanceStrength,
|
|
473
|
+
particleCount
|
|
474
|
+
};
|
|
475
|
+
const handleCreate = (0, import_react4.useCallback)(({ scene }) => {
|
|
476
|
+
const uniforms = buildUniforms(initialUniformsRef.current);
|
|
477
|
+
const geometry = createParticleGeometry(
|
|
478
|
+
Math.max(1, Math.floor(initialUniformsRef.current.particleCount))
|
|
479
|
+
);
|
|
480
|
+
const material = new THREE4.ShaderMaterial({
|
|
481
|
+
fragmentShader: fragment_default3,
|
|
482
|
+
vertexShader: vertex_default3,
|
|
483
|
+
uniforms,
|
|
484
|
+
transparent: true,
|
|
485
|
+
depthWrite: false
|
|
486
|
+
});
|
|
487
|
+
const points = new THREE4.Points(geometry, material);
|
|
488
|
+
points.frustumCulled = false;
|
|
489
|
+
scene.add(points);
|
|
490
|
+
particlesRef.current = { points, geometry, material, uniforms };
|
|
491
|
+
return () => {
|
|
492
|
+
scene.remove(points);
|
|
493
|
+
geometry.dispose();
|
|
494
|
+
material.dispose();
|
|
495
|
+
particlesRef.current = null;
|
|
496
|
+
};
|
|
497
|
+
}, []);
|
|
498
|
+
const handleRender = (0, import_react4.useCallback)((context) => {
|
|
499
|
+
const assets = particlesRef.current;
|
|
500
|
+
if (!assets) return;
|
|
501
|
+
assets.uniforms.uTime.value = context.clock.getElapsedTime();
|
|
502
|
+
}, []);
|
|
503
|
+
const { containerRef } = useScene({
|
|
504
|
+
onCreate: handleCreate,
|
|
505
|
+
onRender: handleRender
|
|
506
|
+
});
|
|
507
|
+
(0, import_react4.useEffect)(() => {
|
|
508
|
+
const assets = particlesRef.current;
|
|
509
|
+
if (!assets) return;
|
|
510
|
+
assets.uniforms.uColor.value.set(color);
|
|
511
|
+
}, [color]);
|
|
512
|
+
(0, import_react4.useEffect)(() => {
|
|
513
|
+
const assets = particlesRef.current;
|
|
514
|
+
if (!assets) return;
|
|
515
|
+
assets.uniforms.uAlpha.value = alpha;
|
|
516
|
+
}, [alpha]);
|
|
517
|
+
(0, import_react4.useEffect)(() => {
|
|
518
|
+
const assets = particlesRef.current;
|
|
519
|
+
if (!assets) return;
|
|
520
|
+
assets.uniforms.uWind.value = wind;
|
|
521
|
+
}, [wind]);
|
|
522
|
+
(0, import_react4.useEffect)(() => {
|
|
523
|
+
const assets = particlesRef.current;
|
|
524
|
+
if (!assets) return;
|
|
525
|
+
assets.uniforms.uBaseSize.value = baseSize;
|
|
526
|
+
}, [baseSize]);
|
|
527
|
+
(0, import_react4.useEffect)(() => {
|
|
528
|
+
const assets = particlesRef.current;
|
|
529
|
+
if (!assets) return;
|
|
530
|
+
assets.uniforms.uDistanceOffset.value = distanceOffset;
|
|
531
|
+
}, [distanceOffset]);
|
|
532
|
+
(0, import_react4.useEffect)(() => {
|
|
533
|
+
const assets = particlesRef.current;
|
|
534
|
+
if (!assets) return;
|
|
535
|
+
assets.uniforms.uDistanceStrength.value = distanceStrength;
|
|
536
|
+
}, [distanceStrength]);
|
|
537
|
+
(0, import_react4.useEffect)(() => {
|
|
538
|
+
const assets = particlesRef.current;
|
|
539
|
+
if (!assets) return;
|
|
540
|
+
const geometry = createParticleGeometry(
|
|
541
|
+
Math.max(1, Math.floor(particleCount))
|
|
542
|
+
);
|
|
543
|
+
assets.points.geometry.dispose();
|
|
544
|
+
assets.points.geometry = geometry;
|
|
545
|
+
assets.geometry = geometry;
|
|
546
|
+
}, [particleCount]);
|
|
547
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
548
|
+
"div",
|
|
549
|
+
{
|
|
550
|
+
ref: containerRef,
|
|
551
|
+
className,
|
|
552
|
+
style: {
|
|
553
|
+
width: width ?? "100%",
|
|
554
|
+
height: height ?? "100%",
|
|
555
|
+
...style
|
|
556
|
+
},
|
|
557
|
+
...divProps
|
|
558
|
+
}
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
562
|
+
0 && (module.exports = {
|
|
563
|
+
FractalFlower,
|
|
564
|
+
OranoParticles,
|
|
565
|
+
ShaderArt
|
|
566
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
// src/components/ShaderArt.tsx
|
|
2
|
+
import { useCallback, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
3
|
+
import * as THREE2 from "three";
|
|
4
|
+
|
|
5
|
+
// src/hooks/useScene.ts
|
|
6
|
+
import { useEffect, useRef } from "react";
|
|
7
|
+
import * as THREE from "three";
|
|
8
|
+
var EMPTY_DEPS = [];
|
|
9
|
+
function useScene({
|
|
10
|
+
renderer: rendererOptions,
|
|
11
|
+
camera: cameraOptions,
|
|
12
|
+
pixelRatio,
|
|
13
|
+
onCreate,
|
|
14
|
+
onRender,
|
|
15
|
+
onResize,
|
|
16
|
+
deps
|
|
17
|
+
} = {}) {
|
|
18
|
+
const containerRef = useRef(null);
|
|
19
|
+
const contextRef = useRef(null);
|
|
20
|
+
const onCreateRef = useRef(onCreate);
|
|
21
|
+
const onRenderRef = useRef(onRender);
|
|
22
|
+
const onResizeRef = useRef(onResize);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
onCreateRef.current = onCreate;
|
|
25
|
+
}, [onCreate]);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
onRenderRef.current = onRender;
|
|
28
|
+
}, [onRender]);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
onResizeRef.current = onResize;
|
|
31
|
+
}, [onResize]);
|
|
32
|
+
const effectDeps = deps ?? EMPTY_DEPS;
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const container = containerRef.current;
|
|
35
|
+
if (!container) return;
|
|
36
|
+
const resolvedPixelRatio = pixelRatio ?? (typeof window !== "undefined" ? Math.min(window.devicePixelRatio, 2) : 1);
|
|
37
|
+
const renderer = new THREE.WebGLRenderer({
|
|
38
|
+
alpha: true,
|
|
39
|
+
antialias: true,
|
|
40
|
+
...rendererOptions
|
|
41
|
+
});
|
|
42
|
+
renderer.setPixelRatio(resolvedPixelRatio);
|
|
43
|
+
renderer.setSize(container.clientWidth, container.clientHeight, false);
|
|
44
|
+
renderer.setClearColor(0, 0);
|
|
45
|
+
renderer.domElement.style.width = "100%";
|
|
46
|
+
renderer.domElement.style.height = "100%";
|
|
47
|
+
container.appendChild(renderer.domElement);
|
|
48
|
+
const scene = new THREE.Scene();
|
|
49
|
+
const camera = new THREE.PerspectiveCamera(
|
|
50
|
+
cameraOptions?.fov ?? 55,
|
|
51
|
+
container.clientWidth / Math.max(1, container.clientHeight),
|
|
52
|
+
cameraOptions?.near ?? 0.1,
|
|
53
|
+
cameraOptions?.far ?? 500
|
|
54
|
+
);
|
|
55
|
+
const [x = 0, y = 0, z = 15] = cameraOptions?.position ?? [];
|
|
56
|
+
camera.position.set(x, y, z);
|
|
57
|
+
const clock = new THREE.Clock();
|
|
58
|
+
const context = { renderer, scene, camera, clock };
|
|
59
|
+
contextRef.current = context;
|
|
60
|
+
const teardownCreate = onCreateRef.current?.(context);
|
|
61
|
+
let animationFrameId = 0;
|
|
62
|
+
const renderLoop = () => {
|
|
63
|
+
onRenderRef.current?.(context);
|
|
64
|
+
renderer.render(scene, camera);
|
|
65
|
+
animationFrameId = requestAnimationFrame(renderLoop);
|
|
66
|
+
};
|
|
67
|
+
animationFrameId = requestAnimationFrame(renderLoop);
|
|
68
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
69
|
+
const width = container.clientWidth;
|
|
70
|
+
const height = container.clientHeight;
|
|
71
|
+
renderer.setSize(width, height, false);
|
|
72
|
+
camera.aspect = width / Math.max(1, height);
|
|
73
|
+
camera.updateProjectionMatrix();
|
|
74
|
+
onResizeRef.current?.(context, { width, height });
|
|
75
|
+
});
|
|
76
|
+
resizeObserver.observe(container);
|
|
77
|
+
return () => {
|
|
78
|
+
teardownCreate?.();
|
|
79
|
+
cancelAnimationFrame(animationFrameId);
|
|
80
|
+
resizeObserver.disconnect();
|
|
81
|
+
scene.clear();
|
|
82
|
+
renderer.dispose();
|
|
83
|
+
if (renderer.domElement.parentNode === container) {
|
|
84
|
+
container.removeChild(renderer.domElement);
|
|
85
|
+
}
|
|
86
|
+
contextRef.current = null;
|
|
87
|
+
};
|
|
88
|
+
}, effectDeps);
|
|
89
|
+
return { containerRef, contextRef };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/shaders/shader-art/fragment.glsl
|
|
93
|
+
var fragment_default = "precision highp float;\n\nvarying vec2 vUv;\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uIterations;\nuniform float uAmplitude;\nuniform float uFreq;\n\nvec3 palette(float t) {\n vec3 a = vec3(0.5, 0.5, 0.5);\n vec3 b = vec3(0.5, 0.5, 0.5);\n vec3 c = vec3(1.0, 1.0, 1.0);\n vec3 d = vec3(0.263, 0.416, 0.557);\n return a + b * cos(6.28318 * (c * t + d));\n}\n\nvoid main() {\n vec2 fragCoord = vec2(vUv.x * iResolution.x, vUv.y * iResolution.y);\n vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;\n vec2 uv0 = uv;\n vec3 finalColor = vec3(0.0);\n const int MAX_ITERATIONS = 8;\n for (int idx = 0; idx < MAX_ITERATIONS; idx++) {\n float i = float(idx);\n if (i >= uIterations) {\n break;\n }\n uv = fract(uv * uAmplitude) - 0.5;\n float d = length(uv) * exp(-length(uv0));\n vec3 col = palette(length(uv0) + i * 0.4 + iTime * uFreq);\n d = sin(d * 8.0 + iTime) / 8.0;\n d = abs(d);\n d = pow(0.01 / max(d, 1e-4), 1.9);\n finalColor += col * d;\n }\n gl_FragColor = vec4(finalColor, 1.0);\n}";
|
|
94
|
+
|
|
95
|
+
// src/shaders/shader-art/vertex.glsl
|
|
96
|
+
var vertex_default = "varying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 1.0);\n}";
|
|
97
|
+
|
|
98
|
+
// src/components/ShaderArt.tsx
|
|
99
|
+
import { jsx } from "react/jsx-runtime";
|
|
100
|
+
function ShaderArt({
|
|
101
|
+
uniforms,
|
|
102
|
+
className,
|
|
103
|
+
style,
|
|
104
|
+
width,
|
|
105
|
+
height,
|
|
106
|
+
...divProps
|
|
107
|
+
}) {
|
|
108
|
+
const shaderUniformsRef = useRef2({
|
|
109
|
+
iTime: { value: 0 },
|
|
110
|
+
iResolution: { value: new THREE2.Vector3(1, 1, 1) },
|
|
111
|
+
uIterations: { value: Math.max(1, uniforms.uIterations) },
|
|
112
|
+
uAmplitude: { value: uniforms.uAmplitude },
|
|
113
|
+
uFreq: { value: uniforms.uFreq }
|
|
114
|
+
});
|
|
115
|
+
const assetsRef = useRef2(null);
|
|
116
|
+
const handleCreate = useCallback(({ scene }) => {
|
|
117
|
+
const geometry = new THREE2.PlaneGeometry(2, 2);
|
|
118
|
+
const material = new THREE2.ShaderMaterial({
|
|
119
|
+
vertexShader: vertex_default,
|
|
120
|
+
fragmentShader: fragment_default,
|
|
121
|
+
uniforms: shaderUniformsRef.current
|
|
122
|
+
});
|
|
123
|
+
const mesh = new THREE2.Mesh(geometry, material);
|
|
124
|
+
scene.add(mesh);
|
|
125
|
+
assetsRef.current = { mesh, geometry, material };
|
|
126
|
+
return () => {
|
|
127
|
+
scene.remove(mesh);
|
|
128
|
+
geometry.dispose();
|
|
129
|
+
material.dispose();
|
|
130
|
+
assetsRef.current = null;
|
|
131
|
+
};
|
|
132
|
+
}, []);
|
|
133
|
+
const handleRender = useCallback((context) => {
|
|
134
|
+
shaderUniformsRef.current.iTime.value = context.clock.getElapsedTime();
|
|
135
|
+
const canvas = context.renderer.domElement;
|
|
136
|
+
shaderUniformsRef.current.iResolution.value.set(
|
|
137
|
+
canvas.width,
|
|
138
|
+
canvas.height,
|
|
139
|
+
1
|
|
140
|
+
);
|
|
141
|
+
}, []);
|
|
142
|
+
const { containerRef } = useScene({
|
|
143
|
+
onCreate: handleCreate,
|
|
144
|
+
onRender: handleRender
|
|
145
|
+
});
|
|
146
|
+
useEffect2(() => {
|
|
147
|
+
shaderUniformsRef.current.uIterations.value = Math.max(
|
|
148
|
+
1,
|
|
149
|
+
uniforms.uIterations
|
|
150
|
+
);
|
|
151
|
+
shaderUniformsRef.current.uAmplitude.value = uniforms.uAmplitude;
|
|
152
|
+
shaderUniformsRef.current.uFreq.value = uniforms.uFreq;
|
|
153
|
+
}, [uniforms.uAmplitude, uniforms.uFreq, uniforms.uIterations]);
|
|
154
|
+
return /* @__PURE__ */ jsx(
|
|
155
|
+
"div",
|
|
156
|
+
{
|
|
157
|
+
ref: containerRef,
|
|
158
|
+
className,
|
|
159
|
+
style: {
|
|
160
|
+
width: width ?? "100%",
|
|
161
|
+
height: height ?? "100%",
|
|
162
|
+
...style
|
|
163
|
+
},
|
|
164
|
+
...divProps
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/components/FractalFlower.tsx
|
|
170
|
+
import {
|
|
171
|
+
useCallback as useCallback2,
|
|
172
|
+
useEffect as useEffect3,
|
|
173
|
+
useMemo,
|
|
174
|
+
useRef as useRef3
|
|
175
|
+
} from "react";
|
|
176
|
+
import * as THREE3 from "three";
|
|
177
|
+
|
|
178
|
+
// src/shaders/fractal-flower/fragment.glsl
|
|
179
|
+
var fragment_default2 = "precision highp float;\n\nuniform float uIntensity;\nuniform float uExposure;\nuniform float uPetalRadius;\nuniform float uMorph;\n\nvarying vec3 vColor;\nvarying float vWeight;\n\nvoid main() {\n vec2 uv = gl_PointCoord - vec2(0.5);\n float sigma = max(0.3, uPetalRadius * 10.0);\n float dist2 = dot(uv, uv);\n float alpha = exp(-dist2 / (sigma * sigma));\n\n if (alpha <= 0.01) {\n discard;\n }\n\n float weight = 0.6 + 0.4 * vWeight;\n float falloff = pow(alpha, 0.8);\n float morphBoost = mix(1.0, 1.35, uMorph);\n vec3 color = clamp(vColor * weight * falloff * (uIntensity * morphBoost), 0.0, 1.0);\n\n float outAlpha = clamp(alpha * mix(0.6, 0.85, uMorph), 0.0, 1.0);\n gl_FragColor = vec4(color, outAlpha);\n}\n";
|
|
180
|
+
|
|
181
|
+
// src/shaders/fractal-flower/vertex.glsl
|
|
182
|
+
var vertex_default2 = "precision highp float;\n\nattribute float aK;\nattribute float aE;\nattribute float aRotation;\n\nuniform float uTime;\nuniform float uTimeScale;\nuniform float uSpread;\nuniform float uPointSize;\nuniform float uMorph;\nuniform vec3 uColor;\n\nvarying vec3 vColor;\nvarying float vWeight;\n\nmat2 rotation(float angle) {\n float c = cos(angle);\n float s = sin(angle);\n return mat2(c, -s, s, c);\n}\n\nvoid main() {\n float time = uTime * uTimeScale;\n float d = 7.0 * cos(length(vec2(aK, aE)) / 3.0 + time * 0.5);\n\n vec2 basePoint = vec2(\n aK * 4.0 + d * aK * sin(d + aE / 9.0 + time),\n aE * 2.0 - d * 9.0 - d * 9.0 * cos(d + time)\n );\n\n vec2 altPoint = vec2(\n aK * 6.0 + sin(time * 1.3 + aE * 2.0) * (42.0 + 12.0 * sin(aE * 1.15)),\n aE * 3.4 - d * 12.5 + 28.0 * cos(time * 0.75 + aK * 0.35)\n );\n\n vec2 morphed = mix(basePoint, altPoint, uMorph);\n float swirl = aRotation + uMorph * (0.8 + 0.25 * sin(time * 0.4 + aE));\n vec2 rotated = rotation(swirl) * morphed;\n\n float spreadMix = mix(uSpread, uSpread * 0.65, uMorph);\n vec2 scaled = rotated / spreadMix;\n vec3 modelPosition = vec3(scaled, 0.0);\n\n float radial = length(morphed) / (spreadMix * 0.9);\n vWeight = clamp(1.0 - radial * radial, 0.0, 1.0);\n\n vColor = uColor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(modelPosition, 1.0);\n gl_PointSize = uPointSize * mix(1.0, 1.35, uMorph);\n}\n";
|
|
183
|
+
|
|
184
|
+
// src/components/FractalFlower.tsx
|
|
185
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
186
|
+
var BASE_POINT_COUNT = 2e4;
|
|
187
|
+
var ROTATIONS = [
|
|
188
|
+
0,
|
|
189
|
+
Math.PI / 3,
|
|
190
|
+
2 * Math.PI / 3,
|
|
191
|
+
Math.PI,
|
|
192
|
+
4 * Math.PI / 3,
|
|
193
|
+
5 * Math.PI / 3
|
|
194
|
+
];
|
|
195
|
+
var ROTATION_COUNT = ROTATIONS.length;
|
|
196
|
+
var TOTAL_POINTS = ROTATION_COUNT * BASE_POINT_COUNT;
|
|
197
|
+
var BASE_SPREAD = 200;
|
|
198
|
+
var MORPH_SPEED = 0.6;
|
|
199
|
+
var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
200
|
+
function FractalFlower({
|
|
201
|
+
timeScale,
|
|
202
|
+
petalRadius,
|
|
203
|
+
scale,
|
|
204
|
+
intensity,
|
|
205
|
+
morphCycle,
|
|
206
|
+
color,
|
|
207
|
+
className,
|
|
208
|
+
style,
|
|
209
|
+
width,
|
|
210
|
+
height,
|
|
211
|
+
...divProps
|
|
212
|
+
}) {
|
|
213
|
+
const attributes = useMemo(() => {
|
|
214
|
+
const positions = new Float32Array(TOTAL_POINTS * 3);
|
|
215
|
+
const kValues = new Float32Array(TOTAL_POINTS);
|
|
216
|
+
const eValues = new Float32Array(TOTAL_POINTS);
|
|
217
|
+
const rotations = new Float32Array(TOTAL_POINTS);
|
|
218
|
+
for (let copy = 0; copy < ROTATION_COUNT; copy++) {
|
|
219
|
+
const angle = ROTATIONS[copy];
|
|
220
|
+
for (let i = 0; i < BASE_POINT_COUNT; i++) {
|
|
221
|
+
const index = copy * BASE_POINT_COUNT + i;
|
|
222
|
+
const k = i % 25 - 12;
|
|
223
|
+
const e = i / 800;
|
|
224
|
+
positions[index * 3] = 0;
|
|
225
|
+
positions[index * 3 + 1] = 0;
|
|
226
|
+
positions[index * 3 + 2] = 0;
|
|
227
|
+
kValues[index] = k;
|
|
228
|
+
eValues[index] = e;
|
|
229
|
+
rotations[index] = angle;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
positions,
|
|
234
|
+
kValues,
|
|
235
|
+
eValues,
|
|
236
|
+
rotations
|
|
237
|
+
};
|
|
238
|
+
}, []);
|
|
239
|
+
const uniformsRef = useRef3({
|
|
240
|
+
uTime: { value: 0 },
|
|
241
|
+
uResolution: { value: new THREE3.Vector2(1, 1) },
|
|
242
|
+
uTimeScale: { value: 0.78539816339 },
|
|
243
|
+
uPetalRadius: { value: 0.02 },
|
|
244
|
+
uIntensity: { value: 0.25 },
|
|
245
|
+
uExposure: { value: 5 },
|
|
246
|
+
uSpread: { value: BASE_SPREAD / Math.max(scale, 1e-4) },
|
|
247
|
+
uPointSize: { value: 2.5 },
|
|
248
|
+
uColor: { value: new THREE3.Color(color) },
|
|
249
|
+
uMorph: { value: 0 }
|
|
250
|
+
});
|
|
251
|
+
const morphRef = useRef3({
|
|
252
|
+
progress: uniformsRef.current.uMorph.value,
|
|
253
|
+
target: 0,
|
|
254
|
+
cycle: morphCycle
|
|
255
|
+
});
|
|
256
|
+
const assetsRef = useRef3(null);
|
|
257
|
+
const handleCreate = useCallback2(
|
|
258
|
+
({ scene }) => {
|
|
259
|
+
const geometry = new THREE3.BufferGeometry();
|
|
260
|
+
geometry.setAttribute(
|
|
261
|
+
"position",
|
|
262
|
+
new THREE3.BufferAttribute(attributes.positions, 3)
|
|
263
|
+
);
|
|
264
|
+
geometry.setAttribute(
|
|
265
|
+
"aK",
|
|
266
|
+
new THREE3.BufferAttribute(attributes.kValues, 1)
|
|
267
|
+
);
|
|
268
|
+
geometry.setAttribute(
|
|
269
|
+
"aE",
|
|
270
|
+
new THREE3.BufferAttribute(attributes.eValues, 1)
|
|
271
|
+
);
|
|
272
|
+
geometry.setAttribute(
|
|
273
|
+
"aRotation",
|
|
274
|
+
new THREE3.BufferAttribute(attributes.rotations, 1)
|
|
275
|
+
);
|
|
276
|
+
const uniformValues = uniformsRef.current;
|
|
277
|
+
const material = new THREE3.ShaderMaterial({
|
|
278
|
+
vertexShader: vertex_default2,
|
|
279
|
+
fragmentShader: fragment_default2,
|
|
280
|
+
uniforms: uniformValues,
|
|
281
|
+
blending: THREE3.AdditiveBlending,
|
|
282
|
+
depthWrite: false,
|
|
283
|
+
transparent: true
|
|
284
|
+
});
|
|
285
|
+
const points = new THREE3.Points(geometry, material);
|
|
286
|
+
points.frustumCulled = false;
|
|
287
|
+
scene.add(points);
|
|
288
|
+
assetsRef.current = { points, geometry, material, uniforms: uniformValues };
|
|
289
|
+
return () => {
|
|
290
|
+
scene.remove(points);
|
|
291
|
+
geometry.dispose();
|
|
292
|
+
material.dispose();
|
|
293
|
+
assetsRef.current = null;
|
|
294
|
+
};
|
|
295
|
+
},
|
|
296
|
+
[attributes.eValues, attributes.kValues, attributes.positions, attributes.rotations]
|
|
297
|
+
);
|
|
298
|
+
const handleRender = useCallback2(
|
|
299
|
+
(context) => {
|
|
300
|
+
const assets = assetsRef.current;
|
|
301
|
+
if (!assets) return;
|
|
302
|
+
const uniforms = assets.uniforms;
|
|
303
|
+
uniforms.uTime.value = context.clock.getElapsedTime();
|
|
304
|
+
const canvas = context.renderer.domElement;
|
|
305
|
+
uniforms.uResolution.value.set(canvas.width, canvas.height);
|
|
306
|
+
const basePointSize = Math.max(
|
|
307
|
+
2,
|
|
308
|
+
canvas.height / 400 * (uniforms.uPetalRadius.value * 32)
|
|
309
|
+
);
|
|
310
|
+
const pointSize = Math.min(basePointSize, 6);
|
|
311
|
+
uniforms.uPointSize.value = pointSize;
|
|
312
|
+
const morph = morphRef.current;
|
|
313
|
+
if (Math.abs(morph.target - morph.progress) > 1e-3) {
|
|
314
|
+
const delta = context.clock.getDelta();
|
|
315
|
+
const direction = Math.sign(morph.target - morph.progress);
|
|
316
|
+
morph.progress = clamp(
|
|
317
|
+
morph.progress + direction * delta * MORPH_SPEED,
|
|
318
|
+
0,
|
|
319
|
+
1
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
uniforms.uMorph.value = morph.progress;
|
|
323
|
+
},
|
|
324
|
+
[]
|
|
325
|
+
);
|
|
326
|
+
const { containerRef } = useScene({
|
|
327
|
+
onCreate: handleCreate,
|
|
328
|
+
onRender: handleRender
|
|
329
|
+
});
|
|
330
|
+
useEffect3(() => {
|
|
331
|
+
const uniforms = uniformsRef.current;
|
|
332
|
+
uniforms.uTimeScale.value = timeScale;
|
|
333
|
+
uniforms.uPetalRadius.value = petalRadius;
|
|
334
|
+
uniforms.uSpread.value = BASE_SPREAD / Math.max(scale, 1e-4);
|
|
335
|
+
uniforms.uIntensity.value = intensity;
|
|
336
|
+
uniforms.uColor.value.set(color);
|
|
337
|
+
uniforms.uMorph.value = morphRef.current.progress;
|
|
338
|
+
}, [color, intensity, petalRadius, scale, timeScale]);
|
|
339
|
+
useEffect3(() => {
|
|
340
|
+
if (morphRef.current.cycle === morphCycle) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
morphRef.current.cycle = morphCycle;
|
|
344
|
+
morphRef.current.target = morphRef.current.target < 0.5 ? 1 : 0;
|
|
345
|
+
}, [morphCycle]);
|
|
346
|
+
return /* @__PURE__ */ jsx2(
|
|
347
|
+
"div",
|
|
348
|
+
{
|
|
349
|
+
ref: containerRef,
|
|
350
|
+
className,
|
|
351
|
+
style: {
|
|
352
|
+
width: width ?? "100%",
|
|
353
|
+
height: height ?? "100%",
|
|
354
|
+
...style
|
|
355
|
+
},
|
|
356
|
+
...divProps
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/components/OranoParticles.tsx
|
|
362
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
363
|
+
import * as THREE4 from "three";
|
|
364
|
+
|
|
365
|
+
// src/shaders/orano-particles/fragment.glsl
|
|
366
|
+
var fragment_default3 = "uniform vec3 uColor;\nuniform float uAlpha;\n\nvarying float vAlpha;\n\nvoid main() {\n gl_FragColor = vec4(uColor, vAlpha * uAlpha);\n}\n";
|
|
367
|
+
|
|
368
|
+
// src/shaders/orano-particles/vertex.glsl
|
|
369
|
+
var vertex_default3 = "#define M_PI 3.1415926535897932384626433832795\n\nuniform float uTime;\nuniform float uWind;\nuniform float uBaseSize;\nuniform float uDistanceOffset;\nuniform float uDistanceStrength;\n\nvarying float vAlpha;\n\nhighp float random(vec2 co) {\n highp float a = 12.9898;\n highp float b = 78.233;\n highp float c = 43758.5453;\n highp float dt = dot(co.xy, vec2(a, b));\n highp float sn = mod(dt, M_PI);\n\n return fract(sin(sn) * c);\n}\n\nvoid main() {\n vec4 modelPosition = modelMatrix * vec4(position, 1.0);\n\n modelPosition.x = mod(\n modelPosition.x + uTime * ((uWind * 0.5) + (uWind * 0.5) * random(modelPosition.yz)),\n 20.0\n ) -\n 10.0;\n\n modelPosition.y =\n modelPosition.y + sin(modelPosition.x) * 0.3 +\n modelPosition.y + sin(modelPosition.x * 0.357) * 0.3;\n\n vec4 viewPosition = viewMatrix * modelPosition;\n float cameraDistance = distance(vec4(0.0), viewPosition);\n\n float sizeFalloff =\n clamp((cameraDistance - uDistanceOffset) * uDistanceStrength, 0.0, uBaseSize);\n\n gl_PointSize = uBaseSize - sizeFalloff;\n gl_Position = projectionMatrix * viewPosition;\n\n vAlpha = 1.0 - clamp(cameraDistance * 0.5 / 12.0, 0.0, 1.0);\n}\n";
|
|
370
|
+
|
|
371
|
+
// src/components/OranoParticles.tsx
|
|
372
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
373
|
+
var X_RANGE = 20;
|
|
374
|
+
var Y_RANGE = { min: -8, max: 2 };
|
|
375
|
+
var Z_RANGE = { min: -5, max: 215 };
|
|
376
|
+
function createParticleGeometry(count) {
|
|
377
|
+
const geometry = new THREE4.BufferGeometry();
|
|
378
|
+
const positions = new Float32Array(count * 3);
|
|
379
|
+
for (let i = 0; i < count; i += 1) {
|
|
380
|
+
const x = (Math.random() - 0.5) * X_RANGE;
|
|
381
|
+
const y = Math.random() * (Y_RANGE.max - Y_RANGE.min) + Y_RANGE.min;
|
|
382
|
+
const z = Math.random() * (Z_RANGE.max - Z_RANGE.min) + Z_RANGE.min;
|
|
383
|
+
positions[i * 3] = x;
|
|
384
|
+
positions[i * 3 + 1] = y;
|
|
385
|
+
positions[i * 3 + 2] = z;
|
|
386
|
+
}
|
|
387
|
+
geometry.setAttribute("position", new THREE4.BufferAttribute(positions, 3));
|
|
388
|
+
geometry.computeBoundingSphere();
|
|
389
|
+
return geometry;
|
|
390
|
+
}
|
|
391
|
+
function buildUniforms({
|
|
392
|
+
color,
|
|
393
|
+
alpha,
|
|
394
|
+
wind,
|
|
395
|
+
baseSize,
|
|
396
|
+
distanceOffset,
|
|
397
|
+
distanceStrength
|
|
398
|
+
}) {
|
|
399
|
+
return {
|
|
400
|
+
uColor: { value: new THREE4.Color(color) },
|
|
401
|
+
uAlpha: { value: alpha },
|
|
402
|
+
uTime: { value: 0 },
|
|
403
|
+
uWind: { value: wind },
|
|
404
|
+
uBaseSize: { value: baseSize },
|
|
405
|
+
uDistanceOffset: { value: distanceOffset },
|
|
406
|
+
uDistanceStrength: { value: distanceStrength }
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function OranoParticles({
|
|
410
|
+
className,
|
|
411
|
+
style,
|
|
412
|
+
width,
|
|
413
|
+
height,
|
|
414
|
+
color,
|
|
415
|
+
alpha,
|
|
416
|
+
wind,
|
|
417
|
+
baseSize,
|
|
418
|
+
distanceOffset,
|
|
419
|
+
distanceStrength,
|
|
420
|
+
particleCount,
|
|
421
|
+
...divProps
|
|
422
|
+
}) {
|
|
423
|
+
const particlesRef = useRef4(null);
|
|
424
|
+
const initialUniformsRef = useRef4({
|
|
425
|
+
color,
|
|
426
|
+
alpha,
|
|
427
|
+
wind,
|
|
428
|
+
baseSize,
|
|
429
|
+
distanceOffset,
|
|
430
|
+
distanceStrength,
|
|
431
|
+
particleCount
|
|
432
|
+
});
|
|
433
|
+
initialUniformsRef.current = {
|
|
434
|
+
color,
|
|
435
|
+
alpha,
|
|
436
|
+
wind,
|
|
437
|
+
baseSize,
|
|
438
|
+
distanceOffset,
|
|
439
|
+
distanceStrength,
|
|
440
|
+
particleCount
|
|
441
|
+
};
|
|
442
|
+
const handleCreate = useCallback3(({ scene }) => {
|
|
443
|
+
const uniforms = buildUniforms(initialUniformsRef.current);
|
|
444
|
+
const geometry = createParticleGeometry(
|
|
445
|
+
Math.max(1, Math.floor(initialUniformsRef.current.particleCount))
|
|
446
|
+
);
|
|
447
|
+
const material = new THREE4.ShaderMaterial({
|
|
448
|
+
fragmentShader: fragment_default3,
|
|
449
|
+
vertexShader: vertex_default3,
|
|
450
|
+
uniforms,
|
|
451
|
+
transparent: true,
|
|
452
|
+
depthWrite: false
|
|
453
|
+
});
|
|
454
|
+
const points = new THREE4.Points(geometry, material);
|
|
455
|
+
points.frustumCulled = false;
|
|
456
|
+
scene.add(points);
|
|
457
|
+
particlesRef.current = { points, geometry, material, uniforms };
|
|
458
|
+
return () => {
|
|
459
|
+
scene.remove(points);
|
|
460
|
+
geometry.dispose();
|
|
461
|
+
material.dispose();
|
|
462
|
+
particlesRef.current = null;
|
|
463
|
+
};
|
|
464
|
+
}, []);
|
|
465
|
+
const handleRender = useCallback3((context) => {
|
|
466
|
+
const assets = particlesRef.current;
|
|
467
|
+
if (!assets) return;
|
|
468
|
+
assets.uniforms.uTime.value = context.clock.getElapsedTime();
|
|
469
|
+
}, []);
|
|
470
|
+
const { containerRef } = useScene({
|
|
471
|
+
onCreate: handleCreate,
|
|
472
|
+
onRender: handleRender
|
|
473
|
+
});
|
|
474
|
+
useEffect4(() => {
|
|
475
|
+
const assets = particlesRef.current;
|
|
476
|
+
if (!assets) return;
|
|
477
|
+
assets.uniforms.uColor.value.set(color);
|
|
478
|
+
}, [color]);
|
|
479
|
+
useEffect4(() => {
|
|
480
|
+
const assets = particlesRef.current;
|
|
481
|
+
if (!assets) return;
|
|
482
|
+
assets.uniforms.uAlpha.value = alpha;
|
|
483
|
+
}, [alpha]);
|
|
484
|
+
useEffect4(() => {
|
|
485
|
+
const assets = particlesRef.current;
|
|
486
|
+
if (!assets) return;
|
|
487
|
+
assets.uniforms.uWind.value = wind;
|
|
488
|
+
}, [wind]);
|
|
489
|
+
useEffect4(() => {
|
|
490
|
+
const assets = particlesRef.current;
|
|
491
|
+
if (!assets) return;
|
|
492
|
+
assets.uniforms.uBaseSize.value = baseSize;
|
|
493
|
+
}, [baseSize]);
|
|
494
|
+
useEffect4(() => {
|
|
495
|
+
const assets = particlesRef.current;
|
|
496
|
+
if (!assets) return;
|
|
497
|
+
assets.uniforms.uDistanceOffset.value = distanceOffset;
|
|
498
|
+
}, [distanceOffset]);
|
|
499
|
+
useEffect4(() => {
|
|
500
|
+
const assets = particlesRef.current;
|
|
501
|
+
if (!assets) return;
|
|
502
|
+
assets.uniforms.uDistanceStrength.value = distanceStrength;
|
|
503
|
+
}, [distanceStrength]);
|
|
504
|
+
useEffect4(() => {
|
|
505
|
+
const assets = particlesRef.current;
|
|
506
|
+
if (!assets) return;
|
|
507
|
+
const geometry = createParticleGeometry(
|
|
508
|
+
Math.max(1, Math.floor(particleCount))
|
|
509
|
+
);
|
|
510
|
+
assets.points.geometry.dispose();
|
|
511
|
+
assets.points.geometry = geometry;
|
|
512
|
+
assets.geometry = geometry;
|
|
513
|
+
}, [particleCount]);
|
|
514
|
+
return /* @__PURE__ */ jsx3(
|
|
515
|
+
"div",
|
|
516
|
+
{
|
|
517
|
+
ref: containerRef,
|
|
518
|
+
className,
|
|
519
|
+
style: {
|
|
520
|
+
width: width ?? "100%",
|
|
521
|
+
height: height ?? "100%",
|
|
522
|
+
...style
|
|
523
|
+
},
|
|
524
|
+
...divProps
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
export {
|
|
529
|
+
FractalFlower,
|
|
530
|
+
OranoParticles,
|
|
531
|
+
ShaderArt
|
|
532
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@toriistudio/shader-ui",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Shader components",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/types/index.d.ts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/types/index.d.ts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"./preset": {
|
|
20
|
+
"import": "./dist/preset.mjs",
|
|
21
|
+
"require": "./dist/preset.js"
|
|
22
|
+
},
|
|
23
|
+
"./styles.css": "./dist/index.css"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"local:push": "yarn build && yalc push",
|
|
34
|
+
"release:patch": "npm version patch && git push --follow-tags && npm publish",
|
|
35
|
+
"release:minor": "npm version minor && git push --follow-tags && npm publish",
|
|
36
|
+
"release:major": "npm version major && git push --follow-tags && npm publish",
|
|
37
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/toriistudio/shader-ui.git"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"react",
|
|
45
|
+
"component",
|
|
46
|
+
"headless",
|
|
47
|
+
"three",
|
|
48
|
+
"shaders"
|
|
49
|
+
],
|
|
50
|
+
"author": "toriistudio",
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/toriistudio/shader-ui/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/toriistudio/shader-ui#readme",
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"react": ">=19 <20",
|
|
58
|
+
"react-dom": ">=19 <20",
|
|
59
|
+
"three": ">=0.180 <1",
|
|
60
|
+
"three-stdlib": ">=2.36.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"three": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"three-stdlib": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@types/node": "^24.0.3",
|
|
72
|
+
"@types/react": "^19.0.0",
|
|
73
|
+
"@types/react-dom": "^19.0.0",
|
|
74
|
+
"three": "^0.177.0",
|
|
75
|
+
"three-stdlib": "^2.36.0",
|
|
76
|
+
"tsup": "^8.4.0",
|
|
77
|
+
"typescript": "^5.8.3"
|
|
78
|
+
},
|
|
79
|
+
"publishConfig": {
|
|
80
|
+
"access": "public"
|
|
81
|
+
},
|
|
82
|
+
"engines": {
|
|
83
|
+
"node": ">=18"
|
|
84
|
+
},
|
|
85
|
+
"tsup": {
|
|
86
|
+
"entry": [
|
|
87
|
+
"src/index.ts"
|
|
88
|
+
],
|
|
89
|
+
"dts": true,
|
|
90
|
+
"format": [
|
|
91
|
+
"cjs",
|
|
92
|
+
"esm"
|
|
93
|
+
],
|
|
94
|
+
"sourcemap": true,
|
|
95
|
+
"clean": true,
|
|
96
|
+
"external": [
|
|
97
|
+
"react",
|
|
98
|
+
"react-dom",
|
|
99
|
+
"three",
|
|
100
|
+
"three-stdlib"
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|