bruce-cesium 7.1.3 → 7.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bruce-cesium.es5.js +1164 -50
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +1134 -25
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +2 -1
- package/dist/lib/bruce-cesium.js.map +1 -1
- package/dist/lib/rendering/displaced-surface-primitive.js +734 -0
- package/dist/lib/rendering/displaced-surface-primitive.js.map +1 -0
- package/dist/lib/rendering/entity-render-engine-polygon.js +211 -17
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine.js +1 -1
- package/dist/lib/rendering/entity-render-engine.js.map +1 -1
- package/dist/lib/rendering/texture-frame-series-animator.js +197 -6
- package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +2 -1
- package/dist/types/rendering/displaced-surface-primitive.d.ts +108 -0
- package/dist/types/rendering/entity-render-engine-polygon.d.ts +1 -1
- package/dist/types/rendering/texture-frame-series-animator.d.ts +53 -0
- package/package.json +2 -2
package/dist/bruce-cesium.umd.js
CHANGED
|
@@ -39622,15 +39622,68 @@
|
|
|
39622
39622
|
return Boolean(data && data.generation && Array.isArray(data.generation.Frames) && data.generation.Frames.length > 0);
|
|
39623
39623
|
}
|
|
39624
39624
|
TextureFrameSeriesAnimator.IsFrameArchiveMetadata = IsFrameArchiveMetadata;
|
|
39625
|
+
// How far covered values bleed outward into uncovered texels before the GPU samples them.
|
|
39626
|
+
const VALUE_DILATE_PASSES = 2;
|
|
39627
|
+
/*
|
|
39628
|
+
* Bleeds covered values outward into uncovered texels, leaving alpha untouched.
|
|
39629
|
+
*
|
|
39630
|
+
* The GPU samples the value texture with linear filtering, so an uncovered texel's RGB still gets
|
|
39631
|
+
* averaged into the vertices next to it even though its alpha is zero. Land sits at one end of the
|
|
39632
|
+
* ramp, so without this the coastline grows a row of spikes exactly where the data stops. Masking the
|
|
39633
|
+
* baseline creates more of these edges, which is what makes this necessary rather than cosmetic.
|
|
39634
|
+
*/
|
|
39635
|
+
function dilateValues(value, width, height, passes) {
|
|
39636
|
+
const texels = width * height;
|
|
39637
|
+
const filled = new Uint8Array(texels);
|
|
39638
|
+
for (let p = 0; p < texels; p++) {
|
|
39639
|
+
filled[p] = value[p * 4 + 3] !== 0 ? 1 : 0;
|
|
39640
|
+
}
|
|
39641
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
39642
|
+
const source = value.slice();
|
|
39643
|
+
const wasFilled = filled.slice();
|
|
39644
|
+
for (let y = 0; y < height; y++) {
|
|
39645
|
+
for (let x = 0; x < width; x++) {
|
|
39646
|
+
const p = y * width + x;
|
|
39647
|
+
if (wasFilled[p]) {
|
|
39648
|
+
continue;
|
|
39649
|
+
}
|
|
39650
|
+
let sum = 0;
|
|
39651
|
+
let hits = 0;
|
|
39652
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
39653
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
39654
|
+
const nx = x + dx;
|
|
39655
|
+
const ny = y + dy;
|
|
39656
|
+
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !wasFilled[ny * width + nx]) {
|
|
39657
|
+
continue;
|
|
39658
|
+
}
|
|
39659
|
+
sum += source[(ny * width + nx) * 4];
|
|
39660
|
+
hits++;
|
|
39661
|
+
}
|
|
39662
|
+
}
|
|
39663
|
+
if (hits > 0) {
|
|
39664
|
+
const v = Math.round(sum / hits);
|
|
39665
|
+
value[p * 4] = v;
|
|
39666
|
+
value[p * 4 + 1] = v;
|
|
39667
|
+
value[p * 4 + 2] = v;
|
|
39668
|
+
filled[p] = 1;
|
|
39669
|
+
}
|
|
39670
|
+
}
|
|
39671
|
+
}
|
|
39672
|
+
}
|
|
39673
|
+
}
|
|
39625
39674
|
const DEFAULT_CROSSFADE_MS = 900;
|
|
39626
39675
|
const DEFAULT_LOW_COLOR = { red: 255, green: 255, blue: 255, alpha: 0.12 };
|
|
39627
39676
|
const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
|
|
39628
39677
|
class Animator {
|
|
39629
39678
|
constructor(options) {
|
|
39630
|
-
var _a;
|
|
39679
|
+
var _a, _b;
|
|
39631
39680
|
this.removeOnTick = null;
|
|
39632
39681
|
this.removeCrossfadeTick = null;
|
|
39633
39682
|
this.disposed = false;
|
|
39683
|
+
// One alpha byte per texel, 1 meaning the texel is baseline. Fetched once, the mesh does not move.
|
|
39684
|
+
this.baselineFlags = null;
|
|
39685
|
+
this.baselineDims = null;
|
|
39686
|
+
this.baselineLoad = null;
|
|
39634
39687
|
this.currentFrameIndex = -1;
|
|
39635
39688
|
// Single-in-flight fetch queue: only one Range GET outstanding at a time, and the NEXT one
|
|
39636
39689
|
// is chosen right before sending, this is what makes reprioritizing mid-scrub take effect
|
|
@@ -39639,6 +39692,8 @@
|
|
|
39639
39692
|
this.pendingIndex = -1;
|
|
39640
39693
|
this.displayedPixels = null;
|
|
39641
39694
|
this.fadeFromPixels = null;
|
|
39695
|
+
this.displayedValuePixels = null;
|
|
39696
|
+
this.fadeFromValuePixels = null;
|
|
39642
39697
|
this.fadeToIndex = -1;
|
|
39643
39698
|
this.fadeToDims = null;
|
|
39644
39699
|
this.fadeStartMs = null;
|
|
@@ -39647,6 +39702,8 @@
|
|
|
39647
39702
|
// forcing Cesium to re-upload the texture only when something changed.
|
|
39648
39703
|
this.pool = [document.createElement("canvas"), document.createElement("canvas")];
|
|
39649
39704
|
this.poolIdx = 0;
|
|
39705
|
+
this.valueDims = null;
|
|
39706
|
+
this.presentVersion = 0;
|
|
39650
39707
|
if (!options.entity.polygon) {
|
|
39651
39708
|
throw new Error("TextureFrameSeriesAnimator requires an entity with polygon graphics.");
|
|
39652
39709
|
}
|
|
@@ -39664,9 +39721,17 @@
|
|
|
39664
39721
|
this.frameDates = this.frames.map((f) => Cesium.JulianDate.fromIso8601(f.Timestamp));
|
|
39665
39722
|
this.frameCache = new Array(this.frames.length).fill(null);
|
|
39666
39723
|
this.frameDims = new Array(this.frames.length).fill(null);
|
|
39724
|
+
this.valueCache = new Array(this.frames.length).fill(null);
|
|
39725
|
+
this.produceValueCanvas = Boolean(options.produceValueCanvas);
|
|
39726
|
+
this.baselineMaskEntry = options.baselineMask || null;
|
|
39727
|
+
this.maskBaseline = (_b = options.maskBaseline) !== null && _b !== void 0 ? _b : Boolean(options.baselineMask);
|
|
39728
|
+
this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
|
|
39729
|
+
this.driveMaterial = options.driveMaterial !== false;
|
|
39667
39730
|
this.originalMaterial = options.entity.polygon.material;
|
|
39668
39731
|
this.imageProperty = new Cesium.CallbackProperty(() => this.pool[this.poolIdx], false);
|
|
39669
|
-
|
|
39732
|
+
if (this.driveMaterial) {
|
|
39733
|
+
options.entity.polygon.material = new Cesium.ImageMaterialProperty({ image: this.imageProperty, transparent: true });
|
|
39734
|
+
}
|
|
39670
39735
|
this.removeCrossfadeTick = this.viewer.scene.postUpdate.addEventListener(() => this.tickCrossfade());
|
|
39671
39736
|
this.removeOnTick = this.viewer.clock.onTick.addEventListener((clock) => this.onClockTick(clock.currentTime));
|
|
39672
39737
|
this.onClockTick(this.viewer.clock.currentTime);
|
|
@@ -39688,7 +39753,7 @@
|
|
|
39688
39753
|
this.removeCrossfadeTick();
|
|
39689
39754
|
this.removeCrossfadeTick = null;
|
|
39690
39755
|
}
|
|
39691
|
-
if (this.entity && this.entity.polygon) {
|
|
39756
|
+
if (this.driveMaterial && this.entity && this.entity.polygon) {
|
|
39692
39757
|
this.entity.polygon.material = this.originalMaterial;
|
|
39693
39758
|
}
|
|
39694
39759
|
}
|
|
@@ -39709,6 +39774,20 @@
|
|
|
39709
39774
|
GetImageProperty() {
|
|
39710
39775
|
return this.imageProperty;
|
|
39711
39776
|
}
|
|
39777
|
+
/**
|
|
39778
|
+
* The untinted canvas carrying the presented frame's value in RGB and its coverage in alpha,
|
|
39779
|
+
* or null unless the instance was constructed with produceValueCanvas.
|
|
39780
|
+
*/
|
|
39781
|
+
GetValueCanvas() {
|
|
39782
|
+
return this.valueDims ? this.valueCanvas : null;
|
|
39783
|
+
}
|
|
39784
|
+
/**
|
|
39785
|
+
* Increments every time a frame is presented, so a consumer holding the value canvas can tell
|
|
39786
|
+
* whether it needs re-uploading without diffing pixels or subscribing to anything.
|
|
39787
|
+
*/
|
|
39788
|
+
GetPresentVersion() {
|
|
39789
|
+
return this.presentVersion;
|
|
39790
|
+
}
|
|
39712
39791
|
onClockTick(currentTime) {
|
|
39713
39792
|
if (this.disposed) {
|
|
39714
39793
|
return;
|
|
@@ -39815,12 +39894,19 @@
|
|
|
39815
39894
|
if (this.disposed) {
|
|
39816
39895
|
return;
|
|
39817
39896
|
}
|
|
39897
|
+
// Awaited before decoding rather than applied later, so no frame is ever cached unmasked and
|
|
39898
|
+
// then corrected once the mask lands.
|
|
39899
|
+
await this.ensureBaselineMask();
|
|
39900
|
+
if (this.disposed) {
|
|
39901
|
+
return;
|
|
39902
|
+
}
|
|
39818
39903
|
const decoded = await this.decodeAndTint(buffer);
|
|
39819
39904
|
if (this.disposed) {
|
|
39820
39905
|
return;
|
|
39821
39906
|
}
|
|
39822
39907
|
this.frameCache[idx] = decoded.pixels;
|
|
39823
39908
|
this.frameDims[idx] = { width: decoded.width, height: decoded.height };
|
|
39909
|
+
this.valueCache[idx] = decoded.valuePixels || null;
|
|
39824
39910
|
if (idx === this.currentFrameIndex) {
|
|
39825
39911
|
this.beginCrossfadeTo(idx);
|
|
39826
39912
|
}
|
|
@@ -39844,11 +39930,90 @@
|
|
|
39844
39930
|
const ctx = canvas.getContext("2d");
|
|
39845
39931
|
ctx.drawImage(image, 0, 0);
|
|
39846
39932
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
39933
|
+
// Copied before tinting: the ramp is a lerp between two colours, so the value cannot be
|
|
39934
|
+
// recovered from the tinted pixels afterwards.
|
|
39935
|
+
const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
|
|
39847
39936
|
ApplyGrayscaleColorMask(imageData, this.lowColor, this.highColor);
|
|
39848
|
-
|
|
39937
|
+
this.applyBaselineMask(imageData.data, valuePixels, canvas.width, canvas.height);
|
|
39938
|
+
if (valuePixels) {
|
|
39939
|
+
dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES);
|
|
39940
|
+
}
|
|
39941
|
+
return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
|
|
39942
|
+
}
|
|
39943
|
+
/**
|
|
39944
|
+
* Fetches and decodes the archive's static baseline mask, at most once.
|
|
39945
|
+
*/
|
|
39946
|
+
ensureBaselineMask() {
|
|
39947
|
+
if (!this.maskBaseline || !this.baselineMaskEntry) {
|
|
39948
|
+
return Promise.resolve();
|
|
39949
|
+
}
|
|
39950
|
+
if (this.baselineLoad) {
|
|
39951
|
+
return this.baselineLoad;
|
|
39952
|
+
}
|
|
39953
|
+
const entry = this.baselineMaskEntry;
|
|
39954
|
+
const start = entry.ByteOffset;
|
|
39955
|
+
const end = entry.ByteOffset + entry.ByteLength - 1;
|
|
39956
|
+
this.baselineLoad = fetch(this.archiveUrl, { headers: { Range: `bytes=${start}-${end}` } })
|
|
39957
|
+
.then((response) => response.arrayBuffer())
|
|
39958
|
+
.then(async (buffer) => {
|
|
39959
|
+
if (this.disposed) {
|
|
39960
|
+
return;
|
|
39961
|
+
}
|
|
39962
|
+
const blob = new Blob([buffer], { type: "image/png" });
|
|
39963
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
39964
|
+
let image;
|
|
39965
|
+
try {
|
|
39966
|
+
image = await loadImage(objectUrl);
|
|
39967
|
+
}
|
|
39968
|
+
finally {
|
|
39969
|
+
URL.revokeObjectURL(objectUrl);
|
|
39970
|
+
}
|
|
39971
|
+
const canvas = document.createElement("canvas");
|
|
39972
|
+
canvas.width = image.width;
|
|
39973
|
+
canvas.height = image.height;
|
|
39974
|
+
const ctx = canvas.getContext("2d");
|
|
39975
|
+
ctx.drawImage(image, 0, 0);
|
|
39976
|
+
const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
39977
|
+
const flags = new Uint8Array(canvas.width * canvas.height);
|
|
39978
|
+
for (let p = 0; p < flags.length; p++) {
|
|
39979
|
+
flags[p] = pixels[p * 4 + 3] >= 128 ? 1 : 0;
|
|
39980
|
+
}
|
|
39981
|
+
this.baselineFlags = flags;
|
|
39982
|
+
this.baselineDims = { width: canvas.width, height: canvas.height };
|
|
39983
|
+
})
|
|
39984
|
+
.catch((e) => {
|
|
39985
|
+
// A missing or unreadable mask must not stop the animation; it just means nothing is hidden.
|
|
39986
|
+
console.warn("TextureFrameSeriesAnimator: could not load the baseline mask, drawing it instead.", e);
|
|
39987
|
+
});
|
|
39988
|
+
return this.baselineLoad;
|
|
39989
|
+
}
|
|
39990
|
+
/**
|
|
39991
|
+
* Clears alpha on baseline texels, which is what makes hiding the dry/land sentinel a render choice.
|
|
39992
|
+
*/
|
|
39993
|
+
applyBaselineMask(pixels, valuePixels, width, height) {
|
|
39994
|
+
const flags = this.baselineFlags;
|
|
39995
|
+
if (!flags || !this.baselineDims) {
|
|
39996
|
+
return;
|
|
39997
|
+
}
|
|
39998
|
+
// A mask from a different raster size cannot be indexed against this frame, and guessing a
|
|
39999
|
+
// resampling would silently hide the wrong ground.
|
|
40000
|
+
if (this.baselineDims.width !== width || this.baselineDims.height !== height) {
|
|
40001
|
+
return;
|
|
40002
|
+
}
|
|
40003
|
+
for (let p = 0; p < flags.length; p++) {
|
|
40004
|
+
if (flags[p]) {
|
|
40005
|
+
pixels[p * 4 + 3] = 0;
|
|
40006
|
+
if (valuePixels) {
|
|
40007
|
+
valuePixels[p * 4 + 3] = 0;
|
|
40008
|
+
}
|
|
40009
|
+
}
|
|
40010
|
+
}
|
|
39849
40011
|
}
|
|
39850
40012
|
beginCrossfadeTo(idx) {
|
|
39851
40013
|
const to = this.frameCache[idx];
|
|
40014
|
+
this.fadeFromValuePixels = this.displayedValuePixels === null && this.valueCache[idx]
|
|
40015
|
+
? new Uint8ClampedArray(this.valueCache[idx].length)
|
|
40016
|
+
: this.displayedValuePixels;
|
|
39852
40017
|
this.fadeFromPixels = this.displayedPixels === null ? new Uint8ClampedArray(to.length) : this.displayedPixels;
|
|
39853
40018
|
this.fadeToIndex = idx;
|
|
39854
40019
|
this.fadeToDims = this.frameDims[idx];
|
|
@@ -39867,13 +40032,28 @@
|
|
|
39867
40032
|
for (let i = 0; i < n; i++) {
|
|
39868
40033
|
blended[i] = from[i] + (to[i] - from[i]) * eased;
|
|
39869
40034
|
}
|
|
39870
|
-
|
|
40035
|
+
let blendedValue = null;
|
|
40036
|
+
const toValue = this.valueCache[this.fadeToIndex];
|
|
40037
|
+
if (toValue) {
|
|
40038
|
+
const fromValue = this.fadeFromValuePixels;
|
|
40039
|
+
blendedValue = new Uint8ClampedArray(toValue.length);
|
|
40040
|
+
if (fromValue && fromValue.length === toValue.length) {
|
|
40041
|
+
for (let i = 0; i < toValue.length; i++) {
|
|
40042
|
+
blendedValue[i] = fromValue[i] + (toValue[i] - fromValue[i]) * eased;
|
|
40043
|
+
}
|
|
40044
|
+
}
|
|
40045
|
+
else {
|
|
40046
|
+
blendedValue.set(toValue);
|
|
40047
|
+
}
|
|
40048
|
+
}
|
|
40049
|
+
this.presentPixels(blended, this.fadeToDims, blendedValue);
|
|
39871
40050
|
if (t >= 1) {
|
|
39872
40051
|
this.fadeStartMs = null;
|
|
39873
40052
|
this.fadeFromPixels = null;
|
|
40053
|
+
this.fadeFromValuePixels = null;
|
|
39874
40054
|
}
|
|
39875
40055
|
}
|
|
39876
|
-
presentPixels(pixels, dims) {
|
|
40056
|
+
presentPixels(pixels, dims, valuePixels) {
|
|
39877
40057
|
this.poolIdx = 1 - this.poolIdx;
|
|
39878
40058
|
const target = this.pool[this.poolIdx];
|
|
39879
40059
|
target.width = dims.width;
|
|
@@ -39883,6 +40063,17 @@
|
|
|
39883
40063
|
imgData.data.set(pixels);
|
|
39884
40064
|
ctx.putImageData(imgData, 0, 0);
|
|
39885
40065
|
this.displayedPixels = pixels;
|
|
40066
|
+
if (this.valueCanvas && valuePixels) {
|
|
40067
|
+
this.valueCanvas.width = dims.width;
|
|
40068
|
+
this.valueCanvas.height = dims.height;
|
|
40069
|
+
const valueCtx = this.valueCanvas.getContext("2d");
|
|
40070
|
+
const valueImgData = valueCtx.createImageData(dims.width, dims.height);
|
|
40071
|
+
valueImgData.data.set(valuePixels);
|
|
40072
|
+
valueCtx.putImageData(valueImgData, 0, 0);
|
|
40073
|
+
this.displayedValuePixels = valuePixels;
|
|
40074
|
+
this.valueDims = dims;
|
|
40075
|
+
}
|
|
40076
|
+
this.presentVersion++;
|
|
39886
40077
|
}
|
|
39887
40078
|
}
|
|
39888
40079
|
TextureFrameSeriesAnimator.Animator = Animator;
|
|
@@ -39894,10 +40085,737 @@
|
|
|
39894
40085
|
}
|
|
39895
40086
|
})(TextureFrameSeriesAnimator || (TextureFrameSeriesAnimator = {}));
|
|
39896
40087
|
|
|
40088
|
+
(function (DisplacedSurfacePrimitive) {
|
|
40089
|
+
// Grid densities a tile can draw at, coarsest first.
|
|
40090
|
+
const TIERS = [32, 64, 128, 192];
|
|
40091
|
+
// Tiles per side the extent is cut into.
|
|
40092
|
+
const TILES_PER_SIDE = 6;
|
|
40093
|
+
// Screen pixels one grid cell should cover. Lower means finer meshes sooner.
|
|
40094
|
+
const TARGET_CELL_PIXELS = 12;
|
|
40095
|
+
// Fraction of its distance to the surface the camera must move before densities are reconsidered.
|
|
40096
|
+
const MOVEMENT_THRESHOLD = 0.12;
|
|
40097
|
+
const RESELECT_INTERVAL_MS = 400;
|
|
40098
|
+
// Skirt depth as a fraction of the displaced range. The sheet is translucent, so an oversized
|
|
40099
|
+
// skirt blends through it and paints a seam down every tile boundary.
|
|
40100
|
+
const SKIRT_FRACTION = 0.015;
|
|
40101
|
+
// Alpha below which a texel counts as no-data and is not drawn at all.
|
|
40102
|
+
const COVERAGE_CUTOFF = 0.35;
|
|
40103
|
+
// With no exaggeration stated, the full value range is drawn as this fraction of the extent's shorter side.
|
|
40104
|
+
const AUTO_RELIEF_FRACTION = 0.02;
|
|
40105
|
+
// Terrain is sampled on this grid and interpolated between samples.
|
|
40106
|
+
// Per-texel sampling would be hundreds of thousands of lookups for a surface whose own data is far coarser than that.
|
|
40107
|
+
const GROUND_SAMPLES_PER_SIDE = 64;
|
|
40108
|
+
// Terrain detail level the samples are taken at.
|
|
40109
|
+
// Most-detailed would fetch every tile under a parcel that can be a hundred kilometres across.
|
|
40110
|
+
const GROUND_SAMPLE_LEVEL = 11;
|
|
40111
|
+
// Height window the packed ground texture covers, wide enough for any terrain on Earth.
|
|
40112
|
+
const GROUND_MIN_METRES = -1000;
|
|
40113
|
+
const GROUND_MAX_METRES = 9000;
|
|
40114
|
+
const DEFAULT_LOW_COLOR = { red: 255, green: 255, blue: 255, alpha: 0.12 };
|
|
40115
|
+
const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
|
|
40116
|
+
const VERTEX_SHADER_GLSL300 = `
|
|
40117
|
+
in vec3 position3DHigh;
|
|
40118
|
+
in vec3 position3DLow;
|
|
40119
|
+
in vec3 normal;
|
|
40120
|
+
in vec2 st;
|
|
40121
|
+
in float skirt;
|
|
40122
|
+
|
|
40123
|
+
uniform sampler2D u_valueTexture;
|
|
40124
|
+
uniform sampler2D u_groundTexture;
|
|
40125
|
+
uniform float u_valueMin;
|
|
40126
|
+
uniform float u_valueRange;
|
|
40127
|
+
uniform float u_exaggeration;
|
|
40128
|
+
uniform float u_baseHeight;
|
|
40129
|
+
uniform float u_skirtDepth;
|
|
40130
|
+
uniform float u_groundWeight;
|
|
40131
|
+
uniform vec2 u_groundRange;
|
|
40132
|
+
uniform vec2 u_groundSize;
|
|
40133
|
+
|
|
40134
|
+
out vec2 v_st;
|
|
40135
|
+
out float v_value;
|
|
40136
|
+
out float v_coverage;
|
|
40137
|
+
|
|
40138
|
+
float decodeGround(vec2 uv) {
|
|
40139
|
+
// Channels arrive normalised to 0..1, so they go back to bytes before being reassembled.
|
|
40140
|
+
vec3 packed = texture(u_groundTexture, uv).rgb * 255.0;
|
|
40141
|
+
float normalised = dot(packed, vec3(65536.0, 256.0, 1.0)) / 16777215.0;
|
|
40142
|
+
return u_groundRange.x + normalised * (u_groundRange.y - u_groundRange.x);
|
|
40143
|
+
}
|
|
40144
|
+
|
|
40145
|
+
/*
|
|
40146
|
+
* Bilinear by hand, because the height is packed across three channels and hardware filtering would
|
|
40147
|
+
* blend the packing rather than the heights, turning a smooth slope into noise at every texel edge.
|
|
40148
|
+
*/
|
|
40149
|
+
float sampleGround(vec2 uv) {
|
|
40150
|
+
vec2 texel = 1.0 / u_groundSize;
|
|
40151
|
+
vec2 coord = uv * u_groundSize - 0.5;
|
|
40152
|
+
vec2 base = floor(coord);
|
|
40153
|
+
vec2 f = coord - base;
|
|
40154
|
+
vec2 origin = (base + 0.5) * texel;
|
|
40155
|
+
float h00 = decodeGround(origin);
|
|
40156
|
+
float h10 = decodeGround(origin + vec2(texel.x, 0.0));
|
|
40157
|
+
float h01 = decodeGround(origin + vec2(0.0, texel.y));
|
|
40158
|
+
float h11 = decodeGround(origin + texel);
|
|
40159
|
+
return mix(mix(h00, h10, f.x), mix(h01, h11, f.x), f.y);
|
|
40160
|
+
}
|
|
40161
|
+
|
|
40162
|
+
void main() {
|
|
40163
|
+
vec4 texel = texture(u_valueTexture, st);
|
|
40164
|
+
v_st = st;
|
|
40165
|
+
v_value = texel.r;
|
|
40166
|
+
v_coverage = texel.a;
|
|
40167
|
+
|
|
40168
|
+
float metres = u_valueMin + texel.r * u_valueRange;
|
|
40169
|
+
|
|
40170
|
+
// Value zero sits at the polygon's own altitude, plus the terrain underneath when the polygon
|
|
40171
|
+
// is following the ground rather than absolutely positioned.
|
|
40172
|
+
float ground = sampleGround(st) * u_groundWeight;
|
|
40173
|
+
float displacement = u_baseHeight + ground + metres * u_exaggeration * texel.a - skirt * u_skirtDepth;
|
|
40174
|
+
|
|
40175
|
+
// Added to the LOW half of the encoded position: a metre-scale offset added to the high half
|
|
40176
|
+
// would be lost to float32 rounding at an earth radius.
|
|
40177
|
+
vec4 p = czm_translateRelativeToEye(position3DHigh, position3DLow + normal * displacement);
|
|
40178
|
+
gl_Position = czm_modelViewProjectionRelativeToEye * p;
|
|
40179
|
+
}
|
|
40180
|
+
`;
|
|
40181
|
+
const FRAGMENT_SHADER_GLSL300 = `
|
|
40182
|
+
uniform sampler2D u_valueTexture;
|
|
40183
|
+
uniform vec4 u_lowColor;
|
|
40184
|
+
uniform vec4 u_highColor;
|
|
40185
|
+
uniform float u_coverageCutoff;
|
|
40186
|
+
uniform vec2 u_texelSize;
|
|
40187
|
+
uniform float u_valueRange;
|
|
40188
|
+
uniform float u_exaggeration;
|
|
40189
|
+
uniform float u_metresPerTexel;
|
|
40190
|
+
|
|
40191
|
+
in vec2 v_st;
|
|
40192
|
+
in float v_value;
|
|
40193
|
+
in float v_coverage;
|
|
40194
|
+
|
|
40195
|
+
void main() {
|
|
40196
|
+
if (v_coverage < u_coverageCutoff) {
|
|
40197
|
+
discard;
|
|
40198
|
+
}
|
|
40199
|
+
vec4 color = mix(u_lowColor, u_highColor, v_value);
|
|
40200
|
+
|
|
40201
|
+
// Relief shading from the value gradient, so the displacement reads at a distance without
|
|
40202
|
+
// recomputing vertex normals every time the texture changes.
|
|
40203
|
+
float left = texture(u_valueTexture, v_st - vec2(u_texelSize.x, 0.0)).r;
|
|
40204
|
+
float right = texture(u_valueTexture, v_st + vec2(u_texelSize.x, 0.0)).r;
|
|
40205
|
+
float down = texture(u_valueTexture, v_st - vec2(0.0, u_texelSize.y)).r;
|
|
40206
|
+
float up = texture(u_valueTexture, v_st + vec2(0.0, u_texelSize.y)).r;
|
|
40207
|
+
float scale = u_valueRange * u_exaggeration;
|
|
40208
|
+
vec3 n = normalize(vec3((left - right) * scale, (down - up) * scale, 2.0 * u_metresPerTexel));
|
|
40209
|
+
float lambert = clamp(dot(n, normalize(vec3(-0.5, -0.6, 0.62))), 0.0, 1.0);
|
|
40210
|
+
color.rgb *= 0.45 + 0.9 * lambert;
|
|
40211
|
+
|
|
40212
|
+
out_FragColor = vec4(color.rgb, color.a * v_coverage);
|
|
40213
|
+
}
|
|
40214
|
+
`;
|
|
40215
|
+
/*
|
|
40216
|
+
* Rewrites a GLSL 300 es source into the GLSL 100 dialect older Cesium builds author in.
|
|
40217
|
+
*/
|
|
40218
|
+
function toGlsl100(source, isFragment) {
|
|
40219
|
+
let result = source
|
|
40220
|
+
.replace(/\btexture\s*\(/g, "texture2D(")
|
|
40221
|
+
.replace(/^in\s+/gm, isFragment ? "varying " : "attribute ")
|
|
40222
|
+
.replace(/^out\s+/gm, "varying ");
|
|
40223
|
+
if (isFragment) {
|
|
40224
|
+
result = result.replace(/\bout_FragColor\b/g, "gl_FragColor");
|
|
40225
|
+
}
|
|
40226
|
+
return result;
|
|
40227
|
+
}
|
|
40228
|
+
let _glsl300Support = null;
|
|
40229
|
+
/*
|
|
40230
|
+
* Works out which GLSL dialect this host's Cesium authors shaders in.
|
|
40231
|
+
*/
|
|
40232
|
+
function usesGlsl300(context) {
|
|
40233
|
+
if (_glsl300Support !== null) {
|
|
40234
|
+
return _glsl300Support;
|
|
40235
|
+
}
|
|
40236
|
+
let modernizes = false;
|
|
40237
|
+
try {
|
|
40238
|
+
const probe = new Cesium.ShaderSource({
|
|
40239
|
+
sources: ["varying float v_probe;\nvoid main() { gl_Position = vec4(0.0); }"],
|
|
40240
|
+
includeBuiltIns: false
|
|
40241
|
+
});
|
|
40242
|
+
const combined = probe.createCombinedVertexShader(context);
|
|
40243
|
+
modernizes = combined.indexOf("varying") < 0;
|
|
40244
|
+
}
|
|
40245
|
+
catch (e) {
|
|
40246
|
+
// No probe means no evidence the host rewrites anything, so trust the context alone.
|
|
40247
|
+
modernizes = false;
|
|
40248
|
+
}
|
|
40249
|
+
_glsl300Support = Boolean(context.webgl2) && !modernizes;
|
|
40250
|
+
return _glsl300Support;
|
|
40251
|
+
}
|
|
40252
|
+
/*
|
|
40253
|
+
* Places grid node (fx, fy) on the ellipsoid, stepping the way the raster was sampled.
|
|
40254
|
+
* 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.
|
|
40255
|
+
*/
|
|
40256
|
+
function makePlacer(extent, pixelPlacement) {
|
|
40257
|
+
if (pixelPlacement === "lonlat") {
|
|
40258
|
+
return (fx, fy) => Cesium.Cartesian3.fromDegrees(Cesium.Math.lerp(extent.West, extent.East, fx), Cesium.Math.lerp(extent.North, extent.South, fy), 0);
|
|
40259
|
+
}
|
|
40260
|
+
const ellipsoid = Cesium.Ellipsoid.WGS84;
|
|
40261
|
+
const centre = Cesium.Cartesian3.fromDegrees((extent.West + extent.East) / 2, (extent.South + extent.North) / 2, 0);
|
|
40262
|
+
const frame = Cesium.Transforms.eastNorthUpToFixedFrame(centre);
|
|
40263
|
+
const toLocal = Cesium.Matrix4.inverse(frame, new Cesium.Matrix4());
|
|
40264
|
+
const southWest = Cesium.Matrix4.multiplyByPoint(toLocal, Cesium.Cartesian3.fromDegrees(extent.West, extent.South, 0), new Cesium.Cartesian3());
|
|
40265
|
+
const northEast = Cesium.Matrix4.multiplyByPoint(toLocal, Cesium.Cartesian3.fromDegrees(extent.East, extent.North, 0), new Cesium.Cartesian3());
|
|
40266
|
+
return (fx, fy) => {
|
|
40267
|
+
const local = new Cesium.Cartesian3(Cesium.Math.lerp(southWest.x, northEast.x, fx), Cesium.Math.lerp(northEast.y, southWest.y, fy), 0);
|
|
40268
|
+
const world = Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3());
|
|
40269
|
+
return ellipsoid.scaleToGeodeticSurface(world, world);
|
|
40270
|
+
};
|
|
40271
|
+
}
|
|
40272
|
+
/*
|
|
40273
|
+
* Builds one tile's grid, optionally plus a duplicated perimeter ring the shader pushes downwards to
|
|
40274
|
+
* cover the crack where a neighbouring tile at a different density interpolates the shared edge.
|
|
40275
|
+
*/
|
|
40276
|
+
function buildGeometry(extent, resolution, patch, pixelPlacement, withSkirt = false) {
|
|
40277
|
+
const cols = resolution;
|
|
40278
|
+
const rows = resolution;
|
|
40279
|
+
const gridCount = (cols + 1) * (rows + 1);
|
|
40280
|
+
const perimeterCount = withSkirt ? 2 * (cols + 1) + 2 * (rows + 1) - 4 : 0;
|
|
40281
|
+
const count = gridCount + perimeterCount;
|
|
40282
|
+
const positions = new Float64Array(count * 3);
|
|
40283
|
+
const normals = new Float32Array(count * 3);
|
|
40284
|
+
const sts = new Float32Array(count * 2);
|
|
40285
|
+
const skirts = new Float32Array(count);
|
|
40286
|
+
const ellipsoid = Cesium.Ellipsoid.WGS84;
|
|
40287
|
+
const place = makePlacer(extent, pixelPlacement);
|
|
40288
|
+
let v = 0;
|
|
40289
|
+
for (let row = 0; row <= rows; row++) {
|
|
40290
|
+
// Texture row 0 is the north edge, so t runs opposite to northing.
|
|
40291
|
+
const fy = Cesium.Math.lerp(patch.v0, patch.v1, row / rows);
|
|
40292
|
+
for (let col = 0; col <= cols; col++) {
|
|
40293
|
+
const fx = Cesium.Math.lerp(patch.u0, patch.u1, col / cols);
|
|
40294
|
+
const position = place(fx, fy);
|
|
40295
|
+
positions[v * 3] = position.x;
|
|
40296
|
+
positions[v * 3 + 1] = position.y;
|
|
40297
|
+
positions[v * 3 + 2] = position.z;
|
|
40298
|
+
const up = ellipsoid.geodeticSurfaceNormal(position, new Cesium.Cartesian3());
|
|
40299
|
+
normals[v * 3] = up.x;
|
|
40300
|
+
normals[v * 3 + 1] = up.y;
|
|
40301
|
+
normals[v * 3 + 2] = up.z;
|
|
40302
|
+
sts[v * 2] = fx;
|
|
40303
|
+
sts[v * 2 + 1] = fy;
|
|
40304
|
+
v++;
|
|
40305
|
+
}
|
|
40306
|
+
}
|
|
40307
|
+
const indices = new Uint32Array(cols * rows * 6 + perimeterCount * 6);
|
|
40308
|
+
let i = 0;
|
|
40309
|
+
for (let row = 0; row < rows; row++) {
|
|
40310
|
+
for (let col = 0; col < cols; col++) {
|
|
40311
|
+
const topLeft = row * (cols + 1) + col;
|
|
40312
|
+
const topRight = topLeft + 1;
|
|
40313
|
+
const bottomLeft = topLeft + (cols + 1);
|
|
40314
|
+
const bottomRight = bottomLeft + 1;
|
|
40315
|
+
indices[i++] = topLeft;
|
|
40316
|
+
indices[i++] = bottomLeft;
|
|
40317
|
+
indices[i++] = topRight;
|
|
40318
|
+
indices[i++] = topRight;
|
|
40319
|
+
indices[i++] = bottomLeft;
|
|
40320
|
+
indices[i++] = bottomRight;
|
|
40321
|
+
}
|
|
40322
|
+
}
|
|
40323
|
+
// Perimeter walked as one closed loop, so consecutive entries are always neighbours.
|
|
40324
|
+
const loop = [];
|
|
40325
|
+
for (let col = 0; col <= cols; col++) {
|
|
40326
|
+
loop.push(col);
|
|
40327
|
+
}
|
|
40328
|
+
for (let row = 1; row <= rows; row++) {
|
|
40329
|
+
loop.push(row * (cols + 1) + cols);
|
|
40330
|
+
}
|
|
40331
|
+
for (let col = cols - 1; col >= 0; col--) {
|
|
40332
|
+
loop.push(rows * (cols + 1) + col);
|
|
40333
|
+
}
|
|
40334
|
+
for (let row = rows - 1; row >= 1; row--) {
|
|
40335
|
+
loop.push(row * (cols + 1));
|
|
40336
|
+
}
|
|
40337
|
+
const skirtStart = gridCount;
|
|
40338
|
+
if (withSkirt) {
|
|
40339
|
+
loop.forEach((gridIndex, k) => {
|
|
40340
|
+
const target = skirtStart + k;
|
|
40341
|
+
positions[target * 3] = positions[gridIndex * 3];
|
|
40342
|
+
positions[target * 3 + 1] = positions[gridIndex * 3 + 1];
|
|
40343
|
+
positions[target * 3 + 2] = positions[gridIndex * 3 + 2];
|
|
40344
|
+
normals[target * 3] = normals[gridIndex * 3];
|
|
40345
|
+
normals[target * 3 + 1] = normals[gridIndex * 3 + 1];
|
|
40346
|
+
normals[target * 3 + 2] = normals[gridIndex * 3 + 2];
|
|
40347
|
+
sts[target * 2] = sts[gridIndex * 2];
|
|
40348
|
+
sts[target * 2 + 1] = sts[gridIndex * 2 + 1];
|
|
40349
|
+
skirts[target] = 1;
|
|
40350
|
+
});
|
|
40351
|
+
for (let k = 0; k < loop.length; k++) {
|
|
40352
|
+
const nextK = (k + 1) % loop.length;
|
|
40353
|
+
indices[i++] = loop[k];
|
|
40354
|
+
indices[i++] = skirtStart + k;
|
|
40355
|
+
indices[i++] = loop[nextK];
|
|
40356
|
+
indices[i++] = loop[nextK];
|
|
40357
|
+
indices[i++] = skirtStart + k;
|
|
40358
|
+
indices[i++] = skirtStart + nextK;
|
|
40359
|
+
}
|
|
40360
|
+
}
|
|
40361
|
+
const geometry = new Cesium.Geometry({
|
|
40362
|
+
attributes: {
|
|
40363
|
+
position: new Cesium.GeometryAttribute({
|
|
40364
|
+
componentDatatype: Cesium.ComponentDatatype.DOUBLE,
|
|
40365
|
+
componentsPerAttribute: 3,
|
|
40366
|
+
values: positions
|
|
40367
|
+
}),
|
|
40368
|
+
normal: new Cesium.GeometryAttribute({
|
|
40369
|
+
componentDatatype: Cesium.ComponentDatatype.FLOAT,
|
|
40370
|
+
componentsPerAttribute: 3,
|
|
40371
|
+
values: normals
|
|
40372
|
+
}),
|
|
40373
|
+
st: new Cesium.GeometryAttribute({
|
|
40374
|
+
componentDatatype: Cesium.ComponentDatatype.FLOAT,
|
|
40375
|
+
componentsPerAttribute: 2,
|
|
40376
|
+
values: sts
|
|
40377
|
+
}),
|
|
40378
|
+
skirt: new Cesium.GeometryAttribute({
|
|
40379
|
+
componentDatatype: Cesium.ComponentDatatype.FLOAT,
|
|
40380
|
+
componentsPerAttribute: 1,
|
|
40381
|
+
values: skirts
|
|
40382
|
+
})
|
|
40383
|
+
},
|
|
40384
|
+
indices: indices,
|
|
40385
|
+
primitiveType: Cesium.PrimitiveType.TRIANGLES,
|
|
40386
|
+
boundingSphere: Cesium.BoundingSphere.fromVertices(Array.prototype.slice.call(positions))
|
|
40387
|
+
});
|
|
40388
|
+
// Splits position into the high/low pair Cesium's relative-to-eye shaders expect.
|
|
40389
|
+
Cesium.GeometryPipeline.encodeAttribute(geometry, "position", "position3DHigh", "position3DLow");
|
|
40390
|
+
return geometry;
|
|
40391
|
+
}
|
|
40392
|
+
class Surface {
|
|
40393
|
+
constructor(options, hasCoverage) {
|
|
40394
|
+
var _a, _b, _c, _d;
|
|
40395
|
+
this.show = true;
|
|
40396
|
+
this.valueSource = null;
|
|
40397
|
+
this.lastPresentVersion = -1;
|
|
40398
|
+
this.texture = null;
|
|
40399
|
+
this.textureDirty = true;
|
|
40400
|
+
this.shaderProgram = null;
|
|
40401
|
+
this.renderState = null;
|
|
40402
|
+
this.destroyed = false;
|
|
40403
|
+
this.lastEyePosition = null;
|
|
40404
|
+
this.lastSelectionMs = -Infinity;
|
|
40405
|
+
this.selectionDirty = true;
|
|
40406
|
+
this.groundTexture = null;
|
|
40407
|
+
this.groundPixels = null;
|
|
40408
|
+
this.groundRequested = false;
|
|
40409
|
+
this.followGround = Boolean(options.followGround);
|
|
40410
|
+
this.terrainProvider = options.terrainProvider || null;
|
|
40411
|
+
this.extent = options.extent;
|
|
40412
|
+
this.source = options.source || null;
|
|
40413
|
+
this.valueMin = (_a = options.valueMin) !== null && _a !== void 0 ? _a : 0;
|
|
40414
|
+
this.valueMax = (_b = options.valueMax) !== null && _b !== void 0 ? _b : 1;
|
|
40415
|
+
this.exaggeration = (_c = options.exaggeration) !== null && _c !== void 0 ? _c : autoExaggeration(this.extent, this.valueMin, this.valueMax);
|
|
40416
|
+
this.baseHeight = (_d = options.baseHeight) !== null && _d !== void 0 ? _d : 0;
|
|
40417
|
+
this.pixelPlacement = options.pixelPlacement;
|
|
40418
|
+
this.tileSkirts = Boolean(options.tileSkirts);
|
|
40419
|
+
this.lowColor = toCesiumColor(options.lowColor || DEFAULT_LOW_COLOR);
|
|
40420
|
+
this.highColor = toCesiumColor(options.highColor || DEFAULT_HIGH_COLOR);
|
|
40421
|
+
this.tiles = buildTiles(hasCoverage);
|
|
40422
|
+
}
|
|
40423
|
+
GetFollowsGround() {
|
|
40424
|
+
return this.followGround;
|
|
40425
|
+
}
|
|
40426
|
+
GetPixelPlacement() {
|
|
40427
|
+
return this.pixelPlacement;
|
|
40428
|
+
}
|
|
40429
|
+
GetBaseHeight() {
|
|
40430
|
+
return this.baseHeight;
|
|
40431
|
+
}
|
|
40432
|
+
GetExaggeration() {
|
|
40433
|
+
return this.exaggeration;
|
|
40434
|
+
}
|
|
40435
|
+
GetExtent() {
|
|
40436
|
+
return this.extent;
|
|
40437
|
+
}
|
|
40438
|
+
SetExaggeration(exaggeration) {
|
|
40439
|
+
this.exaggeration = exaggeration;
|
|
40440
|
+
}
|
|
40441
|
+
SetBaseHeight(baseHeight) {
|
|
40442
|
+
this.baseHeight = baseHeight;
|
|
40443
|
+
}
|
|
40444
|
+
/**
|
|
40445
|
+
* Feeds the surface from an animated frame series.
|
|
40446
|
+
* Height and colour advance with the clock without the caller having to push each frame in.
|
|
40447
|
+
*/
|
|
40448
|
+
BindAnimator(source) {
|
|
40449
|
+
this.valueSource = source;
|
|
40450
|
+
}
|
|
40451
|
+
/**
|
|
40452
|
+
* Tells the surface its source canvas has been redrawn and the texture needs re-uploading.
|
|
40453
|
+
*/
|
|
40454
|
+
MarkSourceDirty() {
|
|
40455
|
+
this.textureDirty = true;
|
|
40456
|
+
}
|
|
40457
|
+
update(frameState) {
|
|
40458
|
+
if (!this.show || this.destroyed || this.tiles.length === 0) {
|
|
40459
|
+
return;
|
|
40460
|
+
}
|
|
40461
|
+
const context = frameState.context;
|
|
40462
|
+
if (this.valueSource) {
|
|
40463
|
+
const canvas = this.valueSource.GetValueCanvas();
|
|
40464
|
+
if (!canvas) {
|
|
40465
|
+
// No frame decoded yet, so there is nothing to displace against.
|
|
40466
|
+
return;
|
|
40467
|
+
}
|
|
40468
|
+
const version = this.valueSource.GetPresentVersion();
|
|
40469
|
+
if (canvas !== this.source || version !== this.lastPresentVersion) {
|
|
40470
|
+
this.source = canvas;
|
|
40471
|
+
this.lastPresentVersion = version;
|
|
40472
|
+
this.textureDirty = true;
|
|
40473
|
+
}
|
|
40474
|
+
}
|
|
40475
|
+
if (!this.source || !this.source.width || !this.source.height) {
|
|
40476
|
+
return;
|
|
40477
|
+
}
|
|
40478
|
+
this.syncTexture(context);
|
|
40479
|
+
this.requestGround();
|
|
40480
|
+
this.syncGroundTexture(context);
|
|
40481
|
+
// A tile needs one mesh before it has a bounding sphere to measure against.
|
|
40482
|
+
for (const tile of this.tiles) {
|
|
40483
|
+
if (!tile.centre) {
|
|
40484
|
+
this.ensureMesh(context, tile, TIERS[0]);
|
|
40485
|
+
}
|
|
40486
|
+
}
|
|
40487
|
+
const nowMs = now();
|
|
40488
|
+
if (this.shouldReselect(frameState, nowMs)) {
|
|
40489
|
+
this.selectTiers(frameState);
|
|
40490
|
+
this.lastSelectionMs = nowMs;
|
|
40491
|
+
this.lastEyePosition = Cesium.Cartesian3.clone(frameState.camera.positionWC, this.lastEyePosition);
|
|
40492
|
+
this.selectionDirty = false;
|
|
40493
|
+
}
|
|
40494
|
+
for (const tile of this.tiles) {
|
|
40495
|
+
const mesh = this.ensureMesh(context, tile, tile.tier);
|
|
40496
|
+
frameState.commandList.push(this.commandFor(context, tile, mesh));
|
|
40497
|
+
}
|
|
40498
|
+
}
|
|
40499
|
+
isDestroyed() {
|
|
40500
|
+
return this.destroyed;
|
|
40501
|
+
}
|
|
40502
|
+
destroy() {
|
|
40503
|
+
this.destroyed = true;
|
|
40504
|
+
for (const tile of this.tiles) {
|
|
40505
|
+
tile.meshes.forEach((mesh) => mesh.vertexArray.destroy());
|
|
40506
|
+
tile.meshes.clear();
|
|
40507
|
+
tile.command = null;
|
|
40508
|
+
}
|
|
40509
|
+
if (this.shaderProgram) {
|
|
40510
|
+
this.shaderProgram.destroy();
|
|
40511
|
+
this.shaderProgram = null;
|
|
40512
|
+
}
|
|
40513
|
+
if (this.texture) {
|
|
40514
|
+
this.texture.destroy();
|
|
40515
|
+
this.texture = null;
|
|
40516
|
+
}
|
|
40517
|
+
if (this.groundTexture) {
|
|
40518
|
+
this.groundTexture.destroy();
|
|
40519
|
+
this.groundTexture = null;
|
|
40520
|
+
}
|
|
40521
|
+
return undefined;
|
|
40522
|
+
}
|
|
40523
|
+
/*
|
|
40524
|
+
* Samples terrain across the extent once and packs it into a texture the vertex shader reads.
|
|
40525
|
+
*
|
|
40526
|
+
* A texture rather than heights baked into the meshes, because the samples arrive
|
|
40527
|
+
* asynchronously and every cached tile mesh would otherwise have to be rebuilt when they land.
|
|
40528
|
+
*/
|
|
40529
|
+
requestGround() {
|
|
40530
|
+
if (this.groundRequested || !this.followGround) {
|
|
40531
|
+
return;
|
|
40532
|
+
}
|
|
40533
|
+
this.groundRequested = true;
|
|
40534
|
+
const sampler = Cesium.sampleTerrain;
|
|
40535
|
+
if (!this.terrainProvider || typeof sampler !== "function") {
|
|
40536
|
+
return;
|
|
40537
|
+
}
|
|
40538
|
+
const positions = [];
|
|
40539
|
+
for (let row = 0; row < GROUND_SAMPLES_PER_SIDE; row++) {
|
|
40540
|
+
const fy = row / (GROUND_SAMPLES_PER_SIDE - 1);
|
|
40541
|
+
const latitude = Cesium.Math.lerp(this.extent.North, this.extent.South, fy);
|
|
40542
|
+
for (let col = 0; col < GROUND_SAMPLES_PER_SIDE; col++) {
|
|
40543
|
+
const fx = col / (GROUND_SAMPLES_PER_SIDE - 1);
|
|
40544
|
+
const longitude = Cesium.Math.lerp(this.extent.West, this.extent.East, fx);
|
|
40545
|
+
positions.push(Cesium.Cartographic.fromDegrees(longitude, latitude));
|
|
40546
|
+
}
|
|
40547
|
+
}
|
|
40548
|
+
Promise.resolve(sampler(this.terrainProvider, GROUND_SAMPLE_LEVEL, positions))
|
|
40549
|
+
.then((sampled) => {
|
|
40550
|
+
if (this.destroyed || !sampled) {
|
|
40551
|
+
return;
|
|
40552
|
+
}
|
|
40553
|
+
const pixels = new Uint8Array(GROUND_SAMPLES_PER_SIDE * GROUND_SAMPLES_PER_SIDE * 4);
|
|
40554
|
+
const span = GROUND_MAX_METRES - GROUND_MIN_METRES;
|
|
40555
|
+
for (let i = 0; i < sampled.length; i++) {
|
|
40556
|
+
const height = sampled[i] && isFinite(sampled[i].height) ? sampled[i].height : 0;
|
|
40557
|
+
const clamped = Math.min(GROUND_MAX_METRES, Math.max(GROUND_MIN_METRES, height));
|
|
40558
|
+
const packed = Math.round(((clamped - GROUND_MIN_METRES) / span) * 16777215);
|
|
40559
|
+
pixels[i * 4] = (packed >> 16) & 255;
|
|
40560
|
+
pixels[i * 4 + 1] = (packed >> 8) & 255;
|
|
40561
|
+
pixels[i * 4 + 2] = packed & 255;
|
|
40562
|
+
pixels[i * 4 + 3] = 255;
|
|
40563
|
+
}
|
|
40564
|
+
this.groundPixels = pixels;
|
|
40565
|
+
})
|
|
40566
|
+
.catch(() => {
|
|
40567
|
+
// Terrain that cannot be sampled leaves the surface on the ellipsoid, which is
|
|
40568
|
+
// where a clamped polygon sat before this existed.
|
|
40569
|
+
});
|
|
40570
|
+
}
|
|
40571
|
+
/*
|
|
40572
|
+
* Uploads whatever ground samples exist, and a flat stand-in until they arrive, so the
|
|
40573
|
+
* shader always has a texture bound whether or not this surface follows the ground.
|
|
40574
|
+
*/
|
|
40575
|
+
syncGroundTexture(context) {
|
|
40576
|
+
if (this.groundTexture && (!this.groundPixels || this.groundTexture.width === GROUND_SAMPLES_PER_SIDE)) {
|
|
40577
|
+
return;
|
|
40578
|
+
}
|
|
40579
|
+
if (this.groundTexture) {
|
|
40580
|
+
this.groundTexture.destroy();
|
|
40581
|
+
}
|
|
40582
|
+
const zero = Math.round(((0 - GROUND_MIN_METRES) / (GROUND_MAX_METRES - GROUND_MIN_METRES)) * 16777215);
|
|
40583
|
+
const size = this.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1;
|
|
40584
|
+
const arrayBufferView = this.groundPixels
|
|
40585
|
+
|| new Uint8Array([(zero >> 16) & 255, (zero >> 8) & 255, zero & 255, 255]);
|
|
40586
|
+
this.groundTexture = new Cesium.Texture({
|
|
40587
|
+
context,
|
|
40588
|
+
pixelFormat: Cesium.PixelFormat.RGBA,
|
|
40589
|
+
source: { width: size, height: size, arrayBufferView },
|
|
40590
|
+
flipY: false,
|
|
40591
|
+
sampler: new Cesium.Sampler({
|
|
40592
|
+
wrapS: Cesium.TextureWrap.CLAMP_TO_EDGE,
|
|
40593
|
+
wrapT: Cesium.TextureWrap.CLAMP_TO_EDGE,
|
|
40594
|
+
// Nearest, because the shader interpolates decoded heights itself.
|
|
40595
|
+
minificationFilter: Cesium.TextureMinificationFilter.NEAREST,
|
|
40596
|
+
magnificationFilter: Cesium.TextureMagnificationFilter.NEAREST
|
|
40597
|
+
})
|
|
40598
|
+
});
|
|
40599
|
+
}
|
|
40600
|
+
/*
|
|
40601
|
+
* Ground distance between neighbouring texels, so the shading normal is built from a real
|
|
40602
|
+
* slope rather than a made up one.
|
|
40603
|
+
*/
|
|
40604
|
+
metresPerTexel() {
|
|
40605
|
+
const southWest = Cesium.Cartesian3.fromDegrees(this.extent.West, this.extent.South, 0);
|
|
40606
|
+
const southEast = Cesium.Cartesian3.fromDegrees(this.extent.East, this.extent.South, 0);
|
|
40607
|
+
const width = Cesium.Cartesian3.distance(southWest, southEast);
|
|
40608
|
+
return Math.max(1, width / Math.max(1, this.source ? this.source.width : 1));
|
|
40609
|
+
}
|
|
40610
|
+
syncTexture(context) {
|
|
40611
|
+
const sizeChanged = this.texture
|
|
40612
|
+
&& (this.texture.width !== this.source.width || this.texture.height !== this.source.height);
|
|
40613
|
+
if (!this.texture || sizeChanged) {
|
|
40614
|
+
if (this.texture) {
|
|
40615
|
+
this.texture.destroy();
|
|
40616
|
+
}
|
|
40617
|
+
this.texture = new Cesium.Texture({
|
|
40618
|
+
context,
|
|
40619
|
+
source: this.source,
|
|
40620
|
+
// Cesium flips canvas rows on upload by default, which would put the archive's
|
|
40621
|
+
// row 0 (its north edge) at the south edge and mirror the whole surface.
|
|
40622
|
+
flipY: false,
|
|
40623
|
+
sampler: new Cesium.Sampler({
|
|
40624
|
+
wrapS: Cesium.TextureWrap.CLAMP_TO_EDGE,
|
|
40625
|
+
wrapT: Cesium.TextureWrap.CLAMP_TO_EDGE,
|
|
40626
|
+
minificationFilter: Cesium.TextureMinificationFilter.LINEAR,
|
|
40627
|
+
magnificationFilter: Cesium.TextureMagnificationFilter.LINEAR
|
|
40628
|
+
})
|
|
40629
|
+
});
|
|
40630
|
+
this.textureDirty = false;
|
|
40631
|
+
return;
|
|
40632
|
+
}
|
|
40633
|
+
if (this.textureDirty) {
|
|
40634
|
+
this.texture.copyFrom({ source: this.source });
|
|
40635
|
+
this.textureDirty = false;
|
|
40636
|
+
}
|
|
40637
|
+
}
|
|
40638
|
+
ensureMesh(context, tile, tier) {
|
|
40639
|
+
const cached = tile.meshes.get(tier);
|
|
40640
|
+
if (cached) {
|
|
40641
|
+
return cached;
|
|
40642
|
+
}
|
|
40643
|
+
const geometry = buildGeometry(this.extent, tier, tile.patch, this.pixelPlacement, this.tileSkirts);
|
|
40644
|
+
const attributeLocations = Cesium.GeometryPipeline.createAttributeLocations(geometry);
|
|
40645
|
+
const mesh = {
|
|
40646
|
+
attributeLocations,
|
|
40647
|
+
vertexArray: Cesium.VertexArray.fromGeometry({
|
|
40648
|
+
context,
|
|
40649
|
+
geometry,
|
|
40650
|
+
attributeLocations,
|
|
40651
|
+
bufferUsage: Cesium.BufferUsage.STATIC_DRAW
|
|
40652
|
+
}),
|
|
40653
|
+
boundingSphere: geometry.boundingSphere
|
|
40654
|
+
};
|
|
40655
|
+
tile.meshes.set(tier, mesh);
|
|
40656
|
+
tile.centre = geometry.boundingSphere.center;
|
|
40657
|
+
tile.radius = geometry.boundingSphere.radius;
|
|
40658
|
+
return mesh;
|
|
40659
|
+
}
|
|
40660
|
+
/*
|
|
40661
|
+
* Picks each tile's density from how much screen it covers.
|
|
40662
|
+
*/
|
|
40663
|
+
selectTiers(frameState) {
|
|
40664
|
+
const camera = frameState.camera;
|
|
40665
|
+
const fovy = camera.frustum && camera.frustum.fovy ? camera.frustum.fovy : Cesium.Math.PI_OVER_THREE;
|
|
40666
|
+
const pixelsPerRadian = frameState.context.drawingBufferHeight / fovy;
|
|
40667
|
+
for (const tile of this.tiles) {
|
|
40668
|
+
if (!tile.centre) {
|
|
40669
|
+
tile.tier = TIERS[0];
|
|
40670
|
+
continue;
|
|
40671
|
+
}
|
|
40672
|
+
const distance = Math.max(1, Cesium.Cartesian3.distance(camera.positionWC, tile.centre) - tile.radius);
|
|
40673
|
+
const screenPixels = (2 * tile.radius / distance) * pixelsPerRadian;
|
|
40674
|
+
const wanted = screenPixels / TARGET_CELL_PIXELS;
|
|
40675
|
+
let tier = TIERS[0];
|
|
40676
|
+
for (const candidate of TIERS) {
|
|
40677
|
+
if (candidate <= wanted) {
|
|
40678
|
+
tier = candidate;
|
|
40679
|
+
}
|
|
40680
|
+
}
|
|
40681
|
+
tile.tier = tier;
|
|
40682
|
+
}
|
|
40683
|
+
}
|
|
40684
|
+
shouldReselect(frameState, nowMs) {
|
|
40685
|
+
if (this.selectionDirty) {
|
|
40686
|
+
return true;
|
|
40687
|
+
}
|
|
40688
|
+
if (nowMs - this.lastSelectionMs < RESELECT_INTERVAL_MS) {
|
|
40689
|
+
return false;
|
|
40690
|
+
}
|
|
40691
|
+
const eye = frameState.camera.positionWC;
|
|
40692
|
+
if (!this.lastEyePosition) {
|
|
40693
|
+
return true;
|
|
40694
|
+
}
|
|
40695
|
+
const moved = Cesium.Cartesian3.distance(eye, this.lastEyePosition);
|
|
40696
|
+
const anchor = this.tiles[0].centre;
|
|
40697
|
+
const reference = anchor ? Math.max(1, Cesium.Cartesian3.distance(eye, anchor)) : 1;
|
|
40698
|
+
return moved / reference > MOVEMENT_THRESHOLD;
|
|
40699
|
+
}
|
|
40700
|
+
commandFor(context, tile, mesh) {
|
|
40701
|
+
if (!this.shaderProgram) {
|
|
40702
|
+
const glsl300 = usesGlsl300(context);
|
|
40703
|
+
this.shaderProgram = Cesium.ShaderProgram.fromCache({
|
|
40704
|
+
context,
|
|
40705
|
+
vertexShaderSource: glsl300 ? VERTEX_SHADER_GLSL300 : toGlsl100(VERTEX_SHADER_GLSL300, false),
|
|
40706
|
+
fragmentShaderSource: glsl300 ? FRAGMENT_SHADER_GLSL300 : toGlsl100(FRAGMENT_SHADER_GLSL300, true),
|
|
40707
|
+
attributeLocations: mesh.attributeLocations
|
|
40708
|
+
});
|
|
40709
|
+
this.renderState = Cesium.RenderState.fromCache({
|
|
40710
|
+
depthTest: { enabled: true },
|
|
40711
|
+
// Translucent ramp: blend, and leave the depth buffer to the opaque world.
|
|
40712
|
+
depthMask: false,
|
|
40713
|
+
blending: Cesium.BlendingState.ALPHA_BLEND,
|
|
40714
|
+
cull: { enabled: false }
|
|
40715
|
+
});
|
|
40716
|
+
}
|
|
40717
|
+
if (!tile.command) {
|
|
40718
|
+
const self = this;
|
|
40719
|
+
tile.command = new Cesium.DrawCommand({
|
|
40720
|
+
shaderProgram: this.shaderProgram,
|
|
40721
|
+
renderState: this.renderState,
|
|
40722
|
+
pass: Cesium.Pass.TRANSLUCENT,
|
|
40723
|
+
modelMatrix: Cesium.Matrix4.IDENTITY,
|
|
40724
|
+
owner: this,
|
|
40725
|
+
uniformMap: {
|
|
40726
|
+
u_valueTexture: () => self.texture,
|
|
40727
|
+
u_groundTexture: () => self.groundTexture,
|
|
40728
|
+
u_groundWeight: () => (self.followGround && self.groundPixels ? 1 : 0),
|
|
40729
|
+
u_groundRange: () => new Cesium.Cartesian2(GROUND_MIN_METRES, GROUND_MAX_METRES),
|
|
40730
|
+
u_groundSize: () => new Cesium.Cartesian2(self.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1, self.groundPixels ? GROUND_SAMPLES_PER_SIDE : 1),
|
|
40731
|
+
u_metresPerTexel: () => self.metresPerTexel(),
|
|
40732
|
+
u_valueMin: () => self.valueMin,
|
|
40733
|
+
u_valueRange: () => self.valueMax - self.valueMin,
|
|
40734
|
+
u_exaggeration: () => self.exaggeration,
|
|
40735
|
+
u_baseHeight: () => self.baseHeight,
|
|
40736
|
+
u_skirtDepth: () => self.tileSkirts
|
|
40737
|
+
? Math.max(2, Math.abs(self.valueMax - self.valueMin) * self.exaggeration * SKIRT_FRACTION)
|
|
40738
|
+
: 0,
|
|
40739
|
+
u_coverageCutoff: () => COVERAGE_CUTOFF,
|
|
40740
|
+
u_lowColor: () => self.lowColor,
|
|
40741
|
+
u_highColor: () => self.highColor,
|
|
40742
|
+
u_texelSize: () => new Cesium.Cartesian2(1 / self.source.width, 1 / self.source.height)
|
|
40743
|
+
}
|
|
40744
|
+
});
|
|
40745
|
+
}
|
|
40746
|
+
// Bounding volume belongs to the mesh, so it follows whichever tier is drawing.
|
|
40747
|
+
tile.command.vertexArray = mesh.vertexArray;
|
|
40748
|
+
tile.command.boundingVolume = mesh.boundingSphere;
|
|
40749
|
+
return tile.command;
|
|
40750
|
+
}
|
|
40751
|
+
}
|
|
40752
|
+
DisplacedSurfacePrimitive.Surface = Surface;
|
|
40753
|
+
/*
|
|
40754
|
+
* Cuts the extent into tiles, dropping any the caller says carry no data.
|
|
40755
|
+
*/
|
|
40756
|
+
function buildTiles(hasCoverage) {
|
|
40757
|
+
const tiles = [];
|
|
40758
|
+
for (let row = 0; row < TILES_PER_SIDE; row++) {
|
|
40759
|
+
for (let col = 0; col < TILES_PER_SIDE; col++) {
|
|
40760
|
+
const patch = {
|
|
40761
|
+
u0: col / TILES_PER_SIDE,
|
|
40762
|
+
u1: (col + 1) / TILES_PER_SIDE,
|
|
40763
|
+
v0: row / TILES_PER_SIDE,
|
|
40764
|
+
v1: (row + 1) / TILES_PER_SIDE
|
|
40765
|
+
};
|
|
40766
|
+
if (hasCoverage && !hasCoverage(patch)) {
|
|
40767
|
+
continue;
|
|
40768
|
+
}
|
|
40769
|
+
tiles.push({
|
|
40770
|
+
patch,
|
|
40771
|
+
meshes: new Map(),
|
|
40772
|
+
tier: TIERS[0],
|
|
40773
|
+
command: null,
|
|
40774
|
+
centre: null,
|
|
40775
|
+
radius: 0
|
|
40776
|
+
});
|
|
40777
|
+
}
|
|
40778
|
+
}
|
|
40779
|
+
return tiles;
|
|
40780
|
+
}
|
|
40781
|
+
/**
|
|
40782
|
+
* Exaggeration that makes the full value range stand AUTO_RELIEF_FRACTION of the extent tall.
|
|
40783
|
+
*
|
|
40784
|
+
* Derived from the whole extent rather than per tile: scaling each tile to its own size would
|
|
40785
|
+
* make neighbours disagree along their shared edge and tear the surface apart.
|
|
40786
|
+
* @param extent
|
|
40787
|
+
* @param valueMin
|
|
40788
|
+
* @param valueMax
|
|
40789
|
+
*/
|
|
40790
|
+
function autoExaggeration(extent, valueMin, valueMax) {
|
|
40791
|
+
const range = Math.abs(valueMax - valueMin);
|
|
40792
|
+
if (!(range > 0)) {
|
|
40793
|
+
return 1;
|
|
40794
|
+
}
|
|
40795
|
+
const southWest = Cesium.Cartesian3.fromDegrees(extent.West, extent.South, 0);
|
|
40796
|
+
const southEast = Cesium.Cartesian3.fromDegrees(extent.East, extent.South, 0);
|
|
40797
|
+
const northWest = Cesium.Cartesian3.fromDegrees(extent.West, extent.North, 0);
|
|
40798
|
+
const shorterSide = Math.min(Cesium.Cartesian3.distance(southWest, southEast), Cesium.Cartesian3.distance(southWest, northWest));
|
|
40799
|
+
if (!(shorterSide > 0)) {
|
|
40800
|
+
return 1;
|
|
40801
|
+
}
|
|
40802
|
+
return (shorterSide * AUTO_RELIEF_FRACTION) / range;
|
|
40803
|
+
}
|
|
40804
|
+
DisplacedSurfacePrimitive.autoExaggeration = autoExaggeration;
|
|
40805
|
+
function toCesiumColor(color) {
|
|
40806
|
+
return new Cesium.Color(color.red / 255, color.green / 255, color.blue / 255, color.alpha);
|
|
40807
|
+
}
|
|
40808
|
+
function now() {
|
|
40809
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
40810
|
+
}
|
|
40811
|
+
})(exports.DisplacedSurfacePrimitive || (exports.DisplacedSurfacePrimitive = {}));
|
|
40812
|
+
|
|
39897
40813
|
const TEXTURE_FRAME_SERIES_ANIMATOR_KEY = "TextureFrameSeriesAnimator.Animator";
|
|
40814
|
+
const EXTRUSION_ANIMATOR_KEY = "TextureFrameSeriesAnimator.ExtrusionAnimator";
|
|
40815
|
+
const DISPLACED_SURFACE_KEY = "DisplacedSurfacePrimitive.Surface";
|
|
39898
40816
|
(function (EntityRenderEnginePolygon) {
|
|
39899
40817
|
async function Render(params) {
|
|
39900
|
-
var _a, _b, _c;
|
|
40818
|
+
var _a, _b, _c, _d;
|
|
39901
40819
|
const entity = params.entity;
|
|
39902
40820
|
const style = params.style;
|
|
39903
40821
|
const pRings = BModels.Entity.GetValue({
|
|
@@ -39930,14 +40848,38 @@
|
|
|
39930
40848
|
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);
|
|
39931
40849
|
}
|
|
39932
40850
|
}
|
|
39933
|
-
const
|
|
39934
|
-
|
|
39935
|
-
|
|
39936
|
-
|
|
39937
|
-
|
|
39938
|
-
|
|
39939
|
-
|
|
39940
|
-
|
|
40851
|
+
const usesTextureExtrusion = (Boolean(style.useExtrusion) &&
|
|
40852
|
+
style.extrusionType === BModels.Style.EPolygonExtrusionType.Texture);
|
|
40853
|
+
let extrusionArchive = null;
|
|
40854
|
+
if (usesTextureExtrusion && !params.offline) {
|
|
40855
|
+
try {
|
|
40856
|
+
const resolved = await resolveTextureValue({
|
|
40857
|
+
api: params.api,
|
|
40858
|
+
textureValue: style.extrusionTexture || style.texture,
|
|
40859
|
+
style,
|
|
40860
|
+
entity,
|
|
40861
|
+
tags: params.tags
|
|
40862
|
+
});
|
|
40863
|
+
extrusionArchive = resolved.frameArchive || null;
|
|
40864
|
+
}
|
|
40865
|
+
catch (e) {
|
|
40866
|
+
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);
|
|
40867
|
+
}
|
|
40868
|
+
}
|
|
40869
|
+
const drawsDisplacedSurface = Boolean(extrusionArchive);
|
|
40870
|
+
// The displaced surface IS the entity's fill, drawn as its own primitive at the value's height.
|
|
40871
|
+
// Leaving the flat polygon filled as well shows a second copy of the parcel on the ground under it.
|
|
40872
|
+
const hasFill = (drawsDisplacedSurface ? true
|
|
40873
|
+
: fillType === BModels.Style.EPolygonFillType.Texture
|
|
40874
|
+
? Boolean(textureDataUri || frameArchive || cFillColor.alpha > 0)
|
|
40875
|
+
: cFillColor.alpha > 0);
|
|
40876
|
+
const fillMaterial = drawsDisplacedSurface
|
|
40877
|
+
? Cesium.Color.WHITE.withAlpha(0)
|
|
40878
|
+
: textureDataUri
|
|
40879
|
+
? new Cesium.ImageMaterialProperty({ image: textureDataUri, transparent: true })
|
|
40880
|
+
: frameArchive
|
|
40881
|
+
? Cesium.Color.WHITE.withAlpha(0)
|
|
40882
|
+
: cFillColor;
|
|
39941
40883
|
const lineColorTrace = style.lineColor ? BModels.Calculator.TraceGetColor(style.lineColor, entity, params.tags) : { value: null, effective: null };
|
|
39942
40884
|
const bLineColor = lineColorTrace.value;
|
|
39943
40885
|
const cLineColor = bLineColor ? ColorToCColor(bLineColor) : Cesium.Color.fromCssColorString("rgba(80, 80, 80, 0.8)");
|
|
@@ -39964,7 +40906,9 @@
|
|
|
39964
40906
|
const points = BModels.Geometry.ParsePoints(outerRing === null || outerRing === void 0 ? void 0 : outerRing.LinearRing);
|
|
39965
40907
|
let posses = points.map(x => Cesium.Cartesian3.fromDegrees(EnsureNumber(x.longitude), EnsureNumber(x.latitude), EnsureNumber(x.altitude)));
|
|
39966
40908
|
BModels.Cartes.CloseRing3(posses);
|
|
39967
|
-
const extrusion =
|
|
40909
|
+
const extrusion = drawsDisplacedSurface
|
|
40910
|
+
? { posses, value: undefined, exHeightRef: Cesium.HeightReference.NONE }
|
|
40911
|
+
: getPolygonExtrusion(entity, params.tags, outerRing, posses, heightRef, style);
|
|
39968
40912
|
posses = extrusion.posses;
|
|
39969
40913
|
posses = CullDuplicateCPosses(posses);
|
|
39970
40914
|
if (posses.length < 4) {
|
|
@@ -40013,6 +40957,7 @@
|
|
|
40013
40957
|
polygon: {
|
|
40014
40958
|
hierarchy: new Cesium.PolygonHierarchy(posses, holePosses.map(x => new Cesium.PolygonHierarchy(x))),
|
|
40015
40959
|
material: fillMaterial,
|
|
40960
|
+
fill: !drawsDisplacedSurface,
|
|
40016
40961
|
extrudedHeight: extrusion.value,
|
|
40017
40962
|
extrudedHeightReference: extrusion.exHeightRef,
|
|
40018
40963
|
shadows: Cesium.ShadowMode.ENABLED,
|
|
@@ -40043,6 +40988,12 @@
|
|
|
40043
40988
|
if (!CompareCPolygonHierarchies(oldHierarchy, newHierarchy)) {
|
|
40044
40989
|
cEntity.polygon.hierarchy = new Cesium.ConstantProperty(newHierarchy);
|
|
40045
40990
|
}
|
|
40991
|
+
// Toggling extrusion styling on an already-rendered entity flips this,
|
|
40992
|
+
// so it has to be reconciled rather than set only at creation.
|
|
40993
|
+
const oldFill = GetCValue(params.viewer, cEntity.polygon.fill);
|
|
40994
|
+
if (oldFill !== !drawsDisplacedSurface) {
|
|
40995
|
+
cEntity.polygon.fill = new Cesium.ConstantProperty(!drawsDisplacedSurface);
|
|
40996
|
+
}
|
|
40046
40997
|
const oldExtrusion = GetCValue(params.viewer, cEntity.polygon.extrudedHeight);
|
|
40047
40998
|
if (oldExtrusion != extrusion.value) {
|
|
40048
40999
|
cEntity.polygon.extrudedHeight = new Cesium.ConstantProperty(extrusion.value);
|
|
@@ -40096,7 +41047,16 @@
|
|
|
40096
41047
|
}
|
|
40097
41048
|
// Must run before any material is baked below: the Animator takes over polygon.material, so a bake
|
|
40098
41049
|
// has to know whether it is baking over a live animation or over a disposed one's restored material.
|
|
40099
|
-
const animator = syncTextureFrameArchive(cEntity, frameArchive, params.viewer, style.textureColorMask);
|
|
41050
|
+
const animator = syncTextureFrameArchive(cEntity, drawsDisplacedSurface ? null : frameArchive, params.viewer, style.textureColorMask, style.maskTextureBaseline);
|
|
41051
|
+
syncDisplacedSurface({
|
|
41052
|
+
cEntity,
|
|
41053
|
+
extrusionArchive,
|
|
41054
|
+
viewer: params.viewer,
|
|
41055
|
+
style,
|
|
41056
|
+
entity,
|
|
41057
|
+
heightRef,
|
|
41058
|
+
outerRingPosses: posses
|
|
41059
|
+
});
|
|
40100
41060
|
if (animator) {
|
|
40101
41061
|
exports.CesiumEntityStyler.SetDefaultTextureImage({
|
|
40102
41062
|
entity: cEntity,
|
|
@@ -40156,7 +41116,7 @@
|
|
|
40156
41116
|
}
|
|
40157
41117
|
}
|
|
40158
41118
|
let borderPosses = posses.map(x => x.clone ? x.clone() : { ...x });
|
|
40159
|
-
let cEntityBorder = (
|
|
41119
|
+
let cEntityBorder = (_d = (_c = params.rendered) === null || _c === void 0 ? void 0 : _c._siblingGraphics) === null || _d === void 0 ? void 0 : _d[0];
|
|
40160
41120
|
cEntity._siblingGraphics = [];
|
|
40161
41121
|
if (!cEntityBorder || ((!cEntityBorder.polyline && units == "px") ||
|
|
40162
41122
|
(!cEntityBorder.corridor && units == "m"))) {
|
|
@@ -40406,12 +41366,13 @@
|
|
|
40406
41366
|
/**
|
|
40407
41367
|
* Disposes a cEntity's TextureFrameSeriesAnimator.Animator (if any).
|
|
40408
41368
|
*/
|
|
40409
|
-
function DisposeTextureFrameSeriesAnimator(cEntity) {
|
|
41369
|
+
function DisposeTextureFrameSeriesAnimator(cEntity, viewer) {
|
|
40410
41370
|
const existing = cEntity === null || cEntity === void 0 ? void 0 : cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY];
|
|
40411
41371
|
if (existing && !existing.IsDisposed()) {
|
|
40412
41372
|
existing.Dispose();
|
|
40413
41373
|
cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = null;
|
|
40414
41374
|
}
|
|
41375
|
+
disposeDisplacedSurface(cEntity, viewer);
|
|
40415
41376
|
}
|
|
40416
41377
|
EntityRenderEnginePolygon.DisposeTextureFrameSeriesAnimator = DisposeTextureFrameSeriesAnimator;
|
|
40417
41378
|
})(exports.EntityRenderEnginePolygon || (exports.EntityRenderEnginePolygon = {}));
|
|
@@ -40525,6 +41486,145 @@
|
|
|
40525
41486
|
// Cache of tinted texture data URIs, keyed by resolved url + color mask, so repeated renders of the
|
|
40526
41487
|
// same polygon (or multiple polygons sharing a texture) don't refetch/reprocess the same image.
|
|
40527
41488
|
const _textureCache = new BModels.LRUCache(50);
|
|
41489
|
+
/**
|
|
41490
|
+
* Reconciles the displaced surface (and the animator feeding it) against this render's resolved
|
|
41491
|
+
* extrusion texture, creating, reusing or tearing down as the style demands.
|
|
41492
|
+
* @param params
|
|
41493
|
+
*/
|
|
41494
|
+
function syncDisplacedSurface(params) {
|
|
41495
|
+
var _a;
|
|
41496
|
+
const { cEntity, extrusionArchive, viewer, style, entity, heightRef, outerRingPosses } = params;
|
|
41497
|
+
const existingAnimator = cEntity[EXTRUSION_ANIMATOR_KEY];
|
|
41498
|
+
const existingSurface = cEntity[DISPLACED_SURFACE_KEY];
|
|
41499
|
+
// Nothing to draw and nothing left over, which is every polygon that does not use this feature.
|
|
41500
|
+
if (!extrusionArchive && !existingAnimator && !existingSurface) {
|
|
41501
|
+
return;
|
|
41502
|
+
}
|
|
41503
|
+
const reusable = extrusionArchive
|
|
41504
|
+
&& existingAnimator
|
|
41505
|
+
&& !existingAnimator.IsDisposed()
|
|
41506
|
+
&& existingAnimator.GetArchiveUrl() === extrusionArchive.url
|
|
41507
|
+
&& existingSurface
|
|
41508
|
+
&& !existingSurface.isDestroyed();
|
|
41509
|
+
const placement = surfacePlacement(entity, heightRef);
|
|
41510
|
+
// Whether the surface follows terrain is baked into its ground sampling, so a change there has
|
|
41511
|
+
// to rebuild rather than update in place.
|
|
41512
|
+
if (reusable && existingSurface.GetFollowsGround() === placement.followGround) {
|
|
41513
|
+
existingSurface.SetBaseHeight(placement.baseHeight);
|
|
41514
|
+
existingSurface.SetExaggeration((_a = style.extrusionExaggeration) !== null && _a !== void 0 ? _a : exports.DisplacedSurfacePrimitive.autoExaggeration(existingSurface.GetExtent(), extrusionArchive.metadata.ValueMin, extrusionArchive.metadata.ValueMax));
|
|
41515
|
+
return;
|
|
41516
|
+
}
|
|
41517
|
+
disposeDisplacedSurface(cEntity, viewer);
|
|
41518
|
+
if (!extrusionArchive) {
|
|
41519
|
+
return;
|
|
41520
|
+
}
|
|
41521
|
+
const extent = archiveExtent(extrusionArchive.metadata, outerRingPosses);
|
|
41522
|
+
if (!extent) {
|
|
41523
|
+
console.warn("Polygon.Render: extrusion texture has no usable extent, skipping displaced surface.");
|
|
41524
|
+
return;
|
|
41525
|
+
}
|
|
41526
|
+
// Drives the value canvas only: the surface paints the colour, so the polygon's own material is
|
|
41527
|
+
// left transparent rather than animated underneath it.
|
|
41528
|
+
const animator = new TextureFrameSeriesAnimator.Animator({
|
|
41529
|
+
viewer,
|
|
41530
|
+
entity: cEntity,
|
|
41531
|
+
archiveUrl: extrusionArchive.url,
|
|
41532
|
+
frames: extrusionArchive.metadata.Frames,
|
|
41533
|
+
textureColorMask: style.textureColorMask,
|
|
41534
|
+
driveMaterial: false,
|
|
41535
|
+
produceValueCanvas: true,
|
|
41536
|
+
baselineMask: extrusionArchive.metadata.BaselineMask,
|
|
41537
|
+
// Off-type for the same reason as the fill path: the pinned bruce-models predates the field.
|
|
41538
|
+
maskBaseline: style.maskTextureBaseline
|
|
41539
|
+
});
|
|
41540
|
+
cEntity[EXTRUSION_ANIMATOR_KEY] = animator;
|
|
41541
|
+
const mask = style.textureColorMask;
|
|
41542
|
+
const surface = new exports.DisplacedSurfacePrimitive.Surface({
|
|
41543
|
+
extent,
|
|
41544
|
+
// The grid has to be walked the way the raster was sampled, or the surface skews across the extent.
|
|
41545
|
+
pixelPlacement: extrusionArchive.metadata.PixelPlacement,
|
|
41546
|
+
source: null,
|
|
41547
|
+
baseHeight: placement.baseHeight,
|
|
41548
|
+
followGround: placement.followGround,
|
|
41549
|
+
terrainProvider: viewer && viewer.terrainProvider,
|
|
41550
|
+
valueMin: extrusionArchive.metadata.ValueMin,
|
|
41551
|
+
valueMax: extrusionArchive.metadata.ValueMax,
|
|
41552
|
+
exaggeration: style.extrusionExaggeration,
|
|
41553
|
+
lowColor: mask ? BModels.Color.ColorFromStr(mask.minColor) : null,
|
|
41554
|
+
highColor: mask ? BModels.Color.ColorFromStr(mask.maxColor) : null
|
|
41555
|
+
});
|
|
41556
|
+
surface.BindAnimator(animator);
|
|
41557
|
+
viewer.scene.primitives.add(surface);
|
|
41558
|
+
cEntity[DISPLACED_SURFACE_KEY] = surface;
|
|
41559
|
+
}
|
|
41560
|
+
/**
|
|
41561
|
+
* Turns the polygon's altitude option into the two things the displaced surface needs:
|
|
41562
|
+
* - how high value zero sits.
|
|
41563
|
+
* - whether that is measured from the terrain or from the ellipsoid.
|
|
41564
|
+
* @param entity
|
|
41565
|
+
* @param heightRef
|
|
41566
|
+
*/
|
|
41567
|
+
function surfacePlacement(entity, heightRef) {
|
|
41568
|
+
const rawAltitude = BModels.Entity.GetValue({
|
|
41569
|
+
entity,
|
|
41570
|
+
path: ["Bruce", "Location", "altitude"]
|
|
41571
|
+
});
|
|
41572
|
+
const altitude = rawAltitude ? EnsureNumber(rawAltitude) : 0;
|
|
41573
|
+
if (heightRef === Cesium.HeightReference.CLAMP_TO_GROUND) {
|
|
41574
|
+
return { baseHeight: 0, followGround: true };
|
|
41575
|
+
}
|
|
41576
|
+
if (heightRef === Cesium.HeightReference.RELATIVE_TO_GROUND) {
|
|
41577
|
+
return { baseHeight: altitude, followGround: true };
|
|
41578
|
+
}
|
|
41579
|
+
return { baseHeight: altitude, followGround: false };
|
|
41580
|
+
}
|
|
41581
|
+
/**
|
|
41582
|
+
* The archive's own extent when it recorded one, otherwise the polygon's own bounds so archives
|
|
41583
|
+
* generated before the extent was written still render somewhere sensible.
|
|
41584
|
+
* @param metadata
|
|
41585
|
+
* @param posses
|
|
41586
|
+
*/
|
|
41587
|
+
function archiveExtent(metadata, posses) {
|
|
41588
|
+
if (metadata.West != null && metadata.East != null && metadata.South != null && metadata.North != null) {
|
|
41589
|
+
return { West: metadata.West, East: metadata.East, South: metadata.South, North: metadata.North };
|
|
41590
|
+
}
|
|
41591
|
+
if (!posses || posses.length === 0) {
|
|
41592
|
+
return null;
|
|
41593
|
+
}
|
|
41594
|
+
const rectangle = Cesium.Rectangle.fromCartesianArray(posses);
|
|
41595
|
+
return {
|
|
41596
|
+
West: Cesium.Math.toDegrees(rectangle.west),
|
|
41597
|
+
East: Cesium.Math.toDegrees(rectangle.east),
|
|
41598
|
+
South: Cesium.Math.toDegrees(rectangle.south),
|
|
41599
|
+
North: Cesium.Math.toDegrees(rectangle.north)
|
|
41600
|
+
};
|
|
41601
|
+
}
|
|
41602
|
+
/**
|
|
41603
|
+
* Tears down the displaced surface and its feeding animator, if this entity has them.
|
|
41604
|
+
* @param cEntity
|
|
41605
|
+
* @param viewer
|
|
41606
|
+
*/
|
|
41607
|
+
function disposeDisplacedSurface(cEntity, viewer) {
|
|
41608
|
+
const surface = cEntity === null || cEntity === void 0 ? void 0 : cEntity[DISPLACED_SURFACE_KEY];
|
|
41609
|
+
if (surface) {
|
|
41610
|
+
const viewerAlive = viewer && (typeof viewer.isDestroyed !== "function" || !viewer.isDestroyed());
|
|
41611
|
+
if (viewerAlive && viewer.scene && viewer.scene.primitives) {
|
|
41612
|
+
// Removing from the collection destroys the primitive, releasing its meshes and texture.
|
|
41613
|
+
viewer.scene.primitives.remove(surface);
|
|
41614
|
+
}
|
|
41615
|
+
else if (!surface.isDestroyed()) {
|
|
41616
|
+
surface.destroy();
|
|
41617
|
+
}
|
|
41618
|
+
cEntity[DISPLACED_SURFACE_KEY] = null;
|
|
41619
|
+
}
|
|
41620
|
+
const animator = cEntity === null || cEntity === void 0 ? void 0 : cEntity[EXTRUSION_ANIMATOR_KEY];
|
|
41621
|
+
if (animator && !animator.IsDisposed()) {
|
|
41622
|
+
animator.Dispose();
|
|
41623
|
+
}
|
|
41624
|
+
if (cEntity) {
|
|
41625
|
+
cEntity[EXTRUSION_ANIMATOR_KEY] = null;
|
|
41626
|
+
}
|
|
41627
|
+
}
|
|
40528
41628
|
/**
|
|
40529
41629
|
* Reconciles cEntity's TextureFrameSeriesAnimator.Animator (if any) against this render's resolved texture.
|
|
40530
41630
|
* Called once cEntity is fully created/updated and definitely has a `polygon` graphics.
|
|
@@ -40534,7 +41634,7 @@
|
|
|
40534
41634
|
* @param viewer
|
|
40535
41635
|
* @param textureColorMask
|
|
40536
41636
|
*/
|
|
40537
|
-
function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask) {
|
|
41637
|
+
function syncTextureFrameArchive(cEntity, frameArchive, viewer, textureColorMask, maskTextureBaseline) {
|
|
40538
41638
|
const existing = cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY];
|
|
40539
41639
|
if (frameArchive && existing && !existing.IsDisposed() && existing.GetArchiveUrl() === frameArchive.url) {
|
|
40540
41640
|
return existing;
|
|
@@ -40551,7 +41651,9 @@
|
|
|
40551
41651
|
entity: cEntity,
|
|
40552
41652
|
archiveUrl: frameArchive.url,
|
|
40553
41653
|
frames: frameArchive.metadata.Frames,
|
|
40554
|
-
textureColorMask
|
|
41654
|
+
textureColorMask,
|
|
41655
|
+
baselineMask: frameArchive.metadata.BaselineMask,
|
|
41656
|
+
maskBaseline: maskTextureBaseline
|
|
40555
41657
|
});
|
|
40556
41658
|
cEntity[TEXTURE_FRAME_SERIES_ANIMATOR_KEY] = animator;
|
|
40557
41659
|
return animator;
|
|
@@ -40615,8 +41717,15 @@
|
|
|
40615
41717
|
* @param params
|
|
40616
41718
|
*/
|
|
40617
41719
|
async function resolveTexturedFill(params) {
|
|
40618
|
-
|
|
40619
|
-
|
|
41720
|
+
return resolveTextureValue({ ...params, textureValue: params.style.texture });
|
|
41721
|
+
}
|
|
41722
|
+
/**
|
|
41723
|
+
* Resolves either shape a texture setting can take (a calculated file id/URL, or a search condition)
|
|
41724
|
+
* into a tinted data URI or, when the resolved file is a frame archive, the archive itself.
|
|
41725
|
+
* @param params
|
|
41726
|
+
*/
|
|
41727
|
+
async function resolveTextureValue(params) {
|
|
41728
|
+
const { api, style, entity, tags, textureValue } = params;
|
|
40620
41729
|
if (textureValue && !Array.isArray(textureValue)) {
|
|
40621
41730
|
return resolveTextureCondition({ api, condition: textureValue });
|
|
40622
41731
|
}
|
|
@@ -41393,7 +42502,7 @@
|
|
|
41393
42502
|
entity._dispose();
|
|
41394
42503
|
entity._dispose = null;
|
|
41395
42504
|
}
|
|
41396
|
-
exports.EntityRenderEnginePolygon.DisposeTextureFrameSeriesAnimator(entity);
|
|
42505
|
+
exports.EntityRenderEnginePolygon.DisposeTextureFrameSeriesAnimator(entity, viewer);
|
|
41397
42506
|
if (entity._parentEntity && !ignoreParent) {
|
|
41398
42507
|
doRemove({
|
|
41399
42508
|
viewer,
|
|
@@ -42141,7 +43250,7 @@
|
|
|
42141
43250
|
StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
|
|
42142
43251
|
})(exports.StyleUtils || (exports.StyleUtils = {}));
|
|
42143
43252
|
|
|
42144
|
-
const VERSION = "7.1.
|
|
43253
|
+
const VERSION = "7.1.5";
|
|
42145
43254
|
/**
|
|
42146
43255
|
* Updates the environment instance used by bruce-cesium to one specified.
|
|
42147
43256
|
* This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
|