bruce-cesium 7.7.7 → 7.7.8

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.
Files changed (49) hide show
  1. package/dist/bruce-cesium.es5.js +2292 -69
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +2271 -66
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +6 -1
  6. package/dist/lib/bruce-cesium.js.map +1 -1
  7. package/dist/lib/rendering/entity-render-engine-model3d.js +2 -2
  8. package/dist/lib/rendering/entity-render-engine-model3d.js.map +1 -1
  9. package/dist/lib/rendering/entity-render-engine-polygon.js +5 -4
  10. package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
  11. package/dist/lib/rendering/entity-render-engine-polyline.js +2 -1
  12. package/dist/lib/rendering/entity-render-engine-polyline.js.map +1 -1
  13. package/dist/lib/rendering/render-managers/common/entity-simulation.js +1 -1
  14. package/dist/lib/rendering/render-managers/common/entity-simulation.js.map +1 -1
  15. package/dist/lib/rendering/tileset-render-engine.js +47 -14
  16. package/dist/lib/rendering/tileset-render-engine.js.map +1 -1
  17. package/dist/lib/rendering/view-render-engine.js +30 -24
  18. package/dist/lib/rendering/view-render-engine.js.map +1 -1
  19. package/dist/lib/rendering/visuals-register.js +93 -8
  20. package/dist/lib/rendering/visuals-register.js.map +1 -1
  21. package/dist/lib/rendering/xgrids-render-engine.js +20 -3
  22. package/dist/lib/rendering/xgrids-render-engine.js.map +1 -1
  23. package/dist/lib/utils/cesium-map-grid.js +807 -0
  24. package/dist/lib/utils/cesium-map-grid.js.map +1 -0
  25. package/dist/lib/utils/device-profile.js +302 -0
  26. package/dist/lib/utils/device-profile.js.map +1 -0
  27. package/dist/lib/utils/gltf-generator.js +307 -0
  28. package/dist/lib/utils/gltf-generator.js.map +1 -0
  29. package/dist/lib/utils/gltf-gizmo.js +136 -0
  30. package/dist/lib/utils/gltf-gizmo.js.map +1 -0
  31. package/dist/lib/utils/view-utils.js +9 -0
  32. package/dist/lib/utils/view-utils.js.map +1 -1
  33. package/dist/lib/viewer/camera-controls.js +544 -0
  34. package/dist/lib/viewer/camera-controls.js.map +1 -0
  35. package/dist/lib/viewer/viewer-utils.js +9 -1
  36. package/dist/lib/viewer/viewer-utils.js.map +1 -1
  37. package/dist/types/bruce-cesium.d.ts +6 -1
  38. package/dist/types/rendering/tileset-render-engine.d.ts +10 -2
  39. package/dist/types/rendering/view-render-engine.d.ts +4 -0
  40. package/dist/types/rendering/visuals-register.d.ts +4 -0
  41. package/dist/types/rendering/xgrids-render-engine.d.ts +2 -0
  42. package/dist/types/utils/cesium-map-grid.d.ts +23 -0
  43. package/dist/types/utils/device-profile.d.ts +84 -0
  44. package/dist/types/utils/gltf-generator.d.ts +104 -0
  45. package/dist/types/utils/gltf-gizmo.d.ts +18 -0
  46. package/dist/types/utils/view-utils.d.ts +4 -0
  47. package/dist/types/viewer/camera-controls.d.ts +19 -0
  48. package/dist/types/viewer/viewer-utils.d.ts +1 -0
  49. package/package.json +3 -2
@@ -1,6 +1,6 @@
1
1
  import * as Cesium from 'cesium';
2
- import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, ShaderSource, Cartesian3, Math as Math$1, Ellipsoid, Transforms, Matrix4, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, PixelDatatype, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, ConstantPositionProperty, HeightReference, DistanceDisplayCondition, JulianDate, Quaternion, HeadingPitchRoll, ColorBlendMode, ShadowMode, Model, ClassificationType, HorizontalOrigin, VerticalOrigin, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, ArcType, CornerType, Rectangle, SceneMode, IntersectionTests, Ray, SceneTransforms, FeatureDetection, Matrix3, NearFarScalar, KmlDataSource, GeoJsonDataSource, Cesium3DTileStyle, HeadingPitchRange, Cesium3DTileColorBlendMode, Ion, IonResource, Cesium3DTileset, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, OrthographicFrustum, EasingFunction, Intersect, CustomDataSource, WebGLConstants, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, PolygonPipeline, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, GeometryInstance, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
- import { Cartes, LRUCache, Carto, ProjectViewTile, EntityHistoricData, BruceEvent, DelayQueue, Entity as Entity$1, EntityTag, ClientFile, ENVIRONMENT, EntityAttachmentType, EntityRelationType, EntityType, Tileset, Calculator, DataLab, Style, ObjectUtils, MenuItem, AccountConcept, RecordChangeFeed, EntityLod, UrlUtils, Api, Bounds, ClientFileValueMap, Color as Color$1, Geometry as Geometry$1, ProjectView, ProjectViewBookmark, ZoomControl, Assembly, EntityCoords, BruceApi, EntityRelation, EntitySource, ProgramKey, ProjectViewLegacyTile, Camera, Account, AccountLimits, Session, EntityAttachment, EntityAttribute, MathUtils, ProjectViewBookmarkGroup } from 'bruce-models';
2
+ import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, ShaderSource, Cartesian3, Math as Math$1, Ellipsoid, Transforms, Matrix4, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, PixelDatatype, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, HeightReference, ColorBlendMode, HeadingPitchRoll, Model, ClassificationType, ConstantPositionProperty, DistanceDisplayCondition, ArcType, CornerType, JulianDate, Quaternion, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, HorizontalOrigin, VerticalOrigin, SceneMode, IntersectionTests, Ray, SceneTransforms, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, FeatureDetection, Cesium3DTileStyle, HeadingPitchRange, Ion, IonResource, Cesium3DTileColorBlendMode, Cesium3DTileset, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Intersect, OrthographicFrustum, EasingFunction, WebGLConstants, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, PolygonPipeline, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, CesiumInspector, ClockRange, ShadowMode, DynamicEnvironmentMapManager, GeometryInstance, PrimitiveCollection, PolylineCollection, LabelCollection, LabelStyle, Material, Event, CustomDataSource, CameraEventType, Plane, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
3
+ import { Cartes, LRUCache, Carto, ProjectViewTile, EntityHistoricData, BruceEvent, DelayQueue, Entity as Entity$1, EntityTag, PathUtils, ClientFile, ENVIRONMENT, EntityAttachmentType, EntityRelationType, EntityType, Tileset, Calculator, DataLab, Style, ObjectUtils, MenuItem, AccountConcept, RecordChangeFeed, EntityLod, UrlUtils, Geometry as Geometry$1, Bounds, ClientFileValueMap, Color as Color$1, Assembly, EntityCoords, ZoomControl, ProjectView, ProjectViewBookmark, Api, EntityRelation, BruceApi, ProgramKey, ProjectViewLegacyTile, Camera, EntitySource, Account, AccountLimits, Session, EntityAttachment, EntityAttribute, MathUtils, ProjectViewBookmarkGroup } from 'bruce-models';
4
4
 
