worldorbit 2.5.16 → 2.5.17

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 (34) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/core/dist/index.js +750 -73
  3. package/dist/browser/editor/dist/index.js +1303 -135
  4. package/dist/browser/markdown/dist/index.js +631 -72
  5. package/dist/browser/viewer/dist/index.js +658 -77
  6. package/dist/unpkg/core/dist/index.js +750 -73
  7. package/dist/unpkg/editor/dist/index.js +1303 -135
  8. package/dist/unpkg/markdown/dist/index.js +631 -72
  9. package/dist/unpkg/viewer/dist/index.js +658 -77
  10. package/dist/unpkg/worldorbit-core.min.js +12 -12
  11. package/dist/unpkg/worldorbit-editor.min.js +284 -202
  12. package/dist/unpkg/worldorbit-markdown.min.js +66 -58
  13. package/dist/unpkg/worldorbit-viewer.min.js +76 -68
  14. package/dist/unpkg/worldorbit.js +797 -78
  15. package/dist/unpkg/worldorbit.min.js +80 -72
  16. package/package.json +1 -1
  17. package/packages/core/dist/atlas-edit.js +74 -0
  18. package/packages/core/dist/atlas-validate.js +122 -8
  19. package/packages/core/dist/draft-parse.js +212 -8
  20. package/packages/core/dist/draft.d.ts +5 -2
  21. package/packages/core/dist/draft.js +59 -3
  22. package/packages/core/dist/format.js +63 -1
  23. package/packages/core/dist/normalize.js +1 -0
  24. package/packages/core/dist/scene.js +248 -46
  25. package/packages/core/dist/types.d.ts +41 -2
  26. package/packages/editor/dist/editor.js +597 -61
  27. package/packages/editor/dist/types.d.ts +3 -1
  28. package/packages/viewer/dist/atlas-state.js +6 -0
  29. package/packages/viewer/dist/atlas-viewer.js +1 -0
  30. package/packages/viewer/dist/render.js +31 -2
  31. package/packages/viewer/dist/theme.js +1 -0
  32. package/packages/viewer/dist/tooltip.js +9 -0
  33. package/packages/viewer/dist/types.d.ts +8 -1
  34. package/packages/viewer/dist/viewer.js +12 -1
