bruce-cesium 7.1.2 → 7.1.4

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, DistanceDisplayCondition, HeightReference, ColorBlendMode, HeadingPitchRoll, Math as Math$1, Transforms, ShadowMode, Cartesian3, ClassificationType, Model, JulianDate, Quaternion, Matrix4, ArcType, CornerType, ConstantPositionProperty, HorizontalOrigin, VerticalOrigin, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Cartesian2, SceneTransforms, NearFarScalar, Matrix3, Rectangle, KmlDataSource, GeoJsonDataSource, SceneMode, HeadingPitchRange, Ion, IonResource, Cesium3DTileStyle, Cesium3DTileColorBlendMode, Cesium3DTileset, OrthographicFrustum, EasingFunction, BoundingSphere, Intersect, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, PolygonPipeline, CesiumInspector, ClockRange, CustomDataSource, GeometryInstance, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
- import { Cartes, Entity as Entity$1, ENVIRONMENT, Calculator, ClientFile, EntityLod, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Carto, Geometry, Api, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, Bounds, Color as Color$1, BruceEvent, EntityCoords, DataLab, DelayQueue, EntityHistoricData, BruceApi, EntityRelation, AccountConcept, RecordChangeFeed, ProgramKey, EntitySource, Camera, ProjectViewTile, ProjectViewLegacyTile, Account, AccountLimits, Session, UrlUtils, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, ProjectViewBookmarkGroup, EntityRelationType } from 'bruce-models';
2
+ import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, ShaderSource, Ellipsoid, Cartesian3, Transforms, Matrix4, Math as Math$1, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, Cartesian2, DistanceDisplayCondition, HeightReference, ColorBlendMode, HeadingPitchRoll, ShadowMode, ClassificationType, Model, ArcType, CornerType, ConstantPositionProperty, JulianDate, Quaternion, HorizontalOrigin, VerticalOrigin, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, SceneTransforms, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, SceneMode, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, Cesium3DTileset, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Cesium3DTileColorBlendMode, Intersect, OrthographicFrustum, EasingFunction, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, PolygonPipeline, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, CesiumInspector, ClockRange, CustomDataSource, ScreenSpaceEventHandler, ScreenSpaceEventType, GeometryInstance, Fullscreen, CzmlDataSource } from 'cesium';
3
+ import { Cartes, Entity as Entity$1, ENVIRONMENT, Calculator, ClientFile, EntityLod, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Carto, Geometry as Geometry$1, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, Api, Bounds, Color as Color$1, BruceEvent, EntityCoords, DataLab, DelayQueue, BruceApi, EntityHistoricData, EntityRelation, AccountConcept, RecordChangeFeed, ProgramKey, EntitySource, ProjectViewLegacyTile, ProjectViewTile, Camera, Account, AccountLimits, Session, UrlUtils, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, ProjectViewBookmarkGroup, 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
  }
@@ -39751,6 +39751,8 @@ var TextureFrameSeriesAnimator;
39751
39751
  this.pendingIndex = -1;
39752
39752
  this.displayedPixels = null;
39753
39753
  this.fadeFromPixels = null;
39754
+ this.displayedValuePixels = null;
39755
+ this.fadeFromValuePixels = null;
39754
39756
  this.fadeToIndex = -1;
39755
39757
  this.fadeToDims = null;
39756
39758
  this.fadeStartMs = null;
@@ -39759,6 +39761,8 @@ var TextureFrameSeriesAnimator;
39759
39761
  // forcing Cesium to re-upload the texture only when something changed.
39760
39762
  this.pool = [document.createElement("canvas"), document.createElement("canvas")];
39761
39763
  this.poolIdx = 0;
39764
+ this.valueDims = null;
39765
+ this.presentVersion = 0;
39762
39766
  if (!options.entity.polygon) {
39763
39767
  throw new Error("TextureFrameSeriesAnimator requires an entity with polygon graphics.");
39764
39768
  }
@@ -39776,9 +39780,15 @@ var TextureFrameSeriesAnimator;
39776
39780
  this.frameDates = this.frames.map((f) => JulianDate.fromIso8601(f.Timestamp));
39777
39781
  this.frameCache = new Array(this.frames.length).fill(null);
39778
39782
  this.frameDims = new Array(this.frames.length).fill(null);
39783
+ this.valueCache = new Array(this.frames.length).fill(null);
39784
+ this.produceValueCanvas = Boolean(options.produceValueCanvas);
39785
+ this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
39786
+ this.driveMaterial = options.driveMaterial !== false;
39779
39787
  this.originalMaterial = options.entity.polygon.material;
39780
39788
  this.imageProperty = new CallbackProperty(() => this.pool[this.poolIdx], false);
39781
- options.entity.polygon.material = new ImageMaterialProperty({ image: this.imageProperty, transparent: true });
39789
+ if (this.driveMaterial) {
39790
+ options.entity.polygon.material = new ImageMaterialProperty({ image: this.imageProperty, transparent: true });
39791
+ }
39782
39792
  this.removeCrossfadeTick = this.viewer.scene.postUpdate.addEventListener(() => this.tickCrossfade());
39783
39793
  this.removeOnTick = this.viewer.clock.onTick.addEventListener((clock) => this.onClockTick(clock.currentTime));
39784
39794
  this.onClockTick(this.viewer.clock.currentTime);
@@ -39800,7 +39810,7 @@ var TextureFrameSeriesAnimator;
39800
39810
  this.removeCrossfadeTick();
39801
39811
  this.removeCrossfadeTick = null;
39802
39812
  }
39803
- if (this.entity && this.entity.polygon) {
39813
+ if (this.driveMaterial && this.entity && this.entity.polygon) {
39804
39814
  this.entity.polygon.material = this.originalMaterial;
39805
39815
  }
39806
39816
  }
@@ -39821,6 +39831,20 @@ var TextureFrameSeriesAnimator;
39821
39831
  GetImageProperty() {
39822
39832
  return this.imageProperty;
39823
39833
  }
39834
+ /**
39835
+ * The untinted canvas carrying the presented frame's value in RGB and its coverage in alpha,
39836
+ * or null unless the instance was constructed with produceValueCanvas.
39837
+ */
39838
+ GetValueCanvas() {
39839
+ return this.valueDims ? this.valueCanvas : null;
39840
+ }
39841
+ /**
39842
+ * Increments every time a frame is presented, so a consumer holding the value canvas can tell
39843
+ * whether it needs re-uploading without diffing pixels or subscribing to anything.
39844
+ */
39845
+ GetPresentVersion() {
39846
+ return this.presentVersion;
39847
+ }
39824
39848
  onClockTick(currentTime) {
39825
39849
  if (this.disposed) {
39826
39850
  return;
@@ -39933,6 +39957,7 @@ var TextureFrameSeriesAnimator;
39933
39957
  }
39934
39958
  this.frameCache[idx] = decoded.pixels;
39935
39959
  this.frameDims[idx] = { width: decoded.width, height: decoded.height };
39960
+ this.valueCache[idx] = decoded.valuePixels || null;
39936
39961
  if (idx === this.currentFrameIndex) {
39937
39962
  this.beginCrossfadeTo(idx);
39938
39963
  }
@@ -39956,11 +39981,17 @@ var TextureFrameSeriesAnimator;
39956
39981
  const ctx = canvas.getContext("2d");
39957
39982
  ctx.drawImage(image, 0, 0);
39958
39983
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
39984
+ // Copied before tinting: the ramp is a lerp between two colours, so the value cannot be
39985
+ // recovered from the tinted pixels afterwards.
39986
+ const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
39959
39987
  ApplyGrayscaleColorMask(imageData, this.lowColor, this.highColor);
39960
- return { pixels: imageData.data, width: canvas.width, height: canvas.height };
39988
+ return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
39961
39989
  }
