worldorbit 3.0.6 → 3.1.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.
Files changed (53) hide show
  1. package/README.md +2 -2
  2. package/dist/browser/core/dist/atlas-edit.js +5 -1
  3. package/dist/browser/core/dist/atlas-validate.js +1 -1
  4. package/dist/browser/core/dist/draft-parse.js +14 -9
  5. package/dist/browser/core/dist/draft.d.ts +1 -0
  6. package/dist/browser/core/dist/draft.js +4 -2
  7. package/dist/browser/core/dist/format.js +5 -3
  8. package/dist/browser/core/dist/load.js +3 -2
  9. package/dist/browser/core/dist/normalize.js +36 -0
  10. package/dist/browser/core/dist/parse.js +54 -0
  11. package/dist/browser/core/dist/scene.js +1 -0
  12. package/dist/browser/core/dist/types.d.ts +21 -1
  13. package/dist/browser/viewer/dist/runtime-3d.js +396 -117
  14. package/dist/browser/viewer/dist/theme.js +27 -0
  15. package/dist/browser/viewer/dist/types.d.ts +17 -0
  16. package/dist/browser/viewer/dist/viewer.js +51 -0
  17. package/dist/unpkg/core/dist/atlas-edit.js +5 -1
  18. package/dist/unpkg/core/dist/atlas-validate.js +1 -1
  19. package/dist/unpkg/core/dist/draft-parse.js +14 -9
  20. package/dist/unpkg/core/dist/draft.d.ts +1 -0
  21. package/dist/unpkg/core/dist/draft.js +4 -2
  22. package/dist/unpkg/core/dist/format.js +5 -3
  23. package/dist/unpkg/core/dist/load.js +3 -2
  24. package/dist/unpkg/core/dist/normalize.js +36 -0
  25. package/dist/unpkg/core/dist/parse.js +54 -0
  26. package/dist/unpkg/core/dist/scene.js +1 -0
  27. package/dist/unpkg/core/dist/types.d.ts +21 -1
  28. package/dist/unpkg/viewer/dist/runtime-3d.js +396 -117
  29. package/dist/unpkg/viewer/dist/theme.js +27 -0
  30. package/dist/unpkg/viewer/dist/types.d.ts +17 -0
  31. package/dist/unpkg/viewer/dist/viewer.js +51 -0
  32. package/dist/unpkg/worldorbit-core.min.js +9 -9
  33. package/dist/unpkg/worldorbit-editor.min.js +360 -356
  34. package/dist/unpkg/worldorbit-markdown.min.js +20 -20
  35. package/dist/unpkg/worldorbit-viewer.min.js +210 -206
  36. package/dist/unpkg/worldorbit.js +557 -120
  37. package/dist/unpkg/worldorbit.min.js +216 -212
  38. package/package.json +1 -1
  39. package/packages/core/dist/atlas-edit.js +5 -1
  40. package/packages/core/dist/atlas-validate.js +1 -1
  41. package/packages/core/dist/draft-parse.js +14 -9
  42. package/packages/core/dist/draft.d.ts +1 -0
  43. package/packages/core/dist/draft.js +4 -2
  44. package/packages/core/dist/format.js +5 -3
  45. package/packages/core/dist/load.js +3 -2
  46. package/packages/core/dist/normalize.js +36 -0
  47. package/packages/core/dist/parse.js +54 -0
  48. package/packages/core/dist/scene.js +1 -0
  49. package/packages/core/dist/types.d.ts +21 -1
  50. package/packages/viewer/dist/runtime-3d.js +396 -117
  51. package/packages/viewer/dist/theme.js +27 -0
  52. package/packages/viewer/dist/types.d.ts +17 -0
  53. package/packages/viewer/dist/viewer.js +51 -0
