@backbay/glia-three 0.2.0-alpha.2
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/package.json +43 -0
- package/src/environment/AuroraLayer/AuroraLayer.stories.tsx +43 -0
- package/src/environment/AuroraLayer/AuroraLayer.tsx +200 -0
- package/src/environment/AuroraLayer/index.ts +2 -0
- package/src/environment/AuroraLayer/types.ts +30 -0
- package/src/environment/EnvironmentLayer/EnvironmentLayer.stories.tsx +262 -0
- package/src/environment/EnvironmentLayer/EnvironmentLayer.tsx +105 -0
- package/src/environment/EnvironmentLayer/index.ts +4 -0
- package/src/environment/EnvironmentLayer/presets.ts +128 -0
- package/src/environment/FogLayer/FogLayer.stories.tsx +83 -0
- package/src/environment/FogLayer/FogLayer.tsx +113 -0
- package/src/environment/FogLayer/index.ts +2 -0
- package/src/environment/FogLayer/types.ts +45 -0
- package/src/environment/VolumetricLight/VolumetricLight.stories.tsx +55 -0
- package/src/environment/VolumetricLight/VolumetricLight.tsx +188 -0
- package/src/environment/VolumetricLight/index.ts +2 -0
- package/src/environment/VolumetricLight/types.ts +33 -0
- package/src/environment/WeatherLayer/WeatherLayer.stories.tsx +348 -0
- package/src/environment/WeatherLayer/WeatherLayer.tsx +266 -0
- package/src/environment/WeatherLayer/cinematicCanvas.tsx +809 -0
- package/src/environment/WeatherLayer/colors.ts +27 -0
- package/src/environment/WeatherLayer/index.ts +4 -0
- package/src/environment/WeatherLayer/leafPresets.ts +12 -0
- package/src/environment/WeatherLayer/particles.ts +227 -0
- package/src/environment/WeatherLayer/types.ts +140 -0
- package/src/environment/index.ts +17 -0
- package/src/environment/shared/index.ts +2 -0
- package/src/environment/shared/noise.ts +10 -0
- package/src/environment/shared/performance.ts +33 -0
- package/src/environment/shared/types.ts +26 -0
- package/src/index.ts +2 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/vision-types.ts +84 -0
- package/src/three/AgentConsole/AgentConsole.stories.tsx +397 -0
- package/src/three/AgentConsole/AgentConsole.tsx +195 -0
- package/src/three/AgentConsole/ConsoleChat.tsx +237 -0
- package/src/three/AgentConsole/FocusConstellation.tsx +297 -0
- package/src/three/AgentConsole/GlyphAvatar.stories.tsx +110 -0
- package/src/three/AgentConsole/GlyphAvatar.tsx +117 -0
- package/src/three/AgentConsole/QuickActions.tsx +111 -0
- package/src/three/AgentConsole/index.ts +41 -0
- package/src/three/AgentConsole/types.ts +241 -0
- package/src/three/AmbientField/AmbientField.stories.tsx +290 -0
- package/src/three/AmbientField/AmbientField.tsx +307 -0
- package/src/three/AmbientField/BackbayFieldBus.ts +326 -0
- package/src/three/AmbientField/FieldProvider.tsx +83 -0
- package/src/three/AmbientField/index.ts +37 -0
- package/src/three/AmbientField/shaders/constellation.ts +384 -0
- package/src/three/AmbientField/types.ts +174 -0
- package/src/three/AttackGraph/AttackGraph.stories.tsx +144 -0
- package/src/three/AttackGraph/AttackGraph.tsx +325 -0
- package/src/three/AttackGraph/index.ts +19 -0
- package/src/three/AttackGraph/types.ts +97 -0
- package/src/three/AuditTrail/AuditTrail.stories.tsx +567 -0
- package/src/three/AuditTrail/AuditTrail.tsx +644 -0
- package/src/three/AuditTrail/index.ts +33 -0
- package/src/three/AuditTrail/types.ts +192 -0
- package/src/three/CrystallineOrganism/Breadcrumb.tsx +61 -0
- package/src/three/CrystallineOrganism/CrystallineOrganism.stories.tsx +509 -0
- package/src/three/CrystallineOrganism/CrystallineOrganism.tsx +273 -0
- package/src/three/CrystallineOrganism/LatticeEdge.tsx +69 -0
- package/src/three/CrystallineOrganism/OrganismLattice.tsx +159 -0
- package/src/three/CrystallineOrganism/OrganismParticles.tsx +148 -0
- package/src/three/CrystallineOrganism/OrganismShell.tsx +277 -0
- package/src/three/CrystallineOrganism/constants.ts +161 -0
- package/src/three/CrystallineOrganism/index.ts +17 -0
- package/src/three/CrystallineOrganism/layouts/hexGrid.ts +85 -0
- package/src/three/CrystallineOrganism/layouts/index.ts +1 -0
- package/src/three/CrystallineOrganism/types.ts +167 -0
- package/src/three/CrystallineOrganism/useOrganismEmotion.ts +84 -0
- package/src/three/FirewallBarrier/FirewallBarrier.stories.tsx +167 -0
- package/src/three/FirewallBarrier/FirewallBarrier.tsx +259 -0
- package/src/three/FirewallBarrier/index.ts +14 -0
- package/src/three/FirewallBarrier/types.ts +52 -0
- package/src/three/Glyph/GlyphObject.stories.tsx +577 -0
- package/src/three/Glyph/GlyphObject.tsx +422 -0
- package/src/three/Glyph/index.ts +10 -0
- package/src/three/Glyph/types.ts +36 -0
- package/src/three/Glyph/useGlyphController.ts +231 -0
- package/src/three/Glyph/useGlyphEmotion.ts +70 -0
- package/src/three/Graph3D/Graph3D.stories.tsx +269 -0
- package/src/three/Graph3D/Graph3D.tsx +248 -0
- package/src/three/Graph3D/GraphEdge.tsx +79 -0
- package/src/three/Graph3D/GraphNode.tsx +239 -0
- package/src/three/Graph3D/types.ts +66 -0
- package/src/three/Graph3D/utils.ts +204 -0
- package/src/three/IntelFeed/IntelFeed.stories.tsx +168 -0
- package/src/three/IntelFeed/IntelFeed.tsx +284 -0
- package/src/three/IntelFeed/index.ts +14 -0
- package/src/three/IntelFeed/types.ts +56 -0
- package/src/three/MetricsGalaxy/MetricsGalaxy.tsx +484 -0
- package/src/three/MetricsGalaxy/index.ts +6 -0
- package/src/three/MetricsGalaxy/types.ts +26 -0
- package/src/three/NetworkTopology/NetworkTopology.stories.tsx +184 -0
- package/src/three/NetworkTopology/NetworkTopology.tsx +421 -0
- package/src/three/NetworkTopology/index.ts +34 -0
- package/src/three/NetworkTopology/types.ts +128 -0
- package/src/three/ParticleField/ParticleField.stories.tsx +162 -0
- package/src/three/ParticleField/ParticleField.tsx +81 -0
- package/src/three/ParticleField/index.ts +1 -0
- package/src/three/PermissionMatrix/PermissionMatrix.stories.tsx +475 -0
- package/src/three/PermissionMatrix/PermissionMatrix.tsx +380 -0
- package/src/three/PermissionMatrix/index.ts +15 -0
- package/src/three/PermissionMatrix/types.ts +54 -0
- package/src/three/QuantumField/ConstellationField.tsx +238 -0
- package/src/three/QuantumField/FieldBus.ts +349 -0
- package/src/three/QuantumField/FieldLayer.tsx +430 -0
- package/src/three/QuantumField/FieldProvider.tsx +460 -0
- package/src/three/QuantumField/PcbField.tsx +406 -0
- package/src/three/QuantumField/QuantumField.stories.tsx +1155 -0
- package/src/three/QuantumField/TrailRTT.ts +212 -0
- package/src/three/QuantumField/WaterField.tsx +226 -0
- package/src/three/QuantumField/WaterSimRTT.ts +283 -0
- package/src/three/QuantumField/domMapping.ts +185 -0
- package/src/three/QuantumField/index.ts +110 -0
- package/src/three/QuantumField/styles/index.ts +9 -0
- package/src/three/QuantumField/styles/styleA.ts +526 -0
- package/src/three/QuantumField/styles/styleB.ts +1210 -0
- package/src/three/QuantumField/styles/styleC.ts +266 -0
- package/src/three/QuantumField/themes.ts +211 -0
- package/src/three/QuantumField/types.ts +380 -0
- package/src/three/SOCCommandCenter/SOCCommandCenter.stories.tsx +591 -0
- package/src/three/SOCCommandCenter/SOCCommandCenter.tsx +248 -0
- package/src/three/SOCCommandCenter/index.ts +26 -0
- package/src/three/SOCCommandCenter/types.ts +201 -0
- package/src/three/SecurityDashboard/SecurityDashboard.stories.tsx +508 -0
- package/src/three/SecurityDashboard/SecurityDashboard.tsx +507 -0
- package/src/three/SecurityDashboard/index.ts +37 -0
- package/src/three/SecurityDashboard/types.ts +143 -0
- package/src/three/SecurityShield/SecurityShield.stories.tsx +257 -0
- package/src/three/SecurityShield/SecurityShield.tsx +502 -0
- package/src/three/SecurityShield/index.ts +25 -0
- package/src/three/SecurityShield/types.ts +64 -0
- package/src/three/Sentinel/AvatarMode.tsx +578 -0
- package/src/three/Sentinel/AvatarRenderer.tsx +199 -0
- package/src/three/Sentinel/CameraPip.tsx +127 -0
- package/src/three/Sentinel/CardinalItem.tsx +83 -0
- package/src/three/Sentinel/CardinalMenu.tsx +370 -0
- package/src/three/Sentinel/DockedMiniOrb.tsx +146 -0
- package/src/three/Sentinel/RadialSubmenu.tsx +273 -0
- package/src/three/Sentinel/SentinelConversation.tsx +802 -0
- package/src/three/Sentinel/SentinelOrb.tsx +316 -0
- package/src/three/Sentinel/SentinelOverlay.tsx +146 -0
- package/src/three/Sentinel/SentinelProvider.tsx +145 -0
- package/src/three/Sentinel/SentinelTether.tsx +182 -0
- package/src/three/Sentinel/SigilPlaceholder.tsx +176 -0
- package/src/three/Sentinel/VerticalSubmenu.tsx +150 -0
- package/src/three/Sentinel/index.ts +145 -0
- package/src/three/Sentinel/sentinelStore.ts +196 -0
- package/src/three/Sentinel/types.ts +403 -0
- package/src/three/Sentinel/useCameraPermission.ts +153 -0
- package/src/three/Sentinel/useThrowPhysics.ts +220 -0
- package/src/three/SpatialWorkspace/CyntraWorkspace.tsx +84 -0
- package/src/three/SpatialWorkspace/JobCluster.tsx +281 -0
- package/src/three/SpatialWorkspace/NodeGraph.tsx +236 -0
- package/src/three/SpatialWorkspace/ReceiptOrbit.tsx +368 -0
- package/src/three/SpatialWorkspace/SpatialWorkspace.stories.tsx +547 -0
- package/src/three/SpatialWorkspace/SpatialWorkspace.tsx +428 -0
- package/src/three/SpatialWorkspace/TrustRings.tsx +228 -0
- package/src/three/SpatialWorkspace/adapters.ts +353 -0
- package/src/three/SpatialWorkspace/index.ts +85 -0
- package/src/three/SpatialWorkspace/nexusAdapter.ts +182 -0
- package/src/three/SpatialWorkspace/types.ts +389 -0
- package/src/three/ThreatRadar/ThreatRadar.stories.tsx +451 -0
- package/src/three/ThreatRadar/ThreatRadar.tsx +542 -0
- package/src/three/ThreatRadar/index.ts +8 -0
- package/src/three/ThreatRadar/types.ts +90 -0
- package/src/three/ThreeErrorBoundary/ThreeErrorBoundary.tsx +235 -0
- package/src/three/ThreeErrorBoundary/index.ts +5 -0
- package/src/three/index.ts +56 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +21 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Quantum Field Canvas - Field Layer Component
|
|
5
|
+
*
|
|
6
|
+
* The main R3F canvas component that renders the reactive field substrate.
|
|
7
|
+
* Supports multiple visual styles: constellation (A), pcb (B), water (C).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Canvas, useFrame, useThree } from "@react-three/fiber";
|
|
11
|
+
import React, { Suspense, useCallback, useEffect, useMemo, useRef } from "react";
|
|
12
|
+
import * as THREE from "three";
|
|
13
|
+
import { ConstellationField } from "./ConstellationField";
|
|
14
|
+
import type { FieldBus } from "./FieldBus";
|
|
15
|
+
import { createFieldBus } from "./FieldBus";
|
|
16
|
+
import { clientToNdc } from "./domMapping";
|
|
17
|
+
import { FieldProvider, useFieldBus } from "./FieldProvider";
|
|
18
|
+
import { PcbField } from "./PcbField";
|
|
19
|
+
import type { FieldConfig, FieldRuntimeState } from "./types";
|
|
20
|
+
import { DEFAULT_FIELD_CONFIG } from "./types";
|
|
21
|
+
import { WaterField } from "./WaterField";
|
|
22
|
+
|
|
23
|
+
// -----------------------------------------------------------------------------
|
|
24
|
+
// Debug Shader Material
|
|
25
|
+
// -----------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
const DEBUG_VERTEX_SHADER = /* glsl */ `
|
|
28
|
+
varying vec2 vUv;
|
|
29
|
+
|
|
30
|
+
void main() {
|
|
31
|
+
vUv = uv;
|
|
32
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const DEBUG_FRAGMENT_SHADER = /* glsl */ `
|
|
37
|
+
uniform float uTime;
|
|
38
|
+
uniform vec4 uHover; // xy = uv, z = intent (0=probe, 1=etch), w = active
|
|
39
|
+
uniform vec4 uImpulses[24]; // xy = uv, z = radius, w = amp
|
|
40
|
+
uniform float uImpulsesAge[24]; // normalized age (0-1, 1 = expired)
|
|
41
|
+
uniform int uImpulseCount;
|
|
42
|
+
uniform vec4 uAnchors[8]; // xy = uv, z = strength, w = phase
|
|
43
|
+
uniform int uAnchorCount;
|
|
44
|
+
|
|
45
|
+
varying vec2 vUv;
|
|
46
|
+
|
|
47
|
+
// Subtle grid pattern
|
|
48
|
+
float grid(vec2 uv, float spacing) {
|
|
49
|
+
vec2 g = abs(fract(uv * spacing - 0.5) - 0.5) / fwidth(uv * spacing);
|
|
50
|
+
return 1.0 - min(min(g.x, g.y), 1.0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Smooth circle
|
|
54
|
+
float circle(vec2 uv, vec2 center, float radius, float softness) {
|
|
55
|
+
float d = length(uv - center);
|
|
56
|
+
return 1.0 - smoothstep(radius - softness, radius + softness, d);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Ring shape
|
|
60
|
+
float ring(vec2 uv, vec2 center, float radius, float thickness, float softness) {
|
|
61
|
+
float d = length(uv - center);
|
|
62
|
+
float inner = smoothstep(radius - thickness - softness, radius - thickness, d);
|
|
63
|
+
float outer = 1.0 - smoothstep(radius, radius + softness, d);
|
|
64
|
+
return inner * outer;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
void main() {
|
|
68
|
+
vec2 uv = vUv;
|
|
69
|
+
|
|
70
|
+
// Base: very subtle dark with faint grid
|
|
71
|
+
vec3 baseColor = vec3(0.02, 0.03, 0.05);
|
|
72
|
+
float gridVal = grid(uv, 20.0) * 0.03;
|
|
73
|
+
vec3 color = baseColor + vec3(gridVal);
|
|
74
|
+
|
|
75
|
+
// Hover probe effect
|
|
76
|
+
if (uHover.w > 0.5) {
|
|
77
|
+
vec2 hoverUv = uHover.xy;
|
|
78
|
+
float probeRadius = 0.08;
|
|
79
|
+
float intensity = uHover.z > 0.5 ? 0.4 : 0.25; // etch vs probe
|
|
80
|
+
|
|
81
|
+
float probe = circle(uv, hoverUv, probeRadius, 0.02);
|
|
82
|
+
vec3 probeColor = uHover.z > 0.5
|
|
83
|
+
? vec3(0.9, 0.2, 0.5) // magenta for etch
|
|
84
|
+
: vec3(0.1, 0.8, 0.9); // cyan for probe
|
|
85
|
+
|
|
86
|
+
color += probeColor * probe * intensity;
|
|
87
|
+
|
|
88
|
+
// Add subtle crackle near probe
|
|
89
|
+
float crackle = fract(sin(dot(uv * 100.0 + uTime * 5.0, vec2(12.9898, 78.233))) * 43758.5453);
|
|
90
|
+
float crackleZone = circle(uv, hoverUv, probeRadius * 1.5, 0.05);
|
|
91
|
+
color += vec3(0.1, 0.6, 0.7) * crackle * crackleZone * 0.1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Impulse rings (bursts)
|
|
95
|
+
for (int i = 0; i < 24; i++) {
|
|
96
|
+
if (i >= uImpulseCount) break;
|
|
97
|
+
|
|
98
|
+
vec4 imp = uImpulses[i];
|
|
99
|
+
float age = uImpulsesAge[i];
|
|
100
|
+
|
|
101
|
+
if (age >= 1.0) continue;
|
|
102
|
+
|
|
103
|
+
vec2 impUv = imp.xy;
|
|
104
|
+
float baseRadius = imp.z;
|
|
105
|
+
float amp = imp.w;
|
|
106
|
+
|
|
107
|
+
// Expanding ring
|
|
108
|
+
float expandedRadius = baseRadius + age * 0.3;
|
|
109
|
+
float thickness = 0.01 + (1.0 - age) * 0.02;
|
|
110
|
+
float ringVal = ring(uv, impUv, expandedRadius, thickness, 0.005);
|
|
111
|
+
|
|
112
|
+
// Fade out with age
|
|
113
|
+
float fade = (1.0 - age) * amp;
|
|
114
|
+
|
|
115
|
+
// Interference pattern
|
|
116
|
+
float interference = sin((length(uv - impUv) - uTime * 0.5) * 60.0) * 0.5 + 0.5;
|
|
117
|
+
|
|
118
|
+
vec3 ringColor = mix(
|
|
119
|
+
vec3(0.2, 0.9, 1.0), // cyan
|
|
120
|
+
vec3(1.0, 0.3, 0.6), // magenta
|
|
121
|
+
interference
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
color += ringColor * ringVal * fade;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Anchors (persistent glowing nodes)
|
|
128
|
+
for (int i = 0; i < 8; i++) {
|
|
129
|
+
if (i >= uAnchorCount) break;
|
|
130
|
+
|
|
131
|
+
vec4 anchor = uAnchors[i];
|
|
132
|
+
vec2 anchorUv = anchor.xy;
|
|
133
|
+
float strength = anchor.z;
|
|
134
|
+
float phase = anchor.w;
|
|
135
|
+
|
|
136
|
+
// Pulsing glow
|
|
137
|
+
float pulse = 0.7 + 0.3 * sin(uTime * 3.0 + phase);
|
|
138
|
+
float anchorGlow = circle(uv, anchorUv, 0.03, 0.02) * strength * pulse;
|
|
139
|
+
|
|
140
|
+
// Tether-like radial streak
|
|
141
|
+
vec2 toAnchor = anchorUv - uv;
|
|
142
|
+
float dist = length(toAnchor);
|
|
143
|
+
float angle = atan(toAnchor.y, toAnchor.x);
|
|
144
|
+
float streak = smoothstep(0.1, 0.0, dist) * (0.5 + 0.5 * sin(angle * 8.0 + uTime * 2.0));
|
|
145
|
+
|
|
146
|
+
color += vec3(0.3, 0.9, 0.4) * anchorGlow; // emerald
|
|
147
|
+
color += vec3(0.2, 0.6, 0.3) * streak * 0.1 * strength;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
gl_FragColor = vec4(color, 1.0);
|
|
151
|
+
}
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
// -----------------------------------------------------------------------------
|
|
155
|
+
// Field Plane Component (inside Canvas)
|
|
156
|
+
// -----------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
interface FieldPlaneProps {
|
|
159
|
+
bus: FieldBus;
|
|
160
|
+
config: FieldConfig;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Reusable raycast objects
|
|
164
|
+
const _raycaster = new THREE.Raycaster();
|
|
165
|
+
const _ndcVec = new THREE.Vector2();
|
|
166
|
+
const _intersections: THREE.Intersection[] = [];
|
|
167
|
+
|
|
168
|
+
function FieldPlane({ bus, config }: FieldPlaneProps) {
|
|
169
|
+
const { camera, size, gl } = useThree();
|
|
170
|
+
const meshRef = useRef<THREE.Mesh>(null);
|
|
171
|
+
|
|
172
|
+
// Local refs for state (avoid re-renders)
|
|
173
|
+
const stateRef = useRef<FieldRuntimeState>(bus.getSnapshot());
|
|
174
|
+
|
|
175
|
+
// Subscribe to bus updates
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
const unsub = bus.subscribe((state) => {
|
|
178
|
+
stateRef.current = state;
|
|
179
|
+
});
|
|
180
|
+
return unsub;
|
|
181
|
+
}, [bus]);
|
|
182
|
+
|
|
183
|
+
// UV computation via raycast
|
|
184
|
+
const computeUv = useCallback(
|
|
185
|
+
(clientX: number, clientY: number): { x: number; y: number } | null => {
|
|
186
|
+
if (!meshRef.current) return null;
|
|
187
|
+
|
|
188
|
+
// Get canvas bounds
|
|
189
|
+
const canvas = gl.domElement;
|
|
190
|
+
const rect = canvas.getBoundingClientRect();
|
|
191
|
+
|
|
192
|
+
// Convert client coords to NDC relative to canvas
|
|
193
|
+
const ndc = clientToNdc(clientX - rect.left, clientY - rect.top, {
|
|
194
|
+
width: rect.width,
|
|
195
|
+
height: rect.height,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
_ndcVec.set(ndc.x, ndc.y);
|
|
199
|
+
_raycaster.setFromCamera(_ndcVec, camera);
|
|
200
|
+
|
|
201
|
+
_intersections.length = 0;
|
|
202
|
+
meshRef.current.raycast(_raycaster, _intersections);
|
|
203
|
+
|
|
204
|
+
if (_intersections.length > 0 && _intersections[0].uv) {
|
|
205
|
+
return {
|
|
206
|
+
x: _intersections[0].uv.x,
|
|
207
|
+
y: _intersections[0].uv.y,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return null;
|
|
212
|
+
},
|
|
213
|
+
[camera, gl.domElement]
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// Create uniforms
|
|
217
|
+
const uniforms = useMemo(
|
|
218
|
+
() => ({
|
|
219
|
+
uTime: { value: 0 },
|
|
220
|
+
uHover: { value: new THREE.Vector4(0.5, 0.5, 0, 0) },
|
|
221
|
+
uImpulses: {
|
|
222
|
+
value: Array(24)
|
|
223
|
+
.fill(null)
|
|
224
|
+
.map(() => new THREE.Vector4(0, 0, 0, 0)),
|
|
225
|
+
},
|
|
226
|
+
uImpulsesAge: { value: new Float32Array(24) },
|
|
227
|
+
uImpulseCount: { value: 0 },
|
|
228
|
+
uAnchors: {
|
|
229
|
+
value: Array(8)
|
|
230
|
+
.fill(null)
|
|
231
|
+
.map(() => new THREE.Vector4(0, 0, 0, 0)),
|
|
232
|
+
},
|
|
233
|
+
uAnchorCount: { value: 0 },
|
|
234
|
+
}),
|
|
235
|
+
[]
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Create shader material
|
|
239
|
+
const material = useMemo(() => {
|
|
240
|
+
return new THREE.ShaderMaterial({
|
|
241
|
+
vertexShader: DEBUG_VERTEX_SHADER,
|
|
242
|
+
fragmentShader: DEBUG_FRAGMENT_SHADER,
|
|
243
|
+
uniforms,
|
|
244
|
+
transparent: true,
|
|
245
|
+
});
|
|
246
|
+
}, [uniforms]);
|
|
247
|
+
|
|
248
|
+
// Calculate plane size based on camera
|
|
249
|
+
const planeSize = useMemo(() => {
|
|
250
|
+
if (camera instanceof THREE.PerspectiveCamera) {
|
|
251
|
+
const distance = camera.position.z;
|
|
252
|
+
const vFov = (camera.fov * Math.PI) / 180;
|
|
253
|
+
const height = 2 * Math.tan(vFov / 2) * distance;
|
|
254
|
+
const width = height * (size.width / size.height);
|
|
255
|
+
return { width: width * 1.2, height: height * 1.2 };
|
|
256
|
+
}
|
|
257
|
+
return { width: 20, height: 15 };
|
|
258
|
+
}, [camera, size]);
|
|
259
|
+
|
|
260
|
+
// Animation frame
|
|
261
|
+
useFrame((state, delta) => {
|
|
262
|
+
if (!meshRef.current) return;
|
|
263
|
+
|
|
264
|
+
const currentTs = Date.now();
|
|
265
|
+
const currentState = stateRef.current;
|
|
266
|
+
|
|
267
|
+
// Process pending events with raycast UV computation
|
|
268
|
+
bus.processPendingWithUv(computeUv);
|
|
269
|
+
|
|
270
|
+
// Tick the bus
|
|
271
|
+
bus.tick(delta * 1000, currentTs);
|
|
272
|
+
|
|
273
|
+
// Update time
|
|
274
|
+
material.uniforms.uTime.value = state.clock.elapsedTime;
|
|
275
|
+
|
|
276
|
+
// Update hover uniform
|
|
277
|
+
if (currentState.hover.active) {
|
|
278
|
+
// Convert last known client position to UV via raycast
|
|
279
|
+
const hover = currentState.hover;
|
|
280
|
+
material.uniforms.uHover.value.set(
|
|
281
|
+
hover.uv.x,
|
|
282
|
+
hover.uv.y,
|
|
283
|
+
hover.intent === "etch" ? 1 : 0,
|
|
284
|
+
1
|
|
285
|
+
);
|
|
286
|
+
} else {
|
|
287
|
+
material.uniforms.uHover.value.w = 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Update impulses
|
|
291
|
+
const impulses = currentState.impulses;
|
|
292
|
+
material.uniforms.uImpulseCount.value = impulses.length;
|
|
293
|
+
|
|
294
|
+
for (let i = 0; i < 24; i++) {
|
|
295
|
+
if (i < impulses.length) {
|
|
296
|
+
const imp = impulses[i];
|
|
297
|
+
const age = Math.min(1, (currentTs - imp.startTs) / config.impulseDecayMs);
|
|
298
|
+
material.uniforms.uImpulses.value[i].set(imp.uv.x, imp.uv.y, imp.radius, imp.amplitude);
|
|
299
|
+
material.uniforms.uImpulsesAge.value[i] = age;
|
|
300
|
+
} else {
|
|
301
|
+
material.uniforms.uImpulsesAge.value[i] = 1;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Update anchors
|
|
306
|
+
const anchors = Array.from(currentState.anchors.values());
|
|
307
|
+
material.uniforms.uAnchorCount.value = anchors.length;
|
|
308
|
+
|
|
309
|
+
for (let i = 0; i < 8; i++) {
|
|
310
|
+
if (i < anchors.length) {
|
|
311
|
+
const anchor = anchors[i];
|
|
312
|
+
const phase = (currentTs - anchor.createdTs) * 0.001;
|
|
313
|
+
material.uniforms.uAnchors.value[i].set(anchor.uv.x, anchor.uv.y, anchor.strength, phase);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Dispose material on unmount
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
return () => {
|
|
321
|
+
material.dispose();
|
|
322
|
+
};
|
|
323
|
+
}, [material]);
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<mesh ref={meshRef} position={[0, 0, 0]} material={material}>
|
|
327
|
+
<planeGeometry args={[planeSize.width, planeSize.height]} />
|
|
328
|
+
</mesh>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// -----------------------------------------------------------------------------
|
|
333
|
+
// Main Field Layer Component
|
|
334
|
+
// -----------------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
export interface FieldLayerProps {
|
|
337
|
+
/** Custom configuration overrides */
|
|
338
|
+
config?: Partial<FieldConfig>;
|
|
339
|
+
/** Custom class for the wrapper */
|
|
340
|
+
className?: string;
|
|
341
|
+
/** Whether to pin to viewport (fixed) or fill parent (absolute) */
|
|
342
|
+
pinToViewport?: boolean;
|
|
343
|
+
/** Z-index for the canvas wrapper */
|
|
344
|
+
zIndex?: number;
|
|
345
|
+
/** Camera FOV */
|
|
346
|
+
fov?: number;
|
|
347
|
+
/** Camera Z position */
|
|
348
|
+
cameraZ?: number;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function FieldLayer({
|
|
352
|
+
config: configOverrides,
|
|
353
|
+
className,
|
|
354
|
+
pinToViewport = true,
|
|
355
|
+
zIndex = -1,
|
|
356
|
+
fov = 50,
|
|
357
|
+
cameraZ = 10,
|
|
358
|
+
}: FieldLayerProps) {
|
|
359
|
+
const bus = useFieldBus();
|
|
360
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
361
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
362
|
+
|
|
363
|
+
// Merge config
|
|
364
|
+
const config = useMemo(
|
|
365
|
+
() => ({ ...DEFAULT_FIELD_CONFIG, ...configOverrides }),
|
|
366
|
+
[configOverrides]
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// Update bus config when it changes
|
|
370
|
+
useEffect(() => {
|
|
371
|
+
bus.setConfig(config);
|
|
372
|
+
}, [bus, config]);
|
|
373
|
+
|
|
374
|
+
const wrapperStyle: React.CSSProperties = {
|
|
375
|
+
position: pinToViewport ? "fixed" : "absolute",
|
|
376
|
+
inset: 0,
|
|
377
|
+
zIndex,
|
|
378
|
+
pointerEvents: "none",
|
|
379
|
+
overflow: "hidden",
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
<div ref={wrapperRef} className={className} style={wrapperStyle} data-field-layer>
|
|
384
|
+
<Canvas
|
|
385
|
+
ref={canvasRef}
|
|
386
|
+
camera={{ position: [0, 0, cameraZ], fov }}
|
|
387
|
+
dpr={config.dpr}
|
|
388
|
+
gl={{ antialias: true, alpha: true }}
|
|
389
|
+
style={{ background: "transparent" }}
|
|
390
|
+
>
|
|
391
|
+
<Suspense fallback={null}>
|
|
392
|
+
{config.style === "constellation" ? (
|
|
393
|
+
<ConstellationField bus={bus} config={config} />
|
|
394
|
+
) : config.style === "pcb" ? (
|
|
395
|
+
<PcbField bus={bus} config={config} />
|
|
396
|
+
) : config.style === "water" ? (
|
|
397
|
+
<WaterField bus={bus} config={config} />
|
|
398
|
+
) : (
|
|
399
|
+
// Fallback to debug plane
|
|
400
|
+
<FieldPlane bus={bus} config={config} />
|
|
401
|
+
)}
|
|
402
|
+
</Suspense>
|
|
403
|
+
</Canvas>
|
|
404
|
+
</div>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// -----------------------------------------------------------------------------
|
|
409
|
+
// Standalone Field Layer (includes its own provider)
|
|
410
|
+
// -----------------------------------------------------------------------------
|
|
411
|
+
|
|
412
|
+
export interface StandaloneFieldLayerProps extends FieldLayerProps {
|
|
413
|
+
/** Initial bus configuration */
|
|
414
|
+
busConfig?: Partial<FieldConfig>;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function StandaloneFieldLayer({ busConfig, ...props }: StandaloneFieldLayerProps) {
|
|
418
|
+
const busRef = useRef<FieldBus | null>(null);
|
|
419
|
+
|
|
420
|
+
// Create bus once
|
|
421
|
+
if (!busRef.current) {
|
|
422
|
+
busRef.current = createFieldBus(busConfig);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return (
|
|
426
|
+
<FieldProvider bus={busRef.current}>
|
|
427
|
+
<FieldLayer {...props} />
|
|
428
|
+
</FieldProvider>
|
|
429
|
+
);
|
|
430
|
+
}
|