5
5
  /**
6
6
  * Ensures a number is returned from a given value.
@@ -907,6 +907,546 @@ function CompareCPolygonHierarchies(a, b) {
907
907
  return true;
908
908
  }
909
909
 
910
+ const CESIUM_CAMERA_CONTROLS_KEY = "_nextspace_camera_controls";
911
+ // How far a wheel notch or its drag equivalent moves the camera, as a share of the distance to what it points at.
912
+ const ZOOM_SHARE = 0.18;
913
+ // The distance a zoom step treats anything closer as, so the camera keeps moving through a surface instead of stalling.
914
+ const MIN_ZOOM_DISTANCE = 4;
915
+ // How far from the origin zooming out may take a model-space camera, beyond which the scene is a dot on a planet.
916
+ const MODEL_SPACE_MAX_RANGE = 250000;
917
+ // Above this height a geospatial scene is a planet, which CesiumJS's own controller navigates better.
918
+ const GEO_HANDOFF_HEIGHT = 15000;
919
+ // The furthest a depth pick is trusted from a camera close to the ground; further up the limit grows with height.
920
+ const PLAUSIBLE_PICK_FLOOR = 1000;
921
+ // Pixels of right drag that make one wheel notch.
922
+ const DRAG_PIXELS_PER_NOTCH = 40;
923
+ // Share of the outstanding wheel movement applied each animation frame.
924
+ const ZOOM_EASE = 0.35;
925
+ // A pan plane seen closer to edge on than this (sine of the angle) is swapped for one facing the camera.
926
+ const GRAZING_SINE = 0.12;
927
+ // Closest the view may get to straight up or down while orbiting, in radians, so the camera never flips over.
928
+ const ORBIT_POLE_MARGIN = Math$1.toRadians(1);
929
+ var EDrag;
930
+ (function (EDrag) {
931
+ EDrag["Pan"] = "pan";
932
+ EDrag["Orbit"] = "orbit";
933
+ EDrag["Zoom"] = "zoom";
934
+ })(EDrag || (EDrag = {}));
935
+ /**
936
+ * Mouse navigation that keeps moving near, through and below the ground, where CesiumJS's own controller stalls.
937
+ */
938
+ var CameraControls;
939
+ (function (CameraControls) {
940
+ /**
941
+ * Takes over mouse drag and wheel navigation close to the ground; CesiumJS keeps planet scale, pinch and look.
942
+ */
943
+ function Attach(viewer) {
944
+ var _a, _b, _c;
945
+ if (!viewer || viewer.isDestroyed() || viewer[CESIUM_CAMERA_CONTROLS_KEY]) {
946
+ return;
947
+ }
948
+ const scene = viewer.scene;
949
+ const controller = scene === null || scene === void 0 ? void 0 : scene.screenSpaceCameraController;
950
+ if (!controller || !scene.canvas || !scene.preUpdate) {
951
+ return;
952
+ }
953
+ const state = {
954
+ viewer,
955
+ getModelSpaceFrame: null,
956
+ drag: null,
957
+ touches: new Set(),
958
+ pendingZoom: new Cartesian3(),
959
+ zoomFrame: null,
960
+ zoomPosition: null,
961
+ lastDistance: 100,
962
+ saved: {
963
+ rotateEventTypes: controller.rotateEventTypes,
964
+ zoomEventTypes: controller.zoomEventTypes,
965
+ tiltEventTypes: controller.tiltEventTypes,
966
+ enableCollisionDetection: controller.enableCollisionDetection,
967
+ backFaceAlpha: (_b = (_a = scene.globe) === null || _a === void 0 ? void 0 : _a.translucency) === null || _b === void 0 ? void 0 : _b.backFaceAlpha,
968
+ showSkirts: (_c = scene.globe) === null || _c === void 0 ? void 0 : _c.showSkirts
969
+ },
970
+ ours: false,
971
+ removals: []
972
+ };
973
+ viewer[CESIUM_CAMERA_CONTROLS_KEY] = state;
974
+ // The camera may go below the ground; zooming slows near a surface, so it takes a deliberate push.
975
+ controller.enableCollisionDetection = false;
976
+ const canvas = scene.canvas;
977
+ const listen = (target, type, handler, options) => {
978
+ target.addEventListener(type, handler, options);
979
+ state.removals.push(() => target.removeEventListener(type, handler, options));
980
+ };
981
+ listen(canvas, "pointerdown", (e) => onPointerDown(state, e));
982
+ listen(window, "pointermove", (e) => onPointerMove(state, e));
983
+ listen(window, "pointerup", (e) => onPointerUp(state, e));
984
+ listen(window, "pointercancel", (e) => onPointerUp(state, e));
985
+ listen(canvas, "wheel", (e) => onWheel(state, e), { passive: true });
986
+ state.removals.push(scene.preUpdate.addEventListener(() => syncController(state)));
987
+ syncController(state);
988
+ scene.requestRender();
989
+ }
990
+ CameraControls.Attach = Attach;
991
+ /**
992
+ * Hands navigation back to CesiumJS's own camera controller, as it was before Attach.
993
+ */
994
+ function Detach(viewer) {
995
+ const state = viewer === null || viewer === void 0 ? void 0 : viewer[CESIUM_CAMERA_CONTROLS_KEY];
996
+ if (!state) {
997
+ return;
998
+ }
999
+ SetModelSpace(viewer, null);
1000
+ viewer[CESIUM_CAMERA_CONTROLS_KEY] = null;
1001
+ state.removals.forEach((removal) => removal());
1002
+ state.removals = [];
1003
+ if (state.zoomFrame != null) {
1004
+ cancelAnimationFrame(state.zoomFrame);
1005
+ }
1006
+ if (viewer.isDestroyed()) {
1007
+ return;
1008
+ }
1009
+ restoreCesiumGestures(state);
1010
+ viewer.scene.screenSpaceCameraController.enableCollisionDetection = state.saved.enableCollisionDetection;
1011
+ viewer.scene.requestRender();
1012
+ }
1013
+ CameraControls.Detach = Detach;
1014
+ function IsAttached(viewer) {
1015
+ return Boolean(viewer === null || viewer === void 0 ? void 0 : viewer[CESIUM_CAMERA_CONTROLS_KEY]);
1016
+ }
1017
+ CameraControls.IsAttached = IsAttached;
1018
+ /**
1019
+ * Switches to navigating model-space measured from the frame getFrame returns, or back to geospatial with null.
1020
+ */
1021
+ function SetModelSpace(viewer, getFrame) {
1022
+ const state = viewer === null || viewer === void 0 ? void 0 : viewer[CESIUM_CAMERA_CONTROLS_KEY];
1023
+ if (!state || viewer.isDestroyed()) {
1024
+ return;
1025
+ }
1026
+ const wasModelSpace = Boolean(state.getModelSpaceFrame);
1027
+ state.getModelSpaceFrame = getFrame !== null && getFrame !== void 0 ? getFrame : null;
1028
+ syncController(state);
1029
+ if (wasModelSpace === Boolean(getFrame)) {
1030
+ return;
1031
+ }
1032
+ const globe = viewer.scene.globe;
1033
+ const translucency = globe === null || globe === void 0 ? void 0 : globe.translucency;
1034
+ if (getFrame) {
1035
+ // The ground is see-through from below, so what stands on it stays in view underground.
1036
+ if (translucency && typeof translucency.backFaceAlpha === "number") {
1037
+ translucency.backFaceAlpha = 0.5;
1038
+ }
1039
+ // Skirts hide seams flat model-space ground never has, and below it they are walls.
1040
+ if (typeof state.saved.showSkirts === "boolean") {
1041
+ globe.showSkirts = false;
1042
+ }
1043
+ }
1044
+ else {
1045
+ if (translucency && typeof state.saved.backFaceAlpha === "number") {
1046
+ translucency.backFaceAlpha = state.saved.backFaceAlpha;
1047
+ }
1048
+ if (typeof state.saved.showSkirts === "boolean") {
1049
+ globe.showSkirts = state.saved.showSkirts;
1050
+ }
1051
+ }
1052
+ viewer.scene.requestRender();
1053
+ }
1054
+ CameraControls.SetModelSpace = SetModelSpace;
1055
+ })(CameraControls || (CameraControls = {}));
1056
+ function restoreCesiumGestures(state) {
1057
+ const controller = state.viewer.scene.screenSpaceCameraController;
1058
+ controller.rotateEventTypes = state.saved.rotateEventTypes;
1059
+ controller.zoomEventTypes = state.saved.zoomEventTypes;
1060
+ controller.tiltEventTypes = state.saved.tiltEventTypes;
1061
+ }
1062
+ /*
1063
+ * Decides each frame who has the mouse: these controls near the ground, CesiumJS for planets, orthographic and tracking.
1064
+ */
1065
+ function syncController(state) {
1066
+ const viewer = state.viewer;
1067
+ if (viewer.isDestroyed() || state.drag) {
1068
+ return;
1069
+ }
1070
+ const ours = shouldControl(state);
1071
+ if (ours === state.ours) {
1072
+ return;
1073
+ }
1074
+ state.ours = ours;
1075
+ if (ours) {
1076
+ const controller = viewer.scene.screenSpaceCameraController;
1077
+ controller.rotateEventTypes = [];
1078
+ controller.zoomEventTypes = [CameraEventType.PINCH];
1079
+ controller.tiltEventTypes = [CameraEventType.PINCH];
1080
+ }
1081
+ else {
1082
+ restoreCesiumGestures(state);
1083
+ }
1084
+ }
1085
+ function shouldControl(state) {
1086
+ const viewer = state.viewer;
1087
+ const camera = viewer.camera;
1088
+ if (viewer.scene.mode !== SceneMode.SCENE3D || camera.frustum instanceof OrthographicFrustum) {
1089
+ return false;
1090
+ }
1091
+ // A camera following something is CesiumJS's to move.
1092
+ if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {
1093
+ return false;
1094
+ }
1095
+ if (state.getModelSpaceFrame) {
1096
+ return true;
1097
+ }
1098
+ return heightOf(state, camera.positionWC) < GEO_HANDOFF_HEIGHT;
1099
+ }
1100
+ function ellipsoidOf(state) {
1101
+ var _a, _b;
1102
+ return (_b = (_a = state.viewer.scene.globe) === null || _a === void 0 ? void 0 : _a.ellipsoid) !== null && _b !== void 0 ? _b : Ellipsoid.WGS84;
1103
+ }
1104
+ function heightOf(state, position) {
1105
+ const carto = ellipsoidOf(state).cartesianToCartographic(position);
1106
+ return carto ? carto.height : Number.POSITIVE_INFINITY;
1107
+ }
1108
+ function canvasPosition(viewer, e) {
1109
+ const rect = viewer.scene.canvas.getBoundingClientRect();
1110
+ return new Cartesian2(e.clientX - rect.left, e.clientY - rect.top);
1111
+ }
1112
+ /*
1113
+ * Up at a point: the model-space frame's up in model-space, else the ellipsoid's surface normal there.
1114
+ */
1115
+ function upAt(state, position) {
1116
+ if (state.getModelSpaceFrame) {
1117
+ const up = Matrix4.multiplyByPointAsVector(state.getModelSpaceFrame(), Cartesian3.UNIT_Z, new Cartesian3());
1118
+ return Cartesian3.normalize(up, up);
1119
+ }
1120
+ return ellipsoidOf(state).geodeticSurfaceNormal(position, new Cartesian3());
1121
+ }
1122
+ /*
1123
+ * The level plane standing in for the ground when nothing is drawn under the cursor.
1124
+ */
1125
+ function groundPlane(state) {
1126
+ if (state.getModelSpaceFrame) {
1127
+ const frame = state.getModelSpaceFrame();
1128
+ return Plane.fromPointNormal(Matrix4.getTranslation(frame, new Cartesian3()), upAt(state, null));
1129
+ }
1130
+ const camera = state.viewer.camera;
1131
+ const ellipsoid = ellipsoidOf(state);
1132
+ const surface = ellipsoid.scaleToGeodeticSurface(camera.positionWC, new Cartesian3());
1133
+ if (!surface) {
1134
+ return null;
1135
+ }
1136
+ return Plane.fromPointNormal(surface, ellipsoid.geodeticSurfaceNormal(surface, new Cartesian3()));
1137
+ }
1138
+ /*
1139
+ * How far away a depth pick is believable, since below ground the depth buffer can hold the globe's inside.
1140
+ */
1141
+ function plausibleDistance(state) {
1142
+ const camera = state.viewer.camera;
1143
+ if (state.getModelSpaceFrame) {
1144
+ const origin = Matrix4.getTranslation(state.getModelSpaceFrame(), new Cartesian3());
1145
+ return Math.max(PLAUSIBLE_PICK_FLOOR, Cartesian3.distance(camera.positionWC, origin) * 10);
1146
+ }
1147
+ const height = Math.abs(heightOf(state, camera.positionWC) - (Number(state.viewer.scene.globeHeight) || 0));
1148
+ const horizon = Math.sqrt(2 * ellipsoidOf(state).maximumRadius * Math.max(height, 0));
1149
+ return Math.max(PLAUSIBLE_PICK_FLOOR, height * 10, horizon * 1.5);
1150
+ }
1151
+ /*
1152
+ * The point under a screen position: what is drawn there, else the ground plane, else a point at the last distance.
1153
+ */
1154
+ function pickTarget(state, position) {
1155
+ const viewer = state.viewer;
1156
+ const scene = viewer.scene;
1157
+ const camera = viewer.camera;
1158
+ const ray = camera.getPickRay(position);
1159
+ if (!ray) {
1160
+ return null;
1161
+ }
1162
+ let picked;
1163
+ if (scene.pickPositionSupported) {
1164
+ try {
1165
+ picked = scene.pickPosition(position);
1166
+ }
1167
+ catch (e) {
1168
+ picked = null;
1169
+ }
1170
+ }
1171
+ const plausible = plausibleDistance(state);
1172
+ if (isAhead(camera, picked) && Cartesian3.distance(picked, camera.positionWC) < plausible) {
1173
+ return picked;
1174
+ }
1175
+ const ground = groundPlane(state);
1176
+ const onGround = ground ? IntersectionTests.rayPlane(ray, ground) : null;
1177
+ if (isAhead(camera, onGround) && Cartesian3.distance(onGround, camera.positionWC) < plausible) {
1178
+ return onGround;
1179
+ }
1180
+ return Ray.getPoint(ray, state.lastDistance);
1181
+ }
1182
+ function isAhead(camera, point) {
1183
+ if (!point || !Number.isFinite(point.x) || !Number.isFinite(point.y) || !Number.isFinite(point.z)) {
1184
+ return false;
1185
+ }
1186
+ const toPoint = Cartesian3.subtract(point, camera.positionWC, new Cartesian3());
1187
+ return Cartesian3.dot(toPoint, camera.directionWC) > 0;
1188
+ }
1189
+ /*
1190
+ * Picks which gesture a button and its modifiers start, matching CesiumJS's own mapping.
1191
+ */
1192
+ function dragKind(e) {
1193
+ if (e.shiftKey) {
1194
+ // Shift drag looks around, which CesiumJS still handles.
1195
+ return null;
1196
+ }
1197
+ if (e.button === 1 || (e.ctrlKey && (e.button === 0 || e.button === 2))) {
1198
+ return EDrag.Orbit;
1199
+ }
1200
+ if (e.button === 0) {
1201
+ return EDrag.Pan;
1202
+ }
1203
+ if (e.button === 2) {
1204
+ return EDrag.Zoom;
1205
+ }
1206
+ return null;
1207
+ }
1208
+ function allows(state, kind) {
1209
+ const controller = state.viewer.scene.screenSpaceCameraController;
1210
+ if (!controller.enableInputs) {
1211
+ return false;
1212
+ }
1213
+ switch (kind) {
1214
+ case EDrag.Pan:
1215
+ return controller.enableRotate;
1216
+ case EDrag.Orbit:
1217
+ return controller.enableTilt;
1218
+ case EDrag.Zoom:
1219
+ return controller.enableZoom;
1220
+ }
1221
+ return false;
1222
+ }
1223
+ function onPointerDown(state, e) {
1224
+ const viewer = state.viewer;
1225
+ if (viewer.isDestroyed() || !state.ours) {
1226
+ return;
1227
+ }
1228
+ if (e.pointerType === "touch") {
1229
+ state.touches.add(e.pointerId);
1230
+ // A second finger makes it a pinch, which CesiumJS handles.
1231
+ if (state.touches.size > 1) {
1232
+ endDrag(state);
1233
+ return;
1234
+ }
1235
+ }
1236
+ const kind = dragKind(e);
1237
+ if (!kind || !allows(state, kind)) {
1238
+ return;
1239
+ }
1240
+ const position = canvasPosition(viewer, e);
1241
+ const target = pickTarget(state, position);
1242
+ if (!target) {
1243
+ return;
1244
+ }
1245
+ const camera = viewer.camera;
1246
+ const distance = Cartesian3.distance(camera.positionWC, target);
1247
+ state.lastDistance = Math.max(distance, MIN_ZOOM_DISTANCE);
1248
+ const drag = { kind, pointerId: e.pointerId, last: position, target, up: upAt(state, target), distance };
1249
+ if (kind === EDrag.Pan) {
1250
+ drag.plane = panPlane(camera, target, drag.up);
1251
+ }
1252
+ else if (kind === EDrag.Zoom) {
1253
+ drag.direction = Cartesian3.normalize(Cartesian3.subtract(target, camera.positionWC, new Cartesian3()), new Cartesian3());
1254
+ }
1255
+ state.drag = drag;
1256
+ }
1257
+ /*
1258
+ * A level plane through the grabbed point, or one facing the camera when the level one is seen edge on.
1259
+ */
1260
+ function panPlane(camera, target, up) {
1261
+ if (Math.abs(Cartesian3.dot(camera.directionWC, up)) > GRAZING_SINE) {
1262
+ return Plane.fromPointNormal(target, up);
1263
+ }
1264
+ return Plane.fromPointNormal(target, Cartesian3.negate(camera.directionWC, new Cartesian3()));
1265
+ }
1266
+ function onPointerMove(state, e) {
1267
+ const drag = state.drag;
1268
+ const viewer = state.viewer;
1269
+ if (!drag || drag.pointerId !== e.pointerId || viewer.isDestroyed()) {
1270
+ return;
1271
+ }
1272
+ if (!allows(state, drag.kind)) {
1273
+ endDrag(state);
1274
+ return;
1275
+ }
1276
+ const position = canvasPosition(viewer, e);
1277
+ const dx = position.x - drag.last.x;
1278
+ const dy = position.y - drag.last.y;
1279
+ if (dx === 0 && dy === 0) {
1280
+ return;
1281
+ }
1282
+ switch (drag.kind) {
1283
+ case EDrag.Pan:
1284
+ pan(state, drag, position);
1285
+ break;
1286
+ case EDrag.Orbit:
1287
+ orbit(state, drag, dx, dy);
1288
+ break;
1289
+ case EDrag.Zoom:
1290
+ // Dragging down zooms in, as it does in CesiumJS.
1291
+ moveAlong(state, drag.direction, drag.target, dy / DRAG_PIXELS_PER_NOTCH);
1292
+ break;
1293
+ }
1294
+ drag.last = position;
1295
+ viewer.scene.requestRender();
1296
+ }
1297
+ function onPointerUp(state, e) {
1298
+ var _a;
1299
+ state.touches.delete(e.pointerId);
1300
+ if (((_a = state.drag) === null || _a === void 0 ? void 0 : _a.pointerId) === e.pointerId) {
1301
+ endDrag(state);
1302
+ }
1303
+ }
1304
+ function endDrag(state) {
1305
+ state.drag = null;
1306
+ }
1307
+ /*
1308
+ * Slides the camera so the grabbed point stays under the cursor.
1309
+ */
1310
+ function pan(state, drag, position) {
1311
+ const camera = state.viewer.camera;
1312
+ const ray = camera.getPickRay(position);
1313
+ const hit = ray ? IntersectionTests.rayPlane(ray, drag.plane) : null;
1314
+ if (!isAhead(camera, hit)) {
1315
+ return;
1316
+ }
1317
+ const delta = Cartesian3.subtract(drag.target, hit, new Cartesian3());
1318
+ // Near the horizon a pixel covers kilometres, so one move is capped at a few times the grab distance.
1319
+ const limit = Math.max(drag.distance, MIN_ZOOM_DISTANCE) * 3;
1320
+ const length = Cartesian3.magnitude(delta);
1321
+ if (length > limit) {
1322
+ Cartesian3.multiplyByScalar(delta, limit / length, delta);
1323
+ }
1324
+ Cartesian3.add(camera.position, delta, camera.position);
1325
+ }
1326
+ /*
1327
+ * Turns the camera around the grabbed point: sideways about up there, and up or down about the camera's right.
1328
+ */
1329
+ function orbit(state, drag, dx, dy) {
1330
+ const viewer = state.viewer;
1331
+ const camera = viewer.camera;
1332
+ const canvas = viewer.scene.canvas;
1333
+ const up = drag.up;
1334
+ const heading = -dx / Math.max(canvas.clientWidth, 1) * Math.PI * 2;
1335
+ // Dragging down looks further down, as CesiumJS's own tilt does.
1336
+ let pitch = -dy / Math.max(canvas.clientHeight, 1) * Math.PI;
1337
+ // Keeps the view from tipping past straight up or down, where it would flip over.
1338
+ const elevation = Math.asin(Math$1.clamp(Cartesian3.dot(camera.direction, up), -1, 1));
1339
+ const limit = Math.PI / 2 - ORBIT_POLE_MARGIN;
1340
+ pitch = Math$1.clamp(elevation + pitch, -limit, limit) - elevation;
1341
+ rotateAbout(camera, drag.target, up, heading);
1342
+ const right = Cartesian3.cross(camera.direction, up, new Cartesian3());
1343
+ if (Cartesian3.magnitude(right) > Math$1.EPSILON6) {
1344
+ Cartesian3.normalize(right, right);
1345
+ rotateAbout(camera, drag.target, right, pitch);
1346
+ }
1347
+ levelCamera(camera, upAt(state, camera.position));
1348
+ }
1349
+ function rotateAbout(camera, pivot, axis, angle) {
1350
+ if (!angle) {
1351
+ return;
1352
+ }
1353
+ const rotation = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(axis, angle));
1354
+ const offset = Cartesian3.subtract(camera.position, pivot, new Cartesian3());
1355
+ Matrix3.multiplyByVector(rotation, offset, offset);
1356
+ Cartesian3.add(pivot, offset, camera.position);
1357
+ Matrix3.multiplyByVector(rotation, camera.direction, camera.direction);
1358
+ Matrix3.multiplyByVector(rotation, camera.up, camera.up);
1359
+ Matrix3.multiplyByVector(rotation, camera.right, camera.right);
1360
+ }
1361
+ /*
1362
+ * Takes any roll out, so the horizon stays level however the camera has turned.
1363
+ */
1364
+ function levelCamera(camera, up) {
1365
+ const direction = Cartesian3.normalize(camera.direction, camera.direction);
1366
+ const right = Cartesian3.cross(direction, up, new Cartesian3());
1367
+ if (Cartesian3.magnitude(right) < Math$1.EPSILON6) {
1368
+ return;
1369
+ }
1370
+ Cartesian3.normalize(right, camera.right);
1371
+ Cartesian3.cross(camera.right, direction, camera.up);
1372
+ Cartesian3.normalize(camera.up, camera.up);
1373
+ }
1374
+ function onWheel(state, e) {
1375
+ const viewer = state.viewer;
1376
+ if (viewer.isDestroyed() || !state.ours || !allows(state, EDrag.Zoom)) {
1377
+ return;
1378
+ }
1379
+ let notches = e.deltaMode === 1 ? e.deltaY / 3 : e.deltaMode === 2 ? e.deltaY : e.deltaY / 100;
1380
+ notches = Math$1.clamp(-notches, -3, 3);
1381
+ if (!notches) {
1382
+ return;
1383
+ }
1384
+ const position = canvasPosition(viewer, e);
1385
+ const camera = viewer.camera;
1386
+ const target = pickTarget(state, position);
1387
+ const ray = camera.getPickRay(position);
1388
+ if (!target || !ray) {
1389
+ return;
1390
+ }
1391
+ const step = zoomStep(state, ray.direction, target, notches);
1392
+ Cartesian3.add(state.pendingZoom, step, state.pendingZoom);
1393
+ state.zoomPosition = Cartesian3.clone(camera.position);
1394
+ if (state.zoomFrame == null) {
1395
+ state.zoomFrame = requestAnimationFrame(() => easeZoom(state));
1396
+ }
1397
+ }
1398
+ /*
1399
+ * Applies part of the outstanding wheel movement each frame, so a notch glides rather than jumps.
1400
+ */
1401
+ function easeZoom(state) {
1402
+ state.zoomFrame = null;
1403
+ const viewer = state.viewer;
1404
+ if (viewer.isDestroyed() || !viewer[CESIUM_CAMERA_CONTROLS_KEY]) {
1405
+ return;
1406
+ }
1407
+ // A tool locking the camera mid glide (walkthrough, a camera lock) owns it from then on.
1408
+ if (!allows(state, EDrag.Zoom) ||
1409
+ !state.zoomPosition ||
1410
+ !Cartesian3.equalsEpsilon(viewer.camera.position, state.zoomPosition, 0, 1e-3)) {
1411
+ state.pendingZoom = new Cartesian3();
1412
+ return;
1413
+ }
1414
+ const remaining = Cartesian3.magnitude(state.pendingZoom);
1415
+ const share = remaining < 0.01 ? 1 : ZOOM_EASE;
1416
+ const step = Cartesian3.multiplyByScalar(state.pendingZoom, share, new Cartesian3());
1417
+ Cartesian3.subtract(state.pendingZoom, step, state.pendingZoom);
1418
+ Cartesian3.add(viewer.camera.position, step, viewer.camera.position);
1419
+ state.zoomPosition = Cartesian3.clone(viewer.camera.position);
1420
+ viewer.scene.requestRender();
1421
+ if (share < 1) {
1422
+ state.zoomFrame = requestAnimationFrame(() => easeZoom(state));
1423
+ }
1424
+ }
1425
+ function moveAlong(state, direction, target, notches) {
1426
+ const step = zoomStep(state, direction, target, notches);
1427
+ Cartesian3.add(state.viewer.camera.position, step, state.viewer.camera.position);
1428
+ }
1429
+ /*
1430
+ * The move for some notches toward a target along a direction, a share of the distance with a floor so it never stalls.
1431
+ */
1432
+ function zoomStep(state, direction, target, notches) {
1433
+ const camera = state.viewer.camera;
1434
+ const distance = Math.max(Cartesian3.distance(camera.positionWC, target), MIN_ZOOM_DISTANCE);
1435
+ state.lastDistance = distance;
1436
+ let length = distance * ZOOM_SHARE * notches;
1437
+ // A geospatial camera zooming out hands over to CesiumJS once it is high enough, so only model-space is capped.
1438
+ if (length < 0 && state.getModelSpaceFrame) {
1439
+ const origin = Matrix4.getTranslation(state.getModelSpaceFrame(), new Cartesian3());
1440
+ const pending = Cartesian3.add(camera.positionWC, state.pendingZoom, new Cartesian3());
1441
+ const range = Cartesian3.distance(pending, origin);
1442
+ length = Math.max(length, range - MODEL_SPACE_MAX_RANGE);
1443
+ if (length >= 0) {
1444
+ return new Cartesian3();
1445
+ }
1446
+ }
1447
+ return Cartesian3.multiplyByScalar(Cartesian3.normalize(direction, new Cartesian3()), length, new Cartesian3());
1448
+ }
1449
+
910
1450
  const CESIUM_INSPECTOR_KEY = "_nextspace_inspector";
911
1451
  const CESIUM_TIMELINE_KEY = "_nextspace_timeline";
912
1452
  const CESIUM_TIMELINE_LIVE_KEY = "_nextspace_timeline_live";
@@ -1384,6 +1924,13 @@ var ViewUtils;
1384
1924
  return overriddenImageBasedLighting || null;
1385
1925
  }
1386
1926
  ViewUtils.GetDefaultImageBasedLighting = GetDefaultImageBasedLighting;
1927
+ /**
1928
+ * Shadow mode for the Entity graphics this library draws, following the scene's shadow switch.
1929
+ */
1930
+ function GetEntityShadowMode(viewer) {
1931
+ return (viewer === null || viewer === void 0 ? void 0 : viewer.shadows) ? ShadowMode.ENABLED : ShadowMode.DISABLED;
1932
+ }
1933
+ ViewUtils.GetEntityShadowMode = GetEntityShadowMode;
1387
1934
  function EngineShadesModelsDarker() {
1388
1935
  return typeof DynamicEnvironmentMapManager !== "undefined";
1389
1936
  }
@@ -1455,6 +2002,7 @@ var ViewUtils;
1455
2002
  viewer[CESIUM_MODEL_SPACE_KEY] = modelSpace;
1456
2003
  // Reload globe since we display it differently between the two modes.
1457
2004
  SetGlobeDetails({ viewer });
2005
+ CameraControls.SetModelSpace(viewer, modelSpace ? () => GetModelSpaceFrame(viewer) : null);
1458
2006
  }
1459
2007
  ViewUtils.SetModelSpace = SetModelSpace;
