@hztxi/cesium-engine 25.0.4 → 25.0.7

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 (202) hide show
  1. package/Build/Workers/chunk-247MLVK2.js +124 -0
  2. package/Build/Workers/chunk-26I7TIAE.js +58 -0
  3. package/Build/Workers/chunk-26OYTC24.js +38 -0
  4. package/Build/Workers/chunk-2GJAWJJT.js +1344 -0
  5. package/Build/Workers/chunk-2IMOZG7R.js +973 -0
  6. package/Build/Workers/chunk-2J2VS5RI.js +288 -0
  7. package/Build/Workers/chunk-2L6JGSYJ.js +57 -0
  8. package/Build/Workers/chunk-32N2SB76.js +56 -0
  9. package/Build/Workers/chunk-375SHKGW.js +44 -0
  10. package/Build/Workers/chunk-37WL527K.js +1287 -0
  11. package/Build/Workers/chunk-3IGH46TC.js +1054 -0
  12. package/Build/Workers/chunk-47BSFGJC.js +837 -0
  13. package/Build/Workers/chunk-4J7PL5AM.js +454 -0
  14. package/Build/Workers/chunk-4UMO63VY.js +76 -0
  15. package/Build/Workers/chunk-5FYUSABU.js +945 -0
  16. package/Build/Workers/chunk-5WU37CUJ.js +630 -0
  17. package/Build/Workers/chunk-5YI3TQQC.js +778 -0
  18. package/Build/Workers/chunk-6EBDN5WS.js +97 -0
  19. package/Build/Workers/chunk-75HIWCEV.js +301 -0
  20. package/Build/Workers/chunk-7AMVPXZY.js +1992 -0
  21. package/Build/Workers/chunk-7BMDD6SM.js +124 -0
  22. package/Build/Workers/chunk-7OME2OKM.js +171 -0
  23. package/Build/Workers/chunk-A2BROW3D.js +352 -0
  24. package/Build/Workers/chunk-AEZQ2UWS.js +288 -0
  25. package/Build/Workers/chunk-AXE2ZIEG.js +219 -0
  26. package/Build/Workers/chunk-B4XOVZGA.js +476 -0
  27. package/Build/Workers/chunk-BDR7GL3E.js +1067 -0
  28. package/Build/Workers/chunk-C342XITR.js +44 -0
  29. package/Build/Workers/chunk-CDLPLYLV.js +1287 -0
  30. package/Build/Workers/chunk-CEKCKPFN.js +368 -0
  31. package/Build/Workers/chunk-CHMDKP5L.js +2342 -0
  32. package/Build/Workers/chunk-CWAUXQRF.js +754 -0
  33. package/Build/Workers/chunk-CWGB3D4Z.js +257 -0
  34. package/Build/Workers/chunk-DQEUGBGA.js +2719 -0
  35. package/Build/Workers/chunk-DUBE5G2H.js +497 -0
  36. package/Build/Workers/chunk-E4PBNWWK.js +58 -0
  37. package/Build/Workers/chunk-E5F7LDEP.js +3201 -0
  38. package/Build/Workers/chunk-E5MWOMVC.js +57 -0
  39. package/Build/Workers/chunk-EE3XBOQI.js +1483 -0
  40. package/Build/Workers/chunk-EHA2ZDPZ.js +415 -0
  41. package/Build/Workers/chunk-EKEKPAJF.js +454 -0
  42. package/Build/Workers/chunk-EOHKUXPX.js +198 -0
  43. package/Build/Workers/chunk-ERENMHG2.js +754 -0
  44. package/Build/Workers/chunk-ESQGFRLZ.js +163 -0
  45. package/Build/Workers/chunk-FAKYMBUA.js +1046 -0
  46. package/Build/Workers/chunk-FSK4OZOO.js +352 -0
  47. package/Build/Workers/chunk-FSLRYNZ6.js +682 -0
  48. package/Build/Workers/chunk-FV4OWDV4.js +9230 -0
  49. package/Build/Workers/chunk-FYRNNQSL.js +280 -0
  50. package/Build/Workers/chunk-GMDOGAXK.js +2719 -0
  51. package/Build/Workers/chunk-GRAFCIPA.js +455 -0
  52. package/Build/Workers/chunk-HEY4IOS3.js +140 -0
  53. package/Build/Workers/chunk-HRWFO57T.js +301 -0
  54. package/Build/Workers/chunk-HTCLVUBZ.js +82 -0
  55. package/Build/Workers/chunk-I2Q7XDRA.js +630 -0
  56. package/Build/Workers/chunk-ILPH3NF7.js +3794 -0
  57. package/Build/Workers/chunk-JMF4ATAS.js +3794 -0
  58. package/Build/Workers/chunk-JZUUODN6.js +97 -0
  59. package/Build/Workers/chunk-KEJKMITY.js +82 -0
  60. package/Build/Workers/chunk-KW2QZDUS.js +147 -0
  61. package/Build/Workers/chunk-KWVB54J6.js +241 -0
  62. package/Build/Workers/chunk-L6B5XPBL.js +73 -0
  63. package/Build/Workers/chunk-LGJXN2VP.js +352 -0
  64. package/Build/Workers/chunk-LY2UG3HV.js +38 -0
  65. package/Build/Workers/chunk-M3B3ZUU2.js +476 -0
  66. package/Build/Workers/chunk-MKR6YCMG.js +497 -0
  67. package/Build/Workers/chunk-MROMS2RL.js +162 -0
  68. package/Build/Workers/chunk-MUNAUSK7.js +280 -0
  69. package/Build/Workers/chunk-NCPJBP24.js +140 -0
  70. package/Build/Workers/chunk-NFY5V6MR.js +102 -0
  71. package/Build/Workers/chunk-NHU5JEPP.js +1344 -0
  72. package/Build/Workers/chunk-OODXPB45.js +73 -0
  73. package/Build/Workers/chunk-OXZ5J3JW.js +368 -0
  74. package/Build/Workers/chunk-OYPSUED3.js +1067 -0
  75. package/Build/Workers/chunk-QBSWSTHY.js +257 -0
  76. package/Build/Workers/chunk-QDJYG7QL.js +352 -0
  77. package/Build/Workers/chunk-QIOOTLWN.js +56 -0
  78. package/Build/Workers/chunk-QUPRALOD.js +101 -0
  79. package/Build/Workers/chunk-RBEVHHXK.js +3201 -0
  80. package/Build/Workers/chunk-RHO5HV52.js +305 -0
  81. package/Build/Workers/chunk-RX3U2BBW.js +1483 -0
  82. package/Build/Workers/chunk-S3XHL7GO.js +1992 -0
  83. package/Build/Workers/chunk-SBIC4OGS.js +171 -0
  84. package/Build/Workers/chunk-SVCZUP5R.js +837 -0
  85. package/Build/Workers/chunk-T4CYNROE.js +102 -0
  86. package/Build/Workers/chunk-T5VLPB4H.js +414 -0
  87. package/Build/Workers/chunk-TBHW5UME.js +973 -0
  88. package/Build/Workers/chunk-TPOX6IOR.js +1054 -0
  89. package/Build/Workers/chunk-TX763OWB.js +387 -0
  90. package/Build/Workers/chunk-ULNBTI6R.js +138 -0
  91. package/Build/Workers/chunk-UMUS2AQQ.js +398 -0
  92. package/Build/Workers/chunk-UTAWMCSE.js +163 -0
  93. package/Build/Workers/chunk-UZ7GTP2L.js +162 -0
  94. package/Build/Workers/chunk-VETQOADR.js +2342 -0
  95. package/Build/Workers/chunk-VXRWOM3T.js +101 -0
  96. package/Build/Workers/chunk-W3C7OHXF.js +387 -0
  97. package/Build/Workers/chunk-WB5GWYDP.js +455 -0
  98. package/Build/Workers/chunk-WN6MAL25.js +682 -0
  99. package/Build/Workers/chunk-WTR7KMQA.js +693 -0
  100. package/Build/Workers/chunk-WTTFMU3P.js +241 -0
  101. package/Build/Workers/chunk-WULONJZC.js +1800 -0
  102. package/Build/Workers/chunk-WVVFCMYF.js +147 -0
  103. package/Build/Workers/chunk-WZPMAVTV.js +511 -0
  104. package/Build/Workers/chunk-XAVNBZQ6.js +56 -0
  105. package/Build/Workers/chunk-XDSDR62Y.js +1800 -0
  106. package/Build/Workers/chunk-XDZCVNIZ.js +198 -0
  107. package/Build/Workers/chunk-XNLCW2OI.js +138 -0
  108. package/Build/Workers/chunk-XNQ2BTJD.js +693 -0
  109. package/Build/Workers/chunk-XZYBDSFP.js +305 -0
  110. package/Build/Workers/chunk-Y7KVUJEB.js +219 -0
  111. package/Build/Workers/chunk-YDYN3RPI.js +398 -0
  112. package/Build/Workers/chunk-YMMEQBEW.js +511 -0
  113. package/Build/Workers/chunk-YQFOQAXD.js +9230 -0
  114. package/Build/Workers/chunk-Z6JV4DZG.js +1046 -0
  115. package/Build/Workers/chunk-ZTK5EBUI.js +76 -0
  116. package/Build/Workers/chunk-ZZPDORJK.js +56 -0
  117. package/Build/Workers/combineGeometry.js +24 -24
  118. package/Build/Workers/createBoxGeometry.js +18 -18
  119. package/Build/Workers/createBoxOutlineGeometry.js +16 -16
  120. package/Build/Workers/createCircleGeometry.js +26 -26
  121. package/Build/Workers/createCircleOutlineGeometry.js +19 -19
  122. package/Build/Workers/createCoplanarPolygonGeometry.js +33 -33
  123. package/Build/Workers/createCoplanarPolygonOutlineGeometry.js +31 -31
  124. package/Build/Workers/createCorridorGeometry.js +29 -29
  125. package/Build/Workers/createCorridorOutlineGeometry.js +28 -28
  126. package/Build/Workers/createCylinderGeometry.js +20 -20
  127. package/Build/Workers/createCylinderOutlineGeometry.js +18 -18
  128. package/Build/Workers/createEllipseGeometry.js +26 -26
  129. package/Build/Workers/createEllipseOutlineGeometry.js +19 -19
  130. package/Build/Workers/createEllipsoidGeometry.js +19 -19
  131. package/Build/Workers/createEllipsoidOutlineGeometry.js +18 -18
  132. package/Build/Workers/createFrustumGeometry.js +18 -18
  133. package/Build/Workers/createFrustumOutlineGeometry.js +18 -18
  134. package/Build/Workers/createGeometry.js +24 -24
  135. package/Build/Workers/createGroundPolylineGeometry.js +95 -31
  136. package/Build/Workers/createPlaneGeometry.js +16 -16
  137. package/Build/Workers/createPlaneOutlineGeometry.js +15 -15
  138. package/Build/Workers/createPolygonGeometry.js +32 -32
  139. package/Build/Workers/createPolygonOutlineGeometry.js +30 -30
  140. package/Build/Workers/createPolylineGeometry.js +25 -25
  141. package/Build/Workers/createPolylineVolumeGeometry.js +31 -31
  142. package/Build/Workers/createPolylineVolumeOutlineGeometry.js +27 -27
  143. package/Build/Workers/createRectangleGeometry.js +27 -27
  144. package/Build/Workers/createRectangleOutlineGeometry.js +20 -20
  145. package/Build/Workers/createSimplePolylineGeometry.js +23 -23
  146. package/Build/Workers/createSphereGeometry.js +19 -19
  147. package/Build/Workers/createSphereOutlineGeometry.js +18 -18
  148. package/Build/Workers/createTaskProcessorWorker.js +3 -3
  149. package/Build/Workers/createVectorTileClampedPolylines.js +14 -14
  150. package/Build/Workers/createVectorTileGeometries.js +24 -24
  151. package/Build/Workers/createVectorTilePoints.js +13 -13
  152. package/Build/Workers/createVectorTilePolygons.js +22 -22
  153. package/Build/Workers/createVectorTilePolylines.js +14 -14
  154. package/Build/Workers/createVerticesFromCesium3DTilesTerrain.js +26 -26
  155. package/Build/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +23 -23
  156. package/Build/Workers/createVerticesFromHeightmap.js +23 -23
  157. package/Build/Workers/createVerticesFromQuantizedTerrainMesh.js +21 -21
  158. package/Build/Workers/createWallGeometry.js +24 -24
  159. package/Build/Workers/createWallOutlineGeometry.js +23 -23
  160. package/Build/Workers/decodeDraco.js +10 -10
  161. package/Build/Workers/decodeGoogleEarthEnterprisePacket.js +5 -5
  162. package/Build/Workers/decodeI3S.js +10 -10
  163. package/Build/Workers/gaussianSplatSorter.js +4 -4
  164. package/Build/Workers/gaussianSplatTextureGenerator.js +4 -4
  165. package/Build/Workers/incrementallyBuildTerrainPicker.js +10 -10
  166. package/Build/Workers/transcodeKTX2.js +6 -6
  167. package/Build/Workers/transferTypedArrayTest.js +1 -1
  168. package/Build/Workers/upsampleQuantizedTerrainMesh.js +23 -23
  169. package/Build/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +26 -26
  170. package/Source/Core/GroundPolylineGeometry.js +99 -4
  171. package/Source/Core/PolylinePipeline.js +9 -2
  172. package/Source/DataSources/PolylineGeometryUpdater.js +125 -7
  173. package/Source/Scene/Camera.js +56 -3
  174. package/Source/Scene/CameraFlightPath.js +14 -0
  175. package/Source/Scene/ClassificationPrimitive.js +63 -3
  176. package/Source/Scene/ColumbusWorlds.js +190 -0
  177. package/Source/Scene/FrameState.js +10 -0
  178. package/Source/Scene/Globe.js +89 -22
  179. package/Source/Scene/GlobeDepth.js +216 -5
  180. package/Source/Scene/GlobeSurfaceTileProvider.js +897 -678
  181. package/Source/Scene/GroundPolylinePrimitive.js +2 -1
  182. package/Source/Scene/Polyline.js +32 -5
  183. package/Source/Scene/PolylineCollection.js +41 -2
  184. package/Source/Scene/Primitive.js +12 -13
  185. package/Source/Scene/PrimitivePipeline.js +158 -28
  186. package/Source/Scene/Scene.js +428 -5
  187. package/Source/Scene/SceneTransforms.js +17 -0
  188. package/Source/Scene/ScreenSpaceCameraController.js +12 -3
  189. package/Source/Shaders/PolylineShadowVolumeFS.glsl +14 -0
  190. package/Source/Shaders/PolylineShadowVolumeFS.js +14 -0
  191. package/Source/Shaders/PolylineShadowVolumeMorphVS.glsl +14 -1
  192. package/Source/Shaders/PolylineShadowVolumeMorphVS.js +14 -1
  193. package/Source/Shaders/PolylineShadowVolumeVS.glsl +7 -0
  194. package/Source/Shaders/PolylineShadowVolumeVS.js +7 -0
  195. package/Source/Shaders/PolylineVS.glsl +16 -6
  196. package/Source/Shaders/PolylineVS.js +16 -6
  197. package/Source/Shaders/RestoreDepth.glsl +8 -0
  198. package/Source/Shaders/RestoreDepth.js +10 -0
  199. package/Source/ThirdParty/zip-module.wasm +0 -0
  200. package/index.d.ts +535 -1393
  201. package/index.js +267 -265
  202. package/package.json +10 -10
