@motionscript/geo 0.1.0-alpha.0 → 0.1.0-alpha.3
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/CHANGELOG.md +16 -0
- package/dist/border/geo-border.d.ts +1 -1
- package/dist/border/geo-border.d.ts.map +1 -1
- package/dist/border/geo-border.js +2 -2
- package/dist/border/geo-border.js.map +1 -1
- package/dist/border/loader.d.ts +1 -1
- package/dist/border/loader.d.ts.map +1 -1
- package/dist/border/loader.js.map +1 -1
- package/dist/browser/index.js +2 -2
- package/dist/browser/index.js.map +2 -2
- package/dist/browser/manifest.json +2 -2
- package/dist/globe/atmosphere.d.ts +1 -1
- package/dist/globe/atmosphere.d.ts.map +1 -1
- package/dist/globe/atmosphere.js +1 -1
- package/dist/globe/atmosphere.js.map +1 -1
- package/dist/globe/globe.d.ts +2 -2
- package/dist/globe/globe.d.ts.map +1 -1
- package/dist/globe/globe.js +2 -2
- package/dist/globe/globe.js.map +1 -1
- package/dist/globe/world-map.d.ts +1 -1
- package/dist/globe/world-map.d.ts.map +1 -1
- package/dist/globe/world-map.js +1 -1
- package/dist/globe/world-map.js.map +1 -1
- package/package.json +4 -3
- package/src/border/geo-border.ts +2 -2
- package/src/border/loader.ts +1 -1
- package/src/globe/atmosphere.ts +1 -1
- package/src/globe/globe.ts +2 -2
- package/src/globe/world-map.ts +1 -1
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* anything — which also means it never darkens the stars or the planet behind
|
|
18
18
|
* it, and needs no sorting against them.
|
|
19
19
|
*/
|
|
20
|
-
import { type Material3D, type NormalizedColor } from "@motionscript/
|
|
20
|
+
import { type Material3D, type NormalizedColor } from "@motionscript/sdk";
|
|
21
21
|
/**
|
|
22
22
|
* The atmosphere's material — including which faces it keeps and how it blends.
|
|
23
23
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atmosphere.d.ts","sourceRoot":"","sources":["../../src/globe/atmosphere.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAO,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"atmosphere.d.ts","sourceRoot":"","sources":["../../src/globe/atmosphere.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAO,KAAK,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAyC9E;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,UAAU,CAgBZ"}
|
package/dist/globe/atmosphere.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* anything — which also means it never darkens the stars or the planet behind
|
|
18
18
|
* it, and needs no sorting against them.
|
|
19
19
|
*/
|
|
20
|
-
import { Mat } from "@motionscript/
|
|
20
|
+
import { Mat } from "@motionscript/sdk";
|
|
21
21
|
/**
|
|
22
22
|
* The eye-facing rim term, and nothing else.
|
|
23
23
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atmosphere.js","sourceRoot":"","sources":["../../src/globe/atmosphere.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,GAAG,EAAyC,MAAM,
|
|
1
|
+
{"version":3,"file":"atmosphere.js","sourceRoot":"","sources":["../../src/globe/atmosphere.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,GAAG,EAAyC,MAAM,mBAAmB,CAAA;AAE9E;;;;;;GAMG;AACH,MAAM,MAAM,GAAG,UAAU,CAAC;;;;;;;;;;CAUzB,CAAA;AAED;;;;GAIG;AACH,MAAM,QAAQ,GAAG,UAAU,CAAC;;;;;;;;;;;;;CAa3B,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAsB,EACtB,OAAe,EACf,SAAiB;IAEjB,OAAO,GAAG,CAAC,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,QAAQ;QAClB,QAAQ,EAAE;YACR,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;YACtC,QAAQ,EAAE,OAAO;YACjB,UAAU,EAAE,SAAS;SACtB;QACD,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,IAAI;QACjB,yEAAyE;QACzE,4CAA4C;QAC5C,UAAU,EAAE,KAAK;KAClB,CAAC,CAAA;AACJ,CAAC","sourcesContent":["/**\n * The halo around the planet's edge — a shell, seen from the inside.\n *\n * The trick is old and still the best one: draw a sphere slightly larger than\n * the planet, keep only its **back** faces, and light nothing. What survives is\n * the far wall of the shell, which the planet hides everywhere except around the\n * silhouette — so the brightness only has to be a function of how edge-on the\n * surface is, and the occlusion comes free from the depth buffer.\n *\n * That is why it is a shader rather than a material with a gradient: the value\n * needed is `1 - |n · v|`, which no material exposes, and which is *per pixel*.\n * A gradient painted onto a sphere is fixed to the geometry and slides as the\n * camera turns; this is anchored to the eye, so the rim stays on the horizon\n * wherever the globe is looked at from.\n *\n * Additively blended, because atmosphere adds light rather than covering\n * anything — which also means it never darkens the stars or the planet behind\n * it, and needs no sorting against them.\n */\n\nimport { Mat, type Material3D, type NormalizedColor } from \"@motionscript/sdk\"\n\n/**\n * The eye-facing rim term, and nothing else.\n *\n * `vView` is the fragment's direction to the camera in view space, where the eye\n * is the origin — so it needs no camera uniform, and the same shader works for\n * an orthographic camera where a `cameraPosition` version would not.\n */\nconst VERTEX = /* glsl */ `\nvarying vec3 vNormal;\nvarying vec3 vView;\n\nvoid main() {\n vec4 viewPosition = modelViewMatrix * vec4(position, 1.0);\n vNormal = normalize(normalMatrix * normal);\n vView = -viewPosition.xyz;\n gl_Position = projectionMatrix * viewPosition;\n}\n`\n\n/**\n * `abs` on the dot product, deliberately: these are back faces, so the normal\n * points away from the eye and the un-absolute term would be brightest face-on —\n * a disc over the planet instead of a ring around it.\n */\nconst FRAGMENT = /* glsl */ `\nuniform vec3 uColor;\nuniform float uFalloff;\nuniform float uIntensity;\n\nvarying vec3 vNormal;\nvarying vec3 vView;\n\nvoid main() {\n float facing = abs(dot(normalize(vNormal), normalize(vView)));\n float rim = pow(clamp(1.0 - facing, 0.0, 1.0), uFalloff) * uIntensity;\n gl_FragColor = vec4(uColor * rim, rim);\n}\n`\n\n/**\n * The atmosphere's material — including which faces it keeps and how it blends.\n *\n * Those two belong *on the descriptor* rather than in the mesh shorthand beside\n * it, and not as a matter of taste: `resolveMaterialShorthand3D` returns an\n * explicit `material` immediately and never reaches the shorthand's own `faces`\n * or `blend`. Set them out there and they are silently dropped — the shell keeps\n * its front faces, covers the planet in a bright disc, and the only clue is that\n * the globe has gone.\n *\n * `color` arrives as core's resolved `[r, g, b, a]` and is handed on as three\n * numbers rather than four: the uniform coercion reads a numeric array of length\n * four as a `vec4`, never as a colour, so passing the tuple whole would bind\n * alpha where the shader expects nothing and shift the hue. Alpha has nothing to\n * say here anyway — the rim term is the opacity.\n */\nexport function atmosphereMaterial(\n color: NormalizedColor,\n falloff: number,\n intensity: number\n): Material3D {\n return Mat.shader({\n vertex: VERTEX,\n fragment: FRAGMENT,\n uniforms: {\n uColor: [color[0], color[1], color[2]],\n uFalloff: falloff,\n uIntensity: intensity,\n },\n faces: \"back\",\n blend: \"additive\",\n transparent: true,\n // Nothing here writes depth: the shell is a light, and one that occluded\n // would hide the planet it is drawn around.\n depthWrite: false,\n })\n}\n"]}
|
package/dist/globe/globe.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Command, type CommandArgs, Canvas3D, Scene3D, type Canvas3DProps, type Color, type NodeConfig } from "@motionscript/
|
|
2
|
-
import { type OrbitTarget } from "@motionscript/
|
|
1
|
+
import { type Command, type CommandArgs, Canvas3D, Scene3D, type Canvas3DProps, type Color, type NodeConfig } from "@motionscript/sdk";
|
|
2
|
+
import { type OrbitTarget } from "@motionscript/sdk/component";
|
|
3
3
|
export interface GlobeProps extends Canvas3DProps {
|
|
4
4
|
resolution: number;
|
|
5
5
|
ocean: Color;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"globe.d.ts","sourceRoot":"","sources":["../../src/globe/globe.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,QAAQ,EAIR,OAAO,EAEP,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,UAAU,EAIhB,MAAM,
|
|
1
|
+
{"version":3,"file":"globe.d.ts","sourceRoot":"","sources":["../../src/globe/globe.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,QAAQ,EAIR,OAAO,EAEP,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,UAAU,EAIhB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAOL,KAAK,WAAW,EACjB,MAAM,6BAA6B,CAAA;AAqBpC,MAAM,WAAW,UAAW,SAAQ,aAAa;IAE/C,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,KAAK,CAAA;IACZ,IAAI,EAAE,KAAK,CAAA;IACX,MAAM,EAAE,KAAK,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,cAAc,EAAE,KAAK,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;IAGtB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IAGX,gBAAgB,EAAE,MAAM,CAAA;IACxB,YAAY,EAAE,KAAK,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,KAAK,CAAA;IAGf,UAAU,EAAE,OAAO,CAAA;IACnB,eAAe,EAAE,KAAK,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,iBAAiB,EAAE,MAAM,CAAA;IACzB,mBAAmB,EAAE,MAAM,CAAA;IAG3B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAea,KAAM,SAAQ,QAAQ,CAAC,UAAU,CAAC;IAQ7C;;;;;;;OAOG;IACoD,UAAU,EAAE,MAAM,CAAA;IAEb,KAAK,EAAE,KAAK,CAAA;IACZ,IAAI,EAAE,KAAK,CAAA;IACvE;;;;;;;OAOG;IACyD,MAAM,EAAE,KAAK,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IAEnB,SAAS,EAAE,OAAO,CAAA;IAEhE,cAAc,EAAE,KAAK,CAAA;IACwB,aAAa,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IAI1E;;;;;;;;OAQG;IACiC,KAAK,EAAE,MAAM,CAAA;IACjD;;;;OAIG;IACgC,SAAS,EAAE,MAAM,CAAA;IACpD,qEAAqE;IACjC,IAAI,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IAIT,gBAAgB,EAAE,MAAM,CAAA;IAErD,YAAY,EAAE,KAAK,CAAA;IACS,YAAY,EAAE,MAAM,CAAA;IAEhD,QAAQ,EAAE,KAAK,CAAA;IAM+B,UAAU,EAAE,OAAO,CAAA;IAEjE,eAAe,EAAE,KAAK,CAAA;IAC9B,uDAAuD;IAClB,cAAc,EAAE,MAAM,CAAA;IAC3D,mEAAmE;IACjC,iBAAiB,EAAE,MAAM,CAAA;IACvB,mBAAmB,EAAE,MAAM,CAAA;IASR,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;gBAEvD,KAAK,CAAC,EAAE,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC;IAIjD;;;;OAIG;IAYH,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,CAAC,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAQ/F;;;;;;;;OAQG;IAEH,KAAK,CAAC,IAAI,EAAE,WAAW,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;cA2B3H,YAAY,IAAI,OAAO;IA0D1C,yCAAyC;IACzC,OAAO,CAAC,SAAS;IAejB,wEAAwE;IACxE,OAAO,CAAC,aAAa;IAYrB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,UAAU;IA6BlB,qCAAqC;IACrC,OAAO,CAAC,OAAO;IA+Bf;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,KAAK,CAA8C;IAE3D,OAAO,CAAC,QAAQ;IA8BhB,4EAA4E;IAC5E,OAAO,CAAC,QAAQ;CAgBjB"}
|
package/dist/globe/globe.js
CHANGED
|
@@ -4,8 +4,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { command, driveCommand, easeInOut, node, Canvas3D, Geo, Graphics3D, Mat, Scene3D, property, } from "@motionscript/
|
|
8
|
-
import { lerpColor3D, resolveColor3D, snapFlag, snapValue, cameraOrbit, orbitProps, } from "@motionscript/
|
|
7
|
+
import { command, driveCommand, easeInOut, node, Canvas3D, Geo, Graphics3D, Mat, Scene3D, property, } from "@motionscript/sdk";
|
|
8
|
+
import { lerpColor3D, resolveColor3D, snapFlag, snapValue, cameraOrbit, orbitProps, } from "@motionscript/sdk/component";
|
|
9
9
|
import { atmosphereMaterial } from "./atmosphere.js";
|
|
10
10
|
import { arcSegments, drawableArcs, drawableMarkers } from "./places.js";
|
|
11
11
|
import { greatCircle, latLonToPosition } from "./projection.js";
|
package/dist/globe/globe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"globe.js","sourceRoot":"","sources":["../../src/globe/globe.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n command,\n driveCommand,\n easeInOut,\n node,\n type Command,\n type CommandArgs,\n Canvas3D,\n Geo,\n Graphics3D,\n Mat,\n Scene3D,\n property,\n type Canvas3DProps,\n type Color,\n type NodeConfig,\n type NormalizedColor,\n type SurfaceTexture3D,\n type Transform3D,\n} from \"@motionscript/core\"\n\nimport {\n lerpColor3D,\n resolveColor3D,\n snapFlag,\n snapValue,\n cameraOrbit,\n orbitProps,\n type OrbitTarget,\n} from \"@motionscript/core/component\"\nimport { atmosphereMaterial } from \"./atmosphere\"\nimport { arcSegments, drawableArcs, drawableMarkers } from \"./places\"\nimport { greatCircle, latLonToPosition } from \"./projection\"\nimport { mapHeight, worldMapGraphics, type WorldMapStyle } from \"./world-map\"\n\n/**\n * The globe's radius, in scene units.\n *\n * One, and not a prop. Every other length the node takes — a marker's size, an\n * arc's width and lift, the camera's `zoom` — is a fraction or a multiple of it,\n * which is what makes those numbers mean the same thing at any framing and is\n * why there is nothing for a radius prop to be relative *to*. The Protein's\n * radius is a prop because a molecule's size is a fact about the molecule; the\n * Earth's is a choice about units.\n */\nconst RADIUS = 1\n\n/** How far off the surface a marker's centre sits, as a fraction of its size. */\nconst MARKER_LIFT = 0.35\n\nexport interface GlobeProps extends Canvas3DProps {\n // ── The map ──\n resolution: number\n ocean: Color\n land: Color\n border: Color\n borderWidth: number\n graticule: boolean\n graticuleColor: Color\n graticuleStep: number\n graticuleWidth: number\n\n // ── The camera ──\n orbit: number\n elevation: number\n zoom: number\n fov: number\n\n // ── Lighting ──\n ambientIntensity: number\n ambientColor: Color\n keyIntensity: number\n keyColor: Color\n\n // ── The atmosphere ──\n atmosphere: boolean\n atmosphereColor: Color\n atmosphereSize: number\n atmosphereFalloff: number\n atmosphereIntensity: number\n\n // ── What is placed on it ──\n markers: string\n arcs: string\n}\n\n/** What the map cache is holding, keyed by the style that produced it. */\ninterface BakedMap {\n texture: SurfaceTexture3D\n}\n\n/**\n * A globe: country boundaries on a lit sphere, flown by an orbit camera.\n *\n * <Globe width=\"fill\" height=\"fill\" orbit={20} elevation={25} zoom={2.6} />\n *\n * Like the Protein and unlike the Canvas 3D viewport, it draws **one subject**\n * derived from its own props rather than being a room the author fills. Four\n * things about it are worth knowing before reading the parts:\n *\n * **The map is a texture, drawn in 2D.** See `world-map.ts`: a WebGL line\n * ignores any width above one pixel, so borders drawn as 3D lines are hairlines\n * forever. Baked, a border is an ordinary Skia stroke at any weight.\n *\n * **That texture is cached against its style, and the cache is not an\n * optimisation.** motion-script re-rasterizes a surface every frame unless the\n * descriptor says otherwise, and re-rasterizing this one means redrawing ten\n * thousand paths, stalling on a GPU read-back and re-uploading a few megabytes —\n * sixty times a second, for an identical image. {@link bakedMap} holds one\n * descriptor per style and marks it `static`; the schema then refuses to animate\n * any field that style is made of, so an export cannot mint a raster per frame.\n *\n * **The camera is three tweenable numbers, and two of them are places.** `orbit`\n * is a longitude and `elevation` is a latitude — see `projection.ts`, which is\n * where that convention is established and defended. There is no `OrbitControls`\n * here for the reason the other 3D nodes have none: a rendered timeline has no\n * pointer, and every frame has to be reproducible under scrubbing and export.\n *\n * **Markers and arcs are real 3D, never baked.** They are the animated content;\n * putting them in the texture would run every frame of a marker's fade through a\n * full re-rasterize.\n */\n@node({\n key: \"globe\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n acceptsChildren: false,\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n },\n seed: {\n width: 640,\n height: 640,\n },\n})\nexport class Globe extends Canvas3D<GlobeProps> {\n // ── The map ──\n //\n // Every prop in this group is part of the texture's cache key, and every one\n // of them is `animatable: false` in the appearance schema. They are plain\n // strings rather than resolved colours for the same reason: a tween would need\n // a mapper, and there is deliberately no tween to have.\n\n /**\n * Texture width in pixels; the height is half of it.\n *\n * Structural, and capped in the schema rather than here. The buffer is\n * re-uploaded to the GPU every frame even when its pixels are cached, so this\n * is a per-frame bandwidth number as much as a sharpness one — 2048 is 8 MB a\n * frame and already finer than the 110m boundaries it draws.\n */\n @property({ default: 2048, tween: snapValue }) declare resolution: number\n\n @property({ default: \"#0b1a2b\", tween: snapValue }) declare ocean: Color\n @property({ default: \"#2c3e50\", tween: snapValue }) declare land: Color\n /**\n * The colour of every coast and border.\n *\n * Alpha works. It did not while the country paths carried the stroke — a\n * border belongs to two countries, so it was drawn twice and came out at\n * double density beside the coastlines. `borders.ts` deduplicates the lines\n * and `world-map.ts` strokes them once.\n */\n @property({ default: \"#4a6076\", tween: snapValue }) declare border: Color\n @property({ default: 1.5, tween: snapValue }) declare borderWidth: number\n\n @property({ default: true, tween: snapFlag }) declare graticule: boolean\n @property({ default: \"#ffffff20\", tween: snapValue })\n declare graticuleColor: Color\n @property({ default: 15, tween: snapValue }) declare graticuleStep: number\n @property({ default: 1, tween: snapValue }) declare graticuleWidth: number\n\n // ── The camera ──\n\n /**\n * The camera's heading, in degrees, in exactly the sense every other 3D node\n * uses it — which is what keeps the canvas drag consistent with them.\n *\n * It is *not* a longitude: a camera at heading θ is centred over longitude −θ\n * (see `orbitForLongitude` in `projection.ts`). Unbounded, like every other\n * viewport's, because the drag wraps it into `[0, 360)` and a range here would\n * clamp half of them.\n */\n @property({ default: -10 }) declare orbit: number\n /**\n * Degrees above the equator, which on a globe *is* the latitude the camera\n * stands over — no conversion, unlike the heading. Held short of the poles by\n * the schema, as every orbit camera's is.\n */\n @property({ default: 25 }) declare elevation: number\n /** Distance from the centre, as a multiple of the globe's radius. */\n @property({ default: 2.8 }) declare zoom: number\n @property({ default: 45 }) declare fov: number\n\n // ── Lighting ──\n\n @property({ default: 0.55 }) declare ambientIntensity: number\n @property({ default: \"#ffffff\", mapper: resolveColor3D, tween: lerpColor3D })\n declare ambientColor: Color\n @property({ default: 1.9 }) declare keyIntensity: number\n @property({ default: \"#ffffff\", mapper: resolveColor3D, tween: lerpColor3D })\n declare keyColor: Color\n\n // ── The atmosphere ──\n //\n // Shading rather than pixels, so unlike the map group these all animate.\n\n @property({ default: true, tween: snapFlag }) declare atmosphere: boolean\n @property({ default: \"#4da3ff\", mapper: resolveColor3D, tween: lerpColor3D })\n declare atmosphereColor: Color\n /** The shell's radius as a multiple of the globe's. */\n @property({ default: 1.06 }) declare atmosphereSize: number\n /** How tightly the glow hugs the edge. Higher is a thinner rim. */\n @property({ default: 3 }) declare atmosphereFalloff: number\n @property({ default: 1.1 }) declare atmosphereIntensity: number\n\n // ── What is placed on it ──\n //\n // Serialized lists, as the graphs store their equations: an appearance value\n // is a number, a string or a boolean, so a list arrives as a string a mapper\n // reads. Parsing is in `places.ts` and is deliberately tolerant — the field is\n // invalid far more often than valid while somebody is typing into it.\n\n @property({ default: \"[]\", tween: snapValue }) declare markers: string\n @property({ default: \"[]\", tween: snapValue }) declare arcs: string\n\n constructor(props?: NodeConfig<Globe, GlobeProps>) {\n super(props as NodeConfig<Canvas3D<GlobeProps>, GlobeProps>)\n }\n\n /**\n * Frame the scene at a spherical camera placement. Every axis is optional,\n * so \"pull back\" and \"spin round\" stay separate intentions — see\n * {@link OrbitTarget}.\n */\n @command({\n args: [\n {\n key: \"target\", kind: \"orbit\", properties: {\n orbit: { kind: \"number\", step: 1, unit: \"°\" },\n elevation: { kind: \"number\", min: -89, max: 89, step: 1, unit: \"°\" },\n zoom: { kind: \"number\", min: 0.2, max: 24, step: 0.1, unit: \"×\" },\n },\n },\n ],\n })\n orbitTo(args: CommandArgs<{ target: OrbitTarget }> & { duration: number }): Command<GlobeProps> {\n return this.to({\n data: orbitProps(args.data?.target ?? {}) as Partial<GlobeProps>,\n duration: args.duration,\n easing: args.easing ?? easeInOut(),\n })\n }\n\n /**\n * Fly the camera to a place on the globe. `latitude`/`longitude` are the\n * point to bring under the camera and `framing` the distance to settle at.\n *\n * **`arc` is what makes it a flight rather than a pan** — above `0` the\n * distance bows outward at the midpoint by that fraction, so a long journey\n * rises off the surface and settles back down rather than scraping the\n * horizon the whole way. Longitude takes the **short way round**.\n */\n @command({ args: [{ key: \"latitude\", kind: \"number\", default: 0, min: -89, max: 89, step: 1, unit: \"°\" }, { key: \"longitude\", kind: \"number\", default: 0, min: -180, max: 180, step: 1, unit: \"°\" }, { key: \"framing\", kind: \"number\", default: 2.2, min: 1.05, max: 12, step: 0.1, unit: \"×\" }, { key: \"arc\", kind: \"number\", default: 0.35, min: 0, max: 3, step: 0.05, scale: 100, unit: \"%\" }] })\n flyTo(args: CommandArgs<{ latitude: number; longitude: number; framing?: number; arc?: number }> & { duration?: number }): Command<GlobeProps> {\n const data = args.data ?? { latitude: 0, longitude: 0 }\n const { latitude, longitude, framing = 2.2, arc = 0.35 } = data\n const duration = args.duration ?? 1.6\n const easing = args.easing ?? easeInOut()\n\n const fromOrbit = this.orbit\n const fromElevation = this.elevation\n const fromZoom = this.zoom\n\n // The globe spins under a fixed camera, so a longitude is reached by\n // orbiting to its negation — and the shortest signed turn is what keeps a\n // crossing of the antimeridian from unwinding the long way.\n const toOrbit = fromOrbit + shortestTurn(fromOrbit, -longitude)\n\n return driveCommand(duration, (t) => {\n const eased = easing(t)\n this.set({\n orbit: fromOrbit + (toOrbit - fromOrbit) * eased,\n elevation: fromElevation + (latitude - fromElevation) * eased,\n // The bow: a half-sine that is 0 at both ends, so the command still\n // lands exactly on `framing` however large `arc` is.\n zoom: (fromZoom + (framing - fromZoom) * eased) * (1 + arc * Math.sin(Math.PI * eased)),\n } as Partial<GlobeProps>)\n }) as Command<GlobeProps>\n }\n\n protected override buildScene3D(): Scene3D {\n const scene = new Scene3D()\n const distance = RADIUS * Math.max(1.02, this.zoom)\n\n // Cut to the globe rather than left to the camera's own derivation from the\n // scene bounds: the atmosphere shell is the outermost thing in the scene and\n // sizing the planes to it would spend depth precision on the halo. The near\n // plane has to survive `zoom` coming right down to the surface, which is\n // what the fraction of the gap is for.\n scene.perspective({\n fov: this.fov,\n near: Math.max(0.01, (distance - RADIUS) * 0.5),\n far: distance + RADIUS * 4,\n // Exactly what every other 3D viewport does — the quarter turn between\n // the stored zero and the camera's, and nothing else. Anything extra here\n // is a globe whose canvas drag runs backwards against every other node's;\n // the longitude convention is handled in the *geometry* instead. See\n // `projection.ts`.\n orbit: cameraOrbit(this.orbit),\n elevation: this.elevation,\n distance,\n })\n\n // The same rig the Protein and the 3D viewport use, at the globe's scale: a\n // key, a rim that is the key from the other side at a fraction of its\n // strength, and enough ambient that the night side is a planet rather than a\n // silhouette. The backdrop is the node's own Fills stack — a `Canvas3D`\n // composites its 3D pass over its 2D fill layers — so there is no space to\n // paint here and no clear colour to set.\n scene\n .light({\n type: \"ambient\",\n intensity: this.ambientIntensity,\n color: this.ambientColor,\n })\n .light(\n { type: \"directional\", intensity: this.keyIntensity, color: this.keyColor },\n { position: [RADIUS * 3, RADIUS * 2, RADIUS * 3] }\n )\n .light(\n {\n type: \"directional\",\n intensity: this.keyIntensity * 0.35,\n color: this.keyColor,\n },\n { position: [-RADIUS * 3, -RADIUS, -RADIUS * 2] }\n )\n\n const g3 = new Graphics3D()\n this.addPlanet(g3)\n this.addArcs(g3)\n this.addMarkers(g3)\n if (this.atmosphere) this.addAtmosphere(g3)\n\n scene.draw(g3)\n return scene\n }\n\n /** The sphere, wearing the baked map. */\n private addPlanet(g3: Graphics3D): void {\n g3.sphere({\n radius: RADIUS,\n // Longitude segments matter more than latitude: the silhouette is a\n // circle of longitude, and a coarse one shows as a faceted rim against\n // the atmosphere's own smooth curve.\n segments: [128, 64],\n fill: this.bakedMap().texture,\n // Land and sea are matte at this distance; a specular highlight on an\n // ocean this size reads as a lens flare rather than as water.\n roughness: 0.95,\n metalness: 0,\n })\n }\n\n /** The halo. A shell, kept for its back faces — see `atmosphere.ts`. */\n private addAtmosphere(g3: Graphics3D): void {\n g3.sphere({\n radius: RADIUS * Math.max(1.001, this.atmosphereSize),\n segments: [96, 48],\n material: atmosphereMaterial(\n this.atmosphereColor as unknown as NormalizedColor,\n this.atmosphereFalloff,\n this.atmosphereIntensity\n ),\n })\n }\n\n /**\n * The markers, as one instanced draw.\n *\n * Instanced rather than a mesh apiece because a marker list is the one thing\n * here with no ceiling — a network map is thousands of points, and a draw call\n * each would not keep up. One geometry, one material, one upload.\n *\n * Note the material carries **no** `vertexColors`: an instanced mesh that asks\n * for it makes the shader read a per-vertex `color` attribute the geometry does\n * not have, gets zeroes, and every marker renders black. The per-instance\n * colours ride the `colors` option instead, which is a different channel.\n */\n private addMarkers(g3: Graphics3D): void {\n const markers = drawableMarkers(this.markers)\n if (markers.length === 0) return\n\n const placements: Transform3D[] = []\n const colors: Color[] = []\n\n for (const marker of markers) {\n const size = marker.size * RADIUS\n placements.push({\n // Lifted by a fraction of its own size so a marker sits *on* the\n // surface rather than half-buried in it, at any size.\n position: latLonToPosition(marker, RADIUS + size * MARKER_LIFT),\n scale: size,\n })\n colors.push(marker.color)\n }\n\n g3.instances(\n Geo.sphere({ radius: 1, segments: [16, 12] }),\n // Unlit, and deliberately: a marker is a signal rather than an object, and\n // one shaded by the scene's key light would go dark exactly when it\n // rotated to the night side — which is where it most needs to be seen.\n Mat.basic({ transparent: true }),\n placements,\n { colors }\n )\n }\n\n /** The arcs, one swept tube each. */\n private addArcs(g3: Graphics3D): void {\n // Resolved against the marker list, because an end may be *pinned* to one —\n // which is what keeps an arc attached when the place it points at moves.\n for (const arc of drawableArcs(this.arcs, this.markers)) {\n const points = greatCircle(arc.from, arc.to, {\n radius: RADIUS,\n lift: arc.lift,\n segments: arcSegments(arc),\n })\n\n // `progress` trims the point list rather than scaling the geometry, so a\n // half-drawn arc is the first half of the real path — the same shape the\n // finished arc has, stopping early. Two points is the floor a tube can be\n // swept along.\n const drawn = Math.max(2, Math.round(points.length * arc.progress))\n\n g3.mesh(\n Geo.tube({\n points: points.slice(0, drawn),\n radius: arc.width * RADIUS,\n segments: [drawn, 8],\n }),\n // Unlit for the reason the markers are: an arc is an annotation, and one\n // that dimmed over the night side would vanish halfway across.\n Mat.basic({ color: arc.color, transparent: true })\n )\n }\n }\n\n // --- The map cache -------------------------------------------------------\n\n /**\n * The baked map, and the descriptor that lets it be baked once.\n *\n * Two caches are being kept in step here, and the key is what keeps them in\n * step. This one holds the `Graphics2D` and its descriptor so the node hands\n * the renderer the *same object* every frame — surface identity is object\n * identity, so a fresh one each frame would defeat the texture cache and\n * orphan a GPU texture per frame. The renderer's own `staticRasters` holds the\n * rasterized pixels against the `identity` string below.\n *\n * They share one key, which is the point: edit a colour and this cache misses\n * (a new `Graphics2D`) *and* that one misses (a new identity string). Derive\n * the identity from anything else — a counter, the node id — and a colour\n * change would serve the old pixels forever, because nothing invalidates\n * `staticRasters` short of tearing down the render context.\n *\n * The descriptor is written out rather than built with `Tex.surface` because\n * the builder's options do not include `static` or `identity`. Handing a bare\n * `Graphics2D` to `fill` instead takes `resolveFill3D`'s surface path, which\n * sets neither — and that is the per-frame read-back this whole arrangement\n * exists to avoid.\n */\n private cache: { key: string; map: BakedMap } | null = null\n\n private bakedMap(): BakedMap {\n const style = this.mapStyle()\n const key = [\n style.width,\n style.ocean,\n style.land,\n style.border,\n style.borderWidth,\n style.graticule,\n style.graticuleColor,\n style.graticuleStep,\n style.graticuleWidth,\n ].join(\"|\")\n\n const cached = this.cache\n if (cached?.key === key) return cached.map\n\n const map: BakedMap = {\n texture: {\n source: worldMapGraphics(style),\n width: style.width,\n height: mapHeight(style.width),\n static: true,\n identity: `globe:${key}`,\n },\n }\n this.cache = { key, map }\n return map\n }\n\n /** The style props as the one value the baker takes. Also the cache key. */\n private mapStyle(): WorldMapStyle {\n return {\n // Rounded to an even number so the 2:1 height is a whole one; a half-pixel\n // buffer height is rounded by the rasterizer and the map then samples a\n // fraction of a row off, which reads as a seam at the equator.\n width: Math.max(256, Math.round(this.resolution / 2) * 2),\n ocean: hexOf(this.ocean),\n land: hexOf(this.land),\n border: hexOf(this.border),\n borderWidth: this.borderWidth,\n graticule: this.graticule,\n graticuleColor: hexOf(this.graticuleColor),\n graticuleStep: this.graticuleStep,\n graticuleWidth: this.graticuleWidth,\n }\n }\n}\n\n/**\n * The signed turn from `from` to `to` that travels less than half a circle.\n *\n * Degrees. What stops a flight from 170° to −170° unwinding 340° the wrong way\n * when it could cross the antimeridian in 20°.\n */\nfunction shortestTurn(from: number, to: number): number {\n return ((((to - from) % 360) + 540) % 360) - 180\n}\n\n\n/**\n * A colour prop as the string the baker and the cache key both want.\n *\n * These props carry no mapper — they are not animatable, so there is no resolved\n * form to hold — but a caller writing JSX is still free to hand one a tuple, and\n * a tuple in a cache key would stringify to something that compares equal for\n * two different colours.\n */\nfunction hexOf(value: Color): string {\n if (typeof value === \"string\") return value\n const [r, g, b, a = 1] = value as unknown as NormalizedColor\n const byte = (channel: number) =>\n Math.round(Math.min(1, Math.max(0, channel)) * 255)\n return `rgba(${byte(r)}, ${byte(g)}, ${byte(b)}, ${a})`\n}\n"]}
|
|
1
|
+
{"version":3,"file":"globe.js","sourceRoot":"","sources":["../../src/globe/globe.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n command,\n driveCommand,\n easeInOut,\n node,\n type Command,\n type CommandArgs,\n Canvas3D,\n Geo,\n Graphics3D,\n Mat,\n Scene3D,\n property,\n type Canvas3DProps,\n type Color,\n type NodeConfig,\n type NormalizedColor,\n type SurfaceTexture3D,\n type Transform3D,\n} from \"@motionscript/sdk\"\n\nimport {\n lerpColor3D,\n resolveColor3D,\n snapFlag,\n snapValue,\n cameraOrbit,\n orbitProps,\n type OrbitTarget,\n} from \"@motionscript/sdk/component\"\nimport { atmosphereMaterial } from \"./atmosphere\"\nimport { arcSegments, drawableArcs, drawableMarkers } from \"./places\"\nimport { greatCircle, latLonToPosition } from \"./projection\"\nimport { mapHeight, worldMapGraphics, type WorldMapStyle } from \"./world-map\"\n\n/**\n * The globe's radius, in scene units.\n *\n * One, and not a prop. Every other length the node takes — a marker's size, an\n * arc's width and lift, the camera's `zoom` — is a fraction or a multiple of it,\n * which is what makes those numbers mean the same thing at any framing and is\n * why there is nothing for a radius prop to be relative *to*. The Protein's\n * radius is a prop because a molecule's size is a fact about the molecule; the\n * Earth's is a choice about units.\n */\nconst RADIUS = 1\n\n/** How far off the surface a marker's centre sits, as a fraction of its size. */\nconst MARKER_LIFT = 0.35\n\nexport interface GlobeProps extends Canvas3DProps {\n // ── The map ──\n resolution: number\n ocean: Color\n land: Color\n border: Color\n borderWidth: number\n graticule: boolean\n graticuleColor: Color\n graticuleStep: number\n graticuleWidth: number\n\n // ── The camera ──\n orbit: number\n elevation: number\n zoom: number\n fov: number\n\n // ── Lighting ──\n ambientIntensity: number\n ambientColor: Color\n keyIntensity: number\n keyColor: Color\n\n // ── The atmosphere ──\n atmosphere: boolean\n atmosphereColor: Color\n atmosphereSize: number\n atmosphereFalloff: number\n atmosphereIntensity: number\n\n // ── What is placed on it ──\n markers: string\n arcs: string\n}\n\n/** What the map cache is holding, keyed by the style that produced it. */\ninterface BakedMap {\n texture: SurfaceTexture3D\n}\n\n/**\n * A globe: country boundaries on a lit sphere, flown by an orbit camera.\n *\n * <Globe width=\"fill\" height=\"fill\" orbit={20} elevation={25} zoom={2.6} />\n *\n * Like the Protein and unlike the Canvas 3D viewport, it draws **one subject**\n * derived from its own props rather than being a room the author fills. Four\n * things about it are worth knowing before reading the parts:\n *\n * **The map is a texture, drawn in 2D.** See `world-map.ts`: a WebGL line\n * ignores any width above one pixel, so borders drawn as 3D lines are hairlines\n * forever. Baked, a border is an ordinary Skia stroke at any weight.\n *\n * **That texture is cached against its style, and the cache is not an\n * optimisation.** motion-script re-rasterizes a surface every frame unless the\n * descriptor says otherwise, and re-rasterizing this one means redrawing ten\n * thousand paths, stalling on a GPU read-back and re-uploading a few megabytes —\n * sixty times a second, for an identical image. {@link bakedMap} holds one\n * descriptor per style and marks it `static`; the schema then refuses to animate\n * any field that style is made of, so an export cannot mint a raster per frame.\n *\n * **The camera is three tweenable numbers, and two of them are places.** `orbit`\n * is a longitude and `elevation` is a latitude — see `projection.ts`, which is\n * where that convention is established and defended. There is no `OrbitControls`\n * here for the reason the other 3D nodes have none: a rendered timeline has no\n * pointer, and every frame has to be reproducible under scrubbing and export.\n *\n * **Markers and arcs are real 3D, never baked.** They are the animated content;\n * putting them in the texture would run every frame of a marker's fade through a\n * full re-rasterize.\n */\n@node({\n key: \"globe\",\n parentKey: \"node\",\n forkable: true,\n layout: {\n children: \"freeform\",\n acceptsChildren: false,\n defaultWidthMode: \"fixed\",\n defaultHeightMode: \"fixed\",\n },\n seed: {\n width: 640,\n height: 640,\n },\n})\nexport class Globe extends Canvas3D<GlobeProps> {\n // ── The map ──\n //\n // Every prop in this group is part of the texture's cache key, and every one\n // of them is `animatable: false` in the appearance schema. They are plain\n // strings rather than resolved colours for the same reason: a tween would need\n // a mapper, and there is deliberately no tween to have.\n\n /**\n * Texture width in pixels; the height is half of it.\n *\n * Structural, and capped in the schema rather than here. The buffer is\n * re-uploaded to the GPU every frame even when its pixels are cached, so this\n * is a per-frame bandwidth number as much as a sharpness one — 2048 is 8 MB a\n * frame and already finer than the 110m boundaries it draws.\n */\n @property({ default: 2048, tween: snapValue }) declare resolution: number\n\n @property({ default: \"#0b1a2b\", tween: snapValue }) declare ocean: Color\n @property({ default: \"#2c3e50\", tween: snapValue }) declare land: Color\n /**\n * The colour of every coast and border.\n *\n * Alpha works. It did not while the country paths carried the stroke — a\n * border belongs to two countries, so it was drawn twice and came out at\n * double density beside the coastlines. `borders.ts` deduplicates the lines\n * and `world-map.ts` strokes them once.\n */\n @property({ default: \"#4a6076\", tween: snapValue }) declare border: Color\n @property({ default: 1.5, tween: snapValue }) declare borderWidth: number\n\n @property({ default: true, tween: snapFlag }) declare graticule: boolean\n @property({ default: \"#ffffff20\", tween: snapValue })\n declare graticuleColor: Color\n @property({ default: 15, tween: snapValue }) declare graticuleStep: number\n @property({ default: 1, tween: snapValue }) declare graticuleWidth: number\n\n // ── The camera ──\n\n /**\n * The camera's heading, in degrees, in exactly the sense every other 3D node\n * uses it — which is what keeps the canvas drag consistent with them.\n *\n * It is *not* a longitude: a camera at heading θ is centred over longitude −θ\n * (see `orbitForLongitude` in `projection.ts`). Unbounded, like every other\n * viewport's, because the drag wraps it into `[0, 360)` and a range here would\n * clamp half of them.\n */\n @property({ default: -10 }) declare orbit: number\n /**\n * Degrees above the equator, which on a globe *is* the latitude the camera\n * stands over — no conversion, unlike the heading. Held short of the poles by\n * the schema, as every orbit camera's is.\n */\n @property({ default: 25 }) declare elevation: number\n /** Distance from the centre, as a multiple of the globe's radius. */\n @property({ default: 2.8 }) declare zoom: number\n @property({ default: 45 }) declare fov: number\n\n // ── Lighting ──\n\n @property({ default: 0.55 }) declare ambientIntensity: number\n @property({ default: \"#ffffff\", mapper: resolveColor3D, tween: lerpColor3D })\n declare ambientColor: Color\n @property({ default: 1.9 }) declare keyIntensity: number\n @property({ default: \"#ffffff\", mapper: resolveColor3D, tween: lerpColor3D })\n declare keyColor: Color\n\n // ── The atmosphere ──\n //\n // Shading rather than pixels, so unlike the map group these all animate.\n\n @property({ default: true, tween: snapFlag }) declare atmosphere: boolean\n @property({ default: \"#4da3ff\", mapper: resolveColor3D, tween: lerpColor3D })\n declare atmosphereColor: Color\n /** The shell's radius as a multiple of the globe's. */\n @property({ default: 1.06 }) declare atmosphereSize: number\n /** How tightly the glow hugs the edge. Higher is a thinner rim. */\n @property({ default: 3 }) declare atmosphereFalloff: number\n @property({ default: 1.1 }) declare atmosphereIntensity: number\n\n // ── What is placed on it ──\n //\n // Serialized lists, as the graphs store their equations: an appearance value\n // is a number, a string or a boolean, so a list arrives as a string a mapper\n // reads. Parsing is in `places.ts` and is deliberately tolerant — the field is\n // invalid far more often than valid while somebody is typing into it.\n\n @property({ default: \"[]\", tween: snapValue }) declare markers: string\n @property({ default: \"[]\", tween: snapValue }) declare arcs: string\n\n constructor(props?: NodeConfig<Globe, GlobeProps>) {\n super(props as NodeConfig<Canvas3D<GlobeProps>, GlobeProps>)\n }\n\n /**\n * Frame the scene at a spherical camera placement. Every axis is optional,\n * so \"pull back\" and \"spin round\" stay separate intentions — see\n * {@link OrbitTarget}.\n */\n @command({\n args: [\n {\n key: \"target\", kind: \"orbit\", properties: {\n orbit: { kind: \"number\", step: 1, unit: \"°\" },\n elevation: { kind: \"number\", min: -89, max: 89, step: 1, unit: \"°\" },\n zoom: { kind: \"number\", min: 0.2, max: 24, step: 0.1, unit: \"×\" },\n },\n },\n ],\n })\n orbitTo(args: CommandArgs<{ target: OrbitTarget }> & { duration: number }): Command<GlobeProps> {\n return this.to({\n data: orbitProps(args.data?.target ?? {}) as Partial<GlobeProps>,\n duration: args.duration,\n easing: args.easing ?? easeInOut(),\n })\n }\n\n /**\n * Fly the camera to a place on the globe. `latitude`/`longitude` are the\n * point to bring under the camera and `framing` the distance to settle at.\n *\n * **`arc` is what makes it a flight rather than a pan** — above `0` the\n * distance bows outward at the midpoint by that fraction, so a long journey\n * rises off the surface and settles back down rather than scraping the\n * horizon the whole way. Longitude takes the **short way round**.\n */\n @command({ args: [{ key: \"latitude\", kind: \"number\", default: 0, min: -89, max: 89, step: 1, unit: \"°\" }, { key: \"longitude\", kind: \"number\", default: 0, min: -180, max: 180, step: 1, unit: \"°\" }, { key: \"framing\", kind: \"number\", default: 2.2, min: 1.05, max: 12, step: 0.1, unit: \"×\" }, { key: \"arc\", kind: \"number\", default: 0.35, min: 0, max: 3, step: 0.05, scale: 100, unit: \"%\" }] })\n flyTo(args: CommandArgs<{ latitude: number; longitude: number; framing?: number; arc?: number }> & { duration?: number }): Command<GlobeProps> {\n const data = args.data ?? { latitude: 0, longitude: 0 }\n const { latitude, longitude, framing = 2.2, arc = 0.35 } = data\n const duration = args.duration ?? 1.6\n const easing = args.easing ?? easeInOut()\n\n const fromOrbit = this.orbit\n const fromElevation = this.elevation\n const fromZoom = this.zoom\n\n // The globe spins under a fixed camera, so a longitude is reached by\n // orbiting to its negation — and the shortest signed turn is what keeps a\n // crossing of the antimeridian from unwinding the long way.\n const toOrbit = fromOrbit + shortestTurn(fromOrbit, -longitude)\n\n return driveCommand(duration, (t) => {\n const eased = easing(t)\n this.set({\n orbit: fromOrbit + (toOrbit - fromOrbit) * eased,\n elevation: fromElevation + (latitude - fromElevation) * eased,\n // The bow: a half-sine that is 0 at both ends, so the command still\n // lands exactly on `framing` however large `arc` is.\n zoom: (fromZoom + (framing - fromZoom) * eased) * (1 + arc * Math.sin(Math.PI * eased)),\n } as Partial<GlobeProps>)\n }) as Command<GlobeProps>\n }\n\n protected override buildScene3D(): Scene3D {\n const scene = new Scene3D()\n const distance = RADIUS * Math.max(1.02, this.zoom)\n\n // Cut to the globe rather than left to the camera's own derivation from the\n // scene bounds: the atmosphere shell is the outermost thing in the scene and\n // sizing the planes to it would spend depth precision on the halo. The near\n // plane has to survive `zoom` coming right down to the surface, which is\n // what the fraction of the gap is for.\n scene.perspective({\n fov: this.fov,\n near: Math.max(0.01, (distance - RADIUS) * 0.5),\n far: distance + RADIUS * 4,\n // Exactly what every other 3D viewport does — the quarter turn between\n // the stored zero and the camera's, and nothing else. Anything extra here\n // is a globe whose canvas drag runs backwards against every other node's;\n // the longitude convention is handled in the *geometry* instead. See\n // `projection.ts`.\n orbit: cameraOrbit(this.orbit),\n elevation: this.elevation,\n distance,\n })\n\n // The same rig the Protein and the 3D viewport use, at the globe's scale: a\n // key, a rim that is the key from the other side at a fraction of its\n // strength, and enough ambient that the night side is a planet rather than a\n // silhouette. The backdrop is the node's own Fills stack — a `Canvas3D`\n // composites its 3D pass over its 2D fill layers — so there is no space to\n // paint here and no clear colour to set.\n scene\n .light({\n type: \"ambient\",\n intensity: this.ambientIntensity,\n color: this.ambientColor,\n })\n .light(\n { type: \"directional\", intensity: this.keyIntensity, color: this.keyColor },\n { position: [RADIUS * 3, RADIUS * 2, RADIUS * 3] }\n )\n .light(\n {\n type: \"directional\",\n intensity: this.keyIntensity * 0.35,\n color: this.keyColor,\n },\n { position: [-RADIUS * 3, -RADIUS, -RADIUS * 2] }\n )\n\n const g3 = new Graphics3D()\n this.addPlanet(g3)\n this.addArcs(g3)\n this.addMarkers(g3)\n if (this.atmosphere) this.addAtmosphere(g3)\n\n scene.draw(g3)\n return scene\n }\n\n /** The sphere, wearing the baked map. */\n private addPlanet(g3: Graphics3D): void {\n g3.sphere({\n radius: RADIUS,\n // Longitude segments matter more than latitude: the silhouette is a\n // circle of longitude, and a coarse one shows as a faceted rim against\n // the atmosphere's own smooth curve.\n segments: [128, 64],\n fill: this.bakedMap().texture,\n // Land and sea are matte at this distance; a specular highlight on an\n // ocean this size reads as a lens flare rather than as water.\n roughness: 0.95,\n metalness: 0,\n })\n }\n\n /** The halo. A shell, kept for its back faces — see `atmosphere.ts`. */\n private addAtmosphere(g3: Graphics3D): void {\n g3.sphere({\n radius: RADIUS * Math.max(1.001, this.atmosphereSize),\n segments: [96, 48],\n material: atmosphereMaterial(\n this.atmosphereColor as unknown as NormalizedColor,\n this.atmosphereFalloff,\n this.atmosphereIntensity\n ),\n })\n }\n\n /**\n * The markers, as one instanced draw.\n *\n * Instanced rather than a mesh apiece because a marker list is the one thing\n * here with no ceiling — a network map is thousands of points, and a draw call\n * each would not keep up. One geometry, one material, one upload.\n *\n * Note the material carries **no** `vertexColors`: an instanced mesh that asks\n * for it makes the shader read a per-vertex `color` attribute the geometry does\n * not have, gets zeroes, and every marker renders black. The per-instance\n * colours ride the `colors` option instead, which is a different channel.\n */\n private addMarkers(g3: Graphics3D): void {\n const markers = drawableMarkers(this.markers)\n if (markers.length === 0) return\n\n const placements: Transform3D[] = []\n const colors: Color[] = []\n\n for (const marker of markers) {\n const size = marker.size * RADIUS\n placements.push({\n // Lifted by a fraction of its own size so a marker sits *on* the\n // surface rather than half-buried in it, at any size.\n position: latLonToPosition(marker, RADIUS + size * MARKER_LIFT),\n scale: size,\n })\n colors.push(marker.color)\n }\n\n g3.instances(\n Geo.sphere({ radius: 1, segments: [16, 12] }),\n // Unlit, and deliberately: a marker is a signal rather than an object, and\n // one shaded by the scene's key light would go dark exactly when it\n // rotated to the night side — which is where it most needs to be seen.\n Mat.basic({ transparent: true }),\n placements,\n { colors }\n )\n }\n\n /** The arcs, one swept tube each. */\n private addArcs(g3: Graphics3D): void {\n // Resolved against the marker list, because an end may be *pinned* to one —\n // which is what keeps an arc attached when the place it points at moves.\n for (const arc of drawableArcs(this.arcs, this.markers)) {\n const points = greatCircle(arc.from, arc.to, {\n radius: RADIUS,\n lift: arc.lift,\n segments: arcSegments(arc),\n })\n\n // `progress` trims the point list rather than scaling the geometry, so a\n // half-drawn arc is the first half of the real path — the same shape the\n // finished arc has, stopping early. Two points is the floor a tube can be\n // swept along.\n const drawn = Math.max(2, Math.round(points.length * arc.progress))\n\n g3.mesh(\n Geo.tube({\n points: points.slice(0, drawn),\n radius: arc.width * RADIUS,\n segments: [drawn, 8],\n }),\n // Unlit for the reason the markers are: an arc is an annotation, and one\n // that dimmed over the night side would vanish halfway across.\n Mat.basic({ color: arc.color, transparent: true })\n )\n }\n }\n\n // --- The map cache -------------------------------------------------------\n\n /**\n * The baked map, and the descriptor that lets it be baked once.\n *\n * Two caches are being kept in step here, and the key is what keeps them in\n * step. This one holds the `Graphics2D` and its descriptor so the node hands\n * the renderer the *same object* every frame — surface identity is object\n * identity, so a fresh one each frame would defeat the texture cache and\n * orphan a GPU texture per frame. The renderer's own `staticRasters` holds the\n * rasterized pixels against the `identity` string below.\n *\n * They share one key, which is the point: edit a colour and this cache misses\n * (a new `Graphics2D`) *and* that one misses (a new identity string). Derive\n * the identity from anything else — a counter, the node id — and a colour\n * change would serve the old pixels forever, because nothing invalidates\n * `staticRasters` short of tearing down the render context.\n *\n * The descriptor is written out rather than built with `Tex.surface` because\n * the builder's options do not include `static` or `identity`. Handing a bare\n * `Graphics2D` to `fill` instead takes `resolveFill3D`'s surface path, which\n * sets neither — and that is the per-frame read-back this whole arrangement\n * exists to avoid.\n */\n private cache: { key: string; map: BakedMap } | null = null\n\n private bakedMap(): BakedMap {\n const style = this.mapStyle()\n const key = [\n style.width,\n style.ocean,\n style.land,\n style.border,\n style.borderWidth,\n style.graticule,\n style.graticuleColor,\n style.graticuleStep,\n style.graticuleWidth,\n ].join(\"|\")\n\n const cached = this.cache\n if (cached?.key === key) return cached.map\n\n const map: BakedMap = {\n texture: {\n source: worldMapGraphics(style),\n width: style.width,\n height: mapHeight(style.width),\n static: true,\n identity: `globe:${key}`,\n },\n }\n this.cache = { key, map }\n return map\n }\n\n /** The style props as the one value the baker takes. Also the cache key. */\n private mapStyle(): WorldMapStyle {\n return {\n // Rounded to an even number so the 2:1 height is a whole one; a half-pixel\n // buffer height is rounded by the rasterizer and the map then samples a\n // fraction of a row off, which reads as a seam at the equator.\n width: Math.max(256, Math.round(this.resolution / 2) * 2),\n ocean: hexOf(this.ocean),\n land: hexOf(this.land),\n border: hexOf(this.border),\n borderWidth: this.borderWidth,\n graticule: this.graticule,\n graticuleColor: hexOf(this.graticuleColor),\n graticuleStep: this.graticuleStep,\n graticuleWidth: this.graticuleWidth,\n }\n }\n}\n\n/**\n * The signed turn from `from` to `to` that travels less than half a circle.\n *\n * Degrees. What stops a flight from 170° to −170° unwinding 340° the wrong way\n * when it could cross the antimeridian in 20°.\n */\nfunction shortestTurn(from: number, to: number): number {\n return ((((to - from) % 360) + 540) % 360) - 180\n}\n\n\n/**\n * A colour prop as the string the baker and the cache key both want.\n *\n * These props carry no mapper — they are not animatable, so there is no resolved\n * form to hold — but a caller writing JSX is still free to hand one a tuple, and\n * a tuple in a cache key would stringify to something that compares equal for\n * two different colours.\n */\nfunction hexOf(value: Color): string {\n if (typeof value === \"string\") return value\n const [r, g, b, a = 1] = value as unknown as NormalizedColor\n const byte = (channel: number) =>\n Math.round(Math.min(1, Math.max(0, channel)) * 255)\n return `rgba(${byte(r)}, ${byte(g)}, ${byte(b)}, ${a})`\n}\n"]}
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* segments and stitches them into runs; this file strokes those instead, so
|
|
46
46
|
* every line on the map is drawn exactly once at exactly one weight.
|
|
47
47
|
*/
|
|
48
|
-
import { Graphics2D } from "@motionscript/
|
|
48
|
+
import { Graphics2D } from "@motionscript/sdk";
|
|
49
49
|
/** Everything that changes the baked pixels. Also the node's cache key. */
|
|
50
50
|
export interface WorldMapStyle {
|
|
51
51
|
/** Buffer width in pixels. Height is half of it — the equirectangular ratio. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"world-map.d.ts","sourceRoot":"","sources":["../../src/globe/world-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,EAAE,UAAU,EAAoB,MAAM,
|
|
1
|
+
{"version":3,"file":"world-map.d.ts","sourceRoot":"","sources":["../../src/globe/world-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,EAAE,UAAU,EAAoB,MAAM,mBAAmB,CAAA;AAMhE,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,uCAAuC;IACvC,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,UAAU,CAuCjE"}
|
package/dist/globe/world-map.js
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* segments and stitches them into runs; this file strokes those instead, so
|
|
46
46
|
* every line on the map is drawn exactly once at exactly one weight.
|
|
47
47
|
*/
|
|
48
|
-
import { Graphics2D } from "@motionscript/
|
|
48
|
+
import { Graphics2D } from "@motionscript/sdk";
|
|
49
49
|
import { canvasBounds, latLonToCanvas } from "./projection.js";
|
|
50
50
|
import { borderLines } from "./borders.js";
|
|
51
51
|
import { world } from "./world.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"world-map.js","sourceRoot":"","sources":["../../src/globe/world-map.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The world as a flat drawing — the one `Graphics2D` that becomes the sphere's\n * texture.\n *\n * Baking the map in 2D rather than tessellating it in 3D is the node's central\n * decision, and it is not a shortcut. A WebGL line ignores any width above one\n * pixel (motion-script says so outright on `LineStroke3D`), so borders drawn as\n * 3D lines are hairlines at every zoom and every resolution, and the only way to\n * thicken one is to sweep a tube along it — three hundred tubes, for a picture\n * that is flat. Drawn here, a border is an ordinary stroke: any weight, any\n * colour, joined and antialiased by Skia, and it costs one rasterize rather than\n * one per frame.\n *\n * Everything here is therefore *style*, and the node's cache key is exactly the\n * arguments below. Nothing that moves — a marker, an arc, the camera — is drawn\n * into this, because baking something that moves would put every frame of its\n * animation through a full re-rasterize.\n *\n * ## One path per country, and this is the whole performance story\n *\n * The obvious shape is one `path` op holding all 289 rings — the nonzero fill\n * rule sorts out what is land, and it is one op instead of 177. **Do not do\n * that.** Skia's cost for filling and stroking a single path is badly\n * superlinear in its *contour* count, and the difference is not marginal:\n *\n * one path, 289 contours, 10,649 commands 10,691 ms\n * one path per country (177 of them) 205 ms\n *\n * Measured in the browser, on the same data, at the same resolution — a 52×\n * difference, and the reason adding a Globe to a scene used to lock the app for\n * ten seconds. It is also almost independent of the texture's resolution (256²\n * and 2048² are within 10% of each other), which is the tell that the cost is\n * geometry, not pixels, and that turning the map down would not have saved it.\n *\n * Splitting per country is safe for the fill: each country's rings — exterior\n * and holes together — stay in *its* path, so the nonzero rule still cuts\n * Lesotho out of South Africa, and Lesotho's own path paints it back.\n *\n * ## The lines are a separate pass, and have to be\n *\n * The country paths are filled and **not** stroked. Stroking them would draw\n * every international border twice — once from each side, since both countries\n * carry it — and a doubly-composited antialiased fringe reads as a line half\n * again as wide as the coastlines beside it. `borders.ts` deduplicates the\n * segments and stitches them into runs; this file strokes those instead, so\n * every line on the map is drawn exactly once at exactly one weight.\n */\n\nimport { Graphics2D, type PathCommand } from \"@motionscript/core\"\n\nimport { canvasBounds, latLonToCanvas } from \"./projection\"\nimport { borderLines } from \"./borders\"\nimport { world, type Ring } from \"./world\"\n\n/** Everything that changes the baked pixels. Also the node's cache key. */\nexport interface WorldMapStyle {\n /** Buffer width in pixels. Height is half of it — the equirectangular ratio. */\n width: number\n ocean: string\n land: string\n /**\n * The colour of every coast and border.\n *\n * Alpha is fine here, and that is worth saying because it briefly was not:\n * while the country paths were stroked, a shared border was drawn twice and a\n * translucent one came out at double density. `drawBoundaries` draws each line\n * exactly once, so the colour means what it says.\n */\n border: string\n borderWidth: number\n graticule: boolean\n graticuleColor: string\n /** Degrees between graticule lines. */\n graticuleStep: number\n graticuleWidth: number\n}\n\n/** The buffer's height for a given width — equirectangular is always 2:1. */\nexport function mapHeight(width: number): number {\n return Math.round(width / 2)\n}\n\n/**\n * The map, drawn.\n *\n * **Hoist the result.** motion-script identifies a surface texture by its source\n * object, so a fresh `Graphics2D` every frame misses the raster cache, redraws\n * ten thousand paths, reads the buffer back off the GPU and uploads it again —\n * sixty times a second, to produce the same image. `Globe` holds one against\n * this style; nothing else should call this.\n */\nexport function worldMapGraphics(style: WorldMapStyle): Graphics2D {\n const width = style.width\n const height = mapHeight(width)\n const bounds = canvasBounds(width, height)\n\n const g = new Graphics2D()\n\n // The ocean is the whole buffer rather than a sphere-coloured backdrop,\n // because the texture has to be opaque: the sphere is lit, and a transparent\n // texel would show the inside of the far side through it.\n g.rect({ width, height }).fill(style.ocean)\n\n // Land, filled and **not** stroked — see `drawBoundaries` for where the lines\n // come from and why they cannot come from here.\n //\n // One path per country, not one for the world: see the module note for what\n // that costs.\n for (const country of world().countries) {\n const commands: PathCommand[] = []\n for (const ring of country.rings) appendRing(commands, ring, width, height)\n if (commands.length === 0) continue\n g.path({ data: commands, centerBounds: bounds }).fill(style.land)\n }\n\n if (style.borderWidth > 0) drawBoundaries(g, style, width, height, bounds)\n\n // Last, and over the land. The graticule is reference rather than art\n // direction — the same reading the Canvas 3D viewport's grid takes — so it\n // reads as an overlay on whatever it crosses rather than as terrain.\n if (style.graticule) {\n for (const line of graticuleLines(style.graticuleStep, width, height)) {\n g.line({ points: line }).stroke({\n fill: style.graticuleColor,\n weight: style.graticuleWidth,\n })\n }\n }\n\n return g\n}\n\nfunction appendRing(\n commands: PathCommand[],\n ring: Ring,\n width: number,\n height: number\n): void {\n const count = ring.points.length / 2\n for (let i = 0; i < count; i++) {\n const { x, y } = latLonToCanvas(\n { lon: ring.points[i * 2]!, lat: ring.points[i * 2 + 1]! },\n width,\n height\n )\n commands.push(i === 0 ? { type: \"M\", x, y } : { type: \"L\", x, y })\n }\n // Closed explicitly: the ring's repeated last point was dropped when it was\n // vendored, so without this the stroke leaves a notch at every country's\n // starting vertex and the fill's last edge is implied rather than drawn.\n commands.push({ type: \"Z\" })\n}\n\n/**\n * Every coast and border, stroked once each.\n *\n * **One path per run**, for exactly the reason the countries are one path each:\n * a path's stroke cost is superlinear in its contour count. Gathering the 277\n * runs into two paths of ~200 contours put this straight back on the cliff the\n * countries were moved off — measured at 2.5 s of stall, against none at one\n * run per path.\n *\n * One-per-run rather than a tuned chunk size because there is no number to\n * defend: 277 paths is the same order as the 177 the countries already use, and\n * a constant here would be a knob whose safe range nobody could state.\n */\nfunction drawBoundaries(\n g: Graphics2D,\n style: WorldMapStyle,\n width: number,\n height: number,\n bounds: readonly [number, number, number, number]\n): void {\n const paint = { fill: style.border, weight: style.borderWidth }\n\n for (const line of borderLines()) {\n const count = line.length / 2\n if (count < 2) continue\n const commands: PathCommand[] = []\n for (let i = 0; i < count; i++) {\n const { x, y } = latLonToCanvas(\n { lon: line[i * 2]!, lat: line[i * 2 + 1]! },\n width,\n height\n )\n commands.push(i === 0 ? { type: \"M\", x, y } : { type: \"L\", x, y })\n }\n // Left open: a boundary run is a line, not a ring, and closing it would draw\n // a chord back to wherever the run happened to start.\n g.path({ data: commands, centerBounds: bounds }).stroke(paint)\n }\n}\n\n/**\n * The meridians and parallels, as polylines in drawing space.\n *\n * Straight lines, because an equirectangular projection is exactly the one where\n * meridians and parallels are straight — the grid is curved on the sphere only\n * because the sphere curves. Meridians run pole to pole and parallels edge to\n * edge, and both stop short of the poles by one step so the drawing does not\n * pile every meridian onto the same two points.\n */\nfunction graticuleLines(\n step: number,\n width: number,\n height: number\n): { x: number; y: number }[][] {\n const spacing = Math.max(5, Math.min(90, step))\n const lines: { x: number; y: number }[][] = []\n\n for (let lon = -180; lon <= 180; lon += spacing) {\n const meridian: { x: number; y: number }[] = []\n for (let lat = -90; lat <= 90; lat += 5) {\n meridian.push(latLonToCanvas({ lat, lon }, width, height))\n }\n lines.push(meridian)\n }\n\n for (let lat = -90 + spacing; lat <= 90 - spacing; lat += spacing) {\n lines.push([\n latLonToCanvas({ lat, lon: -180 }, width, height),\n latLonToCanvas({ lat, lon: 180 }, width, height),\n ])\n }\n\n return lines\n}\n"]}
|
|
1
|
+
{"version":3,"file":"world-map.js","sourceRoot":"","sources":["../../src/globe/world-map.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The world as a flat drawing — the one `Graphics2D` that becomes the sphere's\n * texture.\n *\n * Baking the map in 2D rather than tessellating it in 3D is the node's central\n * decision, and it is not a shortcut. A WebGL line ignores any width above one\n * pixel (motion-script says so outright on `LineStroke3D`), so borders drawn as\n * 3D lines are hairlines at every zoom and every resolution, and the only way to\n * thicken one is to sweep a tube along it — three hundred tubes, for a picture\n * that is flat. Drawn here, a border is an ordinary stroke: any weight, any\n * colour, joined and antialiased by Skia, and it costs one rasterize rather than\n * one per frame.\n *\n * Everything here is therefore *style*, and the node's cache key is exactly the\n * arguments below. Nothing that moves — a marker, an arc, the camera — is drawn\n * into this, because baking something that moves would put every frame of its\n * animation through a full re-rasterize.\n *\n * ## One path per country, and this is the whole performance story\n *\n * The obvious shape is one `path` op holding all 289 rings — the nonzero fill\n * rule sorts out what is land, and it is one op instead of 177. **Do not do\n * that.** Skia's cost for filling and stroking a single path is badly\n * superlinear in its *contour* count, and the difference is not marginal:\n *\n * one path, 289 contours, 10,649 commands 10,691 ms\n * one path per country (177 of them) 205 ms\n *\n * Measured in the browser, on the same data, at the same resolution — a 52×\n * difference, and the reason adding a Globe to a scene used to lock the app for\n * ten seconds. It is also almost independent of the texture's resolution (256²\n * and 2048² are within 10% of each other), which is the tell that the cost is\n * geometry, not pixels, and that turning the map down would not have saved it.\n *\n * Splitting per country is safe for the fill: each country's rings — exterior\n * and holes together — stay in *its* path, so the nonzero rule still cuts\n * Lesotho out of South Africa, and Lesotho's own path paints it back.\n *\n * ## The lines are a separate pass, and have to be\n *\n * The country paths are filled and **not** stroked. Stroking them would draw\n * every international border twice — once from each side, since both countries\n * carry it — and a doubly-composited antialiased fringe reads as a line half\n * again as wide as the coastlines beside it. `borders.ts` deduplicates the\n * segments and stitches them into runs; this file strokes those instead, so\n * every line on the map is drawn exactly once at exactly one weight.\n */\n\nimport { Graphics2D, type PathCommand } from \"@motionscript/sdk\"\n\nimport { canvasBounds, latLonToCanvas } from \"./projection\"\nimport { borderLines } from \"./borders\"\nimport { world, type Ring } from \"./world\"\n\n/** Everything that changes the baked pixels. Also the node's cache key. */\nexport interface WorldMapStyle {\n /** Buffer width in pixels. Height is half of it — the equirectangular ratio. */\n width: number\n ocean: string\n land: string\n /**\n * The colour of every coast and border.\n *\n * Alpha is fine here, and that is worth saying because it briefly was not:\n * while the country paths were stroked, a shared border was drawn twice and a\n * translucent one came out at double density. `drawBoundaries` draws each line\n * exactly once, so the colour means what it says.\n */\n border: string\n borderWidth: number\n graticule: boolean\n graticuleColor: string\n /** Degrees between graticule lines. */\n graticuleStep: number\n graticuleWidth: number\n}\n\n/** The buffer's height for a given width — equirectangular is always 2:1. */\nexport function mapHeight(width: number): number {\n return Math.round(width / 2)\n}\n\n/**\n * The map, drawn.\n *\n * **Hoist the result.** motion-script identifies a surface texture by its source\n * object, so a fresh `Graphics2D` every frame misses the raster cache, redraws\n * ten thousand paths, reads the buffer back off the GPU and uploads it again —\n * sixty times a second, to produce the same image. `Globe` holds one against\n * this style; nothing else should call this.\n */\nexport function worldMapGraphics(style: WorldMapStyle): Graphics2D {\n const width = style.width\n const height = mapHeight(width)\n const bounds = canvasBounds(width, height)\n\n const g = new Graphics2D()\n\n // The ocean is the whole buffer rather than a sphere-coloured backdrop,\n // because the texture has to be opaque: the sphere is lit, and a transparent\n // texel would show the inside of the far side through it.\n g.rect({ width, height }).fill(style.ocean)\n\n // Land, filled and **not** stroked — see `drawBoundaries` for where the lines\n // come from and why they cannot come from here.\n //\n // One path per country, not one for the world: see the module note for what\n // that costs.\n for (const country of world().countries) {\n const commands: PathCommand[] = []\n for (const ring of country.rings) appendRing(commands, ring, width, height)\n if (commands.length === 0) continue\n g.path({ data: commands, centerBounds: bounds }).fill(style.land)\n }\n\n if (style.borderWidth > 0) drawBoundaries(g, style, width, height, bounds)\n\n // Last, and over the land. The graticule is reference rather than art\n // direction — the same reading the Canvas 3D viewport's grid takes — so it\n // reads as an overlay on whatever it crosses rather than as terrain.\n if (style.graticule) {\n for (const line of graticuleLines(style.graticuleStep, width, height)) {\n g.line({ points: line }).stroke({\n fill: style.graticuleColor,\n weight: style.graticuleWidth,\n })\n }\n }\n\n return g\n}\n\nfunction appendRing(\n commands: PathCommand[],\n ring: Ring,\n width: number,\n height: number\n): void {\n const count = ring.points.length / 2\n for (let i = 0; i < count; i++) {\n const { x, y } = latLonToCanvas(\n { lon: ring.points[i * 2]!, lat: ring.points[i * 2 + 1]! },\n width,\n height\n )\n commands.push(i === 0 ? { type: \"M\", x, y } : { type: \"L\", x, y })\n }\n // Closed explicitly: the ring's repeated last point was dropped when it was\n // vendored, so without this the stroke leaves a notch at every country's\n // starting vertex and the fill's last edge is implied rather than drawn.\n commands.push({ type: \"Z\" })\n}\n\n/**\n * Every coast and border, stroked once each.\n *\n * **One path per run**, for exactly the reason the countries are one path each:\n * a path's stroke cost is superlinear in its contour count. Gathering the 277\n * runs into two paths of ~200 contours put this straight back on the cliff the\n * countries were moved off — measured at 2.5 s of stall, against none at one\n * run per path.\n *\n * One-per-run rather than a tuned chunk size because there is no number to\n * defend: 277 paths is the same order as the 177 the countries already use, and\n * a constant here would be a knob whose safe range nobody could state.\n */\nfunction drawBoundaries(\n g: Graphics2D,\n style: WorldMapStyle,\n width: number,\n height: number,\n bounds: readonly [number, number, number, number]\n): void {\n const paint = { fill: style.border, weight: style.borderWidth }\n\n for (const line of borderLines()) {\n const count = line.length / 2\n if (count < 2) continue\n const commands: PathCommand[] = []\n for (let i = 0; i < count; i++) {\n const { x, y } = latLonToCanvas(\n { lon: line[i * 2]!, lat: line[i * 2 + 1]! },\n width,\n height\n )\n commands.push(i === 0 ? { type: \"M\", x, y } : { type: \"L\", x, y })\n }\n // Left open: a boundary run is a line, not a ring, and closing it would draw\n // a chord back to wherever the run happened to start.\n g.path({ data: commands, centerBounds: bounds }).stroke(paint)\n }\n}\n\n/**\n * The meridians and parallels, as polylines in drawing space.\n *\n * Straight lines, because an equirectangular projection is exactly the one where\n * meridians and parallels are straight — the grid is curved on the sphere only\n * because the sphere curves. Meridians run pole to pole and parallels edge to\n * edge, and both stop short of the poles by one step so the drawing does not\n * pile every meridian onto the same two points.\n */\nfunction graticuleLines(\n step: number,\n width: number,\n height: number\n): { x: number; y: number }[][] {\n const spacing = Math.max(5, Math.min(90, step))\n const lines: { x: number; y: number }[][] = []\n\n for (let lon = -180; lon <= 180; lon += spacing) {\n const meridian: { x: number; y: number }[] = []\n for (let lat = -90; lat <= 90; lat += 5) {\n meridian.push(latLonToCanvas({ lat, lon }, width, height))\n }\n lines.push(meridian)\n }\n\n for (let lat = -90 + spacing; lat <= 90 - spacing; lat += spacing) {\n lines.push([\n latLonToCanvas({ lat, lon: -180 }, width, height),\n latLonToCanvas({ lat, lon: 180 }, width, height),\n ])\n }\n\n return lines\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motionscript/geo",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Geography for Motion Script: an animated globe, and country and state outlines by ISO 3166 code.",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
],
|
|
44
44
|
"license": "Apache-2.0",
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@motionscript/
|
|
46
|
+
"@motionscript/sdk": "^0.1.0-alpha.3"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/d3-geo": "^3.1.0",
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"typescript": "^6.0.3",
|
|
56
56
|
"typescript-eslint": "^8.48.0",
|
|
57
57
|
"vitest": "^4.1.5",
|
|
58
|
-
"@motionscript/core": "0.1.0-alpha.
|
|
58
|
+
"@motionscript/core": "0.1.0-alpha.3",
|
|
59
|
+
"@motionscript/sdk": "0.1.0-alpha.3"
|
|
59
60
|
},
|
|
60
61
|
"dependencies": {
|
|
61
62
|
"d3-geo": "^3.1.1"
|
package/src/border/geo-border.ts
CHANGED
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
type ShadowResolved,
|
|
13
13
|
type ShapeProps,
|
|
14
14
|
type StrokeResolved,
|
|
15
|
-
} from "@motionscript/
|
|
16
|
-
import { snapValue } from "@motionscript/
|
|
15
|
+
} from "@motionscript/sdk"
|
|
16
|
+
import { snapValue } from "@motionscript/sdk/component"
|
|
17
17
|
|
|
18
18
|
import { geoGeometry, type GeoBorders, type GeoContour, type GeoGeometry, type GeoProjectionKind } from "./geometry"
|
|
19
19
|
import { geoDataVersion, geoLoader } from "./loader"
|
package/src/border/loader.ts
CHANGED
package/src/globe/atmosphere.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* it, and needs no sorting against them.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { Mat, type Material3D, type NormalizedColor } from "@motionscript/
|
|
21
|
+
import { Mat, type Material3D, type NormalizedColor } from "@motionscript/sdk"
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* The eye-facing rim term, and nothing else.
|
package/src/globe/globe.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type NormalizedColor,
|
|
18
18
|
type SurfaceTexture3D,
|
|
19
19
|
type Transform3D,
|
|
20
|
-
} from "@motionscript/
|
|
20
|
+
} from "@motionscript/sdk"
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
23
|
lerpColor3D,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
cameraOrbit,
|
|
28
28
|
orbitProps,
|
|
29
29
|
type OrbitTarget,
|
|
30
|
-
} from "@motionscript/
|
|
30
|
+
} from "@motionscript/sdk/component"
|
|
31
31
|
import { atmosphereMaterial } from "./atmosphere"
|
|
32
32
|
import { arcSegments, drawableArcs, drawableMarkers } from "./places"
|
|
33
33
|
import { greatCircle, latLonToPosition } from "./projection"
|
package/src/globe/world-map.ts
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
* every line on the map is drawn exactly once at exactly one weight.
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
|
-
import { Graphics2D, type PathCommand } from "@motionscript/
|
|
49
|
+
import { Graphics2D, type PathCommand } from "@motionscript/sdk"
|
|
50
50
|
|
|
51
51
|
import { canvasBounds, latLonToCanvas } from "./projection"
|
|
52
52
|
import { borderLines } from "./borders"
|