1460
2008
  function GetModelSpace(viewer) {
@@ -6177,7 +6725,7 @@ var EntitySimulation;
6177
6725
  const byTime = new Map();
6178
6726
  for (const set of this.sets.values()) {
6179
6727
  for (const [ms, record] of set.records.get(entityId) || []) {
6180
- if (attrKey && record.attrKey && record.attrKey !== attrKey) {
6728
+ if (attrKey && record.attrKey && !PathUtils.Equals(record.attrKey, attrKey)) {
6181
6729
  continue;
6182
6730
  }
6183
6731
  const held = byTime.get(ms);
@@ -11982,6 +12530,28 @@ function getHighestZIndex(viewer, visual) {
11982
12530
  return highest;
11983
12531
  }
11984
12532
  const MAX_SHOW_DEPTH = 10;
12533
+ // Entity graphics whose shadows follow the scene's shadow switch.
12534
+ const SHADOWED_GRAPHICS = ["model", "polygon", "polyline", "corridor"];
12535
+ /*
12536
+ * Points a registered Entity graphic, and the graphics drawn beside it, at the given shadow mode.
12537
+ */
12538
+ function updateCEntityShadows(viewer, visual, mode, depth = 0) {
12539
+ if (!visual || depth > MAX_SHOW_DEPTH) {
12540
+ return;
12541
+ }
12542
+ for (const key of SHADOWED_GRAPHICS) {
12543
+ const graphic = visual[key];
12544
+ // Only graphics drawn with a mode; one left at Cesium's default was never meant to shadow.
12545
+ if ((graphic === null || graphic === void 0 ? void 0 : graphic.shadows) != null) {
12546
+ SetCPropIfChanged({ viewer, graphic, property: "shadows", value: mode });
12547
+ }
12548
+ }
12549
+ if (visual._siblingGraphics) {
12550
+ for (let i = 0; i < visual._siblingGraphics.length; i++) {
12551
+ updateCEntityShadows(viewer, visual._siblingGraphics[i], mode, depth + 1);
12552
+ }
12553
+ }
12554
+ }
11985
12555
  function updateCEntityShow(viewer, visual, rego, show, ignoreParent, depth = 0) {
11986
12556
  if (depth > MAX_SHOW_DEPTH) {
11987
12557
  console.warn("updateCEntityShow(): Max show depth reached. EntityId = " + rego.entityId);
@@ -12318,7 +12888,7 @@ var VisualsRegister;
12318
12888
  }
12319
12889
  get OnLabelGroupsChanged() { return this.labelGroupsChanged; }
12320
12890
  constructor(params) {
12321
- var _a, _b;
12891
+ var _a, _b, _c, _d, _e, _f, _g;
12322
12892
  this.id = ObjectUtils.UId();
12323
12893
  this.disposed = false;
12324
12894
  this.rego = {};
@@ -12368,6 +12938,7 @@ var VisualsRegister;
12368
12938
  this.sourceOrderDirty = false;
12369
12939
  // Stops following what is simulated, so labels can read it again when it changes.
12370
12940
  this.simulationRemoval = null;
12941
+ this.shadowsRemoval = null;
12371
12942
  this.labelGroupsChanged = new BruceEvent();
12372
12943
  this.hoverRingState = {};
12373
12944
  this.retiringHoverLabels = new Map();
@@ -12403,6 +12974,27 @@ var VisualsRegister;
12403
12974
  register: this
12404
12975
  });
12405
12976
  this.simulationRemoval = (_b = (_a = EntitySimulation.Get(this.viewer)) === null || _a === void 0 ? void 0 : _a.OnChanged.Subscribe((change) => this.reloadLabels(change.entityIds))) !== null && _b !== void 0 ? _b : null;
12977
+ this.entityShadows = Boolean((_c = this.viewer) === null || _c === void 0 ? void 0 : _c.shadows);
12978
+ this.shadowsRemoval = (_g = (_f = (_e = (_d = this.viewer) === null || _d === void 0 ? void 0 : _d.scene) === null || _e === void 0 ? void 0 : _e.preUpdate) === null || _f === void 0 ? void 0 : _f.addEventListener(() => this.syncEntityShadows())) !== null && _g !== void 0 ? _g : null;
12979
+ }
12980
+ /*
12981
+ * Follows the scene's shadow switch on every registered Entity, whichever app or bookmark flipped it.
12982
+ */
12983
+ syncEntityShadows() {
12984
+ const viewer = this.viewer;
12985
+ const enabled = Boolean(viewer === null || viewer === void 0 ? void 0 : viewer.shadows);
12986
+ if (enabled === this.entityShadows || this.disposed || !viewer || viewer.isDestroyed()) {
12987
+ return;
12988
+ }
12989
+ this.entityShadows = enabled;
12990
+ const mode = enabled ? ShadowMode.ENABLED : ShadowMode.DISABLED;
12991
+ for (const regos of Object.values(this.rego)) {
12992
+ for (const rego of regos) {
12993
+ if (rego.visual instanceof Entity) {
12994
+ updateCEntityShadows(viewer, rego.visual, mode);
12995
+ }
12996
+ }
12997
+ }
12406
12998
  }
12407
12999
  /*
12408
13000
  * Tells labels to read their Entity again, since they keep what they read when drawn.
@@ -13331,9 +13923,11 @@ var VisualsRegister;
13331
13923
  (_b = this.viewer.scene) === null || _b === void 0 ? void 0 : _b.requestRender();
13332
13924
  }
13333
13925
  Dispose() {
13334
- var _a, _b, _c, _d, _e, _f;
13926
+ var _a, _b, _c, _d, _e, _f, _g;
13335
13927
  (_a = this.simulationRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
13336
13928
  this.simulationRemoval = null;
13929
+ (_b = this.shadowsRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
13930
+ this.shadowsRemoval = null;
13337
13931
  CesiumEntityStyler.SetDimming(this.viewer, this.id, false);
13338
13932
  this.ClearLabelHover();
13339
13933
  clearTimeout(this.hoverRingReset);
@@ -13343,14 +13937,14 @@ var VisualsRegister;
13343
13937
  EntityLabel.Detatch({ label });
13344
13938
  }
13345
13939
  this.retiringHoverLabels.clear();
13346
- (_b = this.labelGroups) === null || _b === void 0 ? void 0 : _b.Dispose();
13940
+ (_c = this.labelGroups) === null || _c === void 0 ? void 0 : _c.Dispose();
13347
13941
  this.labelGroupsChanged.Clear();
13348
13942
  for (const regos of Object.values(this.rego)) {
13349
13943
  for (const rego of regos)
13350
13944
  EntityLabel.Detatch({ rego, force: true });
13351
13945
  }
13352
13946
  EntityLabel.SetContainer(this.viewer);
13353
- (_c = this.cameraCullerDispose) === null || _c === void 0 ? void 0 : _c.call(this, {
13947
+ (_d = this.cameraCullerDispose) === null || _d === void 0 ? void 0 : _d.call(this, {
13354
13948
  uncull: false
13355
13949
  });
13356
13950
  this.cameraCullerDispose = null;
@@ -13367,7 +13961,7 @@ var VisualsRegister;
13367
13961
  this.disposed = true;
13368
13962
  for (const record of Array.from(this.menuItemSources.values())) {
13369
13963
  try {
13370
- (_e = (_d = this.viewer) === null || _d === void 0 ? void 0 : _d.dataSources) === null || _e === void 0 ? void 0 : _e.remove(record.source, true);
13964
+ (_f = (_e = this.viewer) === null || _e === void 0 ? void 0 : _e.dataSources) === null || _f === void 0 ? void 0 : _f.remove(record.source, true);
13371
13965
  }
13372
13966
  catch (e) {
13373
13967
  console.error(e);
@@ -13376,7 +13970,7 @@ var VisualsRegister;
13376
13970
  this.menuItemSources.clear();
13377
13971
  // Hand every tileset's show flag back to its owner, holding whatever the owner last asked for.
13378
13972
  for (const rego of Array.from(this.tilesets.values())) {
13379
- (_f = rego.gate) === null || _f === void 0 ? void 0 : _f.Release();
13973
+ (_g = rego.gate) === null || _g === void 0 ? void 0 : _g.Release();
13380
13974
  }
13381
13975
  this.tilesets.clear();
13382
13976
  }
@@ -14100,8 +14694,11 @@ var VisualsRegister;
14100
14694
  }
14101
14695
  }
14102
14696
  // Add the new visual to the register.
14697
+ // Tileset managers add the same rego again for each tile holding more of the Entity.
14103
14698
  const entityRegos = (_a = this.rego[entityId]) !== null && _a !== void 0 ? _a : [];
14104
- entityRegos.push(rego);
14699
+ if (!entityRegos.includes(rego)) {
14700
+ entityRegos.push(rego);
14701
+ }
14105
14702
  this.rego[entityId] = entityRegos;
14106
14703
  this.indexMenuItemEntity(rego.menuItemId, entityId);
14107
14704
  // Mark the visual as part of this register so selection works.
@@ -14535,6 +15132,28 @@ var VisualsRegister;
14535
15132
  }
14536
15133
  }
14537
15134
  }
15135
+ /*
15136
+ * Hands a tile feature rego to a sibling feature from another tile when its own tile unloads.
15137
+ * An Entity split across tiles keeps drawing from those, so it must stay registered for them.
15138
+ */
15139
+ promoteTileSibling(rego, removing) {
15140
+ var _a;
15141
+ const departing = rego.visual;
15142
+ if (!(departing instanceof Cesium3DTileFeature) || !((_a = departing._siblingGraphics) === null || _a === void 0 ? void 0 : _a.length)) {
15143
+ return false;
15144
+ }
15145
+ const remaining = departing._siblingGraphics.filter(s => !removing.has(s));
15146
+ if (!remaining.length) {
15147
+ return false;
15148
+ }
15149
+ departing._siblingGraphics = undefined;
15150
+ unmarkEntity(departing, true);
15151
+ const promoted = remaining[0];
15152
+ promoted._siblingGraphics = remaining.slice(1);
15153
+ rego.visual = promoted;
15154
+ markEntity(this, rego, promoted, false);
15155
+ return true;
15156
+ }
14538
15157
  /**
14539
15158
  * Removes regos matching given visuals.
14540
15159
  * This is used when we cannot unpack properties from the visual any longer to determine its ID.
@@ -14545,6 +15164,7 @@ var VisualsRegister;
14545
15164
  var _a, _b, _c;
14546
15165
  const { doRemove = true, requestRender = true, source, doUpdate, menuItemId } = params;
14547
15166
  const removedEntityIds = new Set();
15167
+ const removing = new Set(params.visuals);
14548
15168
  for (const visual of params.visuals) {
14549
15169
  const visExt = visual;
14550
15170
  const rego = (visExt === null || visExt === void 0 ? void 0 : visExt._register) === this ? visExt._rego : null;
@@ -14556,7 +15176,20 @@ var VisualsRegister;
14556
15176
  if (!entityRegos) {
14557
15177
  continue;
14558
15178
  }
14559
- if (rego.visual === visual) {
15179
+ if (rego.visual === visual && this.promoteTileSibling(rego, removing)) {
15180
+ if (doRemove != false) {
15181
+ removeEntity(this.viewer, visual);
15182
+ }
15183
+ // The label was placed against the departing feature, the refresh attaches it again.
15184
+ EntityLabel.Detatch({
15185
+ rego, force: true
15186
+ });
15187
+ this.queueUpdate({
15188
+ entityId: entityId,
15189
+ refresh: true
15190
+ });
15191
+ }
15192
+ else if (rego.visual === visual) {
14560
15193
  EntityLabel.Detatch({
14561
15194
  rego, force: true
14562
15195
  });
@@ -16212,6 +16845,304 @@ class TilesetStyler {
16212
16845
  }
16213
16846
  }
16214
16847
 
16848
+ // viewer[VIEWER_WATCH_KEY] is true once the viewer's canvas reports context loss to DeviceProfile.
16849
+ const VIEWER_WATCH_KEY = "_bruceDeviceProfileWatch";
16850
+ // localStorage[DEMOTION_KEY] holds an IDemotion written after this browser lost a WebGL context.
16851
+ const DEMOTION_KEY = "bruce-cesium-device-demotion";
16852
+ // A lost context demotes for this long, so one driver reset does not cap a strong device forever.
16853
+ // Measured on the wall clock, not the viewer's: it spans sessions, and a scene clock can be historic.
16854
+ const DEMOTION_MS = 14 * 24 * 60 * 60 * 1000;
16855
+ // Each loss demotes one more tier, up to this many.
16856
+ const DEMOTION_MAX = 2;
16857
+ /*
16858
+ * Reads the demotion left by earlier context losses, ignoring an expired or unreadable one.
16859
+ */
16860
+ function readDemotion(now) {
16861
+ try {
16862
+ const value = JSON.parse(window.localStorage.getItem(DEMOTION_KEY));
16863
+ if (value &&
16864
+ Number.isFinite(value.count) &&
16865
+ Number.isFinite(value.at) &&
16866
+ now - value.at < DEMOTION_MS) {
16867
+ return value;
16868
+ }
16869
+ }
16870
+ catch (e) {
16871
+ // Storage is blocked (private mode, sandboxed frame) or holds junk; treat as no demotion.
16872
+ }
16873
+ return null;
16874
+ }
16875
+ /*
16876
+ * Stores or clears the demotion, carrying on without it where storage is blocked.
16877
+ */
16878
+ function writeDemotion(value) {
16879
+ try {
16880
+ if (value) {
16881
+ window.localStorage.setItem(DEMOTION_KEY, JSON.stringify(value));
16882
+ }
16883
+ else {
16884
+ window.localStorage.removeItem(DEMOTION_KEY);
16885
+ }
16886
+ }
16887
+ catch (e) {
16888
+ // Without storage the demotion lasts only for this page.
16889
+ }
16890
+ }
16891
+ /*
16892
+ * Reads the GPU name, preferring the unmasked one, which browsers that hide it simply do not offer.
16893
+ */
16894
+ function readRenderer(gl) {
16895
+ try {
16896
+ const ext = gl.getExtension("WEBGL_debug_renderer_info");
16897
+ const name = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER);
16898
+ return typeof name === "string" && name ? name : undefined;
16899
+ }
16900
+ catch (e) {
16901
+ return undefined;
16902
+ }
16903
+ }
16904
+ /**
16905
+ * Picks how much the scene may hold in memory on this device, from a hardware guess and past sessions.
16906
+ */
16907
+ var DeviceProfile;
16908
+ (function (DeviceProfile) {
16909
+ let ETier;
16910
+ (function (ETier) {
16911
+ ETier["Low"] = "LOW";
16912
+ ETier["Mid"] = "MID";
16913
+ ETier["High"] = "HIGH";
16914
+ })(ETier = DeviceProfile.ETier || (DeviceProfile.ETier = {}));
16915
+ DeviceProfile.BUDGETS = {
16916
+ [ETier.Low]: { tilesetMb: 384, splats: 1000000 },
16917
+ [ETier.Mid]: { tilesetMb: 1024, splats: 3000000 },
16918
+ [ETier.High]: { tilesetMb: 2048, splats: 6000000 }
16919
+ };
16920
+ const ORDER = [ETier.Low, ETier.Mid, ETier.High];
16921
+ /**
16922
+ * Raised after the override or a demotion changes the tier, so live budgets can be redistributed.
16923
+ */
16924
+ DeviceProfile.Changed = new Event();
16925
+ let override = null;
16926
+ let detected = null;
16927
+ let logged = false;
16928
+ /*
16929
+ * Sorts a GPU name into a class. Order matters: Intel Arc is discrete, SwiftShader names any vendor.
16930
+ */
16931
+ function ClassifyGpu(renderer) {
16932
+ const name = (renderer || "").toLowerCase();
16933
+ if (!name) {
16934
+ return "unknown";
16935
+ }
16936
+ if (/swiftshader|llvmpipe|softpipe|lavapipe|software|basic render/.test(name)) {
16937
+ return "software";
16938
+ }
16939
+ if (/mali|adreno|powervr|apple gpu|videocore|immortalis|xclipse|tegra/.test(name)) {
16940
+ return "mobile";
16941
+ }
16942
+ if (/apple m\d+ (pro|max|ultra)/.test(name)) {
16943
+ return "discrete";
16944
+ }
16945
+ if (/nvidia|geforce|quadro|rtx|radeon (rx|pro|r9|vii)|firepro|arc(\(tm\))? a\d/.test(name)) {
16946
+ return "discrete";
16947
+ }
16948
+ if (/intel|apple m\d|radeon(\(tm\))? graphics|vega \d/.test(name)) {
16949
+ return "integrated";
16950
+ }
16951
+ return "unknown";
16952
+ }
16953
+ DeviceProfile.ClassifyGpu = ClassifyGpu;
16954
+ /**
16955
+ * Guesses a tier from the hardware alone. Unknown hardware stays mid; only clear evidence moves it.
16956
+ */
16957
+ function Classify(env) {
16958
+ const ua = (env.userAgent || "").toLowerCase();
16959
+ const mobile = /android|iphone|ipad|ipod|mobile/.test(ua) || (/macintosh/.test(ua) && env.maxTouchPoints > 1);
16960
+ const gpuClass = ClassifyGpu(env.gpuRenderer);
16961
+ const memory = env.deviceMemory;
16962
+ const reasons = [];
16963
+ let tier = ETier.Mid;
16964
+ if (gpuClass === "software") {
16965
+ reasons.push("software renderer");
16966
+ tier = ETier.Low;
16967
+ }
16968
+ if (mobile) {
16969
+ reasons.push("mobile device");
16970
+ tier = ETier.Low;
16971
+ }
16972
+ if (Number.isFinite(memory) && memory < 4) {
16973
+ reasons.push(`${memory} GB device memory`);
16974
+ tier = ETier.Low;
16975
+ }
16976
+ if (Number.isFinite(env.maxTextureSize) && env.maxTextureSize < 8192) {
16977
+ reasons.push(`max texture ${env.maxTextureSize}`);
16978
+ tier = ETier.Low;
16979
+ }
16980
+ if (tier === ETier.Mid && gpuClass === "discrete") {
16981
+ if (!Number.isFinite(memory) || memory >= 8) {
16982
+ reasons.push("discrete GPU");
16983
+ tier = ETier.High;
16984
+ }
16985
+ else {
16986
+ reasons.push(`discrete GPU held to mid by ${memory} GB device memory`);
16987
+ }
16988
+ }
16989
+ if (!reasons.length) {
16990
+ reasons.push(`${gpuClass} GPU`);
16991
+ }
16992
+ return { tier, gpuClass, mobile, reasons };
16993
+ }
16994
+ DeviceProfile.Classify = Classify;
16995
+ /*
16996
+ * Reads the browser environment, from the viewer's GL context when given rather than a spare one.
16997
+ */
16998
+ function readEnvironment(viewer) {
16999
+ var _a, _b, _c;
17000
+ const nav = typeof navigator !== "undefined" ? navigator : {};
17001
+ const env = {
17002
+ userAgent: nav.userAgent,
17003
+ maxTouchPoints: nav.maxTouchPoints,
17004
+ deviceMemory: nav.deviceMemory,
17005
+ hardwareConcurrency: nav.hardwareConcurrency
17006
+ };
17007
+ let gl = (_b = (_a = viewer === null || viewer === void 0 ? void 0 : viewer.scene) === null || _a === void 0 ? void 0 : _a.context) === null || _b === void 0 ? void 0 : _b._gl;
17008
+ let spare = false;
17009
+ if (!gl && typeof document !== "undefined") {
17010
+ try {
17011
+ gl = document.createElement("canvas").getContext("webgl");
17012
+ spare = Boolean(gl);
17013
+ }
17014
+ catch (e) {
17015
+ gl = null;
17016
+ }
17017
+ }
17018
+ // Detection must never stop a scene loading, so anything odd leaves the GPU unknown.
17019
+ if (gl &&
17020
+ typeof gl.getParameter === "function" &&
17021
+ typeof gl.getExtension === "function") {
17022
+ env.gpuRenderer = readRenderer(gl);
17023
+ try {
17024
+ env.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE) || undefined;
17025
+ if (spare) {
17026
+ // Browsers cap live contexts per page; give this one back now rather than at GC.
17027
+ (_c = gl.getExtension("WEBGL_lose_context")) === null || _c === void 0 ? void 0 : _c.loseContext();
17028
+ }
17029
+ }
17030
+ catch (e) {
17031
+ // Leave it unknown.
17032
+ }
17033
+ }
17034
+ return env;
17035
+ }
17036
+ /**
17037
+ * Returns this device's profile, detecting it once per page. Pass the viewer to read its GL context.
17038
+ */
17039
+ function Get(viewer) {
17040
+ var _a, _b;
17041
+ if (!detected) {
17042
+ const environment = readEnvironment(viewer);
17043
+ detected = { ...Classify(environment), environment };
17044
+ }
17045
+ const demotion = readDemotion(Date.now());
17046
+ const demoted = Math.min((_a = demotion === null || demotion === void 0 ? void 0 : demotion.count) !== null && _a !== void 0 ? _a : 0, DEMOTION_MAX);
17047
+ let tier = ORDER[Math.max(0, ORDER.indexOf(detected.tier) - demoted)];
17048
+ if (override) {
17049
+ tier = override;
17050
+ }
17051
+ const profile = {
17052
+ tier,
17053
+ detectedTier: detected.tier,
17054
+ gpuClass: detected.gpuClass,
17055
+ mobile: detected.mobile,
17056
+ demoted,
17057
+ overridden: Boolean(override),
17058
+ reasons: [...detected.reasons],
17059
+ environment: { ...detected.environment }
17060
+ };
17061
+ if (!logged) {
17062
+ logged = true;
17063
+ console.info(`[DeviceProfile] tier ${profile.tier} (${profile.reasons.join(", ")}` +
17064
+ `${demoted ? `, demoted ${demoted} after lost WebGL context` : ""}` +
17065
+ `${override ? ", overridden" : ""}). GPU: ${(_b = profile.environment.gpuRenderer) !== null && _b !== void 0 ? _b : "unknown"}.`);
17066
+ }
17067
+ return profile;
17068
+ }
17069
+ DeviceProfile.Get = Get;
17070
+ /**
17071
+ * Returns the memory budgets for this device's tier, or for a given tier.
17072
+ */
17073
+ function GetBudget(tier) {
17074
+ return DeviceProfile.BUDGETS[tier !== null && tier !== void 0 ? tier : Get().tier];
17075
+ }
17076
+ DeviceProfile.GetBudget = GetBudget;
17077
+ /**
17078
+ * Forces a tier, for a user who knows better than the guess. Null returns to detection.
17079
+ */
17080
+ function SetOverride(tier) {
17081
+ const next = tier && ORDER.includes(tier) ? tier : null;
17082
+ if (next === override) {
17083
+ return;
17084
+ }
17085
+ override = next;
17086
+ DeviceProfile.Changed.raiseEvent();
17087
+ }
17088
+ DeviceProfile.SetOverride = SetOverride;
17089
+ /**
17090
+ * Returns the forced tier, or null while detection decides.
17091
+ */
17092
+ function GetOverride() {
17093
+ return override;
17094
+ }
17095
+ DeviceProfile.GetOverride = GetOverride;
17096
+ /**
17097
+ * Records that a WebGL context was lost, so this and later sessions start a tier lower.
17098
+ */
17099
+ function RecordContextLost() {
17100
+ var _a, _b;
17101
+ const now = Date.now();
17102
+ const count = Math.min(((_b = (_a = readDemotion(now)) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0) + 1, DEMOTION_MAX);
17103
+ writeDemotion({ count, at: now });
17104
+ console.warn(`[DeviceProfile] WebGL context lost; demoting the memory tier by ${count}.`);
17105
+ DeviceProfile.Changed.raiseEvent();
17106
+ }
17107
+ DeviceProfile.RecordContextLost = RecordContextLost;
17108
+ /**
17109
+ * Forgets earlier context losses, for a host that offers a reset.
17110
+ */
17111
+ function ClearDemotion() {
17112
+ writeDemotion(null);
17113
+ DeviceProfile.Changed.raiseEvent();
17114
+ }
17115
+ DeviceProfile.ClearDemotion = ClearDemotion;
17116
+ /**
17117
+ * Reports the viewer's context losses. Safe to call repeatedly.
17118
+ */
17119
+ function WatchViewer(viewer) {
17120
+ const canvas = viewer === null || viewer === void 0 ? void 0 : viewer.canvas;
17121
+ if (!canvas ||
17122
+ viewer[VIEWER_WATCH_KEY] ||
17123
+ typeof canvas.addEventListener !== "function") {
17124
+ return;
17125
+ }
17126
+ viewer[VIEWER_WATCH_KEY] = true;
17127
+ canvas.addEventListener("webglcontextlost", () => {
17128
+ // Ignore a loss caused by tearing the viewer down.
17129
+ if (!viewer.isDestroyed()) {
17130
+ RecordContextLost();
17131
+ }
17132
+ });
17133
+ }
17134
+ DeviceProfile.WatchViewer = WatchViewer;
17135
+ /**
17136
+ * Forgets the detected hardware and override, for tests.
17137
+ */
17138
+ function Reset() {
17139
+ detected = null;
17140
+ override = null;
17141
+ logged = false;
17142
+ }
17143
+ DeviceProfile.Reset = Reset;
17144
+ })(DeviceProfile || (DeviceProfile = {}));
17145
+
16215
17146
  /**
16216
17147
  * Returns if a given tileset is alive and in the scene.
16217
17148
  * @param viewer
@@ -16230,7 +17161,7 @@ function isAlive$2(viewer, cTileset) {
16230
17161
  }
16231
17162
  return viewer.scene.primitives.contains(cTileset);
16232
17163
  }
16233
- const VIEWER_WATCH_KEY = "bruce-viewer-watch";
17164
+ const VIEWER_WATCH_KEY$1 = "bruce-viewer-watch";
16234
17165
  const WATCH_KEY = "bruce-tileset-watch";
16235
17166
  const VIEWER_DATE_RANGE_WATCH_KEY = "bruce-viewer-date-range-watch";
16236
17167
  const DATE_RANGE_WATCH_KEY = "bruce-tileset-date-range-watch";
@@ -16292,6 +17223,22 @@ function setDateRangeVisible(renderObject, visible) {
16292
17223
  }
16293
17224
  renderObject._bruceDateRangeVisible = visible;
16294
17225
  }
17226
+ // A tileset may pass its cache share by this fraction before Cesium coarsens it to fit.
17227
+ const TILESET_OVERFLOW_RATIO = 0.25;
17228
+ /*
17229
+ * Returns the options capping a tileset's tile cache at the given MB, in the form the running Cesium reads.
17230
+ */
17231
+ function getTilesetMemoryProps(mb) {
17232
+ const prototype = Cesium3DTileset.prototype;
17233
+ if ("cacheBytes" in prototype) {
17234
+ const props = { cacheBytes: Math.round(mb * 1024 * 1024) };
17235
+ if ("maximumCacheOverflowBytes" in prototype) {
17236
+ props.maximumCacheOverflowBytes = Math.round(mb * TILESET_OVERFLOW_RATIO * 1024 * 1024);
17237
+ }
17238
+ return props;
17239
+ }
17240
+ return { maximumMemoryUsage: mb };
17241
+ }
16295
17242
  /**
16296
17243
  * Creates and returns a tileset instance from a given url.
16297
17244
  * This is made to handle the difference between old and new cesium versions.
@@ -16308,8 +17255,8 @@ async function createTileset(url, props, noMemoryLimit) {
16308
17255
  };
16309
17256
  Object.assign(defaultProps, ViewUtils.DefaultLightingProps());
16310
17257
  if (noMemoryLimit != true) {
16311
- // "The maximum amount of memory in MB that can be used by the tileset.".
16312
- defaultProps.maximumMemoryUsage = 512;
17258
+ // The memory watcher narrows this to a share once the tileset joins it.
17259
+ Object.assign(defaultProps, getTilesetMemoryProps(TilesetRenderEngine.GetTilesetMemoryBudget()));
16313
17260
  }
16314
17261
  const C3DT = Cesium3DTileset;
16315
17262
  if (C3DT.fromUrl) {
@@ -16861,10 +17808,23 @@ var TilesetRenderEngine;
16861
17808
  }
16862
17809
  TilesetRenderEngine.UnwatchDateRange = UnwatchDateRange;
16863
17810
  /**
16864
- * The maximum memory in MB that can be used by all tilesets.
16865
- * This is distributed evenly between all loaded tilesets.
17811
+ * Total MB all tilesets may use, split evenly; null leaves it to the DeviceProfile tier.
17812
+ */
17813
+ TilesetRenderEngine.MAX_TILESET_MEMORY = null;
17814
+ /**
17815
+ * Smallest MB share a tileset gets; small, since Cesium coarsens one that needs more.
16866
17816
  */
16867
- TilesetRenderEngine.MAX_TILESET_MEMORY = 1024;
17817
+ TilesetRenderEngine.MIN_TILESET_MEMORY = 32;
17818
+ /**
17819
+ * Returns the total MB all tilesets may use, from MAX_TILESET_MEMORY or else the device tier.
17820
+ */
17821
+ function GetTilesetMemoryBudget() {
17822
+ if (Number.isFinite(TilesetRenderEngine.MAX_TILESET_MEMORY) && TilesetRenderEngine.MAX_TILESET_MEMORY > 0) {
17823
+ return TilesetRenderEngine.MAX_TILESET_MEMORY;
17824
+ }
17825
+ return DeviceProfile.GetBudget().tilesetMb;
17826
+ }
17827
+ TilesetRenderEngine.GetTilesetMemoryBudget = GetTilesetMemoryBudget;
16868
17828
  /**
16869
17829
  * Watches tileset date cutoffs and toggles their primitive visibility based on the viewer clock.
16870
17830
  */
@@ -16955,23 +17915,26 @@ var TilesetRenderEngine;
16955
17915
  constructor(viewer) {
16956
17916
  this.watched = [];
16957
17917
  this.viewer = viewer;
17918
+ DeviceProfile.Get(viewer);
17919
+ DeviceProfile.WatchViewer(viewer);
17920
+ this.removeChanged = DeviceProfile.Changed.addEventListener(() => this.distributeMemory());
16958
17921
  }
16959
17922
  distributeMemory() {
16960
17923
  this.clean();
16961
- // Total 1gb as default.
16962
- // We'll need to allow user to change this somehow.
16963
- const maxMemory = TilesetRenderEngine.MAX_TILESET_MEMORY;
16964
- // Minimum memory in MB per tileset.
16965
- const minMemory = 80;
16966
- const totalPerTileset = Math.max(this.watched.length ? maxMemory / this.watched.length : maxMemory, minMemory);
17924
+ if (!this.watched.length) {
17925
+ return;
17926
+ }
17927
+ const share = Math.max(GetTilesetMemoryBudget() / this.watched.length, TilesetRenderEngine.MIN_TILESET_MEMORY);
17928
+ const props = getTilesetMemoryProps(share);
16967
17929
  this.watched.forEach(x => {
16968
- // Newer Cesium killed this property.
16969
- // TODO: Check if it's needed then.
16970
- x["maximumMemoryUsage"] = totalPerTileset;
17930
+ Object.assign(x, props);
16971
17931
  });
16972
17932
  }
16973
17933
  destroy() {
17934
+ var _a;
16974
17935
  this.watched = [];
17936
+ (_a = this.removeChanged) === null || _a === void 0 ? void 0 : _a.call(this);
17937
+ this.removeChanged = null;
16975
17938
  }
16976
17939
  /**
16977
17940
  * Remove all dead tilesets.
@@ -17020,10 +17983,10 @@ var TilesetRenderEngine;
17020
17983
  if (!viewer || viewer.isDestroyed()) {
17021
17984
  return null;
17022
17985
  }
17023
- if (!viewer[VIEWER_WATCH_KEY]) {
17024
- viewer[VIEWER_WATCH_KEY] = new MemoryWatcher(viewer);
17986
+ if (!viewer[VIEWER_WATCH_KEY$1]) {
17987
+ viewer[VIEWER_WATCH_KEY$1] = new MemoryWatcher(viewer);
17025
17988
  }
17026
- return viewer[VIEWER_WATCH_KEY];
17989
+ return viewer[VIEWER_WATCH_KEY$1];
17027
17990
  }
17028
17991
  TilesetRenderEngine.GetMemoryWatcher = GetMemoryWatcher;
17029
17992
  function GetDateRangeWatcher(viewer) {
@@ -28227,7 +29190,9 @@ var XGridsRenderEngine;
28227
29190
  if (loadUrl.indexOf("?") !== -1) {
28228
29191
  loadUrl = loadUrl.split("?")[0];
28229
29192
  }
28230
- let maxSplats = 6000000;
29193
+ const profile = DeviceProfile.Get(viewer);
29194
+ DeviceProfile.WatchViewer(viewer);
29195
+ let maxSplats = DeviceProfile.GetBudget(profile.tier).splats;
28231
29196
  /*
28232
29197
  * Configures optional LOD capabilities once metadata or initial geometry is ready.
28233
29198
  */
@@ -28312,8 +29277,7 @@ var XGridsRenderEngine;
28312
29277
  }
28313
29278
  try {
28314
29279
  const sdkBudget = typeof obj.getCurrentConfig === "function" ? (_d = obj.getCurrentConfig()) === null || _d === void 0 ? void 0 : _d.maxLoadSplatCount : undefined;
28315
- // Preserve the SDK's mobile and weak-GPU limits before applying any temporary startup cap.
28316
- maxSplats = Number.isFinite(sdkBudget) && sdkBudget > 0 && sdkBudget <= 2000000 ? sdkBudget : 6000000;
29280
+ maxSplats = GetSplatBudget(sdkBudget, profile);
28317
29281
  if (LCCRender.nextspaceMetadataReady === 1 &&
28318
29282
  typeof obj.setNextspaceMetadataCallback === "function" &&
28319
29283
  typeof obj.setMaxSplats === "function") {
@@ -28354,6 +29318,21 @@ var XGridsRenderEngine;
28354
29318
  return obj;
28355
29319
  }
28356
29320
  XGridsRenderEngine.Render = Render;
29321
+ /*
29322
+ * The most splats a scan may load: the tier's, lowered by the SDK's GPU limit unless overridden.
29323
+ */
29324
+ function GetSplatBudget(sdkBudget, profile) {
29325
+ const budget = DeviceProfile.GetBudget(profile.tier).splats;
29326
+ // Above 2M the SDK is reporting its flat default, not a limit it chose for this GPU.
29327
+ if (!profile.overridden &&
29328
+ Number.isFinite(sdkBudget) &&
29329
+ sdkBudget > 0 &&
29330
+ sdkBudget <= 2000000) {
29331
+ return Math.min(sdkBudget, budget);
29332
+ }
29333
+ return budget;
29334
+ }
29335
+ XGridsRenderEngine.GetSplatBudget = GetSplatBudget;
28357
29336
  /*
28358
29337
  * Detaches optional tuning before unloading the scan.
28359
29338
  */
@@ -38815,6 +39794,1238 @@ var TileRenderEngine;
38815
39794
  })(Terrain = TileRenderEngine.Terrain || (TileRenderEngine.Terrain = {}));
