@@ -18,15 +18,18 @@ interface PulseMarker {
1818 core: THREE .Mesh ;
1919 aura: THREE .Mesh ;
2020 phase: number ;
21- speed: number ;
2221}
2322
2423interface CircuitLine {
25- line: THREE .Line ;
26- material: THREE .LineDashedMaterial ;
24+ group: THREE .Group ;
25+ segments: THREE .Mesh [];
26+ material: THREE .MeshBasicMaterial ;
27+ baseColor: THREE .Color ;
2728 sampler: PathSampler ;
2829 pulses: PulseMarker [];
29- dashSpeed: number ;
30+ speedFactor: number ;
31+ depthOffset: number ;
32+ baseThickness: number ;
3033}
3134
3235let scene: THREE .Scene | null = null ;
@@ -41,9 +44,18 @@ const circuits: CircuitLine[] = [];
4144const pointerParallax = new THREE .Vector2 (0 , 0 );
4245const pointerTarget = new THREE .Vector2 (0 , 0 );
4346const clock = new THREE .Clock ();
44- const FLOW_SPEED_START = 1.45 ;
47+ const FLOW_SPEED_START = 11.5 ;
4548const FLOW_ACCEL_PER_SECOND = 0.018 ;
46- const FLOW_SPEED_MAX = 2.35 ;
49+ const FLOW_SPEED_MAX = 15.5 ;
50+ const FLOW_DEPTH_SPAN = 320 ;
51+ const FLOW_NEAR_LIMIT = 48 ;
52+ const PULSE_SPEED_BASE = 0.032 ;
53+ const PULSE_SPEED_FLOW_GAIN = 0.0022 ;
54+
55+ const SEGMENT_AXIS = new THREE .Vector3 (1 , 0 , 0 );
56+ const cameraToGroup = new THREE .Vector3 ();
57+ const colorNear = new THREE .Color (0xc8ffff );
58+ let flowTravel = 0 ;
4759
4860function rand(min : number , max : number ) {
4961 return min + Math .random () * (max - min );
@@ -142,6 +154,19 @@ function samplePath(sampler: PathSampler, t: number) {
142154 return sampler .points [sampler .points .length - 1 ].clone ();
143155}
144156
157+ function createWireSegment(start : THREE .Vector3 , end : THREE .Vector3 , material : THREE .MeshBasicMaterial , baseThickness : number ) {
158+ const direction = end .clone ().sub (start );
159+ const length = direction .length ();
160+ if (length <= 0.001 ) return null ;
161+
162+ const geometry = new THREE .BoxGeometry (length , 1 , 1 );
163+ const mesh = new THREE .Mesh (geometry , material );
164+ mesh .position .copy (start ).add (end ).multiplyScalar (0.5 );
165+ mesh .quaternion .setFromUnitVectors (SEGMENT_AXIS , direction .normalize ());
166+ mesh .scale .set (1 , baseThickness , baseThickness );
167+ return mesh ;
168+ }
169+
145170function addCenterHole() {
146171 if (! scene ) return ;
147172
@@ -179,26 +204,31 @@ function addCenterHole() {
179204function createCircuitFlow() {
180205 if (! scene ) return ;
181206
182- const wireColor = new THREE .Color (0x45c8ff );
183-
184- for (let i = 0 ; i < 40 ; i += 1 ) {
207+ for (let i = 0 ; i < 42 ; i += 1 ) {
185208 const pathPoints = createOrthogonalPath ();
186- const geometry = new THREE .BufferGeometry ().setFromPoints (pathPoints );
187- const material = new THREE .LineDashedMaterial ({
188- color: wireColor ,
209+ const sampler = createSampler (pathPoints );
210+ const group = new THREE .Group ();
211+ const hue = rand (0.51 , 0.57 );
212+ const baseColor = new THREE .Color ().setHSL (hue , rand (0.78 , 0.94 ), rand (0.5 , 0.62 ));
213+ const material = new THREE .MeshBasicMaterial ({
214+ color: baseColor ,
189215 transparent: true ,
190- opacity: rand ( 0.22 , 0.5 ) ,
191- dashSize: rand ( 1.4 , 3.8 ) ,
192- gapSize: rand ( 0.8 , 2.3 ) ,
216+ opacity: 0.34 ,
217+ depthWrite: false ,
218+ blending: THREE . AdditiveBlending ,
193219 });
194220
195- const line = new THREE .Line (geometry , material );
196- line .computeLineDistances ();
197- scene .add (line );
221+ const baseThickness = rand (0.2 , 0.36 );
222+ const segments: THREE .Mesh [] = [];
223+ for (let j = 1 ; j < pathPoints .length ; j += 1 ) {
224+ const segment = createWireSegment (pathPoints [j - 1 ], pathPoints [j ], material , baseThickness );
225+ if (! segment ) continue ;
226+ group .add (segment );
227+ segments .push (segment );
228+ }
198229
199- const sampler = createSampler (pathPoints );
200230 const pulses: PulseMarker [] = [];
201- const pulseCount = 3 ;
231+ const pulseCount = 2 + Math . floor ( Math . random () * 2 ) ;
202232
203233 for (let j = 0 ; j < pulseCount ; j += 1 ) {
204234 const core = new THREE .Mesh (
@@ -213,7 +243,7 @@ function createCircuitFlow() {
213243 );
214244
215245 const aura = new THREE .Mesh (
216- new THREE .SphereGeometry (rand (0.55 , 0.92 ), 14 , 14 ),
246+ new THREE .SphereGeometry (rand (0.55 , 1.0 ), 14 , 14 ),
217247 new THREE .MeshBasicMaterial ({
218248 color: 0x34b6ff ,
219249 transparent: true ,
@@ -223,22 +253,29 @@ function createCircuitFlow() {
223253 }),
224254 );
225255
226- scene .add (core );
227- scene .add (aura );
256+ group .add (core );
257+ group .add (aura );
228258 pulses .push ({
229259 core ,
230260 aura ,
231261 phase: j / pulseCount + Math .random () * 0.12 ,
232- speed: rand (0.07 , 0.17 ),
233262 });
234263 }
235264
265+ const depthOffset = rand (0 , FLOW_DEPTH_SPAN );
266+ group .position .z = depthOffset - FLOW_DEPTH_SPAN + FLOW_NEAR_LIMIT ;
267+ scene .add (group );
268+
236269 circuits .push ({
237- line ,
270+ group ,
271+ segments ,
238272 material ,
273+ baseColor ,
239274 sampler ,
240275 pulses ,
241- dashSpeed: rand (0.22 , 0.65 ),
276+ speedFactor: rand (0.94 , 1.08 ),
277+ depthOffset ,
278+ baseThickness ,
242279 });
243280 }
244281
@@ -277,19 +314,39 @@ function animate() {
277314
278315 const delta = clock .getDelta ();
279316 const elapsed = clock .elapsedTime ;
280- const flowMultiplier = Math .min (FLOW_SPEED_MAX , FLOW_SPEED_START + elapsed * FLOW_ACCEL_PER_SECOND );
317+ const flowSpeed = Math .min (FLOW_SPEED_MAX , FLOW_SPEED_START + elapsed * FLOW_ACCEL_PER_SECOND );
318+ flowTravel += delta * flowSpeed ;
281319
282320 pointerParallax .lerp (pointerTarget , 0.035 );
283321 camera .position .x = pointerParallax .x * 2.4 ;
284322 camera .position .y = pointerParallax .y * 1.8 ;
285- camera .position .z = 72 + Math . sin ( elapsed * 0.25 ) * 1.8 ;
323+ camera .position .z = 72 ;
286324 camera .lookAt (0 , 0 , 0 );
287325
288326 if (BACKGROUND_MODE === ' circuit' ) {
289327 for (const circuit of circuits ) {
290- circuit .material .dashOffset -= delta * circuit .dashSpeed * flowMultiplier ;
328+ const wrappedDepth = (circuit .depthOffset + flowTravel * circuit .speedFactor ) % FLOW_DEPTH_SPAN ;
329+ circuit .group .position .z = wrappedDepth - FLOW_DEPTH_SPAN + FLOW_NEAR_LIMIT ;
330+ circuit .group .updateMatrixWorld ();
331+ cameraToGroup .setFromMatrixPosition (circuit .group .matrixWorld );
332+ const distanceToCamera = camera .position .distanceTo (cameraToGroup );
333+ const proximity = THREE .MathUtils .clamp (1 - distanceToCamera / 250 , 0 , 1 );
334+ const thickness = circuit .baseThickness * (0.62 + proximity * 2.15 );
335+
336+ for (const segment of circuit .segments ) {
337+ segment .scale .y = thickness ;
338+ segment .scale .z = thickness ;
339+ }
340+ circuit .material .opacity = 0.15 + proximity * 0.7 ;
341+ circuit .material .color .copy (circuit .baseColor ).lerp (colorNear , proximity * 0.75 );
342+
343+ const linePulseSpeed = (PULSE_SPEED_BASE + flowSpeed * PULSE_SPEED_FLOW_GAIN ) * circuit .speedFactor ;
291344 for (const pulse of circuit .pulses ) {
292- const progress = (elapsed * pulse .speed * flowMultiplier + pulse .phase ) % 1 ;
345+ let progress = (elapsed * linePulseSpeed + pulse .phase ) % 1 ;
346+ if (proximity > 0.58 ) {
347+ const jumpStep = THREE .MathUtils .lerp (0.028 , 0.11 , (proximity - 0.58 ) / 0.42 );
348+ progress = Math .round (progress / jumpStep ) * jumpStep ;
349+ }
293350 const flowT = Math .pow (progress , 0.82 );
294351 const position = samplePath (circuit .sampler , flowT );
295352 pulse .core .position .copy (position );
@@ -299,10 +356,10 @@ function animate() {
299356 const pulseWave = 0.88 + Math .sin ((elapsed + pulse .phase ) * 9 ) * 0.12 ;
300357 const coreMaterial = pulse .core .material as THREE .MeshBasicMaterial ;
301358 const auraMaterial = pulse .aura .material as THREE .MeshBasicMaterial ;
302- coreMaterial .opacity = 0.12 + distanceFade * 0.9 ;
303- auraMaterial .opacity = 0.08 + distanceFade * 0.28 ;
304- pulse .core .scale .setScalar (pulseWave );
305- pulse .aura .scale .setScalar (0.9 + pulseWave * 0.28 );
359+ coreMaterial .opacity = 0.08 + proximity * 0.42 + distanceFade * 0.46 ;
360+ auraMaterial .opacity = 0.04 + proximity * 0.19 + distanceFade * 0.16 ;
361+ pulse .core .scale .setScalar (( 0.76 + proximity * 0.36 ) * pulseWave );
362+ pulse .aura .scale .setScalar (( 0.84 + proximity * 0.38 ) + pulseWave * 0.22 );
306363 }
307364 }
308365
@@ -343,6 +400,7 @@ function handleResize() {
343400function initScene() {
344401 if (! canvasRef .value ) return ;
345402
403+ flowTravel = 0 ;
346404 scene = new THREE .Scene ();
347405 scene .fog = new THREE .FogExp2 (0x05070b , 0.01 );
348406
@@ -381,7 +439,9 @@ function disposeScene() {
381439 window .removeEventListener (' mousemove' , handleMouseMove );
382440
383441 for (const circuit of circuits ) {
384- circuit .line .geometry .dispose ();
442+ for (const segment of circuit .segments ) {
443+ segment .geometry .dispose ();
444+ }
385445 circuit .material .dispose ();
386446 for (const pulse of circuit .pulses ) {
387447 pulse .core .geometry .dispose ();
0 commit comments