@project-skymap/library 0.5.0 → 0.6.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/dist/index.cjs +645 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +130 -79
- package/dist/index.d.ts +130 -79
- package/dist/index.js +646 -178
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -319,45 +319,60 @@ var init_shaders = __esm({
|
|
|
319
319
|
uniform float uScale;
|
|
320
320
|
uniform float uAspect;
|
|
321
321
|
uniform float uBlend;
|
|
322
|
+
uniform int uProjectionType;
|
|
322
323
|
|
|
323
324
|
vec4 smartProject(vec4 viewPos) {
|
|
324
325
|
vec3 dir = normalize(viewPos.xyz);
|
|
325
326
|
float dist = length(viewPos.xyz);
|
|
326
|
-
float
|
|
327
|
-
|
|
328
|
-
float kLinear = 1.0 / zLinear;
|
|
329
|
-
float k = mix(kLinear, kStereo, uBlend);
|
|
330
|
-
vec2 projected = vec2(k * dir.x, k * dir.y);
|
|
331
|
-
projected *= uScale;
|
|
332
|
-
projected.x /= uAspect;
|
|
333
|
-
float zMetric = -1.0 + (dist / 15000.0);
|
|
334
|
-
|
|
327
|
+
float k;
|
|
328
|
+
|
|
335
329
|
// Radial Clipping: Push clipped points off-screen in their natural direction
|
|
336
330
|
// to prevent lines "darting" across the center.
|
|
337
331
|
vec2 escapeDir = (length(dir.xy) > 0.0001) ? normalize(dir.xy) : vec2(1.0, 1.0);
|
|
338
332
|
vec2 escapePos = escapeDir * 10000.0;
|
|
339
333
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
334
|
+
if (uProjectionType == 0) {
|
|
335
|
+
// Perspective
|
|
336
|
+
if (dir.z > -0.1) return vec4(escapePos, 10.0, 1.0);
|
|
337
|
+
k = 1.0 / max(0.01, -dir.z);
|
|
338
|
+
} else if (uProjectionType == 1) {
|
|
339
|
+
// Stereographic \u2014 tighter clip to prevent stretch near singularity
|
|
340
|
+
if (dir.z > 0.1) return vec4(escapePos, 10.0, 1.0);
|
|
341
|
+
k = 2.0 / (1.0 - dir.z);
|
|
342
|
+
} else {
|
|
343
|
+
// Blended (auto-blend behavior)
|
|
344
|
+
float zLinear = max(0.01, -dir.z);
|
|
345
|
+
float kStereo = 2.0 / (1.0 - dir.z);
|
|
346
|
+
float kLinear = 1.0 / zLinear;
|
|
347
|
+
k = mix(kLinear, kStereo, uBlend);
|
|
348
|
+
|
|
349
|
+
// Tighter clip threshold that scales with blend factor
|
|
350
|
+
float clipZ = mix(-0.1, 0.1, uBlend);
|
|
351
|
+
if (dir.z > clipZ) return vec4(escapePos, 10.0, 1.0);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
vec2 projected = vec2(k * dir.x, k * dir.y);
|
|
355
|
+
projected *= uScale;
|
|
356
|
+
projected.x /= uAspect;
|
|
357
|
+
float zMetric = -1.0 + (dist / 15000.0);
|
|
358
|
+
|
|
345
359
|
return vec4(projected, zMetric, 1.0);
|
|
346
360
|
}
|
|
347
361
|
`;
|
|
348
362
|
MASK_CHUNK = `
|
|
349
363
|
uniform float uAspect;
|
|
350
364
|
uniform float uBlend;
|
|
365
|
+
uniform int uProjectionType;
|
|
351
366
|
varying vec2 vScreenPos;
|
|
352
367
|
float getMaskAlpha() {
|
|
353
|
-
|
|
368
|
+
// No artificial circular mask \u2014 the horizon, atmosphere, and ground
|
|
369
|
+
// define the dome boundary naturally (as Stellarium does).
|
|
370
|
+
// Only apply a minimal edge softening to catch stray back-face artifacts.
|
|
354
371
|
vec2 p = vScreenPos;
|
|
355
372
|
p.x *= uAspect;
|
|
356
373
|
float dist = length(p);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
float edgeSoftness = mix(0.5, 0.02, t);
|
|
360
|
-
return 1.0 - smoothstep(currentRadius - edgeSoftness, currentRadius, dist);
|
|
374
|
+
// Gentle falloff only at extreme screen edges (beyond NDC ~1.8)
|
|
375
|
+
return 1.0 - smoothstep(1.8, 2.0, dist);
|
|
361
376
|
}
|
|
362
377
|
`;
|
|
363
378
|
}
|
|
@@ -390,13 +405,15 @@ var init_materials = __esm({
|
|
|
390
405
|
uScale: { value: 1 },
|
|
391
406
|
uAspect: { value: 1 },
|
|
392
407
|
uBlend: { value: 0 },
|
|
408
|
+
uProjectionType: { value: 2 },
|
|
409
|
+
// 0=perspective, 1=stereographic, 2=blended
|
|
393
410
|
uTime: { value: 0 },
|
|
394
411
|
// Atmosphere Settings
|
|
395
412
|
uAtmGlow: { value: 1 },
|
|
396
413
|
uAtmDark: { value: 0.6 },
|
|
397
414
|
uAtmExtinction: { value: 4 },
|
|
398
415
|
uAtmTwinkle: { value: 0.8 },
|
|
399
|
-
uColorHorizon: { value: new THREE5.Color(
|
|
416
|
+
uColorHorizon: { value: new THREE5.Color(3825292) },
|
|
400
417
|
uColorZenith: { value: new THREE5.Color(132104) }
|
|
401
418
|
};
|
|
402
419
|
}
|
|
@@ -595,6 +612,10 @@ var init_ConstellationArtworkLayer = __esm({
|
|
|
595
612
|
this.items.push({ config: c, mesh, material, baseOpacity: c.opacity });
|
|
596
613
|
});
|
|
597
614
|
}
|
|
615
|
+
_globalOpacity = 1;
|
|
616
|
+
setGlobalOpacity(v) {
|
|
617
|
+
this._globalOpacity = v;
|
|
618
|
+
}
|
|
598
619
|
update(fov, showArt) {
|
|
599
620
|
this.root.visible = showArt;
|
|
600
621
|
if (!showArt) return;
|
|
@@ -609,7 +630,7 @@ var init_ConstellationArtworkLayer = __esm({
|
|
|
609
630
|
const t = (fade.zoomInStart - fov) / (fade.zoomInStart - fade.zoomInEnd);
|
|
610
631
|
opacity = THREE5.MathUtils.lerp(fade.maxOpacity, fade.minOpacity, t);
|
|
611
632
|
}
|
|
612
|
-
opacity = Math.min(Math.max(opacity, 0), 1);
|
|
633
|
+
opacity = Math.min(Math.max(opacity, 0), 1) * this._globalOpacity;
|
|
613
634
|
item.material.uniforms.uOpacity.value = opacity;
|
|
614
635
|
}
|
|
615
636
|
}
|
|
@@ -635,6 +656,164 @@ var init_ConstellationArtworkLayer = __esm({
|
|
|
635
656
|
}
|
|
636
657
|
});
|
|
637
658
|
|
|
659
|
+
// src/engine/projections.ts
|
|
660
|
+
var PerspectiveProjection, StereographicProjection, BlendedProjection, PROJECTIONS;
|
|
661
|
+
var init_projections = __esm({
|
|
662
|
+
"src/engine/projections.ts"() {
|
|
663
|
+
PerspectiveProjection = class {
|
|
664
|
+
id = "perspective";
|
|
665
|
+
label = "Perspective";
|
|
666
|
+
maxFov = 160;
|
|
667
|
+
glslProjectionType = 0;
|
|
668
|
+
forward(dir) {
|
|
669
|
+
if (dir.z > -0.1) return null;
|
|
670
|
+
const k = 1 / Math.max(0.01, -dir.z);
|
|
671
|
+
return { x: k * dir.x, y: k * dir.y, z: dir.z };
|
|
672
|
+
}
|
|
673
|
+
inverse(uvX, uvY, fovRad) {
|
|
674
|
+
const halfHeight = Math.tan(fovRad / 2);
|
|
675
|
+
const r = Math.sqrt(uvX * uvX + uvY * uvY);
|
|
676
|
+
const theta = Math.atan(r * halfHeight);
|
|
677
|
+
const phi = Math.atan2(uvY, uvX);
|
|
678
|
+
const sinT = Math.sin(theta);
|
|
679
|
+
return {
|
|
680
|
+
x: sinT * Math.cos(phi),
|
|
681
|
+
y: sinT * Math.sin(phi),
|
|
682
|
+
z: -Math.cos(theta)
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
getScale(fovRad) {
|
|
686
|
+
return 1 / Math.tan(fovRad / 2);
|
|
687
|
+
}
|
|
688
|
+
isClipped(dirZ) {
|
|
689
|
+
return dirZ > -0.1;
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
StereographicProjection = class {
|
|
693
|
+
id = "stereographic";
|
|
694
|
+
label = "Stereographic";
|
|
695
|
+
maxFov = 360;
|
|
696
|
+
glslProjectionType = 1;
|
|
697
|
+
forward(dir) {
|
|
698
|
+
if (dir.z > 0.4) return null;
|
|
699
|
+
const k = 2 / (1 - dir.z);
|
|
700
|
+
return { x: k * dir.x, y: k * dir.y, z: dir.z };
|
|
701
|
+
}
|
|
702
|
+
inverse(uvX, uvY, fovRad) {
|
|
703
|
+
const halfHeight = 2 * Math.tan(fovRad / 4);
|
|
704
|
+
const r = Math.sqrt(uvX * uvX + uvY * uvY);
|
|
705
|
+
const theta = 2 * Math.atan(r * halfHeight / 2);
|
|
706
|
+
const phi = Math.atan2(uvY, uvX);
|
|
707
|
+
const sinT = Math.sin(theta);
|
|
708
|
+
return {
|
|
709
|
+
x: sinT * Math.cos(phi),
|
|
710
|
+
y: sinT * Math.sin(phi),
|
|
711
|
+
z: -Math.cos(theta)
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
getScale(fovRad) {
|
|
715
|
+
return 1 / (2 * Math.tan(fovRad / 4));
|
|
716
|
+
}
|
|
717
|
+
isClipped(dirZ) {
|
|
718
|
+
return dirZ > 0.4;
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
BlendedProjection = class {
|
|
722
|
+
id = "blended";
|
|
723
|
+
label = "Blended (Auto)";
|
|
724
|
+
maxFov = 165;
|
|
725
|
+
glslProjectionType = 2;
|
|
726
|
+
/** FOV thresholds for blend transition (degrees) */
|
|
727
|
+
blendStart = 40;
|
|
728
|
+
blendEnd = 100;
|
|
729
|
+
/** Current blend factor, updated via setFov() */
|
|
730
|
+
blend = 0;
|
|
731
|
+
/** Call this each frame / when FOV changes so forward/inverse stay in sync */
|
|
732
|
+
setFov(fovDeg) {
|
|
733
|
+
if (fovDeg <= this.blendStart) {
|
|
734
|
+
this.blend = 0;
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (fovDeg >= this.blendEnd) {
|
|
738
|
+
this.blend = 1;
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const t = (fovDeg - this.blendStart) / (this.blendEnd - this.blendStart);
|
|
742
|
+
this.blend = t * t * (3 - 2 * t);
|
|
743
|
+
}
|
|
744
|
+
getBlend() {
|
|
745
|
+
return this.blend;
|
|
746
|
+
}
|
|
747
|
+
forward(dir) {
|
|
748
|
+
if (this.blend > 0.5 && dir.z > 0.4) return null;
|
|
749
|
+
if (this.blend < 0.1 && dir.z > -0.1) return null;
|
|
750
|
+
const kLinear = 1 / Math.max(0.01, -dir.z);
|
|
751
|
+
const kStereo = 2 / (1 - dir.z);
|
|
752
|
+
const k = kLinear * (1 - this.blend) + kStereo * this.blend;
|
|
753
|
+
return { x: k * dir.x, y: k * dir.y, z: dir.z };
|
|
754
|
+
}
|
|
755
|
+
inverse(uvX, uvY, fovRad) {
|
|
756
|
+
const r = Math.sqrt(uvX * uvX + uvY * uvY);
|
|
757
|
+
const halfHeightLin = Math.tan(fovRad / 2);
|
|
758
|
+
const thetaLin = Math.atan(r * halfHeightLin);
|
|
759
|
+
const halfHeightStereo = 2 * Math.tan(fovRad / 4);
|
|
760
|
+
const thetaStereo = 2 * Math.atan(r * halfHeightStereo / 2);
|
|
761
|
+
const theta = thetaLin * (1 - this.blend) + thetaStereo * this.blend;
|
|
762
|
+
const phi = Math.atan2(uvY, uvX);
|
|
763
|
+
const sinT = Math.sin(theta);
|
|
764
|
+
return {
|
|
765
|
+
x: sinT * Math.cos(phi),
|
|
766
|
+
y: sinT * Math.sin(phi),
|
|
767
|
+
z: -Math.cos(theta)
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
getScale(fovRad) {
|
|
771
|
+
const scaleLinear = 1 / Math.tan(fovRad / 2);
|
|
772
|
+
const scaleStereo = 1 / (2 * Math.tan(fovRad / 4));
|
|
773
|
+
return scaleLinear * (1 - this.blend) + scaleStereo * this.blend;
|
|
774
|
+
}
|
|
775
|
+
isClipped(dirZ) {
|
|
776
|
+
if (this.blend > 0.5) return dirZ > 0.4;
|
|
777
|
+
if (this.blend < 0.1) return dirZ > -0.1;
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
PROJECTIONS = {
|
|
782
|
+
perspective: () => new PerspectiveProjection(),
|
|
783
|
+
stereographic: () => new StereographicProjection(),
|
|
784
|
+
blended: () => new BlendedProjection()
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
// src/engine/fader.ts
|
|
790
|
+
var Fader;
|
|
791
|
+
var init_fader = __esm({
|
|
792
|
+
"src/engine/fader.ts"() {
|
|
793
|
+
Fader = class {
|
|
794
|
+
target = false;
|
|
795
|
+
value = 0;
|
|
796
|
+
duration;
|
|
797
|
+
constructor(duration = 0.3) {
|
|
798
|
+
this.duration = duration;
|
|
799
|
+
}
|
|
800
|
+
update(dt) {
|
|
801
|
+
const goal = this.target ? 1 : 0;
|
|
802
|
+
if (this.value === goal) return;
|
|
803
|
+
const speed = 1 / this.duration;
|
|
804
|
+
const step = speed * dt;
|
|
805
|
+
const diff = goal - this.value;
|
|
806
|
+
this.value += Math.sign(diff) * Math.min(step, Math.abs(diff));
|
|
807
|
+
}
|
|
808
|
+
/** Smoothstep-eased value for perceptually smooth transitions */
|
|
809
|
+
get eased() {
|
|
810
|
+
const v = this.value;
|
|
811
|
+
return v * v * (3 - 2 * v);
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
|
|
638
817
|
// src/engine/createEngine.ts
|
|
639
818
|
var createEngine_exports = {};
|
|
640
819
|
__export(createEngine_exports, {
|
|
@@ -652,9 +831,21 @@ function createEngine({
|
|
|
652
831
|
let orderRevealEnabled = true;
|
|
653
832
|
let activeBookIndex = -1;
|
|
654
833
|
let orderRevealStrength = 0;
|
|
834
|
+
let flyToActive = false;
|
|
835
|
+
let flyToTargetLon = 0;
|
|
836
|
+
let flyToTargetLat = 0;
|
|
837
|
+
let flyToTargetFov = ENGINE_CONFIG.minFov;
|
|
838
|
+
const FLY_TO_SPEED = 0.04;
|
|
839
|
+
let currentFilter = null;
|
|
840
|
+
let filterStrength = 0;
|
|
841
|
+
let filterTestamentIndex = -1;
|
|
842
|
+
let filterDivisionIndex = -1;
|
|
843
|
+
let filterBookIndex = -1;
|
|
655
844
|
const hoverCooldowns = /* @__PURE__ */ new Map();
|
|
656
845
|
const COOLDOWN_MS = 2e3;
|
|
657
846
|
const bookIdToIndex = /* @__PURE__ */ new Map();
|
|
847
|
+
const testamentToIndex = /* @__PURE__ */ new Map();
|
|
848
|
+
const divisionToIndex = /* @__PURE__ */ new Map();
|
|
658
849
|
const renderer = new THREE5.WebGLRenderer({ antialias: true, alpha: false });
|
|
659
850
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
|
660
851
|
renderer.setSize(container.clientWidth, container.clientHeight);
|
|
@@ -704,68 +895,53 @@ function createEngine({
|
|
|
704
895
|
function mix(a, b, t) {
|
|
705
896
|
return a * (1 - t) + b * t;
|
|
706
897
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (
|
|
710
|
-
|
|
711
|
-
|
|
898
|
+
let currentProjection = PROJECTIONS.blended();
|
|
899
|
+
function syncProjectionState() {
|
|
900
|
+
if (currentProjection instanceof BlendedProjection) {
|
|
901
|
+
currentProjection.setFov(state.fov);
|
|
902
|
+
globalUniforms.uBlend.value = currentProjection.getBlend();
|
|
903
|
+
}
|
|
904
|
+
globalUniforms.uProjectionType.value = currentProjection.glslProjectionType;
|
|
712
905
|
}
|
|
713
906
|
function updateUniforms() {
|
|
714
|
-
|
|
715
|
-
globalUniforms.uBlend.value = blend;
|
|
907
|
+
syncProjectionState();
|
|
716
908
|
const fovRad = state.fov * Math.PI / 180;
|
|
717
|
-
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
909
|
+
let scale = currentProjection.getScale(fovRad);
|
|
910
|
+
const aspect = camera.aspect;
|
|
911
|
+
if (currentConfig?.fitProjection) {
|
|
912
|
+
if (aspect > 1) {
|
|
913
|
+
scale /= aspect;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
globalUniforms.uScale.value = scale;
|
|
917
|
+
globalUniforms.uAspect.value = aspect;
|
|
721
918
|
camera.fov = Math.min(state.fov, ENGINE_CONFIG.defaultFov);
|
|
722
919
|
camera.updateProjectionMatrix();
|
|
723
920
|
}
|
|
724
921
|
function getMouseViewVector(fovDeg, aspectRatio) {
|
|
725
|
-
|
|
922
|
+
syncProjectionState();
|
|
726
923
|
const fovRad = fovDeg * Math.PI / 180;
|
|
727
924
|
const uvX = mouseNDC.x * aspectRatio;
|
|
728
925
|
const uvY = mouseNDC.y;
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
const theta_lin = Math.atan(r_uv * halfHeightLinear);
|
|
732
|
-
const halfHeightStereo = 2 * Math.tan(fovRad / 4);
|
|
733
|
-
const theta_str = 2 * Math.atan(r_uv * halfHeightStereo / 2);
|
|
734
|
-
const theta = mix(theta_lin, theta_str, blend);
|
|
735
|
-
const phi = Math.atan2(uvY, uvX);
|
|
736
|
-
const sinTheta = Math.sin(theta);
|
|
737
|
-
const cosTheta = Math.cos(theta);
|
|
738
|
-
return new THREE5.Vector3(sinTheta * Math.cos(phi), sinTheta * Math.sin(phi), -cosTheta).normalize();
|
|
926
|
+
const v = currentProjection.inverse(uvX, uvY, fovRad);
|
|
927
|
+
return new THREE5.Vector3(v.x, v.y, v.z).normalize();
|
|
739
928
|
}
|
|
740
929
|
function getMouseWorldVector(pixelX, pixelY, width, height) {
|
|
741
930
|
const aspect = width / height;
|
|
742
931
|
const ndcX = pixelX / width * 2 - 1;
|
|
743
932
|
const ndcY = -(pixelY / height) * 2 + 1;
|
|
744
|
-
|
|
933
|
+
syncProjectionState();
|
|
745
934
|
const fovRad = state.fov * Math.PI / 180;
|
|
746
|
-
const
|
|
747
|
-
const
|
|
748
|
-
const r_uv = Math.sqrt(uvX * uvX + uvY * uvY);
|
|
749
|
-
const halfHeightLinear = Math.tan(fovRad / 2);
|
|
750
|
-
const theta_lin = Math.atan(r_uv * halfHeightLinear);
|
|
751
|
-
const halfHeightStereo = 2 * Math.tan(fovRad / 4);
|
|
752
|
-
const theta_str = 2 * Math.atan(r_uv * halfHeightStereo / 2);
|
|
753
|
-
const theta = mix(theta_lin, theta_str, blend);
|
|
754
|
-
const phi = Math.atan2(uvY, uvX);
|
|
755
|
-
const sinTheta = Math.sin(theta);
|
|
756
|
-
const cosTheta = Math.cos(theta);
|
|
757
|
-
const vView = new THREE5.Vector3(sinTheta * Math.cos(phi), sinTheta * Math.sin(phi), -cosTheta).normalize();
|
|
935
|
+
const v = currentProjection.inverse(ndcX * aspect, ndcY, fovRad);
|
|
936
|
+
const vView = new THREE5.Vector3(v.x, v.y, v.z).normalize();
|
|
758
937
|
return vView.applyQuaternion(camera.quaternion);
|
|
759
938
|
}
|
|
760
939
|
function smartProjectJS(worldPos) {
|
|
761
940
|
const viewPos = worldPos.clone().applyMatrix4(camera.matrixWorldInverse);
|
|
762
941
|
const dir = viewPos.clone().normalize();
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
const blend = globalUniforms.uBlend.value;
|
|
767
|
-
const k = mix(kLinear, kStereo, blend);
|
|
768
|
-
return { x: k * dir.x, y: k * dir.y, z: dir.z };
|
|
942
|
+
const result = currentProjection.forward(dir);
|
|
943
|
+
if (!result) return { x: 0, y: 0, z: dir.z };
|
|
944
|
+
return result;
|
|
769
945
|
}
|
|
770
946
|
const groundGroup = new THREE5.Group();
|
|
771
947
|
scene.add(groundGroup);
|
|
@@ -775,10 +951,8 @@ function createEngine({
|
|
|
775
951
|
const geometry = new THREE5.SphereGeometry(radius, 128, 64, 0, Math.PI * 2, Math.PI / 2 - 0.15, Math.PI / 2 + 0.15);
|
|
776
952
|
const material = createSmartMaterial({
|
|
777
953
|
uniforms: {
|
|
778
|
-
color: { value: new THREE5.Color(
|
|
779
|
-
|
|
780
|
-
fogColor: { value: new THREE5.Color(331812) }
|
|
781
|
-
// Matches atmosphere bot color
|
|
954
|
+
color: { value: new THREE5.Color(65794) },
|
|
955
|
+
fogColor: { value: new THREE5.Color(663098) }
|
|
782
956
|
},
|
|
783
957
|
vertexShaderBody: `
|
|
784
958
|
varying vec3 vPos;
|
|
@@ -804,24 +978,30 @@ function createEngine({
|
|
|
804
978
|
// Procedural Horizon (Mountains)
|
|
805
979
|
float angle = atan(vPos.z, vPos.x);
|
|
806
980
|
|
|
807
|
-
//
|
|
981
|
+
// FBM-like terrain with increased amplitude
|
|
808
982
|
float h = 0.0;
|
|
809
|
-
h += sin(angle * 6.0) *
|
|
810
|
-
h += sin(angle * 13.0 + 1.0) *
|
|
811
|
-
h += sin(angle * 29.0 + 2.0) *
|
|
812
|
-
h += sin(angle * 63.0 + 4.0) *
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
float terrainHeight = h +
|
|
816
|
-
|
|
983
|
+
h += sin(angle * 6.0) * 35.0;
|
|
984
|
+
h += sin(angle * 13.0 + 1.0) * 18.0;
|
|
985
|
+
h += sin(angle * 29.0 + 2.0) * 8.0;
|
|
986
|
+
h += sin(angle * 63.0 + 4.0) * 3.0;
|
|
987
|
+
h += sin(angle * 97.0 + 5.0) * 1.5;
|
|
988
|
+
|
|
989
|
+
float terrainHeight = h + 12.0;
|
|
990
|
+
|
|
817
991
|
if (vPos.y > terrainHeight) discard;
|
|
818
|
-
|
|
819
|
-
// Atmospheric
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
992
|
+
|
|
993
|
+
// Atmospheric rim glow just below terrain peaks
|
|
994
|
+
float rimDist = terrainHeight - vPos.y;
|
|
995
|
+
float rim = exp(-rimDist * 0.15) * 0.4;
|
|
996
|
+
vec3 rimColor = fogColor * 1.5;
|
|
997
|
+
|
|
998
|
+
// Atmospheric haze \u2014 stronger near horizon
|
|
999
|
+
float fogFactor = smoothstep(-120.0, terrainHeight, vPos.y);
|
|
1000
|
+
vec3 finalCol = mix(color, fogColor, fogFactor * 0.6);
|
|
1001
|
+
|
|
1002
|
+
// Add rim glow near terrain peaks
|
|
1003
|
+
finalCol += rimColor * rim;
|
|
1004
|
+
|
|
825
1005
|
gl_FragColor = vec4(finalCol, 1.0);
|
|
826
1006
|
}
|
|
827
1007
|
`,
|
|
@@ -859,19 +1039,25 @@ function createEngine({
|
|
|
859
1039
|
|
|
860
1040
|
// Altitude angle (Y is up)
|
|
861
1041
|
float h = normalize(vWorldNormal).y;
|
|
862
|
-
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1042
|
+
|
|
1043
|
+
// 1. Base gradient from Horizon to Zenith (wider range)
|
|
1044
|
+
float t = smoothstep(-0.15, 0.7, h);
|
|
1045
|
+
|
|
867
1046
|
// Non-linear mix for realistic sky falloff
|
|
868
|
-
// Zenith darkness adjustment
|
|
869
1047
|
vec3 skyColor = mix(uColorHorizon * uAtmGlow, uColorZenith * (1.0 - uAtmDark), pow(t, 0.6));
|
|
870
|
-
|
|
871
|
-
// 2.
|
|
872
|
-
float
|
|
1048
|
+
|
|
1049
|
+
// 2. Teal tint at mid-altitudes (subtle colour variation)
|
|
1050
|
+
float midBand = exp(-6.0 * pow(h - 0.3, 2.0));
|
|
1051
|
+
skyColor += vec3(0.05, 0.12, 0.15) * midBand * uAtmGlow;
|
|
1052
|
+
|
|
1053
|
+
// 3. Primary horizon glow band (wider than before)
|
|
1054
|
+
float horizonBand = exp(-10.0 * abs(h - 0.02));
|
|
873
1055
|
skyColor += uColorHorizon * horizonBand * 0.5 * uAtmGlow;
|
|
874
1056
|
|
|
1057
|
+
// 4. Warm secondary glow (light pollution / sodium scatter)
|
|
1058
|
+
float warmGlow = exp(-8.0 * abs(h));
|
|
1059
|
+
skyColor += vec3(0.4, 0.25, 0.15) * warmGlow * 0.3 * uAtmGlow;
|
|
1060
|
+
|
|
875
1061
|
gl_FragColor = vec4(skyColor, 1.0);
|
|
876
1062
|
}
|
|
877
1063
|
`,
|
|
@@ -909,7 +1095,24 @@ function createEngine({
|
|
|
909
1095
|
positions.push(x, y, z);
|
|
910
1096
|
const size = 1 + -Math.log(Math.random()) * 0.8 * 1.5;
|
|
911
1097
|
sizes.push(size);
|
|
912
|
-
|
|
1098
|
+
const temp = Math.random();
|
|
1099
|
+
let cr, cg, cb;
|
|
1100
|
+
if (temp < 0.15) {
|
|
1101
|
+
cr = 0.7 + temp * 2;
|
|
1102
|
+
cg = 0.8 + temp;
|
|
1103
|
+
cb = 1;
|
|
1104
|
+
} else if (temp < 0.6) {
|
|
1105
|
+
const t = (temp - 0.15) / 0.45;
|
|
1106
|
+
cr = 1;
|
|
1107
|
+
cg = 1 - t * 0.1;
|
|
1108
|
+
cb = 1 - t * 0.3;
|
|
1109
|
+
} else {
|
|
1110
|
+
const t = (temp - 0.6) / 0.4;
|
|
1111
|
+
cr = 1;
|
|
1112
|
+
cg = 0.85 - t * 0.35;
|
|
1113
|
+
cb = 0.7 - t * 0.35;
|
|
1114
|
+
}
|
|
1115
|
+
colors.push(cr, cg, cb);
|
|
913
1116
|
}
|
|
914
1117
|
geometry.setAttribute("position", new THREE5.Float32BufferAttribute(positions, 3));
|
|
915
1118
|
geometry.setAttribute("size", new THREE5.Float32BufferAttribute(sizes, 1));
|
|
@@ -917,51 +1120,60 @@ function createEngine({
|
|
|
917
1120
|
const material = createSmartMaterial({
|
|
918
1121
|
uniforms: {
|
|
919
1122
|
pixelRatio: { value: renderer.getPixelRatio() },
|
|
920
|
-
uScale: globalUniforms.uScale
|
|
1123
|
+
uScale: globalUniforms.uScale,
|
|
1124
|
+
uTime: globalUniforms.uTime
|
|
921
1125
|
},
|
|
922
1126
|
vertexShaderBody: `
|
|
923
|
-
attribute float size;
|
|
924
|
-
attribute vec3 color;
|
|
925
|
-
varying vec3 vColor;
|
|
926
|
-
uniform float pixelRatio;
|
|
927
|
-
|
|
1127
|
+
attribute float size;
|
|
1128
|
+
attribute vec3 color;
|
|
1129
|
+
varying vec3 vColor;
|
|
1130
|
+
uniform float pixelRatio;
|
|
1131
|
+
|
|
928
1132
|
uniform float uAtmExtinction;
|
|
1133
|
+
uniform float uAtmTwinkle;
|
|
1134
|
+
uniform float uTime;
|
|
929
1135
|
|
|
930
|
-
void main() {
|
|
1136
|
+
void main() {
|
|
931
1137
|
vec3 nPos = normalize(position);
|
|
932
1138
|
float altitude = nPos.y;
|
|
933
|
-
|
|
934
|
-
//
|
|
1139
|
+
|
|
1140
|
+
// Extinction & Horizon Fade
|
|
935
1141
|
float horizonFade = smoothstep(-0.1, 0.1, altitude);
|
|
936
1142
|
float airmass = 1.0 / (max(0.05, altitude + 0.05));
|
|
937
1143
|
float extinction = exp(-uAtmExtinction * 0.15 * airmass);
|
|
938
1144
|
|
|
939
|
-
//
|
|
940
|
-
|
|
1145
|
+
// Scintillation (twinkling) \u2014 stronger near horizon
|
|
1146
|
+
float turbulence = 1.0 + (1.0 - smoothstep(0.0, 1.0, altitude)) * 2.0;
|
|
1147
|
+
float twinkle = sin(uTime * 3.0 + position.x * 0.05 + position.z * 0.03) * 0.5 + 0.5;
|
|
1148
|
+
float scintillation = mix(1.0, twinkle * 2.0, uAtmTwinkle * 0.4 * turbulence);
|
|
941
1149
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
float zoomScale = pow(uScale, 0.5);
|
|
949
|
-
|
|
950
|
-
gl_PointSize =
|
|
1150
|
+
vColor = color * 3.0 * extinction * horizonFade * scintillation;
|
|
1151
|
+
|
|
1152
|
+
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
1153
|
+
gl_Position = smartProject(mvPosition);
|
|
1154
|
+
vScreenPos = gl_Position.xy / gl_Position.w;
|
|
1155
|
+
|
|
1156
|
+
float zoomScale = pow(uScale, 0.5);
|
|
1157
|
+
float perceptualSize = pow(size, 0.55);
|
|
1158
|
+
gl_PointSize = clamp(perceptualSize * zoomScale * 0.5 * pixelRatio * (800.0 / -mvPosition.z) * horizonFade, 0.5, 20.0);
|
|
951
1159
|
}
|
|
952
1160
|
`,
|
|
953
1161
|
fragmentShader: `
|
|
954
|
-
varying vec3 vColor;
|
|
955
|
-
void main() {
|
|
956
|
-
vec2 coord = gl_PointCoord - vec2(0.5);
|
|
957
|
-
float
|
|
958
|
-
if (
|
|
959
|
-
float alphaMask = getMaskAlpha();
|
|
960
|
-
if (alphaMask < 0.01) discard;
|
|
961
|
-
|
|
962
|
-
//
|
|
963
|
-
float
|
|
964
|
-
|
|
1162
|
+
varying vec3 vColor;
|
|
1163
|
+
void main() {
|
|
1164
|
+
vec2 coord = gl_PointCoord - vec2(0.5);
|
|
1165
|
+
float d = length(coord) * 2.0;
|
|
1166
|
+
if (d > 1.0) discard;
|
|
1167
|
+
float alphaMask = getMaskAlpha();
|
|
1168
|
+
if (alphaMask < 0.01) discard;
|
|
1169
|
+
|
|
1170
|
+
// Stellarium-style: sharp core + soft glow
|
|
1171
|
+
float core = smoothstep(0.8, 0.4, d);
|
|
1172
|
+
float glow = smoothstep(1.0, 0.0, d) * 0.08;
|
|
1173
|
+
float k = core + glow;
|
|
1174
|
+
|
|
1175
|
+
vec3 finalColor = mix(vColor, vec3(1.0), core * 0.5);
|
|
1176
|
+
gl_FragColor = vec4(finalColor * k * alphaMask, 1.0);
|
|
965
1177
|
}
|
|
966
1178
|
`,
|
|
967
1179
|
transparent: true,
|
|
@@ -1034,6 +1246,9 @@ function createEngine({
|
|
|
1034
1246
|
let constellationLines = null;
|
|
1035
1247
|
let boundaryLines = null;
|
|
1036
1248
|
let starPoints = null;
|
|
1249
|
+
const linesFader = new Fader(0.4);
|
|
1250
|
+
const artFader = new Fader(0.5);
|
|
1251
|
+
let lastTickTime = 0;
|
|
1037
1252
|
function clearRoot() {
|
|
1038
1253
|
for (const child of [...root.children]) {
|
|
1039
1254
|
root.remove(child);
|
|
@@ -1104,6 +1319,8 @@ function createEngine({
|
|
|
1104
1319
|
function buildFromModel(model, cfg) {
|
|
1105
1320
|
clearRoot();
|
|
1106
1321
|
bookIdToIndex.clear();
|
|
1322
|
+
testamentToIndex.clear();
|
|
1323
|
+
divisionToIndex.clear();
|
|
1107
1324
|
scene.background = cfg.background && cfg.background !== "transparent" ? new THREE5.Color(cfg.background) : new THREE5.Color(0);
|
|
1108
1325
|
const layoutCfg = { ...cfg.layout, radius: cfg.layout?.radius ?? 2e3 };
|
|
1109
1326
|
const laidOut = computeLayoutPositions(model, layoutCfg);
|
|
@@ -1137,6 +1354,8 @@ function createEngine({
|
|
|
1137
1354
|
const starPhases = [];
|
|
1138
1355
|
const starBookIndices = [];
|
|
1139
1356
|
const starChapterIndices = [];
|
|
1357
|
+
const starTestamentIndices = [];
|
|
1358
|
+
const starDivisionIndices = [];
|
|
1140
1359
|
const SPECTRAL_COLORS = [
|
|
1141
1360
|
new THREE5.Color(14544639),
|
|
1142
1361
|
// O - Blueish White
|
|
@@ -1194,12 +1413,32 @@ function createEngine({
|
|
|
1194
1413
|
let cIdx = 0;
|
|
1195
1414
|
if (n.meta?.chapter) cIdx = Number(n.meta.chapter);
|
|
1196
1415
|
starChapterIndices.push(cIdx);
|
|
1416
|
+
let tIdx = -1;
|
|
1417
|
+
if (n.meta?.testament) {
|
|
1418
|
+
const tName = n.meta.testament;
|
|
1419
|
+
if (!testamentToIndex.has(tName)) {
|
|
1420
|
+
testamentToIndex.set(tName, testamentToIndex.size + 1);
|
|
1421
|
+
}
|
|
1422
|
+
tIdx = testamentToIndex.get(tName);
|
|
1423
|
+
}
|
|
1424
|
+
starTestamentIndices.push(tIdx);
|
|
1425
|
+
let dIdx = -1;
|
|
1426
|
+
if (n.meta?.division) {
|
|
1427
|
+
const dName = n.meta.division;
|
|
1428
|
+
if (!divisionToIndex.has(dName)) {
|
|
1429
|
+
divisionToIndex.set(dName, divisionToIndex.size + 1);
|
|
1430
|
+
}
|
|
1431
|
+
dIdx = divisionToIndex.get(dName);
|
|
1432
|
+
}
|
|
1433
|
+
starDivisionIndices.push(dIdx);
|
|
1197
1434
|
}
|
|
1198
1435
|
if (n.level === 1 || n.level === 2 || n.level === 3) {
|
|
1199
1436
|
let color = "#ffffff";
|
|
1200
1437
|
if (n.level === 1) color = "#38bdf8";
|
|
1201
|
-
else if (n.level === 2)
|
|
1202
|
-
|
|
1438
|
+
else if (n.level === 2) {
|
|
1439
|
+
const bookKey = n.meta?.bookKey;
|
|
1440
|
+
color = bookKey && cfg.labelColors?.[bookKey] || "#cbd5e1";
|
|
1441
|
+
} else if (n.level === 3) color = "#94a3b8";
|
|
1203
1442
|
let labelText = n.label;
|
|
1204
1443
|
if (n.level === 3 && n.meta?.chapter) {
|
|
1205
1444
|
labelText = String(n.meta.chapter);
|
|
@@ -1280,6 +1519,8 @@ function createEngine({
|
|
|
1280
1519
|
starGeo.setAttribute("phase", new THREE5.Float32BufferAttribute(starPhases, 1));
|
|
1281
1520
|
starGeo.setAttribute("bookIndex", new THREE5.Float32BufferAttribute(starBookIndices, 1));
|
|
1282
1521
|
starGeo.setAttribute("chapterIndex", new THREE5.Float32BufferAttribute(starChapterIndices, 1));
|
|
1522
|
+
starGeo.setAttribute("testamentIndex", new THREE5.Float32BufferAttribute(starTestamentIndices, 1));
|
|
1523
|
+
starGeo.setAttribute("divisionIndex", new THREE5.Float32BufferAttribute(starDivisionIndices, 1));
|
|
1283
1524
|
const starMat = createSmartMaterial({
|
|
1284
1525
|
uniforms: {
|
|
1285
1526
|
pixelRatio: { value: renderer.getPixelRatio() },
|
|
@@ -1292,7 +1533,12 @@ function createEngine({
|
|
|
1292
1533
|
ORDER_REVEAL_CONFIG.pulseDuration,
|
|
1293
1534
|
ORDER_REVEAL_CONFIG.delayPerChapter,
|
|
1294
1535
|
ORDER_REVEAL_CONFIG.pulseAmplitude
|
|
1295
|
-
) }
|
|
1536
|
+
) },
|
|
1537
|
+
uFilterTestamentIndex: { value: -1 },
|
|
1538
|
+
uFilterDivisionIndex: { value: -1 },
|
|
1539
|
+
uFilterBookIndex: { value: -1 },
|
|
1540
|
+
uFilterStrength: { value: 0 },
|
|
1541
|
+
uFilterDimFactor: { value: 0.08 }
|
|
1296
1542
|
},
|
|
1297
1543
|
vertexShaderBody: `
|
|
1298
1544
|
attribute float size;
|
|
@@ -1300,10 +1546,12 @@ function createEngine({
|
|
|
1300
1546
|
attribute float phase;
|
|
1301
1547
|
attribute float bookIndex;
|
|
1302
1548
|
attribute float chapterIndex;
|
|
1549
|
+
attribute float testamentIndex;
|
|
1550
|
+
attribute float divisionIndex;
|
|
1551
|
+
|
|
1552
|
+
varying vec3 vColor;
|
|
1553
|
+
uniform float pixelRatio;
|
|
1303
1554
|
|
|
1304
|
-
varying vec3 vColor;
|
|
1305
|
-
uniform float pixelRatio;
|
|
1306
|
-
|
|
1307
1555
|
uniform float uTime;
|
|
1308
1556
|
uniform float uAtmExtinction;
|
|
1309
1557
|
uniform float uAtmTwinkle;
|
|
@@ -1313,6 +1561,12 @@ function createEngine({
|
|
|
1313
1561
|
uniform float uGlobalDimFactor;
|
|
1314
1562
|
uniform vec3 uPulseParams;
|
|
1315
1563
|
|
|
1564
|
+
uniform float uFilterTestamentIndex;
|
|
1565
|
+
uniform float uFilterDivisionIndex;
|
|
1566
|
+
uniform float uFilterBookIndex;
|
|
1567
|
+
uniform float uFilterStrength;
|
|
1568
|
+
uniform float uFilterDimFactor;
|
|
1569
|
+
|
|
1316
1570
|
void main() {
|
|
1317
1571
|
vec3 nPos = normalize(position);
|
|
1318
1572
|
|
|
@@ -1347,8 +1601,21 @@ function createEngine({
|
|
|
1347
1601
|
|
|
1348
1602
|
float activePulse = pulse * uPulseParams.z * isTarget * uOrderRevealStrength;
|
|
1349
1603
|
|
|
1604
|
+
// --- Hierarchy Filter ---
|
|
1605
|
+
float filtered = 0.0;
|
|
1606
|
+
if (uFilterTestamentIndex >= 0.0) {
|
|
1607
|
+
filtered = 1.0 - step(0.5, 1.0 - abs(testamentIndex - uFilterTestamentIndex));
|
|
1608
|
+
}
|
|
1609
|
+
if (uFilterDivisionIndex >= 0.0 && filtered < 0.5) {
|
|
1610
|
+
filtered = 1.0 - step(0.5, 1.0 - abs(divisionIndex - uFilterDivisionIndex));
|
|
1611
|
+
}
|
|
1612
|
+
if (uFilterBookIndex >= 0.0 && filtered < 0.5) {
|
|
1613
|
+
filtered = 1.0 - step(0.5, 1.0 - abs(bookIndex - uFilterBookIndex));
|
|
1614
|
+
}
|
|
1615
|
+
float filterDim = mix(1.0, uFilterDimFactor, uFilterStrength * filtered);
|
|
1616
|
+
|
|
1350
1617
|
vec3 baseColor = color * extinction * horizonFade * scintillation;
|
|
1351
|
-
vColor = baseColor * dimFactor;
|
|
1618
|
+
vColor = baseColor * dimFactor * filterDim;
|
|
1352
1619
|
vColor += vec3(1.0, 0.8, 0.4) * activePulse;
|
|
1353
1620
|
|
|
1354
1621
|
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
@@ -1356,7 +1623,8 @@ function createEngine({
|
|
|
1356
1623
|
vScreenPos = gl_Position.xy / gl_Position.w;
|
|
1357
1624
|
|
|
1358
1625
|
float sizeBoost = 1.0 + activePulse * 0.8;
|
|
1359
|
-
|
|
1626
|
+
float perceptualSize = pow(size, 0.55);
|
|
1627
|
+
gl_PointSize = clamp((perceptualSize * sizeBoost * 1.5) * uScale * pixelRatio * (2000.0 / -mvPosition.z) * horizonFade, 1.0, 40.0);
|
|
1360
1628
|
}
|
|
1361
1629
|
`,
|
|
1362
1630
|
fragmentShader: `
|
|
@@ -1369,15 +1637,14 @@ function createEngine({
|
|
|
1369
1637
|
float alphaMask = getMaskAlpha();
|
|
1370
1638
|
if (alphaMask < 0.01) discard;
|
|
1371
1639
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
float
|
|
1375
|
-
float
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
vec3
|
|
1379
|
-
|
|
1380
|
-
gl_FragColor = vec4((cCore + cHalo) * alphaMask, 1.0);
|
|
1640
|
+
// Stellarium-style dual-layer: sharp core + soft glow
|
|
1641
|
+
float core = smoothstep(0.8, 0.4, d);
|
|
1642
|
+
float glow = smoothstep(1.0, 0.0, d) * 0.08;
|
|
1643
|
+
float k = core + glow;
|
|
1644
|
+
|
|
1645
|
+
// White-hot core blending into coloured halo
|
|
1646
|
+
vec3 finalColor = mix(vColor, vec3(1.0), core * 0.7);
|
|
1647
|
+
gl_FragColor = vec4(finalColor * k * alphaMask, 1.0);
|
|
1381
1648
|
}
|
|
1382
1649
|
`,
|
|
1383
1650
|
transparent: true,
|
|
@@ -1411,17 +1678,89 @@ function createEngine({
|
|
|
1411
1678
|
}
|
|
1412
1679
|
}
|
|
1413
1680
|
if (linePoints.length > 0) {
|
|
1681
|
+
const quadPositions = [];
|
|
1682
|
+
const quadUvs = [];
|
|
1683
|
+
const quadIndices = [];
|
|
1684
|
+
const lineWidth = 8;
|
|
1685
|
+
for (let i = 0; i < linePoints.length; i += 6) {
|
|
1686
|
+
const ax = linePoints[i], ay = linePoints[i + 1], az = linePoints[i + 2];
|
|
1687
|
+
const bx = linePoints[i + 3], by = linePoints[i + 4], bz = linePoints[i + 5];
|
|
1688
|
+
const dx = bx - ax, dy = by - ay, dz = bz - az;
|
|
1689
|
+
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1690
|
+
if (len < 1e-3) continue;
|
|
1691
|
+
let px = dy * 0 - dz * 1, py = dz * 0 - dx * 0, pz = dx * 1 - dy * 0;
|
|
1692
|
+
const pLen = Math.sqrt(px * px + py * py + pz * pz);
|
|
1693
|
+
if (pLen < 1e-3) {
|
|
1694
|
+
px = 1;
|
|
1695
|
+
py = 0;
|
|
1696
|
+
pz = 0;
|
|
1697
|
+
} else {
|
|
1698
|
+
px /= pLen;
|
|
1699
|
+
py /= pLen;
|
|
1700
|
+
pz /= pLen;
|
|
1701
|
+
}
|
|
1702
|
+
const hw = lineWidth;
|
|
1703
|
+
const baseIdx = quadPositions.length / 3;
|
|
1704
|
+
quadPositions.push(ax - px * hw, ay - py * hw, az - pz * hw);
|
|
1705
|
+
quadUvs.push(0, -1);
|
|
1706
|
+
quadPositions.push(ax + px * hw, ay + py * hw, az + pz * hw);
|
|
1707
|
+
quadUvs.push(0, 1);
|
|
1708
|
+
quadPositions.push(bx - px * hw, by - py * hw, bz - pz * hw);
|
|
1709
|
+
quadUvs.push(1, -1);
|
|
1710
|
+
quadPositions.push(bx + px * hw, by + py * hw, bz + pz * hw);
|
|
1711
|
+
quadUvs.push(1, 1);
|
|
1712
|
+
quadIndices.push(baseIdx, baseIdx + 1, baseIdx + 2, baseIdx + 1, baseIdx + 3, baseIdx + 2);
|
|
1713
|
+
}
|
|
1414
1714
|
const lineGeo = new THREE5.BufferGeometry();
|
|
1415
|
-
lineGeo.setAttribute("position", new THREE5.Float32BufferAttribute(
|
|
1715
|
+
lineGeo.setAttribute("position", new THREE5.Float32BufferAttribute(quadPositions, 3));
|
|
1716
|
+
lineGeo.setAttribute("lineUv", new THREE5.Float32BufferAttribute(quadUvs, 2));
|
|
1717
|
+
lineGeo.setIndex(quadIndices);
|
|
1416
1718
|
const lineMat = createSmartMaterial({
|
|
1417
|
-
uniforms: {
|
|
1418
|
-
|
|
1419
|
-
|
|
1719
|
+
uniforms: {
|
|
1720
|
+
color: { value: new THREE5.Color(11193599) },
|
|
1721
|
+
uLineWidth: { value: 1.5 },
|
|
1722
|
+
uGlowIntensity: { value: 0.3 }
|
|
1723
|
+
},
|
|
1724
|
+
vertexShaderBody: `
|
|
1725
|
+
attribute vec2 lineUv;
|
|
1726
|
+
varying vec2 vLineUv;
|
|
1727
|
+
void main() {
|
|
1728
|
+
vLineUv = lineUv;
|
|
1729
|
+
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
1730
|
+
gl_Position = smartProject(mvPosition);
|
|
1731
|
+
vScreenPos = gl_Position.xy / gl_Position.w;
|
|
1732
|
+
}
|
|
1733
|
+
`,
|
|
1734
|
+
fragmentShader: `
|
|
1735
|
+
uniform vec3 color;
|
|
1736
|
+
uniform float uLineWidth;
|
|
1737
|
+
uniform float uGlowIntensity;
|
|
1738
|
+
varying vec2 vLineUv;
|
|
1739
|
+
void main() {
|
|
1740
|
+
float alphaMask = getMaskAlpha();
|
|
1741
|
+
if (alphaMask < 0.01) discard;
|
|
1742
|
+
|
|
1743
|
+
float dist = abs(vLineUv.y);
|
|
1744
|
+
|
|
1745
|
+
// Anti-aliased core line
|
|
1746
|
+
float hw = uLineWidth * 0.05;
|
|
1747
|
+
float base = smoothstep(hw + 0.08, hw - 0.08, dist);
|
|
1748
|
+
|
|
1749
|
+
// Soft glow extending outward
|
|
1750
|
+
float glow = (1.0 - dist) * uGlowIntensity;
|
|
1751
|
+
|
|
1752
|
+
float alpha = max(glow, base);
|
|
1753
|
+
if (alpha < 0.005) discard;
|
|
1754
|
+
|
|
1755
|
+
gl_FragColor = vec4(color, alpha * alphaMask);
|
|
1756
|
+
}
|
|
1757
|
+
`,
|
|
1420
1758
|
transparent: true,
|
|
1421
1759
|
depthWrite: false,
|
|
1422
|
-
blending: THREE5.AdditiveBlending
|
|
1760
|
+
blending: THREE5.AdditiveBlending,
|
|
1761
|
+
side: THREE5.DoubleSide
|
|
1423
1762
|
});
|
|
1424
|
-
constellationLines = new THREE5.
|
|
1763
|
+
constellationLines = new THREE5.Mesh(lineGeo, lineMat);
|
|
1425
1764
|
constellationLines.frustumCulled = false;
|
|
1426
1765
|
root.add(constellationLines);
|
|
1427
1766
|
}
|
|
@@ -1593,8 +1932,15 @@ function createEngine({
|
|
|
1593
1932
|
let lastAppliedLon = void 0;
|
|
1594
1933
|
let lastAppliedLat = void 0;
|
|
1595
1934
|
let lastBackdropCount = void 0;
|
|
1935
|
+
function setProjection(id) {
|
|
1936
|
+
const factory = PROJECTIONS[id];
|
|
1937
|
+
if (!factory) return;
|
|
1938
|
+
currentProjection = factory();
|
|
1939
|
+
updateUniforms();
|
|
1940
|
+
}
|
|
1596
1941
|
function setConfig(cfg) {
|
|
1597
1942
|
currentConfig = cfg;
|
|
1943
|
+
if (cfg.projection) setProjection(cfg.projection);
|
|
1598
1944
|
if (typeof cfg.camera?.lon === "number" && cfg.camera.lon !== lastAppliedLon) {
|
|
1599
1945
|
state.lon = cfg.camera.lon;
|
|
1600
1946
|
state.targetLon = cfg.camera.lon;
|
|
@@ -1684,6 +2030,15 @@ function createEngine({
|
|
|
1684
2030
|
Object.assign(arr, state.tempArrangement);
|
|
1685
2031
|
return arr;
|
|
1686
2032
|
}
|
|
2033
|
+
function isNodeFiltered(node) {
|
|
2034
|
+
if (!currentFilter) return false;
|
|
2035
|
+
const meta = node.meta;
|
|
2036
|
+
if (!meta) return false;
|
|
2037
|
+
if (currentFilter.testament && meta.testament !== currentFilter.testament) return true;
|
|
2038
|
+
if (currentFilter.division && meta.division !== currentFilter.division) return true;
|
|
2039
|
+
if (currentFilter.bookKey && meta.bookKey !== currentFilter.bookKey) return true;
|
|
2040
|
+
return false;
|
|
2041
|
+
}
|
|
1687
2042
|
function pick(ev) {
|
|
1688
2043
|
const rect = renderer.domElement.getBoundingClientRect();
|
|
1689
2044
|
const mX = ev.clientX - rect.left;
|
|
@@ -1698,10 +2053,10 @@ function createEngine({
|
|
|
1698
2053
|
let minLabelDist = 40;
|
|
1699
2054
|
for (const item of dynamicLabels) {
|
|
1700
2055
|
if (!item.obj.visible) continue;
|
|
2056
|
+
if (isNodeFiltered(item.node)) continue;
|
|
1701
2057
|
const pWorld = item.obj.position;
|
|
1702
2058
|
const pProj = smartProjectJS(pWorld);
|
|
1703
|
-
|
|
1704
|
-
if (isBehind) continue;
|
|
2059
|
+
if (currentProjection.isClipped(pProj.z)) continue;
|
|
1705
2060
|
const xNDC = pProj.x * uScale / uAspect;
|
|
1706
2061
|
const yNDC = pProj.y * uScale;
|
|
1707
2062
|
const sX = (xNDC * 0.5 + 0.5) * w;
|
|
@@ -1723,8 +2078,7 @@ function createEngine({
|
|
|
1723
2078
|
if (!item.mesh.visible) continue;
|
|
1724
2079
|
const pWorld = item.mesh.position;
|
|
1725
2080
|
const pProj = smartProjectJS(pWorld);
|
|
1726
|
-
|
|
1727
|
-
if (isBehind) continue;
|
|
2081
|
+
if (currentProjection.isClipped(pProj.z)) continue;
|
|
1728
2082
|
const uniforms = item.material.uniforms;
|
|
1729
2083
|
if (!uniforms || !uniforms.uSize) continue;
|
|
1730
2084
|
const uSize = uniforms.uSize.value;
|
|
@@ -1773,12 +2127,16 @@ function createEngine({
|
|
|
1773
2127
|
const id = starIndexToId[pointHit.index];
|
|
1774
2128
|
if (id) {
|
|
1775
2129
|
const node = nodeById.get(id);
|
|
1776
|
-
if (node) return { type: "star", node, index: pointHit.index, point: pointHit.point, object: void 0 };
|
|
2130
|
+
if (node && !isNodeFiltered(node)) return { type: "star", node, index: pointHit.index, point: pointHit.point, object: void 0 };
|
|
1777
2131
|
}
|
|
1778
2132
|
}
|
|
1779
2133
|
}
|
|
1780
2134
|
return void 0;
|
|
1781
2135
|
}
|
|
2136
|
+
function onWindowBlur() {
|
|
2137
|
+
isMouseInWindow = false;
|
|
2138
|
+
edgeHoverStart = 0;
|
|
2139
|
+
}
|
|
1782
2140
|
function onMouseDown(e) {
|
|
1783
2141
|
state.lastMouseX = e.clientX;
|
|
1784
2142
|
state.lastMouseY = e.clientY;
|
|
@@ -1816,6 +2174,7 @@ function createEngine({
|
|
|
1816
2174
|
}
|
|
1817
2175
|
return;
|
|
1818
2176
|
}
|
|
2177
|
+
flyToActive = false;
|
|
1819
2178
|
state.dragMode = "camera";
|
|
1820
2179
|
state.isDragging = true;
|
|
1821
2180
|
state.velocityX = 0;
|
|
@@ -1874,11 +2233,13 @@ function createEngine({
|
|
|
1874
2233
|
state.lastMouseX = e.clientX;
|
|
1875
2234
|
state.lastMouseY = e.clientY;
|
|
1876
2235
|
const speedScale = state.fov / ENGINE_CONFIG.defaultFov;
|
|
2236
|
+
const rotLock = Math.max(0, Math.min(1, (state.fov - 100) / (ENGINE_CONFIG.maxFov - 100)));
|
|
2237
|
+
const latFactor = 1 - rotLock * rotLock;
|
|
1877
2238
|
state.targetLon += deltaX * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
1878
|
-
state.targetLat += deltaY * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
2239
|
+
state.targetLat += deltaY * ENGINE_CONFIG.dragSpeed * speedScale * latFactor;
|
|
1879
2240
|
state.targetLat = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, state.targetLat));
|
|
1880
2241
|
state.velocityX = deltaX * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
1881
|
-
state.velocityY = deltaY * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
2242
|
+
state.velocityY = deltaY * ENGINE_CONFIG.dragSpeed * speedScale * latFactor;
|
|
1882
2243
|
state.lon = state.targetLon;
|
|
1883
2244
|
state.lat = state.targetLat;
|
|
1884
2245
|
} else {
|
|
@@ -1912,6 +2273,9 @@ function createEngine({
|
|
|
1912
2273
|
}
|
|
1913
2274
|
}
|
|
1914
2275
|
function onMouseUp(e) {
|
|
2276
|
+
const dx = e.clientX - state.lastMouseX;
|
|
2277
|
+
const dy = e.clientY - state.lastMouseY;
|
|
2278
|
+
const movedDist = Math.sqrt(dx * dx + dy * dy);
|
|
1915
2279
|
if (state.dragMode === "node") {
|
|
1916
2280
|
const fullArr = getFullArrangement();
|
|
1917
2281
|
handlers.onArrangementChange?.(fullArr);
|
|
@@ -1924,6 +2288,17 @@ function createEngine({
|
|
|
1924
2288
|
state.isDragging = false;
|
|
1925
2289
|
state.dragMode = "none";
|
|
1926
2290
|
document.body.style.cursor = "default";
|
|
2291
|
+
if (movedDist < 5) {
|
|
2292
|
+
const hit = pick(e);
|
|
2293
|
+
if (hit) {
|
|
2294
|
+
handlers.onSelect?.(hit.node);
|
|
2295
|
+
constellationLayer.setFocused(hit.node.id);
|
|
2296
|
+
if (hit.node.level === 2) setFocusedBook(hit.node.id);
|
|
2297
|
+
else if (hit.node.level === 3 && hit.node.parent) setFocusedBook(hit.node.parent);
|
|
2298
|
+
} else {
|
|
2299
|
+
setFocusedBook(null);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
1927
2302
|
} else {
|
|
1928
2303
|
const hit = pick(e);
|
|
1929
2304
|
if (hit) {
|
|
@@ -1938,6 +2313,7 @@ function createEngine({
|
|
|
1938
2313
|
}
|
|
1939
2314
|
function onWheel(e) {
|
|
1940
2315
|
e.preventDefault();
|
|
2316
|
+
flyToActive = false;
|
|
1941
2317
|
const aspect = container.clientWidth / container.clientHeight;
|
|
1942
2318
|
renderer.domElement.getBoundingClientRect();
|
|
1943
2319
|
const vBefore = getMouseViewVector(state.fov, aspect);
|
|
@@ -1948,6 +2324,17 @@ function createEngine({
|
|
|
1948
2324
|
updateUniforms();
|
|
1949
2325
|
const vAfter = getMouseViewVector(state.fov, aspect);
|
|
1950
2326
|
const quaternion = new THREE5.Quaternion().setFromUnitVectors(vAfter, vBefore);
|
|
2327
|
+
const dampStartFov = 40;
|
|
2328
|
+
const dampEndFov = 120;
|
|
2329
|
+
let spinAmount = 1;
|
|
2330
|
+
if (state.fov > dampStartFov) {
|
|
2331
|
+
const t = Math.max(0, Math.min(1, (state.fov - dampStartFov) / (dampEndFov - dampStartFov)));
|
|
2332
|
+
spinAmount = 1 - Math.pow(t, 1.5) * 0.8;
|
|
2333
|
+
}
|
|
2334
|
+
if (spinAmount < 0.999) {
|
|
2335
|
+
const identityQuat = new THREE5.Quaternion();
|
|
2336
|
+
quaternion.slerp(identityQuat, 1 - spinAmount);
|
|
2337
|
+
}
|
|
1951
2338
|
const y = Math.sin(state.lat);
|
|
1952
2339
|
const r = Math.cos(state.lat);
|
|
1953
2340
|
const x = r * Math.sin(state.lon);
|
|
@@ -1996,9 +2383,8 @@ function createEngine({
|
|
|
1996
2383
|
el.addEventListener("mouseenter", () => {
|
|
1997
2384
|
isMouseInWindow = true;
|
|
1998
2385
|
});
|
|
1999
|
-
el.addEventListener("mouseleave",
|
|
2000
|
-
|
|
2001
|
-
});
|
|
2386
|
+
el.addEventListener("mouseleave", onWindowBlur);
|
|
2387
|
+
window.addEventListener("blur", onWindowBlur);
|
|
2002
2388
|
raf = requestAnimationFrame(tick);
|
|
2003
2389
|
}
|
|
2004
2390
|
function tick() {
|
|
@@ -2027,6 +2413,17 @@ function createEngine({
|
|
|
2027
2413
|
if (m.uniforms.uOrderRevealStrength) m.uniforms.uOrderRevealStrength.value = orderRevealStrength;
|
|
2028
2414
|
}
|
|
2029
2415
|
}
|
|
2416
|
+
const filterTarget = currentFilter ? 1 : 0;
|
|
2417
|
+
filterStrength = mix(filterStrength, filterTarget, 0.1);
|
|
2418
|
+
if (filterStrength > 1e-3 || filterTarget > 0) {
|
|
2419
|
+
if (starPoints && starPoints.material) {
|
|
2420
|
+
const m = starPoints.material;
|
|
2421
|
+
if (m.uniforms.uFilterTestamentIndex) m.uniforms.uFilterTestamentIndex.value = filterTestamentIndex;
|
|
2422
|
+
if (m.uniforms.uFilterDivisionIndex) m.uniforms.uFilterDivisionIndex.value = filterDivisionIndex;
|
|
2423
|
+
if (m.uniforms.uFilterBookIndex) m.uniforms.uFilterBookIndex.value = filterBookIndex;
|
|
2424
|
+
if (m.uniforms.uFilterStrength) m.uniforms.uFilterStrength.value = filterStrength;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2030
2427
|
let panX = 0;
|
|
2031
2428
|
let panY = 0;
|
|
2032
2429
|
if (!state.isDragging && isMouseInWindow && !currentConfig?.editable) {
|
|
@@ -2058,12 +2455,28 @@ function createEngine({
|
|
|
2058
2455
|
} else {
|
|
2059
2456
|
edgeHoverStart = 0;
|
|
2060
2457
|
}
|
|
2458
|
+
if (flyToActive && !state.isDragging) {
|
|
2459
|
+
state.lon = mix(state.lon, flyToTargetLon, FLY_TO_SPEED);
|
|
2460
|
+
state.lat = mix(state.lat, flyToTargetLat, FLY_TO_SPEED);
|
|
2461
|
+
state.fov = mix(state.fov, flyToTargetFov, FLY_TO_SPEED);
|
|
2462
|
+
state.targetLon = state.lon;
|
|
2463
|
+
state.targetLat = state.lat;
|
|
2464
|
+
state.velocityX = 0;
|
|
2465
|
+
state.velocityY = 0;
|
|
2466
|
+
handlers.onFovChange?.(state.fov);
|
|
2467
|
+
if (Math.abs(state.lon - flyToTargetLon) < 1e-4 && Math.abs(state.lat - flyToTargetLat) < 1e-4 && Math.abs(state.fov - flyToTargetFov) < 0.05) {
|
|
2468
|
+
flyToActive = false;
|
|
2469
|
+
state.lon = flyToTargetLon;
|
|
2470
|
+
state.lat = flyToTargetLat;
|
|
2471
|
+
state.fov = flyToTargetFov;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2061
2474
|
if (Math.abs(panX) > 0 || Math.abs(panY) > 0) {
|
|
2062
2475
|
state.lon += panX;
|
|
2063
2476
|
state.lat += panY;
|
|
2064
2477
|
state.targetLon = state.lon;
|
|
2065
2478
|
state.targetLat = state.lat;
|
|
2066
|
-
} else if (!state.isDragging) {
|
|
2479
|
+
} else if (!state.isDragging && !flyToActive) {
|
|
2067
2480
|
state.lon += state.velocityX;
|
|
2068
2481
|
state.lat += state.velocityY;
|
|
2069
2482
|
state.velocityX *= ENGINE_CONFIG.inertiaDamping;
|
|
@@ -2084,13 +2497,30 @@ function createEngine({
|
|
|
2084
2497
|
camera.updateMatrixWorld();
|
|
2085
2498
|
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
|
|
2086
2499
|
updateUniforms();
|
|
2087
|
-
|
|
2500
|
+
const nowSec = now / 1e3;
|
|
2501
|
+
const dt = lastTickTime > 0 ? Math.min(nowSec - lastTickTime, 0.1) : 0.016;
|
|
2502
|
+
lastTickTime = nowSec;
|
|
2503
|
+
linesFader.target = currentConfig?.showConstellationLines ?? false;
|
|
2504
|
+
linesFader.update(dt);
|
|
2505
|
+
artFader.target = currentConfig?.showConstellationArt ?? false;
|
|
2506
|
+
artFader.update(dt);
|
|
2507
|
+
constellationLayer.update(state.fov, artFader.eased > 0.01);
|
|
2508
|
+
if (artFader.eased < 1) {
|
|
2509
|
+
constellationLayer.setGlobalOpacity?.(artFader.eased);
|
|
2510
|
+
}
|
|
2088
2511
|
backdropGroup.visible = currentConfig?.showBackdropStars ?? true;
|
|
2089
2512
|
if (atmosphereMesh) atmosphereMesh.visible = currentConfig?.showAtmosphere ?? false;
|
|
2090
2513
|
const DIVISION_THRESHOLD = 60;
|
|
2091
2514
|
const showDivisions = state.fov > DIVISION_THRESHOLD;
|
|
2092
2515
|
if (constellationLines) {
|
|
2093
|
-
constellationLines.visible =
|
|
2516
|
+
constellationLines.visible = linesFader.eased > 0.01;
|
|
2517
|
+
if (constellationLines.visible && constellationLines.material) {
|
|
2518
|
+
const mat = constellationLines.material;
|
|
2519
|
+
if (mat.uniforms?.color) {
|
|
2520
|
+
mat.uniforms.color.value.setHex(11193599);
|
|
2521
|
+
mat.opacity = linesFader.eased;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2094
2524
|
}
|
|
2095
2525
|
if (boundaryLines) {
|
|
2096
2526
|
boundaryLines.visible = currentConfig?.showDivisionBoundaries ?? false;
|
|
@@ -2111,8 +2541,7 @@ function createEngine({
|
|
|
2111
2541
|
const showDivisionLabels = currentConfig?.showDivisionLabels === true;
|
|
2112
2542
|
const showChapterLabels = currentConfig?.showChapterLabels === true;
|
|
2113
2543
|
const showGroupLabels = currentConfig?.showGroupLabels === true;
|
|
2114
|
-
const
|
|
2115
|
-
const showChapters = state.fov < 70;
|
|
2544
|
+
const showChapters = state.fov < 45;
|
|
2116
2545
|
for (const item of dynamicLabels) {
|
|
2117
2546
|
const uniforms = item.obj.material.uniforms;
|
|
2118
2547
|
const level = item.node.level;
|
|
@@ -2133,11 +2562,6 @@ function createEngine({
|
|
|
2133
2562
|
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
2134
2563
|
continue;
|
|
2135
2564
|
}
|
|
2136
|
-
if (level === 2 && !showBooks && item.node.id !== state.draggedNodeId) {
|
|
2137
|
-
uniforms.uAlpha.value = THREE5.MathUtils.lerp(uniforms.uAlpha.value, 0, 0.2);
|
|
2138
|
-
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
2139
|
-
continue;
|
|
2140
|
-
}
|
|
2141
2565
|
if ((level === 3 || level === 2.5) && !showChapters && item.node.id !== state.draggedNodeId) {
|
|
2142
2566
|
uniforms.uAlpha.value = THREE5.MathUtils.lerp(uniforms.uAlpha.value, 0, 0.2);
|
|
2143
2567
|
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
@@ -2170,8 +2594,8 @@ function createEngine({
|
|
|
2170
2594
|
const isSpecial = l.item.node.id === selectedId || l.item.node.id === hoverId;
|
|
2171
2595
|
if (l.level === 1) {
|
|
2172
2596
|
let rot = 0;
|
|
2173
|
-
const
|
|
2174
|
-
if (
|
|
2597
|
+
const isWideAngle = currentProjection.id !== "perspective";
|
|
2598
|
+
if (isWideAngle) {
|
|
2175
2599
|
const dx = l.sX - screenW / 2;
|
|
2176
2600
|
const dy = l.sY - screenH / 2;
|
|
2177
2601
|
rot = Math.atan2(-dy, -dx) - Math.PI / 2;
|
|
@@ -2179,7 +2603,7 @@ function createEngine({
|
|
|
2179
2603
|
l.uniforms.uAngle.value = THREE5.MathUtils.lerp(l.uniforms.uAngle.value, rot, 0.1);
|
|
2180
2604
|
}
|
|
2181
2605
|
if (l.level === 2) {
|
|
2182
|
-
|
|
2606
|
+
{
|
|
2183
2607
|
target2 = 1;
|
|
2184
2608
|
occupied.push({ x: l.sX - l.w / 2, y: l.sY - l.h / 2, w: l.w, h: l.h });
|
|
2185
2609
|
}
|
|
@@ -2201,6 +2625,17 @@ function createEngine({
|
|
|
2201
2625
|
}
|
|
2202
2626
|
}
|
|
2203
2627
|
}
|
|
2628
|
+
if (target2 > 0 && currentFilter && filterStrength > 0.01) {
|
|
2629
|
+
const node = l.item.node;
|
|
2630
|
+
if (node.level === 3) {
|
|
2631
|
+
target2 = 0;
|
|
2632
|
+
} else if (node.level === 2 || node.level === 2.5) {
|
|
2633
|
+
const nodeToCheck = node.level === 2.5 && node.parent ? nodeById.get(node.parent) : node;
|
|
2634
|
+
if (nodeToCheck && isNodeFiltered(nodeToCheck)) {
|
|
2635
|
+
target2 = 0;
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2204
2639
|
l.uniforms.uAlpha.value = THREE5.MathUtils.lerp(l.uniforms.uAlpha.value, target2, 0.1);
|
|
2205
2640
|
l.item.obj.visible = l.uniforms.uAlpha.value > 0.01;
|
|
2206
2641
|
}
|
|
@@ -2215,6 +2650,8 @@ function createEngine({
|
|
|
2215
2650
|
window.removeEventListener("mousemove", onMouseMove);
|
|
2216
2651
|
window.removeEventListener("mouseup", onMouseUp);
|
|
2217
2652
|
el.removeEventListener("wheel", onWheel);
|
|
2653
|
+
el.removeEventListener("mouseleave", onWindowBlur);
|
|
2654
|
+
window.removeEventListener("blur", onWindowBlur);
|
|
2218
2655
|
}
|
|
2219
2656
|
function dispose() {
|
|
2220
2657
|
stop();
|
|
@@ -2235,7 +2672,30 @@ function createEngine({
|
|
|
2235
2672
|
function setOrderRevealEnabled(enabled) {
|
|
2236
2673
|
orderRevealEnabled = enabled;
|
|
2237
2674
|
}
|
|
2238
|
-
|
|
2675
|
+
function flyTo(nodeId, targetFov) {
|
|
2676
|
+
const node = nodeById.get(nodeId);
|
|
2677
|
+
if (!node) return;
|
|
2678
|
+
const pos = getPosition(node).normalize();
|
|
2679
|
+
flyToTargetLat = Math.asin(Math.max(-0.999, Math.min(0.999, pos.y)));
|
|
2680
|
+
flyToTargetLon = Math.atan2(pos.x, -pos.z);
|
|
2681
|
+
flyToTargetFov = targetFov ?? ENGINE_CONFIG.minFov;
|
|
2682
|
+
flyToActive = true;
|
|
2683
|
+
state.velocityX = 0;
|
|
2684
|
+
state.velocityY = 0;
|
|
2685
|
+
}
|
|
2686
|
+
function setHierarchyFilter(filter) {
|
|
2687
|
+
currentFilter = filter;
|
|
2688
|
+
if (filter) {
|
|
2689
|
+
filterTestamentIndex = filter.testament && testamentToIndex.has(filter.testament) ? testamentToIndex.get(filter.testament) : -1;
|
|
2690
|
+
filterDivisionIndex = filter.division && divisionToIndex.has(filter.division) ? divisionToIndex.get(filter.division) : -1;
|
|
2691
|
+
filterBookIndex = filter.bookKey && bookIdToIndex.has(`B:${filter.bookKey}`) ? bookIdToIndex.get(`B:${filter.bookKey}`) : -1;
|
|
2692
|
+
} else {
|
|
2693
|
+
filterTestamentIndex = -1;
|
|
2694
|
+
filterDivisionIndex = -1;
|
|
2695
|
+
filterBookIndex = -1;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
return { setConfig, start, stop, dispose, setHandlers, getFullArrangement, setHoveredBook, setFocusedBook, setOrderRevealEnabled, setHierarchyFilter, flyTo, setProjection };
|
|
2239
2699
|
}
|
|
2240
2700
|
var ENGINE_CONFIG, ORDER_REVEAL_CONFIG;
|
|
2241
2701
|
var init_createEngine = __esm({
|
|
@@ -2243,16 +2703,18 @@ var init_createEngine = __esm({
|
|
|
2243
2703
|
init_layout();
|
|
2244
2704
|
init_materials();
|
|
2245
2705
|
init_ConstellationArtworkLayer();
|
|
2706
|
+
init_projections();
|
|
2707
|
+
init_fader();
|
|
2246
2708
|
ENGINE_CONFIG = {
|
|
2247
2709
|
minFov: 10,
|
|
2248
|
-
maxFov:
|
|
2249
|
-
defaultFov:
|
|
2710
|
+
maxFov: 135,
|
|
2711
|
+
defaultFov: 50,
|
|
2250
2712
|
dragSpeed: 125e-5,
|
|
2251
2713
|
inertiaDamping: 0.92,
|
|
2252
|
-
blendStart:
|
|
2253
|
-
blendEnd:
|
|
2254
|
-
zenithStartFov:
|
|
2255
|
-
zenithStrength: 0.
|
|
2714
|
+
blendStart: 35,
|
|
2715
|
+
blendEnd: 83,
|
|
2716
|
+
zenithStartFov: 75,
|
|
2717
|
+
zenithStrength: 0.15,
|
|
2256
2718
|
horizonLockStrength: 0.05,
|
|
2257
2719
|
edgePanThreshold: 0.15,
|
|
2258
2720
|
edgePanMaxSpeed: 0.02,
|
|
@@ -2274,7 +2736,10 @@ var StarMap = forwardRef(
|
|
|
2274
2736
|
getFullArrangement: () => engineRef.current?.getFullArrangement?.(),
|
|
2275
2737
|
setHoveredBook: (id) => engineRef.current?.setHoveredBook?.(id),
|
|
2276
2738
|
setFocusedBook: (id) => engineRef.current?.setFocusedBook?.(id),
|
|
2277
|
-
setOrderRevealEnabled: (enabled) => engineRef.current?.setOrderRevealEnabled?.(enabled)
|
|
2739
|
+
setOrderRevealEnabled: (enabled) => engineRef.current?.setOrderRevealEnabled?.(enabled),
|
|
2740
|
+
setHierarchyFilter: (filter) => engineRef.current?.setHierarchyFilter?.(filter),
|
|
2741
|
+
flyTo: (nodeId, targetFov) => engineRef.current?.flyTo?.(nodeId, targetFov),
|
|
2742
|
+
setProjection: (id) => engineRef.current?.setProjection?.(id)
|
|
2278
2743
|
}));
|
|
2279
2744
|
useEffect(() => {
|
|
2280
2745
|
let disposed = false;
|
|
@@ -31551,6 +32016,9 @@ function generateArrangement(bible, options = {}) {
|
|
|
31551
32016
|
return arrangement;
|
|
31552
32017
|
}
|
|
31553
32018
|
|
|
31554
|
-
|
|
32019
|
+
// src/index.ts
|
|
32020
|
+
init_projections();
|
|
32021
|
+
|
|
32022
|
+
export { PROJECTIONS, StarMap, bibleToSceneModel, defaultGenerateOptions, default_stars_default as defaultStars, generateArrangement };
|
|
31555
32023
|
//# sourceMappingURL=index.js.map
|
|
31556
32024
|
//# sourceMappingURL=index.js.map
|