@@ -18,8 +18,10 @@ export function renderDocumentToScene(document, options = {}) {
18
18
  const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
19
19
  const spacingFactor = layoutPresetSpacing(layoutPreset);
20
20
  const systemId = document.system?.id ?? null;
21
- const objectMap = new Map(document.objects.map((object) => [object.id, object]));
22
- const relationships = buildSceneRelationships(document.objects, objectMap);
21
+ const activeEventId = options.activeEventId ?? null;
22
+ const effectiveObjects = createEffectiveObjects(document.objects, document.events ?? [], activeEventId);
23
+ const objectMap = new Map(effectiveObjects.map((object) => [object.id, object]));
24
+ const relationships = buildSceneRelationships(effectiveObjects, objectMap);
23
25
  const positions = new Map();
24
26
  const orbitDrafts = [];
25
27
  const leaderDrafts = [];
@@ -28,7 +30,7 @@ export function renderDocumentToScene(document, options = {}) {
28
30
  const atObjects = [];
29
31
  const surfaceChildren = new Map();
30
32
  const orbitChildren = new Map();
31
- for (const object of document.objects) {
33
+ for (const object of effectiveObjects) {
32
34
  const placement = object.placement;
33
35
  if (!placement) {
34
36
  rootObjects.push(object);
@@ -129,13 +131,14 @@ export function renderDocumentToScene(document, options = {}) {
129
131
  const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
130
132
  const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
131
133
  const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
132
- const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
134
+ const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
133
135
  const relations = createSceneRelations(document, objects);
134
- const layers = createSceneLayers(orbitVisuals, relations, leaders, objects, labels);
135
- const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships);
136
+ const events = createSceneEvents(document.events ?? [], objects, activeEventId);
137
+ const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
138
+ const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
136
139
  const semanticGroups = createSceneSemanticGroups(document, objects);
137
140
  const viewpoints = createSceneViewpoints(document, projection, frame.preset, relationships, objectMap);
138
- const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
141
+ const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
139
142
  return {
140
143
  width,
141
144
  height,
@@ -162,6 +165,8 @@ export function renderDocumentToScene(document, options = {}) {
162
165
  groups,
163
166
  semanticGroups,
164
167
  viewpoints,
168
+ events,
169
+ activeEventId,
165
170
  objects,
166
171
  orbitVisuals,
167
172
  relations,
@@ -180,6 +185,37 @@ export function rotatePoint(point, center, rotationDeg) {
180
185
  y: center.y + dx * sin + dy * cos,
181
186
  };
182
187
  }
188
+ function createEffectiveObjects(objects, events, activeEventId) {
189
+ const cloned = objects.map((object) => structuredClone(object));
190
+ if (!activeEventId) {
191
+ return cloned;
192
+ }
193
+ const activeEvent = events.find((event) => event.id === activeEventId);
194
+ if (!activeEvent) {
195
+ return cloned;
196
+ }
197
+ const objectMap = new Map(cloned.map((object) => [object.id, object]));
198
+ for (const pose of activeEvent.positions) {
199
+ const object = objectMap.get(pose.objectId);
200
+ if (!object) {
201
+ continue;
202
+ }
203
+ object.placement = pose.placement ? structuredClone(pose.placement) : null;
204
+ if (pose.inner) {
205
+ object.properties.inner = { ...pose.inner };
206
+ }
207
+ else {
208
+ delete object.properties.inner;
209
+ }
210
+ if (pose.outer) {
211
+ object.properties.outer = { ...pose.outer };
212
+ }
213
+ else {
214
+ delete object.properties.outer;
215
+ }
216
+ }
217
+ return cloned;
218
+ }
183
219
  function resolveLayoutPreset(document) {
184
220
  const rawScale = String(document.system?.properties.scale ?? "balanced").toLowerCase();
185
221
  switch (rawScale) {
@@ -339,26 +375,17 @@ function createLeaderLine(draft) {
339
375
  hidden: draft.object.properties.hidden === true,
340
376
  };
341
377
  }
342
- function createSceneLabels(objects, sceneHeight, labelMultiplier) {
378
+ function createSceneLabels(objects, sceneWidth, sceneHeight, labelMultiplier) {
343
379
  const labels = [];
344
380
  const occupied = [];
381
+ const objectMap = new Map(objects.map((object) => [object.objectId, object]));
345
382
  const visibleObjects = [...objects]
346
383
  .filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false)
347
- .sort((left, right) => left.sortKey - right.sortKey);
384
+ .sort(compareLabelPlacementOrder);
348
385
  for (const object of visibleObjects) {
349
- const direction = object.y > sceneHeight * 0.62 ? -1 : 1;
350
- const labelHalfWidth = estimateLabelHalfWidth(object, labelMultiplier);
351
- let labelY = object.y + direction * (object.radius + 18 * labelMultiplier);
352
- let secondaryY = labelY + direction * (16 * labelMultiplier);
353
- let bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
354
- let attempts = 0;
355
- while (occupied.some((entry) => rectsOverlap(entry, bounds)) && attempts < 10) {
356
- labelY += direction * 14 * labelMultiplier;
357
- secondaryY += direction * 14 * labelMultiplier;
358
- bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
359
- attempts += 1;
360
- }
361
- occupied.push(bounds);
386
+ const placement = selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) ??
387
+ createLabelPlacement(object, defaultVerticalDirection(object, objectMap.get(object.parentId ?? "") ?? null, sceneHeight), 0, labelMultiplier);
388
+ occupied.push(createLabelRect(object, placement, labelMultiplier));
362
389
  labels.push({
363
390
  renderId: `${object.renderId}-label`,
364
391
  objectId: object.objectId,
@@ -367,17 +394,134 @@ function createSceneLabels(objects, sceneHeight, labelMultiplier) {
367
394
  semanticGroupIds: [...object.semanticGroupIds],
368
395
  label: object.label,
369
396
  secondaryLabel: object.secondaryLabel,
370
- x: object.x,
371
- y: labelY,
372
- secondaryY,
373
- textAnchor: "middle",
374
- direction: direction < 0 ? "above" : "below",
397
+ x: placement.x,
398
+ y: placement.labelY,
399
+ secondaryY: placement.secondaryY,
400
+ textAnchor: placement.textAnchor,
401
+ direction: placement.direction,
375
402
  hidden: object.hidden,
376
403
  });
377
404
  }
378
405
  return labels;
379
406
  }
380
- function createSceneLayers(orbitVisuals, relations, leaders, objects, labels) {
407
+ function compareLabelPlacementOrder(left, right) {
408
+ const priorityDiff = labelPlacementPriority(left) - labelPlacementPriority(right);
409
+ if (priorityDiff !== 0) {
410
+ return priorityDiff;
411
+ }
412
+ const renderPriorityDiff = (right.object.renderHints?.renderPriority ?? 0) - (left.object.renderHints?.renderPriority ?? 0);
413
+ if (renderPriorityDiff !== 0) {
414
+ return renderPriorityDiff;
415
+ }
416
+ return left.sortKey - right.sortKey;
417
+ }
418
+ function labelPlacementPriority(object) {
419
+ switch (object.object.type) {
420
+ case "star":
421
+ return 0;
422
+ case "planet":
423
+ return 1;
424
+ case "moon":
425
+ return 2;
426
+ case "belt":
427
+ case "ring":
428
+ return 3;
429
+ case "asteroid":
430
+ case "comet":
431
+ return 4;
432
+ case "structure":
433
+ case "phenomenon":
434
+ return 5;
435
+ }
436
+ }
437
+ function selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) {
438
+ for (const direction of preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight)) {
439
+ const maxAttempts = direction === "left" || direction === "right" ? 4 : 6;
440
+ for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
441
+ const placement = createLabelPlacement(object, direction, attempt, labelMultiplier);
442
+ const rect = createLabelRect(object, placement, labelMultiplier);
443
+ if (!occupied.some((entry) => rectsOverlap(entry, rect))) {
444
+ return placement;
445
+ }
446
+ }
447
+ }
448
+ return null;
449
+ }
450
+ function preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight) {
451
+ const parent = object.parentId ? objectMap.get(object.parentId) ?? null : null;
452
+ const vertical = defaultVerticalDirection(object, parent, sceneHeight);
453
+ const oppositeVertical = vertical === "below" ? "above" : "below";
454
+ const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
455
+ const oppositeHorizontal = horizontal === "right" ? "left" : "right";
456
+ const preferHorizontal = object.object.type === "structure" ||
457
+ object.object.type === "phenomenon" ||
458
+ object.object.placement?.mode === "at" ||
459
+ object.object.placement?.mode === "surface" ||
460
+ object.object.placement?.mode === "free";
461
+ return preferHorizontal
462
+ ? [horizontal, vertical, oppositeHorizontal, oppositeVertical]
463
+ : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
464
+ }
465
+ function defaultVerticalDirection(object, parent, sceneHeight) {
466
+ if (parent && Math.abs(object.y - parent.y) > 6) {
467
+ return object.y >= parent.y ? "below" : "above";
468
+ }
469
+ return object.y > sceneHeight * 0.62 ? "above" : "below";
470
+ }
471
+ function defaultHorizontalDirection(object, parent, sceneWidth) {
472
+ if (parent && Math.abs(object.x - parent.x) > 6) {
473
+ return object.x >= parent.x ? "right" : "left";
474
+ }
475
+ return object.x >= sceneWidth / 2 ? "right" : "left";
476
+ }
477
+ function createLabelPlacement(object, direction, attempt, labelMultiplier) {
478
+ const step = 14 * labelMultiplier;
479
+ switch (direction) {
480
+ case "above": {
481
+ const labelY = object.y - (object.radius + 18 * labelMultiplier + attempt * step);
482
+ return {
483
+ x: object.x,
484
+ labelY,
485
+ secondaryY: labelY - 16 * labelMultiplier,
486
+ textAnchor: "middle",
487
+ direction,
488
+ };
489
+ }
490
+ case "below": {
491
+ const labelY = object.y + object.radius + 18 * labelMultiplier + attempt * step;
492
+ return {
493
+ x: object.x,
494
+ labelY,
495
+ secondaryY: labelY + 16 * labelMultiplier,
496
+ textAnchor: "middle",
497
+ direction,
498
+ };
499
+ }
500
+ case "left": {
501
+ const x = object.x - (object.visualRadius + 16 * labelMultiplier + attempt * step);
502
+ const labelY = object.y - 4 * labelMultiplier;
503
+ return {
504
+ x,
505
+ labelY,
506
+ secondaryY: labelY + 16 * labelMultiplier,
507
+ textAnchor: "end",
508
+ direction,
509
+ };
510
+ }
511
+ case "right": {
512
+ const x = object.x + object.visualRadius + 16 * labelMultiplier + attempt * step;
513
+ const labelY = object.y - 4 * labelMultiplier;
514
+ return {
515
+ x,
516
+ labelY,
517
+ secondaryY: labelY + 16 * labelMultiplier,
518
+ textAnchor: "start",
519
+ direction,
520
+ };
521
+ }
522
+ }
523
+ }
524
+ function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
381
525
  const backOrbitIds = orbitVisuals
382
526
  .filter((visual) => !visual.hidden && Boolean(visual.backArcPath))
383
527
  .map((visual) => visual.renderId);
@@ -396,6 +540,10 @@ function createSceneLayers(orbitVisuals, relations, leaders, objects, labels) {
396
540
  id: "relations",
397
541
  renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId),
398
542
  },
543
+ {
544
+ id: "events",
545
+ renderIds: events.filter((event) => !event.hidden).map((event) => event.renderId),
546
+ },
399
547
  {
400
548
  id: "objects",
401
549
  renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId),
@@ -407,7 +555,7 @@ function createSceneLayers(orbitVisuals, relations, leaders, objects, labels) {
407
555
  { id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] },
408
556
  ];
409
557
  }
410
- function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
558
+ function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, labelMultiplier) {
411
559
  const groups = new Map();
412
560
  const ensureGroup = (groupId) => {
413
561
  if (!groupId) {
@@ -456,7 +604,7 @@ function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships
456
604
  }
457
605
  }
458
606
  for (const group of groups.values()) {
459
- group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
607
+ group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier);
460
608
  }
