3d-marquee 0.1.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/README.md +80 -0
- package/dist/3d-marquee.es.js +217 -0
- package/dist/3d-marquee.umd.js +93 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# LED Marquee Orb
|
|
2
|
+
|
|
3
|
+
A React + Vite single-page application featuring a 3D rotating orb with a scrolling LED marquee effect. The word scrolls horizontally across the center band of the sphere, creating a seamless ticker display.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **3D Rotating Orb**: Smoothly rotating sphere with orbit controls
|
|
8
|
+
- **LED Marquee Effect**: Text scrolls horizontally across the center band
|
|
9
|
+
- **Shader-Based Rendering**: High-performance GPU-accelerated LED matrix effect
|
|
10
|
+
- **Bloom Post-Processing**: Glowing LED effect with bloom shader
|
|
11
|
+
- **Responsive Design**: Full viewport canvas that adapts to screen size
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
1. Install dependencies:
|
|
16
|
+
```bash
|
|
17
|
+
npm install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Running the Application
|
|
21
|
+
|
|
22
|
+
Start the development server:
|
|
23
|
+
```bash
|
|
24
|
+
npm run dev
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The application will be available at `http://localhost:5173` (or the port shown in the terminal).
|
|
28
|
+
|
|
29
|
+
## Building for Production
|
|
30
|
+
|
|
31
|
+
Build the production bundle:
|
|
32
|
+
```bash
|
|
33
|
+
npm run build
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Preview the production build:
|
|
37
|
+
```bash
|
|
38
|
+
npm run preview
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Component API
|
|
42
|
+
|
|
43
|
+
The `LedMarqueeOrb` component accepts the following props:
|
|
44
|
+
|
|
45
|
+
- `word` (string, required): The text to display on the marquee
|
|
46
|
+
- `speed` (number, optional, default: 0.25): Scroll speed multiplier
|
|
47
|
+
- `radius` (number, optional, default: 1): Sphere radius
|
|
48
|
+
- `bandHeight` (number, optional, default: 0.2): Height of the LED band (0-1)
|
|
49
|
+
- `ledSpacing` (number, optional, default: 0.05): Spacing between LED dots
|
|
50
|
+
|
|
51
|
+
## Example Usage
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<LedMarqueeOrb word="ROADHERO" speed={0.3} radius={1.2} />
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Technologies
|
|
58
|
+
|
|
59
|
+
- **React 18**: UI framework
|
|
60
|
+
- **Vite**: Build tool and dev server
|
|
61
|
+
- **TypeScript**: Type safety
|
|
62
|
+
- **Three.js**: 3D graphics library
|
|
63
|
+
- **@react-three/fiber**: React renderer for Three.js
|
|
64
|
+
- **@react-three/drei**: Useful helpers and abstractions
|
|
65
|
+
- **@react-three/postprocessing**: Post-processing effects (bloom)
|
|
66
|
+
|
|
67
|
+
## Controls
|
|
68
|
+
|
|
69
|
+
- **Mouse Drag**: Rotate the camera around the orb
|
|
70
|
+
- **Scroll**: Zoom in/out (if enabled)
|
|
71
|
+
|
|
72
|
+
## Implementation Details
|
|
73
|
+
|
|
74
|
+
The LED marquee effect is implemented using a custom shader material that:
|
|
75
|
+
1. Samples a canvas texture containing the repeated word
|
|
76
|
+
2. Constrains rendering to a horizontal band around the sphere's equator
|
|
77
|
+
3. Quantizes UV coordinates into a grid for the LED dot matrix effect
|
|
78
|
+
4. Applies scrolling offset for the marquee animation
|
|
79
|
+
5. Uses emissive colors and bloom post-processing for the glow effect
|
|
80
|
+
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { jsx as a, jsxs as J } from "react/jsx-runtime";
|
|
2
|
+
import { useFrame as K, Canvas as N } from "@react-three/fiber";
|
|
3
|
+
import { Environment as Z, OrbitControls as $ } from "@react-three/drei";
|
|
4
|
+
import { EffectComposer as ee, Bloom as te } from "@react-three/postprocessing";
|
|
5
|
+
import { useRef as C, useMemo as h, useEffect as oe } from "react";
|
|
6
|
+
import * as c from "three";
|
|
7
|
+
function re({
|
|
8
|
+
word: u = "ROADHERO",
|
|
9
|
+
speed: S = 0.05,
|
|
10
|
+
radius: g = 1,
|
|
11
|
+
ledSpacing: m = 75e-4,
|
|
12
|
+
columnSpacing: M = 5e-3,
|
|
13
|
+
dimColor: E = "#1a261a",
|
|
14
|
+
brightColor: T = "#00ff4d"
|
|
15
|
+
}) {
|
|
16
|
+
const z = C(null), L = C(0), w = C(null), b = C(0), R = C(0), F = h(() => {
|
|
17
|
+
const e = Math.ceil(Math.PI / m), r = Math.ceil(2 * Math.PI / M);
|
|
18
|
+
return { rows: e, cols: r };
|
|
19
|
+
}, [m, M]), q = (e, r) => new Uint8Array(e * r).fill(0), I = (e, r, f, d) => {
|
|
20
|
+
r.fill(0);
|
|
21
|
+
const v = document.createElement("canvas"), t = v.getContext("2d");
|
|
22
|
+
if (!t) return;
|
|
23
|
+
const s = 2048, x = 256;
|
|
24
|
+
v.width = s, v.height = x, t.fillStyle = "#000000", t.fillRect(0, 0, s, x), t.fillStyle = "#ffffff", t.font = "bold 80px monospace", t.textAlign = "center", t.textBaseline = "middle";
|
|
25
|
+
const P = t.measureText(e).width + 200, X = Math.ceil(s / P) + 2;
|
|
26
|
+
for (let l = -1; l < X; l++) {
|
|
27
|
+
const p = s / 2 + l * P;
|
|
28
|
+
t.fillText(e, p, x / 2);
|
|
29
|
+
}
|
|
30
|
+
const j = t.getImageData(0, 0, s, x).data, G = Math.floor(f * 0.4), k = Math.floor(f * 0.6) - G, B = P;
|
|
31
|
+
for (let l = 0; l < x; l++)
|
|
32
|
+
for (let p = 0; p < s; p++) {
|
|
33
|
+
const H = (l * s + p) * 4;
|
|
34
|
+
if (j[H] > 128) {
|
|
35
|
+
const Y = p % B / B, y = Math.floor(Y * d), _ = 1 - l / x, Q = Math.floor(_ * k), U = G + Q;
|
|
36
|
+
U >= 0 && U < f && y >= 0 && y < d && (r[U * d + y] = 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, O = (e, r, f) => {
|
|
40
|
+
const d = new Uint8Array(r * f);
|
|
41
|
+
for (let t = 0; t < e.length; t++)
|
|
42
|
+
d[t] = e[t] * 255;
|
|
43
|
+
const v = new c.DataTexture(d, f, r, c.RedFormat);
|
|
44
|
+
return v.needsUpdate = !0, v;
|
|
45
|
+
}, { rows: i, cols: n } = F;
|
|
46
|
+
b.current = i, R.current = n;
|
|
47
|
+
const A = h(() => {
|
|
48
|
+
const e = q(i, n);
|
|
49
|
+
return I(u, e, i, n), w.current = e, e;
|
|
50
|
+
}, [u, i, n]), D = h(() => O(A, i, n), [A, i, n]), V = h(() => {
|
|
51
|
+
const e = new c.Color(E);
|
|
52
|
+
return new c.Vector3(e.r, e.g, e.b);
|
|
53
|
+
}, [E]), W = h(() => {
|
|
54
|
+
const e = new c.Color(T);
|
|
55
|
+
return new c.Vector3(e.r, e.g, e.b);
|
|
56
|
+
}, [T]), o = h(() => D ? new c.ShaderMaterial({
|
|
57
|
+
transparent: !0,
|
|
58
|
+
uniforms: {
|
|
59
|
+
uLedMatrix: { value: D },
|
|
60
|
+
uScrollOffset: { value: 0 },
|
|
61
|
+
uLedSpacing: { value: m },
|
|
62
|
+
uColumnSpacing: { value: M },
|
|
63
|
+
uRadius: { value: g },
|
|
64
|
+
uTime: { value: 0 },
|
|
65
|
+
uMatrixRows: { value: i },
|
|
66
|
+
uMatrixCols: { value: n },
|
|
67
|
+
uDimColor: { value: V },
|
|
68
|
+
uBrightColor: { value: W }
|
|
69
|
+
},
|
|
70
|
+
vertexShader: `
|
|
71
|
+
varying vec3 vWorldPosition;
|
|
72
|
+
varying vec2 vUv;
|
|
73
|
+
|
|
74
|
+
void main() {
|
|
75
|
+
vUv = uv;
|
|
76
|
+
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
|
|
77
|
+
vWorldPosition = worldPosition.xyz;
|
|
78
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
79
|
+
}
|
|
80
|
+
`,
|
|
81
|
+
fragmentShader: `
|
|
82
|
+
uniform sampler2D uLedMatrix;
|
|
83
|
+
uniform float uScrollOffset;
|
|
84
|
+
uniform float uLedSpacing;
|
|
85
|
+
uniform float uColumnSpacing;
|
|
86
|
+
uniform float uRadius;
|
|
87
|
+
uniform float uTime;
|
|
88
|
+
uniform float uMatrixRows;
|
|
89
|
+
uniform float uMatrixCols;
|
|
90
|
+
uniform vec3 uDimColor;
|
|
91
|
+
uniform vec3 uBrightColor;
|
|
92
|
+
|
|
93
|
+
varying vec3 vWorldPosition;
|
|
94
|
+
varying vec2 vUv;
|
|
95
|
+
|
|
96
|
+
void main() {
|
|
97
|
+
// Convert world position to spherical coordinates
|
|
98
|
+
vec3 pos = normalize(vWorldPosition);
|
|
99
|
+
float latitude = asin(pos.y); // -PI/2 to PI/2 (south pole to north pole)
|
|
100
|
+
float longitude = atan(pos.z, pos.x); // -PI to PI
|
|
101
|
+
|
|
102
|
+
// Calculate matrix coordinates from latitude/longitude
|
|
103
|
+
// Latitude: -PI/2 to PI/2 -> row 0 to uMatrixRows-1
|
|
104
|
+
float normalizedLat = (latitude + 1.5708) / 3.14159; // 0 to 1
|
|
105
|
+
float row = floor(normalizedLat * uMatrixRows);
|
|
106
|
+
row = clamp(row, 0.0, uMatrixRows - 1.0);
|
|
107
|
+
|
|
108
|
+
// Longitude: -PI to PI -> col 0 to uMatrixCols-1
|
|
109
|
+
// Apply scroll offset for marquee effect
|
|
110
|
+
// Flip longitude direction to fix backwards text
|
|
111
|
+
float normalizedLon = 1.0 - ((longitude + 3.14159) / (2.0 * 3.14159)); // 1 to 0 (flipped)
|
|
112
|
+
float scrolledLon = mod(normalizedLon + uScrollOffset, 1.0);
|
|
113
|
+
float col = floor(scrolledLon * uMatrixCols);
|
|
114
|
+
col = clamp(col, 0.0, uMatrixCols - 1.0);
|
|
115
|
+
|
|
116
|
+
// Sample matrix texture at calculated coordinates
|
|
117
|
+
// Texture coordinates: (col/uMatrixCols, row/uMatrixRows)
|
|
118
|
+
vec2 matrixUv = vec2(col / uMatrixCols, row / uMatrixRows);
|
|
119
|
+
vec4 matrixValue = texture2D(uLedMatrix, matrixUv);
|
|
120
|
+
float ledState = matrixValue.r; // 0.0 (off) or 1.0 (on)
|
|
121
|
+
|
|
122
|
+
// Quantize UVs into LED grid for dot matrix effect
|
|
123
|
+
float columnGridSize = 1.0 / uColumnSpacing;
|
|
124
|
+
float rowGridSize = 1.0 / uLedSpacing;
|
|
125
|
+
vec2 gridUv = vec2(
|
|
126
|
+
floor(vUv.x * columnGridSize) / columnGridSize,
|
|
127
|
+
floor(vUv.y * rowGridSize) / rowGridSize
|
|
128
|
+
);
|
|
129
|
+
vec2 gridCenter = vec2(
|
|
130
|
+
gridUv.x + uColumnSpacing * 0.5,
|
|
131
|
+
gridUv.y + uLedSpacing * 0.5
|
|
132
|
+
);
|
|
133
|
+
vec2 distFromCenter = abs(vUv - gridCenter);
|
|
134
|
+
float maxDist = min(uColumnSpacing, uLedSpacing) * 0.2;
|
|
135
|
+
|
|
136
|
+
// Create circular LED dots
|
|
137
|
+
float dist = length(distFromCenter);
|
|
138
|
+
float ledShape = smoothstep(maxDist, maxDist * 0.7, dist);
|
|
139
|
+
|
|
140
|
+
// Use color uniforms from props
|
|
141
|
+
vec3 dimLedColor = uDimColor;
|
|
142
|
+
vec3 brightLedColor = uBrightColor;
|
|
143
|
+
|
|
144
|
+
// Mix between dim and bright based on matrix state
|
|
145
|
+
vec3 ledColor = mix(dimLedColor, brightLedColor, ledState);
|
|
146
|
+
|
|
147
|
+
// Apply LED shape - this creates the circular bulbs with transparent space between
|
|
148
|
+
vec3 finalColor = mix(
|
|
149
|
+
vec3(0.0, 0.0, 0.0), // Black space between bulbs (will be transparent)
|
|
150
|
+
ledColor, // LED color (dim or bright)
|
|
151
|
+
ledShape
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Add emissive glow for bright message LEDs
|
|
155
|
+
float emissive = ledState * ledShape * 1.5;
|
|
156
|
+
|
|
157
|
+
// Calculate alpha: transparent where there are no LEDs, opaque where LEDs are visible
|
|
158
|
+
// Use ledShape directly as alpha so LEDs are visible and space between is transparent
|
|
159
|
+
float alpha = ledShape;
|
|
160
|
+
|
|
161
|
+
gl_FragColor = vec4(finalColor + vec3(emissive * 0.2), alpha);
|
|
162
|
+
}
|
|
163
|
+
`
|
|
164
|
+
}) : null, [D, m, M, g, i, n, V, W]);
|
|
165
|
+
return K((e, r) => {
|
|
166
|
+
z.current && w.current && (L.current += r * S, L.current >= 1 && (L.current -= 1), o && "uniforms" in o && (o.uniforms.uScrollOffset.value = L.current, o.uniforms.uTime.value = e.clock.elapsedTime));
|
|
167
|
+
}), oe(() => {
|
|
168
|
+
if (w.current && (I(
|
|
169
|
+
u,
|
|
170
|
+
w.current,
|
|
171
|
+
b.current,
|
|
172
|
+
R.current
|
|
173
|
+
), o && "uniforms" in o)) {
|
|
174
|
+
const e = O(
|
|
175
|
+
w.current,
|
|
176
|
+
b.current,
|
|
177
|
+
R.current
|
|
178
|
+
);
|
|
179
|
+
o.uniforms.uLedMatrix.value = e, o.needsUpdate = !0;
|
|
180
|
+
}
|
|
181
|
+
}, [u, o]), o ? /* @__PURE__ */ a("mesh", { ref: z, material: o, children: /* @__PURE__ */ a("sphereGeometry", { args: [g, 64, 64] }) }) : null;
|
|
182
|
+
}
|
|
183
|
+
const ve = ({ word: u, speed: S, dimColor: g, brightColor: m }) => /* @__PURE__ */ J(
|
|
184
|
+
N,
|
|
185
|
+
{
|
|
186
|
+
camera: { position: [0, 0, 5], fov: 50 },
|
|
187
|
+
gl: { antialias: !0, alpha: !0 },
|
|
188
|
+
children: [
|
|
189
|
+
/* @__PURE__ */ a("ambientLight", { intensity: 0.3 }),
|
|
190
|
+
/* @__PURE__ */ a("pointLight", { position: [10, 10, 10], intensity: 0.5 }),
|
|
191
|
+
/* @__PURE__ */ a(Z, { preset: "city" }),
|
|
192
|
+
/* @__PURE__ */ a(
|
|
193
|
+
re,
|
|
194
|
+
{
|
|
195
|
+
word: u,
|
|
196
|
+
speed: S,
|
|
197
|
+
dimColor: g,
|
|
198
|
+
brightColor: m
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
/* @__PURE__ */ a(
|
|
202
|
+
$,
|
|
203
|
+
{
|
|
204
|
+
enablePan: !1,
|
|
205
|
+
minDistance: 3,
|
|
206
|
+
maxDistance: 8,
|
|
207
|
+
minPolarAngle: Math.PI / 3,
|
|
208
|
+
maxPolarAngle: Math.PI - Math.PI / 3
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
/* @__PURE__ */ a(ee, { children: /* @__PURE__ */ a(te, { intensity: 1.5, luminanceThreshold: 0.9, luminanceSmoothing: 0.9 }) })
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
export {
|
|
216
|
+
ve as LedMarqueeOrbContainer
|
|
217
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
(function(r,o){typeof exports=="object"&&typeof module<"u"?o(exports,require("react/jsx-runtime"),require("@react-three/fiber"),require("@react-three/drei"),require("@react-three/postprocessing"),require("react"),require("three")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","@react-three/fiber","@react-three/drei","@react-three/postprocessing","react","three"],o):(r=typeof globalThis<"u"?globalThis:r||self,o(r.ThreeDMarquee={},r.jsxRuntime,r.ReactThreeFiber,r.Drei,r.Postprocessing,r.React,r.THREE))})(this,function(r,o,E,U,z,i,_){"use strict";function k(a){const p=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(a){for(const l in a)if(l!=="default"){const c=Object.getOwnPropertyDescriptor(a,l);Object.defineProperty(p,l,c.get?c:{enumerable:!0,get:()=>a[l]})}}return p.default=a,Object.freeze(p)}const m=k(_);function H({word:a="ROADHERO",speed:p=.05,radius:l=1,ledSpacing:c=.0075,columnSpacing:L=.005,dimColor:O="#1a261a",brightColor:I="#00ff4d"}){const q=i.useRef(null),S=i.useRef(0),C=i.useRef(null),b=i.useRef(0),D=i.useRef(0),Y=i.useMemo(()=>{const e=Math.ceil(Math.PI/c),s=Math.ceil(2*Math.PI/L);return{rows:e,cols:s}},[c,L]),N=(e,s)=>new Uint8Array(e*s).fill(0),j=(e,s,x,h)=>{s.fill(0);const g=document.createElement("canvas"),t=g.getContext("2d");if(!t)return;const v=2048,M=256;g.width=v,g.height=M,t.fillStyle="#000000",t.fillRect(0,0,v,M),t.fillStyle="#ffffff",t.font="bold 80px monospace",t.textAlign="center",t.textBaseline="middle";const P=t.measureText(e).width+200,Q=Math.ceil(v/P)+2;for(let d=-1;d<Q;d++){const w=v/2+d*P;t.fillText(e,w,M/2)}const J=t.getImageData(0,0,v,M).data,G=Math.floor(x*.4),K=Math.floor(x*.6)-G,B=P;for(let d=0;d<M;d++)for(let w=0;w<v;w++){const Z=(d*v+w)*4;if(J[Z]>128){const $=w%B/B,R=Math.floor($*h),ee=1-d/M,te=Math.floor(ee*K),T=G+te;T>=0&&T<x&&R>=0&&R<h&&(s[T*h+R]=1)}}},A=(e,s,x)=>{const h=new Uint8Array(s*x);for(let t=0;t<e.length;t++)h[t]=e[t]*255;const g=new m.DataTexture(h,x,s,m.RedFormat);return g.needsUpdate=!0,g},{rows:u,cols:f}=Y;b.current=u,D.current=f;const V=i.useMemo(()=>{const e=N(u,f);return j(a,e,u,f),C.current=e,e},[a,u,f]),y=i.useMemo(()=>A(V,u,f),[V,u,f]),W=i.useMemo(()=>{const e=new m.Color(O);return new m.Vector3(e.r,e.g,e.b)},[O]),F=i.useMemo(()=>{const e=new m.Color(I);return new m.Vector3(e.r,e.g,e.b)},[I]),n=i.useMemo(()=>y?new m.ShaderMaterial({transparent:!0,uniforms:{uLedMatrix:{value:y},uScrollOffset:{value:0},uLedSpacing:{value:c},uColumnSpacing:{value:L},uRadius:{value:l},uTime:{value:0},uMatrixRows:{value:u},uMatrixCols:{value:f},uDimColor:{value:W},uBrightColor:{value:F}},vertexShader:`
|
|
2
|
+
varying vec3 vWorldPosition;
|
|
3
|
+
varying vec2 vUv;
|
|
4
|
+
|
|
5
|
+
void main() {
|
|
6
|
+
vUv = uv;
|
|
7
|
+
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
|
|
8
|
+
vWorldPosition = worldPosition.xyz;
|
|
9
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
10
|
+
}
|
|
11
|
+
`,fragmentShader:`
|
|
12
|
+
uniform sampler2D uLedMatrix;
|
|
13
|
+
uniform float uScrollOffset;
|
|
14
|
+
uniform float uLedSpacing;
|
|
15
|
+
uniform float uColumnSpacing;
|
|
16
|
+
uniform float uRadius;
|
|
17
|
+
uniform float uTime;
|
|
18
|
+
uniform float uMatrixRows;
|
|
19
|
+
uniform float uMatrixCols;
|
|
20
|
+
uniform vec3 uDimColor;
|
|
21
|
+
uniform vec3 uBrightColor;
|
|
22
|
+
|
|
23
|
+
varying vec3 vWorldPosition;
|
|
24
|
+
varying vec2 vUv;
|
|
25
|
+
|
|
26
|
+
void main() {
|
|
27
|
+
// Convert world position to spherical coordinates
|
|
28
|
+
vec3 pos = normalize(vWorldPosition);
|
|
29
|
+
float latitude = asin(pos.y); // -PI/2 to PI/2 (south pole to north pole)
|
|
30
|
+
float longitude = atan(pos.z, pos.x); // -PI to PI
|
|
31
|
+
|
|
32
|
+
// Calculate matrix coordinates from latitude/longitude
|
|
33
|
+
// Latitude: -PI/2 to PI/2 -> row 0 to uMatrixRows-1
|
|
34
|
+
float normalizedLat = (latitude + 1.5708) / 3.14159; // 0 to 1
|
|
35
|
+
float row = floor(normalizedLat * uMatrixRows);
|
|
36
|
+
row = clamp(row, 0.0, uMatrixRows - 1.0);
|
|
37
|
+
|
|
38
|
+
// Longitude: -PI to PI -> col 0 to uMatrixCols-1
|
|
39
|
+
// Apply scroll offset for marquee effect
|
|
40
|
+
// Flip longitude direction to fix backwards text
|
|
41
|
+
float normalizedLon = 1.0 - ((longitude + 3.14159) / (2.0 * 3.14159)); // 1 to 0 (flipped)
|
|
42
|
+
float scrolledLon = mod(normalizedLon + uScrollOffset, 1.0);
|
|
43
|
+
float col = floor(scrolledLon * uMatrixCols);
|
|
44
|
+
col = clamp(col, 0.0, uMatrixCols - 1.0);
|
|
45
|
+
|
|
46
|
+
// Sample matrix texture at calculated coordinates
|
|
47
|
+
// Texture coordinates: (col/uMatrixCols, row/uMatrixRows)
|
|
48
|
+
vec2 matrixUv = vec2(col / uMatrixCols, row / uMatrixRows);
|
|
49
|
+
vec4 matrixValue = texture2D(uLedMatrix, matrixUv);
|
|
50
|
+
float ledState = matrixValue.r; // 0.0 (off) or 1.0 (on)
|
|
51
|
+
|
|
52
|
+
// Quantize UVs into LED grid for dot matrix effect
|
|
53
|
+
float columnGridSize = 1.0 / uColumnSpacing;
|
|
54
|
+
float rowGridSize = 1.0 / uLedSpacing;
|
|
55
|
+
vec2 gridUv = vec2(
|
|
56
|
+
floor(vUv.x * columnGridSize) / columnGridSize,
|
|
57
|
+
floor(vUv.y * rowGridSize) / rowGridSize
|
|
58
|
+
);
|
|
59
|
+
vec2 gridCenter = vec2(
|
|
60
|
+
gridUv.x + uColumnSpacing * 0.5,
|
|
61
|
+
gridUv.y + uLedSpacing * 0.5
|
|
62
|
+
);
|
|
63
|
+
vec2 distFromCenter = abs(vUv - gridCenter);
|
|
64
|
+
float maxDist = min(uColumnSpacing, uLedSpacing) * 0.2;
|
|
65
|
+
|
|
66
|
+
// Create circular LED dots
|
|
67
|
+
float dist = length(distFromCenter);
|
|
68
|
+
float ledShape = smoothstep(maxDist, maxDist * 0.7, dist);
|
|
69
|
+
|
|
70
|
+
// Use color uniforms from props
|
|
71
|
+
vec3 dimLedColor = uDimColor;
|
|
72
|
+
vec3 brightLedColor = uBrightColor;
|
|
73
|
+
|
|
74
|
+
// Mix between dim and bright based on matrix state
|
|
75
|
+
vec3 ledColor = mix(dimLedColor, brightLedColor, ledState);
|
|
76
|
+
|
|
77
|
+
// Apply LED shape - this creates the circular bulbs with transparent space between
|
|
78
|
+
vec3 finalColor = mix(
|
|
79
|
+
vec3(0.0, 0.0, 0.0), // Black space between bulbs (will be transparent)
|
|
80
|
+
ledColor, // LED color (dim or bright)
|
|
81
|
+
ledShape
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Add emissive glow for bright message LEDs
|
|
85
|
+
float emissive = ledState * ledShape * 1.5;
|
|
86
|
+
|
|
87
|
+
// Calculate alpha: transparent where there are no LEDs, opaque where LEDs are visible
|
|
88
|
+
// Use ledShape directly as alpha so LEDs are visible and space between is transparent
|
|
89
|
+
float alpha = ledShape;
|
|
90
|
+
|
|
91
|
+
gl_FragColor = vec4(finalColor + vec3(emissive * 0.2), alpha);
|
|
92
|
+
}
|
|
93
|
+
`}):null,[y,c,L,l,u,f,W,F]);return E.useFrame((e,s)=>{q.current&&C.current&&(S.current+=s*p,S.current>=1&&(S.current-=1),n&&"uniforms"in n&&(n.uniforms.uScrollOffset.value=S.current,n.uniforms.uTime.value=e.clock.elapsedTime))}),i.useEffect(()=>{if(C.current&&(j(a,C.current,b.current,D.current),n&&"uniforms"in n)){const e=A(C.current,b.current,D.current);n.uniforms.uLedMatrix.value=e,n.needsUpdate=!0}},[a,n]),n?o.jsx("mesh",{ref:q,material:n,children:o.jsx("sphereGeometry",{args:[l,64,64]})}):null}const X=({word:a,speed:p,dimColor:l,brightColor:c})=>o.jsxs(E.Canvas,{camera:{position:[0,0,5],fov:50},gl:{antialias:!0,alpha:!0},children:[o.jsx("ambientLight",{intensity:.3}),o.jsx("pointLight",{position:[10,10,10],intensity:.5}),o.jsx(U.Environment,{preset:"city"}),o.jsx(H,{word:a,speed:p,dimColor:l,brightColor:c}),o.jsx(U.OrbitControls,{enablePan:!1,minDistance:3,maxDistance:8,minPolarAngle:Math.PI/3,maxPolarAngle:Math.PI-Math.PI/3}),o.jsx(z.EffectComposer,{children:o.jsx(z.Bloom,{intensity:1.5,luminanceThreshold:.9,luminanceSmoothing:.9})})]});r.LedMarqueeOrbContainer=X,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})});
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "3d-marquee",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/3d-marquee.umd.js",
|
|
6
|
+
"module": "./dist/3d-marquee.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/3d-marquee.js",
|
|
11
|
+
"require": "./dist/3d-marquee.umd.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"build": "tsc && vite build",
|
|
21
|
+
"build:lib": "tsc -p tsconfig.build.json && vite build --mode library",
|
|
22
|
+
"preview": "vite preview"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@react-three/drei": "^9.92.7",
|
|
26
|
+
"@react-three/fiber": "^8.15.11",
|
|
27
|
+
"@react-three/postprocessing": "^2.15.9",
|
|
28
|
+
"react": "^18.2.0",
|
|
29
|
+
"react-dom": "^18.2.0",
|
|
30
|
+
"three": "^0.158.0"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@react-three/drei": "^9.92.7",
|
|
34
|
+
"@react-three/fiber": "^8.15.11",
|
|
35
|
+
"@react-three/postprocessing": "^2.15.9",
|
|
36
|
+
"react": "^18.2.0",
|
|
37
|
+
"react-dom": "^18.2.0",
|
|
38
|
+
"three": "^0.158.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^25.0.3",
|
|
42
|
+
"@types/react": "^18.2.43",
|
|
43
|
+
"@types/react-dom": "^18.2.17",
|
|
44
|
+
"@types/three": "^0.158.3",
|
|
45
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
46
|
+
"typescript": "^5.2.2",
|
|
47
|
+
"vite": "^5.0.8"
|
|
48
|
+
}
|
|
49
|
+
}
|