worldorbit 2.5.16 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -15
- package/dist/browser/core/dist/index.js +1228 -110
- package/dist/browser/editor/dist/index.js +1896 -180
- package/dist/browser/markdown/dist/index.js +1071 -99
- package/dist/browser/viewer/dist/index.js +1127 -113
- package/dist/unpkg/core/dist/index.js +1228 -110
- package/dist/unpkg/editor/dist/index.js +1896 -180
- package/dist/unpkg/markdown/dist/index.js +1071 -99
- package/dist/unpkg/viewer/dist/index.js +1127 -113
- package/dist/unpkg/worldorbit-core.min.js +12 -12
- package/dist/unpkg/worldorbit-editor.min.js +295 -203
- package/dist/unpkg/worldorbit-markdown.min.js +66 -58
- package/dist/unpkg/worldorbit-viewer.min.js +84 -76
- package/dist/unpkg/worldorbit.js +1304 -124
- package/dist/unpkg/worldorbit.min.js +88 -80
- package/package.json +1 -1
- package/packages/core/dist/atlas-edit.js +75 -1
- package/packages/core/dist/atlas-validate.js +211 -8
- package/packages/core/dist/draft-parse.js +401 -22
- package/packages/core/dist/draft.d.ts +5 -2
- package/packages/core/dist/draft.js +103 -8
- package/packages/core/dist/format.js +99 -6
- package/packages/core/dist/load.js +9 -2
- package/packages/core/dist/normalize.js +1 -0
- package/packages/core/dist/scene.js +400 -64
- package/packages/core/dist/types.d.ts +60 -4
- package/packages/editor/dist/editor.js +702 -65
- package/packages/editor/dist/types.d.ts +3 -1
- package/packages/viewer/dist/atlas-state.js +11 -2
- package/packages/viewer/dist/atlas-viewer.js +19 -7
- package/packages/viewer/dist/render.js +31 -2
- package/packages/viewer/dist/theme.js +1 -0
- package/packages/viewer/dist/tooltip.js +9 -0
- package/packages/viewer/dist/types.d.ts +12 -2
- package/packages/viewer/dist/viewer.js +28 -1
|
@@ -541,6 +541,7 @@
|
|
|
541
541
|
system,
|
|
542
542
|
groups: [],
|
|
543
543
|
relations: [],
|
|
544
|
+
events: [],
|
|
544
545
|
objects
|
|
545
546
|
};
|
|
546
547
|
}
|
|
@@ -920,12 +921,16 @@
|
|
|
920
921
|
const height = frame.height;
|
|
921
922
|
const padding = frame.padding;
|
|
922
923
|
const layoutPreset = resolveLayoutPreset(document2);
|
|
923
|
-
const
|
|
924
|
+
const schemaProjection = resolveProjection(document2, options.projection);
|
|
925
|
+
const camera = normalizeViewCamera(options.camera ?? null);
|
|
926
|
+
const renderProjection = resolveRenderProjection(schemaProjection, camera);
|
|
924
927
|
const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
|
|
925
928
|
const spacingFactor = layoutPresetSpacing(layoutPreset);
|
|
926
929
|
const systemId = document2.system?.id ?? null;
|
|
927
|
-
const
|
|
928
|
-
const
|
|
930
|
+
const activeEventId = options.activeEventId ?? null;
|
|
931
|
+
const effectiveObjects = createEffectiveObjects(document2.objects, document2.events ?? [], activeEventId);
|
|
932
|
+
const objectMap = new Map(effectiveObjects.map((object) => [object.id, object]));
|
|
933
|
+
const relationships = buildSceneRelationships(effectiveObjects, objectMap);
|
|
929
934
|
const positions = /* @__PURE__ */ new Map();
|
|
930
935
|
const orbitDrafts = [];
|
|
931
936
|
const leaderDrafts = [];
|
|
@@ -934,7 +939,7 @@
|
|
|
934
939
|
const atObjects = [];
|
|
935
940
|
const surfaceChildren = /* @__PURE__ */ new Map();
|
|
936
941
|
const orbitChildren = /* @__PURE__ */ new Map();
|
|
937
|
-
for (const object of
|
|
942
|
+
for (const object of effectiveObjects) {
|
|
938
943
|
const placement = object.placement;
|
|
939
944
|
if (!placement) {
|
|
940
945
|
rootObjects.push(object);
|
|
@@ -961,7 +966,7 @@
|
|
|
961
966
|
surfaceChildren,
|
|
962
967
|
objectMap,
|
|
963
968
|
spacingFactor,
|
|
964
|
-
projection,
|
|
969
|
+
projection: renderProjection,
|
|
965
970
|
scaleModel
|
|
966
971
|
};
|
|
967
972
|
const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
|
|
@@ -973,7 +978,7 @@
|
|
|
973
978
|
const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
|
|
974
979
|
secondaryRoots.forEach((object, index) => {
|
|
975
980
|
const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
|
|
976
|
-
const offset = projectPolarOffset(angle, rootRingRadius,
|
|
981
|
+
const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
|
|
977
982
|
placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
|
|
978
983
|
});
|
|
979
984
|
}
|
|
@@ -1029,38 +1034,48 @@
|
|
|
1029
1034
|
const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
|
|
1030
1035
|
const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
|
|
1031
1036
|
const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
|
|
1032
|
-
const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
|
|
1037
|
+
const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
|
|
1033
1038
|
const relations = createSceneRelations(document2, objects);
|
|
1034
|
-
const
|
|
1035
|
-
const
|
|
1039
|
+
const events = createSceneEvents(document2.events ?? [], objects, activeEventId);
|
|
1040
|
+
const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
|
|
1041
|
+
const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
|
|
1036
1042
|
const semanticGroups = createSceneSemanticGroups(document2, objects);
|
|
1037
|
-
const viewpoints = createSceneViewpoints(document2,
|
|
1038
|
-
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
|
|
1043
|
+
const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
|
|
1044
|
+
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
|
|
1039
1045
|
return {
|
|
1040
1046
|
width,
|
|
1041
1047
|
height,
|
|
1042
1048
|
padding,
|
|
1043
1049
|
renderPreset: frame.preset,
|
|
1044
|
-
projection,
|
|
1050
|
+
projection: schemaProjection,
|
|
1051
|
+
renderProjection,
|
|
1052
|
+
camera,
|
|
1045
1053
|
scaleModel,
|
|
1046
1054
|
title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1047
|
-
subtitle:
|
|
1055
|
+
subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
|
|
1048
1056
|
systemId,
|
|
1049
|
-
viewMode:
|
|
1057
|
+
viewMode: schemaProjection,
|
|
1050
1058
|
layoutPreset,
|
|
1051
1059
|
metadata: {
|
|
1052
1060
|
format: document2.format,
|
|
1053
1061
|
version: document2.version,
|
|
1054
|
-
view:
|
|
1062
|
+
view: schemaProjection,
|
|
1063
|
+
renderProjection,
|
|
1055
1064
|
scale: String(document2.system?.properties.scale ?? layoutPreset),
|
|
1056
1065
|
units: String(document2.system?.properties.units ?? "mixed"),
|
|
1057
|
-
preset: frame.preset ?? "custom"
|
|
1066
|
+
preset: frame.preset ?? "custom",
|
|
1067
|
+
...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
|
|
1068
|
+
...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
|
|
1069
|
+
...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
|
|
1070
|
+
...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
|
|
1058
1071
|
},
|
|
1059
1072
|
contentBounds,
|
|
1060
1073
|
layers,
|
|
1061
1074
|
groups,
|
|
1062
1075
|
semanticGroups,
|
|
1063
1076
|
viewpoints,
|
|
1077
|
+
events,
|
|
1078
|
+
activeEventId,
|
|
1064
1079
|
objects,
|
|
1065
1080
|
orbitVisuals,
|
|
1066
1081
|
relations,
|
|
@@ -1068,6 +1083,56 @@
|
|
|
1068
1083
|
labels
|
|
1069
1084
|
};
|
|
1070
1085
|
}
|
|
1086
|
+
function createEffectiveObjects(objects, events, activeEventId) {
|
|
1087
|
+
const cloned = objects.map((object) => structuredClone(object));
|
|
1088
|
+
if (!activeEventId) {
|
|
1089
|
+
return cloned;
|
|
1090
|
+
}
|
|
1091
|
+
const activeEvent = events.find((event) => event.id === activeEventId);
|
|
1092
|
+
if (!activeEvent) {
|
|
1093
|
+
return cloned;
|
|
1094
|
+
}
|
|
1095
|
+
const objectMap = new Map(cloned.map((object) => [object.id, object]));
|
|
1096
|
+
const referencedIds = /* @__PURE__ */ new Set([
|
|
1097
|
+
...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
|
|
1098
|
+
...activeEvent.participantObjectIds,
|
|
1099
|
+
...activeEvent.positions.map((pose) => pose.objectId)
|
|
1100
|
+
]);
|
|
1101
|
+
for (const objectId of referencedIds) {
|
|
1102
|
+
const object = objectMap.get(objectId);
|
|
1103
|
+
if (!object) {
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
if (activeEvent.epoch) {
|
|
1107
|
+
object.epoch = activeEvent.epoch;
|
|
1108
|
+
}
|
|
1109
|
+
if (activeEvent.referencePlane) {
|
|
1110
|
+
object.referencePlane = activeEvent.referencePlane;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
for (const pose of activeEvent.positions) {
|
|
1114
|
+
const object = objectMap.get(pose.objectId);
|
|
1115
|
+
if (!object) {
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
if (pose.placement) {
|
|
1119
|
+
object.placement = structuredClone(pose.placement);
|
|
1120
|
+
}
|
|
1121
|
+
if (pose.inner) {
|
|
1122
|
+
object.properties.inner = { ...pose.inner };
|
|
1123
|
+
}
|
|
1124
|
+
if (pose.outer) {
|
|
1125
|
+
object.properties.outer = { ...pose.outer };
|
|
1126
|
+
}
|
|
1127
|
+
if (pose.epoch) {
|
|
1128
|
+
object.epoch = pose.epoch;
|
|
1129
|
+
}
|
|
1130
|
+
if (pose.referencePlane) {
|
|
1131
|
+
object.referencePlane = pose.referencePlane;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return cloned;
|
|
1135
|
+
}
|
|
1071
1136
|
function resolveLayoutPreset(document2) {
|
|
1072
1137
|
const rawScale = String(document2.system?.properties.scale ?? "balanced").toLowerCase();
|
|
1073
1138
|
switch (rawScale) {
|
|
@@ -1104,10 +1169,59 @@
|
|
|
1104
1169
|
}
|
|
1105
1170
|
}
|
|
1106
1171
|
function resolveProjection(document2, projection) {
|
|
1107
|
-
if (projection === "topdown" || projection === "isometric") {
|
|
1172
|
+
if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
|
|
1108
1173
|
return projection;
|
|
1109
1174
|
}
|
|
1110
|
-
|
|
1175
|
+
const documentView = String(document2.system?.properties.view ?? "topdown").toLowerCase();
|
|
1176
|
+
return parseViewProjection(documentView) ?? "topdown";
|
|
1177
|
+
}
|
|
1178
|
+
function resolveRenderProjection(projection, camera) {
|
|
1179
|
+
switch (projection) {
|
|
1180
|
+
case "topdown":
|
|
1181
|
+
return "topdown";
|
|
1182
|
+
case "isometric":
|
|
1183
|
+
return "isometric";
|
|
1184
|
+
case "orthographic":
|
|
1185
|
+
return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
|
|
1186
|
+
case "perspective":
|
|
1187
|
+
return "isometric";
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
function normalizeViewCamera(camera) {
|
|
1191
|
+
if (!camera) {
|
|
1192
|
+
return null;
|
|
1193
|
+
}
|
|
1194
|
+
const normalized = {
|
|
1195
|
+
azimuth: normalizeFiniteCameraValue(camera.azimuth),
|
|
1196
|
+
elevation: normalizeFiniteCameraValue(camera.elevation),
|
|
1197
|
+
roll: normalizeFiniteCameraValue(camera.roll),
|
|
1198
|
+
distance: normalizePositiveCameraDistance(camera.distance)
|
|
1199
|
+
};
|
|
1200
|
+
return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
|
|
1201
|
+
}
|
|
1202
|
+
function normalizeFiniteCameraValue(value) {
|
|
1203
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1204
|
+
}
|
|
1205
|
+
function normalizePositiveCameraDistance(value) {
|
|
1206
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
1207
|
+
}
|
|
1208
|
+
function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
|
|
1209
|
+
const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
|
|
1210
|
+
if (projection !== renderProjection) {
|
|
1211
|
+
parts.push(`2D ${renderProjection} fallback`);
|
|
1212
|
+
}
|
|
1213
|
+
if (camera) {
|
|
1214
|
+
const cameraParts = [
|
|
1215
|
+
camera.azimuth !== null ? `az ${camera.azimuth}` : null,
|
|
1216
|
+
camera.elevation !== null ? `el ${camera.elevation}` : null,
|
|
1217
|
+
camera.roll !== null ? `roll ${camera.roll}` : null,
|
|
1218
|
+
camera.distance !== null ? `dist ${camera.distance}` : null
|
|
1219
|
+
].filter(Boolean);
|
|
1220
|
+
if (cameraParts.length > 0) {
|
|
1221
|
+
parts.push(`camera ${cameraParts.join(" / ")}`);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return parts.join(" - ");
|
|
1111
1225
|
}
|
|
1112
1226
|
function resolveScaleModel(layoutPreset, overrides) {
|
|
1113
1227
|
const defaults = defaultScaleModel(layoutPreset);
|
|
@@ -1223,24 +1337,14 @@
|
|
|
1223
1337
|
hidden: draft.object.properties.hidden === true
|
|
1224
1338
|
};
|
|
1225
1339
|
}
|
|
1226
|
-
function createSceneLabels(objects, sceneHeight, labelMultiplier) {
|
|
1340
|
+
function createSceneLabels(objects, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1227
1341
|
const labels = [];
|
|
1228
1342
|
const occupied = [];
|
|
1229
|
-
const
|
|
1343
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1344
|
+
const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort(compareLabelPlacementOrder);
|
|
1230
1345
|
for (const object of visibleObjects) {
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
let labelY = object.y + direction * (object.radius + 18 * labelMultiplier);
|
|
1234
|
-
let secondaryY = labelY + direction * (16 * labelMultiplier);
|
|
1235
|
-
let bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1236
|
-
let attempts = 0;
|
|
1237
|
-
while (occupied.some((entry) => rectsOverlap(entry, bounds)) && attempts < 10) {
|
|
1238
|
-
labelY += direction * 14 * labelMultiplier;
|
|
1239
|
-
secondaryY += direction * 14 * labelMultiplier;
|
|
1240
|
-
bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1241
|
-
attempts += 1;
|
|
1242
|
-
}
|
|
1243
|
-
occupied.push(bounds);
|
|
1346
|
+
const placement = selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) ?? createLabelPlacement(object, defaultVerticalDirection(object, objectMap.get(object.parentId ?? "") ?? null, sceneHeight), 0, labelMultiplier);
|
|
1347
|
+
occupied.push(createLabelRect(object, placement, labelMultiplier));
|
|
1244
1348
|
labels.push({
|
|
1245
1349
|
renderId: `${object.renderId}-label`,
|
|
1246
1350
|
objectId: object.objectId,
|
|
@@ -1249,17 +1353,128 @@
|
|
|
1249
1353
|
semanticGroupIds: [...object.semanticGroupIds],
|
|
1250
1354
|
label: object.label,
|
|
1251
1355
|
secondaryLabel: object.secondaryLabel,
|
|
1252
|
-
x:
|
|
1253
|
-
y: labelY,
|
|
1254
|
-
secondaryY,
|
|
1255
|
-
textAnchor:
|
|
1256
|
-
direction: direction
|
|
1356
|
+
x: placement.x,
|
|
1357
|
+
y: placement.labelY,
|
|
1358
|
+
secondaryY: placement.secondaryY,
|
|
1359
|
+
textAnchor: placement.textAnchor,
|
|
1360
|
+
direction: placement.direction,
|
|
1257
1361
|
hidden: object.hidden
|
|
1258
1362
|
});
|
|
1259
1363
|
}
|
|
1260
1364
|
return labels;
|
|
1261
1365
|
}
|
|
1262
|
-
function
|
|
1366
|
+
function compareLabelPlacementOrder(left, right) {
|
|
1367
|
+
const priorityDiff = labelPlacementPriority(left) - labelPlacementPriority(right);
|
|
1368
|
+
if (priorityDiff !== 0) {
|
|
1369
|
+
return priorityDiff;
|
|
1370
|
+
}
|
|
1371
|
+
const renderPriorityDiff = (right.object.renderHints?.renderPriority ?? 0) - (left.object.renderHints?.renderPriority ?? 0);
|
|
1372
|
+
if (renderPriorityDiff !== 0) {
|
|
1373
|
+
return renderPriorityDiff;
|
|
1374
|
+
}
|
|
1375
|
+
return left.sortKey - right.sortKey;
|
|
1376
|
+
}
|
|
1377
|
+
function labelPlacementPriority(object) {
|
|
1378
|
+
switch (object.object.type) {
|
|
1379
|
+
case "star":
|
|
1380
|
+
return 0;
|
|
1381
|
+
case "planet":
|
|
1382
|
+
return 1;
|
|
1383
|
+
case "moon":
|
|
1384
|
+
return 2;
|
|
1385
|
+
case "belt":
|
|
1386
|
+
case "ring":
|
|
1387
|
+
return 3;
|
|
1388
|
+
case "asteroid":
|
|
1389
|
+
case "comet":
|
|
1390
|
+
return 4;
|
|
1391
|
+
case "structure":
|
|
1392
|
+
case "phenomenon":
|
|
1393
|
+
return 5;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
function selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1397
|
+
for (const direction of preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight)) {
|
|
1398
|
+
const maxAttempts = direction === "left" || direction === "right" ? 4 : 6;
|
|
1399
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
|
|
1400
|
+
const placement = createLabelPlacement(object, direction, attempt, labelMultiplier);
|
|
1401
|
+
const rect = createLabelRect(object, placement, labelMultiplier);
|
|
1402
|
+
if (!occupied.some((entry) => rectsOverlap(entry, rect))) {
|
|
1403
|
+
return placement;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
return null;
|
|
1408
|
+
}
|
|
1409
|
+
function preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight) {
|
|
1410
|
+
const parent = object.parentId ? objectMap.get(object.parentId) ?? null : null;
|
|
1411
|
+
const vertical = defaultVerticalDirection(object, parent, sceneHeight);
|
|
1412
|
+
const oppositeVertical = vertical === "below" ? "above" : "below";
|
|
1413
|
+
const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
|
|
1414
|
+
const oppositeHorizontal = horizontal === "right" ? "left" : "right";
|
|
1415
|
+
const preferHorizontal = object.object.type === "structure" || object.object.type === "phenomenon" || object.object.placement?.mode === "at" || object.object.placement?.mode === "surface" || object.object.placement?.mode === "free";
|
|
1416
|
+
return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
|
|
1417
|
+
}
|
|
1418
|
+
function defaultVerticalDirection(object, parent, sceneHeight) {
|
|
1419
|
+
if (parent && Math.abs(object.y - parent.y) > 6) {
|
|
1420
|
+
return object.y >= parent.y ? "below" : "above";
|
|
1421
|
+
}
|
|
1422
|
+
return object.y > sceneHeight * 0.62 ? "above" : "below";
|
|
1423
|
+
}
|
|
1424
|
+
function defaultHorizontalDirection(object, parent, sceneWidth) {
|
|
1425
|
+
if (parent && Math.abs(object.x - parent.x) > 6) {
|
|
1426
|
+
return object.x >= parent.x ? "right" : "left";
|
|
1427
|
+
}
|
|
1428
|
+
return object.x >= sceneWidth / 2 ? "right" : "left";
|
|
1429
|
+
}
|
|
1430
|
+
function createLabelPlacement(object, direction, attempt, labelMultiplier) {
|
|
1431
|
+
const step = 14 * labelMultiplier;
|
|
1432
|
+
switch (direction) {
|
|
1433
|
+
case "above": {
|
|
1434
|
+
const labelY = object.y - (object.radius + 18 * labelMultiplier + attempt * step);
|
|
1435
|
+
return {
|
|
1436
|
+
x: object.x,
|
|
1437
|
+
labelY,
|
|
1438
|
+
secondaryY: labelY - 16 * labelMultiplier,
|
|
1439
|
+
textAnchor: "middle",
|
|
1440
|
+
direction
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
case "below": {
|
|
1444
|
+
const labelY = object.y + object.radius + 18 * labelMultiplier + attempt * step;
|
|
1445
|
+
return {
|
|
1446
|
+
x: object.x,
|
|
1447
|
+
labelY,
|
|
1448
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1449
|
+
textAnchor: "middle",
|
|
1450
|
+
direction
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
case "left": {
|
|
1454
|
+
const x = object.x - (object.visualRadius + 16 * labelMultiplier + attempt * step);
|
|
1455
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1456
|
+
return {
|
|
1457
|
+
x,
|
|
1458
|
+
labelY,
|
|
1459
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1460
|
+
textAnchor: "end",
|
|
1461
|
+
direction
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
case "right": {
|
|
1465
|
+
const x = object.x + object.visualRadius + 16 * labelMultiplier + attempt * step;
|
|
1466
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1467
|
+
return {
|
|
1468
|
+
x,
|
|
1469
|
+
labelY,
|
|
1470
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1471
|
+
textAnchor: "start",
|
|
1472
|
+
direction
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
|
|
1263
1478
|
const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
|
|
1264
1479
|
const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
|
|
1265
1480
|
return [
|
|
@@ -1274,6 +1489,10 @@
|
|
|
1274
1489
|
id: "relations",
|
|
1275
1490
|
renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
|
|
1276
1491
|
},
|
|
1492
|
+
{
|
|
1493
|
+
id: "events",
|
|
1494
|
+
renderIds: events.filter((event) => !event.hidden).map((event) => event.renderId)
|
|
1495
|
+
},
|
|
1277
1496
|
{
|
|
1278
1497
|
id: "objects",
|
|
1279
1498
|
renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
|
|
@@ -1285,7 +1504,7 @@
|
|
|
1285
1504
|
{ id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
|
|
1286
1505
|
];
|
|
1287
1506
|
}
|
|
1288
|
-
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
|
|
1507
|
+
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, labelMultiplier) {
|
|
1289
1508
|
const groups = /* @__PURE__ */ new Map();
|
|
1290
1509
|
const ensureGroup = (groupId) => {
|
|
1291
1510
|
if (!groupId) {
|
|
@@ -1334,7 +1553,7 @@
|
|
|
1334
1553
|
}
|
|
1335
1554
|
}
|
|
1336
1555
|
for (const group of groups.values()) {
|
|
1337
|
-
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
|
|
1556
|
+
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier);
|
|
1338
1557
|
}
|
|
1339
1558
|
return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
1340
1559
|
}
|
|
@@ -1368,6 +1587,29 @@
|
|
|
1368
1587
|
};
|
|
1369
1588
|
}).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
|
|
1370
1589
|
}
|
|
1590
|
+
function createSceneEvents(events, objects, activeEventId) {
|
|
1591
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1592
|
+
return events.map((event) => {
|
|
1593
|
+
const objectIds = [.../* @__PURE__ */ new Set([
|
|
1594
|
+
...event.targetObjectId ? [event.targetObjectId] : [],
|
|
1595
|
+
...event.participantObjectIds
|
|
1596
|
+
])];
|
|
1597
|
+
const positions = objectIds.map((objectId) => objectMap.get(objectId)).filter(Boolean);
|
|
1598
|
+
const centroidX = positions.length > 0 ? positions.reduce((sum, object) => sum + object.x, 0) / positions.length : 0;
|
|
1599
|
+
const centroidY = positions.length > 0 ? positions.reduce((sum, object) => sum + object.y, 0) / positions.length : 0;
|
|
1600
|
+
return {
|
|
1601
|
+
renderId: `${createRenderId(event.id)}-event`,
|
|
1602
|
+
eventId: event.id,
|
|
1603
|
+
event,
|
|
1604
|
+
objectIds,
|
|
1605
|
+
participantIds: [...event.participantObjectIds],
|
|
1606
|
+
targetObjectId: event.targetObjectId,
|
|
1607
|
+
x: centroidX,
|
|
1608
|
+
y: centroidY,
|
|
1609
|
+
hidden: event.hidden || positions.length === 0 || positions.every((object) => object.hidden) || activeEventId !== null && event.id !== activeEventId
|
|
1610
|
+
};
|
|
1611
|
+
}).sort((left, right) => left.event.id.localeCompare(right.event.id));
|
|
1612
|
+
}
|
|
1371
1613
|
function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
|
|
1372
1614
|
const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
|
|
1373
1615
|
const drafts = /* @__PURE__ */ new Map();
|
|
@@ -1415,13 +1657,18 @@
|
|
|
1415
1657
|
function createGeneratedOverviewViewpoint(document2, projection, preset) {
|
|
1416
1658
|
const title = document2.system?.title ?? document2.system?.properties.title;
|
|
1417
1659
|
const label = title ? `${String(title)} Overview` : "Overview";
|
|
1660
|
+
const camera = normalizeViewCamera(null);
|
|
1661
|
+
const renderProjection = resolveRenderProjection(projection, camera);
|
|
1418
1662
|
return {
|
|
1419
1663
|
id: "overview",
|
|
1420
1664
|
label,
|
|
1421
1665
|
summary: "Fit the whole system with the current atlas defaults.",
|
|
1422
1666
|
objectId: null,
|
|
1423
1667
|
selectedObjectId: null,
|
|
1668
|
+
eventIds: [],
|
|
1424
1669
|
projection,
|
|
1670
|
+
renderProjection,
|
|
1671
|
+
camera,
|
|
1425
1672
|
preset,
|
|
1426
1673
|
rotationDeg: 0,
|
|
1427
1674
|
scale: null,
|
|
@@ -1457,6 +1704,9 @@
|
|
|
1457
1704
|
draft.select = normalizedValue;
|
|
1458
1705
|
}
|
|
1459
1706
|
return;
|
|
1707
|
+
case "events":
|
|
1708
|
+
draft.eventIds = splitListValue(normalizedValue);
|
|
1709
|
+
return;
|
|
1460
1710
|
case "projection":
|
|
1461
1711
|
case "view":
|
|
1462
1712
|
draft.projection = parseViewProjection(normalizedValue) ?? projection;
|
|
@@ -1468,6 +1718,30 @@
|
|
|
1468
1718
|
case "angle":
|
|
1469
1719
|
draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
|
|
1470
1720
|
return;
|
|
1721
|
+
case "camera.azimuth":
|
|
1722
|
+
draft.camera = {
|
|
1723
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1724
|
+
azimuth: parseFiniteNumber(normalizedValue)
|
|
1725
|
+
};
|
|
1726
|
+
return;
|
|
1727
|
+
case "camera.elevation":
|
|
1728
|
+
draft.camera = {
|
|
1729
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1730
|
+
elevation: parseFiniteNumber(normalizedValue)
|
|
1731
|
+
};
|
|
1732
|
+
return;
|
|
1733
|
+
case "camera.roll":
|
|
1734
|
+
draft.camera = {
|
|
1735
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1736
|
+
roll: parseFiniteNumber(normalizedValue)
|
|
1737
|
+
};
|
|
1738
|
+
return;
|
|
1739
|
+
case "camera.distance":
|
|
1740
|
+
draft.camera = {
|
|
1741
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1742
|
+
distance: parsePositiveNumber(normalizedValue)
|
|
1743
|
+
};
|
|
1744
|
+
return;
|
|
1471
1745
|
case "zoom":
|
|
1472
1746
|
case "scale":
|
|
1473
1747
|
draft.scale = parsePositiveNumber(normalizedValue);
|
|
@@ -1507,13 +1781,19 @@
|
|
|
1507
1781
|
const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
|
|
1508
1782
|
const filter = normalizeViewpointFilter(draft.filter);
|
|
1509
1783
|
const label = draft.label?.trim() || humanizeIdentifier(draft.id);
|
|
1784
|
+
const resolvedProjection = draft.projection ?? projection;
|
|
1785
|
+
const camera = normalizeViewCamera(draft.camera ?? null);
|
|
1786
|
+
const renderProjection = resolveRenderProjection(resolvedProjection, camera);
|
|
1510
1787
|
return {
|
|
1511
1788
|
id: draft.id,
|
|
1512
1789
|
label,
|
|
1513
1790
|
summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
|
|
1514
1791
|
objectId,
|
|
1515
1792
|
selectedObjectId,
|
|
1516
|
-
|
|
1793
|
+
eventIds: [...new Set(draft.eventIds ?? [])],
|
|
1794
|
+
projection: resolvedProjection,
|
|
1795
|
+
renderProjection,
|
|
1796
|
+
camera,
|
|
1517
1797
|
preset: draft.preset ?? preset,
|
|
1518
1798
|
rotationDeg: draft.rotationDeg ?? 0,
|
|
1519
1799
|
scale: draft.scale ?? null,
|
|
@@ -1530,6 +1810,14 @@
|
|
|
1530
1810
|
groupIds: []
|
|
1531
1811
|
};
|
|
1532
1812
|
}
|
|
1813
|
+
function createEmptyViewCamera() {
|
|
1814
|
+
return {
|
|
1815
|
+
azimuth: null,
|
|
1816
|
+
elevation: null,
|
|
1817
|
+
roll: null,
|
|
1818
|
+
distance: null
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1533
1821
|
function normalizeViewpointFilter(filter) {
|
|
1534
1822
|
if (!filter) {
|
|
1535
1823
|
return null;
|
|
@@ -1543,7 +1831,18 @@
|
|
|
1543
1831
|
return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
|
|
1544
1832
|
}
|
|
1545
1833
|
function parseViewProjection(value) {
|
|
1546
|
-
|
|
1834
|
+
switch (value.toLowerCase()) {
|
|
1835
|
+
case "topdown":
|
|
1836
|
+
return "topdown";
|
|
1837
|
+
case "isometric":
|
|
1838
|
+
return "isometric";
|
|
1839
|
+
case "orthographic":
|
|
1840
|
+
return "orthographic";
|
|
1841
|
+
case "perspective":
|
|
1842
|
+
return "perspective";
|
|
1843
|
+
default:
|
|
1844
|
+
return null;
|
|
1845
|
+
}
|
|
1547
1846
|
}
|
|
1548
1847
|
function parseRenderPreset(value) {
|
|
1549
1848
|
const normalized = value.toLowerCase();
|
|
@@ -1570,7 +1869,7 @@
|
|
|
1570
1869
|
next["orbits-front"] = enabled;
|
|
1571
1870
|
continue;
|
|
1572
1871
|
}
|
|
1573
|
-
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1872
|
+
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1574
1873
|
next[rawLayer] = enabled;
|
|
1575
1874
|
}
|
|
1576
1875
|
}
|
|
@@ -1581,7 +1880,7 @@
|
|
|
1581
1880
|
}
|
|
1582
1881
|
function parseViewpointGroups(value, document2, relationships, objectMap) {
|
|
1583
1882
|
return splitListValue(value).map((entry) => {
|
|
1584
|
-
if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
|
|
1883
|
+
if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
|
|
1585
1884
|
return entry;
|
|
1586
1885
|
}
|
|
1587
1886
|
if (entry.startsWith("wo-") && entry.endsWith("-group")) {
|
|
@@ -1618,7 +1917,7 @@
|
|
|
1618
1917
|
}
|
|
1619
1918
|
return parts.join(" - ");
|
|
1620
1919
|
}
|
|
1621
|
-
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
|
|
1920
|
+
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
1622
1921
|
let minX = Number.POSITIVE_INFINITY;
|
|
1623
1922
|
let minY = Number.POSITIVE_INFINITY;
|
|
1624
1923
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -1648,7 +1947,7 @@
|
|
|
1648
1947
|
for (const label of labels) {
|
|
1649
1948
|
if (label.hidden)
|
|
1650
1949
|
continue;
|
|
1651
|
-
includeLabelBounds(label, include);
|
|
1950
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
1652
1951
|
}
|
|
1653
1952
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
1654
1953
|
return createBounds(0, 0, width, height);
|
|
@@ -1686,13 +1985,10 @@
|
|
|
1686
1985
|
include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
|
|
1687
1986
|
include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
|
|
1688
1987
|
}
|
|
1689
|
-
function includeLabelBounds(label, include) {
|
|
1690
|
-
const
|
|
1691
|
-
|
|
1692
|
-
include(
|
|
1693
|
-
include(label.x + labelHalfWidth, label.y + 8);
|
|
1694
|
-
include(label.x - labelHalfWidth, label.secondaryY - 14);
|
|
1695
|
-
include(label.x + labelHalfWidth, label.secondaryY + 8);
|
|
1988
|
+
function includeLabelBounds(label, include, labelMultiplier) {
|
|
1989
|
+
const bounds = createLabelRectFromText(label.x, label.y, label.secondaryY, label.textAnchor, label.direction, label.label, label.secondaryLabel, labelMultiplier);
|
|
1990
|
+
include(bounds.left, bounds.top);
|
|
1991
|
+
include(bounds.right, bounds.bottom);
|
|
1696
1992
|
}
|
|
1697
1993
|
function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
|
|
1698
1994
|
if (positions.has(object.id)) {
|
|
@@ -2082,7 +2378,7 @@
|
|
|
2082
2378
|
return null;
|
|
2083
2379
|
}
|
|
2084
2380
|
}
|
|
2085
|
-
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
|
|
2381
|
+
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
2086
2382
|
let minX = Number.POSITIVE_INFINITY;
|
|
2087
2383
|
let minY = Number.POSITIVE_INFINITY;
|
|
2088
2384
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -2111,7 +2407,7 @@
|
|
|
2111
2407
|
}
|
|
2112
2408
|
for (const label of labels) {
|
|
2113
2409
|
if (!label.hidden && group.labelIds.includes(label.objectId)) {
|
|
2114
|
-
includeLabelBounds(label, include);
|
|
2410
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
2115
2411
|
}
|
|
2116
2412
|
}
|
|
2117
2413
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
@@ -2136,12 +2432,28 @@
|
|
|
2136
2432
|
}
|
|
2137
2433
|
return current.id;
|
|
2138
2434
|
}
|
|
2139
|
-
function createLabelRect(
|
|
2435
|
+
function createLabelRect(object, placement, labelMultiplier) {
|
|
2436
|
+
return createLabelRectFromText(placement.x, placement.labelY, placement.secondaryY, placement.textAnchor, placement.direction, object.label, object.secondaryLabel, labelMultiplier);
|
|
2437
|
+
}
|
|
2438
|
+
function createLabelRectFromText(x, labelY, secondaryY, textAnchor, direction, label, secondaryLabel, labelMultiplier) {
|
|
2439
|
+
const labelHalfWidth = estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier);
|
|
2440
|
+
const labelWidth = labelHalfWidth * 2;
|
|
2441
|
+
const topPadding = direction === "above" ? 18 : 12;
|
|
2442
|
+
const bottomPadding = direction === "above" ? 8 : 12;
|
|
2443
|
+
let left = x - labelHalfWidth;
|
|
2444
|
+
let right = x + labelHalfWidth;
|
|
2445
|
+
if (textAnchor === "start") {
|
|
2446
|
+
left = x;
|
|
2447
|
+
right = x + labelWidth;
|
|
2448
|
+
} else if (textAnchor === "end") {
|
|
2449
|
+
left = x - labelWidth;
|
|
2450
|
+
right = x;
|
|
2451
|
+
}
|
|
2140
2452
|
return {
|
|
2141
|
-
left
|
|
2142
|
-
right
|
|
2143
|
-
top: Math.min(labelY, secondaryY) -
|
|
2144
|
-
bottom: Math.max(labelY, secondaryY) +
|
|
2453
|
+
left,
|
|
2454
|
+
right,
|
|
2455
|
+
top: Math.min(labelY, secondaryY) - topPadding,
|
|
2456
|
+
bottom: Math.max(labelY, secondaryY) + bottomPadding
|
|
2145
2457
|
};
|
|
2146
2458
|
}
|
|
2147
2459
|
function rectsOverlap(left, right) {
|
|
@@ -2328,11 +2640,6 @@
|
|
|
2328
2640
|
function customColorFor(value) {
|
|
2329
2641
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2330
2642
|
}
|
|
2331
|
-
function estimateLabelHalfWidth(object, labelMultiplier) {
|
|
2332
|
-
const primaryWidth = object.label.length * 4.6 * labelMultiplier + 18;
|
|
2333
|
-
const secondaryWidth = object.secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
2334
|
-
return Math.max(primaryWidth, secondaryWidth, object.visualRadius + 18);
|
|
2335
|
-
}
|
|
2336
2643
|
function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
|
|
2337
2644
|
const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
|
|
2338
2645
|
const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
@@ -2349,7 +2656,7 @@
|
|
|
2349
2656
|
}
|
|
2350
2657
|
|
|
2351
2658
|
// packages/core/dist/draft.js
|
|
2352
|
-
function materializeAtlasDocument(document2) {
|
|
2659
|
+
function materializeAtlasDocument(document2, options = {}) {
|
|
2353
2660
|
const system = document2.system ? {
|
|
2354
2661
|
type: "system",
|
|
2355
2662
|
id: document2.system.id,
|
|
@@ -2360,6 +2667,8 @@
|
|
|
2360
2667
|
properties: materializeDraftSystemProperties(document2.system),
|
|
2361
2668
|
info: materializeDraftSystemInfo(document2.system)
|
|
2362
2669
|
} : null;
|
|
2670
|
+
const objects = document2.objects.map(cloneWorldOrbitObject);
|
|
2671
|
+
applyEventPoseOverrides(objects, document2.events ?? [], options.activeEventId ?? null);
|
|
2363
2672
|
return {
|
|
2364
2673
|
format: "worldorbit",
|
|
2365
2674
|
version: "1.0",
|
|
@@ -2367,7 +2676,8 @@
|
|
|
2367
2676
|
system,
|
|
2368
2677
|
groups: structuredClone(document2.groups ?? []),
|
|
2369
2678
|
relations: structuredClone(document2.relations ?? []),
|
|
2370
|
-
|
|
2679
|
+
events: document2.events.map(cloneWorldOrbitEvent),
|
|
2680
|
+
objects
|
|
2371
2681
|
};
|
|
2372
2682
|
}
|
|
2373
2683
|
function cloneWorldOrbitObject(object) {
|
|
@@ -2389,6 +2699,75 @@
|
|
|
2389
2699
|
info: { ...object.info }
|
|
2390
2700
|
};
|
|
2391
2701
|
}
|
|
2702
|
+
function cloneWorldOrbitEvent(event) {
|
|
2703
|
+
return {
|
|
2704
|
+
...event,
|
|
2705
|
+
participantObjectIds: [...event.participantObjectIds],
|
|
2706
|
+
tags: [...event.tags],
|
|
2707
|
+
positions: event.positions.map(cloneWorldOrbitEventPose)
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
function cloneWorldOrbitEventPose(pose) {
|
|
2711
|
+
return {
|
|
2712
|
+
objectId: pose.objectId,
|
|
2713
|
+
placement: clonePlacement(pose.placement),
|
|
2714
|
+
inner: pose.inner ? { ...pose.inner } : void 0,
|
|
2715
|
+
outer: pose.outer ? { ...pose.outer } : void 0,
|
|
2716
|
+
epoch: pose.epoch ?? null,
|
|
2717
|
+
referencePlane: pose.referencePlane ?? null
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
function clonePlacement(placement) {
|
|
2721
|
+
return placement ? structuredClone(placement) : null;
|
|
2722
|
+
}
|
|
2723
|
+
function applyEventPoseOverrides(objects, events, activeEventId) {
|
|
2724
|
+
if (!activeEventId) {
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
const event = events.find((entry) => entry.id === activeEventId);
|
|
2728
|
+
if (!event) {
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
const objectMap = new Map(objects.map((object) => [object.id, object]));
|
|
2732
|
+
const referencedIds = /* @__PURE__ */ new Set([
|
|
2733
|
+
...event.targetObjectId ? [event.targetObjectId] : [],
|
|
2734
|
+
...event.participantObjectIds,
|
|
2735
|
+
...event.positions.map((pose) => pose.objectId)
|
|
2736
|
+
]);
|
|
2737
|
+
for (const objectId of referencedIds) {
|
|
2738
|
+
const object = objectMap.get(objectId);
|
|
2739
|
+
if (!object) {
|
|
2740
|
+
continue;
|
|
2741
|
+
}
|
|
2742
|
+
if (event.epoch) {
|
|
2743
|
+
object.epoch = event.epoch;
|
|
2744
|
+
}
|
|
2745
|
+
if (event.referencePlane) {
|
|
2746
|
+
object.referencePlane = event.referencePlane;
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
for (const pose of event.positions) {
|
|
2750
|
+
const object = objectMap.get(pose.objectId);
|
|
2751
|
+
if (!object) {
|
|
2752
|
+
continue;
|
|
2753
|
+
}
|
|
2754
|
+
if (pose.placement) {
|
|
2755
|
+
object.placement = clonePlacement(pose.placement);
|
|
2756
|
+
}
|
|
2757
|
+
if (pose.inner) {
|
|
2758
|
+
object.properties.inner = { ...pose.inner };
|
|
2759
|
+
}
|
|
2760
|
+
if (pose.outer) {
|
|
2761
|
+
object.properties.outer = { ...pose.outer };
|
|
2762
|
+
}
|
|
2763
|
+
if (pose.epoch) {
|
|
2764
|
+
object.epoch = pose.epoch;
|
|
2765
|
+
}
|
|
2766
|
+
if (pose.referencePlane) {
|
|
2767
|
+
object.referencePlane = pose.referencePlane;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2392
2771
|
function cloneProperties(properties) {
|
|
2393
2772
|
const next = {};
|
|
2394
2773
|
for (const [key, value] of Object.entries(properties)) {
|
|
@@ -2461,6 +2840,18 @@
|
|
|
2461
2840
|
if (viewpoint.rotationDeg !== 0) {
|
|
2462
2841
|
info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
|
|
2463
2842
|
}
|
|
2843
|
+
if (viewpoint.camera?.azimuth !== null) {
|
|
2844
|
+
info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
|
|
2845
|
+
}
|
|
2846
|
+
if (viewpoint.camera?.elevation !== null) {
|
|
2847
|
+
info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
|
|
2848
|
+
}
|
|
2849
|
+
if (viewpoint.camera?.roll !== null) {
|
|
2850
|
+
info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
|
|
2851
|
+
}
|
|
2852
|
+
if (viewpoint.camera?.distance !== null) {
|
|
2853
|
+
info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
|
|
2854
|
+
}
|
|
2464
2855
|
const serializedLayers = serializeViewpointLayers(viewpoint.layers);
|
|
2465
2856
|
if (serializedLayers) {
|
|
2466
2857
|
info2[`${prefix}.layers`] = serializedLayers;
|
|
@@ -2477,6 +2868,9 @@
|
|
|
2477
2868
|
if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
|
|
2478
2869
|
info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
|
|
2479
2870
|
}
|
|
2871
|
+
if (viewpoint.events.length > 0) {
|
|
2872
|
+
info2[`${prefix}.events`] = viewpoint.events.join(" ");
|
|
2873
|
+
}
|
|
2480
2874
|
}
|
|
2481
2875
|
for (const annotation of system.annotations) {
|
|
2482
2876
|
const prefix = `annotation.${annotation.id}`;
|
|
@@ -2501,7 +2895,7 @@
|
|
|
2501
2895
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
2502
2896
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
2503
2897
|
}
|
|
2504
|
-
for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
|
|
2898
|
+
for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
|
|
2505
2899
|
if (layers[key] !== void 0) {
|
|
2506
2900
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
2507
2901
|
}
|
|
@@ -2675,6 +3069,7 @@
|
|
|
2675
3069
|
const diagnostics = [];
|
|
2676
3070
|
const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
|
|
2677
3071
|
const groupIds = new Set(document2.groups.map((group) => group.id));
|
|
3072
|
+
const eventIds = new Set(document2.events.map((event) => event.id));
|
|
2678
3073
|
if (!document2.system) {
|
|
2679
3074
|
diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
|
|
2680
3075
|
}
|
|
@@ -2684,6 +3079,7 @@
|
|
|
2684
3079
|
["viewpoint", document2.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
|
|
2685
3080
|
["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
|
|
2686
3081
|
["relation", document2.relations.map((relation) => relation.id)],
|
|
3082
|
+
["event", document2.events.map((event) => event.id)],
|
|
2687
3083
|
["object", document2.objects.map((object) => object.id)]
|
|
2688
3084
|
]) {
|
|
2689
3085
|
for (const id of ids) {
|
|
@@ -2699,11 +3095,14 @@
|
|
|
2699
3095
|
validateRelation(relation, objectMap, diagnostics);
|
|
2700
3096
|
}
|
|
2701
3097
|
for (const viewpoint of document2.system?.viewpoints ?? []) {
|
|
2702
|
-
|
|
3098
|
+
validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
|
|
2703
3099
|
}
|
|
2704
3100
|
for (const object of document2.objects) {
|
|
2705
3101
|
validateObject(object, document2.system, objectMap, groupIds, diagnostics);
|
|
2706
3102
|
}
|
|
3103
|
+
for (const event of document2.events) {
|
|
3104
|
+
validateEvent(event, document2.system, objectMap, diagnostics);
|
|
3105
|
+
}
|
|
2707
3106
|
return diagnostics;
|
|
2708
3107
|
}
|
|
2709
3108
|
function validateRelation(relation, objectMap, diagnostics) {
|
|
@@ -2721,15 +3120,24 @@
|
|
|
2721
3120
|
diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
|
|
2722
3121
|
}
|
|
2723
3122
|
}
|
|
2724
|
-
function
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
3123
|
+
function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
|
|
3124
|
+
const filter = viewpoint.filter;
|
|
3125
|
+
if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
|
|
3126
|
+
if (filter) {
|
|
3127
|
+
for (const groupId of filter.groupIds) {
|
|
3128
|
+
if (!groupIds.has(groupId)) {
|
|
3129
|
+
diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
for (const eventId of viewpoint.events ?? []) {
|
|
3134
|
+
if (!eventIds.has(eventId)) {
|
|
3135
|
+
diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
|
|
3136
|
+
}
|
|
2731
3137
|
}
|
|
2732
3138
|
}
|
|
3139
|
+
validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
|
|
3140
|
+
validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
|
|
2733
3141
|
}
|
|
2734
3142
|
function validateObject(object, system, objectMap, groupIds, diagnostics) {
|
|
2735
3143
|
const placement = object.placement;
|
|
@@ -2742,6 +3150,12 @@
|
|
|
2742
3150
|
}
|
|
2743
3151
|
}
|
|
2744
3152
|
}
|
|
3153
|
+
if (typeof object.epoch === "string" && !object.epoch.trim()) {
|
|
3154
|
+
diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
|
|
3155
|
+
}
|
|
3156
|
+
if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
|
|
3157
|
+
diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
|
|
3158
|
+
}
|
|
2745
3159
|
if (orbitPlacement) {
|
|
2746
3160
|
if (!objectMap.has(orbitPlacement.target)) {
|
|
2747
3161
|
diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
|
|
@@ -2813,6 +3227,122 @@
|
|
|
2813
3227
|
}
|
|
2814
3228
|
}
|
|
2815
3229
|
}
|
|
3230
|
+
function validateEvent(event, system, objectMap, diagnostics) {
|
|
3231
|
+
const fieldPrefix = `event.${event.id}`;
|
|
3232
|
+
const referencedIds = /* @__PURE__ */ new Set();
|
|
3233
|
+
if (!event.kind.trim()) {
|
|
3234
|
+
diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
|
|
3235
|
+
}
|
|
3236
|
+
if (typeof event.epoch === "string" && !event.epoch.trim()) {
|
|
3237
|
+
diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
|
|
3238
|
+
}
|
|
3239
|
+
if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
|
|
3240
|
+
diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
|
|
3241
|
+
}
|
|
3242
|
+
if (!event.targetObjectId && event.participantObjectIds.length === 0) {
|
|
3243
|
+
diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
|
|
3244
|
+
}
|
|
3245
|
+
if (event.targetObjectId) {
|
|
3246
|
+
referencedIds.add(event.targetObjectId);
|
|
3247
|
+
if (!objectMap.has(event.targetObjectId)) {
|
|
3248
|
+
diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, void 0, `${fieldPrefix}.target`));
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
const seenParticipants = /* @__PURE__ */ new Set();
|
|
3252
|
+
for (const participantId of event.participantObjectIds) {
|
|
3253
|
+
referencedIds.add(participantId);
|
|
3254
|
+
if (seenParticipants.has(participantId)) {
|
|
3255
|
+
diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, void 0, `${fieldPrefix}.participants`));
|
|
3256
|
+
continue;
|
|
3257
|
+
}
|
|
3258
|
+
seenParticipants.add(participantId);
|
|
3259
|
+
if (!objectMap.has(participantId)) {
|
|
3260
|
+
diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, void 0, `${fieldPrefix}.participants`));
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
if (event.targetObjectId && event.participantObjectIds.length > 0 && !event.participantObjectIds.includes(event.targetObjectId)) {
|
|
3264
|
+
diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, void 0, `${fieldPrefix}.target`));
|
|
3265
|
+
}
|
|
3266
|
+
if (event.positions.length === 0) {
|
|
3267
|
+
diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, void 0, `${fieldPrefix}.positions`));
|
|
3268
|
+
}
|
|
3269
|
+
if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
|
|
3270
|
+
diagnostics.push(warn("validate.event.kind.participants", `Event "${event.id}" looks like an eclipse or transit but references fewer than three bodies.`, void 0, `${fieldPrefix}.participants`));
|
|
3271
|
+
}
|
|
3272
|
+
const poseIds = /* @__PURE__ */ new Set();
|
|
3273
|
+
for (const pose of event.positions) {
|
|
3274
|
+
const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
|
|
3275
|
+
if (poseIds.has(pose.objectId)) {
|
|
3276
|
+
diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, void 0, poseFieldPrefix));
|
|
3277
|
+
continue;
|
|
3278
|
+
}
|
|
3279
|
+
poseIds.add(pose.objectId);
|
|
3280
|
+
const object = objectMap.get(pose.objectId);
|
|
3281
|
+
if (!object) {
|
|
3282
|
+
diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, void 0, poseFieldPrefix));
|
|
3283
|
+
continue;
|
|
3284
|
+
}
|
|
3285
|
+
if (!referencedIds.has(pose.objectId)) {
|
|
3286
|
+
diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
|
|
3287
|
+
}
|
|
3288
|
+
validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
|
|
3289
|
+
}
|
|
3290
|
+
const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
|
|
3291
|
+
if (event.positions.length > 0 && missingPoseIds.length > 0) {
|
|
3292
|
+
diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
|
|
3296
|
+
const placement = pose.placement;
|
|
3297
|
+
if (!placement) {
|
|
3298
|
+
diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
|
|
3299
|
+
return;
|
|
3300
|
+
}
|
|
3301
|
+
if (placement.mode === "orbit") {
|
|
3302
|
+
if (!objectMap.has(placement.target)) {
|
|
3303
|
+
diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
|
|
3304
|
+
}
|
|
3305
|
+
if (placement.distance && placement.semiMajor) {
|
|
3306
|
+
diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
|
|
3307
|
+
}
|
|
3308
|
+
if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
|
|
3309
|
+
diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
|
|
3310
|
+
}
|
|
3311
|
+
if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
|
|
3312
|
+
diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
|
|
3313
|
+
}
|
|
3314
|
+
if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
|
|
3315
|
+
diagnostics.push(warn("validate.event.pose.period.massMissing", `Event "${eventId}" pose "${pose.objectId}" sets "period" but its central mass cannot be derived.`, void 0, `${fieldPrefix}.period`));
|
|
3316
|
+
}
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
if (placement.mode === "surface") {
|
|
3320
|
+
const target = objectMap.get(placement.target);
|
|
3321
|
+
if (!target) {
|
|
3322
|
+
diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.surface`));
|
|
3323
|
+
} else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
|
|
3324
|
+
diagnostics.push(error("validate.event.pose.surface.target.invalid", `Event surface target "${placement.target}" on "${eventId}:${pose.objectId}" is not surface-capable.`, void 0, `${fieldPrefix}.surface`));
|
|
3325
|
+
}
|
|
3326
|
+
return;
|
|
3327
|
+
}
|
|
3328
|
+
if (placement.mode === "at") {
|
|
3329
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
3330
|
+
diagnostics.push(error("validate.event.pose.at.objectType", `Only structures and phenomena may use "at" placement in events; found "${object.type}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3331
|
+
}
|
|
3332
|
+
const reference = placement.reference;
|
|
3333
|
+
if (reference.kind === "named" && !objectMap.has(reference.name)) {
|
|
3334
|
+
diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3335
|
+
} else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
|
|
3336
|
+
diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3337
|
+
} else if (reference.kind === "lagrange") {
|
|
3338
|
+
if (!objectMap.has(reference.primary)) {
|
|
3339
|
+
diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3340
|
+
} else if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
3341
|
+
diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
2816
3346
|
function validateAtTarget(object, objectMap, diagnostics) {
|
|
2817
3347
|
const reference = object.placement?.mode === "at" ? object.placement.reference : null;
|
|
2818
3348
|
if (!reference) {
|
|
@@ -2918,6 +3448,52 @@
|
|
|
2918
3448
|
return null;
|
|
2919
3449
|
}
|
|
2920
3450
|
}
|
|
3451
|
+
function validateProjection(projection, diagnostics, field, viewpointId) {
|
|
3452
|
+
if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
|
|
3453
|
+
diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
|
|
3457
|
+
if (!camera) {
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
const prefix = `viewpoint.${viewpointId}.camera`;
|
|
3461
|
+
for (const [key, value] of [
|
|
3462
|
+
["azimuth", camera.azimuth],
|
|
3463
|
+
["elevation", camera.elevation],
|
|
3464
|
+
["roll", camera.roll],
|
|
3465
|
+
["distance", camera.distance]
|
|
3466
|
+
]) {
|
|
3467
|
+
if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
|
|
3468
|
+
diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
if (camera.distance !== null && projection !== "perspective") {
|
|
3472
|
+
diagnostics.push(warn("validate.viewpoint.camera.distance.partialEffect", `Camera "distance" only has a semantic effect in perspective viewpoints; "${viewpointId}" uses "${projection}".`, void 0, `${prefix}.distance`));
|
|
3473
|
+
}
|
|
3474
|
+
if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
|
|
3475
|
+
diagnostics.push(warn("validate.viewpoint.camera.topdownPartial", `Camera elevation/roll on topdown viewpoint "${viewpointId}" are currently stored for future 3D use and only partially affect 2D rendering.`, void 0, prefix));
|
|
3476
|
+
}
|
|
3477
|
+
if (projection === "isometric" && camera.elevation !== null) {
|
|
3478
|
+
diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
|
|
3479
|
+
}
|
|
3480
|
+
if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
|
|
3481
|
+
diagnostics.push(warn("validate.viewpoint.rotation.cameraOverlap", `Viewpoint "${viewpointId}" uses camera.azimuth; keep "rotation" only for 2D screen rotation to avoid ambiguity.`, void 0, `${prefix}.azimuth`));
|
|
3482
|
+
}
|
|
3483
|
+
const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
|
|
3484
|
+
if (!hasAnchor) {
|
|
3485
|
+
diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
function resolveEffectiveEpoch(system, object, event, pose) {
|
|
3489
|
+
return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
|
|
3490
|
+
}
|
|
3491
|
+
function resolveEffectiveReferencePlane(system, object, event, pose) {
|
|
3492
|
+
return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
|
|
3493
|
+
}
|
|
3494
|
+
function normalizeOptionalContextString(value) {
|
|
3495
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
3496
|
+
}
|
|
2921
3497
|
function toleranceForField(object, field) {
|
|
2922
3498
|
const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
|
|
2923
3499
|
if (typeof tolerance === "number") {
|
|
@@ -3013,6 +3589,23 @@
|
|
|
3013
3589
|
});
|
|
3014
3590
|
}
|
|
3015
3591
|
var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
|
|
3592
|
+
var EVENT_POSE_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
3593
|
+
"orbit",
|
|
3594
|
+
"distance",
|
|
3595
|
+
"semiMajor",
|
|
3596
|
+
"eccentricity",
|
|
3597
|
+
"period",
|
|
3598
|
+
"angle",
|
|
3599
|
+
"inclination",
|
|
3600
|
+
"phase",
|
|
3601
|
+
"at",
|
|
3602
|
+
"surface",
|
|
3603
|
+
"free",
|
|
3604
|
+
"inner",
|
|
3605
|
+
"outer",
|
|
3606
|
+
"epoch",
|
|
3607
|
+
"referencePlane"
|
|
3608
|
+
]);
|
|
3016
3609
|
function parseWorldOrbitAtlas(source) {
|
|
3017
3610
|
return parseAtlasSource(source);
|
|
3018
3611
|
}
|
|
@@ -3027,12 +3620,15 @@
|
|
|
3027
3620
|
const objectNodes = [];
|
|
3028
3621
|
const groups = [];
|
|
3029
3622
|
const relations = [];
|
|
3623
|
+
const events = [];
|
|
3624
|
+
const eventPoseNodes = /* @__PURE__ */ new Map();
|
|
3030
3625
|
let sawDefaults = false;
|
|
3031
3626
|
let sawAtlas = false;
|
|
3032
3627
|
const viewpointIds = /* @__PURE__ */ new Set();
|
|
3033
3628
|
const annotationIds = /* @__PURE__ */ new Set();
|
|
3034
3629
|
const groupIds = /* @__PURE__ */ new Set();
|
|
3035
3630
|
const relationIds = /* @__PURE__ */ new Set();
|
|
3631
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
3036
3632
|
for (let index = 0; index < lines.length; index++) {
|
|
3037
3633
|
const rawLine = lines[index];
|
|
3038
3634
|
const lineNumber = index + 1;
|
|
@@ -3050,7 +3646,7 @@
|
|
|
3050
3646
|
if (!sawSchemaHeader) {
|
|
3051
3647
|
sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
|
|
3052
3648
|
sawSchemaHeader = true;
|
|
3053
|
-
if (prepared.comments.length > 0 && sourceSchemaVersion
|
|
3649
|
+
if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
3054
3650
|
diagnostics.push({
|
|
3055
3651
|
code: "parse.schema21.commentCompatibility",
|
|
3056
3652
|
severity: "warning",
|
|
@@ -3063,7 +3659,7 @@
|
|
|
3063
3659
|
continue;
|
|
3064
3660
|
}
|
|
3065
3661
|
if (indent === 0) {
|
|
3066
|
-
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
|
|
3662
|
+
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
|
|
3067
3663
|
if (section.kind === "system") {
|
|
3068
3664
|
system = section.system;
|
|
3069
3665
|
} else if (section.kind === "defaults") {
|
|
@@ -3082,6 +3678,7 @@
|
|
|
3082
3678
|
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
3083
3679
|
}
|
|
3084
3680
|
const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
|
|
3681
|
+
const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
|
|
3085
3682
|
const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
|
|
3086
3683
|
const baseDocument = {
|
|
3087
3684
|
format: "worldorbit",
|
|
@@ -3089,6 +3686,7 @@
|
|
|
3089
3686
|
system,
|
|
3090
3687
|
groups,
|
|
3091
3688
|
relations,
|
|
3689
|
+
events: normalizedEvents,
|
|
3092
3690
|
objects,
|
|
3093
3691
|
diagnostics
|
|
3094
3692
|
};
|
|
@@ -3118,13 +3716,13 @@
|
|
|
3118
3716
|
return document2;
|
|
3119
3717
|
}
|
|
3120
3718
|
function assertDraftSchemaHeader(tokens, line) {
|
|
3121
|
-
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
|
|
3122
|
-
throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
3719
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
|
|
3720
|
+
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);
|
|
3123
3721
|
}
|
|
3124
3722
|
const version = tokens[1].value.toLowerCase();
|
|
3125
|
-
return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
3723
|
+
return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
3126
3724
|
}
|
|
3127
|
-
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
|
|
3725
|
+
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
|
|
3128
3726
|
const keyword = tokens[0]?.value.toLowerCase();
|
|
3129
3727
|
switch (keyword) {
|
|
3130
3728
|
case "system":
|
|
@@ -3142,6 +3740,8 @@
|
|
|
3142
3740
|
return {
|
|
3143
3741
|
kind: "defaults",
|
|
3144
3742
|
system,
|
|
3743
|
+
sourceSchemaVersion,
|
|
3744
|
+
diagnostics,
|
|
3145
3745
|
seenFields: /* @__PURE__ */ new Set()
|
|
3146
3746
|
};
|
|
3147
3747
|
case "atlas":
|
|
@@ -3161,7 +3761,7 @@
|
|
|
3161
3761
|
if (!system) {
|
|
3162
3762
|
throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
|
|
3163
3763
|
}
|
|
3164
|
-
return startViewpointSection(tokens, line, system, viewpointIds);
|
|
3764
|
+
return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
|
|
3165
3765
|
case "annotation":
|
|
3166
3766
|
if (!system) {
|
|
3167
3767
|
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
@@ -3173,6 +3773,9 @@
|
|
|
3173
3773
|
case "relation":
|
|
3174
3774
|
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
|
|
3175
3775
|
return startRelationSection(tokens, line, relations, relationIds);
|
|
3776
|
+
case "event":
|
|
3777
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
|
|
3778
|
+
return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
|
|
3176
3779
|
case "object":
|
|
3177
3780
|
return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
|
|
3178
3781
|
default:
|
|
@@ -3209,7 +3812,7 @@
|
|
|
3209
3812
|
seenFields: /* @__PURE__ */ new Set()
|
|
3210
3813
|
};
|
|
3211
3814
|
}
|
|
3212
|
-
function startViewpointSection(tokens, line, system, viewpointIds) {
|
|
3815
|
+
function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
|
|
3213
3816
|
if (tokens.length !== 2) {
|
|
3214
3817
|
throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
|
|
3215
3818
|
}
|
|
@@ -3226,10 +3829,12 @@
|
|
|
3226
3829
|
summary: "",
|
|
3227
3830
|
focusObjectId: null,
|
|
3228
3831
|
selectedObjectId: null,
|
|
3832
|
+
events: [],
|
|
3229
3833
|
projection: system.defaults.view,
|
|
3230
3834
|
preset: system.defaults.preset,
|
|
3231
3835
|
zoom: null,
|
|
3232
3836
|
rotationDeg: 0,
|
|
3837
|
+
camera: null,
|
|
3233
3838
|
layers: {},
|
|
3234
3839
|
filter: null
|
|
3235
3840
|
};
|
|
@@ -3238,10 +3843,15 @@
|
|
|
3238
3843
|
return {
|
|
3239
3844
|
kind: "viewpoint",
|
|
3240
3845
|
viewpoint,
|
|
3846
|
+
sourceSchemaVersion,
|
|
3847
|
+
diagnostics,
|
|
3241
3848
|
seenFields: /* @__PURE__ */ new Set(),
|
|
3242
3849
|
inFilter: false,
|
|
3243
3850
|
filterIndent: null,
|
|
3244
|
-
seenFilterFields: /* @__PURE__ */ new Set()
|
|
3851
|
+
seenFilterFields: /* @__PURE__ */ new Set(),
|
|
3852
|
+
inCamera: false,
|
|
3853
|
+
cameraIndent: null,
|
|
3854
|
+
seenCameraFields: /* @__PURE__ */ new Set()
|
|
3245
3855
|
};
|
|
3246
3856
|
}
|
|
3247
3857
|
function startAnnotationSection(tokens, line, system, annotationIds) {
|
|
@@ -3328,6 +3938,51 @@
|
|
|
3328
3938
|
seenFields: /* @__PURE__ */ new Set()
|
|
3329
3939
|
};
|
|
3330
3940
|
}
|
|
3941
|
+
function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
|
|
3942
|
+
if (tokens.length !== 2) {
|
|
3943
|
+
throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
|
|
3944
|
+
}
|
|
3945
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
3946
|
+
if (!id) {
|
|
3947
|
+
throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
|
|
3948
|
+
}
|
|
3949
|
+
if (eventIds.has(id)) {
|
|
3950
|
+
throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
|
|
3951
|
+
}
|
|
3952
|
+
const event = {
|
|
3953
|
+
id,
|
|
3954
|
+
kind: "",
|
|
3955
|
+
label: humanizeIdentifier2(id),
|
|
3956
|
+
summary: null,
|
|
3957
|
+
targetObjectId: null,
|
|
3958
|
+
participantObjectIds: [],
|
|
3959
|
+
timing: null,
|
|
3960
|
+
visibility: null,
|
|
3961
|
+
epoch: null,
|
|
3962
|
+
referencePlane: null,
|
|
3963
|
+
tags: [],
|
|
3964
|
+
color: null,
|
|
3965
|
+
hidden: false,
|
|
3966
|
+
positions: []
|
|
3967
|
+
};
|
|
3968
|
+
const rawPoses = [];
|
|
3969
|
+
events.push(event);
|
|
3970
|
+
eventPoseNodes.set(id, rawPoses);
|
|
3971
|
+
eventIds.add(id);
|
|
3972
|
+
return {
|
|
3973
|
+
kind: "event",
|
|
3974
|
+
event,
|
|
3975
|
+
sourceSchemaVersion,
|
|
3976
|
+
diagnostics,
|
|
3977
|
+
seenFields: /* @__PURE__ */ new Set(),
|
|
3978
|
+
rawPoses,
|
|
3979
|
+
inPositions: false,
|
|
3980
|
+
positionsIndent: null,
|
|
3981
|
+
activePose: null,
|
|
3982
|
+
poseIndent: null,
|
|
3983
|
+
activePoseSeenFields: /* @__PURE__ */ new Set()
|
|
3984
|
+
};
|
|
3985
|
+
}
|
|
3331
3986
|
function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
|
|
3332
3987
|
if (tokens.length < 3) {
|
|
3333
3988
|
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
@@ -3384,6 +4039,9 @@
|
|
|
3384
4039
|
case "relation":
|
|
3385
4040
|
applyRelationField(section, tokens, line);
|
|
3386
4041
|
return;
|
|
4042
|
+
case "event":
|
|
4043
|
+
applyEventField(section, indent, tokens, line);
|
|
4044
|
+
return;
|
|
3387
4045
|
case "object":
|
|
3388
4046
|
applyObjectField(section, indent, tokens, line);
|
|
3389
4047
|
return;
|
|
@@ -3426,6 +4084,12 @@
|
|
|
3426
4084
|
const value = joinFieldValue(tokens, line);
|
|
3427
4085
|
switch (key) {
|
|
3428
4086
|
case "view":
|
|
4087
|
+
if (isSchema25Projection(value)) {
|
|
4088
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
|
|
4089
|
+
line,
|
|
4090
|
+
column: tokens[0].column
|
|
4091
|
+
});
|
|
4092
|
+
}
|
|
3429
4093
|
section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
|
|
3430
4094
|
return;
|
|
3431
4095
|
case "scale":
|
|
@@ -3465,14 +4129,36 @@
|
|
|
3465
4129
|
throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3466
4130
|
}
|
|
3467
4131
|
function applyViewpointField2(section, indent, tokens, line) {
|
|
4132
|
+
if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
|
|
4133
|
+
section.inCamera = false;
|
|
4134
|
+
section.cameraIndent = null;
|
|
4135
|
+
}
|
|
3468
4136
|
if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
|
|
3469
4137
|
section.inFilter = false;
|
|
3470
4138
|
section.filterIndent = null;
|
|
3471
4139
|
}
|
|
4140
|
+
if (section.inCamera) {
|
|
4141
|
+
applyViewpointCameraField(section, tokens, line);
|
|
4142
|
+
return;
|
|
4143
|
+
}
|
|
3472
4144
|
if (section.inFilter) {
|
|
3473
4145
|
applyViewpointFilterField(section, tokens, line);
|
|
3474
4146
|
return;
|
|
3475
4147
|
}
|
|
4148
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
|
|
4149
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
|
|
4150
|
+
line,
|
|
4151
|
+
column: tokens[0].column
|
|
4152
|
+
});
|
|
4153
|
+
if (section.seenFields.has("camera")) {
|
|
4154
|
+
throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
|
|
4155
|
+
}
|
|
4156
|
+
section.seenFields.add("camera");
|
|
4157
|
+
section.inCamera = true;
|
|
4158
|
+
section.cameraIndent = indent;
|
|
4159
|
+
section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
|
|
4160
|
+
return;
|
|
4161
|
+
}
|
|
3476
4162
|
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
|
|
3477
4163
|
if (section.seenFields.has("filter")) {
|
|
3478
4164
|
throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
|
|
@@ -3498,6 +4184,12 @@
|
|
|
3498
4184
|
section.viewpoint.selectedObjectId = value;
|
|
3499
4185
|
return;
|
|
3500
4186
|
case "projection":
|
|
4187
|
+
if (isSchema25Projection(value)) {
|
|
4188
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
|
|
4189
|
+
line,
|
|
4190
|
+
column: tokens[0].column
|
|
4191
|
+
});
|
|
4192
|
+
}
|
|
3501
4193
|
section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
|
|
3502
4194
|
return;
|
|
3503
4195
|
case "preset":
|
|
@@ -3509,13 +4201,49 @@
|
|
|
3509
4201
|
case "rotation":
|
|
3510
4202
|
section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
|
|
3511
4203
|
return;
|
|
4204
|
+
case "camera":
|
|
4205
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
|
|
4206
|
+
line,
|
|
4207
|
+
column: tokens[0].column
|
|
4208
|
+
});
|
|
4209
|
+
section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
|
|
4210
|
+
return;
|
|
3512
4211
|
case "layers":
|
|
3513
|
-
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
|
|
4212
|
+
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
|
|
4213
|
+
return;
|
|
4214
|
+
case "events":
|
|
4215
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
|
|
4216
|
+
line,
|
|
4217
|
+
column: tokens[0].column
|
|
4218
|
+
});
|
|
4219
|
+
section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
|
|
3514
4220
|
return;
|
|
3515
4221
|
default:
|
|
3516
4222
|
throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3517
4223
|
}
|
|
3518
4224
|
}
|
|
4225
|
+
function applyViewpointCameraField(section, tokens, line) {
|
|
4226
|
+
const key = requireUniqueField(tokens, section.seenCameraFields, line);
|
|
4227
|
+
const value = joinFieldValue(tokens, line);
|
|
4228
|
+
const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
|
|
4229
|
+
switch (key) {
|
|
4230
|
+
case "azimuth":
|
|
4231
|
+
camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
|
|
4232
|
+
break;
|
|
4233
|
+
case "elevation":
|
|
4234
|
+
camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
|
|
4235
|
+
break;
|
|
4236
|
+
case "roll":
|
|
4237
|
+
camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
|
|
4238
|
+
break;
|
|
4239
|
+
case "distance":
|
|
4240
|
+
camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
|
|
4241
|
+
break;
|
|
4242
|
+
default:
|
|
4243
|
+
throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4244
|
+
}
|
|
4245
|
+
section.viewpoint.camera = camera;
|
|
4246
|
+
}
|
|
3519
4247
|
function applyViewpointFilterField(section, tokens, line) {
|
|
3520
4248
|
const key = requireUniqueField(tokens, section.seenFilterFields, line);
|
|
3521
4249
|
const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
|
|
@@ -3615,6 +4343,126 @@
|
|
|
3615
4343
|
throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3616
4344
|
}
|
|
3617
4345
|
}
|
|
4346
|
+
function applyEventField(section, indent, tokens, line) {
|
|
4347
|
+
if (section.activePose && indent <= (section.poseIndent ?? 0)) {
|
|
4348
|
+
section.activePose = null;
|
|
4349
|
+
section.poseIndent = null;
|
|
4350
|
+
section.activePoseSeenFields.clear();
|
|
4351
|
+
}
|
|
4352
|
+
if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
|
|
4353
|
+
section.inPositions = false;
|
|
4354
|
+
section.positionsIndent = null;
|
|
4355
|
+
}
|
|
4356
|
+
if (section.activePose) {
|
|
4357
|
+
if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
|
|
4358
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
|
|
4359
|
+
line,
|
|
4360
|
+
column: tokens[0]?.column ?? 1
|
|
4361
|
+
});
|
|
4362
|
+
}
|
|
4363
|
+
section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
|
|
4364
|
+
return;
|
|
4365
|
+
}
|
|
4366
|
+
if (section.inPositions) {
|
|
4367
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
|
|
4368
|
+
throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
|
|
4369
|
+
}
|
|
4370
|
+
const objectId = tokens[1].value;
|
|
4371
|
+
if (!objectId.trim()) {
|
|
4372
|
+
throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
|
|
4373
|
+
}
|
|
4374
|
+
const rawPose = {
|
|
4375
|
+
objectId,
|
|
4376
|
+
fields: [],
|
|
4377
|
+
location: { line, column: tokens[0].column }
|
|
4378
|
+
};
|
|
4379
|
+
section.rawPoses.push(rawPose);
|
|
4380
|
+
section.activePose = rawPose;
|
|
4381
|
+
section.poseIndent = indent;
|
|
4382
|
+
section.activePoseSeenFields = /* @__PURE__ */ new Set();
|
|
4383
|
+
return;
|
|
4384
|
+
}
|
|
4385
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
|
|
4386
|
+
if (section.seenFields.has("positions")) {
|
|
4387
|
+
throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
|
|
4388
|
+
}
|
|
4389
|
+
section.seenFields.add("positions");
|
|
4390
|
+
section.inPositions = true;
|
|
4391
|
+
section.positionsIndent = indent;
|
|
4392
|
+
return;
|
|
4393
|
+
}
|
|
4394
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
4395
|
+
switch (key) {
|
|
4396
|
+
case "kind":
|
|
4397
|
+
section.event.kind = joinFieldValue(tokens, line);
|
|
4398
|
+
return;
|
|
4399
|
+
case "label":
|
|
4400
|
+
section.event.label = joinFieldValue(tokens, line);
|
|
4401
|
+
return;
|
|
4402
|
+
case "summary":
|
|
4403
|
+
section.event.summary = joinFieldValue(tokens, line);
|
|
4404
|
+
return;
|
|
4405
|
+
case "target":
|
|
4406
|
+
section.event.targetObjectId = joinFieldValue(tokens, line);
|
|
4407
|
+
return;
|
|
4408
|
+
case "participants":
|
|
4409
|
+
section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
|
|
4410
|
+
return;
|
|
4411
|
+
case "timing":
|
|
4412
|
+
section.event.timing = joinFieldValue(tokens, line);
|
|
4413
|
+
return;
|
|
4414
|
+
case "visibility":
|
|
4415
|
+
section.event.visibility = joinFieldValue(tokens, line);
|
|
4416
|
+
return;
|
|
4417
|
+
case "epoch":
|
|
4418
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
|
|
4419
|
+
line,
|
|
4420
|
+
column: tokens[0].column
|
|
4421
|
+
});
|
|
4422
|
+
section.event.epoch = joinFieldValue(tokens, line);
|
|
4423
|
+
return;
|
|
4424
|
+
case "referenceplane":
|
|
4425
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
|
|
4426
|
+
line,
|
|
4427
|
+
column: tokens[0].column
|
|
4428
|
+
});
|
|
4429
|
+
section.event.referencePlane = joinFieldValue(tokens, line);
|
|
4430
|
+
return;
|
|
4431
|
+
case "tags":
|
|
4432
|
+
section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
4433
|
+
return;
|
|
4434
|
+
case "color":
|
|
4435
|
+
section.event.color = joinFieldValue(tokens, line);
|
|
4436
|
+
return;
|
|
4437
|
+
case "hidden":
|
|
4438
|
+
section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
4439
|
+
line,
|
|
4440
|
+
column: tokens[0].column
|
|
4441
|
+
});
|
|
4442
|
+
return;
|
|
4443
|
+
default:
|
|
4444
|
+
throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
function parseEventPoseField(tokens, line, seenFields) {
|
|
4448
|
+
if (tokens.length < 2) {
|
|
4449
|
+
throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
|
|
4450
|
+
}
|
|
4451
|
+
const key = tokens[0].value;
|
|
4452
|
+
if (!EVENT_POSE_FIELD_KEYS.has(key)) {
|
|
4453
|
+
throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
|
|
4454
|
+
}
|
|
4455
|
+
if (seenFields.has(key)) {
|
|
4456
|
+
throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
|
|
4457
|
+
}
|
|
4458
|
+
seenFields.add(key);
|
|
4459
|
+
return {
|
|
4460
|
+
type: "field",
|
|
4461
|
+
key,
|
|
4462
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
4463
|
+
location: { line, column: tokens[0].column }
|
|
4464
|
+
};
|
|
4465
|
+
}
|
|
3618
4466
|
function applyObjectField(section, indent, tokens, line) {
|
|
3619
4467
|
if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
|
|
3620
4468
|
section.activeBlock = null;
|
|
@@ -3673,7 +4521,7 @@
|
|
|
3673
4521
|
function parseObjectTypeTokens(tokens, line) {
|
|
3674
4522
|
return parseTokenList(tokens, line, "objectTypes").filter((value) => value === "star" || value === "planet" || value === "moon" || value === "belt" || value === "asteroid" || value === "comet" || value === "ring" || value === "structure" || value === "phenomenon");
|
|
3675
4523
|
}
|
|
3676
|
-
function parseLayerTokens(tokens, line) {
|
|
4524
|
+
function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
3677
4525
|
const layers = {};
|
|
3678
4526
|
for (const token of parseTokenList(tokens, line, "layers")) {
|
|
3679
4527
|
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
@@ -3683,7 +4531,13 @@
|
|
|
3683
4531
|
layers["orbits-front"] = enabled;
|
|
3684
4532
|
continue;
|
|
3685
4533
|
}
|
|
3686
|
-
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
4534
|
+
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
4535
|
+
if (raw === "events" && sourceSchemaVersion && diagnostics) {
|
|
4536
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
|
|
4537
|
+
line,
|
|
4538
|
+
column: tokens[0]?.column ?? 1
|
|
4539
|
+
});
|
|
4540
|
+
}
|
|
3687
4541
|
layers[raw] = enabled;
|
|
3688
4542
|
}
|
|
3689
4543
|
}
|
|
@@ -3701,11 +4555,15 @@
|
|
|
3701
4555
|
}
|
|
3702
4556
|
function parseProjectionValue(value, line, column) {
|
|
3703
4557
|
const normalized = value.toLowerCase();
|
|
3704
|
-
if (normalized !== "topdown" && normalized !== "isometric") {
|
|
4558
|
+
if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
|
|
3705
4559
|
throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
|
|
3706
4560
|
}
|
|
3707
4561
|
return normalized;
|
|
3708
4562
|
}
|
|
4563
|
+
function isSchema25Projection(value) {
|
|
4564
|
+
const normalized = value.toLowerCase();
|
|
4565
|
+
return normalized === "orthographic" || normalized === "perspective";
|
|
4566
|
+
}
|
|
3709
4567
|
function parsePresetValue(value, line, column) {
|
|
3710
4568
|
const normalized = value.toLowerCase();
|
|
3711
4569
|
if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
|
|
@@ -3735,6 +4593,48 @@
|
|
|
3735
4593
|
groupIds: []
|
|
3736
4594
|
};
|
|
3737
4595
|
}
|
|
4596
|
+
function createEmptyViewCamera2() {
|
|
4597
|
+
return {
|
|
4598
|
+
azimuth: null,
|
|
4599
|
+
elevation: null,
|
|
4600
|
+
roll: null,
|
|
4601
|
+
distance: null
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
4604
|
+
function parseInlineViewCamera(tokens, line, current) {
|
|
4605
|
+
if (tokens.length === 0 || tokens.length % 2 !== 0) {
|
|
4606
|
+
throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
|
|
4607
|
+
}
|
|
4608
|
+
const camera = current ? { ...current } : createEmptyViewCamera2();
|
|
4609
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4610
|
+
for (let index = 0; index < tokens.length; index += 2) {
|
|
4611
|
+
const fieldToken = tokens[index];
|
|
4612
|
+
const valueToken = tokens[index + 1];
|
|
4613
|
+
const key = fieldToken.value.toLowerCase();
|
|
4614
|
+
if (seen.has(key)) {
|
|
4615
|
+
throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
|
|
4616
|
+
}
|
|
4617
|
+
seen.add(key);
|
|
4618
|
+
const value = valueToken.value;
|
|
4619
|
+
switch (key) {
|
|
4620
|
+
case "azimuth":
|
|
4621
|
+
camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
|
|
4622
|
+
break;
|
|
4623
|
+
case "elevation":
|
|
4624
|
+
camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
|
|
4625
|
+
break;
|
|
4626
|
+
case "roll":
|
|
4627
|
+
camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
|
|
4628
|
+
break;
|
|
4629
|
+
case "distance":
|
|
4630
|
+
camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
|
|
4631
|
+
break;
|
|
4632
|
+
default:
|
|
4633
|
+
throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
return camera;
|
|
4637
|
+
}
|
|
3738
4638
|
function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
3739
4639
|
const fields = [];
|
|
3740
4640
|
let index = 0;
|
|
@@ -3822,7 +4722,7 @@
|
|
|
3822
4722
|
}
|
|
3823
4723
|
function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
3824
4724
|
const fieldMap = collectDraftFields(node.fields);
|
|
3825
|
-
const placement =
|
|
4725
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
3826
4726
|
const properties = normalizeDraftProperties(node.objectType, fieldMap);
|
|
3827
4727
|
const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
|
|
3828
4728
|
const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
|
|
@@ -3867,21 +4767,41 @@
|
|
|
3867
4767
|
object.tolerances = tolerances;
|
|
3868
4768
|
if (typedBlocks && Object.keys(typedBlocks).length > 0)
|
|
3869
4769
|
object.typedBlocks = typedBlocks;
|
|
3870
|
-
if (sourceSchemaVersion
|
|
4770
|
+
if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
3871
4771
|
if (object.groups || object.epoch || object.referencePlane || object.tidalLock !== void 0 || object.resonance || object.renderHints || object.deriveRules?.length || object.validationRules?.length || object.lockedFields?.length || object.tolerances?.length || object.typedBlocks) {
|
|
3872
4772
|
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
|
|
3873
4773
|
}
|
|
3874
4774
|
}
|
|
3875
4775
|
return object;
|
|
3876
4776
|
}
|
|
3877
|
-
function
|
|
4777
|
+
function normalizeDraftEvent(event, rawPoses) {
|
|
4778
|
+
return {
|
|
4779
|
+
...event,
|
|
4780
|
+
participantObjectIds: [...new Set(event.participantObjectIds)],
|
|
4781
|
+
tags: [...new Set(event.tags)],
|
|
4782
|
+
positions: rawPoses.map((pose) => normalizeDraftEventPose(pose))
|
|
4783
|
+
};
|
|
4784
|
+
}
|
|
4785
|
+
function normalizeDraftEventPose(rawPose) {
|
|
4786
|
+
const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
|
|
4787
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
4788
|
+
return {
|
|
4789
|
+
objectId: rawPose.objectId,
|
|
4790
|
+
placement,
|
|
4791
|
+
inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
|
|
4792
|
+
outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
|
|
4793
|
+
epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
|
|
4794
|
+
referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
|
|
4795
|
+
};
|
|
4796
|
+
}
|
|
4797
|
+
function collectDraftFields(fields, _mode = "object") {
|
|
3878
4798
|
const grouped = /* @__PURE__ */ new Map();
|
|
3879
4799
|
for (const field of fields) {
|
|
3880
4800
|
const spec = getDraftObjectFieldSpec(field.key);
|
|
3881
|
-
if (!spec) {
|
|
4801
|
+
if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
|
|
3882
4802
|
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
3883
4803
|
}
|
|
3884
|
-
if (!spec
|
|
4804
|
+
if (!spec?.allowRepeat && grouped.has(field.key)) {
|
|
3885
4805
|
throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
|
|
3886
4806
|
}
|
|
3887
4807
|
const existing = grouped.get(field.key) ?? [];
|
|
@@ -3890,7 +4810,7 @@
|
|
|
3890
4810
|
}
|
|
3891
4811
|
return grouped;
|
|
3892
4812
|
}
|
|
3893
|
-
function
|
|
4813
|
+
function extractPlacementFromFieldMap(fieldMap) {
|
|
3894
4814
|
const orbitField = fieldMap.get("orbit")?.[0];
|
|
3895
4815
|
const atField = fieldMap.get("at")?.[0];
|
|
3896
4816
|
const surfaceField = fieldMap.get("surface")?.[0];
|
|
@@ -4058,7 +4978,7 @@
|
|
|
4058
4978
|
}
|
|
4059
4979
|
}
|
|
4060
4980
|
function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
4061
|
-
if (sourceSchemaVersion
|
|
4981
|
+
if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
4062
4982
|
return;
|
|
4063
4983
|
}
|
|
4064
4984
|
diagnostics.push({
|
|
@@ -4070,6 +4990,34 @@
|
|
|
4070
4990
|
column: location.column
|
|
4071
4991
|
});
|
|
4072
4992
|
}
|
|
4993
|
+
function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
4994
|
+
if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
|
|
4995
|
+
return;
|
|
4996
|
+
}
|
|
4997
|
+
diagnostics.push({
|
|
4998
|
+
code: "parse.schema25.featureCompatibility",
|
|
4999
|
+
severity: "warning",
|
|
5000
|
+
source: "parse",
|
|
5001
|
+
message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
5002
|
+
line: location.line,
|
|
5003
|
+
column: location.column
|
|
5004
|
+
});
|
|
5005
|
+
}
|
|
5006
|
+
function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
|
|
5007
|
+
return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
|
|
5008
|
+
}
|
|
5009
|
+
function schemaVersionRank(version) {
|
|
5010
|
+
switch (version) {
|
|
5011
|
+
case "2.0-draft":
|
|
5012
|
+
return 0;
|
|
5013
|
+
case "2.0":
|
|
5014
|
+
return 1;
|
|
5015
|
+
case "2.1":
|
|
5016
|
+
return 2;
|
|
5017
|
+
case "2.5":
|
|
5018
|
+
return 3;
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
4073
5021
|
function preprocessAtlasSource(source) {
|
|
4074
5022
|
const chars = [...source];
|
|
4075
5023
|
const comments = [];
|
|
@@ -4157,8 +5105,9 @@
|
|
|
4157
5105
|
}
|
|
4158
5106
|
|
|
4159
5107
|
// packages/core/dist/load.js
|
|
4160
|
-
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
|
|
5108
|
+
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
|
|
4161
5109
|
var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
|
|
5110
|
+
var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
|
|
4162
5111
|
var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
|
|
4163
5112
|
function detectWorldOrbitSchemaVersion(source) {
|
|
4164
5113
|
for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
|
|
@@ -4172,6 +5121,9 @@
|
|
|
4172
5121
|
if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
|
|
4173
5122
|
return "2.1";
|
|
4174
5123
|
}
|
|
5124
|
+
if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
|
|
5125
|
+
return "2.5";
|
|
5126
|
+
}
|
|
4175
5127
|
if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
|
|
4176
5128
|
return "2.0";
|
|
4177
5129
|
}
|
|
@@ -4232,7 +5184,7 @@
|
|
|
4232
5184
|
}
|
|
4233
5185
|
function loadWorldOrbitSourceWithDiagnostics(source) {
|
|
4234
5186
|
const schemaVersion = detectWorldOrbitSchemaVersion(source);
|
|
4235
|
-
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
|
|
5187
|
+
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
|
|
4236
5188
|
return loadAtlasSourceWithDiagnostics(source, schemaVersion);
|
|
4237
5189
|
}
|
|
4238
5190
|
let ast;
|
|
@@ -4326,6 +5278,7 @@
|
|
|
4326
5278
|
background: true,
|
|
4327
5279
|
guides: true,
|
|
4328
5280
|
relations: true,
|
|
5281
|
+
events: true,
|
|
4329
5282
|
orbits: true,
|
|
4330
5283
|
objects: true,
|
|
4331
5284
|
labels: true,
|
|
@@ -4529,6 +5482,7 @@
|
|
|
4529
5482
|
const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
|
|
4530
5483
|
const leaderMarkup = layers.guides ? scene.leaders.filter((leader) => !leader.hidden).filter((leader) => visibleObjectIds.has(leader.objectId)).filter((leader) => layers.structures || !isStructureLike(leader.object)).map((leader) => `<line class="wo-leader wo-leader-${leader.mode}" x1="${leader.x1}" y1="${leader.y1}" x2="${leader.x2}" y2="${leader.y2}" data-render-id="${escapeXml(leader.renderId)}" data-group-id="${escapeAttribute(leader.groupId ?? "")}" />`).join("") : "";
|
|
4531
5484
|
const relationMarkup = layers.relations ? scene.relations.filter((relation) => !relation.hidden).filter((relation) => visibleObjectIds.has(relation.fromObjectId) && visibleObjectIds.has(relation.toObjectId)).map((relation) => `<line class="wo-relation" x1="${relation.x1}" y1="${relation.y1}" x2="${relation.x2}" y2="${relation.y2}" data-render-id="${escapeXml(relation.renderId)}" data-relation-id="${escapeAttribute(relation.relationId)}" />`).join("") : "";
|
|
5485
|
+
const eventMarkup = layers.events ? scene.events.filter((event) => !event.hidden).map((event) => renderSceneEventOverlay(scene, event, visibleObjectIds, theme)).join("") : "";
|
|
4532
5486
|
const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
|
|
4533
5487
|
const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
|
|
4534
5488
|
const metadataMarkup = layers.metadata ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
|
|
@@ -4564,6 +5518,9 @@
|
|
|
4564
5518
|
.wo-orbit-front { opacity: 0.9; }
|
|
4565
5519
|
.wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
|
|
4566
5520
|
.wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
|
|
5521
|
+
.wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
|
|
5522
|
+
.wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
|
|
5523
|
+
.wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
4567
5524
|
.wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
|
|
4568
5525
|
.wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
|
|
4569
5526
|
.wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
|
|
@@ -4598,6 +5555,7 @@
|
|
|
4598
5555
|
${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
|
|
4599
5556
|
${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
|
|
4600
5557
|
${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
|
|
5558
|
+
${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
|
|
4601
5559
|
${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
|
|
4602
5560
|
${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
|
|
4603
5561
|
${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
|
|
@@ -4605,6 +5563,20 @@
|
|
|
4605
5563
|
</g>
|
|
4606
5564
|
</g>
|
|
4607
5565
|
</svg>`;
|
|
5566
|
+
}
|
|
5567
|
+
function renderSceneEventOverlay(scene, event, visibleObjectIds, theme) {
|
|
5568
|
+
const participants = event.objectIds.filter((objectId) => visibleObjectIds.has(objectId)).map((objectId) => scene.objects.find((object) => object.objectId === objectId && !object.hidden)).filter(Boolean);
|
|
5569
|
+
if (participants.length === 0) {
|
|
5570
|
+
return "";
|
|
5571
|
+
}
|
|
5572
|
+
const stroke = event.event.color || theme.accent;
|
|
5573
|
+
const label = event.event.label || event.event.id;
|
|
5574
|
+
const lineMarkup = participants.map((object) => `<line class="wo-event-line" x1="${event.x}" y1="${event.y}" x2="${object.x}" y2="${object.y}" stroke="${escapeAttribute(stroke)}" data-event-id="${escapeAttribute(event.eventId)}" data-object-id="${escapeAttribute(object.objectId)}" />`).join("");
|
|
5575
|
+
return `<g class="wo-event" data-render-id="${escapeXml(event.renderId)}" data-event-id="${escapeAttribute(event.eventId)}">
|
|
5576
|
+
${lineMarkup}
|
|
5577
|
+
<circle class="wo-event-node" cx="${event.x}" cy="${event.y}" r="5" fill="${escapeAttribute(stroke)}" />
|
|
5578
|
+
<text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
|
|
5579
|
+
</g>`;
|
|
4608
5580
|
}
|
|
4609
5581
|
function renderOrbitLayer(scene, visibleObjectIds, includeStructures) {
|
|
4610
5582
|
const backParts = [];
|