worldorbit 3.0.5 → 3.0.7

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.
@@ -37777,6 +37777,15 @@ void main() {
37777
37777
  starCore: "#ffcc67",
37778
37778
  starStroke: "rgba(255, 245, 203, 0.85)",
37779
37779
  starGlow: "#ffe8a3",
37780
+ spaceFog: "#07131d",
37781
+ starfield: "rgba(226, 239, 255, 0.9)",
37782
+ starfieldDim: "rgba(164, 194, 228, 0.45)",
37783
+ objectSpecular: "#f5f8ff",
37784
+ orbitOpacity: 0.34,
37785
+ orbitBandOpacity: 0.24,
37786
+ selectionHalo: "rgba(255, 214, 139, 0.9)",
37787
+ atmosphere: "rgba(143, 202, 255, 0.4)",
37788
+ cometTail: "rgba(193, 243, 255, 0.7)",
37780
37789
  fontFamily: '"Segoe UI Variable", "Bahnschrift", sans-serif',
37781
37790
  displayFont: '"Bahnschrift", "Segoe UI Variable", sans-serif'
37782
37791
  },
@@ -37800,6 +37809,15 @@ void main() {
37800
37809
  starCore: "#e5f98c",
37801
37810
  starStroke: "rgba(246, 255, 217, 0.9)",
37802
37811
  starGlow: "#fffab4",
37812
+ spaceFog: "#071723",
37813
+ starfield: "rgba(220, 255, 245, 0.9)",
37814
+ starfieldDim: "rgba(124, 212, 195, 0.42)",
37815
+ objectSpecular: "#ecfffb",
37816
+ orbitOpacity: 0.3,
37817
+ orbitBandOpacity: 0.22,
37818
+ selectionHalo: "rgba(120, 255, 215, 0.85)",
37819
+ atmosphere: "rgba(120, 255, 215, 0.32)",
37820
+ cometTail: "rgba(181, 255, 236, 0.68)",
37803
37821
  fontFamily: '"Segoe UI Variable", "Bahnschrift", sans-serif',
37804
37822
  displayFont: '"Bahnschrift", "Segoe UI Variable", sans-serif'
37805
37823
  },
@@ -37823,6 +37841,15 @@ void main() {
37823
37841
  starCore: "#ffb766",
37824
37842
  starStroke: "rgba(255, 236, 205, 0.88)",
37825
37843
  starGlow: "#ffe2ad",
37844
+ spaceFog: "#1c0d12",
37845
+ starfield: "rgba(255, 232, 214, 0.88)",
37846
+ starfieldDim: "rgba(255, 176, 138, 0.38)",
37847
+ objectSpecular: "#fff0e6",
37848
+ orbitOpacity: 0.3,
37849
+ orbitBandOpacity: 0.24,
37850
+ selectionHalo: "rgba(255, 178, 125, 0.85)",
37851
+ atmosphere: "rgba(255, 190, 140, 0.26)",
37852
+ cometTail: "rgba(255, 214, 173, 0.62)",
37826
37853
  fontFamily: '"Segoe UI Variable", "Bahnschrift", sans-serif',
37827
37854
  displayFont: '"Bahnschrift", "Segoe UI Variable", sans-serif'
37828
37855
  }
