bruce-cesium 7.1.4 → 7.1.6

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, 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';
2
+ import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, 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, DistanceDisplayCondition, JulianDate, Quaternion, HeadingPitchRoll, ClassificationType, ArcType, HeightReference, CornerType, ShadowMode, ConstantPositionProperty, HorizontalOrigin, VerticalOrigin, ColorBlendMode, Model, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, SceneTransforms, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, SceneMode, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, Cesium3DTileColorBlendMode, Cesium3DTileset, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Intersect, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, OrthographicFrustum, EasingFunction, PolygonPipeline, GeometryInstance, CustomDataSource, CesiumInspector, ClockRange, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
+ import { Cartes, Entity as Entity$1, ENVIRONMENT, Calculator, Carto, EntityTag, EntityType, Geometry as Geometry$1, ObjectUtils, Style, Api, ClientFile, LRUCache, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, EntityLod, Bounds, Color as Color$1, BruceEvent, EntityCoords, DataLab, DelayQueue, BruceApi, EntityHistoricData, AccountConcept, RecordChangeFeed, EntityRelation, 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.
@@ -3587,6 +3587,7 @@ var SimplifyGeometry;
3587
3587
  if (!geometry || !isTurfAvailable() || !turf.simplify) {
3588
3588
  return geometry;
3589
3589
  }
3590
+ const original = geometry;
3590
3591
  // Convert to geojson so that we can interact with turf.
3591
3592
  const gFeature = Geometry$1.ToGeoJsonFeature({
3592
3593
  geometry: geometry
@@ -3607,13 +3608,97 @@ var SimplifyGeometry;
3607
3608
  });
3608
3609
  }
3609
3610
  // Converting back to Bruce geometry.
3610
- geometry = (_a = Geometry$1.FromGeoJson({
3611
+ const simplified = (_a = Geometry$1.FromGeoJson({
3611
3612
  geoJson: gFeature
3612
3613
  })) === null || _a === void 0 ? void 0 : _a.geometry;
3613
- return geometry;
3614
+ // A polygon we cannot vouch for is worse than an unsimplified one, so fall back instead of shipping it.
3615
+ if (!simplified || !isSafeToRender(simplified)) {
3616
+ return original;
3617
+ }
3618
+ return simplified;
3614
3619
  }
3615
3620
  SimplifyGeometry.Simplify = Simplify;
3621
+ /**
3622
+ * Whether a simplified geometry still describes something a renderer can triangulate.
3623
+ * @param geometry the simplification result.
3624
+ * @returns false when any polygon has a collapsed ring, a hole outside its outer ring, or a hole
3625
+ * wound the same way as its outer ring.
3626
+ */
3627
+ function IsSafeToRender(geometry) {
3628
+ return isSafeToRender(geometry);
3629
+ }
3630
+ SimplifyGeometry.IsSafeToRender = IsSafeToRender;
3616
3631
  })(SimplifyGeometry || (SimplifyGeometry = {}));