@@ -31171,9 +31171,14 @@ void main() {
31171
31171
  function parseWorldOrbit(source) {
31172
31172
  const lines = source.split(/\r?\n/);
31173
31173
  const objects = [];
31174
+ let themeNode = null;
31174
31175
  let currentObject = null;
31175
31176
  let inInfoBlock = false;
31177
+ let inThemeBlock = false;
31176
31178
  let infoIndent = null;
31179
+ let themeIndent = null;
31180
+ let themeBlockIndent = null;
31181
+ let currentThemeBlock = null;
31177
31182
  for (let index = 0; index < lines.length; index++) {
31178
31183
  const rawLine = lines[index];
31179
31184
  const lineNumber = index + 1;
@@ -31190,12 +31195,48 @@ void main() {
31190
31195
  }
31191
31196
  if (indent === 0) {
31192
31197
  inInfoBlock = false;
31198
+ inThemeBlock = false;
31193
31199
  infoIndent = null;
31200
+ themeIndent = null;
31201
+ themeBlockIndent = null;
31202
+ currentThemeBlock = null;
31203
+ if (tokens.length >= 1 && tokens[0].value === "theme") {
31204
+ inThemeBlock = true;
31205
+ themeIndent = 0;
31206
+ themeNode = {
31207
+ type: "theme",
31208
+ preset: tokens.length >= 2 ? tokens[1].value : null,
31209
+ blocks: [],
31210
+ location: { line: lineNumber, column: tokens[0].column }
31211
+ };
31212
+ continue;
31213
+ }
31194
31214
  const objectNode = parseObjectHeader(tokens, lineNumber);
31195
31215
  objects.push(objectNode);
31196
31216
  currentObject = objectNode;
31197
31217
  continue;
31198
31218
  }
31219
+ if (inThemeBlock) {
31220
+ if (tokens.length >= 2 && tokens[0].value === "preset" && (!themeBlockIndent || indent <= themeBlockIndent)) {
31221
+ if (themeNode) {
31222
+ themeNode.preset = tokens[1].value;
31223
+ }
31224
+ continue;
31225
+ }
31226
+ if (currentThemeBlock && themeBlockIndent !== null && indent > themeBlockIndent) {
31227
+ currentThemeBlock.fields.push(parseThemeField(tokens, lineNumber));
31228
+ } else {
31229
+ themeBlockIndent = indent;
31230
+ currentThemeBlock = {
31231
+ type: "theme-block",
31232
+ target: tokens[0].value,
31233
+ fields: [],
31234
+ location: { line: lineNumber, column: tokens[0].column }
31235
+ };
31236
+ themeNode?.blocks.push(currentThemeBlock);
31237
+ }
31238
+ continue;
31239
+ }
31199
31240
  if (!currentObject) {
31200
31241
  throw new WorldOrbitError("Indented line without parent object", lineNumber, indent + 1);
31201
31242
  }
@@ -31215,6 +31256,7 @@ void main() {
31215
31256
  }
31216
31257
  return {
31217
31258
  type: "document",
31259
+ theme: themeNode,
31218
31260
  objects
31219
31261
  };
31220
31262
  }
@@ -31285,6 +31327,17 @@ void main() {
31285
31327
  location: { line, column: tokens[0].column }
31286
31328
  };
31287
31329
  }
31330
+ function parseThemeField(tokens, line) {
31331
+ if (tokens.length < 2) {
31332
+ throw new WorldOrbitError("Invalid theme field line", line, tokens[0]?.column ?? 1);
31333
+ }
31334
+ return {
31335
+ type: "field",
31336
+ key: tokens[0].value,
31337
+ values: tokens.slice(1).map((token) => token.value),
31338
+ location: { line, column: tokens[0].column }
31339
+ };
31340
+ }
31288
31341
  function parseInfoEntry(tokens, line) {
31289
31342
  if (tokens.length < 2) {
31290
31343
  throw new WorldOrbitError("Invalid info entry", line, tokens[0]?.column ?? 1);
@@ -31309,6 +31362,7 @@ void main() {
31309
31362
  function normalizeDocument(ast) {
31310
31363
  let system = null;
31311
31364
  const objects = [];
31365
+ const theme = ast.theme ? normalizeTheme(ast.theme) : null;
31312
31366
  for (const node of ast.objects) {
31313
31367
  const normalized = normalizeObject(node);
31314
31368
  if (node.objectType === "system") {
@@ -31324,6 +31378,7 @@ void main() {
31324
31378
  format: "worldorbit",
31325
31379
  version: "1.0",
31326
31380
  schemaVersion: "1.0",
31381
+ theme,
31327
31382
  system,
31328
31383
  groups: [],
31329
31384
  relations: [],
@@ -31331,6 +31386,40 @@ void main() {
31331
31386
  objects
31332
31387
  };
31333
31388
  }
31389
+ function normalizeTheme(node) {
31390
+ const styles = {};
31391
+ for (const block of node.blocks) {
31392
+ const fieldMap = collectFields(block.fields);
31393
+ styles[block.target] = normalizeThemeProperties(fieldMap);
31394
+ }
31395
+ return {
31396
+ preset: node.preset,
31397
+ styles
31398
+ };
31399
+ }
31400
+ function normalizeThemeProperties(fieldMap) {
31401
+ const result = {};
31402
+ for (const [key, field] of fieldMap.entries()) {
31403
+ if (field.values.length === 1) {
31404
+ const rawValue = field.values[0];
31405
+ if (rawValue === "true") {
31406
+ result[key] = true;
31407
+ continue;
31408
+ }
31409
+ if (rawValue === "false") {
31410
+ result[key] = false;
31411
+ continue;
31412
+ }
31413
+ const num = Number(rawValue);
31414
+ if (!Number.isNaN(num) && rawValue.trim() !== "") {
31415
+ result[key] = num;
31416
+ continue;
31417
+ }
31418
+ }
31419
+ result[key] = field.values.join(" ");
31420
+ }
31421
+ return result;
31422
+ }
31334
31423
  function normalizeObject(node) {
31335
31424
  const mergedFields = [...node.inlineFields, ...node.blockFields];
31336
31425
  validateFieldCompatibility(node.objectType, mergedFields);
@@ -32751,7 +32840,7 @@ void main() {
32751
32840
  }
32752
32841
  function parseViewpointGroups(value, document2, relationships, objectMap) {
32753
32842
  return splitListValue(value).map((entry) => {
32754
- if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
32843
+ if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.schemaVersion === "2.6.1" || document2.groups.some((group) => group.id === entry)) {
32755
32844
  return entry;
32756
32845
  }
32757
32846
  if (entry.startsWith("wo-") && entry.endsWith("-group")) {
@@ -33941,9 +34030,10 @@ void main() {
33941
34030
  }
33942
34031
  return {
33943
34032
  format: "worldorbit",
33944
- version: "2.5",
33945
- schemaVersion: "2.5",
34033
+ version: "2.6.1",
34034
+ schemaVersion: "2.6.1",
33946
34035
  sourceVersion: document2.version,
34036
+ theme: document2.theme ?? null,
33947
34037
  system,
33948
34038
  groups: structuredClone(document2.groups ?? []),
33949
34039
  relations: structuredClone(document2.relations ?? []),
@@ -33972,6 +34062,7 @@ void main() {
33972
34062
  format: "worldorbit",
33973
34063
  version: "1.0",
33974
34064
  schemaVersion: document2.version,
34065
+ theme: document2.theme ?? null,
33975
34066
  system,
33976
34067
  groups: structuredClone(document2.groups ?? []),
33977
34068
  relations: structuredClone(document2.relations ?? []),
@@ -34397,22 +34488,22 @@ void main() {
34397
34488
  ];
34398
34489
  function formatDocument(document2, options = {}) {
34399
34490
  const schema = options.schema ?? "auto";
34400
- const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.0-draft";
34491
+ const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6.1" || schema === "2.0-draft" || document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.6.1" || document2.version === "2.0-draft";
34401
34492
  if (useDraft) {
34402
34493
  if (schema === "2.0-draft") {
34403
- const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? {
34494
+ const legacyDraftDocument = document2.version === "2.0-draft" ? document2 : document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.6.1" ? {
34404
34495
  ...document2,
34405
34496
  version: "2.0-draft",
34406
34497
  schemaVersion: "2.0-draft"
34407
34498
  } : upgradeDocumentToDraftV2(document2);
34408
34499
  return formatDraftDocument(legacyDraftDocument);
34409
34500
  }
34410
- const atlasDocument = document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" ? document2 : document2.version === "2.0-draft" ? {
34501
+ const atlasDocument = document2.version === "2.0" || document2.version === "2.1" || document2.version === "2.5" || document2.version === "2.6.1" ? document2 : document2.version === "2.0-draft" ? {
34411
34502
  ...document2,
34412
34503
  version: "2.0",
34413
34504
  schemaVersion: "2.0"
34414
34505
  } : upgradeDocumentToV2(document2);
34415
- if ((schema === "2.0" || schema === "2.1" || schema === "2.5") && atlasDocument.version !== schema) {
34506
+ if ((schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.6.1") && atlasDocument.version !== schema) {
34416
34507
  return formatAtlasDocument({
34417
34508
  ...atlasDocument,
34418
34509
  version: schema,
@@ -35166,7 +35257,7 @@ void main() {
35166
35257
  }
35167
35258
  function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
35168
35259
  const filter = viewpoint.filter;
35169
- if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
35260
+ if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5" || sourceSchemaVersion === "2.6.1") {
35170
35261
  if (filter) {
35171
35262
  for (const groupId of filter.groupIds) {
35172
35263
  if (!groupIds.has(groupId)) {
@@ -35730,6 +35821,7 @@ void main() {
35730
35821
  const baseDocument = {
35731
35822
  format: "worldorbit",
35732
35823
  sourceVersion: "1.0",
35824
+ theme: null,
35733
35825
  system,
35734
35826
  groups,
35735
35827
  relations,
@@ -35763,11 +35855,11 @@ void main() {
35763
35855
  return document2;
35764
35856
  }
35765
35857
  function assertDraftSchemaHeader(tokens, line) {
35766
- if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
35767
- throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
35858
+ if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5", "2.6.1"].includes(tokens[1].value.toLowerCase())) {
35859
+ throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", "schema 2.5", "schema 2.6.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
35768
35860
  }
35769
35861
  const version = tokens[1].value.toLowerCase();
35770
- return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
35862
+ return version === "2.6.1" ? "2.6.1" : version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
35771
35863
  }
35772
35864
  function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
35773
35865
  const keyword = tokens[0]?.value.toLowerCase();
@@ -37063,6 +37155,8 @@ void main() {
37063
37155
  return 2;
37064
37156
  case "2.5":
37065
37157
  return 3;
37158
+ case "2.6.1":
37159
+ return 4;
37066
37160
  }
37067
37161
  }
37068
37162
  function preprocessAtlasSource(source) {
@@ -37152,12 +37246,16 @@ void main() {
37152
37246
  }
37153
37247
 
37154
37248
  // packages/core/dist/atlas-edit.js
37155
- function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.5") {
37249
+ function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.6.1") {
37156
37250
  return {
37157
37251
  format: "worldorbit",
37158
37252
  version,
37159
37253
  schemaVersion: version,
37160
37254
  sourceVersion: "1.0",
37255
+ theme: {
37256
+ preset: "blueprint",
37257
+ styles: {}
37258
+ },
37161
37259
  system: {
37162
37260
  type: "system",
37163
37261
  id: systemId,
@@ -37516,7 +37614,7 @@ void main() {
37516
37614
  return "2.1";
37517
37615
  }
37518
37616
  if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
37519
- return "2.5";
37617
+ return "2.6.1";
37520
37618
  }
37521
37619
  if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
37522
37620
  return "2.0";
@@ -37578,7 +37676,7 @@ void main() {
37578
37676
  }
37579
37677
  function loadWorldOrbitSourceWithDiagnostics(source) {
37580
37678
  const schemaVersion = detectWorldOrbitSchemaVersion(source);
37581
- if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
37679
+ if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5" || schemaVersion === "2.6.1") {
37582
37680
  return loadAtlasSourceWithDiagnostics(source, schemaVersion);
37583
37681
  }
37584
37682
  let ast;
@@ -37777,6 +37875,15 @@ void main() {
37777
37875
  starCore: "#ffcc67",
37778
37876
  starStroke: "rgba(255, 245, 203, 0.85)",
37779
37877
  starGlow: "#ffe8a3",
37878
+ spaceFog: "#07131d",
37879
+ starfield: "rgba(226, 239, 255, 0.9)",
37880
+ starfieldDim: "rgba(164, 194, 228, 0.45)",
37881
+ objectSpecular: "#f5f8ff",
37882
+ orbitOpacity: 0.34,
37883
+ orbitBandOpacity: 0.24,
37884
+ selectionHalo: "rgba(255, 214, 139, 0.9)",
37885
+ atmosphere: "rgba(143, 202, 255, 0.4)",
37886
+ cometTail: "rgba(193, 243, 255, 0.7)",
37780
37887
  fontFamily: '"Segoe UI Variable", "Bahnschrift", sans-serif',
37781
37888
  displayFont: '"Bahnschrift", "Segoe UI Variable", sans-serif'
37782
37889
  },
@@ -37800,6 +37907,15 @@ void main() {
37800
37907
  starCore: "#e5f98c",
37801
37908
  starStroke: "rgba(246, 255, 217, 0.9)",
37802
37909
  starGlow: "#fffab4",
37910
+ spaceFog: "#071723",
37911
+ starfield: "rgba(220, 255, 245, 0.9)",
37912
+ starfieldDim: "rgba(124, 212, 195, 0.42)",
37913
+ objectSpecular: "#ecfffb",
37914
+ orbitOpacity: 0.3,
37915
+ orbitBandOpacity: 0.22,
37916
+ selectionHalo: "rgba(120, 255, 215, 0.85)",
37917
+ atmosphere: "rgba(120, 255, 215, 0.32)",
37918
+ cometTail: "rgba(181, 255, 236, 0.68)",
37803
37919
  fontFamily: '"Segoe UI Variable", "Bahnschrift", sans-serif',
37804
37920
  displayFont: '"Bahnschrift", "Segoe UI Variable", sans-serif'
37805
37921
  },
@@ -37823,6 +37939,15 @@ void main() {
37823
37939
  starCore: "#ffb766",
37824
37940
  starStroke: "rgba(255, 236, 205, 0.88)",
37825
37941
  starGlow: "#ffe2ad",
37942
+ spaceFog: "#1c0d12",
37943
+ starfield: "rgba(255, 232, 214, 0.88)",
37944
+ starfieldDim: "rgba(255, 176, 138, 0.38)",
37945
+ objectSpecular: "#fff0e6",
37946
+ orbitOpacity: 0.3,
37947
+ orbitBandOpacity: 0.24,
37948
+ selectionHalo: "rgba(255, 178, 125, 0.85)",
37949
+ atmosphere: "rgba(255, 190, 140, 0.26)",
37950
+ cometTail: "rgba(255, 214, 173, 0.62)",
37826
37951
  fontFamily: '"Segoe UI Variable", "Bahnschrift", sans-serif',
37827
37952
  displayFont: '"Bahnschrift", "Segoe UI Variable", sans-serif'
37828
37953
  }
@@ -38872,10 +38997,12 @@ void main() {
38872
38997
  let currentVisibleObjectIds = /* @__PURE__ */ new Set();
38873
38998
  let currentSelectedObjectId = null;
38874
38999
  let currentHoveredObjectId = null;
38875
- let currentTimeSeconds = 0;
38876
39000
  let currentPositions = /* @__PURE__ */ new Map();
38877
39001
  let pendingUpdate = null;
38878
39002
  let destroyed = false;
39003
+ let smoothedCameraPosition = null;
39004
+ let smoothedCameraTarget = null;
39005
+ let currentEnvironmentKey = "";
38879
39006
  const objectVisuals = /* @__PURE__ */ new Map();
38880
39007
  const orbitVisuals = /* @__PURE__ */ new Map();
38881
39008
  const raycastTargets = [];
@@ -38884,7 +39011,7 @@ void main() {
38884
39011
  return;
38885
39012
  }
38886
39013
  const scene3d = new THREE.Scene();
38887
- const camera = new THREE.PerspectiveCamera(52, 1, 0.1, 2e4);
39014
+ const camera = new THREE.PerspectiveCamera(46, 1, 0.1, 24e3);
38888
39015
  const renderer = new THREE.WebGLRenderer({
38889
39016
  antialias: true,
38890
39017
  alpha: true,
@@ -38894,27 +39021,40 @@ void main() {
38894
39021
  renderer.domElement.dataset.worldorbit3dCanvas = "true";
38895
39022
  root.innerHTML = "";
38896
39023
  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);
39024
+ const ambientLight = new THREE.AmbientLight(16777215, 0.24);
39025
+ const fillLight = new THREE.DirectionalLight(13625855, 0.36);
39026
+ const rimLight = new THREE.DirectionalLight(8304895, 0.24);
39027
+ const keyLight = new THREE.PointLight(16773327, 2.6, 0, 2);
39028
+ fillLight.position.set(-360, 260, 220);
39029
+ rimLight.position.set(340, 180, -280);
38901
39030
  const orbitLayer = new THREE.Group();
38902
39031
  const objectLayer = new THREE.Group();
39032
+ const starfield = createStarfield(THREE, 320);
39033
+ const raycaster = new THREE.Raycaster();
39034
+ raycaster.params.Line = { threshold: 7 };
39035
+ scene3d.add(ambientLight);
39036
+ scene3d.add(fillLight);
39037
+ scene3d.add(rimLight);
39038
+ scene3d.add(keyLight);
39039
+ scene3d.add(starfield);
38903
39040
  scene3d.add(orbitLayer);
38904
39041
  scene3d.add(objectLayer);
38905
- const raycaster = new THREE.Raycaster();
38906
- raycaster.params.Line = { threshold: 10 };
38907
39042
  runtime = {
38908
39043
  THREE,
38909
39044
  scene3d,
38910
39045
  camera,
38911
39046
  renderer,
39047
+ ambientLight,
39048
+ fillLight,
39049
+ rimLight,
38912
39050
  keyLight,
39051
+ starfield,
38913
39052
  orbitLayer,
38914
39053
  objectLayer,
38915
39054
  raycaster,
38916
39055
  pointer: new THREE.Vector2()
38917
39056
  };
39057
+ configureRenderer(renderer, THREE, "balanced");
38918
39058
  if (pendingUpdate) {
38919
39059
  applyUpdate(pendingUpdate);
38920
39060
  }
@@ -38973,6 +39113,8 @@ void main() {
38973
39113
  destroyed = true;
38974
39114
  pendingUpdate = null;
38975
39115
  runtime?.renderer.dispose();
39116
+ runtime?.starfield?.geometry?.dispose?.();
39117
+ runtime?.starfield?.material?.dispose?.();
38976
39118
  root.remove();
38977
39119
  objectVisuals.clear();
38978
39120
  orbitVisuals.clear();
@@ -38991,7 +39133,16 @@ void main() {
38991
39133
  currentVisibleObjectIds = next.visibleObjectIds;
38992
39134
  currentSelectedObjectId = next.selectedObjectId;
38993
39135
  currentHoveredObjectId = next.hoveredObjectId;
38994
- currentTimeSeconds = next.timeSeconds;
39136
+ configureRenderer(runtime.renderer, runtime.THREE, currentRenderOptions?.quality ?? "balanced");
39137
+ const nextEnvironmentKey = JSON.stringify({
39138
+ theme: currentRenderOptions?.theme ?? null,
39139
+ quality: currentRenderOptions?.quality ?? "balanced",
39140
+ style3d: currentRenderOptions?.style3d ?? "symbolic"
39141
+ });
39142
+ if (nextEnvironmentKey !== currentEnvironmentKey) {
39143
+ updateEnvironment(runtime, currentRenderOptions);
39144
+ currentEnvironmentKey = nextEnvironmentKey;
39145
+ }
38995
39146
  if (sceneChanged) {
38996
39147
  rebuildScene(next.spatialScene);
38997
39148
  }
@@ -39001,8 +39152,9 @@ void main() {
39001
39152
  updateOrbitTransforms();
39002
39153
  updateVisibility();
39003
39154
  updateInteractionState();
39155
+ updateLighting();
39004
39156
  updateCamera();
39005
- renderNow();
39157
+ runtime.renderer.render(runtime.scene3d, runtime.camera);
39006
39158
  }
39007
39159
  function rebuildScene(spatialScene) {
39008
39160
  if (!runtime) {
@@ -39013,8 +39165,9 @@ void main() {
39013
39165
  objectVisuals.clear();
39014
39166
  orbitVisuals.clear();
39015
39167
  raycastTargets.length = 0;
39168
+ smoothedCameraPosition = null;
39169
+ smoothedCameraTarget = null;
39016
39170
  const theme = resolveTheme(currentRenderOptions?.theme);
39017
- runtime.scene3d.background = new runtime.THREE.Color(theme.backgroundStart);
39018
39171
  for (const orbit of spatialScene.orbits) {
39019
39172
  const visual = createOrbitVisual2(runtime.THREE, orbit, theme);
39020
39173
  runtime.orbitLayer.add(visual.root);
@@ -39032,10 +39185,9 @@ void main() {
39032
39185
  for (const object of currentScene?.objects ?? []) {
39033
39186
  const visual = objectVisuals.get(object.objectId);
39034
39187
  const position = currentPositions.get(object.objectId);
39035
- if (!visual || !position) {
39036
- continue;
39188
+ if (visual && position) {
39189
+ visual.root.position.set(position.x, position.y, position.z);
39037
39190
  }
39038
- visual.root.position.set(position.x, position.y, position.z);
39039
39191
  }
39040
39192
  }
39041
39193
  function updateOrbitTransforms() {
@@ -39056,8 +39208,7 @@ void main() {
39056
39208
  continue;
39057
39209
  }
39058
39210
  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;
39211
+ visual.root.visible = !object.hidden && currentVisibleObjectIds.has(object.objectId) && layers.objects !== false && !hideStructure;
39061
39212
  }
39062
39213
  for (const orbit of currentScene?.orbits ?? []) {
39063
39214
  const visual = orbitVisuals.get(orbit.objectId);
@@ -39073,14 +39224,27 @@ void main() {
39073
39224
  return;
39074
39225
  }
39075
39226
  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;
39227
+ const selected = currentSelectedObjectId === visual.objectId;
39228
+ const hovered = currentHoveredObjectId === visual.objectId;
39229
+ applyVisualState(runtime.THREE, visual.materials, selected, hovered);
39230
+ const scale = selected ? 1.16 : hovered ? 1.08 : 1;
39078
39231
  visual.root.scale.set(scale, scale, scale);
39232
+ if (visual.halo) {
39233
+ visual.halo.visible = selected || hovered;
39234
+ }
39079
39235
  }
39080
39236
  for (const visual of orbitVisuals.values()) {
39081
- applyVisualState(runtime.THREE, visual.materials, visual.baseColor, currentSelectedObjectId === visual.objectId, currentHoveredObjectId === visual.objectId);
39237
+ applyVisualState(runtime.THREE, visual.materials, currentSelectedObjectId === visual.objectId, currentHoveredObjectId === visual.objectId);
39082
39238
  }
39083
39239
  }
39240
+ function updateLighting() {
39241
+ if (!runtime || !currentScene) {
39242
+ return;
39243
+ }
39244
+ const primaryStar = currentScene.objects.find((object) => object.object.type === "star" && !object.hidden) ?? null;
39245
+ const starPosition = primaryStar ? currentPositions.get(primaryStar.objectId) ?? primaryStar.position : { x: 0, y: 40, z: 0 };
39246
+ runtime.keyLight.position.set(starPosition.x, starPosition.y + 20, starPosition.z);
39247
+ }
39084
39248
  function updateCamera() {
39085
39249
  if (!runtime || !currentScene || !currentState) {
39086
39250
  return;
@@ -39088,19 +39252,27 @@ void main() {
39088
39252
  const sceneCamera = currentRenderOptions?.camera ?? currentScene.camera;
39089
39253
  const bounds = currentScene.contentBounds;
39090
39254
  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);
39255
+ const yaw = degreesToRadians3((sceneCamera?.azimuth ?? 30) + currentState.rotationDeg);
39256
+ const pitch = degreesToRadians3(clampValue(sceneCamera?.elevation ?? 22, -75, 75));
39257
+ const zoomDistanceFactor = clampValue(2.2 / Math.max(currentState.scale, 0.1), 0.35, 7.2);
39258
+ const semanticDistance = clampValue(sceneCamera?.distance ?? 5.4, 2, 24);
39259
+ const distance = clampValue(size * zoomDistanceFactor * (semanticDistance / 5.4), 24, 8e3);
39096
39260
  const panFactor = Math.max(size / 900, 0.12);
39097
39261
  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);
39262
+ 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));
39263
+ const smoothing = (currentRenderOptions?.style3d ?? "symbolic") === "cinematic" ? 0.16 : 0.32;
39264
+ if (!smoothedCameraPosition || !smoothedCameraTarget) {
39265
+ smoothedCameraPosition = desiredPosition.clone();
39266
+ smoothedCameraTarget = target.clone();
39267
+ } else {
39268
+ smoothedCameraPosition.lerp(desiredPosition, smoothing);
39269
+ smoothedCameraTarget.lerp(target, smoothing);
39270
+ }
39271
+ runtime.camera.position.copy(smoothedCameraPosition);
39272
+ runtime.camera.lookAt(smoothedCameraTarget);
39100
39273
  if (sceneCamera?.roll) {
39101
39274
  runtime.camera.rotation.z = degreesToRadians3(sceneCamera.roll);
39102
39275
  }
39103
- runtime.keyLight.position.copy(runtime.camera.position);
39104
39276
  }
39105
39277
  function resizeRenderer(spatialScene) {
39106
39278
  if (!runtime) {
@@ -39112,35 +39284,63 @@ void main() {
39112
39284
  runtime.camera.aspect = width / height;
39113
39285
  runtime.camera.updateProjectionMatrix();
39114
39286
  }
39115
- function renderNow() {
39116
- if (!runtime) {
39117
- return;
39118
- }
39119
- runtime.renderer.render(runtime.scene3d, runtime.camera);
39120
- }
39121
39287
  }
39122
39288
  function createObjectVisual(THREE, object, theme) {
39123
39289
  const root = new THREE.Group();
39124
39290
  root.userData.objectId = object.objectId;
39125
39291
  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);
39292
+ const materials = [];
39293
+ const bodyMaterial = materialForObject(THREE, object, baseColor, theme);
39294
+ const body = new THREE.Mesh(geometryForObject(THREE, object), bodyMaterial.material);
39135
39295
  body.userData.objectId = object.objectId;
39136
39296
  root.add(body);
39137
- return {
39138
- objectId: object.objectId,
39139
- root,
39140
- body,
39141
- materials: [material],
39142
- baseColor
39143
- };
39297
+ materials.push(bodyMaterial);
39298
+ if (shouldRenderAtmosphere(object)) {
39299
+ const atmosphereMaterial = {
39300
+ material: new THREE.MeshBasicMaterial({
39301
+ color: theme.atmosphere,
39302
+ transparent: true,
39303
+ opacity: 0.24,
39304
+ depthWrite: false,
39305
+ side: 2
39306
+ }),
39307
+ baseColor: theme.atmosphere,
39308
+ baseOpacity: 0.24,
39309
+ hoveredOpacity: 0.34,
39310
+ selectedOpacity: 0.42
39311
+ };
39312
+ const atmosphere = new THREE.Mesh(new THREE.SphereGeometry(Math.max(object.visualRadius, 2) * 1.16, 20, 14), atmosphereMaterial.material);
39313
+ atmosphere.userData.objectId = object.objectId;
39314
+ root.add(atmosphere);
39315
+ materials.push(atmosphereMaterial);
39316
+ }
39317
+ if (object.object.type === "comet") {
39318
+ const tailMaterial = {
39319
+ material: new THREE.MeshBasicMaterial({
39320
+ color: theme.cometTail,
39321
+ transparent: true,
39322
+ opacity: 0.36,
39323
+ depthWrite: false
39324
+ }),
39325
+ baseColor: theme.cometTail,
39326
+ baseOpacity: 0.36,
39327
+ hoveredOpacity: 0.48,
39328
+ selectedOpacity: 0.56
39329
+ };
39330
+ 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);
39331
+ tail.position.set(-Math.max(object.visualRadius * 1.4, 4), 0, 0);
39332
+ tail.rotation.z = -Math.PI / 2;
39333
+ tail.userData.objectId = object.objectId;
39334
+ root.add(tail);
39335
+ materials.push(tailMaterial);
39336
+ }
39337
+ const halo = createHalo(THREE, object, theme);
39338
+ if (halo) {
39339
+ halo.visible = false;
39340
+ halo.userData.objectId = object.objectId;
39341
+ root.add(halo);
39342
+ }
39343
+ return { objectId: object.objectId, root, halo, materials };
39144
39344
  }
39145
39345
  function createOrbitVisual2(THREE, orbit, theme) {
39146
39346
  const root = new THREE.Group();
@@ -39148,60 +39348,174 @@ void main() {
39148
39348
  root.rotation.y = degreesToRadians3(orbit.rotationDeg);
39149
39349
  root.rotation.x = degreesToRadians3(orbit.inclinationDeg);
39150
39350
  const baseColor = orbit.object.properties.color ?? theme.orbit;
39151
- const materials = [];
39152
39351
  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);
39352
+ const material2 = {
39353
+ material: new THREE.MeshBasicMaterial({
39354
+ color: baseColor,
39355
+ transparent: true,
39356
+ opacity: theme.orbitBandOpacity,
39357
+ side: 2,
39358
+ depthWrite: false
39359
+ }),
39360
+ baseColor,
39361
+ baseOpacity: theme.orbitBandOpacity,
39362
+ hoveredOpacity: Math.min(theme.orbitBandOpacity + 0.1, 0.58),
39363
+ selectedOpacity: Math.min(theme.orbitBandOpacity + 0.18, 0.72),
39364
+ hoveredColor: theme.accent,
39365
+ selectedColor: theme.accentStrong
39366
+ };
39367
+ const mesh = new THREE.Mesh(bandGeometryForOrbit(THREE, orbit), material2.material);
39161
39368
  mesh.userData.objectId = orbit.objectId;
39162
39369
  root.add(mesh);
39163
- materials.push(material);
39164
- } else {
39165
- const material = new THREE.LineBasicMaterial({
39370
+ return { objectId: orbit.objectId, root, materials: [material2] };
39371
+ }
39372
+ const material = {
39373
+ material: new THREE.LineBasicMaterial({
39166
39374
  color: baseColor,
39167
39375
  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
- }
39376
+ opacity: theme.orbitOpacity
39377
+ }),
39378
+ baseColor,
39379
+ baseOpacity: theme.orbitOpacity,
39380
+ hoveredOpacity: Math.min(theme.orbitOpacity + 0.18, 0.72),
39381
+ selectedOpacity: Math.min(theme.orbitOpacity + 0.3, 0.88),
39382
+ hoveredColor: theme.accent,
39383
+ selectedColor: theme.accentStrong
39384
+ };
39385
+ const geometry = new THREE.BufferGeometry().setFromPoints(sampleOrbitPoints(THREE, orbit, 120));
39386
+ const line = new THREE.LineLoop(geometry, material.material);
39387
+ line.userData.objectId = orbit.objectId;
39388
+ root.add(line);
39389
+ return { objectId: orbit.objectId, root, materials: [material] };
39390
+ }
39391
+ function materialForObject(THREE, object, baseColor, theme) {
39392
+ if (object.object.type === "star") {
39393
+ return {
39394
+ material: new THREE.MeshStandardMaterial({
39395
+ color: baseColor,
39396
+ emissive: new THREE.Color(theme.starGlow),
39397
+ emissiveIntensity: 1.2,
39398
+ roughness: 0.35,
39399
+ metalness: 0.02
39400
+ }),
39401
+ baseColor,
39402
+ baseOpacity: 1,
39403
+ hoveredOpacity: 1,
39404
+ selectedOpacity: 1,
39405
+ hoveredColor: theme.starCore,
39406
+ selectedColor: "#fff2c4",
39407
+ baseEmissive: theme.starGlow,
39408
+ hoveredEmissive: theme.starGlow,
39409
+ selectedEmissive: "#fff6cc",
39410
+ baseEmissiveIntensity: 1.2,
39411
+ hoveredEmissiveIntensity: 1.5,
39412
+ selectedEmissiveIntensity: 1.8
39413
+ };
39414
+ }
39415
+ if (object.object.type === "phenomenon") {
39416
+ return {
39417
+ material: new THREE.MeshPhongMaterial({
39418
+ color: baseColor,
39419
+ transparent: true,
39420
+ opacity: 0.7,
39421
+ emissive: new THREE.Color(baseColor),
39422
+ emissiveIntensity: 0.32,
39423
+ shininess: 90
39424
+ }),
39425
+ baseColor,
39426
+ baseOpacity: 0.7,
39427
+ hoveredOpacity: 0.82,
39428
+ selectedOpacity: 0.9,
39429
+ hoveredColor: theme.accent,
39430
+ selectedColor: theme.selectionHalo,
39431
+ baseEmissive: baseColor,
39432
+ hoveredEmissive: theme.accent,
39433
+ selectedEmissive: theme.selectionHalo,
39434
+ baseEmissiveIntensity: 0.32,
39435
+ hoveredEmissiveIntensity: 0.52,
39436
+ selectedEmissiveIntensity: 0.74
39437
+ };
39438
+ }
39439
+ const shininess = object.object.type === "structure" ? 70 : object.object.type === "ring" ? 42 : object.object.type === "belt" ? 26 : 36;
39177
39440
  return {
39178
- objectId: orbit.objectId,
39179
- root,
39180
- materials,
39181
- baseColor
39441
+ material: new THREE.MeshPhongMaterial({
39442
+ color: baseColor,
39443
+ specular: new THREE.Color(theme.objectSpecular),
39444
+ shininess,
39445
+ transparent: false,
39446
+ opacity: 1,
39447
+ emissive: new THREE.Color(0),
39448
+ emissiveIntensity: 0.02
39449
+ }),
39450
+ baseColor,
39451
+ baseOpacity: 1,
39452
+ hoveredOpacity: 1,
39453
+ selectedOpacity: 1,
39454
+ hoveredColor: shiftColorLightness(THREE, baseColor, 0.08),
39455
+ selectedColor: shiftColorLightness(THREE, baseColor, 0.16),
39456
+ hoveredEmissive: "#8fcaff",
39457
+ selectedEmissive: theme.selectionHalo,
39458
+ baseEmissiveIntensity: 0.02,
39459
+ hoveredEmissiveIntensity: 0.12,
39460
+ selectedEmissiveIntensity: 0.22
39182
39461
  };
39183
39462
  }
39184
39463
  function geometryForObject(THREE, object) {
39185
39464
  const radius = Math.max(object.visualRadius, 2);
39186
39465
  switch (object.object.type) {
39187
39466
  case "star":
39188
- return new THREE.SphereGeometry(radius * 1.12, 28, 20);
39467
+ return new THREE.SphereGeometry(radius * 1.14, 34, 24);
39189
39468
  case "structure":
39190
- return new THREE.BoxGeometry(radius * 1.5, radius * 1.5, radius * 1.5);
39469
+ return geometryForStructure(THREE, object, radius);
39191
39470
  case "phenomenon":
39192
- return new THREE.OctahedronGeometry(radius * 1.25, 0);
39471
+ return new THREE.IcosahedronGeometry(radius * 1.12, 1);
39193
39472
  case "belt":
39473
+ return new THREE.TorusGeometry(Math.max(radius * 1.15, 4), Math.max(radius * 0.28, 1), 10, 24);
39194
39474
  case "ring":
39195
- return new THREE.OctahedronGeometry(Math.max(radius * 0.85, 3), 0);
39475
+ return new THREE.TorusGeometry(Math.max(radius, 4), Math.max(radius * 0.18, 0.8), 10, 30);
39476
+ case "asteroid":
39477
+ return new THREE.DodecahedronGeometry(radius, 0);
39478
+ case "comet":
39479
+ return new THREE.SphereGeometry(radius * 0.94, 18, 14);
39196
39480
  default:
39197
- return new THREE.SphereGeometry(radius, 20, 14);
39481
+ return new THREE.SphereGeometry(radius, 24, 18);
39198
39482
  }
39199
39483
  }
39484
+ function geometryForStructure(THREE, object, radius) {
39485
+ const kind = String(object.object.properties.kind ?? "").toLowerCase();
39486
+ if (kind.includes("relay")) {
39487
+ return new THREE.OctahedronGeometry(radius * 1.15, 0);
39488
+ }
39489
+ if (kind.includes("elevator") || kind.includes("skyhook")) {
39490
+ return new THREE.CylinderGeometry(radius * 0.36, radius * 0.52, radius * 2.4, 10);
39491
+ }
39492
+ if (kind.includes("station")) {
39493
+ return new THREE.TorusKnotGeometry(radius * 0.6, Math.max(radius * 0.18, 0.6), 42, 8);
39494
+ }
39495
+ return new THREE.BoxGeometry(radius * 1.45, radius * 1.2, radius * 1.45);
39496
+ }
39497
+ function createHalo(THREE, object, theme) {
39498
+ const radius = Math.max(object.visualRadius, 2);
39499
+ 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);
39500
+ return new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
39501
+ color: theme.selectionHalo,
39502
+ transparent: true,
39503
+ opacity: 0.18,
39504
+ depthWrite: false,
39505
+ side: 1
39506
+ }));
39507
+ }
39508
+ function shouldRenderAtmosphere(object) {
39509
+ if (object.object.type !== "planet" && object.object.type !== "moon") {
39510
+ return false;
39511
+ }
39512
+ return object.object.properties.atmosphere !== void 0;
39513
+ }
39200
39514
  function bandGeometryForOrbit(THREE, orbit) {
39201
39515
  const thickness = Math.max(orbit.bandThickness ?? 8, 3);
39202
39516
  const points = sampleOrbitPoints(THREE, orbit, 72);
39203
39517
  const curve = new THREE.CatmullRomCurve3(points, true);
39204
- return new THREE.TubeGeometry(curve, 128, thickness * 0.28, 10, true);
39518
+ return new THREE.TubeGeometry(curve, 144, thickness * 0.18, 10, true);
39205
39519
  }
39206
39520
  function sampleOrbitPoints(THREE, orbit, segments = 96) {
39207
39521
  const points = [];
@@ -39213,6 +39527,94 @@ void main() {
39213
39527
  }
39214
39528
  return points;
39215
39529
  }
39530
+ function createStarfield(THREE, count) {
39531
+ const geometry = new THREE.BufferGeometry();
39532
+ const positions = new Float32Array(count * 3);
39533
+ const colors = new Float32Array(count * 3);
39534
+ for (let index = 0; index < count; index += 1) {
39535
+ const offset = index * 3;
39536
+ const radius = 1800 + Math.random() * 2600;
39537
+ const theta = Math.random() * Math.PI * 2;
39538
+ const phi = Math.acos(2 * Math.random() - 1);
39539
+ positions[offset] = radius * Math.sin(phi) * Math.cos(theta);
39540
+ positions[offset + 1] = radius * Math.cos(phi) * 0.45;
39541
+ positions[offset + 2] = radius * Math.sin(phi) * Math.sin(theta);
39542
+ }
39543
+ geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
39544
+ geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
39545
+ return new THREE.Points(geometry, new THREE.PointsMaterial({
39546
+ size: 5,
39547
+ transparent: true,
39548
+ opacity: 0.84,
39549
+ depthWrite: false,
39550
+ vertexColors: true,
39551
+ sizeAttenuation: true
39552
+ }));
39553
+ }
39554
+ function configureRenderer(renderer, THREE, quality) {
39555
+ const pixelRatioCap = quality === "high" ? 2.4 : quality === "low" ? 1.2 : 1.8;
39556
+ renderer.setPixelRatio?.(Math.min(globalThis.window?.devicePixelRatio ?? 1, pixelRatioCap));
39557
+ if ("outputColorSpace" in renderer && "SRGBColorSpace" in THREE) {
39558
+ renderer.outputColorSpace = THREE.SRGBColorSpace;
39559
+ } else if ("outputEncoding" in renderer && "sRGBEncoding" in THREE) {
39560
+ renderer.outputEncoding = THREE.sRGBEncoding;
39561
+ }
39562
+ if ("ACESFilmicToneMapping" in THREE) {
39563
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
39564
+ }
39565
+ }
39566
+ function updateEnvironment(runtime, renderOptions) {
39567
+ const theme = resolveTheme(renderOptions?.theme);
39568
+ const quality = renderOptions?.quality ?? "balanced";
39569
+ const style3d = renderOptions?.style3d ?? "symbolic";
39570
+ const count = quality === "high" ? 520 : quality === "low" ? 180 : 320;
39571
+ const positions = new Float32Array(count * 3);
39572
+ const colors = new Float32Array(count * 3);
39573
+ const bright = new runtime.THREE.Color(theme.starfield);
39574
+ const dim = new runtime.THREE.Color(theme.starfieldDim);
39575
+ for (let index = 0; index < count; index += 1) {
39576
+ const offset = index * 3;
39577
+ const radius = 1800 + Math.random() * 2600;
39578
+ const theta = Math.random() * Math.PI * 2;
39579
+ const phi = Math.acos(2 * Math.random() - 1);
39580
+ positions[offset] = radius * Math.sin(phi) * Math.cos(theta);
39581
+ positions[offset + 1] = radius * Math.cos(phi) * 0.45;
39582
+ positions[offset + 2] = radius * Math.sin(phi) * Math.sin(theta);
39583
+ const color = Math.random() > 0.72 ? bright : dim;
39584
+ colors[offset] = color.r;
39585
+ colors[offset + 1] = color.g;
39586
+ colors[offset + 2] = color.b;
39587
+ }
39588
+ runtime.scene3d.background = new runtime.THREE.Color(theme.backgroundStart);
39589
+ runtime.scene3d.fog = new runtime.THREE.FogExp2(theme.spaceFog, 85e-5);
39590
+ runtime.ambientLight.intensity = style3d === "cinematic" ? 0.18 : 0.26;
39591
+ runtime.fillLight.intensity = style3d === "cinematic" ? 0.32 : 0.4;
39592
+ runtime.rimLight.intensity = style3d === "cinematic" ? 0.28 : 0.22;
39593
+ runtime.renderer.toneMappingExposure = style3d === "cinematic" ? 1.18 : 1.08;
39594
+ runtime.starfield.geometry.setAttribute("position", new runtime.THREE.BufferAttribute(positions, 3));
39595
+ runtime.starfield.geometry.setAttribute("color", new runtime.THREE.BufferAttribute(colors, 3));
39596
+ runtime.starfield.material.opacity = quality === "high" ? 0.96 : quality === "low" ? 0.72 : 0.84;
39597
+ runtime.starfield.material.size = quality === "high" ? 5.5 : quality === "low" ? 4.25 : 5;
39598
+ }
39599
+ function applyVisualState(THREE, materials, selected, hovered) {
39600
+ for (const entry of materials) {
39601
+ const material = entry.material;
39602
+ if (!material) {
39603
+ continue;
39604
+ }
39605
+ const color = selected ? entry.selectedColor ?? entry.baseColor : hovered ? entry.hoveredColor ?? entry.baseColor : entry.baseColor;
39606
+ material.color?.set?.(new THREE.Color(color));
39607
+ if (typeof material.opacity === "number") {
39608
+ material.opacity = selected ? entry.selectedOpacity : hovered ? entry.hoveredOpacity : entry.baseOpacity;
39609
+ material.transparent = material.opacity < 0.999;
39610
+ }
39611
+ const emissive = selected ? entry.selectedEmissive ?? entry.baseEmissive : hovered ? entry.hoveredEmissive ?? entry.baseEmissive : entry.baseEmissive;
39612
+ material.emissive?.set?.(emissive ? new THREE.Color(emissive) : new THREE.Color(0));
39613
+ if ("emissiveIntensity" in material) {
39614
+ material.emissiveIntensity = selected ? entry.selectedEmissiveIntensity ?? entry.baseEmissiveIntensity ?? 0 : hovered ? entry.hoveredEmissiveIntensity ?? entry.baseEmissiveIntensity ?? 0 : entry.baseEmissiveIntensity ?? 0;
39615
+ }
39616
+ }
39617
+ }
39216
39618
  function colorForObject(object) {
39217
39619
  switch (object.object.type) {
39218
39620
  case "star":
@@ -39235,35 +39637,23 @@ void main() {
39235
39637
  return "#b8f2ff";
39236
39638
  }
39237
39639
  }
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
- }
39640
+ function shiftColorLightness(THREE, colorValue, delta) {
39641
+ const color = new THREE.Color(colorValue);
39642
+ color.offsetHSL(0, 0, delta);
39643
+ return `#${color.getHexString()}`;
39256
39644
  }
39257
39645
  function clearGroup(group) {
39258
39646
  while (group.children.length > 0) {
39259
39647
  const child = group.children[0];
39260
39648
  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
- }
39649
+ child.traverse?.((node) => {
39650
+ node.geometry?.dispose?.();
39651
+ if (Array.isArray(node.material)) {
39652
+ node.material.forEach((entry) => entry?.dispose?.());
39653
+ } else {
39654
+ node.material?.dispose?.();
39655
+ }
39656
+ });
39267
39657
  }
39268
39658
  }
39269
39659
  function ensureWebGLSupport() {
@@ -39527,6 +39917,8 @@ void main() {
39527
39917
  preset: options.preset,
39528
39918
  projection: options.projection,
39529
39919
  viewMode: options.viewMode ?? "2d",
39920
+ quality: options.quality ?? "balanced",
39921
+ style3d: options.style3d ?? "symbolic",
39530
39922
  camera: options.camera ? { ...options.camera } : null,
39531
39923
  scaleModel: options.scaleModel ? { ...options.scaleModel } : void 0,
39532
39924
  theme: options.theme,
@@ -40710,6 +41102,9 @@ void main() {
40710
41102
  if (label.hidden || !visibleObjectIds.has(label.objectId)) {
40711
41103
  continue;
40712
41104
  }
41105
+ if (is3DView() && !shouldRender3DLabel(label.objectId, visibleObjectIds)) {
41106
+ continue;
41107
+ }
40713
41108
  const point = is3DView() ? runtime3d?.projectObjectToContainer(label.objectId) ?? null : project2DScenePointToContainer({ x: label.x, y: label.y });
40714
41109
  if (!point) {
40715
41110
  continue;
@@ -40749,6 +41144,40 @@ void main() {
40749
41144
  function isEventVisible(event, visibleObjectIds) {
40750
41145
  return event.objectIds.some((objectId) => visibleObjectIds.has(objectId));
40751
41146
  }
41147
+ function shouldRender3DLabel(objectId, visibleObjectIds) {
41148
+ if (!is3DView()) {
41149
+ return true;
41150
+ }
41151
+ if (objectId === state.selectedObjectId || objectId === hoveredObjectId) {
41152
+ return true;
41153
+ }
41154
+ const object = getObjectById(objectId);
41155
+ if (!object || object.hidden || !visibleObjectIds.has(objectId)) {
41156
+ return false;
41157
+ }
41158
+ if (object.object.type === "star") {
41159
+ return true;
41160
+ }
41161
+ const selected = state.selectedObjectId ? buildObjectDetails(state.selectedObjectId) : null;
41162
+ const hovered = hoveredObjectId ? buildObjectDetails(hoveredObjectId) : null;
41163
+ const selectedFocus = selected ? /* @__PURE__ */ new Set([
41164
+ selected.objectId,
41165
+ ...selected.renderObject.ancestorIds,
41166
+ ...selected.renderObject.childIds
41167
+ ]) : null;
41168
+ const hoveredFocus = hovered ? /* @__PURE__ */ new Set([
41169
+ hovered.objectId,
41170
+ ...hovered.renderObject.ancestorIds,
41171
+ ...hovered.renderObject.childIds
41172
+ ]) : null;
41173
+ if (selectedFocus?.has(objectId) || hoveredFocus?.has(objectId)) {
41174
+ return true;
41175
+ }
41176
+ if (object.semanticGroupIds.length > 0 && object.visualRadius >= 12) {
41177
+ return true;
41178
+ }
41179
+ return object.childIds.length > 0 && object.visualRadius >= 10;
41180
+ }
40752
41181
  function createScreenLabelElement(descriptor) {
40753
41182
  const element = document.createElement("div");
40754
41183
  element.className = `wo-viewer-label wo-viewer-label-${descriptor.kind}`;
@@ -40885,7 +41314,9 @@ void main() {
40885
41314
  layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
40886
41315
  theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme,
40887
41316
  activeEventId: renderOptions.activeEventId ?? null,
40888
- viewMode: renderOptions.viewMode ?? "2d"
41317
+ viewMode: renderOptions.viewMode ?? "2d",
41318
+ quality: renderOptions.quality ?? "balanced",
41319
+ style3d: renderOptions.style3d ?? "symbolic"
40889
41320
  };
40890
41321
  }
40891
41322
  function mergeRenderOptions(current, next) {
@@ -40903,7 +41334,9 @@ void main() {
40903
41334
  ...next.layers
40904
41335
  } : current.layers ? { ...current.layers } : void 0,
40905
41336
  theme: next.theme && typeof next.theme === "object" ? { ...next.theme } : next.theme ?? current.theme,
40906
- viewMode: next.viewMode ?? current.viewMode ?? "2d"
41337
+ viewMode: next.viewMode ?? current.viewMode ?? "2d",
41338
+ quality: next.quality ?? current.quality ?? "balanced",
41339
+ style3d: next.style3d ?? current.style3d ?? "symbolic"
40907
41340
  };
40908
41341
  }
40909
41342
  function hasSceneAffectingRenderOptions(options) {
@@ -41117,6 +41550,10 @@ void main() {
41117
41550
  position: absolute;
41118
41551
  display: grid;
41119
41552
  gap: 2px;
41553
+ padding: 4px 8px;
41554
+ border-radius: 999px;
41555
+ background: linear-gradient(180deg, rgba(5, 16, 26, 0.72), rgba(5, 16, 26, 0.38));
41556
+ border: 1px solid rgba(164, 194, 228, 0.16);
41120
41557
  color: #edf6ff;
41121
41558
  font-family: "Segoe UI Variable", "Segoe UI", sans-serif;
41122
41559
  line-height: 1.15;