bruce-cesium 7.1.3 → 7.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.
@@ -1,6 +1,6 @@
1
1
  import * as Cesium from 'cesium';
2
- import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, Math as Math$1, Cartesian3, JulianDate, Quaternion, Transforms, HeadingPitchRoll, Matrix4, DistanceDisplayCondition, HeightReference, HorizontalOrigin, VerticalOrigin, ClassificationType, ConstantPositionProperty, ColorBlendMode, ShadowMode, Model, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, ArcType, CornerType, Cartesian2, SceneTransforms, NearFarScalar, Matrix3, Rectangle, KmlDataSource, GeoJsonDataSource, SceneMode, HeadingPitchRange, Cesium3DTileStyle, Cesium3DTileColorBlendMode, Ion, IonResource, Cesium3DTileset, BoundingSphere, Intersect, OrthographicFrustum, EasingFunction, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, CustomDataSource, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, PolygonPipeline, CesiumInspector, ClockRange, GeometryInstance, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
- import { Cartes, Entity as Entity$1, ENVIRONMENT, Api, Calculator, ClientFile, EntityTag, EntityType, LRUCache, ObjectUtils, Style, EntityLod, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, Bounds, Color as Color$1, Geometry, BruceEvent, EntityCoords, Carto, DataLab, DelayQueue, EntityHistoricData, AccountConcept, RecordChangeFeed, EntityRelation, BruceApi, ProgramKey, EntitySource, ProjectViewTile, Camera, ProjectViewLegacyTile, Account, AccountLimits, Session, UrlUtils, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, ProjectViewBookmarkGroup, EntityRelationType } from 'bruce-models';
2
+ import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, DistanceDisplayCondition, ShaderSource, Cartesian3, Math as Math$1, Ellipsoid, Transforms, Matrix4, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, Cartesian2, HeightReference, HorizontalOrigin, VerticalOrigin, ClassificationType, ConstantPositionProperty, ArcType, CornerType, ShadowMode, ColorBlendMode, HeadingPitchRoll, Model, JulianDate, Quaternion, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, SceneTransforms, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, SceneMode, Ion, IonResource, Cesium3DTileStyle, HeadingPitchRange, Cesium3DTileColorBlendMode, OrthographicFrustum, EasingFunction, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Cesium3DTileset, Intersect, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, CustomDataSource, PolygonPipeline, CesiumInspector, ClockRange, GeometryInstance, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
+ import { Cartes, Entity as Entity$1, ENVIRONMENT, Api, Calculator, ClientFile, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Carto, Geometry as Geometry$1, EntityLod, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, Bounds, Color as Color$1, BruceEvent, EntityCoords, DataLab, DelayQueue, EntityHistoricData, BruceApi, AccountConcept, RecordChangeFeed, EntityRelation, ProgramKey, EntitySource, Camera, ProjectViewTile, ProjectViewLegacyTile, Account, AccountLimits, Session, UrlUtils, ProjectViewBookmarkGroup, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType } from 'bruce-models';
4
4
 
5
5
  /**
6
6
  * Ensures a number is returned from a given value.
@@ -3563,11 +3563,11 @@ var SimplifyGeometry;
3563
3563
  else if (geometry.Polygon) {
3564
3564
  for (let i = 0; i < geometry.Polygon.length; i++) {
3565
3565
  const polygon = geometry.Polygon[i];
3566
- count += Geometry.ParsePoints(polygon.LinearRing).length;
3566
+ count += Geometry$1.ParsePoints(polygon.LinearRing).length;
3567
3567
  }
3568
3568
  }
3569
3569
  else if (geometry.LineString) {
3570
- count += Geometry.ParsePoints(geometry.LineString).length;
3570
+ count += Geometry$1.ParsePoints(geometry.LineString).length;
3571
3571
  }
3572
3572
  else if (geometry.Point) {
3573
3573
  count += 1;
@@ -3588,7 +3588,7 @@ var SimplifyGeometry;
3588
3588
  return geometry;
3589
3589
  }
3590
3590
  // Convert to geojson so that we can interact with turf.
3591
- const gFeature = Geometry.ToGeoJsonFeature({
3591
+ const gFeature = Geometry$1.ToGeoJsonFeature({
3592
3592
  geometry: geometry
3593
3593
  });
3594
3594
  // Unkink the geometry.
@@ -3607,7 +3607,7 @@ var SimplifyGeometry;
3607
3607
  });
3608
3608
  }
3609
3609
  // Converting back to Bruce geometry.
3610
- geometry = (_a = Geometry.FromGeoJson({
3610
+ geometry = (_a = Geometry$1.FromGeoJson({
3611
3611
  geoJson: gFeature
3612
3612
  })) === null || _a === void 0 ? void 0 : _a.geometry;
3613
3613
  return geometry;
@@ -11288,7 +11288,7 @@ var EntitiesRenderManager;
11288
11288
  };
11289
11289
  for (let i = 0; i < entities.length; i++) {
11290
11290
  const entity = entities[i];
11291
- const feature = Geometry.ToGeoJsonFeature({
11291
+ const feature = Geometry$1.ToGeoJsonFeature({
11292
11292
  geometry: entity.Bruce.VectorGeometry,
11293
11293
  noAltitude: polygonsClamped && lineWidthPx <= 0,
11294
11294
  altitude: lineWidthPx > 0 && polygonsClamped ? 1 : null,
@@ -12324,7 +12324,7 @@ var EntitiesLoadedRenderManager;
12324
12324
  };
12325
12325
  for (let i = 0; i < entities.length; i++) {
12326
12326
  const entity = entities[i];
12327
- const feature = Geometry.ToGeoJsonFeature({
12327
+ const feature = Geometry$1.ToGeoJsonFeature({
12328
12328
  geometry: entity.Bruce.VectorGeometry,
12329
12329
  noAltitude: polygonsClamped && lineWidthPx <= 0,
12330
12330
  altitude: lineWidthPx > 0 && polygonsClamped ? 1 : null,
@@ -17048,7 +17048,7 @@ function createBoundsPolygonGeometry(bounds) {
17048
17048
  return {
17049
17049
  Polygon: [
17050
17050
  {
17051
- Facing: Geometry.EPolygonRingType.Boundaries,
17051
+ Facing: Geometry$1.EPolygonRingType.Boundaries,
17052
17052
  LinearRing: ring
17053
17053
  }
17054
17054
  ]
@@ -35816,7 +35816,7 @@ var EntityUtils;
35816
35816
  // "0.0,0.0" seems to be generated on missing geometry in our re-index.
35817
35817
  // No location -> turns into 0,0 -> turns into 0.0,0.0 point geometry.
35818
35818
  if (pointStr && typeof pointStr == "string" && pointStr != "0.0,0.0") {
35819
- const points = Geometry.ParsePoints(pointStr);
35819
+ const points = Geometry$1.ParsePoints(pointStr);
35820
35820
  const point = points.length > 0 ? points[0] : null;
35821
35821
  if (point && Carto.ValidateCarto(point)) {
35822
35822
  const pos3d = Cartesian3.fromDegrees(EnsureNumber(point.longitude), EnsureNumber(point.latitude), EnsureNumber(point.altitude));
@@ -35825,7 +35825,7 @@ var EntityUtils;
35825
35825
  }
35826
35826
  const lineStr = geometry.LineString;
35827
35827
  if (lineStr && typeof lineStr == "string") {
35828
- const points = Geometry.ParsePoints(lineStr);
35828
+ const points = Geometry$1.ParsePoints(lineStr);
35829
35829
  if (points.length > 0) {
35830
35830
  const pPosses = points.map(x => Cartesian3.fromDegrees(EnsureNumber(x.longitude), EnsureNumber(x.latitude), EnsureNumber(x.altitude)));
35831
35831
  await ensureHeightRefs(pPosses, sample);
@@ -35834,9 +35834,9 @@ var EntityUtils;
35834
35834
  }
35835
35835
  const pRings = geometry.Polygon;
35836
35836
  if (pRings && typeof pRings == "object") {
35837
- const boundary = pRings.find(x => x.Facing == Geometry.EPolygonRingType.Boundaries);
35837
+ const boundary = pRings.find(x => x.Facing == Geometry$1.EPolygonRingType.Boundaries);
35838
35838
  if (boundary === null || boundary === void 0 ? void 0 : boundary.LinearRing) {
35839
- const points = Geometry.ParsePoints(boundary.LinearRing);
35839
+ const points = Geometry$1.ParsePoints(boundary.LinearRing);
35840
35840
  const pPosses = points.map(x => Cartesian3.fromDegrees(EnsureNumber(x.longitude), EnsureNumber(x.latitude), EnsureNumber(x.altitude)));
35841
35841
  await ensureHeightRefs(pPosses, sample);
35842
35842
  posses = posses.concat(pPosses);
@@ -36331,7 +36331,7 @@ var EntityUtils;
36331
36331
  if (geometry && typeof geometry == "object") {
36332
36332
  const pointStr = geometry.Point;
36333
36333
  if (pointStr && typeof pointStr == "string") {
36334
- const points = Geometry.ParsePoints(pointStr);
36334
+ const points = Geometry$1.ParsePoints(pointStr);
36335
36335
  const point = points.length > 0 ? points[0] : null;
36336
36336
  if (point && Carto.ValidateCarto(point)) {
36337
36337
  return Cartesian3.fromDegrees(EnsureNumber(point.longitude), EnsureNumber(point.latitude), EnsureNumber(point.altitude));
@@ -36339,7 +36339,7 @@ var EntityUtils;
36339
36339
  }
36340
36340
  const lineStr = geometry.LineString;
36341
36341
  if (lineStr && typeof lineStr == "string") {
36342
- const points = Geometry.ParsePoints(lineStr);
36342
+ const points = Geometry$1.ParsePoints(lineStr);
36343
36343
  if (points.length > 0) {
36344
36344
  const posses = points.map(x => Cartesian3.fromDegrees(EnsureNumber(x.longitude), EnsureNumber(x.latitude), EnsureNumber(x.altitude)));
36345
36345
  const { totalLength: length } = MeasureUtils.MeasurePolyline({
@@ -36362,9 +36362,9 @@ var EntityUtils;
36362
36362
  }
36363
36363
  const pRings = geometry.Polygon;
36364
36364
  if (pRings && typeof pRings == "object") {
36365
- const boundary = pRings.find(x => x.Facing == Geometry.EPolygonRingType.Boundaries);
36365
+ const boundary = pRings.find(x => x.Facing == Geometry$1.EPolygonRingType.Boundaries);
36366
36366
  if (boundary === null || boundary === void 0 ? void 0 : boundary.LinearRing) {
36367
- const points = Geometry.ParsePoints(boundary.LinearRing);
36367
+ const points = Geometry$1.ParsePoints(boundary.LinearRing);
36368
36368
  const point = Carto.GetCenter(points);
36369
36369
  if (point && Carto.ValidateCarto(point)) {
36370
36370
  return Cartesian3.fromDegrees(EnsureNumber(point.longitude), EnsureNumber(point.latitude), EnsureNumber(point.altitude));
@@ -37098,7 +37098,7 @@ function calculateDistance(point, entity) {
37098
37098
  if (geometry.Polygon) {
37099
37099
  const polygonRings = geometry.Polygon;
37100
37100
  const polygon = typeof polygonRings == "string" ? String(polygonRings) : polygonRings.length ? polygonRings[0].LinearRing : "";
37101
- let points = polygon ? Geometry.ParsePoints(polygon) : [];
37101
+ let points = polygon ? Geometry$1.ParsePoints(polygon) : [];
37102
37102
  if (points.length > 2) {
37103
37103
  const distance = distanceToPolygon(point, points);
37104
37104
  if (!isNaN(distance)) {
@@ -37108,7 +37108,7 @@ function calculateDistance(point, entity) {
37108
37108
  }
37109
37109
  }
37110
37110
  if (!passedGeometry && geometry.LineString) {
37111
- const points = Geometry.ParsePoints(geometry.LineString);
37111
+ const points = Geometry$1.ParsePoints(geometry.LineString);
37112
37112
  if (points.length > 1) {
37113
37113
  const pointsLen = points.length;
37114
37114
  for (let i = 0; i < pointsLen; i++) {
@@ -39252,7 +39252,7 @@ var EntityRenderEnginePolyline;
39252
39252
  entity: entity,
39253
39253
  path: ["Bruce", "VectorGeometry", "LineString"]
39254
39254
  });
39255
- const points = lineString && typeof lineString == "string" ? Geometry.ParsePoints(lineString) : [];
39255
+ const points = lineString && typeof lineString == "string" ? Geometry$1.ParsePoints(lineString) : [];
39256
39256
  if (!points || points.length < 2) {
39257
39257
  return null;
39258
39258
  }
@@ -39734,15 +39734,68 @@ var TextureFrameSeriesAnimator;
39734
39734
  return Boolean(data && data.generation && Array.isArray(data.generation.Frames) && data.generation.Frames.length > 0);
39735
39735
  }
39736
39736
  TextureFrameSeriesAnimator.IsFrameArchiveMetadata = IsFrameArchiveMetadata;
39737
+ // How far covered values bleed outward into uncovered texels before the GPU samples them.
39738
+ const VALUE_DILATE_PASSES = 2;
39739
+ /*
39740
+ * Bleeds covered values outward into uncovered texels, leaving alpha untouched.
39741
+ *
39742
+ * The GPU samples the value texture with linear filtering, so an uncovered texel's RGB still gets
39743
+ * averaged into the vertices next to it even though its alpha is zero. Land sits at one end of the
39744
+ * ramp, so without this the coastline grows a row of spikes exactly where the data stops. Masking the
39745
+ * baseline creates more of these edges, which is what makes this necessary rather than cosmetic.
39746
+ */
39747
+ function dilateValues(value, width, height, passes) {
39748
+ const texels = width * height;
39749
+ const filled = new Uint8Array(texels);
39750
+ for (let p = 0; p < texels; p++) {
39751
+ filled[p] = value[p * 4 + 3] !== 0 ? 1 : 0;
39752
+ }
39753
+ for (let pass = 0; pass < passes; pass++) {
39754
+ const source = value.slice();
39755
+ const wasFilled = filled.slice();
39756
+ for (let y = 0; y < height; y++) {
39757
+ for (let x = 0; x < width; x++) {
39758
+ const p = y * width + x;
39759
+ if (wasFilled[p]) {
39760
+ continue;
39761
+ }
39762
+ let sum = 0;
39763
+ let hits = 0;
39764
+ for (let dy = -1; dy <= 1; dy++) {
39765
+ for (let dx = -1; dx <= 1; dx++) {
39766
+ const nx = x + dx;
39767
+ const ny = y + dy;
39768
+ if (nx < 0 || ny < 0 || nx >= width || ny >= height || !wasFilled[ny * width + nx]) {
39769
+ continue;
39770
+ }
39771
+ sum += source[(ny * width + nx) * 4];
39772
+ hits++;
39773
+ }
39774
+ }
39775
+ if (hits > 0) {
39776
+ const v = Math.round(sum / hits);
39777
+ value[p * 4] = v;
39778
+ value[p * 4 + 1] = v;
39779
+ value[p * 4 + 2] = v;
39780
+ filled[p] = 1;
39781
+ }
39782
+ }
39783
+ }
39784
+ }
39785
+ }
39737
39786
  const DEFAULT_CROSSFADE_MS = 900;
