worldorbit 2.5.2
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/LICENSE.md +5 -0
- package/README.md +250 -0
- package/dist/browser/core/dist/index.js +4009 -0
- package/dist/browser/markdown/dist/index.js +3951 -0
- package/dist/browser/viewer/dist/index.js +5981 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +84 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +16 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +25 -0
- package/dist/normalize.d.ts +2 -0
- package/dist/normalize.js +243 -0
- package/dist/parse.d.ts +2 -0
- package/dist/parse.js +126 -0
- package/dist/render.d.ts +6 -0
- package/dist/render.js +683 -0
- package/dist/tokenize.d.ts +4 -0
- package/dist/tokenize.js +68 -0
- package/dist/types.d.ts +208 -0
- package/dist/types.js +1 -0
- package/dist/unpkg/core/dist/index.js +4081 -0
- package/dist/unpkg/markdown/dist/index.js +3979 -0
- package/dist/unpkg/test.html +1 -0
- package/dist/unpkg/viewer/dist/index.js +6038 -0
- package/dist/unpkg/worldorbit-core.min.js +5 -0
- package/dist/unpkg/worldorbit-markdown.min.js +81 -0
- package/dist/unpkg/worldorbit-viewer.min.js +232 -0
- package/dist/unpkg/worldorbit.d.ts +2 -0
- package/dist/unpkg/worldorbit.js +2 -0
- package/dist/unpkg/worldorbit.min.js +236 -0
- package/dist/validate.d.ts +2 -0
- package/dist/validate.js +31 -0
- package/dist/viewer-state.d.ts +16 -0
- package/dist/viewer-state.js +130 -0
- package/dist/viewer.d.ts +2 -0
- package/dist/viewer.js +434 -0
- package/package.json +64 -0
- package/packages/core/README.md +13 -0
- package/packages/core/dist/atlas-edit.d.ts +11 -0
- package/packages/core/dist/atlas-edit.js +210 -0
- package/packages/core/dist/diagnostics.d.ts +10 -0
- package/packages/core/dist/diagnostics.js +109 -0
- package/packages/core/dist/draft-parse.d.ts +3 -0
- package/packages/core/dist/draft-parse.js +642 -0
- package/packages/core/dist/draft.d.ts +15 -0
- package/packages/core/dist/draft.js +343 -0
- package/packages/core/dist/errors.d.ts +7 -0
- package/packages/core/dist/errors.js +16 -0
- package/packages/core/dist/format.d.ts +4 -0
- package/packages/core/dist/format.js +364 -0
- package/packages/core/dist/index.d.ts +28 -0
- package/packages/core/dist/index.js +44 -0
- package/packages/core/dist/load.d.ts +4 -0
- package/packages/core/dist/load.js +130 -0
- package/packages/core/dist/markdown.d.ts +2 -0
- package/packages/core/dist/markdown.js +37 -0
- package/packages/core/dist/normalize.d.ts +2 -0
- package/packages/core/dist/normalize.js +304 -0
- package/packages/core/dist/parse.d.ts +2 -0
- package/packages/core/dist/parse.js +133 -0
- package/packages/core/dist/scene.d.ts +3 -0
- package/packages/core/dist/scene.js +1484 -0
- package/packages/core/dist/schema.d.ts +8 -0
- package/packages/core/dist/schema.js +298 -0
- package/packages/core/dist/tokenize.d.ts +4 -0
- package/packages/core/dist/tokenize.js +68 -0
- package/packages/core/dist/types.d.ts +382 -0
- package/packages/core/dist/types.js +1 -0
- package/packages/core/dist/validate.d.ts +2 -0
- package/packages/core/dist/validate.js +56 -0
- package/packages/editor/dist/editor.d.ts +2 -0
- package/packages/editor/dist/editor.js +2620 -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 +53 -0
- package/packages/editor/dist/types.js +1 -0
- package/packages/markdown/README.md +9 -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/README.md +12 -0
- package/packages/viewer/dist/atlas-state.d.ts +12 -0
- package/packages/viewer/dist/atlas-state.js +251 -0
- package/packages/viewer/dist/atlas-viewer.d.ts +2 -0
- package/packages/viewer/dist/atlas-viewer.js +448 -0
- package/packages/viewer/dist/custom-element.d.ts +1 -0
- package/packages/viewer/dist/custom-element.js +64 -0
- package/packages/viewer/dist/embed.d.ts +20 -0
- package/packages/viewer/dist/embed.js +138 -0
- package/packages/viewer/dist/index.d.ts +9 -0
- package/packages/viewer/dist/index.js +8 -0
- package/packages/viewer/dist/minimap.d.ts +3 -0
- package/packages/viewer/dist/minimap.js +63 -0
- package/packages/viewer/dist/render.d.ts +6 -0
- package/packages/viewer/dist/render.js +585 -0
- package/packages/viewer/dist/theme.d.ts +4 -0
- package/packages/viewer/dist/theme.js +98 -0
- package/packages/viewer/dist/tooltip.d.ts +3 -0
- package/packages/viewer/dist/tooltip.js +154 -0
- package/packages/viewer/dist/types.d.ts +256 -0
- package/packages/viewer/dist/types.js +1 -0
- package/packages/viewer/dist/viewer-state.d.ts +19 -0
- package/packages/viewer/dist/viewer-state.js +162 -0
- package/packages/viewer/dist/viewer.d.ts +2 -0
- package/packages/viewer/dist/viewer.js +1156 -0
package/dist/render.js
ADDED
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
import { normalizeDocument } from "./normalize.js";
|
|
2
|
+
import { parseWorldOrbit } from "./parse.js";
|
|
3
|
+
import { validateDocument } from "./validate.js";
|
|
4
|
+
const AU_IN_KM = 149_597_870.7;
|
|
5
|
+
const EARTH_RADIUS_IN_KM = 6_371;
|
|
6
|
+
const SOLAR_RADIUS_IN_KM = 695_700;
|
|
7
|
+
const WORLD_LAYER_ID = "worldorbit-camera-root";
|
|
8
|
+
export function renderDocumentToScene(document, options = {}) {
|
|
9
|
+
const width = options.width ?? 1200;
|
|
10
|
+
const height = options.height ?? 780;
|
|
11
|
+
const padding = options.padding ?? 72;
|
|
12
|
+
const objectMap = new Map(document.objects.map((object) => [object.id, object]));
|
|
13
|
+
const positions = new Map();
|
|
14
|
+
const orbitDrafts = [];
|
|
15
|
+
const leaderDrafts = [];
|
|
16
|
+
const rootObjects = [];
|
|
17
|
+
const freeObjects = [];
|
|
18
|
+
const atObjects = [];
|
|
19
|
+
const surfaceChildren = new Map();
|
|
20
|
+
const orbitChildren = new Map();
|
|
21
|
+
for (const object of document.objects) {
|
|
22
|
+
const placement = object.placement;
|
|
23
|
+
if (!placement) {
|
|
24
|
+
rootObjects.push(object);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (placement.mode === "orbit") {
|
|
28
|
+
pushGrouped(orbitChildren, placement.target, object);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (placement.mode === "surface") {
|
|
32
|
+
pushGrouped(surfaceChildren, placement.target, object);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (placement.mode === "at") {
|
|
36
|
+
atObjects.push(object);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
freeObjects.push(object);
|
|
40
|
+
}
|
|
41
|
+
const centerX = freeObjects.length > 0 ? width * 0.42 : width / 2;
|
|
42
|
+
const centerY = height / 2;
|
|
43
|
+
const primaryRoot = rootObjects.find((object) => object.type === "star") ?? rootObjects[0] ?? null;
|
|
44
|
+
if (primaryRoot) {
|
|
45
|
+
placeObject(primaryRoot, centerX, centerY, 0, positions, orbitDrafts, leaderDrafts, {
|
|
46
|
+
orbitChildren,
|
|
47
|
+
surfaceChildren,
|
|
48
|
+
objectMap,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const secondaryRoots = rootObjects.filter((object) => object.id !== primaryRoot?.id);
|
|
52
|
+
if (secondaryRoots.length > 0) {
|
|
53
|
+
const rootRingRadius = Math.min(width, height) * 0.28;
|
|
54
|
+
secondaryRoots.forEach((object, index) => {
|
|
55
|
+
const angle = angleForIndex(index, secondaryRoots.length, -Math.PI / 2);
|
|
56
|
+
const x = centerX + Math.cos(angle) * rootRingRadius;
|
|
57
|
+
const y = centerY + Math.sin(angle) * rootRingRadius;
|
|
58
|
+
placeObject(object, x, y, 0, positions, orbitDrafts, leaderDrafts, {
|
|
59
|
+
orbitChildren,
|
|
60
|
+
surfaceChildren,
|
|
61
|
+
objectMap,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
freeObjects.forEach((object, index) => {
|
|
66
|
+
const x = width - padding - 130;
|
|
67
|
+
const y = padding +
|
|
68
|
+
90 +
|
|
69
|
+
index *
|
|
70
|
+
Math.max(70, (height - padding * 2 - 180) / Math.max(1, freeObjects.length));
|
|
71
|
+
positions.set(object.id, {
|
|
72
|
+
object,
|
|
73
|
+
x,
|
|
74
|
+
y,
|
|
75
|
+
radius: visualRadiusFor(object),
|
|
76
|
+
});
|
|
77
|
+
leaderDrafts.push({
|
|
78
|
+
object,
|
|
79
|
+
x1: x - 60,
|
|
80
|
+
y1: y,
|
|
81
|
+
x2: x - 18,
|
|
82
|
+
y2: y,
|
|
83
|
+
mode: "free",
|
|
84
|
+
});
|
|
85
|
+
placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, {
|
|
86
|
+
orbitChildren,
|
|
87
|
+
surfaceChildren,
|
|
88
|
+
objectMap,
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
atObjects.forEach((object, index) => {
|
|
92
|
+
if (positions.has(object.id)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const placement = object.placement;
|
|
96
|
+
if (!placement || placement.mode !== "at") {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const resolved = resolveAtPosition(placement.reference, positions, objectMap, index, atObjects.length, width, height, padding);
|
|
100
|
+
positions.set(object.id, {
|
|
101
|
+
object,
|
|
102
|
+
x: resolved.x,
|
|
103
|
+
y: resolved.y,
|
|
104
|
+
radius: visualRadiusFor(object),
|
|
105
|
+
anchorX: resolved.anchorX,
|
|
106
|
+
anchorY: resolved.anchorY,
|
|
107
|
+
});
|
|
108
|
+
if (resolved.anchorX !== undefined && resolved.anchorY !== undefined) {
|
|
109
|
+
leaderDrafts.push({
|
|
110
|
+
object,
|
|
111
|
+
x1: resolved.anchorX,
|
|
112
|
+
y1: resolved.anchorY,
|
|
113
|
+
x2: resolved.x,
|
|
114
|
+
y2: resolved.y,
|
|
115
|
+
mode: "at",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, {
|
|
119
|
+
orbitChildren,
|
|
120
|
+
surfaceChildren,
|
|
121
|
+
objectMap,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
const objects = [...positions.values()].map((position) => createSceneObject(position));
|
|
125
|
+
const orbitVisuals = orbitDrafts.map((draft) => createOrbitVisual(draft));
|
|
126
|
+
const leaders = leaderDrafts.map((draft) => createLeaderLine(draft));
|
|
127
|
+
const contentBounds = calculateContentBounds(width, height, objects, orbitVisuals, leaders);
|
|
128
|
+
return {
|
|
129
|
+
width,
|
|
130
|
+
height,
|
|
131
|
+
padding,
|
|
132
|
+
title: String(document.system?.properties.title ?? document.system?.id ?? "WorldOrbit") ||
|
|
133
|
+
"WorldOrbit",
|
|
134
|
+
contentBounds,
|
|
135
|
+
objects,
|
|
136
|
+
orbitVisuals,
|
|
137
|
+
leaders,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export function renderSceneToSvg(scene) {
|
|
141
|
+
const { width, height, title } = scene;
|
|
142
|
+
const orbitMarkup = scene.orbitVisuals
|
|
143
|
+
.filter((visual) => !visual.hidden)
|
|
144
|
+
.map((visual) => {
|
|
145
|
+
const orbitClass = visual.band ? "wo-orbit wo-orbit-band" : "wo-orbit";
|
|
146
|
+
return `<circle class="${orbitClass}" cx="${visual.cx}" cy="${visual.cy}" r="${visual.radius}" data-render-id="${escapeXml(visual.renderId)}" />`;
|
|
147
|
+
})
|
|
148
|
+
.join("");
|
|
149
|
+
const leaderMarkup = scene.leaders
|
|
150
|
+
.filter((leader) => !leader.hidden)
|
|
151
|
+
.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)}" />`)
|
|
152
|
+
.join("");
|
|
153
|
+
const objectMarkup = scene.objects
|
|
154
|
+
.filter((object) => !object.hidden)
|
|
155
|
+
.map((object) => renderSceneObject(object))
|
|
156
|
+
.join("");
|
|
157
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" role="img" aria-labelledby="worldorbit-title worldorbit-desc">
|
|
158
|
+
<title id="worldorbit-title">${escapeXml(title)}</title>
|
|
159
|
+
<desc id="worldorbit-desc">A top-down WorldOrbit render with ${scene.objects.filter((object) => !object.hidden).length} visible objects.</desc>
|
|
160
|
+
<defs>
|
|
161
|
+
<linearGradient id="wo-bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
162
|
+
<stop offset="0%" stop-color="#07131f" />
|
|
163
|
+
<stop offset="55%" stop-color="#0d2230" />
|
|
164
|
+
<stop offset="100%" stop-color="#04070c" />
|
|
165
|
+
</linearGradient>
|
|
166
|
+
<radialGradient id="wo-star-glow" cx="50%" cy="50%" r="50%">
|
|
167
|
+
<stop offset="0%" stop-color="#ffe8a3" stop-opacity="0.95" />
|
|
168
|
+
<stop offset="100%" stop-color="#f7c25c" stop-opacity="0" />
|
|
169
|
+
</radialGradient>
|
|
170
|
+
</defs>
|
|
171
|
+
<style>
|
|
172
|
+
.wo-bg { fill: url(#wo-bg); }
|
|
173
|
+
.wo-grid { fill: none; stroke: rgba(255,255,255,0.04); stroke-width: 1; }
|
|
174
|
+
.wo-orbit { fill: none; stroke: rgba(163, 209, 255, 0.24); stroke-width: 1.5; }
|
|
175
|
+
.wo-orbit-band { stroke: rgba(255, 190, 120, 0.28); stroke-width: 8; stroke-linecap: round; }
|
|
176
|
+
.wo-leader { stroke: rgba(225, 238, 255, 0.4); stroke-width: 1.5; stroke-dasharray: 6 5; }
|
|
177
|
+
.wo-label { fill: #e8f0ff; font: 600 14px "Segoe UI Variable", "Bahnschrift", sans-serif; letter-spacing: 0.02em; }
|
|
178
|
+
.wo-label-secondary { fill: rgba(232, 240, 255, 0.72); font: 500 11px "Segoe UI Variable", "Bahnschrift", sans-serif; }
|
|
179
|
+
.wo-star-core { fill: #ffcc67; stroke: rgba(255, 245, 203, 0.85); stroke-width: 2; }
|
|
180
|
+
.wo-star-glow { fill: url(#wo-star-glow); }
|
|
181
|
+
.wo-planet { fill: #72b7ff; stroke: #d8efff; stroke-width: 1.5; }
|
|
182
|
+
.wo-moon { fill: #c7d7ea; stroke: rgba(255,255,255,0.8); stroke-width: 1; }
|
|
183
|
+
.wo-belt { fill: #f3ba7d; stroke: rgba(255,245,235,0.8); stroke-width: 1.2; }
|
|
184
|
+
.wo-asteroid { fill: #a7a5b8; stroke: #ebe5ff; stroke-width: 1; }
|
|
185
|
+
.wo-comet { fill: #9ce7ff; stroke: #e7fbff; stroke-width: 1.2; }
|
|
186
|
+
.wo-ring { fill: #e59f7d; stroke: #fff0d3; stroke-width: 1.2; }
|
|
187
|
+
.wo-structure { fill: #ff7c6e; stroke: #fff2ea; stroke-width: 1.2; }
|
|
188
|
+
.wo-phenomenon { fill: #78ffd7; stroke: #e9fff7; stroke-width: 1.2; }
|
|
189
|
+
.wo-title { fill: rgba(255,255,255,0.92); font: 700 24px "Bahnschrift", "Segoe UI Variable", sans-serif; letter-spacing: 0.06em; text-transform: uppercase; }
|
|
190
|
+
.wo-subtitle { fill: rgba(232,240,255,0.58); font: 500 12px "Segoe UI Variable", "Bahnschrift", sans-serif; letter-spacing: 0.14em; text-transform: uppercase; }
|
|
191
|
+
.wo-object { cursor: pointer; }
|
|
192
|
+
.wo-object-selected .wo-label { fill: #ffd68b; }
|
|
193
|
+
.wo-object-selected .wo-label-secondary { fill: rgba(255, 214, 139, 0.78); }
|
|
194
|
+
.wo-object-selected .wo-selection-ring { display: block; }
|
|
195
|
+
.wo-selection-ring { display: none; fill: none; stroke: rgba(255, 214, 139, 0.9); stroke-width: 2; stroke-dasharray: 6 5; }
|
|
196
|
+
</style>
|
|
197
|
+
<rect class="wo-bg" x="0" y="0" width="${width}" height="${height}" rx="28" ry="28" />
|
|
198
|
+
${renderBackdrop(width, height)}
|
|
199
|
+
<text class="wo-title" x="56" y="64">${escapeXml(title)}</text>
|
|
200
|
+
<text class="wo-subtitle" x="56" y="88">WorldOrbit v0.8 interactive preview</text>
|
|
201
|
+
<g data-worldorbit-world>
|
|
202
|
+
<g data-worldorbit-camera-root="${WORLD_LAYER_ID}" id="${WORLD_LAYER_ID}">
|
|
203
|
+
<g data-worldorbit-world-content="true">
|
|
204
|
+
${orbitMarkup}
|
|
205
|
+
${leaderMarkup}
|
|
206
|
+
${objectMarkup}
|
|
207
|
+
</g>
|
|
208
|
+
</g>
|
|
209
|
+
</g>
|
|
210
|
+
</svg>`;
|
|
211
|
+
}
|
|
212
|
+
export function renderDocumentToSvg(document, options = {}) {
|
|
213
|
+
return renderSceneToSvg(renderDocumentToScene(document, options));
|
|
214
|
+
}
|
|
215
|
+
export function renderSourceToSvg(source, options = {}) {
|
|
216
|
+
const ast = parseWorldOrbit(source);
|
|
217
|
+
const document = normalizeDocument(ast);
|
|
218
|
+
validateDocument(document);
|
|
219
|
+
return renderDocumentToSvg(document, options);
|
|
220
|
+
}
|
|
221
|
+
function createSceneObject(position) {
|
|
222
|
+
const { object, x, y, radius, anchorX, anchorY } = position;
|
|
223
|
+
const renderId = createRenderId(object.id);
|
|
224
|
+
return {
|
|
225
|
+
renderId,
|
|
226
|
+
objectId: object.id,
|
|
227
|
+
object,
|
|
228
|
+
x,
|
|
229
|
+
y,
|
|
230
|
+
radius,
|
|
231
|
+
visualRadius: visualExtentForObject(object, radius),
|
|
232
|
+
anchorX,
|
|
233
|
+
anchorY,
|
|
234
|
+
label: object.id,
|
|
235
|
+
secondaryLabel: object.type === "structure" ? String(object.properties.kind ?? object.type) : object.type,
|
|
236
|
+
fillColor: customColorFor(object.properties.color),
|
|
237
|
+
hidden: object.properties.hidden === true,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function createOrbitVisual(draft) {
|
|
241
|
+
return {
|
|
242
|
+
renderId: `${createRenderId(draft.object.id)}-orbit`,
|
|
243
|
+
objectId: draft.object.id,
|
|
244
|
+
object: draft.object,
|
|
245
|
+
parentId: draft.parentId,
|
|
246
|
+
cx: draft.cx,
|
|
247
|
+
cy: draft.cy,
|
|
248
|
+
radius: draft.radius,
|
|
249
|
+
band: draft.object.type === "belt" || draft.object.type === "ring",
|
|
250
|
+
hidden: draft.object.properties.hidden === true,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function createLeaderLine(draft) {
|
|
254
|
+
return {
|
|
255
|
+
renderId: `${createRenderId(draft.object.id)}-leader-${draft.mode}`,
|
|
256
|
+
objectId: draft.object.id,
|
|
257
|
+
object: draft.object,
|
|
258
|
+
x1: draft.x1,
|
|
259
|
+
y1: draft.y1,
|
|
260
|
+
x2: draft.x2,
|
|
261
|
+
y2: draft.y2,
|
|
262
|
+
mode: draft.mode,
|
|
263
|
+
hidden: draft.object.properties.hidden === true,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function calculateContentBounds(width, height, objects, orbitVisuals, leaders) {
|
|
267
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
268
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
269
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
270
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
271
|
+
const include = (x, y) => {
|
|
272
|
+
minX = Math.min(minX, x);
|
|
273
|
+
minY = Math.min(minY, y);
|
|
274
|
+
maxX = Math.max(maxX, x);
|
|
275
|
+
maxY = Math.max(maxY, y);
|
|
276
|
+
};
|
|
277
|
+
for (const orbit of orbitVisuals) {
|
|
278
|
+
if (orbit.hidden) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const strokePadding = orbit.band ? 6 : 2;
|
|
282
|
+
include(orbit.cx - orbit.radius - strokePadding, orbit.cy - orbit.radius - strokePadding);
|
|
283
|
+
include(orbit.cx + orbit.radius + strokePadding, orbit.cy + orbit.radius + strokePadding);
|
|
284
|
+
}
|
|
285
|
+
for (const leader of leaders) {
|
|
286
|
+
if (leader.hidden) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
include(leader.x1, leader.y1);
|
|
290
|
+
include(leader.x2, leader.y2);
|
|
291
|
+
}
|
|
292
|
+
for (const object of objects) {
|
|
293
|
+
if (object.hidden) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const horizontalPad = 24;
|
|
297
|
+
const verticalPad = 34;
|
|
298
|
+
include(object.x - object.visualRadius - horizontalPad, object.y - object.visualRadius - 12);
|
|
299
|
+
include(object.x + object.visualRadius + horizontalPad, object.y + object.visualRadius + verticalPad);
|
|
300
|
+
}
|
|
301
|
+
if (!Number.isFinite(minX) || !Number.isFinite(minY)) {
|
|
302
|
+
return createBounds(0, 0, width, height);
|
|
303
|
+
}
|
|
304
|
+
return createBounds(minX, minY, maxX, maxY);
|
|
305
|
+
}
|
|
306
|
+
function createBounds(minX, minY, maxX, maxY) {
|
|
307
|
+
return {
|
|
308
|
+
minX,
|
|
309
|
+
minY,
|
|
310
|
+
maxX,
|
|
311
|
+
maxY,
|
|
312
|
+
width: maxX - minX,
|
|
313
|
+
height: maxY - minY,
|
|
314
|
+
centerX: minX + (maxX - minX) / 2,
|
|
315
|
+
centerY: minY + (maxY - minY) / 2,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function placeObject(object, x, y, depth, positions, orbitDrafts, leaderDrafts, context) {
|
|
319
|
+
if (positions.has(object.id)) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
positions.set(object.id, {
|
|
323
|
+
object,
|
|
324
|
+
x,
|
|
325
|
+
y,
|
|
326
|
+
radius: visualRadiusFor(object, depth),
|
|
327
|
+
});
|
|
328
|
+
placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, context);
|
|
329
|
+
}
|
|
330
|
+
function placeOrbitingChildren(object, positions, orbitDrafts, leaderDrafts, context) {
|
|
331
|
+
const parent = positions.get(object.id);
|
|
332
|
+
if (!parent) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const orbiting = [...(context.orbitChildren.get(object.id) ?? [])].sort(compareOrbiting);
|
|
336
|
+
const orbitRadii = computeOrbitRadii(orbiting, parent.radius);
|
|
337
|
+
orbiting.forEach((child, index) => {
|
|
338
|
+
const angle = resolveOrbitAngle(child, index, orbiting.length);
|
|
339
|
+
const orbitRadius = orbitRadii[index];
|
|
340
|
+
const x = parent.x + Math.cos(angle) * orbitRadius;
|
|
341
|
+
const y = parent.y + Math.sin(angle) * orbitRadius;
|
|
342
|
+
orbitDrafts.push({
|
|
343
|
+
object: child,
|
|
344
|
+
parentId: object.id,
|
|
345
|
+
cx: parent.x,
|
|
346
|
+
cy: parent.y,
|
|
347
|
+
radius: orbitRadius,
|
|
348
|
+
});
|
|
349
|
+
placeObject(child, x, y, 1, positions, orbitDrafts, leaderDrafts, context);
|
|
350
|
+
});
|
|
351
|
+
const surfaceObjects = [...(context.surfaceChildren.get(object.id) ?? [])];
|
|
352
|
+
surfaceObjects.forEach((child, index) => {
|
|
353
|
+
const angle = angleForIndex(index, surfaceObjects.length, -Math.PI / 3);
|
|
354
|
+
const anchorX = parent.x + Math.cos(angle) * parent.radius;
|
|
355
|
+
const anchorY = parent.y + Math.sin(angle) * parent.radius;
|
|
356
|
+
const x = parent.x + Math.cos(angle) * (parent.radius + 28);
|
|
357
|
+
const y = parent.y + Math.sin(angle) * (parent.radius + 28);
|
|
358
|
+
positions.set(child.id, {
|
|
359
|
+
object: child,
|
|
360
|
+
x,
|
|
361
|
+
y,
|
|
362
|
+
radius: visualRadiusFor(child, 2),
|
|
363
|
+
anchorX,
|
|
364
|
+
anchorY,
|
|
365
|
+
});
|
|
366
|
+
leaderDrafts.push({
|
|
367
|
+
object: child,
|
|
368
|
+
x1: anchorX,
|
|
369
|
+
y1: anchorY,
|
|
370
|
+
x2: x,
|
|
371
|
+
y2: y,
|
|
372
|
+
mode: "surface",
|
|
373
|
+
});
|
|
374
|
+
placeOrbitingChildren(child, positions, orbitDrafts, leaderDrafts, context);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
function compareOrbiting(left, right) {
|
|
378
|
+
const leftMetric = orbitMetric(left);
|
|
379
|
+
const rightMetric = orbitMetric(right);
|
|
380
|
+
if (leftMetric !== null && rightMetric !== null && leftMetric !== rightMetric) {
|
|
381
|
+
return leftMetric - rightMetric;
|
|
382
|
+
}
|
|
383
|
+
if (leftMetric !== null && rightMetric === null) {
|
|
384
|
+
return -1;
|
|
385
|
+
}
|
|
386
|
+
if (leftMetric === null && rightMetric !== null) {
|
|
387
|
+
return 1;
|
|
388
|
+
}
|
|
389
|
+
return left.id.localeCompare(right.id);
|
|
390
|
+
}
|
|
391
|
+
function computeOrbitRadii(objects, parentRadius) {
|
|
392
|
+
if (objects.length === 0) {
|
|
393
|
+
return [];
|
|
394
|
+
}
|
|
395
|
+
const metrics = objects.map((object) => orbitMetric(object));
|
|
396
|
+
const presentMetrics = metrics.filter((value) => value !== null);
|
|
397
|
+
const inner = parentRadius + 54;
|
|
398
|
+
const step = objects.length > 2 ? 54 : 64;
|
|
399
|
+
if (presentMetrics.length >= 2) {
|
|
400
|
+
const minMetric = Math.min(...presentMetrics);
|
|
401
|
+
const maxMetric = Math.max(...presentMetrics);
|
|
402
|
+
const spread = maxMetric - minMetric || 1;
|
|
403
|
+
return objects.map((_, index) => {
|
|
404
|
+
const metric = metrics[index];
|
|
405
|
+
if (metric === null) {
|
|
406
|
+
return inner + index * step;
|
|
407
|
+
}
|
|
408
|
+
const normalized = (metric - minMetric) / spread;
|
|
409
|
+
return inner + normalized * Math.max(step * (objects.length - 1), step);
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
return objects.map((_, index) => inner + index * step);
|
|
413
|
+
}
|
|
414
|
+
function orbitMetric(object) {
|
|
415
|
+
if (!object.placement || object.placement.mode !== "orbit") {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
return toDistanceMetric(object.placement.semiMajor ?? object.placement.distance ?? null);
|
|
419
|
+
}
|
|
420
|
+
function resolveOrbitAngle(object, index, count) {
|
|
421
|
+
if (!object.placement || object.placement.mode !== "orbit") {
|
|
422
|
+
return angleForIndex(index, count, -Math.PI / 2);
|
|
423
|
+
}
|
|
424
|
+
const directAngle = object.placement.phase ?? object.placement.angle;
|
|
425
|
+
const degreeValue = directAngle ? unitValueToDegrees(directAngle) : null;
|
|
426
|
+
if (degreeValue !== null) {
|
|
427
|
+
return (degreeValue - 90) * (Math.PI / 180);
|
|
428
|
+
}
|
|
429
|
+
return angleForIndex(index, count, -Math.PI / 2);
|
|
430
|
+
}
|
|
431
|
+
function resolveAtPosition(reference, positions, objectMap, index, count, width, height, padding) {
|
|
432
|
+
if (reference.kind === "lagrange") {
|
|
433
|
+
return resolveLagrangePosition(reference, positions, objectMap, width, height);
|
|
434
|
+
}
|
|
435
|
+
const anchor = positions.get(reference.name);
|
|
436
|
+
if (anchor) {
|
|
437
|
+
const angle = angleForIndex(index, count, Math.PI / 6);
|
|
438
|
+
const distance = anchor.radius + 34;
|
|
439
|
+
return {
|
|
440
|
+
x: anchor.x + Math.cos(angle) * distance,
|
|
441
|
+
y: anchor.y + Math.sin(angle) * distance,
|
|
442
|
+
anchorX: anchor.x,
|
|
443
|
+
anchorY: anchor.y,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
x: width - padding - 160,
|
|
448
|
+
y: height - padding - 80 - index * 56,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function resolveLagrangePosition(reference, positions, objectMap, width, height) {
|
|
452
|
+
const primary = reference.secondary
|
|
453
|
+
? positions.get(reference.primary)
|
|
454
|
+
: deriveParentAnchor(reference.primary, positions, objectMap);
|
|
455
|
+
const secondary = positions.get(reference.secondary ?? reference.primary);
|
|
456
|
+
if (!primary || !secondary) {
|
|
457
|
+
return {
|
|
458
|
+
x: width * 0.7,
|
|
459
|
+
y: height * 0.25,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
const dx = secondary.x - primary.x;
|
|
463
|
+
const dy = secondary.y - primary.y;
|
|
464
|
+
const distance = Math.hypot(dx, dy) || 1;
|
|
465
|
+
const ux = dx / distance;
|
|
466
|
+
const uy = dy / distance;
|
|
467
|
+
const nx = -uy;
|
|
468
|
+
const ny = ux;
|
|
469
|
+
const offset = clamp(distance * 0.25, 24, 64);
|
|
470
|
+
switch (reference.point) {
|
|
471
|
+
case "L1":
|
|
472
|
+
return {
|
|
473
|
+
x: secondary.x - ux * offset,
|
|
474
|
+
y: secondary.y - uy * offset,
|
|
475
|
+
anchorX: secondary.x,
|
|
476
|
+
anchorY: secondary.y,
|
|
477
|
+
};
|
|
478
|
+
case "L2":
|
|
479
|
+
return {
|
|
480
|
+
x: secondary.x + ux * offset,
|
|
481
|
+
y: secondary.y + uy * offset,
|
|
482
|
+
anchorX: secondary.x,
|
|
483
|
+
anchorY: secondary.y,
|
|
484
|
+
};
|
|
485
|
+
case "L3":
|
|
486
|
+
return {
|
|
487
|
+
x: primary.x - ux * offset,
|
|
488
|
+
y: primary.y - uy * offset,
|
|
489
|
+
anchorX: primary.x,
|
|
490
|
+
anchorY: primary.y,
|
|
491
|
+
};
|
|
492
|
+
case "L4":
|
|
493
|
+
return {
|
|
494
|
+
x: secondary.x + (ux * 0.5 - nx * 0.8660254) * offset,
|
|
495
|
+
y: secondary.y + (uy * 0.5 - ny * 0.8660254) * offset,
|
|
496
|
+
anchorX: secondary.x,
|
|
497
|
+
anchorY: secondary.y,
|
|
498
|
+
};
|
|
499
|
+
case "L5":
|
|
500
|
+
return {
|
|
501
|
+
x: secondary.x + (ux * 0.5 + nx * 0.8660254) * offset,
|
|
502
|
+
y: secondary.y + (uy * 0.5 + ny * 0.8660254) * offset,
|
|
503
|
+
anchorX: secondary.x,
|
|
504
|
+
anchorY: secondary.y,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function deriveParentAnchor(objectId, positions, objectMap) {
|
|
509
|
+
const object = objectMap.get(objectId);
|
|
510
|
+
if (!object?.placement || object.placement.mode !== "orbit") {
|
|
511
|
+
return positions.get(objectId);
|
|
512
|
+
}
|
|
513
|
+
return positions.get(object.placement.target);
|
|
514
|
+
}
|
|
515
|
+
function visualRadiusFor(object, depth = 0) {
|
|
516
|
+
const explicitRadius = toVisualSizeMetric(object.properties.radius);
|
|
517
|
+
if (explicitRadius !== null) {
|
|
518
|
+
return explicitRadius;
|
|
519
|
+
}
|
|
520
|
+
switch (object.type) {
|
|
521
|
+
case "star":
|
|
522
|
+
return depth === 0 ? 28 : 20;
|
|
523
|
+
case "planet":
|
|
524
|
+
return 12;
|
|
525
|
+
case "moon":
|
|
526
|
+
return 7;
|
|
527
|
+
case "belt":
|
|
528
|
+
return 5;
|
|
529
|
+
case "asteroid":
|
|
530
|
+
return 5;
|
|
531
|
+
case "comet":
|
|
532
|
+
return 6;
|
|
533
|
+
case "ring":
|
|
534
|
+
return 5;
|
|
535
|
+
case "structure":
|
|
536
|
+
return 6;
|
|
537
|
+
case "phenomenon":
|
|
538
|
+
return 8;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function visualExtentForObject(object, radius) {
|
|
542
|
+
switch (object.type) {
|
|
543
|
+
case "star":
|
|
544
|
+
return radius * 2.4;
|
|
545
|
+
case "phenomenon":
|
|
546
|
+
return radius * 1.2;
|
|
547
|
+
case "structure":
|
|
548
|
+
return radius + 2;
|
|
549
|
+
default:
|
|
550
|
+
return radius;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function renderBackdrop(width, height) {
|
|
554
|
+
const verticalLines = Array.from({ length: 10 }, (_, index) => {
|
|
555
|
+
const x = 90 + index * ((width - 180) / 9);
|
|
556
|
+
return `<line class="wo-grid" x1="${x}" y1="120" x2="${x}" y2="${height - 70}" />`;
|
|
557
|
+
}).join("");
|
|
558
|
+
const horizontalLines = Array.from({ length: 6 }, (_, index) => {
|
|
559
|
+
const y = 150 + index * ((height - 240) / 5);
|
|
560
|
+
return `<line class="wo-grid" x1="56" y1="${y}" x2="${width - 56}" y2="${y}" />`;
|
|
561
|
+
}).join("");
|
|
562
|
+
return `${verticalLines}${horizontalLines}`;
|
|
563
|
+
}
|
|
564
|
+
function renderSceneObject(sceneObject) {
|
|
565
|
+
const { object, x, y, radius, label, secondaryLabel, visualRadius, fillColor } = sceneObject;
|
|
566
|
+
const labelY = y + radius + 18;
|
|
567
|
+
return `<g class="wo-object wo-object-${object.type}" data-object-id="${escapeXml(sceneObject.objectId)}" data-render-id="${escapeXml(sceneObject.renderId)}">
|
|
568
|
+
<circle class="wo-selection-ring" cx="${x}" cy="${y}" r="${visualRadius + 8}" />
|
|
569
|
+
${renderObjectBody(object, x, y, radius, fillColor)}
|
|
570
|
+
<text class="wo-label" x="${x}" y="${labelY}" text-anchor="middle">${escapeXml(label)}</text>
|
|
571
|
+
<text class="wo-label-secondary" x="${x}" y="${labelY + 15}" text-anchor="middle">${escapeXml(secondaryLabel)}</text>
|
|
572
|
+
</g>`;
|
|
573
|
+
}
|
|
574
|
+
function renderObjectBody(object, x, y, radius, fillColor) {
|
|
575
|
+
switch (object.type) {
|
|
576
|
+
case "star":
|
|
577
|
+
return `<circle class="wo-star-glow" cx="${x}" cy="${y}" r="${radius * 2.4}" />
|
|
578
|
+
<circle class="wo-star-core" cx="${x}" cy="${y}" r="${radius}"${fillColor ? ` fill="${fillColor}"` : ""} />`;
|
|
579
|
+
case "planet":
|
|
580
|
+
return `<circle class="wo-planet" cx="${x}" cy="${y}" r="${radius}"${fillColor ? ` fill="${fillColor}"` : ""} />`;
|
|
581
|
+
case "moon":
|
|
582
|
+
return `<circle class="wo-moon" cx="${x}" cy="${y}" r="${radius}"${fillColor ? ` fill="${fillColor}"` : ""} />`;
|
|
583
|
+
case "belt":
|
|
584
|
+
return `<circle class="wo-belt" cx="${x}" cy="${y}" r="${radius}"${fillColor ? ` fill="${fillColor}"` : ""} />`;
|
|
585
|
+
case "asteroid":
|
|
586
|
+
return `<circle class="wo-asteroid" cx="${x}" cy="${y}" r="${radius}"${fillColor ? ` fill="${fillColor}"` : ""} />`;
|
|
587
|
+
case "comet":
|
|
588
|
+
return `<path class="wo-comet" d="M ${x - radius * 1.8} ${y + radius * 1.2} Q ${x - radius * 0.6} ${y + radius * 0.2} ${x - radius * 0.4} ${y}" /><circle class="wo-comet" cx="${x}" cy="${y}" r="${radius}"${fillColor ? ` fill="${fillColor}"` : ""} />`;
|
|
589
|
+
case "ring":
|
|
590
|
+
return `<circle class="wo-ring" cx="${x}" cy="${y}" r="${radius}"${fillColor ? ` fill="${fillColor}"` : ""} />`;
|
|
591
|
+
case "structure":
|
|
592
|
+
return `<polygon class="wo-structure" points="${x},${y - radius} ${x + radius},${y} ${x},${y + radius} ${x - radius},${y}"${fillColor ? ` fill="${fillColor}"` : ""} />`;
|
|
593
|
+
case "phenomenon":
|
|
594
|
+
return `<polygon class="wo-phenomenon" points="${x},${y - radius * 1.2} ${x + radius * 0.9},${y - radius * 0.3} ${x + radius * 1.2},${y + radius * 0.8} ${x},${y + radius * 1.2} ${x - radius * 1.1},${y + radius * 0.3} ${x - radius * 0.8},${y - radius * 0.8}"${fillColor ? ` fill="${fillColor}"` : ""} />`;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function toDistanceMetric(value) {
|
|
598
|
+
if (!value) {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
switch (value.unit) {
|
|
602
|
+
case "au":
|
|
603
|
+
return value.value;
|
|
604
|
+
case "km":
|
|
605
|
+
return value.value / AU_IN_KM;
|
|
606
|
+
case "re":
|
|
607
|
+
return (value.value * EARTH_RADIUS_IN_KM) / AU_IN_KM;
|
|
608
|
+
case "sol":
|
|
609
|
+
return (value.value * SOLAR_RADIUS_IN_KM) / AU_IN_KM;
|
|
610
|
+
default:
|
|
611
|
+
return value.value;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function toVisualSizeMetric(value) {
|
|
615
|
+
if (!value || typeof value !== "object" || !("value" in value)) {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
const unitValue = value;
|
|
619
|
+
switch (unitValue.unit) {
|
|
620
|
+
case "sol":
|
|
621
|
+
return clamp(unitValue.value * 22, 14, 40);
|
|
622
|
+
case "re":
|
|
623
|
+
return clamp(unitValue.value * 10, 6, 18);
|
|
624
|
+
case "km":
|
|
625
|
+
return clamp(Math.log10(Math.max(unitValue.value, 1)) * 2.6, 4, 16);
|
|
626
|
+
default:
|
|
627
|
+
return clamp(unitValue.value * 4, 4, 20);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function unitValueToDegrees(value) {
|
|
631
|
+
if (value.unit === "deg" || value.unit === null) {
|
|
632
|
+
return value.value;
|
|
633
|
+
}
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
function angleForIndex(index, count, startAngle) {
|
|
637
|
+
if (count <= 1) {
|
|
638
|
+
return startAngle;
|
|
639
|
+
}
|
|
640
|
+
return startAngle + (index * Math.PI * 2) / count;
|
|
641
|
+
}
|
|
642
|
+
function clamp(value, min, max) {
|
|
643
|
+
return Math.min(Math.max(value, min), max);
|
|
644
|
+
}
|
|
645
|
+
function pushGrouped(map, key, value) {
|
|
646
|
+
const existing = map.get(key);
|
|
647
|
+
if (existing) {
|
|
648
|
+
existing.push(value);
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
map.set(key, [value]);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function createRenderId(objectId) {
|
|
655
|
+
const normalized = objectId
|
|
656
|
+
.trim()
|
|
657
|
+
.toLowerCase()
|
|
658
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
659
|
+
.replace(/^-+|-+$/g, "") || "object";
|
|
660
|
+
return `wo-${normalized}`;
|
|
661
|
+
}
|
|
662
|
+
function customColorFor(value) {
|
|
663
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
664
|
+
}
|
|
665
|
+
function escapeXml(value) {
|
|
666
|
+
return value
|
|
667
|
+
.replaceAll("&", "&")
|
|
668
|
+
.replaceAll("<", "<")
|
|
669
|
+
.replaceAll(">", ">")
|
|
670
|
+
.replaceAll('"', """)
|
|
671
|
+
.replaceAll("'", "'");
|
|
672
|
+
}
|
|
673
|
+
export function rotatePoint(point, center, rotationDeg) {
|
|
674
|
+
const radians = (rotationDeg * Math.PI) / 180;
|
|
675
|
+
const cos = Math.cos(radians);
|
|
676
|
+
const sin = Math.sin(radians);
|
|
677
|
+
const dx = point.x - center.x;
|
|
678
|
+
const dy = point.y - center.y;
|
|
679
|
+
return {
|
|
680
|
+
x: center.x + dx * cos - dy * sin,
|
|
681
|
+
y: center.y + dx * sin + dy * cos,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { LineToken, TokenizeOptions } from "./types.js";
|
|
2
|
+
export declare function tokenizeLine(input: string): string[];
|
|
3
|
+
export declare function tokenizeLineDetailed(input: string, options?: TokenizeOptions): LineToken[];
|
|
4
|
+
export declare function getIndent(rawLine: string): number;
|