bruce-cesium 7.5.7 → 7.5.9
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 +1918 -330
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +1916 -328
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +1 -1
- package/dist/lib/rendering/displaced-surface-primitive.js +187 -36
- package/dist/lib/rendering/displaced-surface-primitive.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine-polygon.js +20 -9
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/entity-label.js +975 -57
- package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
- package/dist/lib/rendering/texture-frame-series-animator.js +220 -20
- package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
- package/dist/lib/rendering/texture-value-labels.js +478 -194
- package/dist/lib/rendering/texture-value-labels.js.map +1 -1
- package/dist/lib/rendering/visuals-register.js +34 -9
- package/dist/lib/rendering/visuals-register.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +1 -1
- package/dist/types/rendering/displaced-surface-primitive.d.ts +32 -0
- package/dist/types/rendering/entity-render-engine-polygon.d.ts +1 -0
- package/dist/types/rendering/render-managers/common/entity-label.d.ts +134 -20
- package/dist/types/rendering/texture-frame-series-animator.d.ts +42 -0
- package/dist/types/rendering/texture-value-labels.d.ts +59 -11
- package/dist/types/rendering/visuals-register.d.ts +7 -0
- package/package.json +2 -2
package/dist/bruce-cesium.umd.js
CHANGED
|
@@ -5530,6 +5530,24 @@
|
|
|
5530
5530
|
const PHOTO_CACHE_LIMIT = 500;
|
|
5531
5531
|
// Key where the viewer keeps its downsized photos.
|
|
5532
5532
|
const PHOTO_CACHE_KEY = "_nextspaceLabelPhotos";
|
|
5533
|
+
// Clearance around a model's box, as a share of its half-extent on each axis.
|
|
5534
|
+
const MODEL_BOX_PADDING = 0.02;
|
|
5535
|
+
// Share of each edge drawn out from its corners while the box is idle. Hovering draws the edges whole.
|
|
5536
|
+
const MODEL_BOX_BRACKET = 0.2;
|
|
5537
|
+
// Under this many pixels across, a box reads as a smudge and the ring takes over.
|
|
5538
|
+
const MODEL_BOX_MIN_SIZE = 24;
|
|
5539
|
+
// Stem length above the box outline when the options do not set one.
|
|
5540
|
+
const MODEL_BOX_STEM = 32;
|
|
5541
|
+
// Gap in pixels a waiting box needs from a drawn one before it shows, so neighbours never trade places.
|
|
5542
|
+
const MODEL_BOX_CLEARANCE = 12;
|
|
5543
|
+
// Longest a model's label stays hidden waiting to learn whether it has a box, before showing its ring anyway.
|
|
5544
|
+
const MODEL_BOX_WAIT = 1500;
|
|
5545
|
+
// Half the length of edge drawn either side of where a stem lands, when brackets would leave it bare.
|
|
5546
|
+
const MODEL_BOX_LANDING = 10;
|
|
5547
|
+
// Past this many touching boxes one box around them all stands in for tracing their union.
|
|
5548
|
+
const MODEL_BOX_UNION_LIMIT = 24;
|
|
5549
|
+
// Corner pairs of each box edge. Corner i sits on the +X side when bit 0 is set, +Y for bit 1, +Z for bit 2.
|
|
5550
|
+
const MODEL_BOX_EDGES = [[0, 1], [2, 3], [4, 5], [6, 7], [0, 2], [1, 3], [4, 6], [5, 7], [0, 4], [1, 5], [2, 6], [3, 7]];
|
|
5533
5551
|
// One per tinted icon, since an SVG mask is addressed by id across the whole document.
|
|
5534
5552
|
let tintCounter = 0;
|
|
5535
5553
|
// Key where the label instance is stored within the cesium entity.
|
|
@@ -5537,8 +5555,6 @@
|
|
|
5537
5555
|
EntityLabel.ATTACH_KEY = "nextspace-navigator-entity-label";
|
|
5538
5556
|
// Image types a label draws, read from the file extension. Anything else stays text.
|
|
5539
5557
|
const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "bmp", "svg", "webp"];
|
|
5540
|
-
// Shortest side a thumbnail needs before it is preferred over the full file.
|
|
5541
|
-
const IMAGE_THUMBNAIL_MIN = 64;
|
|
5542
5558
|
// Size a found image is drawn at when the group names none, so a photo never lands at its natural size.
|
|
5543
5559
|
const IMAGE_SIZE = 24;
|
|
5544
5560
|
function isImageFile(name) {
|
|
@@ -5567,12 +5583,24 @@
|
|
|
5567
5583
|
return (url.protocol === "http:" || url.protocol === "https:") && isImageFile(url.pathname);
|
|
5568
5584
|
}
|
|
5569
5585
|
EntityLabel.IsImageUrl = IsImageUrl;
|
|
5586
|
+
/*
|
|
5587
|
+
* Device pixels across a photo needs to stay sharp at a chip size, at rest or grown on hover.
|
|
5588
|
+
*/
|
|
5589
|
+
function photoPixels(size, hover) {
|
|
5590
|
+
// The glyph plus the chip's padding of a third either side, enlarged for a photo.
|
|
5591
|
+
const diameter = (size + 2 * (size / 3)) * PHOTO_SIZE_SCALE;
|
|
5592
|
+
// Picking an image can run with no window, where a screen pixel is a CSS pixel.
|
|
5593
|
+
const ratio = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
|
|
5594
|
+
return Math.ceil(diameter * (hover ? PHOTO_HOVER_SCALE : 1) * ratio);
|
|
5595
|
+
}
|
|
5570
5596
|
/*
|
|
5571
5597
|
* The entity's default image as InfoView picks it: its first photo attachment by display order that is an image file.
|
|
5572
|
-
*
|
|
5598
|
+
*
|
|
5599
|
+
* Reads attachments expanded onto the entity. Prefers the smallest thumbnail sharp at the hover size, then the
|
|
5600
|
+
* largest still sharp at rest, so a low resolution thumbnail is never stretched while the full file is unread.
|
|
5573
5601
|
*/
|
|
5574
5602
|
function GetDefaultImageUrl(entity, api) {
|
|
5575
|
-
var _a, _b, _c;
|
|
5603
|
+
var _a, _b, _c, _d;
|
|
5576
5604
|
const attachments = (_b = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.Attachment) !== null && _b !== void 0 ? _b : [];
|
|
5577
5605
|
const photo = attachments
|
|
5578
5606
|
.filter(x => (x === null || x === void 0 ? void 0 : x["EntityAttachmentType.ID"]) == BModels.EntityAttachmentType.EType.Photo && x.ClientFile)
|
|
@@ -5581,9 +5609,12 @@
|
|
|
5581
5609
|
if (!photo) {
|
|
5582
5610
|
return null;
|
|
5583
5611
|
}
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5612
|
+
// How far across a thumbnail is shown: a circle crops to its shortest side, a rectangle keeps its longest.
|
|
5613
|
+
const across = (x) => PhotoShape(x.Width, x.Height) === "circle" ? Math.min(x.Width, x.Height) : Math.max(x.Width, x.Height);
|
|
5614
|
+
const thumbnails = ((_c = photo.ClientFile.Thumbnails) !== null && _c !== void 0 ? _c : [])
|
|
5615
|
+
.filter(x => x.Width > 0 && x.Height > 0 && (x.URL || x["ClientFile.ID"]))
|
|
5616
|
+
.sort((a, b) => a.Width * a.Height - b.Width * b.Height);
|
|
5617
|
+
const thumbnail = (_d = thumbnails.find(x => across(x) >= photoPixels(IMAGE_SIZE, true))) !== null && _d !== void 0 ? _d : thumbnails.reverse().find(x => across(x) >= photoPixels(IMAGE_SIZE, false));
|
|
5587
5618
|
if (thumbnail) {
|
|
5588
5619
|
return thumbnail.URL || BModels.ClientFile.GetUrl({ fileId: thumbnail["ClientFile.ID"], api, viaCdn: true });
|
|
5589
5620
|
}
|
|
@@ -6095,6 +6126,7 @@
|
|
|
6095
6126
|
// Participants whose failure has been logged, so a label that throws every frame logs once.
|
|
6096
6127
|
this.failed = new WeakSet();
|
|
6097
6128
|
this.placementMemory = new PlacementMemory();
|
|
6129
|
+
this.modelBoxes = new ModelBoxes();
|
|
6098
6130
|
this.forwardingWheel = false;
|
|
6099
6131
|
this.pendingDrag = null;
|
|
6100
6132
|
// Null outside a 3D view.
|
|
@@ -6333,6 +6365,12 @@
|
|
|
6333
6365
|
x: (pointer.x - rect.left) * this.viewer.canvas.clientWidth / rect.width,
|
|
6334
6366
|
y: (pointer.y - rect.top) * this.viewer.canvas.clientHeight / rect.height
|
|
6335
6367
|
} : undefined;
|
|
6368
|
+
try {
|
|
6369
|
+
this.modelBoxes.Resolve(this.viewer, this.pointer);
|
|
6370
|
+
}
|
|
6371
|
+
catch (e) {
|
|
6372
|
+
console.error("EntityLabel: model boxes failed to resolve.", e);
|
|
6373
|
+
}
|
|
6336
6374
|
// This runs inside Cesium's frame, where one throwing label would stop rendering for the whole scene.
|
|
6337
6375
|
const projected = new Map();
|
|
6338
6376
|
for (const participant of this.participants) {
|
|
@@ -6354,6 +6392,12 @@
|
|
|
6354
6392
|
this.logFailure(participant, e);
|
|
6355
6393
|
}
|
|
6356
6394
|
}
|
|
6395
|
+
try {
|
|
6396
|
+
this.modelBoxes.Draw(this.viewer, this.element, this.coordinates);
|
|
6397
|
+
}
|
|
6398
|
+
catch (e) {
|
|
6399
|
+
console.error("EntityLabel: model boxes failed to draw.", e);
|
|
6400
|
+
}
|
|
6357
6401
|
}
|
|
6358
6402
|
logFailure(participant, error) {
|
|
6359
6403
|
if (this.failed.has(participant)) {
|
|
@@ -6364,6 +6408,728 @@
|
|
|
6364
6408
|
}
|
|
6365
6409
|
}
|
|
6366
6410
|
let nextLabelId = 0;
|
|
6411
|
+
/*
|
|
6412
|
+
* The record's oriented box in the same Z-up local space as GeometryPivot, or null when it has none.
|
|
6413
|
+
*/
|
|
6414
|
+
function ReadOrientedBox(entity) {
|
|
6415
|
+
var _a, _b;
|
|
6416
|
+
const box = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.GeometryOrientedBox;
|
|
6417
|
+
const vector = (value) => (value === null || value === void 0 ? void 0 : value.length) === 3 && value.every((item) => Number.isFinite(+item)) ? value.map(Number) : null;
|
|
6418
|
+
const centre = vector(box === null || box === void 0 ? void 0 : box.Centre), half = vector(box === null || box === void 0 ? void 0 : box.HalfExtents);
|
|
6419
|
+
const axes = ((_b = box === null || box === void 0 ? void 0 : box.Axes) === null || _b === void 0 ? void 0 : _b.length) === 3 ? box.Axes.map(vector) : null;
|
|
6420
|
+
return centre && half && (axes === null || axes === void 0 ? void 0 : axes.every(Boolean)) ? { centre, axes, half } : null;
|
|
6421
|
+
}
|
|
6422
|
+
EntityLabel.ReadOrientedBox = ReadOrientedBox;
|
|
6423
|
+
/*
|
|
6424
|
+
* The eight padded world corners of an assembly Entity's geometry box, or null when the record cannot give one.
|
|
6425
|
+
*
|
|
6426
|
+
* GeometryPivotExtents are Z-up half-extents about GeometryPivot, and the placement is the one the API uses to put
|
|
6427
|
+
* Bruce.Location at the pivot: ENU(root) * UCS translation * UCS rotation * AssemblyWorldPosition.
|
|
6428
|
+
*/
|
|
6429
|
+
function GetModelBox(entity, shape) {
|
|
6430
|
+
var _a, _b, _c;
|
|
6431
|
+
const bruce = entity === null || entity === void 0 ? void 0 : entity.Bruce;
|
|
6432
|
+
const half = (_b = (_a = bruce === null || bruce === void 0 ? void 0 : bruce.GeometryPivotExtents) === null || _a === void 0 ? void 0 : _a.map) === null || _b === void 0 ? void 0 : _b.call(_a, Number);
|
|
6433
|
+
const matrix = bruce === null || bruce === void 0 ? void 0 : bruce.AssemblyWorldPosition;
|
|
6434
|
+
const root = bruce === null || bruce === void 0 ? void 0 : bruce.AssemblyRootLocation;
|
|
6435
|
+
if ((half === null || half === void 0 ? void 0 : half.length) !== 3 || !half.every(value => Number.isFinite(value) && value >= 0) || Math.max(...half) < 0.01) {
|
|
6436
|
+
return null;
|
|
6437
|
+
}
|
|
6438
|
+
if ((matrix === null || matrix === void 0 ? void 0 : matrix.length) !== 4 || !matrix.every(row => (row === null || row === void 0 ? void 0 : row.length) === 4) || !Number.isFinite(+(root === null || root === void 0 ? void 0 : root.latitude)) || !Number.isFinite(+(root === null || root === void 0 ? void 0 : root.longitude))) {
|
|
6439
|
+
return null;
|
|
6440
|
+
}
|
|
6441
|
+
const pivot = ((_c = bruce.GeometryPivot) === null || _c === void 0 ? void 0 : _c.length) >= 3 ? bruce.GeometryPivot.slice(0, 3).map(Number) : [0, 0, 0];
|
|
6442
|
+
// Without an oriented box the record's own extents are an axis-aligned box about the pivot.
|
|
6443
|
+
const box = shape || { centre: pivot, axes: [[1, 0, 0], [0, 1, 0], [0, 0, 1]], half: half };
|
|
6444
|
+
const transform = bruce.AssemblyWorldTransform;
|
|
6445
|
+
const placement = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(+root.longitude, +root.latitude, EnsureNumber(root.altitude)));
|
|
6446
|
+
Cesium.Matrix4.multiply(placement, Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(EnsureNumber(transform === null || transform === void 0 ? void 0 : transform.x), EnsureNumber(transform === null || transform === void 0 ? void 0 : transform.y), EnsureNumber(transform === null || transform === void 0 ? void 0 : transform.z))), placement);
|
|
6447
|
+
Cesium.Matrix4.multiply(placement, Cesium.Matrix4.fromRotation(Cesium.Matrix3.fromHeadingPitchRoll(Cesium.HeadingPitchRoll.fromDegrees(EnsureNumber(transform === null || transform === void 0 ? void 0 : transform.heading), EnsureNumber(transform === null || transform === void 0 ? void 0 : transform.pitch), EnsureNumber(transform === null || transform === void 0 ? void 0 : transform.roll)))), placement);
|
|
6448
|
+
Cesium.Matrix4.multiply(placement, new Cesium.Matrix4(...matrix.flat().map(Number)), placement);
|
|
6449
|
+
// Pad thin axes by a share of the longest one, so a flat part still gets a visible gap.
|
|
6450
|
+
const longest = Math.max(...box.half);
|
|
6451
|
+
const reach = box.half.map(value => value + Math.max(value, longest * 0.25) * MODEL_BOX_PADDING);
|
|
6452
|
+
const corners = [];
|
|
6453
|
+
for (let i = 0; i < 8; i++) {
|
|
6454
|
+
const signs = [i & 1 ? 1 : -1, i & 2 ? 1 : -1, i & 4 ? 1 : -1];
|
|
6455
|
+
const local = new Cesium.Cartesian3(...[0, 1, 2].map(k => box.centre[k] +
|
|
6456
|
+
signs[0] * reach[0] * box.axes[0][k] + signs[1] * reach[1] * box.axes[1][k] + signs[2] * reach[2] * box.axes[2][k]));
|
|
6457
|
+
const corner = Cesium.Matrix4.multiplyByPoint(placement, local, new Cesium.Cartesian3());
|
|
6458
|
+
if (![corner.x, corner.y, corner.z].every(Number.isFinite)) {
|
|
6459
|
+
return null;
|
|
6460
|
+
}
|
|
6461
|
+
corners.push(corner);
|
|
6462
|
+
}
|
|
6463
|
+
return corners;
|
|
6464
|
+
}
|
|
6465
|
+
EntityLabel.GetModelBox = GetModelBox;
|
|
6466
|
+
const vAdd = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
6467
|
+
const vSub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
6468
|
+
const vScale = (a, s) => [a[0] * s, a[1] * s, a[2] * s];
|
|
6469
|
+
const vDot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
6470
|
+
const vCross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
6471
|
+
const vLength = (a) => Math.hypot(a[0], a[1], a[2]);
|
|
6472
|
+
/*
|
|
6473
|
+
* Reads a solid back from the eight corners GetModelBox gives, relative to the origin.
|
|
6474
|
+
*/
|
|
6475
|
+
function ModelSolid(corners, origin) {
|
|
6476
|
+
const local = corners.map((corner) => [corner.x - origin.x, corner.y - origin.y, corner.z - origin.z]);
|
|
6477
|
+
const spans = [1, 2, 4].map(bit => vSub(local[bit], local[0]));
|
|
6478
|
+
const half = spans.map(span => vLength(span) / 2);
|
|
6479
|
+
return {
|
|
6480
|
+
centre: vScale(vAdd(local[0], local[7]), 0.5),
|
|
6481
|
+
axes: spans.map((span, k) => vScale(span, 0.5 / (half[k] || 1))),
|
|
6482
|
+
half
|
|
6483
|
+
};
|
|
6484
|
+
}
|
|
6485
|
+
EntityLabel.ModelSolid = ModelSolid;
|
|
6486
|
+
function solidCorners(solid) {
|
|
6487
|
+
const corners = [];
|
|
6488
|
+
for (let i = 0; i < 8; i++) {
|
|
6489
|
+
corners.push([0, 1, 2].reduce((point, k) => vAdd(point, vScale(solid.axes[k], solid.half[k] * (i & (1 << k) ? 1 : -1))), solid.centre));
|
|
6490
|
+
}
|
|
6491
|
+
return corners;
|
|
6492
|
+
}
|
|
6493
|
+
function solidContains(solid, point, inset = 0) {
|
|
6494
|
+
const offset = vSub(point, solid.centre);
|
|
6495
|
+
return solid.axes.every((axis, k) => Math.abs(vDot(offset, axis)) < solid.half[k] - inset);
|
|
6496
|
+
}
|
|
6497
|
+
/*
|
|
6498
|
+
* Whether two solids overlap, by the separating axis test. Padding makes parts that sit against each other overlap.
|
|
6499
|
+
*/
|
|
6500
|
+
function ModelSolidsTouch(a, b) {
|
|
6501
|
+
const between = vSub(b.centre, a.centre);
|
|
6502
|
+
const axes = [...a.axes, ...b.axes];
|
|
6503
|
+
for (const first of a.axes) {
|
|
6504
|
+
for (const second of b.axes) {
|
|
6505
|
+
const axis = vCross(first, second);
|
|
6506
|
+
const length = vLength(axis);
|
|
6507
|
+
if (length > 1e-6)
|
|
6508
|
+
axes.push(vScale(axis, 1 / length));
|
|
6509
|
+
}
|
|
6510
|
+
}
|
|
6511
|
+
const reach = (solid, axis) => solid.axes.reduce((sum, own, k) => sum + solid.half[k] * Math.abs(vDot(own, axis)), 0);
|
|
6512
|
+
return axes.every(axis => Math.abs(vDot(between, axis)) <= reach(a, axis) + reach(b, axis));
|
|
6513
|
+
}
|
|
6514
|
+
EntityLabel.ModelSolidsTouch = ModelSolidsTouch;
|
|
6515
|
+
/*
|
|
6516
|
+
* One box around every solid, turned like the largest of them, for groups too big to trace.
|
|
6517
|
+
*/
|
|
6518
|
+
function encloseSolids(solids) {
|
|
6519
|
+
const largest = solids.reduce((best, solid) => solid.half[0] * solid.half[1] * solid.half[2] > best.half[0] * best.half[1] * best.half[2] ? solid : best);
|
|
6520
|
+
const corners = solids.flatMap(solidCorners);
|
|
6521
|
+
const ranges = largest.axes.map(axis => {
|
|
6522
|
+
const values = corners.map(corner => vDot(corner, axis));
|
|
6523
|
+
return [Math.min(...values), Math.max(...values)];
|
|
6524
|
+
});
|
|
6525
|
+
return {
|
|
6526
|
+
centre: largest.axes.reduce((point, axis, k) => vAdd(point, vScale(axis, (ranges[k][0] + ranges[k][1]) / 2)), [0, 0, 0]),
|
|
6527
|
+
axes: largest.axes,
|
|
6528
|
+
half: ranges.map(([low, high]) => (high - low) / 2)
|
|
6529
|
+
};
|
|
6530
|
+
}
|
|
6531
|
+
/*
|
|
6532
|
+
* Narrows [low, high] to where a + b * t stays within -half..half.
|
|
6533
|
+
*/
|
|
6534
|
+
function clipSlab(range, a, b, half) {
|
|
6535
|
+
if (Math.abs(b) < 1e-12) {
|
|
6536
|
+
if (Math.abs(a) > half)
|
|
6537
|
+
range[1] = -Infinity;
|
|
6538
|
+
return;
|
|
6539
|
+
}
|
|
6540
|
+
const first = (-half - a) / b, second = (half - a) / b;
|
|
6541
|
+
range[0] = Math.max(range[0], Math.min(first, second));
|
|
6542
|
+
range[1] = Math.min(range[1], Math.max(first, second));
|
|
6543
|
+
}
|
|
6544
|
+
/*
|
|
6545
|
+
* The edges of the union of the solids: where its surface folds, leaving out seams across flat shared faces.
|
|
6546
|
+
*/
|
|
6547
|
+
function ModelBoxOutline(solids) {
|
|
6548
|
+
const span = Math.max(...solids.map(solid => vLength(solid.half)));
|
|
6549
|
+
const thinnest = Math.min(...solids.flatMap(solid => solid.half));
|
|
6550
|
+
const radius = Math.max(1e-6, Math.min(thinnest * 0.05, span * 1e-3));
|
|
6551
|
+
const tolerance = Math.max(1, span) * 1e-6;
|
|
6552
|
+
const candidates = [];
|
|
6553
|
+
for (const solid of solids) {
|
|
6554
|
+
const corners = solidCorners(solid);
|
|
6555
|
+
for (const [a, b] of MODEL_BOX_EDGES)
|
|
6556
|
+
candidates.push([corners[a], corners[b]]);
|
|
6557
|
+
}
|
|
6558
|
+
const faces = (solid) => [0, 1, 2].flatMap(k => [1, -1].map(sign => ({ solid, k, normal: vScale(solid.axes[k], sign) })));
|
|
6559
|
+
for (let i = 0; i < solids.length; i++) {
|
|
6560
|
+
for (let j = i + 1; j < solids.length; j++) {
|
|
6561
|
+
if (!ModelSolidsTouch(solids[i], solids[j])) {
|
|
6562
|
+
continue;
|
|
6563
|
+
}
|
|
6564
|
+
for (const first of faces(solids[i])) {
|
|
6565
|
+
for (const second of faces(solids[j])) {
|
|
6566
|
+
const direction = vCross(first.normal, second.normal);
|
|
6567
|
+
const sine = vLength(direction);
|
|
6568
|
+
// Faces this close to parallel meet along edges the box edges already give.
|
|
6569
|
+
if (sine < 0.05) {
|
|
6570
|
+
continue;
|
|
6571
|
+
}
|
|
6572
|
+
const d1 = vDot(first.normal, first.solid.centre) + first.solid.half[first.k];
|
|
6573
|
+
const d2 = vDot(second.normal, second.solid.centre) + second.solid.half[second.k];
|
|
6574
|
+
const cosine = vDot(first.normal, second.normal);
|
|
6575
|
+
const base = vScale(vAdd(vScale(first.normal, d1 - d2 * cosine), vScale(second.normal, d2 - d1 * cosine)), 1 / (sine * sine));
|
|
6576
|
+
const unit = vScale(direction, 1 / sine);
|
|
6577
|
+
const range = [-Infinity, Infinity];
|
|
6578
|
+
for (const face of [first, second]) {
|
|
6579
|
+
const offset = vSub(base, face.solid.centre);
|
|
6580
|
+
for (let m = 0; m < 3; m++) {
|
|
6581
|
+
if (m !== face.k)
|
|
6582
|
+
clipSlab(range, vDot(offset, face.solid.axes[m]), vDot(unit, face.solid.axes[m]), face.solid.half[m]);
|
|
6583
|
+
}
|
|
6584
|
+
}
|
|
6585
|
+
if (range[1] - range[0] > tolerance) {
|
|
6586
|
+
candidates.push([vAdd(base, vScale(unit, range[0])), vAdd(base, vScale(unit, range[1]))]);
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
}
|
|
6591
|
+
}
|
|
6592
|
+
const inside = (point) => solids.some(solid => solidContains(solid, point));
|
|
6593
|
+
/*
|
|
6594
|
+
* Whether the union folds along a direction at a point, from which way round it the inside lies.
|
|
6595
|
+
*/
|
|
6596
|
+
const folds = (point, along) => {
|
|
6597
|
+
const first = vCross(along, Math.abs(along[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0]);
|
|
6598
|
+
const e1 = vScale(first, 1 / vLength(first));
|
|
6599
|
+
const e2 = vCross(along, e1);
|
|
6600
|
+
const samples = [];
|
|
6601
|
+
for (let i = 0; i < 16; i++) {
|
|
6602
|
+
// Offset from the axes so no sample lands exactly on a face through the point.
|
|
6603
|
+
const angle = (i + 0.37) * Math.PI / 8;
|
|
6604
|
+
samples.push(inside(vAdd(point, vAdd(vScale(e1, Math.cos(angle) * radius), vScale(e2, Math.sin(angle) * radius)))));
|
|
6605
|
+
}
|
|
6606
|
+
const filled = samples.filter(Boolean).length;
|
|
6607
|
+
const changes = samples.filter((sample, i) => sample !== samples[(i + 1) % 16]).length;
|
|
6608
|
+
return filled > 0 && filled < 16 && !(filled === 8 && changes === 2);
|
|
6609
|
+
};
|
|
6610
|
+
const lines = [];
|
|
6611
|
+
for (const [start, end] of candidates) {
|
|
6612
|
+
const delta = vSub(end, start);
|
|
6613
|
+
const length = vLength(delta);
|
|
6614
|
+
if (length < tolerance) {
|
|
6615
|
+
continue;
|
|
6616
|
+
}
|
|
6617
|
+
const along = vScale(delta, 1 / length);
|
|
6618
|
+
const cuts = [0, 1];
|
|
6619
|
+
for (const solid of solids) {
|
|
6620
|
+
const offset = vSub(start, solid.centre);
|
|
6621
|
+
solid.axes.forEach((axis, k) => {
|
|
6622
|
+
const rate = vDot(delta, axis);
|
|
6623
|
+
if (Math.abs(rate) < 1e-12)
|
|
6624
|
+
return;
|
|
6625
|
+
for (const bound of [-solid.half[k], solid.half[k]]) {
|
|
6626
|
+
const t = (bound - vDot(offset, axis)) / rate;
|
|
6627
|
+
if (t > 0 && t < 1)
|
|
6628
|
+
cuts.push(t);
|
|
6629
|
+
}
|
|
6630
|
+
});
|
|
6631
|
+
}
|
|
6632
|
+
cuts.sort((a, b) => a - b);
|
|
6633
|
+
for (let i = 0; i + 1 < cuts.length; i++) {
|
|
6634
|
+
if ((cuts[i + 1] - cuts[i]) * length < tolerance || !folds(vAdd(start, vScale(delta, (cuts[i] + cuts[i + 1]) / 2)), along)) {
|
|
6635
|
+
continue;
|
|
6636
|
+
}
|
|
6637
|
+
// Merged by line, so a shared edge draws once and a run cut by other boxes is one stroke.
|
|
6638
|
+
const from = vAdd(start, vScale(delta, cuts[i])), to = vAdd(start, vScale(delta, cuts[i + 1]));
|
|
6639
|
+
let line = lines.find(item => vLength(vCross(item.direction, along)) < 1e-6 && vLength(vCross(vSub(from, item.origin), item.direction)) < tolerance);
|
|
6640
|
+
if (!line) {
|
|
6641
|
+
line = { origin: from, direction: along, spans: [] };
|
|
6642
|
+
lines.push(line);
|
|
6643
|
+
}
|
|
6644
|
+
const a = vDot(vSub(from, line.origin), line.direction), b = vDot(vSub(to, line.origin), line.direction);
|
|
6645
|
+
line.spans.push([Math.min(a, b), Math.max(a, b)]);
|
|
6646
|
+
}
|
|
6647
|
+
}
|
|
6648
|
+
const outline = [];
|
|
6649
|
+
for (const line of lines) {
|
|
6650
|
+
line.spans.sort((a, b) => a[0] - b[0]);
|
|
6651
|
+
let current = line.spans[0];
|
|
6652
|
+
for (const next of [...line.spans.slice(1), null]) {
|
|
6653
|
+
if (next && next[0] <= current[1] + tolerance) {
|
|
6654
|
+
current = [current[0], Math.max(current[1], next[1])];
|
|
6655
|
+
continue;
|
|
6656
|
+
}
|
|
6657
|
+
outline.push([vAdd(line.origin, vScale(line.direction, current[0])), vAdd(line.origin, vScale(line.direction, current[1]))]);
|
|
6658
|
+
current = next;
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
return outline;
|
|
6662
|
+
}
|
|
6663
|
+
EntityLabel.ModelBoxOutline = ModelBoxOutline;
|
|
6664
|
+
/*
|
|
6665
|
+
* Whether the sight line from the eye to a point passes through any solid before reaching it.
|
|
6666
|
+
*/
|
|
6667
|
+
function solidsHide(solids, eye, point, inset) {
|
|
6668
|
+
const ray = vSub(point, eye);
|
|
6669
|
+
const length = vLength(ray);
|
|
6670
|
+
// Stop just short of the point so the face it lies on does not count against it.
|
|
6671
|
+
const reach = Math.max(0, 1 - inset * 100 / (length || 1));
|
|
6672
|
+
return solids.some(solid => {
|
|
6673
|
+
const offset = vSub(eye, solid.centre);
|
|
6674
|
+
const range = [0, reach];
|
|
6675
|
+
solid.axes.forEach((axis, k) => clipSlab(range, vDot(offset, axis), vDot(ray, axis), solid.half[k] - inset));
|
|
6676
|
+
return range[0] < range[1];
|
|
6677
|
+
});
|
|
6678
|
+
}
|
|
6679
|
+
/*
|
|
6680
|
+
* Window positions of a box's corners, or null when one is behind the camera and the box would draw inside out.
|
|
6681
|
+
*/
|
|
6682
|
+
function projectCorners(viewer, corners) {
|
|
6683
|
+
const camera = viewer.camera;
|
|
6684
|
+
const cameraPosition = camera.positionWC || camera.position;
|
|
6685
|
+
const direction = camera.directionWC || camera.direction;
|
|
6686
|
+
const transforms = Cesium.SceneTransforms;
|
|
6687
|
+
const projectPosition = transforms.wgs84ToWindowCoordinates || transforms.worldToWindowCoordinates;
|
|
6688
|
+
const points = [];
|
|
6689
|
+
const relative = new Cesium.Cartesian3();
|
|
6690
|
+
for (const corner of corners) {
|
|
6691
|
+
if (Cesium.Cartesian3.dot(Cesium.Cartesian3.subtract(corner, cameraPosition, relative), direction) <= 0) {
|
|
6692
|
+
return null;
|
|
6693
|
+
}
|
|
6694
|
+
const point = projectPosition(viewer.scene, corner);
|
|
6695
|
+
if (!Number.isFinite(point === null || point === void 0 ? void 0 : point.x) || !Number.isFinite(point === null || point === void 0 ? void 0 : point.y)) {
|
|
6696
|
+
return null;
|
|
6697
|
+
}
|
|
6698
|
+
points.push({ x: point.x, y: point.y });
|
|
6699
|
+
}
|
|
6700
|
+
return points;
|
|
6701
|
+
}
|
|
6702
|
+
/*
|
|
6703
|
+
* Whether two convex outlines come within a margin of each other, by the separating axis test.
|
|
6704
|
+
*/
|
|
6705
|
+
function HullsNear(a, b, margin) {
|
|
6706
|
+
for (const hull of [a, b]) {
|
|
6707
|
+
for (let i = 0; i < hull.length; i++) {
|
|
6708
|
+
const from = hull[i], to = hull[(i + 1) % hull.length];
|
|
6709
|
+
const length = Math.hypot(to.x - from.x, to.y - from.y);
|
|
6710
|
+
if (length < 1e-9) {
|
|
6711
|
+
continue;
|
|
6712
|
+
}
|
|
6713
|
+
const normal = { x: (from.y - to.y) / length, y: (to.x - from.x) / length };
|
|
6714
|
+
const project = (points) => points.map(point => point.x * normal.x + point.y * normal.y);
|
|
6715
|
+
const first = project(a), second = project(b);
|
|
6716
|
+
if (Math.max(Math.min(...second) - Math.max(...first), Math.min(...first) - Math.max(...second)) >= margin) {
|
|
6717
|
+
return false;
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
6721
|
+
return true;
|
|
6722
|
+
}
|
|
6723
|
+
EntityLabel.HullsNear = HullsNear;
|
|
6724
|
+
/*
|
|
6725
|
+
* Decides which labels' model boxes are drawn and how, so a multi-selection does not stack box over box.
|
|
6726
|
+
*/
|
|
6727
|
+
class ModelBoxes {
|
|
6728
|
+
constructor() {
|
|
6729
|
+
// Whether a box competes for space comes only from inputs (camera distance, hover, the pointer), never from
|
|
6730
|
+
// how its label was laid out, so an animation moving a label cannot feed back into which boxes show.
|
|
6731
|
+
this.entries = new Map();
|
|
6732
|
+
this.groups = null;
|
|
6733
|
+
// The hovered Entity. Its box is drawn on its own and whole, ahead of every other box.
|
|
6734
|
+
this.hovered = null;
|
|
6735
|
+
this.outlines = new Map();
|
|
6736
|
+
this.hulls = new Map();
|
|
6737
|
+
this.frames = new Map();
|
|
6738
|
+
this.drawn = [];
|
|
6739
|
+
// When each label's box started showing; the longer it has shown, the stronger its claim on the space.
|
|
6740
|
+
this.since = new Map();
|
|
6741
|
+
this.requests = new Map();
|
|
6742
|
+
this.looks = new Map();
|
|
6743
|
+
this.memberLooks = new Map();
|
|
6744
|
+
}
|
|
6745
|
+
Set(id, corners, entityId, inputs) {
|
|
6746
|
+
const current = this.entries.get(id);
|
|
6747
|
+
// A label rebuilt over the same box keeps its place and how far it has animated.
|
|
6748
|
+
if (corners && (current === null || current === void 0 ? void 0 : current.corners) === corners && current.entityId === entityId) {
|
|
6749
|
+
Object.assign(current, inputs);
|
|
6750
|
+
return;
|
|
6751
|
+
}
|
|
6752
|
+
if (!corners && !current) {
|
|
6753
|
+
return;
|
|
6754
|
+
}
|
|
6755
|
+
if (corners) {
|
|
6756
|
+
this.entries.set(id, { ...inputs, corners, entityId, active: false, wanted: false });
|
|
6757
|
+
}
|
|
6758
|
+
else
|
|
6759
|
+
this.entries.delete(id);
|
|
6760
|
+
this.since.delete(id);
|
|
6761
|
+
this.groups = null;
|
|
6762
|
+
for (const [key, group] of this.outlines) {
|
|
6763
|
+
if (group.members.includes(id))
|
|
6764
|
+
this.outlines.delete(key);
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
6767
|
+
/*
|
|
6768
|
+
* The box drawn for a label this frame, or undefined when it has none or lost its place.
|
|
6769
|
+
*/
|
|
6770
|
+
Frame(id) {
|
|
6771
|
+
return this.frames.get(id);
|
|
6772
|
+
}
|
|
6773
|
+
/*
|
|
6774
|
+
* The label's own box on screen whether or not it is drawn, so hovering the model can ask for it.
|
|
6775
|
+
*/
|
|
6776
|
+
Hull(id) {
|
|
6777
|
+
return this.hulls.get(id);
|
|
6778
|
+
}
|
|
6779
|
+
Request(id, request) {
|
|
6780
|
+
this.requests.set(id, request);
|
|
6781
|
+
}
|
|
6782
|
+
group() {
|
|
6783
|
+
const ids = Array.from(this.entries.keys()).filter(id => this.entries.get(id).wanted).sort();
|
|
6784
|
+
if (!ids.length) {
|
|
6785
|
+
return [];
|
|
6786
|
+
}
|
|
6787
|
+
const origin = this.entries.get(ids[0]).corners[0];
|
|
6788
|
+
const solids = new Map(ids.map(id => [id, ModelSolid(this.entries.get(id).corners, origin)]));
|
|
6789
|
+
const parent = new Map(ids.map(id => [id, id]));
|
|
6790
|
+
const root = (id) => parent.get(id) === id ? id : root(parent.get(id));
|
|
6791
|
+
for (let i = 0; i < ids.length; i++) {
|
|
6792
|
+
const a = solids.get(ids[i]);
|
|
6793
|
+
for (let j = i + 1; j < ids.length; j++) {
|
|
6794
|
+
const b = solids.get(ids[j]);
|
|
6795
|
+
if ((this.entries.get(ids[i]).entityId === this.hovered) !== (this.entries.get(ids[j]).entityId === this.hovered)) {
|
|
6796
|
+
continue;
|
|
6797
|
+
}
|
|
6798
|
+
// Cheap sphere check first; most pairs in a scene are nowhere near each other.
|
|
6799
|
+
if (vLength(vSub(a.centre, b.centre)) > vLength(a.half) + vLength(b.half) || !ModelSolidsTouch(a, b)) {
|
|
6800
|
+
continue;
|
|
6801
|
+
}
|
|
6802
|
+
parent.set(root(ids[j]), root(ids[i]));
|
|
6803
|
+
}
|
|
6804
|
+
}
|
|
6805
|
+
const members = new Map();
|
|
6806
|
+
for (const id of ids) {
|
|
6807
|
+
const key = root(id);
|
|
6808
|
+
if (!members.has(key))
|
|
6809
|
+
members.set(key, []);
|
|
6810
|
+
members.get(key).push(id);
|
|
6811
|
+
}
|
|
6812
|
+
const groups = Array.from(members.values()).map(list => {
|
|
6813
|
+
const key = list.join("|");
|
|
6814
|
+
let group = this.outlines.get(key);
|
|
6815
|
+
if (!group) {
|
|
6816
|
+
const groupOrigin = this.entries.get(list[0]).corners[0];
|
|
6817
|
+
const parts = list.map(id => ModelSolid(this.entries.get(id).corners, groupOrigin));
|
|
6818
|
+
const solidsShown = parts.length > MODEL_BOX_UNION_LIMIT ? [encloseSolids(parts)] : parts;
|
|
6819
|
+
group = { key, members: list, hovered: false, origin: groupOrigin, solids: solidsShown, outline: ModelBoxOutline(solidsShown) };
|
|
6820
|
+
}
|
|
6821
|
+
group.hovered = this.entries.get(list[0]).entityId === this.hovered;
|
|
6822
|
+
return group;
|
|
6823
|
+
});
|
|
6824
|
+
// Kept across hover changes, so pulling a part out and putting it back does not trace the union again.
|
|
6825
|
+
const kept = Array.from(this.outlines.values()).filter(group => !groups.includes(group)).slice(-8);
|
|
6826
|
+
this.outlines = new Map([...kept, ...groups].map(group => [group.key, group]));
|
|
6827
|
+
return groups;
|
|
6828
|
+
}
|
|
6829
|
+
/*
|
|
6830
|
+
* Projects every box for this frame and settles which outlines are drawn, before labels place their stems.
|
|
6831
|
+
*/
|
|
6832
|
+
Resolve(viewer, pointer) {
|
|
6833
|
+
var _a, _b, _c, _d;
|
|
6834
|
+
this.frames.clear();
|
|
6835
|
+
this.hulls.clear();
|
|
6836
|
+
this.drawn = [];
|
|
6837
|
+
const points = new Map();
|
|
6838
|
+
for (const [id, entry] of this.entries) {
|
|
6839
|
+
const projected = projectCorners(viewer, entry.corners);
|
|
6840
|
+
if (projected) {
|
|
6841
|
+
points.set(id, projected);
|
|
6842
|
+
this.hulls.set(id, convexHull(projected));
|
|
6843
|
+
}
|
|
6844
|
+
}
|
|
6845
|
+
// The application's hover, which is what its temporary label shows, decides first. Without one the
|
|
6846
|
+
// pointer does: the part it is on holds until it leaves, else the smallest box under it wins.
|
|
6847
|
+
const under = Array.from(this.hulls.entries()).filter(([, hull]) => pointer && hullContains(hull, pointer));
|
|
6848
|
+
let hovered = (_b = (_a = Array.from(this.entries.values()).find(entry => { var _a; return (_a = entry.hovered) === null || _a === void 0 ? void 0 : _a.call(entry); })) === null || _a === void 0 ? void 0 : _a.entityId) !== null && _b !== void 0 ? _b : (under.some(([id]) => this.entries.get(id).entityId === this.hovered) ? this.hovered : null);
|
|
6849
|
+
if (!hovered && under.length) {
|
|
6850
|
+
hovered = this.entries.get(under.map(([id, hull]) => ({ id, area: hullArea(hull) })).sort((a, b) => a.area - b.area || a.id.localeCompare(b.id))[0].id).entityId;
|
|
6851
|
+
}
|
|
6852
|
+
if (hovered !== this.hovered) {
|
|
6853
|
+
this.hovered = hovered;
|
|
6854
|
+
this.groups = null;
|
|
6855
|
+
}
|
|
6856
|
+
for (const [id, entry] of this.entries) {
|
|
6857
|
+
const hull = this.hulls.get(id);
|
|
6858
|
+
entry.active = Boolean(((_c = entry.near) === null || _c === void 0 ? void 0 : _c.call(entry)) || entry.entityId === hovered || (pointer && hull && hullContains(hull, pointer)));
|
|
6859
|
+
// A box keeps its place while it fades out, and leaves the competition once it has.
|
|
6860
|
+
const wanted = entry.active || ((_d = this.memberLooks.get(id)) === null || _d === void 0 ? void 0 : _d.shown) > 0.01;
|
|
6861
|
+
if (wanted !== entry.wanted) {
|
|
6862
|
+
entry.wanted = wanted;
|
|
6863
|
+
this.groups = null;
|
|
6864
|
+
}
|
|
6865
|
+
}
|
|
6866
|
+
if (!this.groups) {
|
|
6867
|
+
this.groups = this.group();
|
|
6868
|
+
}
|
|
6869
|
+
const canvas = viewer.canvas;
|
|
6870
|
+
const candidates = [];
|
|
6871
|
+
for (const group of this.groups) {
|
|
6872
|
+
if (!group.members.every(id => points.has(id))) {
|
|
6873
|
+
continue;
|
|
6874
|
+
}
|
|
6875
|
+
const all = group.members.flatMap(id => points.get(id));
|
|
6876
|
+
const xs = all.map(point => point.x), ys = all.map(point => point.y);
|
|
6877
|
+
const width = Math.max(...xs) - Math.min(...xs), height = Math.max(...ys) - Math.min(...ys);
|
|
6878
|
+
if (Math.max(width, height) < MODEL_BOX_MIN_SIZE || width > canvas.clientWidth * 2 || height > canvas.clientHeight * 2) {
|
|
6879
|
+
continue;
|
|
6880
|
+
}
|
|
6881
|
+
const since = Math.min(...group.members.map(id => { var _a; return (_a = this.since.get(id)) !== null && _a !== void 0 ? _a : Infinity; }));
|
|
6882
|
+
const outline = convexHull(all);
|
|
6883
|
+
candidates.push({ group, outline, selected: group.members.some(id => { var _a, _b; return (_b = (_a = this.entries.get(id)).selected) === null || _b === void 0 ? void 0 : _b.call(_a); }), since, area: hullArea(outline) });
|
|
6884
|
+
}
|
|
6885
|
+
// Between boxes appearing together the larger on screen goes first, as the one the view is about.
|
|
6886
|
+
// What the user points at now outranks what they selected before it.
|
|
6887
|
+
candidates.sort((a, b) => (Number(b.group.hovered) - Number(a.group.hovered) ||
|
|
6888
|
+
Number(b.selected) - Number(a.selected) ||
|
|
6889
|
+
a.since - b.since ||
|
|
6890
|
+
b.area - a.area ||
|
|
6891
|
+
a.group.key.localeCompare(b.group.key)));
|
|
6892
|
+
const transforms = Cesium.SceneTransforms;
|
|
6893
|
+
const projectPosition = transforms.wgs84ToWindowCoordinates || transforms.worldToWindowCoordinates;
|
|
6894
|
+
const scratch = new Cesium.Cartesian3();
|
|
6895
|
+
const now = Date.now();
|
|
6896
|
+
const shown = new Set();
|
|
6897
|
+
for (const candidate of candidates) {
|
|
6898
|
+
// A showing box holds its place until truly overlapped; a waiting one needs clear space.
|
|
6899
|
+
const margin = Number.isFinite(candidate.since) ? 0 : MODEL_BOX_CLEARANCE;
|
|
6900
|
+
if (this.drawn.some(other => HullsNear(candidate.outline, other.outline, margin))) {
|
|
6901
|
+
continue;
|
|
6902
|
+
}
|
|
6903
|
+
const { origin } = candidate.group;
|
|
6904
|
+
const toScreen = (point) => {
|
|
6905
|
+
const projected = projectPosition(viewer.scene, Cesium.Cartesian3.fromElements(origin.x + point[0], origin.y + point[1], origin.z + point[2], scratch));
|
|
6906
|
+
return { x: projected.x, y: projected.y };
|
|
6907
|
+
};
|
|
6908
|
+
const edges = candidate.group.outline.map(([start, end]) => [toScreen(start), toScreen(end)]);
|
|
6909
|
+
const drawn = { ...candidate, edges, landings: [] };
|
|
6910
|
+
this.drawn.push(drawn);
|
|
6911
|
+
for (const id of candidate.group.members) {
|
|
6912
|
+
shown.add(id);
|
|
6913
|
+
if (!this.since.has(id))
|
|
6914
|
+
this.since.set(id, now);
|
|
6915
|
+
const own = points.get(id);
|
|
6916
|
+
// The silhouette's top is the highest drawn edge at that x, so the stem always meets a line.
|
|
6917
|
+
const x = (Math.min(...own.map(point => point.x)) + Math.max(...own.map(point => point.x))) / 2;
|
|
6918
|
+
let anchor = null, edge = -1;
|
|
6919
|
+
edges.forEach(([a, b], i) => {
|
|
6920
|
+
if (x < Math.min(a.x, b.x) || x > Math.max(a.x, b.x) || Math.abs(b.x - a.x) < 1e-6) {
|
|
6921
|
+
return;
|
|
6922
|
+
}
|
|
6923
|
+
const y = a.y + (b.y - a.y) * (x - a.x) / (b.x - a.x);
|
|
6924
|
+
if (!anchor || y < anchor.y) {
|
|
6925
|
+
anchor = { x, y };
|
|
6926
|
+
edge = i;
|
|
6927
|
+
}
|
|
6928
|
+
});
|
|
6929
|
+
anchor = anchor || { x, y: hullTop(candidate.outline, x) };
|
|
6930
|
+
if (edge >= 0)
|
|
6931
|
+
drawn.landings.push({ edge, point: anchor });
|
|
6932
|
+
this.frames.set(id, { points: own, hull: this.hulls.get(id), outline: candidate.outline, anchor, group: candidate.group.key });
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
for (const id of Array.from(this.since.keys())) {
|
|
6936
|
+
if (!shown.has(id))
|
|
6937
|
+
this.since.delete(id);
|
|
6938
|
+
}
|
|
6939
|
+
this.requests.clear();
|
|
6940
|
+
}
|
|
6941
|
+
/*
|
|
6942
|
+
* Draws each settled outline as corner brackets that grow into whole edges, back edges dashed.
|
|
6943
|
+
*/
|
|
6944
|
+
Draw(viewer, element, coordinates) {
|
|
6945
|
+
var _a, _b, _c;
|
|
6946
|
+
const now = Date.now();
|
|
6947
|
+
const looks = new Map();
|
|
6948
|
+
const memberLooks = new Map();
|
|
6949
|
+
let animating = false;
|
|
6950
|
+
for (const { group } of this.drawn) {
|
|
6951
|
+
const requests = group.members.map(id => this.requests.get(id)).filter(Boolean);
|
|
6952
|
+
if (!requests.length) {
|
|
6953
|
+
// Its labels are hidden this frame, perhaps off screen; it keeps its look so it does not regrow.
|
|
6954
|
+
const kept = this.looks.get(group.key);
|
|
6955
|
+
if (kept) {
|
|
6956
|
+
looks.set(group.key, kept);
|
|
6957
|
+
group.members.forEach(id => memberLooks.set(id, kept));
|
|
6958
|
+
if (kept.svg)
|
|
6959
|
+
kept.svg.style.display = "none";
|
|
6960
|
+
}
|
|
6961
|
+
continue;
|
|
6962
|
+
}
|
|
6963
|
+
// A group that gained or lost a member carries on from where its members' outline was.
|
|
6964
|
+
const previous = group.members.map(id => this.memberLooks.get(id)).filter(Boolean);
|
|
6965
|
+
const look = (_a = this.looks.get(group.key)) !== null && _a !== void 0 ? _a : {
|
|
6966
|
+
shown: Math.max(0, ...previous.map(item => item.shown)), whole: Math.max(0, ...previous.map(item => item.whole)),
|
|
6967
|
+
grow: Math.max(0, ...previous.map(item => item.grow)), time: now, svg: (_b = previous.find(item => item.svg)) === null || _b === void 0 ? void 0 : _b.svg
|
|
6968
|
+
};
|
|
6969
|
+
const elapsed = Math.min(32, Math.max(0, now - look.time));
|
|
6970
|
+
const duration = Math.min(...requests.map(request => request.duration));
|
|
6971
|
+
const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
|
|
6972
|
+
const approach = (value, to) => Math.abs(value - to) < 0.001 ? to : value + (to - value) * blend;
|
|
6973
|
+
const shownTarget = group.members.some(id => { var _a; return (_a = this.entries.get(id)) === null || _a === void 0 ? void 0 : _a.active; }) ? 1 : 0;
|
|
6974
|
+
const wholeTarget = group.hovered || requests.some(request => request.whole) ? 1 : 0;
|
|
6975
|
+
look.shown = approach(look.shown, shownTarget);
|
|
6976
|
+
look.whole = approach(look.whole, wholeTarget);
|
|
6977
|
+
look.grow = approach(look.grow, 1);
|
|
6978
|
+
look.time = now;
|
|
6979
|
+
animating = animating || look.shown !== shownTarget || look.whole !== wholeTarget || look.grow !== 1;
|
|
6980
|
+
looks.set(group.key, look);
|
|
6981
|
+
group.members.forEach(id => memberLooks.set(id, look));
|
|
6982
|
+
if (look.svg && this.looks.get(group.key) !== look) {
|
|
6983
|
+
// Inherited from an earlier group; claim it so that group's cleanup leaves it alone.
|
|
6984
|
+
for (const [key, other] of this.looks) {
|
|
6985
|
+
if (other.svg === look.svg && key !== group.key)
|
|
6986
|
+
other.svg = undefined;
|
|
6987
|
+
}
|
|
6988
|
+
}
|
|
6989
|
+
this.drawGroup(viewer, element, coordinates, this.drawn.find(item => item.group === group), look, requests);
|
|
6990
|
+
}
|
|
6991
|
+
for (const [key, look] of this.looks) {
|
|
6992
|
+
if (!looks.has(key))
|
|
6993
|
+
(_c = look.svg) === null || _c === void 0 ? void 0 : _c.remove();
|
|
6994
|
+
}
|
|
6995
|
+
this.looks = looks;
|
|
6996
|
+
this.memberLooks = memberLooks;
|
|
6997
|
+
if (animating) {
|
|
6998
|
+
viewer.scene.requestRender();
|
|
6999
|
+
}
|
|
7000
|
+
}
|
|
7001
|
+
drawGroup(viewer, element, coordinates, drawn, look, requests) {
|
|
7002
|
+
const group = drawn.group;
|
|
7003
|
+
let svg = look.svg;
|
|
7004
|
+
if (!svg) {
|
|
7005
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
7006
|
+
svg = document.createElementNS(ns, "svg");
|
|
7007
|
+
svg.setAttribute("class", "entity-label-model-box");
|
|
7008
|
+
svg.setAttribute("aria-hidden", "true");
|
|
7009
|
+
svg.style.cssText = "position:absolute;width:1px;height:1px;overflow:visible;pointer-events:none;filter:drop-shadow(0 0 2px rgba(0, 0, 0, 0.4));";
|
|
7010
|
+
for (const edges of ["back", "front"]) {
|
|
7011
|
+
const path = document.createElementNS(ns, "path");
|
|
7012
|
+
path.setAttribute("fill", "none");
|
|
7013
|
+
path.setAttribute("stroke-linecap", "round");
|
|
7014
|
+
path.dataset.edges = edges;
|
|
7015
|
+
svg.appendChild(path);
|
|
7016
|
+
}
|
|
7017
|
+
look.svg = svg;
|
|
7018
|
+
}
|
|
7019
|
+
if (svg.parentElement !== element) {
|
|
7020
|
+
element.appendChild(svg);
|
|
7021
|
+
}
|
|
7022
|
+
svg.style.display = "";
|
|
7023
|
+
const transforms = Cesium.SceneTransforms;
|
|
7024
|
+
const projectPosition = transforms.wgs84ToWindowCoordinates || transforms.worldToWindowCoordinates;
|
|
7025
|
+
const origin = group.origin;
|
|
7026
|
+
const scratch = new Cesium.Cartesian3();
|
|
7027
|
+
// A new box grows out of the ring it replaces.
|
|
7028
|
+
const grown = look.grow * look.grow * (3 - 2 * look.grow);
|
|
7029
|
+
const from = requests[0].from;
|
|
7030
|
+
const at = (point) => `${(from.x + (point.x - from.x) * grown) * coordinates.scaleX} ${(from.y + (point.y - from.y) * grown) * coordinates.scaleY}`;
|
|
7031
|
+
const toScreen = (point) => {
|
|
7032
|
+
const projected = projectPosition(viewer.scene, Cesium.Cartesian3.fromElements(origin.x + point[0], origin.y + point[1], origin.z + point[2], scratch));
|
|
7033
|
+
return at(projected);
|
|
7034
|
+
};
|
|
7035
|
+
const camera = viewer.camera.positionWC || viewer.camera.position;
|
|
7036
|
+
const eye = [camera.x - origin.x, camera.y - origin.y, camera.z - origin.z];
|
|
7037
|
+
const span = Math.max(...group.solids.map(solid => vLength(solid.half)));
|
|
7038
|
+
const inset = Math.max(1, span) * 1e-6;
|
|
7039
|
+
const fraction = MODEL_BOX_BRACKET + (0.5 - MODEL_BOX_BRACKET) * look.whole;
|
|
7040
|
+
const parts = fraction >= 0.499 ? [[0, 1]] : [[0, fraction], [1 - fraction, 1]];
|
|
7041
|
+
const paths = { front: "", back: "" };
|
|
7042
|
+
for (const [start, end] of group.outline) {
|
|
7043
|
+
const delta = vSub(end, start);
|
|
7044
|
+
// Sample visibility along the edge, since another member can hide part of it.
|
|
7045
|
+
const steps = group.solids.length > 1 ? Math.min(24, Math.max(2, Math.ceil(vLength(delta) / span * 12))) : 1;
|
|
7046
|
+
const visible = [];
|
|
7047
|
+
for (let i = 0; i < steps; i++) {
|
|
7048
|
+
visible.push(!solidsHide(group.solids, eye, vAdd(start, vScale(delta, (i + 0.5) / steps)), inset));
|
|
7049
|
+
}
|
|
7050
|
+
for (const [from, to] of parts) {
|
|
7051
|
+
let runStart = from;
|
|
7052
|
+
for (let i = Math.floor(from * steps); i < steps && i / steps < to; i++) {
|
|
7053
|
+
const runEnd = Math.min(to, (i + 1) / steps);
|
|
7054
|
+
if (runEnd < to && visible[i + 1] === visible[i]) {
|
|
7055
|
+
continue;
|
|
7056
|
+
}
|
|
7057
|
+
paths[visible[i] ? "front" : "back"] += `M${toScreen(vAdd(start, vScale(delta, runStart)))}L${toScreen(vAdd(start, vScale(delta, runEnd)))}`;
|
|
7058
|
+
runStart = runEnd;
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7062
|
+
// Each stem lands on an edge, which brackets may leave bare, so a short piece of it is drawn there.
|
|
7063
|
+
if (fraction < 0.499) {
|
|
7064
|
+
for (const { edge, point } of drawn.landings) {
|
|
7065
|
+
const [a, b] = drawn.edges[edge];
|
|
7066
|
+
const length = Math.hypot(b.x - a.x, b.y - a.y);
|
|
7067
|
+
const reach = Math.min(MODEL_BOX_LANDING, length / 2);
|
|
7068
|
+
const toward = (sign) => {
|
|
7069
|
+
const along = Math.max(0, Math.min(length, Math.hypot(point.x - a.x, point.y - a.y) + sign * reach));
|
|
7070
|
+
return { x: a.x + (b.x - a.x) * along / length, y: a.y + (b.y - a.y) * along / length };
|
|
7071
|
+
};
|
|
7072
|
+
paths.front += `M${at(toward(-1))}L${at(toward(1))}`;
|
|
7073
|
+
}
|
|
7074
|
+
}
|
|
7075
|
+
svg.style.left = `${coordinates.x}px`;
|
|
7076
|
+
svg.style.top = `${coordinates.y}px`;
|
|
7077
|
+
svg.style.opacity = String(Math.max(...requests.map(request => request.opacity)) * look.shown * Math.min(1, look.grow * 2));
|
|
7078
|
+
svg.style.zIndex = String(Math.max(...requests.map(request => request.zIndex)));
|
|
7079
|
+
const color = requests[0].color;
|
|
7080
|
+
for (const path of Array.from(svg.children)) {
|
|
7081
|
+
const front = path.dataset.edges === "front";
|
|
7082
|
+
path.setAttribute("d", paths[front ? "front" : "back"]);
|
|
7083
|
+
path.setAttribute("stroke", color);
|
|
7084
|
+
path.setAttribute("stroke-width", front ? "1.5" : "1");
|
|
7085
|
+
path.setAttribute("stroke-opacity", front ? "1" : "0.35");
|
|
7086
|
+
path.setAttribute("stroke-dasharray", front ? "" : "3 3");
|
|
7087
|
+
}
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
EntityLabel.ModelBoxes = ModelBoxes;
|
|
7091
|
+
/*
|
|
7092
|
+
* Andrew's monotone chain, counter-clockwise in screen space.
|
|
7093
|
+
*/
|
|
7094
|
+
function convexHull(points) {
|
|
7095
|
+
const sorted = [...points].sort((a, b) => a.x - b.x || a.y - b.y);
|
|
7096
|
+
const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
7097
|
+
const build = (list) => {
|
|
7098
|
+
const chain = [];
|
|
7099
|
+
for (const point of list) {
|
|
7100
|
+
while (chain.length >= 2 && cross(chain[chain.length - 2], chain[chain.length - 1], point) <= 0)
|
|
7101
|
+
chain.pop();
|
|
7102
|
+
chain.push(point);
|
|
7103
|
+
}
|
|
7104
|
+
chain.pop();
|
|
7105
|
+
return chain;
|
|
7106
|
+
};
|
|
7107
|
+
return [...build(sorted), ...build(sorted.reverse())];
|
|
7108
|
+
}
|
|
7109
|
+
/*
|
|
7110
|
+
* Where the top of a convex outline crosses a vertical line, so a stem leaves the box without jumping between corners.
|
|
7111
|
+
*/
|
|
7112
|
+
function hullTop(hull, x) {
|
|
7113
|
+
let top = Infinity;
|
|
7114
|
+
for (let i = 0; i < hull.length; i++) {
|
|
7115
|
+
const a = hull[i];
|
|
7116
|
+
const b = hull[(i + 1) % hull.length];
|
|
7117
|
+
if (x < Math.min(a.x, b.x) || x > Math.max(a.x, b.x)) {
|
|
7118
|
+
continue;
|
|
7119
|
+
}
|
|
7120
|
+
top = Math.min(top, a.x === b.x ? Math.min(a.y, b.y) : a.y + (b.y - a.y) * (x - a.x) / (b.x - a.x));
|
|
7121
|
+
}
|
|
7122
|
+
return top;
|
|
7123
|
+
}
|
|
7124
|
+
function hullArea(hull) {
|
|
7125
|
+
return Math.abs(hull.reduce((sum, point, i) => sum + point.x * hull[(i + 1) % hull.length].y - hull[(i + 1) % hull.length].x * point.y, 0)) / 2;
|
|
7126
|
+
}
|
|
7127
|
+
function hullContains(hull, point) {
|
|
7128
|
+
return hull.length >= 3 && hull.every((a, i) => {
|
|
7129
|
+
const b = hull[(i + 1) % hull.length];
|
|
7130
|
+
return (b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x) >= 0;
|
|
7131
|
+
});
|
|
7132
|
+
}
|
|
6367
7133
|
class Label {
|
|
6368
7134
|
get HasText() { return this.options.presentation !== "icon"; }
|
|
6369
7135
|
SetHoverVisible(visible) {
|
|
@@ -6418,6 +7184,11 @@
|
|
|
6418
7184
|
this.Transient = false;
|
|
6419
7185
|
this.hoverFade = { from: 0, to: 1, start: Date.now() };
|
|
6420
7186
|
this.cursorMotion = { amount: 0, from: 0, target: 0, start: 0, position: undefined };
|
|
7187
|
+
// How far the stem is from the ring onto a model box, and the offset it eases out after changing outline.
|
|
7188
|
+
// The record's box for the Entity this label is on: undefined corners while reading, null when it has none.
|
|
7189
|
+
this.modelBox = null;
|
|
7190
|
+
this.boxMorph = { value: 0, from: 0, target: 0, start: 0, anchor: undefined, group: undefined,
|
|
7191
|
+
shift: { x: 0, y: 0, start: 0 } };
|
|
6421
7192
|
// Counter to help differentiate between createLabel calls.
|
|
6422
7193
|
this.counter = 0;
|
|
6423
7194
|
// Whether this label has asked its group for a default image, which it does once on screen.
|
|
@@ -6503,11 +7274,13 @@
|
|
|
6503
7274
|
* Recalling will be ignored.
|
|
6504
7275
|
*/
|
|
6505
7276
|
Dispose() {
|
|
7277
|
+
var _a;
|
|
6506
7278
|
if (this.disposed) {
|
|
6507
7279
|
return;
|
|
6508
7280
|
}
|
|
6509
7281
|
this.disposed = true;
|
|
6510
7282
|
this.removeLabel();
|
|
7283
|
+
(_a = layers.get(this.viewer)) === null || _a === void 0 ? void 0 : _a.modelBoxes.Set(this.id, null, null, null);
|
|
6511
7284
|
clearTimeout(this.loadTimeout);
|
|
6512
7285
|
this.loadTimeout = null;
|
|
6513
7286
|
}
|
|
@@ -6522,14 +7295,15 @@
|
|
|
6522
7295
|
this.groupId = groupId;
|
|
6523
7296
|
this.additionalTexts = this.options.text != null ? [] : null;
|
|
6524
7297
|
this.photo = null;
|
|
6525
|
-
|
|
7298
|
+
// A label given its position keeps it, or the rebuild goes looking for the entity's own.
|
|
7299
|
+
this.createLabel(this.prePositioned ? this._pos3d : undefined);
|
|
6526
7300
|
}
|
|
6527
7301
|
/**
|
|
6528
7302
|
* Creates cesium entity for the label if one does not exist.
|
|
6529
7303
|
* @param starterPos3d optional position to create label at. Ignored for billboard labels.
|
|
6530
7304
|
*/
|
|
6531
7305
|
async createLabel(starterPos3d) {
|
|
6532
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
7306
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
|
|
6533
7307
|
// The old label stays until its replacement is built, so a rebuild never blinks while a location loads.
|
|
6534
7308
|
let pos3d = starterPos3d ? starterPos3d.clone() : null;
|
|
6535
7309
|
const counter = ++this.counter;
|
|
@@ -6815,6 +7589,9 @@
|
|
|
6815
7589
|
if (this.disposed || this.counter != counter) {
|
|
6816
7590
|
return;
|
|
6817
7591
|
}
|
|
7592
|
+
// Leave the layer only once rejoined, so a rebuild never empties it and closes it along with its boxes.
|
|
7593
|
+
const leaveLayer = this.renderRemoval;
|
|
7594
|
+
this.renderRemoval = null;
|
|
6818
7595
|
this.removeLabel();
|
|
6819
7596
|
// A found photo draws as if configured, without changing the options clustering compares and measures.
|
|
6820
7597
|
const photo = HasIcon(this.options) ? null : (_j = this.options.photo) !== null && _j !== void 0 ? _j : this.photo;
|
|
@@ -7111,9 +7888,65 @@
|
|
|
7111
7888
|
radius: Math.max(width, height) / 2
|
|
7112
7889
|
};
|
|
7113
7890
|
};
|
|
7891
|
+
// Only CAD tilesets and model Entities are assembly parts with a geometry box; anything else stays a point.
|
|
7892
|
+
const boxable = Boolean(normalLeader && ((_r = this.rego) === null || _r === void 0 ? void 0 : _r.entityId) && ((visual instanceof Cesium.Cesium3DTileFeature && this.rego.tilesetType === BModels.Tileset.EType.Cad) ||
|
|
7893
|
+
(visual instanceof Cesium.Entity && Boolean(visual.model))));
|
|
7894
|
+
if (!boxable || ((_s = this.modelBox) === null || _s === void 0 ? void 0 : _s.entityId) !== ((_t = this.rego) === null || _t === void 0 ? void 0 : _t.entityId)) {
|
|
7895
|
+
layer.modelBoxes.Set(this.id, null, null, null);
|
|
7896
|
+
this.modelBox = null;
|
|
7897
|
+
}
|
|
7898
|
+
// Read once per Entity, so a label rebuilt for its loaded text keeps its box instead of waiting again.
|
|
7899
|
+
if (boxable && !this.modelBox) {
|
|
7900
|
+
const box = { entityId: this.rego.entityId, corners: undefined, load: null };
|
|
7901
|
+
box.load = this.loadModelBox().catch(() => null).then(corners => box.corners = corners || null);
|
|
7902
|
+
this.modelBox = box;
|
|
7903
|
+
}
|
|
7904
|
+
// A model's label stays hidden until its box is known, so a ring never shows only to turn into a box.
|
|
7905
|
+
const hydrated = (_y = (_x = (_u = this.visualRegister) === null || _u === void 0 ? void 0 : _u.GetLabelGroupFor((_v = this.rego) === null || _v === void 0 ? void 0 : _v.entityId, (_w = this.rego) === null || _w === void 0 ? void 0 : _w.menuItemId)) === null || _x === void 0 ? void 0 : _x.item) === null || _y === void 0 ? void 0 : _y.entity;
|
|
7906
|
+
let boxPending = boxable && this.modelBox.corners === undefined && Boolean(((_z = hydrated === null || hydrated === void 0 ? void 0 : hydrated.Bruce) === null || _z === void 0 ? void 0 : _z.GeometryPivotExtents) || this.api);
|
|
7907
|
+
const boxPendingUntil = Date.now() + MODEL_BOX_WAIT;
|
|
7908
|
+
// Whether this label has been on screen yet; a box found before then is drawn in place, not eased into.
|
|
7909
|
+
let shownOnce = false;
|
|
7910
|
+
if (boxable) {
|
|
7911
|
+
const found = this._pos3d;
|
|
7912
|
+
const settle = () => {
|
|
7913
|
+
if (boxPending && !this.disposed && this.counter === counter) {
|
|
7914
|
+
boxPending = false;
|
|
7915
|
+
this.viewer.scene.requestRender();
|
|
7916
|
+
}
|
|
7917
|
+
};
|
|
7918
|
+
// Past the wait a slow record no longer holds the label back; a box that comes later eases in.
|
|
7919
|
+
setTimeout(settle, MODEL_BOX_WAIT);
|
|
7920
|
+
this.modelBox.load.then(corners => {
|
|
7921
|
+
if (!corners || this.disposed || this.counter !== counter) {
|
|
7922
|
+
return;
|
|
7923
|
+
}
|
|
7924
|
+
// A record that disagrees with where the model was found describes another placement, such as a moved tileset.
|
|
7925
|
+
const centre = Cesium.Cartesian3.midpoint(corners[0], corners[7], new Cesium.Cartesian3());
|
|
7926
|
+
if (Cesium.Cartesian3.distance(centre, found) > Cesium.Cartesian3.distance(corners[0], corners[7]) * 0.75 + 2) {
|
|
7927
|
+
return;
|
|
7928
|
+
}
|
|
7929
|
+
const rego = this.rego;
|
|
7930
|
+
// Let go a little further out than it is taken, so a camera resting at the limit does not flip it.
|
|
7931
|
+
let near = false;
|
|
7932
|
+
layer.modelBoxes.Set(this.id, corners, rego.entityId, {
|
|
7933
|
+
selected: () => { var _a, _b; return Boolean((_b = (_a = this.visualRegister) === null || _a === void 0 ? void 0 : _a.GetState({ entityId: rego.entityId, menuItemId: rego.menuItemId })) === null || _b === void 0 ? void 0 : _b.selected); },
|
|
7934
|
+
hovered: () => { var _a, _b; return Boolean((_b = (_a = this.visualRegister) === null || _a === void 0 ? void 0 : _a.IsLabelHovered) === null || _b === void 0 ? void 0 : _b.call(_a, rego.entityId, rego.menuItemId)); },
|
|
7935
|
+
near: () => {
|
|
7936
|
+
var _a, _b;
|
|
7937
|
+
const ring = this.options.anchorRing;
|
|
7938
|
+
const camera = this.viewer.camera.positionWC || this.viewer.camera.position;
|
|
7939
|
+
const limit = ((_a = ring === null || ring === void 0 ? void 0 : ring.nearDistance) !== null && _a !== void 0 ? _a : 100) * (near ? 1.1 : 1);
|
|
7940
|
+
near = (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && Number.isFinite((_b = this._pos3d) === null || _b === void 0 ? void 0 : _b.x) && Cesium.Cartesian3.distance(camera, this._pos3d) <= limit;
|
|
7941
|
+
return near || ((ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && Boolean(this.cursorMotion.target));
|
|
7942
|
+
}
|
|
7943
|
+
});
|
|
7944
|
+
this.viewer.scene.requestRender();
|
|
7945
|
+
}).finally(settle);
|
|
7946
|
+
}
|
|
7114
7947
|
let cursorMotion = this.cursorMotion;
|
|
7115
7948
|
const project = () => {
|
|
7116
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
7949
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
|
|
7117
7950
|
if (this.disposed || !this.viewer || this.viewer.isDestroyed() || ((_a = this.visible) === null || _a === void 0 ? void 0 : _a.call(this)) === false ||
|
|
7118
7951
|
(presentation === "icon" && this.options.iconScale === 0)) {
|
|
7119
7952
|
return null;
|
|
@@ -7151,18 +7984,29 @@
|
|
|
7151
7984
|
if (!Number.isFinite(anchor === null || anchor === void 0 ? void 0 : anchor.x) || !Number.isFinite(anchor === null || anchor === void 0 ? void 0 : anchor.y)) {
|
|
7152
7985
|
return null;
|
|
7153
7986
|
}
|
|
7987
|
+
const boxed = layer.modelBoxes.Frame(this.id);
|
|
7988
|
+
const waiting = boxPending && Date.now() < boxPendingUntil;
|
|
7989
|
+
const boxBlend = this.morphToBox(boxed, shownOnce ? (_f = (_e = this.options.anchorRing) === null || _e === void 0 ? void 0 : _e.transitionDuration) !== null && _f !== void 0 ? _f : 200 : 0);
|
|
7990
|
+
// Eased between the ring and where the stem meets the box, so gaining or losing a box never jumps.
|
|
7991
|
+
const boxTarget = this.boxMorph.anchor;
|
|
7992
|
+
const ringPoint = { x: anchor.x + pixelOffset.x, y: anchor.y + pixelOffset.y };
|
|
7993
|
+
const boxAnchor = boxBlend > 0 && boxTarget ? {
|
|
7994
|
+
x: ringPoint.x + (boxTarget.x - ringPoint.x) * boxBlend,
|
|
7995
|
+
y: ringPoint.y + (boxTarget.y - ringPoint.y) * boxBlend
|
|
7996
|
+
} : undefined;
|
|
7997
|
+
const start = boxAnchor || anchor;
|
|
7154
7998
|
const onScreen = anchor.x >= 0 && anchor.y >= 0 && anchor.x < this.viewer.canvas.clientWidth && anchor.y < this.viewer.canvas.clientHeight;
|
|
7155
|
-
if (leader &&
|
|
7999
|
+
if (leader && (start.x < 0 || start.y < 0 || start.x >= this.viewer.canvas.clientWidth || start.y >= this.viewer.canvas.clientHeight))
|
|
7156
8000
|
return null;
|
|
7157
8001
|
if (onScreen && !this.imageAsked && this.groupId && !HasIcon(this.options) && !this.options.photo) {
|
|
7158
8002
|
this.imageAsked = true;
|
|
7159
|
-
(
|
|
8003
|
+
(_g = this.visualRegister) === null || _g === void 0 ? void 0 : _g.RequestLabelImage((_h = this.rego) === null || _h === void 0 ? void 0 : _h.entityId);
|
|
7160
8004
|
}
|
|
7161
8005
|
const transientPivot = (this.Transient && !this.Persistent ?
|
|
7162
|
-
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (
|
|
8006
|
+
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (_j = this.visualRegister) === null || _j === void 0 ? void 0 : _j.GetLabelHoverPosition((_k = this.rego) === null || _k === void 0 ? void 0 : _k.entityId, (_l = this.rego) === null || _l === void 0 ? void 0 : _l.menuItemId)) : undefined);
|
|
7163
8007
|
const hover = (presentation !== "icon" ?
|
|
7164
8008
|
(this.Transient && !this.Persistent && this.hoverFade.to === 0 ? this.retainedHoverPosition :
|
|
7165
|
-
(
|
|
8009
|
+
(_m = this.visualRegister) === null || _m === void 0 ? void 0 : _m.GetLabelHoverPosition((_o = this.rego) === null || _o === void 0 ? void 0 : _o.entityId, (_p = this.rego) === null || _p === void 0 ? void 0 : _p.menuItemId)) : undefined);
|
|
7166
8010
|
const reused = !this.Transient || this.Persistent;
|
|
7167
8011
|
const now = Date.now();
|
|
7168
8012
|
const animateCursor = () => {
|
|
@@ -7186,12 +8030,14 @@
|
|
|
7186
8030
|
const cursorAmount = cursorMotion.amount;
|
|
7187
8031
|
leader = normalLeader || cursorAmount > 0;
|
|
7188
8032
|
// An icon keeps its callout centred on the glyph rather than following the cursor across it.
|
|
7189
|
-
const icon = measureIcon();
|
|
8033
|
+
const icon = boxAnchor ? null : measureIcon();
|
|
7190
8034
|
const iconRing = icon ? Math.max(ICON_RING_MIN, icon.radius + ICON_RING_PADDING) : 0;
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
8035
|
+
// A boxed model is pinned the same way, with its stem leaving the top of the box outline.
|
|
8036
|
+
const pinned = Boolean(icon || boxAnchor);
|
|
8037
|
+
const pivot = pinned ? undefined : transientPivot;
|
|
8038
|
+
let x = boxAnchor ? boxAnchor.x : ((_q = pivot === null || pivot === void 0 ? void 0 : pivot.x) !== null && _q !== void 0 ? _q : anchor.x) + (icon || pixelOffset).x;
|
|
8039
|
+
let y = boxAnchor ? boxAnchor.y : ((_r = pivot === null || pivot === void 0 ? void 0 : pivot.y) !== null && _r !== void 0 ? _r : anchor.y) + (icon || pixelOffset).y;
|
|
8040
|
+
if (cursorAmount > 0 && !pinned) {
|
|
7195
8041
|
// A leader-less label starts centred on its anchor; preserve that box as it lifts away.
|
|
7196
8042
|
if (!normalLeader) {
|
|
7197
8043
|
x -= ele.offsetWidth / layer.coordinates.scaleX / 2;
|
|
@@ -7226,6 +8072,9 @@
|
|
|
7226
8072
|
if (visual instanceof Cesium.Entity && (visual.polygon || visual.polyline))
|
|
7227
8073
|
leaderLength /= 3;
|
|
7228
8074
|
}
|
|
8075
|
+
if (boxAnchor && this.options.leaderLength == null) {
|
|
8076
|
+
leaderLength += (MODEL_BOX_STEM - leaderLength) * boxBlend;
|
|
8077
|
+
}
|
|
7229
8078
|
if (cursorAmount > 0) {
|
|
7230
8079
|
leaderLength += (96 - leaderLength) * cursorAmount;
|
|
7231
8080
|
}
|
|
@@ -7233,7 +8082,7 @@
|
|
|
7233
8082
|
leaderLength += iconRing;
|
|
7234
8083
|
y = anchorY - leaderLength;
|
|
7235
8084
|
}
|
|
7236
|
-
else if (hover && !reused && !
|
|
8085
|
+
else if (hover && !reused && !pinned) {
|
|
7237
8086
|
x = hover.x + pixelOffset.x;
|
|
7238
8087
|
y = hover.y + pixelOffset.y - 24;
|
|
7239
8088
|
}
|
|
@@ -7242,7 +8091,7 @@
|
|
|
7242
8091
|
const width = ele.offsetWidth / coordinates.scaleX;
|
|
7243
8092
|
const height = ele.offsetHeight / coordinates.scaleY;
|
|
7244
8093
|
let clusterBounds;
|
|
7245
|
-
if ((
|
|
8094
|
+
if ((_s = this.options.cluster) === null || _s === void 0 ? void 0 : _s.enabled) {
|
|
7246
8095
|
const textWidth = (sourceText === null || sourceText === void 0 ? void 0 : sourceText.offsetWidth) || 0;
|
|
7247
8096
|
const textHeight = (sourceText === null || sourceText === void 0 ? void 0 : sourceText.offsetHeight) || 0;
|
|
7248
8097
|
// A photo is never part of the footprint, so its arrival cannot change which cluster a label joins.
|
|
@@ -7276,7 +8125,7 @@
|
|
|
7276
8125
|
if (!leader && (bounds.x + bounds.width < 0 || bounds.y + bounds.height < 0 || bounds.x > this.viewer.canvas.clientWidth || bounds.y > this.viewer.canvas.clientHeight)) {
|
|
7277
8126
|
return null;
|
|
7278
8127
|
}
|
|
7279
|
-
const state = (
|
|
8128
|
+
const state = (_t = this.visualRegister) === null || _t === void 0 ? void 0 : _t.GetState({ entityId: (_u = this.rego) === null || _u === void 0 ? void 0 : _u.entityId, menuItemId: (_v = this.rego) === null || _v === void 0 ? void 0 : _v.menuItemId });
|
|
7280
8129
|
const opacity = this.prePositioned || hoverOnly || MAX_DISTANCE === Infinity ? 1 : 1 - distance / MAX_DISTANCE;
|
|
7281
8130
|
return {
|
|
7282
8131
|
canFlip: leader && presentation !== "icon", hasLeader: leader,
|
|
@@ -7284,24 +8133,25 @@
|
|
|
7284
8133
|
transient: this.Transient || Boolean(hover) || cursorAmount > 0,
|
|
7285
8134
|
extendedGeometry: visual instanceof Cesium.Entity && Boolean(visual.polygon || visual.polyline || visual.corridor),
|
|
7286
8135
|
viewportWidth: this.viewer.canvas.clientWidth,
|
|
7287
|
-
id: this.id, entityKey: ((
|
|
7288
|
-
entityId: (
|
|
7289
|
-
iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius,
|
|
8136
|
+
id: this.id, entityKey: ((_w = this.rego) === null || _w === void 0 ? void 0 : _w.entityId) ? JSON.stringify([this.rego.entityId, this.rego.menuItemId || "", this.Transient && !this.Persistent ? "hover" : ""]) : undefined,
|
|
8137
|
+
entityId: (_x = this.rego) === null || _x === void 0 ? void 0 : _x.entityId, group: this.groupId, x, y, width, height, anchorX, anchorY, depth, leaderLength, leaderRun, leaderSide, distance,
|
|
8138
|
+
iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius, modelBox: boxed || undefined, boxBlend,
|
|
7290
8139
|
// Priority shows the hovered member individually without dragging its cluster along.
|
|
7291
|
-
opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
|
|
8140
|
+
opacity: waiting ? 0 : opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
|
|
7292
8141
|
};
|
|
7293
8142
|
};
|
|
7294
8143
|
let displacement = { x: 0, y: 0 };
|
|
7295
8144
|
let nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
|
|
7296
8145
|
const apply = (placement) => {
|
|
7297
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4;
|
|
8146
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5;
|
|
7298
8147
|
const show = (placement === null || placement === void 0 ? void 0 : placement.visible) && placement.opacity > 0;
|
|
8148
|
+
shownOnce = shownOnce || Boolean(show);
|
|
7299
8149
|
clickEntityIds = show ? placement.entityIds : [];
|
|
7300
8150
|
ele.style.visibility = show ? "visible" : "hidden";
|
|
7301
8151
|
lineEle.style.display = show && leader ? "block" : "none";
|
|
7302
8152
|
if (runEle)
|
|
7303
8153
|
runEle.style.display = show && leader ? "block" : "none";
|
|
7304
|
-
this.anchor.style.display = show && leader ? "block" : "none";
|
|
8154
|
+
this.anchor.style.display = show && leader && !(((_a = placement.frame) === null || _a === void 0 ? void 0 : _a.boxBlend) >= 1) ? "block" : "none";
|
|
7305
8155
|
if (!show) {
|
|
7306
8156
|
displacement = { x: 0, y: 0 };
|
|
7307
8157
|
nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
|
|
@@ -7313,7 +8163,7 @@
|
|
|
7313
8163
|
return;
|
|
7314
8164
|
}
|
|
7315
8165
|
// A label standing for a cluster shows its summary only; its photo is put away and never fetched.
|
|
7316
|
-
const solo = !((
|
|
8166
|
+
const solo = !((_b = placement.members) === null || _b === void 0 ? void 0 : _b.length);
|
|
7317
8167
|
if (look.iconPhoto && iconElement && iconElement.dataset.failed !== "true") {
|
|
7318
8168
|
const display = solo ? "inline-flex" : "none";
|
|
7319
8169
|
if (iconElement.style.display !== display) {
|
|
@@ -7331,7 +8181,7 @@
|
|
|
7331
8181
|
}
|
|
7332
8182
|
}, PHOTO_SHOW_DELAY);
|
|
7333
8183
|
}
|
|
7334
|
-
const displayText = (
|
|
8184
|
+
const displayText = (_c = placement.text) !== null && _c !== void 0 ? _c : originalText;
|
|
7335
8185
|
if (textElement && textElement.textContent !== displayText) {
|
|
7336
8186
|
textElement.textContent = displayText;
|
|
7337
8187
|
// Measure the summary's footprint next frame without replacing each member's source text.
|
|
@@ -7355,9 +8205,9 @@
|
|
|
7355
8205
|
const stack = JSON.stringify(placement.icons || []);
|
|
7356
8206
|
if (iconStack && stack !== displayedStack) {
|
|
7357
8207
|
displayedStack = stack;
|
|
7358
|
-
const looks = ((
|
|
8208
|
+
const looks = ((_d = placement.icons) === null || _d === void 0 ? void 0 : _d.length) ? [iconLook(this.options), ...placement.icons] : [];
|
|
7359
8209
|
// One small size for all, so no icon in the arrangement reads as more important than another.
|
|
7360
|
-
const size = (this.options.iconSize || 24) * ((
|
|
8210
|
+
const size = (this.options.iconSize || 24) * ((_e = this.options.iconScale) !== null && _e !== void 0 ? _e : 1) * ICON_STACK_SCALE;
|
|
7361
8211
|
const built = looks.map(look => this.buildIcon({ ...look, iconSize: size }, counter));
|
|
7362
8212
|
// A stack is on show already, so its photos load straight away.
|
|
7363
8213
|
built.forEach(({ start }) => start === null || start === void 0 ? void 0 : start());
|
|
@@ -7392,9 +8242,11 @@
|
|
|
7392
8242
|
// Summary and badge changes must fit on their first visible frame, including after resize.
|
|
7393
8243
|
const ring = this.options.anchorRing;
|
|
7394
8244
|
const pointer = (layer.pointer || (cursorMotion.target ? cursorMotion.position : undefined) || (this.Transient && !this.Persistent ?
|
|
7395
|
-
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (
|
|
8245
|
+
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (_f = this.visualRegister) === null || _f === void 0 ? void 0 : _f.GetLabelHoverPosition((_g = this.rego) === null || _g === void 0 ? void 0 : _g.entityId, (_h = this.rego) === null || _h === void 0 ? void 0 : _h.menuItemId)) : undefined));
|
|
7396
8246
|
const iconRing = frame.iconRadius ? Math.max(ICON_RING_MIN, frame.iconRadius + ICON_RING_PADDING) : 0;
|
|
7397
|
-
const
|
|
8247
|
+
const boxed = leader ? frame.modelBox : undefined;
|
|
8248
|
+
const hovered = pointer && (boxed ? hullContains(boxed.hull, pointer) :
|
|
8249
|
+
Math.hypot(pointer.x - frame.anchorX, pointer.y - frame.anchorY) <= Math.max((_j = ring === null || ring === void 0 ? void 0 : ring.hoverDistance) !== null && _j !== void 0 ? _j : 24, iconRing));
|
|
7398
8250
|
if (look.iconPhoto && growPhoto) {
|
|
7399
8251
|
// A rectangle leans on the leader's side of the label.
|
|
7400
8252
|
if (photoShape === "rect") {
|
|
@@ -7405,15 +8257,15 @@
|
|
|
7405
8257
|
}
|
|
7406
8258
|
// Grown in layout rather than scaled, so the border keeps its width, and the label, whose bottom
|
|
7407
8259
|
// sits on the leader, grows up and sideways without changing the leader's length.
|
|
7408
|
-
growPhoto(Boolean(hovered) && !((
|
|
8260
|
+
growPhoto(Boolean(hovered) && !((_k = placement.members) === null || _k === void 0 ? void 0 : _k.length));
|
|
7409
8261
|
}
|
|
7410
|
-
const normalRadius = (!leader || (ring === null || ring === void 0 ? void 0 : ring.enabled) === false ? 0 : Math.max(frame.distance <= ((
|
|
8262
|
+
const normalRadius = (!leader || (ring === null || ring === void 0 ? void 0 : ring.enabled) === false ? 0 : Math.max(frame.distance <= ((_l = ring === null || ring === void 0 ? void 0 : ring.nearDistance) !== null && _l !== void 0 ? _l : 100) ? ((_m = ring === null || ring === void 0 ? void 0 : ring.nearRadius) !== null && _m !== void 0 ? _m : 10) : 0, hovered ? ((_o = ring === null || ring === void 0 ? void 0 : ring.hoverRadius) !== null && _o !== void 0 ? _o : (this.Transient && !this.Persistent ? 15.4 : 22)) : 0));
|
|
7411
8263
|
const targetRadius = normalRadius + (15.4 - normalRadius) * cursorMotion.amount;
|
|
7412
8264
|
// Around a glyph the ring keeps the glyph's size and only fades its stroke in, so it never covers the icon.
|
|
7413
8265
|
const targetOuter = iconRing || targetRadius || 2.5;
|
|
7414
8266
|
const targetInner = iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
|
|
7415
8267
|
const now = Date.now();
|
|
7416
|
-
const duration = (
|
|
8268
|
+
const duration = (_p = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _p !== void 0 ? _p : 200;
|
|
7417
8269
|
const sharedRing = this.Transient && !this.Persistent && this.hoverFade.to === 1 ? this.hoverRingState : undefined;
|
|
7418
8270
|
let ringAnimation = sharedRing ? sharedRing.animation : this.ringAnimation;
|
|
7419
8271
|
if (!ringAnimation) {
|
|
@@ -7447,7 +8299,8 @@
|
|
|
7447
8299
|
if (sharedRing) {
|
|
7448
8300
|
sharedRing.animation = ringAnimation;
|
|
7449
8301
|
}
|
|
7450
|
-
const
|
|
8302
|
+
const ringShare = 1 - (frame.boxBlend || 0);
|
|
8303
|
+
const radius = ringShare > 0 && ringAnimation.inner > 0 ? ringAnimation.outer * ringShare : 0;
|
|
7451
8304
|
const stem = (leader ? (angled ? fitAngledLeader : fitLeader)(frame.anchorX, frame.anchorY, Math.max(frame.leaderLength, radius ? radius + 4 : 0), ele.offsetWidth / coordinates.scaleX, ele.offsetHeight / coordinates.scaleY, radius) : undefined);
|
|
7452
8305
|
const position = { x: (stem || frame).x, y: (stem || frame).y };
|
|
7453
8306
|
const width = ele.offsetWidth / coordinates.scaleX;
|
|
@@ -7455,19 +8308,19 @@
|
|
|
7455
8308
|
if (leader && angled) {
|
|
7456
8309
|
// The declutter owns the side; the fit only says which one had the room to begin with.
|
|
7457
8310
|
const run = Math.max(ANGLED_LEADER_RUN, radius + 4);
|
|
7458
|
-
const side = (
|
|
8311
|
+
const side = (_r = (_q = placement.textSide) !== null && _q !== void 0 ? _q : frame.leaderSide) !== null && _r !== void 0 ? _r : "right";
|
|
7459
8312
|
const beside = side === "left" ? frame.anchorX - run - width : frame.anchorX + run;
|
|
7460
8313
|
position.x = Math.max(0, Math.min(beside, this.viewer.canvas.clientWidth - width));
|
|
7461
8314
|
}
|
|
7462
8315
|
else if (leader && placement.textSide === "left") {
|
|
7463
8316
|
position.x = Math.max(0, Math.min(frame.anchorX - width, this.viewer.canvas.clientWidth - width));
|
|
7464
8317
|
}
|
|
7465
|
-
const gap = (
|
|
8318
|
+
const gap = (_t = (_s = this.options.pointerAvoidance) === null || _s === void 0 ? void 0 : _s.gap) !== null && _t !== void 0 ? _t : 12;
|
|
7466
8319
|
const containsPointer = (x, y, margin) => {
|
|
7467
8320
|
return pointer && pointer.x >= x - margin && pointer.x <= x + width + margin && pointer.y >= y - margin && pointer.y <= y + height + margin;
|
|
7468
8321
|
};
|
|
7469
8322
|
const passive = !selectOnClick || !this.visualRegister;
|
|
7470
|
-
if (passive && ((
|
|
8323
|
+
if (passive && ((_u = this.options.pointerAvoidance) === null || _u === void 0 ? void 0 : _u.enabled) !== false && pointer &&
|
|
7471
8324
|
(containsPointer(position.x, position.y, gap) || containsPointer(position.x + displacement.x, position.y + displacement.y, gap))) {
|
|
7472
8325
|
if (containsPointer(position.x + displacement.x, position.y + displacement.y, 0)) {
|
|
7473
8326
|
// Choose against the original box and retain the offset until the pointer leaves both boxes.
|
|
@@ -7485,7 +8338,7 @@
|
|
|
7485
8338
|
else {
|
|
7486
8339
|
displacement = { x: 0, y: 0 };
|
|
7487
8340
|
}
|
|
7488
|
-
const nudgeDuration = (
|
|
8341
|
+
const nudgeDuration = (_w = (_v = this.options.pointerAvoidance) === null || _v === void 0 ? void 0 : _v.transitionDuration) !== null && _w !== void 0 ? _w : 200;
|
|
7489
8342
|
const animateNudge = () => {
|
|
7490
8343
|
const progress = now < nudge.start ? 0 : nudgeDuration > 0 ? Math.min(1, (now - nudge.start) / nudgeDuration) : 1;
|
|
7491
8344
|
const eased = progress * progress * (3 - 2 * progress);
|
|
@@ -7501,7 +8354,7 @@
|
|
|
7501
8354
|
fromY: nudge.y,
|
|
7502
8355
|
targetX: displacement.x,
|
|
7503
8356
|
targetY: displacement.y,
|
|
7504
|
-
start: now + ((
|
|
8357
|
+
start: now + ((_y = (_x = this.options.pointerAvoidance) === null || _x === void 0 ? void 0 : _x.delay) !== null && _y !== void 0 ? _y : 180)
|
|
7505
8358
|
};
|
|
7506
8359
|
}
|
|
7507
8360
|
if (animateNudge() < 1 && (nudge.fromX !== nudge.targetX || nudge.fromY !== nudge.targetY)) {
|
|
@@ -7512,10 +8365,10 @@
|
|
|
7512
8365
|
ele.style.left = `${coordinates.x + position.x * coordinates.scaleX}px`;
|
|
7513
8366
|
ele.style.top = `${coordinates.y + position.y * coordinates.scaleY}px`;
|
|
7514
8367
|
for (const clickTarget of Array.from(content.querySelectorAll('[role="button"]'))) {
|
|
7515
|
-
clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(
|
|
8368
|
+
clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(_0 = (_z = this.options.text) !== null && _z !== void 0 ? _z : text) !== null && _0 !== void 0 ? _0 : (_1 = this.rego) === null || _1 === void 0 ? void 0 : _1.entityId}`);
|
|
7516
8369
|
}
|
|
7517
8370
|
if (leader) {
|
|
7518
|
-
const nodeRadius = ringAnimation.outer;
|
|
8371
|
+
const nodeRadius = ringAnimation.outer * (1 - (frame.boxBlend || 0));
|
|
7519
8372
|
const anchor = this.anchor;
|
|
7520
8373
|
anchor.style.left = `${coordinates.x + (frame.anchorX - nodeRadius) * coordinates.scaleX}px`;
|
|
7521
8374
|
anchor.style.top = `${coordinates.y + (frame.anchorY - nodeRadius) * coordinates.scaleY}px`;
|
|
@@ -7528,11 +8381,12 @@
|
|
|
7528
8381
|
const circle = svg.firstElementChild;
|
|
7529
8382
|
circle.setAttribute("cx", String(nodeRadius));
|
|
7530
8383
|
circle.setAttribute("cy", String(nodeRadius));
|
|
7531
|
-
|
|
8384
|
+
const innerRadius = ringAnimation.inner * (1 - (frame.boxBlend || 0));
|
|
8385
|
+
circle.setAttribute("r", String((nodeRadius + innerRadius) / 2));
|
|
7532
8386
|
circle.setAttribute("stroke", this.options.leaderColor || "rgba(255, 255, 255, 0.8)");
|
|
7533
|
-
circle.setAttribute("stroke-width", String(nodeRadius -
|
|
8387
|
+
circle.setAttribute("stroke-width", String(nodeRadius - innerRadius));
|
|
7534
8388
|
anchor.style.boxShadow = radius ? "none" : "0 0 5px rgba(0, 0, 0, 0.3)";
|
|
7535
|
-
anchor.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount));
|
|
8389
|
+
anchor.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount) * (1 - (frame.boxBlend || 0)));
|
|
7536
8390
|
anchor.style.zIndex = String(placement.zIndex);
|
|
7537
8391
|
// The stem starts at the rim, never across the transparent centre, even after passive displacement.
|
|
7538
8392
|
const straightX = Math.max(position.x, Math.min(frame.anchorX, position.x + width));
|
|
@@ -7552,13 +8406,13 @@
|
|
|
7552
8406
|
// Keep the stem inside the 126-degree arc, away from antialiased endpoints.
|
|
7553
8407
|
const arcFraction = 0.35;
|
|
7554
8408
|
const rotationLimit = Math.PI * arcFraction - Math.PI / 30;
|
|
7555
|
-
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((
|
|
8409
|
+
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_2 = ringAnimation.arcOffset) !== null && _2 !== void 0 ? _2 : 0);
|
|
7556
8410
|
const targetOpen = cursorMotion.target || (hovered && (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && targetRadius > 0) ? 1 : 0;
|
|
7557
|
-
const elapsed = Math.min(32, Math.max(0, now - ((
|
|
8411
|
+
const elapsed = Math.min(32, Math.max(0, now - ((_3 = ringAnimation.arcTime) !== null && _3 !== void 0 ? _3 : now)));
|
|
7558
8412
|
const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
|
|
7559
8413
|
const approach = (value, target) => Math.abs(value - target) < 0.001 ? target : value + (target - value) * blend;
|
|
7560
|
-
ringAnimation.arcOffset = approach((
|
|
7561
|
-
ringAnimation.arcOpen = approach((
|
|
8414
|
+
ringAnimation.arcOffset = approach((_4 = ringAnimation.arcOffset) !== null && _4 !== void 0 ? _4 : 0, targetOffset);
|
|
8415
|
+
ringAnimation.arcOpen = approach((_5 = ringAnimation.arcOpen) !== null && _5 !== void 0 ? _5 : 0, targetOpen);
|
|
7562
8416
|
ringAnimation.arcTime = now;
|
|
7563
8417
|
if (ringAnimation.arcOffset !== targetOffset || ringAnimation.arcOpen !== targetOpen) {
|
|
7564
8418
|
this.viewer.scene.requestRender();
|
|
@@ -7584,6 +8438,12 @@
|
|
|
7584
8438
|
element.style.zIndex = String(placement.zIndex);
|
|
7585
8439
|
};
|
|
7586
8440
|
draw(lineEle, startX, startY, endX, endY);
|
|
8441
|
+
if (layer.modelBoxes.Hull(this.id)) {
|
|
8442
|
+
layer.modelBoxes.Request(this.id, {
|
|
8443
|
+
whole: Boolean(cursorMotion.target), from: { x: frame.anchorX, y: frame.anchorY },
|
|
8444
|
+
opacity, zIndex: placement.zIndex, color: this.options.leaderColor || "rgba(255, 255, 255, 0.8)", duration
|
|
8445
|
+
});
|
|
8446
|
+
}
|
|
7587
8447
|
if (runEle) {
|
|
7588
8448
|
// Overlap the corner by the stem's own width so the elbow has no notch in it.
|
|
7589
8449
|
const overlap = frame.anchorX < runToX ? -1 : 1;
|
|
@@ -7596,6 +8456,7 @@
|
|
|
7596
8456
|
ele.style.display = "flex";
|
|
7597
8457
|
ele.style.visibility = "hidden";
|
|
7598
8458
|
this.renderRemoval = layer.Add({ project, apply });
|
|
8459
|
+
leaveLayer === null || leaveLayer === void 0 ? void 0 : leaveLayer();
|
|
7599
8460
|
// If terrain changes then we'll re-call createLabel as the position may have changed.
|
|
7600
8461
|
let terrTimeout = null;
|
|
7601
8462
|
this.terrChangedRemoval = this.viewer.scene.terrainProviderChanged.addEventListener(() => {
|
|
@@ -7741,7 +8602,7 @@
|
|
|
7741
8602
|
// The glyph plus the chip's padding of a third either side, enlarged for a photo.
|
|
7742
8603
|
const diameter = (size + 2 * (size / 3)) * PHOTO_SIZE_SCALE;
|
|
7743
8604
|
// Enough pixels to stay sharp at the hover size.
|
|
7744
|
-
const pixels =
|
|
8605
|
+
const pixels = photoPixels(size, true);
|
|
7745
8606
|
const canvas = document.createElement("canvas");
|
|
7746
8607
|
canvas.className = "entity-label-photo";
|
|
7747
8608
|
canvas.style.cssText = "display:block;box-sizing:border-box;border:2px solid rgba(255,255,255,0.9);box-shadow:0 1px 3px rgba(0,0,0,0.35);";
|
|
@@ -7752,13 +8613,15 @@
|
|
|
7752
8613
|
// Resting size, set once the photo's shape is known.
|
|
7753
8614
|
let width = 0;
|
|
7754
8615
|
let height = 0;
|
|
8616
|
+
// How far hovering grows it, which a small image limits.
|
|
8617
|
+
let hoverScale = PHOTO_HOVER_SCALE;
|
|
7755
8618
|
let grown = false;
|
|
7756
8619
|
const grow = (on) => {
|
|
7757
8620
|
if (on === grown || !width) {
|
|
7758
8621
|
return;
|
|
7759
8622
|
}
|
|
7760
8623
|
grown = on;
|
|
7761
|
-
const scale = on ?
|
|
8624
|
+
const scale = on ? hoverScale : 1;
|
|
7762
8625
|
canvas.style.width = `${width * scale}px`;
|
|
7763
8626
|
canvas.style.height = `${height * scale}px`;
|
|
7764
8627
|
this.renderFor(PHOTO_TRANSITION);
|
|
@@ -7776,11 +8639,15 @@
|
|
|
7776
8639
|
(_a = canvas.getContext("2d")) === null || _a === void 0 ? void 0 : _a.drawImage(photo.source, 0, 0);
|
|
7777
8640
|
const longest = Math.max(photo.width, photo.height);
|
|
7778
8641
|
const circle = PhotoShape(photo.width, photo.height) === "circle";
|
|
7779
|
-
|
|
7780
|
-
|
|
8642
|
+
// Shown no larger than the image has pixels for, one to a CSS pixel, and never smaller than the icon.
|
|
8643
|
+
const native = circle ? Math.min(photo.width, photo.height) : longest;
|
|
8644
|
+
const across = Math.min(diameter, Math.max(size, native));
|
|
8645
|
+
hoverScale = Math.max(1, Math.min(PHOTO_HOVER_SCALE, native / across));
|
|
8646
|
+
width = circle ? across : across * photo.width / longest;
|
|
8647
|
+
height = circle ? across : across * photo.height / longest;
|
|
7781
8648
|
canvas.style.width = `${width}px`;
|
|
7782
8649
|
canvas.style.height = `${height}px`;
|
|
7783
|
-
canvas.style.borderRadius = circle ? "50%" : `${
|
|
8650
|
+
canvas.style.borderRadius = circle ? "50%" : `${across * PHOTO_CORNER}px`;
|
|
7784
8651
|
canvas.style.opacity = "1";
|
|
7785
8652
|
placePhoto === null || placePhoto === void 0 ? void 0 : placePhoto(circle ? "circle" : "rect", grow);
|
|
7786
8653
|
return true;
|
|
@@ -7827,6 +8694,57 @@
|
|
|
7827
8694
|
};
|
|
7828
8695
|
step();
|
|
7829
8696
|
}
|
|
8697
|
+
/*
|
|
8698
|
+
* Reads the record's geometry box, preferring an entity a label group already hydrated.
|
|
8699
|
+
*/
|
|
8700
|
+
async loadModelBox() {
|
|
8701
|
+
var _a, _b, _c, _d;
|
|
8702
|
+
const known = (_c = (_b = (_a = this.visualRegister) === null || _a === void 0 ? void 0 : _a.GetLabelGroupFor(this.rego.entityId, this.rego.menuItemId)) === null || _b === void 0 ? void 0 : _b.item) === null || _c === void 0 ? void 0 : _c.entity;
|
|
8703
|
+
let entity = ((_d = known === null || known === void 0 ? void 0 : known.Bruce) === null || _d === void 0 ? void 0 : _d.GeometryPivotExtents) ? known : null;
|
|
8704
|
+
if (!entity && this.api) {
|
|
8705
|
+
try {
|
|
8706
|
+
entity = (await BModels.Entity.Get({ api: this.api, entityId: this.rego.entityId, expandLocation: true, migrated: true })).entity;
|
|
8707
|
+
}
|
|
8708
|
+
// Expanding location fails for entities outside an assembly, which have no box to give.
|
|
8709
|
+
catch {
|
|
8710
|
+
return null;
|
|
8711
|
+
}
|
|
8712
|
+
}
|
|
8713
|
+
// A file uploaded before the API stored oriented boxes has only the record's axis-aligned extents.
|
|
8714
|
+
return GetModelBox(entity, ReadOrientedBox(entity));
|
|
8715
|
+
}
|
|
8716
|
+
/*
|
|
8717
|
+
* Eases the stem between the ring and the model box, returning how far onto the box it is this frame.
|
|
8718
|
+
*/
|
|
8719
|
+
morphToBox(boxed, duration) {
|
|
8720
|
+
const morph = this.boxMorph;
|
|
8721
|
+
const now = Date.now();
|
|
8722
|
+
const ease = (progress) => progress * progress * (3 - 2 * progress);
|
|
8723
|
+
const progress = (start) => duration > 0 ? Math.min(1, Math.max(0, (now - start) / duration)) : 1;
|
|
8724
|
+
const hadBox = morph.target === 1;
|
|
8725
|
+
const target = boxed ? 1 : 0;
|
|
8726
|
+
if (morph.target !== target) {
|
|
8727
|
+
Object.assign(morph, { from: morph.value, target, start: now });
|
|
8728
|
+
}
|
|
8729
|
+
morph.value = morph.from + (morph.target - morph.from) * ease(progress(morph.start));
|
|
8730
|
+
// Settled, so a later frame with a longer duration does not replay the ease.
|
|
8731
|
+
if (morph.value === morph.target) {
|
|
8732
|
+
morph.from = morph.target;
|
|
8733
|
+
}
|
|
8734
|
+
if (boxed) {
|
|
8735
|
+
// Moving to another outline, such as a hovered part pulled out of its group, eases the stem across.
|
|
8736
|
+
if (hadBox && morph.anchor && morph.group !== boxed.group) {
|
|
8737
|
+
morph.shift = { x: morph.anchor.x - boxed.anchor.x, y: morph.anchor.y - boxed.anchor.y, start: now };
|
|
8738
|
+
}
|
|
8739
|
+
morph.group = boxed.group;
|
|
8740
|
+
const remaining = 1 - ease(progress(morph.shift.start));
|
|
8741
|
+
morph.anchor = { x: boxed.anchor.x + morph.shift.x * remaining, y: boxed.anchor.y + morph.shift.y * remaining };
|
|
8742
|
+
}
|
|
8743
|
+
if (morph.value !== morph.target || (boxed && progress(morph.shift.start) < 1)) {
|
|
8744
|
+
this.viewer.scene.requestRender();
|
|
8745
|
+
}
|
|
8746
|
+
return morph.value;
|
|
8747
|
+
}
|
|
7830
8748
|
/**
|
|
7831
8749
|
* Removes label from the viewer and unsubscribes from events.
|
|
7832
8750
|
*/
|
|
@@ -12872,7 +13790,9 @@
|
|
|
12872
13790
|
entityId: params.entityId,
|
|
12873
13791
|
menuItemId: params.menuItemId,
|
|
12874
13792
|
position: params.position ? { ...params.position } : undefined,
|
|
12875
|
-
createIfMissing: params.createIfMissing !== false
|
|
13793
|
+
createIfMissing: params.createIfMissing !== false,
|
|
13794
|
+
text: params.text,
|
|
13795
|
+
pos3d: params.pos3d ? Cesium.Cartesian3.clone(params.pos3d) : undefined
|
|
12876
13796
|
};
|
|
12877
13797
|
const doc = this.viewer.container.ownerDocument;
|
|
12878
13798
|
// Only a label following the cursor should go when the cursor leaves the canvas.
|
|
@@ -12888,12 +13808,26 @@
|
|
|
12888
13808
|
this.SyncLabelHover();
|
|
12889
13809
|
this.viewer.scene.requestRender();
|
|
12890
13810
|
}
|
|
13811
|
+
/*
|
|
13812
|
+
* Which Entity holds the label hover, if any, so a caller can leave another's hover alone.
|
|
13813
|
+
*/
|
|
13814
|
+
GetLabelHoverTarget() {
|
|
13815
|
+
const hover = this.labelHover;
|
|
13816
|
+
return hover ? { entityId: hover.entityId, menuItemId: hover.menuItemId } : undefined;
|
|
13817
|
+
}
|
|
13818
|
+
/*
|
|
13819
|
+
* Whether the label hover is on this Entity, with or without a cursor position.
|
|
13820
|
+
*/
|
|
13821
|
+
IsLabelHovered(entityId, menuItemId) {
|
|
13822
|
+
const hover = this.labelHover;
|
|
13823
|
+
return Boolean(entityId) && (hover === null || hover === void 0 ? void 0 : hover.entityId) === entityId && (hover.menuItemId == null || hover.menuItemId === menuItemId);
|
|
13824
|
+
}
|
|
12891
13825
|
GetLabelHoverPosition(entityId, menuItemId) {
|
|
12892
13826
|
const hover = this.labelHover;
|
|
12893
13827
|
return (hover === null || hover === void 0 ? void 0 : hover.entityId) === entityId && (hover.menuItemId == null || hover.menuItemId === menuItemId) ? hover.position : undefined;
|
|
12894
13828
|
}
|
|
12895
13829
|
SyncLabelHover() {
|
|
12896
|
-
var _a, _b, _c;
|
|
13830
|
+
var _a, _b, _c, _d;
|
|
12897
13831
|
const hover = this.labelHover;
|
|
12898
13832
|
if (!hover || this.disposed) {
|
|
12899
13833
|
return;
|
|
@@ -12925,12 +13859,24 @@
|
|
|
12925
13859
|
this.ReleaseHoverLabel(true);
|
|
12926
13860
|
return;
|
|
12927
13861
|
}
|
|
13862
|
+
const hoverOptions = {
|
|
13863
|
+
presentation: "text",
|
|
13864
|
+
text: (_b = hover.text) !== null && _b !== void 0 ? _b : group === null || group === void 0 ? void 0 : group.options.text,
|
|
13865
|
+
selectOnClick: false
|
|
13866
|
+
};
|
|
12928
13867
|
if (this.hoverLabel && !this.hoverLabel.Disposed) {
|
|
13868
|
+
// Rebuilds only when the text changed, as it does when the pointer crosses a cell.
|
|
13869
|
+
if (hover.pos3d && !Cesium.Cartesian3.equals(hover.pos3d, this.hoverLabel.pos3d)) {
|
|
13870
|
+
this.hoverLabel.pos3d = hover.pos3d;
|
|
13871
|
+
}
|
|
13872
|
+
if (hover.text != null) {
|
|
13873
|
+
this.hoverLabel.Configure(hoverOptions);
|
|
13874
|
+
}
|
|
12929
13875
|
this.hoverLabel.SetHoverVisible(true);
|
|
12930
13876
|
return;
|
|
12931
13877
|
}
|
|
12932
13878
|
// Query-only icon groups have coordinates but no registered scene visual to attach to.
|
|
12933
|
-
if (!rego && group && [group.item.longitude, group.item.latitude, (
|
|
13879
|
+
if (!rego && group && [group.item.longitude, group.item.latitude, (_c = group.item.altitude) !== null && _c !== void 0 ? _c : 0].every(Number.isFinite)) {
|
|
12934
13880
|
rego = {
|
|
12935
13881
|
entityId: hover.entityId,
|
|
12936
13882
|
menuItemId: hover.menuItemId,
|
|
@@ -12952,15 +13898,12 @@
|
|
|
12952
13898
|
rego,
|
|
12953
13899
|
viewer: this.viewer,
|
|
12954
13900
|
visualRegister: this,
|
|
12955
|
-
api: (
|
|
13901
|
+
api: (_d = this.ApiGetters) === null || _d === void 0 ? void 0 : _d.GetBruceApi(),
|
|
12956
13902
|
persistent: false,
|
|
12957
13903
|
transient: true,
|
|
12958
13904
|
hoverRingState: this.hoverRingState,
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
text: group === null || group === void 0 ? void 0 : group.options.text,
|
|
12962
|
-
selectOnClick: false
|
|
12963
|
-
},
|
|
13905
|
+
pos3d: hover.pos3d,
|
|
13906
|
+
options: hoverOptions,
|
|
12964
13907
|
visible: () => {
|
|
12965
13908
|
var _a;
|
|
12966
13909
|
const current = this.GetState(hover);
|
|
@@ -45784,6 +46727,10 @@
|
|
|
45784
46727
|
TextureFrameSeriesAnimator.AppearanceSignature = AppearanceSignature;
|
|
45785
46728
|
const MAX_COMPOSITE_TEXELS = 2048;
|
|
45786
46729
|
const VALUE_DILATE_PASSES = 2;
|
|
46730
|
+
// Decoded frame bytes kept at once. A 2048 square composite is 16 MB, so a long series cannot all stay.
|
|
46731
|
+
const MAX_CACHED_FRAME_BYTES = 256 * 1024 * 1024;
|
|
46732
|
+
// Fetched bytes kept for decoding again, a small fraction of what they decode to.
|
|
46733
|
+
const MAX_RAW_FRAME_BYTES = 96 * 1024 * 1024;
|
|
45787
46734
|
/*
|
|
45788
46735
|
* Rewrites a packed 16 bit value as the 8 bit grey the colour ramps index on.
|
|
45789
46736
|
*
|
|
@@ -45804,19 +46751,33 @@
|
|
|
45804
46751
|
/*
|
|
45805
46752
|
* Bleeds covered values outward into uncovered texels, leaving alpha untouched.
|
|
45806
46753
|
*/
|
|
45807
|
-
function
|
|
46754
|
+
function DilateValues(value, width, height, passes, packed) {
|
|
45808
46755
|
const texels = width * height;
|
|
45809
|
-
|
|
46756
|
+
// 255 for a texel that had data to begin with, otherwise the 1 based pass that filled it, so a pass
|
|
46757
|
+
// reads only texels filled before it and can work in place.
|
|
46758
|
+
const stamp = new Uint8Array(texels);
|
|
45810
46759
|
for (let p = 0; p < texels; p++) {
|
|
45811
|
-
|
|
46760
|
+
stamp[p] = value[p * 4 + 3] !== 0 ? 255 : 0;
|
|
45812
46761
|
}
|
|
46762
|
+
// Whether a texel or a horizontal neighbour had data, so empty space is skipped in three reads.
|
|
46763
|
+
const across = new Uint8Array(texels);
|
|
45813
46764
|
for (let pass = 0; pass < passes; pass++) {
|
|
45814
|
-
const
|
|
45815
|
-
const
|
|
46765
|
+
const current = pass + 1;
|
|
46766
|
+
const had = (p) => stamp[p] !== 0 && stamp[p] !== current;
|
|
46767
|
+
for (let y = 0; y < height; y++) {
|
|
46768
|
+
const row = y * width;
|
|
46769
|
+
for (let x = 0; x < width; x++) {
|
|
46770
|
+
const p = row + x;
|
|
46771
|
+
across[p] = (had(p) || (x > 0 && had(p - 1)) || (x + 1 < width && had(p + 1))) ? 1 : 0;
|
|
46772
|
+
}
|
|
46773
|
+
}
|
|
45816
46774
|
for (let y = 0; y < height; y++) {
|
|
45817
46775
|
for (let x = 0; x < width; x++) {
|
|
45818
46776
|
const p = y * width + x;
|
|
45819
|
-
if (
|
|
46777
|
+
if (stamp[p] !== 0) {
|
|
46778
|
+
continue;
|
|
46779
|
+
}
|
|
46780
|
+
if (!across[p] && !(y > 0 && across[p - width]) && !(y + 1 < height && across[p + width])) {
|
|
45820
46781
|
continue;
|
|
45821
46782
|
}
|
|
45822
46783
|
let sum = 0;
|
|
@@ -45825,13 +46786,13 @@
|
|
|
45825
46786
|
for (let dx = -1; dx <= 1; dx++) {
|
|
45826
46787
|
const nx = x + dx;
|
|
45827
46788
|
const ny = y + dy;
|
|
45828
|
-
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !
|
|
46789
|
+
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !had(ny * width + nx)) {
|
|
45829
46790
|
continue;
|
|
45830
46791
|
}
|
|
45831
46792
|
const at = (ny * width + nx) * 4;
|
|
45832
46793
|
// Averaged as a value rather than per byte: averaging a packed low byte
|
|
45833
46794
|
// on its own wraps at every 256th step and speckles the bled edge.
|
|
45834
|
-
sum += packed ?
|
|
46795
|
+
sum += packed ? value[at] * 256 + value[at + 1] : value[at];
|
|
45835
46796
|
hits++;
|
|
45836
46797
|
}
|
|
45837
46798
|
}
|
|
@@ -45847,18 +46808,25 @@
|
|
|
45847
46808
|
value[p * 4 + 1] = v;
|
|
45848
46809
|
value[p * 4 + 2] = v;
|
|
45849
46810
|
}
|
|
45850
|
-
|
|
46811
|
+
stamp[p] = current;
|
|
45851
46812
|
}
|
|
45852
46813
|
}
|
|
45853
46814
|
}
|
|
45854
46815
|
}
|
|
45855
46816
|
}
|
|
46817
|
+
TextureFrameSeriesAnimator.DilateValues = DilateValues;
|
|
46818
|
+
/*
|
|
46819
|
+
* Bytes one cached frame holds, counting a buffer shared between colour and value once.
|
|
46820
|
+
*/
|
|
46821
|
+
function frameBytes(pixels, value) {
|
|
46822
|
+
return (pixels ? pixels.byteLength : 0) + (value && value !== pixels ? value.byteLength : 0);
|
|
46823
|
+
}
|
|
45856
46824
|
const DEFAULT_CROSSFADE_MS = 900;
|
|
45857
46825
|
const DEFAULT_LOW_COLOR = { red: 255, green: 255, blue: 255, alpha: 0.12 };
|
|
45858
46826
|
const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
|
|
45859
46827
|
class Animator {
|
|
45860
46828
|
constructor(options) {
|
|
45861
|
-
var _a, _b, _c;
|
|
46829
|
+
var _a, _b, _c, _d;
|
|
45862
46830
|
this.removeOnTick = null;
|
|
45863
46831
|
this.removeCrossfadeTick = null;
|
|
45864
46832
|
this.disposed = false;
|
|
@@ -45889,6 +46857,15 @@
|
|
|
45889
46857
|
this.extremesLoad = null;
|
|
45890
46858
|
this.floorPixels = null;
|
|
45891
46859
|
this.ceilingPixels = null;
|
|
46860
|
+
// How far through the fade from the previous value frame to the presented one, 1 when settled.
|
|
46861
|
+
this.fadeMix = 1;
|
|
46862
|
+
// Where the current fade started from, above zero when it took over from one still running.
|
|
46863
|
+
this.fadeMixStart = 0;
|
|
46864
|
+
// Version the value canvas was last written at, since in value only mode it is written on demand.
|
|
46865
|
+
this.valueCanvasVersion = -1;
|
|
46866
|
+
this.rawBytes = 0;
|
|
46867
|
+
this.cachedBytes = 0;
|
|
46868
|
+
this.lastFrameBytes = 0;
|
|
45892
46869
|
if (!options.entity.polygon) {
|
|
45893
46870
|
throw new Error("TextureFrameSeriesAnimator requires an entity with polygon graphics.");
|
|
45894
46871
|
}
|
|
@@ -45936,6 +46913,9 @@
|
|
|
45936
46913
|
this.maskBaseline = (_c = options.maskBaseline) !== null && _c !== void 0 ? _c : this.tiles.some((tile) => Boolean(tile.BaselineMask));
|
|
45937
46914
|
this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
|
|
45938
46915
|
this.driveMaterial = options.driveMaterial !== false;
|
|
46916
|
+
this.valueOnly = this.produceValueCanvas && !this.driveMaterial;
|
|
46917
|
+
this.rawCache = new Array(this.frames.length).fill(null);
|
|
46918
|
+
this.cacheBudget = (_d = options.cacheBytes) !== null && _d !== void 0 ? _d : MAX_CACHED_FRAME_BYTES;
|
|
45939
46919
|
this.originalMaterial = options.entity.polygon.material;
|
|
45940
46920
|
this.imageProperty = new Cesium.CallbackProperty(() => this.pool[this.poolIdx], false);
|
|
45941
46921
|
if (this.driveMaterial) {
|
|
@@ -46001,7 +46981,40 @@
|
|
|
46001
46981
|
* or null unless the instance was constructed with produceValueCanvas.
|
|
46002
46982
|
*/
|
|
46003
46983
|
GetValueCanvas() {
|
|
46004
|
-
|
|
46984
|
+
if (!this.valueDims) {
|
|
46985
|
+
return null;
|
|
46986
|
+
}
|
|
46987
|
+
if (this.valueOnly && this.valueCanvasVersion !== this.presentVersion && this.displayedValuePixels) {
|
|
46988
|
+
this.writeInto(this.valueCanvas, this.displayedValuePixels, this.valueDims);
|
|
46989
|
+
this.valueCanvasVersion = this.presentVersion;
|
|
46990
|
+
}
|
|
46991
|
+
return this.valueCanvas;
|
|
46992
|
+
}
|
|
46993
|
+
/**
|
|
46994
|
+
* The presented frame's value pixels, or null before one. Shares the cached array, so never write to it.
|
|
46995
|
+
*/
|
|
46996
|
+
GetValueRaster() {
|
|
46997
|
+
if (!this.valueDims || !this.displayedValuePixels) {
|
|
46998
|
+
return null;
|
|
46999
|
+
}
|
|
47000
|
+
return { data: this.displayedValuePixels, width: this.valueDims.width, height: this.valueDims.height };
|
|
47001
|
+
}
|
|
47002
|
+
/**
|
|
47003
|
+
* The two value frames being faded between and the mix, or null unless the material is not driven.
|
|
47004
|
+
*/
|
|
47005
|
+
GetValueFade() {
|
|
47006
|
+
if (!this.valueOnly || !this.valueDims || !this.displayedValuePixels) {
|
|
47007
|
+
return null;
|
|
47008
|
+
}
|
|
47009
|
+
const previous = this.fadeFromValuePixels;
|
|
47010
|
+
const from = previous && previous.length === this.displayedValuePixels.length ? previous : null;
|
|
47011
|
+
return {
|
|
47012
|
+
from,
|
|
47013
|
+
to: this.displayedValuePixels,
|
|
47014
|
+
width: this.valueDims.width,
|
|
47015
|
+
height: this.valueDims.height,
|
|
47016
|
+
mix: from ? this.fadeMix : 1
|
|
47017
|
+
};
|
|
46005
47018
|
}
|
|
46006
47019
|
/**
|
|
46007
47020
|
* Increments every time a frame is presented, so a consumer holding the value canvas can tell
|
|
@@ -46068,7 +47081,11 @@
|
|
|
46068
47081
|
if (this.currentFrameIndex < 0) {
|
|
46069
47082
|
return this.frameCache[0] ? -1 : 0;
|
|
46070
47083
|
}
|
|
46071
|
-
|
|
47084
|
+
// Full once another frame will not fit, so a prefetch only displaces a frame further from the
|
|
47085
|
+
// playhead than itself and never trades the two ends of the window back and forth.
|
|
47086
|
+
const full = this.cachedBytes + this.lastFrameBytes > this.cacheBudget;
|
|
47087
|
+
const reach = full ? this.farthestCachedDistance() : n;
|
|
47088
|
+
for (let d = 0; d < Math.min(n, reach); d++) {
|
|
46072
47089
|
const forward = this.currentFrameIndex + d;
|
|
46073
47090
|
if (forward < n && !this.frameCache[forward]) {
|
|
46074
47091
|
return forward;
|
|
@@ -46080,6 +47097,68 @@
|
|
|
46080
47097
|
}
|
|
46081
47098
|
return -1;
|
|
46082
47099
|
}
|
|
47100
|
+
farthestCachedDistance() {
|
|
47101
|
+
let farthest = 0;
|
|
47102
|
+
for (let i = 0; i < this.frameCache.length; i++) {
|
|
47103
|
+
if (this.frameCache[i]) {
|
|
47104
|
+
farthest = Math.max(farthest, Math.abs(i - this.currentFrameIndex));
|
|
47105
|
+
}
|
|
47106
|
+
}
|
|
47107
|
+
return farthest;
|
|
47108
|
+
}
|
|
47109
|
+
/*
|
|
47110
|
+
* Drops decoded frames furthest from the playhead until the cache fits its budget again.
|
|
47111
|
+
*/
|
|
47112
|
+
evictDecoded(keep) {
|
|
47113
|
+
// A tinted fade reads its target from the cache each tick, where a value fade holds the arrays.
|
|
47114
|
+
const fading = !this.valueOnly && this.fadeStartMs !== null ? this.fadeToIndex : -1;
|
|
47115
|
+
while (this.cachedBytes > this.cacheBudget) {
|
|
47116
|
+
let victim = -1;
|
|
47117
|
+
let distance = -1;
|
|
47118
|
+
for (let i = 0; i < this.frameCache.length; i++) {
|
|
47119
|
+
if (!this.frameCache[i] || i === keep || i === this.currentFrameIndex || i === fading) {
|
|
47120
|
+
continue;
|
|
47121
|
+
}
|
|
47122
|
+
const d = Math.abs(i - this.currentFrameIndex);
|
|
47123
|
+
if (d > distance) {
|
|
47124
|
+
distance = d;
|
|
47125
|
+
victim = i;
|
|
47126
|
+
}
|
|
47127
|
+
}
|
|
47128
|
+
if (victim < 0) {
|
|
47129
|
+
return;
|
|
47130
|
+
}
|
|
47131
|
+
this.cachedBytes -= frameBytes(this.frameCache[victim], this.valueCache[victim]);
|
|
47132
|
+
this.frameCache[victim] = null;
|
|
47133
|
+
this.valueCache[victim] = null;
|
|
47134
|
+
}
|
|
47135
|
+
}
|
|
47136
|
+
/*
|
|
47137
|
+
* Keeps a frame's fetched bytes unless they are large, as a tile major archive's are.
|
|
47138
|
+
*/
|
|
47139
|
+
keepRaw(idx, buffer, base) {
|
|
47140
|
+
if (buffer.byteLength > MAX_RAW_FRAME_BYTES / 16) {
|
|
47141
|
+
return;
|
|
47142
|
+
}
|
|
47143
|
+
this.rawCache[idx] = { buffer, base };
|
|
47144
|
+
this.rawBytes += buffer.byteLength;
|
|
47145
|
+
while (this.rawBytes > MAX_RAW_FRAME_BYTES) {
|
|
47146
|
+
let victim = -1;
|
|
47147
|
+
let distance = -1;
|
|
47148
|
+
for (let i = 0; i < this.rawCache.length; i++) {
|
|
47149
|
+
const d = Math.abs(i - this.currentFrameIndex);
|
|
47150
|
+
if (this.rawCache[i] && i !== idx && d > distance) {
|
|
47151
|
+
distance = d;
|
|
47152
|
+
victim = i;
|
|
47153
|
+
}
|
|
47154
|
+
}
|
|
47155
|
+
if (victim < 0) {
|
|
47156
|
+
return;
|
|
47157
|
+
}
|
|
47158
|
+
this.rawBytes -= this.rawCache[victim].buffer.byteLength;
|
|
47159
|
+
this.rawCache[victim] = null;
|
|
47160
|
+
}
|
|
47161
|
+
}
|
|
46083
47162
|
pumpFetchQueue() {
|
|
46084
47163
|
if (this.disposed || this.inFlightIndex !== -1) {
|
|
46085
47164
|
return;
|
|
@@ -46123,10 +47202,18 @@
|
|
|
46123
47202
|
lo = Math.min(lo, at.ByteOffset);
|
|
46124
47203
|
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
46125
47204
|
}
|
|
46126
|
-
|
|
46127
|
-
const
|
|
46128
|
-
if (
|
|
46129
|
-
|
|
47205
|
+
let buffer;
|
|
47206
|
+
const raw = this.rawCache[idx];
|
|
47207
|
+
if (raw && raw.base === lo) {
|
|
47208
|
+
buffer = raw.buffer;
|
|
47209
|
+
}
|
|
47210
|
+
else {
|
|
47211
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
47212
|
+
buffer = await response.arrayBuffer();
|
|
47213
|
+
if (this.disposed) {
|
|
47214
|
+
return;
|
|
47215
|
+
}
|
|
47216
|
+
this.keepRaw(idx, buffer, lo);
|
|
46130
47217
|
}
|
|
46131
47218
|
// Awaited before decoding rather than applied later, so no frame is ever cached unmasked and
|
|
46132
47219
|
// then corrected once the mask lands.
|
|
@@ -46138,9 +47225,15 @@
|
|
|
46138
47225
|
if (this.disposed) {
|
|
46139
47226
|
return;
|
|
46140
47227
|
}
|
|
47228
|
+
if (this.frameCache[idx]) {
|
|
47229
|
+
this.cachedBytes -= frameBytes(this.frameCache[idx], this.valueCache[idx]);
|
|
47230
|
+
}
|
|
46141
47231
|
this.frameCache[idx] = decoded.pixels;
|
|
46142
47232
|
this.frameDims[idx] = { width: decoded.width, height: decoded.height };
|
|
46143
47233
|
this.valueCache[idx] = decoded.valuePixels || null;
|
|
47234
|
+
this.lastFrameBytes = frameBytes(decoded.pixels, decoded.valuePixels || null);
|
|
47235
|
+
this.cachedBytes += this.lastFrameBytes;
|
|
47236
|
+
this.evictDecoded(idx);
|
|
46144
47237
|
if (this.shouldPresent(idx, isTarget)) {
|
|
46145
47238
|
this.beginCrossfadeTo(idx);
|
|
46146
47239
|
}
|
|
@@ -46210,6 +47303,13 @@
|
|
|
46210
47303
|
*/
|
|
46211
47304
|
tint(imageData) {
|
|
46212
47305
|
const canvas = { width: imageData.width, height: imageData.height };
|
|
47306
|
+
if (this.valueOnly) {
|
|
47307
|
+
// One array serves as both, since nothing will ever display the colour.
|
|
47308
|
+
const values = imageData.data;
|
|
47309
|
+
this.applyBaselineMask(values, undefined, canvas.width, canvas.height);
|
|
47310
|
+
DilateValues(values, canvas.width, canvas.height, VALUE_DILATE_PASSES, IsPackedValue(this.metadata));
|
|
47311
|
+
return { pixels: values, width: canvas.width, height: canvas.height, valuePixels: values };
|
|
47312
|
+
}
|
|
46213
47313
|
// Copied before tinting: the ramp is a lerp between two colours, so the value cannot be
|
|
46214
47314
|
// recovered from the tinted pixels afterwards.
|
|
46215
47315
|
const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
|
|
@@ -46230,7 +47330,7 @@
|
|
|
46230
47330
|
}
|
|
46231
47331
|
this.applyBaselineMask(imageData.data, valuePixels, canvas.width, canvas.height);
|
|
46232
47332
|
if (valuePixels) {
|
|
46233
|
-
|
|
47333
|
+
DilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES, IsPackedValue(this.metadata));
|
|
46234
47334
|
}
|
|
46235
47335
|
return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
|
|
46236
47336
|
}
|
|
@@ -46488,6 +47588,10 @@
|
|
|
46488
47588
|
}
|
|
46489
47589
|
}
|
|
46490
47590
|
beginCrossfadeTo(idx) {
|
|
47591
|
+
if (this.valueOnly) {
|
|
47592
|
+
this.beginValueFadeTo(idx);
|
|
47593
|
+
return;
|
|
47594
|
+
}
|
|
46491
47595
|
const to = this.frameCache[idx];
|
|
46492
47596
|
this.fadeFromValuePixels = this.displayedValuePixels === null && this.valueCache[idx]
|
|
46493
47597
|
? new Uint8ClampedArray(this.valueCache[idx].length)
|
|
@@ -46497,12 +47601,53 @@
|
|
|
46497
47601
|
this.fadeToDims = this.frameDims[idx];
|
|
46498
47602
|
this.fadeStartMs = Now();
|
|
46499
47603
|
}
|
|
47604
|
+
/*
|
|
47605
|
+
* Presents a value frame at once and leaves the fade to the consumer, which blends on the GPU.
|
|
47606
|
+
*/
|
|
47607
|
+
beginValueFadeTo(idx) {
|
|
47608
|
+
const to = this.valueCache[idx];
|
|
47609
|
+
if (!to || to === this.displayedValuePixels) {
|
|
47610
|
+
return;
|
|
47611
|
+
}
|
|
47612
|
+
// A new target mid fade keeps whichever frame is mostly on screen and carries on from the current
|
|
47613
|
+
// mix, so frames landing in quick succession do not jump the sheet back.
|
|
47614
|
+
const midFade = this.fadeStartMs !== null && Boolean(this.fadeFromValuePixels);
|
|
47615
|
+
if (midFade && this.fadeMix < 0.5) {
|
|
47616
|
+
this.fadeMixStart = this.fadeMix;
|
|
47617
|
+
}
|
|
47618
|
+
else {
|
|
47619
|
+
this.fadeFromValuePixels = this.displayedValuePixels;
|
|
47620
|
+
this.fadeMixStart = 0;
|
|
47621
|
+
}
|
|
47622
|
+
this.displayedValuePixels = to;
|
|
47623
|
+
this.valueDims = this.frameDims[idx];
|
|
47624
|
+
this.fadeToIndex = idx;
|
|
47625
|
+
this.fadeMix = this.fadeFromValuePixels ? this.fadeMixStart : 1;
|
|
47626
|
+
this.fadeStartMs = this.fadeFromValuePixels ? Now() : null;
|
|
47627
|
+
this.presentVersion++;
|
|
47628
|
+
this.requestRender();
|
|
47629
|
+
}
|
|
47630
|
+
requestRender() {
|
|
47631
|
+
if (this.viewer && this.viewer.scene && this.viewer.scene.requestRenderMode) {
|
|
47632
|
+
this.viewer.scene.requestRender();
|
|
47633
|
+
}
|
|
47634
|
+
}
|
|
46500
47635
|
tickCrossfade() {
|
|
46501
47636
|
if (this.disposed || this.fadeStartMs === null) {
|
|
46502
47637
|
return;
|
|
46503
47638
|
}
|
|
46504
47639
|
const t = Math.min(1, (Now() - this.fadeStartMs) / this.crossfadeMs);
|
|
46505
47640
|
const eased = easeInOutQuad(t);
|
|
47641
|
+
if (this.valueOnly) {
|
|
47642
|
+
this.fadeMix = this.fadeMixStart + (1 - this.fadeMixStart) * eased;
|
|
47643
|
+
if (t >= 1) {
|
|
47644
|
+
this.fadeStartMs = null;
|
|
47645
|
+
this.fadeFromValuePixels = null;
|
|
47646
|
+
this.fadeMix = 1;
|
|
47647
|
+
}
|
|
47648
|
+
this.requestRender();
|
|
47649
|
+
return;
|
|
47650
|
+
}
|
|
46506
47651
|
const from = this.fadeFromPixels;
|
|
46507
47652
|
const to = this.frameCache[this.fadeToIndex];
|
|
46508
47653
|
const n = Math.min(from.length, to.length);
|
|
@@ -46542,9 +47687,7 @@
|
|
|
46542
47687
|
this.valueDims = dims;
|
|
46543
47688
|
}
|
|
46544
47689
|
this.presentVersion++;
|
|
46545
|
-
|
|
46546
|
-
this.viewer.scene.requestRender();
|
|
46547
|
-
}
|
|
47690
|
+
this.requestRender();
|
|
46548
47691
|
}
|
|
46549
47692
|
}
|
|
46550
47693
|
TextureFrameSeriesAnimator.Animator = Animator;
|
|
@@ -46563,13 +47706,15 @@
|
|
|
46563
47706
|
const MIN_VISIBLE_PIXELS = 60;
|
|
46564
47707
|
const MAX_STRIDE = 512;
|
|
46565
47708
|
const STRIDE_HYSTERESIS = 1.4;
|
|
46566
|
-
|
|
46567
|
-
const
|
|
46568
|
-
const
|
|
46569
|
-
|
|
46570
|
-
|
|
46571
|
-
|
|
46572
|
-
const
|
|
47709
|
+
// Scene labels carry a leader and are wider than a value, so they want room and not many of them.
|
|
47710
|
+
const DEFAULT_MIN_SPACING_PIXELS = 140;
|
|
47711
|
+
const DEFAULT_MAX_LABELS = 60;
|
|
47712
|
+
// How far past the edge of the view an anchor may sit before its label is dropped.
|
|
47713
|
+
const LABEL_EDGE_MARGIN_PIXELS = 120;
|
|
47714
|
+
// Distance discount for a label already up when the cap forces a choice.
|
|
47715
|
+
const LABEL_KEEP_BIAS = 0.6;
|
|
47716
|
+
// How long a label outlives its cell leaving the wanted set, so one back a frame later does not blink.
|
|
47717
|
+
const LABEL_LINGER_MS = 300;
|
|
46573
47718
|
// 8 rather than 5: below this a cell reads as texture rather than as a boundary, and it also
|
|
46574
47719
|
// bounds the worst case, since a full screen of 8 px cells fits inside MAX_GRID_CELLS.
|
|
46575
47720
|
const DEFAULT_MIN_GRID_CELL_PIXELS = 8;
|
|
@@ -46583,7 +47728,8 @@
|
|
|
46583
47728
|
const TILE_OUTLINE_ALPHA = 0.45;
|
|
46584
47729
|
// Cells per grid square at the coarsest. Beyond this a tile is outlined instead.
|
|
46585
47730
|
const MAX_GRID_STRIDE = 256;
|
|
46586
|
-
|
|
47731
|
+
// Times the pointer's ground hit is moved onto the sheet's own height before reading a cell.
|
|
47732
|
+
const HOVER_REFINE_STEPS = 3;
|
|
46587
47733
|
/**
|
|
46588
47734
|
* A clip outline from a projected ring, or null when the ring cannot be drawn.
|
|
46589
47735
|
* @param projected one entry per ring vertex, null where the vertex has no window coordinate
|
|
@@ -46677,11 +47823,25 @@
|
|
|
46677
47823
|
this.sourceMap = null;
|
|
46678
47824
|
this.source = null;
|
|
46679
47825
|
this.painted = null;
|
|
47826
|
+
this.paintedRamp = null;
|
|
46680
47827
|
this.floor = null;
|
|
46681
47828
|
this.ceiling = null;
|
|
46682
47829
|
this.disposed = false;
|
|
46683
|
-
this.drawn = 0;
|
|
46684
47830
|
this.stride = 0;
|
|
47831
|
+
this.cellLabels = new Map();
|
|
47832
|
+
this.lingerTimer = null;
|
|
47833
|
+
// What the last draw was made from, so a frame that changes none of it draws nothing.
|
|
47834
|
+
this.drawnView = null;
|
|
47835
|
+
this.drawnInputs = [];
|
|
47836
|
+
this.scratchWorld = new Cesium.Cartesian3();
|
|
47837
|
+
this.scratchWindow = new Cesium.Cartesian2();
|
|
47838
|
+
this.scratchClip = new Cesium.Cartesian4();
|
|
47839
|
+
// The camera's view and projection as one matrix, rebuilt per draw, or null outside 3D.
|
|
47840
|
+
this.viewProjection = null;
|
|
47841
|
+
this.viewProjectionScratch = new Cesium.Matrix4();
|
|
47842
|
+
this.hovering = false;
|
|
47843
|
+
this.hoverFrame = 0;
|
|
47844
|
+
this.hoverAt = null;
|
|
46685
47845
|
this.viewer = options.viewer;
|
|
46686
47846
|
this.extent = options.extent;
|
|
46687
47847
|
this.settings = options.settings || {};
|
|
@@ -46691,6 +47851,9 @@
|
|
|
46691
47851
|
this.sourceMap = exports.TextureFrameSeriesAnimator.SourceMap(options.metadata);
|
|
46692
47852
|
this.tiles = options.tiles;
|
|
46693
47853
|
this.clipRing = options.clipRing;
|
|
47854
|
+
this.heightAt = options.heightAt;
|
|
47855
|
+
this.heightKey = options.heightKey;
|
|
47856
|
+
this.hover = options.hover;
|
|
46694
47857
|
this.canvas = document.createElement("canvas");
|
|
46695
47858
|
this.canvas.style.position = "absolute";
|
|
46696
47859
|
this.canvas.style.inset = "0";
|
|
@@ -46700,12 +47863,31 @@
|
|
|
46700
47863
|
parent.appendChild(this.canvas);
|
|
46701
47864
|
}
|
|
46702
47865
|
this.ctx = this.canvas.getContext("2d");
|
|
47866
|
+
this.onPointerMove = (event) => {
|
|
47867
|
+
const bounds = this.viewer.canvas.getBoundingClientRect();
|
|
47868
|
+
this.hoverAt = { x: event.clientX - bounds.left, y: event.clientY - bounds.top };
|
|
47869
|
+
// At most once a frame, since a pointer reports far faster than anything is drawn.
|
|
47870
|
+
if (!this.hoverFrame) {
|
|
47871
|
+
this.hoverFrame = requestAnimationFrame(() => {
|
|
47872
|
+
this.hoverFrame = 0;
|
|
47873
|
+
this.updateHover();
|
|
47874
|
+
});
|
|
47875
|
+
}
|
|
47876
|
+
};
|
|
47877
|
+
this.onPointerLeave = () => {
|
|
47878
|
+
this.hoverAt = null;
|
|
47879
|
+
this.releaseHover();
|
|
47880
|
+
};
|
|
47881
|
+
if (this.hover && this.hover.register) {
|
|
47882
|
+
this.viewer.canvas.addEventListener("pointermove", this.onPointerMove, { passive: true });
|
|
47883
|
+
this.viewer.canvas.addEventListener("pointerleave", this.onPointerLeave);
|
|
47884
|
+
}
|
|
46703
47885
|
}
|
|
46704
47886
|
SetExtent(extent) {
|
|
46705
47887
|
this.extent = extent;
|
|
46706
47888
|
}
|
|
46707
47889
|
/**
|
|
46708
|
-
* The current frame's decoded values
|
|
47890
|
+
* The current frame's decoded values.
|
|
46709
47891
|
* @param source value pixels, one texel per cell
|
|
46710
47892
|
*/
|
|
46711
47893
|
SetSource(source) {
|
|
@@ -46713,11 +47895,17 @@
|
|
|
46713
47895
|
}
|
|
46714
47896
|
/**
|
|
46715
47897
|
* The tinted pixels of the same frame, so the grid can skip cells the ramp painted nothing in.
|
|
46716
|
-
* @param painted RGBA of the presented frame, or null to fall back to coverage alone
|
|
47898
|
+
* @param painted RGBA of the presented frame, or null to fall back to the ramp or coverage alone
|
|
46717
47899
|
*/
|
|
46718
47900
|
SetPainted(painted) {
|
|
46719
47901
|
this.painted = painted;
|
|
46720
47902
|
}
|
|
47903
|
+
/**
|
|
47904
|
+
* The surface's 256 step RGBA ramp, for telling whether a cell was painted without a tinted frame.
|
|
47905
|
+
*/
|
|
47906
|
+
SetPaintedRamp(ramp) {
|
|
47907
|
+
this.paintedRamp = ramp;
|
|
47908
|
+
}
|
|
46721
47909
|
/**
|
|
46722
47910
|
* The archive's per-cell extremes, when it publishes them. Without these a label can still
|
|
46723
47911
|
* print the current reading, just not the range behind it.
|
|
@@ -46727,21 +47915,28 @@
|
|
|
46727
47915
|
this.ceiling = ceiling;
|
|
46728
47916
|
}
|
|
46729
47917
|
GetDrawnCount() {
|
|
46730
|
-
return this.
|
|
47918
|
+
return this.cellLabels.size;
|
|
46731
47919
|
}
|
|
46732
47920
|
Clear() {
|
|
46733
47921
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
46734
|
-
this.
|
|
47922
|
+
this.drawnView = null;
|
|
46735
47923
|
}
|
|
46736
47924
|
Dispose() {
|
|
46737
47925
|
this.disposed = true;
|
|
47926
|
+
this.clearCellLabels();
|
|
47927
|
+
this.releaseHover();
|
|
47928
|
+
if (this.hoverFrame) {
|
|
47929
|
+
cancelAnimationFrame(this.hoverFrame);
|
|
47930
|
+
this.hoverFrame = 0;
|
|
47931
|
+
}
|
|
47932
|
+
this.viewer.canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
47933
|
+
this.viewer.canvas.removeEventListener("pointerleave", this.onPointerLeave);
|
|
46738
47934
|
if (this.canvas.parentElement) {
|
|
46739
47935
|
this.canvas.parentElement.removeChild(this.canvas);
|
|
46740
47936
|
}
|
|
46741
47937
|
}
|
|
46742
47938
|
/**
|
|
46743
|
-
* Redraws
|
|
46744
|
-
* count it considers is bounded by the view rather than by the texture.
|
|
47939
|
+
* Redraws the grid and syncs the value labels, when anything they are drawn from has changed.
|
|
46745
47940
|
*/
|
|
46746
47941
|
Render() {
|
|
46747
47942
|
var _a, _b, _c;
|
|
@@ -46749,67 +47944,296 @@
|
|
|
46749
47944
|
return;
|
|
46750
47945
|
}
|
|
46751
47946
|
const view = this.viewer.canvas;
|
|
47947
|
+
const inputs = [this.source, this.painted, this.paintedRamp, this.floor, this.ceiling,
|
|
47948
|
+
view.clientWidth, view.clientHeight, this.heightKey ? this.heightKey() : null];
|
|
47949
|
+
const camera = this.viewer.camera.viewMatrix;
|
|
47950
|
+
if (this.drawnView &&
|
|
47951
|
+
Cesium.Matrix4.equals(this.drawnView, camera) &&
|
|
47952
|
+
inputs.every((input, i) => input === this.drawnInputs[i])) {
|
|
47953
|
+
return;
|
|
47954
|
+
}
|
|
46752
47955
|
if (this.canvas.width !== view.clientWidth || this.canvas.height !== view.clientHeight) {
|
|
46753
47956
|
this.canvas.width = view.clientWidth;
|
|
46754
47957
|
this.canvas.height = view.clientHeight;
|
|
46755
47958
|
}
|
|
46756
47959
|
this.Clear();
|
|
47960
|
+
this.drawnView = Cesium.Matrix4.clone(camera, this.drawnView || undefined);
|
|
47961
|
+
this.drawnInputs = inputs;
|
|
47962
|
+
const projection = this.viewer.camera.frustum.projectionMatrix;
|
|
47963
|
+
this.viewProjection = this.viewer.scene.mode === Cesium.SceneMode.SCENE3D
|
|
47964
|
+
? Cesium.Matrix4.multiply(projection, camera, this.viewProjectionScratch)
|
|
47965
|
+
: null;
|
|
46757
47966
|
const cols = this.source.width;
|
|
46758
47967
|
const rows = this.source.height;
|
|
46759
47968
|
const window = this.visibleTexels(cols, rows);
|
|
46760
47969
|
if (!window) {
|
|
47970
|
+
this.syncCellLabels([]);
|
|
46761
47971
|
return;
|
|
46762
47972
|
}
|
|
46763
|
-
|
|
46764
|
-
|
|
46765
|
-
const
|
|
46766
|
-
const
|
|
47973
|
+
// Measured where the view is looking, since in an oblique view up close the middle of the visible
|
|
47974
|
+
// texels sits out toward the horizon, where cells are a fraction of the size.
|
|
47975
|
+
const focus = this.groundAtScreenCentre();
|
|
47976
|
+
const at = focus ? this.texelAt(focus.lon, focus.lat) : null;
|
|
47977
|
+
const focused = (Boolean(at) &&
|
|
47978
|
+
at.col >= window.col0 &&
|
|
47979
|
+
at.col <= window.col1 &&
|
|
47980
|
+
at.row >= window.row0 &&
|
|
47981
|
+
at.row <= window.row1);
|
|
47982
|
+
const midCol = focused ? at.col : (window.col0 + window.col1) / 2;
|
|
47983
|
+
const midRow = focused ? at.row : (window.row0 + window.row1) / 2;
|
|
47984
|
+
const a = this.projectCell(midCol, midRow, cols, rows);
|
|
47985
|
+
const b = this.projectCell(midCol + 1, midRow + 1, cols, rows);
|
|
46767
47986
|
if (!a || !b) {
|
|
47987
|
+
// Left as they are, since one frame where the focus will not project is not the data leaving.
|
|
46768
47988
|
return;
|
|
46769
47989
|
}
|
|
46770
47990
|
const cellPixels = Math.min(Math.abs(b.x - a.x), Math.abs(b.y - a.y));
|
|
46771
47991
|
// Ahead of the label threshold on purpose: a grid still reads at a cell size where a
|
|
46772
47992
|
// number no longer fits.
|
|
46773
47993
|
this.drawGrid(window, cols, rows, cellPixels);
|
|
46774
|
-
|
|
46775
|
-
|
|
46776
|
-
|
|
46777
|
-
const
|
|
46778
|
-
|
|
46779
|
-
|
|
47994
|
+
// By area rather than the shorter side, which an oblique view foreshortens to a fraction of the other.
|
|
47995
|
+
const across = this.projectCell(midCol + 1, midRow, cols, rows);
|
|
47996
|
+
const down = this.projectCell(midCol, midRow + 1, cols, rows);
|
|
47997
|
+
const cellSize = across && down
|
|
47998
|
+
? Math.sqrt(Math.abs((across.x - a.x) * (down.y - a.y) - (across.y - a.y) * (down.x - a.x)))
|
|
47999
|
+
: cellPixels;
|
|
48000
|
+
const visibleWidth = cellSize * (window.col1 - window.col0 + 1);
|
|
48001
|
+
const visibleHeight = cellSize * (window.row1 - window.row0 + 1);
|
|
48002
|
+
if (cellSize < ((_a = this.settings.minCellPixels) !== null && _a !== void 0 ? _a : DEFAULT_MIN_CELL_PIXELS) ||
|
|
48003
|
+
Math.max(visibleWidth, visibleHeight) < MIN_VISIBLE_PIXELS) {
|
|
48004
|
+
this.syncCellLabels([]);
|
|
46780
48005
|
return;
|
|
46781
48006
|
}
|
|
46782
|
-
const parts = this.settings.parts || ["value", "floor", "ceiling"];
|
|
46783
|
-
this.ctx.font = LABEL_FONT;
|
|
46784
|
-
this.ctx.textAlign = "center";
|
|
46785
48007
|
const maxLabels = (_b = this.settings.maxLabels) !== null && _b !== void 0 ? _b : DEFAULT_MAX_LABELS;
|
|
46786
48008
|
this.stride = ChooseLabelStride({
|
|
46787
|
-
cellPixels,
|
|
48009
|
+
cellPixels: cellSize,
|
|
46788
48010
|
visibleCols: window.col1 - window.col0 + 1,
|
|
46789
48011
|
visibleRows: window.row1 - window.row0 + 1,
|
|
46790
48012
|
maxLabels,
|
|
46791
|
-
|
|
46792
|
-
// one is, so changing what a label says cannot quietly start overlapping them.
|
|
46793
|
-
minSpacingPixels: Math.max((_c = this.settings.minSpacingPixels) !== null && _c !== void 0 ? _c : DEFAULT_MIN_SPACING_PIXELS, this.widestLabel(parts)),
|
|
48013
|
+
minSpacingPixels: (_c = this.settings.minSpacingPixels) !== null && _c !== void 0 ? _c : DEFAULT_MIN_SPACING_PIXELS,
|
|
46794
48014
|
held: this.stride
|
|
46795
48015
|
});
|
|
46796
|
-
const
|
|
48016
|
+
const parts = this.settings.parts || ["value", "floor", "ceiling"];
|
|
48017
|
+
this.syncCellLabels(this.labelCells(window, cols, rows, this.stride, maxLabels, parts));
|
|
48018
|
+
}
|
|
48019
|
+
/*
|
|
48020
|
+
* One labelled cell per lattice block, keyed by the block's origin so it survives frame and stride changes.
|
|
48021
|
+
*/
|
|
48022
|
+
labelCells(window, cols, rows, stride, max, parts) {
|
|
48023
|
+
const width = this.canvas.width;
|
|
48024
|
+
const height = this.canvas.height;
|
|
46797
48025
|
const firstRow = Math.floor(window.row0 / stride) * stride;
|
|
46798
48026
|
const firstCol = Math.floor(window.col0 / stride) * stride;
|
|
46799
|
-
|
|
46800
|
-
|
|
48027
|
+
const candidates = [];
|
|
48028
|
+
for (let row = firstRow; row <= window.row1; row += stride) {
|
|
48029
|
+
for (let col = firstCol; col <= window.col1; col += stride) {
|
|
46801
48030
|
const found = this.readNear(col, row, cols, rows, stride);
|
|
46802
48031
|
if (!found) {
|
|
46803
48032
|
continue;
|
|
46804
48033
|
}
|
|
46805
|
-
const point = this.
|
|
46806
|
-
|
|
46807
|
-
|
|
48034
|
+
const point = this.projectCell(found.col, found.row, cols, rows);
|
|
48035
|
+
// A margin past the edge, so a label does not drop the moment its anchor does.
|
|
48036
|
+
const margin = LABEL_EDGE_MARGIN_PIXELS;
|
|
48037
|
+
if (!point ||
|
|
48038
|
+
point.x < -margin ||
|
|
48039
|
+
point.y < -margin ||
|
|
48040
|
+
point.x > width + margin ||
|
|
48041
|
+
point.y > height + margin) {
|
|
46808
48042
|
continue;
|
|
46809
48043
|
}
|
|
46810
|
-
this.
|
|
48044
|
+
const text = this.labelLines(found.reading, parts).join("\n");
|
|
48045
|
+
if (!text) {
|
|
48046
|
+
continue;
|
|
48047
|
+
}
|
|
48048
|
+
const key = col + "," + row;
|
|
48049
|
+
const distance = Math.hypot(point.x - width / 2, point.y - height / 2);
|
|
48050
|
+
candidates.push({
|
|
48051
|
+
key,
|
|
48052
|
+
lon: this.lonAt(found.col, cols),
|
|
48053
|
+
lat: this.latAt(found.row, rows),
|
|
48054
|
+
text,
|
|
48055
|
+
// Labels already up outrank new ones, so the capped set does not shuffle as the view pans.
|
|
48056
|
+
rank: this.cellLabels.has(key) ? distance * LABEL_KEEP_BIAS : distance
|
|
48057
|
+
});
|
|
48058
|
+
}
|
|
48059
|
+
}
|
|
48060
|
+
if (candidates.length > max) {
|
|
48061
|
+
candidates.sort((x, y) => x.rank - y.rank);
|
|
48062
|
+
candidates.length = max;
|
|
48063
|
+
}
|
|
48064
|
+
return candidates;
|
|
48065
|
+
}
|
|
48066
|
+
/*
|
|
48067
|
+
* Keeps one scene label per wanted cell, reusing those that are still wanted.
|
|
48068
|
+
*/
|
|
48069
|
+
syncCellLabels(wanted) {
|
|
48070
|
+
var _a;
|
|
48071
|
+
const keep = new Set(wanted.map((w) => w.key));
|
|
48072
|
+
const now = Date.now();
|
|
48073
|
+
let lingering = false;
|
|
48074
|
+
for (const [key, entry] of this.cellLabels) {
|
|
48075
|
+
if (keep.has(key)) {
|
|
48076
|
+
entry.missingSince = undefined;
|
|
48077
|
+
continue;
|
|
48078
|
+
}
|
|
48079
|
+
entry.missingSince = (_a = entry.missingSince) !== null && _a !== void 0 ? _a : now;
|
|
48080
|
+
if (now - entry.missingSince >= LABEL_LINGER_MS) {
|
|
48081
|
+
entry.label.Dispose();
|
|
48082
|
+
this.cellLabels.delete(key);
|
|
48083
|
+
}
|
|
48084
|
+
else {
|
|
48085
|
+
lingering = true;
|
|
48086
|
+
}
|
|
48087
|
+
}
|
|
48088
|
+
// A still camera draws nothing more, so a lingering label is given its own later look.
|
|
48089
|
+
if (lingering && !this.lingerTimer) {
|
|
48090
|
+
this.lingerTimer = setTimeout(() => {
|
|
48091
|
+
this.lingerTimer = null;
|
|
48092
|
+
if (!this.disposed) {
|
|
48093
|
+
this.drawnView = null;
|
|
48094
|
+
this.viewer.scene.requestRender();
|
|
48095
|
+
}
|
|
48096
|
+
}, LABEL_LINGER_MS);
|
|
48097
|
+
}
|
|
48098
|
+
for (const want of wanted) {
|
|
48099
|
+
const pos3d = Cesium.Cartesian3.fromDegrees(want.lon, want.lat, this.surfaceHeight(want.lon, want.lat));
|
|
48100
|
+
const options = {
|
|
48101
|
+
presentation: "text",
|
|
48102
|
+
text: want.text,
|
|
48103
|
+
leader: true,
|
|
48104
|
+
selectOnClick: false
|
|
48105
|
+
};
|
|
48106
|
+
const existing = this.cellLabels.get(want.key);
|
|
48107
|
+
if (existing) {
|
|
48108
|
+
if (!Cesium.Cartesian3.equalsEpsilon(existing.label.pos3d, pos3d, 0, 0.01)) {
|
|
48109
|
+
existing.label.pos3d = pos3d;
|
|
48110
|
+
}
|
|
48111
|
+
if (existing.text !== want.text) {
|
|
48112
|
+
existing.label.Configure(options);
|
|
48113
|
+
existing.text = want.text;
|
|
48114
|
+
}
|
|
48115
|
+
continue;
|
|
48116
|
+
}
|
|
48117
|
+
const id = (this.hover ? this.hover.entityId : "texture-value") + "#cell:" + want.key;
|
|
48118
|
+
const label = new exports.EntityLabel.Label({
|
|
48119
|
+
rego: {
|
|
48120
|
+
entityId: id,
|
|
48121
|
+
menuItemId: this.hover ? this.hover.menuItemId : undefined,
|
|
48122
|
+
name: want.text,
|
|
48123
|
+
priority: 0,
|
|
48124
|
+
// No graphic, since a point or billboard visual pulls a label back to itself on rebuild.
|
|
48125
|
+
visual: new Cesium.Entity({ position: pos3d })
|
|
48126
|
+
},
|
|
48127
|
+
viewer: this.viewer,
|
|
48128
|
+
pos3d,
|
|
48129
|
+
options,
|
|
48130
|
+
persistent: true,
|
|
48131
|
+
visible: () => !this.disposed
|
|
48132
|
+
});
|
|
48133
|
+
this.cellLabels.set(want.key, { label, text: want.text });
|
|
48134
|
+
}
|
|
48135
|
+
}
|
|
48136
|
+
clearCellLabels() {
|
|
48137
|
+
if (this.lingerTimer) {
|
|
48138
|
+
clearTimeout(this.lingerTimer);
|
|
48139
|
+
this.lingerTimer = null;
|
|
48140
|
+
}
|
|
48141
|
+
for (const entry of this.cellLabels.values()) {
|
|
48142
|
+
entry.label.Dispose();
|
|
48143
|
+
}
|
|
48144
|
+
this.cellLabels.clear();
|
|
48145
|
+
}
|
|
48146
|
+
/*
|
|
48147
|
+
* Shows the cell under the pointer through the register's hover label, unless something else holds it.
|
|
48148
|
+
*/
|
|
48149
|
+
updateHover() {
|
|
48150
|
+
const hover = this.hover;
|
|
48151
|
+
if (this.disposed || !hover || !hover.register || !this.source || !this.hoverAt) {
|
|
48152
|
+
return;
|
|
48153
|
+
}
|
|
48154
|
+
const register = hover.register;
|
|
48155
|
+
const target = register.GetLabelHoverTarget();
|
|
48156
|
+
const ours = Boolean(target) && register.IsLabelHovered(hover.entityId, hover.menuItemId);
|
|
48157
|
+
if (target && !ours) {
|
|
48158
|
+
this.hovering = false;
|
|
48159
|
+
return;
|
|
48160
|
+
}
|
|
48161
|
+
const under = this.readingUnder(this.hoverAt.x, this.hoverAt.y);
|
|
48162
|
+
if (!under) {
|
|
48163
|
+
this.releaseHover();
|
|
48164
|
+
return;
|
|
48165
|
+
}
|
|
48166
|
+
register.SetLabelHover({
|
|
48167
|
+
entityId: hover.entityId,
|
|
48168
|
+
menuItemId: hover.menuItemId,
|
|
48169
|
+
position: { x: this.hoverAt.x, y: this.hoverAt.y },
|
|
48170
|
+
text: under.text,
|
|
48171
|
+
pos3d: under.pos3d
|
|
48172
|
+
});
|
|
48173
|
+
this.hovering = true;
|
|
48174
|
+
}
|
|
48175
|
+
releaseHover() {
|
|
48176
|
+
const hover = this.hover;
|
|
48177
|
+
if (!this.hovering || !hover || !hover.register) {
|
|
48178
|
+
return;
|
|
48179
|
+
}
|
|
48180
|
+
this.hovering = false;
|
|
48181
|
+
if (hover.register.IsLabelHovered(hover.entityId, hover.menuItemId)) {
|
|
48182
|
+
hover.register.ClearLabelHover();
|
|
48183
|
+
}
|
|
48184
|
+
}
|
|
48185
|
+
/*
|
|
48186
|
+
* The text for the painted cell under a window position and its centre on the sheet, or null.
|
|
48187
|
+
*/
|
|
48188
|
+
readingUnder(x, y) {
|
|
48189
|
+
const at = this.cellUnder(x, y);
|
|
48190
|
+
if (!at) {
|
|
48191
|
+
return null;
|
|
48192
|
+
}
|
|
48193
|
+
const cols = this.source.width;
|
|
48194
|
+
const found = this.readAt(at.col, at.row, cols);
|
|
48195
|
+
if (!found || !this.paintedAt(at.col, at.row, cols)) {
|
|
48196
|
+
return null;
|
|
48197
|
+
}
|
|
48198
|
+
const text = this.labelLines(found, this.settings.parts || ["value", "floor", "ceiling"]).join("\n");
|
|
48199
|
+
if (!text) {
|
|
48200
|
+
return null;
|
|
48201
|
+
}
|
|
48202
|
+
const lon = this.lonAt(at.col, cols);
|
|
48203
|
+
const lat = this.latAt(at.row, this.source.height);
|
|
48204
|
+
return { text, pos3d: Cesium.Cartesian3.fromDegrees(lon, lat, this.surfaceHeight(lon, lat)) };
|
|
48205
|
+
}
|
|
48206
|
+
/*
|
|
48207
|
+
* The cell under a window position, found on the sheet rather than on the ground beneath it.
|
|
48208
|
+
*/
|
|
48209
|
+
cellUnder(x, y) {
|
|
48210
|
+
const scene = this.viewer.scene;
|
|
48211
|
+
const camera = this.viewer.camera;
|
|
48212
|
+
const screen = new Cesium.Cartesian2(x, y);
|
|
48213
|
+
const ray = camera.getPickRay(screen);
|
|
48214
|
+
if (!ray) {
|
|
48215
|
+
return null;
|
|
48216
|
+
}
|
|
48217
|
+
let hit = (scene.globe && scene.globe.pick(ray, scene)) || camera.pickEllipsoid(screen);
|
|
48218
|
+
if (!hit) {
|
|
48219
|
+
return null;
|
|
48220
|
+
}
|
|
48221
|
+
const radii = Cesium.Ellipsoid.WGS84.radii;
|
|
48222
|
+
for (let i = 0; i < HOVER_REFINE_STEPS && this.heightAt; i++) {
|
|
48223
|
+
const carto = Cesium.Cartographic.fromCartesian(hit);
|
|
48224
|
+
const height = this.heightAt(Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude));
|
|
48225
|
+
if (height == null) {
|
|
48226
|
+
break;
|
|
48227
|
+
}
|
|
48228
|
+
const inflated = new Cesium.Ellipsoid(radii.x + height, radii.y + height, radii.z + height);
|
|
48229
|
+
const interval = Cesium.IntersectionTests.rayEllipsoid(ray, inflated);
|
|
48230
|
+
if (!interval) {
|
|
48231
|
+
break;
|
|
46811
48232
|
}
|
|
48233
|
+
hit = Cesium.Ray.getPoint(ray, interval.start);
|
|
46812
48234
|
}
|
|
48235
|
+
const carto = Cesium.Cartographic.fromCartesian(hit);
|
|
48236
|
+
return this.texelAt(Cesium.Math.toDegrees(carto.longitude), Cesium.Math.toDegrees(carto.latitude));
|
|
46813
48237
|
}
|
|
46814
48238
|
/*
|
|
46815
48239
|
* Outlines each covered cell, from a projected lattice of its corners.
|
|
@@ -46837,44 +48261,25 @@
|
|
|
46837
48261
|
if (wide * high > MAX_GRID_CELLS) {
|
|
46838
48262
|
return;
|
|
46839
48263
|
}
|
|
46840
|
-
const lattice = new
|
|
46841
|
-
|
|
46842
|
-
|
|
46843
|
-
|
|
46844
|
-
|
|
46845
|
-
|
|
46846
|
-
|
|
46847
|
-
}
|
|
46848
|
-
}
|
|
46849
|
-
const path = new Path2D();
|
|
46850
|
-
let drew = false;
|
|
48264
|
+
const lattice = new Lattice(wide + 1, high + 1, (c, r) => {
|
|
48265
|
+
// Corners sit half a texel out from the centres lonAt/latAt return.
|
|
48266
|
+
const lon = this.lonAt(window.col0 + c - 0.5, cols);
|
|
48267
|
+
const lat = this.latAt(window.row0 + r - 0.5, rows);
|
|
48268
|
+
return this.projectInto(lon, lat);
|
|
48269
|
+
});
|
|
48270
|
+
const covered = new Uint8Array(wide * high);
|
|
46851
48271
|
for (let r = 0; r < high; r++) {
|
|
46852
48272
|
for (let c = 0; c < wide; c++) {
|
|
46853
|
-
|
|
46854
|
-
continue;
|
|
46855
|
-
}
|
|
46856
|
-
const tl = lattice[r * (wide + 1) + c];
|
|
46857
|
-
const tr = lattice[r * (wide + 1) + c + 1];
|
|
46858
|
-
const bl = lattice[(r + 1) * (wide + 1) + c];
|
|
46859
|
-
const br = lattice[(r + 1) * (wide + 1) + c + 1];
|
|
46860
|
-
if (!tl || !tr || !bl || !br) {
|
|
46861
|
-
continue;
|
|
46862
|
-
}
|
|
46863
|
-
path.moveTo(tl.x, tl.y);
|
|
46864
|
-
path.lineTo(tr.x, tr.y);
|
|
46865
|
-
path.lineTo(br.x, br.y);
|
|
46866
|
-
path.lineTo(bl.x, bl.y);
|
|
46867
|
-
path.closePath();
|
|
46868
|
-
drew = true;
|
|
48273
|
+
covered[r * wide + c] = this.isCovered(window.col0 + c, window.row0 + r, cols) ? 1 : 0;
|
|
46869
48274
|
}
|
|
46870
48275
|
}
|
|
46871
|
-
|
|
48276
|
+
const path = new Path2D();
|
|
48277
|
+
if (!lattice.Trace(path, covered, wide, high)) {
|
|
46872
48278
|
return;
|
|
46873
48279
|
}
|
|
46874
48280
|
this.ctx.strokeStyle = color;
|
|
46875
48281
|
this.ctx.lineWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
46876
48282
|
this.ctx.stroke(path);
|
|
46877
|
-
+" alpha" + this.ctx.globalAlpha;
|
|
46878
48283
|
}
|
|
46879
48284
|
/*
|
|
46880
48285
|
* Restricts drawing to the polygon, so nothing is ruled over ground it does not cover.
|
|
@@ -46896,33 +48301,11 @@
|
|
|
46896
48301
|
path.closePath();
|
|
46897
48302
|
this.ctx.clip(path);
|
|
46898
48303
|
}
|
|
46899
|
-
/*
|
|
46900
|
-
* The cell of whichever tile covers a point, as screen corners.
|
|
46901
|
-
*/
|
|
46902
|
-
tileCellOutline(lon, lat) {
|
|
46903
|
-
for (const tile of this.tiles) {
|
|
46904
|
-
if (lon < tile.West || lon > tile.East || lat < tile.South || lat > tile.North) {
|
|
46905
|
-
continue;
|
|
46906
|
-
}
|
|
46907
|
-
const c = (Math.min(tile.ResolutionX - 1, Math.max(0, Math.floor((lon - tile.West) / (tile.East - tile.West) * tile.ResolutionX))));
|
|
46908
|
-
const r = (Math.min(tile.ResolutionY - 1, Math.max(0, Math.floor((tile.North - lat) / (tile.North - tile.South) * tile.ResolutionY))));
|
|
46909
|
-
const w0 = tile.West + (c / tile.ResolutionX) * (tile.East - tile.West);
|
|
46910
|
-
const w1 = tile.West + ((c + 1) / tile.ResolutionX) * (tile.East - tile.West);
|
|
46911
|
-
const n0 = tile.North - (r / tile.ResolutionY) * (tile.North - tile.South);
|
|
46912
|
-
const n1 = tile.North - ((r + 1) / tile.ResolutionY) * (tile.North - tile.South);
|
|
46913
|
-
const corners = [
|
|
46914
|
-
this.project(w0, n0), this.project(w1, n0),
|
|
46915
|
-
this.project(w1, n1), this.project(w0, n1)
|
|
46916
|
-
];
|
|
46917
|
-
return corners.every((q) => q) ? corners : null;
|
|
46918
|
-
}
|
|
46919
|
-
return null;
|
|
46920
|
-
}
|
|
46921
48304
|
/*
|
|
46922
48305
|
* Outlines each tile's own cells, so cell size still shows where the mesh is refined.
|
|
46923
48306
|
*/
|
|
46924
48307
|
drawTileGrid(color) {
|
|
46925
|
-
var _a, _b
|
|
48308
|
+
var _a, _b;
|
|
46926
48309
|
const view = this.viewer.camera.computeViewRectangle(Cesium.Ellipsoid.WGS84);
|
|
46927
48310
|
if (!view) {
|
|
46928
48311
|
return;
|
|
@@ -46951,7 +48334,7 @@
|
|
|
46951
48334
|
coarse.lineTo(tr.x, tr.y);
|
|
46952
48335
|
coarse.lineTo(br.x, br.y);
|
|
46953
48336
|
coarse.lineTo(bl.x, bl.y);
|
|
46954
|
-
coarse.
|
|
48337
|
+
coarse.lineTo(tl.x, tl.y);
|
|
46955
48338
|
drewCoarse = true;
|
|
46956
48339
|
};
|
|
46957
48340
|
// Planned for every visible tile BEFORE any is drawn.
|
|
@@ -47012,31 +48395,29 @@
|
|
|
47012
48395
|
// moves, and so each step up is a subset of the one below it.
|
|
47013
48396
|
const firstR = Math.floor(r0 / step) * step;
|
|
47014
48397
|
const firstC = Math.floor(c0 / step) * step;
|
|
47015
|
-
|
|
47016
|
-
|
|
47017
|
-
|
|
47018
|
-
|
|
47019
|
-
|
|
47020
|
-
|
|
47021
|
-
|
|
47022
|
-
|
|
47023
|
-
|
|
47024
|
-
|
|
47025
|
-
|
|
47026
|
-
|
|
47027
|
-
|
|
47028
|
-
|
|
47029
|
-
|
|
47030
|
-
|
|
47031
|
-
|
|
47032
|
-
|
|
47033
|
-
|
|
47034
|
-
|
|
47035
|
-
|
|
47036
|
-
|
|
47037
|
-
|
|
47038
|
-
drew = true;
|
|
47039
|
-
}
|
|
48398
|
+
const wide = Math.ceil((c1 - firstC) / step);
|
|
48399
|
+
const high = Math.ceil((r1 - firstR) / step);
|
|
48400
|
+
const lonOf = (c) => tile.West
|
|
48401
|
+
+ (Math.min(firstC + c * step, tile.ResolutionX) / tile.ResolutionX) * (tile.East - tile.West);
|
|
48402
|
+
const latOf = (r) => tile.North
|
|
48403
|
+
- (Math.min(firstR + r * step, tile.ResolutionY) / tile.ResolutionY) * (tile.North - tile.South);
|
|
48404
|
+
const covered = new Uint8Array(wide * high);
|
|
48405
|
+
let any = false;
|
|
48406
|
+
for (let r = 0; r < high; r++) {
|
|
48407
|
+
for (let c = 0; c < wide; c++) {
|
|
48408
|
+
if (this.coveredAt((lonOf(c) + lonOf(c + 1)) / 2, (latOf(r) + latOf(r + 1)) / 2)) {
|
|
48409
|
+
covered[r * wide + c] = 1;
|
|
48410
|
+
any = true;
|
|
48411
|
+
}
|
|
48412
|
+
}
|
|
48413
|
+
}
|
|
48414
|
+
if (!any) {
|
|
48415
|
+
continue;
|
|
48416
|
+
}
|
|
48417
|
+
// Projected on demand, since a sparse raster leaves most corners belonging to no cell.
|
|
48418
|
+
const lattice = new Lattice(wide + 1, high + 1, (c, r) => this.projectInto(lonOf(c), latOf(r)));
|
|
48419
|
+
if (lattice.Trace(path, covered, wide, high)) {
|
|
48420
|
+
drew = true;
|
|
47040
48421
|
}
|
|
47041
48422
|
}
|
|
47042
48423
|
const gridWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
@@ -47054,7 +48435,7 @@
|
|
|
47054
48435
|
return;
|
|
47055
48436
|
}
|
|
47056
48437
|
this.ctx.strokeStyle = color;
|
|
47057
|
-
this.ctx.lineWidth =
|
|
48438
|
+
this.ctx.lineWidth = gridWidth;
|
|
47058
48439
|
this.ctx.stroke(path);
|
|
47059
48440
|
}
|
|
47060
48441
|
/*
|
|
@@ -47074,7 +48455,7 @@
|
|
|
47074
48455
|
};
|
|
47075
48456
|
}
|
|
47076
48457
|
/*
|
|
47077
|
-
* Whether the composited raster holds data at a lon/lat.
|
|
48458
|
+
* Whether the composited raster holds painted data at a lon/lat.
|
|
47078
48459
|
*/
|
|
47079
48460
|
coveredAt(lon, lat) {
|
|
47080
48461
|
if (!this.source) {
|
|
@@ -47087,60 +48468,20 @@
|
|
|
47087
48468
|
if (col < 0 || row < 0 || col >= cols || row >= rows) {
|
|
47088
48469
|
return false;
|
|
47089
48470
|
}
|
|
47090
|
-
const at = (row * cols + col) * 4 + 3;
|
|
47091
48471
|
// Painted, not merely covered: a dry cell holds a reading the ramp deliberately hides, and
|
|
47092
48472
|
// ruling a grid over it draws detail where the picture shows none.
|
|
47093
|
-
if (this.painted
|
|
47094
|
-
return this.
|
|
48473
|
+
if (this.painted || this.paintedRamp) {
|
|
48474
|
+
return this.paintedAt(col, row, cols);
|
|
47095
48475
|
}
|
|
47096
|
-
return this.source.data[
|
|
48476
|
+
return this.source.data[(row * cols + col) * 4 + 3] >= 128;
|
|
47097
48477
|
}
|
|
47098
48478
|
isCovered(col, row, cols) {
|
|
47099
48479
|
return Boolean(this.source && this.source.data[(row * cols + col) * 4 + 3] >= 128);
|
|
47100
48480
|
}
|
|
47101
48481
|
/*
|
|
47102
|
-
*
|
|
48482
|
+
* What a label says about one reading, a line at a time.
|
|
47103
48483
|
*/
|
|
47104
|
-
|
|
47105
|
-
const sample = [];
|
|
47106
|
-
if (parts.indexOf("value") >= 0) {
|
|
47107
|
-
sample.push(valueLine("-000.00", LabelUnits(this.metadata)));
|
|
47108
|
-
}
|
|
47109
|
-
const range = [];
|
|
47110
|
-
const showsRange = !exports.TextureFrameSeriesAnimator.IsTransformed(this.metadata);
|
|
47111
|
-
if (showsRange && parts.indexOf("floor") >= 0) {
|
|
47112
|
-
range.push("min: -000.00");
|
|
47113
|
-
}
|
|
47114
|
-
if (showsRange && parts.indexOf("ceiling") >= 0) {
|
|
47115
|
-
range.push("max: -000.00");
|
|
47116
|
-
}
|
|
47117
|
-
if (range.length) {
|
|
47118
|
-
sample.push(range.join(" - "));
|
|
47119
|
-
}
|
|
47120
|
-
if (!sample.length) {
|
|
47121
|
-
return 0;
|
|
47122
|
-
}
|
|
47123
|
-
const widest = Math.max(...sample.map((line) => this.ctx.measureText(line).width));
|
|
47124
|
-
return widest + LABEL_GAP_PIXELS;
|
|
47125
|
-
}
|
|
47126
|
-
/*
|
|
47127
|
-
* The four screen corners of one cell, or null if any of them will not project.
|
|
47128
|
-
*/
|
|
47129
|
-
cellOutline(col, row, cols, rows) {
|
|
47130
|
-
// On a pyramid the composited texel is not a cell. Outlining it would draw a second,
|
|
47131
|
-
// uniform lattice over the tile grid, misaligned with it everywhere.
|
|
47132
|
-
if (this.tiles && this.tiles.length) {
|
|
47133
|
-
return this.tileCellOutline(this.lonAt(col, cols), this.latAt(row, rows));
|
|
47134
|
-
}
|
|
47135
|
-
const corners = [
|
|
47136
|
-
this.project(this.lonAt(col - 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
47137
|
-
this.project(this.lonAt(col + 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
47138
|
-
this.project(this.lonAt(col + 0.5, cols), this.latAt(row + 0.5, rows)),
|
|
47139
|
-
this.project(this.lonAt(col - 0.5, cols), this.latAt(row + 0.5, rows))
|
|
47140
|
-
];
|
|
47141
|
-
return corners.every((c) => c) ? corners : null;
|
|
47142
|
-
}
|
|
47143
|
-
drawLabel(point, reading, parts, outline) {
|
|
48484
|
+
labelLines(reading, parts) {
|
|
47144
48485
|
var _a;
|
|
47145
48486
|
const dp = (_a = this.settings.decimals) !== null && _a !== void 0 ? _a : (Math.abs(reading.value) < 10 ? 2 : 1);
|
|
47146
48487
|
const lines = [];
|
|
@@ -47159,32 +48500,7 @@
|
|
|
47159
48500
|
if (range.length) {
|
|
47160
48501
|
lines.push(range.join(" - "));
|
|
47161
48502
|
}
|
|
47162
|
-
|
|
47163
|
-
return;
|
|
47164
|
-
}
|
|
47165
|
-
if (outline) {
|
|
47166
|
-
this.ctx.strokeStyle = LABEL_ANCHOR_COLOR;
|
|
47167
|
-
this.ctx.lineWidth = 1.5;
|
|
47168
|
-
this.ctx.beginPath();
|
|
47169
|
-
this.ctx.moveTo(outline[0].x, outline[0].y);
|
|
47170
|
-
for (let i = 1; i < outline.length; i++) {
|
|
47171
|
-
this.ctx.lineTo(outline[i].x, outline[i].y);
|
|
47172
|
-
}
|
|
47173
|
-
this.ctx.closePath();
|
|
47174
|
-
this.ctx.stroke();
|
|
47175
|
-
}
|
|
47176
|
-
const width = Math.max(...lines.map((l) => this.ctx.measureText(l).width)) + 10;
|
|
47177
|
-
const height = lines.length * LINE_HEIGHT + 6;
|
|
47178
|
-
const top = point.y - height / 2;
|
|
47179
|
-
this.ctx.fillStyle = "rgba(16,16,14,0.72)";
|
|
47180
|
-
this.ctx.beginPath();
|
|
47181
|
-
this.ctx.roundRect(point.x - width / 2, top, width, height, LABEL_RADIUS);
|
|
47182
|
-
this.ctx.fill();
|
|
47183
|
-
lines.forEach((line, i) => {
|
|
47184
|
-
this.ctx.fillStyle = i === 0 ? "#ffffff" : "#a8a89f";
|
|
47185
|
-
this.ctx.fillText(line, point.x, top + LINE_HEIGHT * (i + 1) - 1);
|
|
47186
|
-
});
|
|
47187
|
-
this.drawn++;
|
|
48503
|
+
return lines;
|
|
47188
48504
|
}
|
|
47189
48505
|
/*
|
|
47190
48506
|
* The first cell with a reading at or near a lattice point, searched within its own block.
|
|
@@ -47211,11 +48527,24 @@
|
|
|
47211
48527
|
return null;
|
|
47212
48528
|
}
|
|
47213
48529
|
paintedAt(col, row, cols) {
|
|
47214
|
-
|
|
47215
|
-
|
|
48530
|
+
const at = (row * cols + col) * 4;
|
|
48531
|
+
if (this.painted && this.painted.length === this.source.data.length) {
|
|
48532
|
+
return this.painted[at + 3] > 0;
|
|
47216
48533
|
}
|
|
47217
|
-
|
|
47218
|
-
|
|
48534
|
+
if (this.paintedRamp) {
|
|
48535
|
+
const data = this.source.data;
|
|
48536
|
+
if (data[at + 3] === 0) {
|
|
48537
|
+
return false;
|
|
48538
|
+
}
|
|
48539
|
+
// The 8 bit grey the ramp is indexed on, which is what a tinted frame would have used.
|
|
48540
|
+
const grey = exports.TextureFrameSeriesAnimator.IsPackedValue(this.metadata)
|
|
48541
|
+
? Math.round((data[at] * 256 + data[at + 1]) / 65535 * 255)
|
|
48542
|
+
: data[at];
|
|
48543
|
+
const steps = this.paintedRamp.length / 4;
|
|
48544
|
+
const index = Math.min(steps - 1, Math.round(grey / 255 * (steps - 1)));
|
|
48545
|
+
return this.paintedRamp[index * 4 + 3] > 0;
|
|
48546
|
+
}
|
|
48547
|
+
return true;
|
|
47219
48548
|
}
|
|
47220
48549
|
// Decodes one cell, skipping anything the texture marks as having no data.
|
|
47221
48550
|
readAt(col, row, cols) {
|
|
@@ -47231,6 +48560,16 @@
|
|
|
47231
48560
|
sourceMap: this.sourceMap
|
|
47232
48561
|
});
|
|
47233
48562
|
}
|
|
48563
|
+
/*
|
|
48564
|
+
* The texel holding a lon/lat, or null outside the raster.
|
|
48565
|
+
*/
|
|
48566
|
+
texelAt(lon, lat) {
|
|
48567
|
+
const cols = this.source.width;
|
|
48568
|
+
const rows = this.source.height;
|
|
48569
|
+
const col = Math.floor((lon - this.extent.West) / (this.extent.East - this.extent.West) * cols);
|
|
48570
|
+
const row = Math.floor((this.extent.North - lat) / (this.extent.North - this.extent.South) * rows);
|
|
48571
|
+
return col < 0 || row < 0 || col >= cols || row >= rows ? null : { col, row };
|
|
48572
|
+
}
|
|
47234
48573
|
lonAt(col, cols) {
|
|
47235
48574
|
return this.extent.West + ((col + 0.5) / cols) * (this.extent.East - this.extent.West);
|
|
47236
48575
|
}
|
|
@@ -47259,15 +48598,102 @@
|
|
|
47259
48598
|
row1: Math.min(rows - 1, Math.ceil((this.extent.North - south) / spanLat * rows) + 1)
|
|
47260
48599
|
};
|
|
47261
48600
|
}
|
|
48601
|
+
// Metres the sheet sits at over a point, or the ellipsoid when nothing says otherwise.
|
|
48602
|
+
surfaceHeight(lon, lat) {
|
|
48603
|
+
const height = this.heightAt ? this.heightAt(lon, lat) : null;
|
|
48604
|
+
return height == null || !isFinite(height) ? 0 : height;
|
|
48605
|
+
}
|
|
48606
|
+
projectCell(col, row, cols, rows) {
|
|
48607
|
+
return this.project(this.lonAt(col, cols), this.latAt(row, rows));
|
|
48608
|
+
}
|
|
48609
|
+
// Projected at the sheet's own height, since up close the ellipsoid is metres from where it is drawn.
|
|
47262
48610
|
project(lon, lat) {
|
|
47263
|
-
|
|
48611
|
+
return this.projectInto(lon, lat) ? { x: this.scratchWindow.x, y: this.scratchWindow.y } : null;
|
|
48612
|
+
}
|
|
48613
|
+
/*
|
|
48614
|
+
* Projects into the shared scratch window position, returning whether it landed anywhere.
|
|
48615
|
+
*/
|
|
48616
|
+
projectInto(lon, lat) {
|
|
48617
|
+
const world = Cesium.Cartesian3.fromDegrees(lon, lat, this.surfaceHeight(lon, lat), Cesium.Ellipsoid.WGS84, this.scratchWorld);
|
|
48618
|
+
// One matrix multiply in 3D, where the general path rederives the viewport and frustum per point.
|
|
48619
|
+
if (this.viewProjection) {
|
|
48620
|
+
const clip = Cesium.Matrix4.multiplyByVector(this.viewProjection, Cesium.Cartesian4.fromElements(world.x, world.y, world.z, 1, this.scratchClip), this.scratchClip);
|
|
48621
|
+
if (!(clip.w > 0)) {
|
|
48622
|
+
return null;
|
|
48623
|
+
}
|
|
48624
|
+
const view = this.viewer.canvas;
|
|
48625
|
+
this.scratchWindow.x = (clip.x / clip.w * 0.5 + 0.5) * view.clientWidth;
|
|
48626
|
+
this.scratchWindow.y = (0.5 - clip.y / clip.w * 0.5) * view.clientHeight;
|
|
48627
|
+
return this.scratchWindow;
|
|
48628
|
+
}
|
|
47264
48629
|
const transforms = Cesium.SceneTransforms;
|
|
47265
48630
|
// Renamed in newer Cesium, and this library runs on whatever the host app supplies.
|
|
47266
48631
|
const fn = transforms.worldToWindowCoordinates || transforms.wgs84ToWindowCoordinates;
|
|
47267
|
-
return fn ? fn.call(transforms, this.viewer.scene, world) : null;
|
|
48632
|
+
return fn ? fn.call(transforms, this.viewer.scene, world, this.scratchWindow) || null : null;
|
|
47268
48633
|
}
|
|
47269
48634
|
}
|
|
47270
48635
|
TextureValueLabels.Labels = Labels;
|
|
48636
|
+
/*
|
|
48637
|
+
* A grid of window positions projected at most once each, and the cell edges traced through them.
|
|
48638
|
+
*/
|
|
48639
|
+
class Lattice {
|
|
48640
|
+
constructor(wide, high, projectAt) {
|
|
48641
|
+
this.wide = wide;
|
|
48642
|
+
this.projectAt = projectAt;
|
|
48643
|
+
this.xs = new Float64Array(wide * high);
|
|
48644
|
+
this.ys = new Float64Array(wide * high);
|
|
48645
|
+
this.state = new Uint8Array(wide * high);
|
|
48646
|
+
}
|
|
48647
|
+
at(c, r) {
|
|
48648
|
+
const i = r * this.wide + c;
|
|
48649
|
+
if (this.state[i] === 0) {
|
|
48650
|
+
const p = this.projectAt(c, r);
|
|
48651
|
+
if (p) {
|
|
48652
|
+
this.xs[i] = p.x;
|
|
48653
|
+
this.ys[i] = p.y;
|
|
48654
|
+
this.state[i] = 1;
|
|
48655
|
+
}
|
|
48656
|
+
else {
|
|
48657
|
+
this.state[i] = 2;
|
|
48658
|
+
}
|
|
48659
|
+
}
|
|
48660
|
+
return this.state[i] === 1 ? i : -1;
|
|
48661
|
+
}
|
|
48662
|
+
edge(path, a, b) {
|
|
48663
|
+
if (a < 0 || b < 0) {
|
|
48664
|
+
return false;
|
|
48665
|
+
}
|
|
48666
|
+
path.moveTo(this.xs[a], this.ys[a]);
|
|
48667
|
+
path.lineTo(this.xs[b], this.ys[b]);
|
|
48668
|
+
return true;
|
|
48669
|
+
}
|
|
48670
|
+
/*
|
|
48671
|
+
* Adds each covered cell's edges to a path, a shared edge once, and says whether any were added.
|
|
48672
|
+
*/
|
|
48673
|
+
Trace(path, covered, cellsWide, cellsHigh) {
|
|
48674
|
+
let drew = false;
|
|
48675
|
+
for (let r = 0; r < cellsHigh; r++) {
|
|
48676
|
+
for (let c = 0; c < cellsWide; c++) {
|
|
48677
|
+
if (!covered[r * cellsWide + c]) {
|
|
48678
|
+
continue;
|
|
48679
|
+
}
|
|
48680
|
+
const tl = this.at(c, r);
|
|
48681
|
+
const tr = this.at(c + 1, r);
|
|
48682
|
+
const bl = this.at(c, r + 1);
|
|
48683
|
+
if (!(r > 0 && covered[(r - 1) * cellsWide + c])) {
|
|
48684
|
+
drew = this.edge(path, tl, tr) || drew;
|
|
48685
|
+
}
|
|
48686
|
+
if (!(c > 0 && covered[r * cellsWide + c - 1])) {
|
|
48687
|
+
drew = this.edge(path, tl, bl) || drew;
|
|
48688
|
+
}
|
|
48689
|
+
const br = this.at(c + 1, r + 1);
|
|
48690
|
+
drew = this.edge(path, tr, br) || drew;
|
|
48691
|
+
drew = this.edge(path, bl, br) || drew;
|
|
48692
|
+
}
|
|
48693
|
+
}
|
|
48694
|
+
return drew;
|
|
48695
|
+
}
|
|
48696
|
+
}
|
|
47271
48697
|
/*
|
|
47272
48698
|
* The value line. Units go on it alone: repeating them on the range underneath doubles the widest
|
|
47273
48699
|
* label to say nothing, and the two lines are plainly the same quantity.
|
|
@@ -47363,6 +48789,9 @@ in vec2 st;
|
|
|
47363
48789
|
in float skirt;
|
|
47364
48790
|
|
|
47365
48791
|
uniform sampler2D u_valueTexture;
|
|
48792
|
+
// The frame being faded away from, and how far the fade has got. The same texture at 1 when settled.
|
|
48793
|
+
uniform sampler2D u_valueTextureFrom;
|
|
48794
|
+
uniform float u_valueFade;
|
|
47366
48795
|
uniform sampler2D u_groundTexture;
|
|
47367
48796
|
uniform float u_valuePacked;
|
|
47368
48797
|
uniform float u_valueMin;
|
|
@@ -47483,18 +48912,22 @@ float decodeValue(vec4 texel) {
|
|
|
47483
48912
|
}
|
|
47484
48913
|
|
|
47485
48914
|
void main() {
|
|
47486
|
-
vec4
|
|
48915
|
+
vec4 texelTo = texture(u_valueTexture, st);
|
|
48916
|
+
vec4 texelFrom = texture(u_valueTextureFrom, st);
|
|
47487
48917
|
v_st = st;
|
|
48918
|
+
// Decoded before mixing, since blending a packed pair byte by byte reads as some other value
|
|
48919
|
+
// halfway through the fade.
|
|
48920
|
+
float raw = mix(decodeValue(texelFrom), decodeValue(texelTo), u_valueFade);
|
|
48921
|
+
float alpha = mix(texelFrom.a, texelTo.a, u_valueFade);
|
|
47488
48922
|
|
|
47489
48923
|
// Inside a region the scene knows is water, a texel with no value still has to paint, or the
|
|
47490
48924
|
// hole the region exists to close is still a hole.
|
|
47491
48925
|
// The value it paints is the shallowest the ramp is not transparent at.
|
|
47492
48926
|
float inMask = u_maskWeight * texture(u_maskTexture, st).a;
|
|
47493
|
-
float hasData = step(u_coverageCutoffVertex,
|
|
47494
|
-
float raw = decodeValue(texel);
|
|
48927
|
+
float hasData = step(u_coverageCutoffVertex, alpha);
|
|
47495
48928
|
float filled = mix(u_maskFillValue, max(raw, u_maskFillValue), hasData);
|
|
47496
48929
|
v_value = mix(raw, filled, inMask * u_maskFill);
|
|
47497
|
-
v_coverage = max(
|
|
48930
|
+
v_coverage = max(alpha, inMask * u_maskFill);
|
|
47498
48931
|
|
|
47499
48932
|
// Exaggeration stretches the span above the archive's minimum rather than the height itself. On an
|
|
47500
48933
|
// elevation the height is measured from the ellipsoid, tens of metres from any of the data, so
|
|
@@ -47537,6 +48970,8 @@ void main() {
|
|
|
47537
48970
|
`;
|
|
47538
48971
|
const FRAGMENT_SHADER_GLSL300 = `
|
|
47539
48972
|
uniform sampler2D u_valueTexture;
|
|
48973
|
+
uniform sampler2D u_valueTextureFrom;
|
|
48974
|
+
uniform float u_valueFade;
|
|
47540
48975
|
uniform sampler2D u_rampTexture;
|
|
47541
48976
|
uniform float u_coverageCutoff;
|
|
47542
48977
|
uniform vec2 u_texelSize;
|
|
@@ -47573,12 +49008,15 @@ in vec2 v_waveMetres;
|
|
|
47573
49008
|
* Scaled per channel for the same reason the vertex shader's decode is: reassembling the pair reaches
|
|
47574
49009
|
* 65535, which mediump does not have to represent, and this is the shader that may not get highp.
|
|
47575
49010
|
*/
|
|
47576
|
-
float
|
|
47577
|
-
vec4 texel = texture(u_valueTexture, uv);
|
|
49011
|
+
float decodeValue(vec4 texel) {
|
|
47578
49012
|
float packed16 = texel.r * (255.0 * 256.0 / 65535.0) + texel.g * (255.0 / 65535.0);
|
|
47579
49013
|
return mix(texel.r, packed16, u_valuePacked);
|
|
47580
49014
|
}
|
|
47581
49015
|
|
|
49016
|
+
float valueAt(vec2 uv) {
|
|
49017
|
+
return mix(decodeValue(texture(u_valueTextureFrom, uv)), decodeValue(texture(u_valueTexture, uv)), u_valueFade);
|
|
49018
|
+
}
|
|
49019
|
+
|
|
47582
49020
|
//#water
|
|
47583
49021
|
/*
|
|
47584
49022
|
* Slope of the ripple field at a point, in metres of rise per metre travelled.
|
|
@@ -48025,6 +49463,13 @@ void main() {
|
|
|
48025
49463
|
this.rampTexture = null;
|
|
48026
49464
|
this.texture = null;
|
|
48027
49465
|
this.textureDirty = true;
|
|
49466
|
+
// Fed from a fading source: the frame being left, and which pixels each texture holds.
|
|
49467
|
+
this.fade = null;
|
|
49468
|
+
this.textureFrom = null;
|
|
49469
|
+
this.uploadedTo = null;
|
|
49470
|
+
this.uploadedFrom = null;
|
|
49471
|
+
// Size of whichever value raster is bound, canvas or pixels.
|
|
49472
|
+
this.sourceDims = null;
|
|
48028
49473
|
this.shaderProgram = null;
|
|
48029
49474
|
this.shaderProgramWater = false;
|
|
48030
49475
|
this.renderState = null;
|
|
@@ -48195,6 +49640,65 @@ void main() {
|
|
|
48195
49640
|
this.maskGroundTexture = null;
|
|
48196
49641
|
}
|
|
48197
49642
|
}
|
|
49643
|
+
/**
|
|
49644
|
+
* Metres above the ellipsoid the sheet sits at over a point, or null unless a fading source is bound.
|
|
49645
|
+
*/
|
|
49646
|
+
HeightAt(lon, lat) {
|
|
49647
|
+
const pixels = this.fade ? this.fade.to : null;
|
|
49648
|
+
const dims = this.sourceDims;
|
|
49649
|
+
if (!pixels || !dims) {
|
|
49650
|
+
return null;
|
|
49651
|
+
}
|
|
49652
|
+
const fx = (lon - this.extent.West) / (this.extent.East - this.extent.West);
|
|
49653
|
+
const fy = (this.extent.North - lat) / (this.extent.North - this.extent.South);
|
|
49654
|
+
if (!(fx >= 0 && fx <= 1 && fy >= 0 && fy <= 1)) {
|
|
49655
|
+
return null;
|
|
49656
|
+
}
|
|
49657
|
+
const col = Math.min(dims.width - 1, Math.floor(fx * dims.width));
|
|
49658
|
+
const row = Math.min(dims.height - 1, Math.floor(fy * dims.height));
|
|
49659
|
+
const at = (row * dims.width + col) * 4;
|
|
49660
|
+
const coverage = pixels[at + 3] / 255;
|
|
49661
|
+
const normalised = this.packedValue
|
|
49662
|
+
? (pixels[at] * 256 + pixels[at + 1]) / 65535
|
|
49663
|
+
: pixels[at] / 255;
|
|
49664
|
+
const metres = this.valueMin + normalised * (this.valueMax - this.valueMin) * this.exaggeration;
|
|
49665
|
+
const following = this.followGround && Boolean(this.groundPixels);
|
|
49666
|
+
const ground = following ? this.groundAt(fx, fy) : 0;
|
|
49667
|
+
return this.baseHeight + ground + metres * (following ? coverage : 1);
|
|
49668
|
+
}
|
|
49669
|
+
/**
|
|
49670
|
+
* Changes whenever HeightAt would answer differently for the same frame.
|
|
49671
|
+
*/
|
|
49672
|
+
GetPlacementKey() {
|
|
49673
|
+
return [this.groundPixels ? 1 : 0, this.baseHeight, this.exaggeration, this.followGround ? 1 : 0].join("|");
|
|
49674
|
+
}
|
|
49675
|
+
/**
|
|
49676
|
+
* The ramp the sheet is painted with, as 256 RGBA steps across the value range.
|
|
49677
|
+
*/
|
|
49678
|
+
GetRampPixels() {
|
|
49679
|
+
return this.rampPixels;
|
|
49680
|
+
}
|
|
49681
|
+
/*
|
|
49682
|
+
* The packed ground raster read the way sampleGround reads it, bilinear between texel centres.
|
|
49683
|
+
*/
|
|
49684
|
+
groundAt(u, v) {
|
|
49685
|
+
const size = GROUND_SAMPLES_PER_SIDE;
|
|
49686
|
+
const pixels = this.groundPixels;
|
|
49687
|
+
const read = (x, y) => {
|
|
49688
|
+
const i = (Math.min(size - 1, Math.max(0, y)) * size + Math.min(size - 1, Math.max(0, x))) * 4;
|
|
49689
|
+
const packed = (pixels[i] << 16) + (pixels[i + 1] << 8) + pixels[i + 2];
|
|
49690
|
+
return GROUND_MIN_METRES + (packed / 16777215) * (GROUND_MAX_METRES - GROUND_MIN_METRES);
|
|
49691
|
+
};
|
|
49692
|
+
const x = u * size - 0.5;
|
|
49693
|
+
const y = v * size - 0.5;
|
|
49694
|
+
const x0 = Math.floor(x);
|
|
49695
|
+
const y0 = Math.floor(y);
|
|
49696
|
+
const fx = x - x0;
|
|
49697
|
+
const fy = y - y0;
|
|
49698
|
+
const top = read(x0, y0) + (read(x0 + 1, y0) - read(x0, y0)) * fx;
|
|
49699
|
+
const bottom = read(x0, y0 + 1) + (read(x0 + 1, y0 + 1) - read(x0, y0 + 1)) * fx;
|
|
49700
|
+
return top + (bottom - top) * fy;
|
|
49701
|
+
}
|
|
48198
49702
|
/**
|
|
48199
49703
|
* Feeds the surface from an animated frame series.
|
|
48200
49704
|
* Height and colour advance with the clock without the caller having to push each frame in.
|
|
@@ -48213,23 +49717,39 @@ void main() {
|
|
|
48213
49717
|
return;
|
|
48214
49718
|
}
|
|
48215
49719
|
const context = frameState.context;
|
|
48216
|
-
|
|
48217
|
-
|
|
48218
|
-
|
|
48219
|
-
|
|
48220
|
-
|
|
48221
|
-
}
|
|
48222
|
-
|
|
48223
|
-
|
|
48224
|
-
|
|
48225
|
-
|
|
48226
|
-
|
|
49720
|
+
const fade = this.valueSource && this.valueSource.GetValueFade
|
|
49721
|
+
? this.valueSource.GetValueFade()
|
|
49722
|
+
: null;
|
|
49723
|
+
if (fade) {
|
|
49724
|
+
this.fade = fade;
|
|
49725
|
+
this.sourceDims = { width: fade.width, height: fade.height };
|
|
49726
|
+
}
|
|
49727
|
+
else {
|
|
49728
|
+
this.fade = null;
|
|
49729
|
+
if (this.valueSource) {
|
|
49730
|
+
const canvas = this.valueSource.GetValueCanvas();
|
|
49731
|
+
if (!canvas) {
|
|
49732
|
+
// No frame decoded yet, so there is nothing to displace against.
|
|
49733
|
+
return;
|
|
49734
|
+
}
|
|
49735
|
+
const version = this.valueSource.GetPresentVersion();
|
|
49736
|
+
if (canvas !== this.source || version !== this.lastPresentVersion) {
|
|
49737
|
+
this.source = canvas;
|
|
49738
|
+
this.lastPresentVersion = version;
|
|
49739
|
+
this.textureDirty = true;
|
|
49740
|
+
}
|
|
48227
49741
|
}
|
|
49742
|
+
this.sourceDims = this.source ? { width: this.source.width, height: this.source.height } : null;
|
|
48228
49743
|
}
|
|
48229
|
-
if (!this.
|
|
49744
|
+
if (!this.sourceDims || !this.sourceDims.width || !this.sourceDims.height) {
|
|
48230
49745
|
return;
|
|
48231
49746
|
}
|
|
48232
|
-
this.
|
|
49747
|
+
if (this.fade) {
|
|
49748
|
+
this.syncFadeTextures(context);
|
|
49749
|
+
}
|
|
49750
|
+
else {
|
|
49751
|
+
this.syncTexture(context);
|
|
49752
|
+
}
|
|
48233
49753
|
this.syncRampTexture(context);
|
|
48234
49754
|
this.requestGround();
|
|
48235
49755
|
this.syncGroundTexture(context);
|
|
@@ -48277,6 +49797,10 @@ void main() {
|
|
|
48277
49797
|
this.texture.destroy();
|
|
48278
49798
|
this.texture = null;
|
|
48279
49799
|
}
|
|
49800
|
+
if (this.textureFrom) {
|
|
49801
|
+
this.textureFrom.destroy();
|
|
49802
|
+
this.textureFrom = null;
|
|
49803
|
+
}
|
|
48280
49804
|
if (this.rampTexture) {
|
|
48281
49805
|
this.rampTexture.destroy();
|
|
48282
49806
|
this.rampTexture = null;
|
|
@@ -48544,7 +50068,7 @@ void main() {
|
|
|
48544
50068
|
const southWest = Cesium.Cartesian3.fromDegrees(this.extent.West, this.extent.South, 0);
|
|
48545
50069
|
const southEast = Cesium.Cartesian3.fromDegrees(this.extent.East, this.extent.South, 0);
|
|
48546
50070
|
const width = Cesium.Cartesian3.distance(southWest, southEast);
|
|
48547
|
-
return Math.max(1, width / Math.max(1, this.
|
|
50071
|
+
return Math.max(1, width / Math.max(1, this.sourceDims ? this.sourceDims.width : 1));
|
|
48548
50072
|
}
|
|
48549
50073
|
syncRampTexture(context) {
|
|
48550
50074
|
if (this.rampTexture) {
|
|
@@ -48565,6 +50089,67 @@ void main() {
|
|
|
48565
50089
|
})
|
|
48566
50090
|
});
|
|
48567
50091
|
}
|
|
50092
|
+
valueSampler() {
|
|
50093
|
+
// Nearest for a packed value, because filtering would blend the two bytes of the
|
|
50094
|
+
// packing rather than the values they encode, the same trap sampleGround avoids.
|
|
50095
|
+
const minification = this.packedValue
|
|
50096
|
+
? Cesium.TextureMinificationFilter.NEAREST
|
|
50097
|
+
: Cesium.TextureMinificationFilter.LINEAR;
|
|
50098
|
+
const magnification = this.packedValue
|
|
50099
|
+
? Cesium.TextureMagnificationFilter.NEAREST
|
|
50100
|
+
: Cesium.TextureMagnificationFilter.LINEAR;
|
|
50101
|
+
return new Cesium.Sampler({
|
|
50102
|
+
wrapS: Cesium.TextureWrap.CLAMP_TO_EDGE,
|
|
50103
|
+
wrapT: Cesium.TextureWrap.CLAMP_TO_EDGE,
|
|
50104
|
+
minificationFilter: minification,
|
|
50105
|
+
magnificationFilter: magnification
|
|
50106
|
+
});
|
|
50107
|
+
}
|
|
50108
|
+
/*
|
|
50109
|
+
* Uploads value pixels into a texture, reusing it when the size still fits.
|
|
50110
|
+
*/
|
|
50111
|
+
uploadPixels(context, texture, pixels, width, height) {
|
|
50112
|
+
const view = new Uint8Array(pixels.buffer, pixels.byteOffset, pixels.byteLength);
|
|
50113
|
+
const source = { width, height, arrayBufferView: view };
|
|
50114
|
+
if (texture && texture.width === width && texture.height === height) {
|
|
50115
|
+
texture.copyFrom({ source });
|
|
50116
|
+
return texture;
|
|
50117
|
+
}
|
|
50118
|
+
if (texture) {
|
|
50119
|
+
texture.destroy();
|
|
50120
|
+
}
|
|
50121
|
+
return new Cesium.Texture({
|
|
50122
|
+
context,
|
|
50123
|
+
width,
|
|
50124
|
+
height,
|
|
50125
|
+
pixelFormat: Cesium.PixelFormat.RGBA,
|
|
50126
|
+
pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE,
|
|
50127
|
+
source,
|
|
50128
|
+
// Row 0 is the archive's north edge, and has to stay at the top, as the canvas path does.
|
|
50129
|
+
flipY: false,
|
|
50130
|
+
sampler: this.valueSampler()
|
|
50131
|
+
});
|
|
50132
|
+
}
|
|
50133
|
+
/*
|
|
50134
|
+
* Keeps a texture each side of the fade, swapping them on a new frame so only that one is uploaded.
|
|
50135
|
+
*/
|
|
50136
|
+
syncFadeTextures(context) {
|
|
50137
|
+
const fade = this.fade;
|
|
50138
|
+
if (fade.to !== this.uploadedTo) {
|
|
50139
|
+
if (fade.from && fade.from === this.uploadedTo && this.texture) {
|
|
50140
|
+
const previous = this.textureFrom;
|
|
50141
|
+
this.textureFrom = this.texture;
|
|
50142
|
+
this.uploadedFrom = this.uploadedTo;
|
|
50143
|
+
this.texture = previous;
|
|
50144
|
+
}
|
|
50145
|
+
this.texture = this.uploadPixels(context, this.texture, fade.to, fade.width, fade.height);
|
|
50146
|
+
this.uploadedTo = fade.to;
|
|
50147
|
+
}
|
|
50148
|
+
if (fade.from && fade.from !== this.uploadedFrom) {
|
|
50149
|
+
this.textureFrom = this.uploadPixels(context, this.textureFrom, fade.from, fade.width, fade.height);
|
|
50150
|
+
this.uploadedFrom = fade.from;
|
|
50151
|
+
}
|
|
50152
|
+
}
|
|
48568
50153
|
syncTexture(context) {
|
|
48569
50154
|
const sizeChanged = this.texture
|
|
48570
50155
|
&& (this.texture.width !== this.source.width || this.texture.height !== this.source.height);
|
|
@@ -48572,27 +50157,15 @@ void main() {
|
|
|
48572
50157
|
if (this.texture) {
|
|
48573
50158
|
this.texture.destroy();
|
|
48574
50159
|
}
|
|
48575
|
-
// Nearest for a packed value, because filtering would blend the two bytes of the
|
|
48576
|
-
// packing rather than the values they encode, the same trap sampleGround avoids.
|
|
48577
|
-
const minification = this.packedValue
|
|
48578
|
-
? Cesium.TextureMinificationFilter.NEAREST
|
|
48579
|
-
: Cesium.TextureMinificationFilter.LINEAR;
|
|
48580
|
-
const magnification = this.packedValue
|
|
48581
|
-
? Cesium.TextureMagnificationFilter.NEAREST
|
|
48582
|
-
: Cesium.TextureMagnificationFilter.LINEAR;
|
|
48583
50160
|
this.texture = new Cesium.Texture({
|
|
48584
50161
|
context,
|
|
48585
50162
|
source: this.source,
|
|
48586
50163
|
// Cesium flips canvas rows on upload by default, which would put the archive's
|
|
48587
50164
|
// row 0 (its north edge) at the south edge and mirror the whole surface.
|
|
48588
50165
|
flipY: false,
|
|
48589
|
-
sampler:
|
|
48590
|
-
wrapS: Cesium.TextureWrap.CLAMP_TO_EDGE,
|
|
48591
|
-
wrapT: Cesium.TextureWrap.CLAMP_TO_EDGE,
|
|
48592
|
-
minificationFilter: minification,
|
|
48593
|
-
magnificationFilter: magnification
|
|
48594
|
-
})
|
|
50166
|
+
sampler: this.valueSampler()
|
|
48595
50167
|
});
|
|
50168
|
+
this.uploadedTo = null;
|
|
48596
50169
|
this.textureDirty = false;
|
|
48597
50170
|
return;
|
|
48598
50171
|
}
|
|
@@ -48655,7 +50228,7 @@ void main() {
|
|
|
48655
50228
|
* carry, so the ladder is capped one step above the texel count rather than run to its top.
|
|
48656
50229
|
*/
|
|
48657
50230
|
tierCeiling() {
|
|
48658
|
-
const source = this.
|
|
50231
|
+
const source = this.sourceDims;
|
|
48659
50232
|
if (!source || !source.width || !source.height) {
|
|
48660
50233
|
return TIERS[TIERS.length - 1];
|
|
48661
50234
|
}
|
|
@@ -48733,6 +50306,10 @@ void main() {
|
|
|
48733
50306
|
owner: this,
|
|
48734
50307
|
uniformMap: {
|
|
48735
50308
|
u_valueTexture: () => self.texture,
|
|
50309
|
+
u_valueTextureFrom: () => (self.fade && self.fade.from && self.textureFrom
|
|
50310
|
+
? self.textureFrom
|
|
50311
|
+
: self.texture),
|
|
50312
|
+
u_valueFade: () => (self.fade && self.fade.from ? self.fade.mix : 1),
|
|
48736
50313
|
u_groundTexture: () => self.groundTexture,
|
|
48737
50314
|
u_valuePacked: () => (self.packedValue ? 1 : 0),
|
|
48738
50315
|
u_groundWeight: () => (self.followGround && self.groundPixels ? 1 : 0),
|
|
@@ -48770,7 +50347,7 @@ void main() {
|
|
|
48770
50347
|
: 0,
|
|
48771
50348
|
u_coverageCutoff: () => COVERAGE_CUTOFF,
|
|
48772
50349
|
u_rampTexture: () => self.rampTexture,
|
|
48773
|
-
u_texelSize: () => new Cesium.Cartesian2(1 / self.
|
|
50350
|
+
u_texelSize: () => new Cesium.Cartesian2(1 / self.sourceDims.width, 1 / self.sourceDims.height),
|
|
48774
50351
|
u_waterOn: () => (self.water ? 1 : 0),
|
|
48775
50352
|
u_waterWavelength: () => (self.water && self.water.wavelength)
|
|
48776
50353
|
|| DEFAULT_WATER_WAVELENGTH_METRES,
|
|
@@ -49141,6 +50718,10 @@ void main() {
|
|
|
49141
50718
|
syncTextureValueLabels({
|
|
49142
50719
|
cEntity,
|
|
49143
50720
|
viewer: params.viewer,
|
|
50721
|
+
surface: drawsDisplacedSurface ? cEntity[DISPLACED_SURFACE_KEY] : null,
|
|
50722
|
+
hover: params.visualRegister
|
|
50723
|
+
? { register: params.visualRegister, entityId: String(entity.Bruce.ID), menuItemId: params.menuItemId }
|
|
50724
|
+
: undefined,
|
|
49144
50725
|
settings: overlaySettings(style, entity, params.tags),
|
|
49145
50726
|
extent: labelArchive
|
|
49146
50727
|
? (drawsDisplacedSurface ? archiveExtent(labelArchive.metadata, posses) : drapeExtent)
|
|
@@ -49447,6 +51028,7 @@ void main() {
|
|
|
49447
51028
|
// rendered: null
|
|
49448
51029
|
rendered: (_c = params.rendered) === null || _c === void 0 ? void 0 : _c.get(entity.Bruce.ID),
|
|
49449
51030
|
menuItemId: params.menuItemId,
|
|
51031
|
+
visualRegister: params.visualRegister,
|
|
49450
51032
|
onSeriesDiscovered: params.onSeriesDiscovered
|
|
49451
51033
|
});
|
|
49452
51034
|
}
|
|
@@ -49884,8 +51466,8 @@ void main() {
|
|
|
49884
51466
|
*/
|
|
49885
51467
|
const LABEL_GRID_COLOR = "rgba(255,255,255,0.55)";
|
|
49886
51468
|
const LABEL_GRID_WIDTH_PIXELS = 1;
|
|
49887
|
-
const LABEL_MIN_SPACING_PIXELS =
|
|
49888
|
-
const LABEL_MAX_LABELS =
|
|
51469
|
+
const LABEL_MIN_SPACING_PIXELS = 140;
|
|
51470
|
+
const LABEL_MAX_LABELS = 60;
|
|
49889
51471
|
function overlaySettings(style, entity, tags) {
|
|
49890
51472
|
if (!wantsLabels(style)) {
|
|
49891
51473
|
return undefined;
|
|
@@ -49905,6 +51487,7 @@ void main() {
|
|
|
49905
51487
|
*/
|
|
49906
51488
|
function syncTextureValueLabels(params) {
|
|
49907
51489
|
const { cEntity, viewer, settings, extent, animator, metadata } = params;
|
|
51490
|
+
const surface = params.surface && !params.surface.isDestroyed() ? params.surface : null;
|
|
49908
51491
|
const existing = cEntity[TEXTURE_VALUE_LABELS_KEY];
|
|
49909
51492
|
// Offline has no extent to rebuild the overlay from, so an existing one is left running.
|
|
49910
51493
|
if (params.offline && existing) {
|
|
@@ -49923,8 +51506,13 @@ void main() {
|
|
|
49923
51506
|
West: tile.West, East: tile.East, South: tile.South, North: tile.North,
|
|
49924
51507
|
ResolutionX: tile.ResolutionX, ResolutionY: tile.ResolutionY
|
|
49925
51508
|
})),
|
|
49926
|
-
clipRing: params.clipRing
|
|
51509
|
+
clipRing: params.clipRing,
|
|
51510
|
+
heightAt: surface ? (lon, lat) => surface.HeightAt(lon, lat) : undefined,
|
|
51511
|
+
heightKey: surface ? () => surface.GetPlacementKey() : undefined,
|
|
51512
|
+
hover: params.hover
|
|
49927
51513
|
});
|
|
51514
|
+
// A surface keeps no tinted frame, so whether a cell was painted is read off its ramp.
|
|
51515
|
+
labels.SetPaintedRamp(surface ? surface.GetRampPixels() : null);
|
|
49928
51516
|
let lastVersion = -1;
|
|
49929
51517
|
let appliedFloor = null;
|
|
49930
51518
|
let appliedCeiling = null;
|
|
@@ -49936,15 +51524,15 @@ void main() {
|
|
|
49936
51524
|
if (animator.IsDisposed()) {
|
|
49937
51525
|
return;
|
|
49938
51526
|
}
|
|
49939
|
-
const canvas = animator.GetValueCanvas();
|
|
49940
|
-
if (!canvas) {
|
|
49941
|
-
return;
|
|
49942
|
-
}
|
|
49943
51527
|
const version = animator.GetPresentVersion();
|
|
49944
51528
|
if (version !== lastVersion) {
|
|
51529
|
+
// Read from the frame cache, since reading the value canvas back copies a whole frame.
|
|
51530
|
+
const raster = animator.GetValueRaster();
|
|
51531
|
+
if (!raster) {
|
|
51532
|
+
return;
|
|
51533
|
+
}
|
|
49945
51534
|
lastVersion = version;
|
|
49946
|
-
|
|
49947
|
-
labels.SetSource(ctx.getImageData(0, 0, canvas.width, canvas.height));
|
|
51535
|
+
labels.SetSource(raster);
|
|
49948
51536
|
labels.SetPainted(animator.GetDisplayedPixels());
|
|
49949
51537
|
}
|
|
49950
51538
|
// Tracked by reference rather than latched on the first one to arrive: a tiled archive
|
|
@@ -51668,7 +53256,7 @@ void main() {
|
|
|
51668
53256
|
StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
|
|
51669
53257
|
})(exports.StyleUtils || (exports.StyleUtils = {}));
|
|
51670
53258
|
|
|
51671
|
-
const VERSION = "7.5.
|
|
53259
|
+
const VERSION = "7.5.9";
|
|
51672
53260
|
/**
|
|
51673
53261
|
* Updates the environment instance used by bruce-cesium to one specified.
|
|
51674
53262
|
* This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
|