39962
39990
  beginCrossfadeTo(idx) {
39963
39991
  const to = this.frameCache[idx];
39992
+ this.fadeFromValuePixels = this.displayedValuePixels === null && this.valueCache[idx]
39993
+ ? new Uint8ClampedArray(this.valueCache[idx].length)
39994
+ : this.displayedValuePixels;
39964
39995
  this.fadeFromPixels = this.displayedPixels === null ? new Uint8ClampedArray(to.length) : this.displayedPixels;
39965
39996
  this.fadeToIndex = idx;
39966
39997
  this.fadeToDims = this.frameDims[idx];
@@ -39979,13 +40010,28 @@ var TextureFrameSeriesAnimator;
39979
40010
  for (let i = 0; i < n; i++) {
39980
40011
  blended[i] = from[i] + (to[i] - from[i]) * eased;
39981
40012
  }
39982
- this.presentPixels(blended, this.fadeToDims);
40013
+ let blendedValue = null;
40014
+ const toValue = this.valueCache[this.fadeToIndex];
40015
+ if (toValue) {
40016
+ const fromValue = this.fadeFromValuePixels;
40017
+ blendedValue = new Uint8ClampedArray(toValue.length);
40018
+ if (fromValue && fromValue.length === toValue.length) {
40019
+ for (let i = 0; i < toValue.length; i++) {
40020
+ blendedValue[i] = fromValue[i] + (toValue[i] - fromValue[i]) * eased;
40021
+ }
40022
+ }
40023
+ else {
40024
+ blendedValue.set(toValue);
40025
+ }
40026
+ }
40027
+ this.presentPixels(blended, this.fadeToDims, blendedValue);
39983
40028
  if (t >= 1) {
39984
40029
  this.fadeStartMs = null;
39985
40030
  this.fadeFromPixels = null;
40031
+ this.fadeFromValuePixels = null;
39986
40032
  }
39987
40033
  }
39988
- presentPixels(pixels, dims) {
40034
+ presentPixels(pixels, dims, valuePixels) {
39989
40035
  this.poolIdx = 1 - this.poolIdx;
39990
40036
  const target = this.pool[this.poolIdx];
39991
40037
  target.width = dims.width;
@@ -39995,6 +40041,17 @@ var TextureFrameSeriesAnimator;
39995
40041
  imgData.data.set(pixels);
39996
40042
  ctx.putImageData(imgData, 0, 0);
39997
40043
  this.displayedPixels = pixels;
40044
+ if (this.valueCanvas && valuePixels) {
40045
+ this.valueCanvas.width = dims.width;
40046
+ this.valueCanvas.height = dims.height;
40047
+ const valueCtx = this.valueCanvas.getContext("2d");
40048
+ const valueImgData = valueCtx.createImageData(dims.width, dims.height);
40049
+ valueImgData.data.set(valuePixels);
40050
+ valueCtx.putImageData(valueImgData, 0, 0);
40051
+ this.displayedValuePixels = valuePixels;
40052
+ this.valueDims = dims;
40053
+ }
40054
+ this.presentVersion++;
39998
40055
  }
39999
40056
  }
40000
40057
  TextureFrameSeriesAnimator.Animator = Animator;
@@ -40006,11 +40063,732 @@ var TextureFrameSeriesAnimator;
40006
40063
  }
40007
40064
  })(TextureFrameSeriesAnimator || (TextureFrameSeriesAnimator = {}));
40008
40065
 
