worldorbit 2.6.0 → 3.0.1

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 (196) hide show
  1. package/README.md +20 -9
  2. package/dist/browser/core/dist/atlas-edit.d.ts +11 -0
  3. package/dist/browser/core/dist/atlas-edit.js +347 -0
  4. package/dist/browser/core/dist/atlas-utils.d.ts +22 -0
  5. package/dist/browser/core/dist/atlas-utils.js +189 -0
  6. package/dist/browser/core/dist/atlas-validate.d.ts +2 -0
  7. package/dist/browser/core/dist/atlas-validate.js +488 -0
  8. package/dist/browser/core/dist/diagnostics.d.ts +10 -0
  9. package/dist/browser/core/dist/diagnostics.js +109 -0
  10. package/dist/browser/core/dist/draft-parse.d.ts +3 -0
  11. package/dist/browser/core/dist/draft-parse.js +1654 -0
  12. package/dist/browser/core/dist/draft.d.ts +21 -0
  13. package/dist/browser/core/dist/draft.js +482 -0
  14. package/dist/browser/core/dist/errors.d.ts +7 -0
  15. package/dist/browser/core/dist/errors.js +16 -0
  16. package/dist/browser/core/dist/format.d.ts +4 -0
  17. package/dist/browser/core/dist/format.js +613 -0
  18. package/dist/browser/core/dist/index.d.ts +29 -0
  19. package/dist/browser/core/dist/index.js +35 -6542
  20. package/dist/browser/core/dist/load.d.ts +4 -0
  21. package/dist/browser/core/dist/load.js +182 -0
  22. package/dist/browser/core/dist/markdown.d.ts +2 -0
  23. package/dist/browser/core/dist/markdown.js +37 -0
  24. package/dist/browser/core/dist/normalize.d.ts +2 -0
  25. package/dist/browser/core/dist/normalize.js +312 -0
  26. package/dist/browser/core/dist/parse.d.ts +2 -0
  27. package/dist/browser/core/dist/parse.js +133 -0
  28. package/dist/browser/core/dist/scene.d.ts +3 -0
  29. package/dist/browser/core/dist/scene.js +1901 -0
  30. package/dist/browser/core/dist/schema.d.ts +8 -0
  31. package/dist/browser/core/dist/schema.js +298 -0
  32. package/dist/browser/core/dist/spatial-scene.d.ts +3 -0
  33. package/dist/browser/core/dist/spatial-scene.js +420 -0
  34. package/dist/browser/core/dist/tokenize.d.ts +4 -0
  35. package/dist/browser/core/dist/tokenize.js +68 -0
  36. package/dist/browser/core/dist/types.d.ts +637 -0
  37. package/dist/browser/core/dist/types.js +1 -0
  38. package/dist/browser/core/dist/validate.d.ts +2 -0
  39. package/dist/browser/core/dist/validate.js +56 -0
  40. package/dist/browser/editor/dist/editor.d.ts +2 -0
  41. package/dist/browser/editor/dist/editor.js +3700 -0
  42. package/dist/browser/editor/dist/index.d.ts +2 -0
  43. package/dist/browser/editor/dist/index.js +1 -12250
  44. package/dist/browser/editor/dist/types.d.ts +59 -0
  45. package/dist/browser/editor/dist/types.js +1 -0
  46. package/dist/browser/markdown/dist/html.d.ts +3 -0
  47. package/dist/browser/markdown/dist/html.js +64 -0
  48. package/dist/browser/markdown/dist/index.d.ts +4 -0
  49. package/dist/browser/markdown/dist/index.js +3 -6179
  50. package/dist/browser/markdown/dist/rehype.d.ts +10 -0
  51. package/dist/browser/markdown/dist/rehype.js +49 -0
  52. package/dist/browser/markdown/dist/remark.d.ts +9 -0
  53. package/dist/browser/markdown/dist/remark.js +28 -0
  54. package/dist/browser/markdown/dist/types.d.ts +11 -0
  55. package/dist/browser/markdown/dist/types.js +1 -0
  56. package/dist/browser/viewer/dist/atlas-state.d.ts +12 -0
  57. package/dist/browser/viewer/dist/atlas-state.js +269 -0
  58. package/dist/browser/viewer/dist/atlas-viewer.d.ts +2 -0
  59. package/dist/browser/viewer/dist/atlas-viewer.js +495 -0
  60. package/dist/browser/viewer/dist/custom-element.d.ts +1 -0
  61. package/dist/browser/viewer/dist/custom-element.js +78 -0
  62. package/dist/browser/viewer/dist/embed.d.ts +24 -0
  63. package/dist/browser/viewer/dist/embed.js +172 -0
  64. package/dist/browser/viewer/dist/errors.d.ts +6 -0
  65. package/dist/browser/viewer/dist/errors.js +12 -0
  66. package/dist/browser/viewer/dist/index.d.ts +10 -0
  67. package/dist/browser/viewer/dist/index.js +9 -8334
  68. package/dist/browser/viewer/dist/minimap.d.ts +3 -0
  69. package/dist/browser/viewer/dist/minimap.js +63 -0
  70. package/dist/browser/viewer/dist/render.d.ts +6 -0
  71. package/dist/browser/viewer/dist/render.js +670 -0
  72. package/dist/browser/viewer/dist/runtime-3d.d.ts +19 -0
  73. package/dist/browser/viewer/dist/runtime-3d.js +494 -0
  74. package/dist/browser/viewer/dist/theme.d.ts +4 -0
  75. package/dist/browser/viewer/dist/theme.js +103 -0
  76. package/dist/browser/viewer/dist/tooltip.d.ts +3 -0
  77. package/dist/browser/viewer/dist/tooltip.js +198 -0
  78. package/dist/browser/viewer/dist/types.d.ts +292 -0
  79. package/dist/browser/viewer/dist/types.js +1 -0
  80. package/dist/browser/viewer/dist/vendor/three.module.js +53032 -0
  81. package/dist/browser/viewer/dist/viewer-state.d.ts +19 -0
  82. package/dist/browser/viewer/dist/viewer-state.js +162 -0
  83. package/dist/browser/viewer/dist/viewer.d.ts +2 -0
  84. package/dist/browser/viewer/dist/viewer.js +1662 -0
  85. package/dist/unpkg/core/dist/atlas-edit.d.ts +11 -0
  86. package/dist/unpkg/core/dist/atlas-edit.js +347 -0
  87. package/dist/unpkg/core/dist/atlas-utils.d.ts +22 -0
  88. package/dist/unpkg/core/dist/atlas-utils.js +189 -0
  89. package/dist/unpkg/core/dist/atlas-validate.d.ts +2 -0
  90. package/dist/unpkg/core/dist/atlas-validate.js +488 -0
  91. package/dist/unpkg/core/dist/diagnostics.d.ts +10 -0
  92. package/dist/unpkg/core/dist/diagnostics.js +109 -0
  93. package/dist/unpkg/core/dist/draft-parse.d.ts +3 -0
  94. package/dist/unpkg/core/dist/draft-parse.js +1654 -0
  95. package/dist/unpkg/core/dist/draft.d.ts +21 -0
  96. package/dist/unpkg/core/dist/draft.js +482 -0
  97. package/dist/unpkg/core/dist/errors.d.ts +7 -0
  98. package/dist/unpkg/core/dist/errors.js +16 -0
  99. package/dist/unpkg/core/dist/format.d.ts +4 -0
  100. package/dist/unpkg/core/dist/format.js +613 -0
  101. package/dist/unpkg/core/dist/index.d.ts +29 -0
  102. package/dist/unpkg/core/dist/index.js +35 -6614
  103. package/dist/unpkg/core/dist/load.d.ts +4 -0
  104. package/dist/unpkg/core/dist/load.js +182 -0
  105. package/dist/unpkg/core/dist/markdown.d.ts +2 -0
  106. package/dist/unpkg/core/dist/markdown.js +37 -0
  107. package/dist/unpkg/core/dist/normalize.d.ts +2 -0
  108. package/dist/unpkg/core/dist/normalize.js +312 -0
  109. package/dist/unpkg/core/dist/parse.d.ts +2 -0
  110. package/dist/unpkg/core/dist/parse.js +133 -0
  111. package/dist/unpkg/core/dist/scene.d.ts +3 -0
  112. package/dist/unpkg/core/dist/scene.js +1901 -0
  113. package/dist/unpkg/core/dist/schema.d.ts +8 -0
  114. package/dist/unpkg/core/dist/schema.js +298 -0
  115. package/dist/unpkg/core/dist/spatial-scene.d.ts +3 -0
  116. package/dist/unpkg/core/dist/spatial-scene.js +420 -0
  117. package/dist/unpkg/core/dist/tokenize.d.ts +4 -0
  118. package/dist/unpkg/core/dist/tokenize.js +68 -0
  119. package/dist/unpkg/core/dist/types.d.ts +637 -0
  120. package/dist/unpkg/core/dist/types.js +1 -0
  121. package/dist/unpkg/core/dist/validate.d.ts +2 -0
  122. package/dist/unpkg/core/dist/validate.js +56 -0
  123. package/dist/unpkg/editor/dist/editor.d.ts +2 -0
  124. package/dist/unpkg/editor/dist/editor.js +3700 -0
  125. package/dist/unpkg/editor/dist/index.d.ts +2 -0
  126. package/dist/unpkg/editor/dist/index.js +1 -12275
  127. package/dist/unpkg/editor/dist/types.d.ts +59 -0
  128. package/dist/unpkg/editor/dist/types.js +1 -0
  129. package/dist/unpkg/markdown/dist/html.d.ts +3 -0
  130. package/dist/unpkg/markdown/dist/html.js +64 -0
  131. package/dist/unpkg/markdown/dist/index.d.ts +4 -0
  132. package/dist/unpkg/markdown/dist/index.js +3 -6207
  133. package/dist/unpkg/markdown/dist/rehype.d.ts +10 -0
  134. package/dist/unpkg/markdown/dist/rehype.js +49 -0
  135. package/dist/unpkg/markdown/dist/remark.d.ts +9 -0
  136. package/dist/unpkg/markdown/dist/remark.js +28 -0
  137. package/dist/unpkg/markdown/dist/types.d.ts +11 -0
  138. package/dist/unpkg/markdown/dist/types.js +1 -0
  139. package/dist/unpkg/viewer/dist/atlas-state.d.ts +12 -0
  140. package/dist/unpkg/viewer/dist/atlas-state.js +269 -0
  141. package/dist/unpkg/viewer/dist/atlas-viewer.d.ts +2 -0
  142. package/dist/unpkg/viewer/dist/atlas-viewer.js +495 -0
  143. package/dist/unpkg/viewer/dist/custom-element.d.ts +1 -0
  144. package/dist/unpkg/viewer/dist/custom-element.js +78 -0
  145. package/dist/unpkg/viewer/dist/embed.d.ts +24 -0
  146. package/dist/unpkg/viewer/dist/embed.js +172 -0
  147. package/dist/unpkg/viewer/dist/errors.d.ts +6 -0
  148. package/dist/unpkg/viewer/dist/errors.js +12 -0
  149. package/dist/unpkg/viewer/dist/index.d.ts +10 -0
  150. package/dist/unpkg/viewer/dist/index.js +9 -8391
  151. package/dist/unpkg/viewer/dist/minimap.d.ts +3 -0
  152. package/dist/unpkg/viewer/dist/minimap.js +63 -0
  153. package/dist/unpkg/viewer/dist/render.d.ts +6 -0
  154. package/dist/unpkg/viewer/dist/render.js +670 -0
  155. package/dist/unpkg/viewer/dist/runtime-3d.d.ts +19 -0
  156. package/dist/unpkg/viewer/dist/runtime-3d.js +494 -0
  157. package/dist/unpkg/viewer/dist/theme.d.ts +4 -0
  158. package/dist/unpkg/viewer/dist/theme.js +103 -0
  159. package/dist/unpkg/viewer/dist/tooltip.d.ts +3 -0
  160. package/dist/unpkg/viewer/dist/tooltip.js +198 -0
  161. package/dist/unpkg/viewer/dist/types.d.ts +292 -0
  162. package/dist/unpkg/viewer/dist/types.js +1 -0
  163. package/dist/unpkg/viewer/dist/vendor/three.module.js +53032 -0
  164. package/dist/unpkg/viewer/dist/viewer-state.d.ts +19 -0
  165. package/dist/unpkg/viewer/dist/viewer-state.js +162 -0
  166. package/dist/unpkg/viewer/dist/viewer.d.ts +2 -0
  167. package/dist/unpkg/viewer/dist/viewer.js +1662 -0
  168. package/dist/unpkg/worldorbit-core.min.js +10 -10
  169. package/dist/unpkg/worldorbit-editor.min.js +4109 -256
  170. package/dist/unpkg/worldorbit-markdown.min.js +26 -26
  171. package/dist/unpkg/worldorbit-viewer.min.js +3945 -92
  172. package/dist/unpkg/worldorbit.js +32219 -199
  173. package/dist/unpkg/worldorbit.min.js +3949 -96
  174. package/package.json +1 -1
  175. package/packages/core/dist/index.d.ts +1 -0
  176. package/packages/core/dist/index.js +1 -0
  177. package/packages/core/dist/spatial-scene.d.ts +3 -0
  178. package/packages/core/dist/spatial-scene.js +420 -0
  179. package/packages/core/dist/types.d.ts +105 -0
  180. package/packages/editor/dist/editor.js +25 -4
  181. package/packages/editor/dist/types.d.ts +4 -0
  182. package/packages/markdown/dist/html.js +10 -3
  183. package/packages/viewer/dist/atlas-state.js +3 -0
  184. package/packages/viewer/dist/atlas-viewer.js +1 -0
  185. package/packages/viewer/dist/custom-element.js +18 -4
  186. package/packages/viewer/dist/embed.d.ts +5 -1
  187. package/packages/viewer/dist/embed.js +58 -24
  188. package/packages/viewer/dist/errors.d.ts +6 -0
  189. package/packages/viewer/dist/errors.js +12 -0
  190. package/packages/viewer/dist/index.d.ts +1 -0
  191. package/packages/viewer/dist/index.js +1 -0
  192. package/packages/viewer/dist/runtime-3d.d.ts +19 -0
  193. package/packages/viewer/dist/runtime-3d.js +494 -0
  194. package/packages/viewer/dist/types.d.ts +21 -2
  195. package/packages/viewer/dist/vendor/three.module.js +53032 -0
  196. package/packages/viewer/dist/viewer.js +501 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worldorbit",
