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
|
@@ -544,6 +544,7 @@
|
|
|
544
544
|
system,
|
|
545
545
|
groups: [],
|
|
546
546
|
relations: [],
|
|
547
|
+
events: [],
|
|
547
548
|
objects
|
|
548
549
|
};
|
|
549
550
|
}
|
|
@@ -997,12 +998,16 @@
|
|
|
997
998
|
const height = frame.height;
|
|
998
999
|
const padding = frame.padding;
|
|
999
1000
|
const layoutPreset = resolveLayoutPreset(document);
|
|
1000
|
-
const
|
|
1001
|
+
const schemaProjection = resolveProjection(document, options.projection);
|
|
1002
|
+
const camera = normalizeViewCamera(options.camera ?? null);
|
|
1003
|
+
const renderProjection = resolveRenderProjection(schemaProjection, camera);
|
|
1001
1004
|
const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
|
|
1002
1005
|
const spacingFactor = layoutPresetSpacing(layoutPreset);
|
|
1003
1006
|
const systemId = document.system?.id ?? null;
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1007
|
+
const activeEventId = options.activeEventId ?? null;
|
|
1008
|
+
const effectiveObjects = createEffectiveObjects(document.objects, document.events ?? [], activeEventId);
|
|
1009
|
+
const objectMap = new Map(effectiveObjects.map((object) => [object.id, object]));
|
|
1010
|
+
const relationships = buildSceneRelationships(effectiveObjects, objectMap);
|
|
1006
1011
|
const positions = /* @__PURE__ */ new Map();
|
|
1007
1012
|
const orbitDrafts = [];
|
|
1008
1013
|
const leaderDrafts = [];
|
|
@@ -1011,7 +1016,7 @@
|
|
|
1011
1016
|
const atObjects = [];
|
|
1012
1017
|
const surfaceChildren = /* @__PURE__ */ new Map();
|
|
1013
1018
|
const orbitChildren = /* @__PURE__ */ new Map();
|
|
1014
|
-
for (const object of
|
|
1019
|
+
for (const object of effectiveObjects) {
|
|
1015
1020
|
const placement = object.placement;
|
|
1016
1021
|
if (!placement) {
|
|
1017
1022
|
rootObjects.push(object);
|
|
@@ -1038,7 +1043,7 @@
|
|
|
1038
1043
|
surfaceChildren,
|
|
1039
1044
|
objectMap,
|
|
1040
1045
|
spacingFactor,
|
|
1041
|
-
projection,
|
|
1046
|
+
projection: renderProjection,
|
|
1042
1047
|
scaleModel
|
|
1043
1048
|
};
|
|
1044
1049
|
const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
|
|
@@ -1050,7 +1055,7 @@
|
|
|
1050
1055
|
const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
|
|
1051
1056
|
secondaryRoots.forEach((object, index) => {
|
|
1052
1057
|
const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
|
|
1053
|
-
const offset = projectPolarOffset(angle, rootRingRadius,
|
|
1058
|
+
const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
|
|
1054
1059
|
placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
|
|
1055
1060
|
});
|
|
1056
1061
|
}
|
|
@@ -1106,38 +1111,48 @@
|
|
|
1106
1111
|
const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
|
|
1107
1112
|
const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
|
|
1108
1113
|
const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
|
|
1109
|
-
const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
|
|
1114
|
+
const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
|
|
1110
1115
|
const relations = createSceneRelations(document, objects);
|
|
1111
|
-
const
|
|
1112
|
-
const
|
|
1116
|
+
const events = createSceneEvents(document.events ?? [], objects, activeEventId);
|
|
1117
|
+
const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
|
|
1118
|
+
const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
|
|
1113
1119
|
const semanticGroups = createSceneSemanticGroups(document, objects);
|
|
1114
|
-
const viewpoints = createSceneViewpoints(document,
|
|
1115
|
-
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
|
|
1120
|
+
const viewpoints = createSceneViewpoints(document, schemaProjection, frame.preset, relationships, objectMap);
|
|
1121
|
+
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
|
|
1116
1122
|
return {
|
|
1117
1123
|
width,
|
|
1118
1124
|
height,
|
|
1119
1125
|
padding,
|
|
1120
1126
|
renderPreset: frame.preset,
|
|
1121
|
-
projection,
|
|
1127
|
+
projection: schemaProjection,
|
|
1128
|
+
renderProjection,
|
|
1129
|
+
camera,
|
|
1122
1130
|
scaleModel,
|
|
1123
1131
|
title: String(document.system?.title ?? document.system?.properties.title ?? document.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1124
|
-
subtitle:
|
|
1132
|
+
subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
|
|
1125
1133
|
systemId,
|
|
1126
|
-
viewMode:
|
|
1134
|
+
viewMode: schemaProjection,
|
|
1127
1135
|
layoutPreset,
|
|
1128
1136
|
metadata: {
|
|
1129
1137
|
format: document.format,
|
|
1130
1138
|
version: document.version,
|
|
1131
|
-
view:
|
|
1139
|
+
view: schemaProjection,
|
|
1140
|
+
renderProjection,
|
|
1132
1141
|
scale: String(document.system?.properties.scale ?? layoutPreset),
|
|
1133
1142
|
units: String(document.system?.properties.units ?? "mixed"),
|
|
1134
|
-
preset: frame.preset ?? "custom"
|
|
1143
|
+
preset: frame.preset ?? "custom",
|
|
1144
|
+
...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
|
|
1145
|
+
...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
|
|
1146
|
+
...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
|
|
1147
|
+
...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
|
|
1135
1148
|
},
|
|
1136
1149
|
contentBounds,
|
|
1137
1150
|
layers,
|
|
1138
1151
|
groups,
|
|
1139
1152
|
semanticGroups,
|
|
1140
1153
|
viewpoints,
|
|
1154
|
+
events,
|
|
1155
|
+
activeEventId,
|
|
1141
1156
|
objects,
|
|
1142
1157
|
orbitVisuals,
|
|
1143
1158
|
relations,
|
|
@@ -1156,6 +1171,56 @@
|
|
|
1156
1171
|
y: center.y + dx * sin + dy * cos
|
|
1157
1172
|
};
|
|
1158
1173
|
}
|
|
1174
|
+
function createEffectiveObjects(objects, events, activeEventId) {
|
|
1175
|
+
const cloned = objects.map((object) => structuredClone(object));
|
|
1176
|
+
if (!activeEventId) {
|
|
1177
|
+
return cloned;
|
|
1178
|
+
}
|
|
1179
|
+
const activeEvent = events.find((event) => event.id === activeEventId);
|
|
1180
|
+
if (!activeEvent) {
|
|
1181
|
+
return cloned;
|
|
1182
|
+
}
|
|
1183
|
+
const objectMap = new Map(cloned.map((object) => [object.id, object]));
|
|
1184
|
+
const referencedIds = /* @__PURE__ */ new Set([
|
|
1185
|
+
...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
|
|
1186
|
+
...activeEvent.participantObjectIds,
|
|
1187
|
+
...activeEvent.positions.map((pose) => pose.objectId)
|
|
1188
|
+
]);
|
|
1189
|
+
for (const objectId of referencedIds) {
|
|
1190
|
+
const object = objectMap.get(objectId);
|
|
1191
|
+
if (!object) {
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
if (activeEvent.epoch) {
|
|
1195
|
+
object.epoch = activeEvent.epoch;
|
|
1196
|
+
}
|
|
1197
|
+
if (activeEvent.referencePlane) {
|
|
1198
|
+
object.referencePlane = activeEvent.referencePlane;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
for (const pose of activeEvent.positions) {
|
|
1202
|
+
const object = objectMap.get(pose.objectId);
|
|
1203
|
+
if (!object) {
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
if (pose.placement) {
|
|
1207
|
+
object.placement = structuredClone(pose.placement);
|
|
1208
|
+
}
|
|
1209
|
+
if (pose.inner) {
|
|
1210
|
+
object.properties.inner = { ...pose.inner };
|
|
1211
|
+
}
|
|
1212
|
+
if (pose.outer) {
|
|
1213
|
+
object.properties.outer = { ...pose.outer };
|
|
1214
|
+
}
|
|
1215
|
+
if (pose.epoch) {
|
|
1216
|
+
object.epoch = pose.epoch;
|
|
1217
|
+
}
|
|
1218
|
+
if (pose.referencePlane) {
|
|
1219
|
+
object.referencePlane = pose.referencePlane;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return cloned;
|
|
1223
|
+
}
|
|
1159
1224
|
function resolveLayoutPreset(document) {
|
|
1160
1225
|
const rawScale = String(document.system?.properties.scale ?? "balanced").toLowerCase();
|
|
1161
1226
|
switch (rawScale) {
|
|
@@ -1192,10 +1257,59 @@
|
|
|
1192
1257
|
}
|
|
1193
1258
|
}
|
|
1194
1259
|
function resolveProjection(document, projection) {
|
|
1195
|
-
if (projection === "topdown" || projection === "isometric") {
|
|
1260
|
+
if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
|
|
1196
1261
|
return projection;
|
|
1197
1262
|
}
|
|
1198
|
-
|
|
1263
|
+
const documentView = String(document.system?.properties.view ?? "topdown").toLowerCase();
|
|
1264
|
+
return parseViewProjection(documentView) ?? "topdown";
|
|
1265
|
+
}
|
|
1266
|
+
function resolveRenderProjection(projection, camera) {
|
|
1267
|
+
switch (projection) {
|
|
1268
|
+
case "topdown":
|
|
1269
|
+
return "topdown";
|
|
1270
|
+
case "isometric":
|
|
1271
|
+
return "isometric";
|
|
1272
|
+
case "orthographic":
|
|
1273
|
+
return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
|
|
1274
|
+
case "perspective":
|
|
1275
|
+
return "isometric";
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
function normalizeViewCamera(camera) {
|
|
1279
|
+
if (!camera) {
|
|
1280
|
+
return null;
|
|
1281
|
+
}
|
|
1282
|
+
const normalized = {
|
|
1283
|
+
azimuth: normalizeFiniteCameraValue(camera.azimuth),
|
|
1284
|
+
elevation: normalizeFiniteCameraValue(camera.elevation),
|
|
1285
|
+
roll: normalizeFiniteCameraValue(camera.roll),
|
|
1286
|
+
distance: normalizePositiveCameraDistance(camera.distance)
|
|
1287
|
+
};
|
|
1288
|
+
return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
|
|
1289
|
+
}
|
|
1290
|
+
function normalizeFiniteCameraValue(value) {
|
|
1291
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1292
|
+
}
|
|
1293
|
+
function normalizePositiveCameraDistance(value) {
|
|
1294
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
1295
|
+
}
|
|
1296
|
+
function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
|
|
1297
|
+
const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
|
|
1298
|
+
if (projection !== renderProjection) {
|
|
1299
|
+
parts.push(`2D ${renderProjection} fallback`);
|
|
1300
|
+
}
|
|
1301
|
+
if (camera) {
|
|
1302
|
+
const cameraParts = [
|
|
1303
|
+
camera.azimuth !== null ? `az ${camera.azimuth}` : null,
|
|
1304
|
+
camera.elevation !== null ? `el ${camera.elevation}` : null,
|
|
1305
|
+
camera.roll !== null ? `roll ${camera.roll}` : null,
|
|
1306
|
+
camera.distance !== null ? `dist ${camera.distance}` : null
|
|
1307
|
+
].filter(Boolean);
|
|
1308
|
+
if (cameraParts.length > 0) {
|
|
1309
|
+
parts.push(`camera ${cameraParts.join(" / ")}`);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return parts.join(" - ");
|
|
1199
1313
|
}
|
|
1200
1314
|
function resolveScaleModel(layoutPreset, overrides) {
|
|
1201
1315
|
const defaults = defaultScaleModel(layoutPreset);
|
|
@@ -1311,24 +1425,14 @@
|
|
|
1311
1425
|
hidden: draft.object.properties.hidden === true
|
|
1312
1426
|
};
|
|
1313
1427
|
}
|
|
1314
|
-
function createSceneLabels(objects, sceneHeight, labelMultiplier) {
|
|
1428
|
+
function createSceneLabels(objects, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1315
1429
|
const labels = [];
|
|
1316
1430
|
const occupied = [];
|
|
1317
|
-
const
|
|
1431
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1432
|
+
const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort(compareLabelPlacementOrder);
|
|
1318
1433
|
for (const object of visibleObjects) {
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
let labelY = object.y + direction * (object.radius + 18 * labelMultiplier);
|
|
1322
|
-
let secondaryY = labelY + direction * (16 * labelMultiplier);
|
|
1323
|
-
let bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1324
|
-
let attempts = 0;
|
|
1325
|
-
while (occupied.some((entry) => rectsOverlap(entry, bounds)) && attempts < 10) {
|
|
1326
|
-
labelY += direction * 14 * labelMultiplier;
|
|
1327
|
-
secondaryY += direction * 14 * labelMultiplier;
|
|
1328
|
-
bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1329
|
-
attempts += 1;
|
|
1330
|
-
}
|
|
1331
|
-
occupied.push(bounds);
|
|
1434
|
+
const placement = selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) ?? createLabelPlacement(object, defaultVerticalDirection(object, objectMap.get(object.parentId ?? "") ?? null, sceneHeight), 0, labelMultiplier);
|
|
1435
|
+
occupied.push(createLabelRect(object, placement, labelMultiplier));
|
|
1332
1436
|
labels.push({
|
|
1333
1437
|
renderId: `${object.renderId}-label`,
|
|
1334
1438
|
objectId: object.objectId,
|
|
@@ -1337,17 +1441,128 @@
|
|
|
1337
1441
|
semanticGroupIds: [...object.semanticGroupIds],
|
|
1338
1442
|
label: object.label,
|
|
1339
1443
|
secondaryLabel: object.secondaryLabel,
|
|
1340
|
-
x:
|
|
1341
|
-
y: labelY,
|
|
1342
|
-
secondaryY,
|
|
1343
|
-
textAnchor:
|
|
1344
|
-
direction: direction
|
|
1444
|
+
x: placement.x,
|
|
1445
|
+
y: placement.labelY,
|
|
1446
|
+
secondaryY: placement.secondaryY,
|
|
1447
|
+
textAnchor: placement.textAnchor,
|
|
1448
|
+
direction: placement.direction,
|
|
1345
1449
|
hidden: object.hidden
|
|
1346
1450
|
});
|
|
1347
1451
|
}
|
|
1348
1452
|
return labels;
|
|
1349
1453
|
}
|
|
1350
|
-
function
|
|
1454
|
+
function compareLabelPlacementOrder(left, right) {
|
|
1455
|
+
const priorityDiff = labelPlacementPriority(left) - labelPlacementPriority(right);
|
|
1456
|
+
if (priorityDiff !== 0) {
|
|
1457
|
+
return priorityDiff;
|
|
1458
|
+
}
|
|
1459
|
+
const renderPriorityDiff = (right.object.renderHints?.renderPriority ?? 0) - (left.object.renderHints?.renderPriority ?? 0);
|
|
1460
|
+
if (renderPriorityDiff !== 0) {
|
|
1461
|
+
return renderPriorityDiff;
|
|
1462
|
+
}
|
|
1463
|
+
return left.sortKey - right.sortKey;
|
|
1464
|
+
}
|
|
1465
|
+
function labelPlacementPriority(object) {
|
|
1466
|
+
switch (object.object.type) {
|
|
1467
|
+
case "star":
|
|
1468
|
+
return 0;
|
|
1469
|
+
case "planet":
|
|
1470
|
+
return 1;
|
|
1471
|
+
case "moon":
|
|
1472
|
+
return 2;
|
|
1473
|
+
case "belt":
|
|
1474
|
+
case "ring":
|
|
1475
|
+
return 3;
|
|
1476
|
+
case "asteroid":
|
|
1477
|
+
case "comet":
|
|
1478
|
+
return 4;
|
|
1479
|
+
case "structure":
|
|
1480
|
+
case "phenomenon":
|
|
1481
|
+
return 5;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
function selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1485
|
+
for (const direction of preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight)) {
|
|
1486
|
+
const maxAttempts = direction === "left" || direction === "right" ? 4 : 6;
|
|
1487
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
|
|
1488
|
+
const placement = createLabelPlacement(object, direction, attempt, labelMultiplier);
|
|
1489
|
+
const rect = createLabelRect(object, placement, labelMultiplier);
|
|
1490
|
+
if (!occupied.some((entry) => rectsOverlap(entry, rect))) {
|
|
1491
|
+
return placement;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
function preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight) {
|
|
1498
|
+
const parent = object.parentId ? objectMap.get(object.parentId) ?? null : null;
|
|
1499
|
+
const vertical = defaultVerticalDirection(object, parent, sceneHeight);
|
|
1500
|
+
const oppositeVertical = vertical === "below" ? "above" : "below";
|
|
1501
|
+
const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
|
|
1502
|
+
const oppositeHorizontal = horizontal === "right" ? "left" : "right";
|
|
1503
|
+
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";
|
|
1504
|
+
return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
|
|
1505
|
+
}
|
|
1506
|
+
function defaultVerticalDirection(object, parent, sceneHeight) {
|
|
1507
|
+
if (parent && Math.abs(object.y - parent.y) > 6) {
|
|
1508
|
+
return object.y >= parent.y ? "below" : "above";
|
|
1509
|
+
}
|
|
1510
|
+
return object.y > sceneHeight * 0.62 ? "above" : "below";
|
|
1511
|
+
}
|
|
1512
|
+
function defaultHorizontalDirection(object, parent, sceneWidth) {
|
|
1513
|
+
if (parent && Math.abs(object.x - parent.x) > 6) {
|
|
1514
|
+
return object.x >= parent.x ? "right" : "left";
|
|
1515
|
+
}
|
|
1516
|
+
return object.x >= sceneWidth / 2 ? "right" : "left";
|
|
1517
|
+
}
|
|
1518
|
+
function createLabelPlacement(object, direction, attempt, labelMultiplier) {
|
|
1519
|
+
const step = 14 * labelMultiplier;
|
|
1520
|
+
switch (direction) {
|
|
1521
|
+
case "above": {
|
|
1522
|
+
const labelY = object.y - (object.radius + 18 * labelMultiplier + attempt * step);
|
|
1523
|
+
return {
|
|
1524
|
+
x: object.x,
|
|
1525
|
+
labelY,
|
|
1526
|
+
secondaryY: labelY - 16 * labelMultiplier,
|
|
1527
|
+
textAnchor: "middle",
|
|
1528
|
+
direction
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
case "below": {
|
|
1532
|
+
const labelY = object.y + object.radius + 18 * labelMultiplier + attempt * step;
|
|
1533
|
+
return {
|
|
1534
|
+
x: object.x,
|
|
1535
|
+
labelY,
|
|
1536
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1537
|
+
textAnchor: "middle",
|
|
1538
|
+
direction
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
case "left": {
|
|
1542
|
+
const x = object.x - (object.visualRadius + 16 * labelMultiplier + attempt * step);
|
|
1543
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1544
|
+
return {
|
|
1545
|
+
x,
|
|
1546
|
+
labelY,
|
|
1547
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1548
|
+
textAnchor: "end",
|
|
1549
|
+
direction
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
case "right": {
|
|
1553
|
+
const x = object.x + object.visualRadius + 16 * labelMultiplier + attempt * step;
|
|
1554
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1555
|
+
return {
|
|
1556
|
+
x,
|
|
1557
|
+
labelY,
|
|
1558
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1559
|
+
textAnchor: "start",
|
|
1560
|
+
direction
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
|
|
1351
1566
|
const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
|
|
1352
1567
|
const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
|
|
1353
1568
|
return [
|
|
@@ -1362,6 +1577,10 @@
|
|
|
1362
1577
|
id: "relations",
|
|
1363
1578
|
renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
|
|
1364
1579
|
},
|
|
1580
|
+
{
|
|
1581
|
+
id: "events",
|
|
1582
|
+
renderIds: events.filter((event) => !event.hidden).map((event) => event.renderId)
|
|
1583
|
+
},
|
|
1365
1584
|
{
|
|
1366
1585
|
id: "objects",
|
|
1367
1586
|
renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
|
|
@@ -1373,7 +1592,7 @@
|
|
|
1373
1592
|
{ id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
|
|
1374
1593
|
];
|
|
1375
1594
|
}
|
|
1376
|
-
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
|
|
1595
|
+
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, labelMultiplier) {
|
|
1377
1596
|
const groups = /* @__PURE__ */ new Map();
|
|
1378
1597
|
const ensureGroup = (groupId) => {
|
|
1379
1598
|
if (!groupId) {
|
|
@@ -1422,7 +1641,7 @@
|
|
|
1422
1641
|
}
|
|
1423
1642
|
}
|
|
1424
1643
|
for (const group of groups.values()) {
|
|
1425
|
-
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
|
|
1644
|
+
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier);
|
|
1426
1645
|
}
|
|
1427
1646
|
return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
1428
1647
|
}
|
|
@@ -1456,6 +1675,29 @@
|
|
|
1456
1675
|
};
|
|
1457
1676
|
}).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
|
|
1458
1677
|
}
|
|
1678
|
+
function createSceneEvents(events, objects, activeEventId) {
|
|
1679
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1680
|
+
return events.map((event) => {
|
|
1681
|
+
const objectIds = [.../* @__PURE__ */ new Set([
|
|
1682
|
+
...event.targetObjectId ? [event.targetObjectId] : [],
|
|
1683
|
+
...event.participantObjectIds
|
|
1684
|
+
])];
|
|
1685
|
+
const positions = objectIds.map((objectId) => objectMap.get(objectId)).filter(Boolean);
|
|
1686
|
+
const centroidX = positions.length > 0 ? positions.reduce((sum, object) => sum + object.x, 0) / positions.length : 0;
|
|
1687
|
+
const centroidY = positions.length > 0 ? positions.reduce((sum, object) => sum + object.y, 0) / positions.length : 0;
|
|
1688
|
+
return {
|
|
1689
|
+
renderId: `${createRenderId(event.id)}-event`,
|
|
1690
|
+
eventId: event.id,
|
|
1691
|
+
event,
|
|
1692
|
+
objectIds,
|
|
1693
|
+
participantIds: [...event.participantObjectIds],
|
|
1694
|
+
targetObjectId: event.targetObjectId,
|
|
1695
|
+
x: centroidX,
|
|
1696
|
+
y: centroidY,
|
|
1697
|
+
hidden: event.hidden || positions.length === 0 || positions.every((object) => object.hidden) || activeEventId !== null && event.id !== activeEventId
|
|
1698
|
+
};
|
|
1699
|
+
}).sort((left, right) => left.event.id.localeCompare(right.event.id));
|
|
1700
|
+
}
|
|
1459
1701
|
function createSceneViewpoints(document, projection, preset, relationships, objectMap) {
|
|
1460
1702
|
const generatedOverview = createGeneratedOverviewViewpoint(document, projection, preset);
|
|
1461
1703
|
const drafts = /* @__PURE__ */ new Map();
|
|
@@ -1503,13 +1745,18 @@
|
|
|
1503
1745
|
function createGeneratedOverviewViewpoint(document, projection, preset) {
|
|
1504
1746
|
const title = document.system?.title ?? document.system?.properties.title;
|
|
1505
1747
|
const label = title ? `${String(title)} Overview` : "Overview";
|
|
1748
|
+
const camera = normalizeViewCamera(null);
|
|
1749
|
+
const renderProjection = resolveRenderProjection(projection, camera);
|
|
1506
1750
|
return {
|
|
1507
1751
|
id: "overview",
|
|
1508
1752
|
label,
|
|
1509
1753
|
summary: "Fit the whole system with the current atlas defaults.",
|
|
1510
1754
|
objectId: null,
|
|
1511
1755
|
selectedObjectId: null,
|
|
1756
|
+
eventIds: [],
|
|
1512
1757
|
projection,
|
|
1758
|
+
renderProjection,
|
|
1759
|
+
camera,
|
|
1513
1760
|
preset,
|
|
1514
1761
|
rotationDeg: 0,
|
|
1515
1762
|
scale: null,
|
|
@@ -1545,6 +1792,9 @@
|
|
|
1545
1792
|
draft.select = normalizedValue;
|
|
1546
1793
|
}
|
|
1547
1794
|
return;
|
|
1795
|
+
case "events":
|
|
1796
|
+
draft.eventIds = splitListValue(normalizedValue);
|
|
1797
|
+
return;
|
|
1548
1798
|
case "projection":
|
|
1549
1799
|
case "view":
|
|
1550
1800
|
draft.projection = parseViewProjection(normalizedValue) ?? projection;
|
|
@@ -1556,6 +1806,30 @@
|
|
|
1556
1806
|
case "angle":
|
|
1557
1807
|
draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
|
|
1558
1808
|
return;
|
|
1809
|
+
case "camera.azimuth":
|
|
1810
|
+
draft.camera = {
|
|
1811
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1812
|
+
azimuth: parseFiniteNumber(normalizedValue)
|
|
1813
|
+
};
|
|
1814
|
+
return;
|
|
1815
|
+
case "camera.elevation":
|
|
1816
|
+
draft.camera = {
|
|
1817
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1818
|
+
elevation: parseFiniteNumber(normalizedValue)
|
|
1819
|
+
};
|
|
1820
|
+
return;
|
|
1821
|
+
case "camera.roll":
|
|
1822
|
+
draft.camera = {
|
|
1823
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1824
|
+
roll: parseFiniteNumber(normalizedValue)
|
|
1825
|
+
};
|
|
1826
|
+
return;
|
|
1827
|
+
case "camera.distance":
|
|
1828
|
+
draft.camera = {
|
|
1829
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
1830
|
+
distance: parsePositiveNumber(normalizedValue)
|
|
1831
|
+
};
|
|
1832
|
+
return;
|
|
1559
1833
|
case "zoom":
|
|
1560
1834
|
case "scale":
|
|
1561
1835
|
draft.scale = parsePositiveNumber(normalizedValue);
|
|
@@ -1595,13 +1869,19 @@
|
|
|
1595
1869
|
const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
|
|
1596
1870
|
const filter = normalizeViewpointFilter(draft.filter);
|
|
1597
1871
|
const label = draft.label?.trim() || humanizeIdentifier(draft.id);
|
|
1872
|
+
const resolvedProjection = draft.projection ?? projection;
|
|
1873
|
+
const camera = normalizeViewCamera(draft.camera ?? null);
|
|
1874
|
+
const renderProjection = resolveRenderProjection(resolvedProjection, camera);
|
|
1598
1875
|
return {
|
|
1599
1876
|
id: draft.id,
|
|
1600
1877
|
label,
|
|
1601
1878
|
summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
|
|
1602
1879
|
objectId,
|
|
1603
1880
|
selectedObjectId,
|
|
1604
|
-
|
|
1881
|
+
eventIds: [...new Set(draft.eventIds ?? [])],
|
|
1882
|
+
projection: resolvedProjection,
|
|
1883
|
+
renderProjection,
|
|
1884
|
+
camera,
|
|
1605
1885
|
preset: draft.preset ?? preset,
|
|
1606
1886
|
rotationDeg: draft.rotationDeg ?? 0,
|
|
1607
1887
|
scale: draft.scale ?? null,
|
|
@@ -1618,6 +1898,14 @@
|
|
|
1618
1898
|
groupIds: []
|
|
1619
1899
|
};
|
|
1620
1900
|
}
|
|
1901
|
+
function createEmptyViewCamera() {
|
|
1902
|
+
return {
|
|
1903
|
+
azimuth: null,
|
|
1904
|
+
elevation: null,
|
|
1905
|
+
roll: null,
|
|
1906
|
+
distance: null
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1621
1909
|
function normalizeViewpointFilter(filter) {
|
|
1622
1910
|
if (!filter) {
|
|
1623
1911
|
return null;
|
|
@@ -1631,7 +1919,18 @@
|
|
|
1631
1919
|
return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
|
|
1632
1920
|
}
|
|
1633
1921
|
function parseViewProjection(value) {
|
|
1634
|
-
|
|
1922
|
+
switch (value.toLowerCase()) {
|
|
1923
|
+
case "topdown":
|
|
1924
|
+
return "topdown";
|
|
1925
|
+
case "isometric":
|
|
1926
|
+
return "isometric";
|
|
1927
|
+
case "orthographic":
|
|
1928
|
+
return "orthographic";
|
|
1929
|
+
case "perspective":
|
|
1930
|
+
return "perspective";
|
|
1931
|
+
default:
|
|
1932
|
+
return null;
|
|
1933
|
+
}
|
|
1635
1934
|
}
|
|
1636
1935
|
function parseRenderPreset(value) {
|
|
1637
1936
|
const normalized = value.toLowerCase();
|
|
@@ -1658,7 +1957,7 @@
|
|
|
1658
1957
|
next["orbits-front"] = enabled;
|
|
1659
1958
|
continue;
|
|
1660
1959
|
}
|
|
1661
|
-
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1960
|
+
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1662
1961
|
next[rawLayer] = enabled;
|
|
1663
1962
|
}
|
|
1664
1963
|
}
|
|
@@ -1669,7 +1968,7 @@
|
|
|
1669
1968
|
}
|
|
1670
1969
|
function parseViewpointGroups(value, document, relationships, objectMap) {
|
|
1671
1970
|
return splitListValue(value).map((entry) => {
|
|
1672
|
-
if (document.schemaVersion === "2.1" || document.groups.some((group) => group.id === entry)) {
|
|
1971
|
+
if (document.schemaVersion === "2.1" || document.schemaVersion === "2.5" || document.groups.some((group) => group.id === entry)) {
|
|
1673
1972
|
return entry;
|
|
1674
1973
|
}
|
|
1675
1974
|
if (entry.startsWith("wo-") && entry.endsWith("-group")) {
|
|
@@ -1706,7 +2005,7 @@
|
|
|
1706
2005
|
}
|
|
1707
2006
|
return parts.join(" - ");
|
|
1708
2007
|
}
|
|
1709
|
-
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
|
|
2008
|
+
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
1710
2009
|
let minX = Number.POSITIVE_INFINITY;
|
|
1711
2010
|
let minY = Number.POSITIVE_INFINITY;
|
|
1712
2011
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -1736,7 +2035,7 @@
|
|
|
1736
2035
|
for (const label of labels) {
|
|
1737
2036
|
if (label.hidden)
|
|
1738
2037
|
continue;
|
|
1739
|
-
includeLabelBounds(label, include);
|
|
2038
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
1740
2039
|
}
|
|
1741
2040
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
1742
2041
|
return createBounds(0, 0, width, height);
|
|
@@ -1774,13 +2073,10 @@
|
|
|
1774
2073
|
include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
|
|
1775
2074
|
include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
|
|
1776
2075
|
}
|
|
1777
|
-
function includeLabelBounds(label, include) {
|
|
1778
|
-
const
|
|
1779
|
-
|
|
1780
|
-
include(
|
|
1781
|
-
include(label.x + labelHalfWidth, label.y + 8);
|
|
1782
|
-
include(label.x - labelHalfWidth, label.secondaryY - 14);
|
|
1783
|
-
include(label.x + labelHalfWidth, label.secondaryY + 8);
|
|
2076
|
+
function includeLabelBounds(label, include, labelMultiplier) {
|
|
2077
|
+
const bounds = createLabelRectFromText(label.x, label.y, label.secondaryY, label.textAnchor, label.direction, label.label, label.secondaryLabel, labelMultiplier);
|
|
2078
|
+
include(bounds.left, bounds.top);
|
|
2079
|
+
include(bounds.right, bounds.bottom);
|
|
1784
2080
|
}
|
|
1785
2081
|
function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
|
|
1786
2082
|
if (positions.has(object.id)) {
|
|
@@ -2170,7 +2466,7 @@
|
|
|
2170
2466
|
return null;
|
|
2171
2467
|
}
|
|
2172
2468
|
}
|
|
2173
|
-
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
|
|
2469
|
+
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
2174
2470
|
let minX = Number.POSITIVE_INFINITY;
|
|
2175
2471
|
let minY = Number.POSITIVE_INFINITY;
|
|
2176
2472
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -2199,7 +2495,7 @@
|
|
|
2199
2495
|
}
|
|
2200
2496
|
for (const label of labels) {
|
|
2201
2497
|
if (!label.hidden && group.labelIds.includes(label.objectId)) {
|
|
2202
|
-
includeLabelBounds(label, include);
|
|
2498
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
2203
2499
|
}
|
|
2204
2500
|
}
|
|
2205
2501
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
@@ -2224,12 +2520,28 @@
|
|
|
2224
2520
|
}
|
|
2225
2521
|
return current.id;
|
|
2226
2522
|
}
|
|
2227
|
-
function createLabelRect(
|
|
2523
|
+
function createLabelRect(object, placement, labelMultiplier) {
|
|
2524
|
+
return createLabelRectFromText(placement.x, placement.labelY, placement.secondaryY, placement.textAnchor, placement.direction, object.label, object.secondaryLabel, labelMultiplier);
|
|
2525
|
+
}
|
|
2526
|
+
function createLabelRectFromText(x, labelY, secondaryY, textAnchor, direction, label, secondaryLabel, labelMultiplier) {
|
|
2527
|
+
const labelHalfWidth = estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier);
|
|
2528
|
+
const labelWidth = labelHalfWidth * 2;
|
|
2529
|
+
const topPadding = direction === "above" ? 18 : 12;
|
|
2530
|
+
const bottomPadding = direction === "above" ? 8 : 12;
|
|
2531
|
+
let left = x - labelHalfWidth;
|
|
2532
|
+
let right = x + labelHalfWidth;
|
|
2533
|
+
if (textAnchor === "start") {
|
|
2534
|
+
left = x;
|
|
2535
|
+
right = x + labelWidth;
|
|
2536
|
+
} else if (textAnchor === "end") {
|
|
2537
|
+
left = x - labelWidth;
|
|
2538
|
+
right = x;
|
|
2539
|
+
}
|
|
2228
2540
|
return {
|
|
2229
|
-
left
|
|
2230
|
-
right
|
|
2231
|
-
top: Math.min(labelY, secondaryY) -
|
|
2232
|
-
bottom: Math.max(labelY, secondaryY) +
|
|
2541
|
+
left,
|
|
2542
|
+
right,
|
|
2543
|
+
top: Math.min(labelY, secondaryY) - topPadding,
|
|
2544
|
+
bottom: Math.max(labelY, secondaryY) + bottomPadding
|
|
2233
2545
|
};
|
|
2234
2546
|
}
|
|
2235
2547
|
function rectsOverlap(left, right) {
|
|
@@ -2416,11 +2728,6 @@
|
|
|
2416
2728
|
function customColorFor(value) {
|
|
2417
2729
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2418
2730
|
}
|
|
2419
|
-
function estimateLabelHalfWidth(object, labelMultiplier) {
|
|
2420
|
-
const primaryWidth = object.label.length * 4.6 * labelMultiplier + 18;
|
|
2421
|
-
const secondaryWidth = object.secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
2422
|
-
return Math.max(primaryWidth, secondaryWidth, object.visualRadius + 18);
|
|
2423
|
-
}
|
|
2424
2731
|
function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
|
|
2425
2732
|
const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
|
|
2426
2733
|
const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
@@ -2454,12 +2761,13 @@
|
|
|
2454
2761
|
}
|
|
2455
2762
|
return {
|
|
2456
2763
|
format: "worldorbit",
|
|
2457
|
-
version: "2.
|
|
2458
|
-
schemaVersion: "2.
|
|
2764
|
+
version: "2.5",
|
|
2765
|
+
schemaVersion: "2.5",
|
|
2459
2766
|
sourceVersion: document.version,
|
|
2460
2767
|
system,
|
|
2461
2768
|
groups: structuredClone(document.groups ?? []),
|
|
2462
2769
|
relations: structuredClone(document.relations ?? []),
|
|
2770
|
+
events: structuredClone(document.events ?? []),
|
|
2463
2771
|
objects: document.objects.map(cloneWorldOrbitObject),
|
|
2464
2772
|
diagnostics
|
|
2465
2773
|
};
|
|
@@ -2467,7 +2775,7 @@
|
|
|
2467
2775
|
function upgradeDocumentToDraftV2(document, options = {}) {
|
|
2468
2776
|
return convertAtlasDocumentToLegacyDraft(upgradeDocumentToV2(document, options));
|
|
2469
2777
|
}
|
|
2470
|
-
function materializeAtlasDocument(document) {
|
|
2778
|
+
function materializeAtlasDocument(document, options = {}) {
|
|
2471
2779
|
const system = document.system ? {
|
|
2472
2780
|
type: "system",
|
|
2473
2781
|
id: document.system.id,
|
|
@@ -2478,6 +2786,8 @@
|
|
|
2478
2786
|
properties: materializeDraftSystemProperties(document.system),
|
|
2479
2787
|
info: materializeDraftSystemInfo(document.system)
|
|
2480
2788
|
} : null;
|
|
2789
|
+
const objects = document.objects.map(cloneWorldOrbitObject);
|
|
2790
|
+
applyEventPoseOverrides(objects, document.events ?? [], options.activeEventId ?? null);
|
|
2481
2791
|
return {
|
|
2482
2792
|
format: "worldorbit",
|
|
2483
2793
|
version: "1.0",
|
|
@@ -2485,7 +2795,8 @@
|
|
|
2485
2795
|
system,
|
|
2486
2796
|
groups: structuredClone(document.groups ?? []),
|
|
2487
2797
|
relations: structuredClone(document.relations ?? []),
|
|
2488
|
-
|
|
2798
|
+
events: document.events.map(cloneWorldOrbitEvent),
|
|
2799
|
+
objects
|
|
2489
2800
|
};
|
|
2490
2801
|
}
|
|
2491
2802
|
function materializeDraftDocument(document) {
|
|
@@ -2510,8 +2821,9 @@
|
|
|
2510
2821
|
};
|
|
2511
2822
|
}
|
|
2512
2823
|
function createDraftDefaults(document, preset, projection) {
|
|
2824
|
+
const rawView = typeof document.system?.properties.view === "string" ? document.system.properties.view.toLowerCase() : null;
|
|
2513
2825
|
return {
|
|
2514
|
-
view:
|
|
2826
|
+
view: rawView === "topdown" || rawView === "isometric" || rawView === "orthographic" || rawView === "perspective" ? rawView : projection,
|
|
2515
2827
|
scale: typeof document.system?.properties.scale === "string" ? document.system.properties.scale : null,
|
|
2516
2828
|
units: typeof document.system?.properties.units === "string" ? document.system.properties.units : null,
|
|
2517
2829
|
preset,
|
|
@@ -2613,10 +2925,12 @@
|
|
|
2613
2925
|
summary: viewpoint.summary,
|
|
2614
2926
|
focusObjectId: viewpoint.objectId,
|
|
2615
2927
|
selectedObjectId: viewpoint.selectedObjectId,
|
|
2928
|
+
events: [...viewpoint.eventIds],
|
|
2616
2929
|
projection: viewpoint.projection,
|
|
2617
2930
|
preset: viewpoint.preset,
|
|
2618
2931
|
zoom: viewpoint.scale,
|
|
2619
2932
|
rotationDeg: viewpoint.rotationDeg,
|
|
2933
|
+
camera: viewpoint.camera ? { ...viewpoint.camera } : null,
|
|
2620
2934
|
layers: { ...viewpoint.layers },
|
|
2621
2935
|
filter: viewpoint.filter ? {
|
|
2622
2936
|
query: viewpoint.filter.query,
|
|
@@ -2645,6 +2959,75 @@
|
|
|
2645
2959
|
info: { ...object.info }
|
|
2646
2960
|
};
|
|
2647
2961
|
}
|
|
2962
|
+
function cloneWorldOrbitEvent(event) {
|
|
2963
|
+
return {
|
|
2964
|
+
...event,
|
|
2965
|
+
participantObjectIds: [...event.participantObjectIds],
|
|
2966
|
+
tags: [...event.tags],
|
|
2967
|
+
positions: event.positions.map(cloneWorldOrbitEventPose)
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
function cloneWorldOrbitEventPose(pose) {
|
|
2971
|
+
return {
|
|
2972
|
+
objectId: pose.objectId,
|
|
2973
|
+
placement: clonePlacement(pose.placement),
|
|
2974
|
+
inner: pose.inner ? { ...pose.inner } : void 0,
|
|
2975
|
+
outer: pose.outer ? { ...pose.outer } : void 0,
|
|
2976
|
+
epoch: pose.epoch ?? null,
|
|
2977
|
+
referencePlane: pose.referencePlane ?? null
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
function clonePlacement(placement) {
|
|
2981
|
+
return placement ? structuredClone(placement) : null;
|
|
2982
|
+
}
|
|
2983
|
+
function applyEventPoseOverrides(objects, events, activeEventId) {
|
|
2984
|
+
if (!activeEventId) {
|
|
2985
|
+
return;
|
|
2986
|
+
}
|
|
2987
|
+
const event = events.find((entry) => entry.id === activeEventId);
|
|
2988
|
+
if (!event) {
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
const objectMap = new Map(objects.map((object) => [object.id, object]));
|
|
2992
|
+
const referencedIds = /* @__PURE__ */ new Set([
|
|
2993
|
+
...event.targetObjectId ? [event.targetObjectId] : [],
|
|
2994
|
+
...event.participantObjectIds,
|
|
2995
|
+
...event.positions.map((pose) => pose.objectId)
|
|
2996
|
+
]);
|
|
2997
|
+
for (const objectId of referencedIds) {
|
|
2998
|
+
const object = objectMap.get(objectId);
|
|
2999
|
+
if (!object) {
|
|
3000
|
+
continue;
|
|
3001
|
+
}
|
|
3002
|
+
if (event.epoch) {
|
|
3003
|
+
object.epoch = event.epoch;
|
|
3004
|
+
}
|
|
3005
|
+
if (event.referencePlane) {
|
|
3006
|
+
object.referencePlane = event.referencePlane;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
for (const pose of event.positions) {
|
|
3010
|
+
const object = objectMap.get(pose.objectId);
|
|
3011
|
+
if (!object) {
|
|
3012
|
+
continue;
|
|
3013
|
+
}
|
|
3014
|
+
if (pose.placement) {
|
|
3015
|
+
object.placement = clonePlacement(pose.placement);
|
|
3016
|
+
}
|
|
3017
|
+
if (pose.inner) {
|
|
3018
|
+
object.properties.inner = { ...pose.inner };
|
|
3019
|
+
}
|
|
3020
|
+
if (pose.outer) {
|
|
3021
|
+
object.properties.outer = { ...pose.outer };
|
|
3022
|
+
}
|
|
3023
|
+
if (pose.epoch) {
|
|
3024
|
+
object.epoch = pose.epoch;
|
|
3025
|
+
}
|
|
3026
|
+
if (pose.referencePlane) {
|
|
3027
|
+
object.referencePlane = pose.referencePlane;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
2648
3031
|
function cloneProperties(properties) {
|
|
2649
3032
|
const next = {};
|
|
2650
3033
|
for (const [key, value] of Object.entries(properties)) {
|
|
@@ -2726,6 +3109,18 @@
|
|
|
2726
3109
|
if (viewpoint.rotationDeg !== 0) {
|
|
2727
3110
|
info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
|
|
2728
3111
|
}
|
|
3112
|
+
if (viewpoint.camera?.azimuth !== null) {
|
|
3113
|
+
info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
|
|
3114
|
+
}
|
|
3115
|
+
if (viewpoint.camera?.elevation !== null) {
|
|
3116
|
+
info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
|
|
3117
|
+
}
|
|
3118
|
+
if (viewpoint.camera?.roll !== null) {
|
|
3119
|
+
info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
|
|
3120
|
+
}
|
|
3121
|
+
if (viewpoint.camera?.distance !== null) {
|
|
3122
|
+
info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
|
|
3123
|
+
}
|
|
2729
3124
|
const serializedLayers = serializeViewpointLayers(viewpoint.layers);
|
|
2730
3125
|
if (serializedLayers) {
|
|
2731
3126
|
info2[`${prefix}.layers`] = serializedLayers;
|
|
@@ -2742,6 +3137,9 @@
|
|
|
2742
3137
|
if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
|
|
2743
3138
|
info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
|
|
2744
3139
|
}
|
|
3140
|
+
if (viewpoint.events.length > 0) {
|
|
3141
|
+
info2[`${prefix}.events`] = viewpoint.events.join(" ");
|
|
3142
|
+
}
|
|
2745
3143
|
}
|
|
2746
3144
|
for (const annotation of system.annotations) {
|
|
2747
3145
|
const prefix = `annotation.${annotation.id}`;
|
|
@@ -2766,7 +3164,7 @@
|
|
|
2766
3164
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
2767
3165
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
2768
3166
|
}
|
|
2769
|
-
for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
|
|
3167
|
+
for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
|
|
2770
3168
|
if (layers[key] !== void 0) {
|
|
2771
3169
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
2772
3170
|
}
|
|
@@ -2819,26 +3217,26 @@
|
|
|
2819
3217
|
];
|
|
2820
3218
|
function formatDocument(document, options = {}) {
|
|
2821
3219
|
const schema = options.schema ?? "auto";
|
|
2822
|
-
const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.0-draft" || document.version === "2.0" || document.version === "2.1" || document.version === "2.0-draft";
|
|
3220
|
+
const useDraft = schema === "2.0" || schema === "2.1" || schema === "2.5" || schema === "2.0-draft" || document.version === "2.0" || document.version === "2.1" || document.version === "2.5" || document.version === "2.0-draft";
|
|
2823
3221
|
if (useDraft) {
|
|
2824
3222
|
if (schema === "2.0-draft") {
|
|
2825
|
-
const legacyDraftDocument = document.version === "2.0-draft" ? document : document.version === "2.0" || document.version === "2.1" ? {
|
|
3223
|
+
const legacyDraftDocument = document.version === "2.0-draft" ? document : document.version === "2.0" || document.version === "2.1" || document.version === "2.5" ? {
|
|
2826
3224
|
...document,
|
|
2827
3225
|
version: "2.0-draft",
|
|
2828
3226
|
schemaVersion: "2.0-draft"
|
|
2829
3227
|
} : upgradeDocumentToDraftV2(document);
|
|
2830
3228
|
return formatDraftDocument(legacyDraftDocument);
|
|
2831
3229
|
}
|
|
2832
|
-
const atlasDocument = document.version === "2.0" || document.version === "2.1" ? document : document.version === "2.0-draft" ? {
|
|
3230
|
+
const atlasDocument = document.version === "2.0" || document.version === "2.1" || document.version === "2.5" ? document : document.version === "2.0-draft" ? {
|
|
2833
3231
|
...document,
|
|
2834
3232
|
version: "2.0",
|
|
2835
3233
|
schemaVersion: "2.0"
|
|
2836
3234
|
} : upgradeDocumentToV2(document);
|
|
2837
|
-
if (schema === "2.1" && atlasDocument.version !==
|
|
3235
|
+
if ((schema === "2.0" || schema === "2.1" || schema === "2.5") && atlasDocument.version !== schema) {
|
|
2838
3236
|
return formatAtlasDocument({
|
|
2839
3237
|
...atlasDocument,
|
|
2840
|
-
version:
|
|
2841
|
-
schemaVersion:
|
|
3238
|
+
version: schema,
|
|
3239
|
+
schemaVersion: schema
|
|
2842
3240
|
});
|
|
2843
3241
|
}
|
|
2844
3242
|
return formatAtlasDocument(atlasDocument);
|
|
@@ -2870,6 +3268,10 @@
|
|
|
2870
3268
|
lines.push("");
|
|
2871
3269
|
lines.push(...formatAtlasRelation(relation));
|
|
2872
3270
|
}
|
|
3271
|
+
for (const event of [...document.events].sort(compareIdLike)) {
|
|
3272
|
+
lines.push("");
|
|
3273
|
+
lines.push(...formatAtlasEvent(event));
|
|
3274
|
+
}
|
|
2873
3275
|
const sortedObjects = [...document.objects].sort(compareObjects);
|
|
2874
3276
|
if (sortedObjects.length > 0 && lines.at(-1) !== "") {
|
|
2875
3277
|
lines.push("");
|
|
@@ -2900,6 +3302,10 @@
|
|
|
2900
3302
|
lines.push("");
|
|
2901
3303
|
lines.push(...formatAtlasRelation(relation));
|
|
2902
3304
|
}
|
|
3305
|
+
for (const event of [...legacy.events].sort(compareIdLike)) {
|
|
3306
|
+
lines.push("");
|
|
3307
|
+
lines.push(...formatAtlasEvent(event));
|
|
3308
|
+
}
|
|
2903
3309
|
const sortedObjects = [...legacy.objects].sort(compareObjects);
|
|
2904
3310
|
if (sortedObjects.length > 0 && lines.at(-1) !== "") {
|
|
2905
3311
|
lines.push("");
|
|
@@ -3107,10 +3513,28 @@
|
|
|
3107
3513
|
if (viewpoint.rotationDeg !== 0) {
|
|
3108
3514
|
lines.push(` rotation ${viewpoint.rotationDeg}`);
|
|
3109
3515
|
}
|
|
3516
|
+
if (viewpoint.camera && hasCameraValues(viewpoint.camera)) {
|
|
3517
|
+
lines.push(" camera");
|
|
3518
|
+
if (viewpoint.camera.azimuth !== null) {
|
|
3519
|
+
lines.push(` azimuth ${viewpoint.camera.azimuth}`);
|
|
3520
|
+
}
|
|
3521
|
+
if (viewpoint.camera.elevation !== null) {
|
|
3522
|
+
lines.push(` elevation ${viewpoint.camera.elevation}`);
|
|
3523
|
+
}
|
|
3524
|
+
if (viewpoint.camera.roll !== null) {
|
|
3525
|
+
lines.push(` roll ${viewpoint.camera.roll}`);
|
|
3526
|
+
}
|
|
3527
|
+
if (viewpoint.camera.distance !== null) {
|
|
3528
|
+
lines.push(` distance ${viewpoint.camera.distance}`);
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3110
3531
|
const layerTokens = formatDraftLayers(viewpoint.layers);
|
|
3111
3532
|
if (layerTokens.length > 0) {
|
|
3112
3533
|
lines.push(` layers ${layerTokens.join(" ")}`);
|
|
3113
3534
|
}
|
|
3535
|
+
if (viewpoint.events.length > 0) {
|
|
3536
|
+
lines.push(` events ${viewpoint.events.join(" ")}`);
|
|
3537
|
+
}
|
|
3114
3538
|
if (viewpoint.filter) {
|
|
3115
3539
|
lines.push(" filter");
|
|
3116
3540
|
if (viewpoint.filter.query) {
|
|
@@ -3183,6 +3607,65 @@
|
|
|
3183
3607
|
}
|
|
3184
3608
|
return lines;
|
|
3185
3609
|
}
|
|
3610
|
+
function formatAtlasEvent(event) {
|
|
3611
|
+
const lines = [`event ${event.id}`, ` kind ${quoteIfNeeded(event.kind)}`];
|
|
3612
|
+
if (event.label) {
|
|
3613
|
+
lines.push(` label ${quoteIfNeeded(event.label)}`);
|
|
3614
|
+
}
|
|
3615
|
+
if (event.summary) {
|
|
3616
|
+
lines.push(` summary ${quoteIfNeeded(event.summary)}`);
|
|
3617
|
+
}
|
|
3618
|
+
if (event.targetObjectId) {
|
|
3619
|
+
lines.push(` target ${event.targetObjectId}`);
|
|
3620
|
+
}
|
|
3621
|
+
if (event.participantObjectIds.length > 0) {
|
|
3622
|
+
lines.push(` participants ${event.participantObjectIds.join(" ")}`);
|
|
3623
|
+
}
|
|
3624
|
+
if (event.timing) {
|
|
3625
|
+
lines.push(` timing ${quoteIfNeeded(event.timing)}`);
|
|
3626
|
+
}
|
|
3627
|
+
if (event.visibility) {
|
|
3628
|
+
lines.push(` visibility ${quoteIfNeeded(event.visibility)}`);
|
|
3629
|
+
}
|
|
3630
|
+
if (event.epoch) {
|
|
3631
|
+
lines.push(` epoch ${quoteIfNeeded(event.epoch)}`);
|
|
3632
|
+
}
|
|
3633
|
+
if (event.referencePlane) {
|
|
3634
|
+
lines.push(` referencePlane ${quoteIfNeeded(event.referencePlane)}`);
|
|
3635
|
+
}
|
|
3636
|
+
if (event.tags.length > 0) {
|
|
3637
|
+
lines.push(` tags ${event.tags.map(quoteIfNeeded).join(" ")}`);
|
|
3638
|
+
}
|
|
3639
|
+
if (event.color) {
|
|
3640
|
+
lines.push(` color ${quoteIfNeeded(event.color)}`);
|
|
3641
|
+
}
|
|
3642
|
+
if (event.hidden) {
|
|
3643
|
+
lines.push(" hidden true");
|
|
3644
|
+
}
|
|
3645
|
+
if (event.positions.length > 0) {
|
|
3646
|
+
lines.push("");
|
|
3647
|
+
lines.push(" positions");
|
|
3648
|
+
for (const pose of [...event.positions].sort(comparePoseObjectId)) {
|
|
3649
|
+
lines.push(` pose ${pose.objectId}`);
|
|
3650
|
+
for (const fieldLine of formatEventPoseFields(pose)) {
|
|
3651
|
+
lines.push(` ${fieldLine}`);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
return lines;
|
|
3656
|
+
}
|
|
3657
|
+
function formatEventPoseFields(pose) {
|
|
3658
|
+
return [
|
|
3659
|
+
...formatPlacement(pose.placement),
|
|
3660
|
+
...pose.epoch ? [`epoch ${quoteIfNeeded(pose.epoch)}`] : [],
|
|
3661
|
+
...pose.referencePlane ? [`referencePlane ${quoteIfNeeded(pose.referencePlane)}`] : [],
|
|
3662
|
+
...formatOptionalUnit("inner", pose.inner),
|
|
3663
|
+
...formatOptionalUnit("outer", pose.outer)
|
|
3664
|
+
];
|
|
3665
|
+
}
|
|
3666
|
+
function hasCameraValues(camera) {
|
|
3667
|
+
return camera.azimuth !== null || camera.elevation !== null || camera.roll !== null || camera.distance !== null;
|
|
3668
|
+
}
|
|
3186
3669
|
function formatValue(value) {
|
|
3187
3670
|
if (Array.isArray(value)) {
|
|
3188
3671
|
return value.map((item) => quoteIfNeeded(item)).join(" ");
|
|
@@ -3224,7 +3707,7 @@
|
|
|
3224
3707
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
3225
3708
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
3226
3709
|
}
|
|
3227
|
-
for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
|
|
3710
|
+
for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
|
|
3228
3711
|
if (layers[key] !== void 0) {
|
|
3229
3712
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
3230
3713
|
}
|
|
@@ -3252,6 +3735,9 @@
|
|
|
3252
3735
|
function compareIdLike(left, right) {
|
|
3253
3736
|
return left.id.localeCompare(right.id);
|
|
3254
3737
|
}
|
|
3738
|
+
function comparePoseObjectId(left, right) {
|
|
3739
|
+
return left.objectId.localeCompare(right.objectId);
|
|
3740
|
+
}
|
|
3255
3741
|
function objectTypeIndex(objectType) {
|
|
3256
3742
|
switch (objectType) {
|
|
3257
3743
|
case "star":
|
|
@@ -3447,6 +3933,7 @@
|
|
|
3447
3933
|
const diagnostics = [];
|
|
3448
3934
|
const objectMap = new Map(document.objects.map((object) => [object.id, object]));
|
|
3449
3935
|
const groupIds = new Set(document.groups.map((group) => group.id));
|
|
3936
|
+
const eventIds = new Set(document.events.map((event) => event.id));
|
|
3450
3937
|
if (!document.system) {
|
|
3451
3938
|
diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
|
|
3452
3939
|
}
|
|
@@ -3456,6 +3943,7 @@
|
|
|
3456
3943
|
["viewpoint", document.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
|
|
3457
3944
|
["annotation", document.system?.annotations.map((annotation) => annotation.id) ?? []],
|
|
3458
3945
|
["relation", document.relations.map((relation) => relation.id)],
|
|
3946
|
+
["event", document.events.map((event) => event.id)],
|
|
3459
3947
|
["object", document.objects.map((object) => object.id)]
|
|
3460
3948
|
]) {
|
|
3461
3949
|
for (const id of ids) {
|
|
@@ -3471,11 +3959,14 @@
|
|
|
3471
3959
|
validateRelation(relation, objectMap, diagnostics);
|
|
3472
3960
|
}
|
|
3473
3961
|
for (const viewpoint of document.system?.viewpoints ?? []) {
|
|
3474
|
-
|
|
3962
|
+
validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
|
|
3475
3963
|
}
|
|
3476
3964
|
for (const object of document.objects) {
|
|
3477
3965
|
validateObject(object, document.system, objectMap, groupIds, diagnostics);
|
|
3478
3966
|
}
|
|
3967
|
+
for (const event of document.events) {
|
|
3968
|
+
validateEvent(event, document.system, objectMap, diagnostics);
|
|
3969
|
+
}
|
|
3479
3970
|
return diagnostics;
|
|
3480
3971
|
}
|
|
3481
3972
|
function validateRelation(relation, objectMap, diagnostics) {
|
|
@@ -3493,15 +3984,24 @@
|
|
|
3493
3984
|
diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
|
|
3494
3985
|
}
|
|
3495
3986
|
}
|
|
3496
|
-
function
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3987
|
+
function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
|
|
3988
|
+
const filter = viewpoint.filter;
|
|
3989
|
+
if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
|
|
3990
|
+
if (filter) {
|
|
3991
|
+
for (const groupId of filter.groupIds) {
|
|
3992
|
+
if (!groupIds.has(groupId)) {
|
|
3993
|
+
diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
for (const eventId of viewpoint.events ?? []) {
|
|
3998
|
+
if (!eventIds.has(eventId)) {
|
|
3999
|
+
diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
|
|
4000
|
+
}
|
|
3503
4001
|
}
|
|
3504
4002
|
}
|
|
4003
|
+
validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
|
|
4004
|
+
validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
|
|
3505
4005
|
}
|
|
3506
4006
|
function validateObject(object, system, objectMap, groupIds, diagnostics) {
|
|
3507
4007
|
const placement = object.placement;
|
|
@@ -3514,6 +4014,12 @@
|
|
|
3514
4014
|
}
|
|
3515
4015
|
}
|
|
3516
4016
|
}
|
|
4017
|
+
if (typeof object.epoch === "string" && !object.epoch.trim()) {
|
|
4018
|
+
diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
|
|
4019
|
+
}
|
|
4020
|
+
if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
|
|
4021
|
+
diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
|
|
4022
|
+
}
|
|
3517
4023
|
if (orbitPlacement) {
|
|
3518
4024
|
if (!objectMap.has(orbitPlacement.target)) {
|
|
3519
4025
|
diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
|
|
@@ -3585,6 +4091,122 @@
|
|
|
3585
4091
|
}
|
|
3586
4092
|
}
|
|
3587
4093
|
}
|
|
4094
|
+
function validateEvent(event, system, objectMap, diagnostics) {
|
|
4095
|
+
const fieldPrefix = `event.${event.id}`;
|
|
4096
|
+
const referencedIds = /* @__PURE__ */ new Set();
|
|
4097
|
+
if (!event.kind.trim()) {
|
|
4098
|
+
diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
|
|
4099
|
+
}
|
|
4100
|
+
if (typeof event.epoch === "string" && !event.epoch.trim()) {
|
|
4101
|
+
diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
|
|
4102
|
+
}
|
|
4103
|
+
if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
|
|
4104
|
+
diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
|
|
4105
|
+
}
|
|
4106
|
+
if (!event.targetObjectId && event.participantObjectIds.length === 0) {
|
|
4107
|
+
diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
|
|
4108
|
+
}
|
|
4109
|
+
if (event.targetObjectId) {
|
|
4110
|
+
referencedIds.add(event.targetObjectId);
|
|
4111
|
+
if (!objectMap.has(event.targetObjectId)) {
|
|
4112
|
+
diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, void 0, `${fieldPrefix}.target`));
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
const seenParticipants = /* @__PURE__ */ new Set();
|
|
4116
|
+
for (const participantId of event.participantObjectIds) {
|
|
4117
|
+
referencedIds.add(participantId);
|
|
4118
|
+
if (seenParticipants.has(participantId)) {
|
|
4119
|
+
diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, void 0, `${fieldPrefix}.participants`));
|
|
4120
|
+
continue;
|
|
4121
|
+
}
|
|
4122
|
+
seenParticipants.add(participantId);
|
|
4123
|
+
if (!objectMap.has(participantId)) {
|
|
4124
|
+
diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, void 0, `${fieldPrefix}.participants`));
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
if (event.targetObjectId && event.participantObjectIds.length > 0 && !event.participantObjectIds.includes(event.targetObjectId)) {
|
|
4128
|
+
diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, void 0, `${fieldPrefix}.target`));
|
|
4129
|
+
}
|
|
4130
|
+
if (event.positions.length === 0) {
|
|
4131
|
+
diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, void 0, `${fieldPrefix}.positions`));
|
|
4132
|
+
}
|
|
4133
|
+
if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
|
|
4134
|
+
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`));
|
|
4135
|
+
}
|
|
4136
|
+
const poseIds = /* @__PURE__ */ new Set();
|
|
4137
|
+
for (const pose of event.positions) {
|
|
4138
|
+
const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
|
|
4139
|
+
if (poseIds.has(pose.objectId)) {
|
|
4140
|
+
diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, void 0, poseFieldPrefix));
|
|
4141
|
+
continue;
|
|
4142
|
+
}
|
|
4143
|
+
poseIds.add(pose.objectId);
|
|
4144
|
+
const object = objectMap.get(pose.objectId);
|
|
4145
|
+
if (!object) {
|
|
4146
|
+
diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, void 0, poseFieldPrefix));
|
|
4147
|
+
continue;
|
|
4148
|
+
}
|
|
4149
|
+
if (!referencedIds.has(pose.objectId)) {
|
|
4150
|
+
diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
|
|
4151
|
+
}
|
|
4152
|
+
validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
|
|
4153
|
+
}
|
|
4154
|
+
const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
|
|
4155
|
+
if (event.positions.length > 0 && missingPoseIds.length > 0) {
|
|
4156
|
+
diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
|
|
4160
|
+
const placement = pose.placement;
|
|
4161
|
+
if (!placement) {
|
|
4162
|
+
diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
|
|
4163
|
+
return;
|
|
4164
|
+
}
|
|
4165
|
+
if (placement.mode === "orbit") {
|
|
4166
|
+
if (!objectMap.has(placement.target)) {
|
|
4167
|
+
diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
|
|
4168
|
+
}
|
|
4169
|
+
if (placement.distance && placement.semiMajor) {
|
|
4170
|
+
diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
|
|
4171
|
+
}
|
|
4172
|
+
if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
|
|
4173
|
+
diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
|
|
4174
|
+
}
|
|
4175
|
+
if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
|
|
4176
|
+
diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
|
|
4177
|
+
}
|
|
4178
|
+
if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
|
|
4179
|
+
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`));
|
|
4180
|
+
}
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
if (placement.mode === "surface") {
|
|
4184
|
+
const target = objectMap.get(placement.target);
|
|
4185
|
+
if (!target) {
|
|
4186
|
+
diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.surface`));
|
|
4187
|
+
} else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
|
|
4188
|
+
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`));
|
|
4189
|
+
}
|
|
4190
|
+
return;
|
|
4191
|
+
}
|
|
4192
|
+
if (placement.mode === "at") {
|
|
4193
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
4194
|
+
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`));
|
|
4195
|
+
}
|
|
4196
|
+
const reference = placement.reference;
|
|
4197
|
+
if (reference.kind === "named" && !objectMap.has(reference.name)) {
|
|
4198
|
+
diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
4199
|
+
} else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
|
|
4200
|
+
diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
4201
|
+
} else if (reference.kind === "lagrange") {
|
|
4202
|
+
if (!objectMap.has(reference.primary)) {
|
|
4203
|
+
diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
4204
|
+
} else if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
4205
|
+
diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
3588
4210
|
function validateAtTarget(object, objectMap, diagnostics) {
|
|
3589
4211
|
const reference = object.placement?.mode === "at" ? object.placement.reference : null;
|
|
3590
4212
|
if (!reference) {
|
|
@@ -3690,6 +4312,52 @@
|
|
|
3690
4312
|
return null;
|
|
3691
4313
|
}
|
|
3692
4314
|
}
|
|
4315
|
+
function validateProjection(projection, diagnostics, field, viewpointId) {
|
|
4316
|
+
if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
|
|
4317
|
+
diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
|
|
4321
|
+
if (!camera) {
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
const prefix = `viewpoint.${viewpointId}.camera`;
|
|
4325
|
+
for (const [key, value] of [
|
|
4326
|
+
["azimuth", camera.azimuth],
|
|
4327
|
+
["elevation", camera.elevation],
|
|
4328
|
+
["roll", camera.roll],
|
|
4329
|
+
["distance", camera.distance]
|
|
4330
|
+
]) {
|
|
4331
|
+
if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
|
|
4332
|
+
diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
if (camera.distance !== null && projection !== "perspective") {
|
|
4336
|
+
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`));
|
|
4337
|
+
}
|
|
4338
|
+
if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
|
|
4339
|
+
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));
|
|
4340
|
+
}
|
|
4341
|
+
if (projection === "isometric" && camera.elevation !== null) {
|
|
4342
|
+
diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
|
|
4343
|
+
}
|
|
4344
|
+
if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
|
|
4345
|
+
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`));
|
|
4346
|
+
}
|
|
4347
|
+
const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
|
|
4348
|
+
if (!hasAnchor) {
|
|
4349
|
+
diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
function resolveEffectiveEpoch(system, object, event, pose) {
|
|
4353
|
+
return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
|
|
4354
|
+
}
|
|
4355
|
+
function resolveEffectiveReferencePlane(system, object, event, pose) {
|
|
4356
|
+
return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
|
|
4357
|
+
}
|
|
4358
|
+
function normalizeOptionalContextString(value) {
|
|
4359
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
4360
|
+
}
|
|
3693
4361
|
function toleranceForField(object, field) {
|
|
3694
4362
|
const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
|
|
3695
4363
|
if (typeof tolerance === "number") {
|
|
@@ -3785,6 +4453,23 @@
|
|
|
3785
4453
|
});
|
|
3786
4454
|
}
|
|
3787
4455
|
var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
|
|
4456
|
+
var EVENT_POSE_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
4457
|
+
"orbit",
|
|
4458
|
+
"distance",
|
|
4459
|
+
"semiMajor",
|
|
4460
|
+
"eccentricity",
|
|
4461
|
+
"period",
|
|
4462
|
+
"angle",
|
|
4463
|
+
"inclination",
|
|
4464
|
+
"phase",
|
|
4465
|
+
"at",
|
|
4466
|
+
"surface",
|
|
4467
|
+
"free",
|
|
4468
|
+
"inner",
|
|
4469
|
+
"outer",
|
|
4470
|
+
"epoch",
|
|
4471
|
+
"referencePlane"
|
|
4472
|
+
]);
|
|
3788
4473
|
function parseWorldOrbitAtlas(source) {
|
|
3789
4474
|
return parseAtlasSource(source);
|
|
3790
4475
|
}
|
|
@@ -3802,12 +4487,15 @@
|
|
|
3802
4487
|
const objectNodes = [];
|
|
3803
4488
|
const groups = [];
|
|
3804
4489
|
const relations = [];
|
|
4490
|
+
const events = [];
|
|
4491
|
+
const eventPoseNodes = /* @__PURE__ */ new Map();
|
|
3805
4492
|
let sawDefaults = false;
|
|
3806
4493
|
let sawAtlas = false;
|
|
3807
4494
|
const viewpointIds = /* @__PURE__ */ new Set();
|
|
3808
4495
|
const annotationIds = /* @__PURE__ */ new Set();
|
|
3809
4496
|
const groupIds = /* @__PURE__ */ new Set();
|
|
3810
4497
|
const relationIds = /* @__PURE__ */ new Set();
|
|
4498
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
3811
4499
|
for (let index = 0; index < lines.length; index++) {
|
|
3812
4500
|
const rawLine = lines[index];
|
|
3813
4501
|
const lineNumber = index + 1;
|
|
@@ -3825,7 +4513,7 @@
|
|
|
3825
4513
|
if (!sawSchemaHeader) {
|
|
3826
4514
|
sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
|
|
3827
4515
|
sawSchemaHeader = true;
|
|
3828
|
-
if (prepared.comments.length > 0 && sourceSchemaVersion
|
|
4516
|
+
if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
3829
4517
|
diagnostics.push({
|
|
3830
4518
|
code: "parse.schema21.commentCompatibility",
|
|
3831
4519
|
severity: "warning",
|
|
@@ -3838,7 +4526,7 @@
|
|
|
3838
4526
|
continue;
|
|
3839
4527
|
}
|
|
3840
4528
|
if (indent === 0) {
|
|
3841
|
-
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
|
|
4529
|
+
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
|
|
3842
4530
|
if (section.kind === "system") {
|
|
3843
4531
|
system = section.system;
|
|
3844
4532
|
} else if (section.kind === "defaults") {
|
|
@@ -3857,6 +4545,7 @@
|
|
|
3857
4545
|
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
3858
4546
|
}
|
|
3859
4547
|
const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
|
|
4548
|
+
const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
|
|
3860
4549
|
const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
|
|
3861
4550
|
const baseDocument = {
|
|
3862
4551
|
format: "worldorbit",
|
|
@@ -3864,6 +4553,7 @@
|
|
|
3864
4553
|
system,
|
|
3865
4554
|
groups,
|
|
3866
4555
|
relations,
|
|
4556
|
+
events: normalizedEvents,
|
|
3867
4557
|
objects,
|
|
3868
4558
|
diagnostics
|
|
3869
4559
|
};
|
|
@@ -3893,13 +4583,13 @@
|
|
|
3893
4583
|
return document;
|
|
3894
4584
|
}
|
|
3895
4585
|
function assertDraftSchemaHeader(tokens, line) {
|
|
3896
|
-
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
|
|
3897
|
-
throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
4586
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
|
|
4587
|
+
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);
|
|
3898
4588
|
}
|
|
3899
4589
|
const version = tokens[1].value.toLowerCase();
|
|
3900
|
-
return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
4590
|
+
return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
3901
4591
|
}
|
|
3902
|
-
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
|
|
4592
|
+
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
|
|
3903
4593
|
const keyword = tokens[0]?.value.toLowerCase();
|
|
3904
4594
|
switch (keyword) {
|
|
3905
4595
|
case "system":
|
|
@@ -3917,6 +4607,8 @@
|
|
|
3917
4607
|
return {
|
|
3918
4608
|
kind: "defaults",
|
|
3919
4609
|
system,
|
|
4610
|
+
sourceSchemaVersion,
|
|
4611
|
+
diagnostics,
|
|
3920
4612
|
seenFields: /* @__PURE__ */ new Set()
|
|
3921
4613
|
};
|
|
3922
4614
|
case "atlas":
|
|
@@ -3936,7 +4628,7 @@
|
|
|
3936
4628
|
if (!system) {
|
|
3937
4629
|
throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
|
|
3938
4630
|
}
|
|
3939
|
-
return startViewpointSection(tokens, line, system, viewpointIds);
|
|
4631
|
+
return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
|
|
3940
4632
|
case "annotation":
|
|
3941
4633
|
if (!system) {
|
|
3942
4634
|
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
@@ -3948,6 +4640,9 @@
|
|
|
3948
4640
|
case "relation":
|
|
3949
4641
|
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
|
|
3950
4642
|
return startRelationSection(tokens, line, relations, relationIds);
|
|
4643
|
+
case "event":
|
|
4644
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
|
|
4645
|
+
return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
|
|
3951
4646
|
case "object":
|
|
3952
4647
|
return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
|
|
3953
4648
|
default:
|
|
@@ -3984,7 +4679,7 @@
|
|
|
3984
4679
|
seenFields: /* @__PURE__ */ new Set()
|
|
3985
4680
|
};
|
|
3986
4681
|
}
|
|
3987
|
-
function startViewpointSection(tokens, line, system, viewpointIds) {
|
|
4682
|
+
function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
|
|
3988
4683
|
if (tokens.length !== 2) {
|
|
3989
4684
|
throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
|
|
3990
4685
|
}
|
|
@@ -4001,10 +4696,12 @@
|
|
|
4001
4696
|
summary: "",
|
|
4002
4697
|
focusObjectId: null,
|
|
4003
4698
|
selectedObjectId: null,
|
|
4699
|
+
events: [],
|
|
4004
4700
|
projection: system.defaults.view,
|
|
4005
4701
|
preset: system.defaults.preset,
|
|
4006
4702
|
zoom: null,
|
|
4007
4703
|
rotationDeg: 0,
|
|
4704
|
+
camera: null,
|
|
4008
4705
|
layers: {},
|
|
4009
4706
|
filter: null
|
|
4010
4707
|
};
|
|
@@ -4013,10 +4710,15 @@
|
|
|
4013
4710
|
return {
|
|
4014
4711
|
kind: "viewpoint",
|
|
4015
4712
|
viewpoint,
|
|
4713
|
+
sourceSchemaVersion,
|
|
4714
|
+
diagnostics,
|
|
4016
4715
|
seenFields: /* @__PURE__ */ new Set(),
|
|
4017
4716
|
inFilter: false,
|
|
4018
4717
|
filterIndent: null,
|
|
4019
|
-
seenFilterFields: /* @__PURE__ */ new Set()
|
|
4718
|
+
seenFilterFields: /* @__PURE__ */ new Set(),
|
|
4719
|
+
inCamera: false,
|
|
4720
|
+
cameraIndent: null,
|
|
4721
|
+
seenCameraFields: /* @__PURE__ */ new Set()
|
|
4020
4722
|
};
|
|
4021
4723
|
}
|
|
4022
4724
|
function startAnnotationSection(tokens, line, system, annotationIds) {
|
|
@@ -4103,6 +4805,51 @@
|
|
|
4103
4805
|
seenFields: /* @__PURE__ */ new Set()
|
|
4104
4806
|
};
|
|
4105
4807
|
}
|
|
4808
|
+
function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
|
|
4809
|
+
if (tokens.length !== 2) {
|
|
4810
|
+
throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
|
|
4811
|
+
}
|
|
4812
|
+
const id = normalizeIdentifier2(tokens[1].value);
|
|
4813
|
+
if (!id) {
|
|
4814
|
+
throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
|
|
4815
|
+
}
|
|
4816
|
+
if (eventIds.has(id)) {
|
|
4817
|
+
throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
|
|
4818
|
+
}
|
|
4819
|
+
const event = {
|
|
4820
|
+
id,
|
|
4821
|
+
kind: "",
|
|
4822
|
+
label: humanizeIdentifier3(id),
|
|
4823
|
+
summary: null,
|
|
4824
|
+
targetObjectId: null,
|
|
4825
|
+
participantObjectIds: [],
|
|
4826
|
+
timing: null,
|
|
4827
|
+
visibility: null,
|
|
4828
|
+
epoch: null,
|
|
4829
|
+
referencePlane: null,
|
|
4830
|
+
tags: [],
|
|
4831
|
+
color: null,
|
|
4832
|
+
hidden: false,
|
|
4833
|
+
positions: []
|
|
4834
|
+
};
|
|
4835
|
+
const rawPoses = [];
|
|
4836
|
+
events.push(event);
|
|
4837
|
+
eventPoseNodes.set(id, rawPoses);
|
|
4838
|
+
eventIds.add(id);
|
|
4839
|
+
return {
|
|
4840
|
+
kind: "event",
|
|
4841
|
+
event,
|
|
4842
|
+
sourceSchemaVersion,
|
|
4843
|
+
diagnostics,
|
|
4844
|
+
seenFields: /* @__PURE__ */ new Set(),
|
|
4845
|
+
rawPoses,
|
|
4846
|
+
inPositions: false,
|
|
4847
|
+
positionsIndent: null,
|
|
4848
|
+
activePose: null,
|
|
4849
|
+
poseIndent: null,
|
|
4850
|
+
activePoseSeenFields: /* @__PURE__ */ new Set()
|
|
4851
|
+
};
|
|
4852
|
+
}
|
|
4106
4853
|
function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
|
|
4107
4854
|
if (tokens.length < 3) {
|
|
4108
4855
|
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
@@ -4159,6 +4906,9 @@
|
|
|
4159
4906
|
case "relation":
|
|
4160
4907
|
applyRelationField(section, tokens, line);
|
|
4161
4908
|
return;
|
|
4909
|
+
case "event":
|
|
4910
|
+
applyEventField(section, indent, tokens, line);
|
|
4911
|
+
return;
|
|
4162
4912
|
case "object":
|
|
4163
4913
|
applyObjectField(section, indent, tokens, line);
|
|
4164
4914
|
return;
|
|
@@ -4201,6 +4951,12 @@
|
|
|
4201
4951
|
const value = joinFieldValue(tokens, line);
|
|
4202
4952
|
switch (key) {
|
|
4203
4953
|
case "view":
|
|
4954
|
+
if (isSchema25Projection(value)) {
|
|
4955
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
|
|
4956
|
+
line,
|
|
4957
|
+
column: tokens[0].column
|
|
4958
|
+
});
|
|
4959
|
+
}
|
|
4204
4960
|
section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
|
|
4205
4961
|
return;
|
|
4206
4962
|
case "scale":
|
|
@@ -4240,14 +4996,36 @@
|
|
|
4240
4996
|
throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4241
4997
|
}
|
|
4242
4998
|
function applyViewpointField2(section, indent, tokens, line) {
|
|
4999
|
+
if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
|
|
5000
|
+
section.inCamera = false;
|
|
5001
|
+
section.cameraIndent = null;
|
|
5002
|
+
}
|
|
4243
5003
|
if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
|
|
4244
5004
|
section.inFilter = false;
|
|
4245
5005
|
section.filterIndent = null;
|
|
4246
5006
|
}
|
|
5007
|
+
if (section.inCamera) {
|
|
5008
|
+
applyViewpointCameraField(section, tokens, line);
|
|
5009
|
+
return;
|
|
5010
|
+
}
|
|
4247
5011
|
if (section.inFilter) {
|
|
4248
5012
|
applyViewpointFilterField(section, tokens, line);
|
|
4249
5013
|
return;
|
|
4250
5014
|
}
|
|
5015
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
|
|
5016
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
|
|
5017
|
+
line,
|
|
5018
|
+
column: tokens[0].column
|
|
5019
|
+
});
|
|
5020
|
+
if (section.seenFields.has("camera")) {
|
|
5021
|
+
throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
|
|
5022
|
+
}
|
|
5023
|
+
section.seenFields.add("camera");
|
|
5024
|
+
section.inCamera = true;
|
|
5025
|
+
section.cameraIndent = indent;
|
|
5026
|
+
section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
|
|
5027
|
+
return;
|
|
5028
|
+
}
|
|
4251
5029
|
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
|
|
4252
5030
|
if (section.seenFields.has("filter")) {
|
|
4253
5031
|
throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
|
|
@@ -4273,6 +5051,12 @@
|
|
|
4273
5051
|
section.viewpoint.selectedObjectId = value;
|
|
4274
5052
|
return;
|
|
4275
5053
|
case "projection":
|
|
5054
|
+
if (isSchema25Projection(value)) {
|
|
5055
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
|
|
5056
|
+
line,
|
|
5057
|
+
column: tokens[0].column
|
|
5058
|
+
});
|
|
5059
|
+
}
|
|
4276
5060
|
section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
|
|
4277
5061
|
return;
|
|
4278
5062
|
case "preset":
|
|
@@ -4284,13 +5068,49 @@
|
|
|
4284
5068
|
case "rotation":
|
|
4285
5069
|
section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
|
|
4286
5070
|
return;
|
|
5071
|
+
case "camera":
|
|
5072
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
|
|
5073
|
+
line,
|
|
5074
|
+
column: tokens[0].column
|
|
5075
|
+
});
|
|
5076
|
+
section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
|
|
5077
|
+
return;
|
|
4287
5078
|
case "layers":
|
|
4288
|
-
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
|
|
5079
|
+
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
|
|
5080
|
+
return;
|
|
5081
|
+
case "events":
|
|
5082
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
|
|
5083
|
+
line,
|
|
5084
|
+
column: tokens[0].column
|
|
5085
|
+
});
|
|
5086
|
+
section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
|
|
4289
5087
|
return;
|
|
4290
5088
|
default:
|
|
4291
5089
|
throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4292
5090
|
}
|
|
4293
5091
|
}
|
|
5092
|
+
function applyViewpointCameraField(section, tokens, line) {
|
|
5093
|
+
const key = requireUniqueField(tokens, section.seenCameraFields, line);
|
|
5094
|
+
const value = joinFieldValue(tokens, line);
|
|
5095
|
+
const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
|
|
5096
|
+
switch (key) {
|
|
5097
|
+
case "azimuth":
|
|
5098
|
+
camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
|
|
5099
|
+
break;
|
|
5100
|
+
case "elevation":
|
|
5101
|
+
camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
|
|
5102
|
+
break;
|
|
5103
|
+
case "roll":
|
|
5104
|
+
camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
|
|
5105
|
+
break;
|
|
5106
|
+
case "distance":
|
|
5107
|
+
camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
|
|
5108
|
+
break;
|
|
5109
|
+
default:
|
|
5110
|
+
throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
|
|
5111
|
+
}
|
|
5112
|
+
section.viewpoint.camera = camera;
|
|
5113
|
+
}
|
|
4294
5114
|
function applyViewpointFilterField(section, tokens, line) {
|
|
4295
5115
|
const key = requireUniqueField(tokens, section.seenFilterFields, line);
|
|
4296
5116
|
const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
|
|
@@ -4390,6 +5210,126 @@
|
|
|
4390
5210
|
throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4391
5211
|
}
|
|
4392
5212
|
}
|
|
5213
|
+
function applyEventField(section, indent, tokens, line) {
|
|
5214
|
+
if (section.activePose && indent <= (section.poseIndent ?? 0)) {
|
|
5215
|
+
section.activePose = null;
|
|
5216
|
+
section.poseIndent = null;
|
|
5217
|
+
section.activePoseSeenFields.clear();
|
|
5218
|
+
}
|
|
5219
|
+
if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
|
|
5220
|
+
section.inPositions = false;
|
|
5221
|
+
section.positionsIndent = null;
|
|
5222
|
+
}
|
|
5223
|
+
if (section.activePose) {
|
|
5224
|
+
if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
|
|
5225
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
|
|
5226
|
+
line,
|
|
5227
|
+
column: tokens[0]?.column ?? 1
|
|
5228
|
+
});
|
|
5229
|
+
}
|
|
5230
|
+
section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
|
|
5231
|
+
return;
|
|
5232
|
+
}
|
|
5233
|
+
if (section.inPositions) {
|
|
5234
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
|
|
5235
|
+
throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
|
|
5236
|
+
}
|
|
5237
|
+
const objectId = tokens[1].value;
|
|
5238
|
+
if (!objectId.trim()) {
|
|
5239
|
+
throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
|
|
5240
|
+
}
|
|
5241
|
+
const rawPose = {
|
|
5242
|
+
objectId,
|
|
5243
|
+
fields: [],
|
|
5244
|
+
location: { line, column: tokens[0].column }
|
|
5245
|
+
};
|
|
5246
|
+
section.rawPoses.push(rawPose);
|
|
5247
|
+
section.activePose = rawPose;
|
|
5248
|
+
section.poseIndent = indent;
|
|
5249
|
+
section.activePoseSeenFields = /* @__PURE__ */ new Set();
|
|
5250
|
+
return;
|
|
5251
|
+
}
|
|
5252
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
|
|
5253
|
+
if (section.seenFields.has("positions")) {
|
|
5254
|
+
throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
|
|
5255
|
+
}
|
|
5256
|
+
section.seenFields.add("positions");
|
|
5257
|
+
section.inPositions = true;
|
|
5258
|
+
section.positionsIndent = indent;
|
|
5259
|
+
return;
|
|
5260
|
+
}
|
|
5261
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
5262
|
+
switch (key) {
|
|
5263
|
+
case "kind":
|
|
5264
|
+
section.event.kind = joinFieldValue(tokens, line);
|
|
5265
|
+
return;
|
|
5266
|
+
case "label":
|
|
5267
|
+
section.event.label = joinFieldValue(tokens, line);
|
|
5268
|
+
return;
|
|
5269
|
+
case "summary":
|
|
5270
|
+
section.event.summary = joinFieldValue(tokens, line);
|
|
5271
|
+
return;
|
|
5272
|
+
case "target":
|
|
5273
|
+
section.event.targetObjectId = joinFieldValue(tokens, line);
|
|
5274
|
+
return;
|
|
5275
|
+
case "participants":
|
|
5276
|
+
section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
|
|
5277
|
+
return;
|
|
5278
|
+
case "timing":
|
|
5279
|
+
section.event.timing = joinFieldValue(tokens, line);
|
|
5280
|
+
return;
|
|
5281
|
+
case "visibility":
|
|
5282
|
+
section.event.visibility = joinFieldValue(tokens, line);
|
|
5283
|
+
return;
|
|
5284
|
+
case "epoch":
|
|
5285
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
|
|
5286
|
+
line,
|
|
5287
|
+
column: tokens[0].column
|
|
5288
|
+
});
|
|
5289
|
+
section.event.epoch = joinFieldValue(tokens, line);
|
|
5290
|
+
return;
|
|
5291
|
+
case "referenceplane":
|
|
5292
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
|
|
5293
|
+
line,
|
|
5294
|
+
column: tokens[0].column
|
|
5295
|
+
});
|
|
5296
|
+
section.event.referencePlane = joinFieldValue(tokens, line);
|
|
5297
|
+
return;
|
|
5298
|
+
case "tags":
|
|
5299
|
+
section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
5300
|
+
return;
|
|
5301
|
+
case "color":
|
|
5302
|
+
section.event.color = joinFieldValue(tokens, line);
|
|
5303
|
+
return;
|
|
5304
|
+
case "hidden":
|
|
5305
|
+
section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
5306
|
+
line,
|
|
5307
|
+
column: tokens[0].column
|
|
5308
|
+
});
|
|
5309
|
+
return;
|
|
5310
|
+
default:
|
|
5311
|
+
throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5314
|
+
function parseEventPoseField(tokens, line, seenFields) {
|
|
5315
|
+
if (tokens.length < 2) {
|
|
5316
|
+
throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
|
|
5317
|
+
}
|
|
5318
|
+
const key = tokens[0].value;
|
|
5319
|
+
if (!EVENT_POSE_FIELD_KEYS.has(key)) {
|
|
5320
|
+
throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
|
|
5321
|
+
}
|
|
5322
|
+
if (seenFields.has(key)) {
|
|
5323
|
+
throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
|
|
5324
|
+
}
|
|
5325
|
+
seenFields.add(key);
|
|
5326
|
+
return {
|
|
5327
|
+
type: "field",
|
|
5328
|
+
key,
|
|
5329
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
5330
|
+
location: { line, column: tokens[0].column }
|
|
5331
|
+
};
|
|
5332
|
+
}
|
|
4393
5333
|
function applyObjectField(section, indent, tokens, line) {
|
|
4394
5334
|
if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
|
|
4395
5335
|
section.activeBlock = null;
|
|
@@ -4448,7 +5388,7 @@
|
|
|
4448
5388
|
function parseObjectTypeTokens(tokens, line) {
|
|
4449
5389
|
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");
|
|
4450
5390
|
}
|
|
4451
|
-
function parseLayerTokens(tokens, line) {
|
|
5391
|
+
function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
4452
5392
|
const layers = {};
|
|
4453
5393
|
for (const token of parseTokenList(tokens, line, "layers")) {
|
|
4454
5394
|
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
@@ -4458,7 +5398,13 @@
|
|
|
4458
5398
|
layers["orbits-front"] = enabled;
|
|
4459
5399
|
continue;
|
|
4460
5400
|
}
|
|
4461
|
-
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
5401
|
+
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
5402
|
+
if (raw === "events" && sourceSchemaVersion && diagnostics) {
|
|
5403
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
|
|
5404
|
+
line,
|
|
5405
|
+
column: tokens[0]?.column ?? 1
|
|
5406
|
+
});
|
|
5407
|
+
}
|
|
4462
5408
|
layers[raw] = enabled;
|
|
4463
5409
|
}
|
|
4464
5410
|
}
|
|
@@ -4476,11 +5422,15 @@
|
|
|
4476
5422
|
}
|
|
4477
5423
|
function parseProjectionValue(value, line, column) {
|
|
4478
5424
|
const normalized = value.toLowerCase();
|
|
4479
|
-
if (normalized !== "topdown" && normalized !== "isometric") {
|
|
5425
|
+
if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
|
|
4480
5426
|
throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
|
|
4481
5427
|
}
|
|
4482
5428
|
return normalized;
|
|
4483
5429
|
}
|
|
5430
|
+
function isSchema25Projection(value) {
|
|
5431
|
+
const normalized = value.toLowerCase();
|
|
5432
|
+
return normalized === "orthographic" || normalized === "perspective";
|
|
5433
|
+
}
|
|
4484
5434
|
function parsePresetValue(value, line, column) {
|
|
4485
5435
|
const normalized = value.toLowerCase();
|
|
4486
5436
|
if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
|
|
@@ -4510,6 +5460,48 @@
|
|
|
4510
5460
|
groupIds: []
|
|
4511
5461
|
};
|
|
4512
5462
|
}
|
|
5463
|
+
function createEmptyViewCamera2() {
|
|
5464
|
+
return {
|
|
5465
|
+
azimuth: null,
|
|
5466
|
+
elevation: null,
|
|
5467
|
+
roll: null,
|
|
5468
|
+
distance: null
|
|
5469
|
+
};
|
|
5470
|
+
}
|
|
5471
|
+
function parseInlineViewCamera(tokens, line, current) {
|
|
5472
|
+
if (tokens.length === 0 || tokens.length % 2 !== 0) {
|
|
5473
|
+
throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
|
|
5474
|
+
}
|
|
5475
|
+
const camera = current ? { ...current } : createEmptyViewCamera2();
|
|
5476
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5477
|
+
for (let index = 0; index < tokens.length; index += 2) {
|
|
5478
|
+
const fieldToken = tokens[index];
|
|
5479
|
+
const valueToken = tokens[index + 1];
|
|
5480
|
+
const key = fieldToken.value.toLowerCase();
|
|
5481
|
+
if (seen.has(key)) {
|
|
5482
|
+
throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
|
|
5483
|
+
}
|
|
5484
|
+
seen.add(key);
|
|
5485
|
+
const value = valueToken.value;
|
|
5486
|
+
switch (key) {
|
|
5487
|
+
case "azimuth":
|
|
5488
|
+
camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
|
|
5489
|
+
break;
|
|
5490
|
+
case "elevation":
|
|
5491
|
+
camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
|
|
5492
|
+
break;
|
|
5493
|
+
case "roll":
|
|
5494
|
+
camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
|
|
5495
|
+
break;
|
|
5496
|
+
case "distance":
|
|
5497
|
+
camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
|
|
5498
|
+
break;
|
|
5499
|
+
default:
|
|
5500
|
+
throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
return camera;
|
|
5504
|
+
}
|
|
4513
5505
|
function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
4514
5506
|
const fields = [];
|
|
4515
5507
|
let index = 0;
|
|
@@ -4597,7 +5589,7 @@
|
|
|
4597
5589
|
}
|
|
4598
5590
|
function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
4599
5591
|
const fieldMap = collectDraftFields(node.fields);
|
|
4600
|
-
const placement =
|
|
5592
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
4601
5593
|
const properties = normalizeDraftProperties(node.objectType, fieldMap);
|
|
4602
5594
|
const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
|
|
4603
5595
|
const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
|
|
@@ -4642,21 +5634,41 @@
|
|
|
4642
5634
|
object.tolerances = tolerances;
|
|
4643
5635
|
if (typedBlocks && Object.keys(typedBlocks).length > 0)
|
|
4644
5636
|
object.typedBlocks = typedBlocks;
|
|
4645
|
-
if (sourceSchemaVersion
|
|
5637
|
+
if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
4646
5638
|
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) {
|
|
4647
5639
|
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
|
|
4648
5640
|
}
|
|
4649
5641
|
}
|
|
4650
5642
|
return object;
|
|
4651
5643
|
}
|
|
4652
|
-
function
|
|
5644
|
+
function normalizeDraftEvent(event, rawPoses) {
|
|
5645
|
+
return {
|
|
5646
|
+
...event,
|
|
5647
|
+
participantObjectIds: [...new Set(event.participantObjectIds)],
|
|
5648
|
+
tags: [...new Set(event.tags)],
|
|
5649
|
+
positions: rawPoses.map((pose) => normalizeDraftEventPose(pose))
|
|
5650
|
+
};
|
|
5651
|
+
}
|
|
5652
|
+
function normalizeDraftEventPose(rawPose) {
|
|
5653
|
+
const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
|
|
5654
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
5655
|
+
return {
|
|
5656
|
+
objectId: rawPose.objectId,
|
|
5657
|
+
placement,
|
|
5658
|
+
inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
|
|
5659
|
+
outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
|
|
5660
|
+
epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
|
|
5661
|
+
referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
|
|
5662
|
+
};
|
|
5663
|
+
}
|
|
5664
|
+
function collectDraftFields(fields, _mode = "object") {
|
|
4653
5665
|
const grouped = /* @__PURE__ */ new Map();
|
|
4654
5666
|
for (const field of fields) {
|
|
4655
5667
|
const spec = getDraftObjectFieldSpec(field.key);
|
|
4656
|
-
if (!spec) {
|
|
5668
|
+
if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
|
|
4657
5669
|
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
4658
5670
|
}
|
|
4659
|
-
if (!spec
|
|
5671
|
+
if (!spec?.allowRepeat && grouped.has(field.key)) {
|
|
4660
5672
|
throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
|
|
4661
5673
|
}
|
|
4662
5674
|
const existing = grouped.get(field.key) ?? [];
|
|
@@ -4665,7 +5677,7 @@
|
|
|
4665
5677
|
}
|
|
4666
5678
|
return grouped;
|
|
4667
5679
|
}
|
|
4668
|
-
function
|
|
5680
|
+
function extractPlacementFromFieldMap(fieldMap) {
|
|
4669
5681
|
const orbitField = fieldMap.get("orbit")?.[0];
|
|
4670
5682
|
const atField = fieldMap.get("at")?.[0];
|
|
4671
5683
|
const surfaceField = fieldMap.get("surface")?.[0];
|
|
@@ -4833,7 +5845,7 @@
|
|
|
4833
5845
|
}
|
|
4834
5846
|
}
|
|
4835
5847
|
function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
4836
|
-
if (sourceSchemaVersion
|
|
5848
|
+
if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
4837
5849
|
return;
|
|
4838
5850
|
}
|
|
4839
5851
|
diagnostics.push({
|
|
@@ -4845,6 +5857,34 @@
|
|
|
4845
5857
|
column: location.column
|
|
4846
5858
|
});
|
|
4847
5859
|
}
|
|
5860
|
+
function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
5861
|
+
if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
|
|
5862
|
+
return;
|
|
5863
|
+
}
|
|
5864
|
+
diagnostics.push({
|
|
5865
|
+
code: "parse.schema25.featureCompatibility",
|
|
5866
|
+
severity: "warning",
|
|
5867
|
+
source: "parse",
|
|
5868
|
+
message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
5869
|
+
line: location.line,
|
|
5870
|
+
column: location.column
|
|
5871
|
+
});
|
|
5872
|
+
}
|
|
5873
|
+
function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
|
|
5874
|
+
return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
|
|
5875
|
+
}
|
|
5876
|
+
function schemaVersionRank(version) {
|
|
5877
|
+
switch (version) {
|
|
5878
|
+
case "2.0-draft":
|
|
5879
|
+
return 0;
|
|
5880
|
+
case "2.0":
|
|
5881
|
+
return 1;
|
|
5882
|
+
case "2.1":
|
|
5883
|
+
return 2;
|
|
5884
|
+
case "2.5":
|
|
5885
|
+
return 3;
|
|
5886
|
+
}
|
|
5887
|
+
}
|
|
4848
5888
|
function preprocessAtlasSource(source) {
|
|
4849
5889
|
const chars = [...source];
|
|
4850
5890
|
const comments = [];
|
|
@@ -4932,7 +5972,7 @@
|
|
|
4932
5972
|
}
|
|
4933
5973
|
|
|
4934
5974
|
// packages/core/dist/atlas-edit.js
|
|
4935
|
-
function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.
|
|
5975
|
+
function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.5") {
|
|
4936
5976
|
return {
|
|
4937
5977
|
format: "worldorbit",
|
|
4938
5978
|
version,
|
|
@@ -4958,6 +5998,7 @@
|
|
|
4958
5998
|
},
|
|
4959
5999
|
groups: [],
|
|
4960
6000
|
relations: [],
|
|
6001
|
+
events: [],
|
|
4961
6002
|
objects: [],
|
|
4962
6003
|
diagnostics: []
|
|
4963
6004
|
};
|
|
@@ -4984,6 +6025,12 @@
|
|
|
4984
6025
|
for (const relation of [...document.relations].sort(compareIdLike2)) {
|
|
4985
6026
|
paths.push({ kind: "relation", id: relation.id });
|
|
4986
6027
|
}
|
|
6028
|
+
for (const event of [...document.events].sort(compareIdLike2)) {
|
|
6029
|
+
paths.push({ kind: "event", id: event.id });
|
|
6030
|
+
for (const pose of [...event.positions].sort(comparePoseObjectId2)) {
|
|
6031
|
+
paths.push({ kind: "event-pose", id: event.id, key: pose.objectId });
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
4987
6034
|
for (const object of [...document.objects].sort(compareIdLike2)) {
|
|
4988
6035
|
paths.push({ kind: "object", id: object.id });
|
|
4989
6036
|
}
|
|
@@ -4999,6 +6046,10 @@
|
|
|
4999
6046
|
return path.key ? document.system?.atlasMetadata[path.key] ?? null : null;
|
|
5000
6047
|
case "group":
|
|
5001
6048
|
return path.id ? findGroup(document, path.id) : null;
|
|
6049
|
+
case "event":
|
|
6050
|
+
return path.id ? findEvent(document, path.id) : null;
|
|
6051
|
+
case "event-pose":
|
|
6052
|
+
return path.id && path.key ? findEventPose(document, path.id, path.key) : null;
|
|
5002
6053
|
case "object":
|
|
5003
6054
|
return path.id ? findObject(document, path.id) : null;
|
|
5004
6055
|
case "viewpoint":
|
|
@@ -5038,6 +6089,18 @@
|
|
|
5038
6089
|
}
|
|
5039
6090
|
upsertById(next.groups, value);
|
|
5040
6091
|
return next;
|
|
6092
|
+
case "event":
|
|
6093
|
+
if (!path.id) {
|
|
6094
|
+
throw new Error('Event updates require an "id" value.');
|
|
6095
|
+
}
|
|
6096
|
+
upsertById(next.events, value);
|
|
6097
|
+
return next;
|
|
6098
|
+
case "event-pose":
|
|
6099
|
+
if (!path.id || !path.key) {
|
|
6100
|
+
throw new Error('Event pose updates require an event "id" and pose "key" value.');
|
|
6101
|
+
}
|
|
6102
|
+
upsertEventPose(next.events, path.id, value);
|
|
6103
|
+
return next;
|
|
5041
6104
|
case "object":
|
|
5042
6105
|
if (!path.id) {
|
|
5043
6106
|
throw new Error('Object updates require an "id" value.');
|
|
@@ -5086,6 +6149,19 @@
|
|
|
5086
6149
|
next.groups = next.groups.filter((group) => group.id !== path.id);
|
|
5087
6150
|
}
|
|
5088
6151
|
return next;
|
|
6152
|
+
case "event":
|
|
6153
|
+
if (path.id) {
|
|
6154
|
+
next.events = next.events.filter((event) => event.id !== path.id);
|
|
6155
|
+
}
|
|
6156
|
+
return next;
|
|
6157
|
+
case "event-pose":
|
|
6158
|
+
if (path.id && path.key) {
|
|
6159
|
+
const event = findEvent(next, path.id);
|
|
6160
|
+
if (event) {
|
|
6161
|
+
event.positions = event.positions.filter((pose) => pose.objectId !== path.key);
|
|
6162
|
+
}
|
|
6163
|
+
}
|
|
6164
|
+
return next;
|
|
5089
6165
|
case "viewpoint":
|
|
5090
6166
|
if (path.id) {
|
|
5091
6167
|
system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
|
|
@@ -5154,6 +6230,22 @@
|
|
|
5154
6230
|
};
|
|
5155
6231
|
}
|
|
5156
6232
|
}
|
|
6233
|
+
if (diagnostic.field?.startsWith("event.")) {
|
|
6234
|
+
const parts = diagnostic.field.split(".");
|
|
6235
|
+
if (parts[1] && findEvent(document, parts[1])) {
|
|
6236
|
+
if (parts[2] === "pose" && parts[3] && findEventPose(document, parts[1], parts[3])) {
|
|
6237
|
+
return {
|
|
6238
|
+
kind: "event-pose",
|
|
6239
|
+
id: parts[1],
|
|
6240
|
+
key: parts[3]
|
|
6241
|
+
};
|
|
6242
|
+
}
|
|
6243
|
+
return {
|
|
6244
|
+
kind: "event",
|
|
6245
|
+
id: parts[1]
|
|
6246
|
+
};
|
|
6247
|
+
}
|
|
6248
|
+
}
|
|
5157
6249
|
if (diagnostic.field && diagnostic.field in ensureSystem(document).atlasMetadata) {
|
|
5158
6250
|
return {
|
|
5159
6251
|
kind: "metadata",
|
|
@@ -5185,6 +6277,12 @@
|
|
|
5185
6277
|
function findRelation(document, relationId) {
|
|
5186
6278
|
return document.relations.find((relation) => relation.id === relationId) ?? null;
|
|
5187
6279
|
}
|
|
6280
|
+
function findEvent(document, eventId) {
|
|
6281
|
+
return document.events.find((event) => event.id === eventId) ?? null;
|
|
6282
|
+
}
|
|
6283
|
+
function findEventPose(document, eventId, objectId) {
|
|
6284
|
+
return findEvent(document, eventId)?.positions.find((pose) => pose.objectId === objectId) ?? null;
|
|
6285
|
+
}
|
|
5188
6286
|
function findViewpoint(system, viewpointId) {
|
|
5189
6287
|
return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
|
|
5190
6288
|
}
|
|
@@ -5200,13 +6298,30 @@
|
|
|
5200
6298
|
}
|
|
5201
6299
|
items[index] = value;
|
|
5202
6300
|
}
|
|
6301
|
+
function upsertEventPose(events, eventId, value) {
|
|
6302
|
+
const event = events.find((entry) => entry.id === eventId);
|
|
6303
|
+
if (!event) {
|
|
6304
|
+
throw new Error(`Unknown event "${eventId}" for pose update.`);
|
|
6305
|
+
}
|
|
6306
|
+
const index = event.positions.findIndex((entry) => entry.objectId === value.objectId);
|
|
6307
|
+
if (index === -1) {
|
|
6308
|
+
event.positions.push(value);
|
|
6309
|
+
event.positions.sort(comparePoseObjectId2);
|
|
6310
|
+
return;
|
|
6311
|
+
}
|
|
6312
|
+
event.positions[index] = value;
|
|
6313
|
+
}
|
|
5203
6314
|
function compareIdLike2(left, right) {
|
|
5204
6315
|
return left.id.localeCompare(right.id);
|
|
5205
6316
|
}
|
|
6317
|
+
function comparePoseObjectId2(left, right) {
|
|
6318
|
+
return left.objectId.localeCompare(right.objectId);
|
|
6319
|
+
}
|
|
5206
6320
|
|
|
5207
6321
|
// packages/core/dist/load.js
|
|
5208
|
-
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
|
|
6322
|
+
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
|
|
5209
6323
|
var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
|
|
6324
|
+
var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
|
|
5210
6325
|
var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
|
|
5211
6326
|
function detectWorldOrbitSchemaVersion(source) {
|
|
5212
6327
|
for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
|
|
@@ -5220,6 +6335,9 @@
|
|
|
5220
6335
|
if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
|
|
5221
6336
|
return "2.1";
|
|
5222
6337
|
}
|
|
6338
|
+
if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
|
|
6339
|
+
return "2.5";
|
|
6340
|
+
}
|
|
5223
6341
|
if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
|
|
5224
6342
|
return "2.0";
|
|
5225
6343
|
}
|
|
@@ -5280,7 +6398,7 @@
|
|
|
5280
6398
|
}
|
|
5281
6399
|
function loadWorldOrbitSourceWithDiagnostics(source) {
|
|
5282
6400
|
const schemaVersion = detectWorldOrbitSchemaVersion(source);
|
|
5283
|
-
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
|
|
6401
|
+
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
|
|
5284
6402
|
return loadAtlasSourceWithDiagnostics(source, schemaVersion);
|
|
5285
6403
|
}
|
|
5286
6404
|
let ast;
|