40066
+ /**
40067
+ * Renders a value texture as a displaced, coloured surface.
40068
+ * The source canvas is expected as RGBA: value in R, coverage in A.
40069
+ */
40070
+ var DisplacedSurfacePrimitive;
40071
+ (function (DisplacedSurfacePrimitive) {
40072
+ // Grid densities a tile can draw at, coarsest first.
40073
+ const TIERS = [32, 64, 128, 192];
40074
+ // Tiles per side the extent is cut into.
40075
+ const TILES_PER_SIDE = 6;
40076
+ // Screen pixels one grid cell should cover. Lower means finer meshes sooner.
40077
+ const TARGET_CELL_PIXELS = 12;
40078
+ // Fraction of its distance to the surface the camera must move before densities are reconsidered.
40079
+ const MOVEMENT_THRESHOLD = 0.12;
40080
+ const RESELECT_INTERVAL_MS = 400;
40081
+ // Skirt depth as a fraction of the displaced range. The sheet is translucent, so an oversized
40082
+ // skirt blends through it and paints a seam down every tile boundary.
40083
+ const SKIRT_FRACTION = 0.015;
40084
+ // Alpha below which a texel counts as no-data and is not drawn at all.
40085
+ const COVERAGE_CUTOFF = 0.35;
40086
+ // With no exaggeration stated, the full value range is drawn as this fraction of the extent's shorter side.
40087
+ const AUTO_RELIEF_FRACTION = 0.02;
40088
+ // Terrain is sampled on this grid and interpolated between samples.
40089
+ // Per-texel sampling would be hundreds of thousands of lookups for a surface whose own data is far coarser than that.
40090
+ const GROUND_SAMPLES_PER_SIDE = 64;
40091
+ // Terrain detail level the samples are taken at.
40092
+ // Most-detailed would fetch every tile under a parcel that can be a hundred kilometres across.
40093
+ const GROUND_SAMPLE_LEVEL = 11;
40094
+ // Height window the packed ground texture covers, wide enough for any terrain on Earth.
40095
+ const GROUND_MIN_METRES = -1000;
40096
+ const GROUND_MAX_METRES = 9000;
40097
+ const DEFAULT_LOW_COLOR = { red: 255, green: 255, blue: 255, alpha: 0.12 };
40098
+ const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
40099
+ const VERTEX_SHADER_GLSL300 = `
40100
+ in vec3 position3DHigh;
40101
+ in vec3 position3DLow;
40102
+ in vec3 normal;
40103
+ in vec2 st;
40104
+ in float skirt;
40105
+
40106
+ uniform sampler2D u_valueTexture;
40107
+ uniform sampler2D u_groundTexture;
40108
+ uniform float u_valueMin;
40109
+ uniform float u_valueRange;
40110
+ uniform float u_exaggeration;
40111
+ uniform float u_baseHeight;
40112
+ uniform float u_skirtDepth;
40113
+ uniform float u_groundWeight;
40114
+ uniform vec2 u_groundRange;
40115
+ uniform vec2 u_groundSize;
40116
+
40117
+ out vec2 v_st;
40118
+ out float v_value;
40119
+ out float v_coverage;
40120
+
40121
+ float decodeGround(vec2 uv) {
40122
+ // Channels arrive normalised to 0..1, so they go back to bytes before being reassembled.
40123
+ vec3 packed = texture(u_groundTexture, uv).rgb * 255.0;
40124
+ float normalised = dot(packed, vec3(65536.0, 256.0, 1.0)) / 16777215.0;
40125
+ return u_groundRange.x + normalised * (u_groundRange.y - u_groundRange.x);
40126
+ }
40127
+
40128
+ /*
40129
+ * Bilinear by hand, because the height is packed across three channels and hardware filtering would
40130
+ * blend the packing rather than the heights, turning a smooth slope into noise at every texel edge.
40131
+ */
40132
+ float sampleGround(vec2 uv) {
40133
+ vec2 texel = 1.0 / u_groundSize;
40134
+ vec2 coord = uv * u_groundSize - 0.5;
40135
+ vec2 base = floor(coord);
40136
+ vec2 f = coord - base;
40137
+ vec2 origin = (base + 0.5) * texel;
40138
+ float h00 = decodeGround(origin);
40139
+ float h10 = decodeGround(origin + vec2(texel.x, 0.0));
40140
+ float h01 = decodeGround(origin + vec2(0.0, texel.y));
40141
+ float h11 = decodeGround(origin + texel);
40142
+ return mix(mix(h00, h10, f.x), mix(h01, h11, f.x), f.y);
40143
+ }
40144
+
40145
+ void main() {
40146
+ vec4 texel = texture(u_valueTexture, st);
40147
+ v_st = st;
40148
+ v_value = texel.r;
40149
+ v_coverage = texel.a;
40150
+
40151
+ float metres = u_valueMin + texel.r * u_valueRange;
40152
+
40153
+ // Value zero sits at the polygon's own altitude, plus the terrain underneath when the polygon
40154
+ // is following the ground rather than absolutely positioned.
40155
+ float ground = sampleGround(st) * u_groundWeight;
40156
+ float displacement = u_baseHeight + ground + metres * u_exaggeration * texel.a - skirt * u_skirtDepth;
40157
+
40158
+ // Added to the LOW half of the encoded position: a metre-scale offset added to the high half
40159
+ // would be lost to float32 rounding at an earth radius.
40160
+ vec4 p = czm_translateRelativeToEye(position3DHigh, position3DLow + normal * displacement);
40161
+ gl_Position = czm_modelViewProjectionRelativeToEye * p;
40162
+ }
40163
+ `;
40164
+ const FRAGMENT_SHADER_GLSL300 = `
40165
+ uniform sampler2D u_valueTexture;
40166
+ uniform vec4 u_lowColor;
40167
+ uniform vec4 u_highColor;
40168
+ uniform float u_coverageCutoff;
40169
+ uniform vec2 u_texelSize;
40170
+ uniform float u_valueRange;
40171
+ uniform float u_exaggeration;
40172
+ uniform float u_metresPerTexel;
40173
+
40174
+ in vec2 v_st;
40175
+ in float v_value;
40176
+ in float v_coverage;
40177
+
40178
+ void main() {
40179
+ if (v_coverage < u_coverageCutoff) {
40180
+ discard;
40181
+ }
40182
+ vec4 color = mix(u_lowColor, u_highColor, v_value);
40183
+
40184
+ // Relief shading from the value gradient, so the displacement reads at a distance without
40185
+ // recomputing vertex normals every time the texture changes.
40186
+ float left = texture(u_valueTexture, v_st - vec2(u_texelSize.x, 0.0)).r;
40187
+ float right = texture(u_valueTexture, v_st + vec2(u_texelSize.x, 0.0)).r;
40188
+ float down = texture(u_valueTexture, v_st - vec2(0.0, u_texelSize.y)).r;
40189
+ float up = texture(u_valueTexture, v_st + vec2(0.0, u_texelSize.y)).r;
40190
+ float scale = u_valueRange * u_exaggeration;
40191
+ vec3 n = normalize(vec3((left - right) * scale, (down - up) * scale, 2.0 * u_metresPerTexel));
40192
+ float lambert = clamp(dot(n, normalize(vec3(-0.5, -0.6, 0.62))), 0.0, 1.0);
40193
+ color.rgb *= 0.45 + 0.9 * lambert;
40194
+
40195
+ out_FragColor = vec4(color.rgb, color.a * v_coverage);
40196
+ }
40197
+ `;
40198
+ /*
40199
+ * Rewrites a GLSL 300 es source into the GLSL 100 dialect older Cesium builds author in.
40200
+ */
40201
+ function toGlsl100(source, isFragment) {
40202
+ let result = source
40203
+ .replace(/\btexture\s*\(/g, "texture2D(")
40204
+ .replace(/^in\s+/gm, isFragment ? "varying " : "attribute ")
40205
+ .replace(/^out\s+/gm, "varying ");
40206
+ if (isFragment) {
40207
+ result = result.replace(/\bout_FragColor\b/g, "gl_FragColor");
40208
+ }
40209
+ return result;
40210
+ }
40211
+ let _glsl300Support = null;
40212
+ /*
40213
+ * Works out which GLSL dialect this host's Cesium authors shaders in.
40214
+ */
40215
+ function usesGlsl300(context) {
40216
+ if (_glsl300Support !== null) {
40217
+ return _glsl300Support;
40218
+ }
40219
+ let modernizes = false;
40220
+ try {
40221
+ const probe = new ShaderSource({
40222
+ sources: ["varying float v_probe;\nvoid main() { gl_Position = vec4(0.0); }"],
40223
+ includeBuiltIns: false
40224
+ });
40225
+ const combined = probe.createCombinedVertexShader(context);
40226
+ modernizes = combined.indexOf("varying") < 0;
40227
+ }
40228
+ catch (e) {
40229
+ // No probe means no evidence the host rewrites anything, so trust the context alone.
40230
+ modernizes = false;
40231
+ }
40232
+ _glsl300Support = Boolean(context.webgl2) && !modernizes;
40233
+ return _glsl300Support;
40234
+ }
40235
+ /*
40236
+ * Places grid node (fx, fy) on the ellipsoid, walking equal steps of east/north metres.
40237
+ * Cesium maps a polygon's material across a tangent-plane rectangle and the generated raster is
40238
+ * linear in a projected CRS, so stepping in degrees instead would skew the surface against the flat-textured polygon it replaces.
40239
+ */
40240
+ function makePlacer(extent) {
40241
+ const ellipsoid = Ellipsoid.WGS84;
40242
+ const centre = Cartesian3.fromDegrees((extent.West + extent.East) / 2, (extent.South + extent.North) / 2, 0);
40243
+ const frame = Transforms.eastNorthUpToFixedFrame(centre);
40244
+ const toLocal = Matrix4.inverse(frame, new Matrix4());
40245
+ const southWest = Matrix4.multiplyByPoint(toLocal, Cartesian3.fromDegrees(extent.West, extent.South, 0), new Cartesian3());
40246
+ const northEast = Matrix4.multiplyByPoint(toLocal, Cartesian3.fromDegrees(extent.East, extent.North, 0), new Cartesian3());
40247
+ return (fx, fy) => {
40248
+ const local = new Cartesian3(Math$1.lerp(southWest.x, northEast.x, fx), Math$1.lerp(northEast.y, southWest.y, fy), 0);
40249
+ const world = Matrix4.multiplyByPoint(frame, local, new Cartesian3());
40250
+ return ellipsoid.scaleToGeodeticSurface(world, world);
40251
+ };
40252
+ }
40253
+ /*
40254
+ * Builds one tile's grid, plus a duplicated perimeter ring the shader pushes downwards to cover
40255
+ * the crack where a neighbouring tile at a different density interpolates the shared edge.
40256
+ */
40257
+ function buildGeometry(extent, resolution, patch) {
40258
+ const cols = resolution;
40259
+ const rows = resolution;
40260
+ const gridCount = (cols + 1) * (rows + 1);
40261
+ const perimeterCount = 2 * (cols + 1) + 2 * (rows + 1) - 4;
40262
+ const count = gridCount + perimeterCount;
40263
+ const positions = new Float64Array(count * 3);
40264
+ const normals = new Float32Array(count * 3);
40265
+ const sts = new Float32Array(count * 2);
40266
+ const skirts = new Float32Array(count);
40267
+ const ellipsoid = Ellipsoid.WGS84;
40268
+ const place = makePlacer(extent);
40269
+ let v = 0;
40270
+ for (let row = 0; row <= rows; row++) {
40271
+ // Texture row 0 is the north edge, so t runs opposite to northing.
40272
+ const fy = Math$1.lerp(patch.v0, patch.v1, row / rows);
40273
+ for (let col = 0; col <= cols; col++) {
40274
+ const fx = Math$1.lerp(patch.u0, patch.u1, col / cols);
40275
+ const position = place(fx, fy);
40276
+ positions[v * 3] = position.x;
40277
+ positions[v * 3 + 1] = position.y;
40278
+ positions[v * 3 + 2] = position.z;
40279
+ const up = ellipsoid.geodeticSurfaceNormal(position, new Cartesian3());
40280
+ normals[v * 3] = up.x;
40281
+ normals[v * 3 + 1] = up.y;
40282
+ normals[v * 3 + 2] = up.z;
40283
+ sts[v * 2] = fx;
40284
+ sts[v * 2 + 1] = fy;
40285
+ v++;
40286
+ }
40287
+ }
40288
+ const indices = new Uint32Array(cols * rows * 6 + perimeterCount * 6);
40289
+ let i = 0;
40290
+ for (let row = 0; row < rows; row++) {
40291
+ for (let col = 0; col < cols; col++) {
40292
+ const topLeft = row * (cols + 1) + col;
40293
+ const topRight = topLeft + 1;
40294
+ const bottomLeft = topLeft + (cols + 1);
40295
+ const bottomRight = bottomLeft + 1;
40296
+ indices[i++] = topLeft;
40297
+ indices[i++] = bottomLeft;
40298
+ indices[i++] = topRight;
40299
+ indices[i++] = topRight;
40300
+ indices[i++] = bottomLeft;
40301
+ indices[i++] = bottomRight;
40302
+ }
40303
+ }
40304
+ // Perimeter walked as one closed loop, so consecutive entries are always neighbours.
40305
+ const loop = [];
40306
+ for (let col = 0; col <= cols; col++) {
40307
+ loop.push(col);
40308
+ }
40309
+ for (let row = 1; row <= rows; row++) {
40310
+ loop.push(row * (cols + 1) + cols);
40311
+ }
40312
+ for (let col = cols - 1; col >= 0; col--) {
40313
+ loop.push(rows * (cols + 1) + col);
40314
+ }
40315
+ for (let row = rows - 1; row >= 1; row--) {
40316
+ loop.push(row * (cols + 1));
40317
+ }
40318
+ const skirtStart = gridCount;
40319
+ loop.forEach((gridIndex, k) => {
40320
+ const target = skirtStart + k;
40321
+ positions[target * 3] = positions[gridIndex * 3];
40322
+ positions[target * 3 + 1] = positions[gridIndex * 3 + 1];
40323
+ positions[target * 3 + 2] = positions[gridIndex * 3 + 2];
40324
+ normals[target * 3] = normals[gridIndex * 3];
40325
+ normals[target * 3 + 1] = normals[gridIndex * 3 + 1];
40326
+ normals[target * 3 + 2] = normals[gridIndex * 3 + 2];
40327
+ sts[target * 2] = sts[gridIndex * 2];
40328
+ sts[target * 2 + 1] = sts[gridIndex * 2 + 1];
40329
+ skirts[target] = 1;
40330
+ });
40331
+ for (let k = 0; k < loop.length; k++) {
40332
+ const nextK = (k + 1) % loop.length;
40333
+ indices[i++] = loop[k];
40334
+ indices[i++] = skirtStart + k;
40335
+ indices[i++] = loop[nextK];
40336
+ indices[i++] = loop[nextK];
40337
+ indices[i++] = skirtStart + k;
40338
+ indices[i++] = skirtStart + nextK;
40339
+ }
40340
+ const geometry = new Geometry({
40341
+ attributes: {
40342
+ position: new GeometryAttribute({
40343
+ componentDatatype: ComponentDatatype.DOUBLE,
40344
+ componentsPerAttribute: 3,
40345
+ values: positions
40346
+ }),
40347
+ normal: new GeometryAttribute({
40348
+ componentDatatype: ComponentDatatype.FLOAT,
40349
+ componentsPerAttribute: 3,
40350
+ values: normals
40351
+ }),
40352
+ st: new GeometryAttribute({
40353
+ componentDatatype: ComponentDatatype.FLOAT,
40354
+ componentsPerAttribute: 2,
40355
+ values: sts
40356
+ }),
40357
+ skirt: new GeometryAttribute({
40358
+ componentDatatype: ComponentDatatype.FLOAT,
40359
+ componentsPerAttribute: 1,
40360
+ values: skirts
40361
+ })
40362
+ },
40363
+ indices: indices,
40364
+ primitiveType: PrimitiveType.TRIANGLES,
40365
+ boundingSphere: BoundingSphere.fromVertices(Array.prototype.slice.call(positions))
40366
+ });
40367
+ // Splits position into the high/low pair Cesium's relative-to-eye shaders expect.
40368
+ GeometryPipeline.encodeAttribute(geometry, "position", "position3DHigh", "position3DLow");
40369
+ return geometry;
40370
+ }
40371
+ class Surface {
40372
+ constructor(options, hasCoverage) {
40373
+ var _a, _b, _c, _d;
40374
+ this.show = true;
40375
+ this.valueSource = null;
40376
+ this.lastPresentVersion = -1;
40377
+ this.texture = null;
40378
+ this.textureDirty = true;
40379
+ this.shaderProgram = null;
40380
+ this.renderState = null;
40381
+ this.destroyed = false;
40382
+ this.lastEyePosition = null;
40383
+ this.lastSelectionMs = -Infinity;
40384
+ this.selectionDirty = true;
40385
+ this.groundTexture = null;
40386
+ this.groundPixels = null;
40387
+ this.groundRequested = false;
40388
+ this.followGround = Boolean(options.followGround);
40389
+ this.terrainProvider = options.terrainProvider || null;
40390
+ this.extent = options.extent;
40391
+ this.source = options.source || null;
40392
+ this.valueMin = (_a = options.valueMin) !== null && _a !== void 0 ? _a : 0;
40393
+ this.valueMax = (_b = options.valueMax) !== null && _b !== void 0 ? _b : 1;
40394
+ this.exaggeration = (_c = options.exaggeration) !== null && _c !== void 0 ? _c : autoExaggeration(this.extent, this.valueMin, this.valueMax);
40395
+ this.baseHeight = (_d = options.baseHeight) !== null && _d !== void 0 ? _d : 0;
40396
+ this.lowColor = toCesiumColor(options.lowColor || DEFAULT_LOW_COLOR);
40397
+ this.highColor = toCesiumColor(options.highColor || DEFAULT_HIGH_COLOR);
40398
+ this.tiles = buildTiles(hasCoverage);
40399
+ }
40400
+ GetFollowsGround() {
40401
+ return this.followGround;
40402
+ }
40403
+ GetBaseHeight() {
40404
+ return this.baseHeight;
40405
+ }
40406
+ GetExaggeration() {
40407
+ return this.exaggeration;
40408
+ }
40409
+ GetExtent() {
40410
+ return this.extent;
40411
+ }
40412
+ SetExaggeration(exaggeration) {
40413
+ this.exaggeration = exaggeration;
40414
+ }
40415
+ SetBaseHeight(baseHeight) {
40416
+ this.baseHeight = baseHeight;
40417
+ }
40418
+ /**
40419
+ * Feeds the surface from an animated frame series.
40420
+ * Height and colour advance with the clock without the caller having to push each frame in.
40421
+ */
40422
+ BindAnimator(source) {
40423
+ this.valueSource = source;
40424
+ }
40425
+ /**
40426
+ * Tells the surface its source canvas has been redrawn and the texture needs re-uploading.
40427
+ */
40428
+ MarkSourceDirty() {
40429
+ this.textureDirty = true;
40430
+ }
40431
+ update(frameState) {
40432
+ if (!this.show || this.destroyed || this.tiles.length === 0) {
40433
+ return;
40434
+ }
40435
+ const context = frameState.context;
40436
+ if (this.valueSource) {
40437
+ const canvas = this.valueSource.GetValueCanvas();
40438
+ if (!canvas) {
40439
+ // No frame decoded yet, so there is nothing to displace against.
40440
+ return;
40441
+ }
40442
+ const version = this.valueSource.GetPresentVersion();
40443
+ if (canvas !== this.source || version !== this.lastPresentVersion) {
40444
+ this.source = canvas;
40445
+ this.lastPresentVersion = version;
40446
+ this.textureDirty = true;
40447
+ }
40448
+ }
40449
+ if (!this.source || !this.source.width || !this.source.height) {
40450
+ return;
40451
+ }
40452
+ this.syncTexture(context);
40453
+ this.requestGround();
40454
+ this.syncGroundTexture(context);
40455
+ // A tile needs one mesh before it has a bounding sphere to measure against.
40456
+ for (const tile of this.tiles) {
40457
+ if (!tile.centre) {
40458
+ this.ensureMesh(context, tile, TIERS[0]);
40459
+ }
40460
+ }
40461
+ const nowMs = now();
40462
+ if (this.shouldReselect(frameState, nowMs)) {
40463
+ this.selectTiers(frameState);
40464
+ this.lastSelectionMs = nowMs;
40465
+ this.lastEyePosition = Cartesian3.clone(frameState.camera.positionWC, this.lastEyePosition);
40466
+ this.selectionDirty = false;
40467
+ }
40468
+ for (const tile of this.tiles) {
40469
+ const mesh = this.ensureMesh(context, tile, tile.tier);
40470
+ frameState.commandList.push(this.commandFor(context, tile, mesh));
40471
+ }
40472
+ }
40473
+ isDestroyed() {
40474
+ return this.destroyed;
40475
+ }
40476
+ destroy() {
40477
+ this.destroyed = true;
40478
+ for (const tile of this.tiles) {
40479
+ tile.meshes.forEach((mesh) => mesh.vertexArray.destroy());
40480
+ tile.meshes.clear();
40481
+ tile.command = null;
40482
+ }
40483
+ if (this.shaderProgram) {
40484
+ this.shaderProgram.destroy();
40485
+ this.shaderProgram = null;
40486
+ }
40487
+ if (this.texture) {
40488
+ this.texture.destroy();
40489
+ this.texture = null;
40490
+ }
40491
+ if (this.groundTexture) {
40492
+ this.groundTexture.destroy();
40493
+ this.groundTexture = null;
40494
+ }
40495
+ return undefined;
40496
+ }
40497
+ /*
40498
+ * Samples terrain across the extent once and packs it into a texture the vertex shader reads.
40499
+ *
40500
+ * A texture rather than heights baked into the meshes, because the samples arrive
40501
+ * asynchronously and every cached tile mesh would otherwise have to be rebuilt when they land.
40502
+ */
40503
+ requestGround() {
40504
+ if (this.groundRequested || !this.followGround) {
40505
+ return;
40506
+ }
40507
+ this.groundRequested = true;
40508
+ const sampler = sampleTerrain;
40509
+ if (!this.terrainProvider || typeof sampler !== "function") {
40510
+ return;
40511
+ }
40512
+ const positions = [];
40513
+ for (let row = 0; row < GROUND_SAMPLES_PER_SIDE; row++) {
40514
+ const fy = row / (GROUND_SAMPLES_PER_SIDE - 1);
40515
+ const latitude = Math$1.lerp(this.extent.North, this.extent.South, fy);
40516
+ for (let col = 0; col < GROUND_SAMPLES_PER_SIDE; col++) {
40517
+ const fx = col / (GROUND_SAMPLES_PER_SIDE - 1);
40518
+ const longitude = Math$1.lerp(this.extent.West, this.extent.East, fx);
40519
+ positions.push(Cartographic.fromDegrees(longitude, latitude));
40520
+ }
40521
+ }
40522
+ Promise.resolve(sampler(this.terrainProvider, GROUND_SAMPLE_LEVEL, positions))
40523
+ .then((sampled) => {
40524
+ if (this.destroyed || !sampled) {
40525
+ return;
40526
+ }
40527
+ const pixels = new Uint8Array(GROUND_SAMPLES_PER_SIDE * GROUND_SAMPLES_PER_SIDE * 4);
40528
+ const span = GROUND_MAX_METRES - GROUND_MIN_METRES;
40529
+ for (let i = 0; i < sampled.length; i++) {
40530
+ const height = sampled[i] && isFinite(sampled[i].height) ? sampled[i].height : 0;
40531
+ const clamped = Math.min(GROUND_MAX_METRES, Math.max(GROUND_MIN_METRES, height));
40532
+ const packed = Math.round(((clamped - GROUND_MIN_METRES) / span) * 16777215);
40533
+ pixels[i * 4] = (packed >> 16) & 255;
40534
+ pixels[i * 4 + 1] = (packed >> 8) & 255;
40535
+ pixels[i * 4 + 2] = packed & 255;
40536
+ pixels[i * 4 + 3] = 255;
40537
+ }
40538
+ this.groundPixels = pixels;
40539
+ })
40540
+ .catch(() => {
40541
+ // Terrain that cannot be sampled leaves the surface on the ellipsoid, which is
40542
+ // where a clamped polygon sat before this existed.
40543
+ });
40544
+ }
40545
+ /*
40546
+ * Uploads whatever ground samples exist, and a flat stand-in until they arrive, so the
40547
+ * shader always has a texture bound whether or not this surface follows the ground.
40548
+ */
40549
+ syncGroundTexture(context) {
40550
+ if (this.groundTexture && (!this.groundPixels || this.groundTexture.width === GROUND_SAMPLES_PER_SIDE)) {
40551
+ return;
40552
+ }
40553
+ if (this.groundTexture) {
40554
+ this.groundTexture.destroy();
40555
+ }
40556
+ const zero = Math.round(((0 - GROUND_MIN_METRES) / (GROUND_MAX_METRES - GROUND_MIN_METRES)) * 16777215);
40557
+ const size = this.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1;
40558
+ const arrayBufferView = this.groundPixels
40559
+ || new Uint8Array([(zero >> 16) & 255, (zero >> 8) & 255, zero & 255, 255]);
40560
+ this.groundTexture = new Texture({
40561
+ context,
40562
+ pixelFormat: PixelFormat.RGBA,
40563
+ source: { width: size, height: size, arrayBufferView },
40564
+ flipY: false,
40565
+ sampler: new Sampler({
40566
+ wrapS: TextureWrap.CLAMP_TO_EDGE,
40567
+ wrapT: TextureWrap.CLAMP_TO_EDGE,
40568
+ // Nearest, because the shader interpolates decoded heights itself.
40569
+ minificationFilter: TextureMinificationFilter.NEAREST,
40570
+ magnificationFilter: TextureMagnificationFilter.NEAREST
40571
+ })
40572
+ });
40573
+ }
40574
+ /*
40575
+ * Ground distance between neighbouring texels, so the shading normal is built from a real
40576
+ * slope rather than a made up one.
40577
+ */
40578
+ metresPerTexel() {
40579
+ const southWest = Cartesian3.fromDegrees(this.extent.West, this.extent.South, 0);
40580
+ const southEast = Cartesian3.fromDegrees(this.extent.East, this.extent.South, 0);
40581
+ const width = Cartesian3.distance(southWest, southEast);
40582
+ return Math.max(1, width / Math.max(1, this.source ? this.source.width : 1));
40583
+ }
40584
+ syncTexture(context) {
40585
+ const sizeChanged = this.texture
40586
+ && (this.texture.width !== this.source.width || this.texture.height !== this.source.height);
40587
+ if (!this.texture || sizeChanged) {
40588
+ if (this.texture) {
40589
+ this.texture.destroy();
40590
+ }
40591
+ this.texture = new Texture({
40592
+ context,
40593
+ source: this.source,
40594
+ // Cesium flips canvas rows on upload by default, which would put the archive's
40595
+ // row 0 (its north edge) at the south edge and mirror the whole surface.
40596
+ flipY: false,
40597
+ sampler: new Sampler({
40598
+ wrapS: TextureWrap.CLAMP_TO_EDGE,
40599
+ wrapT: TextureWrap.CLAMP_TO_EDGE,
40600
+ minificationFilter: TextureMinificationFilter.LINEAR,
40601
+ magnificationFilter: TextureMagnificationFilter.LINEAR
40602
+ })
40603
+ });
40604
+ this.textureDirty = false;
40605
+ return;
40606
+ }
40607
+ if (this.textureDirty) {
40608
+ this.texture.copyFrom({ source: this.source });
40609
+ this.textureDirty = false;
40610
+ }
40611
+ }
40612
+ ensureMesh(context, tile, tier) {
40613
+ const cached = tile.meshes.get(tier);
40614
+ if (cached) {
40615
+ return cached;
40616
+ }
40617
+ const geometry = buildGeometry(this.extent, tier, tile.patch);
40618
+ const attributeLocations = GeometryPipeline.createAttributeLocations(geometry);
40619
+ const mesh = {
40620
+ attributeLocations,
40621
+ vertexArray: VertexArray.fromGeometry({
40622
+ context,
40623
+ geometry,
40624
+ attributeLocations,
40625
+ bufferUsage: BufferUsage.STATIC_DRAW
40626
+ }),
40627
+ boundingSphere: geometry.boundingSphere
40628
+ };
40629
+ tile.meshes.set(tier, mesh);
40630
+ tile.centre = geometry.boundingSphere.center;
40631
+ tile.radius = geometry.boundingSphere.radius;
40632
+ return mesh;
40633
+ }
40634
+ /*
40635
+ * Picks each tile's density from how much screen it covers.
40636
+ */
40637
+ selectTiers(frameState) {
40638
+ const camera = frameState.camera;
40639
+ const fovy = camera.frustum && camera.frustum.fovy ? camera.frustum.fovy : Math$1.PI_OVER_THREE;
40640
+ const pixelsPerRadian = frameState.context.drawingBufferHeight / fovy;
40641
+ for (const tile of this.tiles) {
40642
+ if (!tile.centre) {
40643
+ tile.tier = TIERS[0];
40644
+ continue;
40645
+ }
40646
+ const distance = Math.max(1, Cartesian3.distance(camera.positionWC, tile.centre) - tile.radius);
40647
+ const screenPixels = (2 * tile.radius / distance) * pixelsPerRadian;
40648
+ const wanted = screenPixels / TARGET_CELL_PIXELS;
40649
+ let tier = TIERS[0];
40650
+ for (const candidate of TIERS) {
40651
+ if (candidate <= wanted) {
40652
+ tier = candidate;
40653
+ }
40654
+ }
40655
+ tile.tier = tier;
40656
+ }
40657
+ }
40658
+ shouldReselect(frameState, nowMs) {
40659
+ if (this.selectionDirty) {
40660
+ return true;
40661
+ }
40662
+ if (nowMs - this.lastSelectionMs < RESELECT_INTERVAL_MS) {
40663
+ return false;
40664
+ }
40665
+ const eye = frameState.camera.positionWC;
40666
+ if (!this.lastEyePosition) {
40667
+ return true;
40668
+ }
40669
+ const moved = Cartesian3.distance(eye, this.lastEyePosition);
40670
+ const anchor = this.tiles[0].centre;
40671
+ const reference = anchor ? Math.max(1, Cartesian3.distance(eye, anchor)) : 1;
40672
+ return moved / reference > MOVEMENT_THRESHOLD;
40673
+ }
40674
+ commandFor(context, tile, mesh) {
40675
+ if (!this.shaderProgram) {
40676
+ const glsl300 = usesGlsl300(context);
40677
+ this.shaderProgram = ShaderProgram.fromCache({
40678
+ context,
40679
+ vertexShaderSource: glsl300 ? VERTEX_SHADER_GLSL300 : toGlsl100(VERTEX_SHADER_GLSL300, false),
40680
+ fragmentShaderSource: glsl300 ? FRAGMENT_SHADER_GLSL300 : toGlsl100(FRAGMENT_SHADER_GLSL300, true),
40681
+ attributeLocations: mesh.attributeLocations
40682
+ });
40683
+ this.renderState = RenderState.fromCache({
40684
+ depthTest: { enabled: true },
40685
+ // Translucent ramp: blend, and leave the depth buffer to the opaque world.
40686
+ depthMask: false,
40687
+ blending: BlendingState.ALPHA_BLEND,
40688
+ cull: { enabled: false }
40689
+ });
40690
+ }
40691
+ if (!tile.command) {
40692
+ const self = this;
40693
+ tile.command = new DrawCommand({
40694
+ shaderProgram: this.shaderProgram,
40695
+ renderState: this.renderState,
40696
+ pass: Pass.TRANSLUCENT,
40697
+ modelMatrix: Matrix4.IDENTITY,
40698
+ owner: this,
40699
+ uniformMap: {
40700
+ u_valueTexture: () => self.texture,
40701
+ u_groundTexture: () => self.groundTexture,
40702
+ u_groundWeight: () => (self.followGround && self.groundPixels ? 1 : 0),
40703
+ u_groundRange: () => new Cartesian2(GROUND_MIN_METRES, GROUND_MAX_METRES),
40704
+ u_groundSize: () => new Cartesian2(self.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1, self.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1),
40705
+ u_metresPerTexel: () => self.metresPerTexel(),
40706
+ u_valueMin: () => self.valueMin,
40707
+ u_valueRange: () => self.valueMax - self.valueMin,
40708
+ u_exaggeration: () => self.exaggeration,
40709
+ u_baseHeight: () => self.baseHeight,
40710
+ u_skirtDepth: () => Math.max(2, Math.abs(self.valueMax - self.valueMin) * self.exaggeration * SKIRT_FRACTION),
40711
+ u_coverageCutoff: () => COVERAGE_CUTOFF,
40712
+ u_lowColor: () => self.lowColor,
40713
+ u_highColor: () => self.highColor,
40714
+ u_texelSize: () => new Cartesian2(1 / self.source.width, 1 / self.source.height)
40715
+ }
40716
+ });
40717
+ }
40718
+ // Bounding volume belongs to the mesh, so it follows whichever tier is drawing.
40719
+ tile.command.vertexArray = mesh.vertexArray;
40720
+ tile.command.boundingVolume = mesh.boundingSphere;
40721
+ return tile.command;
40722
+ }
40723
+ }
40724
+ DisplacedSurfacePrimitive.Surface = Surface;
40725
+ /*
40726
+ * Cuts the extent into tiles, dropping any the caller says carry no data.
40727
+ */
40728
+ function buildTiles(hasCoverage) {
40729
+ const tiles = [];
40730
+ for (let row = 0; row < TILES_PER_SIDE; row++) {
40731
+ for (let col = 0; col < TILES_PER_SIDE; col++) {
40732
+ const patch = {
40733
+ u0: col / TILES_PER_SIDE,
40734
+ u1: (col + 1) / TILES_PER_SIDE,
40735
+ v0: row / TILES_PER_SIDE,
40736
+ v1: (row + 1) / TILES_PER_SIDE
40737
+ };
40738
+ if (hasCoverage && !hasCoverage(patch)) {
40739
+ continue;
40740
+ }
40741
+ tiles.push({
40742
+ patch,
40743
+ meshes: new Map(),
40744
+ tier: TIERS[0],
40745
+ command: null,
40746
+ centre: null,
40747
+ radius: 0
40748
+ });
40749
+ }
40750
+ }
40751
+ return tiles;
40752
+ }
40753
+ /**
40754
+ * Exaggeration that makes the full value range stand AUTO_RELIEF_FRACTION of the extent tall.
40755
+ *
40756
+ * Derived from the whole extent rather than per tile: scaling each tile to its own size would
40757
+ * make neighbours disagree along their shared edge and tear the surface apart.
40758
+ * @param extent
40759
+ * @param valueMin
40760
+ * @param valueMax
40761
+ */
40762
+ function autoExaggeration(extent, valueMin, valueMax) {
40763
+ const range = Math.abs(valueMax - valueMin);
40764
+ if (!(range > 0)) {
40765
+ return 1;
40766
+ }
40767
+ const southWest = Cartesian3.fromDegrees(extent.West, extent.South, 0);
40768
+ const southEast = Cartesian3.fromDegrees(extent.East, extent.South, 0);
40769
+ const northWest = Cartesian3.fromDegrees(extent.West, extent.North, 0);
40770
+ const shorterSide = Math.min(Cartesian3.distance(southWest, southEast), Cartesian3.distance(southWest, northWest));
40771
+ if (!(shorterSide > 0)) {
40772
+ return 1;
40773
+ }
40774
+ return (shorterSide * AUTO_RELIEF_FRACTION) / range;
40775
+ }
40776
+ DisplacedSurfacePrimitive.autoExaggeration = autoExaggeration;
40777
+ function toCesiumColor(color) {
40778
+ return new Color(color.red / 255, color.green / 255, color.blue / 255, color.alpha);
40779
+ }
40780
+ function now() {
40781
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
40782
+ }
40783
+ })(DisplacedSurfacePrimitive || (DisplacedSurfacePrimitive = {}));
40784
+
40009
40785
  const TEXTURE_FRAME_SERIES_ANIMATOR_KEY = "TextureFrameSeriesAnimator.Animator";
40786
+ const EXTRUSION_ANIMATOR_KEY = "TextureFrameSeriesAnimator.ExtrusionAnimator";
40787
+ const DISPLACED_SURFACE_KEY = "DisplacedSurfacePrimitive.Surface";
40010
40788
  var EntityRenderEnginePolygon;
40011
40789
  (function (EntityRenderEnginePolygon) {
40012
40790
  async function Render(params) {
40013
- var _a, _b, _c;
40791
+ var _a, _b, _c, _d;
40014
40792
  const entity = params.entity;
40015
40793
  const style = params.style;
40016
40794
  const pRings = Entity$1.GetValue({
@@ -40043,14 +40821,36 @@ var EntityRenderEnginePolygon;
40043
40821
  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
40822
  }
40045
40823
  }
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;
40824
+ const usesTextureExtrusion = (Boolean(style.useExtrusion) &&
40825
+ style.extrusionType === Style.EPolygonExtrusionType.Texture);
40826
+ let extrusionArchive = null;
40827
+ if (usesTextureExtrusion && !params.offline) {
40828
+ try {
40829
+ const resolved = await resolveTextureValue({
40830
+ api: params.api,
40831
+ textureValue: style.extrusionTexture || style.texture,
40832
+ style,
40833
+ entity,
40834
+ tags: params.tags
40835
+ });
40836
+ extrusionArchive = resolved.frameArchive || null;
40837
+ }
40838
+ catch (e) {
40839
+ 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);
40840
+ }
40841
+ }
40842
+ const drawsDisplacedSurface = Boolean(extrusionArchive);
40843
+ const hasFill = (drawsDisplacedSurface ? true
40844
+ : fillType === Style.EPolygonFillType.Texture
40845
+ ? Boolean(textureDataUri || frameArchive || cFillColor.alpha > 0)
40846
+ : cFillColor.alpha > 0);
40847
+ const fillMaterial = drawsDisplacedSurface
40848
+ ? Color.WHITE.withAlpha(0)
40849
+ : textureDataUri
40850
+ ? new ImageMaterialProperty({ image: textureDataUri, transparent: true })
40851
+ : frameArchive
40852
+ ? Color.WHITE.withAlpha(0)
40853
+ : cFillColor;
40054
40854
  const lineColorTrace = style.lineColor ? Calculator.TraceGetColor(style.lineColor, entity, params.tags) : { value: null, effective: null };
40055
40855
  const bLineColor = lineColorTrace.value;
40056
40856
  const cLineColor = bLineColor ? ColorToCColor(bLineColor) : Color.fromCssColorString("rgba(80, 80, 80, 0.8)");
@@ -40073,19 +40873,21 @@ var EntityRenderEnginePolygon;
40073
40873
  return null;
40074
40874
  }
40075
40875
  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);
40876
+ const outerRing = pRings.find(x => x.Facing == Geometry$1.EPolygonRingType.Boundaries);
40877
+ const points = Geometry$1.ParsePoints(outerRing === null || outerRing === void 0 ? void 0 : outerRing.LinearRing);
40078
40878
  let posses = points.map(x => Cartesian3.fromDegrees(EnsureNumber(x.longitude), EnsureNumber(x.latitude), EnsureNumber(x.altitude)));
40079
40879
  Cartes.CloseRing3(posses);
40080
- const extrusion = getPolygonExtrusion(entity, params.tags, outerRing, posses, heightRef, style);
40880
+ const extrusion = drawsDisplacedSurface
40881
+ ? { posses, value: undefined, exHeightRef: HeightReference.NONE }
40882
+ : getPolygonExtrusion(entity, params.tags, outerRing, posses, heightRef, style);
40081
40883
  posses = extrusion.posses;
40082
40884
  posses = CullDuplicateCPosses(posses);
40083
40885
  if (posses.length < 4) {
40084
40886
  return null;
40085
40887
  }
40086
- const holeRings = pRings.filter(x => x.Facing == Geometry.EPolygonRingType.Hole);
40888
+ const holeRings = pRings.filter(x => x.Facing == Geometry$1.EPolygonRingType.Hole);
40087
40889
  const holePosses = holeRings.map((x) => {
40088
- const points = Geometry.ParsePoints(x.LinearRing);
40890
+ const points = Geometry$1.ParsePoints(x.LinearRing);
40089
40891
  let holePosses = points.map(x => Cartesian3.fromDegrees(EnsureNumber(x.longitude), EnsureNumber(x.latitude), EnsureNumber(x.altitude)));
40090
40892
  holePosses = CullDuplicateCPosses(holePosses);
40091
40893
  Cartes.CloseRing3(holePosses);
@@ -40209,7 +41011,16 @@ var EntityRenderEnginePolygon;
40209
41011
  }
40210
41012
  // Must run before any material is baked below: the Animator takes over polygon.material, so a bake
40211
41013
  // 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);
41014
+ const animator = syncTextureFrameArchive(cEntity, drawsDisplacedSurface ? null : frameArchive, params.viewer, style.textureColorMask);
41015
+ syncDisplacedSurface({
41016
+ cEntity,
41017
+ extrusionArchive,
41018
+ viewer: params.viewer,
41019
+ style,
41020
+ entity,
41021
+ heightRef,
41022
+ outerRingPosses: posses
41023
+ });
40213
41024
  if (animator) {
40214
41025
  CesiumEntityStyler.SetDefaultTextureImage({
40215
41026
  entity: cEntity,
@@ -40269,7 +41080,7 @@ var EntityRenderEnginePolygon;
40269
41080
  }
40270
41081
  }
40271
41082
  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];
41083
+ let cEntityBorder = (_d = (_c = params.rendered) === null || _c === void 0 ? void 0 : _c._siblingGraphics) === null || _d === void 0 ? void 0 : _d[0];
40273
41084
  cEntity._siblingGraphics = [];
40274
41085
  if (!cEntityBorder || ((!cEntityBorder.polyline && units == "px") ||
40275
41086
  (!cEntityBorder.corridor && units == "m"))) {
@@ -40519,12 +41330,13 @@ var EntityRenderEnginePolygon;
40519
41330
  /**
40520
41331
  * Disposes a cEntity's TextureFrameSeriesAnimator.Animator (if any).
40521
41332
  */
40522
- function DisposeTextureFrameSeriesAnimator(cEntity) {
41333
+ function DisposeTextureFrameSeriesAnimator(cEntity, viewer) {
40523
41334
  const existing = cEntity === null || cEntity === void 0 ? void 0 : cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY];
40524
41335
  if (existing && !existing.IsDisposed()) {
40525
41336
  existing.Dispose();
40526
41337
  cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = null;
40527
41338
  }
41339
+ disposeDisplacedSurface(cEntity, viewer);
40528
41340
  }
40529
41341
  EntityRenderEnginePolygon.DisposeTextureFrameSeriesAnimator = DisposeTextureFrameSeriesAnimator;
40530
41342
  })(EntityRenderEnginePolygon || (EntityRenderEnginePolygon = {}));
@@ -40638,6 +41450,140 @@ async function getStyle$4(api, entity, styleId) {
40638
41450
  // Cache of tinted texture data URIs, keyed by resolved url + color mask, so repeated renders of the
40639
41451
  // same polygon (or multiple polygons sharing a texture) don't refetch/reprocess the same image.
40640
41452
  const _textureCache = new LRUCache(50);
41453
+ /**
41454
+ * Reconciles the displaced surface (and the animator feeding it) against this render's resolved
41455
+ * extrusion texture, creating, reusing or tearing down as the style demands.
41456
+ * @param params
41457
+ */
41458
+ function syncDisplacedSurface(params) {
41459
+ var _a;
41460
+ const { cEntity, extrusionArchive, viewer, style, entity, heightRef, outerRingPosses } = params;
41461
+ const existingAnimator = cEntity[EXTRUSION_ANIMATOR_KEY];
41462
+ const existingSurface = cEntity[DISPLACED_SURFACE_KEY];
41463
+ // Nothing to draw and nothing left over, which is every polygon that does not use this feature.
41464
+ if (!extrusionArchive && !existingAnimator && !existingSurface) {
41465
+ return;
41466
+ }
41467
+ const reusable = extrusionArchive
41468
+ && existingAnimator
41469
+ && !existingAnimator.IsDisposed()
41470
+ && existingAnimator.GetArchiveUrl() === extrusionArchive.url
41471
+ && existingSurface
41472
+ && !existingSurface.isDestroyed();
41473
+ const placement = surfacePlacement(entity, heightRef);
41474
+ // Whether the surface follows terrain is baked into its ground sampling, so a change there has
41475
+ // to rebuild rather than update in place.
41476
+ if (reusable && existingSurface.GetFollowsGround() === placement.followGround) {
41477
+ existingSurface.SetBaseHeight(placement.baseHeight);
41478
+ existingSurface.SetExaggeration((_a = style.extrusionExaggeration) !== null && _a !== void 0 ? _a : DisplacedSurfacePrimitive.autoExaggeration(existingSurface.GetExtent(), extrusionArchive.metadata.ValueMin, extrusionArchive.metadata.ValueMax));
41479
+ return;
41480
+ }
41481
+ disposeDisplacedSurface(cEntity, viewer);
41482
+ if (!extrusionArchive) {
41483
+ return;
41484
+ }
41485
+ const extent = archiveExtent(extrusionArchive.metadata, outerRingPosses);
41486
+ if (!extent) {
41487
+ console.warn("Polygon.Render: extrusion texture has no usable extent, skipping displaced surface.");
41488
+ return;
41489
+ }
41490
+ // Drives the value canvas only: the surface paints the colour, so the polygon's own material is
41491
+ // left transparent rather than animated underneath it.
41492
+ const animator = new TextureFrameSeriesAnimator.Animator({
41493
+ viewer,
41494
+ entity: cEntity,
41495
+ archiveUrl: extrusionArchive.url,
41496
+ frames: extrusionArchive.metadata.Frames,
41497
+ textureColorMask: style.textureColorMask,
41498
+ driveMaterial: false,
41499
+ produceValueCanvas: true
41500
+ });
41501
+ cEntity[EXTRUSION_ANIMATOR_KEY] = animator;
41502
+ const mask = style.textureColorMask;
41503
+ const surface = new DisplacedSurfacePrimitive.Surface({
41504
+ extent,
41505
+ source: null,
41506
+ baseHeight: placement.baseHeight,
41507
+ followGround: placement.followGround,
41508
+ terrainProvider: viewer && viewer.terrainProvider,
41509
+ valueMin: extrusionArchive.metadata.ValueMin,
41510
+ valueMax: extrusionArchive.metadata.ValueMax,
41511
+ exaggeration: style.extrusionExaggeration,
41512
+ lowColor: mask ? Color$1.ColorFromStr(mask.minColor) : null,
41513
+ highColor: mask ? Color$1.ColorFromStr(mask.maxColor) : null
41514
+ });
41515
+ surface.BindAnimator(animator);
41516
+ viewer.scene.primitives.add(surface);
41517
+ cEntity[DISPLACED_SURFACE_KEY] = surface;
41518
+ }
41519
+ /**
41520
+ * Turns the polygon's altitude option into the two things the displaced surface needs:
41521
+ * - how high value zero sits.
41522
+ * - whether that is measured from the terrain or from the ellipsoid.
41523
+ * @param entity
41524
+ * @param heightRef
41525
+ */
41526
+ function surfacePlacement(entity, heightRef) {
41527
+ const rawAltitude = Entity$1.GetValue({
41528
+ entity,
41529
+ path: ["Bruce", "Location", "altitude"]
41530
+ });
41531
+ const altitude = rawAltitude ? EnsureNumber(rawAltitude) : 0;
41532
+ if (heightRef === HeightReference.CLAMP_TO_GROUND) {
41533
+ return { baseHeight: 0, followGround: true };
41534
+ }
41535
+ if (heightRef === HeightReference.RELATIVE_TO_GROUND) {
41536
+ return { baseHeight: altitude, followGround: true };
41537
+ }
41538
+ return { baseHeight: altitude, followGround: false };
41539
+ }
41540
+ /**
41541
+ * The archive's own extent when it recorded one, otherwise the polygon's own bounds so archives
41542
+ * generated before the extent was written still render somewhere sensible.
41543
+ * @param metadata
41544
+ * @param posses
41545
+ */
41546
+ function archiveExtent(metadata, posses) {
41547
+ if (metadata.West != null && metadata.East != null && metadata.South != null && metadata.North != null) {
41548
+ return { West: metadata.West, East: metadata.East, South: metadata.South, North: metadata.North };
41549
+ }
41550
+ if (!posses || posses.length === 0) {
41551
+ return null;
41552
+ }
41553
+ const rectangle = Rectangle.fromCartesianArray(posses);
41554
+ return {
41555
+ West: Math$1.toDegrees(rectangle.west),
41556
+ East: Math$1.toDegrees(rectangle.east),
41557
+ South: Math$1.toDegrees(rectangle.south),
41558
+ North: Math$1.toDegrees(rectangle.north)
41559
+ };
41560
+ }
41561
+ /**
41562
+ * Tears down the displaced surface and its feeding animator, if this entity has them.
41563
+ * @param cEntity
41564
+ * @param viewer
41565
+ */
41566
+ function disposeDisplacedSurface(cEntity, viewer) {
41567
+ const surface = cEntity === null || cEntity === void 0 ? void 0 : cEntity[DISPLACED_SURFACE_KEY];
41568
+ if (surface) {
41569
+ const viewerAlive = viewer && (typeof viewer.isDestroyed !== "function" || !viewer.isDestroyed());
41570
+ if (viewerAlive && viewer.scene && viewer.scene.primitives) {
41571
+ // Removing from the collection destroys the primitive, releasing its meshes and texture.
41572
+ viewer.scene.primitives.remove(surface);
41573
+ }
41574
+ else if (!surface.isDestroyed()) {
41575
+ surface.destroy();
41576
+ }
41577
+ cEntity[DISPLACED_SURFACE_KEY] = null;
41578
+ }
41579
+ const animator = cEntity === null || cEntity === void 0 ? void 0 : cEntity[EXTRUSION_ANIMATOR_KEY];
41580
+ if (animator && !animator.IsDisposed()) {
41581
+ animator.Dispose();
41582
+ }
41583
+ if (cEntity) {
41584
+ cEntity[EXTRUSION_ANIMATOR_KEY] = null;
41585
+ }
41586
+ }
40641
41587
  /**
40642
41588
  * Reconciles cEntity's TextureFrameSeriesAnimator.Animator (if any) against this render's resolved texture.
40643
41589
  * Called once cEntity is fully created/updated and definitely has a `polygon` graphics.
@@ -40728,8 +41674,15 @@ function extractClientFileIdFromTextureValue(raw) {
40728
41674
  * @param params
40729
41675
  */
40730
41676
  async function resolveTexturedFill(params) {
40731
- const { api, style, entity, tags } = params;
40732
- const textureValue = style.texture;
41677
+ return resolveTextureValue({ ...params, textureValue: params.style.texture });
41678
+ }
41679
+ /**
41680
+ * Resolves either shape a texture setting can take (a calculated file id/URL, or a search condition)
41681
+ * into a tinted data URI or, when the resolved file is a frame archive, the archive itself.
41682
+ * @param params
41683
+ */
41684
+ async function resolveTextureValue(params) {
41685
+ const { api, style, entity, tags, textureValue } = params;
40733
41686
  if (textureValue && !Array.isArray(textureValue)) {
40734
41687
  return resolveTextureCondition({ api, condition: textureValue });
40735
41688
  }
@@ -41518,7 +42471,7 @@ var EntityRenderEngine;
41518
42471
  entity._dispose();
41519
42472
  entity._dispose = null;
41520
42473
  }
41521
- EntityRenderEnginePolygon.DisposeTextureFrameSeriesAnimator(entity);
42474
+ EntityRenderEnginePolygon.DisposeTextureFrameSeriesAnimator(entity, viewer);
41522
42475
  if (entity._parentEntity && !ignoreParent) {
41523
42476
  doRemove({
41524
42477
  viewer,
@@ -42269,7 +43222,7 @@ var StyleUtils;
42269
43222
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
42270
43223
  })(StyleUtils || (StyleUtils = {}));
42271
43224
 
42272
- const VERSION = "7.1.2";
43225
+ const VERSION = "7.1.4";
42273
43226
  /**
42274
43227
  * Updates the environment instance used by bruce-cesium to one specified.
42275
43228
  * 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 +43239,5 @@ const getENVIRONMENT = () => {
42286
43239
  return ENVIRONMENT;
42287
43240
  };
42288
43241
 
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 };
43242
+ 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
43243
  //# sourceMappingURL=bruce-cesium.es5.js.map