@project-skymap/library 0.2.1 → 0.4.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 +453 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +452 -42
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/dist/index.js
CHANGED
|
@@ -424,7 +424,8 @@ function createEngine({
|
|
|
424
424
|
draggedNodeId: null,
|
|
425
425
|
draggedStarIndex: -1,
|
|
426
426
|
draggedDist: 2e3,
|
|
427
|
-
draggedGroup: null
|
|
427
|
+
draggedGroup: null,
|
|
428
|
+
tempArrangement: {}
|
|
428
429
|
};
|
|
429
430
|
const mouseNDC = new THREE4.Vector2();
|
|
430
431
|
let isMouseInWindow = false;
|
|
@@ -671,7 +672,55 @@ function createEngine({
|
|
|
671
672
|
const nodeById = /* @__PURE__ */ new Map();
|
|
672
673
|
const starIndexToId = [];
|
|
673
674
|
const dynamicLabels = [];
|
|
675
|
+
const hoverLabelMat = createSmartMaterial({
|
|
676
|
+
uniforms: {
|
|
677
|
+
uMap: { value: null },
|
|
678
|
+
uSize: { value: new THREE4.Vector2(1, 1) },
|
|
679
|
+
uAlpha: { value: 0 },
|
|
680
|
+
uAngle: { value: 0 }
|
|
681
|
+
},
|
|
682
|
+
vertexShaderBody: `
|
|
683
|
+
uniform vec2 uSize;
|
|
684
|
+
uniform float uAngle;
|
|
685
|
+
varying vec2 vUv;
|
|
686
|
+
void main() {
|
|
687
|
+
vUv = uv;
|
|
688
|
+
vec4 mvPos = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
|
|
689
|
+
vec4 projected = smartProject(mvPos);
|
|
690
|
+
|
|
691
|
+
float c = cos(uAngle);
|
|
692
|
+
float s = sin(uAngle);
|
|
693
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
694
|
+
vec2 offset = rot * (position.xy * uSize);
|
|
695
|
+
|
|
696
|
+
projected.xy += offset / vec2(uAspect, 1.0);
|
|
697
|
+
gl_Position = projected;
|
|
698
|
+
}
|
|
699
|
+
`,
|
|
700
|
+
fragmentShader: `
|
|
701
|
+
uniform sampler2D uMap;
|
|
702
|
+
uniform float uAlpha;
|
|
703
|
+
varying vec2 vUv;
|
|
704
|
+
void main() {
|
|
705
|
+
float mask = getMaskAlpha();
|
|
706
|
+
if (mask < 0.01) discard;
|
|
707
|
+
vec4 tex = texture2D(uMap, vUv);
|
|
708
|
+
gl_FragColor = vec4(tex.rgb, tex.a * uAlpha * mask);
|
|
709
|
+
}
|
|
710
|
+
`,
|
|
711
|
+
transparent: true,
|
|
712
|
+
depthWrite: false,
|
|
713
|
+
depthTest: false
|
|
714
|
+
// Always on top of stars
|
|
715
|
+
});
|
|
716
|
+
const hoverLabelMesh = new THREE4.Mesh(new THREE4.PlaneGeometry(1, 1), hoverLabelMat);
|
|
717
|
+
hoverLabelMesh.visible = false;
|
|
718
|
+
hoverLabelMesh.renderOrder = 999;
|
|
719
|
+
hoverLabelMesh.frustumCulled = false;
|
|
720
|
+
root.add(hoverLabelMesh);
|
|
721
|
+
let currentHoverNodeId = null;
|
|
674
722
|
let constellationLines = null;
|
|
723
|
+
let boundaryLines = null;
|
|
675
724
|
let starPoints = null;
|
|
676
725
|
function clearRoot() {
|
|
677
726
|
for (const child of [...root.children]) {
|
|
@@ -687,6 +736,7 @@ function createEngine({
|
|
|
687
736
|
starIndexToId.length = 0;
|
|
688
737
|
dynamicLabels.length = 0;
|
|
689
738
|
constellationLines = null;
|
|
739
|
+
boundaryLines = null;
|
|
690
740
|
starPoints = null;
|
|
691
741
|
}
|
|
692
742
|
function createTextTexture(text, color = "#ffffff") {
|
|
@@ -712,7 +762,22 @@ function createEngine({
|
|
|
712
762
|
function getPosition(n) {
|
|
713
763
|
if (currentConfig?.arrangement) {
|
|
714
764
|
const arr = currentConfig.arrangement[n.id];
|
|
715
|
-
if (arr)
|
|
765
|
+
if (arr) {
|
|
766
|
+
if (arr.position[2] === 0) {
|
|
767
|
+
const x = arr.position[0];
|
|
768
|
+
const y = arr.position[1];
|
|
769
|
+
const radius = currentConfig.layout?.radius ?? 2e3;
|
|
770
|
+
const r_norm = Math.min(1, Math.sqrt(x * x + y * y) / radius);
|
|
771
|
+
const phi = Math.atan2(y, x);
|
|
772
|
+
const theta = r_norm * (Math.PI / 2);
|
|
773
|
+
return new THREE4.Vector3(
|
|
774
|
+
Math.sin(theta) * Math.cos(phi),
|
|
775
|
+
Math.cos(theta),
|
|
776
|
+
Math.sin(theta) * Math.sin(phi)
|
|
777
|
+
).multiplyScalar(radius);
|
|
778
|
+
}
|
|
779
|
+
return new THREE4.Vector3(arr.position[0], arr.position[1], arr.position[2]);
|
|
780
|
+
}
|
|
716
781
|
}
|
|
717
782
|
return new THREE4.Vector3(n.meta?.x ?? 0, n.meta?.y ?? 0, n.meta?.z ?? 0);
|
|
718
783
|
}
|
|
@@ -728,6 +793,30 @@ function createEngine({
|
|
|
728
793
|
scene.background = cfg.background && cfg.background !== "transparent" ? new THREE4.Color(cfg.background) : new THREE4.Color(0);
|
|
729
794
|
const layoutCfg = { ...cfg.layout, radius: cfg.layout?.radius ?? 2e3 };
|
|
730
795
|
const laidOut = computeLayoutPositions(model, layoutCfg);
|
|
796
|
+
const divisionPositions = /* @__PURE__ */ new Map();
|
|
797
|
+
if (cfg.arrangement) {
|
|
798
|
+
const divMap = /* @__PURE__ */ new Map();
|
|
799
|
+
for (const n of laidOut.nodes) {
|
|
800
|
+
if (n.level === 2 && n.parent) {
|
|
801
|
+
const list = divMap.get(n.parent) ?? [];
|
|
802
|
+
list.push(n);
|
|
803
|
+
divMap.set(n.parent, list);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
for (const [divId, books] of divMap.entries()) {
|
|
807
|
+
const centroid = new THREE4.Vector3();
|
|
808
|
+
let count = 0;
|
|
809
|
+
for (const b of books) {
|
|
810
|
+
const p = getPosition(b);
|
|
811
|
+
centroid.add(p);
|
|
812
|
+
count++;
|
|
813
|
+
}
|
|
814
|
+
if (count > 0) {
|
|
815
|
+
centroid.divideScalar(count);
|
|
816
|
+
divisionPositions.set(divId, centroid);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
731
820
|
const starPositions = [];
|
|
732
821
|
const starSizes = [];
|
|
733
822
|
const starColors = [];
|
|
@@ -776,26 +865,36 @@ function createEngine({
|
|
|
776
865
|
const colorIdx = Math.floor(Math.pow(Math.random(), 1.5) * SPECTRAL_COLORS.length);
|
|
777
866
|
const c = SPECTRAL_COLORS[Math.min(colorIdx, SPECTRAL_COLORS.length - 1)];
|
|
778
867
|
starColors.push(c.r, c.g, c.b);
|
|
779
|
-
}
|
|
780
|
-
|
|
868
|
+
}
|
|
869
|
+
if (n.level === 1 || n.level === 2 || n.level === 3) {
|
|
870
|
+
const color = n.level === 1 ? "#38bdf8" : "#ffffff";
|
|
781
871
|
const texRes = createTextTexture(n.label, color);
|
|
782
872
|
if (texRes) {
|
|
783
|
-
|
|
873
|
+
let baseScale = 0.05;
|
|
874
|
+
if (n.level === 1) baseScale = 0.08;
|
|
875
|
+
else if (n.level === 3) baseScale = 0.04;
|
|
784
876
|
const size = new THREE4.Vector2(baseScale * texRes.aspect, baseScale);
|
|
785
877
|
const mat = createSmartMaterial({
|
|
786
878
|
uniforms: {
|
|
787
879
|
uMap: { value: texRes.tex },
|
|
788
880
|
uSize: { value: size },
|
|
789
|
-
uAlpha: { value: 0 }
|
|
881
|
+
uAlpha: { value: 0 },
|
|
882
|
+
uAngle: { value: 0 }
|
|
790
883
|
},
|
|
791
884
|
vertexShaderBody: `
|
|
792
885
|
uniform vec2 uSize;
|
|
886
|
+
uniform float uAngle;
|
|
793
887
|
varying vec2 vUv;
|
|
794
888
|
void main() {
|
|
795
889
|
vUv = uv;
|
|
796
890
|
vec4 mvPos = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
|
|
797
891
|
vec4 projected = smartProject(mvPos);
|
|
798
|
-
|
|
892
|
+
|
|
893
|
+
float c = cos(uAngle);
|
|
894
|
+
float s = sin(uAngle);
|
|
895
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
896
|
+
vec2 offset = rot * (position.xy * uSize);
|
|
897
|
+
|
|
799
898
|
projected.xy += offset / vec2(uAspect, 1.0);
|
|
800
899
|
gl_Position = projected;
|
|
801
900
|
}
|
|
@@ -816,7 +915,17 @@ function createEngine({
|
|
|
816
915
|
depthTest: true
|
|
817
916
|
});
|
|
818
917
|
const mesh = new THREE4.Mesh(new THREE4.PlaneGeometry(1, 1), mat);
|
|
819
|
-
|
|
918
|
+
let p = getPosition(n);
|
|
919
|
+
if (n.level === 1) {
|
|
920
|
+
if (divisionPositions.has(n.id)) {
|
|
921
|
+
p.copy(divisionPositions.get(n.id));
|
|
922
|
+
}
|
|
923
|
+
const r = layoutCfg.radius * 0.95;
|
|
924
|
+
const angle = Math.atan2(p.z, p.x);
|
|
925
|
+
p.set(r * Math.cos(angle), 150, r * Math.sin(angle));
|
|
926
|
+
} else if (n.level === 3) {
|
|
927
|
+
p.multiplyScalar(1.002);
|
|
928
|
+
}
|
|
820
929
|
mesh.position.set(p.x, p.y, p.z);
|
|
821
930
|
mesh.scale.set(size.x, size.y, 1);
|
|
822
931
|
mesh.frustumCulled = false;
|
|
@@ -895,9 +1004,9 @@ function createEngine({
|
|
|
895
1004
|
const lineGeo = new THREE4.BufferGeometry();
|
|
896
1005
|
lineGeo.setAttribute("position", new THREE4.Float32BufferAttribute(linePoints, 3));
|
|
897
1006
|
const lineMat = createSmartMaterial({
|
|
898
|
-
uniforms: { color: { value: new THREE4.Color(
|
|
1007
|
+
uniforms: { color: { value: new THREE4.Color(11193599) } },
|
|
899
1008
|
vertexShaderBody: `uniform vec3 color; varying vec3 vColor; void main() { vColor = color; vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_Position = smartProject(mvPosition); vScreenPos = gl_Position.xy / gl_Position.w; }`,
|
|
900
|
-
fragmentShader: `varying vec3 vColor; void main() { float alphaMask = getMaskAlpha(); if (alphaMask < 0.01) discard; gl_FragColor = vec4(vColor, 0.
|
|
1009
|
+
fragmentShader: `varying vec3 vColor; void main() { float alphaMask = getMaskAlpha(); if (alphaMask < 0.01) discard; gl_FragColor = vec4(vColor, 0.4 * alphaMask); }`,
|
|
901
1010
|
transparent: true,
|
|
902
1011
|
depthWrite: false,
|
|
903
1012
|
blending: THREE4.AdditiveBlending
|
|
@@ -930,17 +1039,73 @@ function createEngine({
|
|
|
930
1039
|
}
|
|
931
1040
|
});
|
|
932
1041
|
boundaryGeo.setAttribute("position", new THREE4.Float32BufferAttribute(bPoints, 3));
|
|
933
|
-
|
|
1042
|
+
boundaryLines = new THREE4.LineSegments(boundaryGeo, boundaryMat);
|
|
934
1043
|
boundaryLines.frustumCulled = false;
|
|
935
1044
|
root.add(boundaryLines);
|
|
936
1045
|
}
|
|
1046
|
+
if (cfg.polygons) {
|
|
1047
|
+
const polyPoints = [];
|
|
1048
|
+
const rBase = layoutCfg.radius;
|
|
1049
|
+
for (const pts of Object.values(cfg.polygons)) {
|
|
1050
|
+
if (pts.length < 2) continue;
|
|
1051
|
+
for (let i = 0; i < pts.length; i++) {
|
|
1052
|
+
const p1_2d = pts[i];
|
|
1053
|
+
const p2_2d = pts[(i + 1) % pts.length];
|
|
1054
|
+
if (!p1_2d || !p2_2d) continue;
|
|
1055
|
+
const project2dTo3d = (p) => {
|
|
1056
|
+
const x = p[0];
|
|
1057
|
+
const y = p[1];
|
|
1058
|
+
const r_norm = Math.sqrt(x * x + y * y);
|
|
1059
|
+
const phi = Math.atan2(y, x);
|
|
1060
|
+
const theta = r_norm * (Math.PI / 2);
|
|
1061
|
+
return new THREE4.Vector3(
|
|
1062
|
+
Math.sin(theta) * Math.cos(phi),
|
|
1063
|
+
Math.cos(theta),
|
|
1064
|
+
Math.sin(theta) * Math.sin(phi)
|
|
1065
|
+
).multiplyScalar(rBase);
|
|
1066
|
+
};
|
|
1067
|
+
const v1 = project2dTo3d(p1_2d);
|
|
1068
|
+
const v2 = project2dTo3d(p2_2d);
|
|
1069
|
+
polyPoints.push(v1.x, v1.y, v1.z);
|
|
1070
|
+
polyPoints.push(v2.x, v2.y, v2.z);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
if (polyPoints.length > 0) {
|
|
1074
|
+
const polyGeo = new THREE4.BufferGeometry();
|
|
1075
|
+
polyGeo.setAttribute("position", new THREE4.Float32BufferAttribute(polyPoints, 3));
|
|
1076
|
+
const polyMat = createSmartMaterial({
|
|
1077
|
+
uniforms: { color: { value: new THREE4.Color(3718648) } },
|
|
1078
|
+
// Cyan-ish
|
|
1079
|
+
vertexShaderBody: `uniform vec3 color; varying vec3 vColor; void main() { vColor = color; vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_Position = smartProject(mvPosition); vScreenPos = gl_Position.xy / gl_Position.w; }`,
|
|
1080
|
+
fragmentShader: `varying vec3 vColor; void main() { float alphaMask = getMaskAlpha(); if (alphaMask < 0.01) discard; gl_FragColor = vec4(vColor, 0.2 * alphaMask); }`,
|
|
1081
|
+
transparent: true,
|
|
1082
|
+
depthWrite: false,
|
|
1083
|
+
blending: THREE4.AdditiveBlending
|
|
1084
|
+
});
|
|
1085
|
+
const polyLines = new THREE4.LineSegments(polyGeo, polyMat);
|
|
1086
|
+
polyLines.frustumCulled = false;
|
|
1087
|
+
root.add(polyLines);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
937
1090
|
resize();
|
|
938
1091
|
}
|
|
939
1092
|
let lastData = void 0;
|
|
940
1093
|
let lastAdapter = void 0;
|
|
941
1094
|
let lastModel = void 0;
|
|
1095
|
+
let lastAppliedLon = void 0;
|
|
1096
|
+
let lastAppliedLat = void 0;
|
|
942
1097
|
function setConfig(cfg) {
|
|
943
1098
|
currentConfig = cfg;
|
|
1099
|
+
if (typeof cfg.camera?.lon === "number" && cfg.camera.lon !== lastAppliedLon) {
|
|
1100
|
+
state.lon = cfg.camera.lon;
|
|
1101
|
+
state.targetLon = cfg.camera.lon;
|
|
1102
|
+
lastAppliedLon = cfg.camera.lon;
|
|
1103
|
+
}
|
|
1104
|
+
if (typeof cfg.camera?.lat === "number" && cfg.camera.lat !== lastAppliedLat) {
|
|
1105
|
+
state.lat = cfg.camera.lat;
|
|
1106
|
+
state.targetLat = cfg.camera.lat;
|
|
1107
|
+
lastAppliedLat = cfg.camera.lat;
|
|
1108
|
+
}
|
|
944
1109
|
let shouldRebuild = false;
|
|
945
1110
|
let model = cfg.model;
|
|
946
1111
|
if (!model && cfg.data && cfg.adapter) {
|
|
@@ -971,22 +1136,21 @@ function createEngine({
|
|
|
971
1136
|
function getFullArrangement() {
|
|
972
1137
|
const arr = {};
|
|
973
1138
|
if (starPoints && starPoints.geometry.attributes.position) {
|
|
974
|
-
const
|
|
1139
|
+
const attr = starPoints.geometry.attributes.position;
|
|
975
1140
|
for (let i = 0; i < starIndexToId.length; i++) {
|
|
976
1141
|
const id = starIndexToId[i];
|
|
977
1142
|
if (id) {
|
|
978
|
-
const x =
|
|
979
|
-
const y =
|
|
980
|
-
const z =
|
|
981
|
-
|
|
982
|
-
arr[id] = { position: [x, y, z] };
|
|
983
|
-
}
|
|
1143
|
+
const x = attr.getX(i);
|
|
1144
|
+
const y = attr.getY(i);
|
|
1145
|
+
const z = attr.getZ(i);
|
|
1146
|
+
arr[id] = { position: [x, y, z] };
|
|
984
1147
|
}
|
|
985
1148
|
}
|
|
986
1149
|
}
|
|
987
1150
|
for (const item of dynamicLabels) {
|
|
988
1151
|
arr[item.node.id] = { position: [item.obj.position.x, item.obj.position.y, item.obj.position.z] };
|
|
989
1152
|
}
|
|
1153
|
+
Object.assign(arr, state.tempArrangement);
|
|
990
1154
|
return arr;
|
|
991
1155
|
}
|
|
992
1156
|
function pick(ev) {
|
|
@@ -1072,6 +1236,7 @@ function createEngine({
|
|
|
1072
1236
|
state.isDragging = true;
|
|
1073
1237
|
state.velocityX = 0;
|
|
1074
1238
|
state.velocityY = 0;
|
|
1239
|
+
state.tempArrangement = {};
|
|
1075
1240
|
document.body.style.cursor = "grabbing";
|
|
1076
1241
|
}
|
|
1077
1242
|
function onMouseMove(e) {
|
|
@@ -1092,7 +1257,10 @@ function createEngine({
|
|
|
1092
1257
|
} else if (state.draggedGroup && state.draggedNodeId) {
|
|
1093
1258
|
const group = state.draggedGroup;
|
|
1094
1259
|
const item = dynamicLabels.find((l) => l.node.id === state.draggedNodeId);
|
|
1095
|
-
if (item)
|
|
1260
|
+
if (item) {
|
|
1261
|
+
item.obj.position.copy(newPos);
|
|
1262
|
+
state.tempArrangement[item.node.id] = { position: [newPos.x, newPos.y, newPos.z] };
|
|
1263
|
+
}
|
|
1096
1264
|
const vStart = group.labelInitialPos.clone().normalize();
|
|
1097
1265
|
const vEnd = newPos.clone().normalize();
|
|
1098
1266
|
const q = new THREE4.Quaternion().setFromUnitVectors(vStart, vEnd);
|
|
@@ -1102,6 +1270,10 @@ function createEngine({
|
|
|
1102
1270
|
for (const child of group.children) {
|
|
1103
1271
|
tempVec.copy(child.initialPos).applyQuaternion(q);
|
|
1104
1272
|
attr.setXYZ(child.index, tempVec.x, tempVec.y, tempVec.z);
|
|
1273
|
+
const id = starIndexToId[child.index];
|
|
1274
|
+
if (id) {
|
|
1275
|
+
state.tempArrangement[id] = { position: [tempVec.x, tempVec.y, tempVec.z] };
|
|
1276
|
+
}
|
|
1105
1277
|
}
|
|
1106
1278
|
attr.needsUpdate = true;
|
|
1107
1279
|
}
|
|
@@ -1121,6 +1293,26 @@ function createEngine({
|
|
|
1121
1293
|
state.lat = state.targetLat;
|
|
1122
1294
|
} else {
|
|
1123
1295
|
const hit = pick(e);
|
|
1296
|
+
if (hit && hit.type === "star") {
|
|
1297
|
+
if (currentHoverNodeId !== hit.node.id) {
|
|
1298
|
+
currentHoverNodeId = hit.node.id;
|
|
1299
|
+
const res = createTextTexture(hit.node.label, "#ffd700");
|
|
1300
|
+
if (res) {
|
|
1301
|
+
hoverLabelMat.uniforms.uMap.value = res.tex;
|
|
1302
|
+
const baseScale = 0.03;
|
|
1303
|
+
const size = new THREE4.Vector2(baseScale * res.aspect, baseScale);
|
|
1304
|
+
hoverLabelMat.uniforms.uSize.value = size;
|
|
1305
|
+
hoverLabelMesh.scale.set(size.x, size.y, 1);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
hoverLabelMesh.position.copy(hit.point);
|
|
1309
|
+
hoverLabelMat.uniforms.uAlpha.value = 1;
|
|
1310
|
+
hoverLabelMesh.visible = true;
|
|
1311
|
+
} else {
|
|
1312
|
+
currentHoverNodeId = null;
|
|
1313
|
+
hoverLabelMat.uniforms.uAlpha.value = 0;
|
|
1314
|
+
hoverLabelMesh.visible = false;
|
|
1315
|
+
}
|
|
1124
1316
|
if (hit?.node.id !== handlers._lastHoverId) {
|
|
1125
1317
|
handlers._lastHoverId = hit?.node.id;
|
|
1126
1318
|
handlers.onHover?.(hit?.node);
|
|
@@ -1262,30 +1454,107 @@ function createEngine({
|
|
|
1262
1454
|
camera.up.normalize();
|
|
1263
1455
|
camera.lookAt(target);
|
|
1264
1456
|
updateUniforms();
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1457
|
+
const DIVISION_THRESHOLD = 60;
|
|
1458
|
+
const showDivisions = state.fov > DIVISION_THRESHOLD;
|
|
1459
|
+
if (constellationLines) {
|
|
1460
|
+
constellationLines.visible = currentConfig?.showConstellationLines ?? false;
|
|
1461
|
+
}
|
|
1462
|
+
if (boundaryLines) {
|
|
1463
|
+
boundaryLines.visible = currentConfig?.showDivisionBoundaries ?? false;
|
|
1464
|
+
}
|
|
1465
|
+
const rect = renderer.domElement.getBoundingClientRect();
|
|
1466
|
+
const screenW = rect.width;
|
|
1467
|
+
const screenH = rect.height;
|
|
1468
|
+
const aspect = screenW / screenH;
|
|
1469
|
+
const labelsToCheck = [];
|
|
1470
|
+
const occupied = [];
|
|
1471
|
+
function isOverlapping(x2, y2, w, h) {
|
|
1472
|
+
for (const r2 of occupied) {
|
|
1473
|
+
if (x2 < r2.x + r2.w && x2 + w > r2.x && y2 < r2.y + r2.h && y2 + h > r2.y) return true;
|
|
1474
|
+
}
|
|
1475
|
+
return false;
|
|
1476
|
+
}
|
|
1477
|
+
const showBookLabels = currentConfig?.showBookLabels === true;
|
|
1478
|
+
const showDivisionLabels = currentConfig?.showDivisionLabels === true;
|
|
1479
|
+
const showChapterLabels = currentConfig?.showChapterLabels === true;
|
|
1480
|
+
const showChapters = state.fov < 35;
|
|
1270
1481
|
for (const item of dynamicLabels) {
|
|
1271
1482
|
const uniforms = item.obj.material.uniforms;
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
let gazeOpacity = 0;
|
|
1280
|
-
if (dot >= fullVisibleDot) gazeOpacity = 1;
|
|
1281
|
-
else if (dot > invisibleDot) gazeOpacity = (dot - invisibleDot) / (fullVisibleDot - invisibleDot);
|
|
1282
|
-
const zoomFactor = 1 - THREE4.MathUtils.smoothstep(40, SHOW_LABELS_FOV, state.fov);
|
|
1283
|
-
targetAlpha = gazeOpacity * zoomFactor;
|
|
1284
|
-
}
|
|
1285
|
-
if (uniforms.uAlpha) {
|
|
1286
|
-
uniforms.uAlpha.value = THREE4.MathUtils.lerp(uniforms.uAlpha.value, targetAlpha, 0.1);
|
|
1483
|
+
const level = item.node.level;
|
|
1484
|
+
let isEnabled = false;
|
|
1485
|
+
if (level === 2 && showBookLabels) isEnabled = true;
|
|
1486
|
+
else if (level === 1 && showDivisionLabels) isEnabled = true;
|
|
1487
|
+
else if (level === 3 && showChapterLabels) isEnabled = true;
|
|
1488
|
+
if (!isEnabled) {
|
|
1489
|
+
uniforms.uAlpha.value = THREE4.MathUtils.lerp(uniforms.uAlpha.value, 0, 0.2);
|
|
1287
1490
|
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
1491
|
+
continue;
|
|
1288
1492
|
}
|
|
1493
|
+
const pWorld = item.obj.position;
|
|
1494
|
+
const pProj = smartProjectJS(pWorld);
|
|
1495
|
+
if (pProj.z > 0.2) {
|
|
1496
|
+
uniforms.uAlpha.value = THREE4.MathUtils.lerp(uniforms.uAlpha.value, 0, 0.2);
|
|
1497
|
+
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1500
|
+
if (level === 3 && !showChapters && item.node.id !== state.draggedNodeId) {
|
|
1501
|
+
uniforms.uAlpha.value = THREE4.MathUtils.lerp(uniforms.uAlpha.value, 0, 0.2);
|
|
1502
|
+
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
const ndcX = pProj.x * globalUniforms.uScale.value / aspect;
|
|
1506
|
+
const ndcY = pProj.y * globalUniforms.uScale.value;
|
|
1507
|
+
const sX = (ndcX * 0.5 + 0.5) * screenW;
|
|
1508
|
+
const sY = (-ndcY * 0.5 + 0.5) * screenH;
|
|
1509
|
+
const size = uniforms.uSize.value;
|
|
1510
|
+
const pixelH = size.y * screenH * 0.8;
|
|
1511
|
+
const pixelW = size.x * screenH * 0.8;
|
|
1512
|
+
labelsToCheck.push({ item, sX, sY, w: pixelW, h: pixelH, uniforms, level });
|
|
1513
|
+
}
|
|
1514
|
+
const hoverId = handlers._lastHoverId;
|
|
1515
|
+
const selectedId = state.draggedNodeId;
|
|
1516
|
+
labelsToCheck.sort((a, b) => {
|
|
1517
|
+
const getScore = (l) => {
|
|
1518
|
+
if (l.item.node.id === selectedId) return 10;
|
|
1519
|
+
if (l.item.node.id === hoverId) return 9;
|
|
1520
|
+
const level = l.level;
|
|
1521
|
+
if (level === 2) return 5;
|
|
1522
|
+
if (level === 1) return showDivisions ? 6 : 1;
|
|
1523
|
+
return 0;
|
|
1524
|
+
};
|
|
1525
|
+
return getScore(b) - getScore(a);
|
|
1526
|
+
});
|
|
1527
|
+
for (const l of labelsToCheck) {
|
|
1528
|
+
let target2 = 0;
|
|
1529
|
+
const isSpecial = l.item.node.id === selectedId || l.item.node.id === hoverId;
|
|
1530
|
+
if (l.level === 1) {
|
|
1531
|
+
let rot = 0;
|
|
1532
|
+
const blend = globalUniforms.uBlend.value;
|
|
1533
|
+
if (blend > 0.5) {
|
|
1534
|
+
const dx = l.sX - screenW / 2;
|
|
1535
|
+
const dy = l.sY - screenH / 2;
|
|
1536
|
+
rot = Math.atan2(-dy, -dx) - Math.PI / 2;
|
|
1537
|
+
}
|
|
1538
|
+
l.uniforms.uAngle.value = THREE4.MathUtils.lerp(l.uniforms.uAngle.value, rot, 0.1);
|
|
1539
|
+
}
|
|
1540
|
+
if (l.level === 2) {
|
|
1541
|
+
target2 = 1;
|
|
1542
|
+
occupied.push({ x: l.sX - l.w / 2, y: l.sY - l.h / 2, w: l.w, h: l.h });
|
|
1543
|
+
} else if (l.level === 1) {
|
|
1544
|
+
if (showDivisions || isSpecial) {
|
|
1545
|
+
const pad = -5;
|
|
1546
|
+
if (!isOverlapping(l.sX - l.w / 2 - pad, l.sY - l.h / 2 - pad, l.w + pad * 2, l.h + pad * 2)) {
|
|
1547
|
+
target2 = 1;
|
|
1548
|
+
occupied.push({ x: l.sX - l.w / 2, y: l.sY - l.h / 2, w: l.w, h: l.h });
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
} else if (l.level === 3) {
|
|
1552
|
+
if (showChapters || isSpecial) {
|
|
1553
|
+
target2 = 1;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
l.uniforms.uAlpha.value = THREE4.MathUtils.lerp(l.uniforms.uAlpha.value, target2, 0.1);
|
|
1557
|
+
l.item.obj.visible = l.uniforms.uAlpha.value > 0.01;
|
|
1289
1558
|
}
|
|
1290
1559
|
renderer.render(scene, camera);
|
|
1291
1560
|
}
|
|
@@ -1370,6 +1639,7 @@ var StarMap = forwardRef(
|
|
|
1370
1639
|
function bibleToSceneModel(data) {
|
|
1371
1640
|
const nodes = [];
|
|
1372
1641
|
const links = [];
|
|
1642
|
+
let bookCounter = 0;
|
|
1373
1643
|
const id = {
|
|
1374
1644
|
testament: (t) => `T:${t}`,
|
|
1375
1645
|
division: (t, d) => `D:${t}:${d}`,
|
|
@@ -1390,10 +1660,12 @@ function bibleToSceneModel(data) {
|
|
|
1390
1660
|
});
|
|
1391
1661
|
links.push({ source: did, target: tid });
|
|
1392
1662
|
for (const b of d.books) {
|
|
1663
|
+
bookCounter++;
|
|
1664
|
+
const bookLabel = `${bookCounter}. ${b.name}`;
|
|
1393
1665
|
const bid = id.book(b.key);
|
|
1394
1666
|
nodes.push({
|
|
1395
1667
|
id: bid,
|
|
1396
|
-
label:
|
|
1668
|
+
label: bookLabel,
|
|
1397
1669
|
level: 2,
|
|
1398
1670
|
parent: did,
|
|
1399
1671
|
meta: { testament: t.name, division: d.name, bookKey: b.key, book: b.name }
|
|
@@ -1405,7 +1677,7 @@ function bibleToSceneModel(data) {
|
|
|
1405
1677
|
const cid = id.chapter(b.key, chapterNum);
|
|
1406
1678
|
nodes.push({
|
|
1407
1679
|
id: cid,
|
|
1408
|
-
label: `${
|
|
1680
|
+
label: `${bookLabel} ${chapterNum}`,
|
|
1409
1681
|
level: 3,
|
|
1410
1682
|
parent: bid,
|
|
1411
1683
|
weight: verseCounts[i],
|
|
@@ -30468,7 +30740,145 @@ var default_stars_default = {
|
|
|
30468
30740
|
}
|
|
30469
30741
|
]
|
|
30470
30742
|
};
|
|
30743
|
+
var defaultGenerateOptions = {
|
|
30744
|
+
seed: 12345,
|
|
30745
|
+
discRadius: 2e3,
|
|
30746
|
+
milkyWayEnabled: true,
|
|
30747
|
+
milkyWayAngle: 60,
|
|
30748
|
+
milkyWayWidth: 0.3,
|
|
30749
|
+
// Width in dot-product space
|
|
30750
|
+
milkyWayStrength: 0.7,
|
|
30751
|
+
noiseScale: 2,
|
|
30752
|
+
noiseStrength: 0.4,
|
|
30753
|
+
clusterSpread: 0.08
|
|
30754
|
+
// Radians approx
|
|
30755
|
+
};
|
|
30756
|
+
var RNG = class {
|
|
30757
|
+
seed;
|
|
30758
|
+
constructor(seed) {
|
|
30759
|
+
this.seed = seed;
|
|
30760
|
+
}
|
|
30761
|
+
// Returns 0..1
|
|
30762
|
+
next() {
|
|
30763
|
+
this.seed = (this.seed * 9301 + 49297) % 233280;
|
|
30764
|
+
return this.seed / 233280;
|
|
30765
|
+
}
|
|
30766
|
+
// Returns range [min, max)
|
|
30767
|
+
range(min, max) {
|
|
30768
|
+
return min + this.next() * (max - min);
|
|
30769
|
+
}
|
|
30770
|
+
// Uniform random on upper hemisphere (y > 0)
|
|
30771
|
+
randomOnSphere() {
|
|
30772
|
+
const y = this.next();
|
|
30773
|
+
const theta = 2 * Math.PI * this.next();
|
|
30774
|
+
const r = Math.sqrt(1 - y * y);
|
|
30775
|
+
const x = r * Math.cos(theta);
|
|
30776
|
+
const z = r * Math.sin(theta);
|
|
30777
|
+
return new THREE4.Vector3(x, y, z);
|
|
30778
|
+
}
|
|
30779
|
+
};
|
|
30780
|
+
function simpleNoise3D(v, scale) {
|
|
30781
|
+
const s = scale;
|
|
30782
|
+
return (Math.sin(v.x * s) + Math.sin(v.y * s * 1.3) + Math.sin(v.z * s * 1.7) + Math.sin(v.x * s * 2.1 + v.y * s * 2.1) * 0.5) / 3.5;
|
|
30783
|
+
}
|
|
30784
|
+
function getDensity(v, opts, mwNormal) {
|
|
30785
|
+
let density = 0.3;
|
|
30786
|
+
if (opts.milkyWayEnabled) {
|
|
30787
|
+
const dot = v.dot(mwNormal);
|
|
30788
|
+
const dist = Math.abs(dot);
|
|
30789
|
+
const band = Math.exp(-(dist * dist) / (opts.milkyWayWidth * opts.milkyWayWidth));
|
|
30790
|
+
density += band * opts.milkyWayStrength;
|
|
30791
|
+
}
|
|
30792
|
+
const noise = simpleNoise3D(v, opts.noiseScale);
|
|
30793
|
+
density *= 1 + noise * opts.noiseStrength;
|
|
30794
|
+
return Math.max(0.01, density);
|
|
30795
|
+
}
|
|
30796
|
+
function generateArrangement(bible, options = {}) {
|
|
30797
|
+
const opts = { ...defaultGenerateOptions, ...options };
|
|
30798
|
+
const rng = new RNG(opts.seed);
|
|
30799
|
+
const arrangement = {};
|
|
30800
|
+
const books = [];
|
|
30801
|
+
bible.testaments.forEach((t) => {
|
|
30802
|
+
t.divisions.forEach((d) => {
|
|
30803
|
+
d.books.forEach((b) => {
|
|
30804
|
+
books.push({
|
|
30805
|
+
key: b.key,
|
|
30806
|
+
name: b.name,
|
|
30807
|
+
chapters: b.chapters,
|
|
30808
|
+
division: d.name,
|
|
30809
|
+
testament: t.name
|
|
30810
|
+
});
|
|
30811
|
+
});
|
|
30812
|
+
});
|
|
30813
|
+
});
|
|
30814
|
+
const bookCount = books.length;
|
|
30815
|
+
const mwRad = THREE4.MathUtils.degToRad(opts.milkyWayAngle);
|
|
30816
|
+
const mwNormal = new THREE4.Vector3(Math.sin(mwRad), Math.cos(mwRad), 0).normalize();
|
|
30817
|
+
const anchors = [];
|
|
30818
|
+
for (let i = 0; i < bookCount; i++) {
|
|
30819
|
+
let bestP = new THREE4.Vector3();
|
|
30820
|
+
let valid = false;
|
|
30821
|
+
let attempt = 0;
|
|
30822
|
+
while (!valid && attempt < 100) {
|
|
30823
|
+
const p = rng.randomOnSphere();
|
|
30824
|
+
const d = getDensity(p, opts, mwNormal);
|
|
30825
|
+
if (rng.next() < d) {
|
|
30826
|
+
bestP = p;
|
|
30827
|
+
valid = true;
|
|
30828
|
+
}
|
|
30829
|
+
attempt++;
|
|
30830
|
+
}
|
|
30831
|
+
if (!valid) bestP = rng.randomOnSphere();
|
|
30832
|
+
anchors.push(bestP);
|
|
30833
|
+
}
|
|
30834
|
+
anchors.sort((a, b) => {
|
|
30835
|
+
const lonA = Math.atan2(a.z, a.x);
|
|
30836
|
+
const lonB = Math.atan2(b.z, b.x);
|
|
30837
|
+
return lonA - lonB;
|
|
30838
|
+
});
|
|
30839
|
+
books.forEach((book, i) => {
|
|
30840
|
+
const anchor = anchors[i];
|
|
30841
|
+
const anchorPos = anchor.clone().multiplyScalar(opts.discRadius);
|
|
30842
|
+
arrangement[`B:${book.key}`] = { position: [anchorPos.x, anchorPos.y, anchorPos.z] };
|
|
30843
|
+
for (let c = 0; c < book.chapters; c++) {
|
|
30844
|
+
const localSpread = opts.clusterSpread * (0.8 + rng.next() * 0.4);
|
|
30845
|
+
const offset = new THREE4.Vector3(
|
|
30846
|
+
(rng.next() - 0.5) * 2,
|
|
30847
|
+
(rng.next() - 0.5) * 2,
|
|
30848
|
+
(rng.next() - 0.5) * 2
|
|
30849
|
+
).normalize().multiplyScalar(rng.next() * localSpread);
|
|
30850
|
+
const starDir = anchor.clone().add(offset).normalize();
|
|
30851
|
+
if (starDir.y < 0.01) {
|
|
30852
|
+
starDir.y = 0.01;
|
|
30853
|
+
starDir.normalize();
|
|
30854
|
+
}
|
|
30855
|
+
const starPos = starDir.multiplyScalar(opts.discRadius);
|
|
30856
|
+
const chapId = `C:${book.key}:${c + 1}`;
|
|
30857
|
+
arrangement[chapId] = { position: [starPos.x, starPos.y, starPos.z] };
|
|
30858
|
+
}
|
|
30859
|
+
});
|
|
30860
|
+
const divisions = /* @__PURE__ */ new Map();
|
|
30861
|
+
books.forEach((book, i) => {
|
|
30862
|
+
const anchor = anchors[i];
|
|
30863
|
+
const anchorPos = anchor.clone().multiplyScalar(opts.discRadius);
|
|
30864
|
+
const divId = `D:${book.testament}:${book.division}`;
|
|
30865
|
+
if (!divisions.has(divId)) {
|
|
30866
|
+
divisions.set(divId, { sum: new THREE4.Vector3(), count: 0 });
|
|
30867
|
+
}
|
|
30868
|
+
const entry = divisions.get(divId);
|
|
30869
|
+
entry.sum.add(anchorPos);
|
|
30870
|
+
entry.count++;
|
|
30871
|
+
});
|
|
30872
|
+
divisions.forEach((val, key) => {
|
|
30873
|
+
if (val.count > 0) {
|
|
30874
|
+
val.sum.divideScalar(val.count);
|
|
30875
|
+
val.sum.normalize().multiplyScalar(opts.discRadius * 0.9);
|
|
30876
|
+
arrangement[key] = { position: [val.sum.x, val.sum.y, val.sum.z] };
|
|
30877
|
+
}
|
|
30878
|
+
});
|
|
30879
|
+
return arrangement;
|
|
30880
|
+
}
|
|
30471
30881
|
|
|
30472
|
-
export { StarMap, bibleToSceneModel, default_stars_default as defaultStars };
|
|
30882
|
+
export { StarMap, bibleToSceneModel, defaultGenerateOptions, default_stars_default as defaultStars, generateArrangement };
|
|
30473
30883
|
//# sourceMappingURL=index.js.map
|
|
30474
30884
|
//# sourceMappingURL=index.js.map
|