@zen-ui-capri/system 0.0.0-stage → 1.1.0
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/.agents/skills/capri-zen-ui/SKILL.md +59 -0
- package/AGENTS.md +57 -0
- package/CLAUDE.md +7 -0
- package/LICENSE +21 -0
- package/README.md +93 -2
- package/bin/capri.mjs +1187 -0
- package/dist/lib/audio/ZenAudioController.d.ts +10 -0
- package/dist/lib/audio/ZenAudioController.d.ts.map +1 -0
- package/dist/lib/audio/useZenAudioSynth.d.ts +26 -0
- package/dist/lib/audio/useZenAudioSynth.d.ts.map +1 -0
- package/dist/lib/capri/capri-engine.d.ts +127 -0
- package/dist/lib/capri/capri-engine.d.ts.map +1 -0
- package/dist/lib/components/ZenAIConstruct.d.ts +24 -0
- package/dist/lib/components/ZenAIConstruct.d.ts.map +1 -0
- package/dist/lib/components/ZenAmbientBackground.d.ts +8 -0
- package/dist/lib/components/ZenAmbientBackground.d.ts.map +1 -0
- package/dist/lib/components/ZenBadge.d.ts +21 -0
- package/dist/lib/components/ZenBadge.d.ts.map +1 -0
- package/dist/lib/components/ZenButton.d.ts +24 -0
- package/dist/lib/components/ZenButton.d.ts.map +1 -0
- package/dist/lib/components/ZenCard.d.ts +36 -0
- package/dist/lib/components/ZenCard.d.ts.map +1 -0
- package/dist/lib/components/ZenDataDisplay.d.ts +50 -0
- package/dist/lib/components/ZenDataDisplay.d.ts.map +1 -0
- package/dist/lib/components/ZenDataTable.d.ts +20 -0
- package/dist/lib/components/ZenDataTable.d.ts.map +1 -0
- package/dist/lib/components/ZenDossierPanel.d.ts +33 -0
- package/dist/lib/components/ZenDossierPanel.d.ts.map +1 -0
- package/dist/lib/components/ZenDropdown.d.ts +19 -0
- package/dist/lib/components/ZenDropdown.d.ts.map +1 -0
- package/dist/lib/components/ZenFeedback.d.ts +24 -0
- package/dist/lib/components/ZenFeedback.d.ts.map +1 -0
- package/dist/lib/components/ZenForms.d.ts +84 -0
- package/dist/lib/components/ZenForms.d.ts.map +1 -0
- package/dist/lib/components/ZenHeading.d.ts +15 -0
- package/dist/lib/components/ZenHeading.d.ts.map +1 -0
- package/dist/lib/components/ZenIcon.d.ts +24 -0
- package/dist/lib/components/ZenIcon.d.ts.map +1 -0
- package/dist/lib/components/ZenInput.d.ts +12 -0
- package/dist/lib/components/ZenInput.d.ts.map +1 -0
- package/dist/lib/components/ZenModal.d.ts +12 -0
- package/dist/lib/components/ZenModal.d.ts.map +1 -0
- package/dist/lib/components/ZenNavbar.d.ts +11 -0
- package/dist/lib/components/ZenNavbar.d.ts.map +1 -0
- package/dist/lib/components/ZenNavigation.d.ts +52 -0
- package/dist/lib/components/ZenNavigation.d.ts.map +1 -0
- package/dist/lib/components/ZenOverlays.d.ts +48 -0
- package/dist/lib/components/ZenOverlays.d.ts.map +1 -0
- package/dist/lib/components/ZenSegmentedControl.d.ts +18 -0
- package/dist/lib/components/ZenSegmentedControl.d.ts.map +1 -0
- package/dist/lib/components/ZenSwitch.d.ts +11 -0
- package/dist/lib/components/ZenSwitch.d.ts.map +1 -0
- package/dist/lib/components/ZenTabs.d.ts +17 -0
- package/dist/lib/components/ZenTabs.d.ts.map +1 -0
- package/dist/lib/components/ZenTimelineCard.d.ts +14 -0
- package/dist/lib/components/ZenTimelineCard.d.ts.map +1 -0
- package/dist/lib/components/ZenToast.d.ts +18 -0
- package/dist/lib/components/ZenToast.d.ts.map +1 -0
- package/dist/lib/cosmos/ZenCosmosCanvas.d.ts +10 -0
- package/dist/lib/cosmos/ZenCosmosCanvas.d.ts.map +1 -0
- package/dist/lib/cosmos/ZenTelemetryHud.d.ts +45 -0
- package/dist/lib/cosmos/ZenTelemetryHud.d.ts.map +1 -0
- package/dist/lib/index.d.ts +33 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +3922 -0
- package/dist/lib/theme/ZenThemeProvider.d.ts +47 -0
- package/dist/lib/theme/ZenThemeProvider.d.ts.map +1 -0
- package/dist/lib/tokens.css +888 -0
- package/dist/lib/universal/zen-universal.d.ts +77 -0
- package/dist/lib/universal/zen-universal.d.ts.map +1 -0
- package/dist/lib/universal/zen-universal.js +18 -0
- package/dist/lib/zen-universal-C0PD8AI9.js +1352 -0
- package/package.json +78 -4
- package/src/lib/audio/ZenAudioController.tsx +114 -0
- package/src/lib/audio/useZenAudioSynth.ts +289 -0
- package/src/lib/capri/capri-engine.ts +939 -0
- package/src/lib/components/ZenAIConstruct.tsx +594 -0
- package/src/lib/components/ZenAmbientBackground.tsx +210 -0
- package/src/lib/components/ZenBadge.tsx +111 -0
- package/src/lib/components/ZenButton.tsx +127 -0
- package/src/lib/components/ZenCard.tsx +180 -0
- package/src/lib/components/ZenDataDisplay.tsx +349 -0
- package/src/lib/components/ZenDataTable.tsx +143 -0
- package/src/lib/components/ZenDossierPanel.tsx +120 -0
- package/src/lib/components/ZenDropdown.tsx +208 -0
- package/src/lib/components/ZenFeedback.tsx +137 -0
- package/src/lib/components/ZenForms.tsx +664 -0
- package/src/lib/components/ZenHeading.tsx +63 -0
- package/src/lib/components/ZenIcon.tsx +220 -0
- package/src/lib/components/ZenInput.tsx +120 -0
- package/src/lib/components/ZenModal.tsx +103 -0
- package/src/lib/components/ZenNavbar.tsx +106 -0
- package/src/lib/components/ZenNavigation.tsx +300 -0
- package/src/lib/components/ZenOverlays.tsx +386 -0
- package/src/lib/components/ZenSegmentedControl.tsx +113 -0
- package/src/lib/components/ZenSwitch.tsx +63 -0
- package/src/lib/components/ZenTabs.tsx +103 -0
- package/src/lib/components/ZenTimelineCard.tsx +64 -0
- package/src/lib/components/ZenToast.tsx +96 -0
- package/src/lib/cosmos/ZenCosmosCanvas.tsx +931 -0
- package/src/lib/cosmos/ZenTelemetryHud.tsx +385 -0
- package/src/lib/index.ts +293 -0
- package/src/lib/styles/zen-tokens.css +888 -0
- package/src/lib/theme/ZenThemeProvider.tsx +328 -0
- package/src/lib/universal/zen-universal.ts +743 -0
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
import { useRef, useMemo, useEffect, Suspense } from 'react';
|
|
2
|
+
import { Canvas, useFrame } from '@react-three/fiber';
|
|
3
|
+
import { EffectComposer, Bloom } from '@react-three/postprocessing';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_CELESTIAL_CATALOG,
|
|
7
|
+
ZenTelemetryHud,
|
|
8
|
+
type CelestialCatalogEntry,
|
|
9
|
+
type ZenTelemetryBridge,
|
|
10
|
+
type ZenTelemetryTargetId,
|
|
11
|
+
} from './ZenTelemetryHud';
|
|
12
|
+
import { useZenTheme } from '../theme/ZenThemeProvider';
|
|
13
|
+
|
|
14
|
+
export interface ZenCosmosCanvasProps {
|
|
15
|
+
readonly scrollProgressRef?: React.RefObject<number>;
|
|
16
|
+
readonly lockedTarget?: ZenTelemetryTargetId;
|
|
17
|
+
readonly paused?: boolean;
|
|
18
|
+
readonly timeScale?: number;
|
|
19
|
+
readonly catalog?: readonly CelestialCatalogEntry[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface CelestialBodyState {
|
|
23
|
+
readonly pos: THREE.Vector3;
|
|
24
|
+
readonly vel: THREE.Vector3;
|
|
25
|
+
readonly acc: THREE.Vector3;
|
|
26
|
+
readonly mass: number;
|
|
27
|
+
readonly luminosity: number;
|
|
28
|
+
readonly radius: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const STARFIELD_VERTEX_SHADER = `
|
|
32
|
+
attribute float aSize;
|
|
33
|
+
attribute vec3 aColor;
|
|
34
|
+
varying vec3 vColor;
|
|
35
|
+
|
|
36
|
+
void main() {
|
|
37
|
+
vColor = aColor;
|
|
38
|
+
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
39
|
+
gl_PointSize = clamp(aSize * (18.0 / -mvPosition.z), 1.2, 4.2);
|
|
40
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const STARFIELD_FRAGMENT_SHADER = `
|
|
45
|
+
varying vec3 vColor;
|
|
46
|
+
|
|
47
|
+
void main() {
|
|
48
|
+
vec2 uv = gl_PointCoord - vec2(0.5);
|
|
49
|
+
float r = length(uv) * 2.0;
|
|
50
|
+
if (r > 1.0) discard;
|
|
51
|
+
float airy = exp(-r * r * 5.2);
|
|
52
|
+
gl_FragColor = vec4(vColor * (1.0 + airy * 0.45), airy * 0.92);
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
const NOISE_GLSL_CHUNK = `
|
|
57
|
+
float hash31(vec3 p) {
|
|
58
|
+
p = fract(p * vec3(443.897, 441.423, 437.195));
|
|
59
|
+
p += dot(p, p.yzx + 19.19);
|
|
60
|
+
return fract((p.x + p.y) * p.z);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
float noise3D(vec3 p) {
|
|
64
|
+
vec3 i = floor(p);
|
|
65
|
+
vec3 f = fract(p);
|
|
66
|
+
f = f * f * (3.0 - 2.0 * f);
|
|
67
|
+
return mix(
|
|
68
|
+
mix(
|
|
69
|
+
mix(hash31(i + vec3(0,0,0)), hash31(i + vec3(1,0,0)), f.x),
|
|
70
|
+
mix(hash31(i + vec3(0,1,0)), hash31(i + vec3(1,1,0)), f.x),
|
|
71
|
+
f.y
|
|
72
|
+
),
|
|
73
|
+
mix(
|
|
74
|
+
mix(hash31(i + vec3(0,0,1)), hash31(i + vec3(1,0,1)), f.x),
|
|
75
|
+
mix(hash31(i + vec3(0,1,1)), hash31(i + vec3(1,1,1)), f.x),
|
|
76
|
+
f.y
|
|
77
|
+
),
|
|
78
|
+
f.z
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
float fbm6(vec3 p) {
|
|
83
|
+
float v = 0.0;
|
|
84
|
+
float amp = 0.5;
|
|
85
|
+
mat3 rot = mat3(
|
|
86
|
+
0.00, 0.80, 0.60,
|
|
87
|
+
-0.80, 0.36, -0.48,
|
|
88
|
+
-0.60, -0.48, 0.64
|
|
89
|
+
);
|
|
90
|
+
for (int i = 0; i < 6; i++) {
|
|
91
|
+
v += amp * noise3D(p);
|
|
92
|
+
p = rot * p * 2.07 + vec3(1.7, 9.2, 3.4);
|
|
93
|
+
amp *= 0.48;
|
|
94
|
+
}
|
|
95
|
+
return v;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
float ridgedFbm(vec3 p) {
|
|
99
|
+
float v = 0.0;
|
|
100
|
+
float amp = 0.55;
|
|
101
|
+
float weight = 1.0;
|
|
102
|
+
for (int i = 0; i < 5; i++) {
|
|
103
|
+
float n = 1.0 - abs(noise3D(p) * 2.0 - 1.0);
|
|
104
|
+
n = n * n * weight;
|
|
105
|
+
weight = clamp(n * 1.8, 0.0, 1.0);
|
|
106
|
+
v += n * amp;
|
|
107
|
+
p *= 2.15;
|
|
108
|
+
amp *= 0.48;
|
|
109
|
+
}
|
|
110
|
+
return v;
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
|
|
114
|
+
const SOLAR_PHOTOSPHERE_VERTEX = `
|
|
115
|
+
uniform float uTime;
|
|
116
|
+
varying vec3 vWorldNormal;
|
|
117
|
+
varying vec3 vWorldPosition;
|
|
118
|
+
varying vec3 vLocalPos;
|
|
119
|
+
|
|
120
|
+
void main() {
|
|
121
|
+
vLocalPos = normalize(position);
|
|
122
|
+
vWorldNormal = normalize(mat3(modelMatrix) * normal);
|
|
123
|
+
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
|
|
124
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
const SOLAR_PHOTOSPHERE_FRAGMENT = `
|
|
129
|
+
uniform float uTime;
|
|
130
|
+
uniform vec3 uCoreColor;
|
|
131
|
+
uniform vec3 uEdgeColor;
|
|
132
|
+
uniform vec3 uSpotColor;
|
|
133
|
+
uniform float uGlowGain;
|
|
134
|
+
uniform float uGranuleScale;
|
|
135
|
+
varying vec3 vWorldNormal;
|
|
136
|
+
varying vec3 vWorldPosition;
|
|
137
|
+
varying vec3 vLocalPos;
|
|
138
|
+
|
|
139
|
+
${NOISE_GLSL_CHUNK}
|
|
140
|
+
|
|
141
|
+
void main() {
|
|
142
|
+
vec3 viewDir = normalize(cameraPosition - vWorldPosition);
|
|
143
|
+
vec3 n = normalize(vWorldNormal);
|
|
144
|
+
float mu = clamp(dot(n, viewDir), 0.0, 1.0);
|
|
145
|
+
|
|
146
|
+
vec3 flowPos = vLocalPos * uGranuleScale + vec3(uTime * 0.14, -uTime * 0.11, uTime * 0.09);
|
|
147
|
+
float gran = fbm6(flowPos);
|
|
148
|
+
float filaments = ridgedFbm(vLocalPos * (uGranuleScale * 0.75) - vec3(uTime * 0.18));
|
|
149
|
+
|
|
150
|
+
float spotField = fbm6(vLocalPos * 3.2 + vec3(0.0, uTime * 0.04, 0.0));
|
|
151
|
+
float sunspotMask = smoothstep(0.64, 0.73, spotField);
|
|
152
|
+
|
|
153
|
+
float limbDarkening = pow(mu, 0.52);
|
|
154
|
+
float spiculeRim = pow(1.0 - mu, 3.2) * (0.6 + 0.8 * filaments);
|
|
155
|
+
|
|
156
|
+
vec3 photosphere = mix(uEdgeColor, uCoreColor, clamp(gran * 1.15 + filaments * 0.35, 0.0, 1.0));
|
|
157
|
+
photosphere = mix(photosphere, uSpotColor, sunspotMask * 0.88);
|
|
158
|
+
photosphere *= (0.38 + 0.72 * limbDarkening);
|
|
159
|
+
photosphere += uCoreColor * filaments * 0.42 * (1.0 - sunspotMask);
|
|
160
|
+
photosphere += mix(uEdgeColor, vec3(1.0, 0.95, 0.82), 0.4) * spiculeRim * 1.35;
|
|
161
|
+
|
|
162
|
+
gl_FragColor = vec4(photosphere * uGlowGain, 1.0);
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
165
|
+
|
|
166
|
+
const CORONA_HALO_VERTEX = `
|
|
167
|
+
varying vec3 vWorldNormal;
|
|
168
|
+
varying vec3 vWorldPosition;
|
|
169
|
+
varying vec3 vLocalPos;
|
|
170
|
+
|
|
171
|
+
void main() {
|
|
172
|
+
vLocalPos = normalize(position);
|
|
173
|
+
vWorldNormal = normalize(mat3(modelMatrix) * normal);
|
|
174
|
+
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
|
|
175
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
|
|
179
|
+
const CORONA_HALO_FRAGMENT = `
|
|
180
|
+
uniform float uTime;
|
|
181
|
+
uniform vec3 uCoronaColor;
|
|
182
|
+
varying vec3 vWorldNormal;
|
|
183
|
+
varying vec3 vWorldPosition;
|
|
184
|
+
varying vec3 vLocalPos;
|
|
185
|
+
|
|
186
|
+
${NOISE_GLSL_CHUNK}
|
|
187
|
+
|
|
188
|
+
void main() {
|
|
189
|
+
vec3 viewDir = normalize(cameraPosition - vWorldPosition);
|
|
190
|
+
vec3 n = normalize(vWorldNormal);
|
|
191
|
+
float ndv = abs(dot(n, viewDir));
|
|
192
|
+
float radialFade = pow(clamp(ndv, 0.0, 1.0), 3.2);
|
|
193
|
+
float streamer = ridgedFbm(vLocalPos * 6.0 + vec3(uTime * 0.35, -uTime * 0.28, uTime * 0.22));
|
|
194
|
+
float alpha = radialFade * (0.25 + streamer * 0.75);
|
|
195
|
+
gl_FragColor = vec4(uCoronaColor * (1.1 + streamer * 0.9), clamp(alpha * 0.55, 0.0, 0.75));
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
const TRISOLARIS_VERTEX_SHADER = `
|
|
200
|
+
varying vec3 vWorldNormal;
|
|
201
|
+
varying vec3 vWorldPosition;
|
|
202
|
+
varying vec3 vLocalPos;
|
|
203
|
+
|
|
204
|
+
void main() {
|
|
205
|
+
vLocalPos = normalize(position);
|
|
206
|
+
vWorldNormal = normalize(mat3(modelMatrix) * normal);
|
|
207
|
+
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
|
|
208
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
209
|
+
}
|
|
210
|
+
`;
|
|
211
|
+
|
|
212
|
+
const TRISOLARIS_FRAGMENT_SHADER = `
|
|
213
|
+
uniform float uTime;
|
|
214
|
+
uniform vec3 uSun0Pos;
|
|
215
|
+
uniform vec3 uSun1Pos;
|
|
216
|
+
uniform vec3 uSun2Pos;
|
|
217
|
+
uniform float uTidalHeat;
|
|
218
|
+
uniform float uBolometricFlux;
|
|
219
|
+
varying vec3 vWorldNormal;
|
|
220
|
+
varying vec3 vWorldPosition;
|
|
221
|
+
varying vec3 vLocalPos;
|
|
222
|
+
|
|
223
|
+
${NOISE_GLSL_CHUNK}
|
|
224
|
+
|
|
225
|
+
void main() {
|
|
226
|
+
vec3 p = vLocalPos * 3.8;
|
|
227
|
+
float continent = fbm6(p);
|
|
228
|
+
float mountains = ridgedFbm(p * 1.85 + vec3(2.4, -1.1, 0.7));
|
|
229
|
+
float microCrater = ridgedFbm(p * 6.5);
|
|
230
|
+
float heightField = continent * 0.55 + mountains * 0.35 + microCrater * 0.10;
|
|
231
|
+
|
|
232
|
+
vec3 dPdx = dFdx(vWorldPosition);
|
|
233
|
+
vec3 dPdy = dFdy(vWorldPosition);
|
|
234
|
+
float dHdx = dFdx(heightField);
|
|
235
|
+
float dHdy = dFdy(heightField);
|
|
236
|
+
vec3 baseNorm = normalize(vWorldNormal);
|
|
237
|
+
vec3 surfGrad = (dHdx * cross(baseNorm, dPdy) + dHdy * cross(dPdx, baseNorm));
|
|
238
|
+
vec3 n = normalize(baseNorm - surfGrad * 2.8);
|
|
239
|
+
|
|
240
|
+
float riftNoise = abs(fbm6(vLocalPos * 4.6 + vec3(1.2, 3.4, -2.1)) - 0.48);
|
|
241
|
+
float riftMask = smoothstep(0.026, 0.003, riftNoise) * smoothstep(0.32, 0.58, mountains);
|
|
242
|
+
|
|
243
|
+
vec3 cloudCoord = vec3(
|
|
244
|
+
vLocalPos.x * cos(uTime * 0.08) - vLocalPos.z * sin(uTime * 0.08),
|
|
245
|
+
vLocalPos.y * 1.6,
|
|
246
|
+
vLocalPos.x * sin(uTime * 0.08) + vLocalPos.z * cos(uTime * 0.08)
|
|
247
|
+
);
|
|
248
|
+
float cloudSwirl = fbm6(cloudCoord * 3.4 + vec3(fbm6(cloudCoord * 2.0)));
|
|
249
|
+
float cloudMask = smoothstep(0.52, 0.74, cloudSwirl) * clamp(1.2 - uBolometricFlux * 0.35, 0.15, 0.9);
|
|
250
|
+
|
|
251
|
+
vec3 deepBasaltOcean = vec3(0.04, 0.08, 0.15);
|
|
252
|
+
vec3 lowlandSilicate = vec3(0.22, 0.19, 0.16);
|
|
253
|
+
vec3 highlandGranite = vec3(0.42, 0.37, 0.33);
|
|
254
|
+
vec3 alpinePeak = vec3(0.72, 0.75, 0.80);
|
|
255
|
+
|
|
256
|
+
vec3 baseTerrain = deepBasaltOcean;
|
|
257
|
+
if (heightField > 0.42) {
|
|
258
|
+
baseTerrain = mix(lowlandSilicate, highlandGranite, smoothstep(0.42, 0.64, heightField));
|
|
259
|
+
baseTerrain = mix(baseTerrain, alpinePeak, smoothstep(0.64, 0.82, heightField));
|
|
260
|
+
} else {
|
|
261
|
+
baseTerrain = mix(deepBasaltOcean, vec3(0.08, 0.15, 0.24), smoothstep(0.28, 0.42, heightField));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
float freezeFactor = clamp(1.0 - uBolometricFlux * 0.65, 0.0, 0.88);
|
|
265
|
+
vec3 cryoIce = mix(vec3(0.68, 0.78, 0.90), vec3(0.90, 0.95, 1.0), mountains);
|
|
266
|
+
vec3 surfaceAlbedo = mix(baseTerrain, cryoIce, freezeFactor * 0.78);
|
|
267
|
+
surfaceAlbedo = mix(surfaceAlbedo, vec3(0.92, 0.94, 0.98), cloudMask * 0.65);
|
|
268
|
+
|
|
269
|
+
vec3 v0 = uSun0Pos - vWorldPosition;
|
|
270
|
+
vec3 v1 = uSun1Pos - vWorldPosition;
|
|
271
|
+
vec3 v2 = uSun2Pos - vWorldPosition;
|
|
272
|
+
vec3 l0 = normalize(v0);
|
|
273
|
+
vec3 l1 = normalize(v1);
|
|
274
|
+
vec3 l2 = normalize(v2);
|
|
275
|
+
|
|
276
|
+
float d0Sq = max(0.18, dot(v0, v0));
|
|
277
|
+
float d1Sq = max(0.18, dot(v1, v1));
|
|
278
|
+
float d2Sq = max(0.18, dot(v2, v2));
|
|
279
|
+
|
|
280
|
+
float ndl0 = max(dot(n, l0), 0.0);
|
|
281
|
+
float ndl1 = max(dot(n, l1), 0.0);
|
|
282
|
+
float ndl2 = max(dot(n, l2), 0.0);
|
|
283
|
+
|
|
284
|
+
vec3 colG2V = vec3(1.0, 0.93, 0.80);
|
|
285
|
+
vec3 colK1V = vec3(1.0, 0.68, 0.28);
|
|
286
|
+
vec3 colMDwarf = vec3(0.96, 0.32, 0.12);
|
|
287
|
+
|
|
288
|
+
float irr0 = (1.52 / d0Sq) * ndl0;
|
|
289
|
+
float irr1 = (0.65 / d1Sq) * ndl1;
|
|
290
|
+
float irr2 = (0.18 / d2Sq) * ndl2;
|
|
291
|
+
float localIrradiance = irr0 + irr1 + irr2;
|
|
292
|
+
|
|
293
|
+
vec3 viewDir = normalize(cameraPosition - vWorldPosition);
|
|
294
|
+
float oceanMask = (1.0 - smoothstep(0.39, 0.44, heightField)) * (1.0 - freezeFactor) * (1.0 - cloudMask);
|
|
295
|
+
float spec0 = pow(max(dot(reflect(-l0, n), viewDir), 0.0), 28.0) * oceanMask * (1.52 / d0Sq);
|
|
296
|
+
float spec1 = pow(max(dot(reflect(-l1, n), viewDir), 0.0), 28.0) * oceanMask * (0.65 / d1Sq);
|
|
297
|
+
|
|
298
|
+
vec3 litPlanet = surfaceAlbedo * (
|
|
299
|
+
vec3(0.03, 0.04, 0.06)
|
|
300
|
+
+ colG2V * irr0 * 0.95
|
|
301
|
+
+ colK1V * irr1 * 0.95
|
|
302
|
+
+ colMDwarf * irr2 * 0.95
|
|
303
|
+
) + colG2V * spec0 * 0.45 + colK1V * spec1 * 0.45;
|
|
304
|
+
|
|
305
|
+
float subStellarMelt = smoothstep(1.4, 3.5, localIrradiance) * ridgedFbm(p * 3.5);
|
|
306
|
+
float lavaIntensity = clamp(riftMask * (uTidalHeat * 2.2 + localIrradiance * 0.65) + subStellarMelt * 0.85, 0.0, 2.5);
|
|
307
|
+
vec3 magmaColor = mix(vec3(0.95, 0.18, 0.02), vec3(1.0, 0.85, 0.28), clamp(lavaIntensity * 0.55, 0.0, 1.0));
|
|
308
|
+
|
|
309
|
+
float rim = pow(1.0 - max(dot(viewDir, baseNorm), 0.0), 3.0);
|
|
310
|
+
vec3 atmoColor = mix(vec3(0.32, 0.60, 0.96), vec3(1.0, 0.48, 0.15), clamp(uBolometricFlux * 0.45, 0.0, 1.0));
|
|
311
|
+
|
|
312
|
+
vec3 finalCol = litPlanet + magmaColor * lavaIntensity + atmoColor * rim * 0.48;
|
|
313
|
+
gl_FragColor = vec4(finalCol, 1.0);
|
|
314
|
+
}
|
|
315
|
+
`;
|
|
316
|
+
|
|
317
|
+
function seedThreeBodyEpoch(
|
|
318
|
+
suns: readonly CelestialBodyState[],
|
|
319
|
+
trisolaris: CelestialBodyState
|
|
320
|
+
): void {
|
|
321
|
+
const G = 1.5;
|
|
322
|
+
const m0 = suns[0].mass;
|
|
323
|
+
const m1 = suns[1].mass;
|
|
324
|
+
const m2 = suns[2].mass;
|
|
325
|
+
const m01 = m0 + m1;
|
|
326
|
+
const mTot = m01 + m2;
|
|
327
|
+
|
|
328
|
+
const aIn = 0.96;
|
|
329
|
+
const aOut = 1.76;
|
|
330
|
+
const vRelIn = Math.sqrt((G * m01) / (aIn + 0.02));
|
|
331
|
+
const vRelOut = -Math.sqrt((G * mTot) / (aOut + 0.05));
|
|
332
|
+
|
|
333
|
+
suns[0].pos.set((-m1 / m01) * aIn, (-m2 / mTot) * -aOut, 0);
|
|
334
|
+
suns[0].vel.set((-m2 / mTot) * vRelOut, (-m1 / m01) * vRelIn, 0.08);
|
|
335
|
+
suns[0].acc.set(0, 0, 0);
|
|
336
|
+
|
|
337
|
+
suns[1].pos.set((m0 / m01) * aIn, (-m2 / mTot) * -aOut, 0);
|
|
338
|
+
suns[1].vel.set((-m2 / mTot) * vRelOut, (m0 / m01) * vRelIn, -0.08);
|
|
339
|
+
suns[1].acc.set(0, 0, 0);
|
|
340
|
+
|
|
341
|
+
suns[2].pos.set(0, (m01 / mTot) * -aOut, 0);
|
|
342
|
+
suns[2].vel.set((m01 / mTot) * vRelOut, 0, 0);
|
|
343
|
+
suns[2].acc.set(0, 0, 0);
|
|
344
|
+
|
|
345
|
+
trisolaris.pos.set(-1.2, -0.6, 0.08);
|
|
346
|
+
trisolaris.vel.set(-1.5, 0.3, 0.05);
|
|
347
|
+
trisolaris.acc.set(0, 0, 0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
interface CosmosSceneInternalProps {
|
|
351
|
+
readonly scrollProgressRef?: React.RefObject<number>;
|
|
352
|
+
readonly pointerNdcRef: React.RefObject<THREE.Vector2>;
|
|
353
|
+
readonly bridgeRef: React.RefObject<ZenTelemetryBridge | null>;
|
|
354
|
+
readonly reducedMotion: boolean;
|
|
355
|
+
readonly paused: boolean;
|
|
356
|
+
readonly timeScale: number;
|
|
357
|
+
readonly catalog: readonly CelestialCatalogEntry[];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function CosmosSceneInternal({
|
|
361
|
+
scrollProgressRef,
|
|
362
|
+
pointerNdcRef,
|
|
363
|
+
bridgeRef,
|
|
364
|
+
reducedMotion,
|
|
365
|
+
paused,
|
|
366
|
+
timeScale,
|
|
367
|
+
catalog,
|
|
368
|
+
}: CosmosSceneInternalProps) {
|
|
369
|
+
const systemGroupRef = useRef<THREE.Group>(null);
|
|
370
|
+
const sun0Ref = useRef<THREE.Group>(null);
|
|
371
|
+
const sun1Ref = useRef<THREE.Group>(null);
|
|
372
|
+
const sun2Ref = useRef<THREE.Group>(null);
|
|
373
|
+
const planetGroupRef = useRef<THREE.Group>(null);
|
|
374
|
+
const starfieldRef = useRef<THREE.Points>(null);
|
|
375
|
+
const lastScrollRef = useRef<number>(0);
|
|
376
|
+
const scrollVelRef = useRef<number>(0);
|
|
377
|
+
const hoverSlowdownRef = useRef<number>(1);
|
|
378
|
+
|
|
379
|
+
const {
|
|
380
|
+
sun0Geo,
|
|
381
|
+
sun1Geo,
|
|
382
|
+
sun2Geo,
|
|
383
|
+
planetGeo,
|
|
384
|
+
sunShaderMats,
|
|
385
|
+
coronaShaderMats,
|
|
386
|
+
trisolarisShaderMat,
|
|
387
|
+
starfieldMat,
|
|
388
|
+
} = useMemo(() => {
|
|
389
|
+
const makeSunMat = (
|
|
390
|
+
coreHex: string,
|
|
391
|
+
edgeHex: string,
|
|
392
|
+
spotHex: string,
|
|
393
|
+
gain: number,
|
|
394
|
+
scale: number
|
|
395
|
+
) =>
|
|
396
|
+
new THREE.ShaderMaterial({
|
|
397
|
+
uniforms: {
|
|
398
|
+
uTime: { value: 0 },
|
|
399
|
+
uCoreColor: { value: new THREE.Color(coreHex) },
|
|
400
|
+
uEdgeColor: { value: new THREE.Color(edgeHex) },
|
|
401
|
+
uSpotColor: { value: new THREE.Color(spotHex) },
|
|
402
|
+
uGlowGain: { value: gain },
|
|
403
|
+
uGranuleScale: { value: scale },
|
|
404
|
+
},
|
|
405
|
+
vertexShader: SOLAR_PHOTOSPHERE_VERTEX,
|
|
406
|
+
fragmentShader: SOLAR_PHOTOSPHERE_FRAGMENT,
|
|
407
|
+
toneMapped: false,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const makeCoronaMat = (coronaHex: string) =>
|
|
411
|
+
new THREE.ShaderMaterial({
|
|
412
|
+
uniforms: {
|
|
413
|
+
uTime: { value: 0 },
|
|
414
|
+
uCoronaColor: { value: new THREE.Color(coronaHex) },
|
|
415
|
+
},
|
|
416
|
+
vertexShader: CORONA_HALO_VERTEX,
|
|
417
|
+
fragmentShader: CORONA_HALO_FRAGMENT,
|
|
418
|
+
transparent: true,
|
|
419
|
+
depthWrite: false,
|
|
420
|
+
blending: THREE.AdditiveBlending,
|
|
421
|
+
side: THREE.BackSide,
|
|
422
|
+
toneMapped: false,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const sMats = [
|
|
426
|
+
makeSunMat('#FFF3D1', '#D97706', '#290D02', 1.18, 8.5),
|
|
427
|
+
makeSunMat('#FDBA74', '#C2410C', '#240A01', 1.12, 7.2),
|
|
428
|
+
makeSunMat('#FB7185', '#7F1D1D', '#180303', 1.05, 5.0),
|
|
429
|
+
];
|
|
430
|
+
|
|
431
|
+
const cMats = [
|
|
432
|
+
makeCoronaMat('#FFB347'),
|
|
433
|
+
makeCoronaMat('#F97316'),
|
|
434
|
+
makeCoronaMat('#EF4444'),
|
|
435
|
+
];
|
|
436
|
+
|
|
437
|
+
const planetShader = new THREE.ShaderMaterial({
|
|
438
|
+
uniforms: {
|
|
439
|
+
uTime: { value: 0 },
|
|
440
|
+
uSun0Pos: { value: new THREE.Vector3() },
|
|
441
|
+
uSun1Pos: { value: new THREE.Vector3() },
|
|
442
|
+
uSun2Pos: { value: new THREE.Vector3() },
|
|
443
|
+
uTidalHeat: { value: 0.4 },
|
|
444
|
+
uBolometricFlux: { value: 1.0 },
|
|
445
|
+
},
|
|
446
|
+
vertexShader: TRISOLARIS_VERTEX_SHADER,
|
|
447
|
+
fragmentShader: TRISOLARIS_FRAGMENT_SHADER,
|
|
448
|
+
toneMapped: false,
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const sfMat = new THREE.ShaderMaterial({
|
|
452
|
+
vertexShader: STARFIELD_VERTEX_SHADER,
|
|
453
|
+
fragmentShader: STARFIELD_FRAGMENT_SHADER,
|
|
454
|
+
transparent: true,
|
|
455
|
+
depthWrite: false,
|
|
456
|
+
blending: THREE.AdditiveBlending,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
sun0Geo: new THREE.SphereGeometry(0.43, 48, 48),
|
|
461
|
+
sun1Geo: new THREE.SphereGeometry(0.33, 48, 48),
|
|
462
|
+
sun2Geo: new THREE.SphereGeometry(0.24, 48, 48),
|
|
463
|
+
planetGeo: new THREE.SphereGeometry(0.19, 48, 48),
|
|
464
|
+
sunShaderMats: sMats,
|
|
465
|
+
coronaShaderMats: cMats,
|
|
466
|
+
trisolarisShaderMat: planetShader,
|
|
467
|
+
starfieldMat: sfMat,
|
|
468
|
+
};
|
|
469
|
+
}, []);
|
|
470
|
+
|
|
471
|
+
const { suns, trisolaris } = useMemo(() => {
|
|
472
|
+
const sList: readonly CelestialBodyState[] = [
|
|
473
|
+
{
|
|
474
|
+
pos: new THREE.Vector3(),
|
|
475
|
+
vel: new THREE.Vector3(),
|
|
476
|
+
acc: new THREE.Vector3(),
|
|
477
|
+
mass: 1.1,
|
|
478
|
+
luminosity: 1.52,
|
|
479
|
+
radius: 0.43,
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
pos: new THREE.Vector3(),
|
|
483
|
+
vel: new THREE.Vector3(),
|
|
484
|
+
acc: new THREE.Vector3(),
|
|
485
|
+
mass: 0.91,
|
|
486
|
+
luminosity: 0.5,
|
|
487
|
+
radius: 0.33,
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
pos: new THREE.Vector3(),
|
|
491
|
+
vel: new THREE.Vector3(),
|
|
492
|
+
acc: new THREE.Vector3(),
|
|
493
|
+
mass: 0.54,
|
|
494
|
+
luminosity: 0.08,
|
|
495
|
+
radius: 0.24,
|
|
496
|
+
},
|
|
497
|
+
];
|
|
498
|
+
const pState: CelestialBodyState = {
|
|
499
|
+
pos: new THREE.Vector3(),
|
|
500
|
+
vel: new THREE.Vector3(),
|
|
501
|
+
acc: new THREE.Vector3(),
|
|
502
|
+
mass: 0.000003,
|
|
503
|
+
luminosity: 0,
|
|
504
|
+
radius: 0.19,
|
|
505
|
+
};
|
|
506
|
+
seedThreeBodyEpoch(sList, pState);
|
|
507
|
+
return { suns: sList, trisolaris: pState };
|
|
508
|
+
}, []);
|
|
509
|
+
|
|
510
|
+
const starfieldGeo = useMemo(() => {
|
|
511
|
+
const count = 2000;
|
|
512
|
+
const positions = new Float32Array(count * 3);
|
|
513
|
+
const sizes = new Float32Array(count);
|
|
514
|
+
const colors = new Float32Array(count * 3);
|
|
515
|
+
const palette = [
|
|
516
|
+
new THREE.Color('#DCE8FF'),
|
|
517
|
+
new THREE.Color('#FFF8E7'),
|
|
518
|
+
new THREE.Color('#FFE4B5'),
|
|
519
|
+
new THREE.Color('#FFC699'),
|
|
520
|
+
];
|
|
521
|
+
for (let i = 0; i < count; i++) {
|
|
522
|
+
const r = 5.8 + Math.random() * 11.5;
|
|
523
|
+
const theta = Math.random() * Math.PI * 2;
|
|
524
|
+
const phi = Math.acos(2 * Math.random() - 1);
|
|
525
|
+
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
|
|
526
|
+
positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
|
|
527
|
+
positions[i * 3 + 2] = r * Math.cos(phi);
|
|
528
|
+
sizes[i] = 0.85 + Math.pow(Math.random(), 2.1) * 2.25;
|
|
529
|
+
const col = palette[i % palette.length];
|
|
530
|
+
colors[i * 3] = col.r;
|
|
531
|
+
colors[i * 3 + 1] = col.g;
|
|
532
|
+
colors[i * 3 + 2] = col.b;
|
|
533
|
+
}
|
|
534
|
+
const geo = new THREE.BufferGeometry();
|
|
535
|
+
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
536
|
+
geo.setAttribute('aSize', new THREE.BufferAttribute(sizes, 1));
|
|
537
|
+
geo.setAttribute('aColor', new THREE.BufferAttribute(colors, 3));
|
|
538
|
+
return geo;
|
|
539
|
+
}, []);
|
|
540
|
+
|
|
541
|
+
useEffect(() => {
|
|
542
|
+
return () => {
|
|
543
|
+
sun0Geo.dispose();
|
|
544
|
+
sun1Geo.dispose();
|
|
545
|
+
sun2Geo.dispose();
|
|
546
|
+
planetGeo.dispose();
|
|
547
|
+
starfieldGeo.dispose();
|
|
548
|
+
starfieldMat.dispose();
|
|
549
|
+
trisolarisShaderMat.dispose();
|
|
550
|
+
sunShaderMats.forEach((m) => m.dispose());
|
|
551
|
+
coronaShaderMats.forEach((m) => m.dispose());
|
|
552
|
+
};
|
|
553
|
+
}, [
|
|
554
|
+
sun0Geo,
|
|
555
|
+
sun1Geo,
|
|
556
|
+
sun2Geo,
|
|
557
|
+
planetGeo,
|
|
558
|
+
starfieldGeo,
|
|
559
|
+
starfieldMat,
|
|
560
|
+
trisolarisShaderMat,
|
|
561
|
+
sunShaderMats,
|
|
562
|
+
coronaShaderMats,
|
|
563
|
+
]);
|
|
564
|
+
|
|
565
|
+
const diff = useMemo(() => new THREE.Vector3(), []);
|
|
566
|
+
const worldSun0 = useMemo(() => new THREE.Vector3(), []);
|
|
567
|
+
const worldSun1 = useMemo(() => new THREE.Vector3(), []);
|
|
568
|
+
const worldSun2 = useMemo(() => new THREE.Vector3(), []);
|
|
569
|
+
const worldPlanet = useMemo(() => new THREE.Vector3(), []);
|
|
570
|
+
const projectedVec = useMemo(() => new THREE.Vector3(), []);
|
|
571
|
+
const raycaster = useMemo(() => new THREE.Raycaster(), []);
|
|
572
|
+
|
|
573
|
+
useFrame((state, delta) => {
|
|
574
|
+
const scroll = scrollProgressRef?.current ?? 0;
|
|
575
|
+
const clampedDelta = Math.min(delta, 0.035);
|
|
576
|
+
|
|
577
|
+
const rawScrollVel =
|
|
578
|
+
(scroll - lastScrollRef.current) / Math.max(clampedDelta, 0.001);
|
|
579
|
+
lastScrollRef.current = scroll;
|
|
580
|
+
scrollVelRef.current = THREE.MathUtils.lerp(
|
|
581
|
+
scrollVelRef.current,
|
|
582
|
+
rawScrollVel,
|
|
583
|
+
clampedDelta * 12
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
const baseRate = 0.62 * timeScale;
|
|
587
|
+
const rawTimeRate =
|
|
588
|
+
scrollVelRef.current < -0.015
|
|
589
|
+
? THREE.MathUtils.clamp(scrollVelRef.current * 4.5, -3.2, -0.15)
|
|
590
|
+
: THREE.MathUtils.clamp(
|
|
591
|
+
baseRate + Math.max(0, scrollVelRef.current) * 4.2,
|
|
592
|
+
0.15,
|
|
593
|
+
3.5
|
|
594
|
+
);
|
|
595
|
+
const signedTimeRate =
|
|
596
|
+
reducedMotion || paused ? 0 : rawTimeRate * hoverSlowdownRef.current;
|
|
597
|
+
|
|
598
|
+
const subSteps = 12;
|
|
599
|
+
const dt = (clampedDelta * signedTimeRate) / subSteps;
|
|
600
|
+
const G = 1.5;
|
|
601
|
+
const starSofteningSq = 0.04;
|
|
602
|
+
const planetSofteningSq = 0.05;
|
|
603
|
+
|
|
604
|
+
const computeAccelerations = () => {
|
|
605
|
+
for (let i = 0; i < 3; i++) {
|
|
606
|
+
suns[i].acc.set(0, 0, 0);
|
|
607
|
+
for (let j = 0; j < 3; j++) {
|
|
608
|
+
if (i === j) continue;
|
|
609
|
+
diff.subVectors(suns[j].pos, suns[i].pos);
|
|
610
|
+
const distSq = diff.lengthSq() + starSofteningSq;
|
|
611
|
+
const invDistCube = 1.0 / Math.pow(distSq, 1.5);
|
|
612
|
+
suns[i].acc.addScaledVector(diff, G * suns[j].mass * invDistCube);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
trisolaris.acc.set(0, 0, 0);
|
|
617
|
+
for (let j = 0; j < 3; j++) {
|
|
618
|
+
diff.subVectors(suns[j].pos, trisolaris.pos);
|
|
619
|
+
const distSq = diff.lengthSq() + planetSofteningSq;
|
|
620
|
+
const invDistCube = 1.0 / Math.pow(distSq, 1.5);
|
|
621
|
+
trisolaris.acc.addScaledVector(diff, G * suns[j].mass * invDistCube);
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
if (signedTimeRate !== 0) {
|
|
626
|
+
computeAccelerations();
|
|
627
|
+
for (let step = 0; step < subSteps; step++) {
|
|
628
|
+
for (let i = 0; i < 3; i++) {
|
|
629
|
+
suns[i].vel.addScaledVector(suns[i].acc, 0.5 * dt);
|
|
630
|
+
suns[i].pos.addScaledVector(suns[i].vel, dt);
|
|
631
|
+
}
|
|
632
|
+
trisolaris.vel.addScaledVector(trisolaris.acc, 0.5 * dt);
|
|
633
|
+
trisolaris.pos.addScaledVector(trisolaris.vel, dt);
|
|
634
|
+
|
|
635
|
+
computeAccelerations();
|
|
636
|
+
|
|
637
|
+
for (let i = 0; i < 3; i++) {
|
|
638
|
+
suns[i].vel.addScaledVector(suns[i].acc, 0.5 * dt);
|
|
639
|
+
}
|
|
640
|
+
trisolaris.vel.addScaledVector(trisolaris.acc, 0.5 * dt);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const escapeRadiusSq = 17.64;
|
|
644
|
+
let bodyEjected =
|
|
645
|
+
trisolaris.pos.lengthSq() > escapeRadiusSq &&
|
|
646
|
+
trisolaris.pos.dot(trisolaris.vel) > 0;
|
|
647
|
+
for (let i = 0; i < 3; i++) {
|
|
648
|
+
if (
|
|
649
|
+
suns[i].pos.lengthSq() > escapeRadiusSq &&
|
|
650
|
+
suns[i].pos.dot(suns[i].vel) > 0
|
|
651
|
+
) {
|
|
652
|
+
bodyEjected = true;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (bodyEjected) {
|
|
656
|
+
seedThreeBodyEpoch(suns, trisolaris);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const sunGroups = [sun0Ref.current, sun1Ref.current, sun2Ref.current];
|
|
661
|
+
const baseGains = [1.18, 1.12, 1.05];
|
|
662
|
+
for (let i = 0; i < 3; i++) {
|
|
663
|
+
const g = sunGroups[i];
|
|
664
|
+
if (g) {
|
|
665
|
+
g.position.copy(suns[i].pos);
|
|
666
|
+
g.rotation.y += clampedDelta * signedTimeRate * (0.28 + i * 0.12);
|
|
667
|
+
g.scale.setScalar(1);
|
|
668
|
+
}
|
|
669
|
+
sunShaderMats[i].uniforms.uTime.value +=
|
|
670
|
+
clampedDelta * signedTimeRate * 1.4;
|
|
671
|
+
coronaShaderMats[i].uniforms.uTime.value +=
|
|
672
|
+
clampedDelta * signedTimeRate * 1.4;
|
|
673
|
+
sunShaderMats[i].uniforms.uGlowGain.value = baseGains[i];
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (planetGroupRef.current) {
|
|
677
|
+
planetGroupRef.current.position.copy(trisolaris.pos);
|
|
678
|
+
planetGroupRef.current.rotation.y +=
|
|
679
|
+
clampedDelta * signedTimeRate * 0.65;
|
|
680
|
+
planetGroupRef.current.rotation.z = 0.41;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
let bolometricFlux = 0;
|
|
684
|
+
let tidalHeating = 0;
|
|
685
|
+
let dominantSunIdx = 0;
|
|
686
|
+
let maxPull = -1;
|
|
687
|
+
for (let i = 0; i < 3; i++) {
|
|
688
|
+
const dSq = Math.max(0.22, trisolaris.pos.distanceToSquared(suns[i].pos));
|
|
689
|
+
bolometricFlux += suns[i].luminosity / dSq;
|
|
690
|
+
const d6 = dSq * dSq * dSq;
|
|
691
|
+
tidalHeating += (suns[i].mass * suns[i].mass) / (d6 * 4.5);
|
|
692
|
+
const pull = suns[i].mass / dSq;
|
|
693
|
+
if (pull > maxPull) {
|
|
694
|
+
maxPull = pull;
|
|
695
|
+
dominantSunIdx = i;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (systemGroupRef.current) {
|
|
700
|
+
systemGroupRef.current.updateMatrixWorld(true);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (
|
|
704
|
+
sun0Ref.current &&
|
|
705
|
+
sun1Ref.current &&
|
|
706
|
+
sun2Ref.current &&
|
|
707
|
+
planetGroupRef.current
|
|
708
|
+
) {
|
|
709
|
+
sun0Ref.current.getWorldPosition(worldSun0);
|
|
710
|
+
sun1Ref.current.getWorldPosition(worldSun1);
|
|
711
|
+
sun2Ref.current.getWorldPosition(worldSun2);
|
|
712
|
+
planetGroupRef.current.getWorldPosition(worldPlanet);
|
|
713
|
+
trisolarisShaderMat.uniforms.uSun0Pos.value.copy(worldSun0);
|
|
714
|
+
trisolarisShaderMat.uniforms.uSun1Pos.value.copy(worldSun1);
|
|
715
|
+
trisolarisShaderMat.uniforms.uSun2Pos.value.copy(worldSun2);
|
|
716
|
+
|
|
717
|
+
const ndc = pointerNdcRef.current;
|
|
718
|
+
const uiBlocked = ndc.x < -10;
|
|
719
|
+
|
|
720
|
+
raycaster.setFromCamera(ndc, state.camera);
|
|
721
|
+
const worldCenters = [worldSun0, worldSun1, worldSun2, worldPlanet];
|
|
722
|
+
let hoveredIdx = -1;
|
|
723
|
+
let bestDistSq = 999;
|
|
724
|
+
|
|
725
|
+
if (!uiBlocked) {
|
|
726
|
+
for (let i = 0; i < 4; i++) {
|
|
727
|
+
const hitR = catalog[i]?.hitRadius ?? 0.4;
|
|
728
|
+
const distSq = raycaster.ray.distanceSqToPoint(worldCenters[i]);
|
|
729
|
+
if (distSq <= hitR * hitR && distSq < bestDistSq) {
|
|
730
|
+
bestDistSq = distSq;
|
|
731
|
+
hoveredIdx = i;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
hoverSlowdownRef.current = THREE.MathUtils.lerp(
|
|
737
|
+
hoverSlowdownRef.current,
|
|
738
|
+
hoveredIdx !== -1 ? 0.0 : 1.0,
|
|
739
|
+
clampedDelta * 18
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
for (let i = 0; i < 3; i++) {
|
|
743
|
+
const g = sunGroups[i];
|
|
744
|
+
if (g && hoveredIdx === i) {
|
|
745
|
+
g.scale.setScalar(1.12);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (planetGroupRef.current) {
|
|
749
|
+
planetGroupRef.current.scale.setScalar(hoveredIdx === 3 ? 1.48 : 1.32);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (hoveredIdx !== -1 && catalog[hoveredIdx]) {
|
|
753
|
+
const meta = catalog[hoveredIdx];
|
|
754
|
+
projectedVec.copy(worldCenters[hoveredIdx]).project(state.camera);
|
|
755
|
+
const screenX = (projectedVec.x * 0.5 + 0.5) * window.innerWidth;
|
|
756
|
+
const screenY = (-projectedVec.y * 0.5 + 0.5) * window.innerHeight;
|
|
757
|
+
const rawSpeed =
|
|
758
|
+
hoveredIdx < 3
|
|
759
|
+
? suns[hoveredIdx].vel.length()
|
|
760
|
+
: trisolaris.vel.length();
|
|
761
|
+
const velocityLabel = `${(rawSpeed * 29.8).toFixed(1)} km/s`;
|
|
762
|
+
|
|
763
|
+
let statusLabel = 'Inner Binary Primary · Barycentric Core';
|
|
764
|
+
if (hoveredIdx === 3) {
|
|
765
|
+
const hostNames = ['Alpha Cen A', 'Alpha Cen B', 'Proxima C'];
|
|
766
|
+
const era =
|
|
767
|
+
bolometricFlux > 1.35
|
|
768
|
+
? 'CHAOTIC ERA · SCORCHING'
|
|
769
|
+
: bolometricFlux < 0.55
|
|
770
|
+
? 'CHAOTIC ERA · FROZEN'
|
|
771
|
+
: 'STABLE ERA · TEMPERATE';
|
|
772
|
+
statusLabel = `${era} (Pull: ${hostNames[dominantSunIdx]})`;
|
|
773
|
+
} else if (hoveredIdx === 1) {
|
|
774
|
+
statusLabel = 'Inner Binary Companion · Eccentric Pair';
|
|
775
|
+
} else if (hoveredIdx === 2) {
|
|
776
|
+
statusLabel = 'Retrograde Outer Perturber · Flare Active';
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
bridgeRef.current?.commitFrame({
|
|
780
|
+
badge: meta.badge,
|
|
781
|
+
name: meta.name,
|
|
782
|
+
classification: meta.classification,
|
|
783
|
+
accentHex: meta.accentHex,
|
|
784
|
+
reticleSize: meta.reticleSize,
|
|
785
|
+
screenX,
|
|
786
|
+
screenY,
|
|
787
|
+
massLabel: meta.massLabel,
|
|
788
|
+
tempLumLabel: meta.tempLumLabel,
|
|
789
|
+
velocityLabel,
|
|
790
|
+
statusLabel,
|
|
791
|
+
});
|
|
792
|
+
} else {
|
|
793
|
+
bridgeRef.current?.commitFrame(null);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
trisolarisShaderMat.uniforms.uTime.value +=
|
|
798
|
+
clampedDelta * signedTimeRate * 0.9;
|
|
799
|
+
trisolarisShaderMat.uniforms.uBolometricFlux.value = THREE.MathUtils.lerp(
|
|
800
|
+
trisolarisShaderMat.uniforms.uBolometricFlux.value,
|
|
801
|
+
bolometricFlux,
|
|
802
|
+
clampedDelta * 4
|
|
803
|
+
);
|
|
804
|
+
trisolarisShaderMat.uniforms.uTidalHeat.value = THREE.MathUtils.lerp(
|
|
805
|
+
trisolarisShaderMat.uniforms.uTidalHeat.value,
|
|
806
|
+
THREE.MathUtils.clamp(tidalHeating, 0.05, 2.5),
|
|
807
|
+
clampedDelta * 4
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
if (starfieldRef.current) {
|
|
811
|
+
starfieldRef.current.rotation.y +=
|
|
812
|
+
clampedDelta * signedTimeRate * 0.015;
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
return (
|
|
817
|
+
<group ref={systemGroupRef}>
|
|
818
|
+
<points
|
|
819
|
+
ref={starfieldRef}
|
|
820
|
+
geometry={starfieldGeo}
|
|
821
|
+
material={starfieldMat}
|
|
822
|
+
/>
|
|
823
|
+
|
|
824
|
+
<group ref={sun0Ref}>
|
|
825
|
+
<mesh geometry={sun0Geo} material={sunShaderMats[0]} />
|
|
826
|
+
<mesh
|
|
827
|
+
geometry={sun0Geo}
|
|
828
|
+
material={coronaShaderMats[0]}
|
|
829
|
+
scale={1.22}
|
|
830
|
+
/>
|
|
831
|
+
<pointLight color="#FFF2D6" intensity={7.5} distance={6} />
|
|
832
|
+
</group>
|
|
833
|
+
|
|
834
|
+
<group ref={sun1Ref}>
|
|
835
|
+
<mesh geometry={sun1Geo} material={sunShaderMats[1]} />
|
|
836
|
+
<mesh
|
|
837
|
+
geometry={sun1Geo}
|
|
838
|
+
material={coronaShaderMats[1]}
|
|
839
|
+
scale={1.22}
|
|
840
|
+
/>
|
|
841
|
+
<pointLight color="#FFAE52" intensity={5.2} distance={6} />
|
|
842
|
+
</group>
|
|
843
|
+
|
|
844
|
+
<group ref={sun2Ref}>
|
|
845
|
+
<mesh geometry={sun2Geo} material={sunShaderMats[2]} />
|
|
846
|
+
<mesh
|
|
847
|
+
geometry={sun2Geo}
|
|
848
|
+
material={coronaShaderMats[2]}
|
|
849
|
+
scale={1.25}
|
|
850
|
+
/>
|
|
851
|
+
<pointLight color="#F95738" intensity={3.8} distance={5} />
|
|
852
|
+
</group>
|
|
853
|
+
|
|
854
|
+
<group ref={planetGroupRef} scale={1.32}>
|
|
855
|
+
<mesh geometry={planetGeo} material={trisolarisShaderMat} />
|
|
856
|
+
</group>
|
|
857
|
+
</group>
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
export default function ZenCosmosCanvas({
|
|
862
|
+
scrollProgressRef,
|
|
863
|
+
paused = false,
|
|
864
|
+
timeScale = 1,
|
|
865
|
+
catalog = DEFAULT_CELESTIAL_CATALOG,
|
|
866
|
+
}: ZenCosmosCanvasProps) {
|
|
867
|
+
const { reducedMotion, polarity } = useZenTheme();
|
|
868
|
+
const pointerNdcRef = useRef<THREE.Vector2>(new THREE.Vector2(-99, -99));
|
|
869
|
+
const bridgeRef = useRef<ZenTelemetryBridge | null>(null);
|
|
870
|
+
|
|
871
|
+
useEffect(() => {
|
|
872
|
+
const handlePointerMove = (e: PointerEvent) => {
|
|
873
|
+
if (bridgeRef.current?.isOccludedTarget(e.target)) {
|
|
874
|
+
pointerNdcRef.current.set(-99, -99);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
pointerNdcRef.current.set(
|
|
878
|
+
(e.clientX / window.innerWidth) * 2 - 1,
|
|
879
|
+
-(e.clientY / window.innerHeight) * 2 + 1
|
|
880
|
+
);
|
|
881
|
+
};
|
|
882
|
+
window.addEventListener('pointermove', handlePointerMove, {
|
|
883
|
+
passive: true,
|
|
884
|
+
});
|
|
885
|
+
return () => window.removeEventListener('pointermove', handlePointerMove);
|
|
886
|
+
}, []);
|
|
887
|
+
|
|
888
|
+
return (
|
|
889
|
+
<>
|
|
890
|
+
<div
|
|
891
|
+
aria-hidden="true"
|
|
892
|
+
className={`fixed inset-0 z-0 pointer-events-none transition-opacity duration-500 ${
|
|
893
|
+
polarity === 'light' ? 'opacity-25' : 'opacity-100'
|
|
894
|
+
}`}
|
|
895
|
+
>
|
|
896
|
+
<Canvas
|
|
897
|
+
dpr={[1, 1.5]}
|
|
898
|
+
camera={{ position: [0, 0, 5.2], fov: 44 }}
|
|
899
|
+
gl={{
|
|
900
|
+
antialias: true,
|
|
901
|
+
alpha: true,
|
|
902
|
+
powerPreference: 'high-performance',
|
|
903
|
+
}}
|
|
904
|
+
>
|
|
905
|
+
<ambientLight intensity={0.25} />
|
|
906
|
+
<Suspense fallback={null}>
|
|
907
|
+
<CosmosSceneInternal
|
|
908
|
+
scrollProgressRef={scrollProgressRef}
|
|
909
|
+
pointerNdcRef={pointerNdcRef}
|
|
910
|
+
bridgeRef={bridgeRef}
|
|
911
|
+
reducedMotion={reducedMotion}
|
|
912
|
+
paused={paused}
|
|
913
|
+
timeScale={timeScale}
|
|
914
|
+
catalog={catalog}
|
|
915
|
+
/>
|
|
916
|
+
</Suspense>
|
|
917
|
+
<EffectComposer>
|
|
918
|
+
<Bloom
|
|
919
|
+
intensity={0.85}
|
|
920
|
+
luminanceThreshold={0.78}
|
|
921
|
+
luminanceSmoothing={0.85}
|
|
922
|
+
mipmapBlur
|
|
923
|
+
/>
|
|
924
|
+
</EffectComposer>
|
|
925
|
+
</Canvas>
|
|
926
|
+
</div>
|
|
927
|
+
|
|
928
|
+
<ZenTelemetryHud bridgeRef={bridgeRef} />
|
|
929
|
+
</>
|
|
930
|
+
);
|
|
931
|
+
}
|