@cesium/engine 23.0.0 → 24.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 (226) hide show
  1. package/Build/Workers/{chunk-QTOHIXGF.js → chunk-2N4KDHS3.js} +10 -10
  2. package/Build/Workers/{chunk-SQTRWKO2.js → chunk-44EMEGKW.js} +5 -5
  3. package/Build/Workers/{chunk-OHBM4YG4.js → chunk-4TIG36AW.js} +12 -12
  4. package/Build/Workers/{chunk-BNBD2YZK.js → chunk-5A2SW66E.js} +16 -16
  5. package/Build/Workers/{chunk-52K3GJKG.js → chunk-5GXYJQ2K.js} +3 -3
  6. package/Build/Workers/{chunk-NQCAZU7S.js → chunk-63SIWPLS.js} +13 -13
  7. package/Build/Workers/{chunk-UEEGWJJP.js → chunk-6MPGQEHA.js} +2 -2
  8. package/Build/Workers/{chunk-PQVNT5CJ.js → chunk-6RMS4S72.js} +6 -6
  9. package/Build/Workers/{chunk-HYTFXWMY.js → chunk-6WLHJFD3.js} +7 -7
  10. package/Build/Workers/{chunk-DT5SJBKA.js → chunk-72VYSVEW.js} +9 -9
  11. package/Build/Workers/{chunk-JUGY5AZN.js → chunk-7MDYBRYJ.js} +4 -4
  12. package/Build/Workers/{chunk-6SLGTBB2.js → chunk-7O5XJTVE.js} +5 -5
  13. package/Build/Workers/{chunk-4UNQX2MP.js → chunk-7QRFIKWQ.js} +2 -2
  14. package/Build/Workers/{chunk-GN5NEUDT.js → chunk-7TYLCBGK.js} +8 -8
  15. package/Build/Workers/{chunk-SGKTBHFQ.js → chunk-7X322WP2.js} +13 -13
  16. package/Build/Workers/{chunk-T43FXHNC.js → chunk-A47HVBC5.js} +7 -7
  17. package/Build/Workers/{chunk-FSDVXW7M.js → chunk-AF3MU2RL.js} +5 -5
  18. package/Build/Workers/{chunk-3B65YFXC.js → chunk-AJLTU6PF.js} +1 -1
  19. package/Build/Workers/{chunk-OQ3EAJNW.js → chunk-AMIHNSST.js} +2 -2
  20. package/Build/Workers/{chunk-N4AWE7TZ.js → chunk-B6SCUUFP.js} +10 -10
  21. package/Build/Workers/{chunk-OMXN7YF4.js → chunk-BJD6QGXZ.js} +12 -12
  22. package/Build/Workers/{chunk-APDKMX7W.js → chunk-C6P4IG5P.js} +6 -6
  23. package/Build/Workers/{chunk-QA24Q62E.js → chunk-D4EJ2NXG.js} +17 -17
  24. package/Build/Workers/{chunk-HYLEKKO5.js → chunk-DHKPFLSJ.js} +1 -1
  25. package/Build/Workers/{chunk-MQJG522T.js → chunk-DVB56LG7.js} +6 -6
  26. package/Build/Workers/{chunk-Y5AJSEEN.js → chunk-EDXL4W76.js} +2 -2
  27. package/Build/Workers/{chunk-QQX7S5QT.js → chunk-EV3VGO6Y.js} +17 -17
  28. package/Build/Workers/{chunk-IFA7VHHP.js → chunk-GOQBUCXH.js} +10 -10
  29. package/Build/Workers/{chunk-5A7YSAXT.js → chunk-GQUNK2XC.js} +14 -14
  30. package/Build/Workers/{chunk-36LBFITA.js → chunk-HKUES6UG.js} +12 -12
  31. package/Build/Workers/{chunk-OC6X22PG.js → chunk-HRHRYFHS.js} +9 -9
  32. package/Build/Workers/{chunk-L7RDJ4V7.js → chunk-HXJMS2ZI.js} +1 -1
  33. package/Build/Workers/{chunk-NAYRNPEP.js → chunk-ILMFADZA.js} +4 -4
  34. package/Build/Workers/{chunk-AA33B6PU.js → chunk-ISY4EZCA.js} +4 -4
  35. package/Build/Workers/{chunk-7KRN6JPQ.js → chunk-J6AW5YHS.js} +14 -14
  36. package/Build/Workers/{chunk-OQBPOKPZ.js → chunk-JCI63T3K.js} +6 -6
  37. package/Build/Workers/{chunk-2WI4HGJU.js → chunk-KLPGTXS3.js} +6 -6
  38. package/Build/Workers/{chunk-5R7SBUTW.js → chunk-LIB2H534.js} +10 -10
  39. package/Build/Workers/{chunk-IYVDOB5V.js → chunk-LXMCPFFS.js} +4 -4
  40. package/Build/Workers/{chunk-NUYLNQKV.js → chunk-MKM27KGV.js} +2 -2
  41. package/Build/Workers/{chunk-R7M4XNPP.js → chunk-N65BAKSH.js} +8 -8
  42. package/Build/Workers/{chunk-4LGFX2P5.js → chunk-NKT5K3K3.js} +11 -11
  43. package/Build/Workers/{chunk-NNHU33JK.js → chunk-NPF7SSHZ.js} +7 -7
  44. package/Build/Workers/{chunk-LDQWNMEC.js → chunk-PVV64VFW.js} +1 -1
  45. package/Build/Workers/{chunk-WKXDCVBO.js → chunk-Q3C6VQH6.js} +4 -4
  46. package/Build/Workers/{chunk-ARCFIOKA.js → chunk-RQNJD3O4.js} +12 -12
  47. package/Build/Workers/{chunk-G3YT7M7A.js → chunk-UIGVOKKI.js} +1 -1
  48. package/Build/Workers/{chunk-ZIW5FCIW.js → chunk-UQU4RXLT.js} +27 -19
  49. package/Build/Workers/{chunk-MT5M7RLZ.js → chunk-VLDEUTOP.js} +2 -2
  50. package/Build/Workers/{chunk-5JP5FG7U.js → chunk-VMVOUQ5J.js} +7 -7
  51. package/Build/Workers/{chunk-2GL2EUEE.js → chunk-XJRBB253.js} +11 -11
  52. package/Build/Workers/{chunk-ESD4IEOU.js → chunk-XY6HMDGI.js} +5 -5
  53. package/Build/Workers/{chunk-56OHFSLH.js → chunk-Y7HNNPNK.js} +10 -10
  54. package/Build/Workers/{chunk-6FVFM2KB.js → chunk-YAOZMOFL.js} +5 -5
  55. package/Build/Workers/{chunk-AP6XWET5.js → chunk-YJZGW74L.js} +8 -8
  56. package/Build/Workers/{chunk-KPYU6YZG.js → chunk-YQFTYQMZ.js} +1 -1
  57. package/Build/Workers/{chunk-5VKZVPO5.js → chunk-ZBZRTRJB.js} +10 -10
  58. package/Build/Workers/combineGeometry.js +24 -24
  59. package/Build/Workers/createBoxGeometry.js +18 -18
  60. package/Build/Workers/createBoxOutlineGeometry.js +16 -16
  61. package/Build/Workers/createCircleGeometry.js +26 -26
  62. package/Build/Workers/createCircleOutlineGeometry.js +19 -19
  63. package/Build/Workers/createCoplanarPolygonGeometry.js +33 -33
  64. package/Build/Workers/createCoplanarPolygonOutlineGeometry.js +31 -31
  65. package/Build/Workers/createCorridorGeometry.js +29 -29
  66. package/Build/Workers/createCorridorOutlineGeometry.js +28 -28
  67. package/Build/Workers/createCylinderGeometry.js +20 -20
  68. package/Build/Workers/createCylinderOutlineGeometry.js +18 -18
  69. package/Build/Workers/createEllipseGeometry.js +26 -26
  70. package/Build/Workers/createEllipseOutlineGeometry.js +19 -19
  71. package/Build/Workers/createEllipsoidGeometry.js +19 -19
  72. package/Build/Workers/createEllipsoidOutlineGeometry.js +18 -18
  73. package/Build/Workers/createFrustumGeometry.js +18 -18
  74. package/Build/Workers/createFrustumOutlineGeometry.js +18 -18
  75. package/Build/Workers/createGeometry.js +24 -24
  76. package/Build/Workers/createGroundPolylineGeometry.js +22 -22
  77. package/Build/Workers/createPlaneGeometry.js +16 -16
  78. package/Build/Workers/createPlaneOutlineGeometry.js +15 -15
  79. package/Build/Workers/createPolygonGeometry.js +32 -32
  80. package/Build/Workers/createPolygonOutlineGeometry.js +30 -30
  81. package/Build/Workers/createPolylineGeometry.js +25 -25
  82. package/Build/Workers/createPolylineVolumeGeometry.js +31 -31
  83. package/Build/Workers/createPolylineVolumeOutlineGeometry.js +27 -27
  84. package/Build/Workers/createRectangleGeometry.js +27 -27
  85. package/Build/Workers/createRectangleOutlineGeometry.js +20 -20
  86. package/Build/Workers/createSimplePolylineGeometry.js +23 -23
  87. package/Build/Workers/createSphereGeometry.js +19 -19
  88. package/Build/Workers/createSphereOutlineGeometry.js +18 -18
  89. package/Build/Workers/createTaskProcessorWorker.js +3 -3
  90. package/Build/Workers/createVectorTileClampedPolylines.js +14 -14
  91. package/Build/Workers/createVectorTileGeometries.js +24 -24
  92. package/Build/Workers/createVectorTilePoints.js +13 -13
  93. package/Build/Workers/createVectorTilePolygons.js +22 -22
  94. package/Build/Workers/createVectorTilePolylines.js +14 -14
  95. package/Build/Workers/createVerticesFromCesium3DTilesTerrain.js +25 -25
  96. package/Build/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +22 -22
  97. package/Build/Workers/createVerticesFromHeightmap.js +22 -22
  98. package/Build/Workers/createVerticesFromQuantizedTerrainMesh.js +20 -20
  99. package/Build/Workers/createWallGeometry.js +25 -25
  100. package/Build/Workers/createWallOutlineGeometry.js +24 -24
  101. package/Build/Workers/decodeDraco.js +10 -10
  102. package/Build/Workers/decodeGoogleEarthEnterprisePacket.js +5 -5
  103. package/Build/Workers/decodeI3S.js +12 -12
  104. package/Build/Workers/gaussianSplatSorter.js +4 -4
  105. package/Build/Workers/gaussianSplatTextureGenerator.js +4 -4
  106. package/Build/Workers/incrementallyBuildTerrainPicker.js +10 -10
  107. package/Build/Workers/transcodeKTX2.js +6 -6
  108. package/Build/Workers/transferTypedArrayTest.js +1 -1
  109. package/Build/Workers/upsampleQuantizedTerrainMesh.js +22 -22
  110. package/Build/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +25 -25
  111. package/Source/Core/BoundingSphere.js +1 -1
  112. package/Source/Core/Cartesian2.js +7 -5
  113. package/Source/Core/Cartesian3.js +8 -6
  114. package/Source/Core/Cartesian4.js +8 -6
  115. package/Source/Core/Color.js +5 -3
  116. package/Source/Core/DeveloperError.js +2 -1
  117. package/Source/Core/ITwinPlatform.js +1 -0
  118. package/Source/Core/Ion.js +1 -1
  119. package/Source/Core/S2Cell.js +2 -2
  120. package/Source/Core/TerrainMesh.js +10 -1
  121. package/Source/Core/TerrainPicker.js +1 -0
  122. package/Source/Core/WallGeometry.js +1 -1
  123. package/Source/Core/WallOutlineGeometry.js +1 -1
  124. package/Source/Core/assert.js +27 -0
  125. package/Source/Core/globalTypes.js +4 -0
  126. package/Source/DataSources/CzmlDataSource.js +9 -0
  127. package/Source/DataSources/PathGraphics.js +13 -0
  128. package/Source/DataSources/PathVisualizer.js +330 -18
  129. package/Source/DataSources/StaticGeometryColorBatch.js +7 -2
  130. package/Source/DataSources/StaticGeometryPerMaterialBatch.js +7 -2
  131. package/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js +3 -1
  132. package/Source/DataSources/StaticGroundPolylinePerMaterialBatch.js +3 -1
  133. package/Source/Renderer/Context.js +1 -1
  134. package/Source/Renderer/RenderState.js +2 -2
  135. package/Source/Renderer/VertexArray.js +80 -0
  136. package/Source/Scene/ArcGisMapService.js +1 -1
  137. package/Source/Scene/BillboardCollection.js +65 -364
  138. package/Source/Scene/BufferPoint.js +148 -0
  139. package/Source/Scene/BufferPointCollection.js +135 -0
  140. package/Source/Scene/BufferPointMaterial.js +95 -0
  141. package/Source/Scene/BufferPolygon.js +516 -0
  142. package/Source/Scene/BufferPolygonCollection.js +368 -0
  143. package/Source/Scene/BufferPolygonMaterial.js +41 -0
  144. package/Source/Scene/BufferPolyline.js +185 -0
  145. package/Source/Scene/BufferPolylineCollection.js +136 -0
  146. package/Source/Scene/BufferPolylineMaterial.js +93 -0
  147. package/Source/Scene/BufferPrimitive.js +347 -0
  148. package/Source/Scene/BufferPrimitiveCollection.js +702 -0
  149. package/Source/Scene/BufferPrimitiveMaterial.js +140 -0
  150. package/Source/Scene/Cesium3DTileContent.js +5 -1
  151. package/Source/Scene/Cesium3DTileContentFactory.js +9 -0
  152. package/Source/Scene/Cesium3DTileStyle.js +22 -0
  153. package/Source/Scene/Cesium3DTileset.js +1 -0
  154. package/Source/Scene/ClippingPolygonCollection.js +270 -101
  155. package/Source/Scene/CubeMapPanorama.js +4 -4
  156. package/Source/Scene/GaussianSplat3DTileContent.js +40 -46
  157. package/Source/Scene/GaussianSplatPrimitive.js +413 -70
  158. package/Source/Scene/GetFeatureInfoFormat.js +3 -3
  159. package/Source/Scene/GltfLoader.js +227 -34
  160. package/Source/Scene/GltfSpzLoader.js +109 -0
  161. package/Source/Scene/ITwinData.js +1 -0
  162. package/Source/Scene/Label.js +32 -0
  163. package/Source/Scene/LabelCollection.js +14 -13
  164. package/Source/Scene/Material.js +2 -0
  165. package/Source/Scene/Megatexture.js +1 -0
  166. package/Source/Scene/MetadataClassProperty.js +24 -10
  167. package/Source/Scene/MetadataComponentType.js +115 -23
  168. package/Source/Scene/Model/CustomShaderPipelineStage.js +3 -1
  169. package/Source/Scene/Model/EdgeVisibilityPipelineStage.js +761 -263
  170. package/Source/Scene/Model/MaterialPipelineStage.js +147 -0
  171. package/Source/Scene/Model/MetadataPipelineStage.js +3 -3
  172. package/Source/Scene/Model/ModelDrawCommand.js +6 -0
  173. package/Source/Scene/Model/ModelReader.js +1 -1
  174. package/Source/Scene/Model/ModelUtility.js +3 -1
  175. package/Source/Scene/Model/createVectorTileBuffersFromModelComponents.js +330 -0
  176. package/Source/Scene/ModelComponents.js +1632 -1542
  177. package/Source/Scene/PickedMetadataInfo.js +1 -0
  178. package/Source/Scene/ScreenSpaceCameraController.js +13 -0
  179. package/Source/Scene/SkyBox.js +17 -8
  180. package/Source/Scene/TileBoundingRegion.js +1 -1
  181. package/Source/Scene/TileBoundingS2Cell.js +1 -1
  182. package/Source/Scene/TileBoundingSphere.js +1 -1
  183. package/Source/Scene/TweenCollection.js +7 -0
  184. package/Source/Scene/VectorGltf3DTileContent.js +382 -0
  185. package/Source/Scene/VertexAttributeSemantic.js +14 -0
  186. package/Source/Scene/WebMapServiceImageryProvider.js +2 -2
  187. package/Source/Scene/WebMapTileServiceImageryProvider.js +366 -131
  188. package/Source/Scene/parseStructuralMetadata.js +97 -37
  189. package/Source/Scene/renderBufferPointCollection.js +334 -0
  190. package/Source/Scene/renderBufferPolygonCollection.js +386 -0
  191. package/Source/Scene/renderBufferPolylineCollection.js +529 -0
  192. package/Source/Shaders/BillboardCollectionFS.glsl +2 -77
  193. package/Source/Shaders/BillboardCollectionFS.js +2 -77
  194. package/Source/Shaders/BillboardCollectionVS.glsl +2 -60
  195. package/Source/Shaders/BillboardCollectionVS.js +2 -60
  196. package/Source/Shaders/BufferPointMaterialFS.glsl +27 -0
  197. package/Source/Shaders/BufferPointMaterialFS.js +29 -0
  198. package/Source/Shaders/BufferPointMaterialVS.glsl +48 -0
  199. package/Source/Shaders/BufferPointMaterialVS.js +50 -0
  200. package/Source/Shaders/BufferPolygonMaterialFS.glsl +13 -0
  201. package/Source/Shaders/BufferPolygonMaterialFS.js +15 -0
  202. package/Source/Shaders/BufferPolygonMaterialVS.glsl +30 -0
  203. package/Source/Shaders/BufferPolygonMaterialVS.js +32 -0
  204. package/Source/Shaders/BufferPolylineMaterialFS.glsl +13 -0
  205. package/Source/Shaders/BufferPolylineMaterialFS.js +15 -0
  206. package/Source/Shaders/BufferPolylineMaterialVS.glsl +49 -0
  207. package/Source/Shaders/BufferPolylineMaterialVS.js +51 -0
  208. package/Source/Shaders/Model/EdgeVisibilityStageFS.glsl +34 -48
  209. package/Source/Shaders/Model/EdgeVisibilityStageFS.js +34 -49
  210. package/Source/Shaders/Model/EdgeVisibilityStageVS.glsl +98 -0
  211. package/Source/Shaders/Model/EdgeVisibilityStageVS.js +100 -0
  212. package/Source/Shaders/Model/ModelFS.glsl +15 -0
  213. package/Source/Shaders/Model/ModelFS.js +15 -0
  214. package/Source/Shaders/Model/ModelVS.glsl +8 -0
  215. package/Source/Shaders/Model/ModelVS.js +8 -0
  216. package/Source/Shaders/PolygonSignedDistanceFS.glsl +64 -33
  217. package/Source/Shaders/PolygonSignedDistanceFS.js +64 -33
  218. package/Source/Shaders/PrimitiveGaussianSplatVS.glsl +9 -2
  219. package/Source/Shaders/PrimitiveGaussianSplatVS.js +9 -2
  220. package/Source/Shaders/Voxels/VoxelFS.glsl +1 -1
  221. package/Source/Shaders/Voxels/VoxelFS.js +1 -1
  222. package/Source/Workers/decodeDraco.js +3 -0
  223. package/Source/Workers/decodeI3S.js +5 -2
  224. package/index.d.ts +1007 -111
  225. package/index.js +368 -343
  226. package/package.json +2 -2