3632
+ // A ring needs three distinct corners plus the closing repeat before it encloses any area.
3633
+ const MIN_RING_POSITIONS = 4;
3634
+ function isSafeToRender(geometry) {
3635
+ let safe = true;
3636
+ const check = (g) => {
3637
+ if (!safe) {
3638
+ return;
3639
+ }
3640
+ if (g === null || g === void 0 ? void 0 : g.MultiGeometry) {
3641
+ g.MultiGeometry.forEach(check);
3642
+ return;
3643
+ }
3644
+ if (!(g === null || g === void 0 ? void 0 : g.Polygon) || g.Polygon.length === 0) {
3645
+ return;
3646
+ }
3647
+ let outer = null;
3648
+ let outerIsClockwise = false;
3649
+ for (let i = 0; i < g.Polygon.length; i++) {
3650
+ const points = Geometry$1.ParsePoints(g.Polygon[i].LinearRing);
3651
+ if (!points || points.length < MIN_RING_POSITIONS) {
3652
+ safe = false;
3653
+ return;
3654
+ }
3655
+ const clockwise = signedRingArea(points) < 0;
3656
+ if (g.Polygon[i].Facing !== "in") {
3657
+ outer = points;
3658
+ outerIsClockwise = clockwise;
3659
+ continue;
3660
+ }
3661
+ // A hole with no outer ring ahead of it cannot be subtracted from anything.
3662
+ if (!outer) {
3663
+ safe = false;
3664
+ return;
3665
+ }
3666
+ if (clockwise === outerIsClockwise) {
3667
+ safe = false;
3668
+ return;
3669
+ }
3670
+ for (let j = 0; j < points.length; j++) {
3671
+ if (!isPointInRing(points[j], outer)) {
3672
+ safe = false;
3673
+ return;
3674
+ }
3675
+ }
3676
+ }
3677
+ };
3678
+ check(geometry);
3679
+ return safe;
3680
+ }
3681
+ function signedRingArea(points) {
3682
+ let sum = 0;
3683
+ for (let i = 0; i < points.length; i++) {
3684
+ const a = points[i];
3685
+ const b = points[(i + 1) % points.length];
3686
+ sum += a.longitude * b.latitude - b.longitude * a.latitude;
3687
+ }
3688
+ return sum / 2;
3689
+ }
3690
+ function isPointInRing(point, ring) {
3691
+ let inside = false;
3692
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
3693
+ const a = ring[i];
3694
+ const b = ring[j];
3695
+ if ((a.latitude > point.latitude) !== (b.latitude > point.latitude)
3696
+ && point.longitude < (b.longitude - a.longitude) * (point.latitude - a.latitude) / (b.latitude - a.latitude) + a.longitude) {
3697
+ inside = !inside;
3698
+ }
3699
+ }
3700
+ return inside;
3701
+ }
3617
3702
  /**
3618
3703
  * Runs through all found polygons and runs unkink through turf on them.
3619
3704
  * @param feature
@@ -3655,9 +3740,26 @@ function untangleFeature(feature) {
3655
3740
  }
3656
3741
  // Recreate the rings and reapply to the collection.
3657
3742
  if (outer.length > 0) {
3658
- const combinedCoords = [outer[0]];
3743
+ // Unkinking a self touching boundary splits it into pieces. Keeping whichever piece
3744
+ // happened to come back first can discard most of the shape, so keep the biggest one.
3745
+ let keep = outer[0];
3746
+ if (outer.length > 1) {
3747
+ let best = Math.abs(ringArea(keep));
3748
+ for (let i = 1; i < outer.length; i++) {
3749
+ const area = Math.abs(ringArea(outer[i]));
3750
+ if (area > best) {
3751
+ best = area;
3752
+ keep = outer[i];
3753
+ }
3754
+ }
3755
+ }
3756
+ const combinedCoords = [keep];
3659
3757
  for (let i = 0; i < inner.length; i++) {
3660
- combinedCoords.push(inner[i]);
3758
+ // A hole belonging to a piece that was just discarded is no longer inside anything,
3759
+ // and a hole crossing the boundary is what tears the fill.
3760
+ if (isRingInsideRing(inner[i], keep)) {
3761
+ combinedCoords.push(inner[i]);
3762
+ }
3661
3763
  }
3662
3764
  // Add the combined coordinates to the collection
3663
3765
  const polygon = {
@@ -3805,6 +3907,30 @@ function unionFeature(feature) {
3805
3907
  feature.geometry = collection;
3806
3908
  }
3807
3909
  }
3910
+ function ringArea(ring) {
3911
+ let sum = 0;
3912
+ for (let i = 0; i < ring.length; i++) {
3913
+ const a = ring[i];
3914
+ const b = ring[(i + 1) % ring.length];
3915
+ sum += a[0] * b[1] - b[0] * a[1];
3916
+ }
3917
+ return sum / 2;
3918
+ }
3919
+ function isRingInsideRing(inner, outer) {
3920
+ for (let i = 0; i < inner.length; i++) {
3921
+ let inside = false;
3922
+ for (let a = 0, b = outer.length - 1; a < outer.length; b = a++) {
3923
+ if ((outer[a][1] > inner[i][1]) !== (outer[b][1] > inner[i][1])
3924
+ && inner[i][0] < (outer[b][0] - outer[a][0]) * (inner[i][1] - outer[a][1]) / (outer[b][1] - outer[a][1]) + outer[a][0]) {
3925
+ inside = !inside;
3926
+ }
3927
+ }
3928
+ if (!inside) {
3929
+ return false;
3930
+ }
3931
+ }
3932
+ return true;
3933
+ }
3808
3934
  function dedupeCoordinates(inputCoords) {
3809
3935
  return inputCoords.map(ring => {
3810
3936
  if (ring.length < 2) {
@@ -39734,15 +39860,68 @@ var TextureFrameSeriesAnimator;
39734
39860
  return Boolean(data && data.generation && Array.isArray(data.generation.Frames) && data.generation.Frames.length > 0);
39735
39861
  }
39736
39862
  TextureFrameSeriesAnimator.IsFrameArchiveMetadata = IsFrameArchiveMetadata;
39863
+ // How far covered values bleed outward into uncovered texels before the GPU samples them.
39864
+ const VALUE_DILATE_PASSES = 2;
39865
+ /*
39866
+ * Bleeds covered values outward into uncovered texels, leaving alpha untouched.
39867
+ *
39868
+ * The GPU samples the value texture with linear filtering, so an uncovered texel's RGB still gets
39869
+ * averaged into the vertices next to it even though its alpha is zero. Land sits at one end of the
39870
+ * ramp, so without this the coastline grows a row of spikes exactly where the data stops. Masking the
39871
+ * baseline creates more of these edges, which is what makes this necessary rather than cosmetic.
39872
+ */
39873
+ function dilateValues(value, width, height, passes) {
39874
+ const texels = width * height;
39875
+ const filled = new Uint8Array(texels);
39876
+ for (let p = 0; p < texels; p++) {
39877
+ filled[p] = value[p * 4 + 3] !== 0 ? 1 : 0;
39878
+ }
39879
+ for (let pass = 0; pass < passes; pass++) {
39880
+ const source = value.slice();
39881
+ const wasFilled = filled.slice();
39882
+ for (let y = 0; y < height; y++) {
39883
+ for (let x = 0; x < width; x++) {
39884
+ const p = y * width + x;
39885
+ if (wasFilled[p]) {
39886
+ continue;
39887
+ }
39888
+ let sum = 0;
39889
+ let hits = 0;
39890
+ for (let dy = -1; dy <= 1; dy++) {
39891
+ for (let dx = -1; dx <= 1; dx++) {
39892
+ const nx = x + dx;
39893
+ const ny = y + dy;
39894
+ if (nx < 0 || ny < 0 || nx >= width || ny >= height || !wasFilled[ny * width + nx]) {
39895
+ continue;
39896
+ }
39897
+ sum += source[(ny * width + nx) * 4];
39898
+ hits++;
39899
+ }
39900
+ }
39901
+ if (hits > 0) {
39902
+ const v = Math.round(sum / hits);
39903
+ value[p * 4] = v;
39904
+ value[p * 4 + 1] = v;
39905
+ value[p * 4 + 2] = v;
39906
+ filled[p] = 1;
39907
+ }
39908
+ }
39909
+ }
39910
+ }
39911
+ }
39737
39912
  const DEFAULT_CROSSFADE_MS = 900;
