@cesium/engine 21.0.1 → 22.0.0

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 (244) hide show
  1. package/Build/Workers/{chunk-LPKJSIMD.js → chunk-2D3GLFZD.js} +12 -12
  2. package/Build/Workers/chunk-34YXP222.js +352 -0
  3. package/Build/Workers/{chunk-UKA7OISV.js → chunk-3GL53OCU.js} +17 -18
  4. package/Build/Workers/{chunk-WQZL6CA2.js → chunk-5B7VZQZY.js} +6 -6
  5. package/Build/Workers/{chunk-FE565QHX.js → chunk-5T5SY63I.js} +2 -2
  6. package/Build/Workers/{chunk-RR3NCT4R.js → chunk-5ZF53EY6.js} +4 -4
  7. package/Build/Workers/{chunk-PZUZCAX3.js → chunk-6HBKE43B.js} +7 -7
  8. package/Build/Workers/{chunk-5YXA47QD.js → chunk-726NINF5.js} +16 -16
  9. package/Build/Workers/{chunk-3RKX3UFI.js → chunk-75HAJIDT.js} +1 -1
  10. package/Build/Workers/{chunk-CBPNIEGH.js → chunk-7KAXDKRU.js} +10 -10
  11. package/Build/Workers/chunk-7XEJGXX2.js +2759 -0
  12. package/Build/Workers/{chunk-U2GR5FDL.js → chunk-A4DPJIWS.js} +18 -10
  13. package/Build/Workers/{chunk-G3CKZ34K.js → chunk-A6JAFSZT.js} +5 -5
  14. package/Build/Workers/{chunk-2SI7DEW5.js → chunk-AARZSH4V.js} +5 -5
  15. package/Build/Workers/{chunk-W6KMV4F5.js → chunk-AOM3FHBN.js} +6 -6
  16. package/Build/Workers/{chunk-VVALNLSH.js → chunk-BJSNGGBM.js} +4 -4
  17. package/Build/Workers/{chunk-3YNW5K6O.js → chunk-ECHAJNQJ.js} +12 -12
  18. package/Build/Workers/{chunk-KZ7GDSAJ.js → chunk-EJO3NKMS.js} +1 -1
  19. package/Build/Workers/{chunk-UFSYRVC5.js → chunk-EZ7NJXQN.js} +4 -4
  20. package/Build/Workers/{chunk-P2UKEFCK.js → chunk-FA3SCJDX.js} +2 -2
  21. package/Build/Workers/{chunk-MXOGBWMP.js → chunk-GYRDA3XK.js} +14 -14
  22. package/Build/Workers/{chunk-KVB73ECP.js → chunk-H7B7FU2U.js} +7 -7
  23. package/Build/Workers/{chunk-XJVRYJVZ.js → chunk-IGX772ZQ.js} +1 -1
  24. package/Build/Workers/{chunk-MQPDOWGH.js → chunk-JDJAPBZP.js} +1 -1
  25. package/Build/Workers/{chunk-DDXI6UYW.js → chunk-JZFU2LEM.js} +4 -4
  26. package/Build/Workers/{chunk-DAJKOQW6.js → chunk-KGLDJRSO.js} +9 -9
  27. package/Build/Workers/{chunk-CSDYSXBI.js → chunk-KUB64XW6.js} +11 -11
  28. package/Build/Workers/{chunk-X3ZV3XNO.js → chunk-L7U26FFD.js} +16 -16
  29. package/Build/Workers/{chunk-YZ3RCE45.js → chunk-LEFQBK6H.js} +11 -11
  30. package/Build/Workers/{chunk-KEPPNSBL.js → chunk-LWJXCI3F.js} +5 -5
  31. package/Build/Workers/{chunk-YNOVMSUX.js → chunk-M7HRE4QR.js} +8 -8
  32. package/Build/Workers/{chunk-ASVODZNK.js → chunk-MXHRZHDF.js} +6 -6
  33. package/Build/Workers/{chunk-PJAA7QSC.js → chunk-N52ZRKUE.js} +6 -6
  34. package/Build/Workers/{chunk-W6I3LNPF.js → chunk-NE5JTQJP.js} +7 -7
  35. package/Build/Workers/{chunk-XG54SG4O.js → chunk-NGKF5NTR.js} +5 -5
  36. package/Build/Workers/{chunk-42EJCSIV.js → chunk-NHZNMTIR.js} +5 -5
  37. package/Build/Workers/{chunk-LU7DLY2L.js → chunk-OE22564R.js} +3 -3
  38. package/Build/Workers/{chunk-A2SP2EJZ.js → chunk-OUBBMEWX.js} +6 -6
  39. package/Build/Workers/{chunk-J2XQVKVH.js → chunk-P2H75UV5.js} +67 -33
  40. package/Build/Workers/{chunk-J3KPR6DJ.js → chunk-QCMRUVVF.js} +12 -12
  41. package/Build/Workers/{chunk-RVRN4KDB.js → chunk-QSMIHPUM.js} +2 -2
  42. package/Build/Workers/{chunk-EBDZDBJN.js → chunk-RCCYW6E7.js} +12 -12
  43. package/Build/Workers/{chunk-LV3G32QE.js → chunk-RQRODXVN.js} +4 -4
  44. package/Build/Workers/{chunk-OBS5EMZ6.js → chunk-SZN34LQ5.js} +10 -10
  45. package/Build/Workers/{chunk-IKUOV6DO.js → chunk-U4XMLGSR.js} +7 -7
  46. package/Build/Workers/{chunk-NYYQZ3LH.js → chunk-U7CX3HHE.js} +8 -8
  47. package/Build/Workers/{chunk-QNOIRRJK.js → chunk-VCVA7BMD.js} +4 -4
  48. package/Build/Workers/{chunk-TGJQMEL4.js → chunk-VVZ667H6.js} +2 -2
  49. package/Build/Workers/{chunk-DBHL2UVG.js → chunk-W4PIP5PG.js} +2 -2
  50. package/Build/Workers/{chunk-2QPH5QDK.js → chunk-XPVZWY4A.js} +8 -8
  51. package/Build/Workers/{chunk-4CORGH7N.js → chunk-XQ347YWU.js} +13 -13
  52. package/Build/Workers/{chunk-T7HU6E7G.js → chunk-XZO6F23E.js} +1 -1
  53. package/Build/Workers/{chunk-DTKSVIH5.js → chunk-YLILBDWK.js} +2 -2
  54. package/Build/Workers/{chunk-JG3C3LPY.js → chunk-YLXWC7OW.js} +4 -4
  55. package/Build/Workers/combineGeometry.js +21 -21
  56. package/Build/Workers/createBoxGeometry.js +15 -15
  57. package/Build/Workers/createBoxOutlineGeometry.js +13 -13
  58. package/Build/Workers/createCircleGeometry.js +23 -23
  59. package/Build/Workers/createCircleOutlineGeometry.js +16 -16
  60. package/Build/Workers/createCoplanarPolygonGeometry.js +30 -30
  61. package/Build/Workers/createCoplanarPolygonOutlineGeometry.js +28 -28
  62. package/Build/Workers/createCorridorGeometry.js +26 -26
  63. package/Build/Workers/createCorridorOutlineGeometry.js +25 -25
  64. package/Build/Workers/createCylinderGeometry.js +17 -17
  65. package/Build/Workers/createCylinderOutlineGeometry.js +15 -15
  66. package/Build/Workers/createEllipseGeometry.js +23 -23
  67. package/Build/Workers/createEllipseOutlineGeometry.js +16 -16
  68. package/Build/Workers/createEllipsoidGeometry.js +16 -16
  69. package/Build/Workers/createEllipsoidOutlineGeometry.js +15 -15
  70. package/Build/Workers/createFrustumGeometry.js +15 -15
  71. package/Build/Workers/createFrustumOutlineGeometry.js +15 -15
  72. package/Build/Workers/createGeometry.js +24 -22
  73. package/Build/Workers/createGroundPolylineGeometry.js +19 -19
  74. package/Build/Workers/createPlaneGeometry.js +13 -13
  75. package/Build/Workers/createPlaneOutlineGeometry.js +12 -12
  76. package/Build/Workers/createPolygonGeometry.js +29 -29
  77. package/Build/Workers/createPolygonOutlineGeometry.js +27 -27
  78. package/Build/Workers/createPolylineGeometry.js +22 -22
  79. package/Build/Workers/createPolylineVolumeGeometry.js +28 -28
  80. package/Build/Workers/createPolylineVolumeOutlineGeometry.js +24 -24
  81. package/Build/Workers/createRectangleGeometry.js +24 -24
  82. package/Build/Workers/createRectangleOutlineGeometry.js +17 -17
  83. package/Build/Workers/createSimplePolylineGeometry.js +20 -20
  84. package/Build/Workers/createSphereGeometry.js +16 -16
  85. package/Build/Workers/createSphereOutlineGeometry.js +15 -15
  86. package/Build/Workers/createTaskProcessorWorker.js +3 -3
  87. package/Build/Workers/createVectorTileClampedPolylines.js +12 -12
  88. package/Build/Workers/createVectorTileGeometries.js +21 -21
  89. package/Build/Workers/createVectorTilePoints.js +11 -11
  90. package/Build/Workers/createVectorTilePolygons.js +19 -19
  91. package/Build/Workers/createVectorTilePolylines.js +12 -12
  92. package/Build/Workers/createVerticesFromCesium3DTilesTerrain.js +88 -0
  93. package/Build/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +21 -21
  94. package/Build/Workers/createVerticesFromHeightmap.js +21 -21
  95. package/Build/Workers/createVerticesFromQuantizedTerrainMesh.js +19 -277
  96. package/Build/Workers/createWallGeometry.js +21 -21
  97. package/Build/Workers/createWallOutlineGeometry.js +20 -20
  98. package/Build/Workers/decodeDraco.js +10 -10
  99. package/Build/Workers/decodeGoogleEarthEnterprisePacket.js +5 -5
  100. package/Build/Workers/decodeI3S.js +9 -9
  101. package/Build/Workers/gaussianSplatSorter.js +4 -4
  102. package/Build/Workers/gaussianSplatTextureGenerator.js +4 -4
  103. package/Build/Workers/transcodeKTX2.js +6 -6
  104. package/Build/Workers/transferTypedArrayTest.js +1 -1
  105. package/Build/Workers/upsampleQuantizedTerrainMesh.js +19 -19
  106. package/Build/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +92 -0
  107. package/Source/Core/ArcGISTiledElevationTerrainProvider.js +2 -2
  108. package/Source/Core/Cesium3DTilesTerrainData.js +930 -0
  109. package/Source/Core/Cesium3DTilesTerrainGeometryProcessor.js +1866 -0
  110. package/Source/Core/Cesium3DTilesTerrainProvider.js +1052 -0
  111. package/Source/Core/CesiumTerrainProvider.js +2 -2
  112. package/Source/Core/Color.js +23 -6
  113. package/Source/Core/EllipsoidalOccluder.js +2 -2
  114. package/Source/Core/FeatureDetection.js +0 -2
  115. package/Source/Core/GoogleEarthEnterpriseMetadata.js +1 -1
  116. package/Source/Core/GoogleEarthEnterpriseTerrainData.js +1 -1
  117. package/Source/Core/GoogleEarthEnterpriseTerrainProvider.js +1 -1
  118. package/Source/Core/HeightmapTerrainData.js +10 -22
  119. package/Source/Core/HilbertOrder.js +0 -6
  120. package/Source/Core/ITwinPlatform.js +6 -6
  121. package/Source/Core/Ion.js +1 -1
  122. package/Source/Core/IonResource.js +35 -27
  123. package/Source/Core/PixelFormat.js +2 -2
  124. package/Source/Core/QuantizedMeshTerrainData.js +23 -57
  125. package/Source/Core/Resource.js +15 -11
  126. package/Source/Core/S2Cell.js +6 -18
  127. package/Source/Core/TaskProcessor.js +1 -1
  128. package/Source/Core/TerrainData.js +2 -1
  129. package/Source/Core/TerrainEncoding.js +215 -38
  130. package/Source/Core/TerrainMesh.js +8 -8
  131. package/Source/Core/TerrainProvider.js +151 -0
  132. package/Source/Core/VRTheWorldTerrainProvider.js +2 -2
  133. package/Source/Core/binarySearch.js +1 -1
  134. package/Source/Core/createWorldBathymetryAsync.js +2 -2
  135. package/Source/Core/createWorldTerrainAsync.js +3 -3
  136. package/Source/Core/loadImageFromTypedArray.js +9 -3
  137. package/Source/Core/mergeSort.js +1 -1
  138. package/Source/DataSources/PropertyArray.js +2 -2
  139. package/Source/Renderer/AutomaticUniforms.js +61 -0
  140. package/Source/Renderer/Context.js +13 -7
  141. package/Source/Renderer/Pass.js +10 -9
  142. package/Source/Renderer/UniformState.js +12 -0
  143. package/Source/Renderer/VertexArray.js +1 -1
  144. package/Source/Scene/ArcGisMapServerImageryProvider.js +1 -1
  145. package/Source/Scene/ArcGisMapService.js +1 -1
  146. package/Source/Scene/Azure2DImageryProvider.js +7 -5
  147. package/Source/Scene/BatchTable.js +2 -2
  148. package/Source/Scene/BillboardCollection.js +25 -21
  149. package/Source/Scene/BingMapsImageryProvider.js +1 -1
  150. package/Source/Scene/BoundingVolumeSemantics.js +54 -62
  151. package/Source/Scene/Cesium3DTile.js +3 -3
  152. package/Source/Scene/Cesium3DTilesVoxelProvider.js +1 -1
  153. package/Source/Scene/Cesium3DTileset.js +1 -1
  154. package/Source/Scene/EdgeFramebuffer.js +245 -0
  155. package/Source/Scene/FrameState.js +12 -0
  156. package/Source/Scene/GaussianSplat3DTileContent.js +12 -16
  157. package/Source/Scene/GaussianSplatPrimitive.js +12 -8
  158. package/Source/Scene/GaussianSplatSorter.js +2 -2
  159. package/Source/Scene/Globe.js +1 -1
  160. package/Source/Scene/GlobeSurfaceTile.js +10 -4
  161. package/Source/Scene/GltfBufferViewLoader.js +18 -0
  162. package/Source/Scene/GltfLoader.js +46 -8
  163. package/Source/Scene/Google2DImageryProvider.js +39 -27
  164. package/Source/Scene/GoogleEarthEnterpriseMapsProvider.js +1 -1
  165. package/Source/Scene/I3SDataProvider.js +1 -1
  166. package/Source/Scene/I3SDecoder.js +3 -3
  167. package/Source/Scene/ITwinData.js +4 -4
  168. package/Source/Scene/ImageryLayer.js +1 -1
  169. package/Source/Scene/Implicit3DTileContent.js +18 -7
  170. package/Source/Scene/ImplicitSubtree.js +3 -3
  171. package/Source/Scene/ImplicitTileset.js +2 -2
  172. package/Source/Scene/IonImageryProvider.js +15 -84
  173. package/Source/Scene/IonImageryProviderFactory.js +194 -0
  174. package/Source/Scene/LabelCollection.js +54 -36
  175. package/Source/Scene/MetadataComponentType.js +5 -5
  176. package/Source/Scene/MetadataTableProperty.js +3 -9
  177. package/Source/Scene/Model/EdgeDetectionPipelineStage.js +42 -0
  178. package/Source/Scene/Model/EdgeVisibilityPipelineStage.js +800 -0
  179. package/Source/Scene/Model/MetadataPipelineStage.js +7 -7
  180. package/Source/Scene/Model/ModelDrawCommand.js +69 -0
  181. package/Source/Scene/Model/ModelImagery.js +4 -0
  182. package/Source/Scene/Model/ModelRuntimeNode.js +2 -2
  183. package/Source/Scene/Model/ModelRuntimePrimitive.js +13 -2
  184. package/Source/Scene/Model/ModelSceneGraph.js +15 -3
  185. package/Source/Scene/Model/ModelUtility.js +0 -1
  186. package/Source/Scene/Model/NodeRenderResources.js +1 -1
  187. package/Source/Scene/Model/PrimitiveRenderResources.js +1 -1
  188. package/Source/Scene/OIT.js +11 -1
  189. package/Source/Scene/PickFramebuffer.js +13 -17
  190. package/Source/Scene/Picking.js +104 -80
  191. package/Source/Scene/QuadtreePrimitive.js +34 -26
  192. package/Source/Scene/QuadtreeTile.js +66 -46
  193. package/Source/Scene/ResourceLoaderState.js +1 -1
  194. package/Source/Scene/Scene.js +124 -12
  195. package/Source/Scene/ScreenSpaceCameraController.js +1 -1
  196. package/Source/Scene/SensorVolumePortionToDisplay.js +6 -6
  197. package/Source/Scene/SingleTileImageryProvider.js +3 -3
  198. package/Source/Scene/Terrain.js +5 -5
  199. package/Source/Scene/TileMapServiceImageryProvider.js +2 -2
  200. package/Source/Scene/UrlTemplateImageryProvider.js +1 -1
  201. package/Source/Scene/View.js +3 -0
  202. package/Source/Scene/VoxelPrimitive.js +196 -189
  203. package/Source/Scene/VoxelShape.js +2 -0
  204. package/Source/Scene/createWorldImageryAsync.js +1 -1
  205. package/Source/Shaders/BillboardCollectionFS.glsl +102 -62
  206. package/Source/Shaders/BillboardCollectionFS.js +102 -63
  207. package/Source/Shaders/BillboardCollectionVS.glsl +44 -37
  208. package/Source/Shaders/BillboardCollectionVS.js +45 -38
  209. package/Source/Shaders/Builtin/Constants/passCesium3DTile.glsl +1 -1
  210. package/Source/Shaders/Builtin/Constants/passCesium3DTile.js +1 -1
  211. package/Source/Shaders/Builtin/Constants/passCesium3DTileClassification.glsl +1 -1
  212. package/Source/Shaders/Builtin/Constants/passCesium3DTileClassification.js +1 -1
  213. package/Source/Shaders/Builtin/Constants/passCesium3DTileClassificationIgnoreShow.glsl +1 -1
  214. package/Source/Shaders/Builtin/Constants/passCesium3DTileClassificationIgnoreShow.js +1 -1
  215. package/Source/Shaders/Builtin/Constants/passCesium3DTileEdges.glsl +10 -0
  216. package/Source/Shaders/Builtin/Constants/passCesium3DTileEdges.js +12 -0
  217. package/Source/Shaders/Builtin/Constants/passGaussianSplats.glsl +1 -1
  218. package/Source/Shaders/Builtin/Constants/passGaussianSplats.js +1 -1
  219. package/Source/Shaders/Builtin/Constants/passOpaque.glsl +1 -1
  220. package/Source/Shaders/Builtin/Constants/passOpaque.js +1 -1
  221. package/Source/Shaders/Builtin/Constants/passOverlay.glsl +1 -1
  222. package/Source/Shaders/Builtin/Constants/passOverlay.js +1 -1
  223. package/Source/Shaders/Builtin/Constants/passTranslucent.glsl +1 -1
  224. package/Source/Shaders/Builtin/Constants/passTranslucent.js +1 -1
  225. package/Source/Shaders/Builtin/Constants/passVoxels.glsl +1 -1
  226. package/Source/Shaders/Builtin/Constants/passVoxels.js +1 -1
  227. package/Source/Shaders/Builtin/CzmBuiltins.js +2 -0
  228. package/Source/Shaders/GlobeVS.glsl +1 -1
  229. package/Source/Shaders/GlobeVS.js +1 -1
  230. package/Source/Shaders/Model/EdgeDetectionStageFS.glsl +58 -0
  231. package/Source/Shaders/Model/EdgeDetectionStageFS.js +60 -0
  232. package/Source/Shaders/Model/EdgeVisibilityStageFS.glsl +83 -0
  233. package/Source/Shaders/Model/EdgeVisibilityStageFS.js +85 -0
  234. package/Source/Shaders/Model/ModelFS.glsl +5 -0
  235. package/Source/Shaders/Model/ModelFS.js +5 -0
  236. package/Source/Shaders/Model/PrimitiveOutlineStageFS.glsl +0 -1
  237. package/Source/Shaders/Model/PrimitiveOutlineStageFS.js +0 -1
  238. package/Source/Shaders/ShadowVolumeAppearanceFS.glsl +7 -15
  239. package/Source/Shaders/ShadowVolumeAppearanceFS.js +7 -15
  240. package/Source/Workers/createVerticesFromCesium3DTilesTerrain.js +46 -0
  241. package/Source/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +55 -0
  242. package/index.d.ts +281 -27
  243. package/index.js +200 -190
  244. package/package.json +2 -1