38816
39795
  })(TileRenderEngine || (TileRenderEngine = {}));
38817
39796
 
39797
+ // Builds simple meshes into binary glTF in code. Nothing here imports Cesium, so it can move to bruce-models as is.
39798
+ const GLB_MAGIC = 0x46546C67;
39799
+ const GLB_CHUNK_JSON = 0x4E4F534A;
39800
+ const GLB_CHUNK_BIN = 0x004E4942;
39801
+ const GL_ARRAY_BUFFER = 34962;
39802
+ const GL_ELEMENT_ARRAY_BUFFER = 34963;
39803
+ const GL_UNSIGNED_SHORT = 5123;
39804
+ const GL_UNSIGNED_INT = 5125;
39805
+ const GL_FLOAT = 5126;
39806
+ const GL_TRIANGLES = 4;
39807
+ /**
39808
+ * Writes meshes as binary glTF (GLB), for models simple enough to describe in code rather than ship as files.
39809
+ */
39810
+ var GltfGenerator;
39811
+ (function (GltfGenerator) {
39812
+ /**
39813
+ * Vertices and triangles collected by the shape functions, one material's worth.
39814
+ */
39815
+ class Mesh {
39816
+ constructor() {
39817
+ this.positions = [];
39818
+ this.normals = [];
39819
+ this.indices = [];
39820
+ }
39821
+ AddVertex(position, normal) {
39822
+ this.positions.push(position[0], position[1], position[2]);
39823
+ this.normals.push(normal[0], normal[1], normal[2]);
39824
+ return this.positions.length / 3 - 1;
39825
+ }
39826
+ /**
39827
+ * Adds a triangle wound so its front faces along normal, whichever order the corners come in.
39828
+ * Per corner normals smooth it; without them every corner takes the face normal.
39829
+ */
39830
+ AddTriangle(a, b, c, normal, corners) {
39831
+ var _a, _b, _c;
39832
+ const ia = this.AddVertex(a, (_a = corners === null || corners === void 0 ? void 0 : corners[0]) !== null && _a !== void 0 ? _a : normal);
39833
+ const ib = this.AddVertex(b, (_b = corners === null || corners === void 0 ? void 0 : corners[1]) !== null && _b !== void 0 ? _b : normal);
39834
+ const ic = this.AddVertex(c, (_c = corners === null || corners === void 0 ? void 0 : corners[2]) !== null && _c !== void 0 ? _c : normal);
39835
+ if (Dot(Cross(Sub(b, a), Sub(c, a)), normal) >= 0) {
39836
+ this.indices.push(ia, ib, ic);
39837
+ }
39838
+ else {
39839
+ this.indices.push(ia, ic, ib);
39840
+ }
39841
+ }
39842
+ /**
39843
+ * Joins three added vertices into a triangle facing along normal, so shapes can share corners.
39844
+ */
39845
+ AddIndexedTriangle(ia, ib, ic, normal) {
39846
+ const at = (i) => [this.positions[i * 3], this.positions[i * 3 + 1], this.positions[i * 3 + 2]];
39847
+ const a = at(ia);
39848
+ if (Dot(Cross(Sub(at(ib), a), Sub(at(ic), a)), normal) >= 0) {
39849
+ this.indices.push(ia, ib, ic);
39850
+ }
39851
+ else {
39852
+ this.indices.push(ia, ic, ib);
39853
+ }
39854
+ }
39855
+ get VertexCount() {
39856
+ return this.positions.length / 3;
39857
+ }
39858
+ }
39859
+ GltfGenerator.Mesh = Mesh;
39860
+ function Add(a, b) {
39861
+ return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
39862
+ }
39863
+ GltfGenerator.Add = Add;
39864
+ function Sub(a, b) {
39865
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
39866
+ }
39867
+ GltfGenerator.Sub = Sub;
39868
+ function Scale(a, s) {
39869
+ return [a[0] * s, a[1] * s, a[2] * s];
39870
+ }
39871
+ GltfGenerator.Scale = Scale;
39872
+ function Dot(a, b) {
39873
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
39874
+ }
39875
+ GltfGenerator.Dot = Dot;
39876
+ function Cross(a, b) {
39877
+ return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
39878
+ }
39879
+ GltfGenerator.Cross = Cross;
39880
+ function Normalize(a) {
39881
+ const length = Math.hypot(a[0], a[1], a[2]);
39882
+ return length ? Scale(a, 1 / length) : [0, 0, 0];
39883
+ }
39884
+ GltfGenerator.Normalize = Normalize;
39885
+ /*
39886
+ * Two unit vectors at right angles to an axis and each other, the first along start where one is given.
39887
+ */
39888
+ function perpendiculars(axis, start) {
39889
+ const n = Normalize(axis);
39890
+ let u = start ? Sub(start, Scale(n, Dot(start, n))) : null;
39891
+ if (!u || Math.hypot(u[0], u[1], u[2]) < 1e-9) {
39892
+ u = Math.abs(n[0]) < 0.9 ? Cross(n, [1, 0, 0]) : Cross(n, [0, 1, 0]);
39893
+ }
39894
+ u = Normalize(u);
39895
+ return [u, Cross(n, u)];
39896
+ }
39897
+ /*
39898
+ * The point on a circle around centre at a given turn, in the plane the two perpendiculars span.
39899
+ */
39900
+ function onCircle(centre, u, v, radius, angle) {
39901
+ return Add(centre, Add(Scale(u, Math.cos(angle) * radius), Scale(v, Math.sin(angle) * radius)));
39902
+ }
39903
+ /**
39904
+ * Adds a flat disc facing along normal.
39905
+ */
39906
+ function AddDisc(mesh, params) {
39907
+ const { centre, radius, sides } = params;
39908
+ const normal = Normalize(params.normal);
39909
+ const [u, v] = perpendiculars(normal, params.start);
39910
+ for (let i = 0; i < sides; i++) {
39911
+ const a = onCircle(centre, u, v, radius, i / sides * Math.PI * 2);
39912
+ const b = onCircle(centre, u, v, radius, (i + 1) / sides * Math.PI * 2);
39913
+ mesh.AddTriangle(centre, a, b, normal);
39914
+ }
39915
+ }
39916
+ GltfGenerator.AddDisc = AddDisc;
39917
+ /**
39918
+ * Adds a smooth sided cylinder between two points, closed at the start by default.
39919
+ */
39920
+ function AddCylinder(mesh, params) {
39921
+ const { start, end, radius, sides } = params;
39922
+ const axis = Normalize(Sub(end, start));
39923
+ const [u, v] = perpendiculars(axis);
39924
+ for (let i = 0; i < sides; i++) {
39925
+ const angleA = i / sides * Math.PI * 2;
39926
+ const angleB = (i + 1) / sides * Math.PI * 2;
39927
+ const outA = onCircle([0, 0, 0], u, v, 1, angleA);
39928
+ const outB = onCircle([0, 0, 0], u, v, 1, angleB);
39929
+ const a0 = onCircle(start, u, v, radius, angleA);
39930
+ const b0 = onCircle(start, u, v, radius, angleB);
39931
+ const a1 = onCircle(end, u, v, radius, angleA);
39932
+ const b1 = onCircle(end, u, v, radius, angleB);
39933
+ const face = Normalize(Add(outA, outB));
39934
+ mesh.AddTriangle(a0, b0, b1, face, [outA, outB, outB]);
39935
+ mesh.AddTriangle(a0, b1, a1, face, [outA, outB, outA]);
39936
+ }
39937
+ if (params.capStart != false) {
39938
+ AddDisc(mesh, { centre: start, normal: Scale(axis, -1), radius, sides });
39939
+ }
39940
+ if (params.capEnd) {
39941
+ AddDisc(mesh, { centre: end, normal: axis, radius, sides });
39942
+ }
39943
+ }
39944
+ GltfGenerator.AddCylinder = AddCylinder;
39945
+ /**
39946
+ * Adds a cone from a base circle to a tip, closed at the base by default.
39947
+ */
39948
+ function AddCone(mesh, params) {
39949
+ const { base, tip, radius, sides } = params;
39950
+ const toTip = Sub(tip, base);
39951
+ const height = Math.hypot(toTip[0], toTip[1], toTip[2]);
39952
+ const axis = Normalize(toTip);
39953
+ const [u, v] = perpendiculars(axis, params.start);
39954
+ // A side normal leans toward the tip by the slope of the side.
39955
+ const sideNormal = (angle) => Normalize(Add(Scale(onCircle([0, 0, 0], u, v, 1, angle), height), Scale(axis, radius)));
39956
+ for (let i = 0; i < sides; i++) {
39957
+ const angleA = i / sides * Math.PI * 2;
39958
+ const angleB = (i + 1) / sides * Math.PI * 2;
39959
+ const a = onCircle(base, u, v, radius, angleA);
39960
+ const b = onCircle(base, u, v, radius, angleB);
39961
+ const face = sideNormal((angleA + angleB) / 2);
39962
+ mesh.AddTriangle(a, b, tip, face, params.faceted ? undefined : [sideNormal(angleA), sideNormal(angleB), face]);
39963
+ }
39964
+ if (params.cap != false) {
39965
+ AddDisc(mesh, { centre: base, normal: Scale(axis, -1), radius, sides, start: params.start });
39966
+ }
39967
+ }
39968
+ GltfGenerator.AddCone = AddCone;
39969
+ /**
39970
+ * Adds a smooth ring of tube around an axis through centre.
39971
+ */
39972
+ function AddTorus(mesh, params) {
39973
+ const { centre, radius, tube, segments, sides } = params;
39974
+ const axis = Normalize(params.axis);
39975
+ const [u, v] = perpendiculars(axis);
39976
+ // A grid of shared corners, wrapping round both ways.
39977
+ const first = mesh.VertexCount;
39978
+ for (let i = 0; i < segments; i++) {
39979
+ const out = onCircle([0, 0, 0], u, v, 1, i / segments * Math.PI * 2);
39980
+ for (let j = 0; j < sides; j++) {
39981
+ const turn = j / sides * Math.PI * 2;
39982
+ const normal = Add(Scale(out, Math.cos(turn)), Scale(axis, Math.sin(turn)));
39983
+ mesh.AddVertex(Add(Add(centre, Scale(out, radius)), Scale(normal, tube)), normal);
39984
+ }
39985
+ }
39986
+ const at = (i, j) => first + (i % segments) * sides + (j % sides);
39987
+ const normalAt = (i) => [mesh.normals[i * 3], mesh.normals[i * 3 + 1], mesh.normals[i * 3 + 2]];
39988
+ for (let i = 0; i < segments; i++) {
39989
+ for (let j = 0; j < sides; j++) {
39990
+ const quad = [at(i, j), at(i + 1, j), at(i + 1, j + 1), at(i, j + 1)];
39991
+ const normal = Normalize(Add(normalAt(quad[0]), normalAt(quad[2])));
39992
+ mesh.AddIndexedTriangle(quad[0], quad[1], quad[2], normal);
39993
+ mesh.AddIndexedTriangle(quad[0], quad[2], quad[3], normal);
39994
+ }
39995
+ }
39996
+ }
39997
+ GltfGenerator.AddTorus = AddTorus;
39998
+ /**
39999
+ * Adds a flat convex outline, given in a plane's own across and up coordinates, facing along normal.
40000
+ */
40001
+ function AddConvexOutline(mesh, params) {
40002
+ const { points, origin, across, up } = params;
40003
+ const normal = Normalize(params.normal);
40004
+ const at = ([x, y]) => Add(origin, Add(Scale(across, x), Scale(up, y)));
40005
+ for (let i = 1; i < points.length - 1; i++) {
40006
+ mesh.AddTriangle(at(points[0]), at(points[i]), at(points[i + 1]), normal);
40007
+ }
40008
+ }
40009
+ GltfGenerator.AddConvexOutline = AddConvexOutline;
40010
+ function align4(length) {
40011
+ return Math.ceil(length / 4) * 4;
40012
+ }
40013
+ function bounds(positions) {
40014
+ const min = [Infinity, Infinity, Infinity];
40015
+ const max = [-Infinity, -Infinity, -Infinity];
40016
+ for (let i = 0; i < positions.length; i++) {
40017
+ const k = i % 3;
40018
+ min[k] = Math.min(min[k], positions[i]);
40019
+ max[k] = Math.max(max[k], positions[i]);
40020
+ }
40021
+ return { min, max };
40022
+ }
40023
+ /**
40024
+ * Packs a mesh into GLB bytes, under a chain of node matrices (outermost first) when given.
40025
+ */
40026
+ function ToGlb(params) {
40027
+ var _a, _b, _c;
40028
+ const { mesh, material } = params;
40029
+ const count = mesh.VertexCount;
40030
+ const wide = count > 0xFFFF;
40031
+ const IndexArray = wide ? Uint32Array : Uint16Array;
40032
+ const indexLength = mesh.indices.length * IndexArray.BYTES_PER_ELEMENT;
40033
+ const indexBytes = align4(indexLength);
40034
+ const vertexBytes = count * 12;
40035
+ const bin = new ArrayBuffer(indexBytes + vertexBytes * 2);
40036
+ new IndexArray(bin, 0, mesh.indices.length).set(mesh.indices);
40037
+ new Float32Array(bin, indexBytes, count * 3).set(mesh.positions);
40038
+ new Float32Array(bin, indexBytes + vertexBytes, count * 3).set(mesh.normals);
40039
+ // The mesh node is last, each matrix node holding the next.
40040
+ const matrices = (_a = params.matrices) !== null && _a !== void 0 ? _a : [];
40041
+ const nodes = matrices.map((matrix, i) => ({ matrix, children: [i + 1] }));
40042
+ nodes.push({ mesh: 0 });
40043
+ const json = {
40044
+ asset: { version: "2.0", generator: "bruce-cesium GltfGenerator" },
40045
+ scene: 0,
40046
+ scenes: [{ nodes: [0] }],
40047
+ nodes,
40048
+ meshes: [{ primitives: [{ attributes: { POSITION: 1, NORMAL: 2 }, indices: 0, material: 0, mode: GL_TRIANGLES }] }],
40049
+ materials: [{
40050
+ pbrMetallicRoughness: {
40051
+ baseColorFactor: material.color,
40052
+ metallicFactor: (_b = material.metallic) !== null && _b !== void 0 ? _b : 0,
40053
+ roughnessFactor: (_c = material.roughness) !== null && _c !== void 0 ? _c : 1
40054
+ },
40055
+ doubleSided: Boolean(material.doubleSided)
40056
+ }],
40057
+ buffers: [{ byteLength: bin.byteLength }],
40058
+ bufferViews: [
40059
+ { buffer: 0, byteOffset: 0, byteLength: indexLength, target: GL_ELEMENT_ARRAY_BUFFER },
40060
+ { buffer: 0, byteOffset: indexBytes, byteLength: vertexBytes, target: GL_ARRAY_BUFFER },
40061
+ { buffer: 0, byteOffset: indexBytes + vertexBytes, byteLength: vertexBytes, target: GL_ARRAY_BUFFER }
40062
+ ],
40063
+ accessors: [
40064
+ { bufferView: 0, componentType: wide ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT, count: mesh.indices.length, type: "SCALAR" },
40065
+ { bufferView: 1, componentType: GL_FLOAT, count, type: "VEC3", ...bounds(mesh.positions) },
40066
+ { bufferView: 2, componentType: GL_FLOAT, count, type: "VEC3" }
40067
+ ]
40068
+ };
40069
+ let text = JSON.stringify(json);
40070
+ text += " ".repeat(align4(text.length) - text.length);
40071
+ const jsonBytes = new TextEncoder().encode(text);
40072
+ const glb = new Uint8Array(20 + jsonBytes.length + 8 + bin.byteLength);
40073
+ const view = new DataView(glb.buffer);
40074
+ view.setUint32(0, GLB_MAGIC, true);
40075
+ view.setUint32(4, 2, true);
40076
+ view.setUint32(8, glb.length, true);
40077
+ view.setUint32(12, jsonBytes.length, true);
40078
+ view.setUint32(16, GLB_CHUNK_JSON, true);
40079
+ glb.set(jsonBytes, 20);
40080
+ const binStart = 20 + jsonBytes.length;
40081
+ view.setUint32(binStart, bin.byteLength, true);
40082
+ view.setUint32(binStart + 4, GLB_CHUNK_BIN, true);
40083
+ glb.set(new Uint8Array(bin), binStart + 8);
40084
+ return glb;
40085
+ }
40086
+ GltfGenerator.ToGlb = ToGlb;
40087
+ /**
40088
+ * Packs a mesh into a GLB data URI, which loads as a model url with nothing to fetch.
40089
+ */
40090
+ function ToDataUri(params) {
40091
+ const glb = ToGlb(params);
40092
+ let binary = "";
40093
+ for (let i = 0; i < glb.length; i += 0x8000) {
40094
+ binary += String.fromCharCode.apply(null, glb.subarray(i, i + 0x8000));
40095
+ }
40096
+ return "data:model/gltf-binary;base64," + btoa(binary);
40097
+ }
40098
+ GltfGenerator.ToDataUri = ToDataUri;
40099
+ })(GltfGenerator || (GltfGenerator = {}));
40100
+
40101
+ // Sides on an arrow's shaft and cone.
40102
+ const ARROW_SIDES = 16;
40103
+ const SHAFT_RADIUS = 2.4;
40104
+ const SHAFT_LENGTH = 265;
40105
+ const CONE_RADIUS = 10;
40106
+ const CONE_BASE = 264.6;
40107
+ const CONE_TIP = 306.3;
40108
+ const RING_RADIUS = 210;
40109
+ const RING_TUBE = 1;
40110
+ const RING_SEGMENTS = 60;
40111
+ const RING_SIDES = 10;
40112
+ // The ring's two arrowheads sit either side of its 45 degree mark, each pointing back toward it.
40113
+ const RING_MARK_ANGLES = [137.64, -47.64];
40114
+ const RING_MARK_LENGTH = 5.82;
40115
+ const RING_MARK_CENTRE = 209.8;
40116
+ const RING_MARK_RADIUS = 8.5;
40117
+ const RING_MARK_SIDES = 8;
40118
+ // Turns the original models' Y up node into the Z up frame the pieces were drawn in.
40119
+ const ROOT_MATRIX = [1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1];
40120
+ // Points a piece drawn along +Z down another axis, as the original models' mesh nodes did.
40121
+ const TO_X_MATRIX = [0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1];
40122
+ const TO_Y_ARROW_MATRIX = [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1];
40123
+ const TO_Y_RING_MATRIX = [1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1];
40124
+ const RED = { color: [1, 0, 0, 1] };
40125
+ const GREEN = { color: [0.2196, 0.5216, 0.1647, 1] };
40126
+ const BLUE = { color: [0.1608, 0.3843, 0.698, 1] };
40127
+ const LETTER_X = {
40128
+ across: [0, 1, 0],
40129
+ facing: [-1, 0, 0],
40130
+ strokes: [
40131
+ [[16.4, 318.6], [16.4, 324.2], [-19.4, 349.8], [-19.4, 343.9]],
40132
+ [[-19.4, 316.9], [-19.4, 322.6], [16.4, 348.8], [16.4, 343.6]]
40133
+ ]
40134
+ };
40135
+ const LETTER_Y = {
40136
+ across: [0, 1, 0],
40137
+ facing: [-1, 0, 0],
40138
+ strokes: [
40139
+ [[-1.9, 320.1], [2.8, 320.1], [2.8, 337.7], [-1.9, 337.7]],
40140
+ [[-1.9, 335.3], [2.8, 335.3], [-10.7, 355.9], [-16.2, 355.9]],
40141
+ [[-1.9, 335.3], [2.8, 335.3], [16.6, 355.9], [10.9, 355.9]]
40142
+ ]
40143
+ };
40144
+ const LETTER_Z = {
40145
+ across: [1, 0, 0],
40146
+ facing: [0, -1, 0],
40147
+ strokes: [
40148
+ [[-14.6, 319], [13.7, 319], [13.7, 323.3], [-14.6, 323.3]],
40149
+ [[-14.6, 323.3], [-9.1, 323.3], [13.1, 350.6], [7.5, 350.6]],
40150
+ [[-12.5, 350.6], [13.1, 350.6], [13.1, 354.8], [-12.5, 354.8]]
40151
+ ]
40152
+ };
40153
+ /*
40154
+ * An axis arrow along +Z: shaft, cone and the axis letter past the tip.
40155
+ */
40156
+ function arrowMesh(letter) {
40157
+ const mesh = new GltfGenerator.Mesh();
40158
+ GltfGenerator.AddCylinder(mesh, { start: [0, 0, 0], end: [0, 0, SHAFT_LENGTH], radius: SHAFT_RADIUS, sides: ARROW_SIDES });
40159
+ GltfGenerator.AddCone(mesh, { base: [0, 0, CONE_BASE], tip: [0, 0, CONE_TIP], radius: CONE_RADIUS, sides: ARROW_SIDES });
40160
+ for (const stroke of letter.strokes) {
40161
+ GltfGenerator.AddConvexOutline(mesh, { points: stroke, origin: [0, 0, 0], across: letter.across, up: [0, 0, 1], normal: letter.facing });
40162
+ }
40163
+ return mesh;
40164
+ }
40165
+ function onRing(degrees, radius) {
40166
+ const angle = degrees * Math.PI / 180;
40167
+ return [Math.cos(angle) * radius, Math.sin(angle) * radius, 0];
40168
+ }
40169
+ /*
40170
+ * A rotation ring about +Z with its two arrowheads.
40171
+ */
40172
+ function ringMesh() {
40173
+ const mesh = new GltfGenerator.Mesh();
40174
+ GltfGenerator.AddTorus(mesh, {
40175
+ centre: [0, 0, 0],
40176
+ axis: [0, 0, 1],
40177
+ radius: RING_RADIUS,
40178
+ tube: RING_TUBE,
40179
+ segments: RING_SEGMENTS,
40180
+ sides: RING_SIDES
40181
+ });
40182
+ for (const degrees of RING_MARK_ANGLES) {
40183
+ const toward = degrees > 45 ? -RING_MARK_LENGTH : RING_MARK_LENGTH;
40184
+ GltfGenerator.AddCone(mesh, {
40185
+ base: onRing(degrees, RING_MARK_CENTRE),
40186
+ tip: onRing(degrees + toward, RING_MARK_CENTRE),
40187
+ radius: RING_MARK_RADIUS,
40188
+ sides: RING_MARK_SIDES,
40189
+ faceted: true,
40190
+ // A rim corner points at the ring's centre, as the original arrowheads' did.
40191
+ start: onRing(degrees, 1)
40192
+ });
40193
+ }
40194
+ return mesh;
40195
+ }
40196
+ /**
40197
+ * The transform gizmo's pieces as GLB data URIs, built on first use so there are no model files to host.
40198
+ * Sizes, colours and frames match the models they replaced (Navigator's public/res/gizmo), scales included.
40199
+ */
40200
+ var GltfGizmo;
40201
+ (function (GltfGizmo) {
40202
+ let EPiece;
40203
+ (function (EPiece) {
40204
+ EPiece["X"] = "x";
40205
+ EPiece["Y"] = "y";
40206
+ EPiece["Z"] = "z";
40207
+ EPiece["XRotation"] = "x-rot";
40208
+ EPiece["YRotation"] = "y-rot";
40209
+ EPiece["ZRotation"] = "z-rot";
40210
+ })(EPiece = GltfGizmo.EPiece || (GltfGizmo.EPiece = {}));
40211
+ const BUILDERS = {
40212
+ [EPiece.X]: () => GltfGenerator.ToDataUri({ mesh: arrowMesh(LETTER_X), material: RED, matrices: [ROOT_MATRIX, TO_X_MATRIX] }),
40213
+ [EPiece.Y]: () => GltfGenerator.ToDataUri({ mesh: arrowMesh(LETTER_Y), material: GREEN, matrices: [ROOT_MATRIX, TO_Y_ARROW_MATRIX] }),
40214
+ [EPiece.Z]: () => GltfGenerator.ToDataUri({ mesh: arrowMesh(LETTER_Z), material: BLUE, matrices: [ROOT_MATRIX] }),
40215
+ [EPiece.XRotation]: () => GltfGenerator.ToDataUri({ mesh: ringMesh(), material: RED, matrices: [ROOT_MATRIX, TO_X_MATRIX] }),
40216
+ [EPiece.YRotation]: () => GltfGenerator.ToDataUri({ mesh: ringMesh(), material: GREEN, matrices: [ROOT_MATRIX, TO_Y_RING_MATRIX] }),
40217
+ [EPiece.ZRotation]: () => GltfGenerator.ToDataUri({ mesh: ringMesh(), material: BLUE, matrices: [ROOT_MATRIX] })
40218
+ };
40219
+ // Built pieces, so every gizmo shares one string per piece and CesiumJS's model cache sees the same url.
40220
+ const built = {};
40221
+ /**
40222
+ * Returns a piece's model as a data URI, to pass wherever a model url is taken.
40223
+ */
40224
+ function GetUri(piece) {
40225
+ if (!built[piece]) {
40226
+ built[piece] = BUILDERS[piece]();
40227
+ }
40228
+ return built[piece];
40229
+ }
40230
+ GltfGizmo.GetUri = GetUri;
40231
+ })(GltfGizmo || (GltfGizmo = {}));
40232
+
40233
+ // viewer[CLASS_INSTANCE_KEY] will point at an instance of MapGridHandler.
40234
+ const CLASS_INSTANCE_KEY = "nextspace-navigator-grid-handler";
40235
+ // viewer[UPDATE_MARKER_KEY] will point at a number that is incremented every time the grid is updated.
40236
+ // This is used to determine if the grid is still valid or if it needs to be redrawn.
40237
+ const UPDATE_MARKER_KEY = "nextspace-marker-grid";
40238
+ // viewer[LAST_SETTINGS_KEY] will point at the last settings used for the grid.
40239
+ const LAST_SETTINGS_KEY = "nextspace-navigator-last-settings-grid";
40240
+ /*
40241
+ * The point a distance in metres along a heading in degrees from another, over a spherical earth.
40242
+ */
40243
+ function offsetPoint$1(point, distance, heading) {
40244
+ // Radius of earth.
40245
+ const radius = 6371e3;
40246
+ const δ = distance / radius;
40247
+ const θ = Math$1.toRadians(heading);
40248
+ const φ1 = Math$1.toRadians(point.latitude);
40249
+ const λ1 = Math$1.toRadians(point.longitude);
40250
+ const sinφ2 = Math.sin(φ1) * Math.cos(δ) + Math.cos(φ1) * Math.sin(δ) * Math.cos(θ);
40251
+ const φ2 = Math.asin(sinφ2);
40252
+ const y = Math.sin(θ) * Math.sin(δ) * Math.cos(φ1);
40253
+ const x = Math.cos(δ) - Math.sin(φ1) * sinφ2;
40254
+ const λ2 = λ1 + Math.atan2(y, x);
40255
+ return {
40256
+ altitude: point.altitude,
40257
+ latitude: Math$1.toDegrees(φ2),
40258
+ longitude: Math$1.toDegrees(λ2)
40259
+ };
40260
+ }
40261
+ /**
40262
+ * Handler for drawing a grid of lines on the map with a specified spacing.
40263
+ * Initially this grid is for model-space viewing which means we start at 0,0 lat/lon and have it expand from there.
40264
+ * However, it should be a general-use tool for drawing a grid anywhere.
40265
+ *
40266
+ * It reacts to camera changes and draws the grid within view with padding.
40267
+ * This helps avoid performance issues around drawing too many lines.
40268
+ */
40269
+ class MapGridHandler {
40270
+ constructor(viewer, spacingInMeters = 100, modelSpace = false) {
40271
+ this.gridCenterGizmoPieces = [];
40272
+ this.maxCacheSize = 1000;
40273
+ // === Thresholds for grid stability.
40274
+ // As a fraction of current grid extent.
40275
+ this.CAMERA_MOVE_THRESHOLD = 0.5;
40276
+ // 40% change in camera height triggers redraw.
40277
+ this.HEIGHT_CHANGE_THRESHOLD = 0.4;
40278
+ // 40% change in grid extent triggers redraw.
40279
+ this.GRID_EXTENT_CHANGE_THRESHOLD = 0.4;
40280
+ // 5km - distance that triggers origin reset.
40281
+ this.EXTREME_DISTANCE_THRESHOLD = 5000;
40282
+ // Grid limits to prevent drawing too many lines.
40283
+ this.MAX_LINES = 500;
40284
+ // 50% padding around visible area.
40285
+ this.VIEW_PADDING_FACTOR = 0.5;
40286
+ // Minimum pixels between grid lines to show them.
40287
+ this.MIN_GRID_PIXEL_SIZE = 20;
40288
+ // Base ratio - will be adjusted dynamically based on spacing size and camera height.
40289
+ this.BASE_MAX_VISIBILITY_RATIO = 30;
40290
+ // Height to spacing ratio.
40291
+ this.HEIGHT_TO_SPACING_RATIO = 100;
40292
+ // === Label settings
40293
+ // Label every nth intersection.
40294
+ this.LABEL_INTERVAL = 2;
40295
+ // Maximum number of labels to show.
40296
+ this.MAX_LABELS = 100;
40297
+ // Spacing levels to use for adaptive spacing.
40298
+ this.SPACING_LEVELS = [
40299
+ 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 300, 400, 500, 1000, 2000, 2500, 5000, 10000, 20000, 25000, 50000, 100000
40300
+ ];
40301
+ // Minimum zoom distance to prevent precision issues.
40302
+ this.MINIMUM_ZOOM_DISTANCE = 40;
40303
+ // Base spacing to use for adaptive spacing.
40304
+ this.BASE_SPACING = 1;
40305
+ // Maximum number of attempts to find a suitable spacing.
40306
+ this.MAX_ADAPTIVE_ATTEMPTS = 5;
40307
+ this.viewer = viewer;
40308
+ this.spacingInMeters = spacingInMeters;
40309
+ this.isUpdating = false;
40310
+ this.modelSpace = modelSpace;
40311
+ this.lastCameraPosition = null;
40312
+ this.fixedGridOrigin = null;
40313
+ this.lastGridExtent = 0;
40314
+ this.offsetCache = new Map();
40315
+ this.gridPrimitives = new PrimitiveCollection();
40316
+ this.viewer.scene.primitives.add(this.gridPrimitives);
40317
+ this.currentMarker = viewer[UPDATE_MARKER_KEY] || 1;
40318
+ const isMac = typeof navigator !== "undefined" && (navigator.platform.toUpperCase().indexOf("MAC") !== -1 ||
40319
+ navigator.userAgent.toUpperCase().indexOf("MAC") !== -1);
40320
+ if (modelSpace && isMac) {
40321
+ console.log("[MAC-FIX] Applying camera distance limits for Mac");
40322
+ viewer.scene.screenSpaceCameraController.minimumZoomDistance = this.MINIMUM_ZOOM_DISTANCE;
40323
+ }
40324
+ const manager = ViewerUtils.GetManager({
40325
+ viewer: this.viewer,
40326
+ createIfMissing: false
40327
+ });
40328
+ if (manager) {
40329
+ this.cameraChangeRemoval = manager.Monitor.Updated().Subscribe(() => {
40330
+ this.onCameraChanged();
40331
+ });
40332
+ }
40333
+ // Initial grid draw.
40334
+ this.onCameraChanged();
40335
+ }
40336
+ Update(spacingInMeters, modelSpace) {
40337
+ this.currentMarker = this.viewer[UPDATE_MARKER_KEY] || 1;
40338
+ let needsUpdate = false;
40339
+ if (this.spacingInMeters !== spacingInMeters) {
40340
+ this.spacingInMeters = spacingInMeters;
40341
+ needsUpdate = true;
40342
+ }
40343
+ if (modelSpace !== undefined && this.modelSpace !== modelSpace) {
40344
+ this.modelSpace = modelSpace;
40345
+ needsUpdate = true;
40346
+ }
40347
+ if (needsUpdate) {
40348
+ this.clearGrid();
40349
+ // When spacing or model space changes, clear the cached origin to force recalculation
40350
+ // This ensures grid alignment at the new spacing.
40351
+ this.fixedGridOrigin = null;
40352
+ this.offsetCache.clear();
40353
+ this.onCameraChanged();
40354
+ }
40355
+ }
40356
+ Dispose() {
40357
+ this.clearGrid();
40358
+ this.clearGizmoPieces();
40359
+ if (this.cameraChangeRemoval) {
40360
+ this.cameraChangeRemoval();
40361
+ this.cameraChangeRemoval = null;
40362
+ }
40363
+ if (this.gridPrimitives) {
40364
+ this.viewer.scene.primitives.remove(this.gridPrimitives);
40365
+ this.gridPrimitives = null;
40366
+ }
40367
+ if (this.viewer) {
40368
+ this.viewer = null;
40369
+ }
40370
+ this.offsetCache.clear();
40371
+ this.fixedGridOrigin = null;
40372
+ }
40373
+ clearGrid() {
40374
+ if (this.gridPrimitives) {
40375
+ this.gridPrimitives.removeAll();
40376
+ this.gridLines = null;
40377
+ this.gridLabels = null;
40378
+ }
40379
+ }
40380
+ calculateAdaptiveSpacing(cameraHeight, baseSpacing) {
40381
+ // Define spacing levels that look good (powers of 2, 5, and 10)
40382
+ const spacingLevels = this.SPACING_LEVELS;
40383
+ // Ensure base spacing is at least 1m
40384
+ const adjustedBaseSpacing = Math.max(this.BASE_SPACING, baseSpacing);
40385
+ // For very close cameras, use much smaller spacing regardless of base
40386
+ if (cameraHeight < 10) {
40387
+ // Find the smallest reasonable spacing for this height
40388
+ // Rule: spacing should be roughly 1/20th to 1/50th of camera height, but at least 1m
40389
+ const targetSpacing = Math.max(this.BASE_SPACING, cameraHeight / 30);
40390
+ // Find the closest spacing level to our target
40391
+ let bestSpacing = this.BASE_SPACING; // Start with minimum
40392
+ let bestDifference = Math.abs(1 - targetSpacing);
40393
+ for (const spacing of spacingLevels) {
40394
+ if (spacing >= this.BASE_SPACING) { // Only consider valid spacings
40395
+ const difference = Math.abs(spacing - targetSpacing);
40396
+ if (difference < bestDifference) {
40397
+ bestSpacing = spacing;
40398
+ bestDifference = difference;
40399
+ }
40400
+ }
40401
+ }
40402
+ return bestSpacing;
40403
+ }
40404
+ // For medium distances (10m - 200m), scale more gradually
40405
+ if (cameraHeight < 200) {
40406
+ const targetSpacing = cameraHeight / 10; // More conservative scaling// Find closest spacing level, but prefer going up from base spacing
40407
+ let bestSpacing = adjustedBaseSpacing;
40408
+ let bestDifference = Math.abs(adjustedBaseSpacing - targetSpacing);
40409
+ for (const spacing of spacingLevels) {
40410
+ if (spacing >= Math.min(adjustedBaseSpacing, targetSpacing)) {
40411
+ const difference = Math.abs(spacing - targetSpacing);
40412
+ if (difference < bestDifference) {
40413
+ bestSpacing = spacing;
40414
+ bestDifference = difference;
40415
+ }
40416
+ }
40417
+ }
40418
+ return bestSpacing;
40419
+ }
40420
+ // For far distances (100m+), scale up aggressively
40421
+ const targetSpacing = cameraHeight / 20;
40422
+ // Find the spacing level that's closest to target but not smaller than base
40423
+ let bestSpacing = adjustedBaseSpacing;
40424
+ let bestDifference = Math.abs(adjustedBaseSpacing - targetSpacing);
40425
+ for (const spacing of spacingLevels) {
40426
+ if (spacing >= adjustedBaseSpacing) {
40427
+ const difference = Math.abs(spacing - targetSpacing);
40428
+ if (difference < bestDifference) {
40429
+ bestSpacing = spacing;
40430
+ bestDifference = difference;
40431
+ }
40432
+ }
40433
+ }
40434
+ return bestSpacing;
40435
+ }
40436
+ /*
40437
+ * The point model-space treats as 0,0,0: the UCS in UCS placement, otherwise 0,0 lat/lon at sea level.
40438
+ */
40439
+ modelSpaceOrigin() {
40440
+ const origin = ViewUtils.IsModelSpaceUcs(this.viewer) ? ViewUtils.GetModelSpaceOrigin(this.viewer) : null;
40441
+ return origin !== null && origin !== void 0 ? origin : { latitude: 0, longitude: 0, altitude: 0 };
40442
+ }
40443
+ /*
40444
+ * The origin as a scene position, where the axis gizmo sits.
40445
+ */
40446
+ modelSpaceOriginPos3d() {
40447
+ const origin = this.modelSpaceOrigin();
40448
+ return Cartesian3.fromDegrees(origin.longitude, origin.latitude, origin.altitude);
40449
+ }
40450
+ drawGizmoPieces() {
40451
+ if (this.gridCenterGizmoPieces.length) {
40452
+ return;
40453
+ }
40454
+ // X.
40455
+ {
40456
+ const pos3d = this.modelSpaceOriginPos3d();
40457
+ const scale = new CallbackProperty(() => Cartesian3.distance(this.viewer.camera.position, pos3d) * 0.0002, false);
40458
+ const orientation = Transforms.headingPitchRollQuaternion(pos3d, new HeadingPitchRoll(Math$1.toRadians(90), 0, 0));
40459
+ const entity = this.viewer.entities.add(new Entity({
40460
+ model: {
40461
+ uri: GltfGizmo.GetUri(GltfGizmo.EPiece.X),
40462
+ heightReference: HeightReference.NONE,
40463
+ shadows: ShadowMode.DISABLED,
40464
+ distanceDisplayCondition: new DistanceDisplayCondition(0, Number.MAX_VALUE),
40465
+ scale
40466
+ },
40467
+ position: pos3d,
40468
+ orientation: new ConstantProperty(orientation)
40469
+ }));
40470
+ this.gridCenterGizmoPieces.push(entity);
40471
+ }
40472
+ // Y.
40473
+ {
40474
+ const pos3d = this.modelSpaceOriginPos3d();
40475
+ const scale = new CallbackProperty(() => Cartesian3.distance(this.viewer.camera.position, pos3d) * 0.0002, false);
40476
+ const orientation = Transforms.headingPitchRollQuaternion(pos3d, new HeadingPitchRoll(Math$1.toRadians(90), 0, 0));
40477
+ const entity = this.viewer.entities.add(new Entity({
40478
+ model: {
40479
+ uri: GltfGizmo.GetUri(GltfGizmo.EPiece.Y),
40480
+ heightReference: HeightReference.NONE,
40481
+ shadows: ShadowMode.DISABLED,
40482
+ distanceDisplayCondition: new DistanceDisplayCondition(0, Number.MAX_VALUE),
40483
+ scale
40484
+ },
40485
+ position: pos3d,
40486
+ orientation: new ConstantProperty(orientation)
40487
+ }));
40488
+ this.gridCenterGizmoPieces.push(entity);
40489
+ }
40490
+ // Z.
40491
+ {
40492
+ const pos3d = this.modelSpaceOriginPos3d();
40493
+ const scale = new CallbackProperty(() => Cartesian3.distance(this.viewer.camera.position, pos3d) * 0.0002, false);
40494
+ const orientation = Transforms.headingPitchRollQuaternion(pos3d, new HeadingPitchRoll(Math$1.toRadians(90), 0, 0));
40495
+ const entity = this.viewer.entities.add(new Entity({
40496
+ model: {
40497
+ uri: GltfGizmo.GetUri(GltfGizmo.EPiece.Z),
40498
+ heightReference: HeightReference.NONE,
40499
+ shadows: ShadowMode.DISABLED,
40500
+ distanceDisplayCondition: new DistanceDisplayCondition(0, Number.MAX_VALUE),
40501
+ scale
40502
+ },
40503
+ position: pos3d,
40504
+ orientation: new ConstantProperty(orientation)
40505
+ }));
40506
+ this.gridCenterGizmoPieces.push(entity);
40507
+ }
40508
+ }
40509
+ clearGizmoPieces() {
40510
+ for (const piece of this.gridCenterGizmoPieces) {
40511
+ if (piece && this.viewer.entities.contains(piece)) {
40512
+ this.viewer.entities.remove(piece);
40513
+ }
40514
+ }
40515
+ this.gridCenterGizmoPieces = [];
40516
+ }
40517
+ onCameraChanged() {
40518
+ if (this.isUpdating) {
40519
+ return;
40520
+ }
40521
+ else if (this.currentMarker !== this.viewer[UPDATE_MARKER_KEY]) {
40522
+ return;
40523
+ }
40524
+ this.isUpdating = true;
40525
+ // Small delay to avoid excessive updates during camera movement.
40526
+ setTimeout(() => {
40527
+ if (this.currentMarker !== this.viewer[UPDATE_MARKER_KEY]) {
40528
+ this.isUpdating = false;
40529
+ return;
40530
+ }
40531
+ try {
40532
+ // The grid goes first, since it notices when the origin has moved and the gizmo has to follow.
40533
+ this.updateGrid();
40534
+ if (this.modelSpace) {
40535
+ this.drawGizmoPieces();
40536
+ }
40537
+ else {
40538
+ this.clearGizmoPieces();
40539
+ }
40540
+ }
40541
+ catch (e) {
40542
+ console.error(e);
40543
+ }
40544
+ finally {
40545
+ this.isUpdating = false;
40546
+ }
40547
+ }, 100);
40548
+ }
40549
+ cachedOffsetPoint(point, distance, heading) {
40550
+ const key = `${point.latitude.toFixed(8)},${point.longitude.toFixed(8)},${distance.toFixed(1)},${heading}`;
40551
+ if (this.offsetCache.has(key)) {
40552
+ return this.offsetCache.get(key);
40553
+ }
40554
+ const result = offsetPoint$1(point, distance, heading);
40555
+ this.offsetCache.set(key, result);
40556
+ if (this.offsetCache.size > this.maxCacheSize) {
40557
+ const keys = Array.from(this.offsetCache.keys());
40558
+ for (let i = 0; i < 100; i++) {
40559
+ this.offsetCache.delete(keys[i]);
40560
+ }
40561
+ }
40562
+ return result;
40563
+ }
40564
+ /**
40565
+ * Calculates a dynamic visibility ratio based on spacing size.
40566
+ * For small grid spacings, we can show the grid from farther away.
40567
+ * For large grid spacings, we need to hide it sooner to prevent performance issues.
40568
+ */
40569
+ getMaxVisibilityRatio() {
40570
+ if (this.spacingInMeters <= 10) {
40571
+ return this.BASE_MAX_VISIBILITY_RATIO;
40572
+ }
40573
+ if (this.spacingInMeters <= 100) {
40574
+ const factor = 1 - ((this.spacingInMeters - 10) / 90);
40575
+ return this.BASE_MAX_VISIBILITY_RATIO * (0.5 + (factor * 0.5));
40576
+ }
40577
+ const largeGridFactor = Math.min(1, 100 / this.spacingInMeters);
40578
+ return this.BASE_MAX_VISIBILITY_RATIO * 0.5 * largeGridFactor;
40579
+ }
40580
+ updateGrid() {
40581
+ const cameraPosition = this.viewer.camera.position;
40582
+ const cameraCartographic = Cartographic.fromCartesian(cameraPosition);
40583
+ const cameraHeight = cameraCartographic.height;
40584
+ let adaptiveSpacing = this.calculateAdaptiveSpacing(cameraHeight, this.spacingInMeters);
40585
+ // Calculate visible area.
40586
+ const visibleArea = this.calculateVisibleArea();
40587
+ if (!visibleArea) {
40588
+ return;
40589
+ }
40590
+ let densityCheckPassed = false;
40591
+ let attempts = 0;
40592
+ while (!densityCheckPassed && attempts < this.MAX_ADAPTIVE_ATTEMPTS) {
40593
+ if (this.isGridDensityAcceptable(visibleArea, cameraHeight, adaptiveSpacing)) {
40594
+ densityCheckPassed = true;
40595
+ }
40596
+ else {
40597
+ adaptiveSpacing *= 2;
40598
+ attempts++;
40599
+ }
40600
+ }
40601
+ // The origin follows the UCS of what is shown, so the grid moves with it when that changes.
40602
+ if (this.modelSpace && this.fixedGridOrigin && !Carto.IsEqual(this.fixedGridOrigin, this.modelSpaceOrigin())) {
40603
+ this.fixedGridOrigin = null;
40604
+ this.lastCameraPosition = null;
40605
+ this.clearGizmoPieces();
40606
+ }
40607
+ // Calculate grid origin if we don't have one yet.
40608
+ if (!this.fixedGridOrigin) {
40609
+ if (this.modelSpace) {
40610
+ this.fixedGridOrigin = this.modelSpaceOrigin();
40611
+ }
40612
+ else {
40613
+ const centerCartographic = Cartographic.fromCartesian(visibleArea.centerCartesian);
40614
+ const centerPoint = {
40615
+ latitude: Math$1.toDegrees(centerCartographic.latitude),
40616
+ longitude: Math$1.toDegrees(centerCartographic.longitude),
40617
+ altitude: 0
40618
+ };
40619
+ // Simple attempt at snapping to the grid, should do something smarter.
40620
+ this.fixedGridOrigin = {
40621
+ latitude: Math.round(centerPoint.latitude * 100000) / 100000,
40622
+ longitude: Math.round(centerPoint.longitude * 100000) / 100000,
40623
+ altitude: 0
40624
+ };
40625
+ }
40626
+ }
40627
+ // Calculate appropriate grid extent based on camera height.
40628
+ // For larger grid spacings, we use a tighter extent to prevent performance issues.
40629
+ const scaledExtentFactor = Math.max(0.5, Math.min(3, 3 - Math.log10(Math.max(1, adaptiveSpacing)) * 0.5));
40630
+ const visibilityRadius = Math.min(cameraHeight * scaledExtentFactor, Infinity);
40631
+ // Check if we should redraw the grid.
40632
+ const shouldRedraw = this.shouldRedrawGrid(cameraPosition, cameraHeight, visibilityRadius, adaptiveSpacing);
40633
+ if (!shouldRedraw) {
40634
+ return;
40635
+ }
40636
+ // Remember current settings for next comparison.
40637
+ this.lastCameraHeight = cameraHeight;
40638
+ this.lastCameraPosition = cameraPosition.clone();
40639
+ this.lastGridExtent = visibilityRadius;
40640
+ // Clear existing grid and create a new one.
40641
+ this.clearGrid();
40642
+ this.gridLines = new PolylineCollection();
40643
+ this.gridLabels = new LabelCollection({
40644
+ scene: this.viewer.scene
40645
+ });
40646
+ this.gridPrimitives.add(this.gridLines);
40647
+ this.gridPrimitives.add(this.gridLabels);
40648
+ // Generate grid lines and labels using our fixed origin.
40649
+ this.generateGridLines(this.fixedGridOrigin, visibilityRadius, adaptiveSpacing);
40650
+ }
40651
+ shouldRedrawGrid(cameraPosition, cameraHeight, gridExtent, currentSpacing) {
40652
+ // check if the spacing has changed
40653
+ if (this.lastUsedSpacing && this.lastUsedSpacing !== currentSpacing) {
40654
+ return true;
40655
+ }
40656
+ // Always draw if we don't have a grid yet.
40657
+ if (!this.gridLines || !this.lastCameraPosition || !this.fixedGridOrigin) {
40658
+ return true;
40659
+ }
40660
+ // Check for significant height change.
40661
+ const heightRatio = cameraHeight / this.lastCameraHeight;
40662
+ if (heightRatio > (1 + this.HEIGHT_CHANGE_THRESHOLD) ||
40663
+ heightRatio < (1 - this.HEIGHT_CHANGE_THRESHOLD)) {
40664
+ return true;
40665
+ }
40666
+ // Check for significant position change relative to current grid extent.
40667
+ const distance = Cartesian3.distance(cameraPosition, this.lastCameraPosition);
40668
+ const moveThreshold = gridExtent * this.CAMERA_MOVE_THRESHOLD;
40669
+ if (distance > moveThreshold) {
40670
+ return true;
40671
+ }
40672
+ // Check for significant grid extent change.
40673
+ const extentRatio = gridExtent / this.lastGridExtent;
40674
+ if (extentRatio > (1 + this.GRID_EXTENT_CHANGE_THRESHOLD) ||
40675
+ extentRatio < (1 - this.GRID_EXTENT_CHANGE_THRESHOLD)) {
40676
+ return true;
40677
+ }
40678
+ // No significant changes, keep current grid.
40679
+ return false;
40680
+ }
40681
+ isGridDensityAcceptable(visibleArea, cameraHeight, spacingToCheck = this.spacingInMeters) {
40682
+ const lonRange = visibleArea.eastLon - visibleArea.westLon;
40683
+ const latRange = visibleArea.northLat - visibleArea.southLat;
40684
+ // Convert to approximate meters at center
40685
+ const centerCartographic = Cartographic.fromCartesian(visibleArea.centerCartesian);
40686
+ const metersPerDegreeLon = 111319.9 * Math.cos(centerCartographic.latitude);
40687
+ const canvas = this.viewer.scene.canvas;
40688
+ const spacingInDegrees = spacingToCheck / metersPerDegreeLon;
40689
+ const canvasWidth = canvas.clientWidth;
40690
+ const pixelsPerDegree = canvasWidth / lonRange;
40691
+ const pixelsBetweenGridLines = pixelsPerDegree * spacingInDegrees;
40692
+ const minPixelSize = 1;
40693
+ return pixelsBetweenGridLines >= minPixelSize;
40694
+ }
40695
+ calculateVisibleArea() {
40696
+ const scene = this.viewer.scene;
40697
+ const camera = scene.camera;
40698
+ const globe = scene.globe;
40699
+ // Get the corners of the view frustum
40700
+ const canvas = scene.canvas;
40701
+ const corners = [
40702
+ new Cartesian2(0, 0),
40703
+ new Cartesian2(canvas.clientWidth, 0),
40704
+ new Cartesian2(0, canvas.clientHeight),
40705
+ new Cartesian2(canvas.clientWidth, canvas.clientHeight),
40706
+ new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2) // Center
40707
+ ];
40708
+ // Project corners to the globe
40709
+ const cartographicCorners = [];
40710
+ for (const corner of corners) {
40711
+ const ray = camera.getPickRay(corner);
40712
+ if (!ray) {
40713
+ continue;
40714
+ }
40715
+ const position = globe.pick(ray, scene);
40716
+ if (!position) {
40717
+ continue;
40718
+ }
40719
+ cartographicCorners.push(Cartographic.fromCartesian(position));
40720
+ }
40721
+ if (cartographicCorners.length === 0) {
40722
+ return null;
40723
+ }
40724
+ // Find the bounding rectangle
40725
+ let westLon = Number.POSITIVE_INFINITY;
40726
+ let eastLon = Number.NEGATIVE_INFINITY;
40727
+ let southLat = Number.POSITIVE_INFINITY;
40728
+ let northLat = Number.NEGATIVE_INFINITY;
40729
+ for (const corner of cartographicCorners) {
40730
+ westLon = Math.min(westLon, corner.longitude);
40731
+ eastLon = Math.max(eastLon, corner.longitude);
40732
+ southLat = Math.min(southLat, corner.latitude);
40733
+ northLat = Math.max(northLat, corner.latitude);
40734
+ }
40735
+ // Add some padding around the visible area
40736
+ const lonPadding = (eastLon - westLon) * this.VIEW_PADDING_FACTOR;
40737
+ const latPadding = (northLat - southLat) * this.VIEW_PADDING_FACTOR;
40738
+ westLon -= lonPadding;
40739
+ eastLon += lonPadding;
40740
+ southLat -= latPadding;
40741
+ northLat += latPadding;
40742
+ // Get center position for reference
40743
+ const centerCartographic = new Cartographic((westLon + eastLon) / 2, (southLat + northLat) / 2);
40744
+ const centerCartesian = Cartesian3.fromRadians(centerCartographic.longitude, centerCartographic.latitude, 0);
40745
+ return {
40746
+ westLon: Math$1.toDegrees(westLon),
40747
+ eastLon: Math$1.toDegrees(eastLon),
40748
+ southLat: Math$1.toDegrees(southLat),
40749
+ northLat: Math$1.toDegrees(northLat),
40750
+ centerCartesian: centerCartesian
40751
+ };
40752
+ }
40753
+ generateGridLines(originPoint, visibilityRadius, actualSpacing = this.spacingInMeters) {
40754
+ // Number of grid cells in each direction (add 1 for edge lines).
40755
+ const gridCellCount = Math.ceil(visibilityRadius / actualSpacing) + 1;
40756
+ const maxCells = Math.min(gridCellCount, Math.floor(this.MAX_LINES / 2));
40757
+ // Calculate interval to skip lines for density control.
40758
+ const spacingFactor = actualSpacing / 100;
40759
+ const heightFactor = this.lastCameraHeight / 500;
40760
+ let lineSkipInterval = Math.floor(spacingFactor * heightFactor);
40761
+ lineSkipInterval = Math.min(Math.max(lineSkipInterval, 1), 10); // Clamp between 1 and 10
40762
+ const intersections = [];
40763
+ // Generate north-south grid lines (longitude lines)
40764
+ for (let i = -maxCells; i <= maxCells; i++) {
40765
+ if (i % lineSkipInterval !== 0) {
40766
+ continue;
40767
+ }
40768
+ const positions = [];
40769
+ // Create a vertical line at this longitude offset.
40770
+ // East heading for longitude.
40771
+ const startPoint = this.cachedOffsetPoint(originPoint, i * actualSpacing, 90);
40772
+ // Draw the line from south to north
40773
+ for (let j = -maxCells; j <= maxCells; j++) {
40774
+ if (j % lineSkipInterval !== 0) {
40775
+ continue;
40776
+ }
40777
+ // North heading for latitude.
40778
+ const linePoint = this.cachedOffsetPoint(startPoint, j * actualSpacing, 0);
40779
+ const position = Cartesian3.fromDegrees(linePoint.longitude, linePoint.latitude, linePoint.altitude);
40780
+ positions.push(position);
40781
+ intersections.push({
40782
+ position: position,
40783
+ i: i,
40784
+ j: j
40785
+ });
40786
+ }
40787
+ if (positions.length >= 2) {
40788
+ // Check if this is the primary Y axis (i=0) in model space
40789
+ const isPrimaryYAxis = this.modelSpace && i === 0;
40790
+ this.addGridLine(positions, isPrimaryYAxis ? "y" : null);
40791
+ }
40792
+ }
40793
+ // Generate east-west grid lines (latitude lines).
40794
+ for (let j = -maxCells; j <= maxCells; j++) {
40795
+ if (j % lineSkipInterval !== 0) {
40796
+ continue;
40797
+ }
40798
+ const positions = [];
40799
+ // Create a horizontal line at this latitude offset
40800
+ // North heading for latitude.
40801
+ const startPoint = this.cachedOffsetPoint(originPoint, j * actualSpacing, 0);
40802
+ // Draw the line from west to east.
40803
+ for (let i = -maxCells; i <= maxCells; i++) {
40804
+ if (i % lineSkipInterval !== 0) {
40805
+ continue;
40806
+ }
40807
+ // East heading for longitude.
40808
+ const linePoint = this.cachedOffsetPoint(startPoint, i * actualSpacing, 90);
40809
+ const position = Cartesian3.fromDegrees(linePoint.longitude, linePoint.latitude, linePoint.altitude);
40810
+ positions.push(position);
40811
+ }
40812
+ if (positions.length >= 2) {
40813
+ // Check if this is the primary X axis (j=0) in model space
40814
+ const isPrimaryXAxis = this.modelSpace && j === 0;
40815
+ this.addGridLine(positions, isPrimaryXAxis ? "x" : null);
40816
+ }
40817
+ }
40818
+ this.addGridLabels(intersections, actualSpacing);
40819
+ }
40820
+ addGridLabels(intersections, actualSpacing) {
40821
+ if (!this.gridLabels) {
40822
+ return;
40823
+ }
40824
+ // Determine label interval based on number of intersections.
40825
+ // Use higher interval for more intersections to prevent crowding.
40826
+ const totalIntersections = intersections.length;
40827
+ let labelInterval = this.LABEL_INTERVAL;
40828
+ if (totalIntersections > 1000) {
40829
+ labelInterval = 5;
40830
+ }
40831
+ else if (totalIntersections > 500) {
40832
+ labelInterval = 3;
40833
+ }
40834
+ let labelCount = 0;
40835
+ // Filter intersections to only those at the labelInterval.
40836
+ const labelPoints = intersections.filter(point => {
40837
+ // Always label the origin (0,0).
40838
+ if (point.i === 0 && point.j === 0) {
40839
+ return true;
40840
+ }
40841
+ return (point.i % labelInterval === 0 && point.j % labelInterval === 0);
40842
+ });
40843
+ // Sort points by distance from origin.
40844
+ labelPoints.sort((a, b) => {
40845
+ const distA = Math.abs(a.i) + Math.abs(a.j);
40846
+ const distB = Math.abs(b.i) + Math.abs(b.j);
40847
+ return distA - distB;
40848
+ });
40849
+ // Add labels, prioritizing those closest to the origin.
40850
+ for (const point of labelPoints) {
40851
+ if (labelCount >= this.MAX_LABELS) {
40852
+ // Limit reached.
40853
+ break;
40854
+ }
40855
+ let labelText;
40856
+ // Model space coordinates (meters from origin).
40857
+ if (this.modelSpace) {
40858
+ const xMeters = point.i * actualSpacing;
40859
+ const yMeters = point.j * actualSpacing;
40860
+ if (point.i === 0 && point.j === 0) {
40861
+ labelText = "0,0,0";
40862
+ }
40863
+ else {
40864
+ labelText = `${xMeters},${yMeters}`;
40865
+ }
40866
+ }
40867
+ // Geographic coordinates (degrees).
40868
+ else {
40869
+ const cartographic = Cartographic.fromCartesian(point.position);
40870
+ const latitude = Math$1.toDegrees(cartographic.latitude);
40871
+ const longitude = Math$1.toDegrees(cartographic.longitude);
40872
+ const latFormatted = latitude.toFixed(5);
40873
+ const lonFormatted = longitude.toFixed(5);
40874
+ labelText = `${latFormatted}, ${lonFormatted}`;
40875
+ }
40876
+ this.gridLabels.add({
40877
+ position: point.position,
40878
+ text: labelText,
40879
+ font: this.modelSpace && point.i === 0 && point.j === 0 ? "20px sans-serif" : "14px sans-serif",
40880
+ fillColor: this.modelSpace && point.i === 0 && point.j === 0 ? Color.WHITE : Color.WHITE.withAlpha(0.5),
40881
+ style: LabelStyle.FILL,
40882
+ horizontalOrigin: HorizontalOrigin.CENTER,
40883
+ verticalOrigin: VerticalOrigin.TOP,
40884
+ pixelOffset: new Cartesian2(0, 10),
40885
+ // Fade out with distance.
40886
+ translucencyByDistance: new NearFarScalar(1000, 1.0, 15000, 0.4)
40887
+ });
40888
+ labelCount++;
40889
+ }
40890
+ }
40891
+ addGridLine(positions, primaryAxis = null) {
40892
+ if (!this.gridLines || positions.length < 2) {
40893
+ return;
40894
+ }
40895
+ let lineColor;
40896
+ let lineWidth = 1.0;
40897
+ let lineAlpha = 0.2;
40898
+ // Red for X axis.
40899
+ if (primaryAxis === "x") {
40900
+ lineColor = Color.RED;
40901
+ lineAlpha = 0.5;
40902
+ lineWidth = 1.5;
40903
+ }
40904
+ // Green for Y axis.
40905
+ else if (primaryAxis === "y") {
40906
+ lineColor = Color.GREEN;
40907
+ lineAlpha = 0.5;
40908
+ lineWidth = 1.5;
40909
+ }
40910
+ // Default gray for regular grid lines.
40911
+ else {
40912
+ lineColor = Color.LIGHTGRAY;
40913
+ }
40914
+ this.gridLines.add({
40915
+ positions: positions,
40916
+ width: lineWidth,
40917
+ material: Material.fromType("Color", {
40918
+ color: lineColor.withAlpha(lineAlpha)
40919
+ })
40920
+ });
40921
+ }
40922
+ }
40923
+ // Grid square size used when settings name none, in metres.
40924
+ const DEFAULT_SPACING = 50;
40925
+ var CesiumMapGrid;
40926
+ (function (CesiumMapGrid) {
40927
+ function GetCurrentSpacing(viewer) {
40928
+ if (!viewer || viewer.isDestroyed()) {
40929
+ return 0;
40930
+ }
40931
+ const handler = viewer[CLASS_INSTANCE_KEY];
40932
+ return handler ? handler.getCurrentSpacing() : 0;
40933
+ }
40934
+ CesiumMapGrid.GetCurrentSpacing = GetCurrentSpacing;
40935
+ function Remove(viewer) {
40936
+ if (!viewer || viewer.isDestroyed()) {
40937
+ return;
40938
+ }
40939
+ else if (!viewer[LAST_SETTINGS_KEY]) {
40940
+ return;
40941
+ }
40942
+ if (!viewer[UPDATE_MARKER_KEY]) {
40943
+ viewer[UPDATE_MARKER_KEY] = 1;
40944
+ }
40945
+ else {
40946
+ viewer[UPDATE_MARKER_KEY]++;
40947
+ }
40948
+ // Dispose of the handler.
40949
+ if (viewer[CLASS_INSTANCE_KEY]) {
40950
+ viewer[CLASS_INSTANCE_KEY].Dispose();
40951
+ viewer[CLASS_INSTANCE_KEY] = null;
40952
+ }
40953
+ viewer[LAST_SETTINGS_KEY] = {
40954
+ ...viewer[LAST_SETTINGS_KEY],
40955
+ enabled: false
40956
+ };
40957
+ }
40958
+ CesiumMapGrid.Remove = Remove;
40959
+ function Draw(viewer, spacingInMeters, modelSpace = true) {
40960
+ if (!viewer || viewer.isDestroyed()) {
40961
+ return;
40962
+ }
40963
+ let settings = viewer[LAST_SETTINGS_KEY];
40964
+ if (settings) {
40965
+ // See if the settings are the same as the last time.
40966
+ if (settings.spacing === spacingInMeters &&
40967
+ settings.modelSpace === modelSpace &&
40968
+ settings.enabled) {
40969
+ return;
40970
+ }
40971
+ }
40972
+ if (!viewer[UPDATE_MARKER_KEY]) {
40973
+ viewer[UPDATE_MARKER_KEY] = 1;
40974
+ }
40975
+ else {
40976
+ viewer[UPDATE_MARKER_KEY]++;
40977
+ }
40978
+ settings = {
40979
+ ...settings,
40980
+ enabled: true,
40981
+ spacing: spacingInMeters,
40982
+ modelSpace: modelSpace
40983
+ };
40984
+ viewer[LAST_SETTINGS_KEY] = settings;
40985
+ // Create or update the handler.
40986
+ if (viewer[CLASS_INSTANCE_KEY]) {
40987
+ viewer[CLASS_INSTANCE_KEY].Update(spacingInMeters, modelSpace);
40988
+ }
40989
+ else {
40990
+ viewer[CLASS_INSTANCE_KEY] = new MapGridHandler(viewer, spacingInMeters, modelSpace);
40991
+ }
40992
+ }
40993
+ CesiumMapGrid.Draw = Draw;
40994
+ /**
40995
+ * Draws a bookmark's grid, or removes the grid when it has none; only model-space draws one.
40996
+ */
40997
+ function Apply(viewer, grid, modelSpace) {
40998
+ if ((grid === null || grid === void 0 ? void 0 : grid.enabled) && modelSpace) {
40999
+ Draw(viewer, Number(grid.spacing) || DEFAULT_SPACING, modelSpace);
41000
+ }
41001
+ else {
41002
+ Remove(viewer);
41003
+ }
41004
+ }
41005
+ CesiumMapGrid.Apply = Apply;
41006
+ function GetIsEnabled(viewer) {
41007
+ if (!viewer || viewer.isDestroyed()) {
41008
+ return false;
41009
+ }
41010
+ const settings = viewer[LAST_SETTINGS_KEY];
41011
+ if (settings) {
41012
+ return Boolean(settings.enabled);
41013
+ }
41014
+ return false;
41015
+ }
41016
+ CesiumMapGrid.GetIsEnabled = GetIsEnabled;
41017
+ /**
41018
+ * Returns the last applied grid settings.
41019
+ */
41020
+ function GetSettings(viewer) {
41021
+ if (!viewer || viewer.isDestroyed()) {
41022
+ return null;
41023
+ }
41024
+ return viewer[LAST_SETTINGS_KEY];
41025
+ }
41026
+ CesiumMapGrid.GetSettings = GetSettings;
41027
+ })(CesiumMapGrid || (CesiumMapGrid = {}));
41028
+
38818
41029
  // TODO: Move these to the data model.
