@project-skymap/library 0.8.0 → 0.8.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/dist/index.cjs +373 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +373 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1433,16 +1433,111 @@ function createEngine({
|
|
|
1433
1433
|
function getSceneDebug() {
|
|
1434
1434
|
return currentConfig?.debug?.sceneMechanics;
|
|
1435
1435
|
}
|
|
1436
|
+
function getFreezeBand() {
|
|
1437
|
+
const dbg = getSceneDebug();
|
|
1438
|
+
const startRaw = dbg?.freezeBandStartFov ?? ENGINE_CONFIG.freezeBandStartFov;
|
|
1439
|
+
const endRaw = dbg?.freezeBandEndFov ?? ENGINE_CONFIG.freezeBandEndFov;
|
|
1440
|
+
const start2 = Math.min(startRaw, endRaw);
|
|
1441
|
+
const end = Math.max(startRaw, endRaw);
|
|
1442
|
+
return { start: start2, end };
|
|
1443
|
+
}
|
|
1444
|
+
function isInTransitionFreezeBand(fov) {
|
|
1445
|
+
const band = getFreezeBand();
|
|
1446
|
+
return fov >= band.start && fov <= band.end;
|
|
1447
|
+
}
|
|
1448
|
+
function getZenithBiasStartFov() {
|
|
1449
|
+
return getSceneDebug()?.zenithBiasStartFov ?? ENGINE_CONFIG.zenithBiasStartFov;
|
|
1450
|
+
}
|
|
1451
|
+
function getVerticalPanDampConfig() {
|
|
1452
|
+
const dbg = getSceneDebug();
|
|
1453
|
+
const fovStartRaw = dbg?.verticalPanDampStartFov ?? ENGINE_CONFIG.verticalPanDampStartFov;
|
|
1454
|
+
const fovEndRaw = dbg?.verticalPanDampEndFov ?? ENGINE_CONFIG.verticalPanDampEndFov;
|
|
1455
|
+
const latStartRaw = dbg?.verticalPanDampLatStartDeg ?? ENGINE_CONFIG.verticalPanDampLatStartDeg;
|
|
1456
|
+
const latEndRaw = dbg?.verticalPanDampLatEndDeg ?? ENGINE_CONFIG.verticalPanDampLatEndDeg;
|
|
1457
|
+
return {
|
|
1458
|
+
fovStart: Math.min(fovStartRaw, fovEndRaw),
|
|
1459
|
+
fovEnd: Math.max(fovStartRaw, fovEndRaw),
|
|
1460
|
+
latStartDeg: Math.min(latStartRaw, latEndRaw),
|
|
1461
|
+
latEndDeg: Math.max(latStartRaw, latEndRaw)
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
function getVerticalPanFactor(fov, lat) {
|
|
1465
|
+
if (zenithProjectionLockActive) return 0;
|
|
1466
|
+
const cfg = getVerticalPanDampConfig();
|
|
1467
|
+
const fovT = THREE6__namespace.MathUtils.smoothstep(fov, cfg.fovStart, cfg.fovEnd);
|
|
1468
|
+
const zenithT = THREE6__namespace.MathUtils.smoothstep(
|
|
1469
|
+
Math.max(lat, 0),
|
|
1470
|
+
THREE6__namespace.MathUtils.degToRad(cfg.latStartDeg),
|
|
1471
|
+
THREE6__namespace.MathUtils.degToRad(cfg.latEndDeg)
|
|
1472
|
+
);
|
|
1473
|
+
const lock = Math.max(fovT * 0.65, fovT * zenithT);
|
|
1474
|
+
return THREE6__namespace.MathUtils.clamp(1 - lock, 0, 1);
|
|
1475
|
+
}
|
|
1476
|
+
function getMovementMassFactor(fov, wideFovFactor = ENGINE_CONFIG.movementMassWideFov) {
|
|
1477
|
+
const t = THREE6__namespace.MathUtils.smoothstep(fov, 24, 96);
|
|
1478
|
+
return THREE6__namespace.MathUtils.lerp(1, wideFovFactor, t);
|
|
1479
|
+
}
|
|
1480
|
+
function compressInputDelta(delta) {
|
|
1481
|
+
const absDelta = Math.abs(delta);
|
|
1482
|
+
if (absDelta < 1e-4) return 0;
|
|
1483
|
+
return Math.sign(delta) * (absDelta / (1 + absDelta * ENGINE_CONFIG.inputCompression));
|
|
1484
|
+
}
|
|
1436
1485
|
const constellationLayer = new ConstellationArtworkLayer(scene);
|
|
1437
1486
|
function mix(a, b, t) {
|
|
1438
1487
|
return a * (1 - t) + b * t;
|
|
1439
1488
|
}
|
|
1440
1489
|
let currentProjection = new BlendedProjection(ENGINE_CONFIG.blendStart, ENGINE_CONFIG.blendEnd);
|
|
1490
|
+
let zenithProjectionLockActive = false;
|
|
1491
|
+
function getZenithLockBlendThresholds() {
|
|
1492
|
+
const enterRaw = ENGINE_CONFIG.zenithLockBlendEnter;
|
|
1493
|
+
const exitRaw = ENGINE_CONFIG.zenithLockBlendExit;
|
|
1494
|
+
return {
|
|
1495
|
+
enter: Math.max(0, Math.min(1, enterRaw)),
|
|
1496
|
+
exit: Math.max(0, Math.min(1, Math.min(exitRaw, enterRaw)))
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
function getZenithLockLat() {
|
|
1500
|
+
return Math.PI / 2 - 1e-3;
|
|
1501
|
+
}
|
|
1502
|
+
function getBlendForZenithControl() {
|
|
1503
|
+
if (currentProjection instanceof BlendedProjection) return currentProjection.getBlend();
|
|
1504
|
+
return 0;
|
|
1505
|
+
}
|
|
1506
|
+
function applyZenithAutoCenter() {
|
|
1507
|
+
const zenithLat = getZenithLockLat();
|
|
1508
|
+
const blend = getBlendForZenithControl();
|
|
1509
|
+
let pullT = THREE6__namespace.MathUtils.smoothstep(
|
|
1510
|
+
blend,
|
|
1511
|
+
ENGINE_CONFIG.zenithAutoCenterBlendStart,
|
|
1512
|
+
ENGINE_CONFIG.zenithAutoCenterBlendEnd
|
|
1513
|
+
);
|
|
1514
|
+
if (zenithProjectionLockActive) pullT = 1;
|
|
1515
|
+
if (pullT <= 1e-4) return;
|
|
1516
|
+
const pullLerp = THREE6__namespace.MathUtils.lerp(
|
|
1517
|
+
ENGINE_CONFIG.zenithAutoCenterMinLerp,
|
|
1518
|
+
ENGINE_CONFIG.zenithAutoCenterMaxLerp,
|
|
1519
|
+
pullT
|
|
1520
|
+
);
|
|
1521
|
+
state.lat = THREE6__namespace.MathUtils.lerp(state.lat, zenithLat, pullLerp);
|
|
1522
|
+
state.targetLat = THREE6__namespace.MathUtils.lerp(state.targetLat, zenithLat, Math.min(1, pullLerp * 1.15));
|
|
1523
|
+
state.velocityY *= 1 - 0.85 * pullT;
|
|
1524
|
+
if (zenithProjectionLockActive && Math.abs(state.lat - zenithLat) < 25e-5) {
|
|
1525
|
+
state.lat = zenithLat;
|
|
1526
|
+
state.targetLat = zenithLat;
|
|
1527
|
+
state.velocityY = 0;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1441
1530
|
function syncProjectionState() {
|
|
1442
1531
|
if (currentProjection instanceof BlendedProjection) {
|
|
1443
1532
|
currentProjection.setFov(state.fov);
|
|
1444
1533
|
currentProjection.setBlendOverride(getSceneDebug()?.projectionBlendOverride ?? null);
|
|
1445
1534
|
globalUniforms.uBlend.value = currentProjection.getBlend();
|
|
1535
|
+
const blend = currentProjection.getBlend();
|
|
1536
|
+
const th = getZenithLockBlendThresholds();
|
|
1537
|
+
if (!zenithProjectionLockActive && blend >= th.enter) zenithProjectionLockActive = true;
|
|
1538
|
+
else if (zenithProjectionLockActive && blend <= th.exit) zenithProjectionLockActive = false;
|
|
1539
|
+
} else {
|
|
1540
|
+
zenithProjectionLockActive = false;
|
|
1446
1541
|
}
|
|
1447
1542
|
globalUniforms.uProjectionType.value = currentProjection.glslProjectionType;
|
|
1448
1543
|
}
|
|
@@ -1699,8 +1794,13 @@ function createEngine({
|
|
|
1699
1794
|
}
|
|
1700
1795
|
const flatten = groundMaterial?.uniforms?.uZenithFlatten?.value;
|
|
1701
1796
|
const blend = currentProjection instanceof BlendedProjection ? currentProjection.getBlend() : -1;
|
|
1797
|
+
const freeze = isInTransitionFreezeBand(state.fov) ? 1 : 0;
|
|
1798
|
+
const zenithBiasStart = getZenithBiasStartFov();
|
|
1799
|
+
const vPanCfg = getVerticalPanDampConfig();
|
|
1800
|
+
const vPan = getVerticalPanFactor(state.fov, state.lat);
|
|
1801
|
+
const moveMass = getMovementMassFactor(state.fov);
|
|
1702
1802
|
console.debug(
|
|
1703
|
-
`[HorizonDiag] fov=${state.fov.toFixed(1)} latDeg=${THREE6__namespace.MathUtils.radToDeg(state.lat).toFixed(1)} mode=${activeHorizonProfile.mode} blend=${blend.toFixed(3)} flatten=${Number(flatten ?? 0).toFixed(3)} drops=${dropCount} bins=${JSON.stringify(compact)}`
|
|
1803
|
+
`[HorizonDiag] fov=${state.fov.toFixed(1)} latDeg=${THREE6__namespace.MathUtils.radToDeg(state.lat).toFixed(1)} mode=${activeHorizonProfile.mode} blend=${blend.toFixed(3)} freeze=${freeze} zLock=${zenithProjectionLockActive ? 1 : 0} biasStart=${zenithBiasStart.toFixed(1)} vPan=${vPan.toFixed(3)} moveMass=${moveMass.toFixed(3)} vPanFov=${vPanCfg.fovStart.toFixed(1)}-${vPanCfg.fovEnd.toFixed(1)} vPanLat=${vPanCfg.latStartDeg.toFixed(1)}-${vPanCfg.latEndDeg.toFixed(1)} flatten=${Number(flatten ?? 0).toFixed(3)} drops=${dropCount} bins=${JSON.stringify(compact)}`
|
|
1704
1804
|
);
|
|
1705
1805
|
}
|
|
1706
1806
|
function createGround() {
|
|
@@ -1849,9 +1949,6 @@ function createEngine({
|
|
|
1849
1949
|
let sunDiscMesh = null;
|
|
1850
1950
|
let sunHaloMesh = null;
|
|
1851
1951
|
let milkyWayMesh = null;
|
|
1852
|
-
let editHoverMesh = null;
|
|
1853
|
-
let editHoverTargetPos = null;
|
|
1854
|
-
let editDropFlash = 0;
|
|
1855
1952
|
function createSkyBackground() {
|
|
1856
1953
|
const geo = new THREE6__namespace.SphereGeometry(2400, 32, 32);
|
|
1857
1954
|
const mat = createSmartMaterial({
|
|
@@ -2403,7 +2500,8 @@ function createEngine({
|
|
|
2403
2500
|
uTime: globalUniforms.uTime,
|
|
2404
2501
|
uBackdropGain: { value: 1 },
|
|
2405
2502
|
uBackdropEnergy: { value: 2.2 },
|
|
2406
|
-
uBackdropSizeExp: { value: 0.9 }
|
|
2503
|
+
uBackdropSizeExp: { value: 0.9 },
|
|
2504
|
+
uRevealZoom: { value: 0 }
|
|
2407
2505
|
},
|
|
2408
2506
|
vertexShaderBody: `
|
|
2409
2507
|
attribute float size;
|
|
@@ -2417,6 +2515,7 @@ function createEngine({
|
|
|
2417
2515
|
uniform float uBackdropGain;
|
|
2418
2516
|
uniform float uBackdropEnergy;
|
|
2419
2517
|
uniform float uBackdropSizeExp;
|
|
2518
|
+
uniform float uRevealZoom;
|
|
2420
2519
|
|
|
2421
2520
|
void main() {
|
|
2422
2521
|
vec3 nPos = normalize(position);
|
|
@@ -2432,7 +2531,12 @@ function createEngine({
|
|
|
2432
2531
|
float twinkle = sin(uTime * 3.0 + position.x * 0.05 + position.z * 0.03) * 0.5 + 0.5;
|
|
2433
2532
|
float scintillation = mix(1.0, twinkle * 2.0, uAtmTwinkle * 0.4 * turbulence);
|
|
2434
2533
|
|
|
2435
|
-
|
|
2534
|
+
// Backdrop appears latest \u2014 fully hidden at wide FOV, emerges when zoomed in.
|
|
2535
|
+
// Thresholds come from ZOOM_REVEAL_CONFIG (baked at startup).
|
|
2536
|
+
float mappedZoom = pow(uRevealZoom, ${ZOOM_REVEAL_CONFIG.zoomCurveExp});
|
|
2537
|
+
float backdropReveal = smoothstep(${ZOOM_REVEAL_CONFIG.backdropRevealStart}, ${ZOOM_REVEAL_CONFIG.backdropRevealEnd}, mappedZoom);
|
|
2538
|
+
|
|
2539
|
+
vColor = color * uBackdropEnergy * extinction * horizonFade * scintillation * uBackdropGain * backdropReveal;
|
|
2436
2540
|
|
|
2437
2541
|
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
2438
2542
|
gl_Position = smartProject(mvPosition);
|
|
@@ -2472,55 +2576,6 @@ function createEngine({
|
|
|
2472
2576
|
points.frustumCulled = false;
|
|
2473
2577
|
backdropGroup.add(points);
|
|
2474
2578
|
}
|
|
2475
|
-
function createEditHoverRing() {
|
|
2476
|
-
const geo = new THREE6__namespace.PlaneGeometry(1, 1);
|
|
2477
|
-
const mat = createSmartMaterial({
|
|
2478
|
-
uniforms: {
|
|
2479
|
-
uRingSize: { value: 0.06 },
|
|
2480
|
-
uRingAlpha: { value: 0 },
|
|
2481
|
-
uRingColor: { value: new THREE6__namespace.Color(0.55, 0.88, 1) }
|
|
2482
|
-
},
|
|
2483
|
-
vertexShaderBody: `
|
|
2484
|
-
uniform float uRingSize;
|
|
2485
|
-
varying vec2 vUv;
|
|
2486
|
-
void main() {
|
|
2487
|
-
vUv = uv;
|
|
2488
|
-
vec4 mvPos = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
|
|
2489
|
-
vec4 proj = smartProject(mvPos);
|
|
2490
|
-
if (proj.z > 4.0) { gl_Position = vec4(10.0, 10.0, 10.0, 1.0); return; }
|
|
2491
|
-
vec2 offset = position.xy * uRingSize * uScale;
|
|
2492
|
-
proj.xy += offset / vec2(uAspect, 1.0);
|
|
2493
|
-
vScreenPos = proj.xy / proj.w;
|
|
2494
|
-
gl_Position = proj;
|
|
2495
|
-
}
|
|
2496
|
-
`,
|
|
2497
|
-
fragmentShader: `
|
|
2498
|
-
varying vec2 vUv;
|
|
2499
|
-
uniform float uRingAlpha;
|
|
2500
|
-
uniform vec3 uRingColor;
|
|
2501
|
-
void main() {
|
|
2502
|
-
float alphaMask = getMaskAlpha();
|
|
2503
|
-
if (alphaMask < 0.01) discard;
|
|
2504
|
-
vec2 p = vUv * 2.0 - 1.0;
|
|
2505
|
-
float d = length(p);
|
|
2506
|
-
float ring = smoothstep(0.52, 0.62, d) * (1.0 - smoothstep(0.80, 0.92, d));
|
|
2507
|
-
float glow = (1.0 - smoothstep(0.55, 0.98, d)) * 0.18;
|
|
2508
|
-
float a = (ring + glow) * uRingAlpha * alphaMask;
|
|
2509
|
-
if (a < 0.005) discard;
|
|
2510
|
-
gl_FragColor = vec4(uRingColor * (ring * 1.2 + glow), a);
|
|
2511
|
-
}
|
|
2512
|
-
`,
|
|
2513
|
-
transparent: true,
|
|
2514
|
-
depthWrite: false,
|
|
2515
|
-
depthTest: false,
|
|
2516
|
-
side: THREE6__namespace.DoubleSide,
|
|
2517
|
-
blending: THREE6__namespace.AdditiveBlending
|
|
2518
|
-
});
|
|
2519
|
-
editHoverMesh = new THREE6__namespace.Mesh(geo, mat);
|
|
2520
|
-
editHoverMesh.renderOrder = 500;
|
|
2521
|
-
editHoverMesh.frustumCulled = false;
|
|
2522
|
-
scene.add(editHoverMesh);
|
|
2523
|
-
}
|
|
2524
2579
|
createSkyBackground();
|
|
2525
2580
|
createGround();
|
|
2526
2581
|
createAtmosphere();
|
|
@@ -2528,7 +2583,6 @@ function createEngine({
|
|
|
2528
2583
|
createSun();
|
|
2529
2584
|
createMilkyWay();
|
|
2530
2585
|
createBackdropStars();
|
|
2531
|
-
createEditHoverRing();
|
|
2532
2586
|
const raycaster = new THREE6__namespace.Raycaster();
|
|
2533
2587
|
raycaster.params.Points.threshold = 5;
|
|
2534
2588
|
new THREE6__namespace.Vector2();
|
|
@@ -2635,7 +2689,7 @@ function createEngine({
|
|
|
2635
2689
|
function getPosition(n) {
|
|
2636
2690
|
if (currentConfig?.arrangement) {
|
|
2637
2691
|
const arr = currentConfig.arrangement[n.id];
|
|
2638
|
-
if (arr) {
|
|
2692
|
+
if (arr?.position) {
|
|
2639
2693
|
const [px, py, pz] = arr.position;
|
|
2640
2694
|
if (pz === 0) {
|
|
2641
2695
|
const radius = currentConfig.layout?.radius ?? 2e3;
|
|
@@ -2784,6 +2838,7 @@ function createEngine({
|
|
|
2784
2838
|
const starChapterIndices = [];
|
|
2785
2839
|
const starTestamentIndices = [];
|
|
2786
2840
|
const starDivisionIndices = [];
|
|
2841
|
+
const starRevealThresholds = [];
|
|
2787
2842
|
const chapterLineCutById = /* @__PURE__ */ new Map();
|
|
2788
2843
|
const chapterStarSizeById = /* @__PURE__ */ new Map();
|
|
2789
2844
|
const chapterWeightNormById = /* @__PURE__ */ new Map();
|
|
@@ -2820,12 +2875,23 @@ function createEngine({
|
|
|
2820
2875
|
} else if (minWeight === maxWeight) {
|
|
2821
2876
|
maxWeight = minWeight + 1;
|
|
2822
2877
|
}
|
|
2878
|
+
{
|
|
2879
|
+
const pctCap = THREE6__namespace.MathUtils.clamp(cfg.starSizeWeightPercentile ?? 0.95, 0.5, 1);
|
|
2880
|
+
const allWeights = [];
|
|
2881
|
+
for (const n of laidOut.nodes) {
|
|
2882
|
+
if (n.level === 3 && typeof n.weight === "number") allWeights.push(n.weight);
|
|
2883
|
+
}
|
|
2884
|
+
allWeights.sort((a, b) => a - b);
|
|
2885
|
+
const capIdx = Math.min(Math.floor(pctCap * allWeights.length), allWeights.length - 1);
|
|
2886
|
+
const cappedMax = allWeights[capIdx];
|
|
2887
|
+
if (cappedMax !== void 0 && cappedMax > minWeight) maxWeight = cappedMax;
|
|
2888
|
+
}
|
|
2823
2889
|
for (const n of laidOut.nodes) {
|
|
2824
2890
|
if (n.level === 3) {
|
|
2825
2891
|
let baseSize = 3.5;
|
|
2826
2892
|
let weightNorm = 0;
|
|
2827
2893
|
if (typeof n.weight === "number") {
|
|
2828
|
-
weightNorm = (n.weight - minWeight) / (maxWeight - minWeight);
|
|
2894
|
+
weightNorm = THREE6__namespace.MathUtils.clamp((n.weight - minWeight) / (maxWeight - minWeight), 0, 1);
|
|
2829
2895
|
const sizeExp = cfg.starSizeExponent ?? 4;
|
|
2830
2896
|
const sizeScale = cfg.starSizeScale ?? 6;
|
|
2831
2897
|
baseSize = Math.pow(weightNorm, sizeExp) * 22 * sizeScale;
|
|
@@ -2850,6 +2916,14 @@ function createEngine({
|
|
|
2850
2916
|
starIndexToId.push(n.id);
|
|
2851
2917
|
const baseSize = chapterStarSizeById.get(n.id) ?? 3.5;
|
|
2852
2918
|
starSizes.push(baseSize);
|
|
2919
|
+
{
|
|
2920
|
+
const wn = chapterWeightNormById.get(n.id) ?? 0;
|
|
2921
|
+
starRevealThresholds.push(THREE6__namespace.MathUtils.lerp(
|
|
2922
|
+
-ZOOM_REVEAL_CONFIG.chapterFeather,
|
|
2923
|
+
ZOOM_REVEAL_CONFIG.chapterRevealMax,
|
|
2924
|
+
1 - wn
|
|
2925
|
+
));
|
|
2926
|
+
}
|
|
2853
2927
|
chapterLineCutById.set(
|
|
2854
2928
|
n.id,
|
|
2855
2929
|
THREE6__namespace.MathUtils.clamp(2.5 + baseSize * 0.45, 3, 40)
|
|
@@ -3000,6 +3074,7 @@ function createEngine({
|
|
|
3000
3074
|
starGeo.setAttribute("chapterIndex", new THREE6__namespace.Float32BufferAttribute(starChapterIndices, 1));
|
|
3001
3075
|
starGeo.setAttribute("testamentIndex", new THREE6__namespace.Float32BufferAttribute(starTestamentIndices, 1));
|
|
3002
3076
|
starGeo.setAttribute("divisionIndex", new THREE6__namespace.Float32BufferAttribute(starDivisionIndices, 1));
|
|
3077
|
+
starGeo.setAttribute("revealThreshold", new THREE6__namespace.Float32BufferAttribute(starRevealThresholds, 1));
|
|
3003
3078
|
const starMat = createSmartMaterial({
|
|
3004
3079
|
uniforms: {
|
|
3005
3080
|
pixelRatio: { value: renderer.getPixelRatio() },
|
|
@@ -3017,19 +3092,22 @@ function createEngine({
|
|
|
3017
3092
|
uFilterDivisionIndex: { value: -1 },
|
|
3018
3093
|
uFilterBookIndex: { value: -1 },
|
|
3019
3094
|
uFilterStrength: { value: 0 },
|
|
3020
|
-
uFilterDimFactor: { value: 0.08 }
|
|
3095
|
+
uFilterDimFactor: { value: 0.08 },
|
|
3096
|
+
uRevealZoom: { value: 0 }
|
|
3021
3097
|
},
|
|
3022
3098
|
vertexShaderBody: `
|
|
3023
|
-
attribute float size;
|
|
3024
|
-
attribute vec3 color;
|
|
3099
|
+
attribute float size;
|
|
3100
|
+
attribute vec3 color;
|
|
3025
3101
|
attribute float phase;
|
|
3026
3102
|
attribute float bookIndex;
|
|
3027
3103
|
attribute float chapterIndex;
|
|
3028
3104
|
attribute float testamentIndex;
|
|
3029
3105
|
attribute float divisionIndex;
|
|
3106
|
+
attribute float revealThreshold;
|
|
3030
3107
|
|
|
3031
3108
|
varying vec3 vColor;
|
|
3032
3109
|
varying float vSize;
|
|
3110
|
+
varying float vReveal;
|
|
3033
3111
|
uniform float pixelRatio;
|
|
3034
3112
|
|
|
3035
3113
|
uniform float uTime;
|
|
@@ -3046,6 +3124,7 @@ function createEngine({
|
|
|
3046
3124
|
uniform float uFilterBookIndex;
|
|
3047
3125
|
uniform float uFilterStrength;
|
|
3048
3126
|
uniform float uFilterDimFactor;
|
|
3127
|
+
uniform float uRevealZoom;
|
|
3049
3128
|
|
|
3050
3129
|
void main() {
|
|
3051
3130
|
vec3 nPos = normalize(position);
|
|
@@ -3108,11 +3187,17 @@ function createEngine({
|
|
|
3108
3187
|
float perceptualSize = pow(size, 0.7);
|
|
3109
3188
|
gl_PointSize = clamp((perceptualSize * sizeBoost * 20.0) * uScale * pixelRatio * (2000.0 / length(mvPosition.xyz)) * horizonFade, 1.0, 600.0);
|
|
3110
3189
|
vSize = gl_PointSize;
|
|
3190
|
+
|
|
3191
|
+
// Zoom-based reveal: faint stars hide at wide FOV, fade in as user zooms.
|
|
3192
|
+
// Exponent and feather baked from ZOOM_REVEAL_CONFIG at startup.
|
|
3193
|
+
float mappedZoom = pow(uRevealZoom, ${ZOOM_REVEAL_CONFIG.zoomCurveExp});
|
|
3194
|
+
vReveal = smoothstep(revealThreshold, revealThreshold + ${ZOOM_REVEAL_CONFIG.chapterFeather}, mappedZoom);
|
|
3111
3195
|
}
|
|
3112
3196
|
`,
|
|
3113
3197
|
fragmentShader: `
|
|
3114
3198
|
varying vec3 vColor;
|
|
3115
3199
|
varying float vSize;
|
|
3200
|
+
varying float vReveal;
|
|
3116
3201
|
void main() {
|
|
3117
3202
|
vec2 coord = gl_PointCoord - vec2(0.5);
|
|
3118
3203
|
float d = length(coord) * 2.0;
|
|
@@ -3141,7 +3226,8 @@ function createEngine({
|
|
|
3141
3226
|
float spikeV = exp(-coord.x * coord.x * 180.0) * exp(-abs(coord.y) * 6.0);
|
|
3142
3227
|
float spikes = (spikeH + spikeV) * 0.18 * spikeFactor;
|
|
3143
3228
|
|
|
3144
|
-
|
|
3229
|
+
// vReveal drives the additive contribution (AdditiveBlending uses SRC_ALPHA).
|
|
3230
|
+
gl_FragColor = vec4(finalColor * (k + spikes) * alphaMask, vReveal);
|
|
3145
3231
|
}
|
|
3146
3232
|
`,
|
|
3147
3233
|
transparent: true,
|
|
@@ -3646,20 +3732,23 @@ function createEngine({
|
|
|
3646
3732
|
constellationLayer.load(cfg.constellations, (id) => {
|
|
3647
3733
|
if (cfg.arrangement && cfg.arrangement[id]) {
|
|
3648
3734
|
const arr = cfg.arrangement[id];
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3735
|
+
const coords = arr.center ?? arr.position;
|
|
3736
|
+
if (coords) {
|
|
3737
|
+
if (coords[2] === 0) {
|
|
3738
|
+
const x = coords[0];
|
|
3739
|
+
const y = coords[1];
|
|
3740
|
+
const radius = cfg.layout?.radius ?? 2e3;
|
|
3741
|
+
const r_norm = Math.min(1, Math.sqrt(x * x + y * y) / radius);
|
|
3742
|
+
const phi = Math.atan2(y, x);
|
|
3743
|
+
const theta = r_norm * (Math.PI / 2);
|
|
3744
|
+
return new THREE6__namespace.Vector3(
|
|
3745
|
+
Math.sin(theta) * Math.cos(phi),
|
|
3746
|
+
Math.cos(theta),
|
|
3747
|
+
Math.sin(theta) * Math.sin(phi)
|
|
3748
|
+
).multiplyScalar(radius);
|
|
3749
|
+
}
|
|
3750
|
+
return new THREE6__namespace.Vector3(coords[0], coords[1], coords[2]);
|
|
3661
3751
|
}
|
|
3662
|
-
return new THREE6__namespace.Vector3(arr.position[0], arr.position[1], arr.position[2]);
|
|
3663
3752
|
}
|
|
3664
3753
|
return getLayoutPosition(id);
|
|
3665
3754
|
}, getLayoutPosition);
|
|
@@ -3687,7 +3776,7 @@ function createEngine({
|
|
|
3687
3776
|
arr[item.node.id] = { position: [item.obj.position.x, item.obj.position.y, item.obj.position.z] };
|
|
3688
3777
|
}
|
|
3689
3778
|
for (const item of constellationLayer.getItems()) {
|
|
3690
|
-
arr[item.config.id] = {
|
|
3779
|
+
arr[item.config.id] = { center: [item.center.x, item.center.y, item.center.z] };
|
|
3691
3780
|
}
|
|
3692
3781
|
Object.assign(arr, state.tempArrangement);
|
|
3693
3782
|
return arr;
|
|
@@ -3810,29 +3899,65 @@ function createEngine({
|
|
|
3810
3899
|
isMouseInWindow = false;
|
|
3811
3900
|
edgeHoverStart = 0;
|
|
3812
3901
|
}
|
|
3902
|
+
function screenSpacePickStar(mx, my, maxPx = 50) {
|
|
3903
|
+
if (!starPoints) return null;
|
|
3904
|
+
const attr = starPoints.geometry.attributes.position;
|
|
3905
|
+
const rect = renderer.domElement.getBoundingClientRect();
|
|
3906
|
+
const w = rect.width;
|
|
3907
|
+
const h = rect.height;
|
|
3908
|
+
const uScale = globalUniforms.uScale.value;
|
|
3909
|
+
const uAspect = globalUniforms.uAspect.value;
|
|
3910
|
+
let bestIdx = -1;
|
|
3911
|
+
let bestDist2 = maxPx * maxPx;
|
|
3912
|
+
const worldPos = new THREE6__namespace.Vector3();
|
|
3913
|
+
for (let i = 0; i < attr.count; i++) {
|
|
3914
|
+
worldPos.set(attr.getX(i), attr.getY(i), attr.getZ(i));
|
|
3915
|
+
const proj = smartProjectJS(worldPos);
|
|
3916
|
+
if (currentProjection.isClipped(proj.z)) continue;
|
|
3917
|
+
const sx = (proj.x * uScale / uAspect * 0.5 + 0.5) * w;
|
|
3918
|
+
const sy = (-(proj.y * uScale) * 0.5 + 0.5) * h;
|
|
3919
|
+
const dx = mx - sx;
|
|
3920
|
+
const dy = my - sy;
|
|
3921
|
+
const d2 = dx * dx + dy * dy;
|
|
3922
|
+
if (d2 < bestDist2) {
|
|
3923
|
+
bestDist2 = d2;
|
|
3924
|
+
bestIdx = i;
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
if (bestIdx < 0) return null;
|
|
3928
|
+
return {
|
|
3929
|
+
index: bestIdx,
|
|
3930
|
+
worldPos: new THREE6__namespace.Vector3(attr.getX(bestIdx), attr.getY(bestIdx), attr.getZ(bestIdx))
|
|
3931
|
+
};
|
|
3932
|
+
}
|
|
3813
3933
|
function onMouseDown(e) {
|
|
3814
3934
|
state.lastMouseX = e.clientX;
|
|
3815
3935
|
state.lastMouseY = e.clientY;
|
|
3816
3936
|
if (currentConfig?.editable) {
|
|
3937
|
+
const rect = renderer.domElement.getBoundingClientRect();
|
|
3938
|
+
const mX = e.clientX - rect.left;
|
|
3939
|
+
const mY = e.clientY - rect.top;
|
|
3940
|
+
const starHit = screenSpacePickStar(mX, mY);
|
|
3941
|
+
if (starHit) {
|
|
3942
|
+
state.dragMode = "node";
|
|
3943
|
+
state.draggedStarIndex = starHit.index;
|
|
3944
|
+
state.draggedNodeId = starIndexToId[starHit.index] ?? null;
|
|
3945
|
+
state.draggedDist = starHit.worldPos.length();
|
|
3946
|
+
state.draggedGroup = null;
|
|
3947
|
+
state.tempArrangement = {};
|
|
3948
|
+
state.velocityX = 0;
|
|
3949
|
+
state.velocityY = 0;
|
|
3950
|
+
return;
|
|
3951
|
+
}
|
|
3817
3952
|
const hit = pick(e);
|
|
3818
|
-
if (hit) {
|
|
3953
|
+
if (hit && (hit.type === "label" || hit.type === "constellation")) {
|
|
3819
3954
|
state.dragMode = "node";
|
|
3820
3955
|
state.draggedNodeId = hit.node.id;
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
const starWorldPos = new THREE6__namespace.Vector3(attr.getX(hit.index), attr.getY(hit.index), attr.getZ(hit.index));
|
|
3824
|
-
state.draggedDist = starWorldPos.length();
|
|
3825
|
-
} else {
|
|
3826
|
-
state.draggedDist = hit.point.length();
|
|
3827
|
-
}
|
|
3828
|
-
document.body.style.cursor = "crosshair";
|
|
3956
|
+
state.draggedDist = hit.point.length();
|
|
3957
|
+
state.draggedStarIndex = -1;
|
|
3829
3958
|
state.velocityX = 0;
|
|
3830
3959
|
state.velocityY = 0;
|
|
3831
|
-
if (hit.type === "
|
|
3832
|
-
state.draggedStarIndex = hit.index ?? -1;
|
|
3833
|
-
state.draggedGroup = null;
|
|
3834
|
-
state.tempArrangement = {};
|
|
3835
|
-
} else if (hit.type === "label") {
|
|
3960
|
+
if (hit.type === "label") {
|
|
3836
3961
|
const bookId = hit.node.id;
|
|
3837
3962
|
const children = [];
|
|
3838
3963
|
if (starPoints && starPoints.geometry.attributes.position) {
|
|
@@ -3847,12 +3972,21 @@ function createEngine({
|
|
|
3847
3972
|
}
|
|
3848
3973
|
}
|
|
3849
3974
|
}
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3975
|
+
const constellations = [];
|
|
3976
|
+
for (const cItem of constellationLayer.getItems()) {
|
|
3977
|
+
const anchored = cItem.config.anchors.some((anchorId) => {
|
|
3978
|
+
const n = nodeById.get(anchorId);
|
|
3979
|
+
return n?.parent === bookId;
|
|
3980
|
+
});
|
|
3981
|
+
if (anchored) {
|
|
3982
|
+
constellations.push({ id: cItem.config.id, initialCenter: cItem.center.clone() });
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
state.draggedGroup = { labelInitialPos: hit.object.position.clone(), children, constellations };
|
|
3986
|
+
} else {
|
|
3987
|
+
state.draggedGroup = { labelInitialPos: hit.point.clone(), children: [], constellations: [] };
|
|
3855
3988
|
}
|
|
3989
|
+
return;
|
|
3856
3990
|
}
|
|
3857
3991
|
return;
|
|
3858
3992
|
}
|
|
@@ -3879,7 +4013,6 @@ function createEngine({
|
|
|
3879
4013
|
const attr = starPoints.geometry.attributes.position;
|
|
3880
4014
|
attr.setXYZ(idx, newPos.x, newPos.y, newPos.z);
|
|
3881
4015
|
attr.needsUpdate = true;
|
|
3882
|
-
editHoverTargetPos = newPos.clone();
|
|
3883
4016
|
const starId = starIndexToId[idx];
|
|
3884
4017
|
if (starId) state.tempArrangement[starId] = { position: [newPos.x, newPos.y, newPos.z] };
|
|
3885
4018
|
} else if (state.draggedGroup && state.draggedNodeId) {
|
|
@@ -3895,7 +4028,7 @@ function createEngine({
|
|
|
3895
4028
|
const vE = newPos.clone().normalize();
|
|
3896
4029
|
cItem.mesh.quaternion.setFromUnitVectors(vS, vE);
|
|
3897
4030
|
cItem.center.copy(newPos);
|
|
3898
|
-
state.tempArrangement[state.draggedNodeId] = {
|
|
4031
|
+
state.tempArrangement[state.draggedNodeId] = { center: [newPos.x, newPos.y, newPos.z] };
|
|
3899
4032
|
}
|
|
3900
4033
|
}
|
|
3901
4034
|
const vStart = group.labelInitialPos.clone().normalize();
|
|
@@ -3914,6 +4047,20 @@ function createEngine({
|
|
|
3914
4047
|
}
|
|
3915
4048
|
attr.needsUpdate = true;
|
|
3916
4049
|
}
|
|
4050
|
+
if (group.constellations.length > 0) {
|
|
4051
|
+
for (const { id, initialCenter } of group.constellations) {
|
|
4052
|
+
const cItem = constellationLayer.getItems().find((c) => c.config.id === id);
|
|
4053
|
+
if (cItem) {
|
|
4054
|
+
const newCenter = initialCenter.clone().applyQuaternion(q);
|
|
4055
|
+
cItem.center.copy(newCenter);
|
|
4056
|
+
cItem.mesh.quaternion.setFromUnitVectors(
|
|
4057
|
+
initialCenter.clone().normalize(),
|
|
4058
|
+
newCenter.clone().normalize()
|
|
4059
|
+
);
|
|
4060
|
+
state.tempArrangement[id] = { center: [newCenter.x, newCenter.y, newCenter.z] };
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
3917
4064
|
}
|
|
3918
4065
|
} else if (state.dragMode === "camera") {
|
|
3919
4066
|
const deltaX = e.clientX - state.lastMouseX;
|
|
@@ -3921,13 +4068,15 @@ function createEngine({
|
|
|
3921
4068
|
state.lastMouseX = e.clientX;
|
|
3922
4069
|
state.lastMouseY = e.clientY;
|
|
3923
4070
|
const speedScale = state.fov / ENGINE_CONFIG.defaultFov;
|
|
3924
|
-
const
|
|
3925
|
-
const
|
|
3926
|
-
|
|
3927
|
-
|
|
4071
|
+
const latFactor = getVerticalPanFactor(state.fov, state.lat);
|
|
4072
|
+
const massFactor = getMovementMassFactor(state.fov);
|
|
4073
|
+
const moveX = compressInputDelta(deltaX) * massFactor;
|
|
4074
|
+
const moveY = compressInputDelta(deltaY) * massFactor;
|
|
4075
|
+
state.targetLon += moveX * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
4076
|
+
state.targetLat += moveY * ENGINE_CONFIG.dragSpeed * speedScale * latFactor;
|
|
3928
4077
|
state.targetLat = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, state.targetLat));
|
|
3929
|
-
state.velocityX =
|
|
3930
|
-
state.velocityY =
|
|
4078
|
+
state.velocityX = moveX * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
4079
|
+
state.velocityY = moveY * ENGINE_CONFIG.dragSpeed * speedScale * latFactor;
|
|
3931
4080
|
state.lon = state.targetLon;
|
|
3932
4081
|
state.lat = state.targetLat;
|
|
3933
4082
|
} else {
|
|
@@ -3947,26 +4096,17 @@ function createEngine({
|
|
|
3947
4096
|
hoverLabelMesh.position.copy(hit.point);
|
|
3948
4097
|
hoverLabelMat.uniforms.uAlpha.value = 1;
|
|
3949
4098
|
hoverLabelMesh.visible = true;
|
|
3950
|
-
if (currentConfig?.editable && hit.type === "star" && hit.index !== void 0 && starPoints) {
|
|
3951
|
-
const attr = starPoints.geometry.attributes.position;
|
|
3952
|
-
editHoverTargetPos = new THREE6__namespace.Vector3(attr.getX(hit.index), attr.getY(hit.index), attr.getZ(hit.index));
|
|
3953
|
-
} else if (currentConfig?.editable && hit.type === "star") {
|
|
3954
|
-
editHoverTargetPos = hit.point.clone();
|
|
3955
|
-
}
|
|
3956
4099
|
} else {
|
|
3957
4100
|
currentHoverNodeId = null;
|
|
3958
4101
|
hoverLabelMat.uniforms.uAlpha.value = 0;
|
|
3959
4102
|
hoverLabelMesh.visible = false;
|
|
3960
|
-
if (currentConfig?.editable && state.dragMode !== "node") {
|
|
3961
|
-
editHoverTargetPos = null;
|
|
3962
|
-
}
|
|
3963
4103
|
}
|
|
3964
4104
|
if (hit?.node.id !== handlers._lastHoverId) {
|
|
3965
4105
|
handlers._lastHoverId = hit?.node.id;
|
|
3966
4106
|
handlers.onHover?.(hit?.node);
|
|
3967
4107
|
constellationLayer.setHovered(hit?.node.id ?? null);
|
|
3968
4108
|
}
|
|
3969
|
-
document.body.style.cursor = hit ? currentConfig?.editable ? "
|
|
4109
|
+
document.body.style.cursor = hit ? currentConfig?.editable && hit.type === "star" ? "grab" : "pointer" : "default";
|
|
3970
4110
|
}
|
|
3971
4111
|
}
|
|
3972
4112
|
function onMouseUp(e) {
|
|
@@ -3976,7 +4116,6 @@ function createEngine({
|
|
|
3976
4116
|
if (state.dragMode === "node") {
|
|
3977
4117
|
const fullArr = getFullArrangement();
|
|
3978
4118
|
handlers.onArrangementChange?.(fullArr);
|
|
3979
|
-
editDropFlash = 1;
|
|
3980
4119
|
state.dragMode = "none";
|
|
3981
4120
|
state.draggedNodeId = null;
|
|
3982
4121
|
state.draggedStarIndex = -1;
|
|
@@ -4019,20 +4158,30 @@ function createEngine({
|
|
|
4019
4158
|
const aspect = container.clientWidth / container.clientHeight;
|
|
4020
4159
|
renderer.domElement.getBoundingClientRect();
|
|
4021
4160
|
const vBefore = getMouseViewVector(state.fov, aspect);
|
|
4022
|
-
const
|
|
4161
|
+
const zoomResistance = THREE6__namespace.MathUtils.lerp(
|
|
4162
|
+
1,
|
|
4163
|
+
ENGINE_CONFIG.zoomResistanceWideFov,
|
|
4164
|
+
THREE6__namespace.MathUtils.smoothstep(state.fov, 24, 100)
|
|
4165
|
+
);
|
|
4166
|
+
const zoomSpeed = 1e-3 * state.fov * zoomResistance;
|
|
4023
4167
|
state.fov += e.deltaY * zoomSpeed;
|
|
4024
4168
|
state.fov = Math.max(ENGINE_CONFIG.minFov, Math.min(ENGINE_CONFIG.maxFov, state.fov));
|
|
4025
4169
|
handlers.onFovChange?.(state.fov);
|
|
4026
4170
|
updateUniforms();
|
|
4027
4171
|
const vAfter = getMouseViewVector(state.fov, aspect);
|
|
4028
4172
|
const quaternion = new THREE6__namespace.Quaternion().setFromUnitVectors(vAfter, vBefore);
|
|
4029
|
-
const dampStartFov =
|
|
4030
|
-
const dampEndFov =
|
|
4173
|
+
const dampStartFov = 32;
|
|
4174
|
+
const dampEndFov = 110;
|
|
4031
4175
|
let spinAmount = 1;
|
|
4032
4176
|
if (state.fov > dampStartFov) {
|
|
4033
4177
|
const t = Math.max(0, Math.min(1, (state.fov - dampStartFov) / (dampEndFov - dampStartFov)));
|
|
4034
|
-
spinAmount = 1 - Math.pow(t, 1.
|
|
4178
|
+
spinAmount = 1 - Math.pow(t, 1.35) * 0.92;
|
|
4035
4179
|
}
|
|
4180
|
+
const blendForSpin = getBlendForZenithControl();
|
|
4181
|
+
const blendSpinDamp = THREE6__namespace.MathUtils.smoothstep(blendForSpin, 0.58, 0.9);
|
|
4182
|
+
spinAmount *= 1 - 0.88 * blendSpinDamp;
|
|
4183
|
+
if (zenithProjectionLockActive) spinAmount = Math.min(spinAmount, 0.02);
|
|
4184
|
+
spinAmount = Math.max(0.02, Math.min(1, spinAmount));
|
|
4036
4185
|
if (spinAmount < 0.999) {
|
|
4037
4186
|
const identityQuat = new THREE6__namespace.Quaternion();
|
|
4038
4187
|
quaternion.slerp(identityQuat, 1 - spinAmount);
|
|
@@ -4054,9 +4203,10 @@ function createEngine({
|
|
|
4054
4203
|
state.lon = Math.atan2(newForward.x, -newForward.z);
|
|
4055
4204
|
const newUp = new THREE6__namespace.Vector3(0, 1, 0).applyQuaternion(qNew);
|
|
4056
4205
|
camera.up.copy(newUp);
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4206
|
+
const zenithBiasStartFov = getZenithBiasStartFov();
|
|
4207
|
+
if (!zenithProjectionLockActive && !getSceneDebug()?.disableZenithBias && !isInTransitionFreezeBand(state.fov) && e.deltaY > 0 && state.fov > zenithBiasStartFov) {
|
|
4208
|
+
const range = ENGINE_CONFIG.maxFov - zenithBiasStartFov;
|
|
4209
|
+
let t = (state.fov - zenithBiasStartFov) / range;
|
|
4060
4210
|
t = Math.max(0, Math.min(1, t));
|
|
4061
4211
|
const bias = ENGINE_CONFIG.zenithStrength * t;
|
|
4062
4212
|
const zenithLat = Math.PI / 2 - 1e-3;
|
|
@@ -4148,13 +4298,15 @@ function createEngine({
|
|
|
4148
4298
|
}
|
|
4149
4299
|
}
|
|
4150
4300
|
const speedScale = state.fov / ENGINE_CONFIG.defaultFov;
|
|
4151
|
-
const
|
|
4152
|
-
const
|
|
4153
|
-
|
|
4154
|
-
|
|
4301
|
+
const latFactor = getVerticalPanFactor(state.fov, state.lat);
|
|
4302
|
+
const massFactor = getMovementMassFactor(state.fov);
|
|
4303
|
+
const moveX = compressInputDelta(deltaX) * massFactor;
|
|
4304
|
+
const moveY = compressInputDelta(deltaY) * massFactor;
|
|
4305
|
+
state.targetLon += moveX * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
4306
|
+
state.targetLat += moveY * ENGINE_CONFIG.dragSpeed * speedScale * latFactor;
|
|
4155
4307
|
state.targetLat = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, state.targetLat));
|
|
4156
|
-
state.velocityX =
|
|
4157
|
-
state.velocityY =
|
|
4308
|
+
state.velocityX = moveX * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
4309
|
+
state.velocityY = moveY * ENGINE_CONFIG.dragSpeed * speedScale * latFactor;
|
|
4158
4310
|
state.lon = state.targetLon;
|
|
4159
4311
|
state.lat = state.targetLat;
|
|
4160
4312
|
} else if (touches.length === 2) {
|
|
@@ -4166,9 +4318,10 @@ function createEngine({
|
|
|
4166
4318
|
state.fov = state.pinchStartFov / scale;
|
|
4167
4319
|
state.fov = Math.max(ENGINE_CONFIG.minFov, Math.min(ENGINE_CONFIG.maxFov, state.fov));
|
|
4168
4320
|
handlers.onFovChange?.(state.fov);
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4321
|
+
const zenithBiasStartFov = getZenithBiasStartFov();
|
|
4322
|
+
if (!zenithProjectionLockActive && !getSceneDebug()?.disableZenithBias && !isInTransitionFreezeBand(state.fov) && state.fov > prevFov && state.fov > zenithBiasStartFov) {
|
|
4323
|
+
const range = ENGINE_CONFIG.maxFov - zenithBiasStartFov;
|
|
4324
|
+
let t = (state.fov - zenithBiasStartFov) / range;
|
|
4172
4325
|
t = Math.max(0, Math.min(1, t));
|
|
4173
4326
|
const bias = ENGINE_CONFIG.zenithStrength * t;
|
|
4174
4327
|
const zenithLat = Math.PI / 2 - 1e-3;
|
|
@@ -4181,8 +4334,12 @@ function createEngine({
|
|
|
4181
4334
|
state.lastMouseX = center.x;
|
|
4182
4335
|
state.lastMouseY = center.y;
|
|
4183
4336
|
const speedScale = state.fov / ENGINE_CONFIG.defaultFov;
|
|
4184
|
-
|
|
4185
|
-
|
|
4337
|
+
const latFactor = getVerticalPanFactor(state.fov, state.lat);
|
|
4338
|
+
const massFactor = getMovementMassFactor(state.fov);
|
|
4339
|
+
const moveX = compressInputDelta(deltaX) * massFactor;
|
|
4340
|
+
const moveY = compressInputDelta(deltaY) * massFactor;
|
|
4341
|
+
state.targetLon += moveX * ENGINE_CONFIG.dragSpeed * speedScale * 0.5;
|
|
4342
|
+
state.targetLat += moveY * ENGINE_CONFIG.dragSpeed * speedScale * 0.5 * latFactor;
|
|
4186
4343
|
state.targetLat = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, state.targetLat));
|
|
4187
4344
|
state.lon = state.targetLon;
|
|
4188
4345
|
state.lat = state.targetLat;
|
|
@@ -4345,7 +4502,9 @@ function createEngine({
|
|
|
4345
4502
|
if (inZoneX || inZoneY) {
|
|
4346
4503
|
if (edgeHoverStart === 0) edgeHoverStart = performance.now();
|
|
4347
4504
|
if (performance.now() - edgeHoverStart > ENGINE_CONFIG.edgePanDelay) {
|
|
4348
|
-
const
|
|
4505
|
+
const edgeMassFactor = getMovementMassFactor(state.fov, ENGINE_CONFIG.edgePanMassWideFov);
|
|
4506
|
+
const speedBase = ENGINE_CONFIG.edgePanMaxSpeed * (state.fov / ENGINE_CONFIG.defaultFov) * edgeMassFactor;
|
|
4507
|
+
const verticalPanFactor = getVerticalPanFactor(state.fov, state.lat);
|
|
4349
4508
|
if (mouseNDC.x < -1 + t) {
|
|
4350
4509
|
const s = (-1 + t - mouseNDC.x) / t;
|
|
4351
4510
|
panX = -s * s * speedBase;
|
|
@@ -4355,10 +4514,10 @@ function createEngine({
|
|
|
4355
4514
|
}
|
|
4356
4515
|
if (mouseNDC.y < -1 + t) {
|
|
4357
4516
|
const s = (-1 + t - mouseNDC.y) / t;
|
|
4358
|
-
panY = -s * s * speedBase;
|
|
4517
|
+
panY = -s * s * speedBase * verticalPanFactor;
|
|
4359
4518
|
} else if (mouseNDC.y > 1 - t) {
|
|
4360
4519
|
const s = (mouseNDC.y - (1 - t)) / t;
|
|
4361
|
-
panY = s * s * speedBase;
|
|
4520
|
+
panY = s * s * speedBase * verticalPanFactor;
|
|
4362
4521
|
}
|
|
4363
4522
|
}
|
|
4364
4523
|
} else {
|
|
@@ -4390,14 +4549,32 @@ function createEngine({
|
|
|
4390
4549
|
state.targetLat = state.lat;
|
|
4391
4550
|
} else if (!state.isDragging && !flyToActive) {
|
|
4392
4551
|
state.lon += state.velocityX;
|
|
4552
|
+
state.velocityY *= getVerticalPanFactor(state.fov, state.lat);
|
|
4393
4553
|
state.lat += state.velocityY;
|
|
4394
|
-
const
|
|
4554
|
+
const baseDamping = isTouchDevice ? ENGINE_CONFIG.touchInertiaDamping : ENGINE_CONFIG.inertiaDamping;
|
|
4555
|
+
const speed = Math.hypot(state.velocityX, state.velocityY);
|
|
4556
|
+
const damping = speed < ENGINE_CONFIG.lowSpeedVelocityThreshold ? Math.min(baseDamping, ENGINE_CONFIG.lowSpeedInertiaDamping) : baseDamping;
|
|
4395
4557
|
state.velocityX *= damping;
|
|
4396
4558
|
state.velocityY *= damping;
|
|
4397
|
-
if (Math.abs(state.velocityX) <
|
|
4398
|
-
if (Math.abs(state.velocityY) <
|
|
4559
|
+
if (Math.abs(state.velocityX) < ENGINE_CONFIG.velocityStopThreshold) state.velocityX = 0;
|
|
4560
|
+
if (Math.abs(state.velocityY) < ENGINE_CONFIG.velocityStopThreshold) state.velocityY = 0;
|
|
4399
4561
|
}
|
|
4400
4562
|
state.lat = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, state.lat));
|
|
4563
|
+
if (!flyToActive) {
|
|
4564
|
+
const latDeg = THREE6__namespace.MathUtils.radToDeg(state.lat);
|
|
4565
|
+
if (latDeg < HORIZON_ZOOM_CONFIG.latStartDeg) {
|
|
4566
|
+
const t = THREE6__namespace.MathUtils.clamp(latDeg / HORIZON_ZOOM_CONFIG.latStartDeg, 0, 1);
|
|
4567
|
+
const maxFov = THREE6__namespace.MathUtils.lerp(
|
|
4568
|
+
HORIZON_ZOOM_CONFIG.safeFovAtHorizon,
|
|
4569
|
+
ENGINE_CONFIG.maxFov,
|
|
4570
|
+
t
|
|
4571
|
+
);
|
|
4572
|
+
if (state.fov > maxFov) {
|
|
4573
|
+
state.fov = THREE6__namespace.MathUtils.lerp(state.fov, maxFov, HORIZON_ZOOM_CONFIG.lerpRate);
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
applyZenithAutoCenter();
|
|
4401
4578
|
const y = Math.sin(state.lat);
|
|
4402
4579
|
const r = Math.cos(state.lat);
|
|
4403
4580
|
const x = r * Math.sin(state.lon);
|
|
@@ -4410,11 +4587,13 @@ function createEngine({
|
|
|
4410
4587
|
camera.updateMatrixWorld();
|
|
4411
4588
|
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
|
|
4412
4589
|
if (groundMaterial?.uniforms?.uZenithFlatten) {
|
|
4413
|
-
const
|
|
4590
|
+
const targetFlatten = getSceneDebug()?.disableZenithFlatten ? 0 : THREE6__namespace.MathUtils.smoothstep(
|
|
4414
4591
|
state.lat,
|
|
4415
4592
|
THREE6__namespace.MathUtils.degToRad(68),
|
|
4416
4593
|
THREE6__namespace.MathUtils.degToRad(88)
|
|
4417
4594
|
);
|
|
4595
|
+
const prevFlatten = Number(groundMaterial.uniforms.uZenithFlatten.value ?? 0);
|
|
4596
|
+
const flatten = isInTransitionFreezeBand(state.fov) ? THREE6__namespace.MathUtils.clamp(targetFlatten, prevFlatten - 0.01, prevFlatten + 0.01) : targetFlatten;
|
|
4418
4597
|
groundMaterial.uniforms.uZenithFlatten.value = flatten;
|
|
4419
4598
|
}
|
|
4420
4599
|
updateUniforms();
|
|
@@ -4431,6 +4610,11 @@ function createEngine({
|
|
|
4431
4610
|
const baseArtOpacity = THREE6__namespace.MathUtils.clamp(currentConfig?.constellationBaseOpacity ?? 1, 0, 300);
|
|
4432
4611
|
constellationLayer.setGlobalOpacity?.(artFader.eased * baseArtOpacity);
|
|
4433
4612
|
backdropGroup.visible = currentConfig?.showBackdropStars ?? true;
|
|
4613
|
+
const revealZoom = currentConfig?.starZoomReveal ?? true ? THREE6__namespace.MathUtils.clamp(
|
|
4614
|
+
(ZOOM_REVEAL_CONFIG.wideFov - state.fov) / (ZOOM_REVEAL_CONFIG.wideFov - ZOOM_REVEAL_CONFIG.narrowFov),
|
|
4615
|
+
0,
|
|
4616
|
+
1
|
|
4617
|
+
) : 1;
|
|
4434
4618
|
if (backdropStarsMaterial?.uniforms) {
|
|
4435
4619
|
const minGain = THREE6__namespace.MathUtils.clamp(currentConfig?.backdropWideFovGain ?? 0.42, 0, 1);
|
|
4436
4620
|
const fovT = THREE6__namespace.MathUtils.smoothstep(state.fov, 24, 100);
|
|
@@ -4438,6 +4622,11 @@ function createEngine({
|
|
|
4438
4622
|
backdropStarsMaterial.uniforms.uBackdropGain.value = gain;
|
|
4439
4623
|
backdropStarsMaterial.uniforms.uBackdropEnergy.value = THREE6__namespace.MathUtils.clamp(currentConfig?.backdropEnergy ?? 2.2, 0.2, 5);
|
|
4440
4624
|
backdropStarsMaterial.uniforms.uBackdropSizeExp.value = THREE6__namespace.MathUtils.clamp(currentConfig?.backdropSizeExponent ?? 0.9, 0.4, 1.4);
|
|
4625
|
+
backdropStarsMaterial.uniforms.uRevealZoom.value = revealZoom;
|
|
4626
|
+
}
|
|
4627
|
+
if (starPoints?.material) {
|
|
4628
|
+
const sm = starPoints.material;
|
|
4629
|
+
if (sm.uniforms.uRevealZoom) sm.uniforms.uRevealZoom.value = revealZoom;
|
|
4441
4630
|
}
|
|
4442
4631
|
if (skyBackgroundMesh) skyBackgroundMesh.visible = currentConfig?.background !== "transparent";
|
|
4443
4632
|
if (atmosphereMesh) atmosphereMesh.visible = currentConfig?.showAtmosphere ?? false;
|
|
@@ -4447,32 +4636,6 @@ function createEngine({
|
|
|
4447
4636
|
if (sunDiscMesh) sunDiscMesh.visible = showSun;
|
|
4448
4637
|
if (sunHaloMesh) sunHaloMesh.visible = showSun;
|
|
4449
4638
|
if (milkyWayMesh) milkyWayMesh.visible = currentConfig?.showMilkyWay ?? true;
|
|
4450
|
-
if (editHoverMesh) {
|
|
4451
|
-
const ringMat = editHoverMesh.material;
|
|
4452
|
-
const isEditing = currentConfig?.editable ?? false;
|
|
4453
|
-
const isDraggingStar = state.dragMode === "node" && state.draggedStarIndex !== -1;
|
|
4454
|
-
const hasTarget = isEditing && editHoverTargetPos !== null;
|
|
4455
|
-
if (hasTarget) {
|
|
4456
|
-
editHoverMesh.position.copy(editHoverTargetPos);
|
|
4457
|
-
const pulseBoost = editDropFlash * 1.8;
|
|
4458
|
-
const targetAlpha = 0.8 + pulseBoost;
|
|
4459
|
-
ringMat.uniforms.uRingAlpha.value = THREE6__namespace.MathUtils.lerp(ringMat.uniforms.uRingAlpha.value, targetAlpha, 0.15);
|
|
4460
|
-
const tGold = isDraggingStar ? 1 : editDropFlash;
|
|
4461
|
-
const targetColor = new THREE6__namespace.Color(
|
|
4462
|
-
THREE6__namespace.MathUtils.lerp(0.55, 1, tGold),
|
|
4463
|
-
THREE6__namespace.MathUtils.lerp(0.88, 0.82, tGold),
|
|
4464
|
-
THREE6__namespace.MathUtils.lerp(1, 0.18, tGold)
|
|
4465
|
-
);
|
|
4466
|
-
ringMat.uniforms.uRingColor.value.lerp(targetColor, 0.18);
|
|
4467
|
-
const baseSize = isDraggingStar ? 0.075 : 0.06;
|
|
4468
|
-
const targetSize = baseSize * (1 + editDropFlash * 0.7);
|
|
4469
|
-
ringMat.uniforms.uRingSize.value = THREE6__namespace.MathUtils.lerp(ringMat.uniforms.uRingSize.value, targetSize, 0.18);
|
|
4470
|
-
editDropFlash = Math.max(0, editDropFlash - dt * 3);
|
|
4471
|
-
} else {
|
|
4472
|
-
ringMat.uniforms.uRingAlpha.value = THREE6__namespace.MathUtils.lerp(ringMat.uniforms.uRingAlpha.value, 0, 0.15);
|
|
4473
|
-
ringMat.uniforms.uRingSize.value = THREE6__namespace.MathUtils.lerp(ringMat.uniforms.uRingSize.value, 0.06, 0.2);
|
|
4474
|
-
}
|
|
4475
|
-
}
|
|
4476
4639
|
if (constellationLines) {
|
|
4477
4640
|
constellationLines.visible = linesFader.eased > 0.01;
|
|
4478
4641
|
if (constellationLines.visible && constellationLines.material) {
|
|
@@ -4580,12 +4743,6 @@ function createEngine({
|
|
|
4580
4743
|
skyBackgroundMesh.material.dispose();
|
|
4581
4744
|
skyBackgroundMesh = null;
|
|
4582
4745
|
}
|
|
4583
|
-
if (editHoverMesh) {
|
|
4584
|
-
scene.remove(editHoverMesh);
|
|
4585
|
-
editHoverMesh.geometry.dispose();
|
|
4586
|
-
editHoverMesh.material.dispose();
|
|
4587
|
-
editHoverMesh = null;
|
|
4588
|
-
}
|
|
4589
4746
|
renderer.dispose();
|
|
4590
4747
|
renderer.domElement.remove();
|
|
4591
4748
|
}
|
|
@@ -4628,7 +4785,7 @@ function createEngine({
|
|
|
4628
4785
|
}
|
|
4629
4786
|
return { setConfig, start, stop, dispose, setHandlers, getFullArrangement, setHoveredBook, setFocusedBook, setOrderRevealEnabled, setHierarchyFilter, flyTo, setProjection };
|
|
4630
4787
|
}
|
|
4631
|
-
var ENGINE_CONFIG, ORDER_REVEAL_CONFIG;
|
|
4788
|
+
var ENGINE_CONFIG, ORDER_REVEAL_CONFIG, HORIZON_ZOOM_CONFIG, ZOOM_REVEAL_CONFIG;
|
|
4632
4789
|
var init_createEngine = __esm({
|
|
4633
4790
|
"src/engine/createEngine.ts"() {
|
|
4634
4791
|
init_layout();
|
|
@@ -4643,8 +4800,28 @@ var init_createEngine = __esm({
|
|
|
4643
4800
|
defaultFov: 35,
|
|
4644
4801
|
dragSpeed: 125e-5,
|
|
4645
4802
|
inertiaDamping: 0.92,
|
|
4803
|
+
lowSpeedInertiaDamping: 0.78,
|
|
4804
|
+
lowSpeedVelocityThreshold: 25e-4,
|
|
4805
|
+
velocityStopThreshold: 4e-5,
|
|
4806
|
+
zoomResistanceWideFov: 0.82,
|
|
4807
|
+
movementMassWideFov: 0.74,
|
|
4808
|
+
edgePanMassWideFov: 0.68,
|
|
4809
|
+
inputCompression: 0.018,
|
|
4646
4810
|
blendStart: 35,
|
|
4647
4811
|
blendEnd: 83,
|
|
4812
|
+
freezeBandStartFov: 76,
|
|
4813
|
+
freezeBandEndFov: 84,
|
|
4814
|
+
zenithBiasStartFov: 85,
|
|
4815
|
+
zenithLockBlendEnter: 0.9,
|
|
4816
|
+
zenithLockBlendExit: 0.8,
|
|
4817
|
+
zenithAutoCenterBlendStart: 0.62,
|
|
4818
|
+
zenithAutoCenterBlendEnd: 0.9,
|
|
4819
|
+
zenithAutoCenterMinLerp: 0.012,
|
|
4820
|
+
zenithAutoCenterMaxLerp: 0.16,
|
|
4821
|
+
verticalPanDampStartFov: 72,
|
|
4822
|
+
verticalPanDampEndFov: 96,
|
|
4823
|
+
verticalPanDampLatStartDeg: 45,
|
|
4824
|
+
verticalPanDampLatEndDeg: 82,
|
|
4648
4825
|
zenithStartFov: 75,
|
|
4649
4826
|
zenithStrength: 0.15,
|
|
4650
4827
|
horizonLockStrength: 0.05,
|
|
@@ -4671,6 +4848,30 @@ var init_createEngine = __esm({
|
|
|
4671
4848
|
pulseDuration: 2,
|
|
4672
4849
|
delayPerChapter: 0.1
|
|
4673
4850
|
};
|
|
4851
|
+
HORIZON_ZOOM_CONFIG = {
|
|
4852
|
+
latStartDeg: 20,
|
|
4853
|
+
// coupling is fully off above this elevation
|
|
4854
|
+
safeFovAtHorizon: 60,
|
|
4855
|
+
// max FOV at the horizon (below freeze-band threshold)
|
|
4856
|
+
lerpRate: 0.03
|
|
4857
|
+
// gentle — should feel like a natural breathing-in
|
|
4858
|
+
};
|
|
4859
|
+
ZOOM_REVEAL_CONFIG = {
|
|
4860
|
+
wideFov: 120,
|
|
4861
|
+
// above this FOV, revealZoom = 0 (nothing new revealed)
|
|
4862
|
+
narrowFov: 8,
|
|
4863
|
+
// below this FOV, revealZoom = 1 (everything visible)
|
|
4864
|
+
zoomCurveExp: 1.8,
|
|
4865
|
+
// non-linear curve exponent (try 1.5 – 2.5)
|
|
4866
|
+
chapterRevealMax: 0.5,
|
|
4867
|
+
// faintest chapter star threshold — visible by ~fov 35
|
|
4868
|
+
chapterFeather: 0.1,
|
|
4869
|
+
// smoothstep width for chapter star fade-in
|
|
4870
|
+
backdropRevealStart: 0.4,
|
|
4871
|
+
// backdrop starts appearing at this mappedZoom
|
|
4872
|
+
backdropRevealEnd: 0.65
|
|
4873
|
+
// backdrop fully visible at this mappedZoom
|
|
4874
|
+
};
|
|
4674
4875
|
}
|
|
4675
4876
|
});
|
|
4676
4877
|
var StarMap = react.forwardRef(
|