39738
39787
  const DEFAULT_LOW_COLOR = { red: 255, green: 255, blue: 255, alpha: 0.12 };
39739
39788
  const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
39740
39789
  class Animator {
39741
39790
  constructor(options) {
39742
- var _a;
39791
+ var _a, _b;
39743
39792
  this.removeOnTick = null;
39744
39793
  this.removeCrossfadeTick = null;
39745
39794
  this.disposed = false;
39795
+ // One alpha byte per texel, 1 meaning the texel is baseline. Fetched once, the mesh does not move.
39796
+ this.baselineFlags = null;
39797
+ this.baselineDims = null;
39798
+ this.baselineLoad = null;
39746
39799
  this.currentFrameIndex = -1;
39747
39800
  // Single-in-flight fetch queue: only one Range GET outstanding at a time, and the NEXT one
39748
39801
  // is chosen right before sending, this is what makes reprioritizing mid-scrub take effect
@@ -39751,6 +39804,8 @@ var TextureFrameSeriesAnimator;
39751
39804
  this.pendingIndex = -1;
39752
39805
  this.displayedPixels = null;
39753
39806
  this.fadeFromPixels = null;
39807
+ this.displayedValuePixels = null;
39808
+ this.fadeFromValuePixels = null;
39754
39809
  this.fadeToIndex = -1;
39755
39810
  this.fadeToDims = null;
39756
39811
  this.fadeStartMs = null;
@@ -39759,6 +39814,8 @@ var TextureFrameSeriesAnimator;
39759
39814
  // forcing Cesium to re-upload the texture only when something changed.
39760
39815
  this.pool = [document.createElement("canvas"), document.createElement("canvas")];
39761
39816
  this.poolIdx = 0;
39817
+ this.valueDims = null;
39818
+ this.presentVersion = 0;
39762
39819
  if (!options.entity.polygon) {
39763
39820
  throw new Error("TextureFrameSeriesAnimator requires an entity with polygon graphics.");
39764
39821
  }
@@ -39776,9 +39833,17 @@ var TextureFrameSeriesAnimator;
39776
39833
  this.frameDates = this.frames.map((f) => JulianDate.fromIso8601(f.Timestamp));
39777
39834
  this.frameCache = new Array(this.frames.length).fill(null);
39778
39835
  this.frameDims = new Array(this.frames.length).fill(null);
39836
+ this.valueCache = new Array(this.frames.length).fill(null);
39837
+ this.produceValueCanvas = Boolean(options.produceValueCanvas);
39838
+ this.baselineMaskEntry = options.baselineMask || null;
39839
+ this.maskBaseline = (_b = options.maskBaseline) !== null && _b !== void 0 ? _b : Boolean(options.baselineMask);
39840
+ this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
39841
+ this.driveMaterial = options.driveMaterial !== false;
39779
39842
  this.originalMaterial = options.entity.polygon.material;
39780
39843
  this.imageProperty = new CallbackProperty(() => this.pool[this.poolIdx], false);
39781
- options.entity.polygon.material = new ImageMaterialProperty({ image: this.imageProperty, transparent: true });
39844
+ if (this.driveMaterial) {
39845
+ options.entity.polygon.material = new ImageMaterialProperty({ image: this.imageProperty, transparent: true });
39846
+ }
39782
39847
  this.removeCrossfadeTick = this.viewer.scene.postUpdate.addEventListener(() => this.tickCrossfade());
39783
39848
  this.removeOnTick = this.viewer.clock.onTick.addEventListener((clock) => this.onClockTick(clock.currentTime));
39784
39849
  this.onClockTick(this.viewer.clock.currentTime);
@@ -39800,7 +39865,7 @@ var TextureFrameSeriesAnimator;
39800
39865
  this.removeCrossfadeTick();
39801
39866
  this.removeCrossfadeTick = null;
39802
39867
  }
39803
- if (this.entity && this.entity.polygon) {
39868
+ if (this.driveMaterial && this.entity && this.entity.polygon) {
39804
39869
  this.entity.polygon.material = this.originalMaterial;
39805
39870
  }
39806
39871
  }
@@ -39821,6 +39886,20 @@ var TextureFrameSeriesAnimator;
39821
39886
  GetImageProperty() {
39822
39887
  return this.imageProperty;
39823
39888
  }
39889
+ /**
39890
+ * The untinted canvas carrying the presented frame's value in RGB and its coverage in alpha,
39891
+ * or null unless the instance was constructed with produceValueCanvas.
39892
+ */
39893
+ GetValueCanvas() {
39894
+ return this.valueDims ? this.valueCanvas : null;
39895
+ }
39896
+ /**
39897
+ * Increments every time a frame is presented, so a consumer holding the value canvas can tell
39898
+ * whether it needs re-uploading without diffing pixels or subscribing to anything.
39899
+ */
39900
+ GetPresentVersion() {
39901
+ return this.presentVersion;
39902
+ }
39824
39903
  onClockTick(currentTime) {
39825
39904
  if (this.disposed) {
39826
39905
  return;
@@ -39927,12 +40006,19 @@ var TextureFrameSeriesAnimator;
39927
40006
  if (this.disposed) {
39928
40007
  return;
39929
40008
  }
40009
+ // Awaited before decoding rather than applied later, so no frame is ever cached unmasked and
40010
+ // then corrected once the mask lands.
40011
+ await this.ensureBaselineMask();
40012
+ if (this.disposed) {
40013
+ return;
40014
+ }
39930
40015
  const decoded = await this.decodeAndTint(buffer);
39931
40016
  if (this.disposed) {
39932
40017
  return;
39933
40018
  }
39934
40019
  this.frameCache[idx] = decoded.pixels;
39935
40020
  this.frameDims[idx] = { width: decoded.width, height: decoded.height };
40021
+ this.valueCache[idx] = decoded.valuePixels || null;
39936
40022
  if (idx === this.currentFrameIndex) {
39937
40023
  this.beginCrossfadeTo(idx);
39938
40024
  }
@@ -39956,11 +40042,90 @@ var TextureFrameSeriesAnimator;
39956
40042
  const ctx = canvas.getContext("2d");
39957
40043
  ctx.drawImage(image, 0, 0);
39958
40044
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
40045
+ // Copied before tinting: the ramp is a lerp between two colours, so the value cannot be
40046
+ // recovered from the tinted pixels afterwards.
40047
+ const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
39959
40048
  ApplyGrayscaleColorMask(imageData, this.lowColor, this.highColor);
39960
- return { pixels: imageData.data, width: canvas.width, height: canvas.height };
40049
+ this.applyBaselineMask(imageData.data, valuePixels, canvas.width, canvas.height);
40050
+ if (valuePixels) {
40051
+ dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES);
40052
+ }
40053
+ return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
40054
+ }
40055
+ /**
40056
+ * Fetches and decodes the archive's static baseline mask, at most once.
40057
+ */
40058
+ ensureBaselineMask() {
40059
+ if (!this.maskBaseline || !this.baselineMaskEntry) {
40060
+ return Promise.resolve();
40061
+ }
40062
+ if (this.baselineLoad) {
40063
+ return this.baselineLoad;
40064
+ }
40065
+ const entry = this.baselineMaskEntry;
40066
+ const start = entry.ByteOffset;
40067
+ const end = entry.ByteOffset + entry.ByteLength - 1;
40068
+ this.baselineLoad = fetch(this.archiveUrl, { headers: { Range: `bytes=${start}-${end}` } })
40069
+ .then((response) => response.arrayBuffer())
40070
+ .then(async (buffer) => {
40071
+ if (this.disposed) {
40072
+ return;
40073
+ }
40074
+ const blob = new Blob([buffer], { type: "image/png" });
40075
+ const objectUrl = URL.createObjectURL(blob);
40076
+ let image;
40077
+ try {
40078
+ image = await loadImage(objectUrl);
40079
+ }
40080
+ finally {
40081
+ URL.revokeObjectURL(objectUrl);
40082
+ }
40083
+ const canvas = document.createElement("canvas");
40084
+ canvas.width = image.width;
40085
+ canvas.height = image.height;
40086
+ const ctx = canvas.getContext("2d");
40087
+ ctx.drawImage(image, 0, 0);
40088
+ const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
40089
+ const flags = new Uint8Array(canvas.width * canvas.height);
40090
+ for (let p = 0; p < flags.length; p++) {
40091
+ flags[p] = pixels[p * 4 + 3] >= 128 ? 1 : 0;
40092
+ }
40093
+ this.baselineFlags = flags;
40094
+ this.baselineDims = { width: canvas.width, height: canvas.height };
40095
+ })
40096
+ .catch((e) => {
40097
+ // A missing or unreadable mask must not stop the animation; it just means nothing is hidden.
40098
+ console.warn("TextureFrameSeriesAnimator: could not load the baseline mask, drawing it instead.", e);
40099
+ });
40100
+ return this.baselineLoad;
40101
+ }
40102
+ /**
40103
+ * Clears alpha on baseline texels, which is what makes hiding the dry/land sentinel a render choice.
40104
+ */
40105
+ applyBaselineMask(pixels, valuePixels, width, height) {
40106
+ const flags = this.baselineFlags;
40107
+ if (!flags || !this.baselineDims) {
40108
+ return;
40109
+ }
40110
+ // A mask from a different raster size cannot be indexed against this frame, and guessing a
40111
+ // resampling would silently hide the wrong ground.
40112
+ if (this.baselineDims.width !== width || this.baselineDims.height !== height) {
40113
+ return;
40114
+ }
40115
+ for (let p = 0; p < flags.length; p++) {
40116
+ if (flags[p]) {
40117
+ pixels[p * 4 + 3] = 0;
40118
+ if (valuePixels) {
40119
+ valuePixels[p * 4 + 3] = 0;
40120
+ }
40121
+ }
40122
+ }
39961
40123
  }
39962
40124
  beginCrossfadeTo(idx) {
39963
40125
  const to = this.frameCache[idx];
40126
+ this.fadeFromValuePixels = this.displayedValuePixels === null && this.valueCache[idx]
40127
+ ? new Uint8ClampedArray(this.valueCache[idx].length)
40128
+ : this.displayedValuePixels;
39964
40129
  this.fadeFromPixels = this.displayedPixels === null ? new Uint8ClampedArray(to.length) : this.displayedPixels;
39965
40130
  this.fadeToIndex = idx;
39966
40131
  this.fadeToDims = this.frameDims[idx];
@@ -39979,13 +40144,28 @@ var TextureFrameSeriesAnimator;
39979
40144
  for (let i = 0; i < n; i++) {
39980
40145
  blended[i] = from[i] + (to[i] - from[i]) * eased;
39981
40146
  }
39982
- this.presentPixels(blended, this.fadeToDims);
40147
+ let blendedValue = null;
40148
+ const toValue = this.valueCache[this.fadeToIndex];
40149
+ if (toValue) {
40150
+ const fromValue = this.fadeFromValuePixels;
40151
+ blendedValue = new Uint8ClampedArray(toValue.length);
40152
+ if (fromValue && fromValue.length === toValue.length) {
40153
+ for (let i = 0; i < toValue.length; i++) {
40154
+ blendedValue[i] = fromValue[i] + (toValue[i] - fromValue[i]) * eased;
40155
+ }
40156
+ }
40157
+ else {
40158
+ blendedValue.set(toValue);
40159
+ }
40160
+ }
40161
+ this.presentPixels(blended, this.fadeToDims, blendedValue);
39983
40162
  if (t >= 1) {
39984
40163
  this.fadeStartMs = null;
39985
40164
  this.fadeFromPixels = null;
40165
+ this.fadeFromValuePixels = null;
39986
40166
  }
39987
40167
  }
39988
- presentPixels(pixels, dims) {
40168
+ presentPixels(pixels, dims, valuePixels) {
39989
40169
  this.poolIdx = 1 - this.poolIdx;
39990
40170
  const target = this.pool[this.poolIdx];
39991
40171
  target.width = dims.width;
@@ -39995,6 +40175,17 @@ var TextureFrameSeriesAnimator;
39995
40175
  imgData.data.set(pixels);
39996
40176
  ctx.putImageData(imgData, 0, 0);
39997
40177
  this.displayedPixels = pixels;
40178
+ if (this.valueCanvas && valuePixels) {
40179
+ this.valueCanvas.width = dims.width;
40180
+ this.valueCanvas.height = dims.height;
40181
+ const valueCtx = this.valueCanvas.getContext("2d");
40182
+ const valueImgData = valueCtx.createImageData(dims.width, dims.height);
40183
+ valueImgData.data.set(valuePixels);
40184
+ valueCtx.putImageData(valueImgData, 0, 0);
40185
+ this.displayedValuePixels = valuePixels;
40186
+ this.valueDims = dims;
40187
+ }
40188
+ this.presentVersion++;
39998
40189
  }
39999
40190
  }
40000
40191
  TextureFrameSeriesAnimator.Animator = Animator;
@@ -40006,11 +40197,743 @@ var TextureFrameSeriesAnimator;
40006
40197
  }