@@ -5,13 +5,17 @@ import defined from "../../Core/defined.js";
5
5
  import IndexDatatype from "../../Core/IndexDatatype.js";
6
6
  import ComponentDatatype from "../../Core/ComponentDatatype.js";
7
7
  import PrimitiveType from "../../Core/PrimitiveType.js";
8
+ import Cartesian2 from "../../Core/Cartesian2.js";
8
9
  import Cartesian3 from "../../Core/Cartesian3.js";
10
+ import AttributeCompression from "../../Core/AttributeCompression.js";
9
11
  import Pass from "../../Renderer/Pass.js";
10
12
  import ShaderDestination from "../../Renderer/ShaderDestination.js";
11
13
  import EdgeVisibilityStageFS from "../../Shaders/Model/EdgeVisibilityStageFS.js";
14
+ import EdgeVisibilityStageVS from "../../Shaders/Model/EdgeVisibilityStageVS.js";
12
15
  import ModelUtility from "./ModelUtility.js";
13
16
  import ModelReader from "./ModelReader.js";
14
17
  import VertexAttributeSemantic from "../VertexAttributeSemantic.js";
18
+ import AttributeType from "../AttributeType.js";
15
19
 
16
20
  /**
17
21
  * Builds derived line geometry for model edges using EXT_mesh_primitive_edge_visibility data.
@@ -69,10 +73,18 @@ EdgeVisibilityPipelineStage.process = function (
69
73
  ShaderDestination.FRAGMENT,
70
74
  );
71
75
  shaderBuilder.addFragmentLines(EdgeVisibilityStageFS);
76
+ shaderBuilder.addVertexLines(EdgeVisibilityStageVS);
72
77
 
73
78
  // Add a uniform to distinguish between original geometry pass and edge pass
74
79
  shaderBuilder.addUniform("bool", "u_isEdgePass", ShaderDestination.BOTH);
75
80
 
81
+ // Add line width uniform for edge expansion if not already added by MaterialPipelineStage
82
+ const material = primitive.material;
83
+ const lineStyle = defined(material) ? material.lineStyle : undefined;
84
+ if (!defined(lineStyle) || !defined(lineStyle.width)) {
85
+ shaderBuilder.addUniform("float", "u_lineWidth", ShaderDestination.VERTEX);
86
+ }
87
+
76
88
  // Add edge type attribute and varying
77
89
  const edgeTypeLocation = shaderBuilder.addAttribute("float", "a_edgeType");
78
90
  shaderBuilder.addVarying("float", "v_edgeType", "flat");
@@ -102,23 +114,21 @@ EdgeVisibilityPipelineStage.process = function (
102
114
  shaderBuilder.addVarying("vec3", "v_faceNormalAView", "flat");
103
115
  shaderBuilder.addVarying("vec3", "v_faceNormalBView", "flat");
104
116
 
105
- // Add varying for view space position for perspective-correct silhouette detection
117
+ // Add varying for silhouette discard decision in vertex shader
118
+ shaderBuilder.addVarying("float", "v_shouldDiscard", "flat");
119
+
120
+ // Add attributes and varyings for quad-based wide line rendering
121
+ const edgeOtherPosLocation = shaderBuilder.addAttribute(
122
+ "vec3",
123
+ "a_edgeOtherPos",
124
+ );
125
+ const edgeOffsetLocation = shaderBuilder.addAttribute(
126
+ "float",
127
+ "a_edgeOffset",
128
+ );
129
+ shaderBuilder.addVarying("float", "v_edgeOffset");
106
130
 
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
- ]);
131
+ // Add varying for view space position for perspective-correct silhouette detection
122
132
 
123
133
  // Build triangle adjacency (mapping edges to adjacent triangles) and compute per-triangle face normals.
124
134
  const adjacencyData = buildTriangleAdjacency(primitive);
@@ -133,14 +143,56 @@ EdgeVisibilityPipelineStage.process = function (
133
143
  return;
134
144
  }
135
145
 
146
+ const runtimePrimitive = renderResources.runtimePrimitive.primitive;
147
+ const vertexColorInfo = collectVertexColors(runtimePrimitive);
148
+ const hasEdgeColorOverride = edgeResult.edgeData.some(function (edge) {
149
+ return defined(edge.color);
150
+ });
151
+
152
+ const needsEdgeColorAttribute =
153
+ hasEdgeColorOverride || defined(vertexColorInfo);
154
+
155
+ let edgeColorLocation;
156
+ if (needsEdgeColorAttribute) {
157
+ edgeColorLocation = shaderBuilder.addAttribute("vec4", "a_edgeColor");
158
+ shaderBuilder.addVarying("vec4", "v_edgeColor", "flat");
159
+ shaderBuilder.addDefine(
160
+ "HAS_EDGE_COLOR_ATTRIBUTE",
161
+ undefined,
162
+ ShaderDestination.BOTH,
163
+ );
164
+ }
165
+
166
+ let cumDistAttribute;
167
+ let edgeCumDistLocation;
168
+ const cumDistAttr = ModelUtility.getAttributeBySemantic(
169
+ primitive,
170
+ "BENTLEY_materials_line_style:CUMULATIVE_DISTANCE",
171
+ );
172
+
173
+ if (defined(cumDistAttr)) {
174
+ cumDistAttribute = cumDistAttr;
175
+ edgeCumDistLocation = shaderBuilder.addAttribute(
176
+ "float",
177
+ "a_edgeCumulativeDistance",
178
+ );
179
+ shaderBuilder.addDefine(
180
+ "HAS_EDGE_CUMULATIVE_DISTANCE",
181
+ undefined,
182
+ ShaderDestination.VERTEX,
183
+ );
184
+ }
185
+
136
186
  // Generate paired face normals for each unique edge (used to classify silhouette edges in the shader).
137
187
  const edgeFaceNormals = generateEdgeFaceNormals(
138
188
  adjacencyData,
139
189
  edgeResult.edgeIndices,
190
+ edgeResult.edgeData,
191
+ primitive.edgeVisibility,
140
192
  );
141
193
 
142
- // Create edge-domain line list geometry (2 vertices per edge) with all required attributes.
143
- const edgeGeometry = createCPULineEdgeGeometry(
194
+ // Create edge-domain quad geometry (4 vertices per edge) with all required attributes for wide line rendering.
195
+ const edgeGeometry = createQuadEdgeGeometry(
144
196
  edgeResult.edgeIndices,
145
197
  edgeResult.edgeData,
146
198
  renderResources,
@@ -150,8 +202,14 @@ EdgeVisibilityPipelineStage.process = function (
150
202
  faceNormalALocation,
151
203
  faceNormalBLocation,
152
204
  edgeFeatureIdLocation,
205
+ edgeColorLocation,
206
+ edgeOtherPosLocation,
207
+ edgeOffsetLocation,
208
+ vertexColorInfo,
153
209
  primitive.edgeVisibility,
154
210
  edgeFaceNormals,
211
+ cumDistAttribute,
212
+ edgeCumDistLocation,
155
213
  );
156
214
 
157
215
  if (!defined(edgeGeometry)) {
@@ -171,12 +229,34 @@ EdgeVisibilityPipelineStage.process = function (
171
229
  return false;
172
230
  };
173
231
 
232
+ // Provide a fallback line width when the material did not define one.
233
+ if (!defined(renderResources.uniformMap.u_lineWidth)) {
234
+ renderResources.uniformMap.u_lineWidth = function () {
235
+ return 1.0;
236
+ };
237
+ }
238
+
239
+ if (defined(cumDistAttribute)) {
240
+ if (!defined(renderResources.uniformMap.u_pixelsPerWorld)) {
241
+ renderResources.uniformMap.u_pixelsPerWorld = function () {
242
+ return 1.0;
243
+ };
244
+ }
245
+ }
246
+
247
+ const lineWidth =
248
+ defined(lineStyle) && defined(lineStyle.width)
249
+ ? lineStyle.width * frameState.pixelRatio
250
+ : undefined;
251
+
174
252
  // Store edge geometry metadata so the renderer can issue a separate edges pass.
253
+ // Use TRIANGLES instead of LINES to support wide lines via quad tessellation
175
254
  renderResources.edgeGeometry = {
176
255
  vertexArray: edgeGeometry.vertexArray,
177
256
  indexCount: edgeGeometry.indexCount,
178
- primitiveType: PrimitiveType.LINES,
257
+ primitiveType: PrimitiveType.TRIANGLES,
179
258
  pass: Pass.CESIUM_3D_TILE_EDGES,
259
+ lineWidth: lineWidth,
180
260
  };
181
261
  };
182
262
 
@@ -284,7 +364,7 @@ function buildTriangleAdjacency(primitive) {
284
364
  faceNormals[base + 1] = scratchCross.y;
285
365
  faceNormals[base + 2] = scratchCross.z;
286
366
 
287
- // Edges
367
+ // Register edges
288
368
  processEdge(i0, i1, t);
289
369
  processEdge(i1, i2, t);
290
370
  processEdge(i2, i0, t);
@@ -294,21 +374,83 @@ function buildTriangleAdjacency(primitive) {
294
374
  }
295
375
 
296
376
  /**
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.
377
+ * Generate face normal pairs for each edge. Silhouette edges use normals from the GLB
378
+ * silhouetteNormals accessor if available. Boundary edges synthesize the opposite normal
379
+ * as the negation of the first triangle's normal.
300
380
  *
301
- * @param {{edgeMap:Map<string,number[]>, faceNormals:Float32Array}} adjacencyData The adjacency data from buildTriangleAdjacency
381
+ * @param {{edgeMap:Map<string,number[]>, faceNormals:Float32Array}} adjacencyData Triangle adjacency data
302
382
  * @param {number[]} edgeIndices Packed array of 2 vertex indices per edge
383
+ * @param {Object[]} edgeData Array of edge metadata (edgeType, mateVertexIndex)
384
+ * @param {Object} edgeVisibility Edge visibility extension data
303
385
  * @returns {Float32Array} Packed array: 6 floats per edge (normalA.xyz, normalB.xyz)
304
386
  * @private
305
387
  */
