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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "worldorbit",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.17",
|
|
4
4
|
"description": "A text-based DSL and parser pipeline for orbital worldbuilding",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/unpkg/worldorbit.esm.js",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"license": "MIT",
|
|
53
53
|
"scripts": {
|
|
54
54
|
"build": "node ./scripts/build.mjs",
|
|
55
|
+
"prepack": "node ./scripts/prepack.mjs",
|
|
55
56
|
"test": "npm run build && node --test test/*.test.js",
|
|
56
57
|
"check": "npm run test"
|
|
57
58
|
},
|
|
@@ -64,4 +65,4 @@
|
|
|
64
65
|
"typescript": "^5.6.0",
|
|
65
66
|
"unified": "^11.0.5"
|
|
66
67
|
}
|
|
67
|
-
}
|
|
68
|
+
}
|
|
@@ -25,6 +25,7 @@ export function createEmptyAtlasDocument(systemId = "WorldOrbit", version = "2.0
|
|
|
25
25
|
},
|
|
26
26
|
groups: [],
|
|
27
27
|
relations: [],
|
|
28
|
+
events: [],
|
|
28
29
|
objects: [],
|
|
29
30
|
diagnostics: [],
|
|
30
31
|
};
|
|
@@ -51,6 +52,12 @@ export function listAtlasDocumentPaths(document) {
|
|
|
51
52
|
for (const relation of [...document.relations].sort(compareIdLike)) {
|
|
52
53
|
paths.push({ kind: "relation", id: relation.id });
|
|
53
54
|
}
|
|
55
|
+
for (const event of [...document.events].sort(compareIdLike)) {
|
|
56
|
+
paths.push({ kind: "event", id: event.id });
|
|
57
|
+
for (const pose of [...event.positions].sort(comparePoseObjectId)) {
|
|
58
|
+
paths.push({ kind: "event-pose", id: event.id, key: pose.objectId });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
54
61
|
for (const object of [...document.objects].sort(compareIdLike)) {
|
|
55
62
|
paths.push({ kind: "object", id: object.id });
|
|
56
63
|
}
|
|
@@ -66,6 +73,10 @@ export function getAtlasDocumentNode(document, path) {
|
|
|
66
73
|
return path.key ? (document.system?.atlasMetadata[path.key] ?? null) : null;
|
|
67
74
|
case "group":
|
|
68
75
|
return path.id ? findGroup(document, path.id) : null;
|
|
76
|
+
case "event":
|
|
77
|
+
return path.id ? findEvent(document, path.id) : null;
|
|
78
|
+
case "event-pose":
|
|
79
|
+
return path.id && path.key ? findEventPose(document, path.id, path.key) : null;
|
|
69
80
|
case "object":
|
|
70
81
|
return path.id ? findObject(document, path.id) : null;
|
|
71
82
|
case "viewpoint":
|
|
@@ -106,6 +117,18 @@ export function upsertAtlasDocumentNode(document, path, value) {
|
|
|
106
117
|
}
|
|
107
118
|
upsertById(next.groups, value);
|
|
108
119
|
return next;
|
|
120
|
+
case "event":
|
|
121
|
+
if (!path.id) {
|
|
122
|
+
throw new Error('Event updates require an "id" value.');
|
|
123
|
+
}
|
|
124
|
+
upsertById(next.events, value);
|
|
125
|
+
return next;
|
|
126
|
+
case "event-pose":
|
|
127
|
+
if (!path.id || !path.key) {
|
|
128
|
+
throw new Error('Event pose updates require an event "id" and pose "key" value.');
|
|
129
|
+
}
|
|
130
|
+
upsertEventPose(next.events, path.id, value);
|
|
131
|
+
return next;
|
|
109
132
|
case "object":
|
|
110
133
|
if (!path.id) {
|
|
111
134
|
throw new Error('Object updates require an "id" value.');
|
|
@@ -154,6 +177,19 @@ export function removeAtlasDocumentNode(document, path) {
|
|
|
154
177
|
next.groups = next.groups.filter((group) => group.id !== path.id);
|
|
155
178
|
}
|
|
156
179
|
return next;
|
|
180
|
+
case "event":
|
|
181
|
+
if (path.id) {
|
|
182
|
+
next.events = next.events.filter((event) => event.id !== path.id);
|
|
183
|
+
}
|
|
184
|
+
return next;
|
|
185
|
+
case "event-pose":
|
|
186
|
+
if (path.id && path.key) {
|
|
187
|
+
const event = findEvent(next, path.id);
|
|
188
|
+
if (event) {
|
|
189
|
+
event.positions = event.positions.filter((pose) => pose.objectId !== path.key);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return next;
|
|
157
193
|
case "viewpoint":
|
|
158
194
|
if (path.id) {
|
|
159
195
|
system.viewpoints = system.viewpoints.filter((viewpoint) => viewpoint.id !== path.id);
|
|
@@ -222,6 +258,22 @@ export function resolveAtlasDiagnosticPath(document, diagnostic) {
|
|
|
222
258
|
};
|
|
223
259
|
}
|
|
224
260
|
}
|
|
261
|
+
if (diagnostic.field?.startsWith("event.")) {
|
|
262
|
+
const parts = diagnostic.field.split(".");
|
|
263
|
+
if (parts[1] && findEvent(document, parts[1])) {
|
|
264
|
+
if (parts[2] === "pose" && parts[3] && findEventPose(document, parts[1], parts[3])) {
|
|
265
|
+
return {
|
|
266
|
+
kind: "event-pose",
|
|
267
|
+
id: parts[1],
|
|
268
|
+
key: parts[3],
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
kind: "event",
|
|
273
|
+
id: parts[1],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
225
277
|
if (diagnostic.field && diagnostic.field in ensureSystem(document).atlasMetadata) {
|
|
226
278
|
return {
|
|
227
279
|
kind: "metadata",
|
|
@@ -253,6 +305,12 @@ function findGroup(document, groupId) {
|
|
|
253
305
|
function findRelation(document, relationId) {
|
|
254
306
|
return document.relations.find((relation) => relation.id === relationId) ?? null;
|
|
255
307
|
}
|
|
308
|
+
function findEvent(document, eventId) {
|
|
309
|
+
return document.events.find((event) => event.id === eventId) ?? null;
|
|
310
|
+
}
|
|
311
|
+
function findEventPose(document, eventId, objectId) {
|
|
312
|
+
return findEvent(document, eventId)?.positions.find((pose) => pose.objectId === objectId) ?? null;
|
|
313
|
+
}
|
|
256
314
|
function findViewpoint(system, viewpointId) {
|
|
257
315
|
return system?.viewpoints.find((viewpoint) => viewpoint.id === viewpointId) ?? null;
|
|
258
316
|
}
|
|
@@ -268,6 +326,22 @@ function upsertById(items, value) {
|
|
|
268
326
|
}
|
|
269
327
|
items[index] = value;
|
|
270
328
|
}
|
|
329
|
+
function upsertEventPose(events, eventId, value) {
|
|
330
|
+
const event = events.find((entry) => entry.id === eventId);
|
|
331
|
+
if (!event) {
|
|
332
|
+
throw new Error(`Unknown event "${eventId}" for pose update.`);
|
|
333
|
+
}
|
|
334
|
+
const index = event.positions.findIndex((entry) => entry.objectId === value.objectId);
|
|
335
|
+
if (index === -1) {
|
|
336
|
+
event.positions.push(value);
|
|
337
|
+
event.positions.sort(comparePoseObjectId);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
event.positions[index] = value;
|
|
341
|
+
}
|
|
271
342
|
function compareIdLike(left, right) {
|
|
272
343
|
return left.id.localeCompare(right.id);
|
|
273
344
|
}
|
|
345
|
+
function comparePoseObjectId(left, right) {
|
|
346
|
+
return left.objectId.localeCompare(right.objectId);
|
|
347
|
+
}
|
|
@@ -11,6 +11,7 @@ export function collectAtlasDiagnostics(document, sourceSchemaVersion) {
|
|
|
11
11
|
const diagnostics = [];
|
|
12
12
|
const objectMap = new Map(document.objects.map((object) => [object.id, object]));
|
|
13
13
|
const groupIds = new Set(document.groups.map((group) => group.id));
|
|
14
|
+
const eventIds = new Set(document.events.map((event) => event.id));
|
|
14
15
|
if (!document.system) {
|
|
15
16
|
diagnostics.push(error("validate.system.required", "Atlas documents must declare exactly one system."));
|
|
16
17
|
}
|
|
@@ -20,6 +21,7 @@ export function collectAtlasDiagnostics(document, sourceSchemaVersion) {
|
|
|
20
21
|
["viewpoint", document.system?.viewpoints.map((viewpoint) => viewpoint.id) ?? []],
|
|
21
22
|
["annotation", document.system?.annotations.map((annotation) => annotation.id) ?? []],
|
|
22
23
|
["relation", document.relations.map((relation) => relation.id)],
|
|
24
|
+
["event", document.events.map((event) => event.id)],
|
|
23
25
|
["object", document.objects.map((object) => object.id)],
|
|
24
26
|
]) {
|
|
25
27
|
for (const id of ids) {
|
|
@@ -36,11 +38,14 @@ export function collectAtlasDiagnostics(document, sourceSchemaVersion) {
|
|
|
36
38
|
validateRelation(relation, objectMap, diagnostics);
|
|
37
39
|
}
|
|
38
40
|
for (const viewpoint of document.system?.viewpoints ?? []) {
|
|
39
|
-
|
|
41
|
+
validateViewpoint(viewpoint.filter, viewpoint.events ?? [], groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpoint.id);
|
|
40
42
|
}
|
|
41
43
|
for (const object of document.objects) {
|
|
42
44
|
validateObject(object, document.system, objectMap, groupIds, diagnostics);
|
|
43
45
|
}
|
|
46
|
+
for (const event of document.events) {
|
|
47
|
+
validateEvent(event, objectMap, diagnostics);
|
|
48
|
+
}
|
|
44
49
|
return diagnostics;
|
|
45
50
|
}
|
|
46
51
|
function validateRelation(relation, objectMap, diagnostics) {
|
|
@@ -60,13 +65,19 @@ function validateRelation(relation, objectMap, diagnostics) {
|
|
|
60
65
|
diagnostics.push(error("validate.relation.kind.required", `Relation "${relation.id}" is missing a "kind" value.`));
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
|
-
function
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
function validateViewpoint(filter, eventRefs, groupIds, eventIds, sourceSchemaVersion, diagnostics, viewpointId) {
|
|
69
|
+
if (sourceSchemaVersion === "2.1") {
|
|
70
|
+
if (filter) {
|
|
71
|
+
for (const groupId of filter.groupIds) {
|
|
72
|
+
if (!groupIds.has(groupId)) {
|
|
73
|
+
diagnostics.push(warn("validate.viewpoint.group.unknown", `Unknown group "${groupId}" in viewpoint "${viewpointId}".`, undefined, `viewpoint.${viewpointId}.groups`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const eventId of eventRefs) {
|
|
78
|
+
if (!eventIds.has(eventId)) {
|
|
79
|
+
diagnostics.push(warn("validate.viewpoint.event.unknown", `Unknown event "${eventId}" in viewpoint "${viewpointId}".`, undefined, `viewpoint.${viewpointId}.events`));
|
|
80
|
+
}
|
|
70
81
|
}
|
|
71
82
|
}
|
|
72
83
|
}
|
|
@@ -156,6 +167,109 @@ function validateObject(object, system, objectMap, groupIds, diagnostics) {
|
|
|
156
167
|
}
|
|
157
168
|
}
|
|
158
169
|
}
|
|
170
|
+
function validateEvent(event, objectMap, diagnostics) {
|
|
171
|
+
const fieldPrefix = `event.${event.id}`;
|
|
172
|
+
const referencedIds = new Set();
|
|
173
|
+
if (!event.kind.trim()) {
|
|
174
|
+
diagnostics.push(error("validate.event.kind.required", `Event "${event.id}" is missing a "kind" value.`, undefined, `${fieldPrefix}.kind`));
|
|
175
|
+
}
|
|
176
|
+
if (!event.targetObjectId && event.participantObjectIds.length === 0) {
|
|
177
|
+
diagnostics.push(error("validate.event.references.required", `Event "${event.id}" must define a "target" or at least one participant.`, undefined, `${fieldPrefix}.participants`));
|
|
178
|
+
}
|
|
179
|
+
if (event.targetObjectId) {
|
|
180
|
+
referencedIds.add(event.targetObjectId);
|
|
181
|
+
if (!objectMap.has(event.targetObjectId)) {
|
|
182
|
+
diagnostics.push(error("validate.event.target.unknown", `Unknown event target "${event.targetObjectId}" on "${event.id}".`, undefined, `${fieldPrefix}.target`));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const seenParticipants = new Set();
|
|
186
|
+
for (const participantId of event.participantObjectIds) {
|
|
187
|
+
referencedIds.add(participantId);
|
|
188
|
+
if (seenParticipants.has(participantId)) {
|
|
189
|
+
diagnostics.push(warn("validate.event.participants.duplicate", `Event "${event.id}" repeats participant "${participantId}".`, undefined, `${fieldPrefix}.participants`));
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
seenParticipants.add(participantId);
|
|
193
|
+
if (!objectMap.has(participantId)) {
|
|
194
|
+
diagnostics.push(error("validate.event.participants.unknown", `Unknown event participant "${participantId}" on "${event.id}".`, undefined, `${fieldPrefix}.participants`));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (event.targetObjectId &&
|
|
198
|
+
event.participantObjectIds.length > 0 &&
|
|
199
|
+
!event.participantObjectIds.includes(event.targetObjectId)) {
|
|
200
|
+
diagnostics.push(warn("validate.event.target.notParticipant", `Event "${event.id}" defines a target outside its participants list.`, undefined, `${fieldPrefix}.target`));
|
|
201
|
+
}
|
|
202
|
+
if (event.positions.length === 0) {
|
|
203
|
+
diagnostics.push(warn("validate.event.positions.missing", `Event "${event.id}" has no positions block and cannot drive a scene snapshot.`, undefined, `${fieldPrefix}.positions`));
|
|
204
|
+
}
|
|
205
|
+
if (/(?:^|[-_])(solar-eclipse|lunar-eclipse|transit|occultation)(?:$|[-_])/.test(event.kind) && referencedIds.size < 3) {
|
|
206
|
+
diagnostics.push(warn("validate.event.kind.participants", `Event "${event.id}" looks like an eclipse or transit but references fewer than three bodies.`, undefined, `${fieldPrefix}.participants`));
|
|
207
|
+
}
|
|
208
|
+
const poseIds = new Set();
|
|
209
|
+
for (const pose of event.positions) {
|
|
210
|
+
const poseFieldPrefix = `${fieldPrefix}.pose.${pose.objectId}`;
|
|
211
|
+
if (poseIds.has(pose.objectId)) {
|
|
212
|
+
diagnostics.push(error("validate.event.pose.duplicate", `Event "${event.id}" defines "${pose.objectId}" more than once in positions.`, undefined, poseFieldPrefix));
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
poseIds.add(pose.objectId);
|
|
216
|
+
const object = objectMap.get(pose.objectId);
|
|
217
|
+
if (!object) {
|
|
218
|
+
diagnostics.push(error("validate.event.pose.object.unknown", `Unknown event pose object "${pose.objectId}" on "${event.id}".`, undefined, poseFieldPrefix));
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (!referencedIds.has(pose.objectId)) {
|
|
222
|
+
diagnostics.push(warn("validate.event.pose.unreferenced", `Event pose "${pose.objectId}" on "${event.id}" is not listed in target/participants.`, undefined, poseFieldPrefix));
|
|
223
|
+
}
|
|
224
|
+
validateEventPose(pose, object, objectMap, diagnostics, poseFieldPrefix, event.id);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function validateEventPose(pose, object, objectMap, diagnostics, fieldPrefix, eventId) {
|
|
228
|
+
const placement = pose.placement;
|
|
229
|
+
if (!placement) {
|
|
230
|
+
diagnostics.push(error("validate.event.pose.placement.required", `Event "${eventId}" pose "${pose.objectId}" is missing a placement mode.`, undefined, fieldPrefix));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (placement.mode === "orbit") {
|
|
234
|
+
if (!objectMap.has(placement.target)) {
|
|
235
|
+
diagnostics.push(error("validate.event.pose.orbit.target.unknown", `Unknown event orbit target "${placement.target}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.orbit`));
|
|
236
|
+
}
|
|
237
|
+
if (placement.distance && placement.semiMajor) {
|
|
238
|
+
diagnostics.push(error("validate.event.pose.orbit.distanceConflict", `Event "${eventId}" pose "${pose.objectId}" cannot declare both "distance" and "semiMajor".`, undefined, `${fieldPrefix}.distance`));
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (placement.mode === "surface") {
|
|
243
|
+
const target = objectMap.get(placement.target);
|
|
244
|
+
if (!target) {
|
|
245
|
+
diagnostics.push(error("validate.event.pose.surface.target.unknown", `Unknown event surface target "${placement.target}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.surface`));
|
|
246
|
+
}
|
|
247
|
+
else if (!SURFACE_TARGET_TYPES.has(target.type)) {
|
|
248
|
+
diagnostics.push(error("validate.event.pose.surface.target.invalid", `Event surface target "${placement.target}" on "${eventId}:${pose.objectId}" is not surface-capable.`, undefined, `${fieldPrefix}.surface`));
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (placement.mode === "at") {
|
|
253
|
+
if (object.type !== "structure" && object.type !== "phenomenon") {
|
|
254
|
+
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}".`, undefined, `${fieldPrefix}.at`));
|
|
255
|
+
}
|
|
256
|
+
const reference = placement.reference;
|
|
257
|
+
if (reference.kind === "named" && !objectMap.has(reference.name)) {
|
|
258
|
+
diagnostics.push(error("validate.event.pose.at.target.unknown", `Unknown event at-reference target "${placement.target}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.at`));
|
|
259
|
+
}
|
|
260
|
+
else if (reference.kind === "anchor" && !objectMap.has(reference.objectId)) {
|
|
261
|
+
diagnostics.push(error("validate.event.pose.anchor.target.unknown", `Unknown event anchor target "${reference.objectId}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.at`));
|
|
262
|
+
}
|
|
263
|
+
else if (reference.kind === "lagrange") {
|
|
264
|
+
if (!objectMap.has(reference.primary)) {
|
|
265
|
+
diagnostics.push(error("validate.event.pose.lagrange.primary.unknown", `Unknown event Lagrange target "${reference.primary}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.at`));
|
|
266
|
+
}
|
|
267
|
+
else if (reference.secondary && !objectMap.has(reference.secondary)) {
|
|
268
|
+
diagnostics.push(error("validate.event.pose.lagrange.secondary.unknown", `Unknown event Lagrange target "${reference.secondary}" on "${eventId}:${pose.objectId}".`, undefined, `${fieldPrefix}.at`));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
159
273
|
function validateAtTarget(object, objectMap, diagnostics) {
|
|
160
274
|
const reference = object.placement?.mode === "at" ? object.placement.reference : null;
|
|
161
275
|
if (!reference) {
|
|
@@ -74,6 +74,21 @@ for (const spec of [
|
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
const DRAFT_OBJECT_FIELD_KEYS = new Set(DRAFT_OBJECT_FIELD_SPECS.keys());
|
|
77
|
+
const EVENT_POSE_FIELD_KEYS = new Set([
|
|
78
|
+
"orbit",
|
|
79
|
+
"distance",
|
|
80
|
+
"semiMajor",
|
|
81
|
+
"eccentricity",
|
|
82
|
+
"period",
|
|
83
|
+
"angle",
|
|
84
|
+
"inclination",
|
|
85
|
+
"phase",
|
|
86
|
+
"at",
|
|
87
|
+
"surface",
|
|
88
|
+
"free",
|
|
89
|
+
"inner",
|
|
90
|
+
"outer",
|
|
91
|
+
]);
|
|
77
92
|
export function parseWorldOrbitAtlas(source) {
|
|
78
93
|
return parseAtlasSource(source);
|
|
79
94
|
}
|
|
@@ -91,12 +106,15 @@ function parseAtlasSource(source, forcedOutputVersion) {
|
|
|
91
106
|
const objectNodes = [];
|
|
92
107
|
const groups = [];
|
|
93
108
|
const relations = [];
|
|
109
|
+
const events = [];
|
|
110
|
+
const eventPoseNodes = new Map();
|
|
94
111
|
let sawDefaults = false;
|
|
95
112
|
let sawAtlas = false;
|
|
96
113
|
const viewpointIds = new Set();
|
|
97
114
|
const annotationIds = new Set();
|
|
98
115
|
const groupIds = new Set();
|
|
99
116
|
const relationIds = new Set();
|
|
117
|
+
const eventIds = new Set();
|
|
100
118
|
for (let index = 0; index < lines.length; index++) {
|
|
101
119
|
const rawLine = lines[index];
|
|
102
120
|
const lineNumber = index + 1;
|
|
@@ -127,7 +145,7 @@ function parseAtlasSource(source, forcedOutputVersion) {
|
|
|
127
145
|
continue;
|
|
128
146
|
}
|
|
129
147
|
if (indent === 0) {
|
|
130
|
-
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, { sawDefaults, sawAtlas });
|
|
148
|
+
section = startTopLevelSection(tokens, lineNumber, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, { sawDefaults, sawAtlas });
|
|
131
149
|
if (section.kind === "system") {
|
|
132
150
|
system = section.system;
|
|
133
151
|
}
|
|
@@ -148,6 +166,7 @@ function parseAtlasSource(source, forcedOutputVersion) {
|
|
|
148
166
|
throw new WorldOrbitError('Missing required atlas schema header "schema 2.0"');
|
|
149
167
|
}
|
|
150
168
|
const objects = objectNodes.map((node) => normalizeDraftObject(node, sourceSchemaVersion, diagnostics));
|
|
169
|
+
const normalizedEvents = events.map((event) => normalizeDraftEvent(event, eventPoseNodes.get(event.id) ?? []));
|
|
151
170
|
const outputVersion = forcedOutputVersion ??
|
|
152
171
|
(sourceSchemaVersion === "2.0-draft" ? "2.0" : sourceSchemaVersion);
|
|
153
172
|
const baseDocument = {
|
|
@@ -156,6 +175,7 @@ function parseAtlasSource(source, forcedOutputVersion) {
|
|
|
156
175
|
system,
|
|
157
176
|
groups,
|
|
158
177
|
relations,
|
|
178
|
+
events: normalizedEvents,
|
|
159
179
|
objects,
|
|
160
180
|
diagnostics,
|
|
161
181
|
};
|
|
@@ -197,7 +217,7 @@ function assertDraftSchemaHeader(tokens, line) {
|
|
|
197
217
|
? "2.0-draft"
|
|
198
218
|
: "2.0";
|
|
199
219
|
}
|
|
200
|
-
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, viewpointIds, annotationIds, groupIds, relationIds, flags) {
|
|
220
|
+
function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, system, objectNodes, groups, relations, events, eventPoseNodes, viewpointIds, annotationIds, groupIds, relationIds, eventIds, flags) {
|
|
201
221
|
const keyword = tokens[0]?.value.toLowerCase();
|
|
202
222
|
switch (keyword) {
|
|
203
223
|
case "system":
|
|
@@ -234,7 +254,7 @@ function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, sy
|
|
|
234
254
|
if (!system) {
|
|
235
255
|
throw new WorldOrbitError('Atlas section "viewpoint" requires a preceding system declaration', line, tokens[0].column);
|
|
236
256
|
}
|
|
237
|
-
return startViewpointSection(tokens, line, system, viewpointIds);
|
|
257
|
+
return startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics);
|
|
238
258
|
case "annotation":
|
|
239
259
|
if (!system) {
|
|
240
260
|
throw new WorldOrbitError('Atlas section "annotation" requires a preceding system declaration', line, tokens[0].column);
|
|
@@ -246,6 +266,9 @@ function startTopLevelSection(tokens, line, sourceSchemaVersion, diagnostics, sy
|
|
|
246
266
|
case "relation":
|
|
247
267
|
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "relation", { line, column: tokens[0].column });
|
|
248
268
|
return startRelationSection(tokens, line, relations, relationIds);
|
|
269
|
+
case "event":
|
|
270
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "event", { line, column: tokens[0].column });
|
|
271
|
+
return startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics);
|
|
249
272
|
case "object":
|
|
250
273
|
return startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes);
|
|
251
274
|
default:
|
|
@@ -282,7 +305,7 @@ function startSystemSection(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
|
282
305
|
seenFields: new Set(),
|
|
283
306
|
};
|
|
284
307
|
}
|
|
285
|
-
function startViewpointSection(tokens, line, system, viewpointIds) {
|
|
308
|
+
function startViewpointSection(tokens, line, system, viewpointIds, sourceSchemaVersion, diagnostics) {
|
|
286
309
|
if (tokens.length !== 2) {
|
|
287
310
|
throw new WorldOrbitError("Invalid viewpoint declaration", line, tokens[0]?.column ?? 1);
|
|
288
311
|
}
|
|
@@ -299,6 +322,7 @@ function startViewpointSection(tokens, line, system, viewpointIds) {
|
|
|
299
322
|
summary: "",
|
|
300
323
|
focusObjectId: null,
|
|
301
324
|
selectedObjectId: null,
|
|
325
|
+
events: [],
|
|
302
326
|
projection: system.defaults.view,
|
|
303
327
|
preset: system.defaults.preset,
|
|
304
328
|
zoom: null,
|
|
@@ -311,6 +335,8 @@ function startViewpointSection(tokens, line, system, viewpointIds) {
|
|
|
311
335
|
return {
|
|
312
336
|
kind: "viewpoint",
|
|
313
337
|
viewpoint,
|
|
338
|
+
sourceSchemaVersion,
|
|
339
|
+
diagnostics,
|
|
314
340
|
seenFields: new Set(),
|
|
315
341
|
inFilter: false,
|
|
316
342
|
filterIndent: null,
|
|
@@ -401,6 +427,49 @@ function startRelationSection(tokens, line, relations, relationIds) {
|
|
|
401
427
|
seenFields: new Set(),
|
|
402
428
|
};
|
|
403
429
|
}
|
|
430
|
+
function startEventSection(tokens, line, events, eventPoseNodes, eventIds, sourceSchemaVersion, diagnostics) {
|
|
431
|
+
if (tokens.length !== 2) {
|
|
432
|
+
throw new WorldOrbitError("Invalid event declaration", line, tokens[0]?.column ?? 1);
|
|
433
|
+
}
|
|
434
|
+
const id = normalizeIdentifier(tokens[1].value);
|
|
435
|
+
if (!id) {
|
|
436
|
+
throw new WorldOrbitError("Event id must not be empty", line, tokens[1].column);
|
|
437
|
+
}
|
|
438
|
+
if (eventIds.has(id)) {
|
|
439
|
+
throw new WorldOrbitError(`Duplicate event id "${id}"`, line, tokens[1].column);
|
|
440
|
+
}
|
|
441
|
+
const event = {
|
|
442
|
+
id,
|
|
443
|
+
kind: "",
|
|
444
|
+
label: humanizeIdentifier(id),
|
|
445
|
+
summary: null,
|
|
446
|
+
targetObjectId: null,
|
|
447
|
+
participantObjectIds: [],
|
|
448
|
+
timing: null,
|
|
449
|
+
visibility: null,
|
|
450
|
+
tags: [],
|
|
451
|
+
color: null,
|
|
452
|
+
hidden: false,
|
|
453
|
+
positions: [],
|
|
454
|
+
};
|
|
455
|
+
const rawPoses = [];
|
|
456
|
+
events.push(event);
|
|
457
|
+
eventPoseNodes.set(id, rawPoses);
|
|
458
|
+
eventIds.add(id);
|
|
459
|
+
return {
|
|
460
|
+
kind: "event",
|
|
461
|
+
event,
|
|
462
|
+
sourceSchemaVersion,
|
|
463
|
+
diagnostics,
|
|
464
|
+
seenFields: new Set(),
|
|
465
|
+
rawPoses,
|
|
466
|
+
inPositions: false,
|
|
467
|
+
positionsIndent: null,
|
|
468
|
+
activePose: null,
|
|
469
|
+
poseIndent: null,
|
|
470
|
+
activePoseSeenFields: new Set(),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
404
473
|
function startObjectSection(tokens, line, sourceSchemaVersion, diagnostics, objectNodes) {
|
|
405
474
|
if (tokens.length < 3) {
|
|
406
475
|
throw new WorldOrbitError("Invalid atlas object declaration", line, tokens[0]?.column ?? 1);
|
|
@@ -457,6 +526,9 @@ function handleSectionLine(section, indent, tokens, line) {
|
|
|
457
526
|
case "relation":
|
|
458
527
|
applyRelationField(section, tokens, line);
|
|
459
528
|
return;
|
|
529
|
+
case "event":
|
|
530
|
+
applyEventField(section, indent, tokens, line);
|
|
531
|
+
return;
|
|
460
532
|
case "object":
|
|
461
533
|
applyObjectField(section, indent, tokens, line);
|
|
462
534
|
return;
|
|
@@ -583,7 +655,14 @@ function applyViewpointField(section, indent, tokens, line) {
|
|
|
583
655
|
section.viewpoint.rotationDeg = parseFiniteNumber(value, line, tokens[0].column, "rotation");
|
|
584
656
|
return;
|
|
585
657
|
case "layers":
|
|
586
|
-
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line);
|
|
658
|
+
section.viewpoint.layers = parseLayerTokens(tokens.slice(1), line, section.sourceSchemaVersion, section.diagnostics);
|
|
659
|
+
return;
|
|
660
|
+
case "events":
|
|
661
|
+
warnIfSchema21Feature(section.sourceSchemaVersion, section.diagnostics, "viewpoint.events", {
|
|
662
|
+
line,
|
|
663
|
+
column: tokens[0].column,
|
|
664
|
+
});
|
|
665
|
+
section.viewpoint.events = parseTokenList(tokens.slice(1), line, "events");
|
|
587
666
|
return;
|
|
588
667
|
default:
|
|
589
668
|
throw new WorldOrbitError(`Unknown viewpoint field "${tokens[0].value}"`, line, tokens[0].column);
|
|
@@ -688,6 +767,106 @@ function applyRelationField(section, tokens, line) {
|
|
|
688
767
|
throw new WorldOrbitError(`Unknown relation field "${tokens[0].value}"`, line, tokens[0].column);
|
|
689
768
|
}
|
|
690
769
|
}
|
|
770
|
+
function applyEventField(section, indent, tokens, line) {
|
|
771
|
+
if (section.activePose && indent <= (section.poseIndent ?? 0)) {
|
|
772
|
+
section.activePose = null;
|
|
773
|
+
section.poseIndent = null;
|
|
774
|
+
section.activePoseSeenFields.clear();
|
|
775
|
+
}
|
|
776
|
+
if (!section.activePose && section.inPositions && indent <= (section.positionsIndent ?? 0)) {
|
|
777
|
+
section.inPositions = false;
|
|
778
|
+
section.positionsIndent = null;
|
|
779
|
+
}
|
|
780
|
+
if (section.activePose) {
|
|
781
|
+
section.activePose.fields.push(parseEventPoseField(tokens, line, section.activePoseSeenFields));
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
if (section.inPositions) {
|
|
785
|
+
if (tokens.length !== 2 || tokens[0].value.toLowerCase() !== "pose") {
|
|
786
|
+
throw new WorldOrbitError(`Unknown event positions field "${tokens[0].value}"`, line, tokens[0]?.column ?? 1);
|
|
787
|
+
}
|
|
788
|
+
const objectId = tokens[1].value;
|
|
789
|
+
if (!objectId.trim()) {
|
|
790
|
+
throw new WorldOrbitError("Event pose object id must not be empty", line, tokens[1].column);
|
|
791
|
+
}
|
|
792
|
+
const rawPose = {
|
|
793
|
+
objectId,
|
|
794
|
+
fields: [],
|
|
795
|
+
location: { line, column: tokens[0].column },
|
|
796
|
+
};
|
|
797
|
+
section.rawPoses.push(rawPose);
|
|
798
|
+
section.activePose = rawPose;
|
|
799
|
+
section.poseIndent = indent;
|
|
800
|
+
section.activePoseSeenFields = new Set();
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (tokens.length === 1 && tokens[0].value.toLowerCase() === "positions") {
|
|
804
|
+
if (section.seenFields.has("positions")) {
|
|
805
|
+
throw new WorldOrbitError('Duplicate event field "positions"', line, tokens[0].column);
|
|
806
|
+
}
|
|
807
|
+
section.seenFields.add("positions");
|
|
808
|
+
section.inPositions = true;
|
|
809
|
+
section.positionsIndent = indent;
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const key = requireUniqueField(tokens, section.seenFields, line);
|
|
813
|
+
switch (key) {
|
|
814
|
+
case "kind":
|
|
815
|
+
section.event.kind = joinFieldValue(tokens, line);
|
|
816
|
+
return;
|
|
817
|
+
case "label":
|
|
818
|
+
section.event.label = joinFieldValue(tokens, line);
|
|
819
|
+
return;
|
|
820
|
+
case "summary":
|
|
821
|
+
section.event.summary = joinFieldValue(tokens, line);
|
|
822
|
+
return;
|
|
823
|
+
case "target":
|
|
824
|
+
section.event.targetObjectId = joinFieldValue(tokens, line);
|
|
825
|
+
return;
|
|
826
|
+
case "participants":
|
|
827
|
+
section.event.participantObjectIds = parseTokenList(tokens.slice(1), line, "participants");
|
|
828
|
+
return;
|
|
829
|
+
case "timing":
|
|
830
|
+
section.event.timing = joinFieldValue(tokens, line);
|
|
831
|
+
return;
|
|
832
|
+
case "visibility":
|
|
833
|
+
section.event.visibility = joinFieldValue(tokens, line);
|
|
834
|
+
return;
|
|
835
|
+
case "tags":
|
|
836
|
+
section.event.tags = parseTokenList(tokens.slice(1), line, "tags");
|
|
837
|
+
return;
|
|
838
|
+
case "color":
|
|
839
|
+
section.event.color = joinFieldValue(tokens, line);
|
|
840
|
+
return;
|
|
841
|
+
case "hidden":
|
|
842
|
+
section.event.hidden = parseAtlasBoolean(joinFieldValue(tokens, line), "hidden", {
|
|
843
|
+
line,
|
|
844
|
+
column: tokens[0].column,
|
|
845
|
+
});
|
|
846
|
+
return;
|
|
847
|
+
default:
|
|
848
|
+
throw new WorldOrbitError(`Unknown event field "${tokens[0].value}"`, line, tokens[0].column);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function parseEventPoseField(tokens, line, seenFields) {
|
|
852
|
+
if (tokens.length < 2) {
|
|
853
|
+
throw new WorldOrbitError("Invalid event pose field line", line, tokens[0]?.column ?? 1);
|
|
854
|
+
}
|
|
855
|
+
const key = tokens[0].value;
|
|
856
|
+
if (!EVENT_POSE_FIELD_KEYS.has(key)) {
|
|
857
|
+
throw new WorldOrbitError(`Unknown event pose field "${key}"`, line, tokens[0].column);
|
|
858
|
+
}
|
|
859
|
+
if (seenFields.has(key)) {
|
|
860
|
+
throw new WorldOrbitError(`Duplicate event pose field "${key}"`, line, tokens[0].column);
|
|
861
|
+
}
|
|
862
|
+
seenFields.add(key);
|
|
863
|
+
return {
|
|
864
|
+
type: "field",
|
|
865
|
+
key,
|
|
866
|
+
values: tokens.slice(1).map((token) => token.value),
|
|
867
|
+
location: { line, column: tokens[0].column },
|
|
868
|
+
};
|
|
869
|
+
}
|
|
691
870
|
function applyObjectField(section, indent, tokens, line) {
|
|
692
871
|
if (section.activeBlock && indent <= (section.blockIndent ?? 0)) {
|
|
693
872
|
section.activeBlock = null;
|
|
@@ -760,7 +939,7 @@ function parseObjectTypeTokens(tokens, line) {
|
|
|
760
939
|
value === "structure" ||
|
|
761
940
|
value === "phenomenon");
|
|
762
941
|
}
|
|
763
|
-
function parseLayerTokens(tokens, line) {
|
|
942
|
+
function parseLayerTokens(tokens, line, sourceSchemaVersion, diagnostics) {
|
|
764
943
|
const layers = {};
|
|
765
944
|
for (const token of parseTokenList(tokens, line, "layers")) {
|
|
766
945
|
const enabled = !token.startsWith("-") && !token.startsWith("!");
|
|
@@ -775,9 +954,16 @@ function parseLayerTokens(tokens, line) {
|
|
|
775
954
|
raw === "orbits-back" ||
|
|
776
955
|
raw === "orbits-front" ||
|
|
777
956
|
raw === "relations" ||
|
|
957
|
+
raw === "events" ||
|
|
778
958
|
raw === "objects" ||
|
|
779
959
|
raw === "labels" ||
|
|
780
960
|
raw === "metadata") {
|
|
961
|
+
if (raw === "events" && sourceSchemaVersion && diagnostics) {
|
|
962
|
+
warnIfSchema21Feature(sourceSchemaVersion, diagnostics, "layers.events", {
|
|
963
|
+
line,
|
|
964
|
+
column: tokens[0]?.column ?? 1,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
781
967
|
layers[raw] = enabled;
|
|
782
968
|
}
|
|
783
969
|
}
|
|
@@ -921,7 +1107,7 @@ function parseInfoLikeEntry(tokens, line, errorMessage) {
|
|
|
921
1107
|
}
|
|
922
1108
|
function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
923
1109
|
const fieldMap = collectDraftFields(node.fields);
|
|
924
|
-
const placement =
|
|
1110
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
925
1111
|
const properties = normalizeDraftProperties(node.objectType, fieldMap);
|
|
926
1112
|
const groups = parseOptionalTokenList(fieldMap.get("groups")?.[0]);
|
|
927
1113
|
const epoch = parseOptionalJoinedValue(fieldMap.get("epoch")?.[0]);
|
|
@@ -989,6 +1175,24 @@ function normalizeDraftObject(node, sourceSchemaVersion, diagnostics) {
|
|
|
989
1175
|
}
|
|
990
1176
|
return object;
|
|
991
1177
|
}
|
|
1178
|
+
function normalizeDraftEvent(event, rawPoses) {
|
|
1179
|
+
return {
|
|
1180
|
+
...event,
|
|
1181
|
+
participantObjectIds: [...new Set(event.participantObjectIds)],
|
|
1182
|
+
tags: [...new Set(event.tags)],
|
|
1183
|
+
positions: rawPoses.map((pose) => normalizeDraftEventPose(pose)),
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
function normalizeDraftEventPose(rawPose) {
|
|
1187
|
+
const fieldMap = collectDraftFields(rawPose.fields);
|
|
1188
|
+
const placement = extractPlacementFromFieldMap(fieldMap);
|
|
1189
|
+
return {
|
|
1190
|
+
objectId: rawPose.objectId,
|
|
1191
|
+
placement,
|
|
1192
|
+
inner: parseOptionalUnitField(fieldMap.get("inner")?.[0], "inner"),
|
|
1193
|
+
outer: parseOptionalUnitField(fieldMap.get("outer")?.[0], "outer"),
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
992
1196
|
function collectDraftFields(fields) {
|
|
993
1197
|
const grouped = new Map();
|
|
994
1198
|
for (const field of fields) {
|
|
@@ -1005,7 +1209,7 @@ function collectDraftFields(fields) {
|
|
|
1005
1209
|
}
|
|
1006
1210
|
return grouped;
|
|
1007
1211
|
}
|
|
1008
|
-
function
|
|
1212
|
+
function extractPlacementFromFieldMap(fieldMap) {
|
|
1009
1213
|
const orbitField = fieldMap.get("orbit")?.[0];
|
|
1010
1214
|
const atField = fieldMap.get("at")?.[0];
|
|
1011
1215
|
const surfaceField = fieldMap.get("surface")?.[0];
|