461
609
  return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
462
610
  }
@@ -496,6 +644,40 @@ function createSceneRelations(document, objects) {
496
644
  })
497
645
  .sort((left, right) => left.relation.id.localeCompare(right.relation.id));
498
646
  }
647
+ function createSceneEvents(events, objects, activeEventId) {
648
+ const objectMap = new Map(objects.map((object) => [object.objectId, object]));
649
+ return events
650
+ .map((event) => {
651
+ const objectIds = [...new Set([
652
+ ...(event.targetObjectId ? [event.targetObjectId] : []),
653
+ ...event.participantObjectIds,
654
+ ])];
655
+ const positions = objectIds
656
+ .map((objectId) => objectMap.get(objectId))
657
+ .filter(Boolean);
658
+ const centroidX = positions.length > 0
659
+ ? positions.reduce((sum, object) => sum + object.x, 0) / positions.length
660
+ : 0;
661
+ const centroidY = positions.length > 0
662
+ ? positions.reduce((sum, object) => sum + object.y, 0) / positions.length
663
+ : 0;
664
+ return {
665
+ renderId: `${createRenderId(event.id)}-event`,
666
+ eventId: event.id,
667
+ event,
668
+ objectIds,
669
+ participantIds: [...event.participantObjectIds],
670
+ targetObjectId: event.targetObjectId,
671
+ x: centroidX,
672
+ y: centroidY,
673
+ hidden: event.hidden ||
674
+ positions.length === 0 ||
675
+ positions.every((object) => object.hidden) ||
676
+ (activeEventId !== null && event.id !== activeEventId),
677
+ };
678
+ })
679
+ .sort((left, right) => left.event.id.localeCompare(right.event.id));
680
+ }
499
681
  function createSceneViewpoints(document, projection, preset, relationships, objectMap) {
500
682
  const generatedOverview = createGeneratedOverviewViewpoint(document, projection, preset);
501
683
  const drafts = new Map();
@@ -552,6 +734,7 @@ function createGeneratedOverviewViewpoint(document, projection, preset) {
552
734
  summary: "Fit the whole system with the current atlas defaults.",
553
735
  objectId: null,
554
736
  selectedObjectId: null,
737
+ eventIds: [],
555
738
  projection,
556
739
  preset,
557
740
  rotationDeg: 0,
@@ -588,6 +771,9 @@ function applyViewpointField(draft, field, value, document, projection, preset,
588
771
  draft.select = normalizedValue;
589
772
  }
590
773
  return;
774
+ case "events":
775
+ draft.eventIds = splitListValue(normalizedValue);
776
+ return;
591
777
  case "projection":
592
778
  case "view":
593
779
  draft.projection = parseViewProjection(normalizedValue) ?? projection;
@@ -646,6 +832,7 @@ function finalizeViewpointDraft(draft, projection, preset, objectMap) {
646
832
  summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
647
833
  objectId,
648
834
  selectedObjectId,
835
+ eventIds: [...new Set(draft.eventIds ?? [])],
649
836
  projection: draft.projection ?? projection,
650
837
  preset: draft.preset ?? preset,
651
838
  rotationDeg: draft.rotationDeg ?? 0,
@@ -720,6 +907,7 @@ function parseViewpointLayers(value) {
720
907
  rawLayer === "orbits-back" ||
721
908
  rawLayer === "orbits-front" ||
722
909
  rawLayer === "relations" ||
910
+ rawLayer === "events" ||
723
911
  rawLayer === "objects" ||
724
912
  rawLayer === "labels" ||
725
913
  rawLayer === "metadata") {
@@ -790,7 +978,7 @@ function createViewpointSummary(label, objectId, filter) {
790
978
  }
791
979
  return parts.join(" - ");
792
980
  }
793
- function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
981
+ function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, labelMultiplier) {
794
982
  let minX = Number.POSITIVE_INFINITY;
795
983
  let minY = Number.POSITIVE_INFINITY;
796
984
  let maxX = Number.NEGATIVE_INFINITY;
@@ -820,7 +1008,7 @@ function calculateContentBounds(width, height, objects, orbitVisuals, leaders, l
820
1008
  for (const label of labels) {
821
1009
  if (label.hidden)
822
1010
  continue;
823
- includeLabelBounds(label, include);
1011
+ includeLabelBounds(label, include, labelMultiplier);
824
1012
  }
825
1013
  if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
826
1014
  return createBounds(0, 0, width, height);
@@ -862,13 +1050,10 @@ function includeObjectBounds(object, include) {
862
1050
  include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
863
1051
  include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
864
1052
  }
865
- function includeLabelBounds(label, include) {
866
- const labelScale = 1;
867
- const labelHalfWidth = estimateLabelHalfWidthFromText(label.label, label.secondaryLabel, labelScale);
868
- include(label.x - labelHalfWidth, label.y - 18);
869
- include(label.x + labelHalfWidth, label.y + 8);
870
- include(label.x - labelHalfWidth, label.secondaryY - 14);
871
- include(label.x + labelHalfWidth, label.secondaryY + 8);
1053
+ function includeLabelBounds(label, include, labelMultiplier) {
1054
+ const bounds = createLabelRectFromText(label.x, label.y, label.secondaryY, label.textAnchor, label.direction, label.label, label.secondaryLabel, labelMultiplier);
1055
+ include(bounds.left, bounds.top);
1056
+ include(bounds.right, bounds.bottom);
872
1057
  }
873
1058
  function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
874
1059
  if (positions.has(object.id)) {
@@ -1286,7 +1471,7 @@ function resolveParentId(object, objectMap) {
1286
1471
  return null;
1287
1472
  }
1288
1473
  }
1289
- function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
1474
+ function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier) {
1290
1475
  let minX = Number.POSITIVE_INFINITY;
1291
1476
  let minY = Number.POSITIVE_INFINITY;
1292
1477
  let maxX = Number.NEGATIVE_INFINITY;
@@ -1315,7 +1500,7 @@ function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
1315
1500
  }
1316
1501
  for (const label of labels) {
1317
1502
  if (!label.hidden && group.labelIds.includes(label.objectId)) {
1318
- includeLabelBounds(label, include);
1503
+ includeLabelBounds(label, include, labelMultiplier);
1319
1504
  }
1320
1505
  }
1321
1506
  if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
@@ -1340,12 +1525,29 @@ function resolveGroupRootObjectId(object, objectMap) {
1340
1525
  }
1341
1526
  return current.id;
1342
1527
  }
1343
- function createLabelRect(x, labelY, secondaryY, labelHalfWidth, direction) {
1528
+ function createLabelRect(object, placement, labelMultiplier) {
1529
+ return createLabelRectFromText(placement.x, placement.labelY, placement.secondaryY, placement.textAnchor, placement.direction, object.label, object.secondaryLabel, labelMultiplier);
1530
+ }
1531
+ function createLabelRectFromText(x, labelY, secondaryY, textAnchor, direction, label, secondaryLabel, labelMultiplier) {
1532
+ const labelHalfWidth = estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier);
1533
+ const labelWidth = labelHalfWidth * 2;
1534
+ const topPadding = direction === "above" ? 18 : 12;
1535
+ const bottomPadding = direction === "above" ? 8 : 12;
1536
+ let left = x - labelHalfWidth;
1537
+ let right = x + labelHalfWidth;
1538
+ if (textAnchor === "start") {
1539
+ left = x;
1540
+ right = x + labelWidth;
1541
+ }
1542
+ else if (textAnchor === "end") {
1543
+ left = x - labelWidth;
1544
+ right = x;
1545
+ }
1344
1546
  return {
1345
- left: x - labelHalfWidth,
1346
- right: x + labelHalfWidth,
1347
- top: Math.min(labelY, secondaryY) - (direction < 0 ? 18 : 12),
1348
- bottom: Math.max(labelY, secondaryY) + (direction < 0 ? 8 : 12),
1547
+ left,
1548
+ right,
1549
+ top: Math.min(labelY, secondaryY) - topPadding,
1550
+ bottom: Math.max(labelY, secondaryY) + bottomPadding,
1349
1551
  };
1350
1552
  }
1351
1553
  function rectsOverlap(left, right) {
@@ -60,6 +60,7 @@ export interface WorldOrbitDocument {
60
60
  system: WorldOrbitSystem | null;
61
61
  groups: WorldOrbitGroup[];
62
62
  relations: WorldOrbitRelation[];
63
+ events: WorldOrbitEvent[];
63
64
  objects: WorldOrbitObject[];
64
65
  }
65
66
  export interface WorldOrbitAtlasDocument {
@@ -70,6 +71,7 @@ export interface WorldOrbitAtlasDocument {
70
71
  system: WorldOrbitAtlasSystem | null;
71
72
  groups: WorldOrbitGroup[];
72
73
  relations: WorldOrbitRelation[];
74
+ events: WorldOrbitEvent[];
73
75
  objects: WorldOrbitObject[];
74
76
  diagnostics: WorldOrbitDiagnostic[];
75
77
  }
@@ -81,6 +83,7 @@ export interface WorldOrbitDraftDocument {
81
83
  system: WorldOrbitAtlasSystem | null;
82
84
  groups: WorldOrbitGroup[];
83
85
  relations: WorldOrbitRelation[];
86
+ events: WorldOrbitEvent[];
84
87
  objects: WorldOrbitObject[];
85
88
  diagnostics: WorldOrbitDiagnostic[];
86
89
  }
@@ -132,6 +135,26 @@ export interface WorldOrbitRelation {
132
135
  color: string | null;
133
136
  hidden: boolean;
134
137
  }
138
+ export interface WorldOrbitEvent {
139
+ id: string;
140
+ kind: string;
141
+ label: string;
142
+ summary: string | null;
143
+ targetObjectId: string | null;
144
+ participantObjectIds: string[];
145
+ timing: string | null;
146
+ visibility: string | null;
147
+ tags: string[];
148
+ color: string | null;
149
+ hidden: boolean;
150
+ positions: WorldOrbitEventPose[];
151
+ }
152
+ export interface WorldOrbitEventPose {
153
+ objectId: string;
154
+ placement: Placement | null;
155
+ inner?: UnitValue;
156
+ outer?: UnitValue;
157
+ }
135
158
  export interface WorldOrbitResonance {
136
159
  targetObjectId: string;
137
160
  ratio: string;
@@ -212,6 +235,7 @@ export interface SceneRenderOptions {
212
235
  preset?: RenderPresetName;
213
236
  projection?: "document" | ViewProjection;
214
237
  scaleModel?: Partial<RenderScaleModel>;
238
+ activeEventId?: string | null;
215
239
  }
216
240
  export interface RenderBounds {
217
241
  minX: number;
@@ -294,7 +318,18 @@ export interface RenderSceneLabel {
294
318
  direction: "above" | "below" | "left" | "right";
295
319
  hidden: boolean;
296
320
  }
297
- export type SceneLayerId = "background" | "guides" | "orbits-back" | "orbits-front" | "relations" | "objects" | "labels" | "metadata";
321
+ export interface RenderSceneEvent {
322
+ renderId: string;
323
+ eventId: string;
324
+ event: WorldOrbitEvent;
325
+ objectIds: string[];
326
+ participantIds: string[];
327
+ targetObjectId: string | null;
328
+ x: number;
329
+ y: number;
330
+ hidden: boolean;
331
+ }
332
+ export type SceneLayerId = "background" | "guides" | "orbits-back" | "orbits-front" | "relations" | "events" | "objects" | "labels" | "metadata";
298
333
  export interface RenderSceneViewpointFilter {
299
334
  query: string | null;
300
335
  objectTypes: Array<Exclude<WorldOrbitObjectType, "system">>;
@@ -307,6 +342,7 @@ export interface RenderSceneViewpoint {
307
342
  summary: string;
308
343
  objectId: string | null;
309
344
  selectedObjectId: string | null;
345
+ eventIds: string[];
310
346
  projection: ViewProjection;
311
347
  preset: RenderPresetName | null;
312
348
  rotationDeg: number;
@@ -368,6 +404,8 @@ export interface RenderScene {
368
404
  groups: RenderSceneGroup[];
369
405
  semanticGroups: RenderSceneSemanticGroup[];
370
406
  viewpoints: RenderSceneViewpoint[];
407
+ events: RenderSceneEvent[];
408
+ activeEventId: string | null;
371
409
  objects: RenderSceneObject[];
372
410
  orbitVisuals: RenderOrbitVisual[];
373
411
  relations: RenderSceneRelation[];
@@ -421,6 +459,7 @@ export interface WorldOrbitAtlasViewpoint {
421
459
  summary: string;
422
460
  focusObjectId: string | null;
423
461
  selectedObjectId: string | null;
462
+ events: string[];
424
463
  projection: ViewProjection;
425
464
  preset: RenderPresetName | null;
426
465
  zoom: number | null;
@@ -448,7 +487,7 @@ export interface WorldOrbitAtlasSystem {
448
487
  viewpoints: WorldOrbitAtlasViewpoint[];
449
488
  annotations: WorldOrbitAtlasAnnotation[];
450
489
  }
451
- export type AtlasDocumentPathKind = "system" | "defaults" | "metadata" | "group" | "object" | "viewpoint" | "annotation" | "relation";
490
+ export type AtlasDocumentPathKind = "system" | "defaults" | "metadata" | "group" | "event" | "event-pose" | "object" | "viewpoint" | "annotation" | "relation";
452
491
  export interface AtlasDocumentPath {
453
492
  kind: AtlasDocumentPathKind;
454
493
  id?: string;