@@ -38872,10 +38899,12 @@ void main() {
38872
38899
  let currentVisibleObjectIds = /* @__PURE__ */ new Set();
38873
38900
  let currentSelectedObjectId = null;
38874
38901
  let currentHoveredObjectId = null;
38875
- let currentTimeSeconds = 0;
38876
38902
  let currentPositions = /* @__PURE__ */ new Map();
38877
38903
  let pendingUpdate = null;
38878
38904
  let destroyed = false;
38905
+ let smoothedCameraPosition = null;
38906
+ let smoothedCameraTarget = null;
38907
+ let currentEnvironmentKey = "";
38879
38908
  const objectVisuals = /* @__PURE__ */ new Map();
38880
38909
  const orbitVisuals = /* @__PURE__ */ new Map();
38881
38910
  const raycastTargets = [];
@@ -38884,7 +38913,7 @@ void main() {
38884
38913
  return;
38885
38914
  }
38886
38915
  const scene3d = new THREE.Scene();
38887
- const camera = new THREE.PerspectiveCamera(52, 1, 0.1, 2e4);
38916
+ const camera = new THREE.PerspectiveCamera(46, 1, 0.1, 24e3);
38888
38917
  const renderer = new THREE.WebGLRenderer({
38889
38918
  antialias: true,
38890
38919
  alpha: true,
@@ -38894,27 +38923,40 @@ void main() {
38894
38923
  renderer.domElement.dataset.worldorbit3dCanvas = "true";
38895
38924
  root.innerHTML = "";
38896
38925
  root.append(renderer.domElement);
38897
- const ambientLight = new THREE.AmbientLight(16777215, 1.2);
38898
- const keyLight = new THREE.PointLight(16777215, 1.35, 0, 2);
38899
- scene3d.add(ambientLight);
38900
- scene3d.add(keyLight);
38926
+ const ambientLight = new THREE.AmbientLight(16777215, 0.24);
38927
+ const fillLight = new THREE.DirectionalLight(13625855, 0.36);
38928
+ const rimLight = new THREE.DirectionalLight(8304895, 0.24);
38929
+ const keyLight = new THREE.PointLight(16773327, 2.6, 0, 2);
38930
+ fillLight.position.set(-360, 260, 220);
38931
+ rimLight.position.set(340, 180, -280);
38901
38932
  const orbitLayer = new THREE.Group();
38902
38933
  const objectLayer = new THREE.Group();
38934
+ const starfield = createStarfield(THREE, 320);
38935
+ const raycaster = new THREE.Raycaster();
38936
+ raycaster.params.Line = { threshold: 7 };
38937
+ scene3d.add(ambientLight);
38938
+ scene3d.add(fillLight);
38939
+ scene3d.add(rimLight);
38940
+ scene3d.add(keyLight);
38941
+ scene3d.add(starfield);
38903
38942
  scene3d.add(orbitLayer);
38904
38943
  scene3d.add(objectLayer);
38905
- const raycaster = new THREE.Raycaster();
38906
- raycaster.params.Line = { threshold: 10 };
38907
38944
  runtime = {
38908
38945
  THREE,
38909
38946
  scene3d,
38910
38947
  camera,
38911
38948
  renderer,
38949
+ ambientLight,
38950
+ fillLight,
38951
+ rimLight,
38912
38952
  keyLight,
38953
+ starfield,
38913
38954
  orbitLayer,
38914
38955
  objectLayer,
38915
38956
  raycaster,
38916
38957
  pointer: new THREE.Vector2()
38917
38958
  };
38959
+ configureRenderer(renderer, THREE, "balanced");
38918
38960
  if (pendingUpdate) {
38919
38961
  applyUpdate(pendingUpdate);
38920
38962
  }
@@ -38973,6 +39015,8 @@ void main() {
38973
39015
  destroyed = true;
38974
39016
  pendingUpdate = null;
38975
39017
  runtime?.renderer.dispose();
39018
+ runtime?.starfield?.geometry?.dispose?.();
39019
+ runtime?.starfield?.material?.dispose?.();
38976
39020
  root.remove();
38977
39021
  objectVisuals.clear();
38978
39022
  orbitVisuals.clear();
@@ -38991,7 +39035,16 @@ void main() {
38991
39035
  currentVisibleObjectIds = next.visibleObjectIds;
38992
39036
  currentSelectedObjectId = next.selectedObjectId;
38993
39037
  currentHoveredObjectId = next.hoveredObjectId;
38994
- currentTimeSeconds = next.timeSeconds;
39038
+ configureRenderer(runtime.renderer, runtime.THREE, currentRenderOptions?.quality ?? "balanced");
39039
+ const nextEnvironmentKey = JSON.stringify({
39040
+ theme: currentRenderOptions?.theme ?? null,
39041
+ quality: currentRenderOptions?.quality ?? "balanced",
39042
+ style3d: currentRenderOptions?.style3d ?? "symbolic"
39043
+ });
39044
+ if (nextEnvironmentKey !== currentEnvironmentKey) {
39045
+ updateEnvironment(runtime, currentRenderOptions);
39046
+ currentEnvironmentKey = nextEnvironmentKey;
39047
+ }
38995
39048
  if (sceneChanged) {
38996
39049
  rebuildScene(next.spatialScene);
38997
39050
  }
@@ -39001,8 +39054,9 @@ void main() {
39001
39054
  updateOrbitTransforms();
39002
39055
  updateVisibility();
39003
39056
  updateInteractionState();
39057
+ updateLighting();
39004
39058
  updateCamera();
39005
- renderNow();
39059
+ runtime.renderer.render(runtime.scene3d, runtime.camera);
39006
39060
  }
39007
39061
  function rebuildScene(spatialScene) {
39008
39062
  if (!runtime) {
@@ -39013,8 +39067,9 @@ void main() {
39013
39067
  objectVisuals.clear();
39014
39068
  orbitVisuals.clear();
39015
39069
  raycastTargets.length = 0;
39070
+ smoothedCameraPosition = null;
39071
+ smoothedCameraTarget = null;
39016
39072
  const theme = resolveTheme(currentRenderOptions?.theme);
39017
- runtime.scene3d.background = new runtime.THREE.Color(theme.backgroundStart);
39018
39073
  for (const orbit of spatialScene.orbits) {
39019
39074
  const visual = createOrbitVisual2(runtime.THREE, orbit, theme);
39020
39075
  runtime.orbitLayer.add(visual.root);
@@ -39032,10 +39087,9 @@ void main() {
39032
39087
  for (const object of currentScene?.objects ?? []) {
39033
39088
  const visual = objectVisuals.get(object.objectId);
39034
39089
  const position = currentPositions.get(object.objectId);
39035
- if (!visual || !position) {
39036
- continue;
39090
+ if (visual && position) {
39091
+ visual.root.position.set(position.x, position.y, position.z);
39037
39092
  }
39038
- visual.root.position.set(position.x, position.y, position.z);
39039
39093
  }
39040
39094
  }
39041
39095
  function updateOrbitTransforms() {
@@ -39056,8 +39110,7 @@ void main() {
39056
39110
  continue;
39057
39111
  }
39058
39112
  const hideStructure = layers.structures === false && (object.object.type === "structure" || object.object.type === "phenomenon");
39059
- const hideObjects = layers.objects === false;
39060
- visual.root.visible = !object.hidden && currentVisibleObjectIds.has(object.objectId) && !hideStructure && !hideObjects;
39113
+ visual.root.visible = !object.hidden && currentVisibleObjectIds.has(object.objectId) && layers.objects !== false && !hideStructure;
39061
39114
  }
39062
39115
  for (const orbit of currentScene?.orbits ?? []) {
39063
39116
  const visual = orbitVisuals.get(orbit.objectId);
@@ -39073,14 +39126,27 @@ void main() {
39073
39126
  return;
39074
39127
  }
39075
39128
  for (const visual of objectVisuals.values()) {
39076
- applyVisualState(runtime.THREE, visual.materials, visual.baseColor, currentSelectedObjectId === visual.objectId, currentHoveredObjectId === visual.objectId);
39077
- const scale = currentSelectedObjectId === visual.objectId ? 1.2 : currentHoveredObjectId === visual.objectId ? 1.1 : 1;
39129
+ const selected = currentSelectedObjectId === visual.objectId;
39130
+ const hovered = currentHoveredObjectId === visual.objectId;
39131
+ applyVisualState(runtime.THREE, visual.materials, selected, hovered);
39132
+ const scale = selected ? 1.16 : hovered ? 1.08 : 1;
39078
39133
  visual.root.scale.set(scale, scale, scale);
39134
+ if (visual.halo) {
39135
+ visual.halo.visible = selected || hovered;
39136
+ }
39079
39137
  }
39080
39138
  for (const visual of orbitVisuals.values()) {
39081
- applyVisualState(runtime.THREE, visual.materials, visual.baseColor, currentSelectedObjectId === visual.objectId, currentHoveredObjectId === visual.objectId);
39139
+ applyVisualState(runtime.THREE, visual.materials, currentSelectedObjectId === visual.objectId, currentHoveredObjectId === visual.objectId);
39082
39140
  }
39083
39141
  }
39142
+ function updateLighting() {
39143
+ if (!runtime || !currentScene) {
39144
+ return;
39145
+ }
39146
+ const primaryStar = currentScene.objects.find((object) => object.object.type === "star" && !object.hidden) ?? null;
39147
+ const starPosition = primaryStar ? currentPositions.get(primaryStar.objectId) ?? primaryStar.position : { x: 0, y: 40, z: 0 };
39148
+ runtime.keyLight.position.set(starPosition.x, starPosition.y + 20, starPosition.z);
39149
+ }
39084
39150
  function updateCamera() {
39085
39151
  if (!runtime || !currentScene || !currentState) {
39086
39152
  return;
@@ -39088,19 +39154,27 @@ void main() {
39088
39154
  const sceneCamera = currentRenderOptions?.camera ?? currentScene.camera;
39089
39155
  const bounds = currentScene.contentBounds;
39090
39156
  const size = Math.max(bounds.width, bounds.depth, bounds.height, 160);
39091
- const yaw = degreesToRadians3((sceneCamera?.azimuth ?? 34) + currentState.rotationDeg);
39092
- const pitch = degreesToRadians3(clampValue(sceneCamera?.elevation ?? 24, -75, 75));
39093
- const zoomDistanceFactor = clampValue(2.4 / Math.max(currentState.scale, 0.1), 0.35, 8);
39094
- const semanticDistance = clampValue(sceneCamera?.distance ?? 6, 2, 24);
39095
- const distance = clampValue(size * zoomDistanceFactor * (semanticDistance / 6), 28, 8e3);
39157
+ const yaw = degreesToRadians3((sceneCamera?.azimuth ?? 30) + currentState.rotationDeg);
39158
+ const pitch = degreesToRadians3(clampValue(sceneCamera?.elevation ?? 22, -75, 75));
39159
+ const zoomDistanceFactor = clampValue(2.2 / Math.max(currentState.scale, 0.1), 0.35, 7.2);
39160
+ const semanticDistance = clampValue(sceneCamera?.distance ?? 5.4, 2, 24);
39161
+ const distance = clampValue(size * zoomDistanceFactor * (semanticDistance / 5.4), 24, 8e3);
39096
39162
  const panFactor = Math.max(size / 900, 0.12);
39097
39163
  const target = new runtime.THREE.Vector3(bounds.center.x - currentState.translateX * panFactor, bounds.center.y, bounds.center.z - currentState.translateY * panFactor);
39098
- runtime.camera.position.set(target.x + distance * Math.cos(pitch) * Math.sin(yaw), target.y + distance * Math.sin(pitch), target.z + distance * Math.cos(pitch) * Math.cos(yaw));
39099
- runtime.camera.lookAt(target);
39164
+ const desiredPosition = new runtime.THREE.Vector3(target.x + distance * Math.cos(pitch) * Math.sin(yaw), target.y + distance * Math.sin(pitch), target.z + distance * Math.cos(pitch) * Math.cos(yaw));
39165
+ const smoothing = (currentRenderOptions?.style3d ?? "symbolic") === "cinematic" ? 0.16 : 0.32;
39166
+ if (!smoothedCameraPosition || !smoothedCameraTarget) {
39167
+ smoothedCameraPosition = desiredPosition.clone();
39168
+ smoothedCameraTarget = target.clone();
39169
+ } else {
39170
+ smoothedCameraPosition.lerp(desiredPosition, smoothing);
39171
+ smoothedCameraTarget.lerp(target, smoothing);
39172
+ }
39173
+ runtime.camera.position.copy(smoothedCameraPosition);
39174
+ runtime.camera.lookAt(smoothedCameraTarget);
39100
39175
  if (sceneCamera?.roll) {
39101
39176
  runtime.camera.rotation.z = degreesToRadians3(sceneCamera.roll);
39102
39177
  }
39103
- runtime.keyLight.position.copy(runtime.camera.position);
39104
39178
  }
39105
39179
  function resizeRenderer(spatialScene) {
39106
39180
  if (!runtime) {
@@ -39112,35 +39186,63 @@ void main() {
39112
39186
  runtime.camera.aspect = width / height;
39113
39187
  runtime.camera.updateProjectionMatrix();
39114
39188
  }
39115
- function renderNow() {
39116
- if (!runtime) {
39117
- return;
39118
- }
39119
- runtime.renderer.render(runtime.scene3d, runtime.camera);
39120
- }
39121
39189
  }
39122
39190
  function createObjectVisual(THREE, object, theme) {
39123
39191
  const root = new THREE.Group();
39124
39192
  root.userData.objectId = object.objectId;
39125
39193
  const baseColor = object.fillColor ?? colorForObject(object);
39126
- const material = new THREE.MeshPhongMaterial({
39127
- color: baseColor,
39128
- emissive: object.object.type === "star" ? new THREE.Color(theme.starGlow) : new THREE.Color(0),
39129
- emissiveIntensity: object.object.type === "star" ? 0.6 : 0.08,
39130
- transparent: true,
39131
- opacity: object.object.type === "phenomenon" ? 0.7 : 1
39132
- });
39133
- const geometry = geometryForObject(THREE, object);
39134
- const body = new THREE.Mesh(geometry, material);
39194
+ const materials = [];
39195
+ const bodyMaterial = materialForObject(THREE, object, baseColor, theme);
39196
+ const body = new THREE.Mesh(geometryForObject(THREE, object), bodyMaterial.material);
39135
39197
  body.userData.objectId = object.objectId;
39136
39198
  root.add(body);
39137
- return {
39138
- objectId: object.objectId,
39139
- root,
39140
- body,
39141
- materials: [material],
39142
- baseColor
39143
- };
39199
+ materials.push(bodyMaterial);
39200
+ if (shouldRenderAtmosphere(object)) {
39201
+ const atmosphereMaterial = {
39202
+ material: new THREE.MeshBasicMaterial({
39203
+ color: theme.atmosphere,
39204
+ transparent: true,
39205
+ opacity: 0.24,
39206
+ depthWrite: false,
39207
+ side: 2
39208
+ }),
39209
+ baseColor: theme.atmosphere,
39210
+ baseOpacity: 0.24,
39211
+ hoveredOpacity: 0.34,
39212
+ selectedOpacity: 0.42
39213
+ };
39214
+ const atmosphere = new THREE.Mesh(new THREE.SphereGeometry(Math.max(object.visualRadius, 2) * 1.16, 20, 14), atmosphereMaterial.material);
39215
+ atmosphere.userData.objectId = object.objectId;
39216
+ root.add(atmosphere);
39217
+ materials.push(atmosphereMaterial);
39218
+ }
39219
+ if (object.object.type === "comet") {
39220
+ const tailMaterial = {
39221
+ material: new THREE.MeshBasicMaterial({
39222
+ color: theme.cometTail,
39223
+ transparent: true,
39224
+ opacity: 0.36,
39225
+ depthWrite: false
39226
+ }),
39227
+ baseColor: theme.cometTail,
39228
+ baseOpacity: 0.36,
39229
+ hoveredOpacity: 0.48,
39230
+ selectedOpacity: 0.56
39231
+ };
39232
+ const tail = new THREE.Mesh(new THREE.ConeGeometry(Math.max(object.visualRadius * 0.55, 2), Math.max(object.visualRadius * 2.8, 8), 12, 1, true), tailMaterial.material);
39233
+ tail.position.set(-Math.max(object.visualRadius * 1.4, 4), 0, 0);
39234
+ tail.rotation.z = -Math.PI / 2;
39235
+ tail.userData.objectId = object.objectId;
39236
+ root.add(tail);
39237
+ materials.push(tailMaterial);
39238
+ }
39239
+ const halo = createHalo(THREE, object, theme);
39240
+ if (halo) {
39241
+ halo.visible = false;
39242
+ halo.userData.objectId = object.objectId;
39243
+ root.add(halo);
39244
+ }
39245
+ return { objectId: object.objectId, root, halo, materials };
39144
39246
  }
39145
39247
  function createOrbitVisual2(THREE, orbit, theme) {
39146
39248
  const root = new THREE.Group();
@@ -39148,60 +39250,174 @@ void main() {
39148
39250
  root.rotation.y = degreesToRadians3(orbit.rotationDeg);
39149
39251
  root.rotation.x = degreesToRadians3(orbit.inclinationDeg);
39150
39252
  const baseColor = orbit.object.properties.color ?? theme.orbit;
39151
- const materials = [];
39152
39253
  if (orbit.band) {
39153
- const material = new THREE.MeshBasicMaterial({
39154
- color: baseColor,
39155
- transparent: true,
39156
- opacity: 0.42,
39157
- side: 2
39158
- });
39159
- const geometry = bandGeometryForOrbit(THREE, orbit);
39160
- const mesh = new THREE.Mesh(geometry, material);
39254
+ const material2 = {
39255
+ material: new THREE.MeshBasicMaterial({
39256
+ color: baseColor,
39257
+ transparent: true,
39258
+ opacity: theme.orbitBandOpacity,
39259
+ side: 2,
39260
+ depthWrite: false
39261
+ }),
39262
+ baseColor,
39263
+ baseOpacity: theme.orbitBandOpacity,
39264
+ hoveredOpacity: Math.min(theme.orbitBandOpacity + 0.1, 0.58),
39265
+ selectedOpacity: Math.min(theme.orbitBandOpacity + 0.18, 0.72),
39266
+ hoveredColor: theme.accent,
39267
+ selectedColor: theme.accentStrong
39268
+ };
39269
+ const mesh = new THREE.Mesh(bandGeometryForOrbit(THREE, orbit), material2.material);
39161
39270
  mesh.userData.objectId = orbit.objectId;
39162
39271
  root.add(mesh);
39163
- materials.push(material);
39164
- } else {
39165
- const material = new THREE.LineBasicMaterial({
39272
+ return { objectId: orbit.objectId, root, materials: [material2] };
39273
+ }
39274
+ const material = {
39275
+ material: new THREE.LineBasicMaterial({
39166
39276
  color: baseColor,
39167
39277
  transparent: true,
39168
- opacity: 0.55
39169
- });
39170
- const points = sampleOrbitPoints(THREE, orbit);
39171
- const geometry = new THREE.BufferGeometry().setFromPoints(points);
39172
- const line = new THREE.LineLoop(geometry, material);
39173
- line.userData.objectId = orbit.objectId;
39174
- root.add(line);
39175
- materials.push(material);
39176
- }
39278
+ opacity: theme.orbitOpacity
39279
+ }),
39280
+ baseColor,
39281
+ baseOpacity: theme.orbitOpacity,
39282
+ hoveredOpacity: Math.min(theme.orbitOpacity + 0.18, 0.72),
39283
+ selectedOpacity: Math.min(theme.orbitOpacity + 0.3, 0.88),
39284
+ hoveredColor: theme.accent,
39285
+ selectedColor: theme.accentStrong
39286
+ };
39287
+ const geometry = new THREE.BufferGeometry().setFromPoints(sampleOrbitPoints(THREE, orbit, 120));
39288
+ const line = new THREE.LineLoop(geometry, material.material);
39289
+ line.userData.objectId = orbit.objectId;
39290
+ root.add(line);
39291
+ return { objectId: orbit.objectId, root, materials: [material] };
39292
+ }
39293
+ function materialForObject(THREE, object, baseColor, theme) {
39294
+ if (object.object.type === "star") {
39295
+ return {
39296
+ material: new THREE.MeshStandardMaterial({
39297
+ color: baseColor,
39298
+ emissive: new THREE.Color(theme.starGlow),
39299
+ emissiveIntensity: 1.2,
39300
+ roughness: 0.35,
39301
+ metalness: 0.02
39302
+ }),
39303
+ baseColor,
39304
+ baseOpacity: 1,
39305
+ hoveredOpacity: 1,
39306
+ selectedOpacity: 1,
39307
+ hoveredColor: theme.starCore,
39308
+ selectedColor: "#fff2c4",
39309
+ baseEmissive: theme.starGlow,
39310
+ hoveredEmissive: theme.starGlow,
39311
+ selectedEmissive: "#fff6cc",
39312
+ baseEmissiveIntensity: 1.2,
39313
+ hoveredEmissiveIntensity: 1.5,
39314
+ selectedEmissiveIntensity: 1.8
39315
+ };
39316
+ }
39317
+ if (object.object.type === "phenomenon") {
39318
+ return {
39319
+ material: new THREE.MeshPhongMaterial({
39320
+ color: baseColor,
39321
+ transparent: true,
39322
+ opacity: 0.7,
39323
+ emissive: new THREE.Color(baseColor),
39324
+ emissiveIntensity: 0.32,
39325
+ shininess: 90
39326
+ }),
39327
+ baseColor,
39328
+ baseOpacity: 0.7,
39329
+ hoveredOpacity: 0.82,
39330
+ selectedOpacity: 0.9,
39331
+ hoveredColor: theme.accent,
39332
+ selectedColor: theme.selectionHalo,
39333
+ baseEmissive: baseColor,
39334
+ hoveredEmissive: theme.accent,
39335
+ selectedEmissive: theme.selectionHalo,
39336
+ baseEmissiveIntensity: 0.32,
39337
+ hoveredEmissiveIntensity: 0.52,
39338
+ selectedEmissiveIntensity: 0.74
39339
+ };
39340
+ }
39341
+ const shininess = object.object.type === "structure" ? 70 : object.object.type === "ring" ? 42 : object.object.type === "belt" ? 26 : 36;
39177
39342
  return {
39178
- objectId: orbit.objectId,
39179
- root,
39180
- materials,
39181
- baseColor
39343
+ material: new THREE.MeshPhongMaterial({
39344
+ color: baseColor,
39345
+ specular: new THREE.Color(theme.objectSpecular),
39346
+ shininess,
39347
+ transparent: false,
39348
+ opacity: 1,
39349
+ emissive: new THREE.Color(0),
39350
+ emissiveIntensity: 0.02
39351
+ }),
39352
+ baseColor,
39353
+ baseOpacity: 1,
39354
+ hoveredOpacity: 1,
39355
+ selectedOpacity: 1,
39356
+ hoveredColor: shiftColorLightness(THREE, baseColor, 0.08),
39357
+ selectedColor: shiftColorLightness(THREE, baseColor, 0.16),
39358
+ hoveredEmissive: "#8fcaff",
39359
+ selectedEmissive: theme.selectionHalo,
39360
+ baseEmissiveIntensity: 0.02,
39361
+ hoveredEmissiveIntensity: 0.12,
39362
+ selectedEmissiveIntensity: 0.22
39182
39363
  };
39183
39364
  }
39184
39365
  function geometryForObject(THREE, object) {
39185
39366
  const radius = Math.max(object.visualRadius, 2);
39186
39367
  switch (object.object.type) {
39187
39368
  case "star":
39188
- return new THREE.SphereGeometry(radius * 1.12, 28, 20);
39369
+ return new THREE.SphereGeometry(radius * 1.14, 34, 24);
39189
39370
  case "structure":
39190
- return new THREE.BoxGeometry(radius * 1.5, radius * 1.5, radius * 1.5);
39371
+ return geometryForStructure(THREE, object, radius);
39191
39372
  case "phenomenon":
39192
- return new THREE.OctahedronGeometry(radius * 1.25, 0);
39373
+ return new THREE.IcosahedronGeometry(radius * 1.12, 1);
39193
39374
  case "belt":
39375
+ return new THREE.TorusGeometry(Math.max(radius * 1.15, 4), Math.max(radius * 0.28, 1), 10, 24);
39194
39376
  case "ring":
39195
- return new THREE.OctahedronGeometry(Math.max(radius * 0.85, 3), 0);
39377
+ return new THREE.TorusGeometry(Math.max(radius, 4), Math.max(radius * 0.18, 0.8), 10, 30);
39378
+ case "asteroid":
39379
+ return new THREE.DodecahedronGeometry(radius, 0);
39380
+ case "comet":
39381
+ return new THREE.SphereGeometry(radius * 0.94, 18, 14);
39196
39382
  default:
39197
- return new THREE.SphereGeometry(radius, 20, 14);
39383
+ return new THREE.SphereGeometry(radius, 24, 18);
39384
+ }
39385
+ }
39386
+ function geometryForStructure(THREE, object, radius) {
39387
+ const kind = String(object.object.properties.kind ?? "").toLowerCase();
39388
+ if (kind.includes("relay")) {
39389
+ return new THREE.OctahedronGeometry(radius * 1.15, 0);
39390
+ }
39391
+ if (kind.includes("elevator") || kind.includes("skyhook")) {
39392
+ return new THREE.CylinderGeometry(radius * 0.36, radius * 0.52, radius * 2.4, 10);
39393
+ }
39394
+ if (kind.includes("station")) {
39395
+ return new THREE.TorusKnotGeometry(radius * 0.6, Math.max(radius * 0.18, 0.6), 42, 8);
39396
+ }
39397
+ return new THREE.BoxGeometry(radius * 1.45, radius * 1.2, radius * 1.45);
39398
+ }
39399
+ function createHalo(THREE, object, theme) {
39400
+ const radius = Math.max(object.visualRadius, 2);
39401
+ const geometry = object.object.type === "structure" ? new THREE.BoxGeometry(radius * 2.2, radius * 2.2, radius * 2.2) : new THREE.SphereGeometry(radius * 1.38, 18, 14);
39402
+ return new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
39403
+ color: theme.selectionHalo,
39404
+ transparent: true,
39405
+ opacity: 0.18,
39406
+ depthWrite: false,
39407
+ side: 1
39408
+ }));
39409
+ }
39410
+ function shouldRenderAtmosphere(object) {
39411
+ if (object.object.type !== "planet" && object.object.type !== "moon") {
39412
+ return false;
39198
39413
  }
39414
+ return object.object.properties.atmosphere !== void 0;
39199
39415
  }
39200
39416
  function bandGeometryForOrbit(THREE, orbit) {
39201
39417
  const thickness = Math.max(orbit.bandThickness ?? 8, 3);
39202
39418
  const points = sampleOrbitPoints(THREE, orbit, 72);
39203
39419
  const curve = new THREE.CatmullRomCurve3(points, true);
39204
- return new THREE.TubeGeometry(curve, 128, thickness * 0.28, 10, true);
39420
+ return new THREE.TubeGeometry(curve, 144, thickness * 0.18, 10, true);
39205
39421
  }
39206
39422
  function sampleOrbitPoints(THREE, orbit, segments = 96) {
39207
39423
  const points = [];
@@ -39213,6 +39429,94 @@ void main() {
39213
39429
  }
39214
39430
  return points;
39215
39431
  }
39432
+ function createStarfield(THREE, count) {
39433
+ const geometry = new THREE.BufferGeometry();
39434
+ const positions = new Float32Array(count * 3);
39435
+ const colors = new Float32Array(count * 3);
39436
+ for (let index = 0; index < count; index += 1) {
39437
+ const offset = index * 3;
39438
+ const radius = 1800 + Math.random() * 2600;
39439
+ const theta = Math.random() * Math.PI * 2;
39440
+ const phi = Math.acos(2 * Math.random() - 1);
39441
+ positions[offset] = radius * Math.sin(phi) * Math.cos(theta);
39442
+ positions[offset + 1] = radius * Math.cos(phi) * 0.45;
39443
+ positions[offset + 2] = radius * Math.sin(phi) * Math.sin(theta);
39444
+ }
39445
+ geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
39446
+ geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
39447
+ return new THREE.Points(geometry, new THREE.PointsMaterial({
39448
+ size: 5,
39449
+ transparent: true,
39450
+ opacity: 0.84,
39451
+ depthWrite: false,
39452
+ vertexColors: true,
39453
+ sizeAttenuation: true
39454
+ }));
39455
+ }
39456
+ function configureRenderer(renderer, THREE, quality) {
39457
+ const pixelRatioCap = quality === "high" ? 2.4 : quality === "low" ? 1.2 : 1.8;
39458
+ renderer.setPixelRatio?.(Math.min(globalThis.window?.devicePixelRatio ?? 1, pixelRatioCap));
39459
+ if ("outputColorSpace" in renderer && "SRGBColorSpace" in THREE) {
39460
+ renderer.outputColorSpace = THREE.SRGBColorSpace;
39461
+ } else if ("outputEncoding" in renderer && "sRGBEncoding" in THREE) {
39462
+ renderer.outputEncoding = THREE.sRGBEncoding;
39463
+ }
39464
+ if ("ACESFilmicToneMapping" in THREE) {
39465
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
39466
+ }
39467
+ }
39468
+ function updateEnvironment(runtime, renderOptions) {
39469
+ const theme = resolveTheme(renderOptions?.theme);
39470
+ const quality = renderOptions?.quality ?? "balanced";
39471
+ const style3d = renderOptions?.style3d ?? "symbolic";
39472
+ const count = quality === "high" ? 520 : quality === "low" ? 180 : 320;
39473
+ const positions = new Float32Array(count * 3);
39474
+ const colors = new Float32Array(count * 3);
39475
+ const bright = new runtime.THREE.Color(theme.starfield);
39476
+ const dim = new runtime.THREE.Color(theme.starfieldDim);
39477
+ for (let index = 0; index < count; index += 1) {
39478
+ const offset = index * 3;
39479
+ const radius = 1800 + Math.random() * 2600;
39480
+ const theta = Math.random() * Math.PI * 2;
39481
+ const phi = Math.acos(2 * Math.random() - 1);
39482
+ positions[offset] = radius * Math.sin(phi) * Math.cos(theta);
39483
+ positions[offset + 1] = radius * Math.cos(phi) * 0.45;
39484
+ positions[offset + 2] = radius * Math.sin(phi) * Math.sin(theta);
39485
+ const color = Math.random() > 0.72 ? bright : dim;
39486
+ colors[offset] = color.r;
39487
+ colors[offset + 1] = color.g;
39488
+ colors[offset + 2] = color.b;
39489
+ }
39490
+ runtime.scene3d.background = new runtime.THREE.Color(theme.backgroundStart);
39491
+ runtime.scene3d.fog = new runtime.THREE.FogExp2(theme.spaceFog, 85e-5);
39492
+ runtime.ambientLight.intensity = style3d === "cinematic" ? 0.18 : 0.26;
39493
+ runtime.fillLight.intensity = style3d === "cinematic" ? 0.32 : 0.4;
39494
+ runtime.rimLight.intensity = style3d === "cinematic" ? 0.28 : 0.22;
39495
+ runtime.renderer.toneMappingExposure = style3d === "cinematic" ? 1.18 : 1.08;
39496
+ runtime.starfield.geometry.setAttribute("position", new runtime.THREE.BufferAttribute(positions, 3));
39497
+ runtime.starfield.geometry.setAttribute("color", new runtime.THREE.BufferAttribute(colors, 3));
39498
+ runtime.starfield.material.opacity = quality === "high" ? 0.96 : quality === "low" ? 0.72 : 0.84;
39499
+ runtime.starfield.material.size = quality === "high" ? 5.5 : quality === "low" ? 4.25 : 5;
39500
+ }
39501
+ function applyVisualState(THREE, materials, selected, hovered) {
39502
+ for (const entry of materials) {
39503
+ const material = entry.material;
39504
+ if (!material) {
39505
+ continue;
39506
+ }
39507
+ const color = selected ? entry.selectedColor ?? entry.baseColor : hovered ? entry.hoveredColor ?? entry.baseColor : entry.baseColor;
39508
+ material.color?.set?.(new THREE.Color(color));
39509
+ if (typeof material.opacity === "number") {
39510
+ material.opacity = selected ? entry.selectedOpacity : hovered ? entry.hoveredOpacity : entry.baseOpacity;
39511
+ material.transparent = material.opacity < 0.999;
39512
+ }
39513
+ const emissive = selected ? entry.selectedEmissive ?? entry.baseEmissive : hovered ? entry.hoveredEmissive ?? entry.baseEmissive : entry.baseEmissive;
39514
+ material.emissive?.set?.(emissive ? new THREE.Color(emissive) : new THREE.Color(0));
39515
+ if ("emissiveIntensity" in material) {
39516
+ material.emissiveIntensity = selected ? entry.selectedEmissiveIntensity ?? entry.baseEmissiveIntensity ?? 0 : hovered ? entry.hoveredEmissiveIntensity ?? entry.baseEmissiveIntensity ?? 0 : entry.baseEmissiveIntensity ?? 0;
39517
+ }
39518
+ }
39519
+ }
39216
39520
  function colorForObject(object) {
39217
39521
  switch (object.object.type) {
39218
39522
  case "star":
@@ -39235,35 +39539,23 @@ void main() {
39235
39539
  return "#b8f2ff";
39236
39540
  }
39237
39541
  }
39238
- function applyVisualState(THREE, materials, baseColor, selected, hovered) {
39239
- const color = new THREE.Color(baseColor);
39240
- if (selected) {
39241
- color.offsetHSL(0, 0, 0.16);
39242
- } else if (hovered) {
39243
- color.offsetHSL(0, 0, 0.08);
39244
- }
39245
- for (const material of materials) {
39246
- if (!material) {
39247
- continue;
39248
- }
39249
- material.color?.set?.(color);
39250
- if (typeof material.opacity === "number") {
39251
- material.opacity = selected ? 0.85 : hovered ? 0.72 : material.transparent ? 0.55 : 1;
39252
- }
39253
- material.emissive?.set?.(selected ? new THREE.Color("#ffdda9") : hovered ? new THREE.Color("#cfe9ff") : new THREE.Color(0));
39254
- material.emissiveIntensity = selected ? 0.28 : hovered ? 0.14 : material.emissiveIntensity ?? 0.08;
39255
- }
39542
+ function shiftColorLightness(THREE, colorValue, delta) {
39543
+ const color = new THREE.Color(colorValue);
39544
+ color.offsetHSL(0, 0, delta);
39545
+ return `#${color.getHexString()}`;
39256
39546
  }
39257
39547
  function clearGroup(group) {
39258
39548
  while (group.children.length > 0) {
39259
39549
  const child = group.children[0];
39260
39550
  group.remove(child);
39261
- child.geometry?.dispose?.();
39262
- if (Array.isArray(child.material)) {
39263
- child.material.forEach((entry) => entry?.dispose?.());
39264
- } else {
39265
- child.material?.dispose?.();
39266
- }
39551
+ child.traverse?.((node) => {
39552
+ node.geometry?.dispose?.();
39553
+ if (Array.isArray(node.material)) {
39554
+ node.material.forEach((entry) => entry?.dispose?.());
39555
+ } else {
39556
+ node.material?.dispose?.();
39557
+ }
39558
+ });
39267
39559
  }
39268
39560
  }
39269
39561
  function ensureWebGLSupport() {
@@ -39527,6 +39819,8 @@ void main() {
39527
39819
  preset: options.preset,
39528
39820
  projection: options.projection,
39529
39821
  viewMode: options.viewMode ?? "2d",
39822
+ quality: options.quality ?? "balanced",
39823
+ style3d: options.style3d ?? "symbolic",
39530
39824
  camera: options.camera ? { ...options.camera } : null,
39531
39825
  scaleModel: options.scaleModel ? { ...options.scaleModel } : void 0,
39532
39826
  theme: options.theme,
@@ -39546,6 +39840,7 @@ void main() {
39546
39840
  let cameraRoot = null;
39547
39841
  let runtime3d = null;
39548
39842
  let minimapRoot = null;
39843
+ let labelRoot = null;
39549
39844
  let tooltipRoot = null;
39550
39845
  let suppressClick = false;
39551
39846
  let activePointerId = null;
@@ -39570,7 +39865,7 @@ void main() {
39570
39865
  if (previousTabIndex === null) {
39571
39866
  container.tabIndex = 0;
39572
39867
  }
39573
- installViewerTooltipStyles();
39868
+ installViewerOverlayStyles();
39574
39869
  container.classList.add("wo-viewer-container");
39575
39870
  container.style.touchAction = behavior.touch ? "none" : previousTouchAction;
39576
39871
  if (!container.style.position) {
@@ -40120,6 +40415,8 @@ void main() {
40120
40415
  stopAnimationLoop();
40121
40416
  runtime3d?.destroy();
40122
40417
  runtime3d = null;
40418
+ labelRoot?.remove();
40419
+ labelRoot = null;
40123
40420
  tooltipRoot?.remove();
40124
40421
  tooltipRoot = null;
40125
40422
  minimapRoot?.remove();
@@ -40150,6 +40447,7 @@ void main() {
40150
40447
  svgElement = null;
40151
40448
  cameraRoot = null;
40152
40449
  minimapRoot = null;
40450
+ labelRoot = null;
40153
40451
  tooltipRoot = null;
40154
40452
  if (is3DView()) {
40155
40453
  spatialScene = spatialScene ?? renderSpatialSceneFromInput(currentInput, renderOptions, providedSpatialScene);
@@ -40168,6 +40466,10 @@ void main() {
40168
40466
  minimapRoot.dataset.worldorbitMinimapRoot = "true";
40169
40467
  container.append(minimapRoot);
40170
40468
  }
40469
+ labelRoot = document.createElement("div");
40470
+ labelRoot.className = "wo-viewer-label-root";
40471
+ labelRoot.dataset.worldorbitLabelRoot = "true";
40472
+ container.append(labelRoot);
40171
40473
  if (behavior.tooltipMode !== "disabled") {
40172
40474
  tooltipRoot = document.createElement("div");
40173
40475
  tooltipRoot.className = "wo-viewer-tooltip-root";
@@ -40179,6 +40481,7 @@ void main() {
40179
40481
  if (!is3DView() && (!svgElement || !cameraRoot)) {
40180
40482
  throw new Error("Interactive viewer could not locate the rendered SVG camera root.");
40181
40483
  }
40484
+ suppressStaticLabelLayers();
40182
40485
  state = resetView ? is3DView() ? { ...DEFAULT_VIEWER_STATE } : fitViewerState(scene, { ...DEFAULT_VIEWER_STATE }, constraints) : sanitizeState(state);
40183
40486
  applySelection(state.selectedObjectId && getObjectById(state.selectedObjectId) ? state.selectedObjectId : null, false);
40184
40487
  applyHover(hoveredObjectId && getObjectById(hoveredObjectId) ? hoveredObjectId : null, false);
@@ -40213,19 +40516,24 @@ void main() {
40213
40516
  return;
40214
40517
  }
40215
40518
  cameraRoot.setAttribute("transform", composeViewerTransform(scene, state));
40519
+ updateScreenLabels();
40216
40520
  updateMinimap();
40217
40521
  updateTooltip();
40218
40522
  }
40219
40523
  function applySelection(objectId, emitCallback = true) {
40220
40524
  if (!is3DView() && state.selectedObjectId) {
40221
- container.querySelector(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`)?.classList.remove("wo-object-selected");
40525
+ for (const element of container.querySelectorAll(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`)) {
40526
+ element.classList.remove("wo-object-selected");
40527
+ }
40222
40528
  }
40223
40529
  state = {
40224
40530
  ...state,
40225
40531
  selectedObjectId: objectId && getObjectById(objectId) ? objectId : null
40226
40532
  };
40227
40533
  if (!is3DView() && state.selectedObjectId) {
40228
- container.querySelector(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`)?.classList.add("wo-object-selected");
40534
+ for (const element of container.querySelectorAll(`[data-object-id="${cssEscape(state.selectedObjectId)}"]`)) {
40535
+ element.classList.add("wo-object-selected");
40536
+ }
40229
40537
  }
40230
40538
  syncAtlasHighlights();
40231
40539
  updateTooltip();
@@ -40558,14 +40866,17 @@ void main() {
40558
40866
  };
40559
40867
  }
40560
40868
  function project2DTooltipPoint(renderObject) {
40561
- if (!svgElement) {
40562
- return null;
40563
- }
40564
40869
  const anchor = {
40565
40870
  x: renderObject.anchorX ?? renderObject.x,
40566
40871
  y: renderObject.anchorY ?? renderObject.y - Math.max(renderObject.visualRadius, renderObject.radius)
40567
40872
  };
40568
- const viewportPoint = projectWorldPoint(anchor);
40873
+ return project2DScenePointToContainer(anchor);
40874
+ }
40875
+ function project2DScenePointToContainer(point) {
40876
+ if (!svgElement) {
40877
+ return null;
40878
+ }
40879
+ const viewportPoint = projectWorldPoint(point);
40569
40880
  const svgRect = svgElement.getBoundingClientRect();
40570
40881
  const containerRect = container.getBoundingClientRect();
40571
40882
  return {
@@ -40664,9 +40975,182 @@ void main() {
40664
40975
  state,
40665
40976
  timeSeconds: animationState.timeSeconds
40666
40977
  });
40978
+ updateScreenLabels();
40667
40979
  updateMinimap();
40668
40980
  updateTooltip();
40669
40981
  }
40982
+ function suppressStaticLabelLayers() {
40983
+ if (is3DView()) {
40984
+ return;
40985
+ }
40986
+ container.querySelector('[data-layer-id="labels"]')?.setAttribute("display", "none");
40987
+ for (const element of container.querySelectorAll(".wo-event-label")) {
40988
+ element.setAttribute("display", "none");
40989
+ }
40990
+ }
40991
+ function updateScreenLabels() {
40992
+ if (!labelRoot) {
40993
+ return;
40994
+ }
40995
+ const descriptors = buildScreenLabelDescriptors();
40996
+ labelRoot.replaceChildren(...descriptors.map((descriptor) => createScreenLabelElement(descriptor)));
40997
+ labelRoot.hidden = descriptors.length === 0;
40998
+ }
40999
+ function buildScreenLabelDescriptors() {
41000
+ const descriptors = [];
41001
+ const visibleObjectIds = getVisibleObjectIds();
41002
+ if (layerEnabled("labels")) {
41003
+ for (const label of scene.labels) {
41004
+ if (label.hidden || !visibleObjectIds.has(label.objectId)) {
41005
+ continue;
41006
+ }
41007
+ if (is3DView() && !shouldRender3DLabel(label.objectId, visibleObjectIds)) {
41008
+ continue;
41009
+ }
41010
+ const point = is3DView() ? runtime3d?.projectObjectToContainer(label.objectId) ?? null : project2DScenePointToContainer({ x: label.x, y: label.y });
41011
+ if (!point) {
41012
+ continue;
41013
+ }
41014
+ descriptors.push({
41015
+ key: `object:${label.renderId}`,
41016
+ kind: "object",
41017
+ point: is3DView() ? { x: point.x, y: point.y - 18 } : point,
41018
+ textAnchor: label.textAnchor,
41019
+ objectId: label.objectId,
41020
+ primaryText: label.label,
41021
+ secondaryText: label.secondaryLabel,
41022
+ secondaryOffset: Math.max(label.secondaryY - label.y, 12)
41023
+ });
41024
+ }
41025
+ }
41026
+ if (!is3DView() && layerEnabled("events")) {
41027
+ for (const event of scene.events) {
41028
+ if (event.hidden || !isEventVisible(event, visibleObjectIds)) {
41029
+ continue;
41030
+ }
41031
+ const point = project2DScenePointToContainer({ x: event.x, y: event.y - 10 });
41032
+ if (!point) {
41033
+ continue;
41034
+ }
41035
+ descriptors.push({
41036
+ key: `event:${event.renderId}`,
41037
+ kind: "event",
41038
+ point,
41039
+ textAnchor: "middle",
41040
+ primaryText: event.event.label || event.event.id
41041
+ });
41042
+ }
41043
+ }
41044
+ return descriptors;
41045
+ }
41046
+ function isEventVisible(event, visibleObjectIds) {
41047
+ return event.objectIds.some((objectId) => visibleObjectIds.has(objectId));
41048
+ }
41049
+ function shouldRender3DLabel(objectId, visibleObjectIds) {
41050
+ if (!is3DView()) {
41051
+ return true;
41052
+ }
41053
+ if (objectId === state.selectedObjectId || objectId === hoveredObjectId) {
41054
+ return true;
41055
+ }
41056
+ const object = getObjectById(objectId);
41057
+ if (!object || object.hidden || !visibleObjectIds.has(objectId)) {
41058
+ return false;
41059
+ }
41060
+ if (object.object.type === "star") {
41061
+ return true;
41062
+ }
41063
+ const selected = state.selectedObjectId ? buildObjectDetails(state.selectedObjectId) : null;
41064
+ const hovered = hoveredObjectId ? buildObjectDetails(hoveredObjectId) : null;
41065
+ const selectedFocus = selected ? /* @__PURE__ */ new Set([
41066
+ selected.objectId,
41067
+ ...selected.renderObject.ancestorIds,
41068
+ ...selected.renderObject.childIds
41069
+ ]) : null;
41070
+ const hoveredFocus = hovered ? /* @__PURE__ */ new Set([
41071
+ hovered.objectId,
41072
+ ...hovered.renderObject.ancestorIds,
41073
+ ...hovered.renderObject.childIds
41074
+ ]) : null;
41075
+ if (selectedFocus?.has(objectId) || hoveredFocus?.has(objectId)) {
41076
+ return true;
41077
+ }
41078
+ if (object.semanticGroupIds.length > 0 && object.visualRadius >= 12) {
41079
+ return true;
41080
+ }
41081
+ return object.childIds.length > 0 && object.visualRadius >= 10;
41082
+ }
41083
+ function createScreenLabelElement(descriptor) {
41084
+ const element = document.createElement("div");
41085
+ element.className = `wo-viewer-label wo-viewer-label-${descriptor.kind}`;
41086
+ element.dataset.worldorbitScreenLabel = "true";
41087
+ element.dataset.labelKey = descriptor.key;
41088
+ element.dataset.anchor = descriptor.textAnchor;
41089
+ element.style.left = `${descriptor.point.x}px`;
41090
+ element.style.top = `${descriptor.point.y}px`;
41091
+ if (descriptor.objectId) {
41092
+ element.dataset.objectId = descriptor.objectId;
41093
+ for (const className of resolveScreenLabelClasses(descriptor.objectId)) {
41094
+ element.classList.add(className);
41095
+ }
41096
+ }
41097
+ const primary = document.createElement("span");
41098
+ primary.className = "wo-viewer-label-primary";
41099
+ if (descriptor.kind === "object") {
41100
+ primary.style.fontSize = `${14 * scene.scaleModel.labelMultiplier}px`;
41101
+ }
41102
+ primary.textContent = descriptor.primaryText;
41103
+ element.append(primary);
41104
+ if (descriptor.secondaryText) {
41105
+ const secondary = document.createElement("span");
41106
+ secondary.className = "wo-viewer-label-secondary";
41107
+ secondary.style.fontSize = `${11 * scene.scaleModel.labelMultiplier}px`;
41108
+ secondary.style.marginTop = `${Math.max(descriptor.secondaryOffset ?? 12, 10) - 10}px`;
41109
+ secondary.textContent = descriptor.secondaryText;
41110
+ element.append(secondary);
41111
+ }
41112
+ return element;
41113
+ }
41114
+ function layerEnabled(id) {
41115
+ return renderOptions.layers?.[id] !== false;
41116
+ }
41117
+ function resolveScreenLabelClasses(objectId) {
41118
+ const classes = [];
41119
+ const selectedDetails = buildObjectDetails(state.selectedObjectId);
41120
+ const hoveredDetails = buildObjectDetails(hoveredObjectId);
41121
+ if (state.selectedObjectId === objectId) {
41122
+ classes.push("wo-object-selected");
41123
+ }
41124
+ if (selectedDetails) {
41125
+ const selectedChain = /* @__PURE__ */ new Set([
41126
+ selectedDetails.objectId,
41127
+ ...selectedDetails.renderObject.childIds,
41128
+ ...selectedDetails.renderObject.ancestorIds
41129
+ ]);
41130
+ const selectedAncestors = new Set(selectedDetails.ancestors.map((ancestor) => ancestor.objectId));
41131
+ if (selectedChain.has(objectId)) {
41132
+ classes.push("wo-chain-selected");
41133
+ }
41134
+ if (selectedAncestors.has(objectId)) {
41135
+ classes.push("wo-ancestor-selected");
41136
+ }
41137
+ }
41138
+ if (hoveredDetails) {
41139
+ const hoveredChain = /* @__PURE__ */ new Set([
41140
+ hoveredDetails.objectId,
41141
+ ...hoveredDetails.renderObject.childIds,
41142
+ ...hoveredDetails.renderObject.ancestorIds
41143
+ ]);
41144
+ const hoveredAncestors = new Set(hoveredDetails.ancestors.map((ancestor) => ancestor.objectId));
41145
+ if (hoveredChain.has(objectId)) {
41146
+ classes.push("wo-chain-hover");
41147
+ }
41148
+ if (hoveredAncestors.has(objectId)) {
41149
+ classes.push("wo-ancestor-hover");
41150
+ }
41151
+ }
41152
+ return classes;
41153
+ }
40670
41154
  function create3DFocusState(objectId) {
40671
41155
  const target = spatialScene?.focusTargets.find((entry) => entry.objectId === objectId);
40672
41156
  if (!target) {
@@ -40732,7 +41216,9 @@ void main() {
40732
41216
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
40733
41217
  theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme,
40734
41218
  activeEventId: renderOptions.activeEventId ?? null,
40735
- viewMode: renderOptions.viewMode ?? "2d"
41219
+ viewMode: renderOptions.viewMode ?? "2d",
41220
+ quality: renderOptions.quality ?? "balanced",
41221
+ style3d: renderOptions.style3d ?? "symbolic"
40736
41222
  };
40737
41223
  }
40738
41224
  function mergeRenderOptions(current, next) {
@@ -40750,7 +41236,9 @@ void main() {
40750
41236
  ...next.layers
40751
41237
  } : current.layers ? { ...current.layers } : void 0,
40752
41238
  theme: next.theme && typeof next.theme === "object" ? { ...next.theme } : next.theme ?? current.theme,
40753
- viewMode: next.viewMode ?? current.viewMode ?? "2d"
41239
+ viewMode: next.viewMode ?? current.viewMode ?? "2d",
41240
+ quality: next.quality ?? current.quality ?? "balanced",
41241
+ style3d: next.style3d ?? current.style3d ?? "symbolic"
40754
41242
  };
40755
41243
  }
40756
41244
  function hasSceneAffectingRenderOptions(options) {
@@ -40918,7 +41406,7 @@ void main() {
40918
41406
  }
40919
41407
  return value.replace(/["\\]/g, "\\$&");
40920
41408
  }
40921
- function installViewerTooltipStyles() {
41409
+ function installViewerOverlayStyles() {
40922
41410
  if (typeof document === "undefined" || document.getElementById(TOOLTIP_STYLE_ID)) {
40923
41411
  return;
40924
41412
  }
@@ -40953,6 +41441,60 @@ void main() {
40953
41441
  height: 100%;
40954
41442
  min-height: 320px;
40955
41443
  }
41444
+ .wo-viewer-label-root {
41445
+ position: absolute;
41446
+ inset: 0;
41447
+ z-index: 8;
41448
+ pointer-events: none;
41449
+ overflow: hidden;
41450
+ }
41451
+ .wo-viewer-label {
41452
+ position: absolute;
41453
+ display: grid;
41454
+ gap: 2px;
41455
+ padding: 4px 8px;
41456
+ border-radius: 999px;
41457
+ background: linear-gradient(180deg, rgba(5, 16, 26, 0.72), rgba(5, 16, 26, 0.38));
41458
+ border: 1px solid rgba(164, 194, 228, 0.16);
41459
+ color: #edf6ff;
41460
+ font-family: "Segoe UI Variable", "Segoe UI", sans-serif;
41461
+ line-height: 1.15;
41462
+ text-shadow: 0 1px 2px rgba(7, 16, 25, 0.65), 0 0 18px rgba(7, 16, 25, 0.18);
41463
+ white-space: nowrap;
41464
+ }
41465
+ .wo-viewer-label[data-anchor="middle"] { transform: translate(-50%, 0); }
41466
+ .wo-viewer-label[data-anchor="end"] { transform: translate(-100%, 0); }
41467
+ .wo-viewer-label-primary {
41468
+ font-size: 14px;
41469
+ font-weight: 600;
41470
+ letter-spacing: 0.02em;
41471
+ }
41472
+ .wo-viewer-label-secondary {
41473
+ font-size: 11px;
41474
+ font-weight: 500;
41475
+ color: rgba(237, 246, 255, 0.72);
41476
+ }
41477
+ .wo-viewer-label-event {
41478
+ color: #ffce8a;
41479
+ text-transform: uppercase;
41480
+ letter-spacing: 0.04em;
41481
+ }
41482
+ .wo-viewer-label-event .wo-viewer-label-primary {
41483
+ font-size: 10px;
41484
+ font-weight: 700;
41485
+ }
41486
+ .wo-viewer-label.wo-object-selected .wo-viewer-label-primary,
41487
+ .wo-viewer-label.wo-chain-selected .wo-viewer-label-primary,
41488
+ .wo-viewer-label.wo-chain-hover .wo-viewer-label-primary {
41489
+ color: #ffce8a;
41490
+ }
41491
+ .wo-viewer-label.wo-object-selected .wo-viewer-label-secondary {
41492
+ color: #8fcaff;
41493
+ }
41494
+ .wo-viewer-label.wo-ancestor-selected .wo-viewer-label-primary,
41495
+ .wo-viewer-label.wo-ancestor-hover .wo-viewer-label-primary {
41496
+ opacity: 0.82;
41497
+ }
40956
41498
  .wo-viewer-tooltip-root {
40957
41499
  position: absolute;
40958
41500
  z-index: 12;
@@ -41087,6 +41629,7 @@ void main() {
41087
41629
  }
41088
41630
  function mountWorldOrbitEmbeds(root = document, options = {}) {
41089
41631
  const viewers = /* @__PURE__ */ new Map();
41632
+ const cleanupCallbacks = [];
41090
41633
  const elements = [...root.querySelectorAll(EMBED_SELECTOR)];
41091
41634
  for (const element of elements) {
41092
41635
  const payload = deserializePayloadFromElement(element);
@@ -41100,14 +41643,16 @@ void main() {
41100
41643
  const initialSelectionObjectId = options.viewer?.initialSelectionObjectId ?? payload.options?.initialSelectionObjectId;
41101
41644
  const minimap = options.viewer?.minimap ?? payload.options?.minimap;
41102
41645
  const viewMode = options.viewer?.viewMode ?? payload.options?.viewMode ?? embedModeToViewMode(mode);
41646
+ const measureViewport = () => resolveEmbedViewport(element, payload.scene, options);
41103
41647
  if (mode === "interactive-2d" || mode === "interactive-3d") {
41104
41648
  try {
41649
+ const viewport = measureViewport();
41105
41650
  const viewer = createInteractiveViewer(element, {
41106
41651
  ...options.viewer,
41107
41652
  scene: payload.scene,
41108
41653
  spatialScene: payload.spatialScene,
41109
- width: options.width ?? payload.scene.width,
41110
- height: options.height ?? payload.scene.height,
41654
+ width: viewport.width,
41655
+ height: viewport.height,
41111
41656
  padding: options.padding ?? payload.scene.padding,
41112
41657
  preset,
41113
41658
  theme,
@@ -41123,6 +41668,13 @@ void main() {
41123
41668
  viewer.setAtlasState(payload.options.atlasState);
41124
41669
  }
41125
41670
  viewers.set(element, viewer);
41671
+ cleanupCallbacks.push(bindEmbedResize(element, () => {
41672
+ const nextViewport = measureViewport();
41673
+ viewer.setRenderOptions({
41674
+ width: nextViewport.width,
41675
+ height: nextViewport.height
41676
+ });
41677
+ }));
41126
41678
  options.onMount?.(viewer, element);
41127
41679
  } catch (error2) {
41128
41680
  if (error2 instanceof WorldOrbit3DUnavailableError && mode === "interactive-3d") {
@@ -41133,17 +41685,22 @@ void main() {
41133
41685
  }
41134
41686
  }
41135
41687
  } else {
41136
- element.innerHTML = renderSceneToSvg(payload.scene, {
41137
- width: options.width ?? payload.scene.width,
41138
- height: options.height ?? payload.scene.height,
41139
- padding: options.padding ?? payload.scene.padding,
41140
- preset,
41141
- theme,
41142
- layers,
41143
- filter: initialFilter,
41144
- selectedObjectId: initialSelectionObjectId ?? null,
41145
- subtitle
41146
- });
41688
+ const renderStaticEmbed = () => {
41689
+ const viewport = measureViewport();
41690
+ element.innerHTML = renderSceneToSvg(payload.scene, {
41691
+ width: viewport.width,
41692
+ height: viewport.height,
41693
+ padding: options.padding ?? payload.scene.padding,
41694
+ preset,
41695
+ theme,
41696
+ layers,
41697
+ filter: initialFilter,
41698
+ selectedObjectId: initialSelectionObjectId ?? null,
41699
+ subtitle
41700
+ });
41701
+ };
41702
+ renderStaticEmbed();
41703
+ cleanupCallbacks.push(bindEmbedResize(element, renderStaticEmbed));
41147
41704
  options.onMount?.(null, element);
41148
41705
  }
41149
41706
  element.dataset.worldorbitMounted = "true";
@@ -41151,14 +41708,65 @@ void main() {
41151
41708
  return {
41152
41709
  viewers: [...viewers.values()],
41153
41710
  destroy() {
41711
+ for (const cleanup of cleanupCallbacks) {
41712
+ cleanup();
41713
+ }
41154
41714
  for (const [element, viewer] of viewers.entries()) {
41155
41715
  viewer.destroy();
41156
41716
  element.removeAttribute("data-worldorbit-mounted");
41157
41717
  }
41718
+ for (const element of elements) {
41719
+ element.removeAttribute("data-worldorbit-mounted");
41720
+ }
41158
41721
  viewers.clear();
41159
41722
  }
41160
41723
  };
41161
41724
  }
41725
+ function resolveEmbedViewport(element, scene, options) {
41726
+ const rect = element.getBoundingClientRect();
41727
+ const width = sanitizeViewportDimension(options.width) ?? sanitizeViewportDimension(element.clientWidth) ?? sanitizeViewportDimension(rect.width) ?? scene.width;
41728
+ const explicitHeight = sanitizeViewportDimension(options.height) ?? sanitizeViewportDimension(element.clientHeight) ?? sanitizeViewportDimension(rect.height);
41729
+ const fallbackHeight = Math.max(Math.round(width * (scene.height / Math.max(scene.width, 1))), Math.min(scene.height, 240));
41730
+ return {
41731
+ width,
41732
+ height: explicitHeight ?? fallbackHeight
41733
+ };
41734
+ }
41735
+ function sanitizeViewportDimension(value) {
41736
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.round(value) : null;
41737
+ }
41738
+ function bindEmbedResize(element, callback) {
41739
+ let lastWidth = -1;
41740
+ let lastHeight = -1;
41741
+ const run = () => {
41742
+ const rect = element.getBoundingClientRect();
41743
+ const nextWidth = Math.round(Math.max(element.clientWidth || rect.width, 0));
41744
+ const nextHeight = Math.round(Math.max(element.clientHeight || rect.height, 0));
41745
+ if (nextWidth === lastWidth && nextHeight === lastHeight) {
41746
+ return;
41747
+ }
41748
+ lastWidth = nextWidth;
41749
+ lastHeight = nextHeight;
41750
+ callback();
41751
+ };
41752
+ run();
41753
+ if (typeof ResizeObserver !== "undefined") {
41754
+ const observer = new ResizeObserver(() => {
41755
+ run();
41756
+ });
41757
+ observer.observe(element);
41758
+ return () => {
41759
+ observer.disconnect();
41760
+ };
41761
+ }
41762
+ const handleWindowResize = () => {
41763
+ run();
41764
+ };
41765
+ window.addEventListener("resize", handleWindowResize);
41766
+ return () => {
41767
+ window.removeEventListener("resize", handleWindowResize);
41768
+ };
41769
+ }
41162
41770
  function deserializePayloadFromElement(element) {
41163
41771
  const serialized = element.dataset.worldorbitPayload;
41164
41772
  if (!serialized) {