40007
40198
  })(TextureFrameSeriesAnimator || (TextureFrameSeriesAnimator = {}));
40008
40199
 
40200
+ /**
40201
+ * Renders a value texture as a displaced, coloured surface.
40202
+ * The source canvas is expected as RGBA: value in R, coverage in A.
40203
+ */
40204
+ var DisplacedSurfacePrimitive;
40205
+ (function (DisplacedSurfacePrimitive) {
40206
+ // Grid densities a tile can draw at, coarsest first.
40207
+ const TIERS = [32, 64, 128, 192];
40208
+ // Tiles per side the extent is cut into.
40209
+ const TILES_PER_SIDE = 6;
40210
+ // Screen pixels one grid cell should cover. Lower means finer meshes sooner.
40211
+ const TARGET_CELL_PIXELS = 12;
40212
+ // Fraction of its distance to the surface the camera must move before densities are reconsidered.
40213
+ const MOVEMENT_THRESHOLD = 0.12;
40214
+ const RESELECT_INTERVAL_MS = 400;
40215
+ // Skirt depth as a fraction of the displaced range. The sheet is translucent, so an oversized
40216
+ // skirt blends through it and paints a seam down every tile boundary.
40217
+ const SKIRT_FRACTION = 0.015;
40218
+ // Alpha below which a texel counts as no-data and is not drawn at all.
40219
+ const COVERAGE_CUTOFF = 0.35;
40220
+ // With no exaggeration stated, the full value range is drawn as this fraction of the extent's shorter side.
40221
+ const AUTO_RELIEF_FRACTION = 0.02;
40222
+ // Terrain is sampled on this grid and interpolated between samples.
40223
+ // Per-texel sampling would be hundreds of thousands of lookups for a surface whose own data is far coarser than that.
40224
+ const GROUND_SAMPLES_PER_SIDE = 64;
40225
+ // Terrain detail level the samples are taken at.
40226
+ // Most-detailed would fetch every tile under a parcel that can be a hundred kilometres across.
40227
+ const GROUND_SAMPLE_LEVEL = 11;
40228
+ // Height window the packed ground texture covers, wide enough for any terrain on Earth.
40229
+ const GROUND_MIN_METRES = -1000;
40230
+ const GROUND_MAX_METRES = 9000;
40231
+ const DEFAULT_LOW_COLOR = { red: 255, green: 255, blue: 255, alpha: 0.12 };
40232
+ const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
40233
+ const VERTEX_SHADER_GLSL300 = `
40234
+ in vec3 position3DHigh;
40235
+ in vec3 position3DLow;
40236
+ in vec3 normal;
40237
+ in vec2 st;
40238
+ in float skirt;
40239
+
40240
+ uniform sampler2D u_valueTexture;
40241
+ uniform sampler2D u_groundTexture;
40242
+ uniform float u_valueMin;
40243
+ uniform float u_valueRange;
40244
+ uniform float u_exaggeration;
40245
+ uniform float u_baseHeight;
40246
+ uniform float u_skirtDepth;
40247
+ uniform float u_groundWeight;
40248
+ uniform vec2 u_groundRange;
40249
+ uniform vec2 u_groundSize;
40250
+
40251
+ out vec2 v_st;
40252
+ out float v_value;
40253
+ out float v_coverage;
40254
+
40255
+ float decodeGround(vec2 uv) {
40256
+ // Channels arrive normalised to 0..1, so they go back to bytes before being reassembled.
40257
+ vec3 packed = texture(u_groundTexture, uv).rgb * 255.0;
40258
+ float normalised = dot(packed, vec3(65536.0, 256.0, 1.0)) / 16777215.0;
40259
+ return u_groundRange.x + normalised * (u_groundRange.y - u_groundRange.x);
40260
+ }
40261
+
40262
+ /*
40263
+ * Bilinear by hand, because the height is packed across three channels and hardware filtering would
40264
+ * blend the packing rather than the heights, turning a smooth slope into noise at every texel edge.
40265
+ */
40266
+ float sampleGround(vec2 uv) {
40267
+ vec2 texel = 1.0 / u_groundSize;
40268
+ vec2 coord = uv * u_groundSize - 0.5;
40269
+ vec2 base = floor(coord);
40270
+ vec2 f = coord - base;
40271
+ vec2 origin = (base + 0.5) * texel;
40272
+ float h00 = decodeGround(origin);
40273
+ float h10 = decodeGround(origin + vec2(texel.x, 0.0));
40274
+ float h01 = decodeGround(origin + vec2(0.0, texel.y));
40275
+ float h11 = decodeGround(origin + texel);
40276
+ return mix(mix(h00, h10, f.x), mix(h01, h11, f.x), f.y);
40277
+ }
40278
+
40279
+ void main() {
40280
+ vec4 texel = texture(u_valueTexture, st);
40281
+ v_st = st;
40282
+ v_value = texel.r;
40283
+ v_coverage = texel.a;
40284
+
40285
+ float metres = u_valueMin + texel.r * u_valueRange;
40286
+
40287
+ // Value zero sits at the polygon's own altitude, plus the terrain underneath when the polygon
40288
+ // is following the ground rather than absolutely positioned.
40289
+ float ground = sampleGround(st) * u_groundWeight;
40290
+ float displacement = u_baseHeight + ground + metres * u_exaggeration * texel.a - skirt * u_skirtDepth;
40291
+
40292
+ // Added to the LOW half of the encoded position: a metre-scale offset added to the high half
40293
+ // would be lost to float32 rounding at an earth radius.
40294
+ vec4 p = czm_translateRelativeToEye(position3DHigh, position3DLow + normal * displacement);
40295
+ gl_Position = czm_modelViewProjectionRelativeToEye * p;
40296
+ }
40297
+ `;
40298
+ const FRAGMENT_SHADER_GLSL300 = `
40299
+ uniform sampler2D u_valueTexture;
40300
+ uniform vec4 u_lowColor;
40301
+ uniform vec4 u_highColor;
40302
+ uniform float u_coverageCutoff;
40303
+ uniform vec2 u_texelSize;
40304
+ uniform float u_valueRange;
40305
+ uniform float u_exaggeration;
40306
+ uniform float u_metresPerTexel;
40307
+
40308
+ in vec2 v_st;
40309
+ in float v_value;
40310
+ in float v_coverage;
40311
+
40312
+ void main() {
40313
+ if (v_coverage < u_coverageCutoff) {
40314
+ discard;
40315
+ }
40316
+ vec4 color = mix(u_lowColor, u_highColor, v_value);
40317
+
40318
+ // Relief shading from the value gradient, so the displacement reads at a distance without
40319
+ // recomputing vertex normals every time the texture changes.
40320
+ float left = texture(u_valueTexture, v_st - vec2(u_texelSize.x, 0.0)).r;
40321
+ float right = texture(u_valueTexture, v_st + vec2(u_texelSize.x, 0.0)).r;
40322
+ float down = texture(u_valueTexture, v_st - vec2(0.0, u_texelSize.y)).r;
40323
+ float up = texture(u_valueTexture, v_st + vec2(0.0, u_texelSize.y)).r;
40324
+ float scale = u_valueRange * u_exaggeration;
40325
+ vec3 n = normalize(vec3((left - right) * scale, (down - up) * scale, 2.0 * u_metresPerTexel));
40326
+ float lambert = clamp(dot(n, normalize(vec3(-0.5, -0.6, 0.62))), 0.0, 1.0);
40327
+ color.rgb *= 0.45 + 0.9 * lambert;
40328
+
40329
+ out_FragColor = vec4(color.rgb, color.a * v_coverage);
40330
+ }
40331
+ `;
40332
+ /*
40333
+ * Rewrites a GLSL 300 es source into the GLSL 100 dialect older Cesium builds author in.
40334
+ */
40335
+ function toGlsl100(source, isFragment) {
40336
+ let result = source
40337
+ .replace(/\btexture\s*\(/g, "texture2D(")
40338
+ .replace(/^in\s+/gm, isFragment ? "varying " : "attribute ")
40339
+ .replace(/^out\s+/gm, "varying ");
40340
+ if (isFragment) {
40341
+ result = result.replace(/\bout_FragColor\b/g, "gl_FragColor");
40342
+ }
40343
+ return result;
40344
+ }
40345
+ let _glsl300Support = null;
40346
+ /*
40347
+ * Works out which GLSL dialect this host's Cesium authors shaders in.
40348
+ */
40349
+ function usesGlsl300(context) {
40350
+ if (_glsl300Support !== null) {
40351
+ return _glsl300Support;
40352
+ }
40353
+ let modernizes = false;
40354
+ try {
40355
+ const probe = new ShaderSource({
40356
+ sources: ["varying float v_probe;\nvoid main() { gl_Position = vec4(0.0); }"],
40357
+ includeBuiltIns: false
40358
+ });
40359
+ const combined = probe.createCombinedVertexShader(context);
40360
+ modernizes = combined.indexOf("varying") < 0;
40361
+ }
40362
+ catch (e) {
40363
+ // No probe means no evidence the host rewrites anything, so trust the context alone.
40364
+ modernizes = false;
40365
+ }
40366
+ _glsl300Support = Boolean(context.webgl2) && !modernizes;
40367
+ return _glsl300Support;
40368
+ }
40369
+ /*
40370
+ * Places grid node (fx, fy) on the ellipsoid, stepping the way the raster was sampled.
40371
+ * A raster sampled at lon/lat has to be walked in degrees; one sampled linearly across a projected CRS has to be walked in tangent-plane metres.
40372
+ */
40373
+ function makePlacer(extent, pixelPlacement) {
40374
+ if (pixelPlacement === "lonlat") {
40375
+ return (fx, fy) => Cartesian3.fromDegrees(Math$1.lerp(extent.West, extent.East, fx), Math$1.lerp(extent.North, extent.South, fy), 0);
40376
+ }
40377
+ const ellipsoid = Ellipsoid.WGS84;
40378
+ const centre = Cartesian3.fromDegrees((extent.West + extent.East) / 2, (extent.South + extent.North) / 2, 0);
40379
+ const frame = Transforms.eastNorthUpToFixedFrame(centre);
40380
+ const toLocal = Matrix4.inverse(frame, new Matrix4());
40381
+ const southWest = Matrix4.multiplyByPoint(toLocal, Cartesian3.fromDegrees(extent.West, extent.South, 0), new Cartesian3());
40382
+ const northEast = Matrix4.multiplyByPoint(toLocal, Cartesian3.fromDegrees(extent.East, extent.North, 0), new Cartesian3());
40383
+ return (fx, fy) => {
40384
+ const local = new Cartesian3(Math$1.lerp(southWest.x, northEast.x, fx), Math$1.lerp(northEast.y, southWest.y, fy), 0);
40385
+ const world = Matrix4.multiplyByPoint(frame, local, new Cartesian3());
40386
+ return ellipsoid.scaleToGeodeticSurface(world, world);
40387
+ };
40388
+ }
40389
+ /*
40390
+ * Builds one tile's grid, optionally plus a duplicated perimeter ring the shader pushes downwards to
40391
+ * cover the crack where a neighbouring tile at a different density interpolates the shared edge.
40392
+ */
40393
+ function buildGeometry(extent, resolution, patch, pixelPlacement, withSkirt = false) {
40394
+ const cols = resolution;
40395
+ const rows = resolution;
40396
+ const gridCount = (cols + 1) * (rows + 1);
40397
+ const perimeterCount = withSkirt ? 2 * (cols + 1) + 2 * (rows + 1) - 4 : 0;
40398
+ const count = gridCount + perimeterCount;
40399
+ const positions = new Float64Array(count * 3);
40400
+ const normals = new Float32Array(count * 3);
40401
+ const sts = new Float32Array(count * 2);
40402
+ const skirts = new Float32Array(count);
40403
+ const ellipsoid = Ellipsoid.WGS84;
40404
+ const place = makePlacer(extent, pixelPlacement);
40405
+ let v = 0;
40406
+ for (let row = 0; row <= rows; row++) {
40407
+ // Texture row 0 is the north edge, so t runs opposite to northing.
40408
+ const fy = Math$1.lerp(patch.v0, patch.v1, row / rows);
40409
+ for (let col = 0; col <= cols; col++) {
40410
+ const fx = Math$1.lerp(patch.u0, patch.u1, col / cols);
40411
+ const position = place(fx, fy);
40412
+ positions[v * 3] = position.x;
40413
+ positions[v * 3 + 1] = position.y;
40414
+ positions[v * 3 + 2] = position.z;
40415
+ const up = ellipsoid.geodeticSurfaceNormal(position, new Cartesian3());
40416
+ normals[v * 3] = up.x;
40417
+ normals[v * 3 + 1] = up.y;
40418
+ normals[v * 3 + 2] = up.z;
40419
+ sts[v * 2] = fx;
40420
+ sts[v * 2 + 1] = fy;
40421
+ v++;
40422
+ }
40423
+ }
40424
+ const indices = new Uint32Array(cols * rows * 6 + perimeterCount * 6);
40425
+ let i = 0;
40426
+ for (let row = 0; row < rows; row++) {
40427
+ for (let col = 0; col < cols; col++) {
40428
+ const topLeft = row * (cols + 1) + col;
40429
+ const topRight = topLeft + 1;
40430
+ const bottomLeft = topLeft + (cols + 1);
40431
+ const bottomRight = bottomLeft + 1;
40432
+ indices[i++] = topLeft;
40433
+ indices[i++] = bottomLeft;
40434
+ indices[i++] = topRight;
40435
+ indices[i++] = topRight;
40436
+ indices[i++] = bottomLeft;
40437
+ indices[i++] = bottomRight;
40438
+ }
40439
+ }
40440
+ // Perimeter walked as one closed loop, so consecutive entries are always neighbours.
40441
+ const loop = [];
40442
+ for (let col = 0; col <= cols; col++) {
40443
+ loop.push(col);
40444
+ }
40445
+ for (let row = 1; row <= rows; row++) {
40446
+ loop.push(row * (cols + 1) + cols);
40447
+ }
40448
+ for (let col = cols - 1; col >= 0; col--) {
40449
+ loop.push(rows * (cols + 1) + col);
40450
+ }
40451
+ for (let row = rows - 1; row >= 1; row--) {
40452
+ loop.push(row * (cols + 1));
40453
+ }
40454
+ const skirtStart = gridCount;
40455
+ if (withSkirt) {
40456
+ loop.forEach((gridIndex, k) => {
40457
+ const target = skirtStart + k;
40458
+ positions[target * 3] = positions[gridIndex * 3];
40459
+ positions[target * 3 + 1] = positions[gridIndex * 3 + 1];
40460
+ positions[target * 3 + 2] = positions[gridIndex * 3 + 2];
40461
+ normals[target * 3] = normals[gridIndex * 3];
40462
+ normals[target * 3 + 1] = normals[gridIndex * 3 + 1];
40463
+ normals[target * 3 + 2] = normals[gridIndex * 3 + 2];
40464
+ sts[target * 2] = sts[gridIndex * 2];
40465
+ sts[target * 2 + 1] = sts[gridIndex * 2 + 1];
40466
+ skirts[target] = 1;
40467
+ });
40468
+ for (let k = 0; k < loop.length; k++) {
40469
+ const nextK = (k + 1) % loop.length;
40470
+ indices[i++] = loop[k];
40471
+ indices[i++] = skirtStart + k;
40472
+ indices[i++] = loop[nextK];
40473
+ indices[i++] = loop[nextK];
40474
+ indices[i++] = skirtStart + k;
40475
+ indices[i++] = skirtStart + nextK;
40476
+ }
40477
+ }
40478
+ const geometry = new Geometry({
40479
+ attributes: {
40480
+ position: new GeometryAttribute({
40481
+ componentDatatype: ComponentDatatype.DOUBLE,
40482
+ componentsPerAttribute: 3,
40483
+ values: positions
40484
+ }),
40485
+ normal: new GeometryAttribute({
40486
+ componentDatatype: ComponentDatatype.FLOAT,
40487
+ componentsPerAttribute: 3,
40488
+ values: normals
40489
+ }),
40490
+ st: new GeometryAttribute({
40491
+ componentDatatype: ComponentDatatype.FLOAT,
40492
+ componentsPerAttribute: 2,
40493
+ values: sts
40494
+ }),
40495
+ skirt: new GeometryAttribute({
40496
+ componentDatatype: ComponentDatatype.FLOAT,
40497
+ componentsPerAttribute: 1,
40498
+ values: skirts
40499
+ })
40500
+ },
40501
+ indices: indices,
40502
+ primitiveType: PrimitiveType.TRIANGLES,
40503
+ boundingSphere: BoundingSphere.fromVertices(Array.prototype.slice.call(positions))
40504
+ });
40505
+ // Splits position into the high/low pair Cesium's relative-to-eye shaders expect.
40506
+ GeometryPipeline.encodeAttribute(geometry, "position", "position3DHigh", "position3DLow");
40507
+ return geometry;
40508
+ }
40509
+ class Surface {
40510
+ constructor(options, hasCoverage) {
40511
+ var _a, _b, _c, _d;
40512
+ this.show = true;
40513
+ this.valueSource = null;
40514
+ this.lastPresentVersion = -1;
40515
+ this.texture = null;
40516
+ this.textureDirty = true;
40517
+ this.shaderProgram = null;
40518
+ this.renderState = null;
40519
+ this.destroyed = false;
40520
+ this.lastEyePosition = null;
40521
+ this.lastSelectionMs = -Infinity;
40522
+ this.selectionDirty = true;
40523
+ this.groundTexture = null;
40524
+ this.groundPixels = null;
40525
+ this.groundRequested = false;
40526
+ this.followGround = Boolean(options.followGround);
40527
+ this.terrainProvider = options.terrainProvider || null;
40528
+ this.extent = options.extent;
40529
+ this.source = options.source || null;
40530
+ this.valueMin = (_a = options.valueMin) !== null && _a !== void 0 ? _a : 0;
40531
+ this.valueMax = (_b = options.valueMax) !== null && _b !== void 0 ? _b : 1;
40532
+ this.exaggeration = (_c = options.exaggeration) !== null && _c !== void 0 ? _c : autoExaggeration(this.extent, this.valueMin, this.valueMax);
40533
+ this.baseHeight = (_d = options.baseHeight) !== null && _d !== void 0 ? _d : 0;
40534
+ this.pixelPlacement = options.pixelPlacement;
40535
+ this.tileSkirts = Boolean(options.tileSkirts);
40536
+ this.lowColor = toCesiumColor(options.lowColor || DEFAULT_LOW_COLOR);
40537
+ this.highColor = toCesiumColor(options.highColor || DEFAULT_HIGH_COLOR);
40538
+ this.tiles = buildTiles(hasCoverage);
40539
+ }
40540
+ GetFollowsGround() {
40541
+ return this.followGround;
40542
+ }
40543
+ GetPixelPlacement() {
40544
+ return this.pixelPlacement;
40545
+ }
40546
+ GetBaseHeight() {
40547
+ return this.baseHeight;
40548
+ }
40549
+ GetExaggeration() {
40550
+ return this.exaggeration;
40551
+ }
40552
+ GetExtent() {
40553
+ return this.extent;
40554
+ }
40555
+ SetExaggeration(exaggeration) {
40556
+ this.exaggeration = exaggeration;
40557
+ }
40558
+ SetBaseHeight(baseHeight) {
40559
+ this.baseHeight = baseHeight;
40560
+ }
40561
+ /**
40562
+ * Feeds the surface from an animated frame series.
40563
+ * Height and colour advance with the clock without the caller having to push each frame in.
40564
+ */
40565
+ BindAnimator(source) {
40566
+ this.valueSource = source;
40567
+ }
40568
+ /**
40569
+ * Tells the surface its source canvas has been redrawn and the texture needs re-uploading.
40570
+ */
40571
+ MarkSourceDirty() {
40572
+ this.textureDirty = true;
40573
+ }
40574
+ update(frameState) {
40575
+ if (!this.show || this.destroyed || this.tiles.length === 0) {
40576
+ return;
40577
+ }
40578
+ const context = frameState.context;
40579
+ if (this.valueSource) {
40580
+ const canvas = this.valueSource.GetValueCanvas();
40581
+ if (!canvas) {
40582
+ // No frame decoded yet, so there is nothing to displace against.
40583
+ return;
40584
+ }
40585
+ const version = this.valueSource.GetPresentVersion();
40586
+ if (canvas !== this.source || version !== this.lastPresentVersion) {
40587
+ this.source = canvas;
40588
+ this.lastPresentVersion = version;
40589
+ this.textureDirty = true;
40590
+ }
40591
+ }
40592
+ if (!this.source || !this.source.width || !this.source.height) {
40593
+ return;
40594
+ }
40595
+ this.syncTexture(context);
40596
+ this.requestGround();
40597
+ this.syncGroundTexture(context);
40598
+ // A tile needs one mesh before it has a bounding sphere to measure against.
40599
+ for (const tile of this.tiles) {
40600
+ if (!tile.centre) {
40601
+ this.ensureMesh(context, tile, TIERS[0]);
40602
+ }
40603
+ }
40604
+ const nowMs = now();
40605
+ if (this.shouldReselect(frameState, nowMs)) {
40606
+ this.selectTiers(frameState);
40607
+ this.lastSelectionMs = nowMs;
40608
+ this.lastEyePosition = Cartesian3.clone(frameState.camera.positionWC, this.lastEyePosition);
40609
+ this.selectionDirty = false;
40610
+ }
40611
+ for (const tile of this.tiles) {
40612
+ const mesh = this.ensureMesh(context, tile, tile.tier);
40613
+ frameState.commandList.push(this.commandFor(context, tile, mesh));
40614
+ }
40615
+ }
40616
+ isDestroyed() {
40617
+ return this.destroyed;
40618
+ }
40619
+ destroy() {
40620
+ this.destroyed = true;
40621
+ for (const tile of this.tiles) {
40622
+ tile.meshes.forEach((mesh) => mesh.vertexArray.destroy());
40623
+ tile.meshes.clear();
40624
+ tile.command = null;
40625
+ }
40626
+ if (this.shaderProgram) {
40627
+ this.shaderProgram.destroy();
40628
+ this.shaderProgram = null;
40629
+ }
40630
+ if (this.texture) {
40631
+ this.texture.destroy();
40632
+ this.texture = null;
40633
+ }
40634
+ if (this.groundTexture) {
40635
+ this.groundTexture.destroy();
40636
+ this.groundTexture = null;
40637
+ }
40638
+ return undefined;
40639
+ }
40640
+ /*
40641
+ * Samples terrain across the extent once and packs it into a texture the vertex shader reads.
40642
+ *
40643
+ * A texture rather than heights baked into the meshes, because the samples arrive
40644
+ * asynchronously and every cached tile mesh would otherwise have to be rebuilt when they land.
40645
+ */
40646
+ requestGround() {
40647
+ if (this.groundRequested || !this.followGround) {
40648
+ return;
40649
+ }
40650
+ this.groundRequested = true;
40651
+ const sampler = sampleTerrain;
40652
+ if (!this.terrainProvider || typeof sampler !== "function") {
40653
+ return;
40654
+ }
40655
+ const positions = [];
40656
+ for (let row = 0; row < GROUND_SAMPLES_PER_SIDE; row++) {
40657
+ const fy = row / (GROUND_SAMPLES_PER_SIDE - 1);
40658
+ const latitude = Math$1.lerp(this.extent.North, this.extent.South, fy);
40659
+ for (let col = 0; col < GROUND_SAMPLES_PER_SIDE; col++) {
40660
+ const fx = col / (GROUND_SAMPLES_PER_SIDE - 1);
40661
+ const longitude = Math$1.lerp(this.extent.West, this.extent.East, fx);
40662
+ positions.push(Cartographic.fromDegrees(longitude, latitude));
40663
+ }
40664
+ }
40665
+ Promise.resolve(sampler(this.terrainProvider, GROUND_SAMPLE_LEVEL, positions))
40666
+ .then((sampled) => {
40667
+ if (this.destroyed || !sampled) {
40668
+ return;
40669
+ }
40670
+ const pixels = new Uint8Array(GROUND_SAMPLES_PER_SIDE * GROUND_SAMPLES_PER_SIDE * 4);
40671
+ const span = GROUND_MAX_METRES - GROUND_MIN_METRES;
40672
+ for (let i = 0; i < sampled.length; i++) {
40673
+ const height = sampled[i] && isFinite(sampled[i].height) ? sampled[i].height : 0;
40674
+ const clamped = Math.min(GROUND_MAX_METRES, Math.max(GROUND_MIN_METRES, height));
40675
+ const packed = Math.round(((clamped - GROUND_MIN_METRES) / span) * 16777215);
40676
+ pixels[i * 4] = (packed >> 16) & 255;
40677
+ pixels[i * 4 + 1] = (packed >> 8) & 255;
40678
+ pixels[i * 4 + 2] = packed & 255;
40679
+ pixels[i * 4 + 3] = 255;
40680
+ }
40681
+ this.groundPixels = pixels;
40682
+ })
40683
+ .catch(() => {
40684
+ // Terrain that cannot be sampled leaves the surface on the ellipsoid, which is
40685
+ // where a clamped polygon sat before this existed.
40686
+ });
40687
+ }
40688
+ /*
40689
+ * Uploads whatever ground samples exist, and a flat stand-in until they arrive, so the
40690
+ * shader always has a texture bound whether or not this surface follows the ground.
40691
+ */
40692
+ syncGroundTexture(context) {
40693
+ if (this.groundTexture && (!this.groundPixels || this.groundTexture.width === GROUND_SAMPLES_PER_SIDE)) {
40694
+ return;
40695
+ }
40696
+ if (this.groundTexture) {
40697
+ this.groundTexture.destroy();
40698
+ }
40699
+ const zero = Math.round(((0 - GROUND_MIN_METRES) / (GROUND_MAX_METRES - GROUND_MIN_METRES)) * 16777215);
40700
+ const size = this.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1;
40701
+ const arrayBufferView = this.groundPixels
40702
+ || new Uint8Array([(zero >> 16) & 255, (zero >> 8) & 255, zero & 255, 255]);
40703
+ this.groundTexture = new Texture({
40704
+ context,
40705
+ pixelFormat: PixelFormat.RGBA,
40706
+ source: { width: size, height: size, arrayBufferView },
40707
+ flipY: false,
40708
+ sampler: new Sampler({
40709
+ wrapS: TextureWrap.CLAMP_TO_EDGE,
40710
+ wrapT: TextureWrap.CLAMP_TO_EDGE,
40711
+ // Nearest, because the shader interpolates decoded heights itself.
40712
+ minificationFilter: TextureMinificationFilter.NEAREST,
40713
+ magnificationFilter: TextureMagnificationFilter.NEAREST
40714
+ })
40715
+ });
40716
+ }
40717
+ /*
40718
+ * Ground distance between neighbouring texels, so the shading normal is built from a real
40719
+ * slope rather than a made up one.
40720
+ */
40721
+ metresPerTexel() {
40722
+ const southWest = Cartesian3.fromDegrees(this.extent.West, this.extent.South, 0);
40723
+ const southEast = Cartesian3.fromDegrees(this.extent.East, this.extent.South, 0);
40724
+ const width = Cartesian3.distance(southWest, southEast);
40725
+ return Math.max(1, width / Math.max(1, this.source ? this.source.width : 1));
40726
+ }
40727
+ syncTexture(context) {
40728
+ const sizeChanged = this.texture
40729
+ && (this.texture.width !== this.source.width || this.texture.height !== this.source.height);
40730
+ if (!this.texture || sizeChanged) {
40731
+ if (this.texture) {
40732
+ this.texture.destroy();
40733
+ }
40734
+ this.texture = new Texture({
40735
+ context,
40736
+ source: this.source,
40737
+ // Cesium flips canvas rows on upload by default, which would put the archive's
40738
+ // row 0 (its north edge) at the south edge and mirror the whole surface.
40739
+ flipY: false,
40740
+ sampler: new Sampler({
40741
+ wrapS: TextureWrap.CLAMP_TO_EDGE,
40742
+ wrapT: TextureWrap.CLAMP_TO_EDGE,
40743
+ minificationFilter: TextureMinificationFilter.LINEAR,
40744
+ magnificationFilter: TextureMagnificationFilter.LINEAR
40745
+ })
40746
+ });
40747
+ this.textureDirty = false;
40748
+ return;
40749
+ }
40750
+ if (this.textureDirty) {
40751
+ this.texture.copyFrom({ source: this.source });
40752
+ this.textureDirty = false;
40753
+ }
40754
+ }
40755
+ ensureMesh(context, tile, tier) {
40756
+ const cached = tile.meshes.get(tier);
40757
+ if (cached) {
40758
+ return cached;
40759
+ }
40760
+ const geometry = buildGeometry(this.extent, tier, tile.patch, this.pixelPlacement, this.tileSkirts);
40761
+ const attributeLocations = GeometryPipeline.createAttributeLocations(geometry);
40762
+ const mesh = {
40763
+ attributeLocations,
40764
+ vertexArray: VertexArray.fromGeometry({
40765
+ context,
40766
+ geometry,
40767
+ attributeLocations,
40768
+ bufferUsage: BufferUsage.STATIC_DRAW
40769
+ }),
40770
+ boundingSphere: geometry.boundingSphere
40771
+ };
40772
+ tile.meshes.set(tier, mesh);
40773
+ tile.centre = geometry.boundingSphere.center;
40774
+ tile.radius = geometry.boundingSphere.radius;
40775
+ return mesh;
40776
+ }
40777
+ /*
40778
+ * Picks each tile's density from how much screen it covers.
40779
+ */
40780
+ selectTiers(frameState) {
40781
+ const camera = frameState.camera;
40782
+ const fovy = camera.frustum && camera.frustum.fovy ? camera.frustum.fovy : Math$1.PI_OVER_THREE;
40783
+ const pixelsPerRadian = frameState.context.drawingBufferHeight / fovy;
40784
+ for (const tile of this.tiles) {
40785
+ if (!tile.centre) {
40786
+ tile.tier = TIERS[0];
40787
+ continue;
40788
+ }
40789
+ const distance = Math.max(1, Cartesian3.distance(camera.positionWC, tile.centre) - tile.radius);
40790
+ const screenPixels = (2 * tile.radius / distance) * pixelsPerRadian;
40791
+ const wanted = screenPixels / TARGET_CELL_PIXELS;
40792
+ let tier = TIERS[0];
40793
+ for (const candidate of TIERS) {
40794
+ if (candidate <= wanted) {
40795
+ tier = candidate;
40796
+ }
40797
+ }
40798
+ tile.tier = tier;
40799
+ }
40800
+ }
40801
+ shouldReselect(frameState, nowMs) {
40802
+ if (this.selectionDirty) {
40803
+ return true;
40804
+ }
40805
+ if (nowMs - this.lastSelectionMs < RESELECT_INTERVAL_MS) {
40806
+ return false;
40807
+ }
40808
+ const eye = frameState.camera.positionWC;
40809
+ if (!this.lastEyePosition) {
40810
+ return true;
40811
+ }
40812
+ const moved = Cartesian3.distance(eye, this.lastEyePosition);
40813
+ const anchor = this.tiles[0].centre;
40814
+ const reference = anchor ? Math.max(1, Cartesian3.distance(eye, anchor)) : 1;
40815
+ return moved / reference > MOVEMENT_THRESHOLD;
40816
+ }
40817
+ commandFor(context, tile, mesh) {
40818
+ if (!this.shaderProgram) {
40819
+ const glsl300 = usesGlsl300(context);
40820
+ this.shaderProgram = ShaderProgram.fromCache({
40821
+ context,
40822
+ vertexShaderSource: glsl300 ? VERTEX_SHADER_GLSL300 : toGlsl100(VERTEX_SHADER_GLSL300, false),
40823
+ fragmentShaderSource: glsl300 ? FRAGMENT_SHADER_GLSL300 : toGlsl100(FRAGMENT_SHADER_GLSL300, true),
40824
+ attributeLocations: mesh.attributeLocations
40825
+ });
40826
+ this.renderState = RenderState.fromCache({
40827
+ depthTest: { enabled: true },
40828
+ // Translucent ramp: blend, and leave the depth buffer to the opaque world.
40829
+ depthMask: false,
40830
+ blending: BlendingState.ALPHA_BLEND,
40831
+ cull: { enabled: false }
40832
+ });
40833
+ }
40834
+ if (!tile.command) {
40835
+ const self = this;
40836
+ tile.command = new DrawCommand({
40837
+ shaderProgram: this.shaderProgram,
40838
+ renderState: this.renderState,
40839
+ pass: Pass.TRANSLUCENT,
40840
+ modelMatrix: Matrix4.IDENTITY,
40841
+ owner: this,
40842
+ uniformMap: {
40843
+ u_valueTexture: () => self.texture,
40844
+ u_groundTexture: () => self.groundTexture,
40845
+ u_groundWeight: () => (self.followGround && self.groundPixels ? 1 : 0),
40846
+ u_groundRange: () => new Cartesian2(GROUND_MIN_METRES, GROUND_MAX_METRES),
40847
+ u_groundSize: () => new Cartesian2(self.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1, self.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1),
40848
+ u_metresPerTexel: () => self.metresPerTexel(),
40849
+ u_valueMin: () => self.valueMin,
40850
+ u_valueRange: () => self.valueMax - self.valueMin,
40851
+ u_exaggeration: () => self.exaggeration,
40852
+ u_baseHeight: () => self.baseHeight,
40853
+ u_skirtDepth: () => self.tileSkirts
40854
+ ? Math.max(2, Math.abs(self.valueMax - self.valueMin) * self.exaggeration * SKIRT_FRACTION)
40855
+ : 0,
40856
+ u_coverageCutoff: () => COVERAGE_CUTOFF,
40857
+ u_lowColor: () => self.lowColor,
40858
+ u_highColor: () => self.highColor,
40859
+ u_texelSize: () => new Cartesian2(1 / self.source.width, 1 / self.source.height)
40860
+ }
40861
+ });
40862
+ }
40863
+ // Bounding volume belongs to the mesh, so it follows whichever tier is drawing.
40864
+ tile.command.vertexArray = mesh.vertexArray;
40865
+ tile.command.boundingVolume = mesh.boundingSphere;
40866
+ return tile.command;
40867
+ }
40868
+ }
40869
+ DisplacedSurfacePrimitive.Surface = Surface;
40870
+ /*
40871
+ * Cuts the extent into tiles, dropping any the caller says carry no data.
40872
+ */
40873
+ function buildTiles(hasCoverage) {
40874
+ const tiles = [];
40875
+ for (let row = 0; row < TILES_PER_SIDE; row++) {
40876
+ for (let col = 0; col < TILES_PER_SIDE; col++) {
40877
+ const patch = {
40878
+ u0: col / TILES_PER_SIDE,
40879
+ u1: (col + 1) / TILES_PER_SIDE,
40880
+ v0: row / TILES_PER_SIDE,
40881
+ v1: (row + 1) / TILES_PER_SIDE
40882
+ };
40883
+ if (hasCoverage && !hasCoverage(patch)) {
40884
+ continue;
40885
+ }
40886
+ tiles.push({
40887
+ patch,
40888
+ meshes: new Map(),
40889
+ tier: TIERS[0],
40890
+ command: null,
40891
+ centre: null,
40892
+ radius: 0
40893
+ });
40894
+ }
40895
+ }
40896
+ return tiles;
40897
+ }
40898
+ /**
40899
+ * Exaggeration that makes the full value range stand AUTO_RELIEF_FRACTION of the extent tall.
40900
+ *
40901
+ * Derived from the whole extent rather than per tile: scaling each tile to its own size would
40902
+ * make neighbours disagree along their shared edge and tear the surface apart.
40903
+ * @param extent
40904
+ * @param valueMin
40905
+ * @param valueMax
40906
+ */
40907
+ function autoExaggeration(extent, valueMin, valueMax) {
40908
+ const range = Math.abs(valueMax - valueMin);
40909
+ if (!(range > 0)) {
40910
+ return 1;
40911
+ }
40912
+ const southWest = Cartesian3.fromDegrees(extent.West, extent.South, 0);
40913
+ const southEast = Cartesian3.fromDegrees(extent.East, extent.South, 0);
40914
+ const northWest = Cartesian3.fromDegrees(extent.West, extent.North, 0);
40915
+ const shorterSide = Math.min(Cartesian3.distance(southWest, southEast), Cartesian3.distance(southWest, northWest));
40916
+ if (!(shorterSide > 0)) {
40917
+ return 1;
40918
+ }
40919
+ return (shorterSide * AUTO_RELIEF_FRACTION) / range;
40920
+ }
40921
+ DisplacedSurfacePrimitive.autoExaggeration = autoExaggeration;
40922
+ function toCesiumColor(color) {
40923
+ return new Color(color.red / 255, color.green / 255, color.blue / 255, color.alpha);
40924
+ }
40925
+ function now() {
40926
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
40927
+ }
40928
+ })(DisplacedSurfacePrimitive || (DisplacedSurfacePrimitive = {}));
40929
+
40009
40930
  const TEXTURE_FRAME_SERIES_ANIMATOR_KEY = "TextureFrameSeriesAnimator.Animator";
40931
+ const EXTRUSION_ANIMATOR_KEY = "TextureFrameSeriesAnimator.ExtrusionAnimator";
40932
+ const DISPLACED_SURFACE_KEY = "DisplacedSurfacePrimitive.Surface";
40010
40933
  var EntityRenderEnginePolygon;
40011
40934
  (function (EntityRenderEnginePolygon) {
40012
40935
  async function Render(params) {
40013
- var _a, _b, _c;
40936
+ var _a, _b, _c, _d;
40014
40937
  const entity = params.entity;
40015
40938
  const style = params.style;
40016
40939
  const pRings = Entity$1.GetValue({
@@ -40043,14 +40966,38 @@ var EntityRenderEnginePolygon;
40043
40966
  console.error(`Polygon.Render: failed to resolve textured fill for entity ${((_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID) || "<NONE>"}:`, e);
40044
40967
  }
40045
40968
  }
40046
- const hasFill = fillType === Style.EPolygonFillType.Texture
40047
- ? Boolean(textureDataUri || frameArchive || cFillColor.alpha > 0)
40048
- : cFillColor.alpha > 0;
40049
- const fillMaterial = textureDataUri
40050
- ? new ImageMaterialProperty({ image: textureDataUri, transparent: true })
40051
- : frameArchive
40052
- ? Color.WHITE.withAlpha(0)
40053
- : cFillColor;
40969
+ const usesTextureExtrusion = (Boolean(style.useExtrusion) &&
40970
+ style.extrusionType === Style.EPolygonExtrusionType.Texture);
40971
+ let extrusionArchive = null;
40972
+ if (usesTextureExtrusion && !params.offline) {
40973
+ try {
40974
+ const resolved = await resolveTextureValue({
40975
+ api: params.api,
40976
+ textureValue: style.extrusionTexture || style.texture,
40977
+ style,
40978
+ entity,
40979
+ tags: params.tags
40980
+ });
40981
+ extrusionArchive = resolved.frameArchive || null;
40982
+ }
40983
+ catch (e) {
40984
+ console.error(`Polygon.Render: failed to resolve extrusion texture for entity ${((_b = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _b === void 0 ? void 0 : _b.ID) || "<NONE>"}:`, e);
40985
+ }
40986
+ }
40987
+ const drawsDisplacedSurface = Boolean(extrusionArchive);
40988
+ // The displaced surface IS the entity's fill, drawn as its own primitive at the value's height.
40989
+ // Leaving the flat polygon filled as well shows a second copy of the parcel on the ground under it.
40990
+ const hasFill = (drawsDisplacedSurface ? true
40991
+ : fillType === Style.EPolygonFillType.Texture
40992
+ ? Boolean(textureDataUri || frameArchive || cFillColor.alpha > 0)
40993
+ : cFillColor.alpha > 0);
40994
+ const fillMaterial = drawsDisplacedSurface
40995
+ ? Color.WHITE.withAlpha(0)
40996
+ : textureDataUri
40997
+ ? new ImageMaterialProperty({ image: textureDataUri, transparent: true })
40998
+ : frameArchive
40999
+ ? Color.WHITE.withAlpha(0)
41000
+ : cFillColor;
40054
41001
  const lineColorTrace = style.lineColor ? Calculator.TraceGetColor(style.lineColor, entity, params.tags) : { value: null, effective: null };
40055
41002
  const bLineColor = lineColorTrace.value;
40056
41003
  const cLineColor = bLineColor ? ColorToCColor(bLineColor) : Color.fromCssColorString("rgba(80, 80, 80, 0.8)");
@@ -40073,19 +41020,21 @@ var EntityRenderEnginePolygon;
40073
41020
  return null;
40074
41021
  }
40075
41022
  let heightRef = getHeightRef$3(style);
40076
- const outerRing = pRings.find(x => x.Facing == Geometry.EPolygonRingType.Boundaries);
40077
- const points = Geometry.ParsePoints(outerRing === null || outerRing === void 0 ? void 0 : outerRing.LinearRing);
41023
+ const outerRing = pRings.find(x => x.Facing == Geometry$1.EPolygonRingType.Boundaries);
41024
+ const points = Geometry$1.ParsePoints(outerRing === null || outerRing === void 0 ? void 0 : outerRing.LinearRing);
40078
41025
  let posses = points.map(x => Cartesian3.fromDegrees(EnsureNumber(x.longitude), EnsureNumber(x.latitude), EnsureNumber(x.altitude)));
40079
41026
  Cartes.CloseRing3(posses);
40080
- const extrusion = getPolygonExtrusion(entity, params.tags, outerRing, posses, heightRef, style);
41027
+ const extrusion = drawsDisplacedSurface
41028
+ ? { posses, value: undefined, exHeightRef: HeightReference.NONE }
41029
+ : getPolygonExtrusion(entity, params.tags, outerRing, posses, heightRef, style);
40081
41030
  posses = extrusion.posses;
40082
41031
  posses = CullDuplicateCPosses(posses);
40083
41032
  if (posses.length < 4) {
40084
41033
  return null;
40085
41034
  }
40086
- const holeRings = pRings.filter(x => x.Facing == Geometry.EPolygonRingType.Hole);
41035
+ const holeRings = pRings.filter(x => x.Facing == Geometry$1.EPolygonRingType.Hole);
40087
41036
  const holePosses = holeRings.map((x) => {
40088
- const points = Geometry.ParsePoints(x.LinearRing);
41037
+ const points = Geometry$1.ParsePoints(x.LinearRing);
40089
41038
  let holePosses = points.map(x => Cartesian3.fromDegrees(EnsureNumber(x.longitude), EnsureNumber(x.latitude), EnsureNumber(x.altitude)));
40090
41039
  holePosses = CullDuplicateCPosses(holePosses);
40091
41040
  Cartes.CloseRing3(holePosses);
@@ -40126,6 +41075,7 @@ var EntityRenderEnginePolygon;
40126
41075
  polygon: {
40127
41076
  hierarchy: new PolygonHierarchy(posses, holePosses.map(x => new PolygonHierarchy(x))),
40128
41077
  material: fillMaterial,
41078
+ fill: !drawsDisplacedSurface,
40129
41079
  extrudedHeight: extrusion.value,
40130
41080
  extrudedHeightReference: extrusion.exHeightRef,
40131
41081
  shadows: ShadowMode.ENABLED,
@@ -40156,6 +41106,12 @@ var EntityRenderEnginePolygon;
40156
41106
  if (!CompareCPolygonHierarchies(oldHierarchy, newHierarchy)) {
40157
41107
  cEntity.polygon.hierarchy = new ConstantProperty(newHierarchy);
40158
41108
  }
41109
+ // Toggling extrusion styling on an already-rendered entity flips this,
41110
+ // so it has to be reconciled rather than set only at creation.
41111
+ const oldFill = GetCValue(params.viewer, cEntity.polygon.fill);
41112
+ if (oldFill !== !drawsDisplacedSurface) {
41113
+ cEntity.polygon.fill = new ConstantProperty(!drawsDisplacedSurface);
41114
+ }
40159
41115
  const oldExtrusion = GetCValue(params.viewer, cEntity.polygon.extrudedHeight);
40160
41116
  if (oldExtrusion != extrusion.value) {
40161
41117
  cEntity.polygon.extrudedHeight = new ConstantProperty(extrusion.value);
@@ -40209,7 +41165,16 @@ var EntityRenderEnginePolygon;
40209
41165
  }
40210
41166
  // Must run before any material is baked below: the Animator takes over polygon.material, so a bake
40211
41167
  // has to know whether it is baking over a live animation or over a disposed one's restored material.
40212
- const animator = syncTextureFrameArchive(cEntity, frameArchive, params.viewer, style.textureColorMask);
41168
+ const animator = syncTextureFrameArchive(cEntity, drawsDisplacedSurface ? null : frameArchive, params.viewer, style.textureColorMask, style.maskTextureBaseline);
41169
+ syncDisplacedSurface({
41170
+ cEntity,
41171
+ extrusionArchive,
41172
+ viewer: params.viewer,
41173
+ style,
41174
+ entity,
41175
+ heightRef,
41176
+ outerRingPosses: posses
41177
+ });
40213
41178
  if (animator) {
40214
41179
  CesiumEntityStyler.SetDefaultTextureImage({
40215
41180
  entity: cEntity,
@@ -40269,7 +41234,7 @@ var EntityRenderEnginePolygon;
40269
41234
  }
40270
41235
  }
40271
41236
  let borderPosses = posses.map(x => x.clone ? x.clone() : { ...x });
40272
- let cEntityBorder = (_c = (_b = params.rendered) === null || _b === void 0 ? void 0 : _b._siblingGraphics) === null || _c === void 0 ? void 0 : _c[0];
41237
+ let cEntityBorder = (_d = (_c = params.rendered) === null || _c === void 0 ? void 0 : _c._siblingGraphics) === null || _d === void 0 ? void 0 : _d[0];
40273
41238
  cEntity._siblingGraphics = [];
40274
41239
  if (!cEntityBorder || ((!cEntityBorder.polyline && units == "px") ||
40275
41240
  (!cEntityBorder.corridor && units == "m"))) {
@@ -40519,12 +41484,13 @@ var EntityRenderEnginePolygon;
40519
41484
  /**
40520
41485
  * Disposes a cEntity's TextureFrameSeriesAnimator.Animator (if any).
40521
41486
  */
40522
- function DisposeTextureFrameSeriesAnimator(cEntity) {
41487
+ function DisposeTextureFrameSeriesAnimator(cEntity, viewer) {
40523
41488
  const existing = cEntity === null || cEntity === void 0 ? void 0 : cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY];
40524
41489
  if (existing && !existing.IsDisposed()) {
40525
41490
  existing.Dispose();
40526
41491
  cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = null;
40527
41492
  }
41493
+ disposeDisplacedSurface(cEntity, viewer);
40528
41494
  }
40529
41495
  EntityRenderEnginePolygon.DisposeTextureFrameSeriesAnimator = DisposeTextureFrameSeriesAnimator;
40530
41496
  })(EntityRenderEnginePolygon || (EntityRenderEnginePolygon = {}));
@@ -40638,6 +41604,145 @@ async function getStyle$4(api, entity, styleId) {
40638
41604
  // Cache of tinted texture data URIs, keyed by resolved url + color mask, so repeated renders of the
40639
41605
  // same polygon (or multiple polygons sharing a texture) don't refetch/reprocess the same image.
40640
41606
  const _textureCache = new LRUCache(50);
41607
+ /**
41608
+ * Reconciles the displaced surface (and the animator feeding it) against this render's resolved
41609
+ * extrusion texture, creating, reusing or tearing down as the style demands.
41610
+ * @param params
41611
+ */
41612
+ function syncDisplacedSurface(params) {
41613
+ var _a;
41614
+ const { cEntity, extrusionArchive, viewer, style, entity, heightRef, outerRingPosses } = params;
41615
+ const existingAnimator = cEntity[EXTRUSION_ANIMATOR_KEY];
41616
+ const existingSurface = cEntity[DISPLACED_SURFACE_KEY];
41617
+ // Nothing to draw and nothing left over, which is every polygon that does not use this feature.
41618
+ if (!extrusionArchive && !existingAnimator && !existingSurface) {
41619
+ return;
41620
+ }
41621
+ const reusable = extrusionArchive
41622
+ && existingAnimator
41623
+ && !existingAnimator.IsDisposed()
41624
+ && existingAnimator.GetArchiveUrl() === extrusionArchive.url
41625
+ && existingSurface
41626
+ && !existingSurface.isDestroyed();
41627
+ const placement = surfacePlacement(entity, heightRef);
41628
+ // Whether the surface follows terrain is baked into its ground sampling, so a change there has
41629
+ // to rebuild rather than update in place.
41630
+ if (reusable && existingSurface.GetFollowsGround() === placement.followGround) {
41631
+ existingSurface.SetBaseHeight(placement.baseHeight);
41632
+ existingSurface.SetExaggeration((_a = style.extrusionExaggeration) !== null && _a !== void 0 ? _a : DisplacedSurfacePrimitive.autoExaggeration(existingSurface.GetExtent(), extrusionArchive.metadata.ValueMin, extrusionArchive.metadata.ValueMax));
41633
+ return;
41634
+ }
41635
+ disposeDisplacedSurface(cEntity, viewer);
41636
+ if (!extrusionArchive) {
41637
+ return;
41638
+ }
41639
+ const extent = archiveExtent(extrusionArchive.metadata, outerRingPosses);
41640
+ if (!extent) {
41641
+ console.warn("Polygon.Render: extrusion texture has no usable extent, skipping displaced surface.");
41642
+ return;
41643
+ }
41644
+ // Drives the value canvas only: the surface paints the colour, so the polygon's own material is
41645
+ // left transparent rather than animated underneath it.
41646
+ const animator = new TextureFrameSeriesAnimator.Animator({
41647
+ viewer,
41648
+ entity: cEntity,
41649
+ archiveUrl: extrusionArchive.url,
41650
+ frames: extrusionArchive.metadata.Frames,
41651
+ textureColorMask: style.textureColorMask,
41652
+ driveMaterial: false,
41653
+ produceValueCanvas: true,
41654
+ baselineMask: extrusionArchive.metadata.BaselineMask,
41655
+ // Off-type for the same reason as the fill path: the pinned bruce-models predates the field.
41656
+ maskBaseline: style.maskTextureBaseline
41657
+ });
41658
+ cEntity[EXTRUSION_ANIMATOR_KEY] = animator;
41659
+ const mask = style.textureColorMask;
41660
+ const surface = new DisplacedSurfacePrimitive.Surface({
41661
+ extent,
41662
+ // The grid has to be walked the way the raster was sampled, or the surface skews across the extent.
41663
+ pixelPlacement: extrusionArchive.metadata.PixelPlacement,
41664
+ source: null,
41665
+ baseHeight: placement.baseHeight,
41666
+ followGround: placement.followGround,
41667
+ terrainProvider: viewer && viewer.terrainProvider,
41668
+ valueMin: extrusionArchive.metadata.ValueMin,
41669
+ valueMax: extrusionArchive.metadata.ValueMax,
41670
+ exaggeration: style.extrusionExaggeration,
41671
+ lowColor: mask ? Color$1.ColorFromStr(mask.minColor) : null,
41672
+ highColor: mask ? Color$1.ColorFromStr(mask.maxColor) : null
41673
+ });
41674
+ surface.BindAnimator(animator);
41675
+ viewer.scene.primitives.add(surface);
41676
+ cEntity[DISPLACED_SURFACE_KEY] = surface;
41677
+ }
41678
+ /**
41679
+ * Turns the polygon's altitude option into the two things the displaced surface needs:
41680
+ * - how high value zero sits.
41681
+ * - whether that is measured from the terrain or from the ellipsoid.
41682
+ * @param entity
41683
+ * @param heightRef
41684
+ */
41685
+ function surfacePlacement(entity, heightRef) {
41686
+ const rawAltitude = Entity$1.GetValue({
41687
+ entity,
41688
+ path: ["Bruce", "Location", "altitude"]
41689
+ });
41690
+ const altitude = rawAltitude ? EnsureNumber(rawAltitude) : 0;
41691
+ if (heightRef === HeightReference.CLAMP_TO_GROUND) {
41692
+ return { baseHeight: 0, followGround: true };
41693
+ }
41694
+ if (heightRef === HeightReference.RELATIVE_TO_GROUND) {
41695
+ return { baseHeight: altitude, followGround: true };
41696
+ }
41697
+ return { baseHeight: altitude, followGround: false };
41698
+ }
41699
+ /**
41700
+ * The archive's own extent when it recorded one, otherwise the polygon's own bounds so archives
41701
+ * generated before the extent was written still render somewhere sensible.
41702
+ * @param metadata
41703
+ * @param posses
41704
+ */
41705
+ function archiveExtent(metadata, posses) {
41706
+ if (metadata.West != null && metadata.East != null && metadata.South != null && metadata.North != null) {
41707
+ return { West: metadata.West, East: metadata.East, South: metadata.South, North: metadata.North };
41708
+ }
41709
+ if (!posses || posses.length === 0) {
41710
+ return null;
41711
+ }
41712
+ const rectangle = Rectangle.fromCartesianArray(posses);
41713
+ return {
41714
+ West: Math$1.toDegrees(rectangle.west),
41715
+ East: Math$1.toDegrees(rectangle.east),
41716
+ South: Math$1.toDegrees(rectangle.south),
41717
+ North: Math$1.toDegrees(rectangle.north)
41718
+ };
41719
+ }
41720
+ /**
41721
+ * Tears down the displaced surface and its feeding animator, if this entity has them.
41722
+ * @param cEntity
41723
+ * @param viewer
41724
+ */
41725
+ function disposeDisplacedSurface(cEntity, viewer) {
41726
+ const surface = cEntity === null || cEntity === void 0 ? void 0 : cEntity[DISPLACED_SURFACE_KEY];
41727
+ if (surface) {
41728
+ const viewerAlive = viewer && (typeof viewer.isDestroyed !== "function" || !viewer.isDestroyed());
41729
+ if (viewerAlive && viewer.scene && viewer.scene.primitives) {
41730
+ // Removing from the collection destroys the primitive, releasing its meshes and texture.
41731
+ viewer.scene.primitives.remove(surface);
41732
+ }
41733
+ else if (!surface.isDestroyed()) {
41734
+ surface.destroy();
41735
+ }
41736
+ cEntity[DISPLACED_SURFACE_KEY] = null;
41737
+ }
41738
+ const animator = cEntity === null || cEntity === void 0 ? void 0 : cEntity[EXTRUSION_ANIMATOR_KEY];
41739
+ if (animator && !animator.IsDisposed()) {
41740
+ animator.Dispose();
41741
+ }
41742
+ if (cEntity) {
41743
+ cEntity[EXTRUSION_ANIMATOR_KEY] = null;
41744
+ }
41745
+ }
40641
41746
  /**
40642
41747
  * Reconciles cEntity's TextureFrameSeriesAnimator.Animator (if any) against this render's resolved texture.
40643
41748
  * Called once cEntity is fully created/updated and definitely has a `polygon` graphics.
@@ -40647,7 +41752,7 @@ const _textureCache = new LRUCache(50);
40647
41752
  * @param viewer
40648
41753
  * @param textureColorMask
40649
41754
  */
40650
- function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask) {
41755
+ function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask, maskTextureBaseline) {
40651
41756
  const existing = cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY];
40652
41757
  if (frameArchive && existing && !existing.IsDisposed() && existing.GetArchiveUrl() === frameArchive.url) {
40653
41758
  return existing;
@@ -40664,7 +41769,9 @@ function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask
40664
41769
  entity: cEntity,
40665
41770
  archiveUrl: frameArchive.url,
40666
41771
  frames: frameArchive.metadata.Frames,
40667
- textureColorMask
41772
+ textureColorMask,
41773
+ baselineMask: frameArchive.metadata.BaselineMask,
41774
+ maskBaseline: maskTextureBaseline
40668
41775
  });
40669
41776
  cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = animator;
40670
41777
  return animator;
@@ -40728,8 +41835,15 @@ function extractClientFileIdFromTextureValue(raw) {
40728
41835
  * @param params
40729
41836
  */
40730
41837
  async function resolveTexturedFill(params) {
40731
- const { api, style, entity, tags } = params;
40732
- const textureValue = style.texture;
41838
+ return resolveTextureValue({ ...params, textureValue: params.style.texture });
41839
+ }
41840
+ /**
41841
+ * Resolves either shape a texture setting can take (a calculated file id/URL, or a search condition)
41842
+ * into a tinted data URI or, when the resolved file is a frame archive, the archive itself.
41843
+ * @param params
41844
+ */
41845
+ async function resolveTextureValue(params) {
41846
+ const { api, style, entity, tags, textureValue } = params;
40733
41847
  if (textureValue && !Array.isArray(textureValue)) {
40734
41848
  return resolveTextureCondition({ api, condition: textureValue });
40735
41849
  }
@@ -41518,7 +42632,7 @@ var EntityRenderEngine;
41518
42632
  entity._dispose();
41519
42633
  entity._dispose = null;
41520
42634
  }
41521
- EntityRenderEnginePolygon.DisposeTextureFrameSeriesAnimator(entity);
42635
+ EntityRenderEnginePolygon.DisposeTextureFrameSeriesAnimator(entity, viewer);
41522
42636
  if (entity._parentEntity && !ignoreParent) {
41523
42637
  doRemove({
41524
42638
  viewer,
@@ -42269,7 +43383,7 @@ var StyleUtils;
42269
43383
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
42270
43384
  })(StyleUtils || (StyleUtils = {}));
42271
43385
 
42272
- const VERSION = "7.1.3";
43386
+ const VERSION = "7.1.5";
42273
43387
  /**
42274
43388
  * Updates the environment instance used by bruce-cesium to one specified.
42275
43389
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
@@ -42286,5 +43400,5 @@ const getENVIRONMENT = () => {
42286
43400
  return ENVIRONMENT;
42287
43401
  };
42288
43402
 
42289
- export { VERSION, setENVIRONMENT, getENVIRONMENT, CesiumAnimatedInOut, CesiumAnimatedProperty, isStyleChanged, isOutlineChanged$1 as isOutlineChanged, StyleEffective, EntityRenderEngine, EntityRenderEngineModel3d, EntityRenderEnginePoint, EntityRenderEnginePolygon, EntityRenderEnginePolyline, MenuItemCreator, MenuItemManager, CesiumParabola, EntityLabel, LiveCursor, SharedGetters, DataSourceStaticKmlManager, DataLabRenderManager, EntitiesIdsRenderManager, EntitiesLoadedRenderManager, EntitiesRenderManager, EntityRenderManager, AssemblyRenderManager, GoogleSearchRenderManager, RelationsRenderManager, RenderManager, TilesetArbRenderManager, TilesetCadRenderManager, TilesetEntitiesRenderManager, TilesetGooglePhotosRenderManager, TilesetOsmRenderManager, TilesetPointcloudRenderManager, TileRenderEngine, createTileset, TilesetRenderEngine, ViewRenderEngine, VisualRegisterCuller, VisualsRegister, XGridsRenderEngine, CesiumEntityStyler, DrawingUtils, EntityUtils, MeasureUtils, StyleUtils, CESIUM_INSPECTOR_KEY, CESIUM_TIMELINE_KEY, CESIUM_TIMELINE_LIVE_KEY, CESIUM_TIMELINE_LIVE_PADDING_KEY, CESIUM_TIMELINE_INTERVAL_KEY, CESIUM_MODEL_SPACE_KEY, DEFAULT_LIVE_PADDING_SECONDS, ViewUtils, CesiumViewMonitor, ViewGroundArea, ViewerEventTracker, ViewerUtils, Draw3dPolygon, Draw3dPolyline, MeasureCreator, Walkthrough, WidgetControlViewBar, WidgetControlViewBarSearch, WidgetLeftPanelTab, WidgetLeftPanelTabBookmarks, WidgetLeftPanelTabDashboard, EMenuItemState, WidgetLeftPanelTabMenuItems, Widget, VIEWER_BOOKMARKS_WIDGET_KEY, WidgetBookmarks, WidgetBranding, WidgetCursorBar, WidgetEmbeddedInfoView, WidgetInfoView, VIEWER_LEFT_PANEL_WIDGET_KEY, VIEWER_LEFT_PANEL_CSS_VAR_LEFT, WidgetLeftPanel, WidgetNavCompass$$1 as WidgetNavCompass, VIEWER_VIEW_BAR_WIDGET_KEY, WidgetViewBar };
43403
+ export { VERSION, setENVIRONMENT, getENVIRONMENT, CesiumAnimatedInOut, DisplacedSurfacePrimitive, CesiumAnimatedProperty, isStyleChanged, isOutlineChanged$1 as isOutlineChanged, StyleEffective, EntityRenderEngine, EntityRenderEngineModel3d, EntityRenderEnginePoint, EntityRenderEnginePolygon, EntityRenderEnginePolyline, MenuItemCreator, MenuItemManager, CesiumParabola, EntityLabel, LiveCursor, SharedGetters, DataSourceStaticKmlManager, DataLabRenderManager, EntitiesIdsRenderManager, EntitiesLoadedRenderManager, EntitiesRenderManager, EntityRenderManager, AssemblyRenderManager, GoogleSearchRenderManager, RelationsRenderManager, RenderManager, TilesetArbRenderManager, TilesetCadRenderManager, TilesetEntitiesRenderManager, TilesetGooglePhotosRenderManager, TilesetOsmRenderManager, TilesetPointcloudRenderManager, TileRenderEngine, createTileset, TilesetRenderEngine, ViewRenderEngine, VisualRegisterCuller, VisualsRegister, XGridsRenderEngine, CesiumEntityStyler, DrawingUtils, EntityUtils, MeasureUtils, StyleUtils, CESIUM_INSPECTOR_KEY, CESIUM_TIMELINE_KEY, CESIUM_TIMELINE_LIVE_KEY, CESIUM_TIMELINE_LIVE_PADDING_KEY, CESIUM_TIMELINE_INTERVAL_KEY, CESIUM_MODEL_SPACE_KEY, DEFAULT_LIVE_PADDING_SECONDS, ViewUtils, CesiumViewMonitor, ViewGroundArea, ViewerEventTracker, ViewerUtils, Draw3dPolygon, Draw3dPolyline, MeasureCreator, Walkthrough, WidgetControlViewBar, WidgetControlViewBarSearch, WidgetLeftPanelTab, WidgetLeftPanelTabBookmarks, WidgetLeftPanelTabDashboard, EMenuItemState, WidgetLeftPanelTabMenuItems, Widget, VIEWER_BOOKMARKS_WIDGET_KEY, WidgetBookmarks, WidgetBranding, WidgetCursorBar, WidgetEmbeddedInfoView, WidgetInfoView, VIEWER_LEFT_PANEL_WIDGET_KEY, VIEWER_LEFT_PANEL_CSS_VAR_LEFT, WidgetLeftPanel, WidgetNavCompass$$1 as WidgetNavCompass, VIEWER_VIEW_BAR_WIDGET_KEY, WidgetViewBar };
42290
43404
  //# sourceMappingURL=bruce-cesium.es5.js.map