3
- "version": "2.6.0",
3
+ "version": "3.0.1",
4
4
  "description": "A text-based DSL and parser pipeline for orbital worldbuilding",
5
5
  "type": "module",
6
6
  "main": "./dist/unpkg/worldorbit.esm.js",
@@ -7,6 +7,7 @@ export { normalizeDocument } from "./normalize.js";
7
7
  export { validateDocument } from "./validate.js";
8
8
  export { createDiagnostic, diagnosticFromError, normalizeWithDiagnostics, parseWithDiagnostics, validateDocumentWithDiagnostics, } from "./diagnostics.js";
9
9
  export { renderDocumentToScene, rotatePoint } from "./scene.js";
10
+ export { evaluateSpatialSceneAtTime, renderDocumentToSpatialScene, } from "./spatial-scene.js";
10
11
  export { formatAtlasDocument, formatDocument, formatDraftDocument } from "./format.js";
11
12
  export { materializeAtlasDocument, materializeDraftDocument, upgradeDocumentToDraftV2, upgradeDocumentToV2, } from "./draft.js";
12
13
  export { parseWorldOrbitAtlas, parseWorldOrbitDraft } from "./draft-parse.js";
@@ -7,6 +7,7 @@ export { normalizeDocument } from "./normalize.js";
7
7
  export { validateDocument } from "./validate.js";