@@ -0,0 +1,800 @@
1
+ import Buffer from "../../Renderer/Buffer.js";
2
+ import BufferUsage from "../../Renderer/BufferUsage.js";
3
+ import VertexArray from "../../Renderer/VertexArray.js";
4
+ import defined from "../../Core/defined.js";
5
+ import IndexDatatype from "../../Core/IndexDatatype.js";
6
+ import ComponentDatatype from "../../Core/ComponentDatatype.js";
7
+ import PrimitiveType from "../../Core/PrimitiveType.js";
8
+ import Cartesian3 from "../../Core/Cartesian3.js";
9
+ import Pass from "../../Renderer/Pass.js";
10
+ import ShaderDestination from "../../Renderer/ShaderDestination.js";
11
+ import EdgeVisibilityStageFS from "../../Shaders/Model/EdgeVisibilityStageFS.js";
12
+ import ModelUtility from "./ModelUtility.js";
13
+ import ModelReader from "./ModelReader.js";
14
+ import VertexAttributeSemantic from "../VertexAttributeSemantic.js";
15
+
16
+ /**
17
+ * Builds derived line geometry for model edges using EXT_mesh_primitive_edge_visibility data.
18
+ * It parses the encoded edge visibility bits, creates a separate edge-domain vertex array with
19
+ * per-edge attributes (edge type, optional feature ID, silhouette normal, adjacent face normals),
20
+ * sets up the required shader defines / varyings, and stores the resulting line list geometry on
21
+ * the render resources for a later edge rendering pass.
22
+ *
23
+ * @namespace EdgeVisibilityPipelineStage
24
+ * @private
25
+ */
26
+ const EdgeVisibilityPipelineStage = {
27
+ name: "EdgeVisibilityPipelineStage",
28
+ };
29
+
30
+ /**
31
+ * Process a primitive to derive edge geometry and shader bindings. This modifies the render resources by:
32
+ * <ul>
33
+ * <li>Adding shader defines (<code>HAS_EDGE_VISIBILITY</code>, <code>HAS_EDGE_VISIBILITY_MRT</code>)</li>
34
+ * <li>Injecting the fragment shader logic that outputs edge color / feature information</li>
35
+ * <li>Adding per-vertex attributes: edge type, optional feature ID, silhouette normal, and adjacent face normals</li>
36
+ * <li>Adding varyings to pass these attributes to the fragment stage</li>
37
+ * <li>Creating and storing a derived line list vertex array in <code>renderResources.edgeGeometry</code></li>
38
+ * </ul>
39
+ * If the primitive does not contain edge visibility data, the function returns early.
40
+ *
41
+ * @param {PrimitiveRenderResources} renderResources The render resources for the primitive
42
+ * @param {ModelComponents.Primitive} primitive The primitive to be rendered
43
+ * @param {FrameState} frameState The frame state
44
+ * @private
45
+ */
46
+ EdgeVisibilityPipelineStage.process = function (
47
+ renderResources,
48
+ primitive,
49
+ frameState,
50
+ ) {
51
+ if (!defined(primitive.edgeVisibility)) {
52
+ return;
53
+ }
54
+
55
+ // Fallback request: mark that edge visibility is needed this frame.
56
+ frameState.edgeVisibilityRequested = true;
57
+
58
+ const shaderBuilder = renderResources.shaderBuilder;
59
+
60
+ // Add shader defines and fragment code
61
+ shaderBuilder.addDefine(
62
+ "HAS_EDGE_VISIBILITY",
63
+ undefined,
64
+ ShaderDestination.BOTH,
65
+ );
66
+ shaderBuilder.addDefine(
67
+ "HAS_EDGE_VISIBILITY_MRT",
68
+ undefined,
69
+ ShaderDestination.FRAGMENT,
70
+ );
71
+ shaderBuilder.addFragmentLines(EdgeVisibilityStageFS);
72
+
73
+ // Add a uniform to distinguish between original geometry pass and edge pass
74
+ shaderBuilder.addUniform("bool", "u_isEdgePass", ShaderDestination.BOTH);
75
+
76
+ // Add edge type attribute and varying
77
+ const edgeTypeLocation = shaderBuilder.addAttribute("float", "a_edgeType");
78
+ shaderBuilder.addVarying("float", "v_edgeType", "flat");
79
+
80
+ // Add edge feature ID attribute and varying
81
+ const edgeFeatureIdLocation = shaderBuilder.addAttribute(
82
+ "float",
83
+ "a_edgeFeatureId",
84
+ );
85
+
86
+ // Add silhouette normal attribute and varying for silhouette edges
87
+ const silhouetteNormalLocation = shaderBuilder.addAttribute(
88
+ "vec3",
89
+ "a_silhouetteNormal",
90
+ );
91
+ shaderBuilder.addVarying("vec3", "v_silhouetteNormalView", "flat");
92
+
93
+ // Add face normal attributes for silhouette detection
94
+ const faceNormalALocation = shaderBuilder.addAttribute(
95
+ "vec3",
96
+ "a_faceNormalA",
97
+ );
98
+ const faceNormalBLocation = shaderBuilder.addAttribute(
99
+ "vec3",
100
+ "a_faceNormalB",
101
+ );
102
+ shaderBuilder.addVarying("vec3", "v_faceNormalAView", "flat");
103
+ shaderBuilder.addVarying("vec3", "v_faceNormalBView", "flat");
104
+
105
+ // Add varying for view space position for perspective-correct silhouette detection
106
+
107
+ // Pass edge type, silhouette normal, and face normals from vertex to fragment shader
108
+ shaderBuilder.addFunctionLines("setDynamicVaryingsVS", [
109
+ "#ifdef HAS_EDGE_VISIBILITY",
110
+ " if (u_isEdgePass) {",
111
+ " v_edgeType = a_edgeType;",
112
+ "#ifdef HAS_EDGE_FEATURE_ID",
113
+ " v_featureId_0 = a_edgeFeatureId;",
114
+ "#endif",
115
+ " // Transform normals from model space to view space",
116
+ " v_silhouetteNormalView = czm_normal * a_silhouetteNormal;",
117
+ " v_faceNormalAView = czm_normal * a_faceNormalA;",
118
+ " v_faceNormalBView = czm_normal * a_faceNormalB;",
119
+ " }",
120
+ "#endif",
121
+ ]);
122
+
123
+ // Build triangle adjacency (mapping edges to adjacent triangles) and compute per-triangle face normals.
124
+ const adjacencyData = buildTriangleAdjacency(primitive);
125
+
126
+ const edgeResult = extractVisibleEdges(primitive);
127
+
128
+ if (
129
+ !defined(edgeResult) ||
130
+ !defined(edgeResult.edgeIndices) ||
131
+ edgeResult.edgeIndices.length === 0
132
+ ) {
133
+ return;
134
+ }
135
+
136
+ // Generate paired face normals for each unique edge (used to classify silhouette edges in the shader).
137
+ const edgeFaceNormals = generateEdgeFaceNormals(
138
+ adjacencyData,
139
+ edgeResult.edgeIndices,
140
+ );
141
+
142
+ // Create edge-domain line list geometry (2 vertices per edge) with all required attributes.
143
+ const edgeGeometry = createCPULineEdgeGeometry(
144
+ edgeResult.edgeIndices,
145
+ edgeResult.edgeData,
146
+ renderResources,
147
+ frameState.context,
148
+ edgeTypeLocation,
149
+ silhouetteNormalLocation,
150
+ faceNormalALocation,
151
+ faceNormalBLocation,
152
+ edgeFeatureIdLocation,
153
+ primitive.edgeVisibility,
154
+ edgeFaceNormals,
155
+ );
156
+
157
+ if (!defined(edgeGeometry)) {
158
+ return;
159
+ }
160
+
161
+ if (edgeGeometry.hasEdgeFeatureIds) {
162
+ shaderBuilder.addDefine(
163
+ "HAS_EDGE_FEATURE_ID",
164
+ undefined,
165
+ ShaderDestination.BOTH,
166
+ );
167
+ }
168
+
169
+ // Set default value for u_isEdgePass uniform (false for original geometry pass). A later pass overrides this.
170
+ renderResources.uniformMap.u_isEdgePass = function () {
171
+ return false;
172
+ };
173
+
174
+ // Store edge geometry metadata so the renderer can issue a separate edges pass.
175
+ renderResources.edgeGeometry = {
176
+ vertexArray: edgeGeometry.vertexArray,
177
+ indexCount: edgeGeometry.indexCount,
178
+ primitiveType: PrimitiveType.LINES,
179
+ pass: Pass.CESIUM_3D_TILE_EDGES,
180
+ };
181
+ };
182
+
183
+ /**
184
+ * Build triangle adjacency information and per-triangle face normals in model space.
185
+ * The adjacency map associates an undirected edge (minIndex,maxIndex) with the indices
186
+ * of up to two adjacent triangles. Face normals are normalized and stored sequentially.
187
+ *
188
+ * @param {ModelComponents.Primitive} primitive The primitive containing triangle index + position data
189
+ * @returns {{edgeMap:Map<string, number[]>, faceNormals:Float32Array, triangleCount:number}}
190
+ * @private
191
+ */
192
+ function buildTriangleAdjacency(primitive) {
193
+ const indices = primitive.indices;
194
+ if (!defined(indices)) {
195
+ return {
196
+ edgeMap: new Map(),
197
+ faceNormals: new Float32Array(0),
198
+ triangleCount: 0,
199
+ };
200
+ }
201
+
202
+ const triangleIndexArray = indices.typedArray;
203
+ const triangleCount = Math.floor(triangleIndexArray.length / 3);
204
+
205
+ // Get vertex positions for face normal calculation
206
+ const positionAttribute = ModelUtility.getAttributeBySemantic(
207
+ primitive,
208
+ VertexAttributeSemantic.POSITION,
209
+ );
210
+
211
+ // Retrieve raw (possibly quantized) position data. If the attribute is quantized
212
+ // we must dequantize on the CPU here because we compute face normals and silhouette
213
+ // classification data before the vertex shader's dequantization stage runs.
214
+ let positions = defined(positionAttribute.typedArray)
215
+ ? positionAttribute.typedArray
216
+ : ModelReader.readAttributeAsTypedArray(positionAttribute);
217
+
218
+ const quantization = positionAttribute.quantization;
219
+ if (defined(quantization) && !quantization.octEncoded) {
220
+ const count = positions.length; // length is 3 * vertexCount
221
+ const dequantized = new Float32Array(count);
222
+ const offset = quantization.quantizedVolumeOffset;
223
+ const step = quantization.quantizedVolumeStepSize;
224
+ for (let i = 0; i < count; i += 3) {
225
+ dequantized[i] = offset.x + positions[i] * step.x;
226
+ dequantized[i + 1] = offset.y + positions[i + 1] * step.y;
227
+ dequantized[i + 2] = offset.z + positions[i + 2] * step.z;
228
+ }
229
+ positions = dequantized;
230
+ }
231
+
232
+ // Build edge map: key = "min,max", value = [triangleA, triangleB?]
233
+ const edgeMap = new Map();
234
+
235
+ // Calculate face normals for each triangle (model space)
236
+ const faceNormals = new Float32Array(triangleCount * 3);
237
+
238
+ // Scratch vectors to avoid heap allocations per triangle
239
+ const scratchP0 = new Cartesian3();
240
+ const scratchP1 = new Cartesian3();
241
+ const scratchP2 = new Cartesian3();
242
+ const scratchE1 = new Cartesian3();
243
+ const scratchE2 = new Cartesian3();
244
+ const scratchCross = new Cartesian3();
245
+
246
+ function processEdge(a, b, triIndex) {
247
+ const edgeKey = `${a < b ? a : b},${a < b ? b : a}`;
248
+ let list = edgeMap.get(edgeKey);
249
+ if (!defined(list)) {
250
+ list = [];
251
+ edgeMap.set(edgeKey, list);
252
+ }
253
+ if (list.length < 2) {
254
+ list.push(triIndex);
255
+ }
256
+ }
257
+
258
+ for (let t = 0; t < triangleCount; t++) {
259
+ const base = t * 3;
260
+ const i0 = triangleIndexArray[base];
261
+ const i1 = triangleIndexArray[base + 1];
262
+ const i2 = triangleIndexArray[base + 2];
263
+
264
+ const i0o = i0 * 3;
265
+ const i1o = i1 * 3;
266
+ const i2o = i2 * 3;
267
+
268
+ scratchP0.x = positions[i0o];
269
+ scratchP0.y = positions[i0o + 1];
270
+ scratchP0.z = positions[i0o + 2];
271
+ scratchP1.x = positions[i1o];
272
+ scratchP1.y = positions[i1o + 1];
273
+ scratchP1.z = positions[i1o + 2];
274
+ scratchP2.x = positions[i2o];
275
+ scratchP2.y = positions[i2o + 1];
276
+ scratchP2.z = positions[i2o + 2];
277
+
278
+ Cartesian3.subtract(scratchP1, scratchP0, scratchE1);
279
+ Cartesian3.subtract(scratchP2, scratchP0, scratchE2);
280
+ Cartesian3.cross(scratchE1, scratchE2, scratchCross);
281
+ Cartesian3.normalize(scratchCross, scratchCross);
282
+
283
+ faceNormals[base] = scratchCross.x;
284
+ faceNormals[base + 1] = scratchCross.y;
285
+ faceNormals[base + 2] = scratchCross.z;
286
+
287
+ // Edges
288
+ processEdge(i0, i1, t);
289
+ processEdge(i1, i2, t);
290
+ processEdge(i2, i0, t);
291
+ }
292
+
293
+ return { edgeMap, faceNormals, triangleCount };
294
+ }
295
+
296
+ /**
297
+ * For each unique edge produce a pair of face normals (A,B). For boundary edges where only a single
298
+ * adjacent triangle exists, the second normal is synthesized as the negation of the first to allow
299
+ * the shader to reason about front/back facing transitions uniformly.
300
+ *
301
+ * @param {{edgeMap:Map<string,number[]>, faceNormals:Float32Array}} adjacencyData The adjacency data from buildTriangleAdjacency
302
+ * @param {number[]} edgeIndices Packed array of 2 vertex indices per edge
303
+ * @returns {Float32Array} Packed array: 6 floats per edge (normalA.xyz, normalB.xyz)
304
+ * @private
305
+ */
306
+ function generateEdgeFaceNormals(adjacencyData, edgeIndices) {
307
+ const { edgeMap, faceNormals } = adjacencyData;
308
+ const numEdges = edgeIndices.length / 2;
309
+
310
+ // Each edge needs 2 face normals (left and right side)
311
+ const edgeFaceNormals = new Float32Array(numEdges * 6); // 2 normals * 3 components each
312
+
313
+ for (let i = 0; i < numEdges; i++) {
314
+ const a = edgeIndices[i * 2];
315
+ const b = edgeIndices[i * 2 + 1];
316
+ const edgeKey = `${a < b ? a : b},${a < b ? b : a}`;
317
+ const triangleList = edgeMap.get(edgeKey);
318
+
319
+ // Expect at least one triangle; silently skip if not found (defensive)
320
+ if (!defined(triangleList) || triangleList.length === 0) {
321
+ continue;
322
+ }
323
+
324
+ const tA = triangleList[0];
325
+ const aBase = tA * 3;
326
+ const nAx = faceNormals[aBase];
327
+ const nAy = faceNormals[aBase + 1];
328
+ const nAz = faceNormals[aBase + 2];
329
+
330
+ let nBx;
331
+ let nBy;
332
+ let nBz;
333
+ if (triangleList.length > 1) {
334
+ const tB = triangleList[1];
335
+ const bBase = tB * 3;
336
+ nBx = faceNormals[bBase];
337
+ nBy = faceNormals[bBase + 1];
338
+ nBz = faceNormals[bBase + 2];
339
+ } else {
340
+ // Boundary edge – synthesize opposite normal
341
+ nBx = -nAx;
342
+ nBy = -nAy;
343
+ nBz = -nAz;
344
+ }
345
+
346
+ const baseIdx = i * 6;
347
+ edgeFaceNormals[baseIdx] = nAx;
348
+ edgeFaceNormals[baseIdx + 1] = nAy;
349
+ edgeFaceNormals[baseIdx + 2] = nAz;
350
+ edgeFaceNormals[baseIdx + 3] = nBx;
351
+ edgeFaceNormals[baseIdx + 4] = nBy;
352
+ edgeFaceNormals[baseIdx + 5] = nBz;
353
+ }
354
+
355
+ return edgeFaceNormals;
356
+ }
357
+
358
+ /**
359
+ * Parse the EXT_mesh_primitive_edge_visibility 2-bit edge encoding and extract
360
+ * a unique set of edges that should be considered for rendering. Edge types:
361
+ * <ul>
362
+ * <li>0 HIDDEN - skipped</li>
363
+ * <li>1 SILHOUETTE - candidates for conditional display based on facing</li>
364
+ * <li>2 HARD - always displayed</li>
365
+ * <li>3 REPEATED - secondary encoding for a hard edge (treated same as 2)</li>
366
+ * </ul>
367
+ * Deduplicates edges shared by adjacent triangles and records per-edge metadata.
368
+ *
369
+ * @param {ModelComponents.Primitive} primitive The primitive with EXT_mesh_primitive_edge_visibility data
370
+ * @returns {{edgeIndices:number[], edgeData:Object[], silhouetteEdgeCount:number}} Edge extraction result
371
+ * @private
372
+ */
373
+ function extractVisibleEdges(primitive) {
374
+ const edgeVisibility = primitive.edgeVisibility;
375
+ const visibility = edgeVisibility.visibility;
376
+ const indices = primitive.indices;
377
+
378
+ if (!defined(visibility) || !defined(indices)) {
379
+ return [];
380
+ }
381
+
382
+ const triangleIndexArray = indices.typedArray;
383
+ const vertexCount = primitive.attributes[0].count;
384
+ const edgeIndices = [];
385
+ const edgeData = [];
386
+ const seenEdgeHashes = new Set();
387
+ let silhouetteEdgeCount = 0;
388
+
389
+ // Process triangles and extract edges (2 bits per edge)
390
+ let edgeIndex = 0;
391
+ const totalIndices = triangleIndexArray.length;
392
+
393
+ for (let i = 0; i + 2 < totalIndices; i += 3) {
394
+ const v0 = triangleIndexArray[i];
395
+ const v1 = triangleIndexArray[i + 1];
396
+ const v2 = triangleIndexArray[i + 2];
397
+ for (let e = 0; e < 3; e++) {
398
+ let a, b;
399
+ if (e === 0) {
400
+ a = v0;
401
+ b = v1;
402
+ } else if (e === 1) {
403
+ a = v1;
404
+ b = v2;
405
+ } else if (e === 2) {
406
+ a = v2;
407
+ b = v0;
408
+ }
409
+ const byteIndex = Math.floor(edgeIndex / 4);
410
+ const bitPairOffset = (edgeIndex % 4) * 2;
411
+ edgeIndex++;
412
+
413
+ if (byteIndex >= visibility.length) {
414
+ break;
415
+ }
416
+
417
+ const byte = visibility[byteIndex];
418
+ const visibility2Bit = (byte >> bitPairOffset) & 0x3;
419
+
420
+ // Only include visible edge types according to EXT_mesh_primitive_edge_visibility spec
421
+ let shouldIncludeEdge = false;
422
+ switch (visibility2Bit) {
423
+ case 0: // HIDDEN - never draw
424
+ shouldIncludeEdge = false;
425
+ break;
426
+ case 1: // SILHOUETTE - conditionally visible (front-facing vs back-facing)
427
+ shouldIncludeEdge = true;
428
+ break;
429
+ case 2: // HARD - always draw (primary encoding)
430
+ shouldIncludeEdge = true;
431
+ break;
432
+ case 3: // REPEATED - always draw (secondary encoding of a hard edge already encoded as 2)
433
+ shouldIncludeEdge = true;
434
+ break;
435
+ }
436
+
437
+ if (shouldIncludeEdge) {
438
+ const small = Math.min(a, b);
439
+ const big = Math.max(a, b);
440
+ const hash = small * vertexCount + big;
441
+
442
+ if (!seenEdgeHashes.has(hash)) {
443
+ seenEdgeHashes.add(hash);
444
+ edgeIndices.push(a, b);
445
+
446
+ let mateVertexIndex = -1;
447
+ if (visibility2Bit === 1) {
448
+ mateVertexIndex = silhouetteEdgeCount;
449
+ silhouetteEdgeCount++;
450
+ }
451
+
452
+ edgeData.push({
453
+ edgeType: visibility2Bit,
454
+ triangleIndex: Math.floor(i / 3),
455
+ edgeIndex: e,
456
+ mateVertexIndex: mateVertexIndex,
457
+ currentTriangleVertices: [v0, v1, v2],
458
+ });
459
+ }
460
+ }
461
+ }
462
+ }
463
+
464
+ return { edgeIndices, edgeData, silhouetteEdgeCount };
465
+ }
466
+
467
+ /**
468
+ * Create a derived line list geometry representing edges. A new vertex domain is used so we can pack
469
+ * per-edge attributes (silhouette normal, face normal pair, edge type, optional feature ID) without
470
+ * modifying or duplicating the original triangle mesh. Two vertices are generated per unique edge.
471
+ *
472
+ * @param {number[]} edgeIndices Packed array [a0,b0, a1,b1, ...] of vertex indices into the source mesh
473
+ * @param {Object[]} edgeData Array of edge metadata including edge type and silhouette normal lookup index
474
+ * @param {PrimitiveRenderResources} renderResources The render resources for the primitive
475
+ * @param {Context} context The WebGL rendering context
476
+ * @param {number} edgeTypeLocation Shader attribute location for the edge type
477
+ * @param {number} silhouetteNormalLocation Shader attribute location for input silhouette normal
478
+ * @param {number} faceNormalALocation Shader attribute location for face normal A
479
+ * @param {number} faceNormalBLocation Shader attribute location for face normal B
480
+ * @param {number} edgeFeatureIdLocation Shader attribute location for optional edge feature ID
481
+ * @param {Object} edgeVisibility Edge visibility extension object (may contain silhouetteNormals[])
482
+ * @param {Float32Array} edgeFaceNormals Packed face normals (6 floats per edge)
483
+ * @returns {Object|undefined} Object with {vertexArray, indexBuffer, indexCount} or undefined on failure
484
+ * @private
485
+ */
486
+ function createCPULineEdgeGeometry(
487
+ edgeIndices,
488
+ edgeData,
489
+ renderResources,
490
+ context,
491
+ edgeTypeLocation,
492
+ silhouetteNormalLocation,
493
+ faceNormalALocation,
494
+ faceNormalBLocation,
495
+ edgeFeatureIdLocation,
496
+ edgeVisibility,
497
+ edgeFaceNormals,
498
+ ) {
499
+ if (!defined(edgeIndices) || edgeIndices.length === 0) {
500
+ return undefined;
501
+ }
502
+
503
+ const numEdges = edgeData.length;
504
+ const vertsPerEdge = 2;
505
+ const totalVerts = numEdges * vertsPerEdge;
506
+
507
+ // Always use location 0 for position to avoid conflicts
508
+ const positionLocation = 0;
509
+
510
+ // Get original vertex positions
511
+ const positionAttribute = ModelUtility.getAttributeBySemantic(
512
+ renderResources.runtimePrimitive.primitive,
513
+ VertexAttributeSemantic.POSITION,
514
+ );
515
+ const srcPos = defined(positionAttribute.typedArray)
516
+ ? positionAttribute.typedArray
517
+ : ModelReader.readAttributeAsTypedArray(positionAttribute);
518
+
519
+ // Create edge-domain vertices (2 per edge)
520
+ const edgePosArray = new Float32Array(totalVerts * 3);
521
+ const edgeTypeArray = new Float32Array(totalVerts);
522
+ const silhouetteNormalArray = new Float32Array(totalVerts * 3);
523
+ const faceNormalAArray = new Float32Array(totalVerts * 3);
524
+ const faceNormalBArray = new Float32Array(totalVerts * 3);
525
+ let p = 0;
526
+
527
+ const maxSrcVertex = srcPos.length / 3 - 1;
528
+
529
+ for (let i = 0; i < numEdges; i++) {
530
+ const a = edgeIndices[i * 2];
531
+ const b = edgeIndices[i * 2 + 1];
532
+
533
+ // Validate vertex indices
534
+ if (a < 0 || b < 0 || a > maxSrcVertex || b > maxSrcVertex) {
535
+ // Fill with zeros to maintain indexing
536
+ edgePosArray[p++] = 0;
537
+ edgePosArray[p++] = 0;
538
+ edgePosArray[p++] = 0;
539
+ edgePosArray[p++] = 0;
540
+ edgePosArray[p++] = 0;
541
+ edgePosArray[p++] = 0;
542
+ edgeTypeArray[i * 2] = 0;
543
+ edgeTypeArray[i * 2 + 1] = 0;
544
+ // Fill with default values
545
+ const normalIdx = i * 2;
546
+ silhouetteNormalArray[normalIdx * 3] = 0;
547
+ silhouetteNormalArray[normalIdx * 3 + 1] = 0;
548
+ silhouetteNormalArray[normalIdx * 3 + 2] = 1;
549
+ silhouetteNormalArray[(normalIdx + 1) * 3] = 0;
550
+ silhouetteNormalArray[(normalIdx + 1) * 3 + 1] = 0;
551
+ silhouetteNormalArray[(normalIdx + 1) * 3 + 2] = 1;
552
+
553
+ // Fill face normals with default values
554
+ faceNormalAArray[normalIdx * 3] = 0;
555
+ faceNormalAArray[normalIdx * 3 + 1] = 0;
556
+ faceNormalAArray[normalIdx * 3 + 2] = 1;
557
+ faceNormalAArray[(normalIdx + 1) * 3] = 0;
558
+ faceNormalAArray[(normalIdx + 1) * 3 + 1] = 0;
559
+ faceNormalAArray[(normalIdx + 1) * 3 + 2] = 1;
560
+
561
+ faceNormalBArray[normalIdx * 3] = 0;
562
+ faceNormalBArray[normalIdx * 3 + 1] = 0;
563
+ faceNormalBArray[normalIdx * 3 + 2] = 1;
564
+ faceNormalBArray[(normalIdx + 1) * 3] = 0;
565
+ faceNormalBArray[(normalIdx + 1) * 3 + 1] = 0;
566
+ faceNormalBArray[(normalIdx + 1) * 3 + 2] = 1;
567
+ continue;
568
+ }
569
+
570
+ const ax = srcPos[a * 3];
571
+ const ay = srcPos[a * 3 + 1];
572
+ const az = srcPos[a * 3 + 2];
573
+ const bx = srcPos[b * 3];
574
+ const by = srcPos[b * 3 + 1];
575
+ const bz = srcPos[b * 3 + 2];
576
+
577
+ // Add edge endpoints
578
+ edgePosArray[p++] = ax;
579
+ edgePosArray[p++] = ay;
580
+ edgePosArray[p++] = az;
581
+ edgePosArray[p++] = bx;
582
+ edgePosArray[p++] = by;
583
+ edgePosArray[p++] = bz;
584
+
585
+ const rawType = edgeData[i].edgeType;
586
+ const t = rawType / 255.0;
587
+
588
+ edgeTypeArray[i * 2] = t;
589
+ edgeTypeArray[i * 2 + 1] = t;
590
+
591
+ // Add silhouette normal for silhouette edges (type 1)
592
+ let normalX = 0,
593
+ normalY = 0,
594
+ normalZ = 1; // Default normal pointing up
595
+
596
+ if (rawType === 1 && defined(edgeVisibility.silhouetteNormals)) {
597
+ const mateVertexIndex = edgeData[i].mateVertexIndex;
598
+ if (
599
+ mateVertexIndex >= 0 &&
600
+ mateVertexIndex < edgeVisibility.silhouetteNormals.length
601
+ ) {
602
+ const silhouetteNormals = edgeVisibility.silhouetteNormals;
603
+ const normal = silhouetteNormals[mateVertexIndex];
604
+
605
+ if (defined(normal)) {
606
+ normalX = normal.x;
607
+ normalY = normal.y;
608
+ normalZ = normal.z;
609
+ }
610
+ }
611
+ }
612
+
613
+ // Set silhouette normal for both edge endpoints
614
+ const normalIdx = i * 2;
615
+ silhouetteNormalArray[normalIdx * 3] = normalX;
616
+ silhouetteNormalArray[normalIdx * 3 + 1] = normalY;
617
+ silhouetteNormalArray[normalIdx * 3 + 2] = normalZ;
618
+ silhouetteNormalArray[(normalIdx + 1) * 3] = normalX;
619
+ silhouetteNormalArray[(normalIdx + 1) * 3 + 1] = normalY;
620
+ silhouetteNormalArray[(normalIdx + 1) * 3 + 2] = normalZ;
621
+
622
+ // Set face normals for both edge endpoints
623
+ const faceNormalIdx = i * 6; // 6 floats per edge (2 normals * 3 components)
624
+ const normalAX = edgeFaceNormals[faceNormalIdx];
625
+ const normalAY = edgeFaceNormals[faceNormalIdx + 1];
626
+ const normalAZ = edgeFaceNormals[faceNormalIdx + 2];
627
+ const normalBX = edgeFaceNormals[faceNormalIdx + 3];
628
+ const normalBY = edgeFaceNormals[faceNormalIdx + 4];
629
+ const normalBZ = edgeFaceNormals[faceNormalIdx + 5];
630
+
631
+ // Face normal A for both endpoints
632
+ faceNormalAArray[normalIdx * 3] = normalAX;
633
+ faceNormalAArray[normalIdx * 3 + 1] = normalAY;
634
+ faceNormalAArray[normalIdx * 3 + 2] = normalAZ;
635
+ faceNormalAArray[(normalIdx + 1) * 3] = normalAX;
636
+ faceNormalAArray[(normalIdx + 1) * 3 + 1] = normalAY;
637
+ faceNormalAArray[(normalIdx + 1) * 3 + 2] = normalAZ;
638
+
639
+ // Face normal B for both endpoints
640
+ faceNormalBArray[normalIdx * 3] = normalBX;
641
+ faceNormalBArray[normalIdx * 3 + 1] = normalBY;
642
+ faceNormalBArray[normalIdx * 3 + 2] = normalBZ;
643
+ faceNormalBArray[(normalIdx + 1) * 3] = normalBX;
644
+ faceNormalBArray[(normalIdx + 1) * 3 + 1] = normalBY;
645
+ faceNormalBArray[(normalIdx + 1) * 3 + 2] = normalBZ;
646
+ }
647
+
648
+ // Create vertex buffers
649
+ const edgePosBuffer = Buffer.createVertexBuffer({
650
+ context,
651
+ typedArray: edgePosArray,
652
+ usage: BufferUsage.STATIC_DRAW,
653
+ });
654
+ const edgeTypeBuffer = Buffer.createVertexBuffer({
655
+ context,
656
+ typedArray: edgeTypeArray,
657
+ usage: BufferUsage.STATIC_DRAW,
658
+ });
659
+ const silhouetteNormalBuffer = Buffer.createVertexBuffer({
660
+ context,
661
+ typedArray: silhouetteNormalArray,
662
+ usage: BufferUsage.STATIC_DRAW,
663
+ });
664
+ const faceNormalABuffer = Buffer.createVertexBuffer({
665
+ context,
666
+ typedArray: faceNormalAArray,
667
+ usage: BufferUsage.STATIC_DRAW,
668
+ });
669
+ const faceNormalBBuffer = Buffer.createVertexBuffer({
670
+ context,
671
+ typedArray: faceNormalBArray,
672
+ usage: BufferUsage.STATIC_DRAW,
673
+ });
674
+
675
+ // Create sequential indices for line pairs
676
+ const useU32 = totalVerts > 65534;
677
+ const idx = new Array(totalVerts);
678
+ for (let i = 0; i < totalVerts; i++) {
679
+ idx[i] = i;
680
+ }
681
+
682
+ const indexBuffer = Buffer.createIndexBuffer({
683
+ context,
684
+ typedArray: useU32 ? new Uint32Array(idx) : new Uint16Array(idx),
685
+ usage: BufferUsage.STATIC_DRAW,
686
+ indexDatatype: useU32
687
+ ? IndexDatatype.UNSIGNED_INT
688
+ : IndexDatatype.UNSIGNED_SHORT,
689
+ });
690
+
691
+ // Create vertex array with position, edge type, silhouette normal, and face normal attributes
692
+ const attributes = [
693
+ {
694
+ index: positionLocation,
695
+ vertexBuffer: edgePosBuffer,
696
+ componentsPerAttribute: 3,
697
+ componentDatatype: ComponentDatatype.FLOAT,
698
+ normalize: false,
699
+ },
700
+ {
701
+ index: edgeTypeLocation,
702
+ vertexBuffer: edgeTypeBuffer,
703
+ componentsPerAttribute: 1,
704
+ componentDatatype: ComponentDatatype.FLOAT,
705
+ normalize: false,
706
+ },
707
+ {
708
+ index: silhouetteNormalLocation,
709
+ vertexBuffer: silhouetteNormalBuffer,
710
+ componentsPerAttribute: 3,
711
+ componentDatatype: ComponentDatatype.FLOAT,
712
+ normalize: false,
713
+ },
714
+ {
715
+ index: faceNormalALocation,
716
+ vertexBuffer: faceNormalABuffer,
717
+ componentsPerAttribute: 3,
718
+ componentDatatype: ComponentDatatype.FLOAT,
719
+ normalize: false,
720
+ },
721
+ {
722
+ index: faceNormalBLocation,
723
+ vertexBuffer: faceNormalBBuffer,
724
+ componentsPerAttribute: 3,
725
+ componentDatatype: ComponentDatatype.FLOAT,
726
+ normalize: false,
727
+ },
728
+ ];
729
+
730
+ // Get feature ID from original geometry
731
+ const primitive = renderResources.runtimePrimitive.primitive;
732
+ const getFeatureIdForEdge = function () {
733
+ // Try to get the first feature ID from the original primitive
734
+ if (defined(primitive.featureIds) && primitive.featureIds.length > 0) {
735
+ const firstFeatureIdSet = primitive.featureIds[0];
736
+
737
+ // Handle FeatureIdAttribute objects directly using setIndex
738
+ if (defined(firstFeatureIdSet.setIndex)) {
739
+ const featureIdAttribute = primitive.attributes.find(
740
+ (attr) =>
741
+ attr.semantic === VertexAttributeSemantic.FEATURE_ID &&
742
+ attr.setIndex === firstFeatureIdSet.setIndex,
743
+ );
744
+
745
+ if (defined(featureIdAttribute)) {
746
+ const featureIds = defined(featureIdAttribute.typedArray)
747
+ ? featureIdAttribute.typedArray
748
+ : ModelReader.readAttributeAsTypedArray(featureIdAttribute);
749
+
750
+ // Create edge feature ID buffer based on edge indices
751
+ const edgeFeatureIds = new Float32Array(totalVerts);
752
+ for (let i = 0; i < numEdges; i++) {
753
+ const a = edgeIndices[i * 2];
754
+ const featureId = a < featureIds.length ? featureIds[a] : 0;
755
+ edgeFeatureIds[i * 2] = featureId;
756
+ edgeFeatureIds[i * 2 + 1] = featureId;
757
+ }
758
+
759
+ return edgeFeatureIds;
760
+ }
761
+ }
762
+ }
763
+
764
+ return undefined;
765
+ };
766
+
767
+ const edgeFeatureIds = getFeatureIdForEdge();
768
+ const hasEdgeFeatureIds = defined(edgeFeatureIds);
769
+
770
+ if (hasEdgeFeatureIds) {
771
+ const edgeFeatureIdBuffer = Buffer.createVertexBuffer({
772
+ context,
773
+ typedArray: edgeFeatureIds,
774
+ usage: BufferUsage.STATIC_DRAW,
775
+ });
776
+
777
+ attributes.push({
778
+ index: edgeFeatureIdLocation,
779
+ vertexBuffer: edgeFeatureIdBuffer,
780
+ componentsPerAttribute: 1,
781
+ componentDatatype: ComponentDatatype.FLOAT,
782
+ normalize: false,
783
+ });
784
+ }
785
+
786
+ const vertexArray = new VertexArray({ context, indexBuffer, attributes });
787
+
788
+ if (!vertexArray || totalVerts === 0 || totalVerts % 2 !== 0) {
789
+ return undefined;
790
+ }
791
+
792
+ return {
793
+ vertexArray,
794
+ indexBuffer,
795
+ indexCount: totalVerts,
796
+ hasEdgeFeatureIds,
797
+ };
798
+ }
799
+
800
+ export default EdgeVisibilityPipelineStage;