spacemap 0.1.4 → 0.1.5

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/index.d.ts CHANGED
@@ -1587,6 +1587,9 @@ export declare interface PolylineOptions {
1587
1587
  widthPx?: number;
1588
1588
  /** 0 to 1. */
1589
1589
  opacity?: number;
1590
+ /** A dash and a gap in screen pixels, written as the flat map takes them:
1591
+ * `"4 3"`. Solid when left out. */
1592
+ dash?: string;
1590
1593
  /** Fade the line toward its last point, the way an orbit trail fades
1591
1594
  * behind a body. */
1592
1595
  fade?: boolean;
@@ -2035,6 +2038,17 @@ export declare class SpaceMap {
2035
2038
  * is nothing to choose once a host has said what it looks like.
2036
2039
  */
2037
2040
  setBodyAppearance(id: string, appearance: BodyAppearance): this;
2041
+ /**
2042
+ * Keep these bodies in sight from however far: named, with their orbits,
2043
+ * where the map would let a small or distant one drop out and would give
2044
+ * its name up to a bigger neighbour's. A hidden layer does not take them
2045
+ * either. An id the map has not loaded yet is pinned once it has. Each call
2046
+ * replaces the last; an empty list gives the map its own judgement back.
2047
+ *
2048
+ * A moon is only drawn inside the system the camera is in, pinned or not:
2049
+ * pin its planet to mark it from outside.
2050
+ */
2051
+ setPinnedBodies(ids: readonly string[]): this;
2038
2052
  /** A cap's radius as an angle, from whichever of the two ways the host gave
2039
2053
  * it. A radius in kilometres needs the body's own radius, which an
2040
2054
  * unloaded body has not published yet. */
@@ -2114,6 +2128,9 @@ export declare interface SurfacePolygonOptions extends SurfaceShapeOptions {
2114
2128
  export declare interface SurfacePolylineOptions extends SurfaceShapeOptions {
2115
2129
  /** Join the last place back to the first. */
2116
2130
  closed?: boolean;
2131
+ /** A dash and a gap in screen pixels, written as the flat map takes them:
2132
+ * `"4 3"`. Solid when left out. */
2133
+ dash?: string;
2117
2134
  }
2118
2135
 
2119
2136
  /** A shape on a body's surface, for as long as the host keeps it. */
package/index.js CHANGED
@@ -6789,6 +6789,7 @@ var VisibilityController = class {
6789
6789
  scaledSystem = SYSTEM_DISTANCE_RATIO_THRESHOLDS;
6790
6790
  fullMoonIds = /* @__PURE__ */ new Set();
6791
6791
  moonVisibilityCache = /* @__PURE__ */ new Map();
6792
+ pinned = /* @__PURE__ */ new Set();
6792
6793
  constructor(bodies, getProbeStore = () => null, getEarthSatGroupFilter = () => null, getSmallBodyFilter = () => null, getLayers = () => ALL_LAYERS) {
6793
6794
  this.bodies = bodies;
6794
6795
  this.getProbeStore = getProbeStore;
@@ -6885,8 +6886,19 @@ var VisibilityController = class {
6885
6886
  this.moonVisibilityCache.set(moon.data.id, vis);
6886
6887
  return vis;
6887
6888
  }
6889
+ setPinned(ids) {
6890
+ this.pinned = new Set(ids);
6891
+ }
6892
+ isPinned(id) {
6893
+ return this.pinned.has(id);
6894
+ }
6888
6895
  getPlanetVisibility(body, camDistThreeJS) {
6889
- if (this.getLayers().hidesBody(body.data.objectType, body.data.parentId)) return VISIBILITY.HIDE;
6896
+ const pinned = this.pinned.has(body.data.id);
6897
+ const vis = this.ratioVisibility(body, camDistThreeJS, pinned);
6898
+ return pinned && vis !== VISIBILITY.CLOSE ? VISIBILITY.FULL : vis;
6899
+ }
6900
+ ratioVisibility(body, camDistThreeJS, pinned) {
6901
+ if (!pinned && this.getLayers().hidesBody(body.data.objectType, body.data.parentId)) return VISIBILITY.HIDE;
6890
6902
  if (body.data.orbitalSource === OrbitalSource.SPICE_PROBE) return this.getProbeVisibility(body);
6891
6903
  const earthFilter = this.getEarthSatGroupFilter();
6892
6904
  if (earthFilter && body.data.parentId === "naif-399" && !earthFilter.has(body.data.id)) return VISIBILITY.HIDE;
@@ -6939,7 +6951,7 @@ var VisibilityController = class {
6939
6951
  return sysNaif !== null && this.isInFocusedSystem(`naif-${sysNaif}`);
6940
6952
  }
6941
6953
  hasFullRendering(body) {
6942
- if (!this.activeSystemId) return true;
6954
+ if (!this.activeSystemId || this.pinned.has(body.data.id)) return true;
6943
6955
  if (this.isInActiveSystem(isTopLevelParent(body.data.parentId) ? body.data.id : body.data.parentId)) return true;
6944
6956
  if (body.data.orbitalSource !== OrbitalSource.SPICE_PROBE) return false;
6945
6957
  const ps = this.getProbeStore();
@@ -12286,8 +12298,9 @@ function makeTrailMaterial(color, brightness = TRAIL_DIM) {
12286
12298
  `
12287
12299
  });
12288
12300
  }
12289
- function makeFatTrailMaterial(color, lineWidth, brightness = TRAIL_DIM) {
12301
+ function makeFatTrailMaterial(color, lineWidth, brightness = TRAIL_DIM, dash) {
12290
12302
  return new ShaderMaterial({
12303
+ defines: dash ? { DASHED: "" } : {},
12291
12304
  transparent: true,
12292
12305
  depthWrite: false,
12293
12306
  side: DoubleSide,
@@ -12298,7 +12311,8 @@ function makeFatTrailMaterial(color, lineWidth, brightness = TRAIL_DIM) {
12298
12311
  uAlphaMin: { value: 0 },
12299
12312
  uShowFull: { value: 0 },
12300
12313
  uLineWidth: { value: lineWidth },
12301
- uResolution: { value: TRAIL_RESOLUTION }
12314
+ uResolution: { value: TRAIL_RESOLUTION },
12315
+ uDash: { value: new Vector2(dash?.[0] ?? 0, dash?.[1] ?? 0) }
12302
12316
  },
12303
12317
  vertexShader: `
12304
12318
  #include <common>
@@ -12312,6 +12326,11 @@ function makeFatTrailMaterial(color, lineWidth, brightness = TRAIL_DIM) {
12312
12326
  attribute float trailAlpha;
12313
12327
  attribute float fullAlpha;
12314
12328
  varying float vAlpha;
12329
+ #ifdef DASHED
12330
+ attribute float lineDistance;
12331
+ varying float vDistW;
12332
+ varying float vW;
12333
+ #endif
12315
12334
  void main() {
12316
12335
  vAlpha = mix(trailAlpha, fullAlpha, uShowFull);
12317
12336
  vec3 currRel = position + uCenterOffset;
@@ -12328,6 +12347,13 @@ function makeFatTrailMaterial(color, lineWidth, brightness = TRAIL_DIM) {
12328
12347
  vec2 perp = vec2(-dirN.y, dirN.x) * side * uLineWidth * 0.5;
12329
12348
  vec2 offsetNDC = perp / (uResolution * 0.5);
12330
12349
  gl_Position = vec4((currNDC + offsetNDC) * currClip.w, currClip.zw);
12350
+ #ifdef DASHED
12351
+ // Carried times w and divided back out per fragment, which undoes
12352
+ // the perspective a varying is interpolated with: the dashes are
12353
+ // even on screen however much nearer one end is.
12354
+ vDistW = lineDistance * currClip.w;
12355
+ vW = currClip.w;
12356
+ #endif
12331
12357
  #include <logdepthbuf_vertex>
12332
12358
  }
12333
12359
  `,
@@ -12337,7 +12363,15 @@ function makeFatTrailMaterial(color, lineWidth, brightness = TRAIL_DIM) {
12337
12363
  uniform float uAlphaMultiplier;
12338
12364
  uniform float uAlphaMin;
12339
12365
  varying float vAlpha;
12366
+ #ifdef DASHED
12367
+ uniform vec2 uDash;
12368
+ varying float vDistW;
12369
+ varying float vW;
12370
+ #endif
12340
12371
  void main() {
12372
+ #ifdef DASHED
12373
+ if (mod(vDistW / vW, uDash.x + uDash.y) > uDash.x) discard;
12374
+ #endif
12341
12375
  gl_FragColor = vec4(uColor, clamp(max(vAlpha * uAlphaMultiplier, uAlphaMin), 0.0, 1.0));
12342
12376
  #include <logdepthbuf_fragment>
12343
12377
  }
@@ -14142,7 +14176,7 @@ function cullOverlappingLabels(bodyObjects, screenWidth, screenHeight, camera, f
14142
14176
  c.isCapped = body.data.objectType === ObjectType.MOON ? ctx.visibility.getMoonVisibility(body) === VISIBILITY.CAPPED : false;
14143
14177
  c.isMinor = bo.isMinor;
14144
14178
  c.isFocused = isFocused;
14145
- c.isSelected = isFocused || isHovered;
14179
+ c.isSelected = isFocused || isHovered || ctx.visibility.isPinned(body.data.id);
14146
14180
  c.dist = bo.cachedDist;
14147
14181
  }
14148
14182
  _candidates.length = _candidatesActive;
@@ -17768,10 +17802,18 @@ function buildThinLineFromArrays(posArr, trailAlphas, fullAlphas, total, color)
17768
17802
  line.renderOrder = TRAIL_RENDER_ORDER;
17769
17803
  return line;
17770
17804
  }
17771
- function buildFatLineFromThin(capacity, posArr, trailAlphas, fullAlphas, total, color, lineWidth, brightness) {
17805
+ function writeLineDistances(geometry, src, n) {
17806
+ const attr = geometry.getAttribute("lineDistance");
17807
+ const arr = attr.array;
17808
+ const m = Math.min(n, arr.length / 2);
17809
+ for (let i = 0; i < m; i++) arr[2 * i] = arr[2 * i + 1] = src[i];
17810
+ attr.needsUpdate = true;
17811
+ }
17812
+ function buildFatLineFromThin(capacity, posArr, trailAlphas, fullAlphas, total, color, lineWidth, brightness, dash) {
17772
17813
  const geometry = makeFatTrailGeometry(capacity);
17814
+ if (dash) geometry.setAttribute("lineDistance", new Float32BufferAttribute(new Float32Array(2 * capacity), 1));
17773
17815
  writeFatTrailVertices(geometry, posArr, trailAlphas, fullAlphas, total);
17774
- const mesh = new Mesh(geometry, makeFatTrailMaterial(color, lineWidth, brightness));
17816
+ const mesh = new Mesh(geometry, makeFatTrailMaterial(color, lineWidth, brightness, dash));
17775
17817
  mesh.matrixAutoUpdate = false;
17776
17818
  mesh.renderOrder = TRAIL_RENDER_ORDER;
17777
17819
  return mesh;
@@ -20183,7 +20225,7 @@ function updateBodyVisibility(bodyObjects, camera, ctx, focusTruePos, focusedBod
20183
20225
  isClose = !subPixel;
20184
20226
  showLabel = !isClose;
20185
20227
  }
20186
- if (bo.isMinor && !isFocused && trail) trail.visible = false;
20228
+ if (bo.isMinor && !isFocused && trail && !ctx.visibility.isPinned(body.data.id)) trail.visible = false;
20187
20229
  if (suppressHeliocentricTrails && trail?.visible) {
20188
20230
  const oc = trail.userData.orbitCenter;
20189
20231
  if (oc && oc.distanceToSquared(_focusV3.set(fp[0], fp[1], fp[2])) > HELIO_CENTER_DIST_SQ) trail.visible = false;
@@ -22732,6 +22774,8 @@ var PromotionRegistry = class {
22732
22774
  defaults = /* @__PURE__ */ new Set();
22733
22775
  userPromoted = /* @__PURE__ */ new Set();
22734
22776
  autoPromoted = /* @__PURE__ */ new Set();
22777
+ pinned = /* @__PURE__ */ new Set();
22778
+ pinPromoted = /* @__PURE__ */ new Set();
22735
22779
  groupTargets = null;
22736
22780
  groupMode = "none";
22737
22781
  smallBodyZone = null;
@@ -22741,6 +22785,7 @@ var PromotionRegistry = class {
22741
22785
  this.deps = deps;
22742
22786
  deps.ctx.bodies.onBodiesAdded(() => {
22743
22787
  this.onBodiesAdded();
22788
+ this.buildPinned();
22744
22789
  this.autoPromoteAsteroidMoons();
22745
22790
  this.reevaluateEarthSatMode();
22746
22791
  if (this.smallBodyZone !== null) this.refreshSmallBodyEmphasis();
@@ -22930,6 +22975,31 @@ var PromotionRegistry = class {
22930
22975
  if (toRemove.length === 0) return;
22931
22976
  this.tearDownBodies(toRemove, this.autoPromoted);
22932
22977
  }
22978
+ setPinned(ids) {
22979
+ this.pinned = new Set(ids);
22980
+ const focusedId = this.deps.getFocusedId();
22981
+ const dropped = [...this.pinPromoted].filter((id) => !this.pinned.has(id));
22982
+ if (focusedId && dropped.includes(focusedId)) {
22983
+ this.pinPromoted.delete(focusedId);
22984
+ this.userPromoted.add(focusedId);
22985
+ this.emitUserPromotedCount();
22986
+ }
22987
+ this.tearDownBodies(dropped.filter((id) => id !== focusedId), this.pinPromoted);
22988
+ this.buildPinned();
22989
+ }
22990
+ buildPinned() {
22991
+ let built = false;
22992
+ for (const id of this.pinned) {
22993
+ const body = this.deps.ctx.getBody(id);
22994
+ if (!body || isSurfaceFeature(body) || body.data.objectType === ObjectType.MOON) continue;
22995
+ if (!this.buildBodyInstance(body)) continue;
22996
+ built = true;
22997
+ if (this.userPromoted.delete(id)) this.pinPromoted.add(id);
22998
+ }
22999
+ if (!built) return;
23000
+ this.emitUserPromotedCount();
23001
+ this.finalizeBuilds();
23002
+ }
22933
23003
  clearUserPromoted() {
22934
23004
  const focusedId = this.deps.getFocusedId();
22935
23005
  const toRemove = [...this.userPromoted].filter((id) => id !== focusedId);
@@ -24866,6 +24936,10 @@ var SceneRenderer = class SceneRenderer {
24866
24936
  setBodyAppearance(id, appearance) {
24867
24937
  this.appearances.set(id, appearance);
24868
24938
  }
24939
+ setPinnedBodies(ids) {
24940
+ this.ctx.visibility.setPinned(ids);
24941
+ this.focusController.promotion.setPinned(ids);
24942
+ }
24869
24943
  setNorthReference(id) {
24870
24944
  this.cameraUp.setNorthReference(id);
24871
24945
  }
@@ -25484,6 +25558,153 @@ var MarkerExtension = class {
25484
25558
  }
25485
25559
  };
25486
25560
 
25561
+ //#endregion
25562
+ //#region src/lib/scene/extensions/screen.ts
25563
+ var REACH = 3;
25564
+ var right = new Vector3();
25565
+ var up = new Vector3();
25566
+ var forward = new Vector3();
25567
+ function parseDash(pattern) {
25568
+ const [dash, gap = dash] = (pattern ?? "").split(/[\s,]+/).filter(Boolean).map(Number);
25569
+ return dash > 0 && gap > 0 ? [dash, gap] : null;
25570
+ }
25571
+ function aim(camera, viewportPx) {
25572
+ right.set(1, 0, 0).applyQuaternion(camera.quaternion);
25573
+ up.set(0, 1, 0).applyQuaternion(camera.quaternion);
25574
+ forward.set(0, 0, -1).applyQuaternion(camera.quaternion);
25575
+ return viewportPx / (2 * Math.tan(camera.getEffectiveFOV() * Math.PI / 360));
25576
+ }
25577
+ function view(points, i, camera, out) {
25578
+ const x = points[i * 3] - camera.position.x;
25579
+ const y = points[i * 3 + 1] - camera.position.y;
25580
+ const z = points[i * 3 + 2] - camera.position.z;
25581
+ out[0] = x * right.x + y * right.y + z * right.z;
25582
+ out[1] = x * up.x + y * up.y + z * up.z;
25583
+ out[2] = x * forward.x + y * forward.y + z * forward.z;
25584
+ }
25585
+ function reachesBehind(points, count, camera) {
25586
+ forward.set(0, 0, -1).applyQuaternion(camera.quaternion);
25587
+ const { x, y, z } = camera.position;
25588
+ for (let i = 0; i < count; i++) if ((points[i * 3] - x) * forward.x + (points[i * 3 + 1] - y) * forward.y + (points[i * 3 + 2] - z) * forward.z < camera.near * 2) return true;
25589
+ return false;
25590
+ }
25591
+ function screenLengths(points, count, camera, viewportPx, out) {
25592
+ const focal = aim(camera, viewportPx);
25593
+ const at = [
25594
+ 0,
25595
+ 0,
25596
+ 0
25597
+ ];
25598
+ let length = 0;
25599
+ let lastX = 0;
25600
+ let lastY = 0;
25601
+ let seen = false;
25602
+ for (let i = 0; i < count; i++) {
25603
+ view(points, i, camera, at);
25604
+ if (at[2] > camera.near) {
25605
+ const px = at[0] / at[2] * focal;
25606
+ const py = at[1] / at[2] * focal;
25607
+ if (seen) length += Math.hypot(px - lastX, py - lastY);
25608
+ lastX = px;
25609
+ lastY = py;
25610
+ seen = true;
25611
+ }
25612
+ out[i] = length;
25613
+ }
25614
+ }
25615
+ var SeenLine = class {
25616
+ points = new Float32Array(0);
25617
+ alphas = new Float32Array(0);
25618
+ lengths = new Float32Array(0);
25619
+ count = 0;
25620
+ a = [
25621
+ 0,
25622
+ 0,
25623
+ 0
25624
+ ];
25625
+ b = [
25626
+ 0,
25627
+ 0,
25628
+ 0
25629
+ ];
25630
+ see(points, alphas, count, camera, viewportPx) {
25631
+ if (this.alphas.length < count * 4) {
25632
+ this.points = new Float32Array(count * 12);
25633
+ this.alphas = new Float32Array(count * 4);
25634
+ this.lengths = new Float32Array(count * 4);
25635
+ }
25636
+ this.count = 0;
25637
+ const focal = aim(camera, viewportPx);
25638
+ const halfW = viewportPx * camera.aspect * REACH / 2;
25639
+ const halfH = viewportPx * REACH / 2;
25640
+ const near = camera.near * 2;
25641
+ let { a, b } = this;
25642
+ let joined = false;
25643
+ let length = 0;
25644
+ let lastX = 0;
25645
+ let lastY = 0;
25646
+ if (count > 0) view(points, 0, camera, a);
25647
+ for (let i = 0; i + 1 < count; i++, [a, b] = [b, a]) {
25648
+ view(points, i + 1, camera, b);
25649
+ let from = 0;
25650
+ let to = 1;
25651
+ for (let plane = 0; plane < 5 && from <= to; plane++) {
25652
+ const ga = edge(plane, a, focal, halfW, halfH, near);
25653
+ const gb = edge(plane, b, focal, halfW, halfH, near);
25654
+ if (ga < 0 && gb < 0) to = -1;
25655
+ else if (ga < 0) from = Math.max(from, ga / (ga - gb));
25656
+ else if (gb < 0) to = Math.min(to, ga / (ga - gb));
25657
+ }
25658
+ if (from > to) {
25659
+ joined = false;
25660
+ continue;
25661
+ }
25662
+ const depth = a[2] + (b[2] - a[2]) * from;
25663
+ const x = (a[0] + (b[0] - a[0]) * from) / depth * focal;
25664
+ const y = (a[1] + (b[1] - a[1]) * from) / depth * focal;
25665
+ if (!joined || from > 0) {
25666
+ if (this.count > 0) {
25667
+ this.copy(this.count - 1, 0);
25668
+ this.put(points, alphas, i, from, length, 0);
25669
+ }
25670
+ this.put(points, alphas, i, from, length, 1);
25671
+ lastX = x;
25672
+ lastY = y;
25673
+ }
25674
+ const endDepth = a[2] + (b[2] - a[2]) * to;
25675
+ const endX = (a[0] + (b[0] - a[0]) * to) / endDepth * focal;
25676
+ const endY = (a[1] + (b[1] - a[1]) * to) / endDepth * focal;
25677
+ length += Math.hypot(endX - lastX, endY - lastY);
25678
+ lastX = endX;
25679
+ lastY = endY;
25680
+ this.put(points, alphas, i, to, length, 1);
25681
+ joined = to === 1;
25682
+ }
25683
+ }
25684
+ put(points, alphas, i, t, length, shown) {
25685
+ const at = this.count++;
25686
+ for (let k = 0; k < 3; k++) {
25687
+ const from = points[i * 3 + k];
25688
+ this.points[at * 3 + k] = from + (points[i * 3 + 3 + k] - from) * t;
25689
+ }
25690
+ this.alphas[at] = (alphas[i] + (alphas[i + 1] - alphas[i]) * t) * shown;
25691
+ this.lengths[at] = length;
25692
+ }
25693
+ copy(from, shown) {
25694
+ const at = this.count++;
25695
+ this.points.copyWithin(at * 3, from * 3, from * 3 + 3);
25696
+ this.alphas[at] = this.alphas[from] * shown;
25697
+ this.lengths[at] = this.lengths[from];
25698
+ }
25699
+ };
25700
+ function edge(plane, at, focal, halfW, halfH, near) {
25701
+ if (plane === 0) return at[2] - near;
25702
+ if (plane === 1) return halfW * at[2] - focal * at[0];
25703
+ if (plane === 2) return halfW * at[2] + focal * at[0];
25704
+ if (plane === 3) return halfH * at[2] - focal * at[1];
25705
+ return halfH * at[2] + focal * at[1];
25706
+ }
25707
+
25487
25708
  //#endregion
25488
25709
  //#region src/lib/scene/extensions/polyline.ts
25489
25710
  var REDRAW_EPSILON$1 = 1e-9;
@@ -25493,6 +25714,7 @@ var PolylineExtension = class {
25493
25714
  color;
25494
25715
  widthPx;
25495
25716
  opacity;
25717
+ dash;
25496
25718
  fade;
25497
25719
  closed;
25498
25720
  local;
@@ -25500,6 +25722,7 @@ var PolylineExtension = class {
25500
25722
  mesh = null;
25501
25723
  scratch = new Float32Array(0);
25502
25724
  alphas = new Float32Array(0);
25725
+ seen = new SeenLine();
25503
25726
  capacity = 0;
25504
25727
  lastOrigin = [
25505
25728
  0,
@@ -25514,6 +25737,7 @@ var PolylineExtension = class {
25514
25737
  this.color = options.color ?? "#ffffff";
25515
25738
  this.widthPx = options.widthPx ?? 2;
25516
25739
  this.opacity = options.opacity ?? 1;
25740
+ this.dash = parseDash(options.dash);
25517
25741
  this.fade = options.fade ?? false;
25518
25742
  this.closed = options.closed ?? false;
25519
25743
  this.local = new Float64Array(0);
@@ -25534,6 +25758,10 @@ var PolylineExtension = class {
25534
25758
  }
25535
25759
  if (count > points.length) this.local.copyWithin(points.length * 3, 0, 3);
25536
25760
  this.count = count;
25761
+ if (this.alphas.length < count) {
25762
+ this.scratch = new Float32Array(count * 3);
25763
+ this.alphas = new Float32Array(count);
25764
+ }
25537
25765
  if (count > this.capacity) this.rebuild(count);
25538
25766
  this.fillAlphas();
25539
25767
  this.dirty = true;
@@ -25552,38 +25780,35 @@ var PolylineExtension = class {
25552
25780
  this.removeSelf?.();
25553
25781
  this.removeSelf = null;
25554
25782
  }
25555
- update({ jd, basis, camera, ctx }) {
25556
- const mesh = this.mesh;
25557
- if (!mesh) return;
25783
+ update({ jd, basis, camera, viewportPx, ctx }) {
25558
25784
  const world = this.wanted && this.count >= 2 ? resolveAnchor(this.anchor, ctx, jd) : null;
25559
- mesh.visible = world !== null;
25560
- if (!world) return;
25785
+ if (this.mesh) this.mesh.visible = world !== null;
25786
+ if (!this.mesh || !world) return;
25561
25787
  const origin = [
25562
25788
  world[0] - basis[0],
25563
25789
  world[1] - basis[1],
25564
25790
  world[2] - basis[2]
25565
25791
  ];
25566
- if (this.dirty || Math.abs(origin[0] - this.lastOrigin[0]) > REDRAW_EPSILON$1 || Math.abs(origin[1] - this.lastOrigin[1]) > REDRAW_EPSILON$1 || Math.abs(origin[2] - this.lastOrigin[2]) > REDRAW_EPSILON$1) {
25567
- this.writeVertices(mesh, origin);
25792
+ const moved = this.dirty || Math.abs(origin[0] - this.lastOrigin[0]) > REDRAW_EPSILON$1 || Math.abs(origin[1] - this.lastOrigin[1]) > REDRAW_EPSILON$1 || Math.abs(origin[2] - this.lastOrigin[2]) > REDRAW_EPSILON$1;
25793
+ if (moved) {
25794
+ for (let i = 0; i < this.count * 3; i++) this.scratch[i] = this.local[i] + origin[i % 3];
25568
25795
  this.lastOrigin = origin;
25569
- this.dirty = false;
25570
25796
  }
25571
- mesh.material.uniforms.uCenterOffset.value.copy(camera.position).negate();
25572
- }
25573
- writeVertices(mesh, origin) {
25574
- for (let i = 0; i < this.count; i++) {
25575
- this.scratch[i * 3] = this.local[i * 3] + origin[0];
25576
- this.scratch[i * 3 + 1] = this.local[i * 3 + 1] + origin[1];
25577
- this.scratch[i * 3 + 2] = this.local[i * 3 + 2] + origin[2];
25578
- }
25579
- writeFatTrailVertices(mesh.geometry, this.scratch, this.alphas, this.alphas, this.count);
25797
+ const cut = this.dash !== null || reachesBehind(this.scratch, this.count, camera);
25798
+ if (cut) {
25799
+ const { seen } = this;
25800
+ seen.see(this.scratch, this.alphas, this.count, camera, viewportPx);
25801
+ if (seen.count > this.capacity) this.rebuild(seen.count);
25802
+ writeFatTrailVertices(this.mesh.geometry, seen.points, seen.alphas, seen.alphas, seen.count);
25803
+ if (this.dash) writeLineDistances(this.mesh.geometry, seen.lengths, seen.count);
25804
+ } else if (moved) writeFatTrailVertices(this.mesh.geometry, this.scratch, this.alphas, this.alphas, this.count);
25805
+ this.dirty = cut;
25806
+ this.mesh.material.uniforms.uCenterOffset.value.copy(camera.position).negate();
25580
25807
  }
25581
25808
  rebuild(capacity) {
25582
25809
  this.disposeMesh();
25583
25810
  this.dirty = true;
25584
- this.scratch = new Float32Array(capacity * 3);
25585
- this.alphas = new Float32Array(capacity);
25586
- const mesh = buildFatLineFromThin(capacity, this.scratch, this.alphas, this.alphas, capacity, this.color, this.widthPx, 1);
25811
+ const mesh = buildFatLineFromThin(capacity, this.scratch, this.alphas, this.alphas, 0, this.color, this.widthPx, 1, this.dash ?? void 0);
25587
25812
  mesh.frustumCulled = false;
25588
25813
  this.object.add(mesh);
25589
25814
  this.mesh = mesh;
@@ -26772,6 +26997,7 @@ var SurfaceShapeExtension = class {
26772
26997
  color;
26773
26998
  widthPx;
26774
26999
  opacity;
27000
+ dash;
26775
27001
  fillColor;
26776
27002
  fillOpacity;
26777
27003
  dirs = new Float64Array(0);
@@ -26779,6 +27005,7 @@ var SurfaceShapeExtension = class {
26779
27005
  line = null;
26780
27006
  scratch = new Float32Array(0);
26781
27007
  alphas = new Float32Array(0);
27008
+ lengths = new Float32Array(0);
26782
27009
  fanDirs = new Float64Array(0);
26783
27010
  fill = null;
26784
27011
  fillScratch = new Float32Array(0);
@@ -26798,6 +27025,7 @@ var SurfaceShapeExtension = class {
26798
27025
  this.color = options.color ?? "#ffffff";
26799
27026
  this.widthPx = options.widthPx ?? 2;
26800
27027
  this.opacity = options.opacity ?? 1;
27028
+ this.dash = parseDash(options.dash);
26801
27029
  this.fillColor = options.fill ?? null;
26802
27030
  this.fillOpacity = options.fillOpacity ?? .25;
26803
27031
  this.object.matrixAutoUpdate = false;
@@ -26830,7 +27058,7 @@ var SurfaceShapeExtension = class {
26830
27058
  this.removeSelf?.();
26831
27059
  this.removeSelf = null;
26832
27060
  }
26833
- update({ jd, basis, camera, ctx }) {
27061
+ update({ jd, basis, camera, viewportPx, ctx }) {
26834
27062
  const body = this.wanted && this.count >= 2 ? ctx.getBody(this.body) : void 0;
26835
27063
  const centre = body && bodyCentre(body, ctx, jd);
26836
27064
  if (this.line) this.line.visible = !!centre;
@@ -26847,6 +27075,10 @@ var SurfaceShapeExtension = class {
26847
27075
  if (this.line) {
26848
27076
  this.place(this.dirs, this.count, q, scale, origin, this.scratch);
26849
27077
  writeFatTrailVertices(this.line.geometry, this.scratch, this.alphas, this.alphas, this.count);
27078
+ if (this.dash) {
27079
+ screenLengths(this.scratch, this.count, camera, viewportPx, this.lengths);
27080
+ writeLineDistances(this.line.geometry, this.lengths, this.count);
27081
+ }
26850
27082
  this.line.material.uniforms.uCenterOffset.value.copy(camera.position).negate();
26851
27083
  }
26852
27084
  if (this.fill) {
@@ -26875,7 +27107,8 @@ var SurfaceShapeExtension = class {
26875
27107
  if (this.count < 2 || this.widthPx <= 0) return;
26876
27108
  this.scratch = new Float32Array(this.count * 3);
26877
27109
  this.alphas = new Float32Array(this.count).fill(this.opacity);
26878
- const line = buildFatLineFromThin(this.count, this.scratch, this.alphas, this.alphas, this.count, this.color, this.widthPx, 1);
27110
+ this.lengths = new Float32Array(this.count);
27111
+ const line = buildFatLineFromThin(this.count, this.scratch, this.alphas, this.alphas, this.count, this.color, this.widthPx, 1, this.dash ?? void 0);
26879
27112
  line.frustumCulled = false;
26880
27113
  line.renderOrder = SURFACE_RENDER_ORDER;
26881
27114
  this.object.add(line);
@@ -27914,6 +28147,10 @@ var SpaceMap = class {
27914
28147
  this.requireRenderer().setBodyAppearance(id, appearance);
27915
28148
  return this;
27916
28149
  }
28150
+ setPinnedBodies(ids) {
28151
+ this.requireRenderer().setPinnedBodies(ids);
28152
+ return this;
28153
+ }
27917
28154
  capRadiusDeg(options) {
27918
28155
  if (options.radiusDeg !== void 0) return options.radiusDeg;
27919
28156
  const bodyRadiusKm = this.getBody(options.body)?.radiusKm;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacemap",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "The map of the Solar System from spacemap.co, on your website!",
5
5
  "keywords": ["solar system", "spacecraft", "orbits", "map", "three.js"],
6
6
  "homepage": "https://spacemap.co",