38819
41030
  const MIN_SHADOW_SIZE = 2048;
38820
41031
  const MAX_SHADOW_SIZE = 32768;
@@ -39006,7 +41217,7 @@ async function renderLegacyNavigator(iteration, params, bookmark, view) {
39006
41217
  * @param view
39007
41218
  */
39008
41219
  async function renderNavigator(iteration, params, bookmark, view, getters) {
39009
- 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, _6, _7, _8;
41220
+ 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, _6, _7, _8, _9;
39010
41221
  const viewer = params.manager.Viewer;
39011
41222
  const scene = viewer.scene;
39012
41223
  // Ion requests go through the server-side proxy (set up during viewer init or here as fallback).
@@ -39231,15 +41442,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39231
41442
  scene.globe.translucency.frontFaceAlphaByDistance.farValue = 1;
39232
41443
  }
39233
41444
  scene.globe.translucency.frontFaceAlphaByDistance.nearValue = EnsureNumber(globeAlpha, 1);
39234
- let shadows = bSettings === null || bSettings === void 0 ? void 0 : bSettings.shadows;
39235
- if (shadows == null) {
39236
- shadows = (_u = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _u === void 0 ? void 0 : _u.shadows;
39237
- }
39238
- if (shadows == null) {
39239
- shadows = {
39240
- enabled: false
39241
- };
39242
- }
41445
+ const shadows = ViewRenderEngine.ResolveShadows(bSettings, defaults === null || defaults === void 0 ? void 0 : defaults.settings, contentType);
39243
41446
  viewer.shadows = Boolean(shadows.enabled);
39244
41447
  viewer.shadowMap.enabled = Boolean(shadows.enabled);
39245
41448
  let size = EnsureNumber(shadows.pixelSize);
@@ -39252,9 +41455,11 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39252
41455
  viewer.shadowMap.size = size;
39253
41456
  viewer.shadowMap.softShadows = Boolean(shadows.soften);
39254
41457
  viewer.shadowMap.darkness = EnsureNumber(shadows.darkness, 0.3);
41458
+ // Drawn after the model-space origin is resolved above, since the grid starts from it.
41459
+ CesiumMapGrid.Apply(viewer, (_u = bSettings.grid) !== null && _u !== void 0 ? _u : (_v = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _v === void 0 ? void 0 : _v.grid, contentType == ProjectViewBookmark.EContentType.WEB_3D_MODEL_SPACE);
39255
41460
  let ambientOcclusion = bSettings === null || bSettings === void 0 ? void 0 : bSettings.ambientOcclusion;
39256
41461
  if (ambientOcclusion == null) {
39257
- ambientOcclusion = (_v = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _v === void 0 ? void 0 : _v.ambientOcclusion;
41462
+ ambientOcclusion = (_w = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _w === void 0 ? void 0 : _w.ambientOcclusion;
39258
41463
  }
39259
41464
  if (ambientOcclusion == null) {
39260
41465
  ambientOcclusion = {
@@ -39277,7 +41482,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39277
41482
  }
39278
41483
  let lighting = bSettings === null || bSettings === void 0 ? void 0 : bSettings.lighting;
39279
41484
  if (lighting == null) {
39280
- lighting = (_w = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _w === void 0 ? void 0 : _w.lighting;
41485
+ lighting = (_x = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _x === void 0 ? void 0 : _x.lighting;
39281
41486
  }
39282
41487
  if (lighting == null) {
39283
41488
  lighting = {
@@ -39295,7 +41500,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39295
41500
  ViewUtils.ApplyDefaultBrightness(viewer, EnsureNumber(lighting.brightnessShift, DEFAULT_BRIGHTNESS_SHIFT));
39296
41501
  let quality = bSettings === null || bSettings === void 0 ? void 0 : bSettings.quality;
39297
41502
  if (quality == null) {
39298
- quality = (_x = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _x === void 0 ? void 0 : _x.quality;
41503
+ quality = (_y = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _y === void 0 ? void 0 : _y.quality;
39299
41504
  }
39300
41505
  if (quality == null) {
39301
41506
  quality = {
@@ -39306,11 +41511,11 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39306
41511
  fxaa.enabled = Boolean(quality.fxaa);
39307
41512
  let dateTime = bSettings === null || bSettings === void 0 ? void 0 : bSettings.dateTime;
39308
41513
  if (dateTime == null) {
39309
- dateTime = (_y = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _y === void 0 ? void 0 : _y.dateTime;
41514
+ dateTime = (_z = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _z === void 0 ? void 0 : _z.dateTime;
39310
41515
  }
39311
41516
  let timeline = bSettings === null || bSettings === void 0 ? void 0 : bSettings.timeline;
39312
41517
  if (timeline == null) {
39313
- timeline = (_z = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _z === void 0 ? void 0 : _z.timeline;
41518
+ timeline = (_0 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _0 === void 0 ? void 0 : _0.timeline;
39314
41519
  }
39315
41520
  if (timeline == null) {
39316
41521
  timeline = {
@@ -39334,7 +41539,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39334
41539
  // Newer version that has states per Entity ID + Menu Item ID.
39335
41540
  let states = bSettings === null || bSettings === void 0 ? void 0 : bSettings.states;
39336
41541
  if (states == null) {
39337
- states = (_0 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _0 === void 0 ? void 0 : _0.states;
41542
+ states = (_1 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _1 === void 0 ? void 0 : _1.states;
39338
41543
  }
39339
41544
  if (states != null) {
39340
41545
  params.manager.VisualsRegister.OverrideStates(states);
@@ -39343,7 +41548,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39343
41548
  else {
39344
41549
  let selectedIds = bSettings === null || bSettings === void 0 ? void 0 : bSettings.selectedEntityIds;
39345
41550
  if (selectedIds == null) {
39346
- selectedIds = (_1 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _1 === void 0 ? void 0 : _1.selectedEntityIds;
41551
+ selectedIds = (_2 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _2 === void 0 ? void 0 : _2.selectedEntityIds;
39347
41552
  }
39348
41553
  if (selectedIds != null) {
39349
41554
  params.manager.VisualsRegister.ClearSelected();
@@ -39357,7 +41562,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39357
41562
  params.manager.VisualsRegister.ClearHidden();
39358
41563
  let hiddenIds = bSettings === null || bSettings === void 0 ? void 0 : bSettings.hiddenEntityIds;
39359
41564
  if (hiddenIds == null) {
39360
- hiddenIds = (_2 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _2 === void 0 ? void 0 : _2.hiddenEntityIds;
41565
+ hiddenIds = (_3 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _3 === void 0 ? void 0 : _3.hiddenEntityIds;
39361
41566
  }
39362
41567
  if (hiddenIds != null) {
39363
41568
  params.manager.VisualsRegister.SetHidden({
@@ -39369,7 +41574,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39369
41574
  params.manager.VisualsRegister.ClearIsolated();
39370
41575
  let isolatedIds = bSettings === null || bSettings === void 0 ? void 0 : bSettings.isolatedEntityIds;
39371
41576
  if (isolatedIds == null) {
39372
- isolatedIds = (_3 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _3 === void 0 ? void 0 : _3.isolatedEntityIds;
41577
+ isolatedIds = (_4 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _4 === void 0 ? void 0 : _4.isolatedEntityIds;
39373
41578
  }
39374
41579
  if (isolatedIds != null) {
39375
41580
  params.manager.VisualsRegister.SetIsolated({
@@ -39380,7 +41585,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39380
41585
  }
39381
41586
  let labelledIds = bSettings === null || bSettings === void 0 ? void 0 : bSettings.labelledEntityIds;
39382
41587
  if (labelledIds == null) {
39383
- labelledIds = (_4 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _4 === void 0 ? void 0 : _4.labelledEntityIds;
41588
+ labelledIds = (_5 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _5 === void 0 ? void 0 : _5.labelledEntityIds;
39384
41589
  }
39385
41590
  if (!(labelledIds === null || labelledIds === void 0 ? void 0 : labelledIds.length)) {
39386
41591
  params.manager.VisualsRegister.ClearLabelled({
@@ -39404,7 +41609,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39404
41609
  params.manager.VisualsRegister.ClearOpacity();
39405
41610
  let entityOpacityMap = bSettings === null || bSettings === void 0 ? void 0 : bSettings.entityOpacityMap;
39406
41611
  if (entityOpacityMap == null) {
39407
- entityOpacityMap = (_5 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _5 === void 0 ? void 0 : _5.entityOpacityMap;
41612
+ entityOpacityMap = (_6 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _6 === void 0 ? void 0 : _6.entityOpacityMap;
39408
41613
  }
39409
41614
  if (entityOpacityMap != null) {
39410
41615
  for (const entityId in entityOpacityMap) {
@@ -39419,7 +41624,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39419
41624
  }
39420
41625
  }
39421
41626
  }
39422
- let imagery = (bSettings === null || bSettings === void 0 ? void 0 : bSettings.imagery) != null ? bSettings.imagery : (_6 = defaults.settings) === null || _6 === void 0 ? void 0 : _6.imagery;
41627
+ let imagery = (bSettings === null || bSettings === void 0 ? void 0 : bSettings.imagery) != null ? bSettings.imagery : (_7 = defaults.settings) === null || _7 === void 0 ? void 0 : _7.imagery;
39423
41628
  if (imagery == null) {
39424
41629
  // TODO: Need global default.
39425
41630
  imagery = [
@@ -39489,7 +41694,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39489
41694
  // Otherwise we cause a flicker of rendered graphics.
39490
41695
  // Maybe TODO to such a function for menu item manager.
39491
41696
  const curEnabled = params.manager.GetEnabledItemIds();
39492
- const newItemIds = (_7 = bSettings === null || bSettings === void 0 ? void 0 : bSettings.menuItemIds) !== null && _7 !== void 0 ? _7 : [];
41697
+ const newItemIds = (_8 = bSettings === null || bSettings === void 0 ? void 0 : bSettings.menuItemIds) !== null && _8 !== void 0 ? _8 : [];
39493
41698
  for (const id of curEnabled) {
39494
41699
  let shouldRemove;
39495
41700
  if (id == RELATION_MENU_ITEM_ID) {
@@ -39563,7 +41768,7 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
39563
41768
  }
39564
41769
  let gOcclusion = bSettings === null || bSettings === void 0 ? void 0 : bSettings.groundOcclusion;
39565
41770
  if (gOcclusion == null) {
39566
- gOcclusion = (_8 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _8 === void 0 ? void 0 : _8.groundOcclusion;
41771
+ gOcclusion = (_9 = defaults === null || defaults === void 0 ? void 0 : defaults.settings) === null || _9 === void 0 ? void 0 : _9.groundOcclusion;
39567
41772
  }
39568
41773
  if (gOcclusion == null) {
39569
41774
  // TODO: Need global default.
@@ -39581,6 +41786,17 @@ var ViewRenderEngine;
39581
41786
  ProjectViewBookmark.EContentType.WEB_3D_MODEL_SPACE,
39582
41787
  ProjectViewBookmark.EContentType.WEB_3D_SPLIT_PLUGIN
39583
41788
  ];
41789
+ /**
41790
+ * A bookmark's own shadows, else the view's defaults unless it is model-space, which reads brighter without.
41791
+ */
41792
+ function ResolveShadows(bSettings, defaults, contentType) {
41793
+ let shadows = bSettings === null || bSettings === void 0 ? void 0 : bSettings.shadows;
41794
+ if (shadows == null && contentType != ProjectViewBookmark.EContentType.WEB_3D_MODEL_SPACE) {
41795
+ shadows = defaults === null || defaults === void 0 ? void 0 : defaults.shadows;
41796
+ }
41797
+ return shadows !== null && shadows !== void 0 ? shadows : { enabled: false };
41798
+ }
41799
+ ViewRenderEngine.ResolveShadows = ResolveShadows;
39584
41800
  /**
39585
41801
  * Returns where a model-space bookmark's camera goes, measured from the origin in effect.
39586
41802
  * @param viewer
@@ -44312,7 +46528,7 @@ const HOLD_ACTIVATION_TIME_MS = 350;
44312
46528
  const HOLD_LOADER_DELAY_MS = 150;
44313
46529
  const CONTINUOUS_ANIMATION_SPEED = 0.02 / 3;
44314
46530
  const ZOOM_DISTANCE = 0.85;
44315
- const MIN_ZOOM_DISTANCE = 0.3;
46531
+ const MIN_ZOOM_DISTANCE$1 = 0.3;
44316
46532
  const NEAR_NADIR_PITCH = -Math.PI / 2 + 0.05;
44317
46533
  const MIN_ORBIT_OFFSET_METERS = 5;
44318
46534
  // Caps how far a pivot may be from the camera relative to the camera's own altitude.
@@ -44518,7 +46734,7 @@ function DoZoom(params) {
44518
46734
  const mag = (ZOOM_DISTANCE * 0.5) / Math.max(1, 4 - Math.log10(height));
44519
46735
  const zoom = zoomIn ? 1 - mag : 1 + mag;
44520
46736
  const offset = Cartesian3.multiplyByScalar(Cartesian3.subtract(p, ground, new Cartesian3()), zoom, new Cartesian3());
44521
- if (zoomIn && Cartesian3.magnitude(offset) <= MIN_ZOOM_DISTANCE) {
46737
+ if (zoomIn && Cartesian3.magnitude(offset) <= MIN_ZOOM_DISTANCE$1) {
44522
46738
  return Promise.resolve(null);
44523
46739
  }
44524
46740
  const to = Cartesian3.add(ground, offset, new Cartesian3());
@@ -44742,7 +46958,7 @@ function StartContinuousZoom(params) {
44742
46958
  const mag = (ZOOM_DISTANCE * 0.5) / Math.max(1, 4 - Math.log10(height));
44743
46959
  const zoom = zoomIn ? 1 - mag * 0.03 : 1 + mag * 0.03;
44744
46960
  const offset = Cartesian3.multiplyByScalar(Cartesian3.subtract(p, ground, new Cartesian3()), zoom, new Cartesian3());
44745
- if (zoomIn && Cartesian3.magnitude(offset) <= MIN_ZOOM_DISTANCE) {
46961
+ if (zoomIn && Cartesian3.magnitude(offset) <= MIN_ZOOM_DISTANCE$1) {
44746
46962
  active = false;
44747
46963
  return;
44748
46964
  }
@@ -45552,7 +47768,7 @@ var ViewerUtils;
45552
47768
  * @param viewer
45553
47769
  */
45554
47770
  function InitViewer(params) {
45555
- const { viewer, destroyWidgets, styleContainer, nextspaceWidgets, widgetParams } = params;
47771
+ const { viewer, destroyWidgets, styleContainer, nextspaceWidgets, widgetParams, cameraControls } = params;
45556
47772
  const data = { widgets: {} };
45557
47773
  if (destroyWidgets != false) {
45558
47774
  try {
@@ -45579,6 +47795,12 @@ var ViewerUtils;
45579
47795
  // Newer cesium is much dimmer by default so we shift brightness a bit.
45580
47796
  ViewUtils.ApplyDefaultBrightness(viewer);
45581
47797
  backgroundRendering(viewer);
47798
+ if (cameraControls != false) {
47799
+ CameraControls.Attach(viewer);
47800
+ if (ViewUtils.GetModelSpace(viewer)) {
47801
+ CameraControls.SetModelSpace(viewer, () => ViewUtils.GetModelSpaceFrame(viewer));
47802
+ }
47803
+ }
45582
47804
  // Capturing the destroy call to destroy all widgets.
45583
47805
  const olDestroy = viewer.destroy;
45584
47806
  viewer.destroy = () => {
@@ -45587,6 +47809,7 @@ var ViewerUtils;
45587
47809
  }
45588
47810
  try {
45589
47811
  ViewerUtils.DisposeNextspaceWidgets(viewer);
47812
+ CameraControls.Detach(viewer);
45590
47813
  }
45591
47814
  // We don't want the actual destroy to fail.
45592
47815
  catch (e) {
@@ -49731,7 +51954,7 @@ var EntityRenderEngineModel3d;
49731
51954
  ...ViewUtils.DefaultLightingProps(),
49732
51955
  heightReference: heightRef,
49733
51956
  scale: new CallbackProperty(() => scale * styleScale, true),
49734
- shadows: ShadowMode.ENABLED,
51957
+ shadows: ViewUtils.GetEntityShadowMode(params.viewer),
49735
51958
  colorBlendAmount: blendAmount,
49736
51959
  colorBlendMode: blendMode,
49737
51960
  color: new CallbackProperty(() => {
@@ -49766,7 +51989,7 @@ var EntityRenderEngineModel3d;
49766
51989
  cEntity.model.uri = new ConstantProperty(params.lodUrl);
49767
51990
  }
49768
51991
  cEntity.model.heightReference = new ConstantProperty(heightRef);
49769
- cEntity.model.shadows = new ConstantProperty(ShadowMode.ENABLED);
51992
+ cEntity.model.shadows = new ConstantProperty(ViewUtils.GetEntityShadowMode(params.viewer));
49770
51993
  cEntity.model.colorBlendAmount = new ConstantProperty(blendAmount);
49771
51994
  cEntity.model.colorBlendMode = new ConstantProperty(blendMode);
49772
51995
  cEntity.model.distanceDisplayCondition = new ConstantProperty(EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance));
@@ -50608,7 +52831,7 @@ var EntityRenderEnginePolyline;
50608
52831
  zIndex: getZIndex(style, entity, params.tags),
50609
52832
  distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width),
50610
52833
  cornerType: CornerType.MITERED,
50611
- shadows: ShadowMode.ENABLED,
52834
+ shadows: ViewUtils.GetEntityShadowMode(params.viewer),
50612
52835
  fill: true
50613
52836
  } : null,
50614
52837
  position: EntityUtils.GetPos({
@@ -55074,7 +57297,7 @@ var EntityRenderEnginePolygon;
55074
57297
  fill: !drawsDisplacedSurface,
55075
57298
  extrudedHeight: extrusion.value,
55076
57299
  extrudedHeightReference: extrusion.exHeightRef,
55077
- shadows: ShadowMode.ENABLED,
57300
+ shadows: ViewUtils.GetEntityShadowMode(params.viewer),
55078
57301
  heightReference: heightRef,
55079
57302
  classificationType: classification,
55080
57303
  perPositionHeight: heightRef == HeightReference.CLAMP_TO_GROUND ? false : true,
@@ -55154,7 +57377,7 @@ var EntityRenderEnginePolygon;
55154
57377
  viewer: params.viewer,
55155
57378
  graphic: cEntity.polygon,
55156
57379
  property: "shadows",
55157
- value: ShadowMode.ENABLED
57380
+ value: ViewUtils.GetEntityShadowMode(params.viewer)
55158
57381
  });
55159
57382
  SetCPropIfChanged({
55160
57383
  viewer: params.viewer,
@@ -55289,7 +57512,7 @@ var EntityRenderEnginePolygon;
55289
57512
  cornerType: CornerType.MITERED,
55290
57513
  classificationType: classification,
55291
57514
  distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width, false, size),
55292
- shadows: ShadowMode.ENABLED
57515
+ shadows: ViewUtils.GetEntityShadowMode(params.viewer)
55293
57516
  } : null,
55294
57517
  show: true
55295
57518
  });
@@ -55412,7 +57635,7 @@ var EntityRenderEnginePolygon;
55412
57635
  cornerType: CornerType.MITERED,
55413
57636
  classificationType: classification,
55414
57637
  distanceDisplayCondition: EntityRenderEngine.GetDisplayCondition(params.minDistance, params.maxDistance, width, false, size),
55415
- shadows: ShadowMode.ENABLED,
57638
+ shadows: ViewUtils.GetEntityShadowMode(params.viewer),
55416
57639
  } : null,
55417
57640
  show: true
55418
57641
  });
@@ -57748,7 +59971,7 @@ var StyleUtils;
57748
59971
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
57749
59972
  })(StyleUtils || (StyleUtils = {}));
57750
59973
 
57751
- const VERSION = "7.7.7";
59974
+ const VERSION = "7.7.8";
57752
59975
  /**
57753
59976
  * Updates the environment instance used by bruce-cesium to one specified.
57754
59977
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
@@ -57765,5 +59988,5 @@ const getENVIRONMENT = () => {
57765
59988
  return ENVIRONMENT;
57766
59989
  };
57767
59990
 
57768
- export { VERSION, setENVIRONMENT, getENVIRONMENT, ClusterItems, PointClustering, PointClusterSize, CesiumAnimatedInOut, DisplacedSurfacePrimitive, CesiumAnimatedProperty, isStyleChanged, isOutlineChanged$1 as isOutlineChanged, StyleEffective, EntityRenderEngine, EntityRenderEngineModel3d, EntityRenderEnginePoint, extractMetadataFromFileUrl, EntityRenderEnginePolygon, EntityRenderEnginePolyline, TextureFrameSeriesAnimator, TextureValueLabels, MenuItemCreator, MenuItemManager, MenuItemStyles, MenuItemStyleOverlay, CesiumParabola, EntityLabel, EntityLabelGroup, LiveCursor, SharedGetters, EntitySimulation, DataSourceStaticKmlManager, DataLabRenderManager, EntitiesIdsRenderManager, EntitiesLoadedRenderManager, EntitiesRenderManager, EntityRenderManager, AssemblyRenderManager, GoogleSearchRenderManager, RelationsRenderManager, RenderManager, TilesetArbRenderManager, TilesetCadRenderManager, TilesetEntitiesRenderManager, TilesetGooglePhotosRenderManager, TilesetOsmRenderManager, TilesetPointcloudRenderManager, TileRenderEngine, createTileset, TilesetRenderEngine, ViewRenderEngine, VisualRegisterCuller, VisualsRegister, XGridsRenderEngine, CesiumEntityStyler, DrawingUtils, EntityUtils, MeasureUtils, StyleUtils, CESIUM_INSPECTOR_KEY, CESIUM_TIMELINE_KEY, CESIUM_TIMELINE_LIVE_KEY, CESIUM_TIMELINE_LIVE_PADDING_KEY, CESIUM_TIMELINE_INTERVAL_KEY, CESIUM_MODEL_SPACE_KEY, CESIUM_MODEL_SPACE_POSITIONING_KEY, DEFAULT_LIVE_PADDING_SECONDS, DEFAULT_BRIGHTNESS_SHIFT, DEFAULT_LIGHT_INTENSITY, LIGHTING_SETTINGS_VERSION, LIGHT_INTENSITY_COMPENSATION, MAX_COMPENSATED_LIGHT_INTENSITY, ViewUtils, CesiumViewMonitor, ViewGroundArea, ViewerEventTracker, ViewerUtils, Draw3dPolygon, Draw3dPolyline, MeasureCreator, Walkthrough, WidgetControlViewBar, WidgetControlViewBarSearch, WidgetLeftPanelTab, WidgetLeftPanelTabBookmarks, WidgetLeftPanelTabDashboard, EMenuItemState, WidgetLeftPanelTabMenuItems, Widget, VIEWER_BOOKMARKS_WIDGET_KEY, WidgetBookmarks, WidgetBranding, ECursor, WidgetCursorBar, WidgetEmbeddedInfoView, WidgetInfoView, VIEWER_LEFT_PANEL_WIDGET_KEY, VIEWER_LEFT_PANEL_CSS_VAR_LEFT, CreateTabIconSvg, WidgetLeftPanel, WidgetNavCompass$$1 as WidgetNavCompass, VIEWER_VIEW_BAR_WIDGET_KEY, WidgetViewBar };
59991
+ export { VERSION, setENVIRONMENT, getENVIRONMENT, ClusterItems, PointClustering, PointClusterSize, CesiumAnimatedInOut, DisplacedSurfacePrimitive, CesiumAnimatedProperty, isStyleChanged, isOutlineChanged$1 as isOutlineChanged, StyleEffective, EntityRenderEngine, EntityRenderEngineModel3d, EntityRenderEnginePoint, extractMetadataFromFileUrl, EntityRenderEnginePolygon, EntityRenderEnginePolyline, TextureFrameSeriesAnimator, TextureValueLabels, MenuItemCreator, MenuItemManager, MenuItemStyles, MenuItemStyleOverlay, CesiumParabola, EntityLabel, EntityLabelGroup, LiveCursor, SharedGetters, EntitySimulation, DataSourceStaticKmlManager, DataLabRenderManager, EntitiesIdsRenderManager, EntitiesLoadedRenderManager, EntitiesRenderManager, EntityRenderManager, AssemblyRenderManager, GoogleSearchRenderManager, RelationsRenderManager, RenderManager, TilesetArbRenderManager, TilesetCadRenderManager, TilesetEntitiesRenderManager, TilesetGooglePhotosRenderManager, TilesetOsmRenderManager, TilesetPointcloudRenderManager, TileRenderEngine, createTileset, TilesetRenderEngine, ViewRenderEngine, VisualRegisterCuller, VisualsRegister, XGridsRenderEngine, CesiumEntityStyler, DrawingUtils, EntityUtils, MeasureUtils, StyleUtils, CESIUM_INSPECTOR_KEY, CESIUM_TIMELINE_KEY, CESIUM_TIMELINE_LIVE_KEY, CESIUM_TIMELINE_LIVE_PADDING_KEY, CESIUM_TIMELINE_INTERVAL_KEY, CESIUM_MODEL_SPACE_KEY, CESIUM_MODEL_SPACE_POSITIONING_KEY, DEFAULT_LIVE_PADDING_SECONDS, DEFAULT_BRIGHTNESS_SHIFT, DEFAULT_LIGHT_INTENSITY, LIGHTING_SETTINGS_VERSION, LIGHT_INTENSITY_COMPENSATION, MAX_COMPENSATED_LIGHT_INTENSITY, ViewUtils, CesiumMapGrid, GltfGenerator, GltfGizmo, DeviceProfile, CesiumViewMonitor, CameraControls, ViewGroundArea, ViewerEventTracker, ViewerUtils, Draw3dPolygon, Draw3dPolyline, MeasureCreator, Walkthrough, WidgetControlViewBar, WidgetControlViewBarSearch, WidgetLeftPanelTab, WidgetLeftPanelTabBookmarks, WidgetLeftPanelTabDashboard, EMenuItemState, WidgetLeftPanelTabMenuItems, Widget, VIEWER_BOOKMARKS_WIDGET_KEY, WidgetBookmarks, WidgetBranding, ECursor, WidgetCursorBar, WidgetEmbeddedInfoView, WidgetInfoView, VIEWER_LEFT_PANEL_WIDGET_KEY, VIEWER_LEFT_PANEL_CSS_VAR_LEFT, CreateTabIconSvg, WidgetLeftPanel, WidgetNavCompass$$1 as WidgetNavCompass, VIEWER_VIEW_BAR_WIDGET_KEY, WidgetViewBar };
57769
59992
  //# sourceMappingURL=bruce-cesium.es5.js.map