worldorbit 2.5.15 → 2.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/browser/core/dist/index.js +2444 -342
- package/dist/browser/editor/dist/index.js +11702 -0
- package/dist/browser/markdown/dist/index.js +2207 -392
- package/dist/browser/viewer/dist/index.js +2302 -382
- package/dist/unpkg/core/dist/index.js +2447 -345
- package/dist/unpkg/editor/dist/index.js +11727 -0
- package/dist/unpkg/markdown/dist/index.js +2210 -395
- package/dist/unpkg/viewer/dist/index.js +2305 -385
- package/dist/unpkg/worldorbit-core.min.js +12 -12
- package/dist/unpkg/worldorbit-editor.min.js +894 -0
- package/dist/unpkg/worldorbit-markdown.min.js +66 -58
- package/dist/unpkg/worldorbit-viewer.min.js +76 -68
- package/dist/unpkg/worldorbit.js +797 -78
- package/dist/unpkg/worldorbit.min.js +80 -72
- package/package.json +3 -2
- package/packages/core/dist/atlas-edit.js +74 -0
- package/packages/core/dist/atlas-validate.js +122 -8
- package/packages/core/dist/draft-parse.js +212 -8
- package/packages/core/dist/draft.d.ts +5 -2
- package/packages/core/dist/draft.js +59 -3
- package/packages/core/dist/format.js +63 -1
- package/packages/core/dist/normalize.js +1 -0
- package/packages/core/dist/scene.js +248 -46
- package/packages/core/dist/types.d.ts +41 -2
- package/packages/editor/dist/editor.d.ts +2 -0
- package/packages/editor/dist/editor.js +3578 -0
- package/packages/editor/dist/index.d.ts +2 -0
- package/packages/editor/dist/index.js +1 -0
- package/packages/editor/dist/types.d.ts +55 -0
- package/packages/editor/dist/types.js +1 -0
- package/packages/markdown/dist/html.d.ts +3 -0
- package/packages/markdown/dist/html.js +57 -0
- package/packages/markdown/dist/index.d.ts +4 -0
- package/packages/markdown/dist/index.js +3 -0
- package/packages/markdown/dist/rehype.d.ts +10 -0
- package/packages/markdown/dist/rehype.js +49 -0
- package/packages/markdown/dist/remark.d.ts +9 -0
- package/packages/markdown/dist/remark.js +28 -0
- package/packages/markdown/dist/types.d.ts +11 -0
- package/packages/markdown/dist/types.js +1 -0
- package/packages/viewer/dist/atlas-state.js +6 -0
- package/packages/viewer/dist/atlas-viewer.js +1 -0
- 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 +8 -1
- package/packages/viewer/dist/viewer.js +12 -1
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
var DEFAULT_LAYERS = {
|
|
5
5
|
background: true,
|
|
6
6
|
guides: true,
|
|
7
|
+
relations: true,
|
|
8
|
+
events: true,
|
|
7
9
|
orbits: true,
|
|
8
10
|
objects: true,
|
|
9
11
|
labels: true,
|
|
@@ -18,6 +20,7 @@
|
|
|
18
20
|
backgroundGlow: "rgba(240, 180, 100, 0.18)",
|
|
19
21
|
panel: "rgba(7, 17, 27, 0.9)",
|
|
20
22
|
panelLine: "rgba(168, 207, 242, 0.18)",
|
|
23
|
+
relation: "rgba(240, 180, 100, 0.42)",
|
|
21
24
|
orbit: "rgba(163, 209, 255, 0.24)",
|
|
22
25
|
orbitBand: "rgba(255, 190, 120, 0.28)",
|
|
23
26
|
guide: "rgba(255, 255, 255, 0.04)",
|
|
@@ -40,6 +43,7 @@
|
|
|
40
43
|
backgroundGlow: "rgba(120, 255, 215, 0.16)",
|
|
41
44
|
panel: "rgba(7, 20, 30, 0.9)",
|
|
42
45
|
panelLine: "rgba(120, 255, 215, 0.16)",
|
|
46
|
+
relation: "rgba(156, 231, 255, 0.42)",
|
|
43
47
|
orbit: "rgba(120, 255, 215, 0.2)",
|
|
44
48
|
orbitBand: "rgba(137, 185, 255, 0.24)",
|
|
45
49
|
guide: "rgba(255, 255, 255, 0.035)",
|
|
@@ -62,6 +66,7 @@
|
|
|
62
66
|
backgroundGlow: "rgba(255, 127, 95, 0.18)",
|
|
63
67
|
panel: "rgba(24, 9, 13, 0.9)",
|
|
64
68
|
panelLine: "rgba(255, 166, 149, 0.16)",
|
|
69
|
+
relation: "rgba(255, 178, 125, 0.42)",
|
|
65
70
|
orbit: "rgba(255, 188, 164, 0.22)",
|
|
66
71
|
orbitBand: "rgba(255, 214, 139, 0.24)",
|
|
67
72
|
guide: "rgba(255, 255, 255, 0.03)",
|
|
@@ -152,12 +157,14 @@
|
|
|
152
157
|
return {
|
|
153
158
|
version: "2.0",
|
|
154
159
|
viewpointId,
|
|
160
|
+
activeEventId: renderOptions.activeEventId ?? null,
|
|
155
161
|
viewerState: { ...viewerState },
|
|
156
162
|
renderOptions: {
|
|
157
163
|
preset: renderOptions.preset,
|
|
158
164
|
projection: renderOptions.projection,
|
|
159
165
|
layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
|
|
160
|
-
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0
|
|
166
|
+
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
|
|
167
|
+
activeEventId: renderOptions.activeEventId ?? null
|
|
161
168
|
},
|
|
162
169
|
filter: normalizeViewerFilter(filter)
|
|
163
170
|
};
|
|
@@ -170,6 +177,7 @@
|
|
|
170
177
|
return {
|
|
171
178
|
version: "2.0",
|
|
172
179
|
viewpointId: raw.viewpointId ?? null,
|
|
180
|
+
activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null,
|
|
173
181
|
viewerState: {
|
|
174
182
|
scale: raw.viewerState?.scale ?? 1,
|
|
175
183
|
rotationDeg: raw.viewerState?.rotationDeg ?? 0,
|
|
@@ -181,7 +189,8 @@
|
|
|
181
189
|
preset: raw.renderOptions?.preset,
|
|
182
190
|
projection: raw.renderOptions?.projection,
|
|
183
191
|
layers: raw.renderOptions?.layers ? { ...raw.renderOptions.layers } : void 0,
|
|
184
|
-
scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0
|
|
192
|
+
scaleModel: raw.renderOptions?.scaleModel ? { ...raw.renderOptions.scaleModel } : void 0,
|
|
193
|
+
activeEventId: raw.activeEventId ?? raw.renderOptions?.activeEventId ?? null
|
|
185
194
|
},
|
|
186
195
|
filter: normalizeViewerFilter(raw.filter ?? null)
|
|
187
196
|
};
|
|
@@ -197,7 +206,8 @@
|
|
|
197
206
|
renderOptions: {
|
|
198
207
|
...atlasState.renderOptions,
|
|
199
208
|
layers: atlasState.renderOptions.layers ? { ...atlasState.renderOptions.layers } : void 0,
|
|
200
|
-
scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0
|
|
209
|
+
scaleModel: atlasState.renderOptions.scaleModel ? { ...atlasState.renderOptions.scaleModel } : void 0,
|
|
210
|
+
activeEventId: atlasState.renderOptions.activeEventId ?? null
|
|
201
211
|
},
|
|
202
212
|
filter: atlasState.filter ? { ...atlasState.filter } : null
|
|
203
213
|
}
|
|
@@ -214,6 +224,8 @@
|
|
|
214
224
|
return {
|
|
215
225
|
background: viewpoint.layers.background,
|
|
216
226
|
guides: viewpoint.layers.guides,
|
|
227
|
+
relations: viewpoint.layers.relations,
|
|
228
|
+
events: viewpoint.layers.events,
|
|
217
229
|
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,
|
|
218
230
|
objects: viewpoint.layers.objects,
|
|
219
231
|
labels: viewpoint.layers.labels,
|
|
@@ -251,7 +263,11 @@
|
|
|
251
263
|
return false;
|
|
252
264
|
}
|
|
253
265
|
if (filter.groupIds?.length && (!object.groupId || !filter.groupIds.includes(object.groupId))) {
|
|
254
|
-
|
|
266
|
+
const hasSemanticMatch = object.semanticGroupIds.length > 0 && filter.groupIds.some((groupId) => object.semanticGroupIds.includes(groupId));
|
|
267
|
+
const hasLegacyMatch = Boolean(object.groupId && filter.groupIds.includes(object.groupId));
|
|
268
|
+
if (!hasSemanticMatch && !hasLegacyMatch) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
255
271
|
}
|
|
256
272
|
if (filter.tags?.length) {
|
|
257
273
|
const objectTags = Array.isArray(object.object.properties.tags) ? object.object.properties.tags.filter((entry) => typeof entry === "string") : [];
|
|
@@ -618,13 +634,13 @@
|
|
|
618
634
|
function unitFamilyAllowsUnit(family, unit) {
|
|
619
635
|
switch (family) {
|
|
620
636
|
case "distance":
|
|
621
|
-
return unit === null || ["au", "km", "re", "sol"].includes(unit);
|
|
637
|
+
return unit === null || ["au", "km", "m", "ly", "pc", "kpc", "re", "sol"].includes(unit);
|
|
622
638
|
case "radius":
|
|
623
|
-
return unit === null || ["km", "re", "sol"].includes(unit);
|
|
639
|
+
return unit === null || ["km", "m", "re", "rj", "sol"].includes(unit);
|
|
624
640
|
case "mass":
|
|
625
|
-
return unit === null || ["me", "sol"].includes(unit);
|
|
641
|
+
return unit === null || ["me", "mj", "sol"].includes(unit);
|
|
626
642
|
case "duration":
|
|
627
|
-
return unit === null || ["h", "d", "y"].includes(unit);
|
|
643
|
+
return unit === null || ["s", "min", "h", "d", "y", "ky", "my", "gy"].includes(unit);
|
|
628
644
|
case "angle":
|
|
629
645
|
return unit === null || unit === "deg";
|
|
630
646
|
case "generic":
|
|
@@ -828,7 +844,7 @@
|
|
|
828
844
|
}
|
|
829
845
|
|
|
830
846
|
// packages/core/dist/normalize.js
|
|
831
|
-
var UNIT_PATTERN = /^(-?\d+(?:\.\d+)?)(au|km|re|sol|
|
|
847
|
+
var UNIT_PATTERN = /^(-?\d+(?:\.\d+)?)(kpc|min|mj|rj|ky|my|gy|au|km|me|re|pc|ly|deg|sol|K|m|s|h|d|y)?$/;
|
|
832
848
|
var BOOLEAN_VALUES = /* @__PURE__ */ new Map([
|
|
833
849
|
["true", true],
|
|
834
850
|
["false", false],
|
|
@@ -853,7 +869,11 @@
|
|
|
853
869
|
return {
|
|
854
870
|
format: "worldorbit",
|
|
855
871
|
version: "1.0",
|
|
872
|
+
schemaVersion: "1.0",
|
|
856
873
|
system,
|
|
874
|
+
groups: [],
|
|
875
|
+
relations: [],
|
|
876
|
+
events: [],
|
|
857
877
|
objects
|
|
858
878
|
};
|
|
859
879
|
}
|
|
@@ -863,13 +883,17 @@
|
|
|
863
883
|
const fieldMap = collectFields(mergedFields);
|
|
864
884
|
const placement = extractPlacement(node.objectType, fieldMap);
|
|
865
885
|
const properties = normalizeProperties(fieldMap);
|
|
866
|
-
const
|
|
886
|
+
const info2 = normalizeInfo(node.infoEntries);
|
|
867
887
|
if (node.objectType === "system") {
|
|
868
888
|
return {
|
|
869
889
|
type: "system",
|
|
870
890
|
id: node.name,
|
|
891
|
+
title: typeof properties.title === "string" ? properties.title : null,
|
|
892
|
+
description: null,
|
|
893
|
+
epoch: null,
|
|
894
|
+
referencePlane: null,
|
|
871
895
|
properties,
|
|
872
|
-
info
|
|
896
|
+
info: info2
|
|
873
897
|
};
|
|
874
898
|
}
|
|
875
899
|
return {
|
|
@@ -877,7 +901,7 @@
|
|
|
877
901
|
id: node.name,
|
|
878
902
|
properties,
|
|
879
903
|
placement,
|
|
880
|
-
info
|
|
904
|
+
info: info2
|
|
881
905
|
};
|
|
882
906
|
}
|
|
883
907
|
function validateFieldCompatibility(objectType, fields) {
|
|
@@ -1007,14 +1031,14 @@
|
|
|
1007
1031
|
}
|
|
1008
1032
|
}
|
|
1009
1033
|
function normalizeInfo(entries) {
|
|
1010
|
-
const
|
|
1034
|
+
const info2 = {};
|
|
1011
1035
|
for (const entry of entries) {
|
|
1012
|
-
if (entry.key in
|
|
1036
|
+
if (entry.key in info2) {
|
|
1013
1037
|
throw WorldOrbitError.fromLocation(`Duplicate info key "${entry.key}"`, entry.location);
|
|
1014
1038
|
}
|
|
1015
|
-
|
|
1039
|
+
info2[entry.key] = entry.value;
|
|
1016
1040
|
}
|
|
1017
|
-
return
|
|
1041
|
+
return info2;
|
|
1018
1042
|
}
|
|
1019
1043
|
function parseAtReference(target, location) {
|
|
1020
1044
|
if (/^[A-Za-z0-9._-]+-[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
@@ -1185,37 +1209,41 @@
|
|
|
1185
1209
|
}
|
|
1186
1210
|
|
|
1187
1211
|
// packages/core/dist/diagnostics.js
|
|
1188
|
-
function diagnosticFromError(
|
|
1189
|
-
if (
|
|
1212
|
+
function diagnosticFromError(error2, source, code = `${source}.failed`) {
|
|
1213
|
+
if (error2 instanceof WorldOrbitError) {
|
|
1190
1214
|
return {
|
|
1191
1215
|
code,
|
|
1192
1216
|
severity: "error",
|
|
1193
1217
|
source,
|
|
1194
|
-
message:
|
|
1195
|
-
line:
|
|
1196
|
-
column:
|
|
1218
|
+
message: error2.message,
|
|
1219
|
+
line: error2.line,
|
|
1220
|
+
column: error2.column
|
|
1197
1221
|
};
|
|
1198
1222
|
}
|
|
1199
|
-
if (
|
|
1223
|
+
if (error2 instanceof Error) {
|
|
1200
1224
|
return {
|
|
1201
1225
|
code,
|
|
1202
1226
|
severity: "error",
|
|
1203
1227
|
source,
|
|
1204
|
-
message:
|
|
1228
|
+
message: error2.message
|
|
1205
1229
|
};
|
|
1206
1230
|
}
|
|
1207
1231
|
return {
|
|
1208
1232
|
code,
|
|
1209
1233
|
severity: "error",
|
|
1210
1234
|
source,
|
|
1211
|
-
message: String(
|
|
1235
|
+
message: String(error2)
|
|
1212
1236
|
};
|
|
1213
1237
|
}
|
|
1214
1238
|
|
|
1215
1239
|
// packages/core/dist/scene.js
|
|
1216
1240
|
var AU_IN_KM = 1495978707e-1;
|
|
1217
1241
|
var EARTH_RADIUS_IN_KM = 6371;
|
|
1242
|
+
var JUPITER_RADIUS_IN_KM = 71492;
|
|
1218
1243
|
var SOLAR_RADIUS_IN_KM = 695700;
|
|
1244
|
+
var LY_IN_AU = 63241.077;
|
|
1245
|
+
var PC_IN_AU = 206264.806;
|
|
1246
|
+
var KPC_IN_AU = 206264806;
|
|
1219
1247
|
var ISO_FLATTENING = 0.68;
|
|
1220
1248
|
var MIN_ISO_MINOR_SCALE = 0.2;
|
|
1221
1249
|
var ARC_SAMPLE_COUNT = 28;
|
|
@@ -1229,8 +1257,10 @@
|
|
|
1229
1257
|
const scaleModel = resolveScaleModel(layoutPreset, options.scaleModel);
|
|
1230
1258
|
const spacingFactor = layoutPresetSpacing(layoutPreset);
|
|
1231
1259
|
const systemId = document2.system?.id ?? null;
|
|
1232
|
-
const
|
|
1233
|
-
const
|
|
1260
|
+
const activeEventId = options.activeEventId ?? null;
|
|
1261
|
+
const effectiveObjects = createEffectiveObjects(document2.objects, document2.events ?? [], activeEventId);
|
|
1262
|
+
const objectMap = new Map(effectiveObjects.map((object) => [object.id, object]));
|
|
1263
|
+
const relationships = buildSceneRelationships(effectiveObjects, objectMap);
|
|
1234
1264
|
const positions = /* @__PURE__ */ new Map();
|
|
1235
1265
|
const orbitDrafts = [];
|
|
1236
1266
|
const leaderDrafts = [];
|
|
@@ -1239,7 +1269,7 @@
|
|
|
1239
1269
|
const atObjects = [];
|
|
1240
1270
|
const surfaceChildren = /* @__PURE__ */ new Map();
|
|
1241
1271
|
const orbitChildren = /* @__PURE__ */ new Map();
|
|
1242
|
-
for (const object of
|
|
1272
|
+
for (const object of effectiveObjects) {
|
|
1243
1273
|
const placement = object.placement;
|
|
1244
1274
|
if (!placement) {
|
|
1245
1275
|
rootObjects.push(object);
|
|
@@ -1334,11 +1364,14 @@
|
|
|
1334
1364
|
const objects = [...positions.values()].map((position) => createSceneObject(position, scaleModel, relationships));
|
|
1335
1365
|
const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft, relationships.groupIds.get(draft.object.id) ?? null));
|
|
1336
1366
|
const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
|
|
1337
|
-
const labels = createSceneLabels(objects, height, scaleModel.labelMultiplier);
|
|
1338
|
-
const
|
|
1339
|
-
const
|
|
1367
|
+
const labels = createSceneLabels(objects, width, height, scaleModel.labelMultiplier);
|
|
1368
|
+
const relations = createSceneRelations(document2, objects);
|
|
1369
|
+
const events = createSceneEvents(document2.events ?? [], objects, activeEventId);
|
|
1370
|
+
const layers = createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels);
|
|
1371
|
+
const groups = createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, scaleModel.labelMultiplier);
|
|
1372
|
+
const semanticGroups = createSceneSemanticGroups(document2, objects);
|
|
1340
1373
|
const viewpoints = createSceneViewpoints(document2, projection, frame.preset, relationships, objectMap);
|
|
1341
|
-
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels);
|
|
1374
|
+
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, scaleModel.labelMultiplier);
|
|
1342
1375
|
return {
|
|
1343
1376
|
width,
|
|
1344
1377
|
height,
|
|
@@ -1346,7 +1379,7 @@
|
|
|
1346
1379
|
renderPreset: frame.preset,
|
|
1347
1380
|
projection,
|
|
1348
1381
|
scaleModel,
|
|
1349
|
-
title: String(document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1382
|
+
title: String(document2.system?.title ?? document2.system?.properties.title ?? document2.system?.id ?? "WorldOrbit") || "WorldOrbit",
|
|
1350
1383
|
subtitle: `${capitalizeLabel(projection)} view - ${capitalizeLabel(layoutPreset)} layout`,
|
|
1351
1384
|
systemId,
|
|
1352
1385
|
viewMode: projection,
|
|
@@ -1362,9 +1395,13 @@
|
|
|
1362
1395
|
contentBounds,
|
|
1363
1396
|
layers,
|
|
1364
1397
|
groups,
|
|
1398
|
+
semanticGroups,
|
|
1365
1399
|
viewpoints,
|
|
1400
|
+
events,
|
|
1401
|
+
activeEventId,
|
|
1366
1402
|
objects,
|
|
1367
1403
|
orbitVisuals,
|
|
1404
|
+
relations,
|
|
1368
1405
|
leaders,
|
|
1369
1406
|
labels
|
|
1370
1407
|
};
|
|
@@ -1380,6 +1417,35 @@
|
|
|
1380
1417
|
y: center.y + dx * sin + dy * cos
|
|
1381
1418
|
};
|
|
1382
1419
|
}
|
|
1420
|
+
function createEffectiveObjects(objects, events, activeEventId) {
|
|
1421
|
+
const cloned = objects.map((object) => structuredClone(object));
|
|
1422
|
+
if (!activeEventId) {
|
|
1423
|
+
return cloned;
|
|
1424
|
+
}
|
|
1425
|
+
const activeEvent = events.find((event) => event.id === activeEventId);
|
|
1426
|
+
if (!activeEvent) {
|
|
1427
|
+
return cloned;
|
|
1428
|
+
}
|
|
1429
|
+
const objectMap = new Map(cloned.map((object) => [object.id, object]));
|
|
1430
|
+
for (const pose of activeEvent.positions) {
|
|
1431
|
+
const object = objectMap.get(pose.objectId);
|
|
1432
|
+
if (!object) {
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
object.placement = pose.placement ? structuredClone(pose.placement) : null;
|
|
1436
|
+
if (pose.inner) {
|
|
1437
|
+
object.properties.inner = { ...pose.inner };
|
|
1438
|
+
} else {
|
|
1439
|
+
delete object.properties.inner;
|
|
1440
|
+
}
|
|
1441
|
+
if (pose.outer) {
|
|
1442
|
+
object.properties.outer = { ...pose.outer };
|
|
1443
|
+
} else {
|
|
1444
|
+
delete object.properties.outer;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return cloned;
|
|
1448
|
+
}
|
|
1383
1449
|
function resolveLayoutPreset(document2) {
|
|
1384
1450
|
const rawScale = String(document2.system?.properties.scale ?? "balanced").toLowerCase();
|
|
1385
1451
|
switch (rawScale) {
|
|
@@ -1474,6 +1540,7 @@
|
|
|
1474
1540
|
}
|
|
1475
1541
|
function createSceneObject(position, scaleModel, relationships) {
|
|
1476
1542
|
const { object, x, y, radius, sortKey, anchorX, anchorY } = position;
|
|
1543
|
+
const renderPriority = object.renderHints?.renderPriority ?? 0;
|
|
1477
1544
|
return {
|
|
1478
1545
|
renderId: createRenderId(object.id),
|
|
1479
1546
|
objectId: object.id,
|
|
@@ -1482,11 +1549,12 @@
|
|
|
1482
1549
|
ancestorIds: relationships.ancestorIds.get(object.id) ?? [],
|
|
1483
1550
|
childIds: relationships.childIds.get(object.id) ?? [],
|
|
1484
1551
|
groupId: relationships.groupIds.get(object.id) ?? null,
|
|
1552
|
+
semanticGroupIds: [...object.groups ?? []],
|
|
1485
1553
|
x,
|
|
1486
1554
|
y,
|
|
1487
1555
|
radius,
|
|
1488
1556
|
visualRadius: visualExtentForObject(object, radius, scaleModel),
|
|
1489
|
-
sortKey,
|
|
1557
|
+
sortKey: sortKey + renderPriority * 1e-3,
|
|
1490
1558
|
anchorX,
|
|
1491
1559
|
anchorY,
|
|
1492
1560
|
label: object.id,
|
|
@@ -1503,6 +1571,7 @@
|
|
|
1503
1571
|
object: draft.object,
|
|
1504
1572
|
parentId: draft.parentId,
|
|
1505
1573
|
groupId,
|
|
1574
|
+
semanticGroupIds: [...draft.object.groups ?? []],
|
|
1506
1575
|
kind: draft.kind,
|
|
1507
1576
|
cx: draft.cx,
|
|
1508
1577
|
cy: draft.cy,
|
|
@@ -1514,7 +1583,7 @@
|
|
|
1514
1583
|
bandThickness: draft.bandThickness,
|
|
1515
1584
|
frontArcPath: draft.frontArcPath,
|
|
1516
1585
|
backArcPath: draft.backArcPath,
|
|
1517
|
-
hidden: draft.object.properties.hidden === true
|
|
1586
|
+
hidden: draft.object.properties.hidden === true || draft.object.renderHints?.renderOrbit === false
|
|
1518
1587
|
};
|
|
1519
1588
|
}
|
|
1520
1589
|
function createLeaderLine(draft) {
|
|
@@ -1523,6 +1592,7 @@
|
|
|
1523
1592
|
objectId: draft.object.id,
|
|
1524
1593
|
object: draft.object,
|
|
1525
1594
|
groupId: draft.groupId,
|
|
1595
|
+
semanticGroupIds: [...draft.object.groups ?? []],
|
|
1526
1596
|
x1: draft.x1,
|
|
1527
1597
|
y1: draft.y1,
|
|
1528
1598
|
x2: draft.x2,
|
|
@@ -1531,42 +1601,144 @@
|
|
|
1531
1601
|
hidden: draft.object.properties.hidden === true
|
|
1532
1602
|
};
|
|
1533
1603
|
}
|
|
1534
|
-
function createSceneLabels(objects, sceneHeight, labelMultiplier) {
|
|
1604
|
+
function createSceneLabels(objects, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1535
1605
|
const labels = [];
|
|
1536
1606
|
const occupied = [];
|
|
1537
|
-
const
|
|
1607
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1608
|
+
const visibleObjects = [...objects].filter((object) => !object.hidden && object.object.renderHints?.renderLabel !== false).sort(compareLabelPlacementOrder);
|
|
1538
1609
|
for (const object of visibleObjects) {
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
let labelY = object.y + direction * (object.radius + 18 * labelMultiplier);
|
|
1542
|
-
let secondaryY = labelY + direction * (16 * labelMultiplier);
|
|
1543
|
-
let bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1544
|
-
let attempts = 0;
|
|
1545
|
-
while (occupied.some((entry) => rectsOverlap(entry, bounds)) && attempts < 10) {
|
|
1546
|
-
labelY += direction * 14 * labelMultiplier;
|
|
1547
|
-
secondaryY += direction * 14 * labelMultiplier;
|
|
1548
|
-
bounds = createLabelRect(object.x, labelY, secondaryY, labelHalfWidth, direction);
|
|
1549
|
-
attempts += 1;
|
|
1550
|
-
}
|
|
1551
|
-
occupied.push(bounds);
|
|
1610
|
+
const placement = selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) ?? createLabelPlacement(object, defaultVerticalDirection(object, objectMap.get(object.parentId ?? "") ?? null, sceneHeight), 0, labelMultiplier);
|
|
1611
|
+
occupied.push(createLabelRect(object, placement, labelMultiplier));
|
|
1552
1612
|
labels.push({
|
|
1553
1613
|
renderId: `${object.renderId}-label`,
|
|
1554
1614
|
objectId: object.objectId,
|
|
1555
1615
|
object: object.object,
|
|
1556
1616
|
groupId: object.groupId,
|
|
1617
|
+
semanticGroupIds: [...object.semanticGroupIds],
|
|
1557
1618
|
label: object.label,
|
|
1558
1619
|
secondaryLabel: object.secondaryLabel,
|
|
1559
|
-
x:
|
|
1560
|
-
y: labelY,
|
|
1561
|
-
secondaryY,
|
|
1562
|
-
textAnchor:
|
|
1563
|
-
direction: direction
|
|
1620
|
+
x: placement.x,
|
|
1621
|
+
y: placement.labelY,
|
|
1622
|
+
secondaryY: placement.secondaryY,
|
|
1623
|
+
textAnchor: placement.textAnchor,
|
|
1624
|
+
direction: placement.direction,
|
|
1564
1625
|
hidden: object.hidden
|
|
1565
1626
|
});
|
|
1566
1627
|
}
|
|
1567
1628
|
return labels;
|
|
1568
1629
|
}
|
|
1569
|
-
function
|
|
1630
|
+
function compareLabelPlacementOrder(left, right) {
|
|
1631
|
+
const priorityDiff = labelPlacementPriority(left) - labelPlacementPriority(right);
|
|
1632
|
+
if (priorityDiff !== 0) {
|
|
1633
|
+
return priorityDiff;
|
|
1634
|
+
}
|
|
1635
|
+
const renderPriorityDiff = (right.object.renderHints?.renderPriority ?? 0) - (left.object.renderHints?.renderPriority ?? 0);
|
|
1636
|
+
if (renderPriorityDiff !== 0) {
|
|
1637
|
+
return renderPriorityDiff;
|
|
1638
|
+
}
|
|
1639
|
+
return left.sortKey - right.sortKey;
|
|
1640
|
+
}
|
|
1641
|
+
function labelPlacementPriority(object) {
|
|
1642
|
+
switch (object.object.type) {
|
|
1643
|
+
case "star":
|
|
1644
|
+
return 0;
|
|
1645
|
+
case "planet":
|
|
1646
|
+
return 1;
|
|
1647
|
+
case "moon":
|
|
1648
|
+
return 2;
|
|
1649
|
+
case "belt":
|
|
1650
|
+
case "ring":
|
|
1651
|
+
return 3;
|
|
1652
|
+
case "asteroid":
|
|
1653
|
+
case "comet":
|
|
1654
|
+
return 4;
|
|
1655
|
+
case "structure":
|
|
1656
|
+
case "phenomenon":
|
|
1657
|
+
return 5;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
function selectLabelPlacement(object, objectMap, occupied, sceneWidth, sceneHeight, labelMultiplier) {
|
|
1661
|
+
for (const direction of preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight)) {
|
|
1662
|
+
const maxAttempts = direction === "left" || direction === "right" ? 4 : 6;
|
|
1663
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt += 1) {
|
|
1664
|
+
const placement = createLabelPlacement(object, direction, attempt, labelMultiplier);
|
|
1665
|
+
const rect = createLabelRect(object, placement, labelMultiplier);
|
|
1666
|
+
if (!occupied.some((entry) => rectsOverlap(entry, rect))) {
|
|
1667
|
+
return placement;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
return null;
|
|
1672
|
+
}
|
|
1673
|
+
function preferredLabelDirections(object, objectMap, sceneWidth, sceneHeight) {
|
|
1674
|
+
const parent = object.parentId ? objectMap.get(object.parentId) ?? null : null;
|
|
1675
|
+
const vertical = defaultVerticalDirection(object, parent, sceneHeight);
|
|
1676
|
+
const oppositeVertical = vertical === "below" ? "above" : "below";
|
|
1677
|
+
const horizontal = defaultHorizontalDirection(object, parent, sceneWidth);
|
|
1678
|
+
const oppositeHorizontal = horizontal === "right" ? "left" : "right";
|
|
1679
|
+
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";
|
|
1680
|
+
return preferHorizontal ? [horizontal, vertical, oppositeHorizontal, oppositeVertical] : [vertical, horizontal, oppositeVertical, oppositeHorizontal];
|
|
1681
|
+
}
|
|
1682
|
+
function defaultVerticalDirection(object, parent, sceneHeight) {
|
|
1683
|
+
if (parent && Math.abs(object.y - parent.y) > 6) {
|
|
1684
|
+
return object.y >= parent.y ? "below" : "above";
|
|
1685
|
+
}
|
|
1686
|
+
return object.y > sceneHeight * 0.62 ? "above" : "below";
|
|
1687
|
+
}
|
|
1688
|
+
function defaultHorizontalDirection(object, parent, sceneWidth) {
|
|
1689
|
+
if (parent && Math.abs(object.x - parent.x) > 6) {
|
|
1690
|
+
return object.x >= parent.x ? "right" : "left";
|
|
1691
|
+
}
|
|
1692
|
+
return object.x >= sceneWidth / 2 ? "right" : "left";
|
|
1693
|
+
}
|
|
1694
|
+
function createLabelPlacement(object, direction, attempt, labelMultiplier) {
|
|
1695
|
+
const step = 14 * labelMultiplier;
|
|
1696
|
+
switch (direction) {
|
|
1697
|
+
case "above": {
|
|
1698
|
+
const labelY = object.y - (object.radius + 18 * labelMultiplier + attempt * step);
|
|
1699
|
+
return {
|
|
1700
|
+
x: object.x,
|
|
1701
|
+
labelY,
|
|
1702
|
+
secondaryY: labelY - 16 * labelMultiplier,
|
|
1703
|
+
textAnchor: "middle",
|
|
1704
|
+
direction
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
case "below": {
|
|
1708
|
+
const labelY = object.y + object.radius + 18 * labelMultiplier + attempt * step;
|
|
1709
|
+
return {
|
|
1710
|
+
x: object.x,
|
|
1711
|
+
labelY,
|
|
1712
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1713
|
+
textAnchor: "middle",
|
|
1714
|
+
direction
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
case "left": {
|
|
1718
|
+
const x = object.x - (object.visualRadius + 16 * labelMultiplier + attempt * step);
|
|
1719
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1720
|
+
return {
|
|
1721
|
+
x,
|
|
1722
|
+
labelY,
|
|
1723
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1724
|
+
textAnchor: "end",
|
|
1725
|
+
direction
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
case "right": {
|
|
1729
|
+
const x = object.x + object.visualRadius + 16 * labelMultiplier + attempt * step;
|
|
1730
|
+
const labelY = object.y - 4 * labelMultiplier;
|
|
1731
|
+
return {
|
|
1732
|
+
x,
|
|
1733
|
+
labelY,
|
|
1734
|
+
secondaryY: labelY + 16 * labelMultiplier,
|
|
1735
|
+
textAnchor: "start",
|
|
1736
|
+
direction
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
function createSceneLayers(orbitVisuals, relations, events, leaders, objects, labels) {
|
|
1570
1742
|
const backOrbitIds = orbitVisuals.filter((visual) => !visual.hidden && Boolean(visual.backArcPath)).map((visual) => visual.renderId);
|
|
1571
1743
|
const frontOrbitIds = orbitVisuals.filter((visual) => !visual.hidden).map((visual) => visual.renderId);
|
|
1572
1744
|
return [
|
|
@@ -1577,6 +1749,14 @@
|
|
|
1577
1749
|
},
|
|
1578
1750
|
{ id: "orbits-back", renderIds: backOrbitIds },
|
|
1579
1751
|
{ id: "orbits-front", renderIds: frontOrbitIds },
|
|
1752
|
+
{
|
|
1753
|
+
id: "relations",
|
|
1754
|
+
renderIds: relations.filter((relation) => !relation.hidden).map((relation) => relation.renderId)
|
|
1755
|
+
},
|
|
1756
|
+
{
|
|
1757
|
+
id: "events",
|
|
1758
|
+
renderIds: events.filter((event) => !event.hidden).map((event) => event.renderId)
|
|
1759
|
+
},
|
|
1580
1760
|
{
|
|
1581
1761
|
id: "objects",
|
|
1582
1762
|
renderIds: objects.filter((object) => !object.hidden).map((object) => object.renderId)
|
|
@@ -1588,7 +1768,7 @@
|
|
|
1588
1768
|
{ id: "metadata", renderIds: ["wo-title", "wo-subtitle", "wo-meta"] }
|
|
1589
1769
|
];
|
|
1590
1770
|
}
|
|
1591
|
-
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships) {
|
|
1771
|
+
function createSceneGroups(objects, orbitVisuals, leaders, labels, relationships, labelMultiplier) {
|
|
1592
1772
|
const groups = /* @__PURE__ */ new Map();
|
|
1593
1773
|
const ensureGroup = (groupId) => {
|
|
1594
1774
|
if (!groupId) {
|
|
@@ -1637,10 +1817,63 @@
|
|
|
1637
1817
|
}
|
|
1638
1818
|
}
|
|
1639
1819
|
for (const group of groups.values()) {
|
|
1640
|
-
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels);
|
|
1820
|
+
group.contentBounds = calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier);
|
|
1641
1821
|
}
|
|
1642
1822
|
return [...groups.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
1643
1823
|
}
|
|
1824
|
+
function createSceneSemanticGroups(document2, objects) {
|
|
1825
|
+
return [...document2.groups].map((group) => ({
|
|
1826
|
+
id: group.id,
|
|
1827
|
+
label: group.label,
|
|
1828
|
+
summary: group.summary,
|
|
1829
|
+
color: group.color,
|
|
1830
|
+
tags: [...group.tags],
|
|
1831
|
+
hidden: group.hidden,
|
|
1832
|
+
objectIds: objects.filter((object) => !object.hidden && object.semanticGroupIds.includes(group.id)).map((object) => object.objectId)
|
|
1833
|
+
})).sort((left, right) => left.label.localeCompare(right.label));
|
|
1834
|
+
}
|
|
1835
|
+
function createSceneRelations(document2, objects) {
|
|
1836
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1837
|
+
return document2.relations.map((relation) => {
|
|
1838
|
+
const from = objectMap.get(relation.from);
|
|
1839
|
+
const to = objectMap.get(relation.to);
|
|
1840
|
+
return {
|
|
1841
|
+
renderId: `${createRenderId(relation.id)}-relation`,
|
|
1842
|
+
relationId: relation.id,
|
|
1843
|
+
relation,
|
|
1844
|
+
fromObjectId: relation.from,
|
|
1845
|
+
toObjectId: relation.to,
|
|
1846
|
+
x1: from?.x ?? 0,
|
|
1847
|
+
y1: from?.y ?? 0,
|
|
1848
|
+
x2: to?.x ?? 0,
|
|
1849
|
+
y2: to?.y ?? 0,
|
|
1850
|
+
hidden: relation.hidden || !from || !to || from.hidden || to.hidden
|
|
1851
|
+
};
|
|
1852
|
+
}).sort((left, right) => left.relation.id.localeCompare(right.relation.id));
|
|
1853
|
+
}
|
|
1854
|
+
function createSceneEvents(events, objects, activeEventId) {
|
|
1855
|
+
const objectMap = new Map(objects.map((object) => [object.objectId, object]));
|
|
1856
|
+
return events.map((event) => {
|
|
1857
|
+
const objectIds = [.../* @__PURE__ */ new Set([
|
|
1858
|
+
...event.targetObjectId ? [event.targetObjectId] : [],
|
|
1859
|
+
...event.participantObjectIds
|
|
1860
|
+
])];
|
|
1861
|
+
const positions = objectIds.map((objectId) => objectMap.get(objectId)).filter(Boolean);
|
|
1862
|
+
const centroidX = positions.length > 0 ? positions.reduce((sum, object) => sum + object.x, 0) / positions.length : 0;
|
|
1863
|
+
const centroidY = positions.length > 0 ? positions.reduce((sum, object) => sum + object.y, 0) / positions.length : 0;
|
|
1864
|
+
return {
|
|
1865
|
+
renderId: `${createRenderId(event.id)}-event`,
|
|
1866
|
+
eventId: event.id,
|
|
1867
|
+
event,
|
|
1868
|
+
objectIds,
|
|
1869
|
+
participantIds: [...event.participantObjectIds],
|
|
1870
|
+
targetObjectId: event.targetObjectId,
|
|
1871
|
+
x: centroidX,
|
|
1872
|
+
y: centroidY,
|
|
1873
|
+
hidden: event.hidden || positions.length === 0 || positions.every((object) => object.hidden) || activeEventId !== null && event.id !== activeEventId
|
|
1874
|
+
};
|
|
1875
|
+
}).sort((left, right) => left.event.id.localeCompare(right.event.id));
|
|
1876
|
+
}
|
|
1644
1877
|
function createSceneViewpoints(document2, projection, preset, relationships, objectMap) {
|
|
1645
1878
|
const generatedOverview = createGeneratedOverviewViewpoint(document2, projection, preset);
|
|
1646
1879
|
const drafts = /* @__PURE__ */ new Map();
|
|
@@ -1658,7 +1891,7 @@
|
|
|
1658
1891
|
}
|
|
1659
1892
|
const field = fieldParts.join(".").toLowerCase();
|
|
1660
1893
|
const draft = drafts.get(id) ?? { id };
|
|
1661
|
-
applyViewpointField(draft, field, value, projection, preset, relationships, objectMap);
|
|
1894
|
+
applyViewpointField(draft, field, value, document2, projection, preset, relationships, objectMap);
|
|
1662
1895
|
drafts.set(id, draft);
|
|
1663
1896
|
}
|
|
1664
1897
|
const viewpoints = [...drafts.values()].map((draft) => finalizeViewpointDraft(draft, projection, preset, objectMap)).filter(Boolean);
|
|
@@ -1686,13 +1919,15 @@
|
|
|
1686
1919
|
});
|
|
1687
1920
|
}
|
|
1688
1921
|
function createGeneratedOverviewViewpoint(document2, projection, preset) {
|
|
1689
|
-
const
|
|
1922
|
+
const title = document2.system?.title ?? document2.system?.properties.title;
|
|
1923
|
+
const label = title ? `${String(title)} Overview` : "Overview";
|
|
1690
1924
|
return {
|
|
1691
1925
|
id: "overview",
|
|
1692
1926
|
label,
|
|
1693
1927
|
summary: "Fit the whole system with the current atlas defaults.",
|
|
1694
1928
|
objectId: null,
|
|
1695
1929
|
selectedObjectId: null,
|
|
1930
|
+
eventIds: [],
|
|
1696
1931
|
projection,
|
|
1697
1932
|
preset,
|
|
1698
1933
|
rotationDeg: 0,
|
|
@@ -1702,7 +1937,7 @@
|
|
|
1702
1937
|
generated: true
|
|
1703
1938
|
};
|
|
1704
1939
|
}
|
|
1705
|
-
function applyViewpointField(draft, field, value, projection, preset, relationships, objectMap) {
|
|
1940
|
+
function applyViewpointField(draft, field, value, document2, projection, preset, relationships, objectMap) {
|
|
1706
1941
|
const normalizedValue = value.trim();
|
|
1707
1942
|
switch (field) {
|
|
1708
1943
|
case "label":
|
|
@@ -1729,6 +1964,9 @@
|
|
|
1729
1964
|
draft.select = normalizedValue;
|
|
1730
1965
|
}
|
|
1731
1966
|
return;
|
|
1967
|
+
case "events":
|
|
1968
|
+
draft.eventIds = splitListValue(normalizedValue);
|
|
1969
|
+
return;
|
|
1732
1970
|
case "projection":
|
|
1733
1971
|
case "view":
|
|
1734
1972
|
draft.projection = parseViewProjection(normalizedValue) ?? projection;
|
|
@@ -1769,7 +2007,7 @@
|
|
|
1769
2007
|
case "groups":
|
|
1770
2008
|
draft.filter = {
|
|
1771
2009
|
...draft.filter ?? createEmptyViewpointFilter(),
|
|
1772
|
-
groupIds: parseViewpointGroups(normalizedValue, relationships, objectMap)
|
|
2010
|
+
groupIds: parseViewpointGroups(normalizedValue, document2, relationships, objectMap)
|
|
1773
2011
|
};
|
|
1774
2012
|
return;
|
|
1775
2013
|
}
|
|
@@ -1785,6 +2023,7 @@
|
|
|
1785
2023
|
summary: draft.summary?.trim() || createViewpointSummary(label, objectId, filter),
|
|
1786
2024
|
objectId,
|
|
1787
2025
|
selectedObjectId,
|
|
2026
|
+
eventIds: [...new Set(draft.eventIds ?? [])],
|
|
1788
2027
|
projection: draft.projection ?? projection,
|
|
1789
2028
|
preset: draft.preset ?? preset,
|
|
1790
2029
|
rotationDeg: draft.rotationDeg ?? 0,
|
|
@@ -1842,7 +2081,7 @@
|
|
|
1842
2081
|
next["orbits-front"] = enabled;
|
|
1843
2082
|
continue;
|
|
1844
2083
|
}
|
|
1845
|
-
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
2084
|
+
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "relations" || rawLayer === "events" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
1846
2085
|
next[rawLayer] = enabled;
|
|
1847
2086
|
}
|
|
1848
2087
|
}
|
|
@@ -1851,8 +2090,11 @@
|
|
|
1851
2090
|
function parseViewpointObjectTypes(value) {
|
|
1852
2091
|
return splitListValue(value).filter((entry) => entry === "star" || entry === "planet" || entry === "moon" || entry === "belt" || entry === "asteroid" || entry === "comet" || entry === "ring" || entry === "structure" || entry === "phenomenon");
|
|
1853
2092
|
}
|
|
1854
|
-
function parseViewpointGroups(value, relationships, objectMap) {
|
|
2093
|
+
function parseViewpointGroups(value, document2, relationships, objectMap) {
|
|
1855
2094
|
return splitListValue(value).map((entry) => {
|
|
2095
|
+
if (document2.schemaVersion === "2.1" || document2.groups.some((group) => group.id === entry)) {
|
|
2096
|
+
return entry;
|
|
2097
|
+
}
|
|
1856
2098
|
if (entry.startsWith("wo-") && entry.endsWith("-group")) {
|
|
1857
2099
|
return entry;
|
|
1858
2100
|
}
|
|
@@ -1887,7 +2129,7 @@
|
|
|
1887
2129
|
}
|
|
1888
2130
|
return parts.join(" - ");
|
|
1889
2131
|
}
|
|
1890
|
-
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels) {
|
|
2132
|
+
function calculateContentBounds(width, height, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
1891
2133
|
let minX = Number.POSITIVE_INFINITY;
|
|
1892
2134
|
let minY = Number.POSITIVE_INFINITY;
|
|
1893
2135
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -1917,7 +2159,7 @@
|
|
|
1917
2159
|
for (const label of labels) {
|
|
1918
2160
|
if (label.hidden)
|
|
1919
2161
|
continue;
|
|
1920
|
-
includeLabelBounds(label, include);
|
|
2162
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
1921
2163
|
}
|
|
1922
2164
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
1923
2165
|
return createBounds(0, 0, width, height);
|
|
@@ -1955,13 +2197,10 @@
|
|
|
1955
2197
|
include(object.x - object.visualRadius - 24, object.y - object.visualRadius - 16);
|
|
1956
2198
|
include(object.x + object.visualRadius + 24, object.y + object.visualRadius + 36);
|
|
1957
2199
|
}
|
|
1958
|
-
function includeLabelBounds(label, include) {
|
|
1959
|
-
const
|
|
1960
|
-
|
|
1961
|
-
include(
|
|
1962
|
-
include(label.x + labelHalfWidth, label.y + 8);
|
|
1963
|
-
include(label.x - labelHalfWidth, label.secondaryY - 14);
|
|
1964
|
-
include(label.x + labelHalfWidth, label.secondaryY + 8);
|
|
2200
|
+
function includeLabelBounds(label, include, labelMultiplier) {
|
|
2201
|
+
const bounds = createLabelRectFromText(label.x, label.y, label.secondaryY, label.textAnchor, label.direction, label.label, label.secondaryLabel, labelMultiplier);
|
|
2202
|
+
include(bounds.left, bounds.top);
|
|
2203
|
+
include(bounds.right, bounds.bottom);
|
|
1965
2204
|
}
|
|
1966
2205
|
function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
|
|
1967
2206
|
if (positions.has(object.id)) {
|
|
@@ -1983,8 +2222,9 @@
|
|
|
1983
2222
|
}
|
|
1984
2223
|
const orbiting = [...context.orbitChildren.get(object.id) ?? []].sort(compareOrbiting);
|
|
1985
2224
|
const orbitMetricContext = computeOrbitMetricContext(orbiting, parent.radius, context.spacingFactor, context.scaleModel);
|
|
2225
|
+
const orbitRadiiPx = resolveOrbitRadiiPx(orbiting, orbitMetricContext);
|
|
1986
2226
|
orbiting.forEach((child, index) => {
|
|
1987
|
-
const orbitGeometry = resolveOrbitGeometry(child, index, orbiting.length, parent, orbitMetricContext, context);
|
|
2227
|
+
const orbitGeometry = resolveOrbitGeometry(child, index, orbiting.length, parent, orbitMetricContext, orbitRadiiPx[index] ?? orbitMetricContext.innerPx, context);
|
|
1988
2228
|
orbitDrafts.push({
|
|
1989
2229
|
object: child,
|
|
1990
2230
|
parentId: object.id,
|
|
@@ -2058,7 +2298,8 @@
|
|
|
2058
2298
|
metricSpread: 0,
|
|
2059
2299
|
innerPx,
|
|
2060
2300
|
stepPx,
|
|
2061
|
-
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx)
|
|
2301
|
+
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx),
|
|
2302
|
+
minimumGapPx: stepPx * 0.42
|
|
2062
2303
|
};
|
|
2063
2304
|
}
|
|
2064
2305
|
const minMetric = Math.min(...presentMetrics);
|
|
@@ -2071,10 +2312,11 @@
|
|
|
2071
2312
|
metricSpread,
|
|
2072
2313
|
innerPx,
|
|
2073
2314
|
stepPx,
|
|
2074
|
-
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx)
|
|
2315
|
+
pixelSpread: Math.max(stepPx * Math.max(objects.length - 1, 1), stepPx),
|
|
2316
|
+
minimumGapPx: stepPx * 0.42
|
|
2075
2317
|
};
|
|
2076
2318
|
}
|
|
2077
|
-
function resolveOrbitGeometry(object, index, count, parent, metricContext, context) {
|
|
2319
|
+
function resolveOrbitGeometry(object, index, count, parent, metricContext, orbitRadiusPx, context) {
|
|
2078
2320
|
const placement = object.placement;
|
|
2079
2321
|
const band = object.type === "belt" || object.type === "ring";
|
|
2080
2322
|
if (!placement || placement.mode !== "orbit") {
|
|
@@ -2092,7 +2334,7 @@
|
|
|
2092
2334
|
};
|
|
2093
2335
|
}
|
|
2094
2336
|
const eccentricity = clampNumber(typeof placement.eccentricity === "number" ? placement.eccentricity : 0, 0, 0.92);
|
|
2095
|
-
const semiMajor =
|
|
2337
|
+
const semiMajor = orbitRadiusPx;
|
|
2096
2338
|
const baseMinor = Math.max(semiMajor * Math.sqrt(1 - eccentricity * eccentricity), semiMajor * 0.18);
|
|
2097
2339
|
const inclinationDeg = unitValueToDegrees(placement.inclination) ?? 0;
|
|
2098
2340
|
const inclinationScale = context.projection === "isometric" ? Math.max(MIN_ISO_MINOR_SCALE, Math.cos(degreesToRadians(inclinationDeg))) * ISO_FLATTENING : 1;
|
|
@@ -2122,15 +2364,19 @@
|
|
|
2122
2364
|
objectY: objectPoint.y
|
|
2123
2365
|
};
|
|
2124
2366
|
}
|
|
2125
|
-
function resolveOrbitRadiusPx(
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2367
|
+
function resolveOrbitRadiusPx(metric, metricContext) {
|
|
2368
|
+
return metricContext.innerPx + metricContext.stepPx * log2(Math.max(metric, 0) + 1);
|
|
2369
|
+
}
|
|
2370
|
+
function resolveOrbitRadiiPx(objects, metricContext) {
|
|
2371
|
+
const radii = [];
|
|
2372
|
+
objects.forEach((object, index) => {
|
|
2373
|
+
const metric = orbitMetric(object);
|
|
2374
|
+
const fallbackRadius = metricContext.innerPx + index * metricContext.stepPx;
|
|
2375
|
+
const baseRadius = metric === null ? fallbackRadius : resolveOrbitRadiusPx(metric, metricContext);
|
|
2376
|
+
const minimumRadius = index === 0 ? metricContext.innerPx : (radii[index - 1] ?? metricContext.innerPx) + metricContext.minimumGapPx;
|
|
2377
|
+
radii.push(Math.max(baseRadius, minimumRadius));
|
|
2378
|
+
});
|
|
2379
|
+
return radii;
|
|
2134
2380
|
}
|
|
2135
2381
|
function orbitMetric(object) {
|
|
2136
2382
|
if (!object.placement || object.placement.mode !== "orbit") {
|
|
@@ -2138,6 +2384,9 @@
|
|
|
2138
2384
|
}
|
|
2139
2385
|
return toDistanceMetric(object.placement.semiMajor ?? object.placement.distance ?? null);
|
|
2140
2386
|
}
|
|
2387
|
+
function log2(value) {
|
|
2388
|
+
return Math.log(value) / Math.log(2);
|
|
2389
|
+
}
|
|
2141
2390
|
function resolveOrbitPhase(phase, index, count) {
|
|
2142
2391
|
const degreeValue = phase ? unitValueToDegrees(phase) : null;
|
|
2143
2392
|
if (degreeValue !== null) {
|
|
@@ -2341,7 +2590,7 @@
|
|
|
2341
2590
|
return null;
|
|
2342
2591
|
}
|
|
2343
2592
|
}
|
|
2344
|
-
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels) {
|
|
2593
|
+
function calculateGroupBounds(group, objects, orbitVisuals, leaders, labels, labelMultiplier) {
|
|
2345
2594
|
let minX = Number.POSITIVE_INFINITY;
|
|
2346
2595
|
let minY = Number.POSITIVE_INFINITY;
|
|
2347
2596
|
let maxX = Number.NEGATIVE_INFINITY;
|
|
@@ -2370,7 +2619,7 @@
|
|
|
2370
2619
|
}
|
|
2371
2620
|
for (const label of labels) {
|
|
2372
2621
|
if (!label.hidden && group.labelIds.includes(label.objectId)) {
|
|
2373
|
-
includeLabelBounds(label, include);
|
|
2622
|
+
includeLabelBounds(label, include, labelMultiplier);
|
|
2374
2623
|
}
|
|
2375
2624
|
}
|
|
2376
2625
|
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
@@ -2395,12 +2644,28 @@
|
|
|
2395
2644
|
}
|
|
2396
2645
|
return current.id;
|
|
2397
2646
|
}
|
|
2398
|
-
function createLabelRect(
|
|
2647
|
+
function createLabelRect(object, placement, labelMultiplier) {
|
|
2648
|
+
return createLabelRectFromText(placement.x, placement.labelY, placement.secondaryY, placement.textAnchor, placement.direction, object.label, object.secondaryLabel, labelMultiplier);
|
|
2649
|
+
}
|
|
2650
|
+
function createLabelRectFromText(x, labelY, secondaryY, textAnchor, direction, label, secondaryLabel, labelMultiplier) {
|
|
2651
|
+
const labelHalfWidth = estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier);
|
|
2652
|
+
const labelWidth = labelHalfWidth * 2;
|
|
2653
|
+
const topPadding = direction === "above" ? 18 : 12;
|
|
2654
|
+
const bottomPadding = direction === "above" ? 8 : 12;
|
|
2655
|
+
let left = x - labelHalfWidth;
|
|
2656
|
+
let right = x + labelHalfWidth;
|
|
2657
|
+
if (textAnchor === "start") {
|
|
2658
|
+
left = x;
|
|
2659
|
+
right = x + labelWidth;
|
|
2660
|
+
} else if (textAnchor === "end") {
|
|
2661
|
+
left = x - labelWidth;
|
|
2662
|
+
right = x;
|
|
2663
|
+
}
|
|
2399
2664
|
return {
|
|
2400
|
-
left
|
|
2401
|
-
right
|
|
2402
|
-
top: Math.min(labelY, secondaryY) -
|
|
2403
|
-
bottom: Math.max(labelY, secondaryY) +
|
|
2665
|
+
left,
|
|
2666
|
+
right,
|
|
2667
|
+
top: Math.min(labelY, secondaryY) - topPadding,
|
|
2668
|
+
bottom: Math.max(labelY, secondaryY) + bottomPadding
|
|
2404
2669
|
};
|
|
2405
2670
|
}
|
|
2406
2671
|
function rectsOverlap(left, right) {
|
|
@@ -2461,8 +2726,18 @@
|
|
|
2461
2726
|
return value.value;
|
|
2462
2727
|
case "km":
|
|
2463
2728
|
return value.value / AU_IN_KM;
|
|
2729
|
+
case "m":
|
|
2730
|
+
return value.value / 1e3 / AU_IN_KM;
|
|
2731
|
+
case "ly":
|
|
2732
|
+
return value.value * LY_IN_AU;
|
|
2733
|
+
case "pc":
|
|
2734
|
+
return value.value * PC_IN_AU;
|
|
2735
|
+
case "kpc":
|
|
2736
|
+
return value.value * KPC_IN_AU;
|
|
2464
2737
|
case "re":
|
|
2465
2738
|
return value.value * EARTH_RADIUS_IN_KM / AU_IN_KM;
|
|
2739
|
+
case "rj":
|
|
2740
|
+
return value.value * JUPITER_RADIUS_IN_KM / AU_IN_KM;
|
|
2466
2741
|
case "sol":
|
|
2467
2742
|
return value.value * SOLAR_RADIUS_IN_KM / AU_IN_KM;
|
|
2468
2743
|
default:
|
|
@@ -2577,11 +2852,6 @@
|
|
|
2577
2852
|
function customColorFor(value) {
|
|
2578
2853
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
2579
2854
|
}
|
|
2580
|
-
function estimateLabelHalfWidth(object, labelMultiplier) {
|
|
2581
|
-
const primaryWidth = object.label.length * 4.6 * labelMultiplier + 18;
|
|
2582
|
-
const secondaryWidth = object.secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
2583
|
-
return Math.max(primaryWidth, secondaryWidth, object.visualRadius + 18);
|
|
2584
|
-
}
|
|
2585
2855
|
function estimateLabelHalfWidthFromText(label, secondaryLabel, labelMultiplier) {
|
|
2586
2856
|
const primaryWidth = label.length * 4.6 * labelMultiplier + 18;
|
|
2587
2857
|
const secondaryWidth = secondaryLabel.length * 3.9 * labelMultiplier + 18;
|
|
@@ -2598,28 +2868,95 @@
|
|
|
2598
2868
|
}
|
|
2599
2869
|
|
|
2600
2870
|
// packages/core/dist/draft.js
|
|
2601
|
-
function materializeAtlasDocument(document2) {
|
|
2871
|
+
function materializeAtlasDocument(document2, options = {}) {
|
|
2602
2872
|
const system = document2.system ? {
|
|
2603
2873
|
type: "system",
|
|
2604
2874
|
id: document2.system.id,
|
|
2875
|
+
title: document2.system.title,
|
|
2876
|
+
description: document2.system.description,
|
|
2877
|
+
epoch: document2.system.epoch,
|
|
2878
|
+
referencePlane: document2.system.referencePlane,
|
|
2605
2879
|
properties: materializeDraftSystemProperties(document2.system),
|
|
2606
2880
|
info: materializeDraftSystemInfo(document2.system)
|
|
2607
2881
|
} : null;
|
|
2882
|
+
const objects = document2.objects.map(cloneWorldOrbitObject);
|
|
2883
|
+
applyEventPoseOverrides(objects, document2.events ?? [], options.activeEventId ?? null);
|
|
2608
2884
|
return {
|
|
2609
2885
|
format: "worldorbit",
|
|
2610
2886
|
version: "1.0",
|
|
2887
|
+
schemaVersion: document2.version,
|
|
2611
2888
|
system,
|
|
2612
|
-
|
|
2889
|
+
groups: structuredClone(document2.groups ?? []),
|
|
2890
|
+
relations: structuredClone(document2.relations ?? []),
|
|
2891
|
+
events: document2.events.map(cloneWorldOrbitEvent),
|
|
2892
|
+
objects
|
|
2613
2893
|
};
|
|
2614
2894
|
}
|
|
2615
2895
|
function cloneWorldOrbitObject(object) {
|
|
2616
2896
|
return {
|
|
2617
2897
|
...object,
|
|
2898
|
+
groups: object.groups ? [...object.groups] : void 0,
|
|
2899
|
+
resonance: object.resonance ? { ...object.resonance } : object.resonance,
|
|
2900
|
+
renderHints: object.renderHints ? { ...object.renderHints } : object.renderHints,
|
|
2901
|
+
deriveRules: object.deriveRules ? object.deriveRules.map((rule) => ({ ...rule })) : void 0,
|
|
2902
|
+
validationRules: object.validationRules ? object.validationRules.map((rule) => ({ ...rule })) : void 0,
|
|
2903
|
+
lockedFields: object.lockedFields ? [...object.lockedFields] : void 0,
|
|
2904
|
+
tolerances: object.tolerances ? object.tolerances.map((entry) => ({
|
|
2905
|
+
field: entry.field,
|
|
2906
|
+
value: entry.value && typeof entry.value === "object" && "value" in entry.value ? { value: entry.value.value, unit: entry.value.unit } : Array.isArray(entry.value) ? [...entry.value] : entry.value
|
|
2907
|
+
})) : void 0,
|
|
2908
|
+
typedBlocks: object.typedBlocks ? Object.fromEntries(Object.entries(object.typedBlocks).map(([key, block]) => [key, { ...block ?? {} }])) : void 0,
|
|
2618
2909
|
properties: cloneProperties(object.properties),
|
|
2619
2910
|
placement: object.placement ? structuredClone(object.placement) : null,
|
|
2620
2911
|
info: { ...object.info }
|
|
2621
2912
|
};
|
|
2622
2913
|
}
|
|
2914
|
+
function cloneWorldOrbitEvent(event) {
|
|
2915
|
+
return {
|
|
2916
|
+
...event,
|
|
2917
|
+
participantObjectIds: [...event.participantObjectIds],
|
|
2918
|
+
tags: [...event.tags],
|
|
2919
|
+
positions: event.positions.map(cloneWorldOrbitEventPose)
|
|
2920
|
+
};
|
|
2921
|
+
}
|
|
2922
|
+
function cloneWorldOrbitEventPose(pose) {
|
|
2923
|
+
return {
|
|
2924
|
+
objectId: pose.objectId,
|
|
2925
|
+
placement: clonePlacement(pose.placement),
|
|
2926
|
+
inner: pose.inner ? { ...pose.inner } : void 0,
|
|
2927
|
+
outer: pose.outer ? { ...pose.outer } : void 0
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
function clonePlacement(placement) {
|
|
2931
|
+
return placement ? structuredClone(placement) : null;
|
|
2932
|
+
}
|
|
2933
|
+
function applyEventPoseOverrides(objects, events, activeEventId) {
|
|
2934
|
+
if (!activeEventId) {
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
const event = events.find((entry) => entry.id === activeEventId);
|
|
2938
|
+
if (!event) {
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
const objectMap = new Map(objects.map((object) => [object.id, object]));
|
|
2942
|
+
for (const pose of event.positions) {
|
|
2943
|
+
const object = objectMap.get(pose.objectId);
|
|
2944
|
+
if (!object) {
|
|
2945
|
+
continue;
|
|
2946
|
+
}
|
|
2947
|
+
object.placement = clonePlacement(pose.placement);
|
|
2948
|
+
if (pose.inner) {
|
|
2949
|
+
object.properties.inner = { ...pose.inner };
|
|
2950
|
+
} else {
|
|
2951
|
+
delete object.properties.inner;
|
|
2952
|
+
}
|
|
2953
|
+
if (pose.outer) {
|
|
2954
|
+
object.properties.outer = { ...pose.outer };
|
|
2955
|
+
} else {
|
|
2956
|
+
delete object.properties.outer;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2623
2960
|
function cloneProperties(properties) {
|
|
2624
2961
|
const next = {};
|
|
2625
2962
|
for (const [key, value] of Object.entries(properties)) {
|
|
@@ -2650,71 +2987,83 @@
|
|
|
2650
2987
|
if (system.defaults.units) {
|
|
2651
2988
|
properties.units = system.defaults.units;
|
|
2652
2989
|
}
|
|
2990
|
+
if (system.description) {
|
|
2991
|
+
properties.description = system.description;
|
|
2992
|
+
}
|
|
2993
|
+
if (system.epoch) {
|
|
2994
|
+
properties.epoch = system.epoch;
|
|
2995
|
+
}
|
|
2996
|
+
if (system.referencePlane) {
|
|
2997
|
+
properties.referencePlane = system.referencePlane;
|
|
2998
|
+
}
|
|
2653
2999
|
return properties;
|
|
2654
3000
|
}
|
|
2655
3001
|
function materializeDraftSystemInfo(system) {
|
|
2656
|
-
const
|
|
3002
|
+
const info2 = {
|
|
2657
3003
|
...system.atlasMetadata
|
|
2658
3004
|
};
|
|
2659
3005
|
if (system.defaults.theme) {
|
|
2660
|
-
|
|
3006
|
+
info2["atlas.theme"] = system.defaults.theme;
|
|
2661
3007
|
}
|
|
2662
3008
|
for (const viewpoint of system.viewpoints) {
|
|
2663
3009
|
const prefix = `viewpoint.${viewpoint.id}`;
|
|
2664
|
-
|
|
3010
|
+
info2[`${prefix}.label`] = viewpoint.label;
|
|
2665
3011
|
if (viewpoint.summary) {
|
|
2666
|
-
|
|
3012
|
+
info2[`${prefix}.summary`] = viewpoint.summary;
|
|
2667
3013
|
}
|
|
2668
3014
|
if (viewpoint.focusObjectId) {
|
|
2669
|
-
|
|
3015
|
+
info2[`${prefix}.focus`] = viewpoint.focusObjectId;
|
|
2670
3016
|
}
|
|
2671
3017
|
if (viewpoint.selectedObjectId) {
|
|
2672
|
-
|
|
3018
|
+
info2[`${prefix}.select`] = viewpoint.selectedObjectId;
|
|
2673
3019
|
}
|
|
2674
3020
|
if (viewpoint.projection) {
|
|
2675
|
-
|
|
3021
|
+
info2[`${prefix}.projection`] = viewpoint.projection;
|
|
2676
3022
|
}
|
|
2677
3023
|
if (viewpoint.preset) {
|
|
2678
|
-
|
|
3024
|
+
info2[`${prefix}.preset`] = viewpoint.preset;
|
|
2679
3025
|
}
|
|
2680
3026
|
if (viewpoint.zoom !== null) {
|
|
2681
|
-
|
|
3027
|
+
info2[`${prefix}.zoom`] = String(viewpoint.zoom);
|
|
2682
3028
|
}
|
|
2683
3029
|
if (viewpoint.rotationDeg !== 0) {
|
|
2684
|
-
|
|
3030
|
+
info2[`${prefix}.rotation`] = String(viewpoint.rotationDeg);
|
|
2685
3031
|
}
|
|
2686
3032
|
const serializedLayers = serializeViewpointLayers(viewpoint.layers);
|
|
2687
3033
|
if (serializedLayers) {
|
|
2688
|
-
|
|
3034
|
+
info2[`${prefix}.layers`] = serializedLayers;
|
|
2689
3035
|
}
|
|
2690
3036
|
if (viewpoint.filter?.query) {
|
|
2691
|
-
|
|
3037
|
+
info2[`${prefix}.query`] = viewpoint.filter.query;
|
|
2692
3038
|
}
|
|
2693
3039
|
if ((viewpoint.filter?.objectTypes.length ?? 0) > 0) {
|
|
2694
|
-
|
|
3040
|
+
info2[`${prefix}.types`] = viewpoint.filter?.objectTypes.join(" ") ?? "";
|
|
2695
3041
|
}
|
|
2696
3042
|
if ((viewpoint.filter?.tags.length ?? 0) > 0) {
|
|
2697
|
-
|
|
3043
|
+
info2[`${prefix}.tags`] = viewpoint.filter?.tags.join(" ") ?? "";
|
|
2698
3044
|
}
|
|
2699
3045
|
if ((viewpoint.filter?.groupIds.length ?? 0) > 0) {
|
|
2700
|
-
|
|
3046
|
+
info2[`${prefix}.groups`] = viewpoint.filter?.groupIds.join(" ") ?? "";
|
|
3047
|
+
}
|
|
3048
|
+
if (viewpoint.events.length > 0) {
|
|
3049
|
+
info2[`${prefix}.events`] = viewpoint.events.join(" ");
|
|
2701
3050
|
}
|
|
2702
3051
|
}
|
|
2703
3052
|
for (const annotation of system.annotations) {
|
|
2704
3053
|
const prefix = `annotation.${annotation.id}`;
|
|
2705
|
-
|
|
3054
|
+
info2[`${prefix}.label`] = annotation.label;
|
|
2706
3055
|
if (annotation.targetObjectId) {
|
|
2707
|
-
|
|
3056
|
+
info2[`${prefix}.target`] = annotation.targetObjectId;
|
|
2708
3057
|
}
|
|
2709
|
-
|
|
3058
|
+
info2[`${prefix}.body`] = annotation.body;
|
|
2710
3059
|
if (annotation.tags.length > 0) {
|
|
2711
|
-
|
|
3060
|
+
info2[`${prefix}.tags`] = annotation.tags.join(" ");
|
|
2712
3061
|
}
|
|
2713
3062
|
if (annotation.sourceObjectId) {
|
|
2714
|
-
|
|
3063
|
+
info2[`${prefix}.source`] = annotation.sourceObjectId;
|
|
2715
3064
|
}
|
|
2716
3065
|
}
|
|
2717
|
-
return
|
|
3066
|
+
return info2;
|
|
2718
3067
|
}
|
|
2719
3068
|
function serializeViewpointLayers(layers) {
|
|
2720
3069
|
const tokens = [];
|
|
@@ -2723,7 +3072,7 @@
|
|
|
2723
3072
|
if (orbitFront !== void 0 || orbitBack !== void 0) {
|
|
2724
3073
|
tokens.push(orbitFront !== false || orbitBack !== false ? "orbits" : "-orbits");
|
|
2725
3074
|
}
|
|
2726
|
-
for (const key of ["background", "guides", "objects", "labels", "metadata"]) {
|
|
3075
|
+
for (const key of ["background", "guides", "relations", "events", "objects", "labels", "metadata"]) {
|
|
2727
3076
|
if (layers[key] !== void 0) {
|
|
2728
3077
|
tokens.push(layers[key] ? key : `-${key}`);
|
|
2729
3078
|
}
|
|
@@ -2731,171 +3080,838 @@
|
|
|
2731
3080
|
return tokens.join(" ");
|
|
2732
3081
|
}
|
|
2733
3082
|
|
|
2734
|
-
// packages/core/dist/
|
|
2735
|
-
|
|
2736
|
-
|
|
3083
|
+
// packages/core/dist/atlas-utils.js
|
|
3084
|
+
var UNIT_PATTERN2 = /^(-?\d+(?:\.\d+)?)(kpc|min|mj|rj|ky|my|gy|au|km|me|re|pc|ly|deg|sol|K|m|s|h|d|y)?$/;
|
|
3085
|
+
var BOOLEAN_VALUES2 = /* @__PURE__ */ new Map([
|
|
3086
|
+
["true", true],
|
|
3087
|
+
["false", false],
|
|
3088
|
+
["yes", true],
|
|
3089
|
+
["no", false]
|
|
3090
|
+
]);
|
|
3091
|
+
var URL_SCHEME_PATTERN2 = /^[A-Za-z][A-Za-z0-9+.-]*:/;
|
|
3092
|
+
function normalizeIdentifier(value) {
|
|
3093
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2737
3094
|
}
|
|
2738
|
-
function
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
let sawDefaults = false;
|
|
2746
|
-
let sawAtlas = false;
|
|
2747
|
-
const viewpointIds = /* @__PURE__ */ new Set();
|
|
2748
|
-
const annotationIds = /* @__PURE__ */ new Set();
|
|
2749
|
-
for (let index = 0; index < lines.length; index++) {
|
|
2750
|
-
const rawLine = lines[index];
|
|
2751
|
-
const lineNumber = index + 1;
|
|
2752
|
-
if (!rawLine.trim()) {
|
|
2753
|
-
continue;
|
|
2754
|
-
}
|
|
2755
|
-
const indent = getIndent(rawLine);
|
|
2756
|
-
const tokens = tokenizeLineDetailed(rawLine.slice(indent), {
|
|
2757
|
-
line: lineNumber,
|
|
2758
|
-
columnOffset: indent
|
|
2759
|
-
});
|
|
2760
|
-
if (tokens.length === 0) {
|
|
2761
|
-
continue;
|
|
2762
|
-
}
|
|
2763
|
-
if (!sawSchemaHeader) {
|
|
2764
|
-
schemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
|
|
2765
|
-
sawSchemaHeader = true;
|
|
2766
|
-
continue;
|
|
2767
|
-
}
|
|
2768
|
-
if (indent === 0) {
|
|
2769
|
-
section = startTopLevelSection(tokens, lineNumber, system, objectNodes, viewpointIds, annotationIds, {
|
|
2770
|
-
sawDefaults,
|
|
2771
|
-
sawAtlas
|
|
2772
|
-
});
|
|
2773
|
-
if (section.kind === "system") {
|
|
2774
|
-
system = section.system;
|
|
2775
|
-
} else if (section.kind === "defaults") {
|
|
2776
|
-
sawDefaults = true;
|
|
2777
|
-
} else if (section.kind === "atlas") {
|
|
2778
|
-
sawAtlas = true;
|
|
2779
|
-
}
|
|
2780
|
-
continue;
|
|
2781
|
-
}
|
|
2782
|
-
if (!section) {
|
|
2783
|
-
throw new WorldOrbitError("Indented line without parent atlas section", lineNumber, indent + 1);
|
|
2784
|
-
}
|
|
2785
|
-
handleSectionLine(section, indent, tokens, lineNumber);
|
|
2786
|
-
}
|
|
2787
|
-
if (!sawSchemaHeader) {
|
|
2788
|
-
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
3095
|
+
function humanizeIdentifier2(value) {
|
|
3096
|
+
return value.split(/[-_]+/).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
|
|
3097
|
+
}
|
|
3098
|
+
function parseAtlasUnitValue(input, location, fieldKey) {
|
|
3099
|
+
const match = input.match(UNIT_PATTERN2);
|
|
3100
|
+
if (!match) {
|
|
3101
|
+
throw WorldOrbitError.fromLocation(`Invalid unit value "${input}"`, location);
|
|
2789
3102
|
}
|
|
2790
|
-
const
|
|
2791
|
-
|
|
2792
|
-
|
|
3103
|
+
const unitValue = {
|
|
3104
|
+
value: Number(match[1]),
|
|
3105
|
+
unit: match[2] ?? null
|
|
2793
3106
|
};
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
system: null,
|
|
2799
|
-
objects: normalizedObjects
|
|
2800
|
-
});
|
|
2801
|
-
const diagnostics = schemaVersion === "2.0-draft" && outputVersion === "2.0" ? [
|
|
2802
|
-
{
|
|
2803
|
-
code: "load.schema.deprecatedDraft",
|
|
2804
|
-
severity: "warning",
|
|
2805
|
-
source: "upgrade",
|
|
2806
|
-
message: 'Source header "schema 2.0-draft" is deprecated; canonical v2 documents now use "schema 2.0".'
|
|
3107
|
+
if (fieldKey) {
|
|
3108
|
+
const schema = getFieldSchema(fieldKey);
|
|
3109
|
+
if (schema?.unitFamily && !unitFamilyAllowsUnit(schema.unitFamily, unitValue.unit)) {
|
|
3110
|
+
throw WorldOrbitError.fromLocation(`Unit "${unitValue.unit ?? "none"}" is not valid for "${fieldKey}"`, location);
|
|
2807
3111
|
}
|
|
2808
|
-
|
|
3112
|
+
}
|
|
3113
|
+
return unitValue;
|
|
3114
|
+
}
|
|
3115
|
+
function tryParseAtlasUnitValue(input) {
|
|
3116
|
+
const match = input.match(UNIT_PATTERN2);
|
|
3117
|
+
if (!match) {
|
|
3118
|
+
return null;
|
|
3119
|
+
}
|
|
2809
3120
|
return {
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
sourceVersion: "1.0",
|
|
2813
|
-
system,
|
|
2814
|
-
objects: normalizedObjects,
|
|
2815
|
-
diagnostics
|
|
3121
|
+
value: Number(match[1]),
|
|
3122
|
+
unit: match[2] ?? null
|
|
2816
3123
|
};
|
|
2817
3124
|
}
|
|
2818
|
-
function
|
|
2819
|
-
|
|
2820
|
-
|
|
3125
|
+
function parseAtlasNumber(input, key, location) {
|
|
3126
|
+
const value = Number(input);
|
|
3127
|
+
if (!Number.isFinite(value)) {
|
|
3128
|
+
throw WorldOrbitError.fromLocation(`Invalid numeric value "${input}" for "${key}"`, location);
|
|
2821
3129
|
}
|
|
2822
|
-
return
|
|
3130
|
+
return value;
|
|
2823
3131
|
}
|
|
2824
|
-
function
|
|
2825
|
-
const
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
if (system) {
|
|
2829
|
-
throw new WorldOrbitError('Atlas section "system" may only appear once', line, tokens[0].column);
|
|
2830
|
-
}
|
|
2831
|
-
return startSystemSection(tokens, line);
|
|
2832
|
-
case "defaults":
|
|
2833
|
-
if (!system) {
|
|
2834
|
-
throw new WorldOrbitError('Atlas section "defaults" requires a preceding system declaration', line, tokens[0].column);
|
|
2835
|
-
}
|
|
2836
|
-
if (flags.sawDefaults) {
|
|
2837
|
-
throw new WorldOrbitError('Atlas section "defaults" may only appear once', line, tokens[0].column);
|
|
2838
|
-
}
|
|
2839
|
-
return {
|
|
2840
|
-
kind: "defaults",
|
|
2841
|
-
system,
|
|
2842
|
-
seenFields: /* @__PURE__ */ new Set()
|
|
2843
|
-
};
|
|
2844
|
-
case "atlas":
|
|
2845
|
-
if (!system) {
|
|
2846
|
-
throw new WorldOrbitError('Atlas section "atlas" requires a preceding system declaration', line, tokens[0].column);
|
|
2847
|
-
}
|
|
2848
|
-
if (flags.sawAtlas) {
|
|
2849
|
-
throw new WorldOrbitError('Atlas section "atlas" may only appear once', line, tokens[0].column);
|
|
2850
|
-
}
|
|
2851
|
-
return {
|
|
2852
|
-
kind: "atlas",
|
|
2853
|
-
system,
|
|
2854
|
-
inMetadata: false,
|
|
2855
|
-
metadataIndent: null
|
|
2856
|
-
};
|
|
2857
|
-
case "viewpoint":
|
|
2858
|
-
if (!system) {
|
|
2859
|
-
throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
|
|
2860
|
-
}
|
|
2861
|
-
return startViewpointSection(tokens, line, system, viewpointIds);
|
|
2862
|
-
case "annotation":
|
|
2863
|
-
if (!system) {
|
|
2864
|
-
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
2865
|
-
}
|
|
2866
|
-
return startAnnotationSection(tokens, line, system, annotationIds);
|
|
2867
|
-
case "object":
|
|
2868
|
-
return startObjectSection(tokens, line, objectNodes);
|
|
2869
|
-
default:
|
|
2870
|
-
throw new WorldOrbitError(`Unknown atlas section "${tokens[0]?.value ?? ""}"`, line, tokens[0]?.column ?? 1);
|
|
3132
|
+
function parseAtlasBoolean(input, key, location) {
|
|
3133
|
+
const parsed = BOOLEAN_VALUES2.get(input.toLowerCase());
|
|
3134
|
+
if (parsed === void 0) {
|
|
3135
|
+
throw WorldOrbitError.fromLocation(`Invalid boolean value "${input}" for "${key}"`, location);
|
|
2871
3136
|
}
|
|
3137
|
+
return parsed;
|
|
2872
3138
|
}
|
|
2873
|
-
function
|
|
2874
|
-
if (
|
|
2875
|
-
throw
|
|
3139
|
+
function parseAtlasAtReference(target, location) {
|
|
3140
|
+
if (/^[A-Za-z0-9._-]+-[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
3141
|
+
throw WorldOrbitError.fromLocation(`Invalid special position "${target}"`, location);
|
|
2876
3142
|
}
|
|
2877
|
-
const
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
3143
|
+
const pairedMatch = target.match(/^([A-Za-z0-9._-]+)-([A-Za-z0-9._-]+):(L[1-5])$/);
|
|
3144
|
+
if (pairedMatch) {
|
|
3145
|
+
return {
|
|
3146
|
+
kind: "lagrange",
|
|
3147
|
+
primary: pairedMatch[1],
|
|
3148
|
+
secondary: pairedMatch[2],
|
|
3149
|
+
point: pairedMatch[3]
|
|
3150
|
+
};
|
|
3151
|
+
}
|
|
3152
|
+
const simpleMatch = target.match(/^([A-Za-z0-9._-]+):(L[1-5])$/);
|
|
3153
|
+
if (simpleMatch) {
|
|
3154
|
+
return {
|
|
3155
|
+
kind: "lagrange",
|
|
3156
|
+
primary: simpleMatch[1],
|
|
3157
|
+
secondary: null,
|
|
3158
|
+
point: simpleMatch[2]
|
|
3159
|
+
};
|
|
3160
|
+
}
|
|
3161
|
+
if (/^[A-Za-z0-9._-]+:L\d+$/i.test(target)) {
|
|
3162
|
+
throw WorldOrbitError.fromLocation(`Invalid special position "${target}"`, location);
|
|
3163
|
+
}
|
|
3164
|
+
const anchorMatch = target.match(/^([A-Za-z0-9._-]+):([A-Za-z0-9._-]+)$/);
|
|
3165
|
+
if (anchorMatch) {
|
|
3166
|
+
return {
|
|
3167
|
+
kind: "anchor",
|
|
3168
|
+
objectId: anchorMatch[1],
|
|
3169
|
+
anchor: anchorMatch[2]
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
return {
|
|
3173
|
+
kind: "named",
|
|
3174
|
+
name: target
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
function validateAtlasImageSource(value, location) {
|
|
3178
|
+
if (!value) {
|
|
3179
|
+
throw WorldOrbitError.fromLocation('Field "image" must not be empty', location);
|
|
3180
|
+
}
|
|
3181
|
+
if (value.startsWith("//")) {
|
|
3182
|
+
throw WorldOrbitError.fromLocation('Field "image" must use a relative path, root-relative path, or an http/https URL', location);
|
|
3183
|
+
}
|
|
3184
|
+
const schemeMatch = value.match(URL_SCHEME_PATTERN2);
|
|
3185
|
+
if (!schemeMatch) {
|
|
3186
|
+
return;
|
|
3187
|
+
}
|
|
3188
|
+
const scheme = schemeMatch[0].slice(0, -1).toLowerCase();
|
|
3189
|
+
if (scheme !== "http" && scheme !== "https") {
|
|
3190
|
+
throw WorldOrbitError.fromLocation(`Field "image" does not support the "${scheme}" scheme`, location);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
function normalizeLegacyScalarValue(key, values, location) {
|
|
3194
|
+
const schema = getFieldSchema(key);
|
|
3195
|
+
if (!schema) {
|
|
3196
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${key}"`, location);
|
|
3197
|
+
}
|
|
3198
|
+
if (schema.arity === "single" && values.length !== 1) {
|
|
3199
|
+
throw WorldOrbitError.fromLocation(`Field "${key}" expects exactly one value`, location);
|
|
3200
|
+
}
|
|
3201
|
+
switch (schema.kind) {
|
|
3202
|
+
case "list":
|
|
3203
|
+
return values;
|
|
3204
|
+
case "boolean":
|
|
3205
|
+
return parseAtlasBoolean(singleAtlasValue(values, key, location), key, location);
|
|
3206
|
+
case "number":
|
|
3207
|
+
return parseAtlasNumber(singleAtlasValue(values, key, location), key, location);
|
|
3208
|
+
case "unit":
|
|
3209
|
+
return parseAtlasUnitValue(singleAtlasValue(values, key, location), location, key);
|
|
3210
|
+
case "string": {
|
|
3211
|
+
const value = values.join(" ").trim();
|
|
3212
|
+
if (key === "image") {
|
|
3213
|
+
validateAtlasImageSource(value, location);
|
|
3214
|
+
}
|
|
3215
|
+
return value;
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
function ensureAtlasFieldSupported(key, objectType, location) {
|
|
3220
|
+
const schema = getFieldSchema(key);
|
|
3221
|
+
if (!schema) {
|
|
3222
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${key}"`, location);
|
|
3223
|
+
}
|
|
3224
|
+
if (!schema.objectTypes.includes(objectType)) {
|
|
3225
|
+
throw WorldOrbitError.fromLocation(`Field "${key}" is not valid on "${objectType}"`, location);
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
function singleAtlasValue(values, key, location) {
|
|
3229
|
+
if (values.length !== 1) {
|
|
3230
|
+
throw WorldOrbitError.fromLocation(`Field "${key}" expects exactly one value`, location);
|
|
3231
|
+
}
|
|
3232
|
+
return values[0];
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
// packages/core/dist/atlas-validate.js
|
|
3236
|
+
var SURFACE_TARGET_TYPES2 = /* @__PURE__ */ new Set(["star", "planet", "moon", "asteroid", "comet"]);
|
|
3237
|
+
var EARTH_MASSES_PER_SOLAR = 332946.0487;
|
|
3238
|
+
var JUPITER_MASSES_PER_SOLAR = 1047.3486;
|
|
3239
|
+
var AU_IN_KM2 = 1495978707e-1;
|
|
3240
|
+
var EARTH_RADIUS_IN_KM2 = 6371;
|
|
3241
|
+
var SOLAR_RADIUS_IN_KM2 = 695700;
|
|
3242
|
+
var LY_IN_AU2 = 63241.077;
|
|
3243
|
+
var PC_IN_AU2 = 206264.806;
|
|
3244
|
+
var KPC_IN_AU2 = 206264806;
|
|
3245
|
+
function collectAtlasDiagnostics(document2, sourceSchemaVersion) {
|
|
3246
|
+
const diagnostics = [];
|
|
3247
|
+
const objectMap = new Map(document2.objects.map((object) => [object.id, object]));
|
|
3248
|
+
const groupIds = new Set(document2.groups.map((group) => group.id));
|
|
3249
|
+
const eventIds = new Set(document2.events.map((event) => event.id));
|
|
3250
|
+
if (!document2.system) {
|
|
3251
|
+
diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
|
|
3252
|
+
}
|
|
3253
|
+
const knownIds = /* @__PURE__ */ new Map();
|
|
3254
|
+
for (const [kind, ids] of [
|
|
3255
|
+
["group", document2.groups.map((group) => group.id)],
|
|
3256
|
+
["viewpoint", document2.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
|
|
3257
|
+
["annotation", document2.system?.annotations.map((annotation) => annotation.id) ?? []],
|
|
3258
|
+
["relation", document2.relations.map((relation) => relation.id)],
|
|
3259
|
+
["event", document2.events.map((event) => event.id)],
|
|
3260
|
+
["object", document2.objects.map((object) => object.id)]
|
|
3261
|
+
]) {
|
|
3262
|
+
for (const id of ids) {
|
|
3263
|
+
const previous = knownIds.get(id);
|
|
3264
|
+
if (previous) {
|
|
3265
|
+
diagnostics.push(error("validate.id.duplicate", `Duplicate ${kind} id "${id}" already used by ${previous}.`));
|
|
3266
|
+
} else {
|
|
3267
|
+
knownIds.set(id, kind);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
for (const relation of document2.relations) {
|
|
3272
|
+
validateRelation(relation, objectMap, diagnostics);
|
|
3273
|
+
}
|
|
3274
|
+
for (const viewpoint of document2.system?.viewpoints ?? []) {
|
|
3275
|
+
validateViewpoint(viewpoint.filter, viewpoint.events ?? [], groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpoint.id);
|
|
3276
|
+
}
|
|
3277
|
+
for (const object of document2.objects) {
|
|
3278
|
+
validateObject(object, document2.system, objectMap, groupIds, diagnostics);
|
|
3279
|
+
}
|
|
3280
|
+
for (const event of document2.events) {
|
|
3281
|
+
validateEvent(event, objectMap, diagnostics);
|
|
3282
|
+
}
|
|
3283
|
+
return diagnostics;
|
|
3284
|
+
}
|
|
3285
|
+
function validateRelation(relation, objectMap, diagnostics) {
|
|
3286
|
+
if (!relation.from) {
|
|
3287
|
+
diagnostics.push(error("validate.relation.from.required", `Relation "${relation.id}" is missing a "from" target.`));
|
|
3288
|
+
} else if (!objectMap.has(relation.from)) {
|
|
3289
|
+
diagnostics.push(error("validate.relation.from.unknown", `Unknown relation source "${relation.from}" on "${relation.id}".`));
|
|
3290
|
+
}
|
|
3291
|
+
if (!relation.to) {
|
|
3292
|
+
diagnostics.push(error("validate.relation.to.required", `Relation "${relation.id}" is missing a "to" target.`));
|
|
3293
|
+
} else if (!objectMap.has(relation.to)) {
|
|
3294
|
+
diagnostics.push(error("validate.relation.to.unknown", `Unknown relation target "${relation.to}" on "${relation.id}".`));
|
|
3295
|
+
}
|
|
3296
|
+
if (!relation.kind) {
|
|
3297
|
+
diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
function validateViewpoint(filter, eventRefs, groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpointId) {
|
|
3301
|
+
if (sourceSchemaVersion === "2.1") {
|
|
3302
|
+
if (filter) {
|
|
3303
|
+
for (const groupId of filter.groupIds) {
|
|
3304
|
+
if (!groupIds.has(groupId)) {
|
|
3305
|
+
diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.groups`));
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
for (const eventId of eventRefs) {
|
|
3310
|
+
if (!eventIds.has(eventId)) {
|
|
3311
|
+
diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpointId}".`, void 0, `viewpoint.${viewpointId}.events`));
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
function validateObject(object, system, objectMap, groupIds, diagnostics) {
|
|
3317
|
+
const placement = object.placement;
|
|
3318
|
+
const orbitPlacement = placement?.mode === "orbit" ? placement : null;
|
|
3319
|
+
const parentObject = placement?.mode === "orbit" ? objectMap.get(placement.target) ?? null : null;
|
|
3320
|
+
if (object.groups) {
|
|
3321
|
+
for (const groupId of object.groups) {
|
|
3322
|
+
if (!groupIds.has(groupId)) {
|
|
3323
|
+
diagnostics.push(warn("validate.group.unknown", `Unknown group "${groupId}" on "${object.id}".`, object.id, "groups"));
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
if (orbitPlacement) {
|
|
3328
|
+
if (!objectMap.has(orbitPlacement.target)) {
|
|
3329
|
+
diagnostics.push(error("validate.orbit.target.unknown", `Unknown placement target "${orbitPlacement.target}" on "${object.id}".`, object.id, "orbit"));
|
|
3330
|
+
}
|
|
3331
|
+
if (orbitPlacement.distance && orbitPlacement.semiMajor) {
|
|
3332
|
+
diagnostics.push(error("validate.orbit.distanceConflict", `Object "${object.id}" cannot declare both "distance" and "semiMajor".`, object.id, "distance"));
|
|
3333
|
+
}
|
|
3334
|
+
if (orbitPlacement.phase && !object.epoch && !system?.epoch) {
|
|
3335
|
+
diagnostics.push(warn("validate.phase.epochMissing", `Object "${object.id}" sets "phase" without an object or system epoch.`, object.id, "phase"));
|
|
3336
|
+
}
|
|
3337
|
+
if (orbitPlacement.inclination && !object.referencePlane && !system?.referencePlane) {
|
|
3338
|
+
diagnostics.push(warn("validate.inclination.referencePlaneMissing", `Object "${object.id}" sets "inclination" without an object or system reference plane.`, object.id, "inclination"));
|
|
3339
|
+
}
|
|
3340
|
+
if (orbitPlacement.period && !massInSolar(parentObject?.properties.mass)) {
|
|
3341
|
+
diagnostics.push(warn("validate.period.massMissing", `Object "${object.id}" sets "period" but its central mass cannot be derived.`, object.id, "period"));
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
if (placement?.mode === "surface") {
|
|
3345
|
+
const target = objectMap.get(placement.target);
|
|
3346
|
+
if (!target) {
|
|
3347
|
+
diagnostics.push(error("validate.surface.target.unknown", `Unknown placement target "${placement.target}" on "${object.id}".`, object.id, "surface"));
|
|
3348
|
+
} else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
|
|
3349
|
+
diagnostics.push(error("validate.surface.target.invalid", `Surface target "${placement.target}" on "${object.id}" is not surface-capable.`, object.id, "surface"));
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
if (placement?.mode === "at") {
|
|
3353
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
3354
|
+
diagnostics.push(error("validate.at.objectType", `Only structures and phenomena may use "at" placement; found "${object.type}" on "${object.id}".`, object.id, "at"));
|
|
3355
|
+
}
|
|
3356
|
+
if (!validateAtTarget(object, objectMap, diagnostics)) {
|
|
3357
|
+
diagnostics.push(error("validate.at.target.unknown", `Unknown at-reference target "${placement.target}" on "${object.id}".`, object.id, "at"));
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
if (object.resonance) {
|
|
3361
|
+
const target = objectMap.get(object.resonance.targetObjectId);
|
|
3362
|
+
if (!target) {
|
|
3363
|
+
diagnostics.push(error("validate.resonance.target.unknown", `Unknown resonance target "${object.resonance.targetObjectId}" on "${object.id}".`, object.id, "resonance"));
|
|
3364
|
+
} else if (object.placement?.mode !== "orbit" || target.placement?.mode !== "orbit" || object.placement.target !== target.placement.target) {
|
|
3365
|
+
diagnostics.push(warn("validate.resonance.orbitMismatch", `Resonance target "${object.resonance.targetObjectId}" on "${object.id}" does not share a compatible orbital parent.`, object.id, "resonance"));
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
for (const rule of object.deriveRules ?? []) {
|
|
3369
|
+
if (rule.field !== "period" || rule.strategy !== "kepler") {
|
|
3370
|
+
diagnostics.push(warn("validate.derive.unsupported", `Unsupported derive rule "${rule.field} ${rule.strategy}" on "${object.id}".`, object.id, "derive"));
|
|
3371
|
+
continue;
|
|
3372
|
+
}
|
|
3373
|
+
const derivedPeriodDays = keplerPeriodDays(object, parentObject);
|
|
3374
|
+
if (derivedPeriodDays === null) {
|
|
3375
|
+
diagnostics.push(warn("validate.derive.inputsMissing", `Object "${object.id}" requests "derive period kepler" but lacks enough input data.`, object.id, "derive"));
|
|
3376
|
+
continue;
|
|
3377
|
+
}
|
|
3378
|
+
if (!orbitPlacement?.period) {
|
|
3379
|
+
diagnostics.push(info("validate.derive.period.available", `Object "${object.id}" can derive a Kepler period of ${formatDays(derivedPeriodDays)}.`, object.id, "derive"));
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
for (const rule of object.validationRules ?? []) {
|
|
3383
|
+
if (rule.rule !== "kepler") {
|
|
3384
|
+
diagnostics.push(warn("validate.rule.unsupported", `Unsupported validation rule "${rule.rule}" on "${object.id}".`, object.id, "validate"));
|
|
3385
|
+
continue;
|
|
3386
|
+
}
|
|
3387
|
+
const actualPeriodDays = durationInDays(orbitPlacement?.period);
|
|
3388
|
+
const derivedPeriodDays = keplerPeriodDays(object, parentObject);
|
|
3389
|
+
if (actualPeriodDays === null || derivedPeriodDays === null) {
|
|
3390
|
+
continue;
|
|
3391
|
+
}
|
|
3392
|
+
const toleranceDays = toleranceForField(object, "period");
|
|
3393
|
+
if (Math.abs(actualPeriodDays - derivedPeriodDays) > toleranceDays) {
|
|
3394
|
+
diagnostics.push(error("validate.kepler.mismatch", `Object "${object.id}" fails Kepler validation for "period".`, object.id, "validate"));
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
function validateEvent(event, objectMap, diagnostics) {
|
|
3399
|
+
const fieldPrefix = `event.${event.id}`;
|
|
3400
|
+
const referencedIds = /* @__PURE__ */ new Set();
|
|
3401
|
+
if (!event.kind.trim()) {
|
|
3402
|
+
diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, void 0, `${fieldPrefix}.kind`));
|
|
3403
|
+
}
|
|
3404
|
+
if (!event.targetObjectId && event.participantObjectIds.length === 0) {
|
|
3405
|
+
diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, void 0, `${fieldPrefix}.participants`));
|
|
3406
|
+
}
|
|
3407
|
+
if (event.targetObjectId) {
|
|
3408
|
+
referencedIds.add(event.targetObjectId);
|
|
3409
|
+
if (!objectMap.has(event.targetObjectId)) {
|
|
3410
|
+
diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, void 0, `${fieldPrefix}.target`));
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
const seenParticipants = /* @__PURE__ */ new Set();
|
|
3414
|
+
for (const participantId of event.participantObjectIds) {
|
|
3415
|
+
referencedIds.add(participantId);
|
|
3416
|
+
if (seenParticipants.has(participantId)) {
|
|
3417
|
+
diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, void 0, `${fieldPrefix}.participants`));
|
|
3418
|
+
continue;
|
|
3419
|
+
}
|
|
3420
|
+
seenParticipants.add(participantId);
|
|
3421
|
+
if (!objectMap.has(participantId)) {
|
|
3422
|
+
diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, void 0, `${fieldPrefix}.participants`));
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
if (event.targetObjectId && event.participantObjectIds.length > 0 && !event.participantObjectIds.includes(event.targetObjectId)) {
|
|
3426
|
+
diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, void 0, `${fieldPrefix}.target`));
|
|
3427
|
+
}
|
|
3428
|
+
if (event.positions.length === 0) {
|
|
3429
|
+
diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, void 0, `${fieldPrefix}.positions`));
|
|
3430
|
+
}
|
|
3431
|
+
if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
|
|
3432
|
+
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`));
|
|
3433
|
+
}
|
|
3434
|
+
const poseIds = /* @__PURE__ */ new Set();
|
|
3435
|
+
for (const pose of event.positions) {
|
|
3436
|
+
const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
|
|
3437
|
+
if (poseIds.has(pose.objectId)) {
|
|
3438
|
+
diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, void 0, poseFieldPrefix));
|
|
3439
|
+
continue;
|
|
3440
|
+
}
|
|
3441
|
+
poseIds.add(pose.objectId);
|
|
3442
|
+
const object = objectMap.get(pose.objectId);
|
|
3443
|
+
if (!object) {
|
|
3444
|
+
diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, void 0, poseFieldPrefix));
|
|
3445
|
+
continue;
|
|
3446
|
+
}
|
|
3447
|
+
if (!referencedIds.has(pose.objectId)) {
|
|
3448
|
+
diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, void 0, poseFieldPrefix));
|
|
3449
|
+
}
|
|
3450
|
+
validateEventPose(pose, object, objectMap, diagnostics, poseFieldPrefix, event.id);
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
function validateEventPose(pose, object, objectMap, diagnostics, fieldPrefix, eventId) {
|
|
3454
|
+
const placement = pose.placement;
|
|
3455
|
+
if (!placement) {
|
|
3456
|
+
diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, void 0, fieldPrefix));
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
if (placement.mode === "orbit") {
|
|
3460
|
+
if (!objectMap.has(placement.target)) {
|
|
3461
|
+
diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.orbit`));
|
|
3462
|
+
}
|
|
3463
|
+
if (placement.distance && placement.semiMajor) {
|
|
3464
|
+
diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, void 0, `${fieldPrefix}.distance`));
|
|
3465
|
+
}
|
|
3466
|
+
return;
|
|
3467
|
+
}
|
|
3468
|
+
if (placement.mode === "surface") {
|
|
3469
|
+
const target = objectMap.get(placement.target);
|
|
3470
|
+
if (!target) {
|
|
3471
|
+
diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.surface`));
|
|
3472
|
+
} else if (!SURFACE_TARGET_TYPES2.has(target.type)) {
|
|
3473
|
+
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`));
|
|
3474
|
+
}
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
if (placement.mode === "at") {
|
|
3478
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
3479
|
+
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`));
|
|
3480
|
+
}
|
|
3481
|
+
const reference = placement.reference;
|
|
3482
|
+
if (reference.kind === "named" && !objectMap.has(reference.name)) {
|
|
3483
|
+
diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3484
|
+
} else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
|
|
3485
|
+
diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3486
|
+
} else if (reference.kind === "lagrange") {
|
|
3487
|
+
if (!objectMap.has(reference.primary)) {
|
|
3488
|
+
diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3489
|
+
} else if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
3490
|
+
diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, void 0, `${fieldPrefix}.at`));
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
function validateAtTarget(object, objectMap, diagnostics) {
|
|
3496
|
+
const reference = object.placement?.mode === "at" ? object.placement.reference : null;
|
|
3497
|
+
if (!reference) {
|
|
3498
|
+
return true;
|
|
3499
|
+
}
|
|
3500
|
+
if (reference.kind === "named") {
|
|
3501
|
+
return objectMap.has(reference.name);
|
|
3502
|
+
}
|
|
3503
|
+
if (reference.kind === "anchor") {
|
|
3504
|
+
if (!objectMap.has(reference.objectId)) {
|
|
3505
|
+
diagnostics.push(error("validate.anchor.target.unknown", `Unknown anchor target "${reference.objectId}" on "${object.id}".`, object.id, "at"));
|
|
3506
|
+
return false;
|
|
3507
|
+
}
|
|
3508
|
+
return true;
|
|
3509
|
+
}
|
|
3510
|
+
if (!objectMap.has(reference.primary)) {
|
|
3511
|
+
diagnostics.push(error("validate.lagrange.primary.unknown", `Unknown Lagrange reference "${reference.primary}" on "${object.id}".`, object.id, "at"));
|
|
3512
|
+
return false;
|
|
3513
|
+
}
|
|
3514
|
+
if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
3515
|
+
diagnostics.push(error("validate.lagrange.secondary.unknown", `Unknown Lagrange reference "${reference.secondary}" on "${object.id}".`, object.id, "at"));
|
|
3516
|
+
return false;
|
|
3517
|
+
}
|
|
3518
|
+
return true;
|
|
3519
|
+
}
|
|
3520
|
+
function keplerPeriodDays(object, parentObject) {
|
|
3521
|
+
const placement = object.placement;
|
|
3522
|
+
if (!placement || placement.mode !== "orbit") {
|
|
3523
|
+
return null;
|
|
3524
|
+
}
|
|
3525
|
+
const semiMajorAu = distanceInAu(placement.semiMajor ?? placement.distance);
|
|
3526
|
+
const centralMassSolar = massInSolar(parentObject?.properties.mass);
|
|
3527
|
+
if (semiMajorAu === null || centralMassSolar === null || centralMassSolar <= 0) {
|
|
3528
|
+
return null;
|
|
3529
|
+
}
|
|
3530
|
+
const periodYears = Math.sqrt(semiMajorAu ** 3 / centralMassSolar);
|
|
3531
|
+
return periodYears * 365.25;
|
|
3532
|
+
}
|
|
3533
|
+
function distanceInAu(value) {
|
|
3534
|
+
if (!value)
|
|
3535
|
+
return null;
|
|
3536
|
+
switch (value.unit) {
|
|
3537
|
+
case null:
|
|
3538
|
+
case "au":
|
|
3539
|
+
return value.value;
|
|
3540
|
+
case "km":
|
|
3541
|
+
return value.value / AU_IN_KM2;
|
|
3542
|
+
case "m":
|
|
3543
|
+
return value.value / (AU_IN_KM2 * 1e3);
|
|
3544
|
+
case "ly":
|
|
3545
|
+
return value.value * LY_IN_AU2;
|
|
3546
|
+
case "pc":
|
|
3547
|
+
return value.value * PC_IN_AU2;
|
|
3548
|
+
case "kpc":
|
|
3549
|
+
return value.value * KPC_IN_AU2;
|
|
3550
|
+
case "re":
|
|
3551
|
+
return value.value * EARTH_RADIUS_IN_KM2 / AU_IN_KM2;
|
|
3552
|
+
case "sol":
|
|
3553
|
+
return value.value * SOLAR_RADIUS_IN_KM2 / AU_IN_KM2;
|
|
3554
|
+
default:
|
|
3555
|
+
return null;
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
function massInSolar(value) {
|
|
3559
|
+
if (!value || typeof value !== "object" || !("value" in value)) {
|
|
3560
|
+
return null;
|
|
3561
|
+
}
|
|
3562
|
+
const unitValue = value;
|
|
3563
|
+
switch (unitValue.unit) {
|
|
3564
|
+
case null:
|
|
3565
|
+
case "sol":
|
|
3566
|
+
return unitValue.value;
|
|
3567
|
+
case "me":
|
|
3568
|
+
return unitValue.value / EARTH_MASSES_PER_SOLAR;
|
|
3569
|
+
case "mj":
|
|
3570
|
+
return unitValue.value / JUPITER_MASSES_PER_SOLAR;
|
|
3571
|
+
default:
|
|
3572
|
+
return null;
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
function durationInDays(value) {
|
|
3576
|
+
if (!value)
|
|
3577
|
+
return null;
|
|
3578
|
+
switch (value.unit) {
|
|
3579
|
+
case null:
|
|
3580
|
+
case "d":
|
|
3581
|
+
return value.value;
|
|
3582
|
+
case "s":
|
|
3583
|
+
return value.value / 86400;
|
|
3584
|
+
case "min":
|
|
3585
|
+
return value.value / 1440;
|
|
3586
|
+
case "h":
|
|
3587
|
+
return value.value / 24;
|
|
3588
|
+
case "y":
|
|
3589
|
+
return value.value * 365.25;
|
|
3590
|
+
case "ky":
|
|
3591
|
+
return value.value * 365250;
|
|
3592
|
+
case "my":
|
|
3593
|
+
return value.value * 36525e4;
|
|
3594
|
+
case "gy":
|
|
3595
|
+
return value.value * 36525e7;
|
|
3596
|
+
default:
|
|
3597
|
+
return null;
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
function toleranceForField(object, field) {
|
|
3601
|
+
const tolerance = object.tolerances?.find((entry) => entry.field === field)?.value;
|
|
3602
|
+
if (typeof tolerance === "number") {
|
|
3603
|
+
return tolerance;
|
|
3604
|
+
}
|
|
3605
|
+
if (tolerance && typeof tolerance === "object" && "value" in tolerance) {
|
|
3606
|
+
return durationInDays(tolerance) ?? 0;
|
|
3607
|
+
}
|
|
3608
|
+
return 0;
|
|
3609
|
+
}
|
|
3610
|
+
function formatDays(days) {
|
|
3611
|
+
return `${Math.round(days * 100) / 100}d`;
|
|
3612
|
+
}
|
|
3613
|
+
function error(code, message, objectId, field) {
|
|
3614
|
+
return { code, severity: "error", source: "validate", message, objectId, field };
|
|
3615
|
+
}
|
|
3616
|
+
function warn(code, message, objectId, field) {
|
|
3617
|
+
return { code, severity: "warning", source: "validate", message, objectId, field };
|
|
3618
|
+
}
|
|
3619
|
+
function info(code, message, objectId, field) {
|
|
3620
|
+
return { code, severity: "info", source: "validate", message, objectId, field };
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
// packages/core/dist/draft-parse.js
|
|
3624
|
+
var STRUCTURED_TYPED_BLOCKS = /* @__PURE__ */ new Set([
|
|
3625
|
+
"climate",
|
|
3626
|
+
"habitability",
|
|
3627
|
+
"settlement"
|
|
3628
|
+
]);
|
|
3629
|
+
var DRAFT_OBJECT_FIELD_SPECS = /* @__PURE__ */ new Map();
|
|
3630
|
+
for (const key of [
|
|
3631
|
+
"orbit",
|
|
3632
|
+
"distance",
|
|
3633
|
+
"semiMajor",
|
|
3634
|
+
"eccentricity",
|
|
3635
|
+
"period",
|
|
3636
|
+
"angle",
|
|
3637
|
+
"inclination",
|
|
3638
|
+
"phase",
|
|
3639
|
+
"at",
|
|
3640
|
+
"surface",
|
|
3641
|
+
"free",
|
|
3642
|
+
"kind",
|
|
3643
|
+
"class",
|
|
3644
|
+
"culture",
|
|
3645
|
+
"tags",
|
|
3646
|
+
"color",
|
|
3647
|
+
"image",
|
|
3648
|
+
"hidden",
|
|
3649
|
+
"radius",
|
|
3650
|
+
"mass",
|
|
3651
|
+
"density",
|
|
3652
|
+
"gravity",
|
|
3653
|
+
"temperature",
|
|
3654
|
+
"albedo",
|
|
3655
|
+
"atmosphere",
|
|
3656
|
+
"inner",
|
|
3657
|
+
"outer",
|
|
3658
|
+
"on",
|
|
3659
|
+
"source",
|
|
3660
|
+
"cycle"
|
|
3661
|
+
]) {
|
|
3662
|
+
const schema = getFieldSchema(key);
|
|
3663
|
+
if (schema) {
|
|
3664
|
+
DRAFT_OBJECT_FIELD_SPECS.set(key, {
|
|
3665
|
+
key,
|
|
3666
|
+
version: "2.0",
|
|
3667
|
+
inlineMode: schema.arity === "multiple" ? "multiple" : "single",
|
|
3668
|
+
allowRepeat: false,
|
|
3669
|
+
legacySchema: schema
|
|
3670
|
+
});
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
for (const spec of [
|
|
3674
|
+
{ key: "groups", inlineMode: "multiple", allowRepeat: false },
|
|
3675
|
+
{ key: "epoch", inlineMode: "single", allowRepeat: false },
|
|
3676
|
+
{ key: "referencePlane", inlineMode: "single", allowRepeat: false },
|
|
3677
|
+
{ key: "tidalLock", inlineMode: "single", allowRepeat: false },
|
|
3678
|
+
{ key: "renderLabel", inlineMode: "single", allowRepeat: false },
|
|
3679
|
+
{ key: "renderOrbit", inlineMode: "single", allowRepeat: false },
|
|
3680
|
+
{ key: "renderPriority", inlineMode: "single", allowRepeat: false },
|
|
3681
|
+
{ key: "resonance", inlineMode: "pair", allowRepeat: false },
|
|
3682
|
+
{ key: "derive", inlineMode: "pair", allowRepeat: true },
|
|
3683
|
+
{ key: "validate", inlineMode: "single", allowRepeat: true },
|
|
3684
|
+
{ key: "locked", inlineMode: "multiple", allowRepeat: false },
|
|
3685
|
+
{ key: "tolerance", inlineMode: "pair", allowRepeat: true }
|
|
3686
|
+
]) {
|
|
3687
|
+
DRAFT_OBJECT_FIELD_SPECS.set(spec.key, {
|
|
3688
|
+
key: spec.key,
|
|
3689
|
+
version: "2.1",
|
|
3690
|
+
inlineMode: spec.inlineMode,
|
|
3691
|
+
allowRepeat: spec.allowRepeat
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
3694
|
+
var DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
|
|
3695
|
+
var EVENT_POSE_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
3696
|
+
"orbit",
|
|
3697
|
+
"distance",
|
|
3698
|
+
"semiMajor",
|
|
3699
|
+
"eccentricity",
|
|
3700
|
+
"period",
|
|
3701
|
+
"angle",
|
|
3702
|
+
"inclination",
|
|
3703
|
+
"phase",
|
|
3704
|
+
"at",
|
|
3705
|
+
"surface",
|
|
3706
|
+
"free",
|
|
3707
|
+
"inner",
|
|
3708
|
+
"outer"
|
|
3709
|
+
]);
|
|
3710
|
+
function parseWorldOrbitAtlas(source) {
|
|
3711
|
+
return parseAtlasSource(source);
|
|
3712
|
+
}
|
|
3713
|
+
function parseAtlasSource(source, forcedOutputVersion) {
|
|
3714
|
+
const prepared = preprocessAtlasSource(source);
|
|
3715
|
+
const lines = prepared.source.split(/\r?\n/);
|
|
3716
|
+
const diagnostics = [];
|
|
3717
|
+
let sawSchemaHeader = false;
|
|
3718
|
+
let sourceSchemaVersion = "2.0";
|
|
3719
|
+
let system = null;
|
|
3720
|
+
let section = null;
|
|
3721
|
+
const objectNodes = [];
|
|
3722
|
+
const groups = [];
|
|
3723
|
+
const relations = [];
|
|
3724
|
+
const events = [];
|
|
3725
|
+
const eventPoseNodes = /* @__PURE__ */ new Map();
|
|
3726
|
+
let sawDefaults = false;
|
|
3727
|
+
let sawAtlas = false;
|
|
3728
|
+
const viewpointIds = /* @__PURE__ */ new Set();
|
|
3729
|
+
const annotationIds = /* @__PURE__ */ new Set();
|
|
3730
|
+
const groupIds = /* @__PURE__ */ new Set();
|
|
3731
|
+
const relationIds = /* @__PURE__ */ new Set();
|
|
3732
|
+
const eventIds = /* @__PURE__ */ new Set();
|
|
3733
|
+
for (let index = 0; index < lines.length; index++) {
|
|
3734
|
+
const rawLine = lines[index];
|
|
3735
|
+
const lineNumber = index + 1;
|
|
3736
|
+
if (!rawLine.trim()) {
|
|
3737
|
+
continue;
|
|
3738
|
+
}
|
|
3739
|
+
const indent = getIndent(rawLine);
|
|
3740
|
+
const tokens = tokenizeLineDetailed(rawLine.slice(indent), {
|
|
3741
|
+
line: lineNumber,
|
|
3742
|
+
columnOffset: indent
|
|
3743
|
+
});
|
|
3744
|
+
if (tokens.length === 0) {
|
|
3745
|
+
continue;
|
|
3746
|
+
}
|
|
3747
|
+
if (!sawSchemaHeader) {
|
|
3748
|
+
sourceSchemaVersion = assertDraftSchemaHeader(tokens, lineNumber);
|
|
3749
|
+
sawSchemaHeader = true;
|
|
3750
|
+
if (prepared.comments.length > 0 && sourceSchemaVersion !== "2.1") {
|
|
3751
|
+
diagnostics.push({
|
|
3752
|
+
code: "parse.schema21.commentCompatibility",
|
|
3753
|
+
severity: "warning",
|
|
3754
|
+
source: "parse",
|
|
3755
|
+
message: `Comments require schema 2.1; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
3756
|
+
line: prepared.comments[0].line,
|
|
3757
|
+
column: prepared.comments[0].column
|
|
3758
|
+
});
|
|
3759
|
+
}
|
|
3760
|
+
continue;
|
|
3761
|
+
}
|
|
3762
|
+
if (indent === 0) {
|
|
3763
|
+
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
|
|
3764
|
+
if (section.kind === "system") {
|
|
3765
|
+
system = section.system;
|
|
3766
|
+
} else if (section.kind === "defaults") {
|
|
3767
|
+
sawDefaults = true;
|
|
3768
|
+
} else if (section.kind === "atlas") {
|
|
3769
|
+
sawAtlas = true;
|
|
3770
|
+
}
|
|
3771
|
+
continue;
|
|
3772
|
+
}
|
|
3773
|
+
if (!section) {
|
|
3774
|
+
throw new WorldOrbitError("Indented line without parent atlas section", lineNumber, indent + 1);
|
|
3775
|
+
}
|
|
3776
|
+
handleSectionLine(section, indent, tokens, lineNumber);
|
|
3777
|
+
}
|
|
3778
|
+
if (!sawSchemaHeader) {
|
|
3779
|
+
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
3780
|
+
}
|
|
3781
|
+
const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
|
|
3782
|
+
const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
|
|
3783
|
+
const outputVersion = forcedOutputVersion ?? (sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
|
|
3784
|
+
const baseDocument = {
|
|
3785
|
+
format: "worldorbit",
|
|
3786
|
+
sourceVersion: "1.0",
|
|
3787
|
+
system,
|
|
3788
|
+
groups,
|
|
3789
|
+
relations,
|
|
3790
|
+
events: normalizedEvents,
|
|
3791
|
+
objects,
|
|
3792
|
+
diagnostics
|
|
3793
|
+
};
|
|
3794
|
+
if (outputVersion === "2.0-draft") {
|
|
3795
|
+
const document3 = {
|
|
3796
|
+
...baseDocument,
|
|
3797
|
+
version: "2.0-draft",
|
|
3798
|
+
schemaVersion: "2.0-draft"
|
|
3799
|
+
};
|
|
3800
|
+
document3.diagnostics.push(...collectAtlasDiagnostics(document3, sourceSchemaVersion));
|
|
3801
|
+
return document3;
|
|
3802
|
+
}
|
|
3803
|
+
const document2 = {
|
|
3804
|
+
...baseDocument,
|
|
3805
|
+
version: outputVersion,
|
|
3806
|
+
schemaVersion: outputVersion
|
|
3807
|
+
};
|
|
3808
|
+
if (sourceSchemaVersion === "2.0-draft") {
|
|
3809
|
+
document2.diagnostics.push({
|
|
3810
|
+
code: "load.schema.deprecatedDraft",
|
|
3811
|
+
severity: "warning",
|
|
3812
|
+
source: "upgrade",
|
|
3813
|
+
message: 'Source header "schema 2.0-draft" is deprecated; canonical v2 documents now use "schema 2.0".'
|
|
3814
|
+
});
|
|
3815
|
+
}
|
|
3816
|
+
document2.diagnostics.push(...collectAtlasDiagnostics(document2, sourceSchemaVersion));
|
|
3817
|
+
return document2;
|
|
3818
|
+
}
|
|
3819
|
+
function assertDraftSchemaHeader(tokens, line) {
|
|
3820
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "schema" || !["2.0-draft", "2.0", "2.1"].includes(tokens[1].value.toLowerCase())) {
|
|
3821
|
+
throw new WorldOrbitError('Expected atlas header "schema 2.0", "schema 2.1", or legacy "schema 2.0-draft"', line, tokens[0]?.column ?? 1);
|
|
3822
|
+
}
|
|
3823
|
+
const version = tokens[1].value.toLowerCase();
|
|
3824
|
+
return version === "2.1" ? "2.1" : version === "2.0-draft" ? "2.0-draft" : "2.0";
|
|
3825
|
+
}
|
|
3826
|
+
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
|
|
3827
|
+
const keyword = tokens[0]?.value.toLowerCase();
|
|
3828
|
+
switch (keyword) {
|
|
3829
|
+
case "system":
|
|
3830
|
+
if (system) {
|
|
3831
|
+
throw new WorldOrbitError('Atlas section "system" may only appear once', line, tokens[0].column);
|
|
3832
|
+
}
|
|
3833
|
+
return startSystemSection(tokens, line, sourceSchemaVersion, diagnostics);
|
|
3834
|
+
case "defaults":
|
|
3835
|
+
if (!system) {
|
|
3836
|
+
throw new WorldOrbitError('Atlas section "defaults" requires a preceding system declaration', line, tokens[0].column);
|
|
3837
|
+
}
|
|
3838
|
+
if (flags.sawDefaults) {
|
|
3839
|
+
throw new WorldOrbitError('Atlas section "defaults" may only appear once', line, tokens[0].column);
|
|
3840
|
+
}
|
|
3841
|
+
return {
|
|
3842
|
+
kind: "defaults",
|
|
3843
|
+
system,
|
|
3844
|
+
seenFields: /* @__PURE__ */ new Set()
|
|
3845
|
+
};
|
|
3846
|
+
case "atlas":
|
|
3847
|
+
if (!system) {
|
|
3848
|
+
throw new WorldOrbitError('Atlas section "atlas" requires a preceding system declaration', line, tokens[0].column);
|
|
3849
|
+
}
|
|
3850
|
+
if (flags.sawAtlas) {
|
|
3851
|
+
throw new WorldOrbitError('Atlas section "atlas" may only appear once', line, tokens[0].column);
|
|
3852
|
+
}
|
|
3853
|
+
return {
|
|
3854
|
+
kind: "atlas",
|
|
3855
|
+
system,
|
|
3856
|
+
inMetadata: false,
|
|
3857
|
+
metadataIndent: null
|
|
3858
|
+
};
|
|
3859
|
+
case "viewpoint":
|
|
3860
|
+
if (!system) {
|
|
3861
|
+
throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
|
|
3862
|
+
}
|
|
3863
|
+
return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
|
|
3864
|
+
case "annotation":
|
|
3865
|
+
if (!system) {
|
|
3866
|
+
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
3867
|
+
}
|
|
3868
|
+
return startAnnotationSection(tokens, line, system, annotationIds);
|
|
3869
|
+
case "group":
|
|
3870
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "group", { line, column: tokens[0].column });
|
|
3871
|
+
return startGroupSection(tokens, line, groups, groupIds);
|
|
3872
|
+
case "relation":
|
|
3873
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
|
|
3874
|
+
return startRelationSection(tokens, line, relations, relationIds);
|
|
3875
|
+
case "event":
|
|
3876
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
|
|
3877
|
+
return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
|
|
3878
|
+
case "object":
|
|
3879
|
+
return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
|
|
3880
|
+
default:
|
|
3881
|
+
throw new WorldOrbitError(`Unknown atlas section "${tokens[0]?.value ?? ""}"`, line, tokens[0]?.column ?? 1);
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
function startSystemSection(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
3885
|
+
if (tokens.length !== 2) {
|
|
3886
|
+
throw new WorldOrbitError("Invalid atlas system declaration", line, tokens[0]?.column ?? 1);
|
|
3887
|
+
}
|
|
3888
|
+
const system = {
|
|
3889
|
+
type: "system",
|
|
3890
|
+
id: tokens[1].value,
|
|
3891
|
+
title: null,
|
|
3892
|
+
description: null,
|
|
3893
|
+
epoch: null,
|
|
3894
|
+
referencePlane: null,
|
|
3895
|
+
defaults: {
|
|
3896
|
+
view: "topdown",
|
|
3897
|
+
scale: null,
|
|
3898
|
+
units: null,
|
|
3899
|
+
preset: null,
|
|
3900
|
+
theme: null
|
|
3901
|
+
},
|
|
3902
|
+
atlasMetadata: {},
|
|
3903
|
+
viewpoints: [],
|
|
2890
3904
|
annotations: []
|
|
2891
3905
|
};
|
|
2892
3906
|
return {
|
|
2893
3907
|
kind: "system",
|
|
2894
3908
|
system,
|
|
3909
|
+
sourceSchemaVersion,
|
|
3910
|
+
diagnostics,
|
|
2895
3911
|
seenFields: /* @__PURE__ */ new Set()
|
|
2896
3912
|
};
|
|
2897
3913
|
}
|
|
2898
|
-
function startViewpointSection(tokens, line, system, viewpointIds) {
|
|
3914
|
+
function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
|
|
2899
3915
|
if (tokens.length !== 2) {
|
|
2900
3916
|
throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
|
|
2901
3917
|
}
|
|
@@ -2912,6 +3928,7 @@
|
|
|
2912
3928
|
summary: "",
|
|
2913
3929
|
focusObjectId: null,
|
|
2914
3930
|
selectedObjectId: null,
|
|
3931
|
+
events: [],
|
|
2915
3932
|
projection: system.defaults.view,
|
|
2916
3933
|
preset: system.defaults.preset,
|
|
2917
3934
|
zoom: null,
|
|
@@ -2924,6 +3941,8 @@
|
|
|
2924
3941
|
return {
|
|
2925
3942
|
kind: "viewpoint",
|
|
2926
3943
|
viewpoint,
|
|
3944
|
+
sourceSchemaVersion,
|
|
3945
|
+
diagnostics,
|
|
2927
3946
|
seenFields: /* @__PURE__ */ new Set(),
|
|
2928
3947
|
inFilter: false,
|
|
2929
3948
|
filterIndent: null,
|
|
@@ -2957,7 +3976,107 @@
|
|
|
2957
3976
|
seenFields: /* @__PURE__ */ new Set()
|
|
2958
3977
|
};
|
|
2959
3978
|
}
|
|
2960
|
-
function
|
|
3979
|
+
function startGroupSection(tokens, line, groups, groupIds) {
|
|
3980
|
+
if (tokens.length !== 2) {
|
|
3981
|
+
throw new WorldOrbitError("Invalid group declaration", line, tokens[0]?.column ?? 1);
|
|
3982
|
+
}
|
|
3983
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
3984
|
+
if (!id) {
|
|
3985
|
+
throw new WorldOrbitError("Group id must not be empty", line, tokens[1].column);
|
|
3986
|
+
}
|
|
3987
|
+
if (groupIds.has(id)) {
|
|
3988
|
+
throw new WorldOrbitError(`Duplicate group id "${id}"`, line, tokens[1].column);
|
|
3989
|
+
}
|
|
3990
|
+
const group = {
|
|
3991
|
+
id,
|
|
3992
|
+
label: humanizeIdentifier2(id),
|
|
3993
|
+
summary: "",
|
|
3994
|
+
color: null,
|
|
3995
|
+
tags: [],
|
|
3996
|
+
hidden: false
|
|
3997
|
+
};
|
|
3998
|
+
groups.push(group);
|
|
3999
|
+
groupIds.add(id);
|
|
4000
|
+
return {
|
|
4001
|
+
kind: "group",
|
|
4002
|
+
group,
|
|
4003
|
+
seenFields: /* @__PURE__ */ new Set()
|
|
4004
|
+
};
|
|
4005
|
+
}
|
|
4006
|
+
function startRelationSection(tokens, line, relations, relationIds) {
|
|
4007
|
+
if (tokens.length !== 2) {
|
|
4008
|
+
throw new WorldOrbitError("Invalid relation declaration", line, tokens[0]?.column ?? 1);
|
|
4009
|
+
}
|
|
4010
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
4011
|
+
if (!id) {
|
|
4012
|
+
throw new WorldOrbitError("Relation id must not be empty", line, tokens[1].column);
|
|
4013
|
+
}
|
|
4014
|
+
if (relationIds.has(id)) {
|
|
4015
|
+
throw new WorldOrbitError(`Duplicate relation id "${id}"`, line, tokens[1].column);
|
|
4016
|
+
}
|
|
4017
|
+
const relation = {
|
|
4018
|
+
id,
|
|
4019
|
+
from: "",
|
|
4020
|
+
to: "",
|
|
4021
|
+
kind: "",
|
|
4022
|
+
label: null,
|
|
4023
|
+
summary: null,
|
|
4024
|
+
tags: [],
|
|
4025
|
+
color: null,
|
|
4026
|
+
hidden: false
|
|
4027
|
+
};
|
|
4028
|
+
relations.push(relation);
|
|
4029
|
+
relationIds.add(id);
|
|
4030
|
+
return {
|
|
4031
|
+
kind: "relation",
|
|
4032
|
+
relation,
|
|
4033
|
+
seenFields: /* @__PURE__ */ new Set()
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
|
|
4037
|
+
if (tokens.length !== 2) {
|
|
4038
|
+
throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
|
|
4039
|
+
}
|
|
4040
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
4041
|
+
if (!id) {
|
|
4042
|
+
throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
|
|
4043
|
+
}
|
|
4044
|
+
if (eventIds.has(id)) {
|
|
4045
|
+
throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
|
|
4046
|
+
}
|
|
4047
|
+
const event = {
|
|
4048
|
+
id,
|
|
4049
|
+
kind: "",
|
|
4050
|
+
label: humanizeIdentifier2(id),
|
|
4051
|
+
summary: null,
|
|
4052
|
+
targetObjectId: null,
|
|
4053
|
+
participantObjectIds: [],
|
|
4054
|
+
timing: null,
|
|
4055
|
+
visibility: null,
|
|
4056
|
+
tags: [],
|
|
4057
|
+
color: null,
|
|
4058
|
+
hidden: false,
|
|
4059
|
+
positions: []
|
|
4060
|
+
};
|
|
4061
|
+
const rawPoses = [];
|
|
4062
|
+
events.push(event);
|
|
4063
|
+
eventPoseNodes.set(id, rawPoses);
|
|
4064
|
+
eventIds.add(id);
|
|
4065
|
+
return {
|
|
4066
|
+
kind: "event",
|
|
4067
|
+
event,
|
|
4068
|
+
sourceSchemaVersion,
|
|
4069
|
+
diagnostics,
|
|
4070
|
+
seenFields: /* @__PURE__ */ new Set(),
|
|
4071
|
+
rawPoses,
|
|
4072
|
+
inPositions: false,
|
|
4073
|
+
positionsIndent: null,
|
|
4074
|
+
activePose: null,
|
|
4075
|
+
poseIndent: null,
|
|
4076
|
+
activePoseSeenFields: /* @__PURE__ */ new Set()
|
|
4077
|
+
};
|
|
4078
|
+
}
|
|
4079
|
+
function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
|
|
2961
4080
|
if (tokens.length < 3) {
|
|
2962
4081
|
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
2963
4082
|
}
|
|
@@ -2968,12 +4087,11 @@
|
|
|
2968
4087
|
throw new WorldOrbitError(`Unknown object type "${objectTypeToken.value}"`, line, objectTypeToken.column);
|
|
2969
4088
|
}
|
|
2970
4089
|
const objectNode = {
|
|
2971
|
-
type: "object",
|
|
2972
4090
|
objectType,
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
blockFields: [],
|
|
4091
|
+
id: idToken.value,
|
|
4092
|
+
fields: parseInlineObjectFields(tokens.slice(3), line, objectType, sourceSchemaVersion, diagnostics),
|
|
2976
4093
|
infoEntries: [],
|
|
4094
|
+
typedBlockEntries: {},
|
|
2977
4095
|
location: {
|
|
2978
4096
|
line,
|
|
2979
4097
|
column: objectTypeToken.column
|
|
@@ -2983,8 +4101,12 @@
|
|
|
2983
4101
|
return {
|
|
2984
4102
|
kind: "object",
|
|
2985
4103
|
objectNode,
|
|
2986
|
-
|
|
2987
|
-
|
|
4104
|
+
sourceSchemaVersion,
|
|
4105
|
+
diagnostics,
|
|
4106
|
+
activeBlock: null,
|
|
4107
|
+
blockIndent: null,
|
|
4108
|
+
seenInfoKeys: /* @__PURE__ */ new Set(),
|
|
4109
|
+
seenTypedBlockKeys: {}
|
|
2988
4110
|
};
|
|
2989
4111
|
}
|
|
2990
4112
|
function handleSectionLine(section, indent, tokens, line) {
|
|
@@ -3004,6 +4126,15 @@
|
|
|
3004
4126
|
case "annotation":
|
|
3005
4127
|
applyAnnotationField(section, tokens, line);
|
|
3006
4128
|
return;
|
|
4129
|
+
case "group":
|
|
4130
|
+
applyGroupField(section, tokens, line);
|
|
4131
|
+
return;
|
|
4132
|
+
case "relation":
|
|
4133
|
+
applyRelationField(section, tokens, line);
|
|
4134
|
+
return;
|
|
4135
|
+
case "event":
|
|
4136
|
+
applyEventField(section, indent, tokens, line);
|
|
4137
|
+
return;
|
|
3007
4138
|
case "object":
|
|
3008
4139
|
applyObjectField(section, indent, tokens, line);
|
|
3009
4140
|
return;
|
|
@@ -3011,10 +4142,35 @@
|
|
|
3011
4142
|
}
|
|
3012
4143
|
function applySystemField(section, tokens, line) {
|
|
3013
4144
|
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
3014
|
-
|
|
3015
|
-
|
|
4145
|
+
const value = joinFieldValue(tokens, line);
|
|
4146
|
+
switch (key) {
|
|
4147
|
+
case "title":
|
|
4148
|
+
section.system.title = value;
|
|
4149
|
+
return;
|
|
4150
|
+
case "description":
|
|
4151
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, key, {
|
|
4152
|
+
line,
|
|
4153
|
+
column: tokens[0].column
|
|
4154
|
+
});
|
|
4155
|
+
section.system.description = value;
|
|
4156
|
+
return;
|
|
4157
|
+
case "epoch":
|
|
4158
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, key, {
|
|
4159
|
+
line,
|
|
4160
|
+
column: tokens[0].column
|
|
4161
|
+
});
|
|
4162
|
+
section.system.epoch = value;
|
|
4163
|
+
return;
|
|
4164
|
+
case "referenceplane":
|
|
4165
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "referencePlane", {
|
|
4166
|
+
line,
|
|
4167
|
+
column: tokens[0].column
|
|
4168
|
+
});
|
|
4169
|
+
section.system.referencePlane = value;
|
|
4170
|
+
return;
|
|
4171
|
+
default:
|
|
4172
|
+
throw new WorldOrbitError(`Unknown system atlas field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3016
4173
|
}
|
|
3017
|
-
section.system.title = joinFieldValue(tokens, line);
|
|
3018
4174
|
}
|
|
3019
4175
|
function applyDefaultsField(section, tokens, line) {
|
|
3020
4176
|
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
@@ -3045,14 +4201,11 @@
|
|
|
3045
4201
|
section.metadataIndent = null;
|
|
3046
4202
|
}
|
|
3047
4203
|
if (section.inMetadata) {
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
const key = tokens[0].value;
|
|
3052
|
-
if (key in section.system.atlasMetadata) {
|
|
3053
|
-
throw new WorldOrbitError(`Duplicate atlas metadata key "${key}"`, line, tokens[0].column);
|
|
4204
|
+
const entry = parseInfoLikeEntry(tokens, line, "Invalid atlas metadata entry");
|
|
4205
|
+
if (entry.key in section.system.atlasMetadata) {
|
|
4206
|
+
throw new WorldOrbitError(`Duplicate atlas metadata key "${entry.key}"`, line, tokens[0].column);
|
|
3054
4207
|
}
|
|
3055
|
-
section.system.atlasMetadata[key] =
|
|
4208
|
+
section.system.atlasMetadata[entry.key] = entry.value;
|
|
3056
4209
|
return;
|
|
3057
4210
|
}
|
|
3058
4211
|
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "metadata") {
|
|
@@ -3108,7 +4261,14 @@
|
|
|
3108
4261
|
section.viewpoint.rotationDeg = parseFiniteNumber2(value, line, tokens[0].column, "rotation");
|
|
3109
4262
|
return;
|
|
3110
4263
|
case "layers":
|
|
3111
|
-
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
|
|
4264
|
+
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
|
|
4265
|
+
return;
|
|
4266
|
+
case "events":
|
|
4267
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
|
|
4268
|
+
line,
|
|
4269
|
+
column: tokens[0].column
|
|
4270
|
+
});
|
|
4271
|
+
section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
|
|
3112
4272
|
return;
|
|
3113
4273
|
default:
|
|
3114
4274
|
throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
|
|
@@ -3154,21 +4314,202 @@
|
|
|
3154
4314
|
throw new WorldOrbitError(`Unknown annotation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3155
4315
|
}
|
|
3156
4316
|
}
|
|
4317
|
+
function applyGroupField(section, tokens, line) {
|
|
4318
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
4319
|
+
switch (key) {
|
|
4320
|
+
case "label":
|
|
4321
|
+
section.group.label = joinFieldValue(tokens, line);
|
|
4322
|
+
return;
|
|
4323
|
+
case "summary":
|
|
4324
|
+
section.group.summary = joinFieldValue(tokens, line);
|
|
4325
|
+
return;
|
|
4326
|
+
case "color":
|
|
4327
|
+
section.group.color = joinFieldValue(tokens, line);
|
|
4328
|
+
return;
|
|
4329
|
+
case "tags":
|
|
4330
|
+
section.group.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
4331
|
+
return;
|
|
4332
|
+
case "hidden":
|
|
4333
|
+
section.group.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
4334
|
+
line,
|
|
4335
|
+
column: tokens[0].column
|
|
4336
|
+
});
|
|
4337
|
+
return;
|
|
4338
|
+
default:
|
|
4339
|
+
throw new WorldOrbitError(`Unknown group field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
function applyRelationField(section, tokens, line) {
|
|
4343
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
4344
|
+
switch (key) {
|
|
4345
|
+
case "from":
|
|
4346
|
+
section.relation.from = joinFieldValue(tokens, line);
|
|
4347
|
+
return;
|
|
4348
|
+
case "to":
|
|
4349
|
+
section.relation.to = joinFieldValue(tokens, line);
|
|
4350
|
+
return;
|
|
4351
|
+
case "kind":
|
|
4352
|
+
section.relation.kind = joinFieldValue(tokens, line);
|
|
4353
|
+
return;
|
|
4354
|
+
case "label":
|
|
4355
|
+
section.relation.label = joinFieldValue(tokens, line);
|
|
4356
|
+
return;
|
|
4357
|
+
case "summary":
|
|
4358
|
+
section.relation.summary = joinFieldValue(tokens, line);
|
|
4359
|
+
return;
|
|
4360
|
+
case "tags":
|
|
4361
|
+
section.relation.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
4362
|
+
return;
|
|
4363
|
+
case "color":
|
|
4364
|
+
section.relation.color = joinFieldValue(tokens, line);
|
|
4365
|
+
return;
|
|
4366
|
+
case "hidden":
|
|
4367
|
+
section.relation.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
4368
|
+
line,
|
|
4369
|
+
column: tokens[0].column
|
|
4370
|
+
});
|
|
4371
|
+
return;
|
|
4372
|
+
default:
|
|
4373
|
+
throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
function applyEventField(section, indent, tokens, line) {
|
|
4377
|
+
if (section.activePose && indent <= (section.poseIndent ?? 0)) {
|
|
4378
|
+
section.activePose = null;
|
|
4379
|
+
section.poseIndent = null;
|
|
4380
|
+
section.activePoseSeenFields.clear();
|
|
4381
|
+
}
|
|
4382
|
+
if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
|
|
4383
|
+
section.inPositions = false;
|
|
4384
|
+
section.positionsIndent = null;
|
|
4385
|
+
}
|
|
4386
|
+
if (section.activePose) {
|
|
4387
|
+
section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4390
|
+
if (section.inPositions) {
|
|
4391
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
|
|
4392
|
+
throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
|
|
4393
|
+
}
|
|
4394
|
+
const objectId = tokens[1].value;
|
|
4395
|
+
if (!objectId.trim()) {
|
|
4396
|
+
throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
|
|
4397
|
+
}
|
|
4398
|
+
const rawPose = {
|
|
4399
|
+
objectId,
|
|
4400
|
+
fields: [],
|
|
4401
|
+
location: { line, column: tokens[0].column }
|
|
4402
|
+
};
|
|
4403
|
+
section.rawPoses.push(rawPose);
|
|
4404
|
+
section.activePose = rawPose;
|
|
4405
|
+
section.poseIndent = indent;
|
|
4406
|
+
section.activePoseSeenFields = /* @__PURE__ */ new Set();
|
|
4407
|
+
return;
|
|
4408
|
+
}
|
|
4409
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
|
|
4410
|
+
if (section.seenFields.has("positions")) {
|
|
4411
|
+
throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
|
|
4412
|
+
}
|
|
4413
|
+
section.seenFields.add("positions");
|
|
4414
|
+
section.inPositions = true;
|
|
4415
|
+
section.positionsIndent = indent;
|
|
4416
|
+
return;
|
|
4417
|
+
}
|
|
4418
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
4419
|
+
switch (key) {
|
|
4420
|
+
case "kind":
|
|
4421
|
+
section.event.kind = joinFieldValue(tokens, line);
|
|
4422
|
+
return;
|
|
4423
|
+
case "label":
|
|
4424
|
+
section.event.label = joinFieldValue(tokens, line);
|
|
4425
|
+
return;
|
|
4426
|
+
case "summary":
|
|
4427
|
+
section.event.summary = joinFieldValue(tokens, line);
|
|
4428
|
+
return;
|
|
4429
|
+
case "target":
|
|
4430
|
+
section.event.targetObjectId = joinFieldValue(tokens, line);
|
|
4431
|
+
return;
|
|
4432
|
+
case "participants":
|
|
4433
|
+
section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
|
|
4434
|
+
return;
|
|
4435
|
+
case "timing":
|
|
4436
|
+
section.event.timing = joinFieldValue(tokens, line);
|
|
4437
|
+
return;
|
|
4438
|
+
case "visibility":
|
|
4439
|
+
section.event.visibility = joinFieldValue(tokens, line);
|
|
4440
|
+
return;
|
|
4441
|
+
case "tags":
|
|
4442
|
+
section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
4443
|
+
return;
|
|
4444
|
+
case "color":
|
|
4445
|
+
section.event.color = joinFieldValue(tokens, line);
|
|
4446
|
+
return;
|
|
4447
|
+
case "hidden":
|
|
4448
|
+
section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
4449
|
+
line,
|
|
4450
|
+
column: tokens[0].column
|
|
4451
|
+
});
|
|
4452
|
+
return;
|
|
4453
|
+
default:
|
|
4454
|
+
throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
function parseEventPoseField(tokens, line, seenFields) {
|
|
4458
|
+
if (tokens.length < 2) {
|
|
4459
|
+
throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
|
|
4460
|
+
}
|
|
4461
|
+
const key = tokens[0].value;
|
|
4462
|
+
if (!EVENT_POSE_FIELD_KEYS.has(key)) {
|
|
4463
|
+
throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
|
|
4464
|
+
}
|
|
4465
|
+
if (seenFields.has(key)) {
|
|
4466
|
+
throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
|
|
4467
|
+
}
|
|
4468
|
+
seenFields.add(key);
|
|
4469
|
+
return {
|
|
4470
|
+
type: "field",
|
|
4471
|
+
key,
|
|
4472
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
4473
|
+
location: { line, column: tokens[0].column }
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
3157
4476
|
function applyObjectField(section, indent, tokens, line) {
|
|
3158
|
-
if (
|
|
3159
|
-
section.
|
|
3160
|
-
section.
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
4477
|
+
if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
|
|
4478
|
+
section.activeBlock = null;
|
|
4479
|
+
section.blockIndent = null;
|
|
4480
|
+
}
|
|
4481
|
+
if (tokens.length === 1) {
|
|
4482
|
+
const blockName = tokens[0].value.toLowerCase();
|
|
4483
|
+
if (blockName === "info" || STRUCTURED_TYPED_BLOCKS.has(blockName)) {
|
|
4484
|
+
if (blockName !== "info") {
|
|
4485
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, blockName, { line, column: tokens[0].column });
|
|
4486
|
+
}
|
|
4487
|
+
section.activeBlock = blockName;
|
|
4488
|
+
section.blockIndent = indent;
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
3166
4491
|
}
|
|
3167
|
-
if (section.
|
|
3168
|
-
|
|
4492
|
+
if (section.activeBlock) {
|
|
4493
|
+
const entry = parseInfoLikeEntry(tokens, line, `Invalid ${section.activeBlock} entry`);
|
|
4494
|
+
if (section.activeBlock === "info") {
|
|
4495
|
+
if (section.seenInfoKeys.has(entry.key)) {
|
|
4496
|
+
throw new WorldOrbitError(`Duplicate info key "${entry.key}"`, line, tokens[0].column);
|
|
4497
|
+
}
|
|
4498
|
+
section.seenInfoKeys.add(entry.key);
|
|
4499
|
+
section.objectNode.infoEntries.push(entry);
|
|
4500
|
+
return;
|
|
4501
|
+
}
|
|
4502
|
+
const typedBlock = section.activeBlock;
|
|
4503
|
+
const seenKeys = section.seenTypedBlockKeys[typedBlock] ?? (section.seenTypedBlockKeys[typedBlock] = /* @__PURE__ */ new Set());
|
|
4504
|
+
if (seenKeys.has(entry.key)) {
|
|
4505
|
+
throw new WorldOrbitError(`Duplicate ${typedBlock} key "${entry.key}"`, line, tokens[0].column);
|
|
4506
|
+
}
|
|
4507
|
+
seenKeys.add(entry.key);
|
|
4508
|
+
const entries = section.objectNode.typedBlockEntries[typedBlock] ?? (section.objectNode.typedBlockEntries[typedBlock] = []);
|
|
4509
|
+
entries.push(entry);
|
|
3169
4510
|
return;
|
|
3170
4511
|
}
|
|
3171
|
-
section.objectNode.
|
|
4512
|
+
section.objectNode.fields.push(parseObjectField(tokens, line, section.objectNode.objectType, section.sourceSchemaVersion, section.diagnostics));
|
|
3172
4513
|
}
|
|
3173
4514
|
function requireUniqueField(tokens, seenFields, line) {
|
|
3174
4515
|
if (tokens.length < 2) {
|
|
@@ -3188,50 +4529,46 @@
|
|
|
3188
4529
|
return tokens.slice(1).map((token) => token.value).join(" ").trim();
|
|
3189
4530
|
}
|
|
3190
4531
|
function parseObjectTypeTokens(tokens, line) {
|
|
3191
|
-
|
|
3192
|
-
throw new WorldOrbitError("Missing value for atlas field", line);
|
|
3193
|
-
}
|
|
3194
|
-
return tokens.map((token) => {
|
|
3195
|
-
const value = token.value;
|
|
3196
|
-
if (value !== "star" && value !== "planet" && value !== "moon" && value !== "belt" && value !== "asteroid" && value !== "comet" && value !== "ring" && value !== "structure" && value !== "phenomenon") {
|
|
3197
|
-
throw new WorldOrbitError(`Unknown viewpoint object type "${token.value}"`, line, token.column);
|
|
3198
|
-
}
|
|
3199
|
-
return value;
|
|
3200
|
-
});
|
|
4532
|
+
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");
|
|
3201
4533
|
}
|
|
3202
|
-
function
|
|
3203
|
-
|
|
3204
|
-
|
|
4534
|
+
function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
4535
|
+
const layers = {};
|
|
4536
|
+
for (const token of parseTokenList(tokens, line, "layers")) {
|
|
4537
|
+
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
4538
|
+
const raw = token.replace(/^[-!]+/, "").toLowerCase();
|
|
4539
|
+
if (raw === "orbits") {
|
|
4540
|
+
layers["orbits-back"] = enabled;
|
|
4541
|
+
layers["orbits-front"] = enabled;
|
|
4542
|
+
continue;
|
|
4543
|
+
}
|
|
4544
|
+
if (raw === "background" || raw === "guides" || raw === "orbits-back" || raw === "orbits-front" || raw === "relations" || raw === "events" || raw === "objects" || raw === "labels" || raw === "metadata") {
|
|
4545
|
+
if (raw === "events" && sourceSchemaVersion && diagnostics) {
|
|
4546
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
|
|
4547
|
+
line,
|
|
4548
|
+
column: tokens[0]?.column ?? 1
|
|
4549
|
+
});
|
|
4550
|
+
}
|
|
4551
|
+
layers[raw] = enabled;
|
|
4552
|
+
}
|
|
3205
4553
|
}
|
|
3206
|
-
return
|
|
4554
|
+
return layers;
|
|
3207
4555
|
}
|
|
3208
|
-
function
|
|
4556
|
+
function parseTokenList(tokens, line, fieldName) {
|
|
3209
4557
|
if (tokens.length === 0) {
|
|
3210
|
-
throw new WorldOrbitError(
|
|
4558
|
+
throw new WorldOrbitError(`Missing value for atlas field "${fieldName}"`, line, 1);
|
|
3211
4559
|
}
|
|
3212
|
-
const
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
const rawLayer = token.value.replace(/^[-!]+/, "").toLowerCase();
|
|
3216
|
-
if (rawLayer === "orbits") {
|
|
3217
|
-
next["orbits-back"] = enabled;
|
|
3218
|
-
next["orbits-front"] = enabled;
|
|
3219
|
-
continue;
|
|
3220
|
-
}
|
|
3221
|
-
if (rawLayer === "background" || rawLayer === "guides" || rawLayer === "orbits-back" || rawLayer === "orbits-front" || rawLayer === "objects" || rawLayer === "labels" || rawLayer === "metadata") {
|
|
3222
|
-
next[rawLayer] = enabled;
|
|
3223
|
-
continue;
|
|
3224
|
-
}
|
|
3225
|
-
throw new WorldOrbitError(`Unknown layer token "${token.value}"`, line, token.column);
|
|
4560
|
+
const values = tokens.map((token) => token.value).filter(Boolean);
|
|
4561
|
+
if (values.length === 0) {
|
|
4562
|
+
throw new WorldOrbitError(`Missing value for atlas field "${fieldName}"`, line, tokens[0]?.column ?? 1);
|
|
3226
4563
|
}
|
|
3227
|
-
return
|
|
4564
|
+
return values;
|
|
3228
4565
|
}
|
|
3229
4566
|
function parseProjectionValue(value, line, column) {
|
|
3230
4567
|
const normalized = value.toLowerCase();
|
|
3231
|
-
if (normalized
|
|
3232
|
-
|
|
4568
|
+
if (normalized !== "topdown" && normalized !== "isometric") {
|
|
4569
|
+
throw new WorldOrbitError(`Unknown projection "${value}"`, line, column);
|
|
3233
4570
|
}
|
|
3234
|
-
|
|
4571
|
+
return normalized;
|
|
3235
4572
|
}
|
|
3236
4573
|
function parsePresetValue(value, line, column) {
|
|
3237
4574
|
const normalized = value.toLowerCase();
|
|
@@ -3241,16 +4578,16 @@
|
|
|
3241
4578
|
throw new WorldOrbitError(`Unknown render preset "${value}"`, line, column);
|
|
3242
4579
|
}
|
|
3243
4580
|
function parsePositiveNumber2(value, line, column, field) {
|
|
3244
|
-
const parsed =
|
|
3245
|
-
if (
|
|
3246
|
-
throw new WorldOrbitError(`Field "${field}"
|
|
4581
|
+
const parsed = parseFiniteNumber2(value, line, column, field);
|
|
4582
|
+
if (parsed <= 0) {
|
|
4583
|
+
throw new WorldOrbitError(`Field "${field}" must be greater than zero`, line, column);
|
|
3247
4584
|
}
|
|
3248
4585
|
return parsed;
|
|
3249
4586
|
}
|
|
3250
4587
|
function parseFiniteNumber2(value, line, column, field) {
|
|
3251
4588
|
const parsed = Number(value);
|
|
3252
4589
|
if (!Number.isFinite(parsed)) {
|
|
3253
|
-
throw new WorldOrbitError(`
|
|
4590
|
+
throw new WorldOrbitError(`Invalid numeric value "${value}" for "${field}"`, line, column);
|
|
3254
4591
|
}
|
|
3255
4592
|
return parsed;
|
|
3256
4593
|
}
|
|
@@ -3262,28 +4599,43 @@
|
|
|
3262
4599
|
groupIds: []
|
|
3263
4600
|
};
|
|
3264
4601
|
}
|
|
3265
|
-
function
|
|
4602
|
+
function parseInlineObjectFields(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
3266
4603
|
const fields = [];
|
|
3267
4604
|
let index = 0;
|
|
3268
4605
|
while (index < tokens.length) {
|
|
3269
4606
|
const keyToken = tokens[index];
|
|
3270
|
-
const
|
|
3271
|
-
if (!
|
|
4607
|
+
const spec = getDraftObjectFieldSpec(keyToken.value);
|
|
4608
|
+
if (!spec) {
|
|
3272
4609
|
throw new WorldOrbitError(`Unknown field "${keyToken.value}"`, line, keyToken.column);
|
|
3273
4610
|
}
|
|
4611
|
+
if (spec.version === "2.1") {
|
|
4612
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, keyToken.value, {
|
|
4613
|
+
line,
|
|
4614
|
+
column: keyToken.column
|
|
4615
|
+
});
|
|
4616
|
+
}
|
|
3274
4617
|
index++;
|
|
3275
4618
|
const valueTokens = [];
|
|
3276
|
-
if (
|
|
3277
|
-
while (index < tokens.length && !isKnownFieldKey(tokens[index].value)) {
|
|
3278
|
-
valueTokens.push(tokens[index]);
|
|
3279
|
-
index++;
|
|
3280
|
-
}
|
|
3281
|
-
} else {
|
|
4619
|
+
if (spec.inlineMode === "single") {
|
|
3282
4620
|
const nextToken = tokens[index];
|
|
3283
4621
|
if (nextToken) {
|
|
3284
4622
|
valueTokens.push(nextToken);
|
|
3285
4623
|
index++;
|
|
3286
4624
|
}
|
|
4625
|
+
} else if (spec.inlineMode === "pair") {
|
|
4626
|
+
for (let count = 0; count < 2; count++) {
|
|
4627
|
+
const nextToken = tokens[index];
|
|
4628
|
+
if (!nextToken) {
|
|
4629
|
+
break;
|
|
4630
|
+
}
|
|
4631
|
+
valueTokens.push(nextToken);
|
|
4632
|
+
index++;
|
|
4633
|
+
}
|
|
4634
|
+
} else {
|
|
4635
|
+
while (index < tokens.length && !DRAFT_OBJECT_FIELD_KEYS.has(tokens[index].value)) {
|
|
4636
|
+
valueTokens.push(tokens[index]);
|
|
4637
|
+
index++;
|
|
4638
|
+
}
|
|
3287
4639
|
}
|
|
3288
4640
|
if (valueTokens.length === 0) {
|
|
3289
4641
|
throw new WorldOrbitError(`Missing value for field "${keyToken.value}"`, line, keyToken.column);
|
|
@@ -3295,25 +4647,35 @@
|
|
|
3295
4647
|
location: { line, column: keyToken.column }
|
|
3296
4648
|
});
|
|
3297
4649
|
}
|
|
4650
|
+
validateDraftObjectFieldCompatibility(fields, objectType);
|
|
3298
4651
|
return fields;
|
|
3299
4652
|
}
|
|
3300
|
-
function
|
|
4653
|
+
function parseObjectField(tokens, line, objectType, sourceSchemaVersion, diagnostics) {
|
|
3301
4654
|
if (tokens.length < 2) {
|
|
3302
4655
|
throw new WorldOrbitError("Invalid field line", line, tokens[0]?.column ?? 1);
|
|
3303
4656
|
}
|
|
3304
|
-
|
|
4657
|
+
const spec = getDraftObjectFieldSpec(tokens[0].value);
|
|
4658
|
+
if (!spec) {
|
|
3305
4659
|
throw new WorldOrbitError(`Unknown field "${tokens[0].value}"`, line, tokens[0].column);
|
|
3306
4660
|
}
|
|
3307
|
-
|
|
4661
|
+
if (spec.version === "2.1") {
|
|
4662
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, tokens[0].value, {
|
|
4663
|
+
line,
|
|
4664
|
+
column: tokens[0].column
|
|
4665
|
+
});
|
|
4666
|
+
}
|
|
4667
|
+
const field = {
|
|
3308
4668
|
type: "field",
|
|
3309
4669
|
key: tokens[0].value,
|
|
3310
4670
|
values: tokens.slice(1).map((token) => token.value),
|
|
3311
4671
|
location: { line, column: tokens[0].column }
|
|
3312
4672
|
};
|
|
4673
|
+
validateDraftObjectFieldCompatibility([field], objectType);
|
|
4674
|
+
return field;
|
|
3313
4675
|
}
|
|
3314
|
-
function
|
|
4676
|
+
function parseInfoLikeEntry(tokens, line, errorMessage) {
|
|
3315
4677
|
if (tokens.length < 2) {
|
|
3316
|
-
throw new WorldOrbitError(
|
|
4678
|
+
throw new WorldOrbitError(errorMessage, line, tokens[0]?.column ?? 1);
|
|
3317
4679
|
}
|
|
3318
4680
|
return {
|
|
3319
4681
|
type: "info-entry",
|
|
@@ -3322,18 +4684,366 @@
|
|
|
3322
4684
|
location: { line, column: tokens[0].column }
|
|
3323
4685
|
};
|
|
3324
4686
|
}
|
|
3325
|
-
function
|
|
3326
|
-
|
|
4687
|
+
function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
4688
|
+
const fieldMap = collectDraftFields(node.fields);
|
|
4689
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
4690
|
+
const properties = normalizeDraftProperties(node.objectType, fieldMap);
|
|
4691
|
+
const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
|
|
4692
|
+
const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
|
|
4693
|
+
const referencePlane = parseOptionalJoinedValue(fieldMap.get("referencePlane")?.[0]);
|
|
4694
|
+
const tidalLock = fieldMap.has("tidalLock") ? parseAtlasBoolean(singleFieldValue2(fieldMap.get("tidalLock")[0]), "tidalLock", fieldMap.get("tidalLock")[0].location) : void 0;
|
|
4695
|
+
const resonance = fieldMap.has("resonance") ? parseResonanceField(fieldMap.get("resonance")[0]) : void 0;
|
|
4696
|
+
const renderHints = extractRenderHints(fieldMap);
|
|
4697
|
+
const deriveRules = fieldMap.get("derive")?.map((field) => parseDeriveField(field));
|
|
4698
|
+
const validationRules = fieldMap.get("validate")?.map((field) => ({
|
|
4699
|
+
rule: singleFieldValue2(field)
|
|
4700
|
+
}));
|
|
4701
|
+
const lockedFields = fieldMap.has("locked") ? [...new Set(fieldMap.get("locked").flatMap((field) => field.values))] : void 0;
|
|
4702
|
+
const tolerances = fieldMap.get("tolerance")?.map((field) => parseToleranceField(field));
|
|
4703
|
+
const typedBlocks = normalizeTypedBlocks(node.typedBlockEntries);
|
|
4704
|
+
const info2 = normalizeInfoEntries(node.infoEntries, "info");
|
|
4705
|
+
const object = {
|
|
4706
|
+
type: node.objectType,
|
|
4707
|
+
id: node.id,
|
|
4708
|
+
properties,
|
|
4709
|
+
placement,
|
|
4710
|
+
info: info2
|
|
4711
|
+
};
|
|
4712
|
+
if (groups.length > 0)
|
|
4713
|
+
object.groups = groups;
|
|
4714
|
+
if (epoch)
|
|
4715
|
+
object.epoch = epoch;
|
|
4716
|
+
if (referencePlane)
|
|
4717
|
+
object.referencePlane = referencePlane;
|
|
4718
|
+
if (tidalLock !== void 0)
|
|
4719
|
+
object.tidalLock = tidalLock;
|
|
4720
|
+
if (resonance)
|
|
4721
|
+
object.resonance = resonance;
|
|
4722
|
+
if (renderHints)
|
|
4723
|
+
object.renderHints = renderHints;
|
|
4724
|
+
if (deriveRules?.length)
|
|
4725
|
+
object.deriveRules = deriveRules;
|
|
4726
|
+
if (validationRules?.length)
|
|
4727
|
+
object.validationRules = validationRules;
|
|
4728
|
+
if (lockedFields?.length)
|
|
4729
|
+
object.lockedFields = lockedFields;
|
|
4730
|
+
if (tolerances?.length)
|
|
4731
|
+
object.tolerances = tolerances;
|
|
4732
|
+
if (typedBlocks && Object.keys(typedBlocks).length > 0)
|
|
4733
|
+
object.typedBlocks = typedBlocks;
|
|
4734
|
+
if (sourceSchemaVersion !== "2.1") {
|
|
4735
|
+
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) {
|
|
4736
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, node.id, node.location);
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
return object;
|
|
4740
|
+
}
|
|
4741
|
+
function normalizeDraftEvent(event, rawPoses) {
|
|
4742
|
+
return {
|
|
4743
|
+
...event,
|
|
4744
|
+
participantObjectIds: [...new Set(event.participantObjectIds)],
|
|
4745
|
+
tags: [...new Set(event.tags)],
|
|
4746
|
+
positions: rawPoses.map((pose) => normalizeDraftEventPose(pose))
|
|
4747
|
+
};
|
|
3327
4748
|
}
|
|
3328
|
-
function
|
|
3329
|
-
|
|
4749
|
+
function normalizeDraftEventPose(rawPose) {
|
|
4750
|
+
const fieldMap = collectDraftFields(rawPose.fields);
|
|
4751
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
4752
|
+
return {
|
|
4753
|
+
objectId: rawPose.objectId,
|
|
4754
|
+
placement,
|
|
4755
|
+
inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
|
|
4756
|
+
outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer")
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
function collectDraftFields(fields) {
|
|
4760
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4761
|
+
for (const field of fields) {
|
|
4762
|
+
const spec = getDraftObjectFieldSpec(field.key);
|
|
4763
|
+
if (!spec) {
|
|
4764
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
4765
|
+
}
|
|
4766
|
+
if (!spec.allowRepeat && grouped.has(field.key)) {
|
|
4767
|
+
throw WorldOrbitError.fromLocation(`Duplicate field "${field.key}"`, field.location);
|
|
4768
|
+
}
|
|
4769
|
+
const existing = grouped.get(field.key) ?? [];
|
|
4770
|
+
existing.push(field);
|
|
4771
|
+
grouped.set(field.key, existing);
|
|
4772
|
+
}
|
|
4773
|
+
return grouped;
|
|
4774
|
+
}
|
|
4775
|
+
function extractPlacementFromFieldMap(fieldMap) {
|
|
4776
|
+
const orbitField = fieldMap.get("orbit")?.[0];
|
|
4777
|
+
const atField = fieldMap.get("at")?.[0];
|
|
4778
|
+
const surfaceField = fieldMap.get("surface")?.[0];
|
|
4779
|
+
const freeField = fieldMap.get("free")?.[0];
|
|
4780
|
+
const count = [orbitField, atField, surfaceField, freeField].filter(Boolean).length;
|
|
4781
|
+
if (count > 1) {
|
|
4782
|
+
const conflictingField = orbitField ?? atField ?? surfaceField ?? freeField;
|
|
4783
|
+
throw WorldOrbitError.fromLocation("Object has multiple placement modes", conflictingField?.location);
|
|
4784
|
+
}
|
|
4785
|
+
if (orbitField) {
|
|
4786
|
+
return {
|
|
4787
|
+
mode: "orbit",
|
|
4788
|
+
target: singleFieldValue2(orbitField),
|
|
4789
|
+
distance: parseOptionalUnitField(fieldMap.get("distance")?.[0], "distance"),
|
|
4790
|
+
semiMajor: parseOptionalUnitField(fieldMap.get("semiMajor")?.[0], "semiMajor"),
|
|
4791
|
+
eccentricity: parseOptionalNumberField(fieldMap.get("eccentricity")?.[0], "eccentricity"),
|
|
4792
|
+
period: parseOptionalUnitField(fieldMap.get("period")?.[0], "period"),
|
|
4793
|
+
angle: parseOptionalUnitField(fieldMap.get("angle")?.[0], "angle"),
|
|
4794
|
+
inclination: parseOptionalUnitField(fieldMap.get("inclination")?.[0], "inclination"),
|
|
4795
|
+
phase: parseOptionalUnitField(fieldMap.get("phase")?.[0], "phase")
|
|
4796
|
+
};
|
|
4797
|
+
}
|
|
4798
|
+
if (atField) {
|
|
4799
|
+
const target = singleFieldValue2(atField);
|
|
4800
|
+
return {
|
|
4801
|
+
mode: "at",
|
|
4802
|
+
target,
|
|
4803
|
+
reference: parseAtlasAtReference(target, atField.location)
|
|
4804
|
+
};
|
|
4805
|
+
}
|
|
4806
|
+
if (surfaceField) {
|
|
4807
|
+
return {
|
|
4808
|
+
mode: "surface",
|
|
4809
|
+
target: singleFieldValue2(surfaceField)
|
|
4810
|
+
};
|
|
4811
|
+
}
|
|
4812
|
+
if (freeField) {
|
|
4813
|
+
const raw = singleFieldValue2(freeField);
|
|
4814
|
+
const distance = tryParseAtlasUnitValue(raw);
|
|
4815
|
+
return {
|
|
4816
|
+
mode: "free",
|
|
4817
|
+
distance: distance ?? void 0,
|
|
4818
|
+
descriptor: distance ? void 0 : raw
|
|
4819
|
+
};
|
|
4820
|
+
}
|
|
4821
|
+
return null;
|
|
4822
|
+
}
|
|
4823
|
+
function normalizeDraftProperties(objectType, fieldMap) {
|
|
4824
|
+
const properties = {};
|
|
4825
|
+
for (const [key, fields] of fieldMap.entries()) {
|
|
4826
|
+
const field = fields[0];
|
|
4827
|
+
const spec = getDraftObjectFieldSpec(key);
|
|
4828
|
+
if (!field || !spec?.legacySchema || spec.legacySchema.placement) {
|
|
4829
|
+
continue;
|
|
4830
|
+
}
|
|
4831
|
+
ensureAtlasFieldSupported(key, objectType, field.location);
|
|
4832
|
+
properties[key] = normalizeLegacyScalarValue(key, field.values, field.location);
|
|
4833
|
+
}
|
|
4834
|
+
return properties;
|
|
4835
|
+
}
|
|
4836
|
+
function normalizeInfoEntries(entries, label) {
|
|
4837
|
+
const normalized = {};
|
|
4838
|
+
for (const entry of entries) {
|
|
4839
|
+
if (entry.key in normalized) {
|
|
4840
|
+
throw WorldOrbitError.fromLocation(`Duplicate ${label} key "${entry.key}"`, entry.location);
|
|
4841
|
+
}
|
|
4842
|
+
normalized[entry.key] = entry.value;
|
|
4843
|
+
}
|
|
4844
|
+
return normalized;
|
|
4845
|
+
}
|
|
4846
|
+
function normalizeTypedBlocks(typedBlockEntries) {
|
|
4847
|
+
const typedBlocks = {};
|
|
4848
|
+
for (const blockName of Object.keys(typedBlockEntries)) {
|
|
4849
|
+
const entries = typedBlockEntries[blockName];
|
|
4850
|
+
if (entries?.length) {
|
|
4851
|
+
typedBlocks[blockName] = normalizeInfoEntries(entries, blockName);
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4854
|
+
return typedBlocks;
|
|
4855
|
+
}
|
|
4856
|
+
function extractRenderHints(fieldMap) {
|
|
4857
|
+
const renderHints = {};
|
|
4858
|
+
const renderLabelField = fieldMap.get("renderLabel")?.[0];
|
|
4859
|
+
const renderOrbitField = fieldMap.get("renderOrbit")?.[0];
|
|
4860
|
+
const renderPriorityField = fieldMap.get("renderPriority")?.[0];
|
|
4861
|
+
if (renderLabelField) {
|
|
4862
|
+
renderHints.renderLabel = parseAtlasBoolean(singleFieldValue2(renderLabelField), "renderLabel", renderLabelField.location);
|
|
4863
|
+
}
|
|
4864
|
+
if (renderOrbitField) {
|
|
4865
|
+
renderHints.renderOrbit = parseAtlasBoolean(singleFieldValue2(renderOrbitField), "renderOrbit", renderOrbitField.location);
|
|
4866
|
+
}
|
|
4867
|
+
if (renderPriorityField) {
|
|
4868
|
+
renderHints.renderPriority = parseAtlasNumber(singleFieldValue2(renderPriorityField), "renderPriority", renderPriorityField.location);
|
|
4869
|
+
}
|
|
4870
|
+
return Object.keys(renderHints).length > 0 ? renderHints : void 0;
|
|
4871
|
+
}
|
|
4872
|
+
function parseResonanceField(field) {
|
|
4873
|
+
if (field.values.length !== 2) {
|
|
4874
|
+
throw WorldOrbitError.fromLocation('Field "resonance" expects "<targetObjectId> <ratio>"', field.location);
|
|
4875
|
+
}
|
|
4876
|
+
const ratio = field.values[1];
|
|
4877
|
+
if (!/^\d+:\d+$/.test(ratio)) {
|
|
4878
|
+
throw WorldOrbitError.fromLocation(`Invalid resonance ratio "${ratio}"`, field.location);
|
|
4879
|
+
}
|
|
4880
|
+
return {
|
|
4881
|
+
targetObjectId: field.values[0],
|
|
4882
|
+
ratio
|
|
4883
|
+
};
|
|
4884
|
+
}
|
|
4885
|
+
function parseDeriveField(field) {
|
|
4886
|
+
if (field.values.length !== 2) {
|
|
4887
|
+
throw WorldOrbitError.fromLocation('Field "derive" expects "<field> <strategy>"', field.location);
|
|
4888
|
+
}
|
|
4889
|
+
return {
|
|
4890
|
+
field: field.values[0],
|
|
4891
|
+
strategy: field.values[1]
|
|
4892
|
+
};
|
|
4893
|
+
}
|
|
4894
|
+
function parseToleranceField(field) {
|
|
4895
|
+
if (field.values.length !== 2) {
|
|
4896
|
+
throw WorldOrbitError.fromLocation('Field "tolerance" expects "<field> <value>"', field.location);
|
|
4897
|
+
}
|
|
4898
|
+
const rawValue = field.values[1];
|
|
4899
|
+
const unitValue = tryParseAtlasUnitValue(rawValue);
|
|
4900
|
+
const numericValue2 = Number(rawValue);
|
|
4901
|
+
return {
|
|
4902
|
+
field: field.values[0],
|
|
4903
|
+
value: unitValue ?? (Number.isFinite(numericValue2) ? numericValue2 : rawValue)
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
function parseOptionalTokenList(field) {
|
|
4907
|
+
return field ? [...new Set(field.values)] : [];
|
|
4908
|
+
}
|
|
4909
|
+
function parseOptionalJoinedValue(field) {
|
|
4910
|
+
if (!field) {
|
|
4911
|
+
return null;
|
|
4912
|
+
}
|
|
4913
|
+
return field.values.join(" ").trim() || null;
|
|
4914
|
+
}
|
|
4915
|
+
function parseOptionalUnitField(field, key) {
|
|
4916
|
+
return field ? parseAtlasUnitValue(singleFieldValue2(field), field.location, key) : void 0;
|
|
4917
|
+
}
|
|
4918
|
+
function parseOptionalNumberField(field, key) {
|
|
4919
|
+
return field ? parseAtlasNumber(singleFieldValue2(field), key, field.location) : void 0;
|
|
4920
|
+
}
|
|
4921
|
+
function singleFieldValue2(field) {
|
|
4922
|
+
return singleAtlasValue(field.values, field.key, field.location);
|
|
4923
|
+
}
|
|
4924
|
+
function getDraftObjectFieldSpec(key) {
|
|
4925
|
+
return DRAFT_OBJECT_FIELD_SPECS.get(key);
|
|
4926
|
+
}
|
|
4927
|
+
function validateDraftObjectFieldCompatibility(fields, objectType) {
|
|
4928
|
+
for (const field of fields) {
|
|
4929
|
+
const spec = getDraftObjectFieldSpec(field.key);
|
|
4930
|
+
if (!spec) {
|
|
4931
|
+
throw WorldOrbitError.fromLocation(`Unknown field "${field.key}"`, field.location);
|
|
4932
|
+
}
|
|
4933
|
+
if (spec.legacySchema) {
|
|
4934
|
+
ensureAtlasFieldSupported(field.key, objectType, field.location);
|
|
4935
|
+
continue;
|
|
4936
|
+
}
|
|
4937
|
+
if ((field.key === "renderLabel" || field.key === "renderOrbit" || field.key === "tidalLock") && field.values.length !== 1) {
|
|
4938
|
+
throw WorldOrbitError.fromLocation(`Field "${field.key}" expects exactly one value`, field.location);
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
function warnIfSchema21Feature(sourceSchemaVersion, diagnostics, featureName, location) {
|
|
4943
|
+
if (sourceSchemaVersion === "2.1") {
|
|
4944
|
+
return;
|
|
4945
|
+
}
|
|
4946
|
+
diagnostics.push({
|
|
4947
|
+
code: "parse.schema21.featureCompatibility",
|
|
4948
|
+
severity: "warning",
|
|
4949
|
+
source: "parse",
|
|
4950
|
+
message: `Feature "${featureName}" requires schema 2.1; parsed in compatibility mode because the document header is "schema ${sourceSchemaVersion}".`,
|
|
4951
|
+
line: location.line,
|
|
4952
|
+
column: location.column
|
|
4953
|
+
});
|
|
4954
|
+
}
|
|
4955
|
+
function preprocessAtlasSource(source) {
|
|
4956
|
+
const chars = [...source];
|
|
4957
|
+
const comments = [];
|
|
4958
|
+
let inString = false;
|
|
4959
|
+
let inBlockComment = false;
|
|
4960
|
+
let blockCommentStart = null;
|
|
4961
|
+
let line = 1;
|
|
4962
|
+
let column = 1;
|
|
4963
|
+
for (let index = 0; index < chars.length; index++) {
|
|
4964
|
+
const ch = chars[index];
|
|
4965
|
+
const next = chars[index + 1];
|
|
4966
|
+
if (inBlockComment) {
|
|
4967
|
+
if (ch === "*" && next === "/") {
|
|
4968
|
+
chars[index] = " ";
|
|
4969
|
+
chars[index + 1] = " ";
|
|
4970
|
+
inBlockComment = false;
|
|
4971
|
+
blockCommentStart = null;
|
|
4972
|
+
index++;
|
|
4973
|
+
column += 2;
|
|
4974
|
+
continue;
|
|
4975
|
+
}
|
|
4976
|
+
if (ch !== "\n" && ch !== "\r") {
|
|
4977
|
+
chars[index] = " ";
|
|
4978
|
+
}
|
|
4979
|
+
if (ch === "\n") {
|
|
4980
|
+
line++;
|
|
4981
|
+
column = 1;
|
|
4982
|
+
} else {
|
|
4983
|
+
column++;
|
|
4984
|
+
}
|
|
4985
|
+
continue;
|
|
4986
|
+
}
|
|
4987
|
+
if (!inString && ch === "/" && next === "*") {
|
|
4988
|
+
comments.push({ kind: "block", line, column });
|
|
4989
|
+
chars[index] = " ";
|
|
4990
|
+
chars[index + 1] = " ";
|
|
4991
|
+
inBlockComment = true;
|
|
4992
|
+
blockCommentStart = { line, column };
|
|
4993
|
+
index++;
|
|
4994
|
+
column += 2;
|
|
4995
|
+
continue;
|
|
4996
|
+
}
|
|
4997
|
+
if (!inString && ch === "#" && !isHexColorLiteral(chars, index)) {
|
|
4998
|
+
comments.push({ kind: "line", line, column });
|
|
4999
|
+
chars[index] = " ";
|
|
5000
|
+
let inner = index + 1;
|
|
5001
|
+
while (inner < chars.length && chars[inner] !== "\n" && chars[inner] !== "\r") {
|
|
5002
|
+
chars[inner] = " ";
|
|
5003
|
+
inner++;
|
|
5004
|
+
}
|
|
5005
|
+
column += inner - index;
|
|
5006
|
+
index = inner - 1;
|
|
5007
|
+
continue;
|
|
5008
|
+
}
|
|
5009
|
+
if (ch === '"' && chars[index - 1] !== "\\") {
|
|
5010
|
+
inString = !inString;
|
|
5011
|
+
}
|
|
5012
|
+
if (ch === "\n") {
|
|
5013
|
+
line++;
|
|
5014
|
+
column = 1;
|
|
5015
|
+
} else {
|
|
5016
|
+
column++;
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
if (inBlockComment) {
|
|
5020
|
+
throw WorldOrbitError.fromLocation("Unclosed block comment", blockCommentStart ?? void 0);
|
|
5021
|
+
}
|
|
5022
|
+
return {
|
|
5023
|
+
source: chars.join(""),
|
|
5024
|
+
comments
|
|
5025
|
+
};
|
|
5026
|
+
}
|
|
5027
|
+
function isHexColorLiteral(chars, start) {
|
|
5028
|
+
let index = start + 1;
|
|
5029
|
+
let length = 0;
|
|
5030
|
+
while (index < chars.length && /[0-9a-f]/i.test(chars[index] ?? "")) {
|
|
5031
|
+
index++;
|
|
5032
|
+
length++;
|
|
5033
|
+
}
|
|
5034
|
+
if (![3, 4, 6, 8].includes(length)) {
|
|
5035
|
+
return false;
|
|
5036
|
+
}
|
|
5037
|
+
const next = chars[index];
|
|
5038
|
+
return next === void 0 || next === " " || next === " " || next === "\r" || next === "\n";
|
|
3330
5039
|
}
|
|
3331
5040
|
|
|
3332
5041
|
// packages/core/dist/load.js
|
|
3333
|
-
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0)?$/i;
|
|
5042
|
+
var ATLAS_SCHEMA_PATTERN = /^schema\s+2(?:\.0|\.1)?$/i;
|
|
5043
|
+
var ATLAS_SCHEMA_21_PATTERN = /^schema\s+2\.1$/i;
|
|
3334
5044
|
var LEGACY_DRAFT_SCHEMA_PATTERN = /^schema\s+2\.0-draft$/i;
|
|
3335
5045
|
function detectWorldOrbitSchemaVersion(source) {
|
|
3336
|
-
for (const line of source.split(/\r?\n/)) {
|
|
5046
|
+
for (const line of stripCommentsForSchemaDetection(source).split(/\r?\n/)) {
|
|
3337
5047
|
const trimmed = line.trim();
|
|
3338
5048
|
if (!trimmed) {
|
|
3339
5049
|
continue;
|
|
@@ -3341,6 +5051,9 @@
|
|
|
3341
5051
|
if (LEGACY_DRAFT_SCHEMA_PATTERN.test(trimmed)) {
|
|
3342
5052
|
return "2.0-draft";
|
|
3343
5053
|
}
|
|
5054
|
+
if (ATLAS_SCHEMA_21_PATTERN.test(trimmed)) {
|
|
5055
|
+
return "2.1";
|
|
5056
|
+
}
|
|
3344
5057
|
if (ATLAS_SCHEMA_PATTERN.test(trimmed)) {
|
|
3345
5058
|
return "2.0";
|
|
3346
5059
|
}
|
|
@@ -3348,6 +5061,49 @@
|
|
|
3348
5061
|
}
|
|
3349
5062
|
return "1.0";
|
|
3350
5063
|
}
|
|
5064
|
+
function stripCommentsForSchemaDetection(source) {
|
|
5065
|
+
const chars = [...source];
|
|
5066
|
+
let inString = false;
|
|
5067
|
+
let inBlockComment = false;
|
|
5068
|
+
for (let index = 0; index < chars.length; index++) {
|
|
5069
|
+
const ch = chars[index];
|
|
5070
|
+
const next = chars[index + 1];
|
|
5071
|
+
if (inBlockComment) {
|
|
5072
|
+
if (ch === "*" && next === "/") {
|
|
5073
|
+
chars[index] = " ";
|
|
5074
|
+
chars[index + 1] = " ";
|
|
5075
|
+
inBlockComment = false;
|
|
5076
|
+
index++;
|
|
5077
|
+
continue;
|
|
5078
|
+
}
|
|
5079
|
+
if (ch !== "\n" && ch !== "\r") {
|
|
5080
|
+
chars[index] = " ";
|
|
5081
|
+
}
|
|
5082
|
+
continue;
|
|
5083
|
+
}
|
|
5084
|
+
if (!inString && ch === "/" && next === "*") {
|
|
5085
|
+
chars[index] = " ";
|
|
5086
|
+
chars[index + 1] = " ";
|
|
5087
|
+
inBlockComment = true;
|
|
5088
|
+
index++;
|
|
5089
|
+
continue;
|
|
5090
|
+
}
|
|
5091
|
+
if (!inString && ch === "#") {
|
|
5092
|
+
chars[index] = " ";
|
|
5093
|
+
let inner = index + 1;
|
|
5094
|
+
while (inner < chars.length && chars[inner] !== "\n" && chars[inner] !== "\r") {
|
|
5095
|
+
chars[inner] = " ";
|
|
5096
|
+
inner++;
|
|
5097
|
+
}
|
|
5098
|
+
index = inner - 1;
|
|
5099
|
+
continue;
|
|
5100
|
+
}
|
|
5101
|
+
if (ch === '"' && chars[index - 1] !== "\\") {
|
|
5102
|
+
inString = !inString;
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
return chars.join("");
|
|
5106
|
+
}
|
|
3351
5107
|
function loadWorldOrbitSource(source) {
|
|
3352
5108
|
const result = loadWorldOrbitSourceWithDiagnostics(source);
|
|
3353
5109
|
if (!result.ok || !result.value) {
|
|
@@ -3358,36 +5114,36 @@
|
|
|
3358
5114
|
}
|
|
3359
5115
|
function loadWorldOrbitSourceWithDiagnostics(source) {
|
|
3360
5116
|
const schemaVersion = detectWorldOrbitSchemaVersion(source);
|
|
3361
|
-
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft") {
|
|
5117
|
+
if (schemaVersion === "2.0" || schemaVersion === "2.0-draft" || schemaVersion === "2.1") {
|
|
3362
5118
|
return loadAtlasSourceWithDiagnostics(source, schemaVersion);
|
|
3363
5119
|
}
|
|
3364
5120
|
let ast;
|
|
3365
5121
|
try {
|
|
3366
5122
|
ast = parseWorldOrbit(source);
|
|
3367
|
-
} catch (
|
|
5123
|
+
} catch (error2) {
|
|
3368
5124
|
return {
|
|
3369
5125
|
ok: false,
|
|
3370
5126
|
value: null,
|
|
3371
|
-
diagnostics: [diagnosticFromError(
|
|
5127
|
+
diagnostics: [diagnosticFromError(error2, "parse")]
|
|
3372
5128
|
};
|
|
3373
5129
|
}
|
|
3374
5130
|
let document2;
|
|
3375
5131
|
try {
|
|
3376
5132
|
document2 = normalizeDocument(ast);
|
|
3377
|
-
} catch (
|
|
5133
|
+
} catch (error2) {
|
|
3378
5134
|
return {
|
|
3379
5135
|
ok: false,
|
|
3380
5136
|
value: null,
|
|
3381
|
-
diagnostics: [diagnosticFromError(
|
|
5137
|
+
diagnostics: [diagnosticFromError(error2, "normalize")]
|
|
3382
5138
|
};
|
|
3383
5139
|
}
|
|
3384
5140
|
try {
|
|
3385
5141
|
validateDocument(document2);
|
|
3386
|
-
} catch (
|
|
5142
|
+
} catch (error2) {
|
|
3387
5143
|
return {
|
|
3388
5144
|
ok: false,
|
|
3389
5145
|
value: null,
|
|
3390
|
-
diagnostics: [diagnosticFromError(
|
|
5146
|
+
diagnostics: [diagnosticFromError(error2, "validate")]
|
|
3391
5147
|
};
|
|
3392
5148
|
}
|
|
3393
5149
|
return {
|
|
@@ -3407,30 +5163,29 @@
|
|
|
3407
5163
|
let atlasDocument;
|
|
3408
5164
|
try {
|
|
3409
5165
|
atlasDocument = parseWorldOrbitAtlas(source);
|
|
3410
|
-
} catch (
|
|
5166
|
+
} catch (error2) {
|
|
3411
5167
|
return {
|
|
3412
5168
|
ok: false,
|
|
3413
5169
|
value: null,
|
|
3414
|
-
diagnostics: [diagnosticFromError(
|
|
5170
|
+
diagnostics: [diagnosticFromError(error2, "parse", "load.atlas.failed")]
|
|
3415
5171
|
};
|
|
3416
5172
|
}
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
document2 = materializeAtlasDocument(atlasDocument);
|
|
3420
|
-
} catch (error) {
|
|
5173
|
+
const atlasDiagnostics = [...atlasDocument.diagnostics];
|
|
5174
|
+
if (atlasDiagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
3421
5175
|
return {
|
|
3422
5176
|
ok: false,
|
|
3423
5177
|
value: null,
|
|
3424
|
-
diagnostics:
|
|
5178
|
+
diagnostics: atlasDiagnostics
|
|
3425
5179
|
};
|
|
3426
5180
|
}
|
|
5181
|
+
let document2;
|
|
3427
5182
|
try {
|
|
3428
|
-
|
|
3429
|
-
} catch (
|
|
5183
|
+
document2 = materializeAtlasDocument(atlasDocument);
|
|
5184
|
+
} catch (error2) {
|
|
3430
5185
|
return {
|
|
3431
5186
|
ok: false,
|
|
3432
5187
|
value: null,
|
|
3433
|
-
diagnostics: [diagnosticFromError(
|
|
5188
|
+
diagnostics: [diagnosticFromError(error2, "normalize", "load.atlas.materialize.failed")]
|
|
3434
5189
|
};
|
|
3435
5190
|
}
|
|
3436
5191
|
const loaded = {
|
|
@@ -3439,12 +5194,12 @@
|
|
|
3439
5194
|
document: document2,
|
|
3440
5195
|
atlasDocument,
|
|
3441
5196
|
draftDocument: atlasDocument,
|
|
3442
|
-
diagnostics:
|
|
5197
|
+
diagnostics: atlasDiagnostics
|
|
3443
5198
|
};
|
|
3444
5199
|
return {
|
|
3445
5200
|
ok: true,
|
|
3446
5201
|
value: loaded,
|
|
3447
|
-
diagnostics:
|
|
5202
|
+
diagnostics: atlasDiagnostics
|
|
3448
5203
|
};
|
|
3449
5204
|
}
|
|
3450
5205
|
|
|
@@ -3627,6 +5382,8 @@
|
|
|
3627
5382
|
const imageDefinitions = buildImageDefinitions(visibleObjects);
|
|
3628
5383
|
const orbitMarkup = layers.orbits ? renderOrbitLayer(scene, visibleObjectIds, layers.structures) : { back: "", front: "" };
|
|
3629
5384
|
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("") : "";
|
|
5385
|
+
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("") : "";
|
|
5386
|
+
const eventMarkup = layers.events ? scene.events.filter((event) => !event.hidden).map((event) => renderSceneEventOverlay(scene, event, visibleObjectIds, theme)).join("") : "";
|
|
3630
5387
|
const objectMarkup = layers.objects ? visibleObjects.map((object) => renderSceneObject(object, options.selectedObjectId ?? null, theme)).join("") : "";
|
|
3631
5388
|
const labelMarkup = layers.labels ? visibleLabels.map((label) => renderSceneLabel(scene, label, options.selectedObjectId ?? null)).join("") : "";
|
|
3632
5389
|
const metadataMarkup = layers.metadata ? `<text class="wo-title" x="56" y="64">${escapeXml(scene.title)}</text>
|
|
@@ -3661,6 +5418,10 @@
|
|
|
3661
5418
|
.wo-orbit-back { opacity: 0.38; stroke-dasharray: 8 6; }
|
|
3662
5419
|
.wo-orbit-front { opacity: 0.9; }
|
|
3663
5420
|
.wo-orbit-band { stroke: ${theme.orbitBand}; stroke-linecap: round; }
|
|
5421
|
+
.wo-relation { stroke: ${theme.relation}; stroke-width: 2; stroke-dasharray: 10 6; }
|
|
5422
|
+
.wo-event-line { stroke: ${theme.accent}; stroke-width: 1.6; stroke-dasharray: 5 5; opacity: 0.72; }
|
|
5423
|
+
.wo-event-node { fill: ${theme.accent}; stroke: ${theme.selected}; stroke-width: 1.4; opacity: 0.92; }
|
|
5424
|
+
.wo-event-label { fill: ${theme.accent}; font-family: ${theme.fontFamily}; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
3664
5425
|
.wo-leader { stroke: ${theme.leader}; stroke-width: 1.5; stroke-dasharray: 6 5; }
|
|
3665
5426
|
.wo-label { fill: ${theme.ink}; font-family: ${theme.fontFamily}; font-weight: 600; letter-spacing: 0.02em; }
|
|
3666
5427
|
.wo-label-secondary { fill: ${theme.muted}; font-family: ${theme.fontFamily}; font-weight: 500; }
|
|
@@ -3694,6 +5455,8 @@
|
|
|
3694
5455
|
<g data-worldorbit-world-content="true">
|
|
3695
5456
|
${layers.orbits ? `<g data-layer-id="orbits-back">${orbitMarkup.back}</g>` : ""}
|
|
3696
5457
|
${layers.guides ? `<g data-layer-id="guides">${leaderMarkup}</g>` : ""}
|
|
5458
|
+
${layers.relations ? `<g data-layer-id="relations">${relationMarkup}</g>` : ""}
|
|
5459
|
+
${layers.events ? `<g data-layer-id="events">${eventMarkup}</g>` : ""}
|
|
3697
5460
|
${layers.objects ? `<g data-layer-id="objects">${objectMarkup}</g>` : ""}
|
|
3698
5461
|
${layers.orbits ? `<g data-layer-id="orbits-front">${orbitMarkup.front}</g>` : ""}
|
|
3699
5462
|
${layers.labels ? `<g data-layer-id="labels">${labelMarkup}</g>` : ""}
|
|
@@ -3701,6 +5464,20 @@
|
|
|
3701
5464
|
</g>
|
|
3702
5465
|
</g>
|
|
3703
5466
|
</svg>`;
|
|
5467
|
+
}
|
|
5468
|
+
function renderSceneEventOverlay(scene, event, visibleObjectIds, theme) {
|
|
5469
|
+
const participants = event.objectIds.filter((objectId) => visibleObjectIds.has(objectId)).map((objectId) => scene.objects.find((object) => object.objectId === objectId && !object.hidden)).filter(Boolean);
|
|
5470
|
+
if (participants.length === 0) {
|
|
5471
|
+
return "";
|
|
5472
|
+
}
|
|
5473
|
+
const stroke = event.event.color || theme.accent;
|
|
5474
|
+
const label = event.event.label || event.event.id;
|
|
5475
|
+
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("");
|
|
5476
|
+
return `<g class="wo-event" data-render-id="${escapeXml(event.renderId)}" data-event-id="${escapeAttribute(event.eventId)}">
|
|
5477
|
+
${lineMarkup}
|
|
5478
|
+
<circle class="wo-event-node" cx="${event.x}" cy="${event.y}" r="5" fill="${escapeAttribute(stroke)}" />
|
|
5479
|
+
<text class="wo-event-label" x="${event.x}" y="${event.y - 10}" text-anchor="middle" font-size="10">${escapeXml(label)}</text>
|
|
5480
|
+
</g>`;
|
|
3704
5481
|
}
|
|
3705
5482
|
function renderDocumentToSvg(document2, options = {}) {
|
|
3706
5483
|
return renderSceneToSvg(renderDocumentToScene(document2, options), options);
|
|
@@ -3754,10 +5531,11 @@
|
|
|
3754
5531
|
function renderSceneObject(sceneObject, selectedObjectId, theme) {
|
|
3755
5532
|
const { object, x, y, radius, visualRadius } = sceneObject;
|
|
3756
5533
|
const selectionClass = selectedObjectId === sceneObject.objectId ? " wo-object-selected" : "";
|
|
5534
|
+
const kindClass = object.properties.kind ? ` wo-kind-${String(object.properties.kind).toLowerCase().replace(/[^a-z0-9-]/g, "-")}` : "";
|
|
3757
5535
|
const palette = resolveObjectPalette(sceneObject, theme);
|
|
3758
5536
|
const imageMarkup = renderObjectImage(sceneObject);
|
|
3759
5537
|
const outlineMarkup = imageMarkup ? renderObjectBody(object, x, y, radius, palette, { outlineOnly: true }) : "";
|
|
3760
|
-
return `<g class="wo-object wo-object-${object.type}${selectionClass}" data-object-id="${escapeXml(sceneObject.objectId)}" data-parent-id="${escapeAttribute(sceneObject.parentId ?? "")}" data-group-id="${escapeAttribute(sceneObject.groupId ?? "")}" data-render-id="${escapeXml(sceneObject.renderId)}" tabindex="0" role="button" aria-label="${escapeXml(`${object.type} ${sceneObject.objectId}`)}">
|
|
5538
|
+
return `<g class="wo-object wo-object-${object.type}${kindClass}${selectionClass}" data-object-id="${escapeXml(sceneObject.objectId)}" data-parent-id="${escapeAttribute(sceneObject.parentId ?? "")}" data-group-id="${escapeAttribute(sceneObject.groupId ?? "")}" data-render-id="${escapeXml(sceneObject.renderId)}" tabindex="0" role="button" aria-label="${escapeXml(`${object.type} ${sceneObject.objectId}`)}">
|
|
3761
5539
|
<circle class="wo-selection-ring" cx="${x}" cy="${y}" r="${visualRadius + 8}" />
|
|
3762
5540
|
${renderAtmosphere(sceneObject, palette)}
|
|
3763
5541
|
${renderObjectBody(object, x, y, radius, palette)}
|
|
@@ -3791,8 +5569,33 @@
|
|
|
3791
5569
|
<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
3792
5570
|
case "structure":
|
|
3793
5571
|
return `<polygon points="${diamondPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
3794
|
-
case "phenomenon":
|
|
5572
|
+
case "phenomenon": {
|
|
5573
|
+
const kind = String(object.properties.kind ?? "").toLowerCase().replace(/_/g, "-");
|
|
5574
|
+
if (options.outlineOnly) {
|
|
5575
|
+
if (kind === "black-hole" || kind === "nebula" || kind === "galaxy" || kind === "dwarf-galaxy") {
|
|
5576
|
+
return `<circle cx="${x}" cy="${y}" r="${radius}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
5577
|
+
}
|
|
5578
|
+
return `<polygon points="${phenomenonPoints(x, y, radius)}" fill="transparent" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
5579
|
+
}
|
|
5580
|
+
if (kind === "black-hole") {
|
|
5581
|
+
return `<ellipse cx="${x}" cy="${y}" rx="${radius * 2.4}" ry="${radius * 0.55}" fill="none" stroke="${palette.accentRing ?? palette.stroke}" stroke-width="3.5" />
|
|
5582
|
+
<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="2" />`;
|
|
5583
|
+
}
|
|
5584
|
+
if (kind === "galaxy") {
|
|
5585
|
+
return `<ellipse cx="${x}" cy="${y}" rx="${radius * 2.6}" ry="${radius}" fill="${palette.halo ?? "none"}" stroke="none" />
|
|
5586
|
+
<ellipse cx="${x}" cy="${y}" rx="${radius * 1.5}" ry="${radius * 0.42}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.2" />
|
|
5587
|
+
<circle cx="${x}" cy="${y}" r="${radius * 0.28}" fill="${palette.core ?? "#fff"}" stroke="none" />`;
|
|
5588
|
+
}
|
|
5589
|
+
if (kind === "dwarf-galaxy") {
|
|
5590
|
+
return `<ellipse cx="${x}" cy="${y}" rx="${radius * 1.6}" ry="${radius * 0.55}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1" />
|
|
5591
|
+
<circle cx="${x}" cy="${y}" r="${radius * 0.25}" fill="${palette.core ?? "#fff"}" stroke="none" />`;
|
|
5592
|
+
}
|
|
5593
|
+
if (kind === "nebula") {
|
|
5594
|
+
return `<circle cx="${x}" cy="${y}" r="${radius * 2.2}" fill="${palette.halo ?? "none"}" stroke="none" />
|
|
5595
|
+
<circle cx="${x}" cy="${y}" r="${radius}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1" />`;
|
|
5596
|
+
}
|
|
3795
5597
|
return `<polygon points="${phenomenonPoints(x, y, radius)}" fill="${fill}" stroke="${palette.stroke}" stroke-width="1.4" />`;
|
|
5598
|
+
}
|
|
3796
5599
|
}
|
|
3797
5600
|
}
|
|
3798
5601
|
function renderAtmosphere(sceneObject, palette) {
|
|
@@ -3861,7 +5664,8 @@
|
|
|
3861
5664
|
}
|
|
3862
5665
|
}
|
|
3863
5666
|
function resolveObjectPalette(sceneObject, theme) {
|
|
3864
|
-
const
|
|
5667
|
+
const kind = String(sceneObject.object.properties.kind ?? "").toLowerCase().replace(/_/g, "-");
|
|
5668
|
+
const base = basePaletteForType(sceneObject.object.type, kind, theme);
|
|
3865
5669
|
const customFill = sceneObject.fillColor && isColorLike(sceneObject.fillColor) ? sceneObject.fillColor : base.fill;
|
|
3866
5670
|
const albedo = numericValue(sceneObject.object.properties.albedo);
|
|
3867
5671
|
const temperature = numericValue(sceneObject.object.properties.temperature);
|
|
@@ -3877,7 +5681,7 @@
|
|
|
3877
5681
|
tail: sceneObject.object.type === "comet" ? rgbaString(mixColors(fill, "#ffffff", 0.5) ?? fill, 0.72) : void 0
|
|
3878
5682
|
};
|
|
3879
5683
|
}
|
|
3880
|
-
function basePaletteForType(type, theme) {
|
|
5684
|
+
function basePaletteForType(type, kind, theme) {
|
|
3881
5685
|
switch (type) {
|
|
3882
5686
|
case "star":
|
|
3883
5687
|
return {
|
|
@@ -3899,8 +5703,26 @@
|
|
|
3899
5703
|
case "structure":
|
|
3900
5704
|
return { fill: theme.accentStrong, stroke: "#fff2ea" };
|
|
3901
5705
|
case "phenomenon":
|
|
3902
|
-
return
|
|
5706
|
+
return kindPhenomenonPalette(kind);
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
function kindPhenomenonPalette(kind) {
|
|
5710
|
+
if (kind === "galaxy") {
|
|
5711
|
+
return { fill: "rgba(165,125,255,0.55)", stroke: "rgba(210,185,255,0.75)", halo: "rgba(160,120,255,0.10)", core: "#ede0ff" };
|
|
5712
|
+
}
|
|
5713
|
+
if (kind === "dwarf-galaxy") {
|
|
5714
|
+
return { fill: "rgba(190,165,255,0.45)", stroke: "rgba(220,205,255,0.75)", core: "#ddd0ff" };
|
|
3903
5715
|
}
|
|
5716
|
+
if (kind === "black-hole") {
|
|
5717
|
+
return { fill: "#040408", stroke: "#ff6a00", accentRing: "rgba(255,140,20,0.72)" };
|
|
5718
|
+
}
|
|
5719
|
+
if (kind === "nebula") {
|
|
5720
|
+
return { fill: "rgba(105,205,255,0.45)", stroke: "rgba(180,235,255,0.72)", halo: "rgba(100,200,255,0.08)" };
|
|
5721
|
+
}
|
|
5722
|
+
if (kind === "void") {
|
|
5723
|
+
return { fill: "#05080f", stroke: "rgba(130,160,255,0.4)" };
|
|
5724
|
+
}
|
|
5725
|
+
return { fill: "#78ffd7", stroke: "#e9fff7" };
|
|
3904
5726
|
}
|
|
3905
5727
|
function applyTemperatureAndAlbedo(baseColor, temperature, albedo, type) {
|
|
3906
5728
|
let nextColor = baseColor;
|
|
@@ -4220,6 +6042,48 @@
|
|
|
4220
6042
|
});
|
|
4221
6043
|
}
|
|
4222
6044
|
const placement = details.object.placement;
|
|
6045
|
+
if (details.object.groups?.length) {
|
|
6046
|
+
fields.set("groups", {
|
|
6047
|
+
key: "groups",
|
|
6048
|
+
label: "Groups",
|
|
6049
|
+
value: details.object.groups.join(", ")
|
|
6050
|
+
});
|
|
6051
|
+
}
|
|
6052
|
+
if (details.object.epoch) {
|
|
6053
|
+
fields.set("epoch", {
|
|
6054
|
+
key: "epoch",
|
|
6055
|
+
label: "Epoch",
|
|
6056
|
+
value: details.object.epoch
|
|
6057
|
+
});
|
|
6058
|
+
}
|
|
6059
|
+
if (details.object.referencePlane) {
|
|
6060
|
+
fields.set("referencePlane", {
|
|
6061
|
+
key: "referencePlane",
|
|
6062
|
+
label: "Reference Plane",
|
|
6063
|
+
value: details.object.referencePlane
|
|
6064
|
+
});
|
|
6065
|
+
}
|
|
6066
|
+
if (details.object.tidalLock !== void 0) {
|
|
6067
|
+
fields.set("tidalLock", {
|
|
6068
|
+
key: "tidalLock",
|
|
6069
|
+
label: "Tidal Lock",
|
|
6070
|
+
value: details.object.tidalLock ? "true" : "false"
|
|
6071
|
+
});
|
|
6072
|
+
}
|
|
6073
|
+
if (details.object.resonance) {
|
|
6074
|
+
fields.set("resonance", {
|
|
6075
|
+
key: "resonance",
|
|
6076
|
+
label: "Resonance",
|
|
6077
|
+
value: `${details.object.resonance.targetObjectId} ${details.object.resonance.ratio}`
|
|
6078
|
+
});
|
|
6079
|
+
}
|
|
6080
|
+
if (details.relatedEvents.length > 0) {
|
|
6081
|
+
fields.set("events", {
|
|
6082
|
+
key: "events",
|
|
6083
|
+
label: "Events",
|
|
6084
|
+
value: details.relatedEvents.map((event) => event.event.label || event.event.id).join(", ")
|
|
6085
|
+
});
|
|
6086
|
+
}
|
|
4223
6087
|
if (placement?.mode === "at") {
|
|
4224
6088
|
fields.set("placement", {
|
|
4225
6089
|
key: "placement",
|
|
@@ -4389,6 +6253,9 @@
|
|
|
4389
6253
|
touchPoints.set(event.pointerId, point);
|
|
4390
6254
|
if (touchPoints.size === 2) {
|
|
4391
6255
|
touchGesture = createTouchGestureState(scene, state, touchPoints);
|
|
6256
|
+
} else if (touchPoints.size === 1) {
|
|
6257
|
+
dragDistance = 0;
|
|
6258
|
+
suppressClick = false;
|
|
4392
6259
|
}
|
|
4393
6260
|
return;
|
|
4394
6261
|
}
|
|
@@ -4406,7 +6273,9 @@
|
|
|
4406
6273
|
if (!behavior.touch || !touchPoints.has(event.pointerId)) {
|
|
4407
6274
|
return;
|
|
4408
6275
|
}
|
|
4409
|
-
touchPoints.
|
|
6276
|
+
const prevPoint = touchPoints.get(event.pointerId);
|
|
6277
|
+
const nextPoint2 = getViewportPointFromClient(event.clientX, event.clientY);
|
|
6278
|
+
touchPoints.set(event.pointerId, nextPoint2);
|
|
4410
6279
|
if (touchPoints.size === 2) {
|
|
4411
6280
|
if (!touchGesture) {
|
|
4412
6281
|
touchGesture = createTouchGestureState(scene, state, touchPoints);
|
|
@@ -4417,6 +6286,14 @@
|
|
|
4417
6286
|
const deltaX2 = current.center.x - touchGesture.startViewportCenter.x;
|
|
4418
6287
|
const deltaY2 = current.center.y - touchGesture.startViewportCenter.y;
|
|
4419
6288
|
updateState(panViewerState(zoomedState, deltaX2, deltaY2));
|
|
6289
|
+
} else if (touchPoints.size === 1) {
|
|
6290
|
+
const deltaX2 = nextPoint2.x - prevPoint.x;
|
|
6291
|
+
const deltaY2 = nextPoint2.y - prevPoint.y;
|
|
6292
|
+
dragDistance += Math.abs(deltaX2) + Math.abs(deltaY2);
|
|
6293
|
+
if (dragDistance > 2) {
|
|
6294
|
+
suppressClick = true;
|
|
6295
|
+
}
|
|
6296
|
+
updateState(panViewerState(state, deltaX2, deltaY2));
|
|
4420
6297
|
}
|
|
4421
6298
|
return;
|
|
4422
6299
|
}
|
|
@@ -4621,6 +6498,12 @@
|
|
|
4621
6498
|
emitAtlasStateChange();
|
|
4622
6499
|
return true;
|
|
4623
6500
|
},
|
|
6501
|
+
getActiveEventId() {
|
|
6502
|
+
return renderOptions.activeEventId ?? null;
|
|
6503
|
+
},
|
|
6504
|
+
setActiveEvent(id) {
|
|
6505
|
+
api.setRenderOptions({ activeEventId: id });
|
|
6506
|
+
},
|
|
4624
6507
|
search(query, limit = 12) {
|
|
4625
6508
|
return searchSceneObjects(scene, query, limit);
|
|
4626
6509
|
},
|
|
@@ -4881,8 +6764,11 @@
|
|
|
4881
6764
|
renderObject,
|
|
4882
6765
|
label: scene.labels.find((label) => label.objectId === renderObject.objectId && !label.hidden) ?? null,
|
|
4883
6766
|
group: scene.groups.find((group) => group.renderId === renderObject.groupId) ?? null,
|
|
6767
|
+
semanticGroups: scene.semanticGroups.filter((group) => renderObject.semanticGroupIds.includes(group.id)),
|
|
4884
6768
|
orbit: scene.orbitVisuals.find((orbit) => orbit.objectId === renderObject.objectId && !orbit.hidden) ?? null,
|
|
4885
6769
|
relatedOrbits: scene.orbitVisuals.filter((orbit) => !orbit.hidden && (orbit.objectId === renderObject.objectId || renderObject.ancestorIds.includes(orbit.objectId) || renderObject.childIds.includes(orbit.objectId))),
|
|
6770
|
+
relations: scene.relations.filter((relation) => !relation.hidden && (relation.fromObjectId === renderObject.objectId || relation.toObjectId === renderObject.objectId)),
|
|
6771
|
+
relatedEvents: scene.events.filter((event) => !event.hidden && (event.targetObjectId === renderObject.objectId || event.objectIds.includes(renderObject.objectId))),
|
|
4886
6772
|
parent: getObjectById(renderObject.parentId),
|
|
4887
6773
|
children: renderObject.childIds.map((childId) => getObjectById(childId)).filter(Boolean),
|
|
4888
6774
|
ancestors: renderObject.ancestorIds.map((ancestorId) => getObjectById(ancestorId)).filter(Boolean),
|
|
@@ -5199,7 +7085,8 @@
|
|
|
5199
7085
|
filter: renderOptions.filter ? { ...renderOptions.filter } : void 0,
|
|
5200
7086
|
scaleModel: renderOptions.scaleModel ? { ...renderOptions.scaleModel } : void 0,
|
|
5201
7087
|
layers: renderOptions.layers ? { ...renderOptions.layers } : void 0,
|
|
5202
|
-
theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme
|
|
7088
|
+
theme: renderOptions.theme && typeof renderOptions.theme === "object" ? { ...renderOptions.theme } : renderOptions.theme,
|
|
7089
|
+
activeEventId: renderOptions.activeEventId ?? null
|
|
5203
7090
|
};
|
|
5204
7091
|
}
|
|
5205
7092
|
function mergeRenderOptions(current, next) {
|
|
@@ -5219,7 +7106,7 @@
|
|
|
5219
7106
|
};
|
|
5220
7107
|
}
|
|
5221
7108
|
function hasSceneAffectingRenderOptions(options) {
|
|
5222
|
-
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;
|
|
7109
|
+
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 || options.activeEventId !== void 0;
|
|
5223
7110
|
}
|
|
5224
7111
|
function resolveSourceRenderOptions2(loaded, renderOptions) {
|
|
5225
7112
|
const atlasDocument = loaded.atlasDocument ?? loaded.draftDocument;
|
|
@@ -5507,6 +7394,7 @@
|
|
|
5507
7394
|
const controls = {
|
|
5508
7395
|
search: options.controls?.search ?? true,
|
|
5509
7396
|
typeFilter: options.controls?.typeFilter ?? true,
|
|
7397
|
+
groupFilter: options.controls?.groupFilter ?? true,
|
|
5510
7398
|
viewpointSelect: options.controls?.viewpointSelect ?? true,
|
|
5511
7399
|
inspector: options.controls?.inspector ?? true,
|
|
5512
7400
|
bookmarks: options.controls?.bookmarks ?? true
|
|
@@ -5516,6 +7404,7 @@
|
|
|
5516
7404
|
const toolbar = container.querySelector("[data-atlas-toolbar]");
|
|
5517
7405
|
const searchInput = container.querySelector("[data-atlas-search]");
|
|
5518
7406
|
const typeFilterSelect = container.querySelector("[data-atlas-type-filter]");
|
|
7407
|
+
const groupFilterSelect = container.querySelector("[data-atlas-group-filter]");
|
|
5519
7408
|
const viewpointSelect = container.querySelector("[data-atlas-viewpoint]");
|
|
5520
7409
|
const bookmarkButton = container.querySelector("[data-atlas-bookmark]");
|
|
5521
7410
|
const bookmarkList = container.querySelector("[data-atlas-bookmarks]");
|
|
@@ -5528,6 +7417,7 @@
|
|
|
5528
7417
|
const baseFilter = normalizeViewerFilter(options.initialFilter ?? null);
|
|
5529
7418
|
let searchQuery = options.initialQuery?.trim() ?? baseFilter?.query ?? "";
|
|
5530
7419
|
let objectTypeFilter = options.initialObjectType ?? (baseFilter?.objectTypes?.length === 1 ? baseFilter.objectTypes[0] : null);
|
|
7420
|
+
let groupFilter = baseFilter?.groupIds?.[0] ?? null;
|
|
5531
7421
|
let bookmarks = [];
|
|
5532
7422
|
let viewer;
|
|
5533
7423
|
viewer = createInteractiveViewer(stage, {
|
|
@@ -5575,6 +7465,7 @@
|
|
|
5575
7465
|
});
|
|
5576
7466
|
applyCurrentFilter();
|
|
5577
7467
|
populateViewpoints();
|
|
7468
|
+
populateGroups();
|
|
5578
7469
|
syncControlsFromFilter(viewer.getFilter());
|
|
5579
7470
|
renderBookmarks();
|
|
5580
7471
|
updateSearchResults();
|
|
@@ -5587,6 +7478,10 @@
|
|
|
5587
7478
|
objectTypeFilter = typeFilterSelect.value || null;
|
|
5588
7479
|
applyCurrentFilter();
|
|
5589
7480
|
});
|
|
7481
|
+
groupFilterSelect?.addEventListener("change", () => {
|
|
7482
|
+
groupFilter = groupFilterSelect.value || null;
|
|
7483
|
+
applyCurrentFilter();
|
|
7484
|
+
});
|
|
5590
7485
|
viewpointSelect?.addEventListener("change", () => {
|
|
5591
7486
|
const activeViewer = requireViewer();
|
|
5592
7487
|
if (!viewpointSelect.value) {
|
|
@@ -5728,6 +7623,7 @@
|
|
|
5728
7623
|
return api;
|
|
5729
7624
|
function refreshAfterInputChange() {
|
|
5730
7625
|
populateViewpoints();
|
|
7626
|
+
populateGroups();
|
|
5731
7627
|
applyCurrentFilter();
|
|
5732
7628
|
renderBookmarks();
|
|
5733
7629
|
updateSearchResults();
|
|
@@ -5744,19 +7640,23 @@
|
|
|
5744
7640
|
query: searchQuery || void 0,
|
|
5745
7641
|
objectTypes: objectTypeFilter ? [objectTypeFilter] : void 0,
|
|
5746
7642
|
tags: baseFilter?.tags,
|
|
5747
|
-
groupIds: baseFilter?.groupIds,
|
|
7643
|
+
groupIds: groupFilter ? [groupFilter] : baseFilter?.groupIds,
|
|
5748
7644
|
includeAncestors: baseFilter?.includeAncestors ?? true
|
|
5749
7645
|
});
|
|
5750
7646
|
}
|
|
5751
7647
|
function syncControlsFromFilter(filter) {
|
|
5752
7648
|
searchQuery = filter?.query?.trim() ?? "";
|
|
5753
7649
|
objectTypeFilter = filter?.objectTypes?.length === 1 ? filter.objectTypes[0] : null;
|
|
7650
|
+
groupFilter = filter?.groupIds?.length === 1 ? filter.groupIds[0] : null;
|
|
5754
7651
|
if (searchInput && document.activeElement !== searchInput) {
|
|
5755
7652
|
searchInput.value = searchQuery;
|
|
5756
7653
|
}
|
|
5757
7654
|
if (typeFilterSelect) {
|
|
5758
7655
|
typeFilterSelect.value = objectTypeFilter ?? "";
|
|
5759
7656
|
}
|
|
7657
|
+
if (groupFilterSelect) {
|
|
7658
|
+
groupFilterSelect.value = groupFilter ?? "";
|
|
7659
|
+
}
|
|
5760
7660
|
}
|
|
5761
7661
|
function populateViewpoints() {
|
|
5762
7662
|
if (!viewpointSelect) {
|
|
@@ -5770,6 +7670,17 @@
|
|
|
5770
7670
|
].join("");
|
|
5771
7671
|
viewpointSelect.value = active;
|
|
5772
7672
|
}
|
|
7673
|
+
function populateGroups() {
|
|
7674
|
+
if (!groupFilterSelect) {
|
|
7675
|
+
return;
|
|
7676
|
+
}
|
|
7677
|
+
const activeViewer = requireViewer();
|
|
7678
|
+
groupFilterSelect.innerHTML = [
|
|
7679
|
+
`<option value="">All groups</option>`,
|
|
7680
|
+
...activeViewer.getScene().semanticGroups.map((group) => `<option value="${escapeHtml2(group.id)}">${escapeHtml2(group.label)}</option>`)
|
|
7681
|
+
].join("");
|
|
7682
|
+
groupFilterSelect.value = groupFilter ?? "";
|
|
7683
|
+
}
|
|
5773
7684
|
function syncViewpointControl() {
|
|
5774
7685
|
if (!viewpointSelect) {
|
|
5775
7686
|
return;
|
|
@@ -5803,6 +7714,9 @@
|
|
|
5803
7714
|
projection: activeViewer.getScene().projection,
|
|
5804
7715
|
renderPreset: activeViewer.getScene().renderPreset,
|
|
5805
7716
|
groupCount: activeViewer.getScene().groups.length,
|
|
7717
|
+
semanticGroupCount: activeViewer.getScene().semanticGroups.length,
|
|
7718
|
+
relationCount: activeViewer.getScene().relations.length,
|
|
7719
|
+
eventCount: activeViewer.getScene().events.length,
|
|
5806
7720
|
viewpointCount: activeViewer.getScene().viewpoints.length
|
|
5807
7721
|
}
|
|
5808
7722
|
};
|
|
@@ -5835,6 +7749,12 @@
|
|
|
5835
7749
|
<option value="phenomenon">Phenomenon</option>
|
|
5836
7750
|
</select>
|
|
5837
7751
|
</label>` : "",
|
|
7752
|
+
controls.groupFilter ? `<label class="wo-atlas-field">
|
|
7753
|
+
<span>Group</span>
|
|
7754
|
+
<select data-atlas-group-filter>
|
|
7755
|
+
<option value="">All groups</option>
|
|
7756
|
+
</select>
|
|
7757
|
+
</label>` : "",
|
|
5838
7758
|
controls.viewpointSelect ? `<label class="wo-atlas-field">
|
|
5839
7759
|
<span>Viewpoint</span>
|
|
5840
7760
|
<select data-atlas-viewpoint>
|