306
- function generateEdgeFaceNormals(adjacencyData, edgeIndices) {
388
+ function generateEdgeFaceNormals(
389
+ adjacencyData,
390
+ edgeIndices,
391
+ edgeData,
392
+ edgeVisibility,
393
+ ) {
307
394
  const { edgeMap, faceNormals } = adjacencyData;
308
395
  const numEdges = edgeIndices.length / 2;
396
+ const edgeFaceNormals = new Float32Array(numEdges * 6);
397
+
398
+ const hasGLBSilhouetteNormals =
399
+ defined(edgeVisibility) && defined(edgeVisibility.silhouetteNormals);
400
+ let silhouetteNormalsUint32 = null;
401
+ const scratchDecodedA = new Cartesian3();
402
+ const scratchDecodedB = new Cartesian3();
403
+
404
+ if (hasGLBSilhouetteNormals) {
405
+ // GLB stores VEC3 BYTE as normalized normal vectors (signed bytes).
406
+ // Decode from signed bytes to normalized vectors, then re-encode to 16-bit octahedral format.
407
+ const decodeSignedByte = (val) => 2 * ((val + 128) / 255) - 1;
408
+
409
+ // Re-encode each VEC3 BYTE to 16-bit oct-encoded normal using AttributeCompression
410
+ const uint16Normals = new Uint16Array(
411
+ edgeVisibility.silhouetteNormals.length,
412
+ );
413
+ const scratchNormal = new Cartesian3();
414
+ const scratchEncoded = new Cartesian2();
415
+
416
+ for (let i = 0; i < edgeVisibility.silhouetteNormals.length; i++) {
417
+ const vec3 = edgeVisibility.silhouetteNormals[i];
418
+
419
+ // Decode from signed byte to normal vector
420
+ scratchNormal.x = decodeSignedByte(vec3.x);
421
+ scratchNormal.y = decodeSignedByte(vec3.y);
422
+ scratchNormal.z = decodeSignedByte(vec3.z);
423
+
424
+ // Normalize to unit vector
425
+ const magnitude = Cartesian3.magnitude(scratchNormal);
426
+ if (magnitude > 0) {
427
+ Cartesian3.normalize(scratchNormal, scratchNormal);
428
+ } else {
429
+ // Handle zero vector - use default up vector
430
+ scratchNormal.x = 0;
431
+ scratchNormal.y = 0;
432
+ scratchNormal.z = 1;
433
+ }
434
+
435
+ // Use Cesium's octahedral encoding (returns 0-255 integers)
436
+ AttributeCompression.octEncodeInRange(scratchNormal, 255, scratchEncoded);
437
+
438
+ // Convert to 16-bit integer: (y << 8) | x
439
+ const byteX = scratchEncoded.x & 0xff;
440
+ const byteY = scratchEncoded.y & 0xff;
441
+ uint16Normals[i] = (byteY << 8) | byteX;
442
+ }
309
443
 
310
- // Each edge needs 2 face normals (left and right side)
311
- const edgeFaceNormals = new Float32Array(numEdges * 6); // 2 normals * 3 components each
444
+ // Pack pairs into Uint32Array (little-endian: normalA|normalB<<16)
445
+ const numPairs = Math.floor(uint16Normals.length / 2);
446
+ silhouetteNormalsUint32 = new Uint32Array(numPairs);
447
+
448
+ for (let i = 0; i < numPairs; i++) {
449
+ const normalA = uint16Normals[i * 2];
450
+ const normalB = uint16Normals[i * 2 + 1];
451
+ silhouetteNormalsUint32[i] = normalA | (normalB << 16);
452
+ }
453
+ }
312
454
 
313
455
  for (let i = 0; i < numEdges; i++) {
314
456
  const a = edgeIndices[i * 2];
@@ -316,31 +458,77 @@ function generateEdgeFaceNormals(adjacencyData, edgeIndices) {
316
458
  const edgeKey = `${a < b ? a : b},${a < b ? b : a}`;
317
459
  const triangleList = edgeMap.get(edgeKey);
318
460
 
319
- // Expect at least one triangle; silently skip if not found (defensive)
320
- if (!defined(triangleList) || triangleList.length === 0) {
321
- continue;
461
+ const { edgeType, mateVertexIndex } = edgeData[i];
462
+
463
+ let nAx, nAy, nAz, nBx, nBy, nBz;
464
+ let usedGLBNormals = false;
465
+
466
+ // Use GLB silhouetteNormals for type=1 (SILHOUETTE) edges if available
467
+ if (
468
+ hasGLBSilhouetteNormals &&
469
+ silhouetteNormalsUint32 &&
470
+ edgeType === 1 &&
471
+ mateVertexIndex >= 0
472
+ ) {
473
+ // Each OctEncodedNormalPair is stored as one Uint32 value
474
+ // Uint32 contains 4 bytes: [byte0, byte1, byte2, byte3]
475
+ // normalA = byte0 | (byte1 << 8) - little endian
476
+ // normalB = byte2 | (byte3 << 8) - little endian
477
+
478
+ if (mateVertexIndex < silhouetteNormalsUint32.length) {
479
+ const uint32Value = silhouetteNormalsUint32[mateVertexIndex];
480
+
481
+ // Uint32 contains 4 bytes: [xA, yA, xB, yB]
482
+ // Extract and decode using octDecode (rangeMax=255)
483
+ AttributeCompression.octDecode(
484
+ uint32Value & 0xff, // xA
485
+ (uint32Value >> 8) & 0xff, // yA
486
+ scratchDecodedA,
487
+ );
488
+ AttributeCompression.octDecode(
489
+ (uint32Value >> 16) & 0xff, // xB
490
+ (uint32Value >> 24) & 0xff, // yB
491
+ scratchDecodedB,
492
+ );
493
+
494
+ nAx = scratchDecodedA.x;
495
+ nAy = scratchDecodedA.y;
496
+ nAz = scratchDecodedA.z;
497
+ nBx = scratchDecodedB.x;
498
+ nBy = scratchDecodedB.y;
499
+ nBz = scratchDecodedB.z;
500
+
501
+ usedGLBNormals = true;
502
+ }
322
503
  }
323
504
 
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;
505
+ // Fallback to triangle adjacency if GLB normals not used
506
+ if (!usedGLBNormals) {
507
+ // Expect at least one triangle; silently skip if not found (defensive)
508
+ if (!defined(triangleList) || triangleList.length === 0) {
509
+ continue;
510
+ }
511
+
512
+ const tA = triangleList[0];
513
+ const aBase = tA * 3;
514
+ nAx = faceNormals[aBase];
515
+ nAy = faceNormals[aBase + 1];
516
+ nAz = faceNormals[aBase + 2];
517
+
518
+ const isManifold = triangleList.length > 1;
519
+
520
+ if (isManifold) {
521
+ const tB = triangleList[1];
522
+ const bBase = tB * 3;
523
+ nBx = faceNormals[bBase];
524
+ nBy = faceNormals[bBase + 1];
525
+ nBz = faceNormals[bBase + 2];
526
+ } else {
527
+ // Boundary edge – synthesize opposite normal
528
+ nBx = -nAx;
529
+ nBy = -nAy;
530
+ nBz = -nAz;
531
+ }
344
532
  }
345
533
 
346
534
  const baseIdx = i * 6;
@@ -372,91 +560,167 @@ function generateEdgeFaceNormals(adjacencyData, edgeIndices) {
372
560
  */
373
561
  function extractVisibleEdges(primitive) {
374
562
  const edgeVisibility = primitive.edgeVisibility;
563
+ if (!defined(edgeVisibility)) {
564
+ return [];
565
+ }
566
+
375
567
  const visibility = edgeVisibility.visibility;
376
568
  const indices = primitive.indices;
569
+ const lineStrings = edgeVisibility.lineStrings;
570
+
571
+ const attributes = primitive.attributes;
572
+ const vertexCount =
573
+ defined(attributes) && attributes.length > 0 ? attributes[0].count : 0;
377
574
 
378
- if (!defined(visibility) || !defined(indices)) {
575
+ const hasVisibilityData =
576
+ defined(visibility) &&
577
+ defined(indices) &&
578
+ defined(indices.typedArray) &&
579
+ indices.typedArray.length > 0;
580
+ const hasLineStrings = defined(lineStrings) && lineStrings.length > 0;
581
+
582
+ if (!hasVisibilityData && !hasLineStrings) {
379
583
  return [];
380
584
  }
381
585
 
382
- const triangleIndexArray = indices.typedArray;
383
- const vertexCount = primitive.attributes[0].count;
586
+ const triangleIndexArray = hasVisibilityData ? indices.typedArray : undefined;
384
587
  const edgeIndices = [];
385
588
  const edgeData = [];
386
589
  const seenEdgeHashes = new Set();
387
590
  let silhouetteEdgeCount = 0;
591
+ const globalColor = edgeVisibility.materialColor;
592
+
593
+ if (hasVisibilityData) {
594
+ let edgeIndex = 0;
595
+ const totalIndices = triangleIndexArray.length;
596
+ const visibilityArray = visibility;
597
+
598
+ for (let i = 0; i + 2 < totalIndices; i += 3) {
599
+ const v0 = triangleIndexArray[i];
600
+ const v1 = triangleIndexArray[i + 1];
601
+ const v2 = triangleIndexArray[i + 2];
602
+ for (let e = 0; e < 3; e++) {
603
+ let a;
604
+ let b;
605
+ if (e === 0) {
606
+ a = v0;
607
+ b = v1;
608
+ } else if (e === 1) {
609
+ a = v1;
610
+ b = v2;
611
+ } else {
612
+ a = v2;
613
+ b = v0;
614
+ }
388
615
 
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++;
616
+ const byteIndex = Math.floor(edgeIndex / 4);
617
+ const bitPairOffset = (edgeIndex % 4) * 2;
618
+ edgeIndex++;
412
619
 
413
- if (byteIndex >= visibility.length) {
414
- break;
415
- }
620
+ if (byteIndex >= visibilityArray.length) {
621
+ break;
622
+ }
416
623
 
417
- const byte = visibility[byteIndex];
418
- const visibility2Bit = (byte >> bitPairOffset) & 0x3;
624
+ const byte = visibilityArray[byteIndex];
625
+ const visibility2Bit = (byte >> bitPairOffset) & 0x3;
419
626
 
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
- }
627
+ if (visibility2Bit === 0) {
628
+ continue;
629
+ }
436
630
 
437
- if (shouldIncludeEdge) {
438
631
  const small = Math.min(a, b);
439
632
  const big = Math.max(a, b);
440
- const hash = small * vertexCount + big;
633
+ const edgeKey = `${small},${big}`;
441
634
 
442
- if (!seenEdgeHashes.has(hash)) {
443
- seenEdgeHashes.add(hash);
444
- edgeIndices.push(a, b);
635
+ if (seenEdgeHashes.has(edgeKey)) {
636
+ continue;
637
+ }
638
+ seenEdgeHashes.add(edgeKey);
639
+ edgeIndices.push(a, b);
640
+
641
+ // Only process silhouette edges (type=1) as marked in GLB
642
+ // Use computed edge index for boundary edges
643
+ let mateVertexIndex = -1;
644
+ if (visibility2Bit === 1) {
645
+ mateVertexIndex = silhouetteEdgeCount;
646
+ silhouetteEdgeCount++;
647
+ }
445
648
 
446
- let mateVertexIndex = -1;
447
- if (visibility2Bit === 1) {
448
- mateVertexIndex = silhouetteEdgeCount;
449
- silhouetteEdgeCount++;
450
- }
649
+ edgeData.push({
650
+ edgeType: visibility2Bit, // Use original GLB edge type
651
+ triangleIndex: Math.floor(i / 3),
652
+ edgeIndex: e,
653
+ mateVertexIndex: mateVertexIndex,
654
+ currentTriangleVertices: [v0, v1, v2],
655
+ color: globalColor,
656
+ });
657
+ }
658
+ }
659
+ }
451
660
 
452
- edgeData.push({
453
- edgeType: visibility2Bit,
454
- triangleIndex: Math.floor(i / 3),
455
- edgeIndex: e,
456
- mateVertexIndex: mateVertexIndex,
457
- currentTriangleVertices: [v0, v1, v2],
458
- });
661
+ if (hasLineStrings) {
662
+ for (let i = 0; i < lineStrings.length; i++) {
663
+ const lineString = lineStrings[i];
664
+ if (!defined(lineString) || !defined(lineString.indices)) {
665
+ continue;
666
+ }
667
+
668
+ const indicesArray = lineString.indices;
669
+ if (!defined(indicesArray) || indicesArray.length < 2) {
670
+ continue;
671
+ }
672
+
673
+ const restartValue = lineString.restartIndex;
674
+ const lineColor = defined(lineString.materialColor)
675
+ ? lineString.materialColor
676
+ : globalColor;
677
+
678
+ let previous;
679
+ for (let j = 0; j < indicesArray.length; j++) {
680
+ const currentIndex = indicesArray[j];
681
+ if (defined(restartValue) && currentIndex === restartValue) {
682
+ previous = undefined;
683
+ continue;
684
+ }
685
+
686
+ if (!defined(previous)) {
687
+ previous = currentIndex;
688
+ continue;
689
+ }
690
+
691
+ const a = previous;
692
+ const b = currentIndex;
693
+ previous = currentIndex;
694
+
695
+ if (a === b) {
696
+ continue;
697
+ }
698
+
699
+ if (
700
+ vertexCount > 0 &&
701
+ (a < 0 || a >= vertexCount || b < 0 || b >= vertexCount)
702
+ ) {
703
+ continue;
704
+ }
705
+
706
+ const small = Math.min(a, b);
707
+ const big = Math.max(a, b);
708
+ const edgeKey = `${small},${big}`;
709
+
710
+ if (seenEdgeHashes.has(edgeKey)) {
711
+ continue;
459
712
  }
713
+
714
+ seenEdgeHashes.add(edgeKey);
715
+ edgeIndices.push(a, b);
716
+ edgeData.push({
717
+ edgeType: 2,
718
+ triangleIndex: -1,
719
+ edgeIndex: -1,
720
+ mateVertexIndex: -1,
721
+ currentTriangleVertices: undefined,
722
+ color: defined(lineColor) ? lineColor : undefined,
723
+ });
460
724
  }
461
725
  }
462
726
  }
@@ -464,10 +728,91 @@ function extractVisibleEdges(primitive) {
464
728
  return { edgeIndices, edgeData, silhouetteEdgeCount };
465
729
  }
466
730
 
731
+ function collectVertexColors(runtimePrimitive) {
732
+ if (!defined(runtimePrimitive)) {
733
+ return undefined;
734
+ }
735
+
736
+ const colorAttribute = ModelUtility.getAttributeBySemantic(
737
+ runtimePrimitive,
738
+ VertexAttributeSemantic.COLOR,
739
+ );
740
+ if (!defined(colorAttribute)) {
741
+ return undefined;
742
+ }
743
+
744
+ const components = AttributeType.getNumberOfComponents(colorAttribute.type);
745
+ if (components !== 3 && components !== 4) {
746
+ return undefined;
747
+ }
748
+
749
+ let colorData = colorAttribute.typedArray;
750
+ if (!defined(colorData)) {
751
+ colorData = ModelReader.readAttributeAsTypedArray(colorAttribute);
752
+ }
753
+ if (!defined(colorData)) {
754
+ return undefined;
755
+ }
756
+ const count = colorAttribute.count;
757
+ if (!defined(count) || count === 0) {
758
+ return undefined;
759
+ }
760
+
761
+ if (colorData.length < count * components) {
762
+ return undefined;
763
+ }
764
+
765
+ const isFloatArray =
766
+ colorData instanceof Float32Array || colorData instanceof Float64Array;
767
+ const isUint8Array = colorData instanceof Uint8Array;
768
+ const isUint16Array = colorData instanceof Uint16Array;
769
+
770
+ if (!isFloatArray && !isUint8Array && !isUint16Array) {
771
+ return undefined;
772
+ }
773
+
774
+ const colors = new Float32Array(count * 4);
775
+
776
+ const convertComponent = function (value) {
777
+ let converted;
778
+ if (isFloatArray) {
779
+ converted = value;
780
+ } else if (isUint8Array) {
781
+ converted = value / 255.0;
782
+ } else if (isUint16Array) {
783
+ converted = value / 65535.0;
784
+ }
785
+ return Math.min(Math.max(converted, 0.0), 1.0);
786
+ };
787
+
788
+ for (let i = 0; i < count; i++) {
789
+ const srcBase = i * components;
790
+ const destBase = i * 4;
791
+ colors[destBase] = convertComponent(colorData[srcBase]);
792
+ colors[destBase + 1] = convertComponent(colorData[srcBase + 1]);
793
+ colors[destBase + 2] = convertComponent(colorData[srcBase + 2]);
794
+ if (components === 4) {
795
+ colors[destBase + 3] = convertComponent(colorData[srcBase + 3]);
796
+ } else {
797
+ colors[destBase + 3] = 1.0;
798
+ }
799
+ }
800
+
801
+ return {
802
+ colors: colors,
803
+ count: count,
804
+ };
805
+ }
806
+
467
807
  /**
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.
808
+ * @typedef {object} VertexColorInfo
809
+ * @property {Float32Array} colors The packed per-vertex colors.
810
+ * @property {number} count The number of vertices.
811
+ */
812
+
813
+ /**
814
+ * Create quad-based edge geometry for wide line rendering. Each edge becomes a quad (4 vertices, 2 triangles).
815
+ * This allows proper line width rendering in the vertex shader by extruding perpendicular to the line direction.
471
816
  *
472
817
  * @param {number[]} edgeIndices Packed array [a0,b0, a1,b1, ...] of vertex indices into the source mesh
473
818
  * @param {Object[]} edgeData Array of edge metadata including edge type and silhouette normal lookup index
@@ -478,12 +823,18 @@ function extractVisibleEdges(primitive) {
478
823
  * @param {number} faceNormalALocation Shader attribute location for face normal A
479
824
  * @param {number} faceNormalBLocation Shader attribute location for face normal B
480
825
  * @param {number} edgeFeatureIdLocation Shader attribute location for optional edge feature ID
826
+ * @param {number} edgeColorLocation Shader attribute location for optional edge color data
827
+ * @param {number} edgeOtherPosLocation Shader attribute location for the other endpoint position
828
+ * @param {number} edgeOffsetLocation Shader attribute location for edge offset (-1 or +1)
829
+ * @param {VertexColorInfo} [vertexColorInfo] Packed per-vertex colors (optional)
481
830
  * @param {Object} edgeVisibility Edge visibility extension object (may contain silhouetteNormals[])
482
831
  * @param {Float32Array} edgeFaceNormals Packed face normals (6 floats per edge)
483
- * @returns {Object|undefined} Object with {vertexArray, indexBuffer, indexCount} or undefined on failure
832
+ * @param {Object} [cumDistAttribute] Cumulative distance attribute
833
+ * @param {number} [edgeCumDistLocation] Cumulative distance location
834
+ * @returns {Object|undefined} Object with {vertexArray, indexCount, hasEdgeFeatureIds} or undefined on failure
484
835
  * @private
485
836
  */
486
- function createCPULineEdgeGeometry(
837
+ function createQuadEdgeGeometry(
487
838
  edgeIndices,
488
839
  edgeData,
489
840
  renderResources,
@@ -493,18 +844,24 @@ function createCPULineEdgeGeometry(
493
844
  faceNormalALocation,
494
845
  faceNormalBLocation,
495
846
  edgeFeatureIdLocation,
847
+ edgeColorLocation,
848
+ edgeOtherPosLocation,
849
+ edgeOffsetLocation,
850
+ vertexColorInfo,
496
851
  edgeVisibility,
497
852
  edgeFaceNormals,
853
+ cumDistAttribute,
854
+ edgeCumDistLocation,
498
855
  ) {
499
856
  if (!defined(edgeIndices) || edgeIndices.length === 0) {
500
857
  return undefined;
501
858
  }
502
859
 
503
860
  const numEdges = edgeData.length;
504
- const vertsPerEdge = 2;
861
+ const vertsPerEdge = 4; // Each edge becomes a quad (4 vertices)
505
862
  const totalVerts = numEdges * vertsPerEdge;
506
863
 
507
- // Always use location 0 for position to avoid conflicts
864
+ // Always use location 0 for position
508
865
  const positionLocation = 0;
509
866
 
510
867
  // Get original vertex positions
@@ -516,57 +873,85 @@ function createCPULineEdgeGeometry(
516
873
  ? positionAttribute.typedArray
517
874
  : ModelReader.readAttributeAsTypedArray(positionAttribute);
518
875
 
519
- // Create edge-domain vertices (2 per edge)
876
+ // Create arrays for quad vertices
520
877
  const edgePosArray = new Float32Array(totalVerts * 3);
521
878
  const edgeTypeArray = new Float32Array(totalVerts);
522
879
  const silhouetteNormalArray = new Float32Array(totalVerts * 3);
523
880
  const faceNormalAArray = new Float32Array(totalVerts * 3);
524
881
  const faceNormalBArray = new Float32Array(totalVerts * 3);
525
- let p = 0;
882
+ const edgeOtherPosArray = new Float32Array(totalVerts * 3);
883
+ const edgeOffsetArray = new Float32Array(totalVerts);
884
+
885
+ const needsCumulativeDistance = defined(cumDistAttribute);
886
+ const edgeCumDistArray = needsCumulativeDistance
887
+ ? new Float32Array(totalVerts)
888
+ : undefined;
889
+ let srcCumDist;
890
+ if (needsCumulativeDistance) {
891
+ srcCumDist = defined(cumDistAttribute.typedArray)
892
+ ? cumDistAttribute.typedArray
893
+ : ModelReader.readAttributeAsTypedArray(cumDistAttribute);
894
+ }
526
895
 
527
- const maxSrcVertex = srcPos.length / 3 - 1;
896
+ const needsEdgeColorAttribute = defined(edgeColorLocation);
897
+ const edgeColorArray = needsEdgeColorAttribute
898
+ ? new Float32Array(totalVerts * 4)
899
+ : undefined;
900
+ const vertexColors = defined(vertexColorInfo)
901
+ ? vertexColorInfo.colors
902
+ : undefined;
903
+ const vertexColorCount = defined(vertexColorInfo) ? vertexColorInfo.count : 0;
904
+
905
+ function setNoColor(destVertexIndex) {
906
+ if (!needsEdgeColorAttribute) {
907
+ return;
908
+ }
909
+ const destOffset = destVertexIndex * 4;
910
+ edgeColorArray[destOffset] = 0.0;
911
+ edgeColorArray[destOffset + 1] = 0.0;
912
+ edgeColorArray[destOffset + 2] = 0.0;
913
+ edgeColorArray[destOffset + 3] = -1.0;
914
+ }
915
+
916
+ function setColorFromOverride(destVertexIndex, color) {
917
+ if (!needsEdgeColorAttribute) {
918
+ return;
919
+ }
920
+ const destOffset = destVertexIndex * 4;
921
+ const r = defined(color.x) ? color.x : color[0];
922
+ const g = defined(color.y) ? color.y : color[1];
923
+ const b = defined(color.z) ? color.z : color[2];
924
+ const a = defined(color.w) ? color.w : defined(color[3]) ? color[3] : 1.0;
925
+ edgeColorArray[destOffset] = r;
926
+ edgeColorArray[destOffset + 1] = g;
927
+ edgeColorArray[destOffset + 2] = b;
928
+ edgeColorArray[destOffset + 3] = a;
929
+ }
528
930
 
931
+ function assignVertexColor(destVertexIndex, srcVertexIndex) {
932
+ if (!needsEdgeColorAttribute) {
933
+ return;
934
+ }
935
+ if (srcVertexIndex >= vertexColorCount) {
936
+ setNoColor(destVertexIndex);
937
+ return;
938
+ }
939
+ const srcOffset = srcVertexIndex * 4;
940
+ const destOffset = destVertexIndex * 4;
941
+ edgeColorArray[destOffset] = vertexColors[srcOffset];
942
+ edgeColorArray[destOffset + 1] = vertexColors[srcOffset + 1];
943
+ edgeColorArray[destOffset + 2] = vertexColors[srcOffset + 2];
944
+ edgeColorArray[destOffset + 3] = vertexColors[srcOffset + 3];
945
+ }
946
+
947
+ // Generate quad vertices for each edge
529
948
  for (let i = 0; i < numEdges; i++) {
530
949
  const a = edgeIndices[i * 2];
531
950
  const b = edgeIndices[i * 2 + 1];
951
+ const rawType = edgeData[i].edgeType;
952
+ const normalizedType = rawType / 255.0;
532
953
 
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
-
954
+ // Get positions
570
955
  const ax = srcPos[a * 3];
571
956
  const ay = srcPos[a * 3 + 1];
572
957
  const az = srcPos[a * 3 + 2];
@@ -574,24 +959,80 @@ function createCPULineEdgeGeometry(
574
959
  const by = srcPos[b * 3 + 1];
575
960
  const bz = srcPos[b * 3 + 2];
576
961
 
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;
962
+ // Create 4 vertices for this edge: (A-, A+, B+, B-)
963
+ // where +/- indicates offset perpendicular to the line
964
+ // Store the other endpoint position for each vertex
965
+ const baseVertexIndex = i * 4;
966
+
967
+ // Vertex 0: position at A, other endpoint is B, offset -1
968
+ edgePosArray[baseVertexIndex * 3] = ax;
969
+ edgePosArray[baseVertexIndex * 3 + 1] = ay;
970
+ edgePosArray[baseVertexIndex * 3 + 2] = az;
971
+ edgeOtherPosArray[baseVertexIndex * 3] = bx;
972
+ edgeOtherPosArray[baseVertexIndex * 3 + 1] = by;
973
+ edgeOtherPosArray[baseVertexIndex * 3 + 2] = bz;
974
+ edgeOffsetArray[baseVertexIndex] = -1.0;
975
+ edgeTypeArray[baseVertexIndex] = normalizedType;
976
+
977
+ // Vertex 1: position at A, other endpoint is B, offset +1
978
+ edgePosArray[(baseVertexIndex + 1) * 3] = ax;
979
+ edgePosArray[(baseVertexIndex + 1) * 3 + 1] = ay;
980
+ edgePosArray[(baseVertexIndex + 1) * 3 + 2] = az;
981
+ edgeOtherPosArray[(baseVertexIndex + 1) * 3] = bx;
982
+ edgeOtherPosArray[(baseVertexIndex + 1) * 3 + 1] = by;
983
+ edgeOtherPosArray[(baseVertexIndex + 1) * 3 + 2] = bz;
984
+ edgeOffsetArray[baseVertexIndex + 1] = 1.0;
985
+ edgeTypeArray[baseVertexIndex + 1] = normalizedType;
986
+
987
+ // Vertex 2: position at B, other endpoint is A, offset +1
988
+ edgePosArray[(baseVertexIndex + 2) * 3] = bx;
989
+ edgePosArray[(baseVertexIndex + 2) * 3 + 1] = by;
990
+ edgePosArray[(baseVertexIndex + 2) * 3 + 2] = bz;
991
+ edgeOtherPosArray[(baseVertexIndex + 2) * 3] = ax;
992
+ edgeOtherPosArray[(baseVertexIndex + 2) * 3 + 1] = ay;
993
+ edgeOtherPosArray[(baseVertexIndex + 2) * 3 + 2] = az;
994
+ edgeOffsetArray[baseVertexIndex + 2] = 1.0;
995
+ edgeTypeArray[baseVertexIndex + 2] = normalizedType;
996
+
997
+ // Vertex 3: position at B, other endpoint is A, offset -1
998
+ edgePosArray[(baseVertexIndex + 3) * 3] = bx;
999
+ edgePosArray[(baseVertexIndex + 3) * 3 + 1] = by;
1000
+ edgePosArray[(baseVertexIndex + 3) * 3 + 2] = bz;
1001
+ edgeOtherPosArray[(baseVertexIndex + 3) * 3] = ax;
1002
+ edgeOtherPosArray[(baseVertexIndex + 3) * 3 + 1] = ay;
1003
+ edgeOtherPosArray[(baseVertexIndex + 3) * 3 + 2] = az;
1004
+ edgeOffsetArray[baseVertexIndex + 3] = -1.0;
1005
+ edgeTypeArray[baseVertexIndex + 3] = normalizedType;
1006
+
1007
+ // Handle edge colors
1008
+ const edgeOverrideColor = edgeData[i].color;
1009
+ if (defined(edgeOverrideColor)) {
1010
+ for (let v = 0; v < 4; v++) {
1011
+ setColorFromOverride(baseVertexIndex + v, edgeOverrideColor);
1012
+ }
1013
+ } else if (defined(vertexColors)) {
1014
+ for (let v = 0; v < 4; v++) {
1015
+ assignVertexColor(baseVertexIndex + v, a);
1016
+ }
1017
+ } else {
1018
+ for (let v = 0; v < 4; v++) {
1019
+ setNoColor(baseVertexIndex + v);
1020
+ }
1021
+ }
587
1022
 
588
- edgeTypeArray[i * 2] = t;
589
- edgeTypeArray[i * 2 + 1] = t;
1023
+ if (needsCumulativeDistance) {
1024
+ const cumDistA = srcCumDist[a];
1025
+ const cumDistB = srcCumDist[b];
1026
+ edgeCumDistArray[baseVertexIndex] = cumDistA;
1027
+ edgeCumDistArray[baseVertexIndex + 1] = cumDistA;
1028
+ edgeCumDistArray[baseVertexIndex + 2] = cumDistB;
1029
+ edgeCumDistArray[baseVertexIndex + 3] = cumDistB;
1030
+ }
590
1031
 
591
- // Add silhouette normal for silhouette edges (type 1)
1032
+ // Set silhouette normal (same for all 4 vertices)
592
1033
  let normalX = 0,
593
1034
  normalY = 0,
594
- normalZ = 1; // Default normal pointing up
1035
+ normalZ = 1;
595
1036
 
596
1037
  if (rawType === 1 && defined(edgeVisibility.silhouetteNormals)) {
597
1038
  const mateVertexIndex = edgeData[i].mateVertexIndex;
@@ -599,9 +1040,7 @@ function createCPULineEdgeGeometry(
599
1040
  mateVertexIndex >= 0 &&
600
1041
  mateVertexIndex < edgeVisibility.silhouetteNormals.length
601
1042
  ) {
602
- const silhouetteNormals = edgeVisibility.silhouetteNormals;
603
- const normal = silhouetteNormals[mateVertexIndex];
604
-
1043
+ const normal = edgeVisibility.silhouetteNormals[mateVertexIndex];
605
1044
  if (defined(normal)) {
606
1045
  normalX = normal.x;
607
1046
  normalY = normal.y;
@@ -610,17 +1049,14 @@ function createCPULineEdgeGeometry(
610
1049
  }
611
1050
  }
612
1051
 
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)
1052
+ for (let v = 0; v < 4; v++) {
1053
+ silhouetteNormalArray[(baseVertexIndex + v) * 3] = normalX;
1054
+ silhouetteNormalArray[(baseVertexIndex + v) * 3 + 1] = normalY;
1055
+ silhouetteNormalArray[(baseVertexIndex + v) * 3 + 2] = normalZ;
1056
+ }
1057
+
1058
+ // Set face normals (same for all 4 vertices)
1059
+ const faceNormalIdx = i * 6;
624
1060
  const normalAX = edgeFaceNormals[faceNormalIdx];
625
1061
  const normalAY = edgeFaceNormals[faceNormalIdx + 1];
626
1062
  const normalAZ = edgeFaceNormals[faceNormalIdx + 2];
@@ -628,21 +1064,14 @@ function createCPULineEdgeGeometry(
628
1064
  const normalBY = edgeFaceNormals[faceNormalIdx + 4];
629
1065
  const normalBZ = edgeFaceNormals[faceNormalIdx + 5];
630
1066
 
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;
1067
+ for (let v = 0; v < 4; v++) {
1068
+ faceNormalAArray[(baseVertexIndex + v) * 3] = normalAX;
1069
+ faceNormalAArray[(baseVertexIndex + v) * 3 + 1] = normalAY;
1070
+ faceNormalAArray[(baseVertexIndex + v) * 3 + 2] = normalAZ;
1071
+ faceNormalBArray[(baseVertexIndex + v) * 3] = normalBX;
1072
+ faceNormalBArray[(baseVertexIndex + v) * 3 + 1] = normalBY;
1073
+ faceNormalBArray[(baseVertexIndex + v) * 3 + 2] = normalBZ;
1074
+ }
646
1075
  }
647
1076
 
648
1077
  // Create vertex buffers
@@ -651,44 +1080,92 @@ function createCPULineEdgeGeometry(
651
1080
  typedArray: edgePosArray,
652
1081
  usage: BufferUsage.STATIC_DRAW,
653
1082
  });
1083
+
654
1084
  const edgeTypeBuffer = Buffer.createVertexBuffer({
655
1085
  context,
656
1086
  typedArray: edgeTypeArray,
657
1087
  usage: BufferUsage.STATIC_DRAW,
658
1088
  });
1089
+
659
1090
  const silhouetteNormalBuffer = Buffer.createVertexBuffer({
660
1091
  context,
661
1092
  typedArray: silhouetteNormalArray,
662
1093
  usage: BufferUsage.STATIC_DRAW,
663
1094
  });
1095
+
664
1096
  const faceNormalABuffer = Buffer.createVertexBuffer({
665
1097
  context,
666
1098
  typedArray: faceNormalAArray,
667
1099
  usage: BufferUsage.STATIC_DRAW,
668
1100
  });
1101
+
669
1102
  const faceNormalBBuffer = Buffer.createVertexBuffer({
670
1103
  context,
671
1104
  typedArray: faceNormalBArray,
672
1105
  usage: BufferUsage.STATIC_DRAW,
673
1106
  });
674
1107
 
675
- // Create sequential indices for line pairs
1108
+ const edgeOtherPosBuffer = Buffer.createVertexBuffer({
1109
+ context,
1110
+ typedArray: edgeOtherPosArray,
1111
+ usage: BufferUsage.STATIC_DRAW,
1112
+ });
1113
+
1114
+ const edgeOffsetBuffer = Buffer.createVertexBuffer({
1115
+ context,
1116
+ typedArray: edgeOffsetArray,
1117
+ usage: BufferUsage.STATIC_DRAW,
1118
+ });
1119
+
1120
+ const edgeColorBuffer = needsEdgeColorAttribute
1121
+ ? Buffer.createVertexBuffer({
1122
+ context,
1123
+ typedArray: edgeColorArray,
1124
+ usage: BufferUsage.STATIC_DRAW,
1125
+ })
1126
+ : undefined;
1127
+
1128
+ const edgeCumDistBuffer = needsCumulativeDistance
1129
+ ? Buffer.createVertexBuffer({
1130
+ context,
1131
+ typedArray: edgeCumDistArray,
1132
+ usage: BufferUsage.STATIC_DRAW,
1133
+ })
1134
+ : undefined;
1135
+
1136
+ // Create triangle indices for quads: (0,1,2, 0,2,3) for each quad
1137
+ const numTriangles = numEdges * 2;
1138
+ const numIndices = numTriangles * 3;
676
1139
  const useU32 = totalVerts > 65534;
677
- const idx = new Array(totalVerts);
678
- for (let i = 0; i < totalVerts; i++) {
679
- idx[i] = i;
1140
+ const indices = useU32
1141
+ ? new Uint32Array(numIndices)
1142
+ : new Uint16Array(numIndices);
1143
+
1144
+ for (let i = 0; i < numEdges; i++) {
1145
+ const baseVertex = i * 4;
1146
+ const baseIndex = i * 6;
1147
+
1148
+ // Triangle 1: (v0, v1, v2)
1149
+ indices[baseIndex] = baseVertex;
1150
+ indices[baseIndex + 1] = baseVertex + 1;
1151
+ indices[baseIndex + 2] = baseVertex + 2;
1152
+
1153
+ // Triangle 2: (v0, v2, v3)
1154
+ indices[baseIndex + 3] = baseVertex;
1155
+ indices[baseIndex + 4] = baseVertex + 2;
1156
+ indices[baseIndex + 5] = baseVertex + 3;
680
1157
  }
681
1158
 
682
1159
  const indexBuffer = Buffer.createIndexBuffer({
683
1160
  context,
684
- typedArray: useU32 ? new Uint32Array(idx) : new Uint16Array(idx),
1161
+ typedArray: indices,
685
1162
  usage: BufferUsage.STATIC_DRAW,
686
1163
  indexDatatype: useU32
687
1164
  ? IndexDatatype.UNSIGNED_INT
688
1165
  : IndexDatatype.UNSIGNED_SHORT,
689
1166
  });
690
1167
 
691
- // Create vertex array with position, edge type, silhouette normal, and face normal attributes
1168
+ // Create vertex array with all attributes
692
1169
  const attributes = [
693
1170
  {
694
1171
  index: positionLocation,
@@ -725,75 +1202,96 @@ function createCPULineEdgeGeometry(
725
1202
  componentDatatype: ComponentDatatype.FLOAT,
726
1203
  normalize: false,
727
1204
  },
1205
+ {
1206
+ index: edgeOtherPosLocation,
1207
+ vertexBuffer: edgeOtherPosBuffer,
1208
+ componentsPerAttribute: 3,
1209
+ componentDatatype: ComponentDatatype.FLOAT,
1210
+ normalize: false,
1211
+ },
1212
+ {
1213
+ index: edgeOffsetLocation,
1214
+ vertexBuffer: edgeOffsetBuffer,
1215
+ componentsPerAttribute: 1,
1216
+ componentDatatype: ComponentDatatype.FLOAT,
1217
+ normalize: false,
1218
+ },
728
1219
  ];
729
1220
 
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,
1221
+ if (needsEdgeColorAttribute) {
1222
+ attributes.push({
1223
+ index: edgeColorLocation,
1224
+ vertexBuffer: edgeColorBuffer,
1225
+ componentsPerAttribute: 4,
1226
+ componentDatatype: ComponentDatatype.FLOAT,
1227
+ normalize: false,
775
1228
  });
1229
+ }
776
1230
 
1231
+ if (needsCumulativeDistance) {
777
1232
  attributes.push({
778
- index: edgeFeatureIdLocation,
779
- vertexBuffer: edgeFeatureIdBuffer,
1233
+ index: edgeCumDistLocation,
1234
+ vertexBuffer: edgeCumDistBuffer,
780
1235
  componentsPerAttribute: 1,
781
1236
  componentDatatype: ComponentDatatype.FLOAT,
782
1237
  normalize: false,
783
1238
  });
784
1239
  }
785
1240
 
786
- const vertexArray = new VertexArray({ context, indexBuffer, attributes });
1241
+ const primitive = renderResources.runtimePrimitive.primitive;
1242
+ if (defined(primitive.featureIds) && primitive.featureIds.length > 0) {
1243
+ const firstFeatureIdSet = primitive.featureIds[0];
1244
+
1245
+ if (defined(firstFeatureIdSet.setIndex)) {
1246
+ const featureIdAttribute = primitive.attributes.find(
1247
+ (attr) =>
1248
+ attr.semantic === VertexAttributeSemantic.FEATURE_ID &&
1249
+ attr.setIndex === firstFeatureIdSet.setIndex,
1250
+ );
1251
+
1252
+ if (defined(featureIdAttribute)) {
1253
+ const featureIds = defined(featureIdAttribute.typedArray)
1254
+ ? featureIdAttribute.typedArray
1255
+ : ModelReader.readAttributeAsTypedArray(featureIdAttribute);
1256
+
1257
+ const edgeFeatureIds = new Float32Array(totalVerts);
1258
+ for (let i = 0; i < numEdges; i++) {
1259
+ const a = edgeIndices[i * 2];
1260
+ const featureId = a < featureIds.length ? featureIds[a] : 0;
1261
+ // Set same feature ID for all 4 vertices of the quad
1262
+ for (let v = 0; v < 4; v++) {
1263
+ edgeFeatureIds[i * 4 + v] = featureId;
1264
+ }
1265
+ }
787
1266
 
788
- if (!vertexArray || totalVerts === 0 || totalVerts % 2 !== 0) {
789
- return undefined;
1267
+ const edgeFeatureIdBuffer = Buffer.createVertexBuffer({
1268
+ context,
1269
+ typedArray: edgeFeatureIds,
1270
+ usage: BufferUsage.STATIC_DRAW,
1271
+ });
1272
+
1273
+ attributes.push({
1274
+ index: edgeFeatureIdLocation,
1275
+ vertexBuffer: edgeFeatureIdBuffer,
1276
+ componentsPerAttribute: 1,
1277
+ componentDatatype: ComponentDatatype.FLOAT,
1278
+ normalize: false,
1279
+ });
1280
+ }
1281
+ }
790
1282
  }
791
1283
 
1284
+ const vertexArray = new VertexArray({
1285
+ context,
1286
+ attributes,
1287
+ indexBuffer,
1288
+ });
1289
+
792
1290
  return {
793
1291
  vertexArray,
794
- indexBuffer,
795
- indexCount: totalVerts,
796
- hasEdgeFeatureIds,
1292
+ indexCount: numIndices,
1293
+ hasEdgeFeatureIds:
1294
+ defined(primitive.featureIds) && primitive.featureIds.length > 0,
797
1295
  };
798
1296
  }
799
1297