@project-skymap/library 0.3.0 → 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.cjs
CHANGED
|
@@ -446,7 +446,8 @@ function createEngine({
|
|
|
446
446
|
draggedNodeId: null,
|
|
447
447
|
draggedStarIndex: -1,
|
|
448
448
|
draggedDist: 2e3,
|
|
449
|
-
draggedGroup: null
|
|
449
|
+
draggedGroup: null,
|
|
450
|
+
tempArrangement: {}
|
|
450
451
|
};
|
|
451
452
|
const mouseNDC = new THREE4__namespace.Vector2();
|
|
452
453
|
let isMouseInWindow = false;
|
|
@@ -693,7 +694,55 @@ function createEngine({
|
|
|
693
694
|
const nodeById = /* @__PURE__ */ new Map();
|
|
694
695
|
const starIndexToId = [];
|
|
695
696
|
const dynamicLabels = [];
|
|
697
|
+
const hoverLabelMat = createSmartMaterial({
|
|
698
|
+
uniforms: {
|
|
699
|
+
uMap: { value: null },
|
|
700
|
+
uSize: { value: new THREE4__namespace.Vector2(1, 1) },
|
|
701
|
+
uAlpha: { value: 0 },
|
|
702
|
+
uAngle: { value: 0 }
|
|
703
|
+
},
|
|
704
|
+
vertexShaderBody: `
|
|
705
|
+
uniform vec2 uSize;
|
|
706
|
+
uniform float uAngle;
|
|
707
|
+
varying vec2 vUv;
|
|
708
|
+
void main() {
|
|
709
|
+
vUv = uv;
|
|
710
|
+
vec4 mvPos = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
|
|
711
|
+
vec4 projected = smartProject(mvPos);
|
|
712
|
+
|
|
713
|
+
float c = cos(uAngle);
|
|
714
|
+
float s = sin(uAngle);
|
|
715
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
716
|
+
vec2 offset = rot * (position.xy * uSize);
|
|
717
|
+
|
|
718
|
+
projected.xy += offset / vec2(uAspect, 1.0);
|
|
719
|
+
gl_Position = projected;
|
|
720
|
+
}
|
|
721
|
+
`,
|
|
722
|
+
fragmentShader: `
|
|
723
|
+
uniform sampler2D uMap;
|
|
724
|
+
uniform float uAlpha;
|
|
725
|
+
varying vec2 vUv;
|
|
726
|
+
void main() {
|
|
727
|
+
float mask = getMaskAlpha();
|
|
728
|
+
if (mask < 0.01) discard;
|
|
729
|
+
vec4 tex = texture2D(uMap, vUv);
|
|
730
|
+
gl_FragColor = vec4(tex.rgb, tex.a * uAlpha * mask);
|
|
731
|
+
}
|
|
732
|
+
`,
|
|
733
|
+
transparent: true,
|
|
734
|
+
depthWrite: false,
|
|
735
|
+
depthTest: false
|
|
736
|
+
// Always on top of stars
|
|
737
|
+
});
|
|
738
|
+
const hoverLabelMesh = new THREE4__namespace.Mesh(new THREE4__namespace.PlaneGeometry(1, 1), hoverLabelMat);
|
|
739
|
+
hoverLabelMesh.visible = false;
|
|
740
|
+
hoverLabelMesh.renderOrder = 999;
|
|
741
|
+
hoverLabelMesh.frustumCulled = false;
|
|
742
|
+
root.add(hoverLabelMesh);
|
|
743
|
+
let currentHoverNodeId = null;
|
|
696
744
|
let constellationLines = null;
|
|
745
|
+
let boundaryLines = null;
|
|
697
746
|
let starPoints = null;
|
|
698
747
|
function clearRoot() {
|
|
699
748
|
for (const child of [...root.children]) {
|
|
@@ -709,6 +758,7 @@ function createEngine({
|
|
|
709
758
|
starIndexToId.length = 0;
|
|
710
759
|
dynamicLabels.length = 0;
|
|
711
760
|
constellationLines = null;
|
|
761
|
+
boundaryLines = null;
|
|
712
762
|
starPoints = null;
|
|
713
763
|
}
|
|
714
764
|
function createTextTexture(text, color = "#ffffff") {
|
|
@@ -734,7 +784,22 @@ function createEngine({
|
|
|
734
784
|
function getPosition(n) {
|
|
735
785
|
if (currentConfig?.arrangement) {
|
|
736
786
|
const arr = currentConfig.arrangement[n.id];
|
|
737
|
-
if (arr)
|
|
787
|
+
if (arr) {
|
|
788
|
+
if (arr.position[2] === 0) {
|
|
789
|
+
const x = arr.position[0];
|
|
790
|
+
const y = arr.position[1];
|
|
791
|
+
const radius = currentConfig.layout?.radius ?? 2e3;
|
|
792
|
+
const r_norm = Math.min(1, Math.sqrt(x * x + y * y) / radius);
|
|
793
|
+
const phi = Math.atan2(y, x);
|
|
794
|
+
const theta = r_norm * (Math.PI / 2);
|
|
795
|
+
return new THREE4__namespace.Vector3(
|
|
796
|
+
Math.sin(theta) * Math.cos(phi),
|
|
797
|
+
Math.cos(theta),
|
|
798
|
+
Math.sin(theta) * Math.sin(phi)
|
|
799
|
+
).multiplyScalar(radius);
|
|
800
|
+
}
|
|
801
|
+
return new THREE4__namespace.Vector3(arr.position[0], arr.position[1], arr.position[2]);
|
|
802
|
+
}
|
|
738
803
|
}
|
|
739
804
|
return new THREE4__namespace.Vector3(n.meta?.x ?? 0, n.meta?.y ?? 0, n.meta?.z ?? 0);
|
|
740
805
|
}
|
|
@@ -750,6 +815,30 @@ function createEngine({
|
|
|
750
815
|
scene.background = cfg.background && cfg.background !== "transparent" ? new THREE4__namespace.Color(cfg.background) : new THREE4__namespace.Color(0);
|
|
751
816
|
const layoutCfg = { ...cfg.layout, radius: cfg.layout?.radius ?? 2e3 };
|
|
752
817
|
const laidOut = computeLayoutPositions(model, layoutCfg);
|
|
818
|
+
const divisionPositions = /* @__PURE__ */ new Map();
|
|
819
|
+
if (cfg.arrangement) {
|
|
820
|
+
const divMap = /* @__PURE__ */ new Map();
|
|
821
|
+
for (const n of laidOut.nodes) {
|
|
822
|
+
if (n.level === 2 && n.parent) {
|
|
823
|
+
const list = divMap.get(n.parent) ?? [];
|
|
824
|
+
list.push(n);
|
|
825
|
+
divMap.set(n.parent, list);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
for (const [divId, books] of divMap.entries()) {
|
|
829
|
+
const centroid = new THREE4__namespace.Vector3();
|
|
830
|
+
let count = 0;
|
|
831
|
+
for (const b of books) {
|
|
832
|
+
const p = getPosition(b);
|
|
833
|
+
centroid.add(p);
|
|
834
|
+
count++;
|
|
835
|
+
}
|
|
836
|
+
if (count > 0) {
|
|
837
|
+
centroid.divideScalar(count);
|
|
838
|
+
divisionPositions.set(divId, centroid);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
753
842
|
const starPositions = [];
|
|
754
843
|
const starSizes = [];
|
|
755
844
|
const starColors = [];
|
|
@@ -798,26 +887,36 @@ function createEngine({
|
|
|
798
887
|
const colorIdx = Math.floor(Math.pow(Math.random(), 1.5) * SPECTRAL_COLORS.length);
|
|
799
888
|
const c = SPECTRAL_COLORS[Math.min(colorIdx, SPECTRAL_COLORS.length - 1)];
|
|
800
889
|
starColors.push(c.r, c.g, c.b);
|
|
801
|
-
}
|
|
802
|
-
|
|
890
|
+
}
|
|
891
|
+
if (n.level === 1 || n.level === 2 || n.level === 3) {
|
|
892
|
+
const color = n.level === 1 ? "#38bdf8" : "#ffffff";
|
|
803
893
|
const texRes = createTextTexture(n.label, color);
|
|
804
894
|
if (texRes) {
|
|
805
|
-
|
|
895
|
+
let baseScale = 0.05;
|
|
896
|
+
if (n.level === 1) baseScale = 0.08;
|
|
897
|
+
else if (n.level === 3) baseScale = 0.04;
|
|
806
898
|
const size = new THREE4__namespace.Vector2(baseScale * texRes.aspect, baseScale);
|
|
807
899
|
const mat = createSmartMaterial({
|
|
808
900
|
uniforms: {
|
|
809
901
|
uMap: { value: texRes.tex },
|
|
810
902
|
uSize: { value: size },
|
|
811
|
-
uAlpha: { value: 0 }
|
|
903
|
+
uAlpha: { value: 0 },
|
|
904
|
+
uAngle: { value: 0 }
|
|
812
905
|
},
|
|
813
906
|
vertexShaderBody: `
|
|
814
907
|
uniform vec2 uSize;
|
|
908
|
+
uniform float uAngle;
|
|
815
909
|
varying vec2 vUv;
|
|
816
910
|
void main() {
|
|
817
911
|
vUv = uv;
|
|
818
912
|
vec4 mvPos = modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
|
|
819
913
|
vec4 projected = smartProject(mvPos);
|
|
820
|
-
|
|
914
|
+
|
|
915
|
+
float c = cos(uAngle);
|
|
916
|
+
float s = sin(uAngle);
|
|
917
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
918
|
+
vec2 offset = rot * (position.xy * uSize);
|
|
919
|
+
|
|
821
920
|
projected.xy += offset / vec2(uAspect, 1.0);
|
|
822
921
|
gl_Position = projected;
|
|
823
922
|
}
|
|
@@ -838,7 +937,17 @@ function createEngine({
|
|
|
838
937
|
depthTest: true
|
|
839
938
|
});
|
|
840
939
|
const mesh = new THREE4__namespace.Mesh(new THREE4__namespace.PlaneGeometry(1, 1), mat);
|
|
841
|
-
|
|
940
|
+
let p = getPosition(n);
|
|
941
|
+
if (n.level === 1) {
|
|
942
|
+
if (divisionPositions.has(n.id)) {
|
|
943
|
+
p.copy(divisionPositions.get(n.id));
|
|
944
|
+
}
|
|
945
|
+
const r = layoutCfg.radius * 0.95;
|
|
946
|
+
const angle = Math.atan2(p.z, p.x);
|
|
947
|
+
p.set(r * Math.cos(angle), 150, r * Math.sin(angle));
|
|
948
|
+
} else if (n.level === 3) {
|
|
949
|
+
p.multiplyScalar(1.002);
|
|
950
|
+
}
|
|
842
951
|
mesh.position.set(p.x, p.y, p.z);
|
|
843
952
|
mesh.scale.set(size.x, size.y, 1);
|
|
844
953
|
mesh.frustumCulled = false;
|
|
@@ -917,9 +1026,9 @@ function createEngine({
|
|
|
917
1026
|
const lineGeo = new THREE4__namespace.BufferGeometry();
|
|
918
1027
|
lineGeo.setAttribute("position", new THREE4__namespace.Float32BufferAttribute(linePoints, 3));
|
|
919
1028
|
const lineMat = createSmartMaterial({
|
|
920
|
-
uniforms: { color: { value: new THREE4__namespace.Color(
|
|
1029
|
+
uniforms: { color: { value: new THREE4__namespace.Color(11193599) } },
|
|
921
1030
|
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; }`,
|
|
922
|
-
fragmentShader: `varying vec3 vColor; void main() { float alphaMask = getMaskAlpha(); if (alphaMask < 0.01) discard; gl_FragColor = vec4(vColor, 0.
|
|
1031
|
+
fragmentShader: `varying vec3 vColor; void main() { float alphaMask = getMaskAlpha(); if (alphaMask < 0.01) discard; gl_FragColor = vec4(vColor, 0.4 * alphaMask); }`,
|
|
923
1032
|
transparent: true,
|
|
924
1033
|
depthWrite: false,
|
|
925
1034
|
blending: THREE4__namespace.AdditiveBlending
|
|
@@ -952,17 +1061,73 @@ function createEngine({
|
|
|
952
1061
|
}
|
|
953
1062
|
});
|
|
954
1063
|
boundaryGeo.setAttribute("position", new THREE4__namespace.Float32BufferAttribute(bPoints, 3));
|
|
955
|
-
|
|
1064
|
+
boundaryLines = new THREE4__namespace.LineSegments(boundaryGeo, boundaryMat);
|
|
956
1065
|
boundaryLines.frustumCulled = false;
|
|
957
1066
|
root.add(boundaryLines);
|
|
958
1067
|
}
|
|
1068
|
+
if (cfg.polygons) {
|
|
1069
|
+
const polyPoints = [];
|
|
1070
|
+
const rBase = layoutCfg.radius;
|
|
1071
|
+
for (const pts of Object.values(cfg.polygons)) {
|
|
1072
|
+
if (pts.length < 2) continue;
|
|
1073
|
+
for (let i = 0; i < pts.length; i++) {
|
|
1074
|
+
const p1_2d = pts[i];
|
|
1075
|
+
const p2_2d = pts[(i + 1) % pts.length];
|
|
1076
|
+
if (!p1_2d || !p2_2d) continue;
|
|
1077
|
+
const project2dTo3d = (p) => {
|
|
1078
|
+
const x = p[0];
|
|
1079
|
+
const y = p[1];
|
|
1080
|
+
const r_norm = Math.sqrt(x * x + y * y);
|
|
1081
|
+
const phi = Math.atan2(y, x);
|
|
1082
|
+
const theta = r_norm * (Math.PI / 2);
|
|
1083
|
+
return new THREE4__namespace.Vector3(
|
|
1084
|
+
Math.sin(theta) * Math.cos(phi),
|
|
1085
|
+
Math.cos(theta),
|
|
1086
|
+
Math.sin(theta) * Math.sin(phi)
|
|
1087
|
+
).multiplyScalar(rBase);
|
|
1088
|
+
};
|
|
1089
|
+
const v1 = project2dTo3d(p1_2d);
|
|
1090
|
+
const v2 = project2dTo3d(p2_2d);
|
|
1091
|
+
polyPoints.push(v1.x, v1.y, v1.z);
|
|
1092
|
+
polyPoints.push(v2.x, v2.y, v2.z);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (polyPoints.length > 0) {
|
|
1096
|
+
const polyGeo = new THREE4__namespace.BufferGeometry();
|
|
1097
|
+
polyGeo.setAttribute("position", new THREE4__namespace.Float32BufferAttribute(polyPoints, 3));
|
|
1098
|
+
const polyMat = createSmartMaterial({
|
|
1099
|
+
uniforms: { color: { value: new THREE4__namespace.Color(3718648) } },
|
|
1100
|
+
// Cyan-ish
|
|
1101
|
+
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; }`,
|
|
1102
|
+
fragmentShader: `varying vec3 vColor; void main() { float alphaMask = getMaskAlpha(); if (alphaMask < 0.01) discard; gl_FragColor = vec4(vColor, 0.2 * alphaMask); }`,
|
|
1103
|
+
transparent: true,
|
|
1104
|
+
depthWrite: false,
|
|
1105
|
+
blending: THREE4__namespace.AdditiveBlending
|
|
1106
|
+
});
|
|
1107
|
+
const polyLines = new THREE4__namespace.LineSegments(polyGeo, polyMat);
|
|
1108
|
+
polyLines.frustumCulled = false;
|
|
1109
|
+
root.add(polyLines);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
959
1112
|
resize();
|
|
960
1113
|
}
|
|
961
1114
|
let lastData = void 0;
|
|
962
1115
|
let lastAdapter = void 0;
|
|
963
1116
|
let lastModel = void 0;
|
|
1117
|
+
let lastAppliedLon = void 0;
|
|
1118
|
+
let lastAppliedLat = void 0;
|
|
964
1119
|
function setConfig(cfg) {
|
|
965
1120
|
currentConfig = cfg;
|
|
1121
|
+
if (typeof cfg.camera?.lon === "number" && cfg.camera.lon !== lastAppliedLon) {
|
|
1122
|
+
state.lon = cfg.camera.lon;
|
|
1123
|
+
state.targetLon = cfg.camera.lon;
|
|
1124
|
+
lastAppliedLon = cfg.camera.lon;
|
|
1125
|
+
}
|
|
1126
|
+
if (typeof cfg.camera?.lat === "number" && cfg.camera.lat !== lastAppliedLat) {
|
|
1127
|
+
state.lat = cfg.camera.lat;
|
|
1128
|
+
state.targetLat = cfg.camera.lat;
|
|
1129
|
+
lastAppliedLat = cfg.camera.lat;
|
|
1130
|
+
}
|
|
966
1131
|
let shouldRebuild = false;
|
|
967
1132
|
let model = cfg.model;
|
|
968
1133
|
if (!model && cfg.data && cfg.adapter) {
|
|
@@ -993,22 +1158,21 @@ function createEngine({
|
|
|
993
1158
|
function getFullArrangement() {
|
|
994
1159
|
const arr = {};
|
|
995
1160
|
if (starPoints && starPoints.geometry.attributes.position) {
|
|
996
|
-
const
|
|
1161
|
+
const attr = starPoints.geometry.attributes.position;
|
|
997
1162
|
for (let i = 0; i < starIndexToId.length; i++) {
|
|
998
1163
|
const id = starIndexToId[i];
|
|
999
1164
|
if (id) {
|
|
1000
|
-
const x =
|
|
1001
|
-
const y =
|
|
1002
|
-
const z =
|
|
1003
|
-
|
|
1004
|
-
arr[id] = { position: [x, y, z] };
|
|
1005
|
-
}
|
|
1165
|
+
const x = attr.getX(i);
|
|
1166
|
+
const y = attr.getY(i);
|
|
1167
|
+
const z = attr.getZ(i);
|
|
1168
|
+
arr[id] = { position: [x, y, z] };
|
|
1006
1169
|
}
|
|
1007
1170
|
}
|
|
1008
1171
|
}
|
|
1009
1172
|
for (const item of dynamicLabels) {
|
|
1010
1173
|
arr[item.node.id] = { position: [item.obj.position.x, item.obj.position.y, item.obj.position.z] };
|
|
1011
1174
|
}
|
|
1175
|
+
Object.assign(arr, state.tempArrangement);
|
|
1012
1176
|
return arr;
|
|
1013
1177
|
}
|
|
1014
1178
|
function pick(ev) {
|
|
@@ -1094,6 +1258,7 @@ function createEngine({
|
|
|
1094
1258
|
state.isDragging = true;
|
|
1095
1259
|
state.velocityX = 0;
|
|
1096
1260
|
state.velocityY = 0;
|
|
1261
|
+
state.tempArrangement = {};
|
|
1097
1262
|
document.body.style.cursor = "grabbing";
|
|
1098
1263
|
}
|
|
1099
1264
|
function onMouseMove(e) {
|
|
@@ -1114,7 +1279,10 @@ function createEngine({
|
|
|
1114
1279
|
} else if (state.draggedGroup && state.draggedNodeId) {
|
|
1115
1280
|
const group = state.draggedGroup;
|
|
1116
1281
|
const item = dynamicLabels.find((l) => l.node.id === state.draggedNodeId);
|
|
1117
|
-
if (item)
|
|
1282
|
+
if (item) {
|
|
1283
|
+
item.obj.position.copy(newPos);
|
|
1284
|
+
state.tempArrangement[item.node.id] = { position: [newPos.x, newPos.y, newPos.z] };
|
|
1285
|
+
}
|
|
1118
1286
|
const vStart = group.labelInitialPos.clone().normalize();
|
|
1119
1287
|
const vEnd = newPos.clone().normalize();
|
|
1120
1288
|
const q = new THREE4__namespace.Quaternion().setFromUnitVectors(vStart, vEnd);
|
|
@@ -1124,6 +1292,10 @@ function createEngine({
|
|
|
1124
1292
|
for (const child of group.children) {
|
|
1125
1293
|
tempVec.copy(child.initialPos).applyQuaternion(q);
|
|
1126
1294
|
attr.setXYZ(child.index, tempVec.x, tempVec.y, tempVec.z);
|
|
1295
|
+
const id = starIndexToId[child.index];
|
|
1296
|
+
if (id) {
|
|
1297
|
+
state.tempArrangement[id] = { position: [tempVec.x, tempVec.y, tempVec.z] };
|
|
1298
|
+
}
|
|
1127
1299
|
}
|
|
1128
1300
|
attr.needsUpdate = true;
|
|
1129
1301
|
}
|
|
@@ -1143,6 +1315,26 @@ function createEngine({
|
|
|
1143
1315
|
state.lat = state.targetLat;
|
|
1144
1316
|
} else {
|
|
1145
1317
|
const hit = pick(e);
|
|
1318
|
+
if (hit && hit.type === "star") {
|
|
1319
|
+
if (currentHoverNodeId !== hit.node.id) {
|
|
1320
|
+
currentHoverNodeId = hit.node.id;
|
|
1321
|
+
const res = createTextTexture(hit.node.label, "#ffd700");
|
|
1322
|
+
if (res) {
|
|
1323
|
+
hoverLabelMat.uniforms.uMap.value = res.tex;
|
|
1324
|
+
const baseScale = 0.03;
|
|
1325
|
+
const size = new THREE4__namespace.Vector2(baseScale * res.aspect, baseScale);
|
|
1326
|
+
hoverLabelMat.uniforms.uSize.value = size;
|
|
1327
|
+
hoverLabelMesh.scale.set(size.x, size.y, 1);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
hoverLabelMesh.position.copy(hit.point);
|
|
1331
|
+
hoverLabelMat.uniforms.uAlpha.value = 1;
|
|
1332
|
+
hoverLabelMesh.visible = true;
|
|
1333
|
+
} else {
|
|
1334
|
+
currentHoverNodeId = null;
|
|
1335
|
+
hoverLabelMat.uniforms.uAlpha.value = 0;
|
|
1336
|
+
hoverLabelMesh.visible = false;
|
|
1337
|
+
}
|
|
1146
1338
|
if (hit?.node.id !== handlers._lastHoverId) {
|
|
1147
1339
|
handlers._lastHoverId = hit?.node.id;
|
|
1148
1340
|
handlers.onHover?.(hit?.node);
|
|
@@ -1284,30 +1476,107 @@ function createEngine({
|
|
|
1284
1476
|
camera.up.normalize();
|
|
1285
1477
|
camera.lookAt(target);
|
|
1286
1478
|
updateUniforms();
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1479
|
+
const DIVISION_THRESHOLD = 60;
|
|
1480
|
+
const showDivisions = state.fov > DIVISION_THRESHOLD;
|
|
1481
|
+
if (constellationLines) {
|
|
1482
|
+
constellationLines.visible = currentConfig?.showConstellationLines ?? false;
|
|
1483
|
+
}
|
|
1484
|
+
if (boundaryLines) {
|
|
1485
|
+
boundaryLines.visible = currentConfig?.showDivisionBoundaries ?? false;
|
|
1486
|
+
}
|
|
1487
|
+
const rect = renderer.domElement.getBoundingClientRect();
|
|
1488
|
+
const screenW = rect.width;
|
|
1489
|
+
const screenH = rect.height;
|
|
1490
|
+
const aspect = screenW / screenH;
|
|
1491
|
+
const labelsToCheck = [];
|
|
1492
|
+
const occupied = [];
|
|
1493
|
+
function isOverlapping(x2, y2, w, h) {
|
|
1494
|
+
for (const r2 of occupied) {
|
|
1495
|
+
if (x2 < r2.x + r2.w && x2 + w > r2.x && y2 < r2.y + r2.h && y2 + h > r2.y) return true;
|
|
1496
|
+
}
|
|
1497
|
+
return false;
|
|
1498
|
+
}
|
|
1499
|
+
const showBookLabels = currentConfig?.showBookLabels === true;
|
|
1500
|
+
const showDivisionLabels = currentConfig?.showDivisionLabels === true;
|
|
1501
|
+
const showChapterLabels = currentConfig?.showChapterLabels === true;
|
|
1502
|
+
const showChapters = state.fov < 35;
|
|
1292
1503
|
for (const item of dynamicLabels) {
|
|
1293
1504
|
const uniforms = item.obj.material.uniforms;
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
let gazeOpacity = 0;
|
|
1302
|
-
if (dot >= fullVisibleDot) gazeOpacity = 1;
|
|
1303
|
-
else if (dot > invisibleDot) gazeOpacity = (dot - invisibleDot) / (fullVisibleDot - invisibleDot);
|
|
1304
|
-
const zoomFactor = 1 - THREE4__namespace.MathUtils.smoothstep(40, SHOW_LABELS_FOV, state.fov);
|
|
1305
|
-
targetAlpha = gazeOpacity * zoomFactor;
|
|
1306
|
-
}
|
|
1307
|
-
if (uniforms.uAlpha) {
|
|
1308
|
-
uniforms.uAlpha.value = THREE4__namespace.MathUtils.lerp(uniforms.uAlpha.value, targetAlpha, 0.1);
|
|
1505
|
+
const level = item.node.level;
|
|
1506
|
+
let isEnabled = false;
|
|
1507
|
+
if (level === 2 && showBookLabels) isEnabled = true;
|
|
1508
|
+
else if (level === 1 && showDivisionLabels) isEnabled = true;
|
|
1509
|
+
else if (level === 3 && showChapterLabels) isEnabled = true;
|
|
1510
|
+
if (!isEnabled) {
|
|
1511
|
+
uniforms.uAlpha.value = THREE4__namespace.MathUtils.lerp(uniforms.uAlpha.value, 0, 0.2);
|
|
1309
1512
|
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
1513
|
+
continue;
|
|
1310
1514
|
}
|
|
1515
|
+
const pWorld = item.obj.position;
|
|
1516
|
+
const pProj = smartProjectJS(pWorld);
|
|
1517
|
+
if (pProj.z > 0.2) {
|
|
1518
|
+
uniforms.uAlpha.value = THREE4__namespace.MathUtils.lerp(uniforms.uAlpha.value, 0, 0.2);
|
|
1519
|
+
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
if (level === 3 && !showChapters && item.node.id !== state.draggedNodeId) {
|
|
1523
|
+
uniforms.uAlpha.value = THREE4__namespace.MathUtils.lerp(uniforms.uAlpha.value, 0, 0.2);
|
|
1524
|
+
item.obj.visible = uniforms.uAlpha.value > 0.01;
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
const ndcX = pProj.x * globalUniforms.uScale.value / aspect;
|
|
1528
|
+
const ndcY = pProj.y * globalUniforms.uScale.value;
|
|
1529
|
+
const sX = (ndcX * 0.5 + 0.5) * screenW;
|
|
1530
|
+
const sY = (-ndcY * 0.5 + 0.5) * screenH;
|
|
1531
|
+
const size = uniforms.uSize.value;
|
|
1532
|
+
const pixelH = size.y * screenH * 0.8;
|
|
1533
|
+
const pixelW = size.x * screenH * 0.8;
|
|
1534
|
+
labelsToCheck.push({ item, sX, sY, w: pixelW, h: pixelH, uniforms, level });
|
|
1535
|
+
}
|
|
1536
|
+
const hoverId = handlers._lastHoverId;
|
|
1537
|
+
const selectedId = state.draggedNodeId;
|
|
1538
|
+
labelsToCheck.sort((a, b) => {
|
|
1539
|
+
const getScore = (l) => {
|
|
1540
|
+
if (l.item.node.id === selectedId) return 10;
|
|
1541
|
+
if (l.item.node.id === hoverId) return 9;
|
|
1542
|
+
const level = l.level;
|
|
1543
|
+
if (level === 2) return 5;
|
|
1544
|
+
if (level === 1) return showDivisions ? 6 : 1;
|
|
1545
|
+
return 0;
|
|
1546
|
+
};
|
|
1547
|
+
return getScore(b) - getScore(a);
|
|
1548
|
+
});
|
|
1549
|
+
for (const l of labelsToCheck) {
|
|
1550
|
+
let target2 = 0;
|
|
1551
|
+
const isSpecial = l.item.node.id === selectedId || l.item.node.id === hoverId;
|
|
1552
|
+
if (l.level === 1) {
|
|
1553
|
+
let rot = 0;
|
|
1554
|
+
const blend = globalUniforms.uBlend.value;
|
|
1555
|
+
if (blend > 0.5) {
|
|
1556
|
+
const dx = l.sX - screenW / 2;
|
|
1557
|
+
const dy = l.sY - screenH / 2;
|
|
1558
|
+
rot = Math.atan2(-dy, -dx) - Math.PI / 2;
|
|
1559
|
+
}
|
|
1560
|
+
l.uniforms.uAngle.value = THREE4__namespace.MathUtils.lerp(l.uniforms.uAngle.value, rot, 0.1);
|
|
1561
|
+
}
|
|
1562
|
+
if (l.level === 2) {
|
|
1563
|
+
target2 = 1;
|
|
1564
|
+
occupied.push({ x: l.sX - l.w / 2, y: l.sY - l.h / 2, w: l.w, h: l.h });
|
|
1565
|
+
} else if (l.level === 1) {
|
|
1566
|
+
if (showDivisions || isSpecial) {
|
|
1567
|
+
const pad = -5;
|
|
1568
|
+
if (!isOverlapping(l.sX - l.w / 2 - pad, l.sY - l.h / 2 - pad, l.w + pad * 2, l.h + pad * 2)) {
|
|
1569
|
+
target2 = 1;
|
|
1570
|
+
occupied.push({ x: l.sX - l.w / 2, y: l.sY - l.h / 2, w: l.w, h: l.h });
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
} else if (l.level === 3) {
|
|
1574
|
+
if (showChapters || isSpecial) {
|
|
1575
|
+
target2 = 1;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
l.uniforms.uAlpha.value = THREE4__namespace.MathUtils.lerp(l.uniforms.uAlpha.value, target2, 0.1);
|
|
1579
|
+
l.item.obj.visible = l.uniforms.uAlpha.value > 0.01;
|
|
1311
1580
|
}
|
|
1312
1581
|
renderer.render(scene, camera);
|
|
1313
1582
|
}
|
|
@@ -1392,6 +1661,7 @@ var StarMap = react.forwardRef(
|
|
|
1392
1661
|
function bibleToSceneModel(data) {
|
|
1393
1662
|
const nodes = [];
|
|
1394
1663
|
const links = [];
|
|
1664
|
+
let bookCounter = 0;
|
|
1395
1665
|
const id = {
|
|
1396
1666
|
testament: (t) => `T:${t}`,
|
|
1397
1667
|
division: (t, d) => `D:${t}:${d}`,
|
|
@@ -1412,10 +1682,12 @@ function bibleToSceneModel(data) {
|
|
|
1412
1682
|
});
|
|
1413
1683
|
links.push({ source: did, target: tid });
|
|
1414
1684
|
for (const b of d.books) {
|
|
1685
|
+
bookCounter++;
|
|
1686
|
+
const bookLabel = `${bookCounter}. ${b.name}`;
|
|
1415
1687
|
const bid = id.book(b.key);
|
|
1416
1688
|
nodes.push({
|
|
1417
1689
|
id: bid,
|
|
1418
|
-
label:
|
|
1690
|
+
label: bookLabel,
|
|
1419
1691
|
level: 2,
|
|
1420
1692
|
parent: did,
|
|
1421
1693
|
meta: { testament: t.name, division: d.name, bookKey: b.key, book: b.name }
|
|
@@ -1427,7 +1699,7 @@ function bibleToSceneModel(data) {
|
|
|
1427
1699
|
const cid = id.chapter(b.key, chapterNum);
|
|
1428
1700
|
nodes.push({
|
|
1429
1701
|
id: cid,
|
|
1430
|
-
label: `${
|
|
1702
|
+
label: `${bookLabel} ${chapterNum}`,
|
|
1431
1703
|
level: 3,
|
|
1432
1704
|
parent: bid,
|
|
1433
1705
|
weight: verseCounts[i],
|
|
@@ -30490,9 +30762,149 @@ var default_stars_default = {
|
|
|
30490
30762
|
}
|
|
30491
30763
|
]
|
|
30492
30764
|
};
|
|
30765
|
+
var defaultGenerateOptions = {
|
|
30766
|
+
seed: 12345,
|
|
30767
|
+
discRadius: 2e3,
|
|
30768
|
+
milkyWayEnabled: true,
|
|
30769
|
+
milkyWayAngle: 60,
|
|
30770
|
+
milkyWayWidth: 0.3,
|
|
30771
|
+
// Width in dot-product space
|
|
30772
|
+
milkyWayStrength: 0.7,
|
|
30773
|
+
noiseScale: 2,
|
|
30774
|
+
noiseStrength: 0.4,
|
|
30775
|
+
clusterSpread: 0.08
|
|
30776
|
+
// Radians approx
|
|
30777
|
+
};
|
|
30778
|
+
var RNG = class {
|
|
30779
|
+
seed;
|
|
30780
|
+
constructor(seed) {
|
|
30781
|
+
this.seed = seed;
|
|
30782
|
+
}
|
|
30783
|
+
// Returns 0..1
|
|
30784
|
+
next() {
|
|
30785
|
+
this.seed = (this.seed * 9301 + 49297) % 233280;
|
|
30786
|
+
return this.seed / 233280;
|
|
30787
|
+
}
|
|
30788
|
+
// Returns range [min, max)
|
|
30789
|
+
range(min, max) {
|
|
30790
|
+
return min + this.next() * (max - min);
|
|
30791
|
+
}
|
|
30792
|
+
// Uniform random on upper hemisphere (y > 0)
|
|
30793
|
+
randomOnSphere() {
|
|
30794
|
+
const y = this.next();
|
|
30795
|
+
const theta = 2 * Math.PI * this.next();
|
|
30796
|
+
const r = Math.sqrt(1 - y * y);
|
|
30797
|
+
const x = r * Math.cos(theta);
|
|
30798
|
+
const z = r * Math.sin(theta);
|
|
30799
|
+
return new THREE4__namespace.Vector3(x, y, z);
|
|
30800
|
+
}
|
|
30801
|
+
};
|
|
30802
|
+
function simpleNoise3D(v, scale) {
|
|
30803
|
+
const s = scale;
|
|
30804
|
+
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;
|
|
30805
|
+
}
|
|
30806
|
+
function getDensity(v, opts, mwNormal) {
|
|
30807
|
+
let density = 0.3;
|
|
30808
|
+
if (opts.milkyWayEnabled) {
|
|
30809
|
+
const dot = v.dot(mwNormal);
|
|
30810
|
+
const dist = Math.abs(dot);
|
|
30811
|
+
const band = Math.exp(-(dist * dist) / (opts.milkyWayWidth * opts.milkyWayWidth));
|
|
30812
|
+
density += band * opts.milkyWayStrength;
|
|
30813
|
+
}
|
|
30814
|
+
const noise = simpleNoise3D(v, opts.noiseScale);
|
|
30815
|
+
density *= 1 + noise * opts.noiseStrength;
|
|
30816
|
+
return Math.max(0.01, density);
|
|
30817
|
+
}
|
|
30818
|
+
function generateArrangement(bible, options = {}) {
|
|
30819
|
+
const opts = { ...defaultGenerateOptions, ...options };
|
|
30820
|
+
const rng = new RNG(opts.seed);
|
|
30821
|
+
const arrangement = {};
|
|
30822
|
+
const books = [];
|
|
30823
|
+
bible.testaments.forEach((t) => {
|
|
30824
|
+
t.divisions.forEach((d) => {
|
|
30825
|
+
d.books.forEach((b) => {
|
|
30826
|
+
books.push({
|
|
30827
|
+
key: b.key,
|
|
30828
|
+
name: b.name,
|
|
30829
|
+
chapters: b.chapters,
|
|
30830
|
+
division: d.name,
|
|
30831
|
+
testament: t.name
|
|
30832
|
+
});
|
|
30833
|
+
});
|
|
30834
|
+
});
|
|
30835
|
+
});
|
|
30836
|
+
const bookCount = books.length;
|
|
30837
|
+
const mwRad = THREE4__namespace.MathUtils.degToRad(opts.milkyWayAngle);
|
|
30838
|
+
const mwNormal = new THREE4__namespace.Vector3(Math.sin(mwRad), Math.cos(mwRad), 0).normalize();
|
|
30839
|
+
const anchors = [];
|
|
30840
|
+
for (let i = 0; i < bookCount; i++) {
|
|
30841
|
+
let bestP = new THREE4__namespace.Vector3();
|
|
30842
|
+
let valid = false;
|
|
30843
|
+
let attempt = 0;
|
|
30844
|
+
while (!valid && attempt < 100) {
|
|
30845
|
+
const p = rng.randomOnSphere();
|
|
30846
|
+
const d = getDensity(p, opts, mwNormal);
|
|
30847
|
+
if (rng.next() < d) {
|
|
30848
|
+
bestP = p;
|
|
30849
|
+
valid = true;
|
|
30850
|
+
}
|
|
30851
|
+
attempt++;
|
|
30852
|
+
}
|
|
30853
|
+
if (!valid) bestP = rng.randomOnSphere();
|
|
30854
|
+
anchors.push(bestP);
|
|
30855
|
+
}
|
|
30856
|
+
anchors.sort((a, b) => {
|
|
30857
|
+
const lonA = Math.atan2(a.z, a.x);
|
|
30858
|
+
const lonB = Math.atan2(b.z, b.x);
|
|
30859
|
+
return lonA - lonB;
|
|
30860
|
+
});
|
|
30861
|
+
books.forEach((book, i) => {
|
|
30862
|
+
const anchor = anchors[i];
|
|
30863
|
+
const anchorPos = anchor.clone().multiplyScalar(opts.discRadius);
|
|
30864
|
+
arrangement[`B:${book.key}`] = { position: [anchorPos.x, anchorPos.y, anchorPos.z] };
|
|
30865
|
+
for (let c = 0; c < book.chapters; c++) {
|
|
30866
|
+
const localSpread = opts.clusterSpread * (0.8 + rng.next() * 0.4);
|
|
30867
|
+
const offset = new THREE4__namespace.Vector3(
|
|
30868
|
+
(rng.next() - 0.5) * 2,
|
|
30869
|
+
(rng.next() - 0.5) * 2,
|
|
30870
|
+
(rng.next() - 0.5) * 2
|
|
30871
|
+
).normalize().multiplyScalar(rng.next() * localSpread);
|
|
30872
|
+
const starDir = anchor.clone().add(offset).normalize();
|
|
30873
|
+
if (starDir.y < 0.01) {
|
|
30874
|
+
starDir.y = 0.01;
|
|
30875
|
+
starDir.normalize();
|
|
30876
|
+
}
|
|
30877
|
+
const starPos = starDir.multiplyScalar(opts.discRadius);
|
|
30878
|
+
const chapId = `C:${book.key}:${c + 1}`;
|
|
30879
|
+
arrangement[chapId] = { position: [starPos.x, starPos.y, starPos.z] };
|
|
30880
|
+
}
|
|
30881
|
+
});
|
|
30882
|
+
const divisions = /* @__PURE__ */ new Map();
|
|
30883
|
+
books.forEach((book, i) => {
|
|
30884
|
+
const anchor = anchors[i];
|
|
30885
|
+
const anchorPos = anchor.clone().multiplyScalar(opts.discRadius);
|
|
30886
|
+
const divId = `D:${book.testament}:${book.division}`;
|
|
30887
|
+
if (!divisions.has(divId)) {
|
|
30888
|
+
divisions.set(divId, { sum: new THREE4__namespace.Vector3(), count: 0 });
|
|
30889
|
+
}
|
|
30890
|
+
const entry = divisions.get(divId);
|
|
30891
|
+
entry.sum.add(anchorPos);
|
|
30892
|
+
entry.count++;
|
|
30893
|
+
});
|
|
30894
|
+
divisions.forEach((val, key) => {
|
|
30895
|
+
if (val.count > 0) {
|
|
30896
|
+
val.sum.divideScalar(val.count);
|
|
30897
|
+
val.sum.normalize().multiplyScalar(opts.discRadius * 0.9);
|
|
30898
|
+
arrangement[key] = { position: [val.sum.x, val.sum.y, val.sum.z] };
|
|
30899
|
+
}
|
|
30900
|
+
});
|
|
30901
|
+
return arrangement;
|
|
30902
|
+
}
|
|
30493
30903
|
|
|
30494
30904
|
exports.StarMap = StarMap;
|
|
30495
30905
|
exports.bibleToSceneModel = bibleToSceneModel;
|
|
30906
|
+
exports.defaultGenerateOptions = defaultGenerateOptions;
|
|
30496
30907
|
exports.defaultStars = default_stars_default;
|
|
30908
|
+
exports.generateArrangement = generateArrangement;
|
|
30497
30909
|
//# sourceMappingURL=index.cjs.map
|
|
30498
30910
|
//# sourceMappingURL=index.cjs.map
|