@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.cjs
CHANGED
|
@@ -341,45 +341,60 @@ var init_shaders = __esm({
|
|
|
341
341
|
uniform float uScale;
|
|
342
342
|
uniform float uAspect;
|
|
343
343
|
uniform float uBlend;
|
|
344
|
+
uniform int uProjectionType;
|
|
344
345
|
|
|
345
346
|
vec4 smartProject(vec4 viewPos) {
|
|
346
347
|
vec3 dir = normalize(viewPos.xyz);
|
|
347
348
|
float dist = length(viewPos.xyz);
|
|
348
|
-
float
|
|
349
|
-
|
|
350
|
-
float kLinear = 1.0 / zLinear;
|
|
351
|
-
float k = mix(kLinear, kStereo, uBlend);
|
|
352
|
-
vec2 projected = vec2(k * dir.x, k * dir.y);
|
|
353
|
-
projected *= uScale;
|
|
354
|
-
projected.x /= uAspect;
|
|
355
|
-
float zMetric = -1.0 + (dist / 15000.0);
|
|
356
|
-
|
|
349
|
+
float k;
|
|
350
|
+
|
|
357
351
|
// Radial Clipping: Push clipped points off-screen in their natural direction
|
|
358
352
|
// to prevent lines "darting" across the center.
|
|
359
353
|
vec2 escapeDir = (length(dir.xy) > 0.0001) ? normalize(dir.xy) : vec2(1.0, 1.0);
|
|
360
354
|
vec2 escapePos = escapeDir * 10000.0;
|
|
361
355
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
356
|
+
if (uProjectionType == 0) {
|
|
357
|
+
// Perspective
|
|
358
|
+
if (dir.z > -0.1) return vec4(escapePos, 10.0, 1.0);
|
|
359
|
+
k = 1.0 / max(0.01, -dir.z);
|
|
360
|
+
} else if (uProjectionType == 1) {
|
|
361
|
+
// Stereographic \u2014 tighter clip to prevent stretch near singularity
|
|
362
|
+
if (dir.z > 0.1) return vec4(escapePos, 10.0, 1.0);
|
|
363
|
+
k = 2.0 / (1.0 - dir.z);
|
|
364
|
+
} else {
|
|
365
|
+
// Blended (auto-blend behavior)
|
|
366
|
+
float zLinear = max(0.01, -dir.z);
|
|
367
|
+
float kStereo = 2.0 / (1.0 - dir.z);
|
|
368
|
+
float kLinear = 1.0 / zLinear;
|
|
369
|
+
k = mix(kLinear, kStereo, uBlend);
|
|
370
|
+
|
|
371
|
+
// Tighter clip threshold that scales with blend factor
|
|
372
|
+
float clipZ = mix(-0.1, 0.1, uBlend);
|
|
373
|
+
if (dir.z > clipZ) return vec4(escapePos, 10.0, 1.0);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
vec2 projected = vec2(k * dir.x, k * dir.y);
|
|
377
|
+
projected *= uScale;
|
|
378
|
+
projected.x /= uAspect;
|
|
379
|
+
float zMetric = -1.0 + (dist / 15000.0);
|
|
380
|
+
|
|
367
381
|
return vec4(projected, zMetric, 1.0);
|
|
368
382
|
}
|
|
369
383
|
`;
|
|
370
384
|
MASK_CHUNK = `
|
|
371
385
|
uniform float uAspect;
|
|
372
386
|
uniform float uBlend;
|
|
387
|
+
uniform int uProjectionType;
|
|
373
388
|
varying vec2 vScreenPos;
|
|
374
389
|
float getMaskAlpha() {
|
|
375
|
-
|
|
390
|
+
// No artificial circular mask \u2014 the horizon, atmosphere, and ground
|
|
391
|
+
// define the dome boundary naturally (as Stellarium does).
|
|
392
|
+
// Only apply a minimal edge softening to catch stray back-face artifacts.
|
|
376
393
|
vec2 p = vScreenPos;
|
|
377
394
|
p.x *= uAspect;
|
|
378
395
|
float dist = length(p);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
float edgeSoftness = mix(0.5, 0.02, t);
|
|
382
|
-
return 1.0 - smoothstep(currentRadius - edgeSoftness, currentRadius, dist);
|
|
396
|
+
// Gentle falloff only at extreme screen edges (beyond NDC ~1.8)
|
|
397
|
+
return 1.0 - smoothstep(1.8, 2.0, dist);
|
|
383
398
|
}
|
|
384
399
|
`;
|
|
385
400
|
}
|
|
@@ -412,13 +427,15 @@ var init_materials = __esm({
|
|
|
412
427
|
uScale: { value: 1 },
|
|
413
428
|
uAspect: { value: 1 },
|
|
414
429
|
uBlend: { value: 0 },
|
|
430
|
+
uProjectionType: { value: 2 },
|
|
431
|
+
// 0=perspective, 1=stereographic, 2=blended
|
|
415
432
|
uTime: { value: 0 },
|
|
416
433
|
// Atmosphere Settings
|
|
417
434
|
uAtmGlow: { value: 1 },
|
|
418
435
|
uAtmDark: { value: 0.6 },
|
|
419
436
|
uAtmExtinction: { value: 4 },
|
|
420
437
|
uAtmTwinkle: { value: 0.8 },
|
|
421
|
-
uColorHorizon: { value: new THREE5__namespace.Color(
|
|
438
|
+
uColorHorizon: { value: new THREE5__namespace.Color(3825292) },
|
|
422
439
|
uColorZenith: { value: new THREE5__namespace.Color(132104) }
|
|
423
440
|
};
|
|
424
441
|
}
|
|
@@ -617,6 +634,10 @@ var init_ConstellationArtworkLayer = __esm({
|
|
|
617
634
|
this.items.push({ config: c, mesh, material, baseOpacity: c.opacity });
|
|
618
635
|
});
|
|
619
636
|
}
|
|
637
|
+
_globalOpacity = 1;
|
|
638
|
+
setGlobalOpacity(v) {
|
|
639
|
+
this._globalOpacity = v;
|
|
640
|
+
}
|
|
620
641
|
update(fov, showArt) {
|
|
621
642
|
this.root.visible = showArt;
|
|
622
643
|
if (!showArt) return;
|
|
@@ -631,7 +652,7 @@ var init_ConstellationArtworkLayer = __esm({
|
|
|
631
652
|
const t = (fade.zoomInStart - fov) / (fade.zoomInStart - fade.zoomInEnd);
|
|
632
653
|
opacity = THREE5__namespace.MathUtils.lerp(fade.maxOpacity, fade.minOpacity, t);
|
|
633
654
|
}
|
|
634
|
-
opacity = Math.min(Math.max(opacity, 0), 1);
|
|
655
|
+
opacity = Math.min(Math.max(opacity, 0), 1) * this._globalOpacity;
|
|
635
656
|
item.material.uniforms.uOpacity.value = opacity;
|
|
636
657
|
}
|
|
637
658
|
}
|
|
@@ -657,6 +678,164 @@ var init_ConstellationArtworkLayer = __esm({
|
|
|
657
678
|
}
|
|
658
679
|
});
|
|
659
680
|
|
|
681
|
+
// src/engine/projections.ts
|
|
682
|
+
var PerspectiveProjection, StereographicProjection, BlendedProjection; exports.PROJECTIONS = void 0;
|
|
683
|
+
var init_projections = __esm({
|
|
684
|
+
"src/engine/projections.ts"() {
|
|
685
|
+
PerspectiveProjection = class {
|
|
686
|
+
id = "perspective";
|
|
687
|
+
label = "Perspective";
|
|
688
|
+
maxFov = 160;
|
|
689
|
+
glslProjectionType = 0;
|
|
690
|
+
forward(dir) {
|
|
691
|
+
if (dir.z > -0.1) return null;
|
|
692
|
+
const k = 1 / Math.max(0.01, -dir.z);
|
|
693
|
+
return { x: k * dir.x, y: k * dir.y, z: dir.z };
|
|
694
|
+
}
|
|
695
|
+
inverse(uvX, uvY, fovRad) {
|
|
696
|
+
const halfHeight = Math.tan(fovRad / 2);
|
|
697
|
+
const r = Math.sqrt(uvX * uvX + uvY * uvY);
|
|
698
|
+
const theta = Math.atan(r * halfHeight);
|
|
699
|
+
const phi = Math.atan2(uvY, uvX);
|
|
700
|
+
const sinT = Math.sin(theta);
|
|
701
|
+
return {
|
|
702
|
+
x: sinT * Math.cos(phi),
|
|
703
|
+
y: sinT * Math.sin(phi),
|
|
704
|
+
z: -Math.cos(theta)
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
getScale(fovRad) {
|
|
708
|
+
return 1 / Math.tan(fovRad / 2);
|
|
709
|
+
}
|
|
710
|
+
isClipped(dirZ) {
|
|
711
|
+
return dirZ > -0.1;
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
StereographicProjection = class {
|
|
715
|
+
id = "stereographic";
|
|
716
|
+
label = "Stereographic";
|
|
717
|
+
maxFov = 360;
|
|
718
|
+
glslProjectionType = 1;
|
|
719
|
+
forward(dir) {
|
|
720
|
+
if (dir.z > 0.4) return null;
|
|
721
|
+
const k = 2 / (1 - dir.z);
|
|
722
|
+
return { x: k * dir.x, y: k * dir.y, z: dir.z };
|
|
723
|
+
}
|
|
724
|
+
inverse(uvX, uvY, fovRad) {
|
|
725
|
+
const halfHeight = 2 * Math.tan(fovRad / 4);
|
|
726
|
+
const r = Math.sqrt(uvX * uvX + uvY * uvY);
|
|
727
|
+
const theta = 2 * Math.atan(r * halfHeight / 2);
|
|
728
|
+
const phi = Math.atan2(uvY, uvX);
|
|
729
|
+
const sinT = Math.sin(theta);
|
|
730
|
+
return {
|
|
731
|
+
x: sinT * Math.cos(phi),
|
|
732
|
+
y: sinT * Math.sin(phi),
|
|
733
|
+
z: -Math.cos(theta)
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
getScale(fovRad) {
|
|
737
|
+
return 1 / (2 * Math.tan(fovRad / 4));
|
|
738
|
+
}
|
|
739
|
+
isClipped(dirZ) {
|
|
740
|
+
return dirZ > 0.4;
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
BlendedProjection = class {
|
|
744
|
+
id = "blended";
|
|
745
|
+
label = "Blended (Auto)";
|
|
746
|
+
maxFov = 165;
|
|
747
|
+
glslProjectionType = 2;
|
|
748
|
+
/** FOV thresholds for blend transition (degrees) */
|
|
749
|
+
blendStart = 40;
|
|
750
|
+
blendEnd = 100;
|
|
751
|
+
/** Current blend factor, updated via setFov() */
|
|
752
|
+
blend = 0;
|
|
753
|
+
/** Call this each frame / when FOV changes so forward/inverse stay in sync */
|
|
754
|
+
setFov(fovDeg) {
|
|
755
|
+
if (fovDeg <= this.blendStart) {
|
|
756
|
+
this.blend = 0;
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
if (fovDeg >= this.blendEnd) {
|
|
760
|
+
this.blend = 1;
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const t = (fovDeg - this.blendStart) / (this.blendEnd - this.blendStart);
|
|
764
|
+
this.blend = t * t * (3 - 2 * t);
|
|
765
|
+
}
|
|
766
|
+
getBlend() {
|
|
767
|
+
return this.blend;
|
|
768
|
+
}
|
|
769
|
+
forward(dir) {
|
|
770
|
+
if (this.blend > 0.5 && dir.z > 0.4) return null;
|
|
771
|
+
if (this.blend < 0.1 && dir.z > -0.1) return null;
|
|
772
|
+
const kLinear = 1 / Math.max(0.01, -dir.z);
|
|
773
|
+
const kStereo = 2 / (1 - dir.z);
|
|
774
|
+
const k = kLinear * (1 - this.blend) + kStereo * this.blend;
|
|
775
|
+
return { x: k * dir.x, y: k * dir.y, z: dir.z };
|
|
776
|
+
}
|
|
777
|
+
inverse(uvX, uvY, fovRad) {
|
|
778
|
+
const r = Math.sqrt(uvX * uvX + uvY * uvY);
|
|
779
|
+
const halfHeightLin = Math.tan(fovRad / 2);
|
|
780
|
+
const thetaLin = Math.atan(r * halfHeightLin);
|
|
781
|
+
const halfHeightStereo = 2 * Math.tan(fovRad / 4);
|
|
782
|
+
const thetaStereo = 2 * Math.atan(r * halfHeightStereo / 2);
|
|
783
|
+
const theta = thetaLin * (1 - this.blend) + thetaStereo * this.blend;
|
|
784
|
+
const phi = Math.atan2(uvY, uvX);
|
|
785
|
+
const sinT = Math.sin(theta);
|
|
786
|
+
return {
|
|
787
|
+
x: sinT * Math.cos(phi),
|
|
788
|
+
y: sinT * Math.sin(phi),
|
|
789
|
+
z: -Math.cos(theta)
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
getScale(fovRad) {
|
|
793
|
+
const scaleLinear = 1 / Math.tan(fovRad / 2);
|
|
794
|
+
const scaleStereo = 1 / (2 * Math.tan(fovRad / 4));
|
|
795
|
+
return scaleLinear * (1 - this.blend) + scaleStereo * this.blend;
|
|
796
|
+
}
|
|
797
|
+
isClipped(dirZ) {
|
|
798
|
+
if (this.blend > 0.5) return dirZ > 0.4;
|
|
799
|
+
if (this.blend < 0.1) return dirZ > -0.1;
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
exports.PROJECTIONS = {
|
|
804
|
+
perspective: () => new PerspectiveProjection(),
|
|
805
|
+
stereographic: () => new StereographicProjection(),
|
|
806
|
+
blended: () => new BlendedProjection()
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// src/engine/fader.ts
|
|
812
|
+
var Fader;
|
|
813
|
+
var init_fader = __esm({
|
|
814
|
+
"src/engine/fader.ts"() {
|
|
815
|
+
Fader = class {
|
|
816
|
+
target = false;
|
|
817
|
+
value = 0;
|
|
818
|
+
duration;
|
|
819
|
+
constructor(duration = 0.3) {
|
|
820
|
+
this.duration = duration;
|
|
821
|
+
}
|
|
822
|
+
update(dt) {
|
|
823
|
+
const goal = this.target ? 1 : 0;
|
|
824
|
+
if (this.value === goal) return;
|
|
825
|
+
const speed = 1 / this.duration;
|
|
826
|
+
const step = speed * dt;
|
|
827
|
+
const diff = goal - this.value;
|
|
828
|
+
this.value += Math.sign(diff) * Math.min(step, Math.abs(diff));
|
|
829
|
+
}
|
|
830
|
+
/** Smoothstep-eased value for perceptually smooth transitions */
|
|
831
|
+
get eased() {
|
|
832
|
+
const v = this.value;
|
|
833
|
+
return v * v * (3 - 2 * v);
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
|
|
660
839
|
// src/engine/createEngine.ts
|
|
661
840
|
var createEngine_exports = {};
|
|
662
841
|
__export(createEngine_exports, {
|
|
@@ -674,9 +853,21 @@ function createEngine({
|
|
|
674
853
|
let orderRevealEnabled = true;
|
|
675
854
|
let activeBookIndex = -1;
|
|
676
855
|
let orderRevealStrength = 0;
|
|
856
|
+
let flyToActive = false;
|
|
857
|
+
let flyToTargetLon = 0;
|
|
858
|
+
let flyToTargetLat = 0;
|
|
859
|
+
let flyToTargetFov = ENGINE_CONFIG.minFov;
|
|
860
|
+
const FLY_TO_SPEED = 0.04;
|
|
861
|
+
let currentFilter = null;
|
|
862
|
+
let filterStrength = 0;
|
|
863
|
+
let filterTestamentIndex = -1;
|
|
864
|
+
let filterDivisionIndex = -1;
|
|
865
|
+
let filterBookIndex = -1;
|
|
677
866
|
const hoverCooldowns = /* @__PURE__ */ new Map();
|
|
678
867
|
const COOLDOWN_MS = 2e3;
|
|
679
868
|
const bookIdToIndex = /* @__PURE__ */ new Map();
|
|
869
|
+
const testamentToIndex = /* @__PURE__ */ new Map();
|
|
870
|
+
const divisionToIndex = /* @__PURE__ */ new Map();
|
|
680
871
|
const renderer = new THREE5__namespace.WebGLRenderer({ antialias: true, alpha: false });
|
|
681
872
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
|
682
873
|
renderer.setSize(container.clientWidth, container.clientHeight);
|
|
@@ -726,68 +917,53 @@ function createEngine({
|
|
|
726
917
|
function mix(a, b, t) {
|
|
727
918
|
return a * (1 - t) + b * t;
|
|
728
919
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
if (
|
|
732
|
-
|
|
733
|
-
|
|
920
|
+
let currentProjection = exports.PROJECTIONS.blended();
|
|
921
|
+
function syncProjectionState() {
|
|
922
|
+
if (currentProjection instanceof BlendedProjection) {
|
|
923
|
+
currentProjection.setFov(state.fov);
|
|
924
|
+
globalUniforms.uBlend.value = currentProjection.getBlend();
|
|
925
|
+
}
|
|
926
|
+
globalUniforms.uProjectionType.value = currentProjection.glslProjectionType;
|
|
734
927
|
}
|
|
735
928
|
function updateUniforms() {
|
|
736
|
-
|
|
737
|
-
globalUniforms.uBlend.value = blend;
|
|
929
|
+
syncProjectionState();
|
|
738
930
|
const fovRad = state.fov * Math.PI / 180;
|
|
739
|
-
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
931
|
+
let scale = currentProjection.getScale(fovRad);
|
|
932
|
+
const aspect = camera.aspect;
|
|
933
|
+
if (currentConfig?.fitProjection) {
|
|
934
|
+
if (aspect > 1) {
|
|
935
|
+
scale /= aspect;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
globalUniforms.uScale.value = scale;
|
|
939
|
+
globalUniforms.uAspect.value = aspect;
|
|
743
940
|
camera.fov = Math.min(state.fov, ENGINE_CONFIG.defaultFov);
|
|
744
941
|
camera.updateProjectionMatrix();
|
|
745
942
|
}
|
|
746
943
|
function getMouseViewVector(fovDeg, aspectRatio) {
|
|
747
|
-
|
|
944
|
+
syncProjectionState();
|
|
748
945
|
const fovRad = fovDeg * Math.PI / 180;
|
|
749
946
|
const uvX = mouseNDC.x * aspectRatio;
|
|
750
947
|
const uvY = mouseNDC.y;
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
const theta_lin = Math.atan(r_uv * halfHeightLinear);
|
|
754
|
-
const halfHeightStereo = 2 * Math.tan(fovRad / 4);
|
|
755
|
-
const theta_str = 2 * Math.atan(r_uv * halfHeightStereo / 2);
|
|
756
|
-
const theta = mix(theta_lin, theta_str, blend);
|
|
757
|
-
const phi = Math.atan2(uvY, uvX);
|
|
758
|
-
const sinTheta = Math.sin(theta);
|
|
759
|
-
const cosTheta = Math.cos(theta);
|
|
760
|
-
return new THREE5__namespace.Vector3(sinTheta * Math.cos(phi), sinTheta * Math.sin(phi), -cosTheta).normalize();
|
|
948
|
+
const v = currentProjection.inverse(uvX, uvY, fovRad);
|
|
949
|
+
return new THREE5__namespace.Vector3(v.x, v.y, v.z).normalize();
|
|
761
950
|
}
|
|
762
951
|
function getMouseWorldVector(pixelX, pixelY, width, height) {
|
|
763
952
|
const aspect = width / height;
|
|
764
953
|
const ndcX = pixelX / width * 2 - 1;
|
|
765
954
|
const ndcY = -(pixelY / height) * 2 + 1;
|
|
766
|
-
|
|
955
|
+
syncProjectionState();
|
|
767
956
|
const fovRad = state.fov * Math.PI / 180;
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
const r_uv = Math.sqrt(uvX * uvX + uvY * uvY);
|
|
771
|
-
const halfHeightLinear = Math.tan(fovRad / 2);
|
|
772
|
-
const theta_lin = Math.atan(r_uv * halfHeightLinear);
|
|
773
|
-
const halfHeightStereo = 2 * Math.tan(fovRad / 4);
|
|
774
|
-
const theta_str = 2 * Math.atan(r_uv * halfHeightStereo / 2);
|
|
775
|
-
const theta = mix(theta_lin, theta_str, blend);
|
|
776
|
-
const phi = Math.atan2(uvY, uvX);
|
|
777
|
-
const sinTheta = Math.sin(theta);
|
|
778
|
-
const cosTheta = Math.cos(theta);
|
|
779
|
-
const vView = new THREE5__namespace.Vector3(sinTheta * Math.cos(phi), sinTheta * Math.sin(phi), -cosTheta).normalize();
|
|
957
|
+
const v = currentProjection.inverse(ndcX * aspect, ndcY, fovRad);
|
|
958
|
+
const vView = new THREE5__namespace.Vector3(v.x, v.y, v.z).normalize();
|
|
780
959
|
return vView.applyQuaternion(camera.quaternion);
|
|
781
960
|
}
|
|
782
961
|
function smartProjectJS(worldPos) {
|
|
783
962
|
const viewPos = worldPos.clone().applyMatrix4(camera.matrixWorldInverse);
|
|
784
963
|
const dir = viewPos.clone().normalize();
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const blend = globalUniforms.uBlend.value;
|
|
789
|
-
const k = mix(kLinear, kStereo, blend);
|
|
790
|
-
return { x: k * dir.x, y: k * dir.y, z: dir.z };
|
|
964
|
+
const result = currentProjection.forward(dir);
|
|
965
|
+
if (!result) return { x: 0, y: 0, z: dir.z };
|
|
966
|
+
return result;
|
|
791
967
|
}
|
|
792
968
|
const groundGroup = new THREE5__namespace.Group();
|
|
793
969
|
scene.add(groundGroup);
|
|
@@ -797,10 +973,8 @@ function createEngine({
|
|
|
797
973
|
const geometry = new THREE5__namespace.SphereGeometry(radius, 128, 64, 0, Math.PI * 2, Math.PI / 2 - 0.15, Math.PI / 2 + 0.15);
|
|
798
974
|
const material = createSmartMaterial({
|
|
799
975
|
uniforms: {
|
|
800
|
-
color: { value: new THREE5__namespace.Color(
|
|
801
|
-
|
|
802
|
-
fogColor: { value: new THREE5__namespace.Color(331812) }
|
|
803
|
-
// Matches atmosphere bot color
|
|
976
|
+
color: { value: new THREE5__namespace.Color(65794) },
|
|
977
|
+
fogColor: { value: new THREE5__namespace.Color(663098) }
|
|
804
978
|
},
|
|
805
979
|
vertexShaderBody: `
|
|
806
980
|
varying vec3 vPos;
|
|
@@ -826,24 +1000,30 @@ function createEngine({
|
|
|
826
1000
|
// Procedural Horizon (Mountains)
|
|
827
1001
|
float angle = atan(vPos.z, vPos.x);
|
|
828
1002
|
|
|
829
|
-
//
|
|
1003
|
+
// FBM-like terrain with increased amplitude
|
|
830
1004
|
float h = 0.0;
|
|
831
|
-
h += sin(angle * 6.0) *
|
|
832
|
-
h += sin(angle * 13.0 + 1.0) *
|
|
833
|
-
h += sin(angle * 29.0 + 2.0) *
|
|
834
|
-
h += sin(angle * 63.0 + 4.0) *
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
float terrainHeight = h +
|
|
838
|
-
|
|
1005
|
+
h += sin(angle * 6.0) * 35.0;
|
|
1006
|
+
h += sin(angle * 13.0 + 1.0) * 18.0;
|
|
1007
|
+
h += sin(angle * 29.0 + 2.0) * 8.0;
|
|
1008
|
+
h += sin(angle * 63.0 + 4.0) * 3.0;
|
|
1009
|
+
h += sin(angle * 97.0 + 5.0) * 1.5;
|
|
1010
|
+
|
|
1011
|
+
float terrainHeight = h + 12.0;
|
|
1012
|
+
|
|
839
1013
|
if (vPos.y > terrainHeight) discard;
|
|
840
|
-
|
|
841
|
-
// Atmospheric
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1014
|
+
|
|
1015
|
+
// Atmospheric rim glow just below terrain peaks
|
|
1016
|
+
float rimDist = terrainHeight - vPos.y;
|
|
1017
|
+
float rim = exp(-rimDist * 0.15) * 0.4;
|
|
1018
|
+
vec3 rimColor = fogColor * 1.5;
|
|
1019
|
+
|
|
1020
|
+
// Atmospheric haze \u2014 stronger near horizon
|
|
1021
|
+
float fogFactor = smoothstep(-120.0, terrainHeight, vPos.y);
|
|
1022
|
+
vec3 finalCol = mix(color, fogColor, fogFactor * 0.6);
|
|
1023
|
+
|
|
1024
|
+
// Add rim glow near terrain peaks
|
|
1025
|
+
finalCol += rimColor * rim;
|
|
1026
|
+
|
|
847
1027
|
gl_FragColor = vec4(finalCol, 1.0);
|
|
848
1028
|
}
|
|
849
1029
|
`,
|
|
@@ -881,19 +1061,25 @@ function createEngine({
|
|
|
881
1061
|
|
|
882
1062
|
// Altitude angle (Y is up)
|
|
883
1063
|
float h = normalize(vWorldNormal).y;
|
|
884
|
-
|
|
885
|
-
//
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1064
|
+
|
|
1065
|
+
// 1. Base gradient from Horizon to Zenith (wider range)
|
|
1066
|
+
float t = smoothstep(-0.15, 0.7, h);
|
|
1067
|
+
|
|
889
1068
|
// Non-linear mix for realistic sky falloff
|
|
890
|
-
// Zenith darkness adjustment
|
|
891
1069
|
vec3 skyColor = mix(uColorHorizon * uAtmGlow, uColorZenith * (1.0 - uAtmDark), pow(t, 0.6));
|
|
892
|
-
|
|
893
|
-
// 2.
|
|
894
|
-
float
|
|
1070
|
+
|
|
1071
|
+
// 2. Teal tint at mid-altitudes (subtle colour variation)
|
|
1072
|
+
float midBand = exp(-6.0 * pow(h - 0.3, 2.0));
|
|
1073
|
+
skyColor += vec3(0.05, 0.12, 0.15) * midBand * uAtmGlow;
|
|
1074
|
+
|
|
1075
|
+
// 3. Primary horizon glow band (wider than before)
|
|
1076
|
+
float horizonBand = exp(-10.0 * abs(h - 0.02));
|
|
895
1077
|
skyColor += uColorHorizon * horizonBand * 0.5 * uAtmGlow;
|
|
896
1078
|
|
|
1079
|
+
// 4. Warm secondary glow (light pollution / sodium scatter)
|
|
1080
|
+
float warmGlow = exp(-8.0 * abs(h));
|
|
1081
|
+
skyColor += vec3(0.4, 0.25, 0.15) * warmGlow * 0.3 * uAtmGlow;
|
|
1082
|
+
|
|
897
1083
|
gl_FragColor = vec4(skyColor, 1.0);
|
|
898
1084
|
}
|
|
899
1085
|
`,
|
|
@@ -931,7 +1117,24 @@ function createEngine({
|
|
|
931
1117
|
positions.push(x, y, z);
|
|
932
1118
|
const size = 1 + -Math.log(Math.random()) * 0.8 * 1.5;
|
|
933
1119
|
sizes.push(size);
|
|
934
|
-
|
|
1120
|
+
const temp = Math.random();
|
|
1121
|
+
let cr, cg, cb;
|
|
1122
|
+
if (temp < 0.15) {
|
|
1123
|
+
cr = 0.7 + temp * 2;
|
|
1124
|
+
cg = 0.8 + temp;
|
|
1125
|
+
cb = 1;
|
|
1126
|
+
} else if (temp < 0.6) {
|
|
1127
|
+
const t = (temp - 0.15) / 0.45;
|
|
1128
|
+
cr = 1;
|
|
1129
|
+
cg = 1 - t * 0.1;
|
|
1130
|
+
cb = 1 - t * 0.3;
|
|
1131
|
+
} else {
|
|
1132
|
+
const t = (temp - 0.6) / 0.4;
|
|
1133
|
+
cr = 1;
|
|
1134
|
+
cg = 0.85 - t * 0.35;
|
|
1135
|
+
cb = 0.7 - t * 0.35;
|
|
1136
|
+
}
|
|
1137
|
+
colors.push(cr, cg, cb);
|
|
935
1138
|
}
|
|
936
1139
|
geometry.setAttribute("position", new THREE5__namespace.Float32BufferAttribute(positions, 3));
|
|
937
1140
|
geometry.setAttribute("size", new THREE5__namespace.Float32BufferAttribute(sizes, 1));
|
|
@@ -939,51 +1142,60 @@ function createEngine({
|
|
|
939
1142
|
const material = createSmartMaterial({
|
|
940
1143
|
uniforms: {
|
|
941
1144
|
pixelRatio: { value: renderer.getPixelRatio() },
|
|
942
|
-
uScale: globalUniforms.uScale
|
|
1145
|
+
uScale: globalUniforms.uScale,
|
|
1146
|
+
uTime: globalUniforms.uTime
|
|
943
1147
|
},
|
|
944
1148
|
vertexShaderBody: `
|
|
945
|
-
attribute float size;
|
|
946
|
-
attribute vec3 color;
|
|
947
|
-
varying vec3 vColor;
|
|
948
|
-
uniform float pixelRatio;
|
|
949
|
-
|
|
1149
|
+
attribute float size;
|
|
1150
|
+
attribute vec3 color;
|
|
1151
|
+
varying vec3 vColor;
|
|
1152
|
+
uniform float pixelRatio;
|
|
1153
|
+
|
|
950
1154
|
uniform float uAtmExtinction;
|
|
1155
|
+
uniform float uAtmTwinkle;
|
|
1156
|
+
uniform float uTime;
|
|
951
1157
|
|
|
952
|
-
void main() {
|
|
1158
|
+
void main() {
|
|
953
1159
|
vec3 nPos = normalize(position);
|
|
954
1160
|
float altitude = nPos.y;
|
|
955
|
-
|
|
956
|
-
//
|
|
1161
|
+
|
|
1162
|
+
// Extinction & Horizon Fade
|
|
957
1163
|
float horizonFade = smoothstep(-0.1, 0.1, altitude);
|
|
958
1164
|
float airmass = 1.0 / (max(0.05, altitude + 0.05));
|
|
959
1165
|
float extinction = exp(-uAtmExtinction * 0.15 * airmass);
|
|
960
1166
|
|
|
961
|
-
//
|
|
962
|
-
|
|
1167
|
+
// Scintillation (twinkling) \u2014 stronger near horizon
|
|
1168
|
+
float turbulence = 1.0 + (1.0 - smoothstep(0.0, 1.0, altitude)) * 2.0;
|
|
1169
|
+
float twinkle = sin(uTime * 3.0 + position.x * 0.05 + position.z * 0.03) * 0.5 + 0.5;
|
|
1170
|
+
float scintillation = mix(1.0, twinkle * 2.0, uAtmTwinkle * 0.4 * turbulence);
|
|
963
1171
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
float zoomScale = pow(uScale, 0.5);
|
|
971
|
-
|
|
972
|
-
gl_PointSize =
|
|
1172
|
+
vColor = color * 3.0 * extinction * horizonFade * scintillation;
|
|
1173
|
+
|
|
1174
|
+
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
1175
|
+
gl_Position = smartProject(mvPosition);
|
|
1176
|
+
vScreenPos = gl_Position.xy / gl_Position.w;
|
|
1177
|
+
|
|
1178
|
+
float zoomScale = pow(uScale, 0.5);
|
|
1179
|
+
float perceptualSize = pow(size, 0.55);
|
|
1180
|
+
gl_PointSize = clamp(perceptualSize * zoomScale * 0.5 * pixelRatio * (800.0 / -mvPosition.z) * horizonFade, 0.5, 20.0);
|
|
973
1181
|
}
|
|
974
1182
|
`,
|
|
975
1183
|
fragmentShader: `
|
|
976
|
-
varying vec3 vColor;
|
|
977
|
-
void main() {
|
|
978
|
-
vec2 coord = gl_PointCoord - vec2(0.5);
|
|
979
|
-
float
|
|
980
|
-
if (
|
|
981
|
-
float alphaMask = getMaskAlpha();
|
|
982
|
-
if (alphaMask < 0.01) discard;
|
|
983
|
-
|
|
984
|
-
//
|
|
985
|
-
float
|
|
986
|
-
|
|
1184
|
+
varying vec3 vColor;
|
|
1185
|
+
void main() {
|
|
1186
|
+
vec2 coord = gl_PointCoord - vec2(0.5);
|
|
1187
|
+
float d = length(coord) * 2.0;
|
|
1188
|
+
if (d > 1.0) discard;
|
|
1189
|
+
float alphaMask = getMaskAlpha();
|
|
1190
|
+
if (alphaMask < 0.01) discard;
|
|
1191
|
+
|
|
1192
|
+
// Stellarium-style: sharp core + soft glow
|
|
1193
|
+
float core = smoothstep(0.8, 0.4, d);
|
|
1194
|
+
float glow = smoothstep(1.0, 0.0, d) * 0.08;
|
|
1195
|
+
float k = core + glow;
|
|
1196
|
+
|
|
1197
|
+
vec3 finalColor = mix(vColor, vec3(1.0), core * 0.5);
|
|
1198
|
+
gl_FragColor = vec4(finalColor * k * alphaMask, 1.0);
|
|
987
1199
|
}
|
|
988
1200
|
`,
|
|
989
1201
|
transparent: true,
|
|
@@ -1056,6 +1268,9 @@ function createEngine({
|
|
|
1056
1268
|
let constellationLines = null;
|
|
1057
1269
|
let boundaryLines = null;
|
|
1058
1270
|
let starPoints = null;
|
|
1271
|
+
const linesFader = new Fader(0.4);
|
|
1272
|
+
const artFader = new Fader(0.5);
|
|
1273
|
+
let lastTickTime = 0;
|
|
1059
1274
|
function clearRoot() {
|
|
1060
1275
|
for (const child of [...root.children]) {
|
|
1061
1276
|
root.remove(child);
|
|
@@ -1126,6 +1341,8 @@ function createEngine({
|
|
|
1126
1341
|
function buildFromModel(model, cfg) {
|
|
1127
1342
|
clearRoot();
|
|
1128
1343
|
bookIdToIndex.clear();
|
|
1344
|
+
testamentToIndex.clear();
|
|
1345
|
+
divisionToIndex.clear();
|
|
1129
1346
|
scene.background = cfg.background && cfg.background !== "transparent" ? new THREE5__namespace.Color(cfg.background) : new THREE5__namespace.Color(0);
|
|
1130
1347
|
const layoutCfg = { ...cfg.layout, radius: cfg.layout?.radius ?? 2e3 };
|
|
1131
1348
|
const laidOut = computeLayoutPositions(model, layoutCfg);
|
|
@@ -1159,6 +1376,8 @@ function createEngine({
|
|
|
1159
1376
|
const starPhases = [];
|
|
1160
1377
|
const starBookIndices = [];
|
|
1161
1378
|
const starChapterIndices = [];
|
|
1379
|
+
const starTestamentIndices = [];
|
|
1380
|
+
const starDivisionIndices = [];
|
|
1162
1381
|
const SPECTRAL_COLORS = [
|
|
1163
1382
|
new THREE5__namespace.Color(14544639),
|
|
1164
1383
|
// O - Blueish White
|
|
@@ -1216,12 +1435,32 @@ function createEngine({
|
|
|
1216
1435
|
let cIdx = 0;
|
|
1217
1436
|
if (n.meta?.chapter) cIdx = Number(n.meta.chapter);
|
|
1218
1437
|
starChapterIndices.push(cIdx);
|
|
1438
|
+
let tIdx = -1;
|
|
1439
|
+
if (n.meta?.testament) {
|
|
1440
|
+
const tName = n.meta.testament;
|
|
1441
|
+
if (!testamentToIndex.has(tName)) {
|
|
1442
|
+
testamentToIndex.set(tName, testamentToIndex.size + 1);
|
|
1443
|
+
}
|
|
1444
|
+
tIdx = testamentToIndex.get(tName);
|
|
1445
|
+
}
|
|
1446
|
+
starTestamentIndices.push(tIdx);
|
|
1447
|
+
let dIdx = -1;
|
|
1448
|
+
if (n.meta?.division) {
|
|
1449
|
+
const dName = n.meta.division;
|
|
1450
|
+
if (!divisionToIndex.has(dName)) {
|
|
1451
|
+
divisionToIndex.set(dName, divisionToIndex.size + 1);
|
|
1452
|
+
}
|
|
1453
|
+
dIdx = divisionToIndex.get(dName);
|
|
1454
|
+
}
|
|
1455
|
+
starDivisionIndices.push(dIdx);
|
|
1219
1456
|
}
|
|
1220
1457
|
if (n.level === 1 || n.level === 2 || n.level === 3) {
|
|
1221
1458
|
let color = "#ffffff";
|
|
1222
1459
|
if (n.level === 1) color = "#38bdf8";
|
|
1223
|
-
else if (n.level === 2)
|
|
1224
|
-
|
|
1460
|
+
else if (n.level === 2) {
|
|
1461
|
+
const bookKey = n.meta?.bookKey;
|
|
1462
|
+
color = bookKey && cfg.labelColors?.[bookKey] || "#cbd5e1";
|
|
1463
|
+
} else if (n.level === 3) color = "#94a3b8";
|
|
1225
1464
|
let labelText = n.label;
|
|
1226
1465
|
if (n.level === 3 && n.meta?.chapter) {
|
|
1227
1466
|
labelText = String(n.meta.chapter);
|
|
@@ -1302,6 +1541,8 @@ function createEngine({
|
|
|
1302
1541
|
starGeo.setAttribute("phase", new THREE5__namespace.Float32BufferAttribute(starPhases, 1));
|
|
1303
1542
|
starGeo.setAttribute("bookIndex", new THREE5__namespace.Float32BufferAttribute(starBookIndices, 1));
|
|
1304
1543
|
starGeo.setAttribute("chapterIndex", new THREE5__namespace.Float32BufferAttribute(starChapterIndices, 1));
|
|
1544
|
+
starGeo.setAttribute("testamentIndex", new THREE5__namespace.Float32BufferAttribute(starTestamentIndices, 1));
|
|
1545
|
+
starGeo.setAttribute("divisionIndex", new THREE5__namespace.Float32BufferAttribute(starDivisionIndices, 1));
|
|
1305
1546
|
const starMat = createSmartMaterial({
|
|
1306
1547
|
uniforms: {
|
|
1307
1548
|
pixelRatio: { value: renderer.getPixelRatio() },
|
|
@@ -1314,7 +1555,12 @@ function createEngine({
|
|
|
1314
1555
|
ORDER_REVEAL_CONFIG.pulseDuration,
|
|
1315
1556
|
ORDER_REVEAL_CONFIG.delayPerChapter,
|
|
1316
1557
|
ORDER_REVEAL_CONFIG.pulseAmplitude
|
|
1317
|
-
) }
|
|
1558
|
+
) },
|
|
1559
|
+
uFilterTestamentIndex: { value: -1 },
|
|
1560
|
+
uFilterDivisionIndex: { value: -1 },
|
|
1561
|
+
uFilterBookIndex: { value: -1 },
|
|
1562
|
+
uFilterStrength: { value: 0 },
|
|
1563
|
+
uFilterDimFactor: { value: 0.08 }
|
|
1318
1564
|
},
|
|
1319
1565
|
vertexShaderBody: `
|
|
1320
1566
|
attribute float size;
|
|
@@ -1322,10 +1568,12 @@ function createEngine({
|
|
|
1322
1568
|
attribute float phase;
|
|
1323
1569
|
attribute float bookIndex;
|
|
1324
1570
|
attribute float chapterIndex;
|
|
1571
|
+
attribute float testamentIndex;
|
|
1572
|
+
attribute float divisionIndex;
|
|
1573
|
+
|
|
1574
|
+
varying vec3 vColor;
|
|
1575
|
+
uniform float pixelRatio;
|
|
1325
1576
|
|
|
1326
|
-
varying vec3 vColor;
|
|
1327
|
-
uniform float pixelRatio;
|
|
1328
|
-
|
|
1329
1577
|
uniform float uTime;
|
|
1330
1578
|
uniform float uAtmExtinction;
|
|
1331
1579
|
uniform float uAtmTwinkle;
|
|
@@ -1335,6 +1583,12 @@ function createEngine({
|
|
|
1335
1583
|
uniform float uGlobalDimFactor;
|
|
1336
1584
|
uniform vec3 uPulseParams;
|
|
1337
1585
|
|
|
1586
|
+
uniform float uFilterTestamentIndex;
|
|
1587
|
+
uniform float uFilterDivisionIndex;
|
|
1588
|
+
uniform float uFilterBookIndex;
|
|
1589
|
+
uniform float uFilterStrength;
|
|
1590
|
+
uniform float uFilterDimFactor;
|
|
1591
|
+
|
|
1338
1592
|
void main() {
|
|
1339
1593
|
vec3 nPos = normalize(position);
|
|
1340
1594
|
|
|
@@ -1369,8 +1623,21 @@ function createEngine({
|
|
|
1369
1623
|
|
|
1370
1624
|
float activePulse = pulse * uPulseParams.z * isTarget * uOrderRevealStrength;
|
|
1371
1625
|
|
|
1626
|
+
// --- Hierarchy Filter ---
|
|
1627
|
+
float filtered = 0.0;
|
|
1628
|
+
if (uFilterTestamentIndex >= 0.0) {
|
|
1629
|
+
filtered = 1.0 - step(0.5, 1.0 - abs(testamentIndex - uFilterTestamentIndex));
|
|
1630
|
+
}
|
|
1631
|
+
if (uFilterDivisionIndex >= 0.0 && filtered < 0.5) {
|
|
1632
|
+
filtered = 1.0 - step(0.5, 1.0 - abs(divisionIndex - uFilterDivisionIndex));
|
|
1633
|
+
}
|
|
1634
|
+
if (uFilterBookIndex >= 0.0 && filtered < 0.5) {
|
|
1635
|
+
filtered = 1.0 - step(0.5, 1.0 - abs(bookIndex - uFilterBookIndex));
|
|
1636
|
+
}
|
|
1637
|
+
float filterDim = mix(1.0, uFilterDimFactor, uFilterStrength * filtered);
|
|
1638
|
+
|
|
1372
1639
|
vec3 baseColor = color * extinction * horizonFade * scintillation;
|
|
1373
|
-
vColor = baseColor * dimFactor;
|
|
1640
|
+
vColor = baseColor * dimFactor * filterDim;
|
|
1374
1641
|
vColor += vec3(1.0, 0.8, 0.4) * activePulse;
|
|
1375
1642
|
|
|
1376
1643
|
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
@@ -1378,7 +1645,8 @@ function createEngine({
|
|
|
1378
1645
|
vScreenPos = gl_Position.xy / gl_Position.w;
|
|
1379
1646
|
|
|
1380
1647
|
float sizeBoost = 1.0 + activePulse * 0.8;
|
|
1381
|
-
|
|
1648
|
+
float perceptualSize = pow(size, 0.55);
|
|
1649
|
+
gl_PointSize = clamp((perceptualSize * sizeBoost * 1.5) * uScale * pixelRatio * (2000.0 / -mvPosition.z) * horizonFade, 1.0, 40.0);
|
|
1382
1650
|
}
|
|
1383
1651
|
`,
|
|
1384
1652
|
fragmentShader: `
|
|
@@ -1391,15 +1659,14 @@ function createEngine({
|
|
|
1391
1659
|
float alphaMask = getMaskAlpha();
|
|
1392
1660
|
if (alphaMask < 0.01) discard;
|
|
1393
1661
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
float
|
|
1397
|
-
float
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
vec3
|
|
1401
|
-
|
|
1402
|
-
gl_FragColor = vec4((cCore + cHalo) * alphaMask, 1.0);
|
|
1662
|
+
// Stellarium-style dual-layer: sharp core + soft glow
|
|
1663
|
+
float core = smoothstep(0.8, 0.4, d);
|
|
1664
|
+
float glow = smoothstep(1.0, 0.0, d) * 0.08;
|
|
1665
|
+
float k = core + glow;
|
|
1666
|
+
|
|
1667
|
+
// White-hot core blending into coloured halo
|
|
1668
|
+
vec3 finalColor = mix(vColor, vec3(1.0), core * 0.7);
|
|
1669
|
+
gl_FragColor = vec4(finalColor * k * alphaMask, 1.0);
|
|
1403
1670
|
}
|
|
1404
1671
|
`,
|
|
1405
1672
|
transparent: true,
|
|
@@ -1433,17 +1700,89 @@ function createEngine({
|
|
|
1433
1700
|
}
|
|
1434
1701
|
}
|
|
1435
1702
|
if (linePoints.length > 0) {
|
|
1703
|
+
const quadPositions = [];
|
|
1704
|
+
const quadUvs = [];
|
|
1705
|
+
const quadIndices = [];
|
|
1706
|
+
const lineWidth = 8;
|
|
1707
|
+
for (let i = 0; i < linePoints.length; i += 6) {
|
|
1708
|
+
const ax = linePoints[i], ay = linePoints[i + 1], az = linePoints[i + 2];
|
|
1709
|
+
const bx = linePoints[i + 3], by = linePoints[i + 4], bz = linePoints[i + 5];
|
|
1710
|
+
const dx = bx - ax, dy = by - ay, dz = bz - az;
|
|
1711
|
+
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1712
|
+
if (len < 1e-3) continue;
|
|
1713
|
+
let px = dy * 0 - dz * 1, py = dz * 0 - dx * 0, pz = dx * 1 - dy * 0;
|
|
1714
|
+
const pLen = Math.sqrt(px * px + py * py + pz * pz);
|
|
1715
|
+
if (pLen < 1e-3) {
|
|
1716
|
+
px = 1;
|
|
1717
|
+
py = 0;
|
|
1718
|
+
pz = 0;
|
|
1719
|
+
} else {
|
|
1720
|
+
px /= pLen;
|
|
1721
|
+
py /= pLen;
|
|
1722
|
+
pz /= pLen;
|
|
1723
|
+
}
|
|
1724
|
+
const hw = lineWidth;
|
|
1725
|
+
const baseIdx = quadPositions.length / 3;
|
|
1726
|
+
quadPositions.push(ax - px * hw, ay - py * hw, az - pz * hw);
|
|
1727
|
+
quadUvs.push(0, -1);
|
|
1728
|
+
quadPositions.push(ax + px * hw, ay + py * hw, az + pz * hw);
|
|
1729
|
+
quadUvs.push(0, 1);
|
|
1730
|
+
quadPositions.push(bx - px * hw, by - py * hw, bz - pz * hw);
|
|
1731
|
+
quadUvs.push(1, -1);
|
|
1732
|
+
quadPositions.push(bx + px * hw, by + py * hw, bz + pz * hw);
|
|
1733
|
+
quadUvs.push(1, 1);
|
|
1734
|
+
quadIndices.push(baseIdx, baseIdx + 1, baseIdx + 2, baseIdx + 1, baseIdx + 3, baseIdx + 2);
|
|
1735
|
+
}
|
|
1436
1736
|
const lineGeo = new THREE5__namespace.BufferGeometry();
|
|
1437
|
-
lineGeo.setAttribute("position", new THREE5__namespace.Float32BufferAttribute(
|
|
1737
|
+
lineGeo.setAttribute("position", new THREE5__namespace.Float32BufferAttribute(quadPositions, 3));
|
|
1738
|
+
lineGeo.setAttribute("lineUv", new THREE5__namespace.Float32BufferAttribute(quadUvs, 2));
|
|
1739
|
+
lineGeo.setIndex(quadIndices);
|
|
1438
1740
|
const lineMat = createSmartMaterial({
|
|
1439
|
-
uniforms: {
|
|
1440
|
-
|
|
1441
|
-
|
|
1741
|
+
uniforms: {
|
|
1742
|
+
color: { value: new THREE5__namespace.Color(11193599) },
|
|
1743
|
+
uLineWidth: { value: 1.5 },
|
|
1744
|
+
uGlowIntensity: { value: 0.3 }
|
|
1745
|
+
},
|
|
1746
|
+
vertexShaderBody: `
|
|
1747
|
+
attribute vec2 lineUv;
|
|
1748
|
+
varying vec2 vLineUv;
|
|
1749
|
+
void main() {
|
|
1750
|
+
vLineUv = lineUv;
|
|
1751
|
+
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
1752
|
+
gl_Position = smartProject(mvPosition);
|
|
1753
|
+
vScreenPos = gl_Position.xy / gl_Position.w;
|
|
1754
|
+
}
|
|
1755
|
+
`,
|
|
1756
|
+
fragmentShader: `
|
|
1757
|
+
uniform vec3 color;
|
|
1758
|
+
uniform float uLineWidth;
|
|
1759
|
+
uniform float uGlowIntensity;
|
|
1760
|
+
varying vec2 vLineUv;
|
|
1761
|
+
void main() {
|
|
1762
|
+
float alphaMask = getMaskAlpha();
|
|
1763
|
+
if (alphaMask < 0.01) discard;
|
|
1764
|
+
|
|
1765
|
+
float dist = abs(vLineUv.y);
|
|
1766
|
+
|
|
1767
|
+
// Anti-aliased core line
|
|
1768
|
+
float hw = uLineWidth * 0.05;
|
|
1769
|
+
float base = smoothstep(hw + 0.08, hw - 0.08, dist);
|
|
1770
|
+
|
|
1771
|
+
// Soft glow extending outward
|
|
1772
|
+
float glow = (1.0 - dist) * uGlowIntensity;
|
|
1773
|
+
|
|
1774
|
+
float alpha = max(glow, base);
|
|
1775
|
+
if (alpha < 0.005) discard;
|
|
1776
|
+
|
|
1777
|
+
gl_FragColor = vec4(color, alpha * alphaMask);
|
|
1778
|
+
}
|
|
1779
|
+
`,
|
|
1442
1780
|
transparent: true,
|
|
1443
1781
|
depthWrite: false,
|
|
1444
|
-
blending: THREE5__namespace.AdditiveBlending
|
|
1782
|
+
blending: THREE5__namespace.AdditiveBlending,
|
|
1783
|
+
side: THREE5__namespace.DoubleSide
|
|
1445
1784
|
});
|
|
1446
|
-
constellationLines = new THREE5__namespace.
|
|
1785
|
+
constellationLines = new THREE5__namespace.Mesh(lineGeo, lineMat);
|
|
1447
1786
|
constellationLines.frustumCulled = false;
|
|
1448
1787
|
root.add(constellationLines);
|
|
1449
1788
|
}
|
|
@@ -1615,8 +1954,15 @@ function createEngine({
|
|
|
1615
1954
|
let lastAppliedLon = void 0;
|
|
1616
1955
|
let lastAppliedLat = void 0;
|
|
1617
1956
|
let lastBackdropCount = void 0;
|
|
1957
|
+
function setProjection(id) {
|
|
1958
|
+
const factory = exports.PROJECTIONS[id];
|
|
1959
|
+
if (!factory) return;
|
|
1960
|
+
currentProjection = factory();
|
|
1961
|
+
updateUniforms();
|
|
1962
|
+
}
|
|
1618
1963
|
function setConfig(cfg) {
|
|
1619
1964
|
currentConfig = cfg;
|
|
1965
|
+
if (cfg.projection) setProjection(cfg.projection);
|
|
1620
1966
|
if (typeof cfg.camera?.lon === "number" && cfg.camera.lon !== lastAppliedLon) {
|
|
1621
1967
|
state.lon = cfg.camera.lon;
|
|
1622
1968
|
state.targetLon = cfg.camera.lon;
|
|
@@ -1706,6 +2052,15 @@ function createEngine({
|
|
|
1706
2052
|
Object.assign(arr, state.tempArrangement);
|
|
1707
2053
|
return arr;
|
|
1708
2054
|
}
|
|
2055
|
+
function isNodeFiltered(node) {
|
|
2056
|
+
if (!currentFilter) return false;
|
|
2057
|
+
const meta = node.meta;
|
|
2058
|
+
if (!meta) return false;
|
|
2059
|
+
if (currentFilter.testament && meta.testament !== currentFilter.testament) return true;
|
|
2060
|
+
if (currentFilter.division && meta.division !== currentFilter.division) return true;
|
|
2061
|
+
if (currentFilter.bookKey && meta.bookKey !== currentFilter.bookKey) return true;
|
|
2062
|
+
return false;
|
|
2063
|
+
}
|
|
1709
2064
|
function pick(ev) {
|
|
1710
2065
|
const rect = renderer.domElement.getBoundingClientRect();
|
|
1711
2066
|
const mX = ev.clientX - rect.left;
|
|
@@ -1720,10 +2075,10 @@ function createEngine({
|
|
|
1720
2075
|
let minLabelDist = 40;
|
|
1721
2076
|
for (const item of dynamicLabels) {
|
|
1722
2077
|
if (!item.obj.visible) continue;
|
|
2078
|
+
if (isNodeFiltered(item.node)) continue;
|
|
1723
2079
|
const pWorld = item.obj.position;
|
|
1724
2080
|
const pProj = smartProjectJS(pWorld);
|
|
1725
|
-
|
|
1726
|
-
if (isBehind) continue;
|
|
2081
|
+
if (currentProjection.isClipped(pProj.z)) continue;
|
|
1727
2082
|
const xNDC = pProj.x * uScale / uAspect;
|
|
1728
2083
|
const yNDC = pProj.y * uScale;
|
|
1729
2084
|
const sX = (xNDC * 0.5 + 0.5) * w;
|
|
@@ -1745,8 +2100,7 @@ function createEngine({
|
|
|
1745
2100
|
if (!item.mesh.visible) continue;
|
|
1746
2101
|
const pWorld = item.mesh.position;
|
|
1747
2102
|
const pProj = smartProjectJS(pWorld);
|
|
1748
|
-
|
|
1749
|
-
if (isBehind) continue;
|
|
2103
|
+
if (currentProjection.isClipped(pProj.z)) continue;
|
|
1750
2104
|
const uniforms = item.material.uniforms;
|
|
1751
2105
|
if (!uniforms || !uniforms.uSize) continue;
|
|
1752
2106
|
const uSize = uniforms.uSize.value;
|
|
@@ -1795,12 +2149,16 @@ function createEngine({
|
|
|
1795
2149
|
const id = starIndexToId[pointHit.index];
|
|
1796
2150
|
if (id) {
|
|
1797
2151
|
const node = nodeById.get(id);
|
|
1798
|
-
if (node) return { type: "star", node, index: pointHit.index, point: pointHit.point, object: void 0 };
|
|
2152
|
+
if (node && !isNodeFiltered(node)) return { type: "star", node, index: pointHit.index, point: pointHit.point, object: void 0 };
|
|
1799
2153
|
}
|
|
1800
2154
|
}
|
|
1801
2155
|
}
|
|
1802
2156
|
return void 0;
|
|
1803
2157
|
}
|
|
2158
|
+
function onWindowBlur() {
|
|
2159
|
+
isMouseInWindow = false;
|
|
2160
|
+
edgeHoverStart = 0;
|
|
2161
|
+
}
|
|
1804
2162
|
function onMouseDown(e) {
|
|
1805
2163
|
state.lastMouseX = e.clientX;
|
|
1806
2164
|
state.lastMouseY = e.clientY;
|
|
@@ -1838,6 +2196,7 @@ function createEngine({
|
|
|
1838
2196
|
}
|
|
1839
2197
|
return;
|
|
1840
2198
|
}
|
|
2199
|
+
flyToActive = false;
|
|
1841
2200
|
state.dragMode = "camera";
|
|
1842
2201
|
state.isDragging = true;
|
|
1843
2202
|
state.velocityX = 0;
|
|
@@ -1896,11 +2255,13 @@ function createEngine({
|
|
|
1896
2255
|
state.lastMouseX = e.clientX;
|
|
1897
2256
|
state.lastMouseY = e.clientY;
|
|
1898
2257
|
const speedScale = state.fov / ENGINE_CONFIG.defaultFov;
|
|
2258
|
+
const rotLock = Math.max(0, Math.min(1, (state.fov - 100) / (ENGINE_CONFIG.maxFov - 100)));
|
|
2259
|
+
const latFactor = 1 - rotLock * rotLock;
|
|
1899
2260
|
state.targetLon += deltaX * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
1900
|
-
state.targetLat += deltaY * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
2261
|
+
state.targetLat += deltaY * ENGINE_CONFIG.dragSpeed * speedScale * latFactor;
|
|
1901
2262
|
state.targetLat = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, state.targetLat));
|
|
1902
2263
|
state.velocityX = deltaX * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
1903
|
-
state.velocityY = deltaY * ENGINE_CONFIG.dragSpeed * speedScale;
|
|
2264
|
+
state.velocityY = deltaY * ENGINE_CONFIG.dragSpeed * speedScale * latFactor;
|
|
1904
2265
|
state.lon = state.targetLon;
|
|
1905
2266
|
state.lat = state.targetLat;
|
|
1906
2267
|
} else {
|
|
@@ -1934,6 +2295,9 @@ function createEngine({
|
|
|
1934
2295
|
}
|
|
1935
2296
|
}
|
|
1936
2297
|
function onMouseUp(e) {
|
|
2298
|
+
const dx = e.clientX - state.lastMouseX;
|
|
2299
|
+
const dy = e.clientY - state.lastMouseY;
|
|
2300
|
+
const movedDist = Math.sqrt(dx * dx + dy * dy);
|
|
1937
2301
|
if (state.dragMode === "node") {
|
|
1938
2302
|
const fullArr = getFullArrangement();
|
|
1939
2303
|
handlers.onArrangementChange?.(fullArr);
|
|
@@ -1946,6 +2310,17 @@ function createEngine({
|
|
|
1946
2310
|
state.isDragging = false;
|
|
1947
2311
|
state.dragMode = "none";
|
|
1948
2312
|
document.body.style.cursor = "default";
|
|
2313
|
+
if (movedDist < 5) {
|
|
2314
|
+
const hit = pick(e);
|
|
2315
|
+
if (hit) {
|
|
2316
|
+
handlers.onSelect?.(hit.node);
|
|
2317
|
+
constellationLayer.setFocused(hit.node.id);
|
|
2318
|
+
if (hit.node.level === 2) setFocusedBook(hit.node.id);
|
|
2319
|
+
else if (hit.node.level === 3 && hit.node.parent) setFocusedBook(hit.node.parent);
|
|
2320
|
+
} else {
|
|
2321
|
+
setFocusedBook(null);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
1949
2324
|
} else {
|
|
1950
2325
|
const hit = pick(e);
|
|
1951
2326
|
if (hit) {
|
|
@@ -1960,6 +2335,7 @@ function createEngine({
|
|
|
1960
2335
|
}
|
|
1961
2336
|
function onWheel(e) {
|
|
1962
2337
|
e.preventDefault();
|
|
2338
|
+
flyToActive = false;
|
|
1963
2339
|
const aspect = container.clientWidth / container.clientHeight;
|
|
1964
2340
|
renderer.domElement.getBoundingClientRect();
|
|
1965
2341
|
const vBefore = getMouseViewVector(state.fov, aspect);
|
|
@@ -1970,6 +2346,17 @@ function createEngine({
|
|
|
1970
2346
|
updateUniforms();
|
|
1971
2347
|
const vAfter = getMouseViewVector(state.fov, aspect);
|
|
1972
2348
|
const quaternion = new THREE5__namespace.Quaternion().setFromUnitVectors(vAfter, vBefore);
|
|
2349
|
+
const dampStartFov = 40;
|
|
2350
|
+
const dampEndFov = 120;
|
|
2351
|
+
let spinAmount = 1;
|
|
2352
|
+
if (state.fov > dampStartFov) {
|
|
2353
|
+
const t = Math.max(0, Math.min(1, (state.fov - dampStartFov) / (dampEndFov - dampStartFov)));
|
|
2354
|
+
spinAmount = 1 - Math.pow(t, 1.5) * 0.8;
|
|
2355
|
+
}
|
|
2356
|
+
if (spinAmount < 0.999) {
|
|
2357
|
+
const identityQuat = new THREE5__namespace.Quaternion();
|
|
2358
|
+
quaternion.slerp(identityQuat, 1 - spinAmount);
|
|
2359
|
+
}
|
|
1973
2360
|
const y = Math.sin(state.lat);
|
|
1974
2361
|
const r = Math.cos(state.lat);
|
|
1975
2362
|
const x = r * Math.sin(state.lon);
|
|
@@ -2018,9 +2405,8 @@ function createEngine({
|
|
|
2018
2405
|
el.addEventListener("mouseenter", () => {
|
|
2019
2406
|
isMouseInWindow = true;
|
|
2020
2407
|
});
|
|
2021
|
-
el.addEventListener("mouseleave",
|
|
2022
|
-
|
|
2023
|
-
});
|
|
2408
|
+
el.addEventListener("mouseleave", onWindowBlur);
|
|
2409
|
+
window.addEventListener("blur", onWindowBlur);
|
|
2024
2410
|
raf = requestAnimationFrame(tick);
|
|
2025
2411
|
}
|
|
2026
2412
|
function tick() {
|
|
@@ -2049,6 +2435,17 @@ function createEngine({
|
|
|
2049
2435
|
if (m.uniforms.uOrderRevealStrength) m.uniforms.uOrderRevealStrength.value = orderRevealStrength;
|
|
2050
2436
|
}
|
|
2051
2437
|
}
|
|
2438
|
+
const filterTarget = currentFilter ? 1 : 0;
|
|
2439
|
+
filterStrength = mix(filterStrength, filterTarget, 0.1);
|
|
2440
|
+
if (filterStrength > 1e-3 || filterTarget > 0) {
|
|
2441
|
+
if (starPoints && starPoints.material) {
|
|
2442
|
+
const m = starPoints.material;
|
|
2443
|
+
if (m.uniforms.uFilterTestamentIndex) m.uniforms.uFilterTestamentIndex.value = filterTestamentIndex;
|
|
2444
|
+
if (m.uniforms.uFilterDivisionIndex) m.uniforms.uFilterDivisionIndex.value = filterDivisionIndex;
|
|
2445
|
+
if (m.uniforms.uFilterBookIndex) m.uniforms.uFilterBookIndex.value = filterBookIndex;
|
|
2446
|
+
if (m.uniforms.uFilterStrength) m.uniforms.uFilterStrength.value = filterStrength;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2052
2449
|
let panX = 0;
|
|
2053
2450
|
let panY = 0;
|
|
2054
2451
|
if (!state.isDragging && isMouseInWindow && !currentConfig?.editable) {
|
|
@@ -2080,12 +2477,28 @@ function createEngine({
|
|
|
2080
2477
|
} else {
|
|
2081
2478
|
edgeHoverStart = 0;
|
|
2082
2479
|
}
|
|
2480
|
+
if (flyToActive && !state.isDragging) {
|
|
2481
|
+
state.lon = mix(state.lon, flyToTargetLon, FLY_TO_SPEED);
|
|
2482
|
+
state.lat = mix(state.lat, flyToTargetLat, FLY_TO_SPEED);
|
|
2483
|
+
state.fov = mix(state.fov, flyToTargetFov, FLY_TO_SPEED);
|
|
2484
|
+
state.targetLon = state.lon;
|
|
2485
|
+
state.targetLat = state.lat;
|
|
2486
|
+
state.velocityX = 0;
|
|
2487
|
+
state.velocityY = 0;
|
|
2488
|
+
handlers.onFovChange?.(state.fov);
|
|
2489
|
+
if (Math.abs(state.lon - flyToTargetLon) < 1e-4 && Math.abs(state.lat - flyToTargetLat) < 1e-4 && Math.abs(state.fov - flyToTargetFov) < 0.05) {
|
|
2490
|
+
flyToActive = false;
|
|
2491
|
+
state.lon = flyToTargetLon;
|
|
2492
|
+
state.lat = flyToTargetLat;
|
|
2493
|
+
state.fov = flyToTargetFov;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2083
2496
|
if (Math.abs(panX) > 0 || Math.abs(panY) > 0) {
|
|
2084
2497
|
state.lon += panX;
|
|
2085
2498
|
state.lat += panY;
|
|
2086
2499
|
state.targetLon = state.lon;
|
|
2087
2500
|
state.targetLat = state.lat;
|
|
2088
|
-
} else if (!state.isDragging) {
|
|
2501
|
+
} else if (!state.isDragging && !flyToActive) {
|
|
2089
2502
|
state.lon += state.velocityX;
|
|
2090
2503
|
state.lat += state.velocityY;
|
|
2091
2504
|
state.velocityX *= ENGINE_CONFIG.inertiaDamping;
|
|
@@ -2106,13 +2519,30 @@ function createEngine({
|
|
|
2106
2519
|
camera.updateMatrixWorld();
|
|
2107
2520
|
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
|
|
2108
2521
|
updateUniforms();
|
|
2109
|
-
|
|
2522
|
+
const nowSec = now / 1e3;
|
|
2523
|
+
const dt = lastTickTime > 0 ? Math.min(nowSec - lastTickTime, 0.1) : 0.016;
|
|
2524
|
+
lastTickTime = nowSec;
|
|
2525
|
+
linesFader.target = currentConfig?.showConstellationLines ?? false;
|
|
2526
|
+
linesFader.update(dt);
|
|
2527
|
+
artFader.target = currentConfig?.showConstellationArt ?? false;
|
|
2528
|
+
artFader.update(dt);
|
|
2529
|
+
constellationLayer.update(state.fov, artFader.eased > 0.01);
|
|
2530
|
+
if (artFader.eased < 1) {
|
|
2531
|
+
constellationLayer.setGlobalOpacity?.(artFader.eased);
|
|
2532
|
+
}
|
|
2110
2533
|
backdropGroup.visible = currentConfig?.showBackdropStars ?? true;
|
|
2111
2534
|
if (atmosphereMesh) atmosphereMesh.visible = currentConfig?.showAtmosphere ?? false;
|
|
2112
2535
|
const DIVISION_THRESHOLD = 60;
|
|
2113
2536
|
const showDivisions = state.fov > DIVISION_THRESHOLD;
|
|
2114
2537
|
if (constellationLines) {
|
|
2115
|
-
constellationLines.visible =
|
|
2538
|
+
constellationLines.visible = linesFader.eased > 0.01;
|
|
2539
|
+
if (constellationLines.visible && constellationLines.material) {
|
|
2540
|
+
const mat = constellationLines.material;
|
|
2541
|
+
if (mat.uniforms?.color) {
|
|
2542
|
+
mat.uniforms.color.value.setHex(11193599);
|
|
2543
|
+
mat.opacity = linesFader.eased;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2116
2546
|
}
|
|
2117
2547
|
if (boundaryLines) {
|
|
2118
2548
|
boundaryLines.visible = currentConfig?.showDivisionBoundaries ?? false;
|
|
@@ -2133,8 +2563,7 @@ function createEngine({
|
|
|
2133
2563
|
const showDivisionLabels = currentConfig?.showDivisionLabels === true;
|
|
2134
2564
|
const showChapterLabels = currentConfig?.showChapterLabels === true;
|
|
2135
2565
|
const showGroupLabels = currentConfig?.showGroupLabels === true;
|
|
2136
|
-
const
|
|
2137
|
-
const showChapters = state.fov < 70;
|
|
2566
|
+
const showChapters = state.fov < 45;
|
|
2138
2567
|
for (const item of dynamicLabels) {
|
|
2139
2568
|
const uniforms = item.obj.material.uniforms;
|
|
2140
2569
|
const level = item.node.level;
|
|
@@ -2155,11 +2584,6 @@ function createEngine({
|
|
|
2155
2584
|
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
2156
2585
|
continue;
|
|
2157
2586
|
}
|
|
2158
|
-
if (level === 2 && !showBooks && item.node.id !== state.draggedNodeId) {
|
|
2159
|
-
uniforms.uAlpha.value = THREE5__namespace.MathUtils.lerp(uniforms.uAlpha.value, 0, 0.2);
|
|
2160
|
-
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
2161
|
-
continue;
|
|
2162
|
-
}
|
|
2163
2587
|
if ((level === 3 || level === 2.5) && !showChapters && item.node.id !== state.draggedNodeId) {
|
|
2164
2588
|
uniforms.uAlpha.value = THREE5__namespace.MathUtils.lerp(uniforms.uAlpha.value, 0, 0.2);
|
|
2165
2589
|
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
@@ -2192,8 +2616,8 @@ function createEngine({
|
|
|
2192
2616
|
const isSpecial = l.item.node.id === selectedId || l.item.node.id === hoverId;
|
|
2193
2617
|
if (l.level === 1) {
|
|
2194
2618
|
let rot = 0;
|
|
2195
|
-
const
|
|
2196
|
-
if (
|
|
2619
|
+
const isWideAngle = currentProjection.id !== "perspective";
|
|
2620
|
+
if (isWideAngle) {
|
|
2197
2621
|
const dx = l.sX - screenW / 2;
|
|
2198
2622
|
const dy = l.sY - screenH / 2;
|
|
2199
2623
|
rot = Math.atan2(-dy, -dx) - Math.PI / 2;
|
|
@@ -2201,7 +2625,7 @@ function createEngine({
|
|
|
2201
2625
|
l.uniforms.uAngle.value = THREE5__namespace.MathUtils.lerp(l.uniforms.uAngle.value, rot, 0.1);
|
|
2202
2626
|
}
|
|
2203
2627
|
if (l.level === 2) {
|
|
2204
|
-
|
|
2628
|
+
{
|
|
2205
2629
|
target2 = 1;
|
|
2206
2630
|
occupied.push({ x: l.sX - l.w / 2, y: l.sY - l.h / 2, w: l.w, h: l.h });
|
|
2207
2631
|
}
|
|
@@ -2223,6 +2647,17 @@ function createEngine({
|
|
|
2223
2647
|
}
|
|
2224
2648
|
}
|
|
2225
2649
|
}
|
|
2650
|
+
if (target2 > 0 && currentFilter && filterStrength > 0.01) {
|
|
2651
|
+
const node = l.item.node;
|
|
2652
|
+
if (node.level === 3) {
|
|
2653
|
+
target2 = 0;
|
|
2654
|
+
} else if (node.level === 2 || node.level === 2.5) {
|
|
2655
|
+
const nodeToCheck = node.level === 2.5 && node.parent ? nodeById.get(node.parent) : node;
|
|
2656
|
+
if (nodeToCheck && isNodeFiltered(nodeToCheck)) {
|
|
2657
|
+
target2 = 0;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2226
2661
|
l.uniforms.uAlpha.value = THREE5__namespace.MathUtils.lerp(l.uniforms.uAlpha.value, target2, 0.1);
|
|
2227
2662
|
l.item.obj.visible = l.uniforms.uAlpha.value > 0.01;
|
|
2228
2663
|
}
|
|
@@ -2237,6 +2672,8 @@ function createEngine({
|
|
|
2237
2672
|
window.removeEventListener("mousemove", onMouseMove);
|
|
2238
2673
|
window.removeEventListener("mouseup", onMouseUp);
|
|
2239
2674
|
el.removeEventListener("wheel", onWheel);
|
|
2675
|
+
el.removeEventListener("mouseleave", onWindowBlur);
|
|
2676
|
+
window.removeEventListener("blur", onWindowBlur);
|
|
2240
2677
|
}
|
|
2241
2678
|
function dispose() {
|
|
2242
2679
|
stop();
|
|
@@ -2257,7 +2694,30 @@ function createEngine({
|
|
|
2257
2694
|
function setOrderRevealEnabled(enabled) {
|
|
2258
2695
|
orderRevealEnabled = enabled;
|
|
2259
2696
|
}
|
|
2260
|
-
|
|
2697
|
+
function flyTo(nodeId, targetFov) {
|
|
2698
|
+
const node = nodeById.get(nodeId);
|
|
2699
|
+
if (!node) return;
|
|
2700
|
+
const pos = getPosition(node).normalize();
|
|
2701
|
+
flyToTargetLat = Math.asin(Math.max(-0.999, Math.min(0.999, pos.y)));
|
|
2702
|
+
flyToTargetLon = Math.atan2(pos.x, -pos.z);
|
|
2703
|
+
flyToTargetFov = targetFov ?? ENGINE_CONFIG.minFov;
|
|
2704
|
+
flyToActive = true;
|
|
2705
|
+
state.velocityX = 0;
|
|
2706
|
+
state.velocityY = 0;
|
|
2707
|
+
}
|
|
2708
|
+
function setHierarchyFilter(filter) {
|
|
2709
|
+
currentFilter = filter;
|
|
2710
|
+
if (filter) {
|
|
2711
|
+
filterTestamentIndex = filter.testament && testamentToIndex.has(filter.testament) ? testamentToIndex.get(filter.testament) : -1;
|
|
2712
|
+
filterDivisionIndex = filter.division && divisionToIndex.has(filter.division) ? divisionToIndex.get(filter.division) : -1;
|
|
2713
|
+
filterBookIndex = filter.bookKey && bookIdToIndex.has(`B:${filter.bookKey}`) ? bookIdToIndex.get(`B:${filter.bookKey}`) : -1;
|
|
2714
|
+
} else {
|
|
2715
|
+
filterTestamentIndex = -1;
|
|
2716
|
+
filterDivisionIndex = -1;
|
|
2717
|
+
filterBookIndex = -1;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
return { setConfig, start, stop, dispose, setHandlers, getFullArrangement, setHoveredBook, setFocusedBook, setOrderRevealEnabled, setHierarchyFilter, flyTo, setProjection };
|
|
2261
2721
|
}
|
|
2262
2722
|
var ENGINE_CONFIG, ORDER_REVEAL_CONFIG;
|
|
2263
2723
|
var init_createEngine = __esm({
|
|
@@ -2265,16 +2725,18 @@ var init_createEngine = __esm({
|
|
|
2265
2725
|
init_layout();
|
|
2266
2726
|
init_materials();
|
|
2267
2727
|
init_ConstellationArtworkLayer();
|
|
2728
|
+
init_projections();
|
|
2729
|
+
init_fader();
|
|
2268
2730
|
ENGINE_CONFIG = {
|
|
2269
2731
|
minFov: 10,
|
|
2270
|
-
maxFov:
|
|
2271
|
-
defaultFov:
|
|
2732
|
+
maxFov: 135,
|
|
2733
|
+
defaultFov: 50,
|
|
2272
2734
|
dragSpeed: 125e-5,
|
|
2273
2735
|
inertiaDamping: 0.92,
|
|
2274
|
-
blendStart:
|
|
2275
|
-
blendEnd:
|
|
2276
|
-
zenithStartFov:
|
|
2277
|
-
zenithStrength: 0.
|
|
2736
|
+
blendStart: 35,
|
|
2737
|
+
blendEnd: 83,
|
|
2738
|
+
zenithStartFov: 75,
|
|
2739
|
+
zenithStrength: 0.15,
|
|
2278
2740
|
horizonLockStrength: 0.05,
|
|
2279
2741
|
edgePanThreshold: 0.15,
|
|
2280
2742
|
edgePanMaxSpeed: 0.02,
|
|
@@ -2296,7 +2758,10 @@ var StarMap = react.forwardRef(
|
|
|
2296
2758
|
getFullArrangement: () => engineRef.current?.getFullArrangement?.(),
|
|
2297
2759
|
setHoveredBook: (id) => engineRef.current?.setHoveredBook?.(id),
|
|
2298
2760
|
setFocusedBook: (id) => engineRef.current?.setFocusedBook?.(id),
|
|
2299
|
-
setOrderRevealEnabled: (enabled) => engineRef.current?.setOrderRevealEnabled?.(enabled)
|
|
2761
|
+
setOrderRevealEnabled: (enabled) => engineRef.current?.setOrderRevealEnabled?.(enabled),
|
|
2762
|
+
setHierarchyFilter: (filter) => engineRef.current?.setHierarchyFilter?.(filter),
|
|
2763
|
+
flyTo: (nodeId, targetFov) => engineRef.current?.flyTo?.(nodeId, targetFov),
|
|
2764
|
+
setProjection: (id) => engineRef.current?.setProjection?.(id)
|
|
2300
2765
|
}));
|
|
2301
2766
|
react.useEffect(() => {
|
|
2302
2767
|
let disposed = false;
|
|
@@ -31573,6 +32038,9 @@ function generateArrangement(bible, options = {}) {
|
|
|
31573
32038
|
return arrangement;
|
|
31574
32039
|
}
|
|
31575
32040
|
|
|
32041
|
+
// src/index.ts
|
|
32042
|
+
init_projections();
|
|
32043
|
+
|
|
31576
32044
|
exports.StarMap = StarMap;
|
|
31577
32045
|
exports.bibleToSceneModel = bibleToSceneModel;
|
|
31578
32046
|
exports.defaultGenerateOptions = defaultGenerateOptions;
|