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
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
background: true,
|
|
6
6
|
guides: true,
|
|
7
7
|
relations: true,
|
|
8
|
+
events: true,
|
|
8
9
|
orbits: true,
|
|
9
10
|
objects: true,
|
|
10
11
|
labels: true,
|
|
@@ -154,14 +155,17 @@
|
|
|
154
155
|
}
|
|
155
156
|
function createAtlasStateSnapshot(viewerState, renderOptions, filter, viewpointId) {
|
|
156
157
|
return {
|
|
157
|
-
version: "2.
|
|
158
|
+
version: "2.5",
|
|
158
159
|
viewpointId,
|
|
160
|
+
activeEventId: renderOptions.activeEventId ?? null,
|
|
159
161
|
viewerState: { ...viewerState },
|
|
160
162
|
renderOptions: {
|
|
161
163
|
preset: renderOptions.preset,
|
|
162
164
|
projection: renderOptions.projection,
|
|
165
|
+
camera: renderOptions.camera ? { ...renderOptions.camera } : null,
|
|
163
166
|
layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
|
|
164
|
-
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0
|
|
167
|
+
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
|
|
168
|
+
activeEventId: renderOptions.activeEventId ?? null
|
|
165
169
|
},
|
|
166
170
|
filter: normalizeViewerFilter(filter)
|
|
167
171
|
};
|
|
@@ -172,8 +176,9 @@
|
|
|
172
176
|
function deserializeViewerAtlasState(serialized) {
|
|
173
177
|
const raw = JSON.parse(decodeURIComponent(serialized));
|
|
174
178
|
return {
|
|
175
|
-
version: "2.0",
|
|
179
|
+
version: raw.version === "2.0" ? "2.0" : "2.5",
|
|
176
180
|
viewpointId: raw.viewpointId ?? null,
|
|
181
|
+
activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null,
|
|
177
182
|
viewerState: {
|
|
178
183
|
scale: raw.viewerState?.scale ?? 1,
|
|
179
184
|
rotationDeg: raw.viewerState?.rotationDeg ?? 0,
|
|
@@ -184,8 +189,10 @@
|
|
|
184
189
|
renderOptions: {
|
|
185
190
|
preset: raw.renderOptions?.preset,
|
|
186
191
|
projection: raw.renderOptions?.projection,
|
|
192
|
+
camera: raw.renderOptions?.camera ? { ...raw.renderOptions.camera } : null,
|
|
187
193
|
layers: raw.renderOptions?.layers ? { ...raw.renderOptions.layers } : void 0,
|
|
188
|
-
scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0
|
|
194
|
+
scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0,
|
|
195
|
+
activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null
|
|
189
196
|
},
|
|
190
197
|
filter: normalizeViewerFilter(raw.filter ?? null)
|
|
191
198
|
};
|
|
@@ -200,8 +207,10 @@
|
|
|
200
207
|
viewerState: { ...atlasState.viewerState },
|
|
201
208
|
renderOptions: {
|
|
202
209
|
...atlasState.renderOptions,
|
|
210
|
+
camera: atlasState.renderOptions.camera ? { ...atlasState.renderOptions.camera } : null,
|
|
203
211
|
layers: atlasState.renderOptions.layers ? { ...atlasState.renderOptions.layers } : void 0,
|
|
204
|
-
scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0
|
|
212
|
+
scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0,
|
|
213
|
+
activeEventId: atlasState.renderOptions.activeEventId ?? null
|
|
205
214
|
},
|
|
206
215
|
filter: atlasState.filter ? { ...atlasState.filter } : null
|
|
207
216
|
}
|
|
@@ -219,6 +228,7 @@
|
|
|
219
228
|
background: viewpoint.layers.background,
|
|
220
229
|
guides: viewpoint.layers.guides,
|
|
221
230
|
relations: viewpoint.layers.relations,
|
|
231
|
+
events: viewpoint.layers.events,
|
|
222
232
|
orbits: viewpoint.layers["orbits-front"] === void 0 && viewpoint.layers["orbits-back"] === void 0 ? void 0 : viewpoint.layers["orbits-front"] !== false || viewpoint.layers["orbits-back"] !== false,
|
|
223
233
|
objects: viewpoint.layers.objects,
|
|
224
234
|
labels: viewpoint.layers.labels,
|
|
@@ -866,6 +876,7 @@
|
|
|
866
876
|
system,
|
|
867
877
|
groups: [],
|
|
868
878
|
relations: [],
|
|
879
|
+
events: [],
|
|
869
880
|
objects
|
|
870
881
|
};
|
|
871
882
|
}
|
|
@@ -1245,12 +1256,16 @@
|
|
|
1245
1256
|
const height = frame.height;
|
|
1246
1257
|
const padding = frame.padding;
|
|
1247
1258
|
const layoutPreset = resolveLayoutPreset(document2);
|
|
1248
|
-
const
|
|
1259
|
+
const schemaProjection = resolveProjection(document2, options.projection);
|
|
1260
|
+
const camera = normalizeViewCamera(options.camera ?? null);
|
|
1261
|
+
const renderProjection = resolveRenderProjection(schemaProjection, camera);
|
|
1249
1262
|
const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
|
|
1250
1263
|
const spacingFactor = layoutPresetSpacing(layoutPreset);
|
|
1251
1264
|
const systemId = document2.system?.id ?? null;
|
|
1252
|
-
const
|
|
1253
|
-
const
|
|
1265
|
+
const activeEventId = options.activeEventId ?? null;
|
|
1266
|
+
const effectiveObjects = createEffectiveObjects(document2.objects, document2.events ?? [], activeEventId);
|
|
1267
|
+
const objectMap = new Map(effectiveObjects.map((object) => [object.id, object]));
|
|
1268
|
+
const relationships = buildSceneRelationships(effectiveObjects, objectMap);
|
|
1254
1269
|
const positions = /* @__PURE__ */ new Map();
|
|
1255
1270
|
const orbitDrafts = [];
|
|
1256
1271
|
const leaderDrafts = [];
|
|
@@ -1259,7 +1274,7 @@
|
|
|
1259
1274
|
const atObjects = [];
|
|
1260
1275
|
const surfaceChildren = /* @__PURE__ */ new Map();
|
|
1261
1276
|
const orbitChildren = /* @__PURE__ */ new Map();
|
|
1262
|
-
for (const object of
|
|
1277
|
+
for (const object of effectiveObjects) {
|
|
1263
1278
|
const placement = object.placement;
|
|
1264
1279
|
if (!placement) {
|
|
1265
1280
|
rootObjects.push(object);
|
|
@@ -1286,7 +1301,7 @@
|
|
|
1286
1301
|
surfaceChildren,
|
|
1287
1302
|
objectMap,
|
|
1288
1303
|
spacingFactor,
|
|
1289
|
-
projection,
|
|
1304
|
+
projection: renderProjection,
|
|
1290
1305
|
scaleModel
|
|
1291
1306
|
};
|
|
1292
1307
|
const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
|
|
@@ -1298,7 +1313,7 @@
|
|
|
1298
1313
|
const rootRingRadius = Math.min(width, height) * 0.28 * spacingFactor * scaleModel.orbitDistanceMultiplier;
|
|
1299
1314
|
secondaryRoots.forEach((object, index) => {
|
|
1300
1315
|
const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
|
|
1301
|
-
const offset = projectPolarOffset(angle, rootRingRadius,
|
|
1316
|
+
const offset = projectPolarOffset(angle, rootRingRadius, renderProjection, 1);
|
|
1302
1317
|
placeObject(object, centerX + offset.x, centerY + offset.y, 0, positions, orbitDrafts, leaderDrafts, context);
|
|
1303
1318
|
});
|
|
1304
1319
|
}
|
|
@@ -1354,38 +1369,48 @@
|
|
|
1354
1369
|
const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
|
|
1355
1370
|
const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
|
|
1356
1371
|
const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
|
|
1357
|
-
const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
|
|
1372
|
+
const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
|
|
1358
1373
|
const relations = createSceneRelations(document2, objects);
|
|
1359
|
-
const
|
|
1360
|
-
const
|
|
1374
|
+
const events = createSceneEvents(document2.events ?? [], objects, activeEventId);
|
|
1375
|
+
const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
|
|
1376
|
+
const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
|
|
1361
1377
|
const semanticGroups = createSceneSemanticGroups(document2, objects);
|
|
1362
|
-
const viewpoints = createSceneViewpoints(document2,
|
|
1363
|
-
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
|
|
1378
|
+
const viewpoints = createSceneViewpoints(document2, schemaProjection, frame.preset, relationships, objectMap);
|
|
1379
|
+
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
|
|
1364
1380
|
return {
|
|
1365
1381
|
width,
|
|
1366
1382
|
height,
|
|
1367
1383
|
padding,
|
|
1368
1384
|
renderPreset: frame.preset,
|
|
1369
|
-
projection,
|
|
1385
|
+
projection: schemaProjection,
|
|
1386
|
+
renderProjection,
|
|
1387
|
+
camera,
|
|
1370
1388
|
scaleModel,
|
|
1371
1389
|
title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1372
|
-
subtitle:
|
|
1390
|
+
subtitle: buildSceneSubtitle(schemaProjection, renderProjection, layoutPreset, camera),
|
|
1373
1391
|
systemId,
|
|
1374
|
-
viewMode:
|
|
1392
|
+
viewMode: schemaProjection,
|
|
1375
1393
|
layoutPreset,
|
|
1376
1394
|
metadata: {
|
|
1377
1395
|
format: document2.format,
|
|
1378
1396
|
version: document2.version,
|
|
1379
|
-
view:
|
|
1397
|
+
view: schemaProjection,
|
|
1398
|
+
renderProjection,
|
|
1380
1399
|
scale: String(document2.system?.properties.scale ?? layoutPreset),
|
|
1381
1400
|
units: String(document2.system?.properties.units ?? "mixed"),
|
|
1382
|
-
preset: frame.preset ?? "custom"
|
|
1401
|
+
preset: frame.preset ?? "custom",
|
|
1402
|
+
...camera?.azimuth !== null ? { "camera.azimuth": String(camera?.azimuth) } : {},
|
|
1403
|
+
...camera?.elevation !== null ? { "camera.elevation": String(camera?.elevation) } : {},
|
|
1404
|
+
...camera?.roll !== null ? { "camera.roll": String(camera?.roll) } : {},
|
|
1405
|
+
...camera?.distance !== null ? { "camera.distance": String(camera?.distance) } : {}
|
|
1383
1406
|
},
|
|
1384
1407
|
contentBounds,
|
|
1385
1408
|
layers,
|
|
1386
1409
|
groups,
|
|
1387
1410
|
semanticGroups,
|
|
1388
1411
|
viewpoints,
|
|
1412
|
+
events,
|
|
1413
|
+
activeEventId,
|
|
1389
1414
|
objects,
|
|
1390
1415
|
orbitVisuals,
|
|
1391
1416
|
relations,
|
|
@@ -1404,6 +1429,56 @@
|
|
|
1404
1429
|
y: center.y + dx * sin + dy * cos
|
|
1405
1430
|
};
|
|
1406
1431
|
}
|
|
1432
|
+
function createEffectiveObjects(objects, events, activeEventId) {
|
|
1433
|
+
const cloned = objects.map((object) => structuredClone(object));
|
|
1434
|
+
if (!activeEventId) {
|
|
1435
|
+
return cloned;
|
|
1436
|
+
}
|
|
1437
|
+
const activeEvent = events.find((event) => event.id === activeEventId);
|
|
1438
|
+
if (!activeEvent) {
|
|
1439
|
+
return cloned;
|
|
1440
|
+
}
|
|
1441
|
+
const objectMap = new Map(cloned.map((object) => [object.id, object]));
|
|
1442
|
+
const referencedIds = /* @__PURE__ */ new Set([
|
|
1443
|
+
...activeEvent.targetObjectId ? [activeEvent.targetObjectId] : [],
|
|
1444
|
+
...activeEvent.participantObjectIds,
|
|
1445
|
+
...activeEvent.positions.map((pose) => pose.objectId)
|
|
1446
|
+
]);
|
|
1447
|
+
for (const objectId of referencedIds) {
|
|
1448
|
+
const object = objectMap.get(objectId);
|
|
1449
|
+
if (!object) {
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
if (activeEvent.epoch) {
|
|
1453
|
+
object.epoch = activeEvent.epoch;
|
|
1454
|
+
}
|
|
1455
|
+
if (activeEvent.referencePlane) {
|
|
1456
|
+
object.referencePlane = activeEvent.referencePlane;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
for (const pose of activeEvent.positions) {
|
|
1460
|
+
const object = objectMap.get(pose.objectId);
|
|
1461
|
+
if (!object) {
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
if (pose.placement) {
|
|
1465
|
+
object.placement = structuredClone(pose.placement);
|
|
1466
|
+
}
|
|
1467
|
+
if (pose.inner) {
|
|
1468
|
+
object.properties.inner = { ...pose.inner };
|
|
1469
|
+
}
|
|
1470
|
+
if (pose.outer) {
|
|
1471
|
+
object.properties.outer = { ...pose.outer };
|
|
1472
|
+
}
|
|
1473
|
+
if (pose.epoch) {
|
|
1474
|
+
object.epoch = pose.epoch;
|
|
1475
|
+
}
|
|
1476
|
+
if (pose.referencePlane) {
|
|
1477
|
+
object.referencePlane = pose.referencePlane;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return cloned;
|
|
1481
|
+
}
|
|
1407
1482
|
function resolveLayoutPreset(document2) {
|
|
1408
1483
|
const rawScale = String(document2.system?.properties.scale ?? "balanced").toLowerCase();
|
|
1409
1484
|
switch (rawScale) {
|
|
@@ -1440,10 +1515,59 @@
|
|
|
1440
1515
|
}
|
|
1441
1516
|
}
|
|
1442
1517
|
function resolveProjection(document2, projection) {
|
|
1443
|
-
if (projection === "topdown" || projection === "isometric") {
|
|
1518
|
+
if (projection === "topdown" || projection === "isometric" || projection === "orthographic" || projection === "perspective") {
|
|
1444
1519
|
return projection;
|
|
1445
1520
|
}
|
|
1446
|
-
|
|
1521
|
+
const documentView = String(document2.system?.properties.view ?? "topdown").toLowerCase();
|
|
1522
|
+
return parseViewProjection(documentView) ?? "topdown";
|
|
1523
|
+
}
|
|
1524
|
+
function resolveRenderProjection(projection, camera) {
|
|
1525
|
+
switch (projection) {
|
|
1526
|
+
case "topdown":
|
|
1527
|
+
return "topdown";
|
|
1528
|
+
case "isometric":
|
|
1529
|
+
return "isometric";
|
|
1530
|
+
case "orthographic":
|
|
1531
|
+
return camera && (camera.azimuth !== null || camera.elevation !== null || camera.roll !== null) ? "isometric" : "topdown";
|
|
1532
|
+
case "perspective":
|
|
1533
|
+
return "isometric";
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
function normalizeViewCamera(camera) {
|
|
1537
|
+
if (!camera) {
|
|
1538
|
+
return null;
|
|
1539
|
+
}
|
|
1540
|
+
const normalized = {
|
|
1541
|
+
azimuth: normalizeFiniteCameraValue(camera.azimuth),
|
|
1542
|
+
elevation: normalizeFiniteCameraValue(camera.elevation),
|
|
1543
|
+
roll: normalizeFiniteCameraValue(camera.roll),
|
|
1544
|
+
distance: normalizePositiveCameraDistance(camera.distance)
|
|
1545
|
+
};
|
|
1546
|
+
return normalized.azimuth !== null || normalized.elevation !== null || normalized.roll !== null || normalized.distance !== null ? normalized : null;
|
|
1547
|
+
}
|
|
1548
|
+
function normalizeFiniteCameraValue(value) {
|
|
1549
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1550
|
+
}
|
|
1551
|
+
function normalizePositiveCameraDistance(value) {
|
|
1552
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
1553
|
+
}
|
|
1554
|
+
function buildSceneSubtitle(projection, renderProjection, layoutPreset, camera) {
|
|
1555
|
+
const parts = [`${capitalizeLabel(projection)} view`, `${capitalizeLabel(layoutPreset)} layout`];
|
|
1556
|
+
if (projection !== renderProjection) {
|
|
1557
|
+
parts.push(`2D ${renderProjection} fallback`);
|
|
1558
|
+
}
|
|
1559
|
+
if (camera) {
|
|
1560
|
+
const cameraParts = [
|
|
1561
|
+
camera.azimuth !== null ? `az ${camera.azimuth}` : null,
|
|
1562
|
+
camera.elevation !== null ? `el ${camera.elevation}` : null,
|
|
1563
|
+
camera.roll !== null ? `roll ${camera.roll}` : null,
|
|
1564
|
+
camera.distance !== null ? `dist ${camera.distance}` : null
|
|
1565
|
+
].filter(Boolean);
|
|
1566
|
+
if (cameraParts.length > 0) {
|
|
1567
|
+
parts.push(`camera ${cameraParts.join(" / ")}`);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
return parts.join(" - ");
|
|
1447
1571
|
}
|
|
1448
1572
|
function resolveScaleModel(layoutPreset, overrides) {
|
|
1449
1573
|
const defaults = defaultScaleModel(layoutPreset);
|
|
@@ -1559,24 +1683,14 @@
|
|
|
1559
1683
|
hidden: draft.object.properties.hidden === true
|
|
1560
1684
|
};
|
|
1561
1685
|
}
|
|
1562
|
-
function createSceneLabels(objects, sceneHeight, labelMultiplier) {
|
|
1686
|
+
function createSceneLabels(objects, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1563
1687
|
const labels = [];
|
|
1564
1688
|
const occupied = [];
|
|
1565
|
-
const
|
|
1689
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1690
|
+
const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort(compareLabelPlacementOrder);
|
|
1566
1691
|
for (const object of visibleObjects) {
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
let labelY = object.y + direction * (object.radius + 18 * labelMultiplier);
|
|
1570
|
-
let secondaryY = labelY + direction * (16 * labelMultiplier);
|
|
1571
|
-
let bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1572
|
-
let attempts = 0;
|
|
1573
|
-
while (occupied.some((entry) => rectsOverlap(entry, bounds)) && attempts < 10) {
|
|
1574
|
-
labelY += direction * 14 * labelMultiplier;
|
|
1575
|
-
secondaryY += direction * 14 * labelMultiplier;
|
|
1576
|
-
bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1577
|
-
attempts += 1;
|
|
1578
|
-
}
|
|
1579
|
-
occupied.push(bounds);
|
|
1692
|
+
const placement = selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) ?? createLabelPlacement(object, defaultVerticalDirection(object, objectMap.get(object.parentId ?? "") ?? null, sceneHeight), 0, labelMultiplier);
|
|
1693
|
+
occupied.push(createLabelRect(object, placement, labelMultiplier));
|
|
1580
1694
|
labels.push({
|
|
1581
1695
|
renderId: `${object.renderId}-label`,
|
|
1582
1696
|
objectId: object.objectId,
|
|
@@ -1585,17 +1699,128 @@
|
|
|
1585
1699
|
semanticGroupIds: [...object.semanticGroupIds],
|
|
1586
1700
|
label: object.label,
|
|
1587
1701
|
secondaryLabel: object.secondaryLabel,
|
|
1588
|
-
x:
|
|
1589
|
-
y: labelY,
|
|
1590
|
-
secondaryY,
|
|
1591
|
-
textAnchor:
|
|
1592
|
-
direction: direction
|
|
1702
|
+
x: placement.x,
|
|
1703
|
+
y: placement.labelY,
|
|
1704
|
+
secondaryY: placement.secondaryY,
|
|
1705
|
+
textAnchor: placement.textAnchor,
|
|
1706
|
+
direction: placement.direction,
|
|
1593
1707
|
hidden: object.hidden
|
|
1594
1708
|
});
|
|
1595
1709
|
}
|
|
1596
1710
|
return labels;
|
|
1597
1711
|
}
|
|
1598
|
-
function
|
|
1712
|
+
function compareLabelPlacementOrder(left, right) {
|
|
1713
|
+
const priorityDiff = labelPlacementPriority(left) - labelPlacementPriority(right);
|
|
1714
|
+
if (priorityDiff !== 0) {
|
|
1715
|
+
return priorityDiff;
|
|
1716
|
+
}
|
|
1717
|
+
const renderPriorityDiff = (right.object.renderHints?.renderPriority ?? 0) - (left.object.renderHints?.renderPriority ?? 0);
|
|
1718
|
+
if (renderPriorityDiff !== 0) {
|
|
1719
|
+
return renderPriorityDiff;
|
|
1720
|
+
}
|
|
1721
|
+
return left.sortKey - right.sortKey;
|
|
1722
|
+
}
|
|
1723
|
+
function labelPlacementPriority(object) {
|
|
1724
|
+
switch (object.object.type) {
|
|
1725
|
+
case "star":
|
|
1726
|
+
return 0;
|
|
1727
|
+
case "planet":
|
|
1728
|
+
return 1;
|
|
1729
|
+
case "moon":
|
|
1730
|
+
return 2;
|
|
1731
|
+
case "belt":
|
|
1732
|
+
case "ring":
|
|
1733
|
+
return 3;
|
|
1734
|
+
case "asteroid":
|
|
1735
|
+
case "comet":
|
|
1736
|
+
return 4;
|
|
1737
|
+
case "structure":
|
|
1738
|
+
case "phenomenon":
|
|
1739
|
+
return 5;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1743
|
+
for (const direction of preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight)) {
|
|
1744
|
+
const maxAttempts = direction === "left" || direction === "right" ? 4 : 6;
|
|
1745
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
|
|
1746
|
+
const placement = createLabelPlacement(object, direction, attempt, labelMultiplier);
|
|
1747
|
+
const rect = createLabelRect(object, placement, labelMultiplier);
|
|
1748
|
+
if (!occupied.some((entry) => rectsOverlap(entry, rect))) {
|
|
1749
|
+
return placement;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
return null;
|
|
1754
|
+
}
|
|
1755
|
+
function preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight) {
|
|
1756
|
+
const parent = object.parentId ? objectMap.get(object.parentId) ?? null : null;
|
|
1757
|
+
const vertical = defaultVerticalDirection(object, parent, sceneHeight);
|
|
1758
|
+
const oppositeVertical = vertical === "below" ? "above" : "below";
|
|
1759
|
+
const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
|
|
1760
|
+
const oppositeHorizontal = horizontal === "right" ? "left" : "right";
|
|
1761
|
+
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";
|
|
1762
|
+
return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
|
|
1763
|
+
}
|
|
1764
|
+
function defaultVerticalDirection(object, parent, sceneHeight) {
|
|
1765
|
+
if (parent && Math.abs(object.y - parent.y) > 6) {
|
|
1766
|
+
return object.y >= parent.y ? "below" : "above";
|
|
1767
|
+
}
|
|
1768
|
+
return object.y > sceneHeight * 0.62 ? "above" : "below";
|
|
1769
|
+
}
|
|
1770
|
+
function defaultHorizontalDirection(object, parent, sceneWidth) {
|
|
1771
|
+
if (parent && Math.abs(object.x - parent.x) > 6) {
|
|
1772
|
+
return object.x >= parent.x ? "right" : "left";
|
|
1773
|
+
}
|
|
1774
|
+
return object.x >= sceneWidth / 2 ? "right" : "left";
|
|
1775
|
+
}
|
|
1776
|
+
function createLabelPlacement(object, direction, attempt, labelMultiplier) {
|
|
1777
|
+
const step = 14 * labelMultiplier;
|
|
1778
|
+
switch (direction) {
|
|
1779
|
+
case "above": {
|
|
1780
|
+
const labelY = object.y - (object.radius + 18 * labelMultiplier + attempt * step);
|
|
1781
|
+
return {
|
|
1782
|
+
x: object.x,
|
|
1783
|
+
labelY,
|
|
1784
|
+
secondaryY: labelY - 16 * labelMultiplier,
|
|
1785
|
+
textAnchor: "middle",
|
|
1786
|
+
direction
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
case "below": {
|
|
1790
|
+
const labelY = object.y + object.radius + 18 * labelMultiplier + attempt * step;
|
|
1791
|
+
return {
|
|
1792
|
+
x: object.x,
|
|
1793
|
+
labelY,
|
|
1794
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1795
|
+
textAnchor: "middle",
|
|
1796
|
+
direction
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
case "left": {
|
|
1800
|
+
const x = object.x - (object.visualRadius + 16 * labelMultiplier + attempt * step);
|
|
1801
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1802
|
+
return {
|
|
1803
|
+
x,
|
|
1804
|
+
labelY,
|
|
1805
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1806
|
+
textAnchor: "end",
|
|
1807
|
+
direction
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
case "right": {
|
|
1811
|
+
const x = object.x + object.visualRadius + 16 * labelMultiplier + attempt * step;
|
|
1812
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1813
|
+
return {
|
|
1814
|
+
x,
|
|
1815
|
+
labelY,
|
|
1816
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1817
|
+
textAnchor: "start",
|
|
1818
|
+
direction
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
|
|
1599
1824
|
const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
|
|
1600
1825
|
const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
|
|
1601
1826
|
return [
|
|
@@ -1610,6 +1835,10 @@
|
|
|
1610
1835
|
id: "relations",
|
|
1611
1836
|
renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
|
|
1612
1837
|
},
|
|
1838
|
+
{
|
|
1839
|
+
id: "events",
|
|
1840
|
+
renderIds: events.filter((event) => !event.hidden).map((event) => event.renderId)
|
|
1841
|
+
},
|
|
1613
1842
|
{
|
|
1614
1843
|
id: "objects",
|
|
1615
1844
|
renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
|
|
@@ -1621,7 +1850,7 @@
|
|
|
1621
1850
|
{ id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
|
|
1622
1851
|
];
|
|
1623
1852
|
}
|
|
1624
|
-
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
|
|
1853
|
+
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, labelMultiplier) {
|
|
1625
1854
|
const groups = /* @__PURE__ */ new Map();
|
|
1626
1855
|
const ensureGroup = (groupId) => {
|
|
1627
1856
|
if (!groupId) {
|
|
@@ -1670,7 +1899,7 @@
|
|
|
1670
1899
|
}
|
|
1671
1900
|
}
|
|
1672
1901
|
for (const group of groups.values()) {
|
|
1673
|
-
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
|
|
1902
|
+
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier);
|
|
1674
1903
|
}
|
|
1675
1904
|
return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
1676
1905
|
}
|
|
@@ -1704,6 +1933,29 @@
|
|
|
1704
1933
|
};
|
|
1705
1934
|
}).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
|
|
1706
1935
|
}
|
|
1936
|
+
function createSceneEvents(events, objects, activeEventId) {
|
|
1937
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1938
|
+
return events.map((event) => {
|
|
1939
|
+
const objectIds = [.../* @__PURE__ */ new Set([
|
|
1940
|
+
...event.targetObjectId ? [event.targetObjectId] : [],
|
|
1941
|
+
...event.participantObjectIds
|
|
1942
|
+
])];
|
|
1943
|
+
const positions = objectIds.map((objectId) => objectMap.get(objectId)).filter(Boolean);
|
|
1944
|
+
const centroidX = positions.length > 0 ? positions.reduce((sum, object) => sum + object.x, 0) / positions.length : 0;
|
|
1945
|
+
const centroidY = positions.length > 0 ? positions.reduce((sum, object) => sum + object.y, 0) / positions.length : 0;
|
|
1946
|
+
return {
|
|
1947
|
+
renderId: `${createRenderId(event.id)}-event`,
|
|
1948
|
+
eventId: event.id,
|
|
1949
|
+
event,
|
|
1950
|
+
objectIds,
|
|
1951
|
+
participantIds: [...event.participantObjectIds],
|
|
1952
|
+
targetObjectId: event.targetObjectId,
|
|
1953
|
+
x: centroidX,
|
|
1954
|
+
y: centroidY,
|
|
1955
|
+
hidden: event.hidden || positions.length === 0 || positions.every((object) => object.hidden) || activeEventId !== null && event.id !== activeEventId
|
|
1956
|
+
};
|
|
1957
|
+
}).sort((left, right) => left.event.id.localeCompare(right.event.id));
|
|
1958
|
+
}
|
|
1707
1959
|
function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
|
|
1708
1960
|
const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
|
|
1709
1961
|
const drafts = /* @__PURE__ */ new Map();
|
|
@@ -1751,13 +2003,18 @@
|
|
|
1751
2003
|
function createGeneratedOverviewViewpoint(document2, projection, preset) {
|
|
1752
2004
|
const title = document2.system?.title ?? document2.system?.properties.title;
|
|
1753
2005
|
const label = title ? `${String(title)} Overview` : "Overview";
|
|
2006
|
+
const camera = normalizeViewCamera(null);
|
|
2007
|
+
const renderProjection = resolveRenderProjection(projection, camera);
|
|
1754
2008
|
return {
|
|
1755
2009
|
id: "overview",
|
|
1756
2010
|
label,
|
|
1757
2011
|
summary: "Fit the whole system with the current atlas defaults.",
|
|
1758
2012
|
objectId: null,
|
|
1759
2013
|
selectedObjectId: null,
|
|
2014
|
+
eventIds: [],
|
|
1760
2015
|
projection,
|
|
2016
|
+
renderProjection,
|
|
2017
|
+
camera,
|
|
1761
2018
|
preset,
|
|
1762
2019
|
rotationDeg: 0,
|
|
1763
2020
|
scale: null,
|
|
@@ -1793,6 +2050,9 @@
|
|
|
1793
2050
|
draft.select = normalizedValue;
|
|
1794
2051
|
}
|
|
1795
2052
|
return;
|
|
2053
|
+
case "events":
|
|
2054
|
+
draft.eventIds = splitListValue(normalizedValue);
|
|
2055
|
+
return;
|
|
1796
2056
|
case "projection":
|
|
1797
2057
|
case "view":
|
|
1798
2058
|
draft.projection = parseViewProjection(normalizedValue) ?? projection;
|
|
@@ -1804,6 +2064,30 @@
|
|
|
1804
2064
|
case "angle":
|
|
1805
2065
|
draft.rotationDeg = parseFiniteNumber(normalizedValue) ?? draft.rotationDeg ?? 0;
|
|
1806
2066
|
return;
|
|
2067
|
+
case "camera.azimuth":
|
|
2068
|
+
draft.camera = {
|
|
2069
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
2070
|
+
azimuth: parseFiniteNumber(normalizedValue)
|
|
2071
|
+
};
|
|
2072
|
+
return;
|
|
2073
|
+
case "camera.elevation":
|
|
2074
|
+
draft.camera = {
|
|
2075
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
2076
|
+
elevation: parseFiniteNumber(normalizedValue)
|
|
2077
|
+
};
|
|
2078
|
+
return;
|
|
2079
|
+
case "camera.roll":
|
|
2080
|
+
draft.camera = {
|
|
2081
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
2082
|
+
roll: parseFiniteNumber(normalizedValue)
|
|
2083
|
+
};
|
|
2084
|
+
return;
|
|
2085
|
+
case "camera.distance":
|
|
2086
|
+
draft.camera = {
|
|
2087
|
+
...draft.camera ?? createEmptyViewCamera(),
|
|
2088
|
+
distance: parsePositiveNumber(normalizedValue)
|
|
2089
|
+
};
|
|
2090
|
+
return;
|
|
1807
2091
|
case "zoom":
|
|
1808
2092
|
case "scale":
|
|
1809
2093
|
draft.scale = parsePositiveNumber(normalizedValue);
|
|
@@ -1843,13 +2127,19 @@
|
|
|
1843
2127
|
const selectedObjectId = draft.select && objectMap.has(draft.select) ? draft.select : objectId;
|
|
1844
2128
|
const filter = normalizeViewpointFilter(draft.filter);
|
|
1845
2129
|
const label = draft.label?.trim() || humanizeIdentifier(draft.id);
|
|
2130
|
+
const resolvedProjection = draft.projection ?? projection;
|
|
2131
|
+
const camera = normalizeViewCamera(draft.camera ?? null);
|
|
2132
|
+
const renderProjection = resolveRenderProjection(resolvedProjection, camera);
|
|
1846
2133
|
return {
|
|
1847
2134
|
id: draft.id,
|
|
1848
2135
|
label,
|
|
1849
2136
|
summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
|
|
1850
2137
|
objectId,
|
|
1851
2138
|
selectedObjectId,
|
|
1852
|
-
|
|
2139
|
+
eventIds: [...new Set(draft.eventIds ?? [])],
|
|
2140
|
+
projection: resolvedProjection,
|
|
2141
|
+
renderProjection,
|
|
2142
|
+
camera,
|
|
1853
2143
|
preset: draft.preset ?? preset,
|
|
1854
2144
|
rotationDeg: draft.rotationDeg ?? 0,
|
|
1855
2145
|
scale: draft.scale ?? null,
|
|
@@ -1866,6 +2156,14 @@
|
|
|
1866
2156
|
groupIds: []
|
|
1867
2157
|
};
|
|
1868
2158
|
}
|
|
2159
|
+
function createEmptyViewCamera() {
|
|
2160
|
+
return {
|
|
2161
|
+
azimuth: null,
|
|
2162
|
+
elevation: null,
|
|
2163
|
+
roll: null,
|
|
2164
|
+
distance: null
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
1869
2167
|
function normalizeViewpointFilter(filter) {
|
|
1870
2168
|
if (!filter) {
|
|
1871
2169
|
return null;
|
|
@@ -1879,7 +2177,18 @@
|
|
|
1879
2177
|
return normalized.query || normalized.objectTypes.length > 0 || normalized.tags.length > 0 || normalized.groupIds.length > 0 ? normalized : null;
|
|
1880
2178
|
}
|
|
1881
2179
|
function parseViewProjection(value) {
|
|
1882
|
-
|
|
2180
|
+
switch (value.toLowerCase()) {
|
|
2181
|
+
case "topdown":
|
|
2182
|
+
return "topdown";
|
|
2183
|
+
case "isometric":
|
|
2184
|
+
return "isometric";
|
|
2185
|
+
case "orthographic":
|
|
2186
|
+
return "orthographic";
|
|
2187
|
+
case "perspective":
|
|
2188
|
+
return "perspective";
|
|
2189
|
+
default:
|
|
2190
|
+
return null;
|
|
2191
|
+
}
|
|
1883
2192
|
}
|
|
1884
2193
|
function parseRenderPreset(value) {
|
|
1885
2194
|
const normalized = value.toLowerCase();
|
|
@@ -1906,7 +2215,7 @@
|
|
|
1906
2215
|
next["orbits-front"] = enabled;
|
|
1907
2216
|
continue;
|
|
1908
2217
|
}
|
|
1909
|
-
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
2218
|
+
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1910
2219
|
next[rawLayer] = enabled;
|
|
1911
2220
|
}
|
|
1912
2221
|
}
|
|
@@ -1917,7 +2226,7 @@
|
|
|
1917
2226
|
}
|
|
1918
2227
|
function parseViewpointGroups(value, document2, relationships, objectMap) {
|
|
1919
2228
|
return splitListValue(value).map((entry) => {
|
|
1920
|
-
if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
|
|
2229
|
+
if (document2.schemaVersion === "2.1" || document2.schemaVersion === "2.5" || document2.groups.some((group) => group.id === entry)) {
|
|
1921
2230
|
return entry;
|
|
1922
2231
|
}
|
|
1923
2232
|
if (entry.startsWith("wo-") && entry.endsWith("-group")) {
|
|
@@ -1954,7 +2263,7 @@
|
|
|
1954
2263
|
}
|
|
1955
2264
|
return parts.join(" - ");
|
|
1956
2265
|
}
|
|
1957
|
-
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
|
|
2266
|
+
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
1958
2267
|
let minX = Number.POSITIVE_INFINITY;
|
|
1959
2268
|
let minY = Number.POSITIVE_INFINITY;
|
|
1960
2269
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -1984,7 +2293,7 @@
|
|
|
1984
2293
|
for (const label of labels) {
|
|
1985
2294
|
if (label.hidden)
|
|
1986
2295
|
continue;
|
|
1987
|
-
includeLabelBounds(label, include);
|
|
2296
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
1988
2297
|
}
|
|
1989
2298
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
1990
2299
|
return createBounds(0, 0, width, height);
|
|
@@ -2022,13 +2331,10 @@
|
|
|
2022
2331
|
include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
|
|
2023
2332
|
include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
|
|
2024
2333
|
}
|
|
2025
|
-
function includeLabelBounds(label, include) {
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2028
|
-
include(
|
|
2029
|
-
include(label.x + labelHalfWidth, label.y + 8);
|
|
2030
|
-
include(label.x - labelHalfWidth, label.secondaryY - 14);
|
|
2031
|
-
include(label.x + labelHalfWidth, label.secondaryY + 8);
|
|
2334
|
+
function includeLabelBounds(label, include, labelMultiplier) {
|
|
2335
|
+
const bounds = createLabelRectFromText(label.x, label.y, label.secondaryY, label.textAnchor, label.direction, label.label, label.secondaryLabel, labelMultiplier);
|
|
2336
|
+
include(bounds.left, bounds.top);
|
|
2337
|
+
include(bounds.right, bounds.bottom);
|
|
2032
2338
|
}
|
|
2033
2339
|
function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
|
|
2034
2340
|
if (positions.has(object.id)) {
|
|
@@ -2418,7 +2724,7 @@
|
|
|
2418
2724
|
return null;
|
|
2419
2725
|
}
|
|
2420
2726
|
}
|
|
2421
|
-
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
|
|
2727
|
+
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
2422
2728
|
let minX = Number.POSITIVE_INFINITY;
|
|
2423
2729
|
let minY = Number.POSITIVE_INFINITY;
|
|
2424
2730
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -2447,7 +2753,7 @@
|
|
|
2447
2753
|
}
|
|
2448
2754
|
for (const label of labels) {
|
|
2449
2755
|
if (!label.hidden && group.labelIds.includes(label.objectId)) {
|
|
2450
|
-
includeLabelBounds(label, include);
|
|
2756
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
2451
2757
|
}
|
|
2452
2758
|
}
|
|
2453
2759
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
@@ -2472,12 +2778,28 @@
|
|
|
2472
2778
|
}
|
|
2473
2779
|
return current.id;
|
|
2474
2780
|
}
|
|
2475
|
-
function createLabelRect(
|
|
2781
|
+
function createLabelRect(object, placement, labelMultiplier) {
|
|
2782
|
+
return createLabelRectFromText(placement.x, placement.labelY, placement.secondaryY, placement.textAnchor, placement.direction, object.label, object.secondaryLabel, labelMultiplier);
|
|
2783
|
+
}
|
|
2784
|
+
function createLabelRectFromText(x, labelY, secondaryY, textAnchor, direction, label, secondaryLabel, labelMultiplier) {
|
|
2785
|
+
const labelHalfWidth = estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier);
|
|
2786
|
+
const labelWidth = labelHalfWidth * 2;
|
|
2787
|
+
const topPadding = direction === "above" ? 18 : 12;
|
|
2788
|
+
const bottomPadding = direction === "above" ? 8 : 12;
|
|
2789
|
+
let left = x - labelHalfWidth;
|
|
2790
|
+
let right = x + labelHalfWidth;
|
|
2791
|
+
if (textAnchor === "start") {
|
|
2792
|
+
left = x;
|
|
2793
|
+
right = x + labelWidth;
|
|
2794
|
+
} else if (textAnchor === "end") {
|
|
2795
|
+
left = x - labelWidth;
|
|
2796
|
+
right = x;
|
|
2797
|
+
}
|
|
2476
2798
|
return {
|
|
2477
|
-
left
|
|
2478
|
-
right
|
|
2479
|
-
top: Math.min(labelY, secondaryY) -
|
|
2480
|
-
bottom: Math.max(labelY, secondaryY) +
|
|
2799
|
+
left,
|
|
2800
|
+
right,
|
|
2801
|
+
top: Math.min(labelY, secondaryY) - topPadding,
|
|
2802
|
+
bottom: Math.max(labelY, secondaryY) + bottomPadding
|
|
2481
2803
|
};
|
|
2482
2804
|
}
|
|
2483
2805
|
function rectsOverlap(left, right) {
|
|
@@ -2664,11 +2986,6 @@
|
|
|
2664
2986
|
function customColorFor(value) {
|
|
2665
2987
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2666
2988
|
}
|
|
2667
|
-
function estimateLabelHalfWidth(object, labelMultiplier) {
|
|
2668
|
-
const primaryWidth = object.label.length * 4.6 * labelMultiplier + 18;
|
|
2669
|
-
const secondaryWidth = object.secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
2670
|
-
return Math.max(primaryWidth, secondaryWidth, object.visualRadius + 18);
|
|
2671
|
-
}
|
|
2672
2989
|
function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
|
|
2673
2990
|
const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
|
|
2674
2991
|
const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
@@ -2685,7 +3002,7 @@
|
|
|
2685
3002
|
}
|
|
2686
3003
|
|
|
2687
3004
|
// packages/core/dist/draft.js
|
|
2688
|
-
function materializeAtlasDocument(document2) {
|
|
3005
|
+
function materializeAtlasDocument(document2, options = {}) {
|
|
2689
3006
|
const system = document2.system ? {
|
|
2690
3007
|
type: "system",
|
|
2691
3008
|
id: document2.system.id,
|
|
@@ -2696,6 +3013,8 @@
|
|
|
2696
3013
|
properties: materializeDraftSystemProperties(document2.system),
|
|
2697
3014
|
info: materializeDraftSystemInfo(document2.system)
|
|
2698
3015
|
} : null;
|
|
3016
|
+
const objects = document2.objects.map(cloneWorldOrbitObject);
|
|
3017
|
+
applyEventPoseOverrides(objects, document2.events ?? [], options.activeEventId ?? null);
|
|
2699
3018
|
return {
|
|
2700
3019
|
format: "worldorbit",
|
|
2701
3020
|
version: "1.0",
|
|
@@ -2703,7 +3022,8 @@
|
|
|
2703
3022
|
system,
|
|
2704
3023
|
groups: structuredClone(document2.groups ?? []),
|
|
2705
3024
|
relations: structuredClone(document2.relations ?? []),
|
|
2706
|
-
|
|
3025
|
+
events: document2.events.map(cloneWorldOrbitEvent),
|
|
3026
|
+
objects
|
|
2707
3027
|
};
|
|
2708
3028
|
}
|
|
2709
3029
|
function cloneWorldOrbitObject(object) {
|
|
@@ -2725,6 +3045,75 @@
|
|
|
2725
3045
|
info: { ...object.info }
|
|
2726
3046
|
};
|
|
2727
3047
|
}
|
|
3048
|
+
function cloneWorldOrbitEvent(event) {
|
|
3049
|
+
return {
|
|
3050
|
+
...event,
|
|
3051
|
+
participantObjectIds: [...event.participantObjectIds],
|
|
3052
|
+
tags: [...event.tags],
|
|
3053
|
+
positions: event.positions.map(cloneWorldOrbitEventPose)
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
function cloneWorldOrbitEventPose(pose) {
|
|
3057
|
+
return {
|
|
3058
|
+
objectId: pose.objectId,
|
|
3059
|
+
placement: clonePlacement(pose.placement),
|
|
3060
|
+
inner: pose.inner ? { ...pose.inner } : void 0,
|
|
3061
|
+
outer: pose.outer ? { ...pose.outer } : void 0,
|
|
3062
|
+
epoch: pose.epoch ?? null,
|
|
3063
|
+
referencePlane: pose.referencePlane ?? null
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
function clonePlacement(placement) {
|
|
3067
|
+
return placement ? structuredClone(placement) : null;
|
|
3068
|
+
}
|
|
3069
|
+
function applyEventPoseOverrides(objects, events, activeEventId) {
|
|
3070
|
+
if (!activeEventId) {
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
const event = events.find((entry) => entry.id === activeEventId);
|
|
3074
|
+
if (!event) {
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
const objectMap = new Map(objects.map((object) => [object.id, object]));
|
|
3078
|
+
const referencedIds = /* @__PURE__ */ new Set([
|
|
3079
|
+
...event.targetObjectId ? [event.targetObjectId] : [],
|
|
3080
|
+
...event.participantObjectIds,
|
|
3081
|
+
...event.positions.map((pose) => pose.objectId)
|
|
3082
|
+
]);
|
|
3083
|
+
for (const objectId of referencedIds) {
|
|
3084
|
+
const object = objectMap.get(objectId);
|
|
3085
|
+
if (!object) {
|
|
3086
|
+
continue;
|
|
3087
|
+
}
|
|
3088
|
+
if (event.epoch) {
|
|
3089
|
+
object.epoch = event.epoch;
|
|
3090
|
+
}
|
|
3091
|
+
if (event.referencePlane) {
|
|
3092
|
+
object.referencePlane = event.referencePlane;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
for (const pose of event.positions) {
|
|
3096
|
+
const object = objectMap.get(pose.objectId);
|
|
3097
|
+
if (!object) {
|
|
3098
|
+
continue;
|
|
3099
|
+
}
|
|
3100
|
+
if (pose.placement) {
|
|
3101
|
+
object.placement = clonePlacement(pose.placement);
|
|
3102
|
+
}
|
|
3103
|
+
if (pose.inner) {
|
|
3104
|
+
object.properties.inner = { ...pose.inner };
|
|
3105
|
+
}
|
|
3106
|
+
if (pose.outer) {
|
|
3107
|
+
object.properties.outer = { ...pose.outer };
|
|
3108
|
+
}
|
|
3109
|
+
if (pose.epoch) {
|
|
3110
|
+
object.epoch = pose.epoch;
|
|
3111
|
+
}
|
|
3112
|
+
if (pose.referencePlane) {
|
|
3113
|
+
object.referencePlane = pose.referencePlane;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
2728
3117
|
function cloneProperties(properties) {
|
|
2729
3118
|
const next = {};
|
|
2730
3119
|
for (const [key, value] of Object.entries(properties)) {
|
|
@@ -2797,6 +3186,18 @@
|
|
|
2797
3186
|
if (viewpoint.rotationDeg !== 0) {
|
|
2798
3187
|
info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
|
|
2799
3188
|
}
|
|
3189
|
+
if (viewpoint.camera?.azimuth !== null) {
|
|
3190
|
+
info2[`${prefix}.camera.azimuth`] = String(viewpoint.camera?.azimuth);
|
|
3191
|
+
}
|
|
3192
|
+
if (viewpoint.camera?.elevation !== null) {
|
|
3193
|
+
info2[`${prefix}.camera.elevation`] = String(viewpoint.camera?.elevation);
|
|
3194
|
+
}
|
|
3195
|
+
if (viewpoint.camera?.roll !== null) {
|
|
3196
|
+
info2[`${prefix}.camera.roll`] = String(viewpoint.camera?.roll);
|
|
3197
|
+
}
|
|
3198
|
+
if (viewpoint.camera?.distance !== null) {
|
|
3199
|
+
info2[`${prefix}.camera.distance`] = String(viewpoint.camera?.distance);
|
|
3200
|
+
}
|
|
2800
3201
|
const serializedLayers = serializeViewpointLayers(viewpoint.layers);
|
|
2801
3202
|
if (serializedLayers) {
|
|
2802
3203
|
info2[`${prefix}.layers`] = serializedLayers;
|
|
@@ -2813,6 +3214,9 @@
|
|
|
2813
3214
|
if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
|
|
2814
3215
|
info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
|
|
2815
3216
|
}
|
|
3217
|
+
if (viewpoint.events.length > 0) {
|
|
3218
|
+
info2[`${prefix}.events`] = viewpoint.events.join(" ");
|
|
3219
|
+
}
|
|
2816
3220
|
}
|
|
2817
3221
|
for (const annotation of system.annotations) {
|
|
2818
3222
|
const prefix = `annotation.${annotation.id}`;
|
|
@@ -2837,7 +3241,7 @@
|
|
|
2837
3241
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
2838
3242
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
2839
3243
|
}
|
|
2840
|
-
for (const key of ["background", "guides", "relations", "objects", "labels", "metadata"]) {
|
|
3244
|
+
for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
|
|
2841
3245
|
if (layers[key] !== void 0) {
|
|
2842
3246
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
2843
3247
|
}
|
|
@@ -3011,6 +3415,7 @@
|
|
|
3011
3415
|
const diagnostics = [];
|
|
3012
3416
|
const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
|
|
3013
3417
|
const groupIds = new Set(document2.groups.map((group) => group.id));
|
|
3418
|
+
const eventIds = new Set(document2.events.map((event) => event.id));
|
|
3014
3419
|
if (!document2.system) {
|
|
3015
3420
|
diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
|
|
3016
3421
|
}
|
|
@@ -3020,6 +3425,7 @@
|
|
|
3020
3425
|
["viewpoint", document2.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
|
|
3021
3426
|
["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
|
|
3022
3427
|
["relation", document2.relations.map((relation) => relation.id)],
|
|
3428
|
+
["event", document2.events.map((event) => event.id)],
|
|
3023
3429
|
["object", document2.objects.map((object) => object.id)]
|
|
3024
3430
|
]) {
|
|
3025
3431
|
for (const id of ids) {
|
|
@@ -3035,11 +3441,14 @@
|
|
|
3035
3441
|
validateRelation(relation, objectMap, diagnostics);
|
|
3036
3442
|
}
|
|
3037
3443
|
for (const viewpoint of document2.system?.viewpoints ?? []) {
|
|
3038
|
-
|
|
3444
|
+
validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap);
|
|
3039
3445
|
}
|
|
3040
3446
|
for (const object of document2.objects) {
|
|
3041
3447
|
validateObject(object, document2.system, objectMap, groupIds, diagnostics);
|
|
3042
3448
|
}
|
|
3449
|
+
for (const event of document2.events) {
|
|
3450
|
+
validateEvent(event, document2.system, objectMap, diagnostics);
|
|
3451
|
+
}
|
|
3043
3452
|
return diagnostics;
|
|
3044
3453
|
}
|
|
3045
3454
|
function validateRelation(relation, objectMap, diagnostics) {
|
|
@@ -3057,15 +3466,24 @@
|
|
|
3057
3466
|
diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
|
|
3058
3467
|
}
|
|
3059
3468
|
}
|
|
3060
|
-
function
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3469
|
+
function validateViewpoint(viewpoint, groupIds, eventIds, sourceSchemaVersion, diagnostics, objectMap) {
|
|
3470
|
+
const filter = viewpoint.filter;
|
|
3471
|
+
if (sourceSchemaVersion === "2.1" || sourceSchemaVersion === "2.5") {
|
|
3472
|
+
if (filter) {
|
|
3473
|
+
for (const groupId of filter.groupIds) {
|
|
3474
|
+
if (!groupIds.has(groupId)) {
|
|
3475
|
+
diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.groups`));
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
for (const eventId of viewpoint.events ?? []) {
|
|
3480
|
+
if (!eventIds.has(eventId)) {
|
|
3481
|
+
diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpoint.id}".`, void 0, `viewpoint.${viewpoint.id}.events`));
|
|
3482
|
+
}
|
|
3067
3483
|
}
|
|
3068
3484
|
}
|
|
3485
|
+
validateProjection(viewpoint.projection, diagnostics, `viewpoint.${viewpoint.id}.projection`, viewpoint.id);
|
|
3486
|
+
validateCamera(viewpoint.camera, viewpoint.projection, viewpoint.rotationDeg, diagnostics, viewpoint.id, viewpoint.focusObjectId, viewpoint.selectedObjectId, filter, objectMap);
|
|
3069
3487
|
}
|
|
3070
3488
|
function validateObject(object, system, objectMap, groupIds, diagnostics) {
|
|
3071
3489
|
const placement = object.placement;
|
|
@@ -3078,6 +3496,12 @@
|
|
|
3078
3496
|
}
|
|
3079
3497
|
}
|
|
3080
3498
|
}
|
|
3499
|
+
if (typeof object.epoch === "string" && !object.epoch.trim()) {
|
|
3500
|
+
diagnostics.push(warn("validate.epoch.empty", `Object "${object.id}" defines an empty epoch string.`, object.id, "epoch"));
|
|
3501
|
+
}
|
|
3502
|
+
if (typeof object.referencePlane === "string" && !object.referencePlane.trim()) {
|
|
3503
|
+
diagnostics.push(warn("validate.referencePlane.empty", `Object "${object.id}" defines an empty reference plane string.`, object.id, "referencePlane"));
|
|
3504
|
+
}
|
|
3081
3505
|
if (orbitPlacement) {
|
|
3082
3506
|
if (!objectMap.has(orbitPlacement.target)) {
|
|
3083
3507
|
diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
|
|
@@ -3149,6 +3573,122 @@
|
|
|
3149
3573
|
}
|
|
3150
3574
|
}
|
|
3151
3575
|
}
|
|
3576
|
+
function validateEvent(event, system, objectMap, diagnostics) {
|
|
3577
|
+
const fieldPrefix = `event.${event.id}`;
|
|
3578
|
+
const referencedIds = /* @__PURE__ */ new Set();
|
|
3579
|
+
if (!event.kind.trim()) {
|
|
3580
|
+
diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
|
|
3581
|
+
}
|
|
3582
|
+
if (typeof event.epoch === "string" && !event.epoch.trim()) {
|
|
3583
|
+
diagnostics.push(warn("validate.event.epoch.empty", `Event "${event.id}" defines an empty epoch string.`, void 0, `${fieldPrefix}.epoch`));
|
|
3584
|
+
}
|
|
3585
|
+
if (typeof event.referencePlane === "string" && !event.referencePlane.trim()) {
|
|
3586
|
+
diagnostics.push(warn("validate.event.referencePlane.empty", `Event "${event.id}" defines an empty reference plane string.`, void 0, `${fieldPrefix}.referencePlane`));
|
|
3587
|
+
}
|
|
3588
|
+
if (!event.targetObjectId && event.participantObjectIds.length === 0) {
|
|
3589
|
+
diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
|
|
3590
|
+
}
|
|
3591
|
+
if (event.targetObjectId) {
|
|
3592
|
+
referencedIds.add(event.targetObjectId);
|
|
3593
|
+
if (!objectMap.has(event.targetObjectId)) {
|
|
3594
|
+
diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, void 0, `${fieldPrefix}.target`));
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
const seenParticipants = /* @__PURE__ */ new Set();
|
|
3598
|
+
for (const participantId of event.participantObjectIds) {
|
|
3599
|
+
referencedIds.add(participantId);
|
|
3600
|
+
if (seenParticipants.has(participantId)) {
|
|
3601
|
+
diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, void 0, `${fieldPrefix}.participants`));
|
|
3602
|
+
continue;
|
|
3603
|
+
}
|
|
3604
|
+
seenParticipants.add(participantId);
|
|
3605
|
+
if (!objectMap.has(participantId)) {
|
|
3606
|
+
diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, void 0, `${fieldPrefix}.participants`));
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
if (event.targetObjectId && event.participantObjectIds.length > 0 && !event.participantObjectIds.includes(event.targetObjectId)) {
|
|
3610
|
+
diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, void 0, `${fieldPrefix}.target`));
|
|
3611
|
+
}
|
|
3612
|
+
if (event.positions.length === 0) {
|
|
3613
|
+
diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, void 0, `${fieldPrefix}.positions`));
|
|
3614
|
+
}
|
|
3615
|
+
if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
|
|
3616
|
+
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`));
|
|
3617
|
+
}
|
|
3618
|
+
const poseIds = /* @__PURE__ */ new Set();
|
|
3619
|
+
for (const pose of event.positions) {
|
|
3620
|
+
const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
|
|
3621
|
+
if (poseIds.has(pose.objectId)) {
|
|
3622
|
+
diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, void 0, poseFieldPrefix));
|
|
3623
|
+
continue;
|
|
3624
|
+
}
|
|
3625
|
+
poseIds.add(pose.objectId);
|
|
3626
|
+
const object = objectMap.get(pose.objectId);
|
|
3627
|
+
if (!object) {
|
|
3628
|
+
diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, void 0, poseFieldPrefix));
|
|
3629
|
+
continue;
|
|
3630
|
+
}
|
|
3631
|
+
if (!referencedIds.has(pose.objectId)) {
|
|
3632
|
+
diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
|
|
3633
|
+
}
|
|
3634
|
+
validateEventPose(pose, object, event, system, objectMap, diagnostics, poseFieldPrefix, event.id);
|
|
3635
|
+
}
|
|
3636
|
+
const missingPoseIds = [...referencedIds].filter((objectId) => !poseIds.has(objectId));
|
|
3637
|
+
if (event.positions.length > 0 && missingPoseIds.length > 0) {
|
|
3638
|
+
diagnostics.push(warn("validate.event.positions.partial", `Event "${event.id}" leaves ${missingPoseIds.length} referenced object(s) on their base placement.`, void 0, `${fieldPrefix}.positions`));
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
function validateEventPose(pose, object, event, system, objectMap, diagnostics, fieldPrefix, eventId) {
|
|
3642
|
+
const placement = pose.placement;
|
|
3643
|
+
if (!placement) {
|
|
3644
|
+
diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3647
|
+
if (placement.mode === "orbit") {
|
|
3648
|
+
if (!objectMap.has(placement.target)) {
|
|
3649
|
+
diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
|
|
3650
|
+
}
|
|
3651
|
+
if (placement.distance && placement.semiMajor) {
|
|
3652
|
+
diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
|
|
3653
|
+
}
|
|
3654
|
+
if (placement.phase && !resolveEffectiveEpoch(system, object, event, pose)) {
|
|
3655
|
+
diagnostics.push(warn("validate.event.pose.phase.epochMissing", `Event "${eventId}" pose "${pose.objectId}" sets "phase" without an effective epoch.`, void 0, `${fieldPrefix}.phase`));
|
|
3656
|
+
}
|
|
3657
|
+
if (placement.inclination && !resolveEffectiveReferencePlane(system, object, event, pose)) {
|
|
3658
|
+
diagnostics.push(warn("validate.event.pose.inclination.referencePlaneMissing", `Event "${eventId}" pose "${pose.objectId}" sets "inclination" without an effective reference plane.`, void 0, `${fieldPrefix}.inclination`));
|
|
3659
|
+
}
|
|
3660
|
+
if (placement.period && !massInSolar(objectMap.get(placement.target)?.properties.mass)) {
|
|
3661
|
+
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`));
|
|
3662
|
+
}
|
|
3663
|
+
return;
|
|
3664
|
+
}
|
|
3665
|
+
if (placement.mode === "surface") {
|
|
3666
|
+
const target = objectMap.get(placement.target);
|
|
3667
|
+
if (!target) {
|
|
3668
|
+
diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.surface`));
|
|
3669
|
+
} else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
|
|
3670
|
+
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`));
|
|
3671
|
+
}
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
if (placement.mode === "at") {
|
|
3675
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
3676
|
+
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`));
|
|
3677
|
+
}
|
|
3678
|
+
const reference = placement.reference;
|
|
3679
|
+
if (reference.kind === "named" && !objectMap.has(reference.name)) {
|
|
3680
|
+
diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3681
|
+
} else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
|
|
3682
|
+
diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3683
|
+
} else if (reference.kind === "lagrange") {
|
|
3684
|
+
if (!objectMap.has(reference.primary)) {
|
|
3685
|
+
diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3686
|
+
} else if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
3687
|
+
diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3152
3692
|
function validateAtTarget(object, objectMap, diagnostics) {
|
|
3153
3693
|
const reference = object.placement?.mode === "at" ? object.placement.reference : null;
|
|
3154
3694
|
if (!reference) {
|
|
@@ -3254,6 +3794,52 @@
|
|
|
3254
3794
|
return null;
|
|
3255
3795
|
}
|
|
3256
3796
|
}
|
|
3797
|
+
function validateProjection(projection, diagnostics, field, viewpointId) {
|
|
3798
|
+
if (projection !== "topdown" && projection !== "isometric" && projection !== "orthographic" && projection !== "perspective") {
|
|
3799
|
+
diagnostics.push(error("validate.viewpoint.projection.invalid", `Unknown projection "${String(projection)}" in viewpoint "${viewpointId}".`, void 0, field));
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
function validateCamera(camera, projection, rotationDeg, diagnostics, viewpointId, focusObjectId, selectedObjectId, filter, objectMap) {
|
|
3803
|
+
if (!camera) {
|
|
3804
|
+
return;
|
|
3805
|
+
}
|
|
3806
|
+
const prefix = `viewpoint.${viewpointId}.camera`;
|
|
3807
|
+
for (const [key, value] of [
|
|
3808
|
+
["azimuth", camera.azimuth],
|
|
3809
|
+
["elevation", camera.elevation],
|
|
3810
|
+
["roll", camera.roll],
|
|
3811
|
+
["distance", camera.distance]
|
|
3812
|
+
]) {
|
|
3813
|
+
if (value !== null && (!Number.isFinite(value) || key === "distance" && value <= 0)) {
|
|
3814
|
+
diagnostics.push(error("validate.viewpoint.camera.invalid", `Invalid camera ${key} "${String(value)}" in viewpoint "${viewpointId}".`, void 0, `${prefix}.${key}`));
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
if (camera.distance !== null && projection !== "perspective") {
|
|
3818
|
+
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`));
|
|
3819
|
+
}
|
|
3820
|
+
if (projection === "topdown" && (camera.elevation !== null || camera.roll !== null)) {
|
|
3821
|
+
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));
|
|
3822
|
+
}
|
|
3823
|
+
if (projection === "isometric" && camera.elevation !== null) {
|
|
3824
|
+
diagnostics.push(info("validate.viewpoint.camera.isometricStored", `Camera elevation on isometric viewpoint "${viewpointId}" is preserved semantically for future 3D rendering.`, void 0, `${prefix}.elevation`));
|
|
3825
|
+
}
|
|
3826
|
+
if (camera.azimuth !== null && camera.azimuth !== 0 && rotationDeg !== 0) {
|
|
3827
|
+
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`));
|
|
3828
|
+
}
|
|
3829
|
+
const hasAnchor = focusObjectId !== null && objectMap.has(focusObjectId) || selectedObjectId !== null && objectMap.has(selectedObjectId) || !!filter;
|
|
3830
|
+
if (!hasAnchor) {
|
|
3831
|
+
diagnostics.push(info("validate.viewpoint.camera.anchorMissing", `Viewpoint "${viewpointId}" stores camera settings without a focus object, selection, or filter anchor.`, void 0, prefix));
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
function resolveEffectiveEpoch(system, object, event, pose) {
|
|
3835
|
+
return normalizeOptionalContextString(pose?.epoch) ?? normalizeOptionalContextString(event?.epoch) ?? normalizeOptionalContextString(object.epoch) ?? normalizeOptionalContextString(system?.epoch) ?? null;
|
|
3836
|
+
}
|
|
3837
|
+
function resolveEffectiveReferencePlane(system, object, event, pose) {
|
|
3838
|
+
return normalizeOptionalContextString(pose?.referencePlane) ?? normalizeOptionalContextString(event?.referencePlane) ?? normalizeOptionalContextString(object.referencePlane) ?? normalizeOptionalContextString(system?.referencePlane) ?? null;
|
|
3839
|
+
}
|
|
3840
|
+
function normalizeOptionalContextString(value) {
|
|
3841
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
3842
|
+
}
|
|
3257
3843
|
function toleranceForField(object, field) {
|
|
3258
3844
|
const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
|
|
3259
3845
|
if (typeof tolerance === "number") {
|
|
@@ -3349,6 +3935,23 @@
|
|
|
3349
3935
|
});
|
|
3350
3936
|
}
|
|
3351
3937
|
var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
|
|
3938
|
+
var EVENT_POSE_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
3939
|
+
"orbit",
|
|
3940
|
+
"distance",
|
|
3941
|
+
"semiMajor",
|
|
3942
|
+
"eccentricity",
|
|
3943
|
+
"period",
|
|
3944
|
+
"angle",
|
|
3945
|
+
"inclination",
|
|
3946
|
+
"phase",
|
|
3947
|
+
"at",
|
|
3948
|
+
"surface",
|
|
3949
|
+
"free",
|
|
3950
|
+
"inner",
|
|
3951
|
+
"outer",
|
|
3952
|
+
"epoch",
|
|
3953
|
+
"referencePlane"
|
|
3954
|
+
]);
|
|
3352
3955
|
function parseWorldOrbitAtlas(source) {
|
|
3353
3956
|
return parseAtlasSource(source);
|
|
3354
3957
|
}
|
|
@@ -3363,12 +3966,15 @@
|
|
|
3363
3966
|
const objectNodes = [];
|
|
3364
3967
|
const groups = [];
|
|
3365
3968
|
const relations = [];
|
|
3969
|
+
const events = [];
|
|
3970
|
+
const eventPoseNodes = /* @__PURE__ */ new Map();
|
|
3366
3971
|
let sawDefaults = false;
|
|
3367
3972
|
let sawAtlas = false;
|
|
3368
3973
|
const viewpointIds = /* @__PURE__ */ new Set();
|
|
3369
3974
|
const annotationIds = /* @__PURE__ */ new Set();
|
|
3370
3975
|
const groupIds = /* @__PURE__ */ new Set();
|
|
3371
3976
|
const relationIds = /* @__PURE__ */ new Set();
|
|
3977
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
3372
3978
|
for (let index = 0; index < lines.length; index++) {
|
|
3373
3979
|
const rawLine = lines[index];
|
|
3374
3980
|
const lineNumber = index + 1;
|
|
@@ -3386,7 +3992,7 @@
|
|
|
3386
3992
|
if (!sawSchemaHeader) {
|
|
3387
3993
|
sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
|
|
3388
3994
|
sawSchemaHeader = true;
|
|
3389
|
-
if (prepared.comments.length > 0 && sourceSchemaVersion
|
|
3995
|
+
if (prepared.comments.length > 0 && isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
3390
3996
|
diagnostics.push({
|
|
3391
3997
|
code: "parse.schema21.commentCompatibility",
|
|
3392
3998
|
severity: "warning",
|
|
@@ -3399,7 +4005,7 @@
|
|
|
3399
4005
|
continue;
|
|
3400
4006
|
}
|
|
3401
4007
|
if (indent === 0) {
|
|
3402
|
-
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
|
|
4008
|
+
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
|
|
3403
4009
|
if (section.kind === "system") {
|
|
3404
4010
|
system = section.system;
|
|
3405
4011
|
} else if (section.kind === "defaults") {
|
|
@@ -3418,6 +4024,7 @@
|
|
|
3418
4024
|
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
3419
4025
|
}
|
|
3420
4026
|
const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
|
|
4027
|
+
const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
|
|
3421
4028
|
const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
|
|
3422
4029
|
const baseDocument = {
|
|
3423
4030
|
format: "worldorbit",
|
|
@@ -3425,6 +4032,7 @@
|
|
|
3425
4032
|
system,
|
|
3426
4033
|
groups,
|
|
3427
4034
|
relations,
|
|
4035
|
+
events: normalizedEvents,
|
|
3428
4036
|
objects,
|
|
3429
4037
|
diagnostics
|
|
3430
4038
|
};
|
|
@@ -3454,13 +4062,13 @@
|
|
|
3454
4062
|
return document2;
|
|
3455
4063
|
}
|
|
3456
4064
|
function assertDraftSchemaHeader(tokens, line) {
|
|
3457
|
-
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
|
|
3458
|
-
throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
4065
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1", "2.5"].includes(tokens[1].value.toLowerCase())) {
|
|
4066
|
+
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);
|
|
3459
4067
|
}
|
|
3460
4068
|
const version = tokens[1].value.toLowerCase();
|
|
3461
|
-
return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
4069
|
+
return version === "2.5" ? "2.5" : version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
3462
4070
|
}
|
|
3463
|
-
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
|
|
4071
|
+
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
|
|
3464
4072
|
const keyword = tokens[0]?.value.toLowerCase();
|
|
3465
4073
|
switch (keyword) {
|
|
3466
4074
|
case "system":
|
|
@@ -3478,6 +4086,8 @@
|
|
|
3478
4086
|
return {
|
|
3479
4087
|
kind: "defaults",
|
|
3480
4088
|
system,
|
|
4089
|
+
sourceSchemaVersion,
|
|
4090
|
+
diagnostics,
|
|
3481
4091
|
seenFields: /* @__PURE__ */ new Set()
|
|
3482
4092
|
};
|
|
3483
4093
|
case "atlas":
|
|
@@ -3497,7 +4107,7 @@
|
|
|
3497
4107
|
if (!system) {
|
|
3498
4108
|
throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
|
|
3499
4109
|
}
|
|
3500
|
-
return startViewpointSection(tokens, line, system, viewpointIds);
|
|
4110
|
+
return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
|
|
3501
4111
|
case "annotation":
|
|
3502
4112
|
if (!system) {
|
|
3503
4113
|
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
@@ -3509,6 +4119,9 @@
|
|
|
3509
4119
|
case "relation":
|
|
3510
4120
|
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
|
|
3511
4121
|
return startRelationSection(tokens, line, relations, relationIds);
|
|
4122
|
+
case "event":
|
|
4123
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
|
|
4124
|
+
return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
|
|
3512
4125
|
case "object":
|
|
3513
4126
|
return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
|
|
3514
4127
|
default:
|
|
@@ -3545,7 +4158,7 @@
|
|
|
3545
4158
|
seenFields: /* @__PURE__ */ new Set()
|
|
3546
4159
|
};
|
|
3547
4160
|
}
|
|
3548
|
-
function startViewpointSection(tokens, line, system, viewpointIds) {
|
|
4161
|
+
function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
|
|
3549
4162
|
if (tokens.length !== 2) {
|
|
3550
4163
|
throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
|
|
3551
4164
|
}
|
|
@@ -3562,10 +4175,12 @@
|
|
|
3562
4175
|
summary: "",
|
|
3563
4176
|
focusObjectId: null,
|
|
3564
4177
|
selectedObjectId: null,
|
|
4178
|
+
events: [],
|
|
3565
4179
|
projection: system.defaults.view,
|
|
3566
4180
|
preset: system.defaults.preset,
|
|
3567
4181
|
zoom: null,
|
|
3568
4182
|
rotationDeg: 0,
|
|
4183
|
+
camera: null,
|
|
3569
4184
|
layers: {},
|
|
3570
4185
|
filter: null
|
|
3571
4186
|
};
|
|
@@ -3574,10 +4189,15 @@
|
|
|
3574
4189
|
return {
|
|
3575
4190
|
kind: "viewpoint",
|
|
3576
4191
|
viewpoint,
|
|
4192
|
+
sourceSchemaVersion,
|
|
4193
|
+
diagnostics,
|
|
3577
4194
|
seenFields: /* @__PURE__ */ new Set(),
|
|
3578
4195
|
inFilter: false,
|
|
3579
4196
|
filterIndent: null,
|
|
3580
|
-
seenFilterFields: /* @__PURE__ */ new Set()
|
|
4197
|
+
seenFilterFields: /* @__PURE__ */ new Set(),
|
|
4198
|
+
inCamera: false,
|
|
4199
|
+
cameraIndent: null,
|
|
4200
|
+
seenCameraFields: /* @__PURE__ */ new Set()
|
|
3581
4201
|
};
|
|
3582
4202
|
}
|
|
3583
4203
|
function startAnnotationSection(tokens, line, system, annotationIds) {
|
|
@@ -3664,6 +4284,51 @@
|
|
|
3664
4284
|
seenFields: /* @__PURE__ */ new Set()
|
|
3665
4285
|
};
|
|
3666
4286
|
}
|
|
4287
|
+
function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
|
|
4288
|
+
if (tokens.length !== 2) {
|
|
4289
|
+
throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
|
|
4290
|
+
}
|
|
4291
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
4292
|
+
if (!id) {
|
|
4293
|
+
throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
|
|
4294
|
+
}
|
|
4295
|
+
if (eventIds.has(id)) {
|
|
4296
|
+
throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
|
|
4297
|
+
}
|
|
4298
|
+
const event = {
|
|
4299
|
+
id,
|
|
4300
|
+
kind: "",
|
|
4301
|
+
label: humanizeIdentifier2(id),
|
|
4302
|
+
summary: null,
|
|
4303
|
+
targetObjectId: null,
|
|
4304
|
+
participantObjectIds: [],
|
|
4305
|
+
timing: null,
|
|
4306
|
+
visibility: null,
|
|
4307
|
+
epoch: null,
|
|
4308
|
+
referencePlane: null,
|
|
4309
|
+
tags: [],
|
|
4310
|
+
color: null,
|
|
4311
|
+
hidden: false,
|
|
4312
|
+
positions: []
|
|
4313
|
+
};
|
|
4314
|
+
const rawPoses = [];
|
|
4315
|
+
events.push(event);
|
|
4316
|
+
eventPoseNodes.set(id, rawPoses);
|
|
4317
|
+
eventIds.add(id);
|
|
4318
|
+
return {
|
|
4319
|
+
kind: "event",
|
|
4320
|
+
event,
|
|
4321
|
+
sourceSchemaVersion,
|
|
4322
|
+
diagnostics,
|
|
4323
|
+
seenFields: /* @__PURE__ */ new Set(),
|
|
4324
|
+
rawPoses,
|
|
4325
|
+
inPositions: false,
|
|
4326
|
+
positionsIndent: null,
|
|
4327
|
+
activePose: null,
|
|
4328
|
+
poseIndent: null,
|
|
4329
|
+
activePoseSeenFields: /* @__PURE__ */ new Set()
|
|
4330
|
+
};
|
|
4331
|
+
}
|
|
3667
4332
|
function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
|
|
3668
4333
|
if (tokens.length < 3) {
|
|
3669
4334
|
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
@@ -3720,6 +4385,9 @@
|
|
|
3720
4385
|
case "relation":
|
|
3721
4386
|
applyRelationField(section, tokens, line);
|
|
3722
4387
|
return;
|
|
4388
|
+
case "event":
|
|
4389
|
+
applyEventField(section, indent, tokens, line);
|
|
4390
|
+
return;
|
|
3723
4391
|
case "object":
|
|
3724
4392
|
applyObjectField(section, indent, tokens, line);
|
|
3725
4393
|
return;
|
|
@@ -3762,6 +4430,12 @@
|
|
|
3762
4430
|
const value = joinFieldValue(tokens, line);
|
|
3763
4431
|
switch (key) {
|
|
3764
4432
|
case "view":
|
|
4433
|
+
if (isSchema25Projection(value)) {
|
|
4434
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "defaults.view", {
|
|
4435
|
+
line,
|
|
4436
|
+
column: tokens[0].column
|
|
4437
|
+
});
|
|
4438
|
+
}
|
|
3765
4439
|
section.system.defaults.view = parseProjectionValue(value, line, tokens[0].column);
|
|
3766
4440
|
return;
|
|
3767
4441
|
case "scale":
|
|
@@ -3801,14 +4475,36 @@
|
|
|
3801
4475
|
throw new WorldOrbitError(`Unknown atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3802
4476
|
}
|
|
3803
4477
|
function applyViewpointField2(section, indent, tokens, line) {
|
|
4478
|
+
if (section.inCamera && indent <= (section.cameraIndent ?? 0)) {
|
|
4479
|
+
section.inCamera = false;
|
|
4480
|
+
section.cameraIndent = null;
|
|
4481
|
+
}
|
|
3804
4482
|
if (section.inFilter && indent <= (section.filterIndent ?? 0)) {
|
|
3805
4483
|
section.inFilter = false;
|
|
3806
4484
|
section.filterIndent = null;
|
|
3807
4485
|
}
|
|
4486
|
+
if (section.inCamera) {
|
|
4487
|
+
applyViewpointCameraField(section, tokens, line);
|
|
4488
|
+
return;
|
|
4489
|
+
}
|
|
3808
4490
|
if (section.inFilter) {
|
|
3809
4491
|
applyViewpointFilterField(section, tokens, line);
|
|
3810
4492
|
return;
|
|
3811
4493
|
}
|
|
4494
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "camera") {
|
|
4495
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
|
|
4496
|
+
line,
|
|
4497
|
+
column: tokens[0].column
|
|
4498
|
+
});
|
|
4499
|
+
if (section.seenFields.has("camera")) {
|
|
4500
|
+
throw new WorldOrbitError('Duplicate viewpoint field "camera"', line, tokens[0].column);
|
|
4501
|
+
}
|
|
4502
|
+
section.seenFields.add("camera");
|
|
4503
|
+
section.inCamera = true;
|
|
4504
|
+
section.cameraIndent = indent;
|
|
4505
|
+
section.viewpoint.camera = section.viewpoint.camera ?? createEmptyViewCamera2();
|
|
4506
|
+
return;
|
|
4507
|
+
}
|
|
3812
4508
|
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "filter") {
|
|
3813
4509
|
if (section.seenFields.has("filter")) {
|
|
3814
4510
|
throw new WorldOrbitError('Duplicate viewpoint field "filter"', line, tokens[0].column);
|
|
@@ -3834,6 +4530,12 @@
|
|
|
3834
4530
|
section.viewpoint.selectedObjectId = value;
|
|
3835
4531
|
return;
|
|
3836
4532
|
case "projection":
|
|
4533
|
+
if (isSchema25Projection(value)) {
|
|
4534
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "projection", {
|
|
4535
|
+
line,
|
|
4536
|
+
column: tokens[0].column
|
|
4537
|
+
});
|
|
4538
|
+
}
|
|
3837
4539
|
section.viewpoint.projection = parseProjectionValue(value, line, tokens[0].column);
|
|
3838
4540
|
return;
|
|
3839
4541
|
case "preset":
|
|
@@ -3845,13 +4547,49 @@
|
|
|
3845
4547
|
case "rotation":
|
|
3846
4548
|
section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
|
|
3847
4549
|
return;
|
|
4550
|
+
case "camera":
|
|
4551
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.camera", {
|
|
4552
|
+
line,
|
|
4553
|
+
column: tokens[0].column
|
|
4554
|
+
});
|
|
4555
|
+
section.viewpoint.camera = parseInlineViewCamera(tokens.slice(1), line, section.viewpoint.camera);
|
|
4556
|
+
return;
|
|
3848
4557
|
case "layers":
|
|
3849
|
-
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
|
|
4558
|
+
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
|
|
4559
|
+
return;
|
|
4560
|
+
case "events":
|
|
4561
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
|
|
4562
|
+
line,
|
|
4563
|
+
column: tokens[0].column
|
|
4564
|
+
});
|
|
4565
|
+
section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
|
|
3850
4566
|
return;
|
|
3851
4567
|
default:
|
|
3852
4568
|
throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3853
4569
|
}
|
|
3854
4570
|
}
|
|
4571
|
+
function applyViewpointCameraField(section, tokens, line) {
|
|
4572
|
+
const key = requireUniqueField(tokens, section.seenCameraFields, line);
|
|
4573
|
+
const value = joinFieldValue(tokens, line);
|
|
4574
|
+
const camera = section.viewpoint.camera ?? createEmptyViewCamera2();
|
|
4575
|
+
switch (key) {
|
|
4576
|
+
case "azimuth":
|
|
4577
|
+
camera.azimuth = parseFiniteNumber2(value, line, tokens[0].column, "camera.azimuth");
|
|
4578
|
+
break;
|
|
4579
|
+
case "elevation":
|
|
4580
|
+
camera.elevation = parseFiniteNumber2(value, line, tokens[0].column, "camera.elevation");
|
|
4581
|
+
break;
|
|
4582
|
+
case "roll":
|
|
4583
|
+
camera.roll = parseFiniteNumber2(value, line, tokens[0].column, "camera.roll");
|
|
4584
|
+
break;
|
|
4585
|
+
case "distance":
|
|
4586
|
+
camera.distance = parsePositiveNumber2(value, line, tokens[0].column, "camera.distance");
|
|
4587
|
+
break;
|
|
4588
|
+
default:
|
|
4589
|
+
throw new WorldOrbitError(`Unknown viewpoint camera field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4590
|
+
}
|
|
4591
|
+
section.viewpoint.camera = camera;
|
|
4592
|
+
}
|
|
3855
4593
|
function applyViewpointFilterField(section, tokens, line) {
|
|
3856
4594
|
const key = requireUniqueField(tokens, section.seenFilterFields, line);
|
|
3857
4595
|
const filter = section.viewpoint.filter ?? createEmptyViewpointFilter2();
|
|
@@ -3951,6 +4689,126 @@
|
|
|
3951
4689
|
throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3952
4690
|
}
|
|
3953
4691
|
}
|
|
4692
|
+
function applyEventField(section, indent, tokens, line) {
|
|
4693
|
+
if (section.activePose && indent <= (section.poseIndent ?? 0)) {
|
|
4694
|
+
section.activePose = null;
|
|
4695
|
+
section.poseIndent = null;
|
|
4696
|
+
section.activePoseSeenFields.clear();
|
|
4697
|
+
}
|
|
4698
|
+
if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
|
|
4699
|
+
section.inPositions = false;
|
|
4700
|
+
section.positionsIndent = null;
|
|
4701
|
+
}
|
|
4702
|
+
if (section.activePose) {
|
|
4703
|
+
if (tokens[0]?.value === "epoch" || tokens[0]?.value === "referencePlane") {
|
|
4704
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, `pose.${tokens[0].value}`, {
|
|
4705
|
+
line,
|
|
4706
|
+
column: tokens[0]?.column ?? 1
|
|
4707
|
+
});
|
|
4708
|
+
}
|
|
4709
|
+
section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
|
|
4710
|
+
return;
|
|
4711
|
+
}
|
|
4712
|
+
if (section.inPositions) {
|
|
4713
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
|
|
4714
|
+
throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
|
|
4715
|
+
}
|
|
4716
|
+
const objectId = tokens[1].value;
|
|
4717
|
+
if (!objectId.trim()) {
|
|
4718
|
+
throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
|
|
4719
|
+
}
|
|
4720
|
+
const rawPose = {
|
|
4721
|
+
objectId,
|
|
4722
|
+
fields: [],
|
|
4723
|
+
location: { line, column: tokens[0].column }
|
|
4724
|
+
};
|
|
4725
|
+
section.rawPoses.push(rawPose);
|
|
4726
|
+
section.activePose = rawPose;
|
|
4727
|
+
section.poseIndent = indent;
|
|
4728
|
+
section.activePoseSeenFields = /* @__PURE__ */ new Set();
|
|
4729
|
+
return;
|
|
4730
|
+
}
|
|
4731
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
|
|
4732
|
+
if (section.seenFields.has("positions")) {
|
|
4733
|
+
throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
|
|
4734
|
+
}
|
|
4735
|
+
section.seenFields.add("positions");
|
|
4736
|
+
section.inPositions = true;
|
|
4737
|
+
section.positionsIndent = indent;
|
|
4738
|
+
return;
|
|
4739
|
+
}
|
|
4740
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
4741
|
+
switch (key) {
|
|
4742
|
+
case "kind":
|
|
4743
|
+
section.event.kind = joinFieldValue(tokens, line);
|
|
4744
|
+
return;
|
|
4745
|
+
case "label":
|
|
4746
|
+
section.event.label = joinFieldValue(tokens, line);
|
|
4747
|
+
return;
|
|
4748
|
+
case "summary":
|
|
4749
|
+
section.event.summary = joinFieldValue(tokens, line);
|
|
4750
|
+
return;
|
|
4751
|
+
case "target":
|
|
4752
|
+
section.event.targetObjectId = joinFieldValue(tokens, line);
|
|
4753
|
+
return;
|
|
4754
|
+
case "participants":
|
|
4755
|
+
section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
|
|
4756
|
+
return;
|
|
4757
|
+
case "timing":
|
|
4758
|
+
section.event.timing = joinFieldValue(tokens, line);
|
|
4759
|
+
return;
|
|
4760
|
+
case "visibility":
|
|
4761
|
+
section.event.visibility = joinFieldValue(tokens, line);
|
|
4762
|
+
return;
|
|
4763
|
+
case "epoch":
|
|
4764
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.epoch", {
|
|
4765
|
+
line,
|
|
4766
|
+
column: tokens[0].column
|
|
4767
|
+
});
|
|
4768
|
+
section.event.epoch = joinFieldValue(tokens, line);
|
|
4769
|
+
return;
|
|
4770
|
+
case "referenceplane":
|
|
4771
|
+
warnIfSchema25Feature(section.sourceSchemaVersion, section.diagnostics, "event.referencePlane", {
|
|
4772
|
+
line,
|
|
4773
|
+
column: tokens[0].column
|
|
4774
|
+
});
|
|
4775
|
+
section.event.referencePlane = joinFieldValue(tokens, line);
|
|
4776
|
+
return;
|
|
4777
|
+
case "tags":
|
|
4778
|
+
section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
4779
|
+
return;
|
|
4780
|
+
case "color":
|
|
4781
|
+
section.event.color = joinFieldValue(tokens, line);
|
|
4782
|
+
return;
|
|
4783
|
+
case "hidden":
|
|
4784
|
+
section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
4785
|
+
line,
|
|
4786
|
+
column: tokens[0].column
|
|
4787
|
+
});
|
|
4788
|
+
return;
|
|
4789
|
+
default:
|
|
4790
|
+
throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
function parseEventPoseField(tokens, line, seenFields) {
|
|
4794
|
+
if (tokens.length < 2) {
|
|
4795
|
+
throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
|
|
4796
|
+
}
|
|
4797
|
+
const key = tokens[0].value;
|
|
4798
|
+
if (!EVENT_POSE_FIELD_KEYS.has(key)) {
|
|
4799
|
+
throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
|
|
4800
|
+
}
|
|
4801
|
+
if (seenFields.has(key)) {
|
|
4802
|
+
throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
|
|
4803
|
+
}
|
|
4804
|
+
seenFields.add(key);
|
|
4805
|
+
return {
|
|
4806
|
+
type: "field",
|
|
4807
|
+
key,
|
|
4808
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
4809
|
+
location: { line, column: tokens[0].column }
|
|
4810
|
+
};
|
|
4811
|
+
}
|
|
3954
4812
|
function applyObjectField(section, indent, tokens, line) {
|
|
3955
4813
|
if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
|
|
3956
4814
|
section.activeBlock = null;
|
|
@@ -4009,7 +4867,7 @@
|
|
|
4009
4867
|
function parseObjectTypeTokens(tokens, line) {
|
|
4010
4868
|
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");
|
|
4011
4869
|
}
|
|
4012
|
-
function parseLayerTokens(tokens, line) {
|
|
4870
|
+
function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
4013
4871
|
const layers = {};
|
|
4014
4872
|
for (const token of parseTokenList(tokens, line, "layers")) {
|
|
4015
4873
|
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
@@ -4019,7 +4877,13 @@
|
|
|
4019
4877
|
layers["orbits-front"] = enabled;
|
|
4020
4878
|
continue;
|
|
4021
4879
|
}
|
|
4022
|
-
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
4880
|
+
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
4881
|
+
if (raw === "events" && sourceSchemaVersion && diagnostics) {
|
|
4882
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
|
|
4883
|
+
line,
|
|
4884
|
+
column: tokens[0]?.column ?? 1
|
|
4885
|
+
});
|
|
4886
|
+
}
|
|
4023
4887
|
layers[raw] = enabled;
|
|
4024
4888
|
}
|
|
4025
4889
|
}
|
|
@@ -4037,11 +4901,15 @@
|
|
|
4037
4901
|
}
|
|
4038
4902
|
function parseProjectionValue(value, line, column) {
|
|
4039
4903
|
const normalized = value.toLowerCase();
|
|
4040
|
-
if (normalized !== "topdown" && normalized !== "isometric") {
|
|
4904
|
+
if (normalized !== "topdown" && normalized !== "isometric" && normalized !== "orthographic" && normalized !== "perspective") {
|
|
4041
4905
|
throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
|
|
4042
4906
|
}
|
|
4043
4907
|
return normalized;
|
|
4044
4908
|
}
|
|
4909
|
+
function isSchema25Projection(value) {
|
|
4910
|
+
const normalized = value.toLowerCase();
|
|
4911
|
+
return normalized === "orthographic" || normalized === "perspective";
|
|
4912
|
+
}
|
|
4045
4913
|
function parsePresetValue(value, line, column) {
|
|
4046
4914
|
const normalized = value.toLowerCase();
|
|
4047
4915
|
if (normalized === "diagram" || normalized === "presentation" || normalized === "atlas-card" || normalized === "markdown") {
|
|
@@ -4071,6 +4939,48 @@
|
|
|
4071
4939
|
groupIds: []
|
|
4072
4940
|
};
|
|
4073
4941
|
}
|
|
4942
|
+
function createEmptyViewCamera2() {
|
|
4943
|
+
return {
|
|
4944
|
+
azimuth: null,
|
|
4945
|
+
elevation: null,
|
|
4946
|
+
roll: null,
|
|
4947
|
+
distance: null
|
|
4948
|
+
};
|
|
4949
|
+
}
|
|
4950
|
+
function parseInlineViewCamera(tokens, line, current) {
|
|
4951
|
+
if (tokens.length === 0 || tokens.length % 2 !== 0) {
|
|
4952
|
+
throw new WorldOrbitError('Field "camera" expects "<field> <value>" pairs', line, tokens[0]?.column ?? 1);
|
|
4953
|
+
}
|
|
4954
|
+
const camera = current ? { ...current } : createEmptyViewCamera2();
|
|
4955
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4956
|
+
for (let index = 0; index < tokens.length; index += 2) {
|
|
4957
|
+
const fieldToken = tokens[index];
|
|
4958
|
+
const valueToken = tokens[index + 1];
|
|
4959
|
+
const key = fieldToken.value.toLowerCase();
|
|
4960
|
+
if (seen.has(key)) {
|
|
4961
|
+
throw new WorldOrbitError(`Duplicate viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
|
|
4962
|
+
}
|
|
4963
|
+
seen.add(key);
|
|
4964
|
+
const value = valueToken.value;
|
|
4965
|
+
switch (key) {
|
|
4966
|
+
case "azimuth":
|
|
4967
|
+
camera.azimuth = parseFiniteNumber2(value, line, fieldToken.column, "camera.azimuth");
|
|
4968
|
+
break;
|
|
4969
|
+
case "elevation":
|
|
4970
|
+
camera.elevation = parseFiniteNumber2(value, line, fieldToken.column, "camera.elevation");
|
|
4971
|
+
break;
|
|
4972
|
+
case "roll":
|
|
4973
|
+
camera.roll = parseFiniteNumber2(value, line, fieldToken.column, "camera.roll");
|
|
4974
|
+
break;
|
|
4975
|
+
case "distance":
|
|
4976
|
+
camera.distance = parsePositiveNumber2(value, line, fieldToken.column, "camera.distance");
|
|
4977
|
+
break;
|
|
4978
|
+
default:
|
|
4979
|
+
throw new WorldOrbitError(`Unknown viewpoint camera field "${fieldToken.value}"`, line, fieldToken.column);
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
return camera;
|
|
4983
|
+
}
|
|
4074
4984
|
function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
4075
4985
|
const fields = [];
|
|
4076
4986
|
let index = 0;
|
|
@@ -4158,7 +5068,7 @@
|
|
|
4158
5068
|
}
|
|
4159
5069
|
function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
4160
5070
|
const fieldMap = collectDraftFields(node.fields);
|
|
4161
|
-
const placement =
|
|
5071
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
4162
5072
|
const properties = normalizeDraftProperties(node.objectType, fieldMap);
|
|
4163
5073
|
const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
|
|
4164
5074
|
const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
|
|
@@ -4203,21 +5113,41 @@
|
|
|
4203
5113
|
object.tolerances = tolerances;
|
|
4204
5114
|
if (typedBlocks && Object.keys(typedBlocks).length > 0)
|
|
4205
5115
|
object.typedBlocks = typedBlocks;
|
|
4206
|
-
if (sourceSchemaVersion
|
|
5116
|
+
if (isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
4207
5117
|
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) {
|
|
4208
5118
|
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
|
|
4209
5119
|
}
|
|
4210
5120
|
}
|
|
4211
5121
|
return object;
|
|
4212
5122
|
}
|
|
4213
|
-
function
|
|
5123
|
+
function normalizeDraftEvent(event, rawPoses) {
|
|
5124
|
+
return {
|
|
5125
|
+
...event,
|
|
5126
|
+
participantObjectIds: [...new Set(event.participantObjectIds)],
|
|
5127
|
+
tags: [...new Set(event.tags)],
|
|
5128
|
+
positions: rawPoses.map((pose) => normalizeDraftEventPose(pose))
|
|
5129
|
+
};
|
|
5130
|
+
}
|
|
5131
|
+
function normalizeDraftEventPose(rawPose) {
|
|
5132
|
+
const fieldMap = collectDraftFields(rawPose.fields, "event-pose");
|
|
5133
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
5134
|
+
return {
|
|
5135
|
+
objectId: rawPose.objectId,
|
|
5136
|
+
placement,
|
|
5137
|
+
inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
|
|
5138
|
+
outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
|
|
5139
|
+
epoch: parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]),
|
|
5140
|
+
referencePlane: parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0])
|
|
5141
|
+
};
|
|
5142
|
+
}
|
|
5143
|
+
function collectDraftFields(fields, _mode = "object") {
|
|
4214
5144
|
const grouped = /* @__PURE__ */ new Map();
|
|
4215
5145
|
for (const field of fields) {
|
|
4216
5146
|
const spec = getDraftObjectFieldSpec(field.key);
|
|
4217
|
-
if (!spec) {
|
|
5147
|
+
if (!spec && !EVENT_POSE_FIELD_KEYS.has(field.key)) {
|
|
4218
5148
|
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
4219
5149
|
}
|
|
4220
|
-
if (!spec
|
|
5150
|
+
if (!spec?.allowRepeat && grouped.has(field.key)) {
|
|
4221
5151
|
throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
|
|
4222
5152
|
}
|
|
4223
5153
|
const existing = grouped.get(field.key) ?? [];
|
|
@@ -4226,7 +5156,7 @@
|
|
|
4226
5156
|
}
|
|
4227
5157
|
return grouped;
|
|
4228
5158
|
}
|
|
4229
|
-
function
|
|
5159
|
+
function extractPlacementFromFieldMap(fieldMap) {
|
|
4230
5160
|
const orbitField = fieldMap.get("orbit")?.[0];
|
|
4231
5161
|
const atField = fieldMap.get("at")?.[0];
|
|
4232
5162
|
const surfaceField = fieldMap.get("surface")?.[0];
|
|
@@ -4394,7 +5324,7 @@
|
|
|
4394
5324
|
}
|
|
4395
5325
|
}
|
|
4396
5326
|
function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
4397
|
-
if (sourceSchemaVersion
|
|
5327
|
+
if (!isSchemaOlderThan(sourceSchemaVersion, "2.1")) {
|
|
4398
5328
|
return;
|
|
4399
5329
|
}
|
|
4400
5330
|
diagnostics.push({
|
|
@@ -4406,6 +5336,34 @@
|
|
|
4406
5336
|
column: location.column
|
|
4407
5337
|
});
|
|
4408
5338
|
}
|
|
5339
|
+
function warnIfSchema25Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
5340
|
+
if (!isSchemaOlderThan(sourceSchemaVersion, "2.5")) {
|
|
5341
|
+
return;
|
|
5342
|
+
}
|
|
5343
|
+
diagnostics.push({
|
|
5344
|
+
code: "parse.schema25.featureCompatibility",
|
|
5345
|
+
severity: "warning",
|
|
5346
|
+
source: "parse",
|
|
5347
|
+
message: `Feature "${featureName}" requires schema 2.5; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
5348
|
+
line: location.line,
|
|
5349
|
+
column: location.column
|
|
5350
|
+
});
|
|
5351
|
+
}
|
|
5352
|
+
function isSchemaOlderThan(sourceSchemaVersion, requiredVersion) {
|
|
5353
|
+
return schemaVersionRank(sourceSchemaVersion) < schemaVersionRank(requiredVersion);
|
|
5354
|
+
}
|
|
5355
|
+
function schemaVersionRank(version) {
|
|
5356
|
+
switch (version) {
|
|
5357
|
+
case "2.0-draft":
|
|
5358
|
+
return 0;
|
|
5359
|
+
case "2.0":
|
|
5360
|
+
return 1;
|
|
5361
|
+
case "2.1":
|
|
5362
|
+
return 2;
|
|
5363
|
+
case "2.5":
|
|
5364
|
+
return 3;
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
4409
5367
|
function preprocessAtlasSource(source) {
|
|
4410
5368
|
const chars = [...source];
|
|
4411
5369
|
const comments = [];
|
|
@@ -4493,8 +5451,9 @@
|
|
|
4493
5451
|
}
|
|
4494
5452
|
|
|
4495
5453
|
// packages/core/dist/load.js
|
|
4496
|
-
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
|
|
5454
|
+
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1|\.5)?$/i;
|
|
4497
5455
|
var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
|
|
5456
|
+
var ATLAS_SCHEMA_25_PATTERN = /^schema\s+2\.5$/i;
|
|
4498
5457
|
var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
|
|
4499
5458
|
function detectWorldOrbitSchemaVersion(source) {
|
|
4500
5459
|
for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
|
|
@@ -4508,6 +5467,9 @@
|
|
|
4508
5467
|
if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
|
|
4509
5468
|
return "2.1";
|
|
4510
5469
|
}
|
|
5470
|
+
if (ATLAS_SCHEMA_25_PATTERN.test(trimmed)) {
|
|
5471
|
+
return "2.5";
|
|
5472
|
+
}
|
|
4511
5473
|
if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
|
|
4512
5474
|
return "2.0";
|
|
4513
5475
|
}
|
|
@@ -4568,7 +5530,7 @@
|
|
|
4568
5530
|
}
|
|
4569
5531
|
function loadWorldOrbitSourceWithDiagnostics(source) {
|
|
4570
5532
|
const schemaVersion = detectWorldOrbitSchemaVersion(source);
|
|
4571
|
-
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
|
|
5533
|
+
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1" || schemaVersion === "2.5") {
|
|
4572
5534
|
return loadAtlasSourceWithDiagnostics(source, schemaVersion);
|
|
4573
5535
|
}
|
|
4574
5536
|
let ast;
|
|
@@ -4837,6 +5799,7 @@
|
|
|
4837
5799
|
const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
|
|
4838
5800
|
const leaderMarkup = layers.guides ? scene.leaders.filter((leader) => !leader.hidden).filter((leader) => visibleObjectIds.has(leader.objectId)).filter((leader) => layers.structures || !isStructureLike(leader.object)).map((leader) => `<line class="wo-leader wo-leader-${leader.mode}" x1="${leader.x1}" y1="${leader.y1}" x2="${leader.x2}" y2="${leader.y2}" data-render-id="${escapeXml(leader.renderId)}" data-group-id="${escapeAttribute(leader.groupId ?? "")}" />`).join("") : "";
|
|
4839
5801
|
const relationMarkup = layers.relations ? scene.relations.filter((relation) => !relation.hidden).filter((relation) => visibleObjectIds.has(relation.fromObjectId) && visibleObjectIds.has(relation.toObjectId)).map((relation) => `<line class="wo-relation" x1="${relation.x1}" y1="${relation.y1}" x2="${relation.x2}" y2="${relation.y2}" data-render-id="${escapeXml(relation.renderId)}" data-relation-id="${escapeAttribute(relation.relationId)}" />`).join("") : "";
|
|
5802
|
+
const eventMarkup = layers.events ? scene.events.filter((event) => !event.hidden).map((event) => renderSceneEventOverlay(scene, event, visibleObjectIds, theme)).join("") : "";
|
|
4840
5803
|
const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
|
|
4841
5804
|
const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
|
|
4842
5805
|
const metadataMarkup = layers.metadata ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
|
|
@@ -4872,6 +5835,9 @@
|
|
|
4872
5835
|
.wo-orbit-front { opacity: 0.9; }
|
|
4873
5836
|
.wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
|
|
4874
5837
|
.wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
|
|
5838
|
+
.wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
|
|
5839
|
+
.wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
|
|
5840
|
+
.wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
4875
5841
|
.wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
|
|
4876
5842
|
.wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
|
|
4877
5843
|
.wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
|
|
@@ -4906,6 +5872,7 @@
|
|
|
4906
5872
|
${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
|
|
4907
5873
|
${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
|
|
4908
5874
|
${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
|
|
5875
|
+
${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
|
|
4909
5876
|
${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
|
|
4910
5877
|
${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
|
|
4911
5878
|
${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
|
|
@@ -4913,6 +5880,20 @@
|
|
|
4913
5880
|
</g>
|
|
4914
5881
|
</g>
|
|
4915
5882
|
</svg>`;
|
|
5883
|
+
}
|
|
5884
|
+
function renderSceneEventOverlay(scene, event, visibleObjectIds, theme) {
|
|
5885
|
+
const participants = event.objectIds.filter((objectId) => visibleObjectIds.has(objectId)).map((objectId) => scene.objects.find((object) => object.objectId === objectId && !object.hidden)).filter(Boolean);
|
|
5886
|
+
if (participants.length === 0) {
|
|
5887
|
+
return "";
|
|
5888
|
+
}
|
|
5889
|
+
const stroke = event.event.color || theme.accent;
|
|
5890
|
+
const label = event.event.label || event.event.id;
|
|
5891
|
+
const lineMarkup = participants.map((object) => `<line class="wo-event-line" x1="${event.x}" y1="${event.y}" x2="${object.x}" y2="${object.y}" stroke="${escapeAttribute(stroke)}" data-event-id="${escapeAttribute(event.eventId)}" data-object-id="${escapeAttribute(object.objectId)}" />`).join("");
|
|
5892
|
+
return `<g class="wo-event" data-render-id="${escapeXml(event.renderId)}" data-event-id="${escapeAttribute(event.eventId)}">
|
|
5893
|
+
${lineMarkup}
|
|
5894
|
+
<circle class="wo-event-node" cx="${event.x}" cy="${event.y}" r="5" fill="${escapeAttribute(stroke)}" />
|
|
5895
|
+
<text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
|
|
5896
|
+
</g>`;
|
|
4916
5897
|
}
|
|
4917
5898
|
function renderDocumentToSvg(document2, options = {}) {
|
|
4918
5899
|
return renderSceneToSvg(renderDocumentToScene(document2, options), options);
|
|
@@ -5512,6 +6493,13 @@
|
|
|
5512
6493
|
value: `${details.object.resonance.targetObjectId} ${details.object.resonance.ratio}`
|
|
5513
6494
|
});
|
|
5514
6495
|
}
|
|
6496
|
+
if (details.relatedEvents.length > 0) {
|
|
6497
|
+
fields.set("events", {
|
|
6498
|
+
key: "events",
|
|
6499
|
+
label: "Events",
|
|
6500
|
+
value: details.relatedEvents.map((event) => event.event.label || event.event.id).join(", ")
|
|
6501
|
+
});
|
|
6502
|
+
}
|
|
5515
6503
|
if (placement?.mode === "at") {
|
|
5516
6504
|
fields.set("placement", {
|
|
5517
6505
|
key: "placement",
|
|
@@ -5616,6 +6604,7 @@
|
|
|
5616
6604
|
padding: options.padding,
|
|
5617
6605
|
preset: options.preset,
|
|
5618
6606
|
projection: options.projection,
|
|
6607
|
+
camera: options.camera ? { ...options.camera } : null,
|
|
5619
6608
|
scaleModel: options.scaleModel ? { ...options.scaleModel } : void 0,
|
|
5620
6609
|
theme: options.theme,
|
|
5621
6610
|
layers: options.layers,
|
|
@@ -5904,6 +6893,11 @@
|
|
|
5904
6893
|
if (currentInput.kind !== "scene" && viewpoint.projection !== scene.projection) {
|
|
5905
6894
|
nextRenderOptions.projection = viewpoint.projection;
|
|
5906
6895
|
}
|
|
6896
|
+
if (viewpoint.camera) {
|
|
6897
|
+
nextRenderOptions.camera = { ...viewpoint.camera };
|
|
6898
|
+
} else if (renderOptions.camera) {
|
|
6899
|
+
nextRenderOptions.camera = null;
|
|
6900
|
+
}
|
|
5907
6901
|
if (viewpointLayers) {
|
|
5908
6902
|
nextRenderOptions.layers = viewpointLayers;
|
|
5909
6903
|
}
|
|
@@ -5926,6 +6920,12 @@
|
|
|
5926
6920
|
emitAtlasStateChange();
|
|
5927
6921
|
return true;
|
|
5928
6922
|
},
|
|
6923
|
+
getActiveEventId() {
|
|
6924
|
+
return renderOptions.activeEventId ?? null;
|
|
6925
|
+
},
|
|
6926
|
+
setActiveEvent(id) {
|
|
6927
|
+
api.setRenderOptions({ activeEventId: id });
|
|
6928
|
+
},
|
|
5929
6929
|
search(query, limit = 12) {
|
|
5930
6930
|
return searchSceneObjects(scene, query, limit);
|
|
5931
6931
|
},
|
|
@@ -6190,6 +7190,7 @@
|
|
|
6190
7190
|
orbit: scene.orbitVisuals.find((orbit) => orbit.objectId === renderObject.objectId && !orbit.hidden) ?? null,
|
|
6191
7191
|
relatedOrbits: scene.orbitVisuals.filter((orbit) => !orbit.hidden && (orbit.objectId === renderObject.objectId || renderObject.ancestorIds.includes(orbit.objectId) || renderObject.childIds.includes(orbit.objectId))),
|
|
6192
7192
|
relations: scene.relations.filter((relation) => !relation.hidden && (relation.fromObjectId === renderObject.objectId || relation.toObjectId === renderObject.objectId)),
|
|
7193
|
+
relatedEvents: scene.events.filter((event) => !event.hidden && (event.targetObjectId === renderObject.objectId || event.objectIds.includes(renderObject.objectId))),
|
|
6193
7194
|
parent: getObjectById(renderObject.parentId),
|
|
6194
7195
|
children: renderObject.childIds.map((childId) => getObjectById(childId)).filter(Boolean),
|
|
6195
7196
|
ancestors: renderObject.ancestorIds.map((ancestorId) => getObjectById(ancestorId)).filter(Boolean),
|
|
@@ -6503,16 +7504,19 @@
|
|
|
6503
7504
|
function cloneRenderOptions(renderOptions) {
|
|
6504
7505
|
return {
|
|
6505
7506
|
...renderOptions,
|
|
7507
|
+
camera: renderOptions.camera ? { ...renderOptions.camera } : null,
|
|
6506
7508
|
filter: renderOptions.filter ? { ...renderOptions.filter } : void 0,
|
|
6507
7509
|
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
|
|
6508
7510
|
layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
|
|
6509
|
-
theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme
|
|
7511
|
+
theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme,
|
|
7512
|
+
activeEventId: renderOptions.activeEventId ?? null
|
|
6510
7513
|
};
|
|
6511
7514
|
}
|
|
6512
7515
|
function mergeRenderOptions(current, next) {
|
|
6513
7516
|
return {
|
|
6514
7517
|
...current,
|
|
6515
7518
|
...next,
|
|
7519
|
+
camera: next.camera !== void 0 ? next.camera ? { ...next.camera } : null : current.camera ? { ...current.camera } : null,
|
|
6516
7520
|
filter: next.filter !== void 0 ? normalizeViewerFilter(next.filter) : current.filter ? { ...current.filter } : void 0,
|
|
6517
7521
|
scaleModel: next.scaleModel ? {
|
|
6518
7522
|
...current.scaleModel ?? {},
|
|
@@ -6526,7 +7530,7 @@
|
|
|
6526
7530
|
};
|
|
6527
7531
|
}
|
|
6528
7532
|
function hasSceneAffectingRenderOptions(options) {
|
|
6529
|
-
return options.width !== void 0 || options.height !== void 0 || options.padding !== void 0 || options.preset !== void 0 || options.projection !== void 0 || options.scaleModel !== void 0;
|
|
7533
|
+
return options.width !== void 0 || options.height !== void 0 || options.padding !== void 0 || options.preset !== void 0 || options.projection !== void 0 || options.camera !== void 0 || options.scaleModel !== void 0 || options.activeEventId !== void 0;
|
|
6530
7534
|
}
|
|
6531
7535
|
function resolveSourceRenderOptions2(loaded, renderOptions) {
|
|
6532
7536
|
const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
|
|
@@ -7123,6 +8127,8 @@
|
|
|
7123
8127
|
}
|
|
7124
8128
|
function buildInspectorSnapshot() {
|
|
7125
8129
|
const activeViewer = requireViewer();
|
|
8130
|
+
const scene = activeViewer.getScene();
|
|
8131
|
+
const camera = scene.camera;
|
|
7126
8132
|
return {
|
|
7127
8133
|
selection: activeViewer.getSelectionDetails(),
|
|
7128
8134
|
activeViewpoint: activeViewer.getActiveViewpoint(),
|
|
@@ -7130,13 +8136,21 @@
|
|
|
7130
8136
|
atlasState: activeViewer.getAtlasState(),
|
|
7131
8137
|
visibleObjectIds: activeViewer.getVisibleObjects().map((object) => object.objectId),
|
|
7132
8138
|
scene: {
|
|
7133
|
-
title:
|
|
7134
|
-
projection:
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
8139
|
+
title: scene.title,
|
|
8140
|
+
projection: scene.projection,
|
|
8141
|
+
renderProjection: scene.renderProjection,
|
|
8142
|
+
camera: camera ? {
|
|
8143
|
+
azimuth: camera.azimuth,
|
|
8144
|
+
elevation: camera.elevation,
|
|
8145
|
+
roll: camera.roll,
|
|
8146
|
+
distance: camera.distance
|
|
8147
|
+
} : null,
|
|
8148
|
+
renderPreset: scene.renderPreset,
|
|
8149
|
+
groupCount: scene.groups.length,
|
|
8150
|
+
semanticGroupCount: scene.semanticGroups.length,
|
|
8151
|
+
relationCount: scene.relations.length,
|
|
8152
|
+
eventCount: scene.events.length,
|
|
8153
|
+
viewpointCount: scene.viewpoints.length
|
|
7140
8154
|
}
|
|
7141
8155
|
};
|
|
7142
8156
|
}
|