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.es5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Cesium from 'cesium';
|
|
2
|
-
import { Cartographic, ShaderSource, Cartesian3, Math as Math$1, Ellipsoid, Transforms, Matrix4, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, Entity, ConstantPositionProperty, HeightReference, Color, DistanceDisplayCondition, ColorMaterialProperty, JulianDate, Quaternion, HeadingPitchRoll,
|
|
3
|
-
import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Calculator,
|
|
2
|
+
import { Cartographic, ShaderSource, Cartesian3, Math as Math$1, Ellipsoid, Transforms, Matrix4, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, PixelDatatype, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, Entity, ConstantPositionProperty, HeightReference, Color, DistanceDisplayCondition, ColorMaterialProperty, JulianDate, Quaternion, HeadingPitchRoll, CallbackProperty, HorizontalOrigin, VerticalOrigin, ConstantProperty, ClassificationType, Primitive, Cesium3DTileFeature, ColorBlendMode, ShadowMode, Model, ArcType, CornerType, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, SceneMode, IntersectionTests, Ray, SceneTransforms, FeatureDetection, Matrix3, NearFarScalar, KmlDataSource, GeoJsonDataSource, Cesium3DTileStyle, HeadingPitchRange, Ion, Cesium3DTileColorBlendMode, IonResource, Cesium3DTileset, Intersect, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, OrthographicFrustum, EasingFunction, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, PolygonPipeline, GeometryInstance, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, CustomDataSource, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
|
|
3
|
+
import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Api, Calculator, ClientFile, Entity as Entity$1, EntityTag, EntityType, ObjectUtils, Style, EntityLod, UrlUtils, Carto, Geometry as Geometry$1, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, ClientFileValueMap, Color as Color$1, Bounds, BruceEvent, EntityCoords, DataLab, DelayQueue, AccountConcept, BruceApi, RecordChangeFeed, EntityRelation, EntitySource, ProgramKey, ProjectViewLegacyTile, Camera, Account, AccountLimits, Session, ProjectViewBookmarkGroup, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType, EntityHistoricData } from 'bruce-models';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Ensures a number is returned from a given value.
|
|
@@ -5546,6 +5546,24 @@ var EntityLabel;
|
|
|
5546
5546
|
const PHOTO_CACHE_LIMIT = 500;
|
|
5547
5547
|
// Key where the viewer keeps its downsized photos.
|
|
5548
5548
|
const PHOTO_CACHE_KEY = "_nextspaceLabelPhotos";
|
|
5549
|
+
// Clearance around a model's box, as a share of its half-extent on each axis.
|
|
5550
|
+
const MODEL_BOX_PADDING = 0.02;
|
|
5551
|
+
// Share of each edge drawn out from its corners while the box is idle. Hovering draws the edges whole.
|
|
5552
|
+
const MODEL_BOX_BRACKET = 0.2;
|
|
5553
|
+
// Under this many pixels across, a box reads as a smudge and the ring takes over.
|
|
5554
|
+
const MODEL_BOX_MIN_SIZE = 24;
|
|
5555
|
+
// Stem length above the box outline when the options do not set one.
|
|
5556
|
+
const MODEL_BOX_STEM = 32;
|
|
5557
|
+
// Gap in pixels a waiting box needs from a drawn one before it shows, so neighbours never trade places.
|
|
5558
|
+
const MODEL_BOX_CLEARANCE = 12;
|
|
5559
|
+
// Longest a model's label stays hidden waiting to learn whether it has a box, before showing its ring anyway.
|
|
5560
|
+
const MODEL_BOX_WAIT = 1500;
|
|
5561
|
+
// Half the length of edge drawn either side of where a stem lands, when brackets would leave it bare.
|
|
5562
|
+
const MODEL_BOX_LANDING = 10;
|
|
5563
|
+
// Past this many touching boxes one box around them all stands in for tracing their union.
|
|
5564
|
+
const MODEL_BOX_UNION_LIMIT = 24;
|
|
5565
|
+
// 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.
|
|
5566
|
+
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]];
|
|
5549
5567
|
// One per tinted icon, since an SVG mask is addressed by id across the whole document.
|
|
5550
5568
|
let tintCounter = 0;
|
|
5551
5569
|
// Key where the label instance is stored within the cesium entity.
|
|
@@ -5553,8 +5571,6 @@ var EntityLabel;
|
|
|
5553
5571
|
EntityLabel.ATTACH_KEY = "nextspace-navigator-entity-label";
|
|
5554
5572
|
// Image types a label draws, read from the file extension. Anything else stays text.
|
|
5555
5573
|
const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "bmp", "svg", "webp"];
|
|
5556
|
-
// Shortest side a thumbnail needs before it is preferred over the full file.
|
|
5557
|
-
const IMAGE_THUMBNAIL_MIN = 64;
|
|
5558
5574
|
// Size a found image is drawn at when the group names none, so a photo never lands at its natural size.
|
|
5559
5575
|
const IMAGE_SIZE = 24;
|
|
5560
5576
|
function isImageFile(name) {
|
|
@@ -5583,12 +5599,24 @@ var EntityLabel;
|
|
|
5583
5599
|
return (url.protocol === "http:" || url.protocol === "https:") && isImageFile(url.pathname);
|
|
5584
5600
|
}
|
|
5585
5601
|
EntityLabel.IsImageUrl = IsImageUrl;
|
|
5602
|
+
/*
|
|
5603
|
+
* Device pixels across a photo needs to stay sharp at a chip size, at rest or grown on hover.
|
|
5604
|
+
*/
|
|
5605
|
+
function photoPixels(size, hover) {
|
|
5606
|
+
// The glyph plus the chip's padding of a third either side, enlarged for a photo.
|
|
5607
|
+
const diameter = (size + 2 * (size / 3)) * PHOTO_SIZE_SCALE;
|
|
5608
|
+
// Picking an image can run with no window, where a screen pixel is a CSS pixel.
|
|
5609
|
+
const ratio = typeof window === "undefined" ? 1 : window.devicePixelRatio || 1;
|
|
5610
|
+
return Math.ceil(diameter * (hover ? PHOTO_HOVER_SCALE : 1) * ratio);
|
|
5611
|
+
}
|
|
5586
5612
|
/*
|
|
5587
5613
|
* The entity's default image as InfoView picks it: its first photo attachment by display order that is an image file.
|
|
5588
|
-
*
|
|
5614
|
+
*
|
|
5615
|
+
* Reads attachments expanded onto the entity. Prefers the smallest thumbnail sharp at the hover size, then the
|
|
5616
|
+
* largest still sharp at rest, so a low resolution thumbnail is never stretched while the full file is unread.
|
|
5589
5617
|
*/
|
|
5590
5618
|
function GetDefaultImageUrl(entity, api) {
|
|
5591
|
-
var _a, _b, _c;
|
|
5619
|
+
var _a, _b, _c, _d;
|
|
5592
5620
|
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 : [];
|
|
5593
5621
|
const photo = attachments
|
|
5594
5622
|
.filter(x => (x === null || x === void 0 ? void 0 : x["EntityAttachmentType.ID"]) == EntityAttachmentType.EType.Photo && x.ClientFile)
|
|
@@ -5597,9 +5625,12 @@ var EntityLabel;
|
|
|
5597
5625
|
if (!photo) {
|
|
5598
5626
|
return null;
|
|
5599
5627
|
}
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5628
|
+
// How far across a thumbnail is shown: a circle crops to its shortest side, a rectangle keeps its longest.
|
|
5629
|
+
const across = (x) => PhotoShape(x.Width, x.Height) === "circle" ? Math.min(x.Width, x.Height) : Math.max(x.Width, x.Height);
|
|
5630
|
+
const thumbnails = ((_c = photo.ClientFile.Thumbnails) !== null && _c !== void 0 ? _c : [])
|
|
5631
|
+
.filter(x => x.Width > 0 && x.Height > 0 && (x.URL || x["ClientFile.ID"]))
|
|
5632
|
+
.sort((a, b) => a.Width * a.Height - b.Width * b.Height);
|
|
5633
|
+
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));
|
|
5603
5634
|
if (thumbnail) {
|
|
5604
5635
|
return thumbnail.URL || ClientFile.GetUrl({ fileId: thumbnail["ClientFile.ID"], api, viaCdn: true });
|
|
5605
5636
|
}
|
|
@@ -6111,6 +6142,7 @@ var EntityLabel;
|
|
|
6111
6142
|
// Participants whose failure has been logged, so a label that throws every frame logs once.
|
|
6112
6143
|
this.failed = new WeakSet();
|
|
6113
6144
|
this.placementMemory = new PlacementMemory();
|
|
6145
|
+
this.modelBoxes = new ModelBoxes();
|
|
6114
6146
|
this.forwardingWheel = false;
|
|
6115
6147
|
this.pendingDrag = null;
|
|
6116
6148
|
// Null outside a 3D view.
|
|
@@ -6349,6 +6381,12 @@ var EntityLabel;
|
|
|
6349
6381
|
x: (pointer.x - rect.left) * this.viewer.canvas.clientWidth / rect.width,
|
|
6350
6382
|
y: (pointer.y - rect.top) * this.viewer.canvas.clientHeight / rect.height
|
|
6351
6383
|
} : undefined;
|
|
6384
|
+
try {
|
|
6385
|
+
this.modelBoxes.Resolve(this.viewer, this.pointer);
|
|
6386
|
+
}
|
|
6387
|
+
catch (e) {
|
|
6388
|
+
console.error("EntityLabel: model boxes failed to resolve.", e);
|
|
6389
|
+
}
|
|
6352
6390
|
// This runs inside Cesium's frame, where one throwing label would stop rendering for the whole scene.
|
|
6353
6391
|
const projected = new Map();
|
|
6354
6392
|
for (const participant of this.participants) {
|
|
@@ -6370,6 +6408,12 @@ var EntityLabel;
|
|
|
6370
6408
|
this.logFailure(participant, e);
|
|
6371
6409
|
}
|
|
6372
6410
|
}
|
|
6411
|
+
try {
|
|
6412
|
+
this.modelBoxes.Draw(this.viewer, this.element, this.coordinates);
|
|
6413
|
+
}
|
|
6414
|
+
catch (e) {
|
|
6415
|
+
console.error("EntityLabel: model boxes failed to draw.", e);
|
|
6416
|
+
}
|
|
6373
6417
|
}
|
|
6374
6418
|
logFailure(participant, error) {
|
|
6375
6419
|
if (this.failed.has(participant)) {
|
|
@@ -6380,6 +6424,728 @@ var EntityLabel;
|
|
|
6380
6424
|
}
|
|
6381
6425
|
}
|
|
6382
6426
|
let nextLabelId = 0;
|
|
6427
|
+
/*
|
|
6428
|
+
* The record's oriented box in the same Z-up local space as GeometryPivot, or null when it has none.
|
|
6429
|
+
*/
|
|
6430
|
+
function ReadOrientedBox(entity) {
|
|
6431
|
+
var _a, _b;
|
|
6432
|
+
const box = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.GeometryOrientedBox;
|
|
6433
|
+
const vector = (value) => (value === null || value === void 0 ? void 0 : value.length) === 3 && value.every((item) => Number.isFinite(+item)) ? value.map(Number) : null;
|
|
6434
|
+
const centre = vector(box === null || box === void 0 ? void 0 : box.Centre), half = vector(box === null || box === void 0 ? void 0 : box.HalfExtents);
|
|
6435
|
+
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;
|
|
6436
|
+
return centre && half && (axes === null || axes === void 0 ? void 0 : axes.every(Boolean)) ? { centre, axes, half } : null;
|
|
6437
|
+
}
|
|
6438
|
+
EntityLabel.ReadOrientedBox = ReadOrientedBox;
|
|
6439
|
+
/*
|
|
6440
|
+
* The eight padded world corners of an assembly Entity's geometry box, or null when the record cannot give one.
|
|
6441
|
+
*
|
|
6442
|
+
* GeometryPivotExtents are Z-up half-extents about GeometryPivot, and the placement is the one the API uses to put
|
|
6443
|
+
* Bruce.Location at the pivot: ENU(root) * UCS translation * UCS rotation * AssemblyWorldPosition.
|
|
6444
|
+
*/
|
|
6445
|
+
function GetModelBox(entity, shape) {
|
|
6446
|
+
var _a, _b, _c;
|
|
6447
|
+
const bruce = entity === null || entity === void 0 ? void 0 : entity.Bruce;
|
|
6448
|
+
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);
|
|
6449
|
+
const matrix = bruce === null || bruce === void 0 ? void 0 : bruce.AssemblyWorldPosition;
|
|
6450
|
+
const root = bruce === null || bruce === void 0 ? void 0 : bruce.AssemblyRootLocation;
|
|
6451
|
+
if ((half === null || half === void 0 ? void 0 : half.length) !== 3 || !half.every(value => Number.isFinite(value) && value >= 0) || Math.max(...half) < 0.01) {
|
|
6452
|
+
return null;
|
|
6453
|
+
}
|
|
6454
|
+
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))) {
|
|
6455
|
+
return null;
|
|
6456
|
+
}
|
|
6457
|
+
const pivot = ((_c = bruce.GeometryPivot) === null || _c === void 0 ? void 0 : _c.length) >= 3 ? bruce.GeometryPivot.slice(0, 3).map(Number) : [0, 0, 0];
|
|
6458
|
+
// Without an oriented box the record's own extents are an axis-aligned box about the pivot.
|
|
6459
|
+
const box = shape || { centre: pivot, axes: [[1, 0, 0], [0, 1, 0], [0, 0, 1]], half: half };
|
|
6460
|
+
const transform = bruce.AssemblyWorldTransform;
|
|
6461
|
+
const placement = Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(+root.longitude, +root.latitude, EnsureNumber(root.altitude)));
|
|
6462
|
+
Matrix4.multiply(placement, Matrix4.fromTranslation(new 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);
|
|
6463
|
+
Matrix4.multiply(placement, Matrix4.fromRotation(Matrix3.fromHeadingPitchRoll(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);
|
|
6464
|
+
Matrix4.multiply(placement, new Matrix4(...matrix.flat().map(Number)), placement);
|
|
6465
|
+
// Pad thin axes by a share of the longest one, so a flat part still gets a visible gap.
|
|
6466
|
+
const longest = Math.max(...box.half);
|
|
6467
|
+
const reach = box.half.map(value => value + Math.max(value, longest * 0.25) * MODEL_BOX_PADDING);
|
|
6468
|
+
const corners = [];
|
|
6469
|
+
for (let i = 0; i < 8; i++) {
|
|
6470
|
+
const signs = [i & 1 ? 1 : -1, i & 2 ? 1 : -1, i & 4 ? 1 : -1];
|
|
6471
|
+
const local = new Cartesian3(...[0, 1, 2].map(k => box.centre[k] +
|
|
6472
|
+
signs[0] * reach[0] * box.axes[0][k] + signs[1] * reach[1] * box.axes[1][k] + signs[2] * reach[2] * box.axes[2][k]));
|
|
6473
|
+
const corner = Matrix4.multiplyByPoint(placement, local, new Cartesian3());
|
|
6474
|
+
if (![corner.x, corner.y, corner.z].every(Number.isFinite)) {
|
|
6475
|
+
return null;
|
|
6476
|
+
}
|
|
6477
|
+
corners.push(corner);
|
|
6478
|
+
}
|
|
6479
|
+
return corners;
|
|
6480
|
+
}
|
|
6481
|
+
EntityLabel.GetModelBox = GetModelBox;
|
|
6482
|
+
const vAdd = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
6483
|
+
const vSub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
6484
|
+
const vScale = (a, s) => [a[0] * s, a[1] * s, a[2] * s];
|
|
6485
|
+
const vDot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
6486
|
+
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]];
|
|
6487
|
+
const vLength = (a) => Math.hypot(a[0], a[1], a[2]);
|
|
6488
|
+
/*
|
|
6489
|
+
* Reads a solid back from the eight corners GetModelBox gives, relative to the origin.
|
|
6490
|
+
*/
|
|
6491
|
+
function ModelSolid(corners, origin) {
|
|
6492
|
+
const local = corners.map((corner) => [corner.x - origin.x, corner.y - origin.y, corner.z - origin.z]);
|
|
6493
|
+
const spans = [1, 2, 4].map(bit => vSub(local[bit], local[0]));
|
|
6494
|
+
const half = spans.map(span => vLength(span) / 2);
|
|
6495
|
+
return {
|
|
6496
|
+
centre: vScale(vAdd(local[0], local[7]), 0.5),
|
|
6497
|
+
axes: spans.map((span, k) => vScale(span, 0.5 / (half[k] || 1))),
|
|
6498
|
+
half
|
|
6499
|
+
};
|
|
6500
|
+
}
|
|
6501
|
+
EntityLabel.ModelSolid = ModelSolid;
|
|
6502
|
+
function solidCorners(solid) {
|
|
6503
|
+
const corners = [];
|
|
6504
|
+
for (let i = 0; i < 8; i++) {
|
|
6505
|
+
corners.push([0, 1, 2].reduce((point, k) => vAdd(point, vScale(solid.axes[k], solid.half[k] * (i & (1 << k) ? 1 : -1))), solid.centre));
|
|
6506
|
+
}
|
|
6507
|
+
return corners;
|
|
6508
|
+
}
|
|
6509
|
+
function solidContains(solid, point, inset = 0) {
|
|
6510
|
+
const offset = vSub(point, solid.centre);
|
|
6511
|
+
return solid.axes.every((axis, k) => Math.abs(vDot(offset, axis)) < solid.half[k] - inset);
|
|
6512
|
+
}
|
|
6513
|
+
/*
|
|
6514
|
+
* Whether two solids overlap, by the separating axis test. Padding makes parts that sit against each other overlap.
|
|
6515
|
+
*/
|
|
6516
|
+
function ModelSolidsTouch(a, b) {
|
|
6517
|
+
const between = vSub(b.centre, a.centre);
|
|
6518
|
+
const axes = [...a.axes, ...b.axes];
|
|
6519
|
+
for (const first of a.axes) {
|
|
6520
|
+
for (const second of b.axes) {
|
|
6521
|
+
const axis = vCross(first, second);
|
|
6522
|
+
const length = vLength(axis);
|
|
6523
|
+
if (length > 1e-6)
|
|
6524
|
+
axes.push(vScale(axis, 1 / length));
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6527
|
+
const reach = (solid, axis) => solid.axes.reduce((sum, own, k) => sum + solid.half[k] * Math.abs(vDot(own, axis)), 0);
|
|
6528
|
+
return axes.every(axis => Math.abs(vDot(between, axis)) <= reach(a, axis) + reach(b, axis));
|
|
6529
|
+
}
|
|
6530
|
+
EntityLabel.ModelSolidsTouch = ModelSolidsTouch;
|
|
6531
|
+
/*
|
|
6532
|
+
* One box around every solid, turned like the largest of them, for groups too big to trace.
|
|
6533
|
+
*/
|
|
6534
|
+
function encloseSolids(solids) {
|
|
6535
|
+
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);
|
|
6536
|
+
const corners = solids.flatMap(solidCorners);
|
|
6537
|
+
const ranges = largest.axes.map(axis => {
|
|
6538
|
+
const values = corners.map(corner => vDot(corner, axis));
|
|
6539
|
+
return [Math.min(...values), Math.max(...values)];
|
|
6540
|
+
});
|
|
6541
|
+
return {
|
|
6542
|
+
centre: largest.axes.reduce((point, axis, k) => vAdd(point, vScale(axis, (ranges[k][0] + ranges[k][1]) / 2)), [0, 0, 0]),
|
|
6543
|
+
axes: largest.axes,
|
|
6544
|
+
half: ranges.map(([low, high]) => (high - low) / 2)
|
|
6545
|
+
};
|
|
6546
|
+
}
|
|
6547
|
+
/*
|
|
6548
|
+
* Narrows [low, high] to where a + b * t stays within -half..half.
|
|
6549
|
+
*/
|
|
6550
|
+
function clipSlab(range, a, b, half) {
|
|
6551
|
+
if (Math.abs(b) < 1e-12) {
|
|
6552
|
+
if (Math.abs(a) > half)
|
|
6553
|
+
range[1] = -Infinity;
|
|
6554
|
+
return;
|
|
6555
|
+
}
|
|
6556
|
+
const first = (-half - a) / b, second = (half - a) / b;
|
|
6557
|
+
range[0] = Math.max(range[0], Math.min(first, second));
|
|
6558
|
+
range[1] = Math.min(range[1], Math.max(first, second));
|
|
6559
|
+
}
|
|
6560
|
+
/*
|
|
6561
|
+
* The edges of the union of the solids: where its surface folds, leaving out seams across flat shared faces.
|
|
6562
|
+
*/
|
|
6563
|
+
function ModelBoxOutline(solids) {
|
|
6564
|
+
const span = Math.max(...solids.map(solid => vLength(solid.half)));
|
|
6565
|
+
const thinnest = Math.min(...solids.flatMap(solid => solid.half));
|
|
6566
|
+
const radius = Math.max(1e-6, Math.min(thinnest * 0.05, span * 1e-3));
|
|
6567
|
+
const tolerance = Math.max(1, span) * 1e-6;
|
|
6568
|
+
const candidates = [];
|
|
6569
|
+
for (const solid of solids) {
|
|
6570
|
+
const corners = solidCorners(solid);
|
|
6571
|
+
for (const [a, b] of MODEL_BOX_EDGES)
|
|
6572
|
+
candidates.push([corners[a], corners[b]]);
|
|
6573
|
+
}
|
|
6574
|
+
const faces = (solid) => [0, 1, 2].flatMap(k => [1, -1].map(sign => ({ solid, k, normal: vScale(solid.axes[k], sign) })));
|
|
6575
|
+
for (let i = 0; i < solids.length; i++) {
|
|
6576
|
+
for (let j = i + 1; j < solids.length; j++) {
|
|
6577
|
+
if (!ModelSolidsTouch(solids[i], solids[j])) {
|
|
6578
|
+
continue;
|
|
6579
|
+
}
|
|
6580
|
+
for (const first of faces(solids[i])) {
|
|
6581
|
+
for (const second of faces(solids[j])) {
|
|
6582
|
+
const direction = vCross(first.normal, second.normal);
|
|
6583
|
+
const sine = vLength(direction);
|
|
6584
|
+
// Faces this close to parallel meet along edges the box edges already give.
|
|
6585
|
+
if (sine < 0.05) {
|
|
6586
|
+
continue;
|
|
6587
|
+
}
|
|
6588
|
+
const d1 = vDot(first.normal, first.solid.centre) + first.solid.half[first.k];
|
|
6589
|
+
const d2 = vDot(second.normal, second.solid.centre) + second.solid.half[second.k];
|
|
6590
|
+
const cosine = vDot(first.normal, second.normal);
|
|
6591
|
+
const base = vScale(vAdd(vScale(first.normal, d1 - d2 * cosine), vScale(second.normal, d2 - d1 * cosine)), 1 / (sine * sine));
|
|
6592
|
+
const unit = vScale(direction, 1 / sine);
|
|
6593
|
+
const range = [-Infinity, Infinity];
|
|
6594
|
+
for (const face of [first, second]) {
|
|
6595
|
+
const offset = vSub(base, face.solid.centre);
|
|
6596
|
+
for (let m = 0; m < 3; m++) {
|
|
6597
|
+
if (m !== face.k)
|
|
6598
|
+
clipSlab(range, vDot(offset, face.solid.axes[m]), vDot(unit, face.solid.axes[m]), face.solid.half[m]);
|
|
6599
|
+
}
|
|
6600
|
+
}
|
|
6601
|
+
if (range[1] - range[0] > tolerance) {
|
|
6602
|
+
candidates.push([vAdd(base, vScale(unit, range[0])), vAdd(base, vScale(unit, range[1]))]);
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
}
|
|
6606
|
+
}
|
|
6607
|
+
}
|
|
6608
|
+
const inside = (point) => solids.some(solid => solidContains(solid, point));
|
|
6609
|
+
/*
|
|
6610
|
+
* Whether the union folds along a direction at a point, from which way round it the inside lies.
|
|
6611
|
+
*/
|
|
6612
|
+
const folds = (point, along) => {
|
|
6613
|
+
const first = vCross(along, Math.abs(along[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0]);
|
|
6614
|
+
const e1 = vScale(first, 1 / vLength(first));
|
|
6615
|
+
const e2 = vCross(along, e1);
|
|
6616
|
+
const samples = [];
|
|
6617
|
+
for (let i = 0; i < 16; i++) {
|
|
6618
|
+
// Offset from the axes so no sample lands exactly on a face through the point.
|
|
6619
|
+
const angle = (i + 0.37) * Math.PI / 8;
|
|
6620
|
+
samples.push(inside(vAdd(point, vAdd(vScale(e1, Math.cos(angle) * radius), vScale(e2, Math.sin(angle) * radius)))));
|
|
6621
|
+
}
|
|
6622
|
+
const filled = samples.filter(Boolean).length;
|
|
6623
|
+
const changes = samples.filter((sample, i) => sample !== samples[(i + 1) % 16]).length;
|
|
6624
|
+
return filled > 0 && filled < 16 && !(filled === 8 && changes === 2);
|
|
6625
|
+
};
|
|
6626
|
+
const lines = [];
|
|
6627
|
+
for (const [start, end] of candidates) {
|
|
6628
|
+
const delta = vSub(end, start);
|
|
6629
|
+
const length = vLength(delta);
|
|
6630
|
+
if (length < tolerance) {
|
|
6631
|
+
continue;
|
|
6632
|
+
}
|
|
6633
|
+
const along = vScale(delta, 1 / length);
|
|
6634
|
+
const cuts = [0, 1];
|
|
6635
|
+
for (const solid of solids) {
|
|
6636
|
+
const offset = vSub(start, solid.centre);
|
|
6637
|
+
solid.axes.forEach((axis, k) => {
|
|
6638
|
+
const rate = vDot(delta, axis);
|
|
6639
|
+
if (Math.abs(rate) < 1e-12)
|
|
6640
|
+
return;
|
|
6641
|
+
for (const bound of [-solid.half[k], solid.half[k]]) {
|
|
6642
|
+
const t = (bound - vDot(offset, axis)) / rate;
|
|
6643
|
+
if (t > 0 && t < 1)
|
|
6644
|
+
cuts.push(t);
|
|
6645
|
+
}
|
|
6646
|
+
});
|
|
6647
|
+
}
|
|
6648
|
+
cuts.sort((a, b) => a - b);
|
|
6649
|
+
for (let i = 0; i + 1 < cuts.length; i++) {
|
|
6650
|
+
if ((cuts[i + 1] - cuts[i]) * length < tolerance || !folds(vAdd(start, vScale(delta, (cuts[i] + cuts[i + 1]) / 2)), along)) {
|
|
6651
|
+
continue;
|
|
6652
|
+
}
|
|
6653
|
+
// Merged by line, so a shared edge draws once and a run cut by other boxes is one stroke.
|
|
6654
|
+
const from = vAdd(start, vScale(delta, cuts[i])), to = vAdd(start, vScale(delta, cuts[i + 1]));
|
|
6655
|
+
let line = lines.find(item => vLength(vCross(item.direction, along)) < 1e-6 && vLength(vCross(vSub(from, item.origin), item.direction)) < tolerance);
|
|
6656
|
+
if (!line) {
|
|
6657
|
+
line = { origin: from, direction: along, spans: [] };
|
|
6658
|
+
lines.push(line);
|
|
6659
|
+
}
|
|
6660
|
+
const a = vDot(vSub(from, line.origin), line.direction), b = vDot(vSub(to, line.origin), line.direction);
|
|
6661
|
+
line.spans.push([Math.min(a, b), Math.max(a, b)]);
|
|
6662
|
+
}
|
|
6663
|
+
}
|
|
6664
|
+
const outline = [];
|
|
6665
|
+
for (const line of lines) {
|
|
6666
|
+
line.spans.sort((a, b) => a[0] - b[0]);
|
|
6667
|
+
let current = line.spans[0];
|
|
6668
|
+
for (const next of [...line.spans.slice(1), null]) {
|
|
6669
|
+
if (next && next[0] <= current[1] + tolerance) {
|
|
6670
|
+
current = [current[0], Math.max(current[1], next[1])];
|
|
6671
|
+
continue;
|
|
6672
|
+
}
|
|
6673
|
+
outline.push([vAdd(line.origin, vScale(line.direction, current[0])), vAdd(line.origin, vScale(line.direction, current[1]))]);
|
|
6674
|
+
current = next;
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
return outline;
|
|
6678
|
+
}
|
|
6679
|
+
EntityLabel.ModelBoxOutline = ModelBoxOutline;
|
|
6680
|
+
/*
|
|
6681
|
+
* Whether the sight line from the eye to a point passes through any solid before reaching it.
|
|
6682
|
+
*/
|
|
6683
|
+
function solidsHide(solids, eye, point, inset) {
|
|
6684
|
+
const ray = vSub(point, eye);
|
|
6685
|
+
const length = vLength(ray);
|
|
6686
|
+
// Stop just short of the point so the face it lies on does not count against it.
|
|
6687
|
+
const reach = Math.max(0, 1 - inset * 100 / (length || 1));
|
|
6688
|
+
return solids.some(solid => {
|
|
6689
|
+
const offset = vSub(eye, solid.centre);
|
|
6690
|
+
const range = [0, reach];
|
|
6691
|
+
solid.axes.forEach((axis, k) => clipSlab(range, vDot(offset, axis), vDot(ray, axis), solid.half[k] - inset));
|
|
6692
|
+
return range[0] < range[1];
|
|
6693
|
+
});
|
|
6694
|
+
}
|
|
6695
|
+
/*
|
|
6696
|
+
* Window positions of a box's corners, or null when one is behind the camera and the box would draw inside out.
|
|
6697
|
+
*/
|
|
6698
|
+
function projectCorners(viewer, corners) {
|
|
6699
|
+
const camera = viewer.camera;
|
|
6700
|
+
const cameraPosition = camera.positionWC || camera.position;
|
|
6701
|
+
const direction = camera.directionWC || camera.direction;
|
|
6702
|
+
const transforms = SceneTransforms;
|
|
6703
|
+
const projectPosition = transforms.wgs84ToWindowCoordinates || transforms.worldToWindowCoordinates;
|
|
6704
|
+
const points = [];
|
|
6705
|
+
const relative = new Cartesian3();
|
|
6706
|
+
for (const corner of corners) {
|
|
6707
|
+
if (Cartesian3.dot(Cartesian3.subtract(corner, cameraPosition, relative), direction) <= 0) {
|
|
6708
|
+
return null;
|
|
6709
|
+
}
|
|
6710
|
+
const point = projectPosition(viewer.scene, corner);
|
|
6711
|
+
if (!Number.isFinite(point === null || point === void 0 ? void 0 : point.x) || !Number.isFinite(point === null || point === void 0 ? void 0 : point.y)) {
|
|
6712
|
+
return null;
|
|
6713
|
+
}
|
|
6714
|
+
points.push({ x: point.x, y: point.y });
|
|
6715
|
+
}
|
|
6716
|
+
return points;
|
|
6717
|
+
}
|
|
6718
|
+
/*
|
|
6719
|
+
* Whether two convex outlines come within a margin of each other, by the separating axis test.
|
|
6720
|
+
*/
|
|
6721
|
+
function HullsNear(a, b, margin) {
|
|
6722
|
+
for (const hull of [a, b]) {
|
|
6723
|
+
for (let i = 0; i < hull.length; i++) {
|
|
6724
|
+
const from = hull[i], to = hull[(i + 1) % hull.length];
|
|
6725
|
+
const length = Math.hypot(to.x - from.x, to.y - from.y);
|
|
6726
|
+
if (length < 1e-9) {
|
|
6727
|
+
continue;
|
|
6728
|
+
}
|
|
6729
|
+
const normal = { x: (from.y - to.y) / length, y: (to.x - from.x) / length };
|
|
6730
|
+
const project = (points) => points.map(point => point.x * normal.x + point.y * normal.y);
|
|
6731
|
+
const first = project(a), second = project(b);
|
|
6732
|
+
if (Math.max(Math.min(...second) - Math.max(...first), Math.min(...first) - Math.max(...second)) >= margin) {
|
|
6733
|
+
return false;
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
6737
|
+
return true;
|
|
6738
|
+
}
|
|
6739
|
+
EntityLabel.HullsNear = HullsNear;
|
|
6740
|
+
/*
|
|
6741
|
+
* Decides which labels' model boxes are drawn and how, so a multi-selection does not stack box over box.
|
|
6742
|
+
*/
|
|
6743
|
+
class ModelBoxes {
|
|
6744
|
+
constructor() {
|
|
6745
|
+
// Whether a box competes for space comes only from inputs (camera distance, hover, the pointer), never from
|
|
6746
|
+
// how its label was laid out, so an animation moving a label cannot feed back into which boxes show.
|
|
6747
|
+
this.entries = new Map();
|
|
6748
|
+
this.groups = null;
|
|
6749
|
+
// The hovered Entity. Its box is drawn on its own and whole, ahead of every other box.
|
|
6750
|
+
this.hovered = null;
|
|
6751
|
+
this.outlines = new Map();
|
|
6752
|
+
this.hulls = new Map();
|
|
6753
|
+
this.frames = new Map();
|
|
6754
|
+
this.drawn = [];
|
|
6755
|
+
// When each label's box started showing; the longer it has shown, the stronger its claim on the space.
|
|
6756
|
+
this.since = new Map();
|
|
6757
|
+
this.requests = new Map();
|
|
6758
|
+
this.looks = new Map();
|
|
6759
|
+
this.memberLooks = new Map();
|
|
6760
|
+
}
|
|
6761
|
+
Set(id, corners, entityId, inputs) {
|
|
6762
|
+
const current = this.entries.get(id);
|
|
6763
|
+
// A label rebuilt over the same box keeps its place and how far it has animated.
|
|
6764
|
+
if (corners && (current === null || current === void 0 ? void 0 : current.corners) === corners && current.entityId === entityId) {
|
|
6765
|
+
Object.assign(current, inputs);
|
|
6766
|
+
return;
|
|
6767
|
+
}
|
|
6768
|
+
if (!corners && !current) {
|
|
6769
|
+
return;
|
|
6770
|
+
}
|
|
6771
|
+
if (corners) {
|
|
6772
|
+
this.entries.set(id, { ...inputs, corners, entityId, active: false, wanted: false });
|
|
6773
|
+
}
|
|
6774
|
+
else
|
|
6775
|
+
this.entries.delete(id);
|
|
6776
|
+
this.since.delete(id);
|
|
6777
|
+
this.groups = null;
|
|
6778
|
+
for (const [key, group] of this.outlines) {
|
|
6779
|
+
if (group.members.includes(id))
|
|
6780
|
+
this.outlines.delete(key);
|
|
6781
|
+
}
|
|
6782
|
+
}
|
|
6783
|
+
/*
|
|
6784
|
+
* The box drawn for a label this frame, or undefined when it has none or lost its place.
|
|
6785
|
+
*/
|
|
6786
|
+
Frame(id) {
|
|
6787
|
+
return this.frames.get(id);
|
|
6788
|
+
}
|
|
6789
|
+
/*
|
|
6790
|
+
* The label's own box on screen whether or not it is drawn, so hovering the model can ask for it.
|
|
6791
|
+
*/
|
|
6792
|
+
Hull(id) {
|
|
6793
|
+
return this.hulls.get(id);
|
|
6794
|
+
}
|
|
6795
|
+
Request(id, request) {
|
|
6796
|
+
this.requests.set(id, request);
|
|
6797
|
+
}
|
|
6798
|
+
group() {
|
|
6799
|
+
const ids = Array.from(this.entries.keys()).filter(id => this.entries.get(id).wanted).sort();
|
|
6800
|
+
if (!ids.length) {
|
|
6801
|
+
return [];
|
|
6802
|
+
}
|
|
6803
|
+
const origin = this.entries.get(ids[0]).corners[0];
|
|
6804
|
+
const solids = new Map(ids.map(id => [id, ModelSolid(this.entries.get(id).corners, origin)]));
|
|
6805
|
+
const parent = new Map(ids.map(id => [id, id]));
|
|
6806
|
+
const root = (id) => parent.get(id) === id ? id : root(parent.get(id));
|
|
6807
|
+
for (let i = 0; i < ids.length; i++) {
|
|
6808
|
+
const a = solids.get(ids[i]);
|
|
6809
|
+
for (let j = i + 1; j < ids.length; j++) {
|
|
6810
|
+
const b = solids.get(ids[j]);
|
|
6811
|
+
if ((this.entries.get(ids[i]).entityId === this.hovered) !== (this.entries.get(ids[j]).entityId === this.hovered)) {
|
|
6812
|
+
continue;
|
|
6813
|
+
}
|
|
6814
|
+
// Cheap sphere check first; most pairs in a scene are nowhere near each other.
|
|
6815
|
+
if (vLength(vSub(a.centre, b.centre)) > vLength(a.half) + vLength(b.half) || !ModelSolidsTouch(a, b)) {
|
|
6816
|
+
continue;
|
|
6817
|
+
}
|
|
6818
|
+
parent.set(root(ids[j]), root(ids[i]));
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
const members = new Map();
|
|
6822
|
+
for (const id of ids) {
|
|
6823
|
+
const key = root(id);
|
|
6824
|
+
if (!members.has(key))
|
|
6825
|
+
members.set(key, []);
|
|
6826
|
+
members.get(key).push(id);
|
|
6827
|
+
}
|
|
6828
|
+
const groups = Array.from(members.values()).map(list => {
|
|
6829
|
+
const key = list.join("|");
|
|
6830
|
+
let group = this.outlines.get(key);
|
|
6831
|
+
if (!group) {
|
|
6832
|
+
const groupOrigin = this.entries.get(list[0]).corners[0];
|
|
6833
|
+
const parts = list.map(id => ModelSolid(this.entries.get(id).corners, groupOrigin));
|
|
6834
|
+
const solidsShown = parts.length > MODEL_BOX_UNION_LIMIT ? [encloseSolids(parts)] : parts;
|
|
6835
|
+
group = { key, members: list, hovered: false, origin: groupOrigin, solids: solidsShown, outline: ModelBoxOutline(solidsShown) };
|
|
6836
|
+
}
|
|
6837
|
+
group.hovered = this.entries.get(list[0]).entityId === this.hovered;
|
|
6838
|
+
return group;
|
|
6839
|
+
});
|
|
6840
|
+
// Kept across hover changes, so pulling a part out and putting it back does not trace the union again.
|
|
6841
|
+
const kept = Array.from(this.outlines.values()).filter(group => !groups.includes(group)).slice(-8);
|
|
6842
|
+
this.outlines = new Map([...kept, ...groups].map(group => [group.key, group]));
|
|
6843
|
+
return groups;
|
|
6844
|
+
}
|
|
6845
|
+
/*
|
|
6846
|
+
* Projects every box for this frame and settles which outlines are drawn, before labels place their stems.
|
|
6847
|
+
*/
|
|
6848
|
+
Resolve(viewer, pointer) {
|
|
6849
|
+
var _a, _b, _c, _d;
|
|
6850
|
+
this.frames.clear();
|
|
6851
|
+
this.hulls.clear();
|
|
6852
|
+
this.drawn = [];
|
|
6853
|
+
const points = new Map();
|
|
6854
|
+
for (const [id, entry] of this.entries) {
|
|
6855
|
+
const projected = projectCorners(viewer, entry.corners);
|
|
6856
|
+
if (projected) {
|
|
6857
|
+
points.set(id, projected);
|
|
6858
|
+
this.hulls.set(id, convexHull(projected));
|
|
6859
|
+
}
|
|
6860
|
+
}
|
|
6861
|
+
// The application's hover, which is what its temporary label shows, decides first. Without one the
|
|
6862
|
+
// pointer does: the part it is on holds until it leaves, else the smallest box under it wins.
|
|
6863
|
+
const under = Array.from(this.hulls.entries()).filter(([, hull]) => pointer && hullContains(hull, pointer));
|
|
6864
|
+
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);
|
|
6865
|
+
if (!hovered && under.length) {
|
|
6866
|
+
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;
|
|
6867
|
+
}
|
|
6868
|
+
if (hovered !== this.hovered) {
|
|
6869
|
+
this.hovered = hovered;
|
|
6870
|
+
this.groups = null;
|
|
6871
|
+
}
|
|
6872
|
+
for (const [id, entry] of this.entries) {
|
|
6873
|
+
const hull = this.hulls.get(id);
|
|
6874
|
+
entry.active = Boolean(((_c = entry.near) === null || _c === void 0 ? void 0 : _c.call(entry)) || entry.entityId === hovered || (pointer && hull && hullContains(hull, pointer)));
|
|
6875
|
+
// A box keeps its place while it fades out, and leaves the competition once it has.
|
|
6876
|
+
const wanted = entry.active || ((_d = this.memberLooks.get(id)) === null || _d === void 0 ? void 0 : _d.shown) > 0.01;
|
|
6877
|
+
if (wanted !== entry.wanted) {
|
|
6878
|
+
entry.wanted = wanted;
|
|
6879
|
+
this.groups = null;
|
|
6880
|
+
}
|
|
6881
|
+
}
|
|
6882
|
+
if (!this.groups) {
|
|
6883
|
+
this.groups = this.group();
|
|
6884
|
+
}
|
|
6885
|
+
const canvas = viewer.canvas;
|
|
6886
|
+
const candidates = [];
|
|
6887
|
+
for (const group of this.groups) {
|
|
6888
|
+
if (!group.members.every(id => points.has(id))) {
|
|
6889
|
+
continue;
|
|
6890
|
+
}
|
|
6891
|
+
const all = group.members.flatMap(id => points.get(id));
|
|
6892
|
+
const xs = all.map(point => point.x), ys = all.map(point => point.y);
|
|
6893
|
+
const width = Math.max(...xs) - Math.min(...xs), height = Math.max(...ys) - Math.min(...ys);
|
|
6894
|
+
if (Math.max(width, height) < MODEL_BOX_MIN_SIZE || width > canvas.clientWidth * 2 || height > canvas.clientHeight * 2) {
|
|
6895
|
+
continue;
|
|
6896
|
+
}
|
|
6897
|
+
const since = Math.min(...group.members.map(id => { var _a; return (_a = this.since.get(id)) !== null && _a !== void 0 ? _a : Infinity; }));
|
|
6898
|
+
const outline = convexHull(all);
|
|
6899
|
+
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) });
|
|
6900
|
+
}
|
|
6901
|
+
// Between boxes appearing together the larger on screen goes first, as the one the view is about.
|
|
6902
|
+
// What the user points at now outranks what they selected before it.
|
|
6903
|
+
candidates.sort((a, b) => (Number(b.group.hovered) - Number(a.group.hovered) ||
|
|
6904
|
+
Number(b.selected) - Number(a.selected) ||
|
|
6905
|
+
a.since - b.since ||
|
|
6906
|
+
b.area - a.area ||
|
|
6907
|
+
a.group.key.localeCompare(b.group.key)));
|
|
6908
|
+
const transforms = SceneTransforms;
|
|
6909
|
+
const projectPosition = transforms.wgs84ToWindowCoordinates || transforms.worldToWindowCoordinates;
|
|
6910
|
+
const scratch = new Cartesian3();
|
|
6911
|
+
const now = Date.now();
|
|
6912
|
+
const shown = new Set();
|
|
6913
|
+
for (const candidate of candidates) {
|
|
6914
|
+
// A showing box holds its place until truly overlapped; a waiting one needs clear space.
|
|
6915
|
+
const margin = Number.isFinite(candidate.since) ? 0 : MODEL_BOX_CLEARANCE;
|
|
6916
|
+
if (this.drawn.some(other => HullsNear(candidate.outline, other.outline, margin))) {
|
|
6917
|
+
continue;
|
|
6918
|
+
}
|
|
6919
|
+
const { origin } = candidate.group;
|
|
6920
|
+
const toScreen = (point) => {
|
|
6921
|
+
const projected = projectPosition(viewer.scene, Cartesian3.fromElements(origin.x + point[0], origin.y + point[1], origin.z + point[2], scratch));
|
|
6922
|
+
return { x: projected.x, y: projected.y };
|
|
6923
|
+
};
|
|
6924
|
+
const edges = candidate.group.outline.map(([start, end]) => [toScreen(start), toScreen(end)]);
|
|
6925
|
+
const drawn = { ...candidate, edges, landings: [] };
|
|
6926
|
+
this.drawn.push(drawn);
|
|
6927
|
+
for (const id of candidate.group.members) {
|
|
6928
|
+
shown.add(id);
|
|
6929
|
+
if (!this.since.has(id))
|
|
6930
|
+
this.since.set(id, now);
|
|
6931
|
+
const own = points.get(id);
|
|
6932
|
+
// The silhouette's top is the highest drawn edge at that x, so the stem always meets a line.
|
|
6933
|
+
const x = (Math.min(...own.map(point => point.x)) + Math.max(...own.map(point => point.x))) / 2;
|
|
6934
|
+
let anchor = null, edge = -1;
|
|
6935
|
+
edges.forEach(([a, b], i) => {
|
|
6936
|
+
if (x < Math.min(a.x, b.x) || x > Math.max(a.x, b.x) || Math.abs(b.x - a.x) < 1e-6) {
|
|
6937
|
+
return;
|
|
6938
|
+
}
|
|
6939
|
+
const y = a.y + (b.y - a.y) * (x - a.x) / (b.x - a.x);
|
|
6940
|
+
if (!anchor || y < anchor.y) {
|
|
6941
|
+
anchor = { x, y };
|
|
6942
|
+
edge = i;
|
|
6943
|
+
}
|
|
6944
|
+
});
|
|
6945
|
+
anchor = anchor || { x, y: hullTop(candidate.outline, x) };
|
|
6946
|
+
if (edge >= 0)
|
|
6947
|
+
drawn.landings.push({ edge, point: anchor });
|
|
6948
|
+
this.frames.set(id, { points: own, hull: this.hulls.get(id), outline: candidate.outline, anchor, group: candidate.group.key });
|
|
6949
|
+
}
|
|
6950
|
+
}
|
|
6951
|
+
for (const id of Array.from(this.since.keys())) {
|
|
6952
|
+
if (!shown.has(id))
|
|
6953
|
+
this.since.delete(id);
|
|
6954
|
+
}
|
|
6955
|
+
this.requests.clear();
|
|
6956
|
+
}
|
|
6957
|
+
/*
|
|
6958
|
+
* Draws each settled outline as corner brackets that grow into whole edges, back edges dashed.
|
|
6959
|
+
*/
|
|
6960
|
+
Draw(viewer, element, coordinates) {
|
|
6961
|
+
var _a, _b, _c;
|
|
6962
|
+
const now = Date.now();
|
|
6963
|
+
const looks = new Map();
|
|
6964
|
+
const memberLooks = new Map();
|
|
6965
|
+
let animating = false;
|
|
6966
|
+
for (const { group } of this.drawn) {
|
|
6967
|
+
const requests = group.members.map(id => this.requests.get(id)).filter(Boolean);
|
|
6968
|
+
if (!requests.length) {
|
|
6969
|
+
// Its labels are hidden this frame, perhaps off screen; it keeps its look so it does not regrow.
|
|
6970
|
+
const kept = this.looks.get(group.key);
|
|
6971
|
+
if (kept) {
|
|
6972
|
+
looks.set(group.key, kept);
|
|
6973
|
+
group.members.forEach(id => memberLooks.set(id, kept));
|
|
6974
|
+
if (kept.svg)
|
|
6975
|
+
kept.svg.style.display = "none";
|
|
6976
|
+
}
|
|
6977
|
+
continue;
|
|
6978
|
+
}
|
|
6979
|
+
// A group that gained or lost a member carries on from where its members' outline was.
|
|
6980
|
+
const previous = group.members.map(id => this.memberLooks.get(id)).filter(Boolean);
|
|
6981
|
+
const look = (_a = this.looks.get(group.key)) !== null && _a !== void 0 ? _a : {
|
|
6982
|
+
shown: Math.max(0, ...previous.map(item => item.shown)), whole: Math.max(0, ...previous.map(item => item.whole)),
|
|
6983
|
+
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
|
|
6984
|
+
};
|
|
6985
|
+
const elapsed = Math.min(32, Math.max(0, now - look.time));
|
|
6986
|
+
const duration = Math.min(...requests.map(request => request.duration));
|
|
6987
|
+
const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
|
|
6988
|
+
const approach = (value, to) => Math.abs(value - to) < 0.001 ? to : value + (to - value) * blend;
|
|
6989
|
+
const shownTarget = group.members.some(id => { var _a; return (_a = this.entries.get(id)) === null || _a === void 0 ? void 0 : _a.active; }) ? 1 : 0;
|
|
6990
|
+
const wholeTarget = group.hovered || requests.some(request => request.whole) ? 1 : 0;
|
|
6991
|
+
look.shown = approach(look.shown, shownTarget);
|
|
6992
|
+
look.whole = approach(look.whole, wholeTarget);
|
|
6993
|
+
look.grow = approach(look.grow, 1);
|
|
6994
|
+
look.time = now;
|
|
6995
|
+
animating = animating || look.shown !== shownTarget || look.whole !== wholeTarget || look.grow !== 1;
|
|
6996
|
+
looks.set(group.key, look);
|
|
6997
|
+
group.members.forEach(id => memberLooks.set(id, look));
|
|
6998
|
+
if (look.svg && this.looks.get(group.key) !== look) {
|
|
6999
|
+
// Inherited from an earlier group; claim it so that group's cleanup leaves it alone.
|
|
7000
|
+
for (const [key, other] of this.looks) {
|
|
7001
|
+
if (other.svg === look.svg && key !== group.key)
|
|
7002
|
+
other.svg = undefined;
|
|
7003
|
+
}
|
|
7004
|
+
}
|
|
7005
|
+
this.drawGroup(viewer, element, coordinates, this.drawn.find(item => item.group === group), look, requests);
|
|
7006
|
+
}
|
|
7007
|
+
for (const [key, look] of this.looks) {
|
|
7008
|
+
if (!looks.has(key))
|
|
7009
|
+
(_c = look.svg) === null || _c === void 0 ? void 0 : _c.remove();
|
|
7010
|
+
}
|
|
7011
|
+
this.looks = looks;
|
|
7012
|
+
this.memberLooks = memberLooks;
|
|
7013
|
+
if (animating) {
|
|
7014
|
+
viewer.scene.requestRender();
|
|
7015
|
+
}
|
|
7016
|
+
}
|
|
7017
|
+
drawGroup(viewer, element, coordinates, drawn, look, requests) {
|
|
7018
|
+
const group = drawn.group;
|
|
7019
|
+
let svg = look.svg;
|
|
7020
|
+
if (!svg) {
|
|
7021
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
7022
|
+
svg = document.createElementNS(ns, "svg");
|
|
7023
|
+
svg.setAttribute("class", "entity-label-model-box");
|
|
7024
|
+
svg.setAttribute("aria-hidden", "true");
|
|
7025
|
+
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));";
|
|
7026
|
+
for (const edges of ["back", "front"]) {
|
|
7027
|
+
const path = document.createElementNS(ns, "path");
|
|
7028
|
+
path.setAttribute("fill", "none");
|
|
7029
|
+
path.setAttribute("stroke-linecap", "round");
|
|
7030
|
+
path.dataset.edges = edges;
|
|
7031
|
+
svg.appendChild(path);
|
|
7032
|
+
}
|
|
7033
|
+
look.svg = svg;
|
|
7034
|
+
}
|
|
7035
|
+
if (svg.parentElement !== element) {
|
|
7036
|
+
element.appendChild(svg);
|
|
7037
|
+
}
|
|
7038
|
+
svg.style.display = "";
|
|
7039
|
+
const transforms = SceneTransforms;
|
|
7040
|
+
const projectPosition = transforms.wgs84ToWindowCoordinates || transforms.worldToWindowCoordinates;
|
|
7041
|
+
const origin = group.origin;
|
|
7042
|
+
const scratch = new Cartesian3();
|
|
7043
|
+
// A new box grows out of the ring it replaces.
|
|
7044
|
+
const grown = look.grow * look.grow * (3 - 2 * look.grow);
|
|
7045
|
+
const from = requests[0].from;
|
|
7046
|
+
const at = (point) => `${(from.x + (point.x - from.x) * grown) * coordinates.scaleX} ${(from.y + (point.y - from.y) * grown) * coordinates.scaleY}`;
|
|
7047
|
+
const toScreen = (point) => {
|
|
7048
|
+
const projected = projectPosition(viewer.scene, Cartesian3.fromElements(origin.x + point[0], origin.y + point[1], origin.z + point[2], scratch));
|
|
7049
|
+
return at(projected);
|
|
7050
|
+
};
|
|
7051
|
+
const camera = viewer.camera.positionWC || viewer.camera.position;
|
|
7052
|
+
const eye = [camera.x - origin.x, camera.y - origin.y, camera.z - origin.z];
|
|
7053
|
+
const span = Math.max(...group.solids.map(solid => vLength(solid.half)));
|
|
7054
|
+
const inset = Math.max(1, span) * 1e-6;
|
|
7055
|
+
const fraction = MODEL_BOX_BRACKET + (0.5 - MODEL_BOX_BRACKET) * look.whole;
|
|
7056
|
+
const parts = fraction >= 0.499 ? [[0, 1]] : [[0, fraction], [1 - fraction, 1]];
|
|
7057
|
+
const paths = { front: "", back: "" };
|
|
7058
|
+
for (const [start, end] of group.outline) {
|
|
7059
|
+
const delta = vSub(end, start);
|
|
7060
|
+
// Sample visibility along the edge, since another member can hide part of it.
|
|
7061
|
+
const steps = group.solids.length > 1 ? Math.min(24, Math.max(2, Math.ceil(vLength(delta) / span * 12))) : 1;
|
|
7062
|
+
const visible = [];
|
|
7063
|
+
for (let i = 0; i < steps; i++) {
|
|
7064
|
+
visible.push(!solidsHide(group.solids, eye, vAdd(start, vScale(delta, (i + 0.5) / steps)), inset));
|
|
7065
|
+
}
|
|
7066
|
+
for (const [from, to] of parts) {
|
|
7067
|
+
let runStart = from;
|
|
7068
|
+
for (let i = Math.floor(from * steps); i < steps && i / steps < to; i++) {
|
|
7069
|
+
const runEnd = Math.min(to, (i + 1) / steps);
|
|
7070
|
+
if (runEnd < to && visible[i + 1] === visible[i]) {
|
|
7071
|
+
continue;
|
|
7072
|
+
}
|
|
7073
|
+
paths[visible[i] ? "front" : "back"] += `M${toScreen(vAdd(start, vScale(delta, runStart)))}L${toScreen(vAdd(start, vScale(delta, runEnd)))}`;
|
|
7074
|
+
runStart = runEnd;
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
}
|
|
7078
|
+
// Each stem lands on an edge, which brackets may leave bare, so a short piece of it is drawn there.
|
|
7079
|
+
if (fraction < 0.499) {
|
|
7080
|
+
for (const { edge, point } of drawn.landings) {
|
|
7081
|
+
const [a, b] = drawn.edges[edge];
|
|
7082
|
+
const length = Math.hypot(b.x - a.x, b.y - a.y);
|
|
7083
|
+
const reach = Math.min(MODEL_BOX_LANDING, length / 2);
|
|
7084
|
+
const toward = (sign) => {
|
|
7085
|
+
const along = Math.max(0, Math.min(length, Math.hypot(point.x - a.x, point.y - a.y) + sign * reach));
|
|
7086
|
+
return { x: a.x + (b.x - a.x) * along / length, y: a.y + (b.y - a.y) * along / length };
|
|
7087
|
+
};
|
|
7088
|
+
paths.front += `M${at(toward(-1))}L${at(toward(1))}`;
|
|
7089
|
+
}
|
|
7090
|
+
}
|
|
7091
|
+
svg.style.left = `${coordinates.x}px`;
|
|
7092
|
+
svg.style.top = `${coordinates.y}px`;
|
|
7093
|
+
svg.style.opacity = String(Math.max(...requests.map(request => request.opacity)) * look.shown * Math.min(1, look.grow * 2));
|
|
7094
|
+
svg.style.zIndex = String(Math.max(...requests.map(request => request.zIndex)));
|
|
7095
|
+
const color = requests[0].color;
|
|
7096
|
+
for (const path of Array.from(svg.children)) {
|
|
7097
|
+
const front = path.dataset.edges === "front";
|
|
7098
|
+
path.setAttribute("d", paths[front ? "front" : "back"]);
|
|
7099
|
+
path.setAttribute("stroke", color);
|
|
7100
|
+
path.setAttribute("stroke-width", front ? "1.5" : "1");
|
|
7101
|
+
path.setAttribute("stroke-opacity", front ? "1" : "0.35");
|
|
7102
|
+
path.setAttribute("stroke-dasharray", front ? "" : "3 3");
|
|
7103
|
+
}
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
7106
|
+
EntityLabel.ModelBoxes = ModelBoxes;
|
|
7107
|
+
/*
|
|
7108
|
+
* Andrew's monotone chain, counter-clockwise in screen space.
|
|
7109
|
+
*/
|
|
7110
|
+
function convexHull(points) {
|
|
7111
|
+
const sorted = [...points].sort((a, b) => a.x - b.x || a.y - b.y);
|
|
7112
|
+
const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
7113
|
+
const build = (list) => {
|
|
7114
|
+
const chain = [];
|
|
7115
|
+
for (const point of list) {
|
|
7116
|
+
while (chain.length >= 2 && cross(chain[chain.length - 2], chain[chain.length - 1], point) <= 0)
|
|
7117
|
+
chain.pop();
|
|
7118
|
+
chain.push(point);
|
|
7119
|
+
}
|
|
7120
|
+
chain.pop();
|
|
7121
|
+
return chain;
|
|
7122
|
+
};
|
|
7123
|
+
return [...build(sorted), ...build(sorted.reverse())];
|
|
7124
|
+
}
|
|
7125
|
+
/*
|
|
7126
|
+
* Where the top of a convex outline crosses a vertical line, so a stem leaves the box without jumping between corners.
|
|
7127
|
+
*/
|
|
7128
|
+
function hullTop(hull, x) {
|
|
7129
|
+
let top = Infinity;
|
|
7130
|
+
for (let i = 0; i < hull.length; i++) {
|
|
7131
|
+
const a = hull[i];
|
|
7132
|
+
const b = hull[(i + 1) % hull.length];
|
|
7133
|
+
if (x < Math.min(a.x, b.x) || x > Math.max(a.x, b.x)) {
|
|
7134
|
+
continue;
|
|
7135
|
+
}
|
|
7136
|
+
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));
|
|
7137
|
+
}
|
|
7138
|
+
return top;
|
|
7139
|
+
}
|
|
7140
|
+
function hullArea(hull) {
|
|
7141
|
+
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;
|
|
7142
|
+
}
|
|
7143
|
+
function hullContains(hull, point) {
|
|
7144
|
+
return hull.length >= 3 && hull.every((a, i) => {
|
|
7145
|
+
const b = hull[(i + 1) % hull.length];
|
|
7146
|
+
return (b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x) >= 0;
|
|
7147
|
+
});
|
|
7148
|
+
}
|
|
6383
7149
|
class Label {
|
|
6384
7150
|
get HasText() { return this.options.presentation !== "icon"; }
|
|
6385
7151
|
SetHoverVisible(visible) {
|
|
@@ -6434,6 +7200,11 @@ var EntityLabel;
|
|
|
6434
7200
|
this.Transient = false;
|
|
6435
7201
|
this.hoverFade = { from: 0, to: 1, start: Date.now() };
|
|
6436
7202
|
this.cursorMotion = { amount: 0, from: 0, target: 0, start: 0, position: undefined };
|
|
7203
|
+
// How far the stem is from the ring onto a model box, and the offset it eases out after changing outline.
|
|
7204
|
+
// The record's box for the Entity this label is on: undefined corners while reading, null when it has none.
|
|
7205
|
+
this.modelBox = null;
|
|
7206
|
+
this.boxMorph = { value: 0, from: 0, target: 0, start: 0, anchor: undefined, group: undefined,
|
|
7207
|
+
shift: { x: 0, y: 0, start: 0 } };
|
|
6437
7208
|
// Counter to help differentiate between createLabel calls.
|
|
6438
7209
|
this.counter = 0;
|
|
6439
7210
|
// Whether this label has asked its group for a default image, which it does once on screen.
|
|
@@ -6519,11 +7290,13 @@ var EntityLabel;
|
|
|
6519
7290
|
* Recalling will be ignored.
|
|
6520
7291
|
*/
|
|
6521
7292
|
Dispose() {
|
|
7293
|
+
var _a;
|
|
6522
7294
|
if (this.disposed) {
|
|
6523
7295
|
return;
|
|
6524
7296
|
}
|
|
6525
7297
|
this.disposed = true;
|
|
6526
7298
|
this.removeLabel();
|
|
7299
|
+
(_a = layers.get(this.viewer)) === null || _a === void 0 ? void 0 : _a.modelBoxes.Set(this.id, null, null, null);
|
|
6527
7300
|
clearTimeout(this.loadTimeout);
|
|
6528
7301
|
this.loadTimeout = null;
|
|
6529
7302
|
}
|
|
@@ -6538,14 +7311,15 @@ var EntityLabel;
|
|
|
6538
7311
|
this.groupId = groupId;
|
|
6539
7312
|
this.additionalTexts = this.options.text != null ? [] : null;
|
|
6540
7313
|
this.photo = null;
|
|
6541
|
-
|
|
7314
|
+
// A label given its position keeps it, or the rebuild goes looking for the entity's own.
|
|
7315
|
+
this.createLabel(this.prePositioned ? this._pos3d : undefined);
|
|
6542
7316
|
}
|
|
6543
7317
|
/**
|
|
6544
7318
|
* Creates cesium entity for the label if one does not exist.
|
|
6545
7319
|
* @param starterPos3d optional position to create label at. Ignored for billboard labels.
|
|
6546
7320
|
*/
|
|
6547
7321
|
async createLabel(starterPos3d) {
|
|
6548
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
7322
|
+
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;
|
|
6549
7323
|
// The old label stays until its replacement is built, so a rebuild never blinks while a location loads.
|
|
6550
7324
|
let pos3d = starterPos3d ? starterPos3d.clone() : null;
|
|
6551
7325
|
const counter = ++this.counter;
|
|
@@ -6831,6 +7605,9 @@ var EntityLabel;
|
|
|
6831
7605
|
if (this.disposed || this.counter != counter) {
|
|
6832
7606
|
return;
|
|
6833
7607
|
}
|
|
7608
|
+
// Leave the layer only once rejoined, so a rebuild never empties it and closes it along with its boxes.
|
|
7609
|
+
const leaveLayer = this.renderRemoval;
|
|
7610
|
+
this.renderRemoval = null;
|
|
6834
7611
|
this.removeLabel();
|
|
6835
7612
|
// A found photo draws as if configured, without changing the options clustering compares and measures.
|
|
6836
7613
|
const photo = HasIcon(this.options) ? null : (_j = this.options.photo) !== null && _j !== void 0 ? _j : this.photo;
|
|
@@ -7127,9 +7904,65 @@ var EntityLabel;
|
|
|
7127
7904
|
radius: Math.max(width, height) / 2
|
|
7128
7905
|
};
|
|
7129
7906
|
};
|
|
7907
|
+
// Only CAD tilesets and model Entities are assembly parts with a geometry box; anything else stays a point.
|
|
7908
|
+
const boxable = Boolean(normalLeader && ((_r = this.rego) === null || _r === void 0 ? void 0 : _r.entityId) && ((visual instanceof Cesium3DTileFeature && this.rego.tilesetType === Tileset.EType.Cad) ||
|
|
7909
|
+
(visual instanceof Entity && Boolean(visual.model))));
|
|
7910
|
+
if (!boxable || ((_s = this.modelBox) === null || _s === void 0 ? void 0 : _s.entityId) !== ((_t = this.rego) === null || _t === void 0 ? void 0 : _t.entityId)) {
|
|
7911
|
+
layer.modelBoxes.Set(this.id, null, null, null);
|
|
7912
|
+
this.modelBox = null;
|
|
7913
|
+
}
|
|
7914
|
+
// Read once per Entity, so a label rebuilt for its loaded text keeps its box instead of waiting again.
|
|
7915
|
+
if (boxable && !this.modelBox) {
|
|
7916
|
+
const box = { entityId: this.rego.entityId, corners: undefined, load: null };
|
|
7917
|
+
box.load = this.loadModelBox().catch(() => null).then(corners => box.corners = corners || null);
|
|
7918
|
+
this.modelBox = box;
|
|
7919
|
+
}
|
|
7920
|
+
// A model's label stays hidden until its box is known, so a ring never shows only to turn into a box.
|
|
7921
|
+
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;
|
|
7922
|
+
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);
|
|
7923
|
+
const boxPendingUntil = Date.now() + MODEL_BOX_WAIT;
|
|
7924
|
+
// Whether this label has been on screen yet; a box found before then is drawn in place, not eased into.
|
|
7925
|
+
let shownOnce = false;
|
|
7926
|
+
if (boxable) {
|
|
7927
|
+
const found = this._pos3d;
|
|
7928
|
+
const settle = () => {
|
|
7929
|
+
if (boxPending && !this.disposed && this.counter === counter) {
|
|
7930
|
+
boxPending = false;
|
|
7931
|
+
this.viewer.scene.requestRender();
|
|
7932
|
+
}
|
|
7933
|
+
};
|
|
7934
|
+
// Past the wait a slow record no longer holds the label back; a box that comes later eases in.
|
|
7935
|
+
setTimeout(settle, MODEL_BOX_WAIT);
|
|
7936
|
+
this.modelBox.load.then(corners => {
|
|
7937
|
+
if (!corners || this.disposed || this.counter !== counter) {
|
|
7938
|
+
return;
|
|
7939
|
+
}
|
|
7940
|
+
// A record that disagrees with where the model was found describes another placement, such as a moved tileset.
|
|
7941
|
+
const centre = Cartesian3.midpoint(corners[0], corners[7], new Cartesian3());
|
|
7942
|
+
if (Cartesian3.distance(centre, found) > Cartesian3.distance(corners[0], corners[7]) * 0.75 + 2) {
|
|
7943
|
+
return;
|
|
7944
|
+
}
|
|
7945
|
+
const rego = this.rego;
|
|
7946
|
+
// Let go a little further out than it is taken, so a camera resting at the limit does not flip it.
|
|
7947
|
+
let near = false;
|
|
7948
|
+
layer.modelBoxes.Set(this.id, corners, rego.entityId, {
|
|
7949
|
+
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); },
|
|
7950
|
+
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)); },
|
|
7951
|
+
near: () => {
|
|
7952
|
+
var _a, _b;
|
|
7953
|
+
const ring = this.options.anchorRing;
|
|
7954
|
+
const camera = this.viewer.camera.positionWC || this.viewer.camera.position;
|
|
7955
|
+
const limit = ((_a = ring === null || ring === void 0 ? void 0 : ring.nearDistance) !== null && _a !== void 0 ? _a : 100) * (near ? 1.1 : 1);
|
|
7956
|
+
near = (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && Number.isFinite((_b = this._pos3d) === null || _b === void 0 ? void 0 : _b.x) && Cartesian3.distance(camera, this._pos3d) <= limit;
|
|
7957
|
+
return near || ((ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && Boolean(this.cursorMotion.target));
|
|
7958
|
+
}
|
|
7959
|
+
});
|
|
7960
|
+
this.viewer.scene.requestRender();
|
|
7961
|
+
}).finally(settle);
|
|
7962
|
+
}
|
|
7130
7963
|
let cursorMotion = this.cursorMotion;
|
|
7131
7964
|
const project = () => {
|
|
7132
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
7965
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
|
|
7133
7966
|
if (this.disposed || !this.viewer || this.viewer.isDestroyed() || ((_a = this.visible) === null || _a === void 0 ? void 0 : _a.call(this)) === false ||
|
|
7134
7967
|
(presentation === "icon" && this.options.iconScale === 0)) {
|
|
7135
7968
|
return null;
|
|
@@ -7167,18 +8000,29 @@ var EntityLabel;
|
|
|
7167
8000
|
if (!Number.isFinite(anchor === null || anchor === void 0 ? void 0 : anchor.x) || !Number.isFinite(anchor === null || anchor === void 0 ? void 0 : anchor.y)) {
|
|
7168
8001
|
return null;
|
|
7169
8002
|
}
|
|
8003
|
+
const boxed = layer.modelBoxes.Frame(this.id);
|
|
8004
|
+
const waiting = boxPending && Date.now() < boxPendingUntil;
|
|
8005
|
+
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);
|
|
8006
|
+
// Eased between the ring and where the stem meets the box, so gaining or losing a box never jumps.
|
|
8007
|
+
const boxTarget = this.boxMorph.anchor;
|
|
8008
|
+
const ringPoint = { x: anchor.x + pixelOffset.x, y: anchor.y + pixelOffset.y };
|
|
8009
|
+
const boxAnchor = boxBlend > 0 && boxTarget ? {
|
|
8010
|
+
x: ringPoint.x + (boxTarget.x - ringPoint.x) * boxBlend,
|
|
8011
|
+
y: ringPoint.y + (boxTarget.y - ringPoint.y) * boxBlend
|
|
8012
|
+
} : undefined;
|
|
8013
|
+
const start = boxAnchor || anchor;
|
|
7170
8014
|
const onScreen = anchor.x >= 0 && anchor.y >= 0 && anchor.x < this.viewer.canvas.clientWidth && anchor.y < this.viewer.canvas.clientHeight;
|
|
7171
|
-
if (leader &&
|
|
8015
|
+
if (leader && (start.x < 0 || start.y < 0 || start.x >= this.viewer.canvas.clientWidth || start.y >= this.viewer.canvas.clientHeight))
|
|
7172
8016
|
return null;
|
|
7173
8017
|
if (onScreen && !this.imageAsked && this.groupId && !HasIcon(this.options) && !this.options.photo) {
|
|
7174
8018
|
this.imageAsked = true;
|
|
7175
|
-
(
|
|
8019
|
+
(_g = this.visualRegister) === null || _g === void 0 ? void 0 : _g.RequestLabelImage((_h = this.rego) === null || _h === void 0 ? void 0 : _h.entityId);
|
|
7176
8020
|
}
|
|
7177
8021
|
const transientPivot = (this.Transient && !this.Persistent ?
|
|
7178
|
-
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (
|
|
8022
|
+
(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);
|
|
7179
8023
|
const hover = (presentation !== "icon" ?
|
|
7180
8024
|
(this.Transient && !this.Persistent && this.hoverFade.to === 0 ? this.retainedHoverPosition :
|
|
7181
|
-
(
|
|
8025
|
+
(_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);
|
|
7182
8026
|
const reused = !this.Transient || this.Persistent;
|
|
7183
8027
|
const now = Date.now();
|
|
7184
8028
|
const animateCursor = () => {
|
|
@@ -7202,12 +8046,14 @@ var EntityLabel;
|
|
|
7202
8046
|
const cursorAmount = cursorMotion.amount;
|
|
7203
8047
|
leader = normalLeader || cursorAmount > 0;
|
|
7204
8048
|
// An icon keeps its callout centred on the glyph rather than following the cursor across it.
|
|
7205
|
-
const icon = measureIcon();
|
|
8049
|
+
const icon = boxAnchor ? null : measureIcon();
|
|
7206
8050
|
const iconRing = icon ? Math.max(ICON_RING_MIN, icon.radius + ICON_RING_PADDING) : 0;
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
8051
|
+
// A boxed model is pinned the same way, with its stem leaving the top of the box outline.
|
|
8052
|
+
const pinned = Boolean(icon || boxAnchor);
|
|
8053
|
+
const pivot = pinned ? undefined : transientPivot;
|
|
8054
|
+
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;
|
|
8055
|
+
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;
|
|
8056
|
+
if (cursorAmount > 0 && !pinned) {
|
|
7211
8057
|
// A leader-less label starts centred on its anchor; preserve that box as it lifts away.
|
|
7212
8058
|
if (!normalLeader) {
|
|
7213
8059
|
x -= ele.offsetWidth / layer.coordinates.scaleX / 2;
|
|
@@ -7242,6 +8088,9 @@ var EntityLabel;
|
|
|
7242
8088
|
if (visual instanceof Entity && (visual.polygon || visual.polyline))
|
|
7243
8089
|
leaderLength /= 3;
|
|
7244
8090
|
}
|
|
8091
|
+
if (boxAnchor && this.options.leaderLength == null) {
|
|
8092
|
+
leaderLength += (MODEL_BOX_STEM - leaderLength) * boxBlend;
|
|
8093
|
+
}
|
|
7245
8094
|
if (cursorAmount > 0) {
|
|
7246
8095
|
leaderLength += (96 - leaderLength) * cursorAmount;
|
|
7247
8096
|
}
|
|
@@ -7249,7 +8098,7 @@ var EntityLabel;
|
|
|
7249
8098
|
leaderLength += iconRing;
|
|
7250
8099
|
y = anchorY - leaderLength;
|
|
7251
8100
|
}
|
|
7252
|
-
else if (hover && !reused && !
|
|
8101
|
+
else if (hover && !reused && !pinned) {
|
|
7253
8102
|
x = hover.x + pixelOffset.x;
|
|
7254
8103
|
y = hover.y + pixelOffset.y - 24;
|
|
7255
8104
|
}
|
|
@@ -7258,7 +8107,7 @@ var EntityLabel;
|
|
|
7258
8107
|
const width = ele.offsetWidth / coordinates.scaleX;
|
|
7259
8108
|
const height = ele.offsetHeight / coordinates.scaleY;
|
|
7260
8109
|
let clusterBounds;
|
|
7261
|
-
if ((
|
|
8110
|
+
if ((_s = this.options.cluster) === null || _s === void 0 ? void 0 : _s.enabled) {
|
|
7262
8111
|
const textWidth = (sourceText === null || sourceText === void 0 ? void 0 : sourceText.offsetWidth) || 0;
|
|
7263
8112
|
const textHeight = (sourceText === null || sourceText === void 0 ? void 0 : sourceText.offsetHeight) || 0;
|
|
7264
8113
|
// A photo is never part of the footprint, so its arrival cannot change which cluster a label joins.
|
|
@@ -7292,7 +8141,7 @@ var EntityLabel;
|
|
|
7292
8141
|
if (!leader && (bounds.x + bounds.width < 0 || bounds.y + bounds.height < 0 || bounds.x > this.viewer.canvas.clientWidth || bounds.y > this.viewer.canvas.clientHeight)) {
|
|
7293
8142
|
return null;
|
|
7294
8143
|
}
|
|
7295
|
-
const state = (
|
|
8144
|
+
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 });
|
|
7296
8145
|
const opacity = this.prePositioned || hoverOnly || MAX_DISTANCE === Infinity ? 1 : 1 - distance / MAX_DISTANCE;
|
|
7297
8146
|
return {
|
|
7298
8147
|
canFlip: leader && presentation !== "icon", hasLeader: leader,
|
|
@@ -7300,24 +8149,25 @@ var EntityLabel;
|
|
|
7300
8149
|
transient: this.Transient || Boolean(hover) || cursorAmount > 0,
|
|
7301
8150
|
extendedGeometry: visual instanceof Entity && Boolean(visual.polygon || visual.polyline || visual.corridor),
|
|
7302
8151
|
viewportWidth: this.viewer.canvas.clientWidth,
|
|
7303
|
-
id: this.id, entityKey: ((
|
|
7304
|
-
entityId: (
|
|
7305
|
-
iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius,
|
|
8152
|
+
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,
|
|
8153
|
+
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,
|
|
8154
|
+
iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius, modelBox: boxed || undefined, boxBlend,
|
|
7306
8155
|
// Priority shows the hovered member individually without dragging its cluster along.
|
|
7307
|
-
opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
|
|
8156
|
+
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
|
|
7308
8157
|
};
|
|
7309
8158
|
};
|
|
7310
8159
|
let displacement = { x: 0, y: 0 };
|
|
7311
8160
|
let nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
|
|
7312
8161
|
const apply = (placement) => {
|
|
7313
|
-
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;
|
|
8162
|
+
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;
|
|
7314
8163
|
const show = (placement === null || placement === void 0 ? void 0 : placement.visible) && placement.opacity > 0;
|
|
8164
|
+
shownOnce = shownOnce || Boolean(show);
|
|
7315
8165
|
clickEntityIds = show ? placement.entityIds : [];
|
|
7316
8166
|
ele.style.visibility = show ? "visible" : "hidden";
|
|
7317
8167
|
lineEle.style.display = show && leader ? "block" : "none";
|
|
7318
8168
|
if (runEle)
|
|
7319
8169
|
runEle.style.display = show && leader ? "block" : "none";
|
|
7320
|
-
this.anchor.style.display = show && leader ? "block" : "none";
|
|
8170
|
+
this.anchor.style.display = show && leader && !(((_a = placement.frame) === null || _a === void 0 ? void 0 : _a.boxBlend) >= 1) ? "block" : "none";
|
|
7321
8171
|
if (!show) {
|
|
7322
8172
|
displacement = { x: 0, y: 0 };
|
|
7323
8173
|
nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
|
|
@@ -7329,7 +8179,7 @@ var EntityLabel;
|
|
|
7329
8179
|
return;
|
|
7330
8180
|
}
|
|
7331
8181
|
// A label standing for a cluster shows its summary only; its photo is put away and never fetched.
|
|
7332
|
-
const solo = !((
|
|
8182
|
+
const solo = !((_b = placement.members) === null || _b === void 0 ? void 0 : _b.length);
|
|
7333
8183
|
if (look.iconPhoto && iconElement && iconElement.dataset.failed !== "true") {
|
|
7334
8184
|
const display = solo ? "inline-flex" : "none";
|
|
7335
8185
|
if (iconElement.style.display !== display) {
|
|
@@ -7347,7 +8197,7 @@ var EntityLabel;
|
|
|
7347
8197
|
}
|
|
7348
8198
|
}, PHOTO_SHOW_DELAY);
|
|
7349
8199
|
}
|
|
7350
|
-
const displayText = (
|
|
8200
|
+
const displayText = (_c = placement.text) !== null && _c !== void 0 ? _c : originalText;
|
|
7351
8201
|
if (textElement && textElement.textContent !== displayText) {
|
|
7352
8202
|
textElement.textContent = displayText;
|
|
7353
8203
|
// Measure the summary's footprint next frame without replacing each member's source text.
|
|
@@ -7371,9 +8221,9 @@ var EntityLabel;
|
|
|
7371
8221
|
const stack = JSON.stringify(placement.icons || []);
|
|
7372
8222
|
if (iconStack && stack !== displayedStack) {
|
|
7373
8223
|
displayedStack = stack;
|
|
7374
|
-
const looks = ((
|
|
8224
|
+
const looks = ((_d = placement.icons) === null || _d === void 0 ? void 0 : _d.length) ? [iconLook(this.options), ...placement.icons] : [];
|
|
7375
8225
|
// One small size for all, so no icon in the arrangement reads as more important than another.
|
|
7376
|
-
const size = (this.options.iconSize || 24) * ((
|
|
8226
|
+
const size = (this.options.iconSize || 24) * ((_e = this.options.iconScale) !== null && _e !== void 0 ? _e : 1) * ICON_STACK_SCALE;
|
|
7377
8227
|
const built = looks.map(look => this.buildIcon({ ...look, iconSize: size }, counter));
|
|
7378
8228
|
// A stack is on show already, so its photos load straight away.
|
|
7379
8229
|
built.forEach(({ start }) => start === null || start === void 0 ? void 0 : start());
|
|
@@ -7408,9 +8258,11 @@ var EntityLabel;
|
|
|
7408
8258
|
// Summary and badge changes must fit on their first visible frame, including after resize.
|
|
7409
8259
|
const ring = this.options.anchorRing;
|
|
7410
8260
|
const pointer = (layer.pointer || (cursorMotion.target ? cursorMotion.position : undefined) || (this.Transient && !this.Persistent ?
|
|
7411
|
-
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (
|
|
8261
|
+
(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));
|
|
7412
8262
|
const iconRing = frame.iconRadius ? Math.max(ICON_RING_MIN, frame.iconRadius + ICON_RING_PADDING) : 0;
|
|
7413
|
-
const
|
|
8263
|
+
const boxed = leader ? frame.modelBox : undefined;
|
|
8264
|
+
const hovered = pointer && (boxed ? hullContains(boxed.hull, pointer) :
|
|
8265
|
+
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));
|
|
7414
8266
|
if (look.iconPhoto && growPhoto) {
|
|
7415
8267
|
// A rectangle leans on the leader's side of the label.
|
|
7416
8268
|
if (photoShape === "rect") {
|
|
@@ -7421,15 +8273,15 @@ var EntityLabel;
|
|
|
7421
8273
|
}
|
|
7422
8274
|
// Grown in layout rather than scaled, so the border keeps its width, and the label, whose bottom
|
|
7423
8275
|
// sits on the leader, grows up and sideways without changing the leader's length.
|
|
7424
|
-
growPhoto(Boolean(hovered) && !((
|
|
8276
|
+
growPhoto(Boolean(hovered) && !((_k = placement.members) === null || _k === void 0 ? void 0 : _k.length));
|
|
7425
8277
|
}
|
|
7426
|
-
const normalRadius = (!leader || (ring === null || ring === void 0 ? void 0 : ring.enabled) === false ? 0 : Math.max(frame.distance <= ((
|
|
8278
|
+
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));
|
|
7427
8279
|
const targetRadius = normalRadius + (15.4 - normalRadius) * cursorMotion.amount;
|
|
7428
8280
|
// Around a glyph the ring keeps the glyph's size and only fades its stroke in, so it never covers the icon.
|
|
7429
8281
|
const targetOuter = iconRing || targetRadius || 2.5;
|
|
7430
8282
|
const targetInner = iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
|
|
7431
8283
|
const now = Date.now();
|
|
7432
|
-
const duration = (
|
|
8284
|
+
const duration = (_p = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _p !== void 0 ? _p : 200;
|
|
7433
8285
|
const sharedRing = this.Transient && !this.Persistent && this.hoverFade.to === 1 ? this.hoverRingState : undefined;
|
|
7434
8286
|
let ringAnimation = sharedRing ? sharedRing.animation : this.ringAnimation;
|
|
7435
8287
|
if (!ringAnimation) {
|
|
@@ -7463,7 +8315,8 @@ var EntityLabel;
|
|
|
7463
8315
|
if (sharedRing) {
|
|
7464
8316
|
sharedRing.animation = ringAnimation;
|
|
7465
8317
|
}
|
|
7466
|
-
const
|
|
8318
|
+
const ringShare = 1 - (frame.boxBlend || 0);
|
|
8319
|
+
const radius = ringShare > 0 && ringAnimation.inner > 0 ? ringAnimation.outer * ringShare : 0;
|
|
7467
8320
|
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);
|
|
7468
8321
|
const position = { x: (stem || frame).x, y: (stem || frame).y };
|
|
7469
8322
|
const width = ele.offsetWidth / coordinates.scaleX;
|
|
@@ -7471,19 +8324,19 @@ var EntityLabel;
|
|
|
7471
8324
|
if (leader && angled) {
|
|
7472
8325
|
// The declutter owns the side; the fit only says which one had the room to begin with.
|
|
7473
8326
|
const run = Math.max(ANGLED_LEADER_RUN, radius + 4);
|
|
7474
|
-
const side = (
|
|
8327
|
+
const side = (_r = (_q = placement.textSide) !== null && _q !== void 0 ? _q : frame.leaderSide) !== null && _r !== void 0 ? _r : "right";
|
|
7475
8328
|
const beside = side === "left" ? frame.anchorX - run - width : frame.anchorX + run;
|
|
7476
8329
|
position.x = Math.max(0, Math.min(beside, this.viewer.canvas.clientWidth - width));
|
|
7477
8330
|
}
|
|
7478
8331
|
else if (leader && placement.textSide === "left") {
|
|
7479
8332
|
position.x = Math.max(0, Math.min(frame.anchorX - width, this.viewer.canvas.clientWidth - width));
|
|
7480
8333
|
}
|
|
7481
|
-
const gap = (
|
|
8334
|
+
const gap = (_t = (_s = this.options.pointerAvoidance) === null || _s === void 0 ? void 0 : _s.gap) !== null && _t !== void 0 ? _t : 12;
|
|
7482
8335
|
const containsPointer = (x, y, margin) => {
|
|
7483
8336
|
return pointer && pointer.x >= x - margin && pointer.x <= x + width + margin && pointer.y >= y - margin && pointer.y <= y + height + margin;
|
|
7484
8337
|
};
|
|
7485
8338
|
const passive = !selectOnClick || !this.visualRegister;
|
|
7486
|
-
if (passive && ((
|
|
8339
|
+
if (passive && ((_u = this.options.pointerAvoidance) === null || _u === void 0 ? void 0 : _u.enabled) !== false && pointer &&
|
|
7487
8340
|
(containsPointer(position.x, position.y, gap) || containsPointer(position.x + displacement.x, position.y + displacement.y, gap))) {
|
|
7488
8341
|
if (containsPointer(position.x + displacement.x, position.y + displacement.y, 0)) {
|
|
7489
8342
|
// Choose against the original box and retain the offset until the pointer leaves both boxes.
|
|
@@ -7501,7 +8354,7 @@ var EntityLabel;
|
|
|
7501
8354
|
else {
|
|
7502
8355
|
displacement = { x: 0, y: 0 };
|
|
7503
8356
|
}
|
|
7504
|
-
const nudgeDuration = (
|
|
8357
|
+
const nudgeDuration = (_w = (_v = this.options.pointerAvoidance) === null || _v === void 0 ? void 0 : _v.transitionDuration) !== null && _w !== void 0 ? _w : 200;
|
|
7505
8358
|
const animateNudge = () => {
|
|
7506
8359
|
const progress = now < nudge.start ? 0 : nudgeDuration > 0 ? Math.min(1, (now - nudge.start) / nudgeDuration) : 1;
|
|
7507
8360
|
const eased = progress * progress * (3 - 2 * progress);
|
|
@@ -7517,7 +8370,7 @@ var EntityLabel;
|
|
|
7517
8370
|
fromY: nudge.y,
|
|
7518
8371
|
targetX: displacement.x,
|
|
7519
8372
|
targetY: displacement.y,
|
|
7520
|
-
start: now + ((
|
|
8373
|
+
start: now + ((_y = (_x = this.options.pointerAvoidance) === null || _x === void 0 ? void 0 : _x.delay) !== null && _y !== void 0 ? _y : 180)
|
|
7521
8374
|
};
|
|
7522
8375
|
}
|
|
7523
8376
|
if (animateNudge() < 1 && (nudge.fromX !== nudge.targetX || nudge.fromY !== nudge.targetY)) {
|
|
@@ -7528,10 +8381,10 @@ var EntityLabel;
|
|
|
7528
8381
|
ele.style.left = `${coordinates.x + position.x * coordinates.scaleX}px`;
|
|
7529
8382
|
ele.style.top = `${coordinates.y + position.y * coordinates.scaleY}px`;
|
|
7530
8383
|
for (const clickTarget of Array.from(content.querySelectorAll('[role="button"]'))) {
|
|
7531
|
-
clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(
|
|
8384
|
+
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}`);
|
|
7532
8385
|
}
|
|
7533
8386
|
if (leader) {
|
|
7534
|
-
const nodeRadius = ringAnimation.outer;
|
|
8387
|
+
const nodeRadius = ringAnimation.outer * (1 - (frame.boxBlend || 0));
|
|
7535
8388
|
const anchor = this.anchor;
|
|
7536
8389
|
anchor.style.left = `${coordinates.x + (frame.anchorX - nodeRadius) * coordinates.scaleX}px`;
|
|
7537
8390
|
anchor.style.top = `${coordinates.y + (frame.anchorY - nodeRadius) * coordinates.scaleY}px`;
|
|
@@ -7544,11 +8397,12 @@ var EntityLabel;
|
|
|
7544
8397
|
const circle = svg.firstElementChild;
|
|
7545
8398
|
circle.setAttribute("cx", String(nodeRadius));
|
|
7546
8399
|
circle.setAttribute("cy", String(nodeRadius));
|
|
7547
|
-
|
|
8400
|
+
const innerRadius = ringAnimation.inner * (1 - (frame.boxBlend || 0));
|
|
8401
|
+
circle.setAttribute("r", String((nodeRadius + innerRadius) / 2));
|
|
7548
8402
|
circle.setAttribute("stroke", this.options.leaderColor || "rgba(255, 255, 255, 0.8)");
|
|
7549
|
-
circle.setAttribute("stroke-width", String(nodeRadius -
|
|
8403
|
+
circle.setAttribute("stroke-width", String(nodeRadius - innerRadius));
|
|
7550
8404
|
anchor.style.boxShadow = radius ? "none" : "0 0 5px rgba(0, 0, 0, 0.3)";
|
|
7551
|
-
anchor.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount));
|
|
8405
|
+
anchor.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount) * (1 - (frame.boxBlend || 0)));
|
|
7552
8406
|
anchor.style.zIndex = String(placement.zIndex);
|
|
7553
8407
|
// The stem starts at the rim, never across the transparent centre, even after passive displacement.
|
|
7554
8408
|
const straightX = Math.max(position.x, Math.min(frame.anchorX, position.x + width));
|
|
@@ -7568,13 +8422,13 @@ var EntityLabel;
|
|
|
7568
8422
|
// Keep the stem inside the 126-degree arc, away from antialiased endpoints.
|
|
7569
8423
|
const arcFraction = 0.35;
|
|
7570
8424
|
const rotationLimit = Math.PI * arcFraction - Math.PI / 30;
|
|
7571
|
-
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((
|
|
8425
|
+
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_2 = ringAnimation.arcOffset) !== null && _2 !== void 0 ? _2 : 0);
|
|
7572
8426
|
const targetOpen = cursorMotion.target || (hovered && (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && targetRadius > 0) ? 1 : 0;
|
|
7573
|
-
const elapsed = Math.min(32, Math.max(0, now - ((
|
|
8427
|
+
const elapsed = Math.min(32, Math.max(0, now - ((_3 = ringAnimation.arcTime) !== null && _3 !== void 0 ? _3 : now)));
|
|
7574
8428
|
const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
|
|
7575
8429
|
const approach = (value, target) => Math.abs(value - target) < 0.001 ? target : value + (target - value) * blend;
|
|
7576
|
-
ringAnimation.arcOffset = approach((
|
|
7577
|
-
ringAnimation.arcOpen = approach((
|
|
8430
|
+
ringAnimation.arcOffset = approach((_4 = ringAnimation.arcOffset) !== null && _4 !== void 0 ? _4 : 0, targetOffset);
|
|
8431
|
+
ringAnimation.arcOpen = approach((_5 = ringAnimation.arcOpen) !== null && _5 !== void 0 ? _5 : 0, targetOpen);
|
|
7578
8432
|
ringAnimation.arcTime = now;
|
|
7579
8433
|
if (ringAnimation.arcOffset !== targetOffset || ringAnimation.arcOpen !== targetOpen) {
|
|
7580
8434
|
this.viewer.scene.requestRender();
|
|
@@ -7600,6 +8454,12 @@ var EntityLabel;
|
|
|
7600
8454
|
element.style.zIndex = String(placement.zIndex);
|
|
7601
8455
|
};
|
|
7602
8456
|
draw(lineEle, startX, startY, endX, endY);
|
|
8457
|
+
if (layer.modelBoxes.Hull(this.id)) {
|
|
8458
|
+
layer.modelBoxes.Request(this.id, {
|
|
8459
|
+
whole: Boolean(cursorMotion.target), from: { x: frame.anchorX, y: frame.anchorY },
|
|
8460
|
+
opacity, zIndex: placement.zIndex, color: this.options.leaderColor || "rgba(255, 255, 255, 0.8)", duration
|
|
8461
|
+
});
|
|
8462
|
+
}
|
|
7603
8463
|
if (runEle) {
|
|
7604
8464
|
// Overlap the corner by the stem's own width so the elbow has no notch in it.
|
|
7605
8465
|
const overlap = frame.anchorX < runToX ? -1 : 1;
|
|
@@ -7612,6 +8472,7 @@ var EntityLabel;
|
|
|
7612
8472
|
ele.style.display = "flex";
|
|
7613
8473
|
ele.style.visibility = "hidden";
|
|
7614
8474
|
this.renderRemoval = layer.Add({ project, apply });
|
|
8475
|
+
leaveLayer === null || leaveLayer === void 0 ? void 0 : leaveLayer();
|
|
7615
8476
|
// If terrain changes then we'll re-call createLabel as the position may have changed.
|
|
7616
8477
|
let terrTimeout = null;
|
|
7617
8478
|
this.terrChangedRemoval = this.viewer.scene.terrainProviderChanged.addEventListener(() => {
|
|
@@ -7757,7 +8618,7 @@ var EntityLabel;
|
|
|
7757
8618
|
// The glyph plus the chip's padding of a third either side, enlarged for a photo.
|
|
7758
8619
|
const diameter = (size + 2 * (size / 3)) * PHOTO_SIZE_SCALE;
|
|
7759
8620
|
// Enough pixels to stay sharp at the hover size.
|
|
7760
|
-
const pixels =
|
|
8621
|
+
const pixels = photoPixels(size, true);
|
|
7761
8622
|
const canvas = document.createElement("canvas");
|
|
7762
8623
|
canvas.className = "entity-label-photo";
|
|
7763
8624
|
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);";
|
|
@@ -7768,13 +8629,15 @@ var EntityLabel;
|
|
|
7768
8629
|
// Resting size, set once the photo's shape is known.
|
|
7769
8630
|
let width = 0;
|
|
7770
8631
|
let height = 0;
|
|
8632
|
+
// How far hovering grows it, which a small image limits.
|
|
8633
|
+
let hoverScale = PHOTO_HOVER_SCALE;
|
|
7771
8634
|
let grown = false;
|
|
7772
8635
|
const grow = (on) => {
|
|
7773
8636
|
if (on === grown || !width) {
|
|
7774
8637
|
return;
|
|
7775
8638
|
}
|
|
7776
8639
|
grown = on;
|
|
7777
|
-
const scale = on ?
|
|
8640
|
+
const scale = on ? hoverScale : 1;
|
|
7778
8641
|
canvas.style.width = `${width * scale}px`;
|
|
7779
8642
|
canvas.style.height = `${height * scale}px`;
|
|
7780
8643
|
this.renderFor(PHOTO_TRANSITION);
|
|
@@ -7792,11 +8655,15 @@ var EntityLabel;
|
|
|
7792
8655
|
(_a = canvas.getContext("2d")) === null || _a === void 0 ? void 0 : _a.drawImage(photo.source, 0, 0);
|
|
7793
8656
|
const longest = Math.max(photo.width, photo.height);
|
|
7794
8657
|
const circle = PhotoShape(photo.width, photo.height) === "circle";
|
|
7795
|
-
|
|
7796
|
-
|
|
8658
|
+
// Shown no larger than the image has pixels for, one to a CSS pixel, and never smaller than the icon.
|
|
8659
|
+
const native = circle ? Math.min(photo.width, photo.height) : longest;
|
|
8660
|
+
const across = Math.min(diameter, Math.max(size, native));
|
|
8661
|
+
hoverScale = Math.max(1, Math.min(PHOTO_HOVER_SCALE, native / across));
|
|
8662
|
+
width = circle ? across : across * photo.width / longest;
|
|
8663
|
+
height = circle ? across : across * photo.height / longest;
|
|
7797
8664
|
canvas.style.width = `${width}px`;
|
|
7798
8665
|
canvas.style.height = `${height}px`;
|
|
7799
|
-
canvas.style.borderRadius = circle ? "50%" : `${
|
|
8666
|
+
canvas.style.borderRadius = circle ? "50%" : `${across * PHOTO_CORNER}px`;
|
|
7800
8667
|
canvas.style.opacity = "1";
|
|
7801
8668
|
placePhoto === null || placePhoto === void 0 ? void 0 : placePhoto(circle ? "circle" : "rect", grow);
|
|
7802
8669
|
return true;
|
|
@@ -7843,6 +8710,57 @@ var EntityLabel;
|
|
|
7843
8710
|
};
|
|
7844
8711
|
step();
|
|
7845
8712
|
}
|
|
8713
|
+
/*
|
|
8714
|
+
* Reads the record's geometry box, preferring an entity a label group already hydrated.
|
|
8715
|
+
*/
|
|
8716
|
+
async loadModelBox() {
|
|
8717
|
+
var _a, _b, _c, _d;
|
|
8718
|
+
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;
|
|
8719
|
+
let entity = ((_d = known === null || known === void 0 ? void 0 : known.Bruce) === null || _d === void 0 ? void 0 : _d.GeometryPivotExtents) ? known : null;
|
|
8720
|
+
if (!entity && this.api) {
|
|
8721
|
+
try {
|
|
8722
|
+
entity = (await Entity$1.Get({ api: this.api, entityId: this.rego.entityId, expandLocation: true, migrated: true })).entity;
|
|
8723
|
+
}
|
|
8724
|
+
// Expanding location fails for entities outside an assembly, which have no box to give.
|
|
8725
|
+
catch {
|
|
8726
|
+
return null;
|
|
8727
|
+
}
|
|
8728
|
+
}
|
|
8729
|
+
// A file uploaded before the API stored oriented boxes has only the record's axis-aligned extents.
|
|
8730
|
+
return GetModelBox(entity, ReadOrientedBox(entity));
|
|
8731
|
+
}
|
|
8732
|
+
/*
|
|
8733
|
+
* Eases the stem between the ring and the model box, returning how far onto the box it is this frame.
|
|
8734
|
+
*/
|
|
8735
|
+
morphToBox(boxed, duration) {
|
|
8736
|
+
const morph = this.boxMorph;
|
|
8737
|
+
const now = Date.now();
|
|
8738
|
+
const ease = (progress) => progress * progress * (3 - 2 * progress);
|
|
8739
|
+
const progress = (start) => duration > 0 ? Math.min(1, Math.max(0, (now - start) / duration)) : 1;
|
|
8740
|
+
const hadBox = morph.target === 1;
|
|
8741
|
+
const target = boxed ? 1 : 0;
|
|
8742
|
+
if (morph.target !== target) {
|
|
8743
|
+
Object.assign(morph, { from: morph.value, target, start: now });
|
|
8744
|
+
}
|
|
8745
|
+
morph.value = morph.from + (morph.target - morph.from) * ease(progress(morph.start));
|
|
8746
|
+
// Settled, so a later frame with a longer duration does not replay the ease.
|
|
8747
|
+
if (morph.value === morph.target) {
|
|
8748
|
+
morph.from = morph.target;
|
|
8749
|
+
}
|
|
8750
|
+
if (boxed) {
|
|
8751
|
+
// Moving to another outline, such as a hovered part pulled out of its group, eases the stem across.
|
|
8752
|
+
if (hadBox && morph.anchor && morph.group !== boxed.group) {
|
|
8753
|
+
morph.shift = { x: morph.anchor.x - boxed.anchor.x, y: morph.anchor.y - boxed.anchor.y, start: now };
|
|
8754
|
+
}
|
|
8755
|
+
morph.group = boxed.group;
|
|
8756
|
+
const remaining = 1 - ease(progress(morph.shift.start));
|
|
8757
|
+
morph.anchor = { x: boxed.anchor.x + morph.shift.x * remaining, y: boxed.anchor.y + morph.shift.y * remaining };
|
|
8758
|
+
}
|
|
8759
|
+
if (morph.value !== morph.target || (boxed && progress(morph.shift.start) < 1)) {
|
|
8760
|
+
this.viewer.scene.requestRender();
|
|
8761
|
+
}
|
|
8762
|
+
return morph.value;
|
|
8763
|
+
}
|
|
7846
8764
|
/**
|
|
7847
8765
|
* Removes label from the viewer and unsubscribes from events.
|
|
7848
8766
|
*/
|
|
@@ -12894,7 +13812,9 @@ var VisualsRegister;
|
|
|
12894
13812
|
entityId: params.entityId,
|
|
12895
13813
|
menuItemId: params.menuItemId,
|
|
12896
13814
|
position: params.position ? { ...params.position } : undefined,
|
|
12897
|
-
createIfMissing: params.createIfMissing !== false
|
|
13815
|
+
createIfMissing: params.createIfMissing !== false,
|
|
13816
|
+
text: params.text,
|
|
13817
|
+
pos3d: params.pos3d ? Cartesian3.clone(params.pos3d) : undefined
|
|
12898
13818
|
};
|
|
12899
13819
|
const doc = this.viewer.container.ownerDocument;
|
|
12900
13820
|
// Only a label following the cursor should go when the cursor leaves the canvas.
|
|
@@ -12910,12 +13830,26 @@ var VisualsRegister;
|
|
|
12910
13830
|
this.SyncLabelHover();
|
|
12911
13831
|
this.viewer.scene.requestRender();
|
|
12912
13832
|
}
|
|
13833
|
+
/*
|
|
13834
|
+
* Which Entity holds the label hover, if any, so a caller can leave another's hover alone.
|
|
13835
|
+
*/
|
|
13836
|
+
GetLabelHoverTarget() {
|
|
13837
|
+
const hover = this.labelHover;
|
|
13838
|
+
return hover ? { entityId: hover.entityId, menuItemId: hover.menuItemId } : undefined;
|
|
13839
|
+
}
|
|
13840
|
+
/*
|
|
13841
|
+
* Whether the label hover is on this Entity, with or without a cursor position.
|
|
13842
|
+
*/
|
|
13843
|
+
IsLabelHovered(entityId, menuItemId) {
|
|
13844
|
+
const hover = this.labelHover;
|
|
13845
|
+
return Boolean(entityId) && (hover === null || hover === void 0 ? void 0 : hover.entityId) === entityId && (hover.menuItemId == null || hover.menuItemId === menuItemId);
|
|
13846
|
+
}
|
|
12913
13847
|
GetLabelHoverPosition(entityId, menuItemId) {
|
|
12914
13848
|
const hover = this.labelHover;
|
|
12915
13849
|
return (hover === null || hover === void 0 ? void 0 : hover.entityId) === entityId && (hover.menuItemId == null || hover.menuItemId === menuItemId) ? hover.position : undefined;
|
|
12916
13850
|
}
|
|
12917
13851
|
SyncLabelHover() {
|
|
12918
|
-
var _a, _b, _c;
|
|
13852
|
+
var _a, _b, _c, _d;
|
|
12919
13853
|
const hover = this.labelHover;
|
|
12920
13854
|
if (!hover || this.disposed) {
|
|
12921
13855
|
return;
|
|
@@ -12947,12 +13881,24 @@ var VisualsRegister;
|
|
|
12947
13881
|
this.ReleaseHoverLabel(true);
|
|
12948
13882
|
return;
|
|
12949
13883
|
}
|
|
13884
|
+
const hoverOptions = {
|
|
13885
|
+
presentation: "text",
|
|
13886
|
+
text: (_b = hover.text) !== null && _b !== void 0 ? _b : group === null || group === void 0 ? void 0 : group.options.text,
|
|
13887
|
+
selectOnClick: false
|
|
13888
|
+
};
|
|
12950
13889
|
if (this.hoverLabel && !this.hoverLabel.Disposed) {
|
|
13890
|
+
// Rebuilds only when the text changed, as it does when the pointer crosses a cell.
|
|
13891
|
+
if (hover.pos3d && !Cartesian3.equals(hover.pos3d, this.hoverLabel.pos3d)) {
|
|
13892
|
+
this.hoverLabel.pos3d = hover.pos3d;
|
|
13893
|
+
}
|
|
13894
|
+
if (hover.text != null) {
|
|
13895
|
+
this.hoverLabel.Configure(hoverOptions);
|
|
13896
|
+
}
|
|
12951
13897
|
this.hoverLabel.SetHoverVisible(true);
|
|
12952
13898
|
return;
|
|
12953
13899
|
}
|
|
12954
13900
|
// Query-only icon groups have coordinates but no registered scene visual to attach to.
|
|
12955
|
-
if (!rego && group && [group.item.longitude, group.item.latitude, (
|
|
13901
|
+
if (!rego && group && [group.item.longitude, group.item.latitude, (_c = group.item.altitude) !== null && _c !== void 0 ? _c : 0].every(Number.isFinite)) {
|
|
12956
13902
|
rego = {
|
|
12957
13903
|
entityId: hover.entityId,
|
|
12958
13904
|
menuItemId: hover.menuItemId,
|
|
@@ -12974,15 +13920,12 @@ var VisualsRegister;
|
|
|
12974
13920
|
rego,
|
|
12975
13921
|
viewer: this.viewer,
|
|
12976
13922
|
visualRegister: this,
|
|
12977
|
-
api: (
|
|
13923
|
+
api: (_d = this.ApiGetters) === null || _d === void 0 ? void 0 : _d.GetBruceApi(),
|
|
12978
13924
|
persistent: false,
|
|
12979
13925
|
transient: true,
|
|
12980
13926
|
hoverRingState: this.hoverRingState,
|
|
12981
|
-
|
|
12982
|
-
|
|
12983
|
-
text: group === null || group === void 0 ? void 0 : group.options.text,
|
|
12984
|
-
selectOnClick: false
|
|
12985
|
-
},
|
|
13927
|
+
pos3d: hover.pos3d,
|
|
13928
|
+
options: hoverOptions,
|
|
12986
13929
|
visible: () => {
|
|
12987
13930
|
var _a;
|
|
12988
13931
|
const current = this.GetState(hover);
|
|
@@ -45899,6 +46842,10 @@ var TextureFrameSeriesAnimator;
|
|
|
45899
46842
|
TextureFrameSeriesAnimator.AppearanceSignature = AppearanceSignature;
|
|
45900
46843
|
const MAX_COMPOSITE_TEXELS = 2048;
|
|
45901
46844
|
const VALUE_DILATE_PASSES = 2;
|
|
46845
|
+
// Decoded frame bytes kept at once. A 2048 square composite is 16 MB, so a long series cannot all stay.
|
|
46846
|
+
const MAX_CACHED_FRAME_BYTES = 256 * 1024 * 1024;
|
|
46847
|
+
// Fetched bytes kept for decoding again, a small fraction of what they decode to.
|
|
46848
|
+
const MAX_RAW_FRAME_BYTES = 96 * 1024 * 1024;
|
|
45902
46849
|
/*
|
|
45903
46850
|
* Rewrites a packed 16 bit value as the 8 bit grey the colour ramps index on.
|
|
45904
46851
|
*
|
|
@@ -45919,19 +46866,33 @@ var TextureFrameSeriesAnimator;
|
|
|
45919
46866
|
/*
|
|
45920
46867
|
* Bleeds covered values outward into uncovered texels, leaving alpha untouched.
|
|
45921
46868
|
*/
|
|
45922
|
-
function
|
|
46869
|
+
function DilateValues(value, width, height, passes, packed) {
|
|
45923
46870
|
const texels = width * height;
|
|
45924
|
-
|
|
46871
|
+
// 255 for a texel that had data to begin with, otherwise the 1 based pass that filled it, so a pass
|
|
46872
|
+
// reads only texels filled before it and can work in place.
|
|
46873
|
+
const stamp = new Uint8Array(texels);
|
|
45925
46874
|
for (let p = 0; p < texels; p++) {
|
|
45926
|
-
|
|
46875
|
+
stamp[p] = value[p * 4 + 3] !== 0 ? 255 : 0;
|
|
45927
46876
|
}
|
|
46877
|
+
// Whether a texel or a horizontal neighbour had data, so empty space is skipped in three reads.
|
|
46878
|
+
const across = new Uint8Array(texels);
|
|
45928
46879
|
for (let pass = 0; pass < passes; pass++) {
|
|
45929
|
-
const
|
|
45930
|
-
const
|
|
46880
|
+
const current = pass + 1;
|
|
46881
|
+
const had = (p) => stamp[p] !== 0 && stamp[p] !== current;
|
|
46882
|
+
for (let y = 0; y < height; y++) {
|
|
46883
|
+
const row = y * width;
|
|
46884
|
+
for (let x = 0; x < width; x++) {
|
|
46885
|
+
const p = row + x;
|
|
46886
|
+
across[p] = (had(p) || (x > 0 && had(p - 1)) || (x + 1 < width && had(p + 1))) ? 1 : 0;
|
|
46887
|
+
}
|
|
46888
|
+
}
|
|
45931
46889
|
for (let y = 0; y < height; y++) {
|
|
45932
46890
|
for (let x = 0; x < width; x++) {
|
|
45933
46891
|
const p = y * width + x;
|
|
45934
|
-
if (
|
|
46892
|
+
if (stamp[p] !== 0) {
|
|
46893
|
+
continue;
|
|
46894
|
+
}
|
|
46895
|
+
if (!across[p] && !(y > 0 && across[p - width]) && !(y + 1 < height && across[p + width])) {
|
|
45935
46896
|
continue;
|
|
45936
46897
|
}
|
|
45937
46898
|
let sum = 0;
|
|
@@ -45940,13 +46901,13 @@ var TextureFrameSeriesAnimator;
|
|
|
45940
46901
|
for (let dx = -1; dx <= 1; dx++) {
|
|
45941
46902
|
const nx = x + dx;
|
|
45942
46903
|
const ny = y + dy;
|
|
45943
|
-
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !
|
|
46904
|
+
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !had(ny * width + nx)) {
|
|
45944
46905
|
continue;
|
|
45945
46906
|
}
|
|
45946
46907
|
const at = (ny * width + nx) * 4;
|
|
45947
46908
|
// Averaged as a value rather than per byte: averaging a packed low byte
|
|
45948
46909
|
// on its own wraps at every 256th step and speckles the bled edge.
|
|
45949
|
-
sum += packed ?
|
|
46910
|
+
sum += packed ? value[at] * 256 + value[at + 1] : value[at];
|
|
45950
46911
|
hits++;
|
|
45951
46912
|
}
|
|
45952
46913
|
}
|
|
@@ -45962,18 +46923,25 @@ var TextureFrameSeriesAnimator;
|
|
|
45962
46923
|
value[p * 4 + 1] = v;
|
|
45963
46924
|
value[p * 4 + 2] = v;
|
|
45964
46925
|
}
|
|
45965
|
-
|
|
46926
|
+
stamp[p] = current;
|
|
45966
46927
|
}
|
|
45967
46928
|
}
|
|
45968
46929
|
}
|
|
45969
46930
|
}
|
|
45970
46931
|
}
|
|
46932
|
+
TextureFrameSeriesAnimator.DilateValues = DilateValues;
|
|
46933
|
+
/*
|
|
46934
|
+
* Bytes one cached frame holds, counting a buffer shared between colour and value once.
|
|
46935
|
+
*/
|
|
46936
|
+
function frameBytes(pixels, value) {
|
|
46937
|
+
return (pixels ? pixels.byteLength : 0) + (value && value !== pixels ? value.byteLength : 0);
|
|
46938
|
+
}
|
|
45971
46939
|
const DEFAULT_CROSSFADE_MS = 900;
|
|
45972
46940
|
const DEFAULT_LOW_COLOR = { red: 255, green: 255, blue: 255, alpha: 0.12 };
|
|
45973
46941
|
const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
|
|
45974
46942
|
class Animator {
|
|
45975
46943
|
constructor(options) {
|
|
45976
|
-
var _a, _b, _c;
|
|
46944
|
+
var _a, _b, _c, _d;
|
|
45977
46945
|
this.removeOnTick = null;
|
|
45978
46946
|
this.removeCrossfadeTick = null;
|
|
45979
46947
|
this.disposed = false;
|
|
@@ -46004,6 +46972,15 @@ var TextureFrameSeriesAnimator;
|
|
|
46004
46972
|
this.extremesLoad = null;
|
|
46005
46973
|
this.floorPixels = null;
|
|
46006
46974
|
this.ceilingPixels = null;
|
|
46975
|
+
// How far through the fade from the previous value frame to the presented one, 1 when settled.
|
|
46976
|
+
this.fadeMix = 1;
|
|
46977
|
+
// Where the current fade started from, above zero when it took over from one still running.
|
|
46978
|
+
this.fadeMixStart = 0;
|
|
46979
|
+
// Version the value canvas was last written at, since in value only mode it is written on demand.
|
|
46980
|
+
this.valueCanvasVersion = -1;
|
|
46981
|
+
this.rawBytes = 0;
|
|
46982
|
+
this.cachedBytes = 0;
|
|
46983
|
+
this.lastFrameBytes = 0;
|
|
46007
46984
|
if (!options.entity.polygon) {
|
|
46008
46985
|
throw new Error("TextureFrameSeriesAnimator requires an entity with polygon graphics.");
|
|
46009
46986
|
}
|
|
@@ -46051,6 +47028,9 @@ var TextureFrameSeriesAnimator;
|
|
|
46051
47028
|
this.maskBaseline = (_c = options.maskBaseline) !== null && _c !== void 0 ? _c : this.tiles.some((tile) => Boolean(tile.BaselineMask));
|
|
46052
47029
|
this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
|
|
46053
47030
|
this.driveMaterial = options.driveMaterial !== false;
|
|
47031
|
+
this.valueOnly = this.produceValueCanvas && !this.driveMaterial;
|
|
47032
|
+
this.rawCache = new Array(this.frames.length).fill(null);
|
|
47033
|
+
this.cacheBudget = (_d = options.cacheBytes) !== null && _d !== void 0 ? _d : MAX_CACHED_FRAME_BYTES;
|
|
46054
47034
|
this.originalMaterial = options.entity.polygon.material;
|
|
46055
47035
|
this.imageProperty = new CallbackProperty(() => this.pool[this.poolIdx], false);
|
|
46056
47036
|
if (this.driveMaterial) {
|
|
@@ -46116,7 +47096,40 @@ var TextureFrameSeriesAnimator;
|
|
|
46116
47096
|
* or null unless the instance was constructed with produceValueCanvas.
|
|
46117
47097
|
*/
|
|
46118
47098
|
GetValueCanvas() {
|
|
46119
|
-
|
|
47099
|
+
if (!this.valueDims) {
|
|
47100
|
+
return null;
|
|
47101
|
+
}
|
|
47102
|
+
if (this.valueOnly && this.valueCanvasVersion !== this.presentVersion && this.displayedValuePixels) {
|
|
47103
|
+
this.writeInto(this.valueCanvas, this.displayedValuePixels, this.valueDims);
|
|
47104
|
+
this.valueCanvasVersion = this.presentVersion;
|
|
47105
|
+
}
|
|
47106
|
+
return this.valueCanvas;
|
|
47107
|
+
}
|
|
47108
|
+
/**
|
|
47109
|
+
* The presented frame's value pixels, or null before one. Shares the cached array, so never write to it.
|
|
47110
|
+
*/
|
|
47111
|
+
GetValueRaster() {
|
|
47112
|
+
if (!this.valueDims || !this.displayedValuePixels) {
|
|
47113
|
+
return null;
|
|
47114
|
+
}
|
|
47115
|
+
return { data: this.displayedValuePixels, width: this.valueDims.width, height: this.valueDims.height };
|
|
47116
|
+
}
|
|
47117
|
+
/**
|
|
47118
|
+
* The two value frames being faded between and the mix, or null unless the material is not driven.
|
|
47119
|
+
*/
|
|
47120
|
+
GetValueFade() {
|
|
47121
|
+
if (!this.valueOnly || !this.valueDims || !this.displayedValuePixels) {
|
|
47122
|
+
return null;
|
|
47123
|
+
}
|
|
47124
|
+
const previous = this.fadeFromValuePixels;
|
|
47125
|
+
const from = previous && previous.length === this.displayedValuePixels.length ? previous : null;
|
|
47126
|
+
return {
|
|
47127
|
+
from,
|
|
47128
|
+
to: this.displayedValuePixels,
|
|
47129
|
+
width: this.valueDims.width,
|
|
47130
|
+
height: this.valueDims.height,
|
|
47131
|
+
mix: from ? this.fadeMix : 1
|
|
47132
|
+
};
|
|
46120
47133
|
}
|
|
46121
47134
|
/**
|
|
46122
47135
|
* Increments every time a frame is presented, so a consumer holding the value canvas can tell
|
|
@@ -46183,7 +47196,11 @@ var TextureFrameSeriesAnimator;
|
|
|
46183
47196
|
if (this.currentFrameIndex < 0) {
|
|
46184
47197
|
return this.frameCache[0] ? -1 : 0;
|
|
46185
47198
|
}
|
|
46186
|
-
|
|
47199
|
+
// Full once another frame will not fit, so a prefetch only displaces a frame further from the
|
|
47200
|
+
// playhead than itself and never trades the two ends of the window back and forth.
|
|
47201
|
+
const full = this.cachedBytes + this.lastFrameBytes > this.cacheBudget;
|
|
47202
|
+
const reach = full ? this.farthestCachedDistance() : n;
|
|
47203
|
+
for (let d = 0; d < Math.min(n, reach); d++) {
|
|
46187
47204
|
const forward = this.currentFrameIndex + d;
|
|
46188
47205
|
if (forward < n && !this.frameCache[forward]) {
|
|
46189
47206
|
return forward;
|
|
@@ -46195,6 +47212,68 @@ var TextureFrameSeriesAnimator;
|
|
|
46195
47212
|
}
|
|
46196
47213
|
return -1;
|
|
46197
47214
|
}
|
|
47215
|
+
farthestCachedDistance() {
|
|
47216
|
+
let farthest = 0;
|
|
47217
|
+
for (let i = 0; i < this.frameCache.length; i++) {
|
|
47218
|
+
if (this.frameCache[i]) {
|
|
47219
|
+
farthest = Math.max(farthest, Math.abs(i - this.currentFrameIndex));
|
|
47220
|
+
}
|
|
47221
|
+
}
|
|
47222
|
+
return farthest;
|
|
47223
|
+
}
|
|
47224
|
+
/*
|
|
47225
|
+
* Drops decoded frames furthest from the playhead until the cache fits its budget again.
|
|
47226
|
+
*/
|
|
47227
|
+
evictDecoded(keep) {
|
|
47228
|
+
// A tinted fade reads its target from the cache each tick, where a value fade holds the arrays.
|
|
47229
|
+
const fading = !this.valueOnly && this.fadeStartMs !== null ? this.fadeToIndex : -1;
|
|
47230
|
+
while (this.cachedBytes > this.cacheBudget) {
|
|
47231
|
+
let victim = -1;
|
|
47232
|
+
let distance = -1;
|
|
47233
|
+
for (let i = 0; i < this.frameCache.length; i++) {
|
|
47234
|
+
if (!this.frameCache[i] || i === keep || i === this.currentFrameIndex || i === fading) {
|
|
47235
|
+
continue;
|
|
47236
|
+
}
|
|
47237
|
+
const d = Math.abs(i - this.currentFrameIndex);
|
|
47238
|
+
if (d > distance) {
|
|
47239
|
+
distance = d;
|
|
47240
|
+
victim = i;
|
|
47241
|
+
}
|
|
47242
|
+
}
|
|
47243
|
+
if (victim < 0) {
|
|
47244
|
+
return;
|
|
47245
|
+
}
|
|
47246
|
+
this.cachedBytes -= frameBytes(this.frameCache[victim], this.valueCache[victim]);
|
|
47247
|
+
this.frameCache[victim] = null;
|
|
47248
|
+
this.valueCache[victim] = null;
|
|
47249
|
+
}
|
|
47250
|
+
}
|
|
47251
|
+
/*
|
|
47252
|
+
* Keeps a frame's fetched bytes unless they are large, as a tile major archive's are.
|
|
47253
|
+
*/
|
|
47254
|
+
keepRaw(idx, buffer, base) {
|
|
47255
|
+
if (buffer.byteLength > MAX_RAW_FRAME_BYTES / 16) {
|
|
47256
|
+
return;
|
|
47257
|
+
}
|
|
47258
|
+
this.rawCache[idx] = { buffer, base };
|
|
47259
|
+
this.rawBytes += buffer.byteLength;
|
|
47260
|
+
while (this.rawBytes > MAX_RAW_FRAME_BYTES) {
|
|
47261
|
+
let victim = -1;
|
|
47262
|
+
let distance = -1;
|
|
47263
|
+
for (let i = 0; i < this.rawCache.length; i++) {
|
|
47264
|
+
const d = Math.abs(i - this.currentFrameIndex);
|
|
47265
|
+
if (this.rawCache[i] && i !== idx && d > distance) {
|
|
47266
|
+
distance = d;
|
|
47267
|
+
victim = i;
|
|
47268
|
+
}
|
|
47269
|
+
}
|
|
47270
|
+
if (victim < 0) {
|
|
47271
|
+
return;
|
|
47272
|
+
}
|
|
47273
|
+
this.rawBytes -= this.rawCache[victim].buffer.byteLength;
|
|
47274
|
+
this.rawCache[victim] = null;
|
|
47275
|
+
}
|
|
47276
|
+
}
|
|
46198
47277
|
pumpFetchQueue() {
|
|
46199
47278
|
if (this.disposed || this.inFlightIndex !== -1) {
|
|
46200
47279
|
return;
|
|
@@ -46238,10 +47317,18 @@ var TextureFrameSeriesAnimator;
|
|
|
46238
47317
|
lo = Math.min(lo, at.ByteOffset);
|
|
46239
47318
|
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
46240
47319
|
}
|
|
46241
|
-
|
|
46242
|
-
const
|
|
46243
|
-
if (
|
|
46244
|
-
|
|
47320
|
+
let buffer;
|
|
47321
|
+
const raw = this.rawCache[idx];
|
|
47322
|
+
if (raw && raw.base === lo) {
|
|
47323
|
+
buffer = raw.buffer;
|
|
47324
|
+
}
|
|
47325
|
+
else {
|
|
47326
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
47327
|
+
buffer = await response.arrayBuffer();
|
|
47328
|
+
if (this.disposed) {
|
|
47329
|
+
return;
|
|
47330
|
+
}
|
|
47331
|
+
this.keepRaw(idx, buffer, lo);
|
|
46245
47332
|
}
|
|
46246
47333
|
// Awaited before decoding rather than applied later, so no frame is ever cached unmasked and
|
|
46247
47334
|
// then corrected once the mask lands.
|
|
@@ -46253,9 +47340,15 @@ var TextureFrameSeriesAnimator;
|
|
|
46253
47340
|
if (this.disposed) {
|
|
46254
47341
|
return;
|
|
46255
47342
|
}
|
|
47343
|
+
if (this.frameCache[idx]) {
|
|
47344
|
+
this.cachedBytes -= frameBytes(this.frameCache[idx], this.valueCache[idx]);
|
|
47345
|
+
}
|
|
46256
47346
|
this.frameCache[idx] = decoded.pixels;
|
|
46257
47347
|
this.frameDims[idx] = { width: decoded.width, height: decoded.height };
|
|
46258
47348
|
this.valueCache[idx] = decoded.valuePixels || null;
|
|
47349
|
+
this.lastFrameBytes = frameBytes(decoded.pixels, decoded.valuePixels || null);
|
|
47350
|
+
this.cachedBytes += this.lastFrameBytes;
|
|
47351
|
+
this.evictDecoded(idx);
|
|
46259
47352
|
if (this.shouldPresent(idx, isTarget)) {
|
|
46260
47353
|
this.beginCrossfadeTo(idx);
|
|
46261
47354
|
}
|
|
@@ -46325,6 +47418,13 @@ var TextureFrameSeriesAnimator;
|
|
|
46325
47418
|
*/
|
|
46326
47419
|
tint(imageData) {
|
|
46327
47420
|
const canvas = { width: imageData.width, height: imageData.height };
|
|
47421
|
+
if (this.valueOnly) {
|
|
47422
|
+
// One array serves as both, since nothing will ever display the colour.
|
|
47423
|
+
const values = imageData.data;
|
|
47424
|
+
this.applyBaselineMask(values, undefined, canvas.width, canvas.height);
|
|
47425
|
+
DilateValues(values, canvas.width, canvas.height, VALUE_DILATE_PASSES, IsPackedValue(this.metadata));
|
|
47426
|
+
return { pixels: values, width: canvas.width, height: canvas.height, valuePixels: values };
|
|
47427
|
+
}
|
|
46328
47428
|
// Copied before tinting: the ramp is a lerp between two colours, so the value cannot be
|
|
46329
47429
|
// recovered from the tinted pixels afterwards.
|
|
46330
47430
|
const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
|
|
@@ -46345,7 +47445,7 @@ var TextureFrameSeriesAnimator;
|
|
|
46345
47445
|
}
|
|
46346
47446
|
this.applyBaselineMask(imageData.data, valuePixels, canvas.width, canvas.height);
|
|
46347
47447
|
if (valuePixels) {
|
|
46348
|
-
|
|
47448
|
+
DilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES, IsPackedValue(this.metadata));
|
|
46349
47449
|
}
|
|
46350
47450
|
return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
|
|
46351
47451
|
}
|
|
@@ -46603,6 +47703,10 @@ var TextureFrameSeriesAnimator;
|
|
|
46603
47703
|
}
|
|
46604
47704
|
}
|
|
46605
47705
|
beginCrossfadeTo(idx) {
|
|
47706
|
+
if (this.valueOnly) {
|
|
47707
|
+
this.beginValueFadeTo(idx);
|
|
47708
|
+
return;
|
|
47709
|
+
}
|
|
46606
47710
|
const to = this.frameCache[idx];
|
|
46607
47711
|
this.fadeFromValuePixels = this.displayedValuePixels === null && this.valueCache[idx]
|
|
46608
47712
|
? new Uint8ClampedArray(this.valueCache[idx].length)
|
|
@@ -46612,12 +47716,53 @@ var TextureFrameSeriesAnimator;
|
|
|
46612
47716
|
this.fadeToDims = this.frameDims[idx];
|
|
46613
47717
|
this.fadeStartMs = Now();
|
|
46614
47718
|
}
|
|
47719
|
+
/*
|
|
47720
|
+
* Presents a value frame at once and leaves the fade to the consumer, which blends on the GPU.
|
|
47721
|
+
*/
|
|
47722
|
+
beginValueFadeTo(idx) {
|
|
47723
|
+
const to = this.valueCache[idx];
|
|
47724
|
+
if (!to || to === this.displayedValuePixels) {
|
|
47725
|
+
return;
|
|
47726
|
+
}
|
|
47727
|
+
// A new target mid fade keeps whichever frame is mostly on screen and carries on from the current
|
|
47728
|
+
// mix, so frames landing in quick succession do not jump the sheet back.
|
|
47729
|
+
const midFade = this.fadeStartMs !== null && Boolean(this.fadeFromValuePixels);
|
|
47730
|
+
if (midFade && this.fadeMix < 0.5) {
|
|
47731
|
+
this.fadeMixStart = this.fadeMix;
|
|
47732
|
+
}
|
|
47733
|
+
else {
|
|
47734
|
+
this.fadeFromValuePixels = this.displayedValuePixels;
|
|
47735
|
+
this.fadeMixStart = 0;
|
|
47736
|
+
}
|
|
47737
|
+
this.displayedValuePixels = to;
|
|
47738
|
+
this.valueDims = this.frameDims[idx];
|
|
47739
|
+
this.fadeToIndex = idx;
|
|
47740
|
+
this.fadeMix = this.fadeFromValuePixels ? this.fadeMixStart : 1;
|
|
47741
|
+
this.fadeStartMs = this.fadeFromValuePixels ? Now() : null;
|
|
47742
|
+
this.presentVersion++;
|
|
47743
|
+
this.requestRender();
|
|
47744
|
+
}
|
|
47745
|
+
requestRender() {
|
|
47746
|
+
if (this.viewer && this.viewer.scene && this.viewer.scene.requestRenderMode) {
|
|
47747
|
+
this.viewer.scene.requestRender();
|
|
47748
|
+
}
|
|
47749
|
+
}
|
|
46615
47750
|
tickCrossfade() {
|
|
46616
47751
|
if (this.disposed || this.fadeStartMs === null) {
|
|
46617
47752
|
return;
|
|
46618
47753
|
}
|
|
46619
47754
|
const t = Math.min(1, (Now() - this.fadeStartMs) / this.crossfadeMs);
|
|
46620
47755
|
const eased = easeInOutQuad(t);
|
|
47756
|
+
if (this.valueOnly) {
|
|
47757
|
+
this.fadeMix = this.fadeMixStart + (1 - this.fadeMixStart) * eased;
|
|
47758
|
+
if (t >= 1) {
|
|
47759
|
+
this.fadeStartMs = null;
|
|
47760
|
+
this.fadeFromValuePixels = null;
|
|
47761
|
+
this.fadeMix = 1;
|
|
47762
|
+
}
|
|
47763
|
+
this.requestRender();
|
|
47764
|
+
return;
|
|
47765
|
+
}
|
|
46621
47766
|
const from = this.fadeFromPixels;
|
|
46622
47767
|
const to = this.frameCache[this.fadeToIndex];
|
|
46623
47768
|
const n = Math.min(from.length, to.length);
|
|
@@ -46657,9 +47802,7 @@ var TextureFrameSeriesAnimator;
|
|
|
46657
47802
|
this.valueDims = dims;
|
|
46658
47803
|
}
|
|
46659
47804
|
this.presentVersion++;
|
|
46660
|
-
|
|
46661
|
-
this.viewer.scene.requestRender();
|
|
46662
|
-
}
|
|
47805
|
+
this.requestRender();
|
|
46663
47806
|
}
|
|
46664
47807
|
}
|
|
46665
47808
|
TextureFrameSeriesAnimator.Animator = Animator;
|
|
@@ -46682,13 +47825,15 @@ var TextureValueLabels;
|
|
|
46682
47825
|
const MIN_VISIBLE_PIXELS = 60;
|
|
46683
47826
|
const MAX_STRIDE = 512;
|
|
46684
47827
|
const STRIDE_HYSTERESIS = 1.4;
|
|
46685
|
-
|
|
46686
|
-
const
|
|
46687
|
-
const
|
|
46688
|
-
|
|
46689
|
-
|
|
46690
|
-
|
|
46691
|
-
const
|
|
47828
|
+
// Scene labels carry a leader and are wider than a value, so they want room and not many of them.
|
|
47829
|
+
const DEFAULT_MIN_SPACING_PIXELS = 140;
|
|
47830
|
+
const DEFAULT_MAX_LABELS = 60;
|
|
47831
|
+
// How far past the edge of the view an anchor may sit before its label is dropped.
|
|
47832
|
+
const LABEL_EDGE_MARGIN_PIXELS = 120;
|
|
47833
|
+
// Distance discount for a label already up when the cap forces a choice.
|
|
47834
|
+
const LABEL_KEEP_BIAS = 0.6;
|
|
47835
|
+
// How long a label outlives its cell leaving the wanted set, so one back a frame later does not blink.
|
|
47836
|
+
const LABEL_LINGER_MS = 300;
|
|
46692
47837
|
// 8 rather than 5: below this a cell reads as texture rather than as a boundary, and it also
|
|
46693
47838
|
// bounds the worst case, since a full screen of 8 px cells fits inside MAX_GRID_CELLS.
|
|
46694
47839
|
const DEFAULT_MIN_GRID_CELL_PIXELS = 8;
|
|
@@ -46702,7 +47847,8 @@ var TextureValueLabels;
|
|
|
46702
47847
|
const TILE_OUTLINE_ALPHA = 0.45;
|
|
46703
47848
|
// Cells per grid square at the coarsest. Beyond this a tile is outlined instead.
|
|
46704
47849
|
const MAX_GRID_STRIDE = 256;
|
|
46705
|
-
|
|
47850
|
+
// Times the pointer's ground hit is moved onto the sheet's own height before reading a cell.
|
|
47851
|
+
const HOVER_REFINE_STEPS = 3;
|
|
46706
47852
|
/**
|
|
46707
47853
|
* A clip outline from a projected ring, or null when the ring cannot be drawn.
|
|
46708
47854
|
* @param projected one entry per ring vertex, null where the vertex has no window coordinate
|
|
@@ -46796,11 +47942,25 @@ var TextureValueLabels;
|
|
|
46796
47942
|
this.sourceMap = null;
|
|
46797
47943
|
this.source = null;
|
|
46798
47944
|
this.painted = null;
|
|
47945
|
+
this.paintedRamp = null;
|
|
46799
47946
|
this.floor = null;
|
|
46800
47947
|
this.ceiling = null;
|
|
46801
47948
|
this.disposed = false;
|
|
46802
|
-
this.drawn = 0;
|
|
46803
47949
|
this.stride = 0;
|
|
47950
|
+
this.cellLabels = new Map();
|
|
47951
|
+
this.lingerTimer = null;
|
|
47952
|
+
// What the last draw was made from, so a frame that changes none of it draws nothing.
|
|
47953
|
+
this.drawnView = null;
|
|
47954
|
+
this.drawnInputs = [];
|
|
47955
|
+
this.scratchWorld = new Cartesian3();
|
|
47956
|
+
this.scratchWindow = new Cartesian2();
|
|
47957
|
+
this.scratchClip = new Cartesian4();
|
|
47958
|
+
// The camera's view and projection as one matrix, rebuilt per draw, or null outside 3D.
|
|
47959
|
+
this.viewProjection = null;
|
|
47960
|
+
this.viewProjectionScratch = new Matrix4();
|
|
47961
|
+
this.hovering = false;
|
|
47962
|
+
this.hoverFrame = 0;
|
|
47963
|
+
this.hoverAt = null;
|
|
46804
47964
|
this.viewer = options.viewer;
|
|
46805
47965
|
this.extent = options.extent;
|
|
46806
47966
|
this.settings = options.settings || {};
|
|
@@ -46810,6 +47970,9 @@ var TextureValueLabels;
|
|
|
46810
47970
|
this.sourceMap = TextureFrameSeriesAnimator.SourceMap(options.metadata);
|
|
46811
47971
|
this.tiles = options.tiles;
|
|
46812
47972
|
this.clipRing = options.clipRing;
|
|
47973
|
+
this.heightAt = options.heightAt;
|
|
47974
|
+
this.heightKey = options.heightKey;
|
|
47975
|
+
this.hover = options.hover;
|
|
46813
47976
|
this.canvas = document.createElement("canvas");
|
|
46814
47977
|
this.canvas.style.position = "absolute";
|
|
46815
47978
|
this.canvas.style.inset = "0";
|
|
@@ -46819,12 +47982,31 @@ var TextureValueLabels;
|
|
|
46819
47982
|
parent.appendChild(this.canvas);
|
|
46820
47983
|
}
|
|
46821
47984
|
this.ctx = this.canvas.getContext("2d");
|
|
47985
|
+
this.onPointerMove = (event) => {
|
|
47986
|
+
const bounds = this.viewer.canvas.getBoundingClientRect();
|
|
47987
|
+
this.hoverAt = { x: event.clientX - bounds.left, y: event.clientY - bounds.top };
|
|
47988
|
+
// At most once a frame, since a pointer reports far faster than anything is drawn.
|
|
47989
|
+
if (!this.hoverFrame) {
|
|
47990
|
+
this.hoverFrame = requestAnimationFrame(() => {
|
|
47991
|
+
this.hoverFrame = 0;
|
|
47992
|
+
this.updateHover();
|
|
47993
|
+
});
|
|
47994
|
+
}
|
|
47995
|
+
};
|
|
47996
|
+
this.onPointerLeave = () => {
|
|
47997
|
+
this.hoverAt = null;
|
|
47998
|
+
this.releaseHover();
|
|
47999
|
+
};
|
|
48000
|
+
if (this.hover && this.hover.register) {
|
|
48001
|
+
this.viewer.canvas.addEventListener("pointermove", this.onPointerMove, { passive: true });
|
|
48002
|
+
this.viewer.canvas.addEventListener("pointerleave", this.onPointerLeave);
|
|
48003
|
+
}
|
|
46822
48004
|
}
|
|
46823
48005
|
SetExtent(extent) {
|
|
46824
48006
|
this.extent = extent;
|
|
46825
48007
|
}
|
|
46826
48008
|
/**
|
|
46827
|
-
* The current frame's decoded values
|
|
48009
|
+
* The current frame's decoded values.
|
|
46828
48010
|
* @param source value pixels, one texel per cell
|
|
46829
48011
|
*/
|
|
46830
48012
|
SetSource(source) {
|
|
@@ -46832,11 +48014,17 @@ var TextureValueLabels;
|
|
|
46832
48014
|
}
|
|
46833
48015
|
/**
|
|
46834
48016
|
* The tinted pixels of the same frame, so the grid can skip cells the ramp painted nothing in.
|
|
46835
|
-
* @param painted RGBA of the presented frame, or null to fall back to coverage alone
|
|
48017
|
+
* @param painted RGBA of the presented frame, or null to fall back to the ramp or coverage alone
|
|
46836
48018
|
*/
|
|
46837
48019
|
SetPainted(painted) {
|
|
46838
48020
|
this.painted = painted;
|
|
46839
48021
|
}
|
|
48022
|
+
/**
|
|
48023
|
+
* The surface's 256 step RGBA ramp, for telling whether a cell was painted without a tinted frame.
|
|
48024
|
+
*/
|
|
48025
|
+
SetPaintedRamp(ramp) {
|
|
48026
|
+
this.paintedRamp = ramp;
|
|
48027
|
+
}
|
|
46840
48028
|
/**
|
|
46841
48029
|
* The archive's per-cell extremes, when it publishes them. Without these a label can still
|
|
46842
48030
|
* print the current reading, just not the range behind it.
|
|
@@ -46846,21 +48034,28 @@ var TextureValueLabels;
|
|
|
46846
48034
|
this.ceiling = ceiling;
|
|
46847
48035
|
}
|
|
46848
48036
|
GetDrawnCount() {
|
|
46849
|
-
return this.
|
|
48037
|
+
return this.cellLabels.size;
|
|
46850
48038
|
}
|
|
46851
48039
|
Clear() {
|
|
46852
48040
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
46853
|
-
this.
|
|
48041
|
+
this.drawnView = null;
|
|
46854
48042
|
}
|
|
46855
48043
|
Dispose() {
|
|
46856
48044
|
this.disposed = true;
|
|
48045
|
+
this.clearCellLabels();
|
|
48046
|
+
this.releaseHover();
|
|
48047
|
+
if (this.hoverFrame) {
|
|
48048
|
+
cancelAnimationFrame(this.hoverFrame);
|
|
48049
|
+
this.hoverFrame = 0;
|
|
48050
|
+
}
|
|
48051
|
+
this.viewer.canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
48052
|
+
this.viewer.canvas.removeEventListener("pointerleave", this.onPointerLeave);
|
|
46857
48053
|
if (this.canvas.parentElement) {
|
|
46858
48054
|
this.canvas.parentElement.removeChild(this.canvas);
|
|
46859
48055
|
}
|
|
46860
48056
|
}
|
|
46861
48057
|
/**
|
|
46862
|
-
* Redraws
|
|
46863
|
-
* count it considers is bounded by the view rather than by the texture.
|
|
48058
|
+
* Redraws the grid and syncs the value labels, when anything they are drawn from has changed.
|
|
46864
48059
|
*/
|
|
46865
48060
|
Render() {
|
|
46866
48061
|
var _a, _b, _c;
|
|
@@ -46868,67 +48063,296 @@ var TextureValueLabels;
|
|
|
46868
48063
|
return;
|
|
46869
48064
|
}
|
|
46870
48065
|
const view = this.viewer.canvas;
|
|
48066
|
+
const inputs = [this.source, this.painted, this.paintedRamp, this.floor, this.ceiling,
|
|
48067
|
+
view.clientWidth, view.clientHeight, this.heightKey ? this.heightKey() : null];
|
|
48068
|
+
const camera = this.viewer.camera.viewMatrix;
|
|
48069
|
+
if (this.drawnView &&
|
|
48070
|
+
Matrix4.equals(this.drawnView, camera) &&
|
|
48071
|
+
inputs.every((input, i) => input === this.drawnInputs[i])) {
|
|
48072
|
+
return;
|
|
48073
|
+
}
|
|
46871
48074
|
if (this.canvas.width !== view.clientWidth || this.canvas.height !== view.clientHeight) {
|
|
46872
48075
|
this.canvas.width = view.clientWidth;
|
|
46873
48076
|
this.canvas.height = view.clientHeight;
|
|
46874
48077
|
}
|
|
46875
48078
|
this.Clear();
|
|
48079
|
+
this.drawnView = Matrix4.clone(camera, this.drawnView || undefined);
|
|
48080
|
+
this.drawnInputs = inputs;
|
|
48081
|
+
const projection = this.viewer.camera.frustum.projectionMatrix;
|
|
48082
|
+
this.viewProjection = this.viewer.scene.mode === SceneMode.SCENE3D
|
|
48083
|
+
? Matrix4.multiply(projection, camera, this.viewProjectionScratch)
|
|
48084
|
+
: null;
|
|
46876
48085
|
const cols = this.source.width;
|
|
46877
48086
|
const rows = this.source.height;
|
|
46878
48087
|
const window = this.visibleTexels(cols, rows);
|
|
46879
48088
|
if (!window) {
|
|
48089
|
+
this.syncCellLabels([]);
|
|
46880
48090
|
return;
|
|
46881
48091
|
}
|
|
46882
|
-
|
|
46883
|
-
|
|
46884
|
-
const
|
|
46885
|
-
const
|
|
48092
|
+
// Measured where the view is looking, since in an oblique view up close the middle of the visible
|
|
48093
|
+
// texels sits out toward the horizon, where cells are a fraction of the size.
|
|
48094
|
+
const focus = this.groundAtScreenCentre();
|
|
48095
|
+
const at = focus ? this.texelAt(focus.lon, focus.lat) : null;
|
|
48096
|
+
const focused = (Boolean(at) &&
|
|
48097
|
+
at.col >= window.col0 &&
|
|
48098
|
+
at.col <= window.col1 &&
|
|
48099
|
+
at.row >= window.row0 &&
|
|
48100
|
+
at.row <= window.row1);
|
|
48101
|
+
const midCol = focused ? at.col : (window.col0 + window.col1) / 2;
|
|
48102
|
+
const midRow = focused ? at.row : (window.row0 + window.row1) / 2;
|
|
48103
|
+
const a = this.projectCell(midCol, midRow, cols, rows);
|
|
48104
|
+
const b = this.projectCell(midCol + 1, midRow + 1, cols, rows);
|
|
46886
48105
|
if (!a || !b) {
|
|
48106
|
+
// Left as they are, since one frame where the focus will not project is not the data leaving.
|
|
46887
48107
|
return;
|
|
46888
48108
|
}
|
|
46889
48109
|
const cellPixels = Math.min(Math.abs(b.x - a.x), Math.abs(b.y - a.y));
|
|
46890
48110
|
// Ahead of the label threshold on purpose: a grid still reads at a cell size where a
|
|
46891
48111
|
// number no longer fits.
|
|
46892
48112
|
this.drawGrid(window, cols, rows, cellPixels);
|
|
46893
|
-
|
|
46894
|
-
|
|
46895
|
-
|
|
46896
|
-
const
|
|
46897
|
-
|
|
46898
|
-
|
|
48113
|
+
// By area rather than the shorter side, which an oblique view foreshortens to a fraction of the other.
|
|
48114
|
+
const across = this.projectCell(midCol + 1, midRow, cols, rows);
|
|
48115
|
+
const down = this.projectCell(midCol, midRow + 1, cols, rows);
|
|
48116
|
+
const cellSize = across && down
|
|
48117
|
+
? Math.sqrt(Math.abs((across.x - a.x) * (down.y - a.y) - (across.y - a.y) * (down.x - a.x)))
|
|
48118
|
+
: cellPixels;
|
|
48119
|
+
const visibleWidth = cellSize * (window.col1 - window.col0 + 1);
|
|
48120
|
+
const visibleHeight = cellSize * (window.row1 - window.row0 + 1);
|
|
48121
|
+
if (cellSize < ((_a = this.settings.minCellPixels) !== null && _a !== void 0 ? _a : DEFAULT_MIN_CELL_PIXELS) ||
|
|
48122
|
+
Math.max(visibleWidth, visibleHeight) < MIN_VISIBLE_PIXELS) {
|
|
48123
|
+
this.syncCellLabels([]);
|
|
46899
48124
|
return;
|
|
46900
48125
|
}
|
|
46901
|
-
const parts = this.settings.parts || ["value", "floor", "ceiling"];
|
|
46902
|
-
this.ctx.font = LABEL_FONT;
|
|
46903
|
-
this.ctx.textAlign = "center";
|
|
46904
48126
|
const maxLabels = (_b = this.settings.maxLabels) !== null && _b !== void 0 ? _b : DEFAULT_MAX_LABELS;
|
|
46905
48127
|
this.stride = ChooseLabelStride({
|
|
46906
|
-
cellPixels,
|
|
48128
|
+
cellPixels: cellSize,
|
|
46907
48129
|
visibleCols: window.col1 - window.col0 + 1,
|
|
46908
48130
|
visibleRows: window.row1 - window.row0 + 1,
|
|
46909
48131
|
maxLabels,
|
|
46910
|
-
|
|
46911
|
-
// one is, so changing what a label says cannot quietly start overlapping them.
|
|
46912
|
-
minSpacingPixels: Math.max((_c = this.settings.minSpacingPixels) !== null && _c !== void 0 ? _c : DEFAULT_MIN_SPACING_PIXELS, this.widestLabel(parts)),
|
|
48132
|
+
minSpacingPixels: (_c = this.settings.minSpacingPixels) !== null && _c !== void 0 ? _c : DEFAULT_MIN_SPACING_PIXELS,
|
|
46913
48133
|
held: this.stride
|
|
46914
48134
|
});
|
|
46915
|
-
const
|
|
48135
|
+
const parts = this.settings.parts || ["value", "floor", "ceiling"];
|
|
48136
|
+
this.syncCellLabels(this.labelCells(window, cols, rows, this.stride, maxLabels, parts));
|
|
48137
|
+
}
|
|
48138
|
+
/*
|
|
48139
|
+
* One labelled cell per lattice block, keyed by the block's origin so it survives frame and stride changes.
|
|
48140
|
+
*/
|
|
48141
|
+
labelCells(window, cols, rows, stride, max, parts) {
|
|
48142
|
+
const width = this.canvas.width;
|
|
48143
|
+
const height = this.canvas.height;
|
|
46916
48144
|
const firstRow = Math.floor(window.row0 / stride) * stride;
|
|
46917
48145
|
const firstCol = Math.floor(window.col0 / stride) * stride;
|
|
46918
|
-
|
|
46919
|
-
|
|
48146
|
+
const candidates = [];
|
|
48147
|
+
for (let row = firstRow; row <= window.row1; row += stride) {
|
|
48148
|
+
for (let col = firstCol; col <= window.col1; col += stride) {
|
|
46920
48149
|
const found = this.readNear(col, row, cols, rows, stride);
|
|
46921
48150
|
if (!found) {
|
|
46922
48151
|
continue;
|
|
46923
48152
|
}
|
|
46924
|
-
const point = this.
|
|
46925
|
-
|
|
46926
|
-
|
|
48153
|
+
const point = this.projectCell(found.col, found.row, cols, rows);
|
|
48154
|
+
// A margin past the edge, so a label does not drop the moment its anchor does.
|
|
48155
|
+
const margin = LABEL_EDGE_MARGIN_PIXELS;
|
|
48156
|
+
if (!point ||
|
|
48157
|
+
point.x < -margin ||
|
|
48158
|
+
point.y < -margin ||
|
|
48159
|
+
point.x > width + margin ||
|
|
48160
|
+
point.y > height + margin) {
|
|
46927
48161
|
continue;
|
|
46928
48162
|
}
|
|
46929
|
-
this.
|
|
48163
|
+
const text = this.labelLines(found.reading, parts).join("\n");
|
|
48164
|
+
if (!text) {
|
|
48165
|
+
continue;
|
|
48166
|
+
}
|
|
48167
|
+
const key = col + "," + row;
|
|
48168
|
+
const distance = Math.hypot(point.x - width / 2, point.y - height / 2);
|
|
48169
|
+
candidates.push({
|
|
48170
|
+
key,
|
|
48171
|
+
lon: this.lonAt(found.col, cols),
|
|
48172
|
+
lat: this.latAt(found.row, rows),
|
|
48173
|
+
text,
|
|
48174
|
+
// Labels already up outrank new ones, so the capped set does not shuffle as the view pans.
|
|
48175
|
+
rank: this.cellLabels.has(key) ? distance * LABEL_KEEP_BIAS : distance
|
|
48176
|
+
});
|
|
48177
|
+
}
|
|
48178
|
+
}
|
|
48179
|
+
if (candidates.length > max) {
|
|
48180
|
+
candidates.sort((x, y) => x.rank - y.rank);
|
|
48181
|
+
candidates.length = max;
|
|
48182
|
+
}
|
|
48183
|
+
return candidates;
|
|
48184
|
+
}
|
|
48185
|
+
/*
|
|
48186
|
+
* Keeps one scene label per wanted cell, reusing those that are still wanted.
|
|
48187
|
+
*/
|
|
48188
|
+
syncCellLabels(wanted) {
|
|
48189
|
+
var _a;
|
|
48190
|
+
const keep = new Set(wanted.map((w) => w.key));
|
|
48191
|
+
const now = Date.now();
|
|
48192
|
+
let lingering = false;
|
|
48193
|
+
for (const [key, entry] of this.cellLabels) {
|
|
48194
|
+
if (keep.has(key)) {
|
|
48195
|
+
entry.missingSince = undefined;
|
|
48196
|
+
continue;
|
|
48197
|
+
}
|
|
48198
|
+
entry.missingSince = (_a = entry.missingSince) !== null && _a !== void 0 ? _a : now;
|
|
48199
|
+
if (now - entry.missingSince >= LABEL_LINGER_MS) {
|
|
48200
|
+
entry.label.Dispose();
|
|
48201
|
+
this.cellLabels.delete(key);
|
|
48202
|
+
}
|
|
48203
|
+
else {
|
|
48204
|
+
lingering = true;
|
|
48205
|
+
}
|
|
48206
|
+
}
|
|
48207
|
+
// A still camera draws nothing more, so a lingering label is given its own later look.
|
|
48208
|
+
if (lingering && !this.lingerTimer) {
|
|
48209
|
+
this.lingerTimer = setTimeout(() => {
|
|
48210
|
+
this.lingerTimer = null;
|
|
48211
|
+
if (!this.disposed) {
|
|
48212
|
+
this.drawnView = null;
|
|
48213
|
+
this.viewer.scene.requestRender();
|
|
48214
|
+
}
|
|
48215
|
+
}, LABEL_LINGER_MS);
|
|
48216
|
+
}
|
|
48217
|
+
for (const want of wanted) {
|
|
48218
|
+
const pos3d = Cartesian3.fromDegrees(want.lon, want.lat, this.surfaceHeight(want.lon, want.lat));
|
|
48219
|
+
const options = {
|
|
48220
|
+
presentation: "text",
|
|
48221
|
+
text: want.text,
|
|
48222
|
+
leader: true,
|
|
48223
|
+
selectOnClick: false
|
|
48224
|
+
};
|
|
48225
|
+
const existing = this.cellLabels.get(want.key);
|
|
48226
|
+
if (existing) {
|
|
48227
|
+
if (!Cartesian3.equalsEpsilon(existing.label.pos3d, pos3d, 0, 0.01)) {
|
|
48228
|
+
existing.label.pos3d = pos3d;
|
|
48229
|
+
}
|
|
48230
|
+
if (existing.text !== want.text) {
|
|
48231
|
+
existing.label.Configure(options);
|
|
48232
|
+
existing.text = want.text;
|
|
48233
|
+
}
|
|
48234
|
+
continue;
|
|
48235
|
+
}
|
|
48236
|
+
const id = (this.hover ? this.hover.entityId : "texture-value") + "#cell:" + want.key;
|
|
48237
|
+
const label = new EntityLabel.Label({
|
|
48238
|
+
rego: {
|
|
48239
|
+
entityId: id,
|
|
48240
|
+
menuItemId: this.hover ? this.hover.menuItemId : undefined,
|
|
48241
|
+
name: want.text,
|
|
48242
|
+
priority: 0,
|
|
48243
|
+
// No graphic, since a point or billboard visual pulls a label back to itself on rebuild.
|
|
48244
|
+
visual: new Entity({ position: pos3d })
|
|
48245
|
+
},
|
|
48246
|
+
viewer: this.viewer,
|
|
48247
|
+
pos3d,
|
|
48248
|
+
options,
|
|
48249
|
+
persistent: true,
|
|
48250
|
+
visible: () => !this.disposed
|
|
48251
|
+
});
|
|
48252
|
+
this.cellLabels.set(want.key, { label, text: want.text });
|
|
48253
|
+
}
|
|
48254
|
+
}
|
|
48255
|
+
clearCellLabels() {
|
|
48256
|
+
if (this.lingerTimer) {
|
|
48257
|
+
clearTimeout(this.lingerTimer);
|
|
48258
|
+
this.lingerTimer = null;
|
|
48259
|
+
}
|
|
48260
|
+
for (const entry of this.cellLabels.values()) {
|
|
48261
|
+
entry.label.Dispose();
|
|
48262
|
+
}
|
|
48263
|
+
this.cellLabels.clear();
|
|
48264
|
+
}
|
|
48265
|
+
/*
|
|
48266
|
+
* Shows the cell under the pointer through the register's hover label, unless something else holds it.
|
|
48267
|
+
*/
|
|
48268
|
+
updateHover() {
|
|
48269
|
+
const hover = this.hover;
|
|
48270
|
+
if (this.disposed || !hover || !hover.register || !this.source || !this.hoverAt) {
|
|
48271
|
+
return;
|
|
48272
|
+
}
|
|
48273
|
+
const register = hover.register;
|
|
48274
|
+
const target = register.GetLabelHoverTarget();
|
|
48275
|
+
const ours = Boolean(target) && register.IsLabelHovered(hover.entityId, hover.menuItemId);
|
|
48276
|
+
if (target && !ours) {
|
|
48277
|
+
this.hovering = false;
|
|
48278
|
+
return;
|
|
48279
|
+
}
|
|
48280
|
+
const under = this.readingUnder(this.hoverAt.x, this.hoverAt.y);
|
|
48281
|
+
if (!under) {
|
|
48282
|
+
this.releaseHover();
|
|
48283
|
+
return;
|
|
48284
|
+
}
|
|
48285
|
+
register.SetLabelHover({
|
|
48286
|
+
entityId: hover.entityId,
|
|
48287
|
+
menuItemId: hover.menuItemId,
|
|
48288
|
+
position: { x: this.hoverAt.x, y: this.hoverAt.y },
|
|
48289
|
+
text: under.text,
|
|
48290
|
+
pos3d: under.pos3d
|
|
48291
|
+
});
|
|
48292
|
+
this.hovering = true;
|
|
48293
|
+
}
|
|
48294
|
+
releaseHover() {
|
|
48295
|
+
const hover = this.hover;
|
|
48296
|
+
if (!this.hovering || !hover || !hover.register) {
|
|
48297
|
+
return;
|
|
48298
|
+
}
|
|
48299
|
+
this.hovering = false;
|
|
48300
|
+
if (hover.register.IsLabelHovered(hover.entityId, hover.menuItemId)) {
|
|
48301
|
+
hover.register.ClearLabelHover();
|
|
48302
|
+
}
|
|
48303
|
+
}
|
|
48304
|
+
/*
|
|
48305
|
+
* The text for the painted cell under a window position and its centre on the sheet, or null.
|
|
48306
|
+
*/
|
|
48307
|
+
readingUnder(x, y) {
|
|
48308
|
+
const at = this.cellUnder(x, y);
|
|
48309
|
+
if (!at) {
|
|
48310
|
+
return null;
|
|
48311
|
+
}
|
|
48312
|
+
const cols = this.source.width;
|
|
48313
|
+
const found = this.readAt(at.col, at.row, cols);
|
|
48314
|
+
if (!found || !this.paintedAt(at.col, at.row, cols)) {
|
|
48315
|
+
return null;
|
|
48316
|
+
}
|
|
48317
|
+
const text = this.labelLines(found, this.settings.parts || ["value", "floor", "ceiling"]).join("\n");
|
|
48318
|
+
if (!text) {
|
|
48319
|
+
return null;
|
|
48320
|
+
}
|
|
48321
|
+
const lon = this.lonAt(at.col, cols);
|
|
48322
|
+
const lat = this.latAt(at.row, this.source.height);
|
|
48323
|
+
return { text, pos3d: Cartesian3.fromDegrees(lon, lat, this.surfaceHeight(lon, lat)) };
|
|
48324
|
+
}
|
|
48325
|
+
/*
|
|
48326
|
+
* The cell under a window position, found on the sheet rather than on the ground beneath it.
|
|
48327
|
+
*/
|
|
48328
|
+
cellUnder(x, y) {
|
|
48329
|
+
const scene = this.viewer.scene;
|
|
48330
|
+
const camera = this.viewer.camera;
|
|
48331
|
+
const screen = new Cartesian2(x, y);
|
|
48332
|
+
const ray = camera.getPickRay(screen);
|
|
48333
|
+
if (!ray) {
|
|
48334
|
+
return null;
|
|
48335
|
+
}
|
|
48336
|
+
let hit = (scene.globe && scene.globe.pick(ray, scene)) || camera.pickEllipsoid(screen);
|
|
48337
|
+
if (!hit) {
|
|
48338
|
+
return null;
|
|
48339
|
+
}
|
|
48340
|
+
const radii = Ellipsoid.WGS84.radii;
|
|
48341
|
+
for (let i = 0; i < HOVER_REFINE_STEPS && this.heightAt; i++) {
|
|
48342
|
+
const carto = Cartographic.fromCartesian(hit);
|
|
48343
|
+
const height = this.heightAt(Math$1.toDegrees(carto.longitude), Math$1.toDegrees(carto.latitude));
|
|
48344
|
+
if (height == null) {
|
|
48345
|
+
break;
|
|
48346
|
+
}
|
|
48347
|
+
const inflated = new Ellipsoid(radii.x + height, radii.y + height, radii.z + height);
|
|
48348
|
+
const interval = IntersectionTests.rayEllipsoid(ray, inflated);
|
|
48349
|
+
if (!interval) {
|
|
48350
|
+
break;
|
|
46930
48351
|
}
|
|
48352
|
+
hit = Ray.getPoint(ray, interval.start);
|
|
46931
48353
|
}
|
|
48354
|
+
const carto = Cartographic.fromCartesian(hit);
|
|
48355
|
+
return this.texelAt(Math$1.toDegrees(carto.longitude), Math$1.toDegrees(carto.latitude));
|
|
46932
48356
|
}
|
|
46933
48357
|
/*
|
|
46934
48358
|
* Outlines each covered cell, from a projected lattice of its corners.
|
|
@@ -46956,44 +48380,25 @@ var TextureValueLabels;
|
|
|
46956
48380
|
if (wide * high > MAX_GRID_CELLS) {
|
|
46957
48381
|
return;
|
|
46958
48382
|
}
|
|
46959
|
-
const lattice = new
|
|
46960
|
-
|
|
46961
|
-
|
|
46962
|
-
|
|
46963
|
-
|
|
46964
|
-
|
|
46965
|
-
|
|
46966
|
-
}
|
|
46967
|
-
}
|
|
46968
|
-
const path = new Path2D();
|
|
46969
|
-
let drew = false;
|
|
48383
|
+
const lattice = new Lattice(wide + 1, high + 1, (c, r) => {
|
|
48384
|
+
// Corners sit half a texel out from the centres lonAt/latAt return.
|
|
48385
|
+
const lon = this.lonAt(window.col0 + c - 0.5, cols);
|
|
48386
|
+
const lat = this.latAt(window.row0 + r - 0.5, rows);
|
|
48387
|
+
return this.projectInto(lon, lat);
|
|
48388
|
+
});
|
|
48389
|
+
const covered = new Uint8Array(wide * high);
|
|
46970
48390
|
for (let r = 0; r < high; r++) {
|
|
46971
48391
|
for (let c = 0; c < wide; c++) {
|
|
46972
|
-
|
|
46973
|
-
continue;
|
|
46974
|
-
}
|
|
46975
|
-
const tl = lattice[r * (wide + 1) + c];
|
|
46976
|
-
const tr = lattice[r * (wide + 1) + c + 1];
|
|
46977
|
-
const bl = lattice[(r + 1) * (wide + 1) + c];
|
|
46978
|
-
const br = lattice[(r + 1) * (wide + 1) + c + 1];
|
|
46979
|
-
if (!tl || !tr || !bl || !br) {
|
|
46980
|
-
continue;
|
|
46981
|
-
}
|
|
46982
|
-
path.moveTo(tl.x, tl.y);
|
|
46983
|
-
path.lineTo(tr.x, tr.y);
|
|
46984
|
-
path.lineTo(br.x, br.y);
|
|
46985
|
-
path.lineTo(bl.x, bl.y);
|
|
46986
|
-
path.closePath();
|
|
46987
|
-
drew = true;
|
|
48392
|
+
covered[r * wide + c] = this.isCovered(window.col0 + c, window.row0 + r, cols) ? 1 : 0;
|
|
46988
48393
|
}
|
|
46989
48394
|
}
|
|
46990
|
-
|
|
48395
|
+
const path = new Path2D();
|
|
48396
|
+
if (!lattice.Trace(path, covered, wide, high)) {
|
|
46991
48397
|
return;
|
|
46992
48398
|
}
|
|
46993
48399
|
this.ctx.strokeStyle = color;
|
|
46994
48400
|
this.ctx.lineWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
46995
48401
|
this.ctx.stroke(path);
|
|
46996
|
-
+" alpha" + this.ctx.globalAlpha;
|
|
46997
48402
|
}
|
|
46998
48403
|
/*
|
|
46999
48404
|
* Restricts drawing to the polygon, so nothing is ruled over ground it does not cover.
|
|
@@ -47015,33 +48420,11 @@ var TextureValueLabels;
|
|
|
47015
48420
|
path.closePath();
|
|
47016
48421
|
this.ctx.clip(path);
|
|
47017
48422
|
}
|
|
47018
|
-
/*
|
|
47019
|
-
* The cell of whichever tile covers a point, as screen corners.
|
|
47020
|
-
*/
|
|
47021
|
-
tileCellOutline(lon, lat) {
|
|
47022
|
-
for (const tile of this.tiles) {
|
|
47023
|
-
if (lon < tile.West || lon > tile.East || lat < tile.South || lat > tile.North) {
|
|
47024
|
-
continue;
|
|
47025
|
-
}
|
|
47026
|
-
const c = (Math.min(tile.ResolutionX - 1, Math.max(0, Math.floor((lon - tile.West) / (tile.East - tile.West) * tile.ResolutionX))));
|
|
47027
|
-
const r = (Math.min(tile.ResolutionY - 1, Math.max(0, Math.floor((tile.North - lat) / (tile.North - tile.South) * tile.ResolutionY))));
|
|
47028
|
-
const w0 = tile.West + (c / tile.ResolutionX) * (tile.East - tile.West);
|
|
47029
|
-
const w1 = tile.West + ((c + 1) / tile.ResolutionX) * (tile.East - tile.West);
|
|
47030
|
-
const n0 = tile.North - (r / tile.ResolutionY) * (tile.North - tile.South);
|
|
47031
|
-
const n1 = tile.North - ((r + 1) / tile.ResolutionY) * (tile.North - tile.South);
|
|
47032
|
-
const corners = [
|
|
47033
|
-
this.project(w0, n0), this.project(w1, n0),
|
|
47034
|
-
this.project(w1, n1), this.project(w0, n1)
|
|
47035
|
-
];
|
|
47036
|
-
return corners.every((q) => q) ? corners : null;
|
|
47037
|
-
}
|
|
47038
|
-
return null;
|
|
47039
|
-
}
|
|
47040
48423
|
/*
|
|
47041
48424
|
* Outlines each tile's own cells, so cell size still shows where the mesh is refined.
|
|
47042
48425
|
*/
|
|
47043
48426
|
drawTileGrid(color) {
|
|
47044
|
-
var _a, _b
|
|
48427
|
+
var _a, _b;
|
|
47045
48428
|
const view = this.viewer.camera.computeViewRectangle(Ellipsoid.WGS84);
|
|
47046
48429
|
if (!view) {
|
|
47047
48430
|
return;
|
|
@@ -47070,7 +48453,7 @@ var TextureValueLabels;
|
|
|
47070
48453
|
coarse.lineTo(tr.x, tr.y);
|
|
47071
48454
|
coarse.lineTo(br.x, br.y);
|
|
47072
48455
|
coarse.lineTo(bl.x, bl.y);
|
|
47073
|
-
coarse.
|
|
48456
|
+
coarse.lineTo(tl.x, tl.y);
|
|
47074
48457
|
drewCoarse = true;
|
|
47075
48458
|
};
|
|
47076
48459
|
// Planned for every visible tile BEFORE any is drawn.
|
|
@@ -47131,31 +48514,29 @@ var TextureValueLabels;
|
|
|
47131
48514
|
// moves, and so each step up is a subset of the one below it.
|
|
47132
48515
|
const firstR = Math.floor(r0 / step) * step;
|
|
47133
48516
|
const firstC = Math.floor(c0 / step) * step;
|
|
47134
|
-
|
|
47135
|
-
|
|
47136
|
-
|
|
47137
|
-
|
|
47138
|
-
|
|
47139
|
-
|
|
47140
|
-
|
|
47141
|
-
|
|
47142
|
-
|
|
47143
|
-
|
|
47144
|
-
|
|
47145
|
-
|
|
47146
|
-
|
|
47147
|
-
|
|
47148
|
-
|
|
47149
|
-
|
|
47150
|
-
|
|
47151
|
-
|
|
47152
|
-
|
|
47153
|
-
|
|
47154
|
-
|
|
47155
|
-
|
|
47156
|
-
|
|
47157
|
-
drew = true;
|
|
47158
|
-
}
|
|
48517
|
+
const wide = Math.ceil((c1 - firstC) / step);
|
|
48518
|
+
const high = Math.ceil((r1 - firstR) / step);
|
|
48519
|
+
const lonOf = (c) => tile.West
|
|
48520
|
+
+ (Math.min(firstC + c * step, tile.ResolutionX) / tile.ResolutionX) * (tile.East - tile.West);
|
|
48521
|
+
const latOf = (r) => tile.North
|
|
48522
|
+
- (Math.min(firstR + r * step, tile.ResolutionY) / tile.ResolutionY) * (tile.North - tile.South);
|
|
48523
|
+
const covered = new Uint8Array(wide * high);
|
|
48524
|
+
let any = false;
|
|
48525
|
+
for (let r = 0; r < high; r++) {
|
|
48526
|
+
for (let c = 0; c < wide; c++) {
|
|
48527
|
+
if (this.coveredAt((lonOf(c) + lonOf(c + 1)) / 2, (latOf(r) + latOf(r + 1)) / 2)) {
|
|
48528
|
+
covered[r * wide + c] = 1;
|
|
48529
|
+
any = true;
|
|
48530
|
+
}
|
|
48531
|
+
}
|
|
48532
|
+
}
|
|
48533
|
+
if (!any) {
|
|
48534
|
+
continue;
|
|
48535
|
+
}
|
|
48536
|
+
// Projected on demand, since a sparse raster leaves most corners belonging to no cell.
|
|
48537
|
+
const lattice = new Lattice(wide + 1, high + 1, (c, r) => this.projectInto(lonOf(c), latOf(r)));
|
|
48538
|
+
if (lattice.Trace(path, covered, wide, high)) {
|
|
48539
|
+
drew = true;
|
|
47159
48540
|
}
|
|
47160
48541
|
}
|
|
47161
48542
|
const gridWidth = (_b = this.settings.gridWidthPixels) !== null && _b !== void 0 ? _b : DEFAULT_GRID_WIDTH_PIXELS;
|
|
@@ -47173,7 +48554,7 @@ var TextureValueLabels;
|
|
|
47173
48554
|
return;
|
|
47174
48555
|
}
|
|
47175
48556
|
this.ctx.strokeStyle = color;
|
|
47176
|
-
this.ctx.lineWidth =
|
|
48557
|
+
this.ctx.lineWidth = gridWidth;
|
|
47177
48558
|
this.ctx.stroke(path);
|
|
47178
48559
|
}
|
|
47179
48560
|
/*
|
|
@@ -47193,7 +48574,7 @@ var TextureValueLabels;
|
|
|
47193
48574
|
};
|
|
47194
48575
|
}
|
|
47195
48576
|
/*
|
|
47196
|
-
* Whether the composited raster holds data at a lon/lat.
|
|
48577
|
+
* Whether the composited raster holds painted data at a lon/lat.
|
|
47197
48578
|
*/
|
|
47198
48579
|
coveredAt(lon, lat) {
|
|
47199
48580
|
if (!this.source) {
|
|
@@ -47206,60 +48587,20 @@ var TextureValueLabels;
|
|
|
47206
48587
|
if (col < 0 || row < 0 || col >= cols || row >= rows) {
|
|
47207
48588
|
return false;
|
|
47208
48589
|
}
|
|
47209
|
-
const at = (row * cols + col) * 4 + 3;
|
|
47210
48590
|
// Painted, not merely covered: a dry cell holds a reading the ramp deliberately hides, and
|
|
47211
48591
|
// ruling a grid over it draws detail where the picture shows none.
|
|
47212
|
-
if (this.painted
|
|
47213
|
-
return this.
|
|
48592
|
+
if (this.painted || this.paintedRamp) {
|
|
48593
|
+
return this.paintedAt(col, row, cols);
|
|
47214
48594
|
}
|
|
47215
|
-
return this.source.data[
|
|
48595
|
+
return this.source.data[(row * cols + col) * 4 + 3] >= 128;
|
|
47216
48596
|
}
|
|
47217
48597
|
isCovered(col, row, cols) {
|
|
47218
48598
|
return Boolean(this.source && this.source.data[(row * cols + col) * 4 + 3] >= 128);
|
|
47219
48599
|
}
|
|
47220
48600
|
/*
|
|
47221
|
-
*
|
|
48601
|
+
* What a label says about one reading, a line at a time.
|
|
47222
48602
|
*/
|
|
47223
|
-
|
|
47224
|
-
const sample = [];
|
|
47225
|
-
if (parts.indexOf("value") >= 0) {
|
|
47226
|
-
sample.push(valueLine("-000.00", LabelUnits(this.metadata)));
|
|
47227
|
-
}
|
|
47228
|
-
const range = [];
|
|
47229
|
-
const showsRange = !TextureFrameSeriesAnimator.IsTransformed(this.metadata);
|
|
47230
|
-
if (showsRange && parts.indexOf("floor") >= 0) {
|
|
47231
|
-
range.push("min: -000.00");
|
|
47232
|
-
}
|
|
47233
|
-
if (showsRange && parts.indexOf("ceiling") >= 0) {
|
|
47234
|
-
range.push("max: -000.00");
|
|
47235
|
-
}
|
|
47236
|
-
if (range.length) {
|
|
47237
|
-
sample.push(range.join(" - "));
|
|
47238
|
-
}
|
|
47239
|
-
if (!sample.length) {
|
|
47240
|
-
return 0;
|
|
47241
|
-
}
|
|
47242
|
-
const widest = Math.max(...sample.map((line) => this.ctx.measureText(line).width));
|
|
47243
|
-
return widest + LABEL_GAP_PIXELS;
|
|
47244
|
-
}
|
|
47245
|
-
/*
|
|
47246
|
-
* The four screen corners of one cell, or null if any of them will not project.
|
|
47247
|
-
*/
|
|
47248
|
-
cellOutline(col, row, cols, rows) {
|
|
47249
|
-
// On a pyramid the composited texel is not a cell. Outlining it would draw a second,
|
|
47250
|
-
// uniform lattice over the tile grid, misaligned with it everywhere.
|
|
47251
|
-
if (this.tiles && this.tiles.length) {
|
|
47252
|
-
return this.tileCellOutline(this.lonAt(col, cols), this.latAt(row, rows));
|
|
47253
|
-
}
|
|
47254
|
-
const corners = [
|
|
47255
|
-
this.project(this.lonAt(col - 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
47256
|
-
this.project(this.lonAt(col + 0.5, cols), this.latAt(row - 0.5, rows)),
|
|
47257
|
-
this.project(this.lonAt(col + 0.5, cols), this.latAt(row + 0.5, rows)),
|
|
47258
|
-
this.project(this.lonAt(col - 0.5, cols), this.latAt(row + 0.5, rows))
|
|
47259
|
-
];
|
|
47260
|
-
return corners.every((c) => c) ? corners : null;
|
|
47261
|
-
}
|
|
47262
|
-
drawLabel(point, reading, parts, outline) {
|
|
48603
|
+
labelLines(reading, parts) {
|
|
47263
48604
|
var _a;
|
|
47264
48605
|
const dp = (_a = this.settings.decimals) !== null && _a !== void 0 ? _a : (Math.abs(reading.value) < 10 ? 2 : 1);
|
|
47265
48606
|
const lines = [];
|
|
@@ -47278,32 +48619,7 @@ var TextureValueLabels;
|
|
|
47278
48619
|
if (range.length) {
|
|
47279
48620
|
lines.push(range.join(" - "));
|
|
47280
48621
|
}
|
|
47281
|
-
|
|
47282
|
-
return;
|
|
47283
|
-
}
|
|
47284
|
-
if (outline) {
|
|
47285
|
-
this.ctx.strokeStyle = LABEL_ANCHOR_COLOR;
|
|
47286
|
-
this.ctx.lineWidth = 1.5;
|
|
47287
|
-
this.ctx.beginPath();
|
|
47288
|
-
this.ctx.moveTo(outline[0].x, outline[0].y);
|
|
47289
|
-
for (let i = 1; i < outline.length; i++) {
|
|
47290
|
-
this.ctx.lineTo(outline[i].x, outline[i].y);
|
|
47291
|
-
}
|
|
47292
|
-
this.ctx.closePath();
|
|
47293
|
-
this.ctx.stroke();
|
|
47294
|
-
}
|
|
47295
|
-
const width = Math.max(...lines.map((l) => this.ctx.measureText(l).width)) + 10;
|
|
47296
|
-
const height = lines.length * LINE_HEIGHT + 6;
|
|
47297
|
-
const top = point.y - height / 2;
|
|
47298
|
-
this.ctx.fillStyle = "rgba(16,16,14,0.72)";
|
|
47299
|
-
this.ctx.beginPath();
|
|
47300
|
-
this.ctx.roundRect(point.x - width / 2, top, width, height, LABEL_RADIUS);
|
|
47301
|
-
this.ctx.fill();
|
|
47302
|
-
lines.forEach((line, i) => {
|
|
47303
|
-
this.ctx.fillStyle = i === 0 ? "#ffffff" : "#a8a89f";
|
|
47304
|
-
this.ctx.fillText(line, point.x, top + LINE_HEIGHT * (i + 1) - 1);
|
|
47305
|
-
});
|
|
47306
|
-
this.drawn++;
|
|
48622
|
+
return lines;
|
|
47307
48623
|
}
|
|
47308
48624
|
/*
|
|
47309
48625
|
* The first cell with a reading at or near a lattice point, searched within its own block.
|
|
@@ -47330,11 +48646,24 @@ var TextureValueLabels;
|
|
|
47330
48646
|
return null;
|
|
47331
48647
|
}
|
|
47332
48648
|
paintedAt(col, row, cols) {
|
|
47333
|
-
|
|
47334
|
-
|
|
48649
|
+
const at = (row * cols + col) * 4;
|
|
48650
|
+
if (this.painted && this.painted.length === this.source.data.length) {
|
|
48651
|
+
return this.painted[at + 3] > 0;
|
|
47335
48652
|
}
|
|
47336
|
-
|
|
47337
|
-
|
|
48653
|
+
if (this.paintedRamp) {
|
|
48654
|
+
const data = this.source.data;
|
|
48655
|
+
if (data[at + 3] === 0) {
|
|
48656
|
+
return false;
|
|
48657
|
+
}
|
|
48658
|
+
// The 8 bit grey the ramp is indexed on, which is what a tinted frame would have used.
|
|
48659
|
+
const grey = TextureFrameSeriesAnimator.IsPackedValue(this.metadata)
|
|
48660
|
+
? Math.round((data[at] * 256 + data[at + 1]) / 65535 * 255)
|
|
48661
|
+
: data[at];
|
|
48662
|
+
const steps = this.paintedRamp.length / 4;
|
|
48663
|
+
const index = Math.min(steps - 1, Math.round(grey / 255 * (steps - 1)));
|
|
48664
|
+
return this.paintedRamp[index * 4 + 3] > 0;
|
|
48665
|
+
}
|
|
48666
|
+
return true;
|
|
47338
48667
|
}
|
|
47339
48668
|
// Decodes one cell, skipping anything the texture marks as having no data.
|
|
47340
48669
|
readAt(col, row, cols) {
|
|
@@ -47350,6 +48679,16 @@ var TextureValueLabels;
|
|
|
47350
48679
|
sourceMap: this.sourceMap
|
|
47351
48680
|
});
|
|
47352
48681
|
}
|
|
48682
|
+
/*
|
|
48683
|
+
* The texel holding a lon/lat, or null outside the raster.
|
|
48684
|
+
*/
|
|
48685
|
+
texelAt(lon, lat) {
|
|
48686
|
+
const cols = this.source.width;
|
|
48687
|
+
const rows = this.source.height;
|
|
48688
|
+
const col = Math.floor((lon - this.extent.West) / (this.extent.East - this.extent.West) * cols);
|
|
48689
|
+
const row = Math.floor((this.extent.North - lat) / (this.extent.North - this.extent.South) * rows);
|
|
48690
|
+
return col < 0 || row < 0 || col >= cols || row >= rows ? null : { col, row };
|
|
48691
|
+
}
|
|
47353
48692
|
lonAt(col, cols) {
|
|
47354
48693
|
return this.extent.West + ((col + 0.5) / cols) * (this.extent.East - this.extent.West);
|
|
47355
48694
|
}
|
|
@@ -47378,15 +48717,102 @@ var TextureValueLabels;
|
|
|
47378
48717
|
row1: Math.min(rows - 1, Math.ceil((this.extent.North - south) / spanLat * rows) + 1)
|
|
47379
48718
|
};
|
|
47380
48719
|
}
|
|
48720
|
+
// Metres the sheet sits at over a point, or the ellipsoid when nothing says otherwise.
|
|
48721
|
+
surfaceHeight(lon, lat) {
|
|
48722
|
+
const height = this.heightAt ? this.heightAt(lon, lat) : null;
|
|
48723
|
+
return height == null || !isFinite(height) ? 0 : height;
|
|
48724
|
+
}
|
|
48725
|
+
projectCell(col, row, cols, rows) {
|
|
48726
|
+
return this.project(this.lonAt(col, cols), this.latAt(row, rows));
|
|
48727
|
+
}
|
|
48728
|
+
// Projected at the sheet's own height, since up close the ellipsoid is metres from where it is drawn.
|
|
47381
48729
|
project(lon, lat) {
|
|
47382
|
-
|
|
48730
|
+
return this.projectInto(lon, lat) ? { x: this.scratchWindow.x, y: this.scratchWindow.y } : null;
|
|
48731
|
+
}
|
|
48732
|
+
/*
|
|
48733
|
+
* Projects into the shared scratch window position, returning whether it landed anywhere.
|
|
48734
|
+
*/
|
|
48735
|
+
projectInto(lon, lat) {
|
|
48736
|
+
const world = Cartesian3.fromDegrees(lon, lat, this.surfaceHeight(lon, lat), Ellipsoid.WGS84, this.scratchWorld);
|
|
48737
|
+
// One matrix multiply in 3D, where the general path rederives the viewport and frustum per point.
|
|
48738
|
+
if (this.viewProjection) {
|
|
48739
|
+
const clip = Matrix4.multiplyByVector(this.viewProjection, Cartesian4.fromElements(world.x, world.y, world.z, 1, this.scratchClip), this.scratchClip);
|
|
48740
|
+
if (!(clip.w > 0)) {
|
|
48741
|
+
return null;
|
|
48742
|
+
}
|
|
48743
|
+
const view = this.viewer.canvas;
|
|
48744
|
+
this.scratchWindow.x = (clip.x / clip.w * 0.5 + 0.5) * view.clientWidth;
|
|
48745
|
+
this.scratchWindow.y = (0.5 - clip.y / clip.w * 0.5) * view.clientHeight;
|
|
48746
|
+
return this.scratchWindow;
|
|
48747
|
+
}
|
|
47383
48748
|
const transforms = SceneTransforms;
|
|
47384
48749
|
// Renamed in newer Cesium, and this library runs on whatever the host app supplies.
|
|
47385
48750
|
const fn = transforms.worldToWindowCoordinates || transforms.wgs84ToWindowCoordinates;
|
|
47386
|
-
return fn ? fn.call(transforms, this.viewer.scene, world) : null;
|
|
48751
|
+
return fn ? fn.call(transforms, this.viewer.scene, world, this.scratchWindow) || null : null;
|
|
47387
48752
|
}
|
|
47388
48753
|
}
|
|
47389
48754
|
TextureValueLabels.Labels = Labels;
|
|
48755
|
+
/*
|
|
48756
|
+
* A grid of window positions projected at most once each, and the cell edges traced through them.
|
|
48757
|
+
*/
|
|
48758
|
+
class Lattice {
|
|
48759
|
+
constructor(wide, high, projectAt) {
|
|
48760
|
+
this.wide = wide;
|
|
48761
|
+
this.projectAt = projectAt;
|
|
48762
|
+
this.xs = new Float64Array(wide * high);
|
|
48763
|
+
this.ys = new Float64Array(wide * high);
|
|
48764
|
+
this.state = new Uint8Array(wide * high);
|
|
48765
|
+
}
|
|
48766
|
+
at(c, r) {
|
|
48767
|
+
const i = r * this.wide + c;
|
|
48768
|
+
if (this.state[i] === 0) {
|
|
48769
|
+
const p = this.projectAt(c, r);
|
|
48770
|
+
if (p) {
|
|
48771
|
+
this.xs[i] = p.x;
|
|
48772
|
+
this.ys[i] = p.y;
|
|
48773
|
+
this.state[i] = 1;
|
|
48774
|
+
}
|
|
48775
|
+
else {
|
|
48776
|
+
this.state[i] = 2;
|
|
48777
|
+
}
|
|
48778
|
+
}
|
|
48779
|
+
return this.state[i] === 1 ? i : -1;
|
|
48780
|
+
}
|
|
48781
|
+
edge(path, a, b) {
|
|
48782
|
+
if (a < 0 || b < 0) {
|
|
48783
|
+
return false;
|
|
48784
|
+
}
|
|
48785
|
+
path.moveTo(this.xs[a], this.ys[a]);
|
|
48786
|
+
path.lineTo(this.xs[b], this.ys[b]);
|
|
48787
|
+
return true;
|
|
48788
|
+
}
|
|
48789
|
+
/*
|
|
48790
|
+
* Adds each covered cell's edges to a path, a shared edge once, and says whether any were added.
|
|
48791
|
+
*/
|
|
48792
|
+
Trace(path, covered, cellsWide, cellsHigh) {
|
|
48793
|
+
let drew = false;
|
|
48794
|
+
for (let r = 0; r < cellsHigh; r++) {
|
|
48795
|
+
for (let c = 0; c < cellsWide; c++) {
|
|
48796
|
+
if (!covered[r * cellsWide + c]) {
|
|
48797
|
+
continue;
|
|
48798
|
+
}
|
|
48799
|
+
const tl = this.at(c, r);
|
|
48800
|
+
const tr = this.at(c + 1, r);
|
|
48801
|
+
const bl = this.at(c, r + 1);
|
|
48802
|
+
if (!(r > 0 && covered[(r - 1) * cellsWide + c])) {
|
|
48803
|
+
drew = this.edge(path, tl, tr) || drew;
|
|
48804
|
+
}
|
|
48805
|
+
if (!(c > 0 && covered[r * cellsWide + c - 1])) {
|
|
48806
|
+
drew = this.edge(path, tl, bl) || drew;
|
|
48807
|
+
}
|
|
48808
|
+
const br = this.at(c + 1, r + 1);
|
|
48809
|
+
drew = this.edge(path, tr, br) || drew;
|
|
48810
|
+
drew = this.edge(path, bl, br) || drew;
|
|
48811
|
+
}
|
|
48812
|
+
}
|
|
48813
|
+
return drew;
|
|
48814
|
+
}
|
|
48815
|
+
}
|
|
47390
48816
|
/*
|
|
47391
48817
|
* The value line. Units go on it alone: repeating them on the range underneath doubles the widest
|
|
47392
48818
|
* label to say nothing, and the two lines are plainly the same quantity.
|
|
@@ -47487,6 +48913,9 @@ in vec2 st;
|
|
|
47487
48913
|
in float skirt;
|
|
47488
48914
|
|
|
47489
48915
|
uniform sampler2D u_valueTexture;
|
|
48916
|
+
// The frame being faded away from, and how far the fade has got. The same texture at 1 when settled.
|
|
48917
|
+
uniform sampler2D u_valueTextureFrom;
|
|
48918
|
+
uniform float u_valueFade;
|
|
47490
48919
|
uniform sampler2D u_groundTexture;
|
|
47491
48920
|
uniform float u_valuePacked;
|
|
47492
48921
|
uniform float u_valueMin;
|
|
@@ -47607,18 +49036,22 @@ float decodeValue(vec4 texel) {
|
|
|
47607
49036
|
}
|
|
47608
49037
|
|
|
47609
49038
|
void main() {
|
|
47610
|
-
vec4
|
|
49039
|
+
vec4 texelTo = texture(u_valueTexture, st);
|
|
49040
|
+
vec4 texelFrom = texture(u_valueTextureFrom, st);
|
|
47611
49041
|
v_st = st;
|
|
49042
|
+
// Decoded before mixing, since blending a packed pair byte by byte reads as some other value
|
|
49043
|
+
// halfway through the fade.
|
|
49044
|
+
float raw = mix(decodeValue(texelFrom), decodeValue(texelTo), u_valueFade);
|
|
49045
|
+
float alpha = mix(texelFrom.a, texelTo.a, u_valueFade);
|
|
47612
49046
|
|
|
47613
49047
|
// Inside a region the scene knows is water, a texel with no value still has to paint, or the
|
|
47614
49048
|
// hole the region exists to close is still a hole.
|
|
47615
49049
|
// The value it paints is the shallowest the ramp is not transparent at.
|
|
47616
49050
|
float inMask = u_maskWeight * texture(u_maskTexture, st).a;
|
|
47617
|
-
float hasData = step(u_coverageCutoffVertex,
|
|
47618
|
-
float raw = decodeValue(texel);
|
|
49051
|
+
float hasData = step(u_coverageCutoffVertex, alpha);
|
|
47619
49052
|
float filled = mix(u_maskFillValue, max(raw, u_maskFillValue), hasData);
|
|
47620
49053
|
v_value = mix(raw, filled, inMask * u_maskFill);
|
|
47621
|
-
v_coverage = max(
|
|
49054
|
+
v_coverage = max(alpha, inMask * u_maskFill);
|
|
47622
49055
|
|
|
47623
49056
|
// Exaggeration stretches the span above the archive's minimum rather than the height itself. On an
|
|
47624
49057
|
// elevation the height is measured from the ellipsoid, tens of metres from any of the data, so
|
|
@@ -47661,6 +49094,8 @@ void main() {
|
|
|
47661
49094
|
`;
|
|
47662
49095
|
const FRAGMENT_SHADER_GLSL300 = `
|
|
47663
49096
|
uniform sampler2D u_valueTexture;
|
|
49097
|
+
uniform sampler2D u_valueTextureFrom;
|
|
49098
|
+
uniform float u_valueFade;
|
|
47664
49099
|
uniform sampler2D u_rampTexture;
|
|
47665
49100
|
uniform float u_coverageCutoff;
|
|
47666
49101
|
uniform vec2 u_texelSize;
|
|
@@ -47697,12 +49132,15 @@ in vec2 v_waveMetres;
|
|
|
47697
49132
|
* Scaled per channel for the same reason the vertex shader's decode is: reassembling the pair reaches
|
|
47698
49133
|
* 65535, which mediump does not have to represent, and this is the shader that may not get highp.
|
|
47699
49134
|
*/
|
|
47700
|
-
float
|
|
47701
|
-
vec4 texel = texture(u_valueTexture, uv);
|
|
49135
|
+
float decodeValue(vec4 texel) {
|
|
47702
49136
|
float packed16 = texel.r * (255.0 * 256.0 / 65535.0) + texel.g * (255.0 / 65535.0);
|
|
47703
49137
|
return mix(texel.r, packed16, u_valuePacked);
|
|
47704
49138
|
}
|
|
47705
49139
|
|
|
49140
|
+
float valueAt(vec2 uv) {
|
|
49141
|
+
return mix(decodeValue(texture(u_valueTextureFrom, uv)), decodeValue(texture(u_valueTexture, uv)), u_valueFade);
|
|
49142
|
+
}
|
|
49143
|
+
|
|
47706
49144
|
//#water
|
|
47707
49145
|
/*
|
|
47708
49146
|
* Slope of the ripple field at a point, in metres of rise per metre travelled.
|
|
@@ -48149,6 +49587,13 @@ void main() {
|
|
|
48149
49587
|
this.rampTexture = null;
|
|
48150
49588
|
this.texture = null;
|
|
48151
49589
|
this.textureDirty = true;
|
|
49590
|
+
// Fed from a fading source: the frame being left, and which pixels each texture holds.
|
|
49591
|
+
this.fade = null;
|
|
49592
|
+
this.textureFrom = null;
|
|
49593
|
+
this.uploadedTo = null;
|
|
49594
|
+
this.uploadedFrom = null;
|
|
49595
|
+
// Size of whichever value raster is bound, canvas or pixels.
|
|
49596
|
+
this.sourceDims = null;
|
|
48152
49597
|
this.shaderProgram = null;
|
|
48153
49598
|
this.shaderProgramWater = false;
|
|
48154
49599
|
this.renderState = null;
|
|
@@ -48319,6 +49764,65 @@ void main() {
|
|
|
48319
49764
|
this.maskGroundTexture = null;
|
|
48320
49765
|
}
|
|
48321
49766
|
}
|
|
49767
|
+
/**
|
|
49768
|
+
* Metres above the ellipsoid the sheet sits at over a point, or null unless a fading source is bound.
|
|
49769
|
+
*/
|
|
49770
|
+
HeightAt(lon, lat) {
|
|
49771
|
+
const pixels = this.fade ? this.fade.to : null;
|
|
49772
|
+
const dims = this.sourceDims;
|
|
49773
|
+
if (!pixels || !dims) {
|
|
49774
|
+
return null;
|
|
49775
|
+
}
|
|
49776
|
+
const fx = (lon - this.extent.West) / (this.extent.East - this.extent.West);
|
|
49777
|
+
const fy = (this.extent.North - lat) / (this.extent.North - this.extent.South);
|
|
49778
|
+
if (!(fx >= 0 && fx <= 1 && fy >= 0 && fy <= 1)) {
|
|
49779
|
+
return null;
|
|
49780
|
+
}
|
|
49781
|
+
const col = Math.min(dims.width - 1, Math.floor(fx * dims.width));
|
|
49782
|
+
const row = Math.min(dims.height - 1, Math.floor(fy * dims.height));
|
|
49783
|
+
const at = (row * dims.width + col) * 4;
|
|
49784
|
+
const coverage = pixels[at + 3] / 255;
|
|
49785
|
+
const normalised = this.packedValue
|
|
49786
|
+
? (pixels[at] * 256 + pixels[at + 1]) / 65535
|
|
49787
|
+
: pixels[at] / 255;
|
|
49788
|
+
const metres = this.valueMin + normalised * (this.valueMax - this.valueMin) * this.exaggeration;
|
|
49789
|
+
const following = this.followGround && Boolean(this.groundPixels);
|
|
49790
|
+
const ground = following ? this.groundAt(fx, fy) : 0;
|
|
49791
|
+
return this.baseHeight + ground + metres * (following ? coverage : 1);
|
|
49792
|
+
}
|
|
49793
|
+
/**
|
|
49794
|
+
* Changes whenever HeightAt would answer differently for the same frame.
|
|
49795
|
+
*/
|
|
49796
|
+
GetPlacementKey() {
|
|
49797
|
+
return [this.groundPixels ? 1 : 0, this.baseHeight, this.exaggeration, this.followGround ? 1 : 0].join("|");
|
|
49798
|
+
}
|
|
49799
|
+
/**
|
|
49800
|
+
* The ramp the sheet is painted with, as 256 RGBA steps across the value range.
|
|
49801
|
+
*/
|
|
49802
|
+
GetRampPixels() {
|
|
49803
|
+
return this.rampPixels;
|
|
49804
|
+
}
|
|
49805
|
+
/*
|
|
49806
|
+
* The packed ground raster read the way sampleGround reads it, bilinear between texel centres.
|
|
49807
|
+
*/
|
|
49808
|
+
groundAt(u, v) {
|
|
49809
|
+
const size = GROUND_SAMPLES_PER_SIDE;
|
|
49810
|
+
const pixels = this.groundPixels;
|
|
49811
|
+
const read = (x, y) => {
|
|
49812
|
+
const i = (Math.min(size - 1, Math.max(0, y)) * size + Math.min(size - 1, Math.max(0, x))) * 4;
|
|
49813
|
+
const packed = (pixels[i] << 16) + (pixels[i + 1] << 8) + pixels[i + 2];
|
|
49814
|
+
return GROUND_MIN_METRES + (packed / 16777215) * (GROUND_MAX_METRES - GROUND_MIN_METRES);
|
|
49815
|
+
};
|
|
49816
|
+
const x = u * size - 0.5;
|
|
49817
|
+
const y = v * size - 0.5;
|
|
49818
|
+
const x0 = Math.floor(x);
|
|
49819
|
+
const y0 = Math.floor(y);
|
|
49820
|
+
const fx = x - x0;
|
|
49821
|
+
const fy = y - y0;
|
|
49822
|
+
const top = read(x0, y0) + (read(x0 + 1, y0) - read(x0, y0)) * fx;
|
|
49823
|
+
const bottom = read(x0, y0 + 1) + (read(x0 + 1, y0 + 1) - read(x0, y0 + 1)) * fx;
|
|
49824
|
+
return top + (bottom - top) * fy;
|
|
49825
|
+
}
|
|
48322
49826
|
/**
|
|
48323
49827
|
* Feeds the surface from an animated frame series.
|
|
48324
49828
|
* Height and colour advance with the clock without the caller having to push each frame in.
|
|
@@ -48337,23 +49841,39 @@ void main() {
|
|
|
48337
49841
|
return;
|
|
48338
49842
|
}
|
|
48339
49843
|
const context = frameState.context;
|
|
48340
|
-
|
|
48341
|
-
|
|
48342
|
-
|
|
48343
|
-
|
|
48344
|
-
|
|
48345
|
-
}
|
|
48346
|
-
|
|
48347
|
-
|
|
48348
|
-
|
|
48349
|
-
|
|
48350
|
-
|
|
49844
|
+
const fade = this.valueSource && this.valueSource.GetValueFade
|
|
49845
|
+
? this.valueSource.GetValueFade()
|
|
49846
|
+
: null;
|
|
49847
|
+
if (fade) {
|
|
49848
|
+
this.fade = fade;
|
|
49849
|
+
this.sourceDims = { width: fade.width, height: fade.height };
|
|
49850
|
+
}
|
|
49851
|
+
else {
|
|
49852
|
+
this.fade = null;
|
|
49853
|
+
if (this.valueSource) {
|
|
49854
|
+
const canvas = this.valueSource.GetValueCanvas();
|
|
49855
|
+
if (!canvas) {
|
|
49856
|
+
// No frame decoded yet, so there is nothing to displace against.
|
|
49857
|
+
return;
|
|
49858
|
+
}
|
|
49859
|
+
const version = this.valueSource.GetPresentVersion();
|
|
49860
|
+
if (canvas !== this.source || version !== this.lastPresentVersion) {
|
|
49861
|
+
this.source = canvas;
|
|
49862
|
+
this.lastPresentVersion = version;
|
|
49863
|
+
this.textureDirty = true;
|
|
49864
|
+
}
|
|
48351
49865
|
}
|
|
49866
|
+
this.sourceDims = this.source ? { width: this.source.width, height: this.source.height } : null;
|
|
48352
49867
|
}
|
|
48353
|
-
if (!this.
|
|
49868
|
+
if (!this.sourceDims || !this.sourceDims.width || !this.sourceDims.height) {
|
|
48354
49869
|
return;
|
|
48355
49870
|
}
|
|
48356
|
-
this.
|
|
49871
|
+
if (this.fade) {
|
|
49872
|
+
this.syncFadeTextures(context);
|
|
49873
|
+
}
|
|
49874
|
+
else {
|
|
49875
|
+
this.syncTexture(context);
|
|
49876
|
+
}
|
|
48357
49877
|
this.syncRampTexture(context);
|
|
48358
49878
|
this.requestGround();
|
|
48359
49879
|
this.syncGroundTexture(context);
|
|
@@ -48401,6 +49921,10 @@ void main() {
|
|
|
48401
49921
|
this.texture.destroy();
|
|
48402
49922
|
this.texture = null;
|
|
48403
49923
|
}
|
|
49924
|
+
if (this.textureFrom) {
|
|
49925
|
+
this.textureFrom.destroy();
|
|
49926
|
+
this.textureFrom = null;
|
|
49927
|
+
}
|
|
48404
49928
|
if (this.rampTexture) {
|
|
48405
49929
|
this.rampTexture.destroy();
|
|
48406
49930
|
this.rampTexture = null;
|
|
@@ -48668,7 +50192,7 @@ void main() {
|
|
|
48668
50192
|
const southWest = Cartesian3.fromDegrees(this.extent.West, this.extent.South, 0);
|
|
48669
50193
|
const southEast = Cartesian3.fromDegrees(this.extent.East, this.extent.South, 0);
|
|
48670
50194
|
const width = Cartesian3.distance(southWest, southEast);
|
|
48671
|
-
return Math.max(1, width / Math.max(1, this.
|
|
50195
|
+
return Math.max(1, width / Math.max(1, this.sourceDims ? this.sourceDims.width : 1));
|
|
48672
50196
|
}
|
|
48673
50197
|
syncRampTexture(context) {
|
|
48674
50198
|
if (this.rampTexture) {
|
|
@@ -48689,6 +50213,67 @@ void main() {
|
|
|
48689
50213
|
})
|
|
48690
50214
|
});
|
|
48691
50215
|
}
|
|
50216
|
+
valueSampler() {
|
|
50217
|
+
// Nearest for a packed value, because filtering would blend the two bytes of the
|
|
50218
|
+
// packing rather than the values they encode, the same trap sampleGround avoids.
|
|
50219
|
+
const minification = this.packedValue
|
|
50220
|
+
? TextureMinificationFilter.NEAREST
|
|
50221
|
+
: TextureMinificationFilter.LINEAR;
|
|
50222
|
+
const magnification = this.packedValue
|
|
50223
|
+
? TextureMagnificationFilter.NEAREST
|
|
50224
|
+
: TextureMagnificationFilter.LINEAR;
|
|
50225
|
+
return new Sampler({
|
|
50226
|
+
wrapS: TextureWrap.CLAMP_TO_EDGE,
|
|
50227
|
+
wrapT: TextureWrap.CLAMP_TO_EDGE,
|
|
50228
|
+
minificationFilter: minification,
|
|
50229
|
+
magnificationFilter: magnification
|
|
50230
|
+
});
|
|
50231
|
+
}
|
|
50232
|
+
/*
|
|
50233
|
+
* Uploads value pixels into a texture, reusing it when the size still fits.
|
|
50234
|
+
*/
|
|
50235
|
+
uploadPixels(context, texture, pixels, width, height) {
|
|
50236
|
+
const view = new Uint8Array(pixels.buffer, pixels.byteOffset, pixels.byteLength);
|
|
50237
|
+
const source = { width, height, arrayBufferView: view };
|
|
50238
|
+
if (texture && texture.width === width && texture.height === height) {
|
|
50239
|
+
texture.copyFrom({ source });
|
|
50240
|
+
return texture;
|
|
50241
|
+
}
|
|
50242
|
+
if (texture) {
|
|
50243
|
+
texture.destroy();
|
|
50244
|
+
}
|
|
50245
|
+
return new Texture({
|
|
50246
|
+
context,
|
|
50247
|
+
width,
|
|
50248
|
+
height,
|
|
50249
|
+
pixelFormat: PixelFormat.RGBA,
|
|
50250
|
+
pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
|
|
50251
|
+
source,
|
|
50252
|
+
// Row 0 is the archive's north edge, and has to stay at the top, as the canvas path does.
|
|
50253
|
+
flipY: false,
|
|
50254
|
+
sampler: this.valueSampler()
|
|
50255
|
+
});
|
|
50256
|
+
}
|
|
50257
|
+
/*
|
|
50258
|
+
* Keeps a texture each side of the fade, swapping them on a new frame so only that one is uploaded.
|
|
50259
|
+
*/
|
|
50260
|
+
syncFadeTextures(context) {
|
|
50261
|
+
const fade = this.fade;
|
|
50262
|
+
if (fade.to !== this.uploadedTo) {
|
|
50263
|
+
if (fade.from && fade.from === this.uploadedTo && this.texture) {
|
|
50264
|
+
const previous = this.textureFrom;
|
|
50265
|
+
this.textureFrom = this.texture;
|
|
50266
|
+
this.uploadedFrom = this.uploadedTo;
|
|
50267
|
+
this.texture = previous;
|
|
50268
|
+
}
|
|
50269
|
+
this.texture = this.uploadPixels(context, this.texture, fade.to, fade.width, fade.height);
|
|
50270
|
+
this.uploadedTo = fade.to;
|
|
50271
|
+
}
|
|
50272
|
+
if (fade.from && fade.from !== this.uploadedFrom) {
|
|
50273
|
+
this.textureFrom = this.uploadPixels(context, this.textureFrom, fade.from, fade.width, fade.height);
|
|
50274
|
+
this.uploadedFrom = fade.from;
|
|
50275
|
+
}
|
|
50276
|
+
}
|
|
48692
50277
|
syncTexture(context) {
|
|
48693
50278
|
const sizeChanged = this.texture
|
|
48694
50279
|
&& (this.texture.width !== this.source.width || this.texture.height !== this.source.height);
|
|
@@ -48696,27 +50281,15 @@ void main() {
|
|
|
48696
50281
|
if (this.texture) {
|
|
48697
50282
|
this.texture.destroy();
|
|
48698
50283
|
}
|
|
48699
|
-
// Nearest for a packed value, because filtering would blend the two bytes of the
|
|
48700
|
-
// packing rather than the values they encode, the same trap sampleGround avoids.
|
|
48701
|
-
const minification = this.packedValue
|
|
48702
|
-
? TextureMinificationFilter.NEAREST
|
|
48703
|
-
: TextureMinificationFilter.LINEAR;
|
|
48704
|
-
const magnification = this.packedValue
|
|
48705
|
-
? TextureMagnificationFilter.NEAREST
|
|
48706
|
-
: TextureMagnificationFilter.LINEAR;
|
|
48707
50284
|
this.texture = new Texture({
|
|
48708
50285
|
context,
|
|
48709
50286
|
source: this.source,
|
|
48710
50287
|
// Cesium flips canvas rows on upload by default, which would put the archive's
|
|
48711
50288
|
// row 0 (its north edge) at the south edge and mirror the whole surface.
|
|
48712
50289
|
flipY: false,
|
|
48713
|
-
sampler:
|
|
48714
|
-
wrapS: TextureWrap.CLAMP_TO_EDGE,
|
|
48715
|
-
wrapT: TextureWrap.CLAMP_TO_EDGE,
|
|
48716
|
-
minificationFilter: minification,
|
|
48717
|
-
magnificationFilter: magnification
|
|
48718
|
-
})
|
|
50290
|
+
sampler: this.valueSampler()
|
|
48719
50291
|
});
|
|
50292
|
+
this.uploadedTo = null;
|
|
48720
50293
|
this.textureDirty = false;
|
|
48721
50294
|
return;
|
|
48722
50295
|
}
|
|
@@ -48779,7 +50352,7 @@ void main() {
|
|
|
48779
50352
|
* carry, so the ladder is capped one step above the texel count rather than run to its top.
|
|
48780
50353
|
*/
|
|
48781
50354
|
tierCeiling() {
|
|
48782
|
-
const source = this.
|
|
50355
|
+
const source = this.sourceDims;
|
|
48783
50356
|
if (!source || !source.width || !source.height) {
|
|
48784
50357
|
return TIERS[TIERS.length - 1];
|
|
48785
50358
|
}
|
|
@@ -48857,6 +50430,10 @@ void main() {
|
|
|
48857
50430
|
owner: this,
|
|
48858
50431
|
uniformMap: {
|
|
48859
50432
|
u_valueTexture: () => self.texture,
|
|
50433
|
+
u_valueTextureFrom: () => (self.fade && self.fade.from && self.textureFrom
|
|
50434
|
+
? self.textureFrom
|
|
50435
|
+
: self.texture),
|
|
50436
|
+
u_valueFade: () => (self.fade && self.fade.from ? self.fade.mix : 1),
|
|
48860
50437
|
u_groundTexture: () => self.groundTexture,
|
|
48861
50438
|
u_valuePacked: () => (self.packedValue ? 1 : 0),
|
|
48862
50439
|
u_groundWeight: () => (self.followGround && self.groundPixels ? 1 : 0),
|
|
@@ -48894,7 +50471,7 @@ void main() {
|
|
|
48894
50471
|
: 0,
|
|
48895
50472
|
u_coverageCutoff: () => COVERAGE_CUTOFF,
|
|
48896
50473
|
u_rampTexture: () => self.rampTexture,
|
|
48897
|
-
u_texelSize: () => new Cartesian2(1 / self.
|
|
50474
|
+
u_texelSize: () => new Cartesian2(1 / self.sourceDims.width, 1 / self.sourceDims.height),
|
|
48898
50475
|
u_waterOn: () => (self.water ? 1 : 0),
|
|
48899
50476
|
u_waterWavelength: () => (self.water && self.water.wavelength)
|
|
48900
50477
|
|| DEFAULT_WATER_WAVELENGTH_METRES,
|
|
@@ -49266,6 +50843,10 @@ var EntityRenderEnginePolygon;
|
|
|
49266
50843
|
syncTextureValueLabels({
|
|
49267
50844
|
cEntity,
|
|
49268
50845
|
viewer: params.viewer,
|
|
50846
|
+
surface: drawsDisplacedSurface ? cEntity[DISPLACED_SURFACE_KEY] : null,
|
|
50847
|
+
hover: params.visualRegister
|
|
50848
|
+
? { register: params.visualRegister, entityId: String(entity.Bruce.ID), menuItemId: params.menuItemId }
|
|
50849
|
+
: undefined,
|
|
49269
50850
|
settings: overlaySettings(style, entity, params.tags),
|
|
49270
50851
|
extent: labelArchive
|
|
49271
50852
|
? (drawsDisplacedSurface ? archiveExtent(labelArchive.metadata, posses) : drapeExtent)
|
|
@@ -49572,6 +51153,7 @@ var EntityRenderEnginePolygon;
|
|
|
49572
51153
|
// rendered: null
|
|
49573
51154
|
rendered: (_c = params.rendered) === null || _c === void 0 ? void 0 : _c.get(entity.Bruce.ID),
|
|
49574
51155
|
menuItemId: params.menuItemId,
|
|
51156
|
+
visualRegister: params.visualRegister,
|
|
49575
51157
|
onSeriesDiscovered: params.onSeriesDiscovered
|
|
49576
51158
|
});
|
|
49577
51159
|
}
|
|
@@ -50009,8 +51591,8 @@ function wantsLabels(style) {
|
|
|
50009
51591
|
*/
|
|
50010
51592
|
const LABEL_GRID_COLOR = "rgba(255,255,255,0.55)";
|
|
50011
51593
|
const LABEL_GRID_WIDTH_PIXELS = 1;
|
|
50012
|
-
const LABEL_MIN_SPACING_PIXELS =
|
|
50013
|
-
const LABEL_MAX_LABELS =
|
|
51594
|
+
const LABEL_MIN_SPACING_PIXELS = 140;
|
|
51595
|
+
const LABEL_MAX_LABELS = 60;
|
|
50014
51596
|
function overlaySettings(style, entity, tags) {
|
|
50015
51597
|
if (!wantsLabels(style)) {
|
|
50016
51598
|
return undefined;
|
|
@@ -50030,6 +51612,7 @@ function overlaySettings(style, entity, tags) {
|
|
|
50030
51612
|
*/
|
|
50031
51613
|
function syncTextureValueLabels(params) {
|
|
50032
51614
|
const { cEntity, viewer, settings, extent, animator, metadata } = params;
|
|
51615
|
+
const surface = params.surface && !params.surface.isDestroyed() ? params.surface : null;
|
|
50033
51616
|
const existing = cEntity[TEXTURE_VALUE_LABELS_KEY];
|
|
50034
51617
|
// Offline has no extent to rebuild the overlay from, so an existing one is left running.
|
|
50035
51618
|
if (params.offline && existing) {
|
|
@@ -50048,8 +51631,13 @@ function syncTextureValueLabels(params) {
|
|
|
50048
51631
|
West: tile.West, East: tile.East, South: tile.South, North: tile.North,
|
|
50049
51632
|
ResolutionX: tile.ResolutionX, ResolutionY: tile.ResolutionY
|
|
50050
51633
|
})),
|
|
50051
|
-
clipRing: params.clipRing
|
|
51634
|
+
clipRing: params.clipRing,
|
|
51635
|
+
heightAt: surface ? (lon, lat) => surface.HeightAt(lon, lat) : undefined,
|
|
51636
|
+
heightKey: surface ? () => surface.GetPlacementKey() : undefined,
|
|
51637
|
+
hover: params.hover
|
|
50052
51638
|
});
|
|
51639
|
+
// A surface keeps no tinted frame, so whether a cell was painted is read off its ramp.
|
|
51640
|
+
labels.SetPaintedRamp(surface ? surface.GetRampPixels() : null);
|
|
50053
51641
|
let lastVersion = -1;
|
|
50054
51642
|
let appliedFloor = null;
|
|
50055
51643
|
let appliedCeiling = null;
|
|
@@ -50061,15 +51649,15 @@ function syncTextureValueLabels(params) {
|
|
|
50061
51649
|
if (animator.IsDisposed()) {
|
|
50062
51650
|
return;
|
|
50063
51651
|
}
|
|
50064
|
-
const canvas = animator.GetValueCanvas();
|
|
50065
|
-
if (!canvas) {
|
|
50066
|
-
return;
|
|
50067
|
-
}
|
|
50068
51652
|
const version = animator.GetPresentVersion();
|
|
50069
51653
|
if (version !== lastVersion) {
|
|
51654
|
+
// Read from the frame cache, since reading the value canvas back copies a whole frame.
|
|
51655
|
+
const raster = animator.GetValueRaster();
|
|
51656
|
+
if (!raster) {
|
|
51657
|
+
return;
|
|
51658
|
+
}
|
|
50070
51659
|
lastVersion = version;
|
|
50071
|
-
|
|
50072
|
-
labels.SetSource(ctx.getImageData(0, 0, canvas.width, canvas.height));
|
|
51660
|
+
labels.SetSource(raster);
|
|
50073
51661
|
labels.SetPainted(animator.GetDisplayedPixels());
|
|
50074
51662
|
}
|
|
50075
51663
|
// Tracked by reference rather than latched on the first one to arrive: a tiled archive
|
|
@@ -51808,7 +53396,7 @@ var StyleUtils;
|
|
|
51808
53396
|
StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
|
|
51809
53397
|
})(StyleUtils || (StyleUtils = {}));
|
|
51810
53398
|
|
|
51811
|
-
const VERSION = "7.5.
|
|
53399
|
+
const VERSION = "7.5.9";
|
|
51812
53400
|
/**
|
|
51813
53401
|
* Updates the environment instance used by bruce-cesium to one specified.
|
|
51814
53402
|
* This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
|