@@ -45,6 +45,7 @@ import BrdfLutGenerator from "./BrdfLutGenerator.js";
45
45
  import Camera from "./Camera.js";
46
46
  import Cesium3DTilePass from "./Cesium3DTilePass.js";
47
47
  import Cesium3DTilePassState from "./Cesium3DTilePassState.js";
48
+ import ColumbusWorlds from "./ColumbusWorlds.js";
48
49
  import CreditDisplay from "./CreditDisplay.js";
49
50
  import DebugCameraPrimitive from "./DebugCameraPrimitive.js";
50
51
  import DepthPlane from "./DepthPlane.js";
@@ -108,6 +109,9 @@ const requestRenderAfterFrame = function (scene) {
108
109
  * @param {boolean} [options.scene3DOnly=false] If true, optimizes memory use and performance for 3D mode but disables the ability to use 2D or Columbus View.
109
110
  * @param {boolean} [options.shadows=false] Determines if shadows are cast by light sources.
110
111
  * @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
112
+ * @param {boolean} [options.columbusRepeatWorlds=false] If true, the Columbus View map repeats horizontally: translated
113
+ * copies of the globe surface tiles are rendered beyond the ±180° meridian so the map, entity placement, and
114
+ * picking continue seamlessly across the antimeridian.
111
115
  * @param {boolean} [options.requestRenderMode=false] If true, rendering a frame will only occur when needed as determined by changes within the scene. Enabling improves performance of the application, but requires using {@link Scene#requestRender} to render a new frame explicitly in this mode. This will be necessary in many cases after making changes to the scene in other parts of the API. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}.
112
116
  * @param {number} [options.maximumRenderTimeChange=0.0] If requestRenderMode is true, this value defines the maximum change in simulation time allowed before a render is requested. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}.
113
117
  * @param {number} [options.depthPlaneEllipsoidOffset=0.0] Adjust the DepthPlane to address rendering artefacts below ellipsoid zero elevation.
@@ -627,6 +631,7 @@ function Scene(options) {
627
631
  this._screenSpaceCameraController = new ScreenSpaceCameraController(this);
628
632
  this._cameraUnderground = false;
629
633
  this._mapMode2D = options.mapMode2D ?? MapMode2D.INFINITE_SCROLL;
634
+ this._columbusRepeatWorlds = options.columbusRepeatWorlds ?? false;
630
635
 
631
636
  // Keeps track of the state of a frame. FrameState is the state across
632
637
  // the primitives of the scene. This state is for internally keeping track
@@ -1524,6 +1529,26 @@ Object.defineProperties(Scene.prototype, {
1524
1529
  },
1525
1530
  },
1526
1531
 
1532
+ /**
1533
+ * Gets or sets whether the Columbus View map repeats horizontally. When true,
1534
+ * translated copies of the globe surface tiles are rendered beyond the ±180°
1535
+ * meridian, and entity placement/picking wrap to the copy nearest the camera,
1536
+ * so editing and animation work seamlessly across the antimeridian.
1537
+ * Only has an effect in {@link SceneMode.COLUMBUS_VIEW}.
1538
+ * @memberof Scene.prototype
1539
+ *
1540
+ * @type {boolean}
1541
+ * @default false
1542
+ */
1543
+ columbusRepeatWorlds: {
1544
+ get: function () {
1545
+ return this._columbusRepeatWorlds;
1546
+ },
1547
+ set: function (value) {
1548
+ this._columbusRepeatWorlds = value;
1549
+ },
1550
+ },
1551
+
1527
1552
  /**
1528
1553
  * Gets or sets the position of the splitter within the viewport. Valid values are between 0.0 and 1.0.
1529
1554
  * @memberof Scene.prototype
@@ -1985,6 +2010,7 @@ Scene.prototype.updateFrameState = function () {
1985
2010
  frameState.mode = this._mode;
1986
2011
  frameState.morphTime = this.morphTime;
1987
2012
  frameState.mapProjection = this.mapProjection;
2013
+ frameState.columbusRepeatWorlds = this._columbusRepeatWorlds;
1988
2014
  frameState.camera = camera;
1989
2015
  frameState.cullingVolume = camera.frustum.computeCullingVolume(
1990
2016
  camera.positionWC,
@@ -2618,6 +2644,355 @@ function performCesium3DTileEdgesPass(scene, passState, frustumCommands) {
2618
2644
  passState.framebuffer = originalFramebuffer;
2619
2645
  }
2620
2646
 
2647
+ const COLUMBUS_MAX_COPY_WORLDS = 2;
2648
+ const columbusWorldIndicesScratch = [];
2649
+
2650
+ /**
2651
+ * Finds the primary pass' frustum whose depth range best matches a copy's
2652
+ * frustum. A copy's visible set is culled from the shifted camera, so its
2653
+ * frustum split can differ from the primary pass' (the globe emits translated
2654
+ * tile commands whose distances change under the shift). Pairing by index then
2655
+ * matches a copy's terrain commands with a primary frustum that holds no
2656
+ * terrain, and the classification shader samples that frustum's empty globe
2657
+ * depth copy, discarding every fragment. Pair by depth range instead: a frustum
2658
+ * whose midpoint falls inside a primary range maps to that range, and a frustum
2659
+ * outside every range maps to the closest one.
2660
+ *
2661
+ * @param {FrustumCommands[]} primaryFrustumCommandsList The primary pass' bins.
2662
+ * @param {FrustumCommands} frustumCommands The copy's bin.
2663
+ * @returns {number} The index of the matching primary frustum, or -1 when the primary visible set is empty.
2664
+ * @private
2665
+ */
2666
+ function matchPrimaryFrustumIndex(primaryFrustumCommandsList, frustumCommands) {
2667
+ const count = primaryFrustumCommandsList.length;
2668
+ if (count === 0) {
2669
+ return -1;
2670
+ }
2671
+
2672
+ const midpoint = (frustumCommands.near + frustumCommands.far) * 0.5;
2673
+ let bestIndex = count - 1;
2674
+ let bestDistance = Number.POSITIVE_INFINITY;
2675
+ for (let i = 0; i < count; ++i) {
2676
+ const primary = primaryFrustumCommandsList[i];
2677
+ let distance = 0.0;
2678
+ if (midpoint < primary.near) {
2679
+ distance = primary.near - midpoint;
2680
+ } else if (midpoint > primary.far) {
2681
+ distance = midpoint - primary.far;
2682
+ }
2683
+ if (distance < bestDistance) {
2684
+ bestDistance = distance;
2685
+ bestIndex = i;
2686
+ }
2687
+ }
2688
+ return bestIndex;
2689
+ }
2690
+
2691
+ const columbusCopyCullingVolume = new CullingVolume();
2692
+ const columbusSavedPositionWC = new Cartesian3();
2693
+ const columbusSavedViewMatrix = new Matrix4();
2694
+ const columbusSavedInvViewMatrix = new Matrix4();
2695
+
2696
+ /**
2697
+ * Renders the data passes again for each visible world copy when Columbus
2698
+ * repeat worlds is enabled, so primitives authored in the primary world also
2699
+ * appear on the copies around the camera. Each copy is the primary-world
2700
+ * command list viewed by a camera shifted a whole world along map-u, which
2701
+ * replicates every draw command type (and its pick derived command) without
2702
+ * duplicating geometry, memory or per-element placement logic.
2703
+ *
2704
+ * Globe tiles are deliberately excluded: {@link GlobeSurfaceTileProvider}
2705
+ * already emits its own translated tile commands, which the regular GLOBE
2706
+ * pass renders once.
2707
+ *
2708
+ * @param {Scene} scene The scene.
2709
+ * @param {PassState} passState The render pass state.
2710
+ * @param {Frustum} frustum The working frustum used by executeCommands.
2711
+ * @param {Function} performPass Executes a pass for a frustum's command bin.
2712
+ * @param {boolean} classifyInline Whether the copies' ground classification was already executed by executeCommands.
2713
+ * @private
2714
+ */
2715
+ function executeColumbusWorldCopyCommands(
2716
+ scene,
2717
+ passState,
2718
+ frustum,
2719
+ performPass,
2720
+ classifyInline,
2721
+ ) {
2722
+ const { camera, context, frameState } = scene;
2723
+ const { uniformState } = context;
2724
+
2725
+ const worldWidth = ColumbusWorlds.getWorldWidth(frameState.mapProjection);
2726
+ const worldIndices = ColumbusWorlds.getVisibleWorldIndices(
2727
+ camera._positionWC.y,
2728
+ worldWidth,
2729
+ frameState.cullingVolume,
2730
+ COLUMBUS_MAX_COPY_WORLDS,
2731
+ columbusWorldIndicesScratch,
2732
+ );
2733
+ if (worldIndices.length === 0) {
2734
+ return;
2735
+ }
2736
+
2737
+ const view = scene._view;
2738
+
2739
+ // The globe depth texture is produced inside the primary pass' frustum loop.
2740
+ // That loop is skipped when no command is visible from the camera's own copy
2741
+ // (the camera can sit on a non-primary copy, with all data a world away), so
2742
+ // billboards, labels and other shaders sampling czm_globeDepthTexture would
2743
+ // get an undefined uniform while the copies draw. Produce the (cleared) globe
2744
+ // depth once here - the copies share the primary pass' screen space, so the
2745
+ // texture is as valid for them as it would be for a regular frame.
2746
+ if (!defined(uniformState.globeDepthTexture) && defined(view.globeDepth)) {
2747
+ view.globeDepth.executeCopyDepth(context, passState);
2748
+ }
2749
+
2750
+ const baseFrustumCommandsList = view.frustumCommandsList;
2751
+ let copyFrustumCommandsList = scene._columbusFrustumCommands;
2752
+ if (!defined(copyFrustumCommandsList)) {
2753
+ copyFrustumCommandsList = scene._columbusFrustumCommands = [];
2754
+ }
2755
+
2756
+ const cullingVolume = frameState.cullingVolume;
2757
+ const positionWC = Cartesian3.clone(
2758
+ camera._positionWC,
2759
+ columbusSavedPositionWC,
2760
+ );
2761
+ const viewMatrix = Matrix4.clone(camera._viewMatrix, columbusSavedViewMatrix);
2762
+ const invViewMatrix = Matrix4.clone(
2763
+ camera._invViewMatrix,
2764
+ columbusSavedInvViewMatrix,
2765
+ );
2766
+
2767
+ for (let c = 0; c < worldIndices.length; ++c) {
2768
+ const worldOffset = worldIndices[c] * worldWidth;
2769
+
2770
+ // Shift the camera to the copy and cull the data against its frustum,
2771
+ // building the bins in a list private to the copies so the primary
2772
+ // world's bins (used by pickPosition) stay intact.
2773
+ ColumbusWorlds.applyCameraWorldOffset(camera, worldOffset);
2774
+ frameState.cullingVolume = ColumbusWorlds.translateCullingVolume(
2775
+ cullingVolume,
2776
+ worldOffset,
2777
+ columbusCopyCullingVolume,
2778
+ );
2779
+
2780
+ view.frustumCommandsList = copyFrustumCommandsList;
2781
+ view.createPotentiallyVisibleSet(scene);
2782
+ view.frustumCommandsList = baseFrustumCommandsList;
2783
+
2784
+ uniformState.update(frameState);
2785
+
2786
+ const numFrustums = copyFrustumCommandsList.length;
2787
+ for (let i = 0; i < numFrustums; ++i) {
2788
+ const index = numFrustums - i - 1;
2789
+ const frustumCommands = copyFrustumCommandsList[index];
2790
+
2791
+ // Draw with the primary pass' frustum ranges. Shifting the camera does
2792
+ // not change any distance, so a copy's commands must be depth-encoded
2793
+ // exactly like the primary pass - the depth buffer was written with
2794
+ // these ranges, and log depth (or any frustum-dependent depth math) only
2795
+ // decodes correctly against them. The copy's bins are not guaranteed to
2796
+ // split at the same distances as the primary's, so match by range rather
2797
+ // than by position in the list.
2798
+ //
2799
+ // The primary visible set is empty when nothing of the camera's own world
2800
+ // is on screen (the camera can sit on a world copy with all data a world
2801
+ // away). There is then no primary depth to match, so the copy falls back
2802
+ // to its own ranges instead of indexing past the empty list.
2803
+ const primaryIndex = matchPrimaryFrustumIndex(
2804
+ baseFrustumCommandsList,
2805
+ frustumCommands,
2806
+ );
2807
+ const primaryFrustum =
2808
+ primaryIndex >= 0
2809
+ ? baseFrustumCommandsList[primaryIndex]
2810
+ : frustumCommands;
2811
+ frustum.near =
2812
+ primaryIndex !== 0
2813
+ ? primaryFrustum.near * scene.opaqueFrustumNearOffset
2814
+ : primaryFrustum.near;
2815
+ frustum.far = primaryFrustum.far;
2816
+ uniformState.updateFrustum(frustum);
2817
+
2818
+ if (context.stencilBuffer) {
2819
+ scene._stencilClearCommand.execute(context, passState);
2820
+ }
2821
+
2822
+ // Classic ground classification samples czm_globeDepthTexture. Bind the
2823
+ // copy that the primary pass produced for the matching frustum: each copy
2824
+ // is packed against its own frustum's near/far, so sampling another
2825
+ // frustum's copy decodes garbage - the near frustum contains no terrain
2826
+ // and packs to zero, which the ground polyline shader treats as sky, so
2827
+ // every clamped line in the copy would be discarded.
2828
+ const globeDepth = view.globeDepth;
2829
+ const copyFrustumIndex = primaryIndex >= 0 ? primaryIndex : index;
2830
+
2831
+ // This bin's classification was already executed by the primary loop
2832
+ // (executeColumbusWorldCopyClassification) against the live terrain
2833
+ // depth. Replaying it here would only re-do the stencil work against a
2834
+ // restored copy of that depth, so it is skipped - except when the primary
2835
+ // loop could not cover it: pick passes, or a bin matching no primary
2836
+ // frustum (the primary visible set can be empty).
2837
+ const classifiedInline = classifyInline && primaryIndex >= 0;
2838
+
2839
+ let restoredDepth = false;
2840
+ if (defined(globeDepth)) {
2841
+ globeDepth.useCopyDepth(context, copyFrustumIndex);
2842
+
2843
+ // Ground classification is resolved against the terrain depth in the
2844
+ // depth buffer (its stencil pass depth tests the shadow volume against
2845
+ // it), not against czm_globeDepthTexture. The primary loop clears the
2846
+ // globe depth right after its classification pass when the globe does
2847
+ // not depth test against terrain - the default. Put the matching
2848
+ // frustum's globe depth back before re-running the classification
2849
+ // commands, otherwise every copy's ground element is classified
2850
+ // against an empty buffer and silently dropped.
2851
+ restoredDepth =
2852
+ !classifiedInline &&
2853
+ frameState.passes.render &&
2854
+ scene._environmentState.clearGlobeDepth &&
2855
+ frustumCommands.indices[Pass.TERRAIN_CLASSIFICATION] > 0;
2856
+ if (restoredDepth) {
2857
+ globeDepth.executeRestoreDepth(context, passState, copyFrustumIndex);
2858
+ }
2859
+ }
2860
+
2861
+ if (!classifiedInline) {
2862
+ performPass(frustumCommands, Pass.TERRAIN_CLASSIFICATION);
2863
+ }
2864
+
2865
+ // Mirror the primary pass, which clears the globe depth before its opaque
2866
+ // commands so primitives drawn on the ground are not occluded by the
2867
+ // terrain. The restore above is only for the classification.
2868
+ if (restoredDepth) {
2869
+ scene._depthClearCommand.execute(context, passState);
2870
+ }
2871
+
2872
+ performPass(frustumCommands, Pass.OPAQUE);
2873
+ performTranslucentPass(scene, passState, frustumCommands);
2874
+ }
2875
+
2876
+ Cartesian3.clone(positionWC, camera._positionWC);
2877
+ Matrix4.clone(viewMatrix, camera._viewMatrix);
2878
+ Matrix4.clone(invViewMatrix, camera._invViewMatrix);
2879
+ }
2880
+
2881
+ frameState.cullingVolume = cullingVolume;
2882
+ uniformState.update(frameState);
2883
+ }
2884
+
2885
+ /**
2886
+ * Executes the world copies' terrain classification while the primary pass'
2887
+ * terrain depth is still in the depth buffer.
2888
+ *
2889
+ * Ground classification is resolved by depth testing the shadow volume against
2890
+ * the terrain depth (z-fail stencil counting), so it needs the live buffer.
2891
+ * Replaying those commands later - after `clearGlobeDepth` wiped the buffer -
2892
+ * only works off a restored copy of the globe depth, and a restored copy is not
2893
+ * equivalent to the live buffer: it is a resolved, packed copy of it, so
2894
+ * coplanar ground surfaces miss the comparison and drop out once the view is
2895
+ * oblique (partial or speckled fills). Running the copies here, right after the
2896
+ * primary classification and before the depth clear, hands them exactly the
2897
+ * depth buffer the primary classification used.
2898
+ *
2899
+ * @param {Scene} scene The scene.
2900
+ * @param {PassState} passState The render pass state.
2901
+ * @param {Frustum} frustum The working frustum, already set to the primary frustum's depth ranges.
2902
+ * @param {number} frustumIndex The primary frustum index currently being drawn.
2903
+ * @param {Function} performPass Executes a pass for a frustum's command bin.
2904
+ * @private
2905
+ */
2906
+ function executeColumbusWorldCopyClassification(
2907
+ scene,
2908
+ passState,
2909
+ frustum,
2910
+ frustumIndex,
2911
+ performPass,
2912
+ ) {
2913
+ const { camera, frameState } = scene;
2914
+ const { uniformState } = scene.context;
2915
+
2916
+ const worldWidth = ColumbusWorlds.getWorldWidth(frameState.mapProjection);
2917
+ const worldIndices = ColumbusWorlds.getVisibleWorldIndices(
2918
+ camera._positionWC.y,
2919
+ worldWidth,
2920
+ frameState.cullingVolume,
2921
+ COLUMBUS_MAX_COPY_WORLDS,
2922
+ columbusWorldIndicesScratch,
2923
+ );
2924
+ if (worldIndices.length === 0) {
2925
+ return;
2926
+ }
2927
+
2928
+ const view = scene._view;
2929
+ const baseFrustumCommandsList = view.frustumCommandsList;
2930
+ let copyFrustumCommandsList = scene._columbusFrustumCommands;
2931
+ if (!defined(copyFrustumCommandsList)) {
2932
+ copyFrustumCommandsList = scene._columbusFrustumCommands = [];
2933
+ }
2934
+
2935
+ const cullingVolume = frameState.cullingVolume;
2936
+ const positionWC = Cartesian3.clone(
2937
+ camera._positionWC,
2938
+ columbusSavedPositionWC,
2939
+ );
2940
+ const viewMatrix = Matrix4.clone(camera._viewMatrix, columbusSavedViewMatrix);
2941
+ const invViewMatrix = Matrix4.clone(
2942
+ camera._invViewMatrix,
2943
+ columbusSavedInvViewMatrix,
2944
+ );
2945
+
2946
+ for (let c = 0; c < worldIndices.length; ++c) {
2947
+ const worldOffset = worldIndices[c] * worldWidth;
2948
+
2949
+ ColumbusWorlds.applyCameraWorldOffset(camera, worldOffset);
2950
+ frameState.cullingVolume = ColumbusWorlds.translateCullingVolume(
2951
+ cullingVolume,
2952
+ worldOffset,
2953
+ columbusCopyCullingVolume,
2954
+ );
2955
+
2956
+ view.frustumCommandsList = copyFrustumCommandsList;
2957
+ view.createPotentiallyVisibleSet(scene);
2958
+ view.frustumCommandsList = baseFrustumCommandsList;
2959
+
2960
+ // Draw with the primary frustum's ranges: the depth buffer was written
2961
+ // with them, and the copies are only translations of the same distances.
2962
+ uniformState.update(frameState);
2963
+ uniformState.updateFrustum(frustum);
2964
+
2965
+ const numFrustums = copyFrustumCommandsList.length;
2966
+ for (let i = 0; i < numFrustums; ++i) {
2967
+ const copyFrustumCommands = copyFrustumCommandsList[numFrustums - i - 1];
2968
+ // Every copy bin belongs to exactly one primary frustum, so it is
2969
+ // classified once while that frustum is drawn.
2970
+ if (
2971
+ matchPrimaryFrustumIndex(
2972
+ baseFrustumCommandsList,
2973
+ copyFrustumCommands,
2974
+ ) !== frustumIndex
2975
+ ) {
2976
+ continue;
2977
+ }
2978
+
2979
+ if (scene.context.stencilBuffer) {
2980
+ scene._stencilClearCommand.execute(scene.context, passState);
2981
+ }
2982
+
2983
+ performPass(copyFrustumCommands, Pass.TERRAIN_CLASSIFICATION);
2984
+ }
2985
+
2986
+ Cartesian3.clone(positionWC, camera._positionWC);
2987
+ Matrix4.clone(viewMatrix, camera._viewMatrix);
2988
+ Matrix4.clone(invViewMatrix, camera._invViewMatrix);
2989
+ }
2990
+
2991
+ frameState.cullingVolume = cullingVolume;
2992
+ uniformState.update(frameState);
2993
+ uniformState.updateFrustum(frustum);
2994
+ }
2995
+
2621
2996
  /**
2622
2997
  * Execute the draw commands for all the render passes.
2623
2998
  *
@@ -2655,6 +3030,19 @@ function executeCommands(scene, passState) {
2655
3030
  usePostProcessSelected,
2656
3031
  } = scene._environmentState;
2657
3032
 
3033
+ // World copies classify per frustum, inside the loop below, so they see the
3034
+ // live terrain depth of the frustum they belong to - for color and for pick
3035
+ // passes alike, since the primary loop classifies in both. Requires the globe
3036
+ // depth framebuffer: that is what the primary loop packs for the frustum and
3037
+ // what ground classification shaders sample. Everything else (no globe depth,
3038
+ // translucent depth pick) keeps classifying in the copy pass further down.
3039
+ const classifyCopyInline =
3040
+ !renderTranslucentDepthForPick &&
3041
+ useGlobeDepthFramebuffer &&
3042
+ frameState.columbusRepeatWorlds === true &&
3043
+ scene.mode === SceneMode.COLUMBUS_VIEW &&
3044
+ (passes.render || passes.pick || passes.pickVoxel);
3045
+
2658
3046
  const {
2659
3047
  globeDepth,
2660
3048
  globeTranslucencyFramebuffer,
@@ -2733,7 +3121,16 @@ function executeCommands(scene, passState) {
2733
3121
  }
2734
3122
 
2735
3123
  if (useGlobeDepthFramebuffer) {
2736
- globeDepth.executeCopyDepth(context, passState);
3124
+ // Keep one globe depth copy per frustum when the Columbus world copies
3125
+ // are drawn: they re-execute a primary frustum's classification, which
3126
+ // decodes depth against that frustum's near/far, so they need the copy
3127
+ // packed with those same ranges. Without the copies a single shared
3128
+ // texture is enough (and avoids allocating a texture per frustum).
3129
+ globeDepth.executeCopyDepth(
3130
+ context,
3131
+ passState,
3132
+ frameState.columbusRepeatWorlds === true ? index : 0,
3133
+ );
2737
3134
  }
2738
3135
 
2739
3136
  // Draw terrain classification
@@ -2752,6 +3149,19 @@ function executeCommands(scene, passState) {
2752
3149
  }
2753
3150
  }
2754
3151
 
3152
+ // The world copies classify while this frustum's terrain depth is still
3153
+ // live - the depth clear below destroys it, and a restored copy of it is
3154
+ // not good enough for an oblique view.
3155
+ if (classifyCopyInline) {
3156
+ executeColumbusWorldCopyClassification(
3157
+ scene,
3158
+ passState,
3159
+ frustum,
3160
+ index,
3161
+ performPass,
3162
+ );
3163
+ }
3164
+
2755
3165
  if (clearGlobeDepth) {
2756
3166
  clearDepth.execute(context, passState);
2757
3167
  if (useDepthPlane) {
@@ -2865,7 +3275,7 @@ function executeCommands(scene, passState) {
2865
3275
  passState.framebuffer = scene._invertClassification._fbo.framebuffer;
2866
3276
 
2867
3277
  // Draw normally
2868
- commandCount = performPass(frustumCommands, Pass.CESIUM_3D_TILE);
3278
+ performPass(frustumCommands, Pass.CESIUM_3D_TILE);
2869
3279
 
2870
3280
  if (useGlobeDepthFramebuffer) {
2871
3281
  scene._invertClassification.prepareTextures(context);
@@ -2978,6 +3388,20 @@ function executeCommands(scene, passState) {
2978
3388
 
2979
3389
  passState.framebuffer = originalFramebuffer;
2980
3390
  }
3391
+
3392
+ if (
3393
+ frameState.columbusRepeatWorlds === true &&
3394
+ frameState.mode === SceneMode.COLUMBUS_VIEW &&
3395
+ (passes.render || passes.pick || passes.pickVoxel)
3396
+ ) {
3397
+ executeColumbusWorldCopyCommands(
3398
+ scene,
3399
+ passState,
3400
+ frustum,
3401
+ performPass,
3402
+ classifyCopyInline,
3403
+ );
3404
+ }
2981
3405
  }
2982
3406
 
2983
3407
  /**
@@ -3609,10 +4033,9 @@ Scene.prototype.updateEnvironment = function () {
3609
4033
  );
3610
4034
 
3611
4035
  const envMaps = this.specularEnvironmentMaps;
3612
- let specularEnvironmentCubeMap = this._specularEnvironmentCubeMap;
4036
+ const specularEnvironmentCubeMap = this._specularEnvironmentCubeMap;
3613
4037
  if (defined(envMaps) && specularEnvironmentCubeMap?.url !== envMaps) {
3614
- specularEnvironmentCubeMap =
3615
- specularEnvironmentCubeMap && specularEnvironmentCubeMap.destroy();
4038
+ specularEnvironmentCubeMap?.destroy();
3616
4039
  this._specularEnvironmentCubeMap = new SpecularEnvironmentCubeMap(envMaps);
3617
4040
  } else if (!defined(envMaps) && defined(specularEnvironmentCubeMap)) {
3618
4041
  specularEnvironmentCubeMap.destroy();
@@ -10,6 +10,7 @@ import Matrix4 from "../Core/Matrix4.js";
10
10
  import OrthographicFrustum from "../Core/OrthographicFrustum.js";
11
11
  import OrthographicOffCenterFrustum from "../Core/OrthographicOffCenterFrustum.js";
12
12
  import Transforms from "../Core/Transforms.js";
13
+ import ColumbusWorlds from "./ColumbusWorlds.js";
13
14
  import SceneMode from "./SceneMode.js";
14
15
 
15
16
  /**
@@ -132,6 +133,22 @@ SceneTransforms.worldWithEyeOffsetToWindowCoordinates = function (
132
133
  viewport.height = canvas.clientHeight;
133
134
 
134
135
  const camera = scene.camera;
136
+
137
+ if (
138
+ frameState.mode === SceneMode.COLUMBUS_VIEW &&
139
+ frameState.columbusRepeatWorlds === true
140
+ ) {
141
+ // Columbus repeat worlds: rendering replicates the data onto every world
142
+ // copy, so screen-space consumers (HTML overlays, editor handles) must
143
+ // project onto the copy nearest the camera as well. Columbus world
144
+ // coordinates are (height, u, v), so the copy offset applies to y.
145
+ actualPosition.y = ColumbusWorlds.nearestWorldU(
146
+ camera.positionWC.y,
147
+ actualPosition.y,
148
+ scene.mapProjection,
149
+ );
150
+ }
151
+
135
152
  let cameraCentered = false;
136
153
 
137
154
  if (frameState.mode === SceneMode.SCENE2D) {
@@ -20,6 +20,7 @@ import VerticalExaggeration from "../Core/VerticalExaggeration.js";
20
20
  import Transforms from "../Core/Transforms.js";
21
21
  import CameraEventAggregator from "./CameraEventAggregator.js";
22
22
  import CameraEventType from "./CameraEventType.js";
23
+ import ColumbusWorlds from "./ColumbusWorlds.js";
23
24
  import MapMode2D from "./MapMode2D.js";
24
25
  import SceneMode from "./SceneMode.js";
25
26
  import SceneTransforms from "./SceneTransforms.js";
@@ -1959,7 +1960,7 @@ function spin3D(controller, startPosition, movement) {
1959
1960
  scratchMousePosition,
1960
1961
  );
1961
1962
  if (defined(mousePos)) {
1962
- let strafing = false;
1963
+ let strafing;
1963
1964
  const ray = camera.getPickRay(
1964
1965
  movement.startPosition,
1965
1966
  pickGlobeScratchRay,
@@ -2367,7 +2368,6 @@ function zoom3D(controller, startPosition, movement) {
2367
2368
  if (!defined(controller._globe) && defined(distance)) {
2368
2369
  const targetDistance = camera.getMagnitude();
2369
2370
  if (targetDistance < distance) {
2370
- intersection = undefined;
2371
2371
  distance = undefined;
2372
2372
  }
2373
2373
  }
@@ -2993,8 +2993,17 @@ ScreenSpaceCameraController.prototype.onMap = function () {
2993
2993
  const camera = scene.camera;
2994
2994
 
2995
2995
  if (mode === SceneMode.COLUMBUS_VIEW) {
2996
+ let positionX = camera.position.x;
2997
+ if (scene.columbusRepeatWorlds === true) {
2998
+ // Columbus repeat worlds: the camera may sit on any world copy; wrap
2999
+ // its map-u coordinate into the primary world for the bounds check.
3000
+ const worldWidth = ColumbusWorlds.getWorldWidth(scene.mapProjection);
3001
+ positionX =
3002
+ CesiumMath.mod(positionX + worldWidth * 0.5, worldWidth) -
3003
+ worldWidth * 0.5;
3004
+ }
2996
3005
  return (
2997
- Math.abs(camera.position.x) - this._maxCoord.x < 0 &&
3006
+ Math.abs(positionX) - this._maxCoord.x < 0 &&
2998
3007
  Math.abs(camera.position.y) - this._maxCoord.y < 0
2999
3008
  );
3000
3009
  }
@@ -34,6 +34,20 @@ void main(void)
34
34
  float distanceFromStart = czm_planeDistance(v_startPlaneNormalEcAndHalfWidth.xyz, -dot(ecStart, v_startPlaneNormalEcAndHalfWidth.xyz), eyeCoordinate.xyz);
35
35
  float distanceFromEnd = czm_planeDistance(v_endPlaneNormalEcAndBatchId.xyz, -dot(v_endEcAndStartEcX.xyz, v_endPlaneNormalEcAndBatchId.xyz), eyeCoordinate.xyz);
36
36
 
37
+ // HZ patch: in Columbus View ground polylines are tessellated at a fixed
38
+ // ground distance (granularity, ~10 km), so pulling the camera back makes a
39
+ // segment shorter than a pixel while the eye point reconstructed from the
40
+ // globe depth stays only accurate to about a pixel. Both miter planes then
41
+ // land on the same fragment and reject it, and once the whole line is
42
+ // sub-pixel it disappears entirely (visible from ~16M m altitude, worst at
43
+ // the antimeridian seam). Widen the miter planes by a pixel in CV only;
44
+ // neighbouring segments already overlap there, so the joins do not change.
45
+ if (czm_sceneMode == czm_sceneModeColumbusView) {
46
+ float miterTolerance = czm_metersPerPixel(eyeCoordinate);
47
+ distanceFromStart += miterTolerance;
48
+ distanceFromEnd += miterTolerance;
49
+ }
50
+
37
51
  if (abs(widthwiseDistance) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0) {
38
52
  #ifdef DEBUG_SHOW_VOLUME
39
53
  out_FragColor = vec4(1.0, 0.0, 0.0, 0.5);
@@ -34,6 +34,20 @@ void main(void)\n\
34
34
  // Check eye coordinate against the mitering planes\n\
35
35
  float distanceFromStart = czm_planeDistance(v_startPlaneNormalEcAndHalfWidth.xyz, -dot(ecStart, v_startPlaneNormalEcAndHalfWidth.xyz), eyeCoordinate.xyz);\n\
36
36
  float distanceFromEnd = czm_planeDistance(v_endPlaneNormalEcAndBatchId.xyz, -dot(v_endEcAndStartEcX.xyz, v_endPlaneNormalEcAndBatchId.xyz), eyeCoordinate.xyz);\n\
37
+ \n\
38
+ // HZ patch: in Columbus View ground polylines are tessellated at a fixed\n\
39
+ // ground distance (granularity, ~10 km), so pulling the camera back makes a\n\
40
+ // segment shorter than a pixel while the eye point reconstructed from the\n\
41
+ // globe depth stays only accurate to about a pixel. Both miter planes then\n\
42
+ // land on the same fragment and reject it, and once the whole line is\n\
43
+ // sub-pixel it disappears entirely (visible from ~16M m altitude, worst at\n\
44
+ // the antimeridian seam). Widen the miter planes by a pixel in CV only;\n\
45
+ // neighbouring segments already overlap there, so the joins do not change.\n\
46
+ if (czm_sceneMode == czm_sceneModeColumbusView) {\n\
47
+ float miterTolerance = czm_metersPerPixel(eyeCoordinate);\n\
48
+ distanceFromStart += miterTolerance;\n\
49
+ distanceFromEnd += miterTolerance;\n\
50
+ }\n\
37
51
  \n\
38
52
  if (abs(widthwiseDistance) > halfMaxWidth || distanceFromStart < 0.0 || distanceFromEnd < 0.0) {\n\
39
53
  #ifdef DEBUG_SHOW_VOLUME\n\
@@ -104,11 +104,24 @@ void main()
104
104
 
105
105
  #ifdef WIDTH_VARYING
106
106
  float width = czm_batchTable_width(batchId);
107
+ // HZ patch: negative batch width encodes metric sizing (world meters).
108
+ if (width < 0.0)
109
+ {
110
+ vec4 metricPosEC = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position3DHigh, position3DLow);
111
+ width = clamp((-width) / czm_metersPerPixel(metricPosEC), 1.0, 4096.0);
112
+ }
107
113
  float halfWidth = width * 0.5;
108
114
  v_width = width;
109
115
  v_texcoordNormalizationAndHalfWidth.z = halfWidth;
110
116
  #else
111
- float halfWidth = 0.5 * czm_batchTable_width(batchId);
117
+ float width = czm_batchTable_width(batchId);
118
+ // HZ patch: negative batch width encodes metric sizing (world meters).
119
+ if (width < 0.0)
120
+ {
121
+ vec4 metricPosEC = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position3DHigh, position3DLow);
122
+ width = clamp((-width) / czm_metersPerPixel(metricPosEC), 1.0, 4096.0);
123
+ }
124
+ float halfWidth = width * 0.5;
112
125
  v_texcoordNormalizationAndHalfWidth.z = halfWidth;
113
126
  #endif
114
127
 
@@ -105,11 +105,24 @@ void main()\n\
105
105
  \n\
106
106
  #ifdef WIDTH_VARYING\n\
107
107
  float width = czm_batchTable_width(batchId);\n\
108
+ // HZ patch: negative batch width encodes metric sizing (world meters).\n\
109
+ if (width < 0.0)\n\
110
+ {\n\
111
+ vec4 metricPosEC = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position3DHigh, position3DLow);\n\
112
+ width = clamp((-width) / czm_metersPerPixel(metricPosEC), 1.0, 4096.0);\n\
113
+ }\n\
108
114
  float halfWidth = width * 0.5;\n\
109
115
  v_width = width;\n\
110
116
  v_texcoordNormalizationAndHalfWidth.z = halfWidth;\n\
111
117
  #else\n\
112
- float halfWidth = 0.5 * czm_batchTable_width(batchId);\n\
118
+ float width = czm_batchTable_width(batchId);\n\
119
+ // HZ patch: negative batch width encodes metric sizing (world meters).\n\
120
+ if (width < 0.0)\n\
121
+ {\n\
122
+ vec4 metricPosEC = czm_modelViewRelativeToEye * czm_translateRelativeToEye(position3DHigh, position3DLow);\n\
123
+ width = clamp((-width) / czm_metersPerPixel(metricPosEC), 1.0, 4096.0);\n\
124
+ }\n\
125
+ float halfWidth = width * 0.5;\n\
113
126
  v_texcoordNormalizationAndHalfWidth.z = halfWidth;\n\
114
127
  #endif\n\
115
128
  \n\