8
8
  export { createDiagnostic, diagnosticFromError, normalizeWithDiagnostics, parseWithDiagnostics, validateDocumentWithDiagnostics, } from "./diagnostics.js";
9
9
  export { renderDocumentToScene, rotatePoint } from "./scene.js";
10
+ export { evaluateSpatialSceneAtTime, renderDocumentToSpatialScene, } from "./spatial-scene.js";
10
11
  export { formatAtlasDocument, formatDocument, formatDraftDocument } from "./format.js";
11
12
  export { materializeAtlasDocument, materializeDraftDocument, upgradeDocumentToDraftV2, upgradeDocumentToV2, } from "./draft.js";
12
13
  export { parseWorldOrbitAtlas, parseWorldOrbitDraft } from "./draft-parse.js";
@@ -0,0 +1,3 @@
1
+ import type { CoordinatePoint3D, SpatialScene, SpatialSceneRenderOptions, WorldOrbitDocument } from "./types.js";
2
+ export declare function renderDocumentToSpatialScene(document: WorldOrbitDocument, options?: SpatialSceneRenderOptions): SpatialScene;
3
+ export declare function evaluateSpatialSceneAtTime(scene: SpatialScene, timeSeconds: number): Map<string, CoordinatePoint3D>;
@@ -0,0 +1,420 @@
1
+ import { renderDocumentToScene } from "./scene.js";
2
+ const DAY_IN_SECONDS = 86_400;
3
+ const YEAR_IN_SECONDS = DAY_IN_SECONDS * 365.25;
4
+ const FASTEST_VISIBLE_ORBIT_SECONDS = 18;
5
+ const SLOWEST_VISIBLE_ORBIT_SECONDS = 180;
6
+ export function renderDocumentToSpatialScene(document, options = {}) {
7
+ const renderOptions = {
8
+ width: options.width,
9
+ height: options.height,
10
+ padding: options.padding,
11
+ preset: options.preset,
12
+ projection: options.projection,
13
+ camera: options.camera,
14
+ scaleModel: options.scaleModel,
15
+ activeEventId: options.activeEventId,
16
+ };
17
+ const scene = renderDocumentToScene(document, renderOptions);
18
+ const scaleModel = resolveSpatialScaleModel(scene.layoutPreset, options.spatialScaleModel);
19
+ const sceneCenter = {
20
+ x: scene.contentBounds.centerX,
21
+ y: scene.contentBounds.centerY,
22
+ };
23
+ const objectMap = new Map(scene.objects.map((object) => [object.objectId, object]));
24
+ const orbitMap = new Map(scene.orbitVisuals.map((orbit) => [orbit.objectId, orbit]));
25
+ const rawMotionMetrics = new Map();
26
+ for (const object of scene.objects) {
27
+ const placement = object.object.placement;
28
+ if (!placement || placement.mode !== "orbit") {
29
+ continue;
30
+ }
31
+ rawMotionMetrics.set(object.objectId, resolveRawOrbitMetric(placement));
32
+ }
33
+ const minimumMotionMetric = Math.min(...[...rawMotionMetrics.values()].filter((value) => Number.isFinite(value) && value > 0)) || 1;
34
+ const positionCache = new Map();
35
+ const spatialObjects = scene.objects.map((entry) => createSpatialObject(entry, scene, sceneCenter, objectMap, orbitMap, scaleModel, positionCache, minimumMotionMetric));
36
+ const spatialObjectMap = new Map(spatialObjects.map((object) => [object.objectId, object]));
37
+ const spatialOrbits = scene.orbitVisuals.map((orbit) => createSpatialOrbit(orbit, spatialObjectMap, minimumMotionMetric, scene.activeEventId !== null));
38
+ const focusTargets = spatialObjects.map((object) => ({
39
+ objectId: object.objectId,
40
+ center: { ...object.position },
41
+ radius: object.visualRadius + scaleModel.focusPadding,
42
+ }));
43
+ return {
44
+ width: scene.width,
45
+ height: scene.height,
46
+ padding: scene.padding,
47
+ renderPreset: scene.renderPreset,
48
+ projection: scene.projection,
49
+ camera: scene.camera,
50
+ scaleModel,
51
+ title: scene.title,
52
+ subtitle: scene.subtitle,
53
+ systemId: scene.systemId,
54
+ viewMode: "3d",
55
+ layoutPreset: scene.layoutPreset,
56
+ metadata: {
57
+ ...scene.metadata,
58
+ "viewer.mode": "3d",
59
+ },
60
+ contentBounds: calculateSpatialBounds(spatialObjects),
61
+ semanticGroups: scene.semanticGroups,
62
+ viewpoints: scene.viewpoints,
63
+ activeEventId: scene.activeEventId,
64
+ timeFrozen: scene.activeEventId !== null,
65
+ objects: spatialObjects,
66
+ orbits: spatialOrbits,
67
+ focusTargets,
68
+ };
69
+ }
70
+ function createSpatialObject(entry, scene, sceneCenter, objectMap, orbitMap, scaleModel, positionCache, minimumMotionMetric) {
71
+ const position = resolveSpatialObjectPosition(entry, scene, sceneCenter, objectMap, orbitMap, positionCache);
72
+ const motion = createMotionModel(entry.object, orbitMap.get(entry.objectId), minimumMotionMetric, scene.activeEventId !== null);
73
+ return {
74
+ objectId: entry.objectId,
75
+ object: entry.object,
76
+ parentId: entry.parentId,
77
+ ancestorIds: entry.ancestorIds.slice(),
78
+ childIds: entry.childIds.slice(),
79
+ groupId: entry.groupId,
80
+ semanticGroupIds: entry.semanticGroupIds.slice(),
81
+ position,
82
+ radius: clampNumber(entry.radius * scaleModel.bodyRadiusMultiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius),
83
+ visualRadius: clampNumber(entry.visualRadius * scaleModel.bodyRadiusMultiplier, scaleModel.minBodyRadius, scaleModel.maxBodyRadius + 24),
84
+ label: entry.label,
85
+ secondaryLabel: entry.secondaryLabel,
86
+ fillColor: entry.fillColor,
87
+ imageHref: entry.imageHref,
88
+ hidden: entry.hidden,
89
+ motion,
90
+ };
91
+ }
92
+ function createSpatialOrbit(orbit, objectMap, minimumMotionMetric, frozen) {
93
+ const owner = objectMap.get(orbit.objectId);
94
+ const parent = objectMap.get(orbit.parentId);
95
+ return {
96
+ objectId: orbit.objectId,
97
+ object: orbit.object,
98
+ parentId: orbit.parentId,
99
+ groupId: orbit.groupId,
100
+ semanticGroupIds: orbit.semanticGroupIds.slice(),
101
+ center: parent?.position ?? { x: 0, y: 0, z: 0 },
102
+ kind: orbit.kind,
103
+ radius: orbit.radius,
104
+ semiMajor: orbit.radius ?? orbit.rx ?? 0,
105
+ semiMinor: orbit.radius ?? orbit.ry ?? 0,
106
+ rotationDeg: orbit.rotationDeg,
107
+ inclinationDeg: owner?.motion?.inclinationDeg ??
108
+ unitValueToDegrees(orbit.object.placement?.mode === "orbit"
109
+ ? orbit.object.placement.inclination
110
+ : undefined) ??
111
+ 0,
112
+ band: orbit.band,
113
+ bandThickness: orbit.bandThickness,
114
+ hidden: orbit.hidden,
115
+ motion: owner?.motion ??
116
+ createMotionModel(orbit.object, orbit, minimumMotionMetric, frozen),
117
+ };
118
+ }
119
+ function resolveSpatialObjectPosition(entry, scene, sceneCenter, objectMap, orbitMap, cache) {
120
+ const cached = cache.get(entry.objectId);
121
+ if (cached) {
122
+ return cached;
123
+ }
124
+ const placement = entry.object.placement;
125
+ let position;
126
+ if (placement?.mode === "orbit" && entry.parentId) {
127
+ const parent = objectMap.get(entry.parentId);
128
+ const parentPosition = parent
129
+ ? resolveSpatialObjectPosition(parent, scene, sceneCenter, objectMap, orbitMap, cache)
130
+ : { x: 0, y: 0, z: 0 };
131
+ const orbit = orbitMap.get(entry.objectId);
132
+ const motion = createMotionModel(entry.object, orbit, 1, scene.activeEventId !== null);
133
+ const local = motion
134
+ ? computeOrbitPosition(motion, 0)
135
+ : {
136
+ x: (entry.x - sceneCenter.x) * 0.8,
137
+ y: 0,
138
+ z: (entry.y - sceneCenter.y) * 0.8,
139
+ };
140
+ position = addPoint3D(parentPosition, local);
141
+ }
142
+ else if (placement?.mode === "surface" && entry.parentId) {
143
+ const parent = objectMap.get(entry.parentId);
144
+ const parentPosition = parent
145
+ ? resolveSpatialObjectPosition(parent, scene, sceneCenter, objectMap, orbitMap, cache)
146
+ : { x: 0, y: 0, z: 0 };
147
+ const parentRadius = parent?.visualRadius ?? 16;
148
+ const seed = hashUnit(entry.objectId);
149
+ const angle = seed * Math.PI * 2;
150
+ position = {
151
+ x: parentPosition.x + Math.cos(angle) * (parentRadius + entry.visualRadius * 0.9),
152
+ y: parentPosition.y + Math.sin(seed * Math.PI) * Math.max(entry.visualRadius * 0.2, 2),
153
+ z: parentPosition.z + Math.sin(angle) * (parentRadius + entry.visualRadius * 0.9),
154
+ };
155
+ }
156
+ else if (placement?.mode === "at" && entry.parentId) {
157
+ const parent = objectMap.get(entry.parentId);
158
+ const parentPosition = parent
159
+ ? resolveSpatialObjectPosition(parent, scene, sceneCenter, objectMap, orbitMap, cache)
160
+ : { x: 0, y: 0, z: 0 };
161
+ const anchorX = entry.anchorX ?? parent?.x ?? sceneCenter.x;
162
+ const anchorY = entry.anchorY ?? parent?.y ?? sceneCenter.y;
163
+ position = {
164
+ x: parentPosition.x + (entry.x - anchorX),
165
+ y: parentPosition.y,
166
+ z: parentPosition.z + (entry.y - anchorY),
167
+ };
168
+ }
169
+ else {
170
+ position = {
171
+ x: (entry.x - sceneCenter.x) * 0.8,
172
+ y: 0,
173
+ z: (entry.y - sceneCenter.y) * 0.8,
174
+ };
175
+ }
176
+ cache.set(entry.objectId, position);
177
+ return position;
178
+ }
179
+ export function evaluateSpatialSceneAtTime(scene, timeSeconds) {
180
+ const objectMap = new Map(scene.objects.map((object) => [object.objectId, object]));
181
+ const cache = new Map();
182
+ const resolvePosition = (objectId) => {
183
+ const cached = cache.get(objectId);
184
+ if (cached) {
185
+ return cached;
186
+ }
187
+ const object = objectMap.get(objectId);
188
+ if (!object) {
189
+ return { x: 0, y: 0, z: 0 };
190
+ }
191
+ let next = { ...object.position };
192
+ if (object.motion && object.parentId) {
193
+ const parentPosition = resolvePosition(object.parentId);
194
+ const local = computeOrbitPosition(object.motion, scene.timeFrozen ? 0 : timeSeconds);
195
+ next = addPoint3D(parentPosition, local);
196
+ }
197
+ cache.set(objectId, next);
198
+ return next;
199
+ };
200
+ for (const object of scene.objects) {
201
+ resolvePosition(object.objectId);
202
+ }
203
+ return cache;
204
+ }
205
+ function computeOrbitPosition(motion, timeSeconds) {
206
+ const angleDeg = motion.phase0Deg + motion.angularVelocityDegPerSecond * timeSeconds;
207
+ const angle = degreesToRadians(angleDeg);
208
+ const rotation = degreesToRadians(motion.rotationDeg);
209
+ const inclination = degreesToRadians(motion.inclinationDeg);
210
+ const localX = Math.cos(angle) * motion.semiMajor;
211
+ const localZ = Math.sin(angle) * motion.semiMinor;
212
+ const rotatedX = localX * Math.cos(rotation) - localZ * Math.sin(rotation);
213
+ const rotatedZ = localX * Math.sin(rotation) + localZ * Math.cos(rotation);
214
+ return {
215
+ x: rotatedX,
216
+ y: rotatedZ * Math.sin(inclination),
217
+ z: rotatedZ * Math.cos(inclination),
218
+ };
219
+ }
220
+ function createMotionModel(object, orbit, minimumMotionMetric, frozen) {
221
+ const placement = object.placement;
222
+ if (!placement || placement.mode !== "orbit") {
223
+ return null;
224
+ }
225
+ const semiMajor = orbit?.radius ??
226
+ orbit?.rx ??
227
+ clampNumber(resolveRawOrbitMetric(placement) * 48, 24, 1_200);
228
+ const semiMinor = orbit?.radius ?? orbit?.ry ?? semiMajor;
229
+ const periodSeconds = unitValueToDurationSeconds(placement.period);
230
+ const rawMetric = resolveRawOrbitMetric(placement);
231
+ const ratio = clampNumber(rawMetric / Math.max(minimumMotionMetric, 0.0001), 1, 20);
232
+ const visualDurationSeconds = periodSeconds
233
+ ? clampNumber(FASTEST_VISIBLE_ORBIT_SECONDS * ratio, FASTEST_VISIBLE_ORBIT_SECONDS, SLOWEST_VISIBLE_ORBIT_SECONDS)
234
+ : clampNumber(FASTEST_VISIBLE_ORBIT_SECONDS * Math.pow(ratio, 0.75), FASTEST_VISIBLE_ORBIT_SECONDS, SLOWEST_VISIBLE_ORBIT_SECONDS);
235
+ return {
236
+ phase0Deg: unitValueToDegrees(placement.phase) ?? 0,
237
+ rotationDeg: unitValueToDegrees(placement.angle) ??
238
+ orbit?.rotationDeg ??
239
+ 0,
240
+ inclinationDeg: unitValueToDegrees(placement.inclination) ?? 0,
241
+ semiMajor,
242
+ semiMinor,
243
+ eccentricity: placement.eccentricity ?? 0,
244
+ periodSeconds,
245
+ angularVelocityDegPerSecond: 360 / Math.max(visualDurationSeconds, 0.001),
246
+ heuristic: periodSeconds === null,
247
+ frozen,
248
+ };
249
+ }
250
+ function resolveRawOrbitMetric(placement) {
251
+ const distance = unitValueToDistanceMetric(placement.semiMajor) ??
252
+ unitValueToDistanceMetric(placement.distance) ??
253
+ 1;
254
+ return Math.max(distance, 0.01);
255
+ }
256
+ function unitValueToDistanceMetric(value) {
257
+ if (!value) {
258
+ return null;
259
+ }
260
+ switch (value.unit) {
261
+ case "au":
262
+ return value.value;
263
+ case "km":
264
+ return value.value / 149_597_870.7;
265
+ case "m":
266
+ return value.value / 149_597_870_700;
267
+ case "re":
268
+ return (value.value * 6_371) / 149_597_870.7;
269
+ case "rj":
270
+ return (value.value * 71_492) / 149_597_870.7;
271
+ case "sol":
272
+ return (value.value * 695_700) / 149_597_870.7;
273
+ case "ly":
274
+ return value.value * 63_241.077;
275
+ case "pc":
276
+ return value.value * 206_264.806;
277
+ case "kpc":
278
+ return value.value * 206_264_806;
279
+ default:
280
+ return value.value;
281
+ }
282
+ }
283
+ function unitValueToDurationSeconds(value) {
284
+ if (!value) {
285
+ return null;
286
+ }
287
+ switch (value.unit) {
288
+ case "s":
289
+ return value.value;
290
+ case "min":
291
+ return value.value * 60;
292
+ case "h":
293
+ return value.value * 3_600;
294
+ case "d":
295
+ return value.value * DAY_IN_SECONDS;
296
+ case "y":
297
+ return value.value * YEAR_IN_SECONDS;
298
+ case "ky":
299
+ return value.value * YEAR_IN_SECONDS * 1_000;
300
+ case "my":
301
+ return value.value * YEAR_IN_SECONDS * 1_000_000;
302
+ case "gy":
303
+ return value.value * YEAR_IN_SECONDS * 1_000_000_000;
304
+ default:
305
+ return null;
306
+ }
307
+ }
308
+ function unitValueToDegrees(value) {
309
+ if (!value) {
310
+ return null;
311
+ }
312
+ return value.unit === "deg" || value.unit === null ? value.value : null;
313
+ }
314
+ function resolveSpatialScaleModel(layoutPreset, overrides) {
315
+ const defaults = defaultSpatialScaleModel(layoutPreset);
316
+ return {
317
+ ...defaults,
318
+ ...overrides,
319
+ };
320
+ }
321
+ function defaultSpatialScaleModel(layoutPreset) {
322
+ switch (layoutPreset) {
323
+ case "compact":
324
+ return {
325
+ orbitDistanceMultiplier: 0.92,
326
+ bodyRadiusMultiplier: 0.92,
327
+ markerSizeMultiplier: 0.92,
328
+ ringThicknessMultiplier: 0.9,
329
+ focusPadding: 10,
330
+ minBodyRadius: 4,
331
+ maxBodyRadius: 34,
332
+ };
333
+ case "presentation":
334
+ return {
335
+ orbitDistanceMultiplier: 1.15,
336
+ bodyRadiusMultiplier: 1.12,
337
+ markerSizeMultiplier: 1.08,
338
+ ringThicknessMultiplier: 1.14,
339
+ focusPadding: 16,
340
+ minBodyRadius: 5,
341
+ maxBodyRadius: 44,
342
+ };
343
+ default:
344
+ return {
345
+ orbitDistanceMultiplier: 1,
346
+ bodyRadiusMultiplier: 1,
347
+ markerSizeMultiplier: 1,
348
+ ringThicknessMultiplier: 1,
349
+ focusPadding: 12,
350
+ minBodyRadius: 4,
351
+ maxBodyRadius: 40,
352
+ };
353
+ }
354
+ }
355
+ function calculateSpatialBounds(objects) {
356
+ if (objects.length === 0) {
357
+ return {
358
+ minX: 0,
359
+ minY: 0,
360
+ minZ: 0,
361
+ maxX: 0,
362
+ maxY: 0,
363
+ maxZ: 0,
364
+ width: 0,
365
+ height: 0,
366
+ depth: 0,
367
+ center: { x: 0, y: 0, z: 0 },
368
+ };
369
+ }
370
+ let minX = Number.POSITIVE_INFINITY;
371
+ let minY = Number.POSITIVE_INFINITY;
372
+ let minZ = Number.POSITIVE_INFINITY;
373
+ let maxX = Number.NEGATIVE_INFINITY;
374
+ let maxY = Number.NEGATIVE_INFINITY;
375
+ let maxZ = Number.NEGATIVE_INFINITY;
376
+ for (const object of objects) {
377
+ minX = Math.min(minX, object.position.x - object.visualRadius);
378
+ minY = Math.min(minY, object.position.y - object.visualRadius);
379
+ minZ = Math.min(minZ, object.position.z - object.visualRadius);
380
+ maxX = Math.max(maxX, object.position.x + object.visualRadius);
381
+ maxY = Math.max(maxY, object.position.y + object.visualRadius);
382
+ maxZ = Math.max(maxZ, object.position.z + object.visualRadius);
383
+ }
384
+ return {
385
+ minX,
386
+ minY,
387
+ minZ,
388
+ maxX,
389
+ maxY,
390
+ maxZ,
391
+ width: maxX - minX,
392
+ height: maxY - minY,
393
+ depth: maxZ - minZ,
394
+ center: {
395
+ x: (minX + maxX) / 2,
396
+ y: (minY + maxY) / 2,
397
+ z: (minZ + maxZ) / 2,
398
+ },
399
+ };
400
+ }
401
+ function addPoint3D(left, right) {
402
+ return {
403
+ x: left.x + right.x,
404
+ y: left.y + right.y,
405
+ z: left.z + right.z,
406
+ };
407
+ }
408
+ function hashUnit(value) {
409
+ let hash = 0;
410
+ for (let index = 0; index < value.length; index += 1) {
411
+ hash = (hash * 31 + value.charCodeAt(index)) >>> 0;
412
+ }
413
+ return (hash % 10_000) / 10_000;
414
+ }
415
+ function clampNumber(value, min, max) {
416
+ return Math.min(Math.max(value, min), max);
417
+ }
418
+ function degreesToRadians(value) {
419
+ return (value * Math.PI) / 180;
420
+ }
@@ -12,6 +12,11 @@ export interface CoordinatePoint {
12
12
  x: number;
13
13
  y: number;
14
14
  }