39738
39913
  const DEFAULT_LOW_COLOR = { red: 255, green: 255, blue: 255, alpha: 0.12 };
39739
39914
  const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
39740
39915
  class Animator {
39741
39916
  constructor(options) {
39742
- var _a;
39917
+ var _a, _b;
39743
39918
  this.removeOnTick = null;
39744
39919
  this.removeCrossfadeTick = null;
39745
39920
  this.disposed = false;
39921
+ // One alpha byte per texel, 1 meaning the texel is baseline. Fetched once, the mesh does not move.
39922
+ this.baselineFlags = null;
39923
+ this.baselineDims = null;
39924
+ this.baselineLoad = null;
39746
39925
  this.currentFrameIndex = -1;
39747
39926
  // Single-in-flight fetch queue: only one Range GET outstanding at a time, and the NEXT one
39748
39927
  // is chosen right before sending, this is what makes reprioritizing mid-scrub take effect
@@ -39782,6 +39961,8 @@ var TextureFrameSeriesAnimator;
39782
39961
  this.frameDims = new Array(this.frames.length).fill(null);
39783
39962
  this.valueCache = new Array(this.frames.length).fill(null);
39784
39963
  this.produceValueCanvas = Boolean(options.produceValueCanvas);
39964
+ this.baselineMaskEntry = options.baselineMask || null;
39965
+ this.maskBaseline = (_b = options.maskBaseline) !== null && _b !== void 0 ? _b : Boolean(options.baselineMask);
39785
39966
  this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
39786
39967
  this.driveMaterial = options.driveMaterial !== false;
39787
39968
  this.originalMaterial = options.entity.polygon.material;
@@ -39951,6 +40132,12 @@ var TextureFrameSeriesAnimator;
39951
40132
  if (this.disposed) {
39952
40133
  return;
39953
40134
  }
40135
+ // Awaited before decoding rather than applied later, so no frame is ever cached unmasked and
40136
+ // then corrected once the mask lands.
40137
+ await this.ensureBaselineMask();
40138
+ if (this.disposed) {
40139
+ return;
40140
+ }
39954
40141
  const decoded = await this.decodeAndTint(buffer);
39955
40142
  if (this.disposed) {
39956
40143
  return;
@@ -39985,8 +40172,81 @@ var TextureFrameSeriesAnimator;
39985
40172
  // recovered from the tinted pixels afterwards.
39986
40173
  const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
39987
40174
  ApplyGrayscaleColorMask(imageData, this.lowColor, this.highColor);
40175
+ this.applyBaselineMask(imageData.data, valuePixels, canvas.width, canvas.height);
40176
+ if (valuePixels) {
40177
+ dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES);
40178
+ }
39988
40179
  return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
39989
40180
  }
40181
+ /**
40182
+ * Fetches and decodes the archive's static baseline mask, at most once.
40183
+ */
40184
+ ensureBaselineMask() {
40185
+ if (!this.maskBaseline || !this.baselineMaskEntry) {
40186
+ return Promise.resolve();
40187
+ }
40188
+ if (this.baselineLoad) {
40189
+ return this.baselineLoad;
40190
+ }
40191
+ const entry = this.baselineMaskEntry;
40192
+ const start = entry.ByteOffset;
40193
+ const end = entry.ByteOffset + entry.ByteLength - 1;
40194
+ this.baselineLoad = fetch(this.archiveUrl, { headers: { Range: `bytes=${start}-${end}` } })
40195
+ .then((response) => response.arrayBuffer())
40196
+ .then(async (buffer) => {
40197
+ if (this.disposed) {
40198
+ return;
40199
+ }
40200
+ const blob = new Blob([buffer], { type: "image/png" });
40201
+ const objectUrl = URL.createObjectURL(blob);
40202
+ let image;
40203
+ try {
40204
+ image = await loadImage(objectUrl);
40205
+ }
40206
+ finally {
40207
+ URL.revokeObjectURL(objectUrl);
40208
+ }
40209
+ const canvas = document.createElement("canvas");
40210
+ canvas.width = image.width;
40211
+ canvas.height = image.height;
40212
+ const ctx = canvas.getContext("2d");
40213
+ ctx.drawImage(image, 0, 0);
40214
+ const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
40215
+ const flags = new Uint8Array(canvas.width * canvas.height);
40216
+ for (let p = 0; p < flags.length; p++) {
40217
+ flags[p] = pixels[p * 4 + 3] >= 128 ? 1 : 0;
40218
+ }
40219
+ this.baselineFlags = flags;
40220
+ this.baselineDims = { width: canvas.width, height: canvas.height };
40221
+ })
40222
+ .catch((e) => {
40223
+ // A missing or unreadable mask must not stop the animation; it just means nothing is hidden.
40224
+ console.warn("TextureFrameSeriesAnimator: could not load the baseline mask, drawing it instead.", e);
40225
+ });
40226
+ return this.baselineLoad;
40227
+ }
40228
+ /**
40229
+ * Clears alpha on baseline texels, which is what makes hiding the dry/land sentinel a render choice.
40230
+ */
40231
+ applyBaselineMask(pixels, valuePixels, width, height) {
40232
+ const flags = this.baselineFlags;
40233
+ if (!flags || !this.baselineDims) {
40234
+ return;
40235
+ }
40236
+ // A mask from a different raster size cannot be indexed against this frame, and guessing a
40237
+ // resampling would silently hide the wrong ground.
40238
+ if (this.baselineDims.width !== width || this.baselineDims.height !== height) {
40239
+ return;
40240
+ }
40241
+ for (let p = 0; p < flags.length; p++) {
40242
+ if (flags[p]) {
40243
+ pixels[p * 4 + 3] = 0;
40244
+ if (valuePixels) {
40245
+ valuePixels[p * 4 + 3] = 0;
40246
+ }
40247
+ }
40248
+ }
40249
+ }
39990
40250
  beginCrossfadeTo(idx) {
39991
40251
  const to = this.frameCache[idx];
39992
40252
  this.fadeFromValuePixels = this.displayedValuePixels === null && this.valueCache[idx]
@@ -40233,11 +40493,13 @@ void main() {
40233
40493
  return _glsl300Support;
40234
40494
  }
40235
40495
  /*
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.
40496
+ * Places grid node (fx, fy) on the ellipsoid, stepping the way the raster was sampled.
40497
+ * 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.
40239
40498
  */
40240
- function makePlacer(extent) {
40499
+ function makePlacer(extent, pixelPlacement) {
40500
+ if (pixelPlacement === "lonlat") {
40501
+ return (fx, fy) => Cartesian3.fromDegrees(Math$1.lerp(extent.West, extent.East, fx), Math$1.lerp(extent.North, extent.South, fy), 0);
40502
+ }
40241
40503
  const ellipsoid = Ellipsoid.WGS84;
40242
40504
  const centre = Cartesian3.fromDegrees((extent.West + extent.East) / 2, (extent.South + extent.North) / 2, 0);
40243
40505
  const frame = Transforms.eastNorthUpToFixedFrame(centre);
@@ -40251,21 +40513,21 @@ void main() {
40251
40513
  };
40252
40514
  }
40253
40515
  /*
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.
40516
+ * Builds one tile's grid, optionally plus a duplicated perimeter ring the shader pushes downwards to
40517
+ * cover the crack where a neighbouring tile at a different density interpolates the shared edge.
40256
40518
  */
40257
- function buildGeometry(extent, resolution, patch) {
40519
+ function buildGeometry(extent, resolution, patch, pixelPlacement, withSkirt = false) {
40258
40520
  const cols = resolution;
40259
40521
  const rows = resolution;
40260
40522
  const gridCount = (cols + 1) * (rows + 1);
40261
- const perimeterCount = 2 * (cols + 1) + 2 * (rows + 1) - 4;
40523
+ const perimeterCount = withSkirt ? 2 * (cols + 1) + 2 * (rows + 1) - 4 : 0;
40262
40524
  const count = gridCount + perimeterCount;
40263
40525
  const positions = new Float64Array(count * 3);
40264
40526
  const normals = new Float32Array(count * 3);
40265
40527
  const sts = new Float32Array(count * 2);
40266
40528
  const skirts = new Float32Array(count);
40267
40529
  const ellipsoid = Ellipsoid.WGS84;
40268
- const place = makePlacer(extent);
40530
+ const place = makePlacer(extent, pixelPlacement);
40269
40531
  let v = 0;
40270
40532
  for (let row = 0; row <= rows; row++) {
40271
40533
  // Texture row 0 is the north edge, so t runs opposite to northing.
@@ -40316,26 +40578,28 @@ void main() {
40316
40578
  loop.push(row * (cols + 1));
40317
40579
  }
40318
40580
  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;
40581
+ if (withSkirt) {
40582
+ loop.forEach((gridIndex, k) => {
40583
+ const target = skirtStart + k;
40584
+ positions[target * 3] = positions[gridIndex * 3];
40585
+ positions[target * 3 + 1] = positions[gridIndex * 3 + 1];
40586
+ positions[target * 3 + 2] = positions[gridIndex * 3 + 2];
40587
+ normals[target * 3] = normals[gridIndex * 3];
40588
+ normals[target * 3 + 1] = normals[gridIndex * 3 + 1];
40589
+ normals[target * 3 + 2] = normals[gridIndex * 3 + 2];
40590
+ sts[target * 2] = sts[gridIndex * 2];
40591
+ sts[target * 2 + 1] = sts[gridIndex * 2 + 1];
40592
+ skirts[target] = 1;
40593
+ });
40594
+ for (let k = 0; k < loop.length; k++) {
40595
+ const nextK = (k + 1) % loop.length;
40596
+ indices[i++] = loop[k];
40597
+ indices[i++] = skirtStart + k;
40598
+ indices[i++] = loop[nextK];
40599
+ indices[i++] = loop[nextK];
40600
+ indices[i++] = skirtStart + k;
40601
+ indices[i++] = skirtStart + nextK;
40602
+ }
40339
40603
  }
40340
40604
  const geometry = new Geometry({
40341
40605
  attributes: {
@@ -40393,6 +40657,8 @@ void main() {
40393
40657
  this.valueMax = (_b = options.valueMax) !== null && _b !== void 0 ? _b : 1;
40394
40658
  this.exaggeration = (_c = options.exaggeration) !== null && _c !== void 0 ? _c : autoExaggeration(this.extent, this.valueMin, this.valueMax);
40395
40659
  this.baseHeight = (_d = options.baseHeight) !== null && _d !== void 0 ? _d : 0;
40660
+ this.pixelPlacement = options.pixelPlacement;
40661
+ this.tileSkirts = Boolean(options.tileSkirts);
40396
40662
  this.lowColor = toCesiumColor(options.lowColor || DEFAULT_LOW_COLOR);
40397
40663
  this.highColor = toCesiumColor(options.highColor || DEFAULT_HIGH_COLOR);
40398
40664
  this.tiles = buildTiles(hasCoverage);
@@ -40400,6 +40666,9 @@ void main() {
40400
40666
  GetFollowsGround() {
40401
40667
  return this.followGround;
40402
40668
  }
40669
+ GetPixelPlacement() {
40670
+ return this.pixelPlacement;
40671
+ }
40403
40672
  GetBaseHeight() {
40404
40673
  return this.baseHeight;
40405
40674
  }
@@ -40614,7 +40883,7 @@ void main() {
40614
40883
  if (cached) {
40615
40884
  return cached;
40616
40885
  }
40617
- const geometry = buildGeometry(this.extent, tier, tile.patch);
40886
+ const geometry = buildGeometry(this.extent, tier, tile.patch, this.pixelPlacement, this.tileSkirts);
40618
40887
  const attributeLocations = GeometryPipeline.createAttributeLocations(geometry);
40619
40888
  const mesh = {
40620
40889
  attributeLocations,
@@ -40707,7 +40976,9 @@ void main() {
40707
40976
  u_valueRange: () => self.valueMax - self.valueMin,
40708
40977
  u_exaggeration: () => self.exaggeration,
40709
40978
  u_baseHeight: () => self.baseHeight,
40710
- u_skirtDepth: () => Math.max(2, Math.abs(self.valueMax - self.valueMin) * self.exaggeration * SKIRT_FRACTION),
40979
+ u_skirtDepth: () => self.tileSkirts
40980
+ ? Math.max(2, Math.abs(self.valueMax - self.valueMin) * self.exaggeration * SKIRT_FRACTION)
40981
+ : 0,
40711
40982
  u_coverageCutoff: () => COVERAGE_CUTOFF,
40712
40983
  u_lowColor: () => self.lowColor,
40713
40984
  u_highColor: () => self.highColor,
@@ -40840,6 +41111,8 @@ var EntityRenderEnginePolygon;
40840
41111
  }
40841
41112
  }
40842
41113
  const drawsDisplacedSurface = Boolean(extrusionArchive);
41114
+ // The displaced surface IS the entity's fill, drawn as its own primitive at the value's height.
41115
+ // Leaving the flat polygon filled as well shows a second copy of the parcel on the ground under it.
40843
41116
  const hasFill = (drawsDisplacedSurface ? true
40844
41117
  : fillType === Style.EPolygonFillType.Texture
40845
41118
  ? Boolean(textureDataUri || frameArchive || cFillColor.alpha > 0)
@@ -40928,6 +41201,7 @@ var EntityRenderEnginePolygon;
40928
41201
  polygon: {
40929
41202
  hierarchy: new PolygonHierarchy(posses, holePosses.map(x => new PolygonHierarchy(x))),
40930
41203
  material: fillMaterial,
41204
+ fill: !drawsDisplacedSurface,
40931
41205
  extrudedHeight: extrusion.value,
40932
41206
  extrudedHeightReference: extrusion.exHeightRef,
40933
41207
  shadows: ShadowMode.ENABLED,
@@ -40958,6 +41232,12 @@ var EntityRenderEnginePolygon;
40958
41232
  if (!CompareCPolygonHierarchies(oldHierarchy, newHierarchy)) {
40959
41233
  cEntity.polygon.hierarchy = new ConstantProperty(newHierarchy);
40960
41234
  }
41235
+ // Toggling extrusion styling on an already-rendered entity flips this,
41236
+ // so it has to be reconciled rather than set only at creation.
41237
+ const oldFill = GetCValue(params.viewer, cEntity.polygon.fill);
41238
+ if (oldFill !== !drawsDisplacedSurface) {
41239
+ cEntity.polygon.fill = new ConstantProperty(!drawsDisplacedSurface);
41240
+ }
40961
41241
  const oldExtrusion = GetCValue(params.viewer, cEntity.polygon.extrudedHeight);
40962
41242
  if (oldExtrusion != extrusion.value) {
40963
41243
  cEntity.polygon.extrudedHeight = new ConstantProperty(extrusion.value);
@@ -41011,7 +41291,7 @@ var EntityRenderEnginePolygon;
41011
41291
  }
41012
41292
  // Must run before any material is baked below: the Animator takes over polygon.material, so a bake
41013
41293
  // has to know whether it is baking over a live animation or over a disposed one's restored material.
41014
- const animator = syncTextureFrameArchive(cEntity, drawsDisplacedSurface ? null : frameArchive, params.viewer, style.textureColorMask);
41294
+ const animator = syncTextureFrameArchive(cEntity, drawsDisplacedSurface ? null : frameArchive, params.viewer, style.textureColorMask, style.maskTextureBaseline);
41015
41295
  syncDisplacedSurface({
41016
41296
  cEntity,
41017
41297
  extrusionArchive,
@@ -41496,12 +41776,17 @@ function syncDisplacedSurface(params) {
41496
41776
  frames: extrusionArchive.metadata.Frames,
41497
41777
  textureColorMask: style.textureColorMask,
41498
41778
  driveMaterial: false,
41499
- produceValueCanvas: true
41779
+ produceValueCanvas: true,
41780
+ baselineMask: extrusionArchive.metadata.BaselineMask,
41781
+ // Off-type for the same reason as the fill path: the pinned bruce-models predates the field.
41782
+ maskBaseline: style.maskTextureBaseline
41500
41783
  });
41501
41784
  cEntity[EXTRUSION_ANIMATOR_KEY] = animator;
41502
41785
  const mask = style.textureColorMask;
41503
41786
  const surface = new DisplacedSurfacePrimitive.Surface({
41504
41787
  extent,
41788
+ // The grid has to be walked the way the raster was sampled, or the surface skews across the extent.
41789
+ pixelPlacement: extrusionArchive.metadata.PixelPlacement,
41505
41790
  source: null,
41506
41791
  baseHeight: placement.baseHeight,
41507
41792
  followGround: placement.followGround,
@@ -41593,7 +41878,7 @@ function disposeDisplacedSurface(cEntity, viewer) {
41593
41878
  * @param viewer
41594
41879
  * @param textureColorMask
41595
41880
  */
41596
- function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask) {
41881
+ function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask, maskTextureBaseline) {
41597
41882
  const existing = cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY];
41598
41883
  if (frameArchive && existing && !existing.IsDisposed() && existing.GetArchiveUrl() === frameArchive.url) {
41599
41884
  return existing;
@@ -41610,7 +41895,9 @@ function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask
41610
41895
  entity: cEntity,
41611
41896
  archiveUrl: frameArchive.url,
41612
41897
  frames: frameArchive.metadata.Frames,
41613
- textureColorMask
41898
+ textureColorMask,
41899
+ baselineMask: frameArchive.metadata.BaselineMask,
41900
+ maskBaseline: maskTextureBaseline
41614
41901
  });
41615
41902
  cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = animator;
41616
41903
  return animator;
@@ -43222,7 +43509,7 @@ var StyleUtils;
43222
43509
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
43223
43510
  })(StyleUtils || (StyleUtils = {}));
43224
43511
 
43225
- const VERSION = "7.1.4";
43512
+ const VERSION = "7.1.6";
43226
43513
  /**
43227
43514
  * Updates the environment instance used by bruce-cesium to one specified.
43228
43515
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.