15
+ export interface CoordinatePoint3D {
16
+ x: number;
17
+ y: number;
18
+ z: number;
19
+ }
15
20
  export interface UnitValue {
16
21
  value: number;
17
22
  unit: Unit | null;
@@ -249,6 +254,18 @@ export interface SceneRenderOptions {
249
254
  scaleModel?: Partial<RenderScaleModel>;
250
255
  activeEventId?: string | null;
251
256
  }
257
+ export interface SpatialScaleModel {
258
+ orbitDistanceMultiplier: number;
259
+ bodyRadiusMultiplier: number;
260
+ markerSizeMultiplier: number;
261
+ ringThicknessMultiplier: number;
262
+ focusPadding: number;
263
+ minBodyRadius: number;
264
+ maxBodyRadius: number;
265
+ }
266
+ export interface SpatialSceneRenderOptions extends SceneRenderOptions {
267
+ spatialScaleModel?: Partial<SpatialScaleModel>;
268
+ }
252
269
  export interface RenderBounds {
253
270
  minX: number;
254
271
  minY: number;
@@ -400,6 +417,71 @@ export interface RenderSceneRelation {
400
417
  y2: number;
401
418
  hidden: boolean;
402
419
  }
420
+ export interface SpatialBounds {
421
+ minX: number;
422
+ minY: number;
423
+ minZ: number;
424
+ maxX: number;
425
+ maxY: number;
426
+ maxZ: number;
427
+ width: number;
428
+ height: number;
429
+ depth: number;
430
+ center: CoordinatePoint3D;
431
+ }
432
+ export interface OrbitalMotionModel {
433
+ phase0Deg: number;
434
+ rotationDeg: number;
435
+ inclinationDeg: number;
436
+ semiMajor: number;
437
+ semiMinor: number;
438
+ eccentricity: number;
439
+ periodSeconds: number | null;
440
+ angularVelocityDegPerSecond: number;
441
+ heuristic: boolean;
442
+ frozen: boolean;
443
+ }
444
+ export interface SpatialSceneObject {
445
+ objectId: string;
446
+ object: WorldOrbitObject;
447
+ parentId: string | null;
448
+ ancestorIds: string[];
449
+ childIds: string[];
450
+ groupId: string | null;
451
+ semanticGroupIds: string[];
452
+ position: CoordinatePoint3D;
453
+ radius: number;
454
+ visualRadius: number;
455
+ label: string;
456
+ secondaryLabel: string;
457
+ fillColor?: string;
458
+ imageHref?: string;
459
+ hidden: boolean;
460
+ motion: OrbitalMotionModel | null;
461
+ }
462
+ export interface SpatialOrbit {
463
+ objectId: string;
464
+ object: WorldOrbitObject;
465
+ parentId: string;
466
+ groupId: string | null;
467
+ semanticGroupIds: string[];
468
+ center: CoordinatePoint3D;
469
+ kind: "circle" | "ellipse";
470
+ radius?: number;
471
+ semiMajor: number;
472
+ semiMinor: number;
473
+ rotationDeg: number;
474
+ inclinationDeg: number;
475
+ band: boolean;
476
+ bandThickness?: number;
477
+ hidden: boolean;
478
+ motion: OrbitalMotionModel | null;
479
+ }
480
+ export interface SpatialFocusTarget {
481
+ objectId: string;
482
+ center: CoordinatePoint3D;
483
+ radius: number;
484
+ }
403
485
  export interface RenderScene {
404
486
  width: number;
405
487
  height: number;
@@ -428,6 +510,29 @@ export interface RenderScene {
428
510
  leaders: RenderLeaderLine[];
429
511
  labels: RenderSceneLabel[];
430
512
  }
513
+ export interface SpatialScene {
514
+ width: number;
515
+ height: number;
516
+ padding: number;
517
+ renderPreset: RenderPresetName | null;
518
+ projection: ViewProjection;
519
+ camera: WorldOrbitViewCamera | null;
520
+ scaleModel: SpatialScaleModel;
521
+ title: string;
522
+ subtitle: string;
523
+ systemId: string | null;
524
+ viewMode: "3d";
525
+ layoutPreset: SceneLayoutPreset;
526
+ metadata: Record<string, string>;
527
+ contentBounds: SpatialBounds;
528
+ semanticGroups: RenderSceneSemanticGroup[];
529
+ viewpoints: RenderSceneViewpoint[];
530
+ activeEventId: string | null;
531
+ timeFrozen: boolean;
532
+ objects: SpatialSceneObject[];
533
+ orbits: SpatialOrbit[];
534
+ focusTargets: SpatialFocusTarget[];
535
+ }
431
536
  export type SceneLayoutPreset = "compact" | "balanced" | "presentation";
432
537
  export type FieldValueKind = "string" | "list" | "boolean" | "number" | "unit";
433
538
  export type UnitFamily = "distance" | "radius" | "mass" | "duration" | "angle" | "generic";
@@ -246,6 +246,7 @@ export function createWorldOrbitEditor(container, options = {}) {
246
246
  const showInspector = options.showInspector ?? true;
247
247
  const showPreview = options.showPreview ?? true;
248
248
  const shortcutsEnabled = options.shortcuts ?? true;
249
+ let viewMode = options.viewMode ?? "2d";
249
250
  container.classList.add("wo-editor");
250
251
  container.dataset.woShowInspector = String(showInspector);
251
252
  container.dataset.woShowTextPane = String(showTextPane);
@@ -271,6 +272,7 @@ export function createWorldOrbitEditor(container, options = {}) {
271
272
  height: options.viewerHeight ?? 680,
272
273
  preset: atlasDocument.system?.defaults.preset ?? "atlas-card",
273
274
  projection: "document",
275
+ viewMode,
274
276
  minimap: true,
275
277
  tooltipMode: "hover",
276
278
  onSelectionChange(selectedObject) {
@@ -337,6 +339,22 @@ export function createWorldOrbitEditor(container, options = {}) {
337
339
  getSelection() {
338
340
  return selection ? { path: { ...selection } } : null;
339
341
  },
342
+ getViewMode() {
343
+ return viewMode;
344
+ },
345
+ setViewMode(mode) {
346
+ const previousViewMode = viewMode;
347
+ try {
348
+ viewer.setViewMode(mode);
349
+ viewMode = mode;
350
+ renderStageOverlay();
351
+ renderPreviewNow();
352
+ }
353
+ catch (error) {
354
+ viewMode = previousViewMode;
355
+ throw error;
356
+ }
357
+ },
340
358
  isDirty() {
341
359
  return dirty;
342
360
  },
@@ -461,7 +479,7 @@ export function createWorldOrbitEditor(container, options = {}) {
461
479
  return viewer.exportSvg();
462
480
  },
463
481
  exportEmbedMarkup() {
464
- return buildEmbedMarkup(getCurrentSourceForExport(), atlasDocument);
482
+ return buildEmbedMarkup(getCurrentSourceForExport(), atlasDocument, viewMode);
465
483
  },
466
484
  destroy() {
467
485
  if (destroyed) {
@@ -906,6 +924,9 @@ export function createWorldOrbitEditor(container, options = {}) {
906
924
  return;
907
925
  }
908
926
  overlay.innerHTML = "";
927
+ if (viewMode === "3d") {
928
+ return;
929
+ }
909
930
  const selectedObjectId = selection?.kind === "object"
910
931
  ? selection.id ?? null
911
932
  : selection?.kind === "event-pose"
@@ -1577,7 +1598,7 @@ export function createWorldOrbitEditor(container, options = {}) {
1577
1598
  previewVisual.innerHTML = nextSvg;
1578
1599
  lastPreviewSvg = nextSvg;
1579
1600
  }
1580
- const nextMarkup = buildEmbedMarkup(getCurrentSourceForExport(), atlasDocument);
1601
+ const nextMarkup = buildEmbedMarkup(getCurrentSourceForExport(), atlasDocument, viewMode);
1581
1602
  if (previewMarkup && nextMarkup !== lastPreviewMarkup) {
1582
1603
  previewMarkup.textContent = nextMarkup;
1583
1604
  lastPreviewMarkup = nextMarkup;
@@ -2969,9 +2990,9 @@ function mapDiagnosticFieldToInputNames(selection, field) {
2969
2990
  return [`prop-${field}`];
2970
2991
  }
2971
2992
  }
2972
- function buildEmbedMarkup(source, document) {
2993
+ function buildEmbedMarkup(source, document, viewMode) {
2973
2994
  return renderWorldOrbitBlock(source, {
2974
- mode: "interactive",
2995
+ mode: viewMode === "3d" ? "interactive-3d" : "interactive-2d",
2975
2996
  preset: document.system?.defaults.preset ?? "atlas-card",
2976
2997
  projection: document.system?.defaults.view ?? "topdown",
2977
2998
  });
@@ -1,4 +1,5 @@
1
1
  import type { AtlasDocumentPath, AtlasResolvedDiagnostic, WorldOrbitAtlasDocument, WorldOrbitEvent, WorldOrbitAtlasSystem, WorldOrbitAtlasViewpoint, WorldOrbitObject } from "@worldorbit/core";
2
+ import type { WorldOrbitViewMode } from "@worldorbit/viewer";
2
3
  export interface WorldOrbitEditorSelection {
3
4
  path: AtlasDocumentPath | null;
4
5
  }
@@ -16,6 +17,7 @@ export interface WorldOrbitEditorOptions {
16
17
  showPreview?: boolean;
17
18
  viewerWidth?: number;
18
19
  viewerHeight?: number;
20
+ viewMode?: WorldOrbitViewMode;
19
21
  shortcuts?: boolean;
20
22
  onChange?: (snapshot: WorldOrbitEditorSnapshot) => void;
21
23
  onDiagnosticsChange?: (diagnostics: AtlasResolvedDiagnostic[]) => void;
@@ -29,6 +31,8 @@ export interface WorldOrbitEditor {
29
31
  getAtlasDocument(): WorldOrbitAtlasDocument;
30
32
  getDiagnostics(): AtlasResolvedDiagnostic[];
31
33
  getSelection(): WorldOrbitEditorSelection | null;
34
+ getViewMode(): WorldOrbitViewMode;
35
+ setViewMode(mode: WorldOrbitViewMode): void;
32
36
  isDirty(): boolean;
33
37
  markSaved(): void;
34
38
  selectPath(path: AtlasDocumentPath | null): void;