diff --git a/Apps/SampleData/models/StyledLines/BENTLEY_materials_line_style.gltf b/Apps/SampleData/models/StyledLines/BENTLEY_materials_line_style.gltf new file mode 100644 index 000000000000..242c1c37cd85 --- /dev/null +++ b/Apps/SampleData/models/StyledLines/BENTLEY_materials_line_style.gltf @@ -0,0 +1,339 @@ +{ + "extensionsUsed": [ + "KHR_mesh_quantization", + "EXT_mesh_features", + "EXT_mesh_primitive_edge_visibility", + "EXT_structural_metadata", + "BENTLEY_materials_line_style" + ], + "extensionsRequired": [ + "KHR_mesh_quantization" + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5123, + "count": 1056, + "type": "SCALAR" + }, + { + "bufferView": 1, + "componentType": 5123, + "count": 354, + "type": "VEC3", + "max": [ + 63939.0, + 45112.0, + 44922.0 + ], + "min": [ + 2405.0, + 21232.0, + 21422.0 + ] + }, + { + "bufferView": 2, + "componentType": 5126, + "count": 354, + "type": "VEC3" + }, + { + "bufferView": 3, + "componentType": 5121, + "normalized": true, + "count": 354, + "type": "VEC4" + }, + { + "bufferView": 4, + "componentType": 5126, + "count": 354, + "type": "SCALAR" + }, + { + "bufferView": 5, + "componentType": 5121, + "count": 264, + "type": "SCALAR" + }, + { + "bufferView": 6, + "componentType": 5120, + "count": 176, + "type": "VEC3" + } + ], + "asset": { + "generator": "3DFT", + "version": "2.0" + }, + "buffers": [ + { + "byteLength": 13036, + "uri": "data:application/octet-stream;base64,AAABAAIAAQADAAIAAwAEAAIABAAFAAIABQAGAAIABgAHAAIABwAIAAIACAAJAAIACQAKAAIACgALAAIACwAMAAIADAANAAIADQAOAAIADgAPAAIADwAQAAIAEAARAAIAEQASAAIAEgATAAIAEwAUAAIAFAAVAAIAFQAWAAIAFgAXAAIAFwAYAAIAGAAZAAIAGQAaAAIAGgAbAAIAGwAcAAIAHAAdAAIAHQAeAAIAHgAfAAIAHwAgAAIAIAAhAAIAIQAiAAIAIgAjAAIAIwAkAAIAJAAlAAIAJQAmAAIAJgAnAAIAJwAoAAIAKAApAAIAKQAqAAIAKgArAAIAKwAsAAIALAAtAAIALQAuAAIALgAvAAIALwAwAAIAMAAxAAIAMQAyAAIAMgAzAAIAMwA0AAIANAA1AAIANQA2AAIANgA3AAIANwA4AAIAOAA5AAIAOQA6AAIAOgA7AAIAOwA8AAIAPAA9AAIAPQA+AAIAPgA/AAIAPwBAAAIAQABBAAIAQQBCAAIAQgBDAAIAQwBEAAIARABFAAIARQBGAAIARgBHAAIARwBIAAIASABJAAIASQBKAAIASgBLAAIASwBMAAIATABNAAIATQBOAAIATgBPAAIATwBQAAIAUABRAAIAUQBSAAIAUgBTAAIAUwBUAAIAVABVAAIAVQBWAAIAVgBXAAIAVwBYAAIAWAAAAAIAWQBaAFsAWgBcAFsAXABdAFsAXQBeAFsAXgBfAFsAXwBgAFsAYABhAFsAYQBiAFsAYgBjAFsAYwBkAFsAZABlAFsAZQBmAFsAZgBnAFsAZwBoAFsAaABpAFsAaQBqAFsAagBrAFsAawBsAFsAbABtAFsAbQBuAFsAbgBvAFsAbwBwAFsAcABxAFsAcQByAFsAcgBzAFsAcwB0AFsAdAB1AFsAdQB2AFsAdgB3AFsAdwB4AFsAeAB5AFsAeQB6AFsAegB7AFsAewB8AFsAfAB9AFsAfQB+AFsAfgB/AFsAfwCAAFsAgACBAFsAgQCCAFsAggCDAFsAgwCEAFsAhACFAFsAhQCGAFsAhgCHAFsAhwCIAFsAiACJAFsAiQCKAFsAigCLAFsAiwCMAFsAjACNAFsAjQCOAFsAjgCPAFsAjwCQAFsAkACRAFsAkQCSAFsAkgCTAFsAkwCUAFsAlACVAFsAlQCWAFsAlgCXAFsAlwCYAFsAmACZAFsAmQCaAFsAmgCbAFsAmwCcAFsAnACdAFsAnQCeAFsAngCfAFsAnwCgAFsAoAChAFsAoQCiAFsAogCjAFsAowCkAFsApAClAFsApQCmAFsApgCnAFsApwCoAFsAqACpAFsAqQCqAFsAqgCrAFsAqwCsAFsArACtAFsArQCuAFsArgCvAFsArwCwAFsAsACxAFsAsQBZAFsAsgCzALQAsgC0ALUAswC2ALcAswC3ALQAtgC4ALkAtgC5ALcAuAC6ALsAuAC7ALkAugC8AL0AugC9ALsAvAC+AL8AvAC/AL0AvgDAAMEAvgDBAL8AwADCAMMAwADDAMEAwgDEAMUAwgDFAMMAxADGAMcAxADHAMUAxgDIAMkAxgDJAMcAyADKAMsAyADLAMkAygDMAM0AygDNAMsAzADOAM8AzADPAM0AzgDQANEAzgDRAM8A0ADSANMA0ADTANEA0gDUANUA0gDVANMA1ADWANcA1ADXANUA1gDYANkA1gDZANcA2ADaANsA2ADbANkA2gDcAN0A2gDdANsA3ADeAN8A3ADfAN0A3gDgAOEA3gDhAN8A4ADiAOMA4ADjAOEA4gDkAOUA4gDlAOMA5ADmAOcA5ADnAOUA5gDoAOkA5gDpAOcA6ADqAOsA6ADrAOkA6gDsAO0A6gDtAOsA7ADuAO8A7ADvAO0A7gDwAPEA7gDxAO8A8ADyAPMA8ADzAPEA8gD0APUA8gD1APMA9AD2APcA9AD3APUA9gD4APkA9gD5APcA+AD6APsA+AD7APkA+gD8AP0A+gD9APsA/AD+AP8A/AD/AP0A/gAAAQEB/gABAf8AAAECAQMBAAEDAQEBAgEEAQUBAgEFAQMBBAEGAQcBBAEHAQUBBgEIAQkBBgEJAQcBCAEKAQsBCAELAQkBCgEMAQ0BCgENAQsBDAEOAQ8BDAEPAQ0BDgEQAREBDgERAQ8BEAESARMBEAETAREBEgEUARUBEgEVARMBFAEWARcBFAEXARUBFgEYARkBFgEZARcBGAEaARsBGAEbARkBGgEcAR0BGgEdARsBHAEeAR8BHAEfAR0BHgEgASEBHgEhAR8BIAEiASMBIAEjASEBIgEkASUBIgElASMBJAEmAScBJAEnASUBJgEoASkBJgEpAScBKAEqASsBKAErASkBKgEsAS0BKgEtASsBLAEuAS8BLAEvAS0B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+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7CYSBu/r+f78JhIE70nsKvEtUf7+PI5O90nsKvEtUf7+PI5O9CYSBu/r+f78JhIE77i0SvKmHfb9/nA2+7i0SvKmHfb9/nA2+KvgZvEQzer/7hFi+KvgZvEQzer/7hFi+eIYgvOfNdb/D946+eIYgvOfNdb/D946+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zSmyu56+V79UzAk/zSmyu56+V79UzAk/Icmuu0VPYb+yD/M+Icmuu0VPYb+yD/M+5Kiqu+dSab95qNI+5Kiqu+dSab95qNI+c/Slu07bb7+M67I+c/Slu07bb7+M67I+f8efuwnpdb+tTY4+f8efuwnpdb+tTY4+QEWZuxJDer9hiVc+QEWZuxJDer9hiVc+6KSSu7kwfb8QOhc+6KSSu7kwfb8QOhc+8+eJu3lXf79zhpI98+eJu3lXf79zhpI9AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIBEQABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABAAA/4H7AP+B8gD/gfsA/4EEAP+B8gD/g+kA/4PpAP+E4QD/hOEA/4fYAP+H2AD/itAA/4rQAP+NyAD/jcgAAJK/AACSvwAAmLcAAJi3AACdrwAAna8AAKOpAACjqQAAqqIAAKqiAACwnQAAsJ0AALiXAAC4lwAAwJIAAMCSAADIjgAAyI4AANCKAADQigAA14cAANeHAADghAAA4IQAAOqCAADqggAA84EAAPOBAAD8gQAA/IEAAAOBAAADgQAADIEAAAyBAAAVggAAFYIAAB+EAAAfhAAAKIcAACiHAAAvigAAL4oAADeOAAA3jgAAP5IAAD+SAABHlwAAR5cAAE+dAABPnQAAVaIAAFWiAABcqQAAXKkAAGKvAABirwAAZ7cAAGe3AABtvwAAbb8AAHLIAAByyAAAddAAAHXQAAB42AAAeNgAAHvhAAB74QAAfOkAAHzpAAB+8gAAfvIAAH77AAB++wAAfgQAAH4EAAB+DQAAfg0AAHwWAAB8FgAAex4AAHseAAB4JwAAeCcAAHUwAAB1MAAAcTgAAHE4AABsQAAAbEAAAGdIAABnSAAAYk8AAGJPAABcVgAAXFYAAFZcAABWXAAAT2IAAE9iAABIZwAASGcAAEBsAABAbAAAOHEAADhxAAAwdQAAMHUAACd4AAAneAAAHnsAAB57AAAWfAAAFnwAAA1+AAANfgAABH4AAAR+AP/7fgD/+34A//J+AP/yfgD/6XwA/+l8AP/hewD/4XsA/9h4AP/YeAD/z3UA/891AP/HcQD/x3EA/79sAP+/bAD/t2cA/7dnAP+wYgD/sGIA/6lcAP+pXAD/o1YA/6NWAP+dTwD/nU8A/5hIAP+YSAD/k0AA/5NAAP+OOAD/jjgA/4owAP+KMAD/hycA/4cnAP+EHgD/hB4A/4MWAP+DFgD/gQ0A/4ENAP+BBABVAAAAAAAAABoAAAAAAAAAIgAAAAAAAAAjAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 2112, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 2112, + "byteLength": 2832, + "byteStride": 8, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 4944, + "byteLength": 4248, + "byteStride": 12, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 9192, + "byteLength": 1416, + "byteStride": 4, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 10608, + "byteLength": 1416, + "byteStride": 4, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 12024, + "byteLength": 264, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 12288, + "byteLength": 704, + "byteStride": 4, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 12992, + "byteLength": 8 + }, + { + "buffer": 0, + "byteOffset": 13000, + "byteLength": 8 + }, + { + "buffer": 0, + "byteOffset": 13008, + "byteLength": 8 + }, + { + "buffer": 0, + "byteOffset": 13016, + "byteLength": 8 + }, + { + "buffer": 0, + "byteOffset": 13024, + "byteLength": 4 + }, + { + "buffer": 0, + "byteOffset": 13028, + "byteLength": 8 + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "metallicFactor": 0.0 + }, + "doubleSided": true, + "extensions": { + "BENTLEY_materials_line_style": { + "width": 5, + "pattern": 61680 + } + } + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 2, + "POSITION": 1, + "_FEATURE_ID_0": 4, + "COLOR_0": 3 + }, + "indices": 0, + "material": 0, + "extensions": { + "EXT_mesh_features": { + "featureIds": [ + { + "featureCount": 1, + "attribute": 0, + "propertyTable": 0 + }, + { + "featureCount": 1, + "attribute": 0, + "propertyTable": 1 + } + ] + }, + "EXT_mesh_primitive_edge_visibility": { + "visibility": 5, + "silhouetteNormals": 6 + } + } + } + ] + } + ], + "nodes": [ + { + "matrix": [ + 0.00012359807736324102, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.00012359807736324102, + 0.0, + 0.0, + 0.00012359807736324102, + 0.0, + 0.0, + -4.1, + -4.1, + 4.1, + 1.0 + ], + "mesh": 0 + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "extensions": { + "EXT_structural_metadata": { + "schema": { + "id": "", + "classes": { + "materials": { + "properties": { + "material": { + "type": "SCALAR", + "componentType": "UINT64" + } + } + }, + "MeshPart": { + "properties": { + "element": { + "type": "SCALAR", + "componentType": "UINT64" + }, + "model": { + "type": "SCALAR", + "componentType": "UINT64" + }, + "subcategory": { + "type": "SCALAR", + "componentType": "UINT64" + }, + "category": { + "type": "SCALAR", + "componentType": "UINT64" + }, + "geometryClass": { + "type": "ENUM", + "componentType": "UINT8", + "enumType": "geometryClassEnum" + } + } + } + }, + "enums": { + "geometryClassEnum": { + "name": "Geometry Class", + "description": "", + "valueType": "UINT8", + "values": [ + { + "name": "Primary", + "description": "", + "value": 0 + }, + { + "name": "Construction", + "description": "", + "value": 1 + }, + { + "name": "Dimension", + "description": "", + "value": 2 + }, + { + "name": "Pattern", + "description": "", + "value": 3 + } + ] + } + } + }, + "propertyTables": [ + { + "class": "MeshPart", + "count": 1, + "properties": { + "element": { + "values": 7 + }, + "model": { + "values": 8 + }, + "category": { + "values": 9 + }, + "subcategory": { + "values": 10 + }, + "geometryClass": { + "values": 11 + } + } + }, + { + "class": "materials", + "count": 1, + "properties": { + "material": { + "values": 12 + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/Apps/SampleData/models/StyledPoints/points-r5-g8-b14-y10.gltf b/Apps/SampleData/models/StyledPoints/points-r5-g8-b14-y10.gltf new file mode 100755 index 000000000000..3de571c1a3b3 --- /dev/null +++ b/Apps/SampleData/models/StyledPoints/points-r5-g8-b14-y10.gltf @@ -0,0 +1,222 @@ +{ + "asset": { + "version": "2.0" + }, + "extensionsUsed": [ + "BENTLEY_materials_point_style" + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5126, + "count": 4, + "type": "VEC3", + "max": [ + 5.0, + 5.0, + 5.0 + ], + "min": [ + -5.0, + -5.0, + -5.0 + ] + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5123, + "count": 1, + "type": "SCALAR", + "max": [ + 0 + ], + "min": [ + 0 + ] + }, + { + "bufferView": 1, + "byteOffset": 4, + "componentType": 5123, + "count": 1, + "type": "SCALAR", + "max": [ + 1 + ], + "min": [ + 1 + ] + }, + { + "bufferView": 1, + "byteOffset": 8, + "componentType": 5123, + "count": 1, + "type": "SCALAR", + "max": [ + 2 + ], + "min": [ + 2 + ] + }, + { + "bufferView": 1, + "byteOffset": 12, + "componentType": 5123, + "count": 1, + "type": "SCALAR", + "max": [ + 3 + ], + "min": [ + 3 + ] + } + ], + "buffers": [ + { + "byteLength": 64, + "uri": "data:application/octet-stream;base64,AACgwAAAoMAAAKDAAACgQAAAoMAAAKDAAAAAAAAAoEAAAKDAAAAAAAAAAAAAAKBAAAAAAAEAAAACAAAAAwAAAA==" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 48, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 48, + "byteLength": 16, + "target": 34963 + } + ], + "materials": [ + { + "name": "Red", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 1.0, + 0.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "extensions": { + "BENTLEY_materials_point_style": { + "diameter": 5.0 + } + } + }, + { + "name": "Green", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.0, + 1.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "extensions": { + "BENTLEY_materials_point_style": { + "diameter": 8.0 + } + } + }, + { + "name": "Blue", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.0, + 0.0, + 1.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "extensions": { + "BENTLEY_materials_point_style": { + "diameter": 14.0 + } + } + }, + { + "name": "Yellow", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 1.0, + 1.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "extensions": { + "BENTLEY_materials_point_style": { + "diameter": 10.0 + } + } + } + ], + "meshes": [ + { + "name": "PointCloud", + "primitives": [ + { + "mode": 0, + "material": 0, + "indices": 1, + "attributes": { + "POSITION": 0 + } + }, + { + "mode": 0, + "material": 1, + "indices": 2, + "attributes": { + "POSITION": 0 + } + }, + { + "mode": 0, + "material": 2, + "indices": 3, + "attributes": { + "POSITION": 0 + } + }, + { + "mode": 0, + "material": 3, + "indices": 4, + "attributes": { + "POSITION": 0 + } + } + ] + } + ], + "nodes": [ + { + "name": "PointCloudNode", + "mesh": 0 + } + ], + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "scene": 0 +} diff --git a/CHANGES.md b/CHANGES.md index 9e7c825dac05..a7e736c39c23 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,12 @@ - Fixes label positioning in workflows that delete and recreate clamped labels [#12949](https://github.com/CesiumGS/cesium/issues/12949) +#### Additions :tada: + +- Added support for the proposed [BENTLEY_materials_point_style](https://github.com/CesiumGS/glTF/pull/91) glTF extension. This allows point primitives to have a diameter property specified and respected when loaded via glTF. +- Added support for the proposed [BENTLEY_materials_line_style](https://github.com/CesiumGS/glTF/pull/XXX) glTF extension. This enables CAD-style line visualization with variable width and dash patterns. Lines and edges can now have customizable `width` (in screen pixels) and `pattern` (16-bit repeating on/off pattern) properties when loaded via glTF. +- Refactored `EXT_mesh_primitive_edge_visibility` implementation to use quad-based rendering instead of `gl_line` primitives. This enables variable line width support, as WebGL does not support line widths greater than 1. Each edge is now tessellated into a quad (4 vertices, 2 triangles) that expands perpendicular to the edge direction based on the material's width property. + ## 1.136 - 2025-12-01 ### @cesium/engine @@ -38,6 +44,7 @@ - Added experimental support for loading 3D Tiles as terrain, via `Cesium3DTilesTerrainProvider`. See [the PR](https://github.com/CesiumGS/cesium/pull/12963) for limitations on the types of 3D Tiles that can be used. [#12296](https://github.com/CesiumGS/cesium/issues/12296) - Added support for [EXT_mesh_primitive_edge_visibility](https://github.com/KhronosGroup/glTF/pull/2479) glTF extension. [#12765](https://github.com/CesiumGS/cesium/issues/12765) +- Extended edge visibility loading to honor material colors and line-string overrides from EXT_mesh_primitive_edge_visibility. #### Fixes :wrench: diff --git a/Specs/Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibilityLineString.glb b/Specs/Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibilityLineString.glb new file mode 100644 index 000000000000..a089101d26b3 Binary files /dev/null and b/Specs/Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibilityLineString.glb differ diff --git a/Specs/Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibilityMaterial.glb b/Specs/Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibilityMaterial.glb new file mode 100644 index 000000000000..e2896cbebad4 Binary files /dev/null and b/Specs/Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibilityMaterial.glb differ diff --git a/Specs/Data/Models/glTF-2.0/StyledLines/BENTLEY_materials_line_style.gltf b/Specs/Data/Models/glTF-2.0/StyledLines/BENTLEY_materials_line_style.gltf new file mode 100644 index 000000000000..242c1c37cd85 --- /dev/null +++ b/Specs/Data/Models/glTF-2.0/StyledLines/BENTLEY_materials_line_style.gltf @@ -0,0 +1,339 @@ +{ + "extensionsUsed": [ + "KHR_mesh_quantization", + "EXT_mesh_features", + "EXT_mesh_primitive_edge_visibility", + "EXT_structural_metadata", + "BENTLEY_materials_line_style" + ], + "extensionsRequired": [ + "KHR_mesh_quantization" + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5123, + "count": 1056, + "type": "SCALAR" + }, + { + "bufferView": 1, + "componentType": 5123, + "count": 354, + "type": "VEC3", + "max": [ + 63939.0, + 45112.0, + 44922.0 + ], + "min": [ + 2405.0, + 21232.0, + 21422.0 + ] + }, + { + "bufferView": 2, + "componentType": 5126, + "count": 354, + "type": "VEC3" + }, + { + "bufferView": 3, + "componentType": 5121, + "normalized": true, + "count": 354, + "type": "VEC4" + }, + { + "bufferView": 4, + "componentType": 5126, + "count": 354, + "type": "SCALAR" + }, + { + "bufferView": 5, + "componentType": 5121, + "count": 264, + "type": "SCALAR" + }, + { + "bufferView": 6, + "componentType": 5120, + "count": 176, + "type": "VEC3" + } + ], + "asset": { + "generator": "3DFT", + "version": "2.0" + }, + "buffers": [ + { + "byteLength": 13036, + "uri": "data:application/octet-stream;base64,AAABAAIAAQADAAIAAwAEAAIABAAFAAIABQAGAAIABgAHAAIABwAIAAIACAAJAAIACQAKAAIACgALAAIACwAMAAIADAANAAIADQAOAAIADgAPAAIADwAQAAIAEAARAAIAEQASAAIAEgATAAIAEwAUAAIAFAAVAAIAFQAWAAIAFgAXAAIAFwAYAAIAGAAZAAIAGQAaAAIAGgAbAAIAGwAcAAIAHAAdAAIAHQAeAAIAHgAfAAIAHwAgAAIAIAAhAAIAIQAiAAIAIgAjAAIAIwAkAAIAJAAlAAIAJQAmAAIAJgAnAAIAJwAoAAIAKAApAAIAKQAqAAIAKgArAAIAKwAsAAIALAAtAAIALQAuAAIALgAvAAIALwAwAAIAMAAxAAIAMQAyAAIAMgAzAAIAMwA0AAIANAA1AAIANQA2AAIANgA3AAIANwA4AAIAOAA5AAIAOQA6AAIAOgA7AAIAOwA8AAIAPAA9AAIAPQA+AAIAPgA/AAIAPwBAAAIAQABBAAIAQQBCAAIAQgBDAAIAQwBEAAIARABFAAIARQBGAAIARgBHAAIARwBIAAIASABJAAIASQBKAAIASgBLAAIASwBMAAIATABNAAIATQBOAAIATgBPAAIATwBQAAIAUABRAAIAUQBSAAIAUgBTAAIAUwBUAAIAVABVAAIAVQBWAAIAVgBXAAIAVwBYAAIAWAAAAAIAWQBaAFsAWgBcAFsAXABdAFsAXQBeAFsAXgBfAFsAXwBgAFsAYABhAFsAYQBiAFsAYgBjAFsAYwBkAFsAZABlAFsAZQBmAFsAZgBnAFsAZwBoAFsAaABpAFsAaQBqAFsAagBrAFsAawBsAFsAbABtAFsAbQBuAFsAbgBvAFsAbwBwAFsAcABxAFsAcQByAFsAcgBzAFsAcwB0AFsAdAB1AFsAdQB2AFsAdgB3AFsAdwB4AFsAeAB5AFsAeQB6AFsAegB7AFsAewB8AFsAfAB9AFsAfQB+AFsAfgB/AFsAfwCAAFsAgACBAFsAgQCCAFsAggCDAFsAgwCEAFsAhACFAFsAhQCGAFsAhgCHAFsAhwCIAFsAiACJAFsAiQCKAFsAigCLAFsAiwCMAFsAjACNAFsAjQCOAFsAjgCPAFsAjwCQAFsAkACRAFsAkQCSAFsAkgCTAFsAkwCUAFsAlACVAFsAlQCWAFsAlgCXAFsAlwCYAFsAmACZAFsAmQCaAFsAmgCbAFsAmwCcAFsAnACdAFsAnQCeAFsAngCfAFsAnwCgAFsAoAChAFsAoQCiAFsAogCjAFsAowCkAFsApAClAFsApQCmAFsApgCnAFsApwCoAFsAqACpAFsAqQCqAFsAqgCrAFsAqwCsAFsArACtAFsArQCuAFsArgCvAFsArwCwAFsAsACxAFsAsQBZAFsAsgCzALQAsgC0ALUAswC2ALcAswC3ALQAtgC4ALkAtgC5ALcAuAC6ALsAuAC7ALkAugC8AL0AugC9ALsAvAC+AL8AvAC/AL0AvgDAAMEAvgDBAL8AwADCAMMAwADDAMEAwgDEAMUAwgDFAMMAxADGAMcAxADHAMUAxgDIAMkAxgDJAMcAyADKAMsAyADLAMkAygDMAM0AygDNAMsAzADOAM8AzADPAM0AzgDQANEAzgDRAM8A0ADSANMA0ADTANEA0gDUANUA0gDVANMA1ADWANcA1ADXANUA1gDYANkA1gDZANcA2ADaANsA2ADbANkA2gDcAN0A2gDdANsA3ADeAN8A3ADfAN0A3gDgAOEA3gDhAN8A4ADiAOMA4ADjAOEA4gDkAOUA4gDlAOMA5ADmAOcA5ADnAOUA5gDoAOkA5gDpAOcA6ADqAOsA6ADrAOkA6gDsAO0A6gDtAOsA7ADuAO8A7ADvAO0A7gDwAPEA7gDxAO8A8ADyAPMA8ADzAPEA8gD0APUA8gD1APMA9AD2APcA9AD3APUA9gD4APkA9gD5APcA+AD6APsA+AD7APkA+gD8AP0A+gD9APsA/AD+AP8A/AD/AP0A/gAAAQEB/gABAf8AAAECAQMBAAEDAQEBAgEEAQUBAgEFAQMBBAEGAQcBBAEHAQUBBgEIAQkBBgEJAQcBCAEKAQsBCAELAQkBCgEMAQ0BCgENAQsBDAEOAQ8BDAEPAQ0BDgEQAREBDgERAQ8BEAESARMBEAETAREBEgEUARUBEgEVARMBFAEWARcBFAEXARUBFgEYARkBFgEZARcBGAEaARsBGAEbARkBGgEcAR0BGgEdARsBHAEeAR8BHAEfAR0BHgEgASEBHgEhAR8BIAEiASMBIAEjASEBIgEkASUBIgElASMBJAEmAScBJAEnASUBJgEoASkBJgEpAScBKAEqASsBKAErASkBKgEsAS0BKgEtASsBLAEuAS8BLAEvAS0BLgEwATEBLgExAS8BMAEyATMBMAEzATEBMgE0ATUBMgE1ATMBNAE2ATcBNAE3ATUBNgE4ATkBNgE5ATcBOAE6ATsBOAE7ATkBOgE8AT0BOgE9ATsBPAE+AT8BPAE/AT0BPgFAAUEBPgFBAT8BQAFCAUMBQAFDAUEBQgFEAUUBQgFFAUMBRAFGAUcBRAFHAUUBRgFIAUkBRgFJAUcBSAFKAUsBSAFLAUkBSgFMAU0BSgFNAUsBTAFOAU8BTAFPAU0BTgFQAVEBTgFRAU8BUAFSAVMBUAFTAVEBUgFUAVUBUgFVAVMBVAFWAVcBVAFXAVUBVgFYAVkBVgFZAVcBWAFaAVsBWAFbAVkBWgFcAV0BWgFdAVsBXAFeAV8BXAFfAV0BXgFgAWEBXgFhAV8BYAGyALUAYAG1AGEBZQlsVJSBAABlCYpU2oQAAK4JUoKUgQAAZgnjVByIAABnCXhVVosAAGgJSFaCjgAAaglRV56RAABsCZJYpZQAAG4JCVqTlwAAcQm1W2WaAABzCZNdFp0AAHcJol+jnwAAegndYQmiAAB+CUNkRKQAAIIJ0GZTpgAAhgmBaTGoAACLCVNs3akAAI8JQW9UqwAAlAlIcpWsAACZCWR1nq0AAJ4JkHhurgAAownKewOvAACoCQx/XK8AAK4JUoJ6rwAAswmYhVyvAAC4CdqIA68AAL0JFIxurgAAwglAj56tAADHCVySlawAAMwJY5VUqwAA0QlRmN2pAADVCSKbMagAANkJ051TpgAA3QlhoESkAADhCcaiCaIAAOUJAqWjnwAA6AkQpxadAADrCe+oZZoAAO0Jm6qTlwAA8AkSrKWUAADyCVOtnpEAAPMJXK6CjgAA9Qkrr1aLAAD2CcCvHIgAAPYJGrDahAAA9gk4sJSBAAD2CRqwTn4AAPYJwK8MewAA9Qkrr9J3AADzCVyupnQAAPIJU62KcQAA8AkSrINuAADtCZuqlWsAAOsJ76jDaAAA6AkQpxJmAADlCQKlhWMAAOEJxqIfYQAA3QlhoOReAADZCdOd1VwAANUJIpv3WgAA0QlRmEtZAADMCWOV1FcAAMcJXJKTVgAAwglAj4pVAAC9CRSMulQAALgJ2oglVAAAswmYhcxTAACuCVKCrlMAAKgJDH/MUwAAownKeyVUAACeCZB4ulQAAJkJZHWKVQAAlAlIcpNWAACPCUFv1FcAAIsJU2xLWQAAhgmBafdaAACCCdBm1VwAAH4JQ2TkXgAAegndYR9hAAB3CaJfhWMAAHMJk10SZgAAcQm1W8NoAABuCQlalWsAAGwJkliDbgAAaglRV4pxAABoCUhWpnQAAGcJeFXSdwAAZgnjVAx7AABlCYpUTn4AADL58FKUgQAAMvkOU05+AAB6+daAlIEAADL5aFMMewAAM/n9U9J3AAA1+cxUpnQAADb51VWKcQAAOPkWV4NuAAA7+Y1YlWsAAD35OVrDaAAAQPkYXBJmAABD+SZehWMAAEf5YmAfYQAAS/nIYuReAABP+VVl1VwAAFP5Bmj3WgAAWPnXaktZAABc+cVt1FcAAGH5zHCTVgAAZvnoc4pVAABr+RV3ulQAAHD5TnolVAAAdfmQfcxTAAB6+daArlMAAID5HITMUwAAhflehyVUAACK+ZiKulQAAI/5xY2KVQAAlPngkJNWAACZ+eeT1FcAAJ351ZZLWQAAovmnmfdaAACm+Vic1VwAAKr55Z7kXgAArvlLoR9hAACx+YajhWMAALX5laUSZgAAuPlzp8NoAAC6+R+plWsAAL35lqqDbgAAv/nXq4pxAADA+eCspnQAAMH5sK3SdwAAwvlFrgx7AADD+Z6uTn4AAMP5vK6UgQAAw/mertqEAADC+UWuHIgAAMH5sK1WiwAAwPngrIKOAAC/+dernpEAAL35lqqllAAAuvkfqZOXAAC4+XOnZZoAALX5laUWnQAAsfmGo6OfAACu+UuhCaIAAKr55Z5EpAAApvlYnFOmAACi+aeZMagAAJ351ZbdqQAAmfnnk1SrAACU+eCQlawAAI/5xY2erQAAivmYim6uAACF+V6HA68AAID5HIRcrwAAevnWgHqvAAB1+ZB9XK8AAHD5TnoDrwAAa/kVd26uAABm+ehznq0AAGH5zHCVrAAAXPnFbVSrAABY+ddq3akAAFP5BmgxqAAAT/lVZVOmAABL+chiRKQAAEf5YmAJogAAQ/kmXqOfAABA+RhcFp0AAD35OVplmgAAO/mNWJOXAAA4+RZXpZQAADb51VWekQAANfnMVIKOAAAz+f1TVosAADL5aFMciAAAMvkOU9qEAABlCWxUlIEAAGUJilROfgAAMvkOU05+AAAy+fBSlIEAAGYJ41QMewAAMvloUwx7AABnCXhV0ncAADP5/VPSdwAAaAlIVqZ0AAA1+cxUpnQAAGoJUVeKcQAANvnVVYpxAABsCZJYg24AADj5FleDbgAAbgkJWpVrAAA7+Y1YlWsAAHEJtVvDaAAAPfk5WsNoAABzCZNdEmYAAED5GFwSZgAAdwmiX4VjAABD+SZehWMAAHoJ3WEfYQAAR/liYB9hAAB+CUNk5F4AAEv5yGLkXgAAggnQZtVcAABP+VVl1VwAAIYJgWn3WgAAU/kGaPdaAACLCVNsS1kAAFj512pLWQAAjwlBb9RXAABc+cVt1FcAAJQJSHKTVgAAYfnMcJNWAACZCWR1ilUAAGb56HOKVQAAngmQeLpUAABr+RV3ulQAAKMJynslVAAAcPlOeiVUAACoCQx/zFMAAHX5kH3MUwAArglSgq5TAAB6+daArlMAALMJmIXMUwAAgPkchMxTAAC4CdqIJVQAAIX5XoclVAAAvQkUjLpUAACK+ZiKulQAAMIJQI+KVQAAj/nFjYpVAADHCVySk1YAAJT54JCTVgAAzAljldRXAACZ+eeT1FcAANEJUZhLWQAAnfnVlktZAADVCSKb91oAAKL5p5n3WgAA2QnTndVcAACm+Vic1VwAAN0JYaDkXgAAqvnlnuReAADhCcaiH2EAAK75S6EfYQAA5QkCpYVjAACx+YajhWMAAOgJEKcSZgAAtfmVpRJmAADrCe+ow2gAALj5c6fDaAAA7QmbqpVrAAC6+R+plWsAAPAJEqyDbgAAvfmWqoNuAADyCVOtinEAAL/516uKcQAA8wlcrqZ0AADA+eCspnQAAPUJK6/SdwAAwfmwrdJ3AAD2CcCvDHsAAML5Ra4MewAA9gkasE5+AADD+Z6uTn4AAPYJOLCUgQAAw/m8rpSBAAD2CRqw2oQAAMP5nq7ahAAA9gnArxyIAADC+UWuHIgAAPUJK69WiwAAwfmwrVaLAADzCVyugo4AAMD54KyCjgAA8glTrZ6RAAC/+dernpEAAPAJEqyllAAAvfmWqqWUAADtCZuqk5cAALr5H6mTlwAA6wnvqGWaAAC4+XOnZZoAAOgJEKcWnQAAtfmVpRadAADlCQKlo58AALH5hqOjnwAA4QnGogmiAACu+UuhCaIAAN0JYaBEpAAAqvnlnkSkAADZCdOdU6YAAKb5WJxTpgAA1QkimzGoAACi+aeZMagAANEJUZjdqQAAnfnVlt2pAADMCWOVVKsAAJn555NUqwAAxwlckpWsAACU+eCQlawAAMIJQI+erQAAj/nFjZ6tAAC9CRSMbq4AAIr5mIpurgAAuAnaiAOvAACF+V6HA68AALMJmIVcrwAAgPkchFyvAACuCVKCeq8AAHr51oB6rwAAqAkMf1yvAAB1+ZB9XK8AAKMJynsDrwAAcPlOegOvAACeCZB4bq4AAGv5FXdurgAAmQlkdZ6tAABm+ehznq0AAJQJSHKVrAAAYfnMcJWsAACPCUFvVKsAAFz5xW1UqwAAiwlTbN2pAABY+ddq3akAAIYJgWkxqAAAU/kGaDGoAACCCdBmU6YAAE/5VWVTpgAAfglDZESkAABL+chiRKQAAHoJ3WEJogAAR/liYAmiAAB3CaJfo58AAEP5Jl6jnwAAcwmTXRadAABA+RhcFp0AAHEJtVtlmgAAPfk5WmWaAABuCQlak5cAADv5jViTlwAAbAmSWKWUAAA4+RZXpZQAAGoJUVeekQAANvnVVZ6RAABoCUhWgo4AADX5zFSCjgAAZwl4VVaLAAAz+f1TVosAAGYJ41QciAAAMvloUxyIAABlCYpU2oQAADL5DlPahAAA+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/vwmEgTsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7+v5/PwmEgbsJhIE7CYSBu/r+f78JhIE70nsKvEtUf7+PI5O90nsKvEtUf7+PI5O9CYSBu/r+f78JhIE77i0SvKmHfb9/nA2+7i0SvKmHfb9/nA2+KvgZvEQzer/7hFi+KvgZvEQzer/7hFi+eIYgvOfNdb/D946+eIYgvOfNdb/D946+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zSmyu56+V79UzAk/zSmyu56+V79UzAk/Icmuu0VPYb+yD/M+Icmuu0VPYb+yD/M+5Kiqu+dSab95qNI+5Kiqu+dSab95qNI+c/Slu07bb7+M67I+c/Slu07bb7+M67I+f8efuwnpdb+tTY4+f8efuwnpdb+tTY4+QEWZuxJDer9hiVc+QEWZuxJDer9hiVc+6KSSu7kwfb8QOhc+6KSSu7kwfb8QOhc+8+eJu3lXf79zhpI98+eJu3lXf79zhpI9AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIgiAIBEQABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABEAABAAA/4H7AP+B8gD/gfsA/4EEAP+B8gD/g+kA/4PpAP+E4QD/hOEA/4fYAP+H2AD/itAA/4rQAP+NyAD/jcgAAJK/AACSvwAAmLcAAJi3AACdrwAAna8AAKOpAACjqQAAqqIAAKqiAACwnQAAsJ0AALiXAAC4lwAAwJIAAMCSAADIjgAAyI4AANCKAADQigAA14cAANeHAADghAAA4IQAAOqCAADqggAA84EAAPOBAAD8gQAA/IEAAAOBAAADgQAADIEAAAyBAAAVggAAFYIAAB+EAAAfhAAAKIcAACiHAAAvigAAL4oAADeOAAA3jgAAP5IAAD+SAABHlwAAR5cAAE+dAABPnQAAVaIAAFWiAABcqQAAXKkAAGKvAABirwAAZ7cAAGe3AABtvwAAbb8AAHLIAAByyAAAddAAAHXQAAB42AAAeNgAAHvhAAB74QAAfOkAAHzpAAB+8gAAfvIAAH77AAB++wAAfgQAAH4EAAB+DQAAfg0AAHwWAAB8FgAAex4AAHseAAB4JwAAeCcAAHUwAAB1MAAAcTgAAHE4AABsQAAAbEAAAGdIAABnSAAAYk8AAGJPAABcVgAAXFYAAFZcAABWXAAAT2IAAE9iAABIZwAASGcAAEBsAABAbAAAOHEAADhxAAAwdQAAMHUAACd4AAAneAAAHnsAAB57AAAWfAAAFnwAAA1+AAANfgAABH4AAAR+AP/7fgD/+34A//J+AP/yfgD/6XwA/+l8AP/hewD/4XsA/9h4AP/YeAD/z3UA/891AP/HcQD/x3EA/79sAP+/bAD/t2cA/7dnAP+wYgD/sGIA/6lcAP+pXAD/o1YA/6NWAP+dTwD/nU8A/5hIAP+YSAD/k0AA/5NAAP+OOAD/jjgA/4owAP+KMAD/hycA/4cnAP+EHgD/hB4A/4MWAP+DFgD/gQ0A/4ENAP+BBABVAAAAAAAAABoAAAAAAAAAIgAAAAAAAAAjAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 2112, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 2112, + "byteLength": 2832, + "byteStride": 8, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 4944, + "byteLength": 4248, + "byteStride": 12, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 9192, + "byteLength": 1416, + "byteStride": 4, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 10608, + "byteLength": 1416, + "byteStride": 4, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 12024, + "byteLength": 264, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 12288, + "byteLength": 704, + "byteStride": 4, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 12992, + "byteLength": 8 + }, + { + "buffer": 0, + "byteOffset": 13000, + "byteLength": 8 + }, + { + "buffer": 0, + "byteOffset": 13008, + "byteLength": 8 + }, + { + "buffer": 0, + "byteOffset": 13016, + "byteLength": 8 + }, + { + "buffer": 0, + "byteOffset": 13024, + "byteLength": 4 + }, + { + "buffer": 0, + "byteOffset": 13028, + "byteLength": 8 + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "metallicFactor": 0.0 + }, + "doubleSided": true, + "extensions": { + "BENTLEY_materials_line_style": { + "width": 5, + "pattern": 61680 + } + } + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 2, + "POSITION": 1, + "_FEATURE_ID_0": 4, + "COLOR_0": 3 + }, + "indices": 0, + "material": 0, + "extensions": { + "EXT_mesh_features": { + "featureIds": [ + { + "featureCount": 1, + "attribute": 0, + "propertyTable": 0 + }, + { + "featureCount": 1, + "attribute": 0, + "propertyTable": 1 + } + ] + }, + "EXT_mesh_primitive_edge_visibility": { + "visibility": 5, + "silhouetteNormals": 6 + } + } + } + ] + } + ], + "nodes": [ + { + "matrix": [ + 0.00012359807736324102, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.00012359807736324102, + 0.0, + 0.0, + 0.00012359807736324102, + 0.0, + 0.0, + -4.1, + -4.1, + 4.1, + 1.0 + ], + "mesh": 0 + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "extensions": { + "EXT_structural_metadata": { + "schema": { + "id": "", + "classes": { + "materials": { + "properties": { + "material": { + "type": "SCALAR", + "componentType": "UINT64" + } + } + }, + "MeshPart": { + "properties": { + "element": { + "type": "SCALAR", + "componentType": "UINT64" + }, + "model": { + "type": "SCALAR", + "componentType": "UINT64" + }, + "subcategory": { + "type": "SCALAR", + "componentType": "UINT64" + }, + "category": { + "type": "SCALAR", + "componentType": "UINT64" + }, + "geometryClass": { + "type": "ENUM", + "componentType": "UINT8", + "enumType": "geometryClassEnum" + } + } + } + }, + "enums": { + "geometryClassEnum": { + "name": "Geometry Class", + "description": "", + "valueType": "UINT8", + "values": [ + { + "name": "Primary", + "description": "", + "value": 0 + }, + { + "name": "Construction", + "description": "", + "value": 1 + }, + { + "name": "Dimension", + "description": "", + "value": 2 + }, + { + "name": "Pattern", + "description": "", + "value": 3 + } + ] + } + } + }, + "propertyTables": [ + { + "class": "MeshPart", + "count": 1, + "properties": { + "element": { + "values": 7 + }, + "model": { + "values": 8 + }, + "category": { + "values": 9 + }, + "subcategory": { + "values": 10 + }, + "geometryClass": { + "values": 11 + } + } + }, + { + "class": "materials", + "count": 1, + "properties": { + "material": { + "values": 12 + } + } + } + ] + } + } +} \ No newline at end of file diff --git a/Specs/Data/Models/glTF-2.0/StyledPoints/points-r5-g8-b14-y10.gltf b/Specs/Data/Models/glTF-2.0/StyledPoints/points-r5-g8-b14-y10.gltf new file mode 100755 index 000000000000..3de571c1a3b3 --- /dev/null +++ b/Specs/Data/Models/glTF-2.0/StyledPoints/points-r5-g8-b14-y10.gltf @@ -0,0 +1,222 @@ +{ + "asset": { + "version": "2.0" + }, + "extensionsUsed": [ + "BENTLEY_materials_point_style" + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5126, + "count": 4, + "type": "VEC3", + "max": [ + 5.0, + 5.0, + 5.0 + ], + "min": [ + -5.0, + -5.0, + -5.0 + ] + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5123, + "count": 1, + "type": "SCALAR", + "max": [ + 0 + ], + "min": [ + 0 + ] + }, + { + "bufferView": 1, + "byteOffset": 4, + "componentType": 5123, + "count": 1, + "type": "SCALAR", + "max": [ + 1 + ], + "min": [ + 1 + ] + }, + { + "bufferView": 1, + "byteOffset": 8, + "componentType": 5123, + "count": 1, + "type": "SCALAR", + "max": [ + 2 + ], + "min": [ + 2 + ] + }, + { + "bufferView": 1, + "byteOffset": 12, + "componentType": 5123, + "count": 1, + "type": "SCALAR", + "max": [ + 3 + ], + "min": [ + 3 + ] + } + ], + "buffers": [ + { + "byteLength": 64, + "uri": "data:application/octet-stream;base64,AACgwAAAoMAAAKDAAACgQAAAoMAAAKDAAAAAAAAAoEAAAKDAAAAAAAAAAAAAAKBAAAAAAAEAAAACAAAAAwAAAA==" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 48, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 48, + "byteLength": 16, + "target": 34963 + } + ], + "materials": [ + { + "name": "Red", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 1.0, + 0.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "extensions": { + "BENTLEY_materials_point_style": { + "diameter": 5.0 + } + } + }, + { + "name": "Green", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.0, + 1.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "extensions": { + "BENTLEY_materials_point_style": { + "diameter": 8.0 + } + } + }, + { + "name": "Blue", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.0, + 0.0, + 1.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "extensions": { + "BENTLEY_materials_point_style": { + "diameter": 14.0 + } + } + }, + { + "name": "Yellow", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 1.0, + 1.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "extensions": { + "BENTLEY_materials_point_style": { + "diameter": 10.0 + } + } + } + ], + "meshes": [ + { + "name": "PointCloud", + "primitives": [ + { + "mode": 0, + "material": 0, + "indices": 1, + "attributes": { + "POSITION": 0 + } + }, + { + "mode": 0, + "material": 1, + "indices": 2, + "attributes": { + "POSITION": 0 + } + }, + { + "mode": 0, + "material": 2, + "indices": 3, + "attributes": { + "POSITION": 0 + } + }, + { + "mode": 0, + "material": 3, + "indices": 4, + "attributes": { + "POSITION": 0 + } + } + ] + } + ], + "nodes": [ + { + "name": "PointCloudNode", + "mesh": 0 + } + ], + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "scene": 0 +} diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index 800d4a7c2428..3ace47e0ba9c 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -1811,6 +1811,35 @@ function loadMaterial(loader, gltfMaterial, frameState) { material.alphaCutoff = gltfMaterial.alphaCutoff; material.doubleSided = gltfMaterial.doubleSided; + // BENTLEY_materials_point_style extension + const pointStyleExtension = extensions.BENTLEY_materials_point_style; + if (defined(pointStyleExtension) && defined(pointStyleExtension.diameter)) { + const diameter = pointStyleExtension.diameter; + // Validate that diameter is a positive integer as the extension specification requires. + if (diameter > 0 && Math.floor(diameter) === diameter) { + material.pointDiameter = diameter; + } + } + + // BENTLEY_materials_line_style extension + const lineStyleExtension = extensions.BENTLEY_materials_line_style; + if (defined(lineStyleExtension)) { + if (defined(lineStyleExtension.width)) { + const width = lineStyleExtension.width; + // Validate that width is a positive integer as the extension specification requires. + if (width > 0 && Math.floor(width) === width) { + material.lineWidth = width; + } + } + if (defined(lineStyleExtension.pattern)) { + const pattern = lineStyleExtension.pattern; + // Validate that pattern is a non-negative integer (16-bit unsigned) + if (pattern >= 0 && pattern <= 65535 && Math.floor(pattern) === pattern) { + material.linePattern = pattern; + } + } + } + return material; } @@ -2002,6 +2031,89 @@ function fetchSpzExtensionFrom(extensions) { return undefined; } +function getEdgeVisibilityMaterialColor(loader, materialIndex) { + if (!defined(materialIndex)) { + return undefined; + } + + const materials = loader.gltfJson.materials; + if ( + !defined(materials) || + materialIndex < 0 || + materialIndex >= materials.length + ) { + return undefined; + } + + const material = materials[materialIndex]; + if (!defined(material)) { + return undefined; + } + + const metallicRoughness = + material.pbrMetallicRoughness ?? Frozen.EMPTY_OBJECT; + const color = fromArray(Cartesian4, metallicRoughness.baseColorFactor); + + if (defined(color)) { + return color; + } + + return new Cartesian4(1.0, 1.0, 1.0, 1.0); +} + +function getLineStringPrimitiveRestartValue(componentType) { + switch (componentType) { + case ComponentDatatype.UNSIGNED_BYTE: + return 255; + case ComponentDatatype.UNSIGNED_SHORT: + return 65535; + case ComponentDatatype.UNSIGNED_INT: + return 4294967295; + default: + throw new RuntimeError( + "EXT_mesh_primitive_edge_visibility line strings indices must use unsigned scalar component types.", + ); + } +} + +function loadEdgeVisibilityLineStrings( + loader, + lineStringDefinitions, + defaultMaterialIndex, +) { + if (!defined(lineStringDefinitions) || lineStringDefinitions.length === 0) { + return undefined; + } + + const result = new Array(lineStringDefinitions.length); + for (let i = 0; i < lineStringDefinitions.length; i++) { + const definition = lineStringDefinitions[i] ?? Frozen.EMPTY_OBJECT; + const accessorId = definition.indices; + const accessor = loader.gltfJson.accessors[accessorId]; + + if (!defined(accessor)) { + throw new RuntimeError("Edge visibility line string accessor not found!"); + } + + const indices = loadAccessor(loader, accessor); + const restartIndex = getLineStringPrimitiveRestartValue( + accessor.componentType, + ); + const materialIndex = defined(definition.material) + ? definition.material + : defaultMaterialIndex; + + result[i] = { + indices: indices, + restartIndex: restartIndex, + componentType: accessor.componentType, + materialColor: getEdgeVisibilityMaterialColor(loader, materialIndex), + }; + } + + return result; +} + /** * Load resources associated with a mesh primitive for a glTF node * @param {GltfLoader} loader @@ -2041,37 +2153,44 @@ function loadPrimitive(loader, gltfPrimitive, hasInstances, frameState) { // Edge Visibility const edgeVisibilityExtension = extensions.EXT_mesh_primitive_edge_visibility; - const hasEdgeVisibility = defined(edgeVisibilityExtension); - if (hasEdgeVisibility) { - const visibilityAccessor = - loader.gltfJson.accessors[edgeVisibilityExtension.visibility]; - if (!defined(visibilityAccessor)) { - throw new RuntimeError("Edge visibility accessor not found!"); + if (defined(edgeVisibilityExtension)) { + const edgeVisibility = {}; + + const visibilityAccessorId = edgeVisibilityExtension.visibility; + if (defined(visibilityAccessorId)) { + const visibilityAccessor = + loader.gltfJson.accessors[visibilityAccessorId]; + if (!defined(visibilityAccessor)) { + throw new RuntimeError("Edge visibility accessor not found!"); + } + edgeVisibility.visibility = loadAccessor(loader, visibilityAccessor); } - const visibilityValues = loadAccessor(loader, visibilityAccessor); - primitive.edgeVisibility = { - visibility: visibilityValues, - material: edgeVisibilityExtension.material, - }; - // Load silhouette normals + edgeVisibility.materialColor = getEdgeVisibilityMaterialColor( + loader, + edgeVisibilityExtension.material, + ); + if (defined(edgeVisibilityExtension.silhouetteNormals)) { const silhouetteNormalsAccessor = loader.gltfJson.accessors[edgeVisibilityExtension.silhouetteNormals]; if (defined(silhouetteNormalsAccessor)) { - const silhouetteNormalsValues = loadAccessor( + edgeVisibility.silhouetteNormals = loadAccessor( loader, silhouetteNormalsAccessor, ); - primitive.edgeVisibility.silhouetteNormals = silhouetteNormalsValues; } } - // Load line strings if (defined(edgeVisibilityExtension.lineStrings)) { - primitivePlan.edgeVisibility.lineStrings = - edgeVisibilityExtension.lineStrings; + edgeVisibility.lineStrings = loadEdgeVisibilityLineStrings( + loader, + edgeVisibilityExtension.lineStrings, + edgeVisibilityExtension.material, + ); } + + primitive.edgeVisibility = edgeVisibility; } //support the latest glTF spec and the legacy extension diff --git a/packages/engine/Source/Scene/Model/EdgeVisibilityPipelineStage.js b/packages/engine/Source/Scene/Model/EdgeVisibilityPipelineStage.js index 768a04f7c6f4..b30ef2ac1d27 100644 --- a/packages/engine/Source/Scene/Model/EdgeVisibilityPipelineStage.js +++ b/packages/engine/Source/Scene/Model/EdgeVisibilityPipelineStage.js @@ -5,13 +5,17 @@ import defined from "../../Core/defined.js"; import IndexDatatype from "../../Core/IndexDatatype.js"; import ComponentDatatype from "../../Core/ComponentDatatype.js"; import PrimitiveType from "../../Core/PrimitiveType.js"; +import Cartesian2 from "../../Core/Cartesian2.js"; import Cartesian3 from "../../Core/Cartesian3.js"; +import AttributeCompression from "../../Core/AttributeCompression.js"; import Pass from "../../Renderer/Pass.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; import EdgeVisibilityStageFS from "../../Shaders/Model/EdgeVisibilityStageFS.js"; +import EdgeVisibilityStageVS from "../../Shaders/Model/EdgeVisibilityStageVS.js"; import ModelUtility from "./ModelUtility.js"; import ModelReader from "./ModelReader.js"; import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; +import AttributeType from "../AttributeType.js"; /** * Builds derived line geometry for model edges using EXT_mesh_primitive_edge_visibility data. @@ -69,6 +73,7 @@ EdgeVisibilityPipelineStage.process = function ( ShaderDestination.FRAGMENT, ); shaderBuilder.addFragmentLines(EdgeVisibilityStageFS); + shaderBuilder.addVertexLines(EdgeVisibilityStageVS); // Add a uniform to distinguish between original geometry pass and edge pass shaderBuilder.addUniform("bool", "u_isEdgePass", ShaderDestination.BOTH); @@ -102,23 +107,21 @@ EdgeVisibilityPipelineStage.process = function ( shaderBuilder.addVarying("vec3", "v_faceNormalAView", "flat"); shaderBuilder.addVarying("vec3", "v_faceNormalBView", "flat"); - // Add varying for view space position for perspective-correct silhouette detection + // Add varying for silhouette discard decision in vertex shader + shaderBuilder.addVarying("float", "v_shouldDiscard", "flat"); - // Pass edge type, silhouette normal, and face normals from vertex to fragment shader - shaderBuilder.addFunctionLines("setDynamicVaryingsVS", [ - "#ifdef HAS_EDGE_VISIBILITY", - " if (u_isEdgePass) {", - " v_edgeType = a_edgeType;", - "#ifdef HAS_EDGE_FEATURE_ID", - " v_featureId_0 = a_edgeFeatureId;", - "#endif", - " // Transform normals from model space to view space", - " v_silhouetteNormalView = czm_normal * a_silhouetteNormal;", - " v_faceNormalAView = czm_normal * a_faceNormalA;", - " v_faceNormalBView = czm_normal * a_faceNormalB;", - " }", - "#endif", - ]); + // Add attributes and varyings for quad-based wide line rendering + const edgeOtherPosLocation = shaderBuilder.addAttribute( + "vec3", + "a_edgeOtherPos", + ); + const edgeOffsetLocation = shaderBuilder.addAttribute( + "float", + "a_edgeOffset", + ); + shaderBuilder.addVarying("float", "v_edgeOffset"); + + // Add varying for view space position for perspective-correct silhouette detection // Build triangle adjacency (mapping edges to adjacent triangles) and compute per-triangle face normals. const adjacencyData = buildTriangleAdjacency(primitive); @@ -133,14 +136,36 @@ EdgeVisibilityPipelineStage.process = function ( return; } + const runtimePrimitive = renderResources.runtimePrimitive.primitive; + const vertexColorInfo = collectVertexColors(runtimePrimitive); + const hasEdgeColorOverride = edgeResult.edgeData.some(function (edge) { + return defined(edge.color); + }); + + const needsEdgeColorAttribute = + hasEdgeColorOverride || defined(vertexColorInfo); + + let edgeColorLocation; + if (needsEdgeColorAttribute) { + edgeColorLocation = shaderBuilder.addAttribute("vec4", "a_edgeColor"); + shaderBuilder.addVarying("vec4", "v_edgeColor", "flat"); + shaderBuilder.addDefine( + "HAS_EDGE_COLOR_ATTRIBUTE", + undefined, + ShaderDestination.BOTH, + ); + } + // Generate paired face normals for each unique edge (used to classify silhouette edges in the shader). const edgeFaceNormals = generateEdgeFaceNormals( adjacencyData, edgeResult.edgeIndices, + edgeResult.edgeData, + primitive.edgeVisibility, ); - // Create edge-domain line list geometry (2 vertices per edge) with all required attributes. - const edgeGeometry = createCPULineEdgeGeometry( + // Create edge-domain quad geometry (4 vertices per edge) with all required attributes for wide line rendering. + const edgeGeometry = createQuadEdgeGeometry( edgeResult.edgeIndices, edgeResult.edgeData, renderResources, @@ -150,6 +175,10 @@ EdgeVisibilityPipelineStage.process = function ( faceNormalALocation, faceNormalBLocation, edgeFeatureIdLocation, + edgeColorLocation, + edgeOtherPosLocation, + edgeOffsetLocation, + vertexColorInfo, primitive.edgeVisibility, edgeFaceNormals, ); @@ -171,12 +200,26 @@ EdgeVisibilityPipelineStage.process = function ( return false; }; + // Set default line width uniform (will be overridden in edge pass) + renderResources.uniformMap.u_lineWidth = function () { + return 1.0; + }; + + // Get line width from primitive's material if available + const material = primitive.material; + const lineWidth = + defined(material) && defined(material.lineWidth) + ? material.lineWidth * frameState.pixelRatio + : undefined; + // Store edge geometry metadata so the renderer can issue a separate edges pass. + // Use TRIANGLES instead of LINES to support wide lines via quad tessellation renderResources.edgeGeometry = { vertexArray: edgeGeometry.vertexArray, indexCount: edgeGeometry.indexCount, - primitiveType: PrimitiveType.LINES, + primitiveType: PrimitiveType.TRIANGLES, pass: Pass.CESIUM_3D_TILE_EDGES, + lineWidth: lineWidth, }; }; @@ -284,7 +327,7 @@ function buildTriangleAdjacency(primitive) { faceNormals[base + 1] = scratchCross.y; faceNormals[base + 2] = scratchCross.z; - // Edges + // Register edges processEdge(i0, i1, t); processEdge(i1, i2, t); processEdge(i2, i0, t); @@ -294,21 +337,81 @@ function buildTriangleAdjacency(primitive) { } /** - * For each unique edge produce a pair of face normals (A,B). For boundary edges where only a single - * adjacent triangle exists, the second normal is synthesized as the negation of the first to allow - * the shader to reason about front/back facing transitions uniformly. + * Generate face normal pairs for each edge. Silhouette edges use normals from the GLB + * silhouetteNormals accessor if available. Boundary edges synthesize the opposite normal + * as the negation of the first triangle's normal. * - * @param {{edgeMap:Map, faceNormals:Float32Array}} adjacencyData The adjacency data from buildTriangleAdjacency + * @param {{edgeMap:Map, faceNormals:Float32Array}} adjacencyData Triangle adjacency data * @param {number[]} edgeIndices Packed array of 2 vertex indices per edge + * @param {Object[]} edgeData Array of edge metadata (edgeType, mateVertexIndex) + * @param {Object} edgeVisibility Edge visibility extension data * @returns {Float32Array} Packed array: 6 floats per edge (normalA.xyz, normalB.xyz) * @private */ -function generateEdgeFaceNormals(adjacencyData, edgeIndices) { +function generateEdgeFaceNormals( + adjacencyData, + edgeIndices, + edgeData, + edgeVisibility, +) { const { edgeMap, faceNormals } = adjacencyData; const numEdges = edgeIndices.length / 2; + const edgeFaceNormals = new Float32Array(numEdges * 6); - // Each edge needs 2 face normals (left and right side) - const edgeFaceNormals = new Float32Array(numEdges * 6); // 2 normals * 3 components each + const hasGLBSilhouetteNormals = + defined(edgeVisibility) && defined(edgeVisibility.silhouetteNormals); + let silhouetteNormalsUint32 = null; + + if (hasGLBSilhouetteNormals) { + // GLB stores VEC3 BYTE as normalized normal vectors (signed bytes). + // Decode from signed bytes to normalized vectors, then re-encode to 16-bit octahedral format. + const normalize = (val) => 2 * ((val + 128) / 255) - 1; + + // Re-encode each VEC3 BYTE to 16-bit oct-encoded normal using AttributeCompression + const uint16Normals = new Uint16Array( + edgeVisibility.silhouetteNormals.length, + ); + const scratchNormal = new Cartesian3(); + const scratchEncoded = new Cartesian2(); + + for (let i = 0; i < edgeVisibility.silhouetteNormals.length; i++) { + const vec3 = edgeVisibility.silhouetteNormals[i]; + + // Denormalize from signed byte to normal vector + scratchNormal.x = normalize(vec3.x); + scratchNormal.y = normalize(vec3.y); + scratchNormal.z = normalize(vec3.z); + + // Normalize to unit vector + const magnitude = Cartesian3.magnitude(scratchNormal); + if (magnitude > 0) { + Cartesian3.normalize(scratchNormal, scratchNormal); + } else { + // Handle zero vector - use default up vector + scratchNormal.x = 0; + scratchNormal.y = 0; + scratchNormal.z = 1; + } + + // Use Cesium's octahedral encoding (returns 0-255 integers) + AttributeCompression.octEncodeInRange(scratchNormal, 255, scratchEncoded); + + // Convert to 16-bit integer: (y << 8) | x + const byteX = scratchEncoded.x & 0xff; + const byteY = scratchEncoded.y & 0xff; + uint16Normals[i] = (byteY << 8) | byteX; + } + + // Pack pairs into Uint32Array (little-endian: normalA|normalB<<16) + const numPairs = Math.floor(uint16Normals.length / 2); + silhouetteNormalsUint32 = new Uint32Array(numPairs); + + for (let i = 0; i < numPairs; i++) { + const normalA = uint16Normals[i * 2]; + const normalB = uint16Normals[i * 2 + 1]; + silhouetteNormalsUint32[i] = normalA | (normalB << 16); + } + } for (let i = 0; i < numEdges; i++) { const a = edgeIndices[i * 2]; @@ -316,31 +419,82 @@ function generateEdgeFaceNormals(adjacencyData, edgeIndices) { const edgeKey = `${a < b ? a : b},${a < b ? b : a}`; const triangleList = edgeMap.get(edgeKey); - // Expect at least one triangle; silently skip if not found (defensive) - if (!defined(triangleList) || triangleList.length === 0) { - continue; + const currentEdgeData = edgeData[i]; + const edgeType = currentEdgeData.edgeType; + const mateVertexIndex = currentEdgeData.mateVertexIndex; + + let nAx, nAy, nAz, nBx, nBy, nBz; + let usedGLBNormals = false; + + // Use GLB silhouetteNormals for type=1 (SILHOUETTE) edges if available + if ( + hasGLBSilhouetteNormals && + silhouetteNormalsUint32 && + edgeType === 1 && + mateVertexIndex >= 0 + ) { + // Each OctEncodedNormalPair is stored as one Uint32 value + // Uint32 contains 4 bytes: [byte0, byte1, byte2, byte3] + // normalA = byte0 | (byte1 << 8) - little endian + // normalB = byte2 | (byte3 << 8) - little endian + + if (mateVertexIndex < silhouetteNormalsUint32.length) { + const uint32Value = silhouetteNormalsUint32[mateVertexIndex]; + + const decodedA = new Cartesian3(); + const decodedB = new Cartesian3(); + + // Uint32 contains 4 bytes: [xA, yA, xB, yB] + // Extract and decode using octDecode (rangeMax=255) + AttributeCompression.octDecode( + uint32Value & 0xff, // xA + (uint32Value >> 8) & 0xff, // yA + decodedA, + ); + AttributeCompression.octDecode( + (uint32Value >> 16) & 0xff, // xB + (uint32Value >> 24) & 0xff, // yB + decodedB, + ); + + nAx = decodedA.x; + nAy = decodedA.y; + nAz = decodedA.z; + nBx = decodedB.x; + nBy = decodedB.y; + nBz = decodedB.z; + + usedGLBNormals = true; + } } - const tA = triangleList[0]; - const aBase = tA * 3; - const nAx = faceNormals[aBase]; - const nAy = faceNormals[aBase + 1]; - const nAz = faceNormals[aBase + 2]; - - let nBx; - let nBy; - let nBz; - if (triangleList.length > 1) { - const tB = triangleList[1]; - const bBase = tB * 3; - nBx = faceNormals[bBase]; - nBy = faceNormals[bBase + 1]; - nBz = faceNormals[bBase + 2]; - } else { - // Boundary edge – synthesize opposite normal - nBx = -nAx; - nBy = -nAy; - nBz = -nAz; + // Fallback to triangle adjacency if GLB normals not used + if (!usedGLBNormals) { + // Expect at least one triangle; silently skip if not found (defensive) + if (!defined(triangleList) || triangleList.length === 0) { + continue; + } + + const tA = triangleList[0]; + const aBase = tA * 3; + nAx = faceNormals[aBase]; + nAy = faceNormals[aBase + 1]; + nAz = faceNormals[aBase + 2]; + + const isManifold = triangleList.length > 1; + + if (isManifold) { + const tB = triangleList[1]; + const bBase = tB * 3; + nBx = faceNormals[bBase]; + nBy = faceNormals[bBase + 1]; + nBz = faceNormals[bBase + 2]; + } else { + // Boundary edge – synthesize opposite normal + nBx = -nAx; + nBy = -nAy; + nBz = -nAz; + } } const baseIdx = i * 6; @@ -372,91 +526,167 @@ function generateEdgeFaceNormals(adjacencyData, edgeIndices) { */ function extractVisibleEdges(primitive) { const edgeVisibility = primitive.edgeVisibility; + if (!defined(edgeVisibility)) { + return []; + } + const visibility = edgeVisibility.visibility; const indices = primitive.indices; + const lineStrings = edgeVisibility.lineStrings; + + const attributes = primitive.attributes; + const vertexCount = + defined(attributes) && attributes.length > 0 ? attributes[0].count : 0; + + const hasVisibilityData = + defined(visibility) && + defined(indices) && + defined(indices.typedArray) && + indices.typedArray.length > 0; + const hasLineStrings = defined(lineStrings) && lineStrings.length > 0; - if (!defined(visibility) || !defined(indices)) { + if (!hasVisibilityData && !hasLineStrings) { return []; } - const triangleIndexArray = indices.typedArray; - const vertexCount = primitive.attributes[0].count; + const triangleIndexArray = hasVisibilityData ? indices.typedArray : undefined; const edgeIndices = []; const edgeData = []; const seenEdgeHashes = new Set(); let silhouetteEdgeCount = 0; + const globalColor = edgeVisibility.materialColor; + + if (hasVisibilityData) { + let edgeIndex = 0; + const totalIndices = triangleIndexArray.length; + const visibilityArray = visibility; + + for (let i = 0; i + 2 < totalIndices; i += 3) { + const v0 = triangleIndexArray[i]; + const v1 = triangleIndexArray[i + 1]; + const v2 = triangleIndexArray[i + 2]; + for (let e = 0; e < 3; e++) { + let a; + let b; + if (e === 0) { + a = v0; + b = v1; + } else if (e === 1) { + a = v1; + b = v2; + } else { + a = v2; + b = v0; + } - // Process triangles and extract edges (2 bits per edge) - let edgeIndex = 0; - const totalIndices = triangleIndexArray.length; - - for (let i = 0; i + 2 < totalIndices; i += 3) { - const v0 = triangleIndexArray[i]; - const v1 = triangleIndexArray[i + 1]; - const v2 = triangleIndexArray[i + 2]; - for (let e = 0; e < 3; e++) { - let a, b; - if (e === 0) { - a = v0; - b = v1; - } else if (e === 1) { - a = v1; - b = v2; - } else if (e === 2) { - a = v2; - b = v0; - } - const byteIndex = Math.floor(edgeIndex / 4); - const bitPairOffset = (edgeIndex % 4) * 2; - edgeIndex++; + const byteIndex = Math.floor(edgeIndex / 4); + const bitPairOffset = (edgeIndex % 4) * 2; + edgeIndex++; - if (byteIndex >= visibility.length) { - break; - } + if (byteIndex >= visibilityArray.length) { + break; + } - const byte = visibility[byteIndex]; - const visibility2Bit = (byte >> bitPairOffset) & 0x3; + const byte = visibilityArray[byteIndex]; + const visibility2Bit = (byte >> bitPairOffset) & 0x3; - // Only include visible edge types according to EXT_mesh_primitive_edge_visibility spec - let shouldIncludeEdge = false; - switch (visibility2Bit) { - case 0: // HIDDEN - never draw - shouldIncludeEdge = false; - break; - case 1: // SILHOUETTE - conditionally visible (front-facing vs back-facing) - shouldIncludeEdge = true; - break; - case 2: // HARD - always draw (primary encoding) - shouldIncludeEdge = true; - break; - case 3: // REPEATED - always draw (secondary encoding of a hard edge already encoded as 2) - shouldIncludeEdge = true; - break; - } + if (visibility2Bit === 0) { + continue; + } - if (shouldIncludeEdge) { const small = Math.min(a, b); const big = Math.max(a, b); - const hash = small * vertexCount + big; + const edgeKey = `${small},${big}`; - if (!seenEdgeHashes.has(hash)) { - seenEdgeHashes.add(hash); - edgeIndices.push(a, b); + if (seenEdgeHashes.has(edgeKey)) { + continue; + } + seenEdgeHashes.add(edgeKey); + edgeIndices.push(a, b); + + // Only process silhouette edges (type=1) as marked in GLB + // Use computed edge index for boundary edges + let mateVertexIndex = -1; + if (visibility2Bit === 1) { + mateVertexIndex = silhouetteEdgeCount; + silhouetteEdgeCount++; + } - let mateVertexIndex = -1; - if (visibility2Bit === 1) { - mateVertexIndex = silhouetteEdgeCount; - silhouetteEdgeCount++; - } + edgeData.push({ + edgeType: visibility2Bit, // Use original GLB edge type + triangleIndex: Math.floor(i / 3), + edgeIndex: e, + mateVertexIndex: mateVertexIndex, + currentTriangleVertices: [v0, v1, v2], + color: globalColor, + }); + } + } + } + + if (hasLineStrings) { + for (let i = 0; i < lineStrings.length; i++) { + const lineString = lineStrings[i]; + if (!defined(lineString) || !defined(lineString.indices)) { + continue; + } + + const indicesArray = lineString.indices; + if (!defined(indicesArray) || indicesArray.length < 2) { + continue; + } - edgeData.push({ - edgeType: visibility2Bit, - triangleIndex: Math.floor(i / 3), - edgeIndex: e, - mateVertexIndex: mateVertexIndex, - currentTriangleVertices: [v0, v1, v2], - }); + const restartValue = lineString.restartIndex; + const lineColor = defined(lineString.materialColor) + ? lineString.materialColor + : globalColor; + + let previous; + for (let j = 0; j < indicesArray.length; j++) { + const currentIndex = indicesArray[j]; + if (defined(restartValue) && currentIndex === restartValue) { + previous = undefined; + continue; } + + if (!defined(previous)) { + previous = currentIndex; + continue; + } + + const a = previous; + const b = currentIndex; + previous = currentIndex; + + if (a === b) { + continue; + } + + if ( + vertexCount > 0 && + (a < 0 || a >= vertexCount || b < 0 || b >= vertexCount) + ) { + continue; + } + + const small = Math.min(a, b); + const big = Math.max(a, b); + const edgeKey = `${small},${big}`; + + if (seenEdgeHashes.has(edgeKey)) { + continue; + } + + seenEdgeHashes.add(edgeKey); + edgeIndices.push(a, b); + edgeData.push({ + edgeType: 2, + triangleIndex: -1, + edgeIndex: -1, + mateVertexIndex: -1, + currentTriangleVertices: undefined, + color: defined(lineColor) ? lineColor : undefined, + }); } } } @@ -464,10 +694,91 @@ function extractVisibleEdges(primitive) { return { edgeIndices, edgeData, silhouetteEdgeCount }; } +function collectVertexColors(runtimePrimitive) { + if (!defined(runtimePrimitive)) { + return undefined; + } + + const colorAttribute = ModelUtility.getAttributeBySemantic( + runtimePrimitive, + VertexAttributeSemantic.COLOR, + ); + if (!defined(colorAttribute)) { + return undefined; + } + + const components = AttributeType.getNumberOfComponents(colorAttribute.type); + if (components !== 3 && components !== 4) { + return undefined; + } + + let colorData = colorAttribute.typedArray; + if (!defined(colorData)) { + colorData = ModelReader.readAttributeAsTypedArray(colorAttribute); + } + if (!defined(colorData)) { + return undefined; + } + const count = colorAttribute.count; + if (!defined(count) || count === 0) { + return undefined; + } + + if (colorData.length < count * components) { + return undefined; + } + + const isFloatArray = + colorData instanceof Float32Array || colorData instanceof Float64Array; + const isUint8Array = colorData instanceof Uint8Array; + const isUint16Array = colorData instanceof Uint16Array; + + if (!isFloatArray && !isUint8Array && !isUint16Array) { + return undefined; + } + + const colors = new Float32Array(count * 4); + + const convertComponent = function (value) { + let converted; + if (isFloatArray) { + converted = value; + } else if (isUint8Array) { + converted = value / 255.0; + } else if (isUint16Array) { + converted = value / 65535.0; + } + return Math.min(Math.max(converted, 0.0), 1.0); + }; + + for (let i = 0; i < count; i++) { + const srcBase = i * components; + const destBase = i * 4; + colors[destBase] = convertComponent(colorData[srcBase]); + colors[destBase + 1] = convertComponent(colorData[srcBase + 1]); + colors[destBase + 2] = convertComponent(colorData[srcBase + 2]); + if (components === 4) { + colors[destBase + 3] = convertComponent(colorData[srcBase + 3]); + } else { + colors[destBase + 3] = 1.0; + } + } + + return { + colors: colors, + count: count, + }; +} + +/** + * @typedef {object} VertexColorInfo + * @property {Float32Array} colors The packed per-vertex colors. + * @property {number} count The number of vertices. + */ + /** - * Create a derived line list geometry representing edges. A new vertex domain is used so we can pack - * per-edge attributes (silhouette normal, face normal pair, edge type, optional feature ID) without - * modifying or duplicating the original triangle mesh. Two vertices are generated per unique edge. + * Create quad-based edge geometry for wide line rendering. Each edge becomes a quad (4 vertices, 2 triangles). + * This allows proper line width rendering in the vertex shader by extruding perpendicular to the line direction. * * @param {number[]} edgeIndices Packed array [a0,b0, a1,b1, ...] of vertex indices into the source mesh * @param {Object[]} edgeData Array of edge metadata including edge type and silhouette normal lookup index @@ -478,12 +789,16 @@ function extractVisibleEdges(primitive) { * @param {number} faceNormalALocation Shader attribute location for face normal A * @param {number} faceNormalBLocation Shader attribute location for face normal B * @param {number} edgeFeatureIdLocation Shader attribute location for optional edge feature ID + * @param {number} edgeColorLocation Shader attribute location for optional edge color data + * @param {number} edgeOtherPosLocation Shader attribute location for the other endpoint position + * @param {number} edgeOffsetLocation Shader attribute location for edge offset (-1 or +1) + * @param {VertexColorInfo} [vertexColorInfo] Packed per-vertex colors (optional) * @param {Object} edgeVisibility Edge visibility extension object (may contain silhouetteNormals[]) * @param {Float32Array} edgeFaceNormals Packed face normals (6 floats per edge) * @returns {Object|undefined} Object with {vertexArray, indexBuffer, indexCount} or undefined on failure * @private */ -function createCPULineEdgeGeometry( +function createQuadEdgeGeometry( edgeIndices, edgeData, renderResources, @@ -493,6 +808,10 @@ function createCPULineEdgeGeometry( faceNormalALocation, faceNormalBLocation, edgeFeatureIdLocation, + edgeColorLocation, + edgeOtherPosLocation, + edgeOffsetLocation, + vertexColorInfo, edgeVisibility, edgeFaceNormals, ) { @@ -501,10 +820,10 @@ function createCPULineEdgeGeometry( } const numEdges = edgeData.length; - const vertsPerEdge = 2; + const vertsPerEdge = 4; // Each edge becomes a quad (4 vertices) const totalVerts = numEdges * vertsPerEdge; - // Always use location 0 for position to avoid conflicts + // Always use location 0 for position const positionLocation = 0; // Get original vertex positions @@ -516,57 +835,74 @@ function createCPULineEdgeGeometry( ? positionAttribute.typedArray : ModelReader.readAttributeAsTypedArray(positionAttribute); - // Create edge-domain vertices (2 per edge) + // Create arrays for quad vertices const edgePosArray = new Float32Array(totalVerts * 3); const edgeTypeArray = new Float32Array(totalVerts); const silhouetteNormalArray = new Float32Array(totalVerts * 3); const faceNormalAArray = new Float32Array(totalVerts * 3); const faceNormalBArray = new Float32Array(totalVerts * 3); - let p = 0; + const edgeOtherPosArray = new Float32Array(totalVerts * 3); // Position of the other endpoint + const edgeOffsetArray = new Float32Array(totalVerts); // -1 or +1 for quad expansion + + const needsEdgeColorAttribute = defined(edgeColorLocation); + const edgeColorArray = needsEdgeColorAttribute + ? new Float32Array(totalVerts * 4) + : undefined; + const vertexColors = defined(vertexColorInfo) + ? vertexColorInfo.colors + : undefined; + const vertexColorCount = defined(vertexColorInfo) ? vertexColorInfo.count : 0; + + function setNoColor(destVertexIndex) { + if (!needsEdgeColorAttribute) { + return; + } + const destOffset = destVertexIndex * 4; + edgeColorArray[destOffset] = 0.0; + edgeColorArray[destOffset + 1] = 0.0; + edgeColorArray[destOffset + 2] = 0.0; + edgeColorArray[destOffset + 3] = -1.0; + } + + function setColorFromOverride(destVertexIndex, color) { + if (!needsEdgeColorAttribute) { + return; + } + const destOffset = destVertexIndex * 4; + const r = defined(color.x) ? color.x : color[0]; + const g = defined(color.y) ? color.y : color[1]; + const b = defined(color.z) ? color.z : color[2]; + const a = defined(color.w) ? color.w : defined(color[3]) ? color[3] : 1.0; + edgeColorArray[destOffset] = r; + edgeColorArray[destOffset + 1] = g; + edgeColorArray[destOffset + 2] = b; + edgeColorArray[destOffset + 3] = a; + } - const maxSrcVertex = srcPos.length / 3 - 1; + function assignVertexColor(destVertexIndex, srcVertexIndex) { + if (!needsEdgeColorAttribute) { + return; + } + if (srcVertexIndex >= vertexColorCount) { + setNoColor(destVertexIndex); + return; + } + const srcOffset = srcVertexIndex * 4; + const destOffset = destVertexIndex * 4; + edgeColorArray[destOffset] = vertexColors[srcOffset]; + edgeColorArray[destOffset + 1] = vertexColors[srcOffset + 1]; + edgeColorArray[destOffset + 2] = vertexColors[srcOffset + 2]; + edgeColorArray[destOffset + 3] = vertexColors[srcOffset + 3]; + } + // Generate quad vertices for each edge for (let i = 0; i < numEdges; i++) { const a = edgeIndices[i * 2]; const b = edgeIndices[i * 2 + 1]; + const rawType = edgeData[i].edgeType; + const normalizedType = rawType / 255.0; - // Validate vertex indices - if (a < 0 || b < 0 || a > maxSrcVertex || b > maxSrcVertex) { - // Fill with zeros to maintain indexing - edgePosArray[p++] = 0; - edgePosArray[p++] = 0; - edgePosArray[p++] = 0; - edgePosArray[p++] = 0; - edgePosArray[p++] = 0; - edgePosArray[p++] = 0; - edgeTypeArray[i * 2] = 0; - edgeTypeArray[i * 2 + 1] = 0; - // Fill with default values - const normalIdx = i * 2; - silhouetteNormalArray[normalIdx * 3] = 0; - silhouetteNormalArray[normalIdx * 3 + 1] = 0; - silhouetteNormalArray[normalIdx * 3 + 2] = 1; - silhouetteNormalArray[(normalIdx + 1) * 3] = 0; - silhouetteNormalArray[(normalIdx + 1) * 3 + 1] = 0; - silhouetteNormalArray[(normalIdx + 1) * 3 + 2] = 1; - - // Fill face normals with default values - faceNormalAArray[normalIdx * 3] = 0; - faceNormalAArray[normalIdx * 3 + 1] = 0; - faceNormalAArray[normalIdx * 3 + 2] = 1; - faceNormalAArray[(normalIdx + 1) * 3] = 0; - faceNormalAArray[(normalIdx + 1) * 3 + 1] = 0; - faceNormalAArray[(normalIdx + 1) * 3 + 2] = 1; - - faceNormalBArray[normalIdx * 3] = 0; - faceNormalBArray[normalIdx * 3 + 1] = 0; - faceNormalBArray[normalIdx * 3 + 2] = 1; - faceNormalBArray[(normalIdx + 1) * 3] = 0; - faceNormalBArray[(normalIdx + 1) * 3 + 1] = 0; - faceNormalBArray[(normalIdx + 1) * 3 + 2] = 1; - continue; - } - + // Get positions const ax = srcPos[a * 3]; const ay = srcPos[a * 3 + 1]; const az = srcPos[a * 3 + 2]; @@ -574,24 +910,71 @@ function createCPULineEdgeGeometry( const by = srcPos[b * 3 + 1]; const bz = srcPos[b * 3 + 2]; - // Add edge endpoints - edgePosArray[p++] = ax; - edgePosArray[p++] = ay; - edgePosArray[p++] = az; - edgePosArray[p++] = bx; - edgePosArray[p++] = by; - edgePosArray[p++] = bz; - - const rawType = edgeData[i].edgeType; - const t = rawType / 255.0; - - edgeTypeArray[i * 2] = t; - edgeTypeArray[i * 2 + 1] = t; + // Create 4 vertices for this edge: (A-, A+, B+, B-) + // where +/- indicates offset perpendicular to the line + // Store the other endpoint position for each vertex + const baseVertexIndex = i * 4; + + // Vertex 0: position at A, other endpoint is B, offset -1 + edgePosArray[baseVertexIndex * 3] = ax; + edgePosArray[baseVertexIndex * 3 + 1] = ay; + edgePosArray[baseVertexIndex * 3 + 2] = az; + edgeOtherPosArray[baseVertexIndex * 3] = bx; + edgeOtherPosArray[baseVertexIndex * 3 + 1] = by; + edgeOtherPosArray[baseVertexIndex * 3 + 2] = bz; + edgeOffsetArray[baseVertexIndex] = -1.0; + edgeTypeArray[baseVertexIndex] = normalizedType; + + // Vertex 1: position at A, other endpoint is B, offset +1 + edgePosArray[(baseVertexIndex + 1) * 3] = ax; + edgePosArray[(baseVertexIndex + 1) * 3 + 1] = ay; + edgePosArray[(baseVertexIndex + 1) * 3 + 2] = az; + edgeOtherPosArray[(baseVertexIndex + 1) * 3] = bx; + edgeOtherPosArray[(baseVertexIndex + 1) * 3 + 1] = by; + edgeOtherPosArray[(baseVertexIndex + 1) * 3 + 2] = bz; + edgeOffsetArray[baseVertexIndex + 1] = 1.0; + edgeTypeArray[baseVertexIndex + 1] = normalizedType; + + // Vertex 2: position at B, other endpoint is A, offset +1 + edgePosArray[(baseVertexIndex + 2) * 3] = bx; + edgePosArray[(baseVertexIndex + 2) * 3 + 1] = by; + edgePosArray[(baseVertexIndex + 2) * 3 + 2] = bz; + edgeOtherPosArray[(baseVertexIndex + 2) * 3] = ax; + edgeOtherPosArray[(baseVertexIndex + 2) * 3 + 1] = ay; + edgeOtherPosArray[(baseVertexIndex + 2) * 3 + 2] = az; + edgeOffsetArray[baseVertexIndex + 2] = 1.0; + edgeTypeArray[baseVertexIndex + 2] = normalizedType; + + // Vertex 3: position at B, other endpoint is A, offset -1 + edgePosArray[(baseVertexIndex + 3) * 3] = bx; + edgePosArray[(baseVertexIndex + 3) * 3 + 1] = by; + edgePosArray[(baseVertexIndex + 3) * 3 + 2] = bz; + edgeOtherPosArray[(baseVertexIndex + 3) * 3] = ax; + edgeOtherPosArray[(baseVertexIndex + 3) * 3 + 1] = ay; + edgeOtherPosArray[(baseVertexIndex + 3) * 3 + 2] = az; + edgeOffsetArray[baseVertexIndex + 3] = -1.0; + edgeTypeArray[baseVertexIndex + 3] = normalizedType; + + // Handle edge colors + const edgeOverrideColor = edgeData[i].color; + if (defined(edgeOverrideColor)) { + for (let v = 0; v < 4; v++) { + setColorFromOverride(baseVertexIndex + v, edgeOverrideColor); + } + } else if (defined(vertexColors)) { + for (let v = 0; v < 4; v++) { + assignVertexColor(baseVertexIndex + v, a); + } + } else { + for (let v = 0; v < 4; v++) { + setNoColor(baseVertexIndex + v); + } + } - // Add silhouette normal for silhouette edges (type 1) + // Set silhouette normal (same for all 4 vertices) let normalX = 0, normalY = 0, - normalZ = 1; // Default normal pointing up + normalZ = 1; if (rawType === 1 && defined(edgeVisibility.silhouetteNormals)) { const mateVertexIndex = edgeData[i].mateVertexIndex; @@ -599,9 +982,7 @@ function createCPULineEdgeGeometry( mateVertexIndex >= 0 && mateVertexIndex < edgeVisibility.silhouetteNormals.length ) { - const silhouetteNormals = edgeVisibility.silhouetteNormals; - const normal = silhouetteNormals[mateVertexIndex]; - + const normal = edgeVisibility.silhouetteNormals[mateVertexIndex]; if (defined(normal)) { normalX = normal.x; normalY = normal.y; @@ -610,17 +991,14 @@ function createCPULineEdgeGeometry( } } - // Set silhouette normal for both edge endpoints - const normalIdx = i * 2; - silhouetteNormalArray[normalIdx * 3] = normalX; - silhouetteNormalArray[normalIdx * 3 + 1] = normalY; - silhouetteNormalArray[normalIdx * 3 + 2] = normalZ; - silhouetteNormalArray[(normalIdx + 1) * 3] = normalX; - silhouetteNormalArray[(normalIdx + 1) * 3 + 1] = normalY; - silhouetteNormalArray[(normalIdx + 1) * 3 + 2] = normalZ; - - // Set face normals for both edge endpoints - const faceNormalIdx = i * 6; // 6 floats per edge (2 normals * 3 components) + for (let v = 0; v < 4; v++) { + silhouetteNormalArray[(baseVertexIndex + v) * 3] = normalX; + silhouetteNormalArray[(baseVertexIndex + v) * 3 + 1] = normalY; + silhouetteNormalArray[(baseVertexIndex + v) * 3 + 2] = normalZ; + } + + // Set face normals (same for all 4 vertices) + const faceNormalIdx = i * 6; const normalAX = edgeFaceNormals[faceNormalIdx]; const normalAY = edgeFaceNormals[faceNormalIdx + 1]; const normalAZ = edgeFaceNormals[faceNormalIdx + 2]; @@ -628,21 +1006,14 @@ function createCPULineEdgeGeometry( const normalBY = edgeFaceNormals[faceNormalIdx + 4]; const normalBZ = edgeFaceNormals[faceNormalIdx + 5]; - // Face normal A for both endpoints - faceNormalAArray[normalIdx * 3] = normalAX; - faceNormalAArray[normalIdx * 3 + 1] = normalAY; - faceNormalAArray[normalIdx * 3 + 2] = normalAZ; - faceNormalAArray[(normalIdx + 1) * 3] = normalAX; - faceNormalAArray[(normalIdx + 1) * 3 + 1] = normalAY; - faceNormalAArray[(normalIdx + 1) * 3 + 2] = normalAZ; - - // Face normal B for both endpoints - faceNormalBArray[normalIdx * 3] = normalBX; - faceNormalBArray[normalIdx * 3 + 1] = normalBY; - faceNormalBArray[normalIdx * 3 + 2] = normalBZ; - faceNormalBArray[(normalIdx + 1) * 3] = normalBX; - faceNormalBArray[(normalIdx + 1) * 3 + 1] = normalBY; - faceNormalBArray[(normalIdx + 1) * 3 + 2] = normalBZ; + for (let v = 0; v < 4; v++) { + faceNormalAArray[(baseVertexIndex + v) * 3] = normalAX; + faceNormalAArray[(baseVertexIndex + v) * 3 + 1] = normalAY; + faceNormalAArray[(baseVertexIndex + v) * 3 + 2] = normalAZ; + faceNormalBArray[(baseVertexIndex + v) * 3] = normalBX; + faceNormalBArray[(baseVertexIndex + v) * 3 + 1] = normalBY; + faceNormalBArray[(baseVertexIndex + v) * 3 + 2] = normalBZ; + } } // Create vertex buffers @@ -651,44 +1022,84 @@ function createCPULineEdgeGeometry( typedArray: edgePosArray, usage: BufferUsage.STATIC_DRAW, }); + const edgeTypeBuffer = Buffer.createVertexBuffer({ context, typedArray: edgeTypeArray, usage: BufferUsage.STATIC_DRAW, }); + const silhouetteNormalBuffer = Buffer.createVertexBuffer({ context, typedArray: silhouetteNormalArray, usage: BufferUsage.STATIC_DRAW, }); + const faceNormalABuffer = Buffer.createVertexBuffer({ context, typedArray: faceNormalAArray, usage: BufferUsage.STATIC_DRAW, }); + const faceNormalBBuffer = Buffer.createVertexBuffer({ context, typedArray: faceNormalBArray, usage: BufferUsage.STATIC_DRAW, }); - // Create sequential indices for line pairs + const edgeOtherPosBuffer = Buffer.createVertexBuffer({ + context, + typedArray: edgeOtherPosArray, + usage: BufferUsage.STATIC_DRAW, + }); + + const edgeOffsetBuffer = Buffer.createVertexBuffer({ + context, + typedArray: edgeOffsetArray, + usage: BufferUsage.STATIC_DRAW, + }); + + const edgeColorBuffer = needsEdgeColorAttribute + ? Buffer.createVertexBuffer({ + context, + typedArray: edgeColorArray, + usage: BufferUsage.STATIC_DRAW, + }) + : undefined; + + // Create triangle indices for quads: (0,1,2, 0,2,3) for each quad + const numTriangles = numEdges * 2; + const numIndices = numTriangles * 3; const useU32 = totalVerts > 65534; - const idx = new Array(totalVerts); - for (let i = 0; i < totalVerts; i++) { - idx[i] = i; + const indices = useU32 + ? new Uint32Array(numIndices) + : new Uint16Array(numIndices); + + for (let i = 0; i < numEdges; i++) { + const baseVertex = i * 4; + const baseIndex = i * 6; + + // Triangle 1: (v0, v1, v2) + indices[baseIndex] = baseVertex; + indices[baseIndex + 1] = baseVertex + 1; + indices[baseIndex + 2] = baseVertex + 2; + + // Triangle 2: (v0, v2, v3) + indices[baseIndex + 3] = baseVertex; + indices[baseIndex + 4] = baseVertex + 2; + indices[baseIndex + 5] = baseVertex + 3; } const indexBuffer = Buffer.createIndexBuffer({ context, - typedArray: useU32 ? new Uint32Array(idx) : new Uint16Array(idx), + typedArray: indices, usage: BufferUsage.STATIC_DRAW, indexDatatype: useU32 ? IndexDatatype.UNSIGNED_INT : IndexDatatype.UNSIGNED_SHORT, }); - // Create vertex array with position, edge type, silhouette normal, and face normal attributes + // Create vertex array with all attributes const attributes = [ { index: positionLocation, @@ -725,75 +1136,87 @@ function createCPULineEdgeGeometry( componentDatatype: ComponentDatatype.FLOAT, normalize: false, }, + { + index: edgeOtherPosLocation, + vertexBuffer: edgeOtherPosBuffer, + componentsPerAttribute: 3, + componentDatatype: ComponentDatatype.FLOAT, + normalize: false, + }, + { + index: edgeOffsetLocation, + vertexBuffer: edgeOffsetBuffer, + componentsPerAttribute: 1, + componentDatatype: ComponentDatatype.FLOAT, + normalize: false, + }, ]; - // Get feature ID from original geometry - const primitive = renderResources.runtimePrimitive.primitive; - const getFeatureIdForEdge = function () { - // Try to get the first feature ID from the original primitive - if (defined(primitive.featureIds) && primitive.featureIds.length > 0) { - const firstFeatureIdSet = primitive.featureIds[0]; - - // Handle FeatureIdAttribute objects directly using setIndex - if (defined(firstFeatureIdSet.setIndex)) { - const featureIdAttribute = primitive.attributes.find( - (attr) => - attr.semantic === VertexAttributeSemantic.FEATURE_ID && - attr.setIndex === firstFeatureIdSet.setIndex, - ); - - if (defined(featureIdAttribute)) { - const featureIds = defined(featureIdAttribute.typedArray) - ? featureIdAttribute.typedArray - : ModelReader.readAttributeAsTypedArray(featureIdAttribute); - - // Create edge feature ID buffer based on edge indices - const edgeFeatureIds = new Float32Array(totalVerts); - for (let i = 0; i < numEdges; i++) { - const a = edgeIndices[i * 2]; - const featureId = a < featureIds.length ? featureIds[a] : 0; - edgeFeatureIds[i * 2] = featureId; - edgeFeatureIds[i * 2 + 1] = featureId; - } - - return edgeFeatureIds; - } - } - } - - return undefined; - }; - - const edgeFeatureIds = getFeatureIdForEdge(); - const hasEdgeFeatureIds = defined(edgeFeatureIds); - - if (hasEdgeFeatureIds) { - const edgeFeatureIdBuffer = Buffer.createVertexBuffer({ - context, - typedArray: edgeFeatureIds, - usage: BufferUsage.STATIC_DRAW, - }); - + if (needsEdgeColorAttribute) { attributes.push({ - index: edgeFeatureIdLocation, - vertexBuffer: edgeFeatureIdBuffer, - componentsPerAttribute: 1, + index: edgeColorLocation, + vertexBuffer: edgeColorBuffer, + componentsPerAttribute: 4, componentDatatype: ComponentDatatype.FLOAT, normalize: false, }); } - const vertexArray = new VertexArray({ context, indexBuffer, attributes }); + // Handle feature IDs (same logic as line geometry) + const primitive = renderResources.runtimePrimitive.primitive; + if (defined(primitive.featureIds) && primitive.featureIds.length > 0) { + const firstFeatureIdSet = primitive.featureIds[0]; + + if (defined(firstFeatureIdSet.setIndex)) { + const featureIdAttribute = primitive.attributes.find( + (attr) => + attr.semantic === VertexAttributeSemantic.FEATURE_ID && + attr.setIndex === firstFeatureIdSet.setIndex, + ); + + if (defined(featureIdAttribute)) { + const featureIds = defined(featureIdAttribute.typedArray) + ? featureIdAttribute.typedArray + : ModelReader.readAttributeAsTypedArray(featureIdAttribute); + + const edgeFeatureIds = new Float32Array(totalVerts); + for (let i = 0; i < numEdges; i++) { + const a = edgeIndices[i * 2]; + const featureId = a < featureIds.length ? featureIds[a] : 0; + // Set same feature ID for all 4 vertices of the quad + for (let v = 0; v < 4; v++) { + edgeFeatureIds[i * 4 + v] = featureId; + } + } - if (!vertexArray || totalVerts === 0 || totalVerts % 2 !== 0) { - return undefined; + const edgeFeatureIdBuffer = Buffer.createVertexBuffer({ + context, + typedArray: edgeFeatureIds, + usage: BufferUsage.STATIC_DRAW, + }); + + attributes.push({ + index: edgeFeatureIdLocation, + vertexBuffer: edgeFeatureIdBuffer, + componentsPerAttribute: 1, + componentDatatype: ComponentDatatype.FLOAT, + normalize: false, + }); + } + } } + const vertexArray = new VertexArray({ + context, + attributes, + indexBuffer, + }); + return { vertexArray, - indexBuffer, - indexCount: totalVerts, - hasEdgeFeatureIds, + indexCount: numIndices, + hasEdgeFeatureIds: + defined(primitive.featureIds) && primitive.featureIds.length > 0, }; } diff --git a/packages/engine/Source/Scene/Model/MaterialPipelineStage.js b/packages/engine/Source/Scene/Model/MaterialPipelineStage.js index ed2f9e2f1f94..832f129d1654 100644 --- a/packages/engine/Source/Scene/Model/MaterialPipelineStage.js +++ b/packages/engine/Source/Scene/Model/MaterialPipelineStage.js @@ -167,6 +167,61 @@ MaterialPipelineStage.process = function ( alphaOptions.alphaCutoff = material.alphaCutoff; } + // Configure and handle point diameter for POINTS primitives (BENTLEY_materials_point_style extension). + if (defined(material.pointDiameter)) { + shaderBuilder.addDefine( + "HAS_POINT_DIAMETER", + undefined, + ShaderDestination.VERTEX, + ); + shaderBuilder.addUniform( + "float", + "u_pointDiameter", + ShaderDestination.VERTEX, + ); + uniformMap.u_pointDiameter = function () { + return material.pointDiameter * frameState.pixelRatio; + }; + } + + // Configure and handle line style for LINES primitives (BENTLEY_materials_line_style extension). + if (defined(material.lineWidth) || defined(material.linePattern)) { + shaderBuilder.addDefine( + "HAS_LINE_STYLE", + undefined, + ShaderDestination.BOTH, + ); + } + + if (defined(material.lineWidth)) { + shaderBuilder.addDefine( + "HAS_LINE_WIDTH", + undefined, + ShaderDestination.BOTH, + ); + shaderBuilder.addUniform("float", "u_lineWidth", ShaderDestination.VERTEX); + uniformMap.u_lineWidth = function () { + return material.lineWidth * frameState.pixelRatio; + }; + } + + if (defined(material.linePattern)) { + shaderBuilder.addDefine( + "HAS_LINE_PATTERN", + undefined, + ShaderDestination.BOTH, + ); + shaderBuilder.addUniform( + "float", + "u_linePattern", + ShaderDestination.FRAGMENT, + ); + shaderBuilder.addVarying("float", "v_lineCoord"); + uniformMap.u_linePattern = function () { + return material.linePattern; + }; + } + shaderBuilder.addFragmentLines(MaterialStageFS); if (material.doubleSided) { diff --git a/packages/engine/Source/Scene/Model/ModelDrawCommand.js b/packages/engine/Source/Scene/Model/ModelDrawCommand.js index f03a8a84382d..a40f2b4d924b 100644 --- a/packages/engine/Source/Scene/Model/ModelDrawCommand.js +++ b/packages/engine/Source/Scene/Model/ModelDrawCommand.js @@ -574,6 +574,11 @@ ModelDrawCommand.prototype.pushCommands = function (frameState, result) { pushCommand(result, this._originalCommand, use2D); + // Push edge commands after the original command + if (this._needsEdgeCommands) { + pushCommand(result, this._edgeCommand, use2D); + } + return result; }; @@ -815,6 +820,15 @@ function deriveEdgeCommand(command, renderResources) { }; edgeCommand.uniformMap = uniformMap; + // Set line width uniform for quad-based edge rendering + const lineWidth = defined(edgeGeometry.lineWidth) + ? edgeGeometry.lineWidth + : 1.0; + uniformMap.u_lineWidth = function () { + return lineWidth; + }; + + edgeCommand.uniformMap = uniformMap; edgeCommand.castShadows = false; edgeCommand.receiveShadows = false; diff --git a/packages/engine/Source/Scene/ModelComponents.js b/packages/engine/Source/Scene/ModelComponents.js index 71d7fb088076..a7ce77edfac0 100644 --- a/packages/engine/Source/Scene/ModelComponents.js +++ b/packages/engine/Source/Scene/ModelComponents.js @@ -630,6 +630,14 @@ function Primitive() { * @private */ this.modelPrimitiveImagery = undefined; + + /** + * Data loaded from the EXT_mesh_primitive_edge_visibility extension. + * + * @type {Object} + * @private + */ + this.edgeVisibility = undefined; } /** @@ -1623,6 +1631,37 @@ function Material() { * @private */ this.unlit = false; + + /** + * The point diameter in pixels for POINTS primitives. This is set by the + * BENTLEY_materials_point_style extension. + * + * @type {number} + * @default undefined + * @private + */ + this.pointDiameter = undefined; + + /** + * The line width in pixels for LINES primitives. This is set by the + * BENTLEY_materials_line_style extension. + * + * @type {number} + * @default undefined + * @private + */ + this.lineWidth = undefined; + + /** + * The line dash pattern for LINES primitives. This is set by the + * BENTLEY_materials_line_style extension. Encoded as a 16-bit unsigned integer + * where each bit represents a pixel (1=on, 0=off). + * + * @type {number} + * @default undefined + * @private + */ + this.linePattern = undefined; } /** diff --git a/packages/engine/Source/Shaders/Model/EdgeVisibilityStageFS.glsl b/packages/engine/Source/Shaders/Model/EdgeVisibilityStageFS.glsl index 99081026c07f..510f380787ff 100644 --- a/packages/engine/Source/Shaders/Model/EdgeVisibilityStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/EdgeVisibilityStageFS.glsl @@ -13,59 +13,45 @@ void edgeVisibilityStage(inout vec4 color, inout FeatureIds featureIds) if (!u_isEdgePass) { return; } - + float edgeTypeInt = v_edgeType * 255.0; - - // Color code different edge types - vec4 edgeColor = vec4(0.0); - - if (edgeTypeInt < 0.5) { // HIDDEN (0) - edgeColor = vec4(0.0, 0.0, 0.0, 0.0); // Transparent for hidden edges + + if (edgeTypeInt < 0.5) { + discard; } - else if (edgeTypeInt > 0.5 && edgeTypeInt < 1.5) { // SILHOUETTE (1) - Conditional visibility - // Proper silhouette detection using face normals - vec3 normalA = normalize(v_faceNormalAView); - vec3 normalB = normalize(v_faceNormalBView); - - // Calculate view direction using existing eye-space position varying (v_positionEC) - vec3 viewDir = -normalize(v_positionEC); - - // Calculate dot products to determine triangle facing - float dotA = dot(normalA, viewDir); - float dotB = dot(normalB, viewDir); - - const float eps = 1e-3; - bool frontA = dotA > eps; - bool backA = dotA < -eps; - bool frontB = dotB > eps; - bool backB = dotB < -eps; - - // True silhouette: one triangle front-facing, other back-facing - bool oppositeFacing = (frontA && backB) || (backA && frontB); - - // Exclude edges where both triangles are nearly grazing (perpendicular to view) - // This handles the top-view cylinder case where both normals are ~horizontal - bool bothNearGrazing = (abs(dotA) <= eps && abs(dotB) <= eps); - - if (!(oppositeFacing && !bothNearGrazing)) { - discard; // Not a true silhouette edge - } else { - // True silhouette - edgeColor = vec4(1.0, 0.0, 0.0, 1.0); + + if (edgeTypeInt > 0.5 && edgeTypeInt < 1.5) { // silhouette candidate + // Silhouette check done in vertex shader + // v_shouldDiscard will be > 0.5 if this edge should be discarded + if (v_shouldDiscard > 0.5) { + discard; } } - else if (edgeTypeInt > 1.5 && edgeTypeInt < 2.5) { // HARD (2) - BRIGHT GREEN - edgeColor = vec4(0.0, 1.0, 0.0, 1.0); // Extra bright green - } - else if (edgeTypeInt > 2.5 && edgeTypeInt < 3.5) { // REPEATED (3) - edgeColor = vec4(0.0, 0.0, 1.0, 1.0); - } else { - edgeColor = vec4(0.0, 0.0, 0.0, 0.0); + + vec4 finalColor = color; +#ifdef HAS_EDGE_COLOR_ATTRIBUTE + if (v_edgeColor.a >= 0.0) { + finalColor = v_edgeColor; } +#endif - // Temporary color: white - edgeColor = vec4(1.0, 1.0, 1.0, 1.0); - color = edgeColor; +#ifdef HAS_LINE_PATTERN + // Pattern is 16-bit, each bit represents visibility at that position + const float maskLength = 16.0; + + // Get the relative position within the dash from 0 to 1 + float dashPosition = fract(v_lineCoord / maskLength); + // Figure out the mask index + float maskIndex = floor(dashPosition * maskLength); + // Test the bit mask + float maskTest = floor(u_linePattern / pow(2.0, maskIndex)); + + // If bit is 0 (gap), discard the fragment (use < 1.0 for better numerical stability) + if (mod(maskTest, 2.0) < 1.0) { + discard; + } +#endif + color = finalColor; #if defined(HAS_EDGE_VISIBILITY_MRT) && !defined(CESIUM_REDIRECTED_COLOR_OUTPUT) // Write edge metadata @@ -80,4 +66,4 @@ void edgeVisibilityStage(inout vec4 color, inout FeatureIds featureIds) out_edgeDepth = czm_packDepth(gl_FragCoord.z); #endif #endif -} +} \ No newline at end of file diff --git a/packages/engine/Source/Shaders/Model/EdgeVisibilityStageVS.glsl b/packages/engine/Source/Shaders/Model/EdgeVisibilityStageVS.glsl new file mode 100644 index 000000000000..9722a5432cfb --- /dev/null +++ b/packages/engine/Source/Shaders/Model/EdgeVisibilityStageVS.glsl @@ -0,0 +1,95 @@ +#ifdef HAS_EDGE_VISIBILITY +void edgeVisibilityStageVS() { + if (u_isEdgePass) { + v_edgeType = a_edgeType; + v_silhouetteNormalView = czm_normal * a_silhouetteNormal; + v_faceNormalAView = czm_normal * a_faceNormalA; + v_faceNormalBView = czm_normal * a_faceNormalB; + v_edgeOffset = a_edgeOffset; + + // Silhouette detection: check both endpoints of the edge + v_shouldDiscard = 0.0; + float edgeTypeInt = a_edgeType * 255.0; + if (edgeTypeInt > 0.5 && edgeTypeInt < 1.5) { + vec3 normalA = normalize(v_faceNormalAView); + vec3 normalB = normalize(v_faceNormalBView); + const float perpTol = 2.5e-4; + + // Check at current vertex (first endpoint) + vec4 currentPosEC = czm_modelView * vec4(v_positionMC, 1.0); + vec3 toEye1 = normalize(-currentPosEC.xyz); + float dotA1 = dot(normalA, toEye1); + float dotB1 = dot(normalB, toEye1); + + // Check at other vertex (second endpoint) + vec4 otherPosEC = czm_modelView * vec4(a_edgeOtherPos, 1.0); + vec3 toEye2 = normalize(-otherPosEC.xyz); + float dotA2 = dot(normalA, toEye2); + float dotB2 = dot(normalB, toEye2); + + // Discard if EITHER endpoint is non-silhouette + if (dotA1 * dotB1 > perpTol || dotA2 * dotB2 > perpTol) { + v_shouldDiscard = 1.0; + } + } + +#ifdef HAS_EDGE_FEATURE_ID + v_featureId_0 = a_edgeFeatureId; +#endif + +#ifdef HAS_EDGE_COLOR_ATTRIBUTE + v_edgeColor = a_edgeColor; +#endif + +#ifdef HAS_LINE_PATTERN + // 16-bit pattern, 1 bit = 1 screen pixel, repeats every 16 pixels + vec4 currentClip = czm_modelViewProjection * vec4(v_positionMC, 1.0); + vec2 currentScreen = ((currentClip.xy / currentClip.w) * 0.5 + 0.5) * czm_viewport.zw; + + vec4 otherClip = czm_modelViewProjection * vec4(a_edgeOtherPos, 1.0); + vec2 otherScreen = ((otherClip.xy / otherClip.w) * 0.5 + 0.5) * czm_viewport.zw; + vec2 windowDir = otherScreen - currentScreen; + + // Offset base for texture coordinates to handle perspective clipping + const float textureCoordinateBase = 8192.0; + + if (abs(windowDir.x) > abs(windowDir.y)) { + v_lineCoord = textureCoordinateBase + currentScreen.x; + } else { + v_lineCoord = textureCoordinateBase + currentScreen.y; + } +#endif + + // Expand vertex to form quad + vec4 posClip = gl_Position; + + if (length(a_edgeOtherPos) > 0.0 && abs(a_edgeOffset) > 0.0) { + vec4 currentClip = posClip; + vec4 otherClip = czm_modelViewProjection * vec4(a_edgeOtherPos, 1.0); + + vec2 currentNDC = currentClip.xy / currentClip.w; + vec2 otherNDC = otherClip.xy / otherClip.w; + + vec2 edgeDirNDC = otherNDC - currentNDC; + + // Ensure consistent edge direction + if (edgeDirNDC.x < 0.0 || (abs(edgeDirNDC.x) < 0.001 && edgeDirNDC.y < 0.0)) { + edgeDirNDC = -edgeDirNDC; + } + + edgeDirNDC = normalize(edgeDirNDC); + vec2 perpNDC = vec2(-edgeDirNDC.y, edgeDirNDC.x); + + // Convert line width from pixels to clip space + float lineWidthPixels = u_lineWidth; + vec2 viewportSize = czm_viewport.zw; + vec2 clipPerPixel = (2.0 / viewportSize) * currentClip.w; + vec2 offsetClip = perpNDC * lineWidthPixels * clipPerPixel * 0.5 * a_edgeOffset; + + posClip.xy += offsetClip; + } + + gl_Position = posClip; + } +} +#endif diff --git a/packages/engine/Source/Shaders/Model/ModelFS.glsl b/packages/engine/Source/Shaders/Model/ModelFS.glsl index 76d3469ef094..4be74fa86844 100644 --- a/packages/engine/Source/Shaders/Model/ModelFS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelFS.glsl @@ -29,6 +29,14 @@ SelectedFeature selectedFeature; void main() { + #ifdef PRIMITIVE_TYPE_POINTS + // Render points as circles + float distanceToCenter = length(gl_PointCoord - vec2(0.5)); + if (distanceToCenter > 0.5) { + discard; + } + #endif + #ifdef HAS_POINT_CLOUD_SHOW_STYLE if (v_pointCloudShow == 0.0) { diff --git a/packages/engine/Source/Shaders/Model/ModelVS.glsl b/packages/engine/Source/Shaders/Model/ModelVS.glsl index 96cee64b06e1..b05b4bcae715 100644 --- a/packages/engine/Source/Shaders/Model/ModelVS.glsl +++ b/packages/engine/Source/Shaders/Model/ModelVS.glsl @@ -142,6 +142,8 @@ void main() gl_PointSize = vsOutput.pointSize; #elif defined(HAS_POINT_CLOUD_POINT_SIZE_STYLE) || defined(HAS_POINT_CLOUD_ATTENUATION) gl_PointSize = pointCloudPointSizeStylingStage(attributes, metadata); + #elif defined(HAS_POINT_DIAMETER) + gl_PointSize = u_pointDiameter; #else gl_PointSize = 1.0; #endif @@ -155,6 +157,10 @@ void main() // We will discard points with v_pointCloudShow == 0 in the fragment shader. gl_Position = positionClip; + #ifdef HAS_EDGE_VISIBILITY + edgeVisibilityStageVS(); + #endif + #ifdef HAS_POINT_CLOUD_SHOW_STYLE v_pointCloudShow = show; #endif diff --git a/packages/engine/Specs/Scene/GltfLoaderSpec.js b/packages/engine/Specs/Scene/GltfLoaderSpec.js index 41ac57e6330d..7816fbe3e63e 100644 --- a/packages/engine/Specs/Scene/GltfLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfLoaderSpec.js @@ -128,10 +128,18 @@ describe( "./Data/Models/glTF-2.0/BoxAnisotropy/glTF/BoxAnisotropy.gltf"; const clearcoatTestData = "./Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.gltf"; + const pointStyleTestData = + "./Data/Models/glTF-2.0/StyledPoints/points-r5-g8-b14-y10.gltf"; const meshPrimitiveRestartTestData = "./Data/Models/glTF-2.0/MeshPrimitiveRestart/glTF/MeshPrimitiveRestart.gltf"; const edgeVisibilityTestData = "./Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibility.glb"; + const edgeVisibilityMaterialTestData = + "./Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibilityMaterial.glb"; + const edgeVisibilityLineStringTestData = + "./Data/Models/glTF-2.0/EdgeVisibility/glTF-Binary/EdgeVisibilityLineString.glb"; + const styledLines = + "./Data/Models/glTF-2.0/StyledLines/BENTLEY_materials_line_style.gltf"; let scene; const gltfLoaders = []; @@ -227,14 +235,16 @@ describe( } async function loadModifiedGltfAndTest(gltfPath, options, modifyFunction) { - let gltf = await Resource.fetchJson({ + const arrayBuffer = await Resource.fetchArrayBuffer({ url: gltfPath, }); - gltf = modifyFunction(gltf); + const gltfData = parseGlb(arrayBuffer); + const modifiedGltf = modifyFunction(gltfData.gltf) ?? gltfData.gltf; + const rebuiltGlb = createGlbBuffer(modifiedGltf, gltfData.binaryChunk); spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( - Promise.resolve(generateJsonBuffer(gltf).buffer), + Promise.resolve(rebuiltGlb), ); const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); @@ -246,6 +256,133 @@ describe( return gltfLoader; } + async function loadModifiedGlbAndTest(gltfPath, options, modifyFunction) { + const arrayBuffer = await Resource.fetchArrayBuffer({ + url: gltfPath, + }); + + const gltfData = parseGlb(arrayBuffer); + const modifiedGltf = modifyFunction(gltfData.gltf) ?? gltfData.gltf; + const rebuiltGlb = createGlbBuffer(modifiedGltf, gltfData.binaryChunk); + + spyOn(GltfJsonLoader.prototype, "_fetchGltf").and.returnValue( + Promise.resolve(rebuiltGlb), + ); + + const gltfLoader = new GltfLoader(getOptions(gltfPath, options)); + gltfLoaders.push(gltfLoader); + + await gltfLoader.load(); + await waitForLoaderProcess(gltfLoader, scene); + + return gltfLoader; + } + + function parseGlb(arrayBuffer) { + const dataView = new DataView(arrayBuffer); + if (dataView.byteLength < 12) { + const jsonText = new TextDecoder().decode(new Uint8Array(arrayBuffer)); + return { gltf: JSON.parse(jsonText), binaryChunk: undefined }; + } + + const magic = dataView.getUint32(0, true); + if (magic !== 0x46546c67) { + const jsonText = new TextDecoder().decode(new Uint8Array(arrayBuffer)); + return { gltf: JSON.parse(jsonText), binaryChunk: undefined }; + } + + let offset = 12; + let jsonObject; + let binaryChunk; + const textDecoder = new TextDecoder(); + + while (offset < arrayBuffer.byteLength) { + const chunkLength = dataView.getUint32(offset, true); + offset += 4; + const chunkType = dataView.getUint32(offset, true); + offset += 4; + + const chunkData = new Uint8Array(arrayBuffer, offset, chunkLength); + if (chunkType === 0x4e4f534a) { + jsonObject = JSON.parse(textDecoder.decode(chunkData)); + } else if (chunkType === 0x004e4942) { + binaryChunk = chunkData.slice(); + } + + offset += chunkLength; + } + + if (!jsonObject) { + throw new RuntimeError("GLB JSON chunk not found."); + } + + if (binaryChunk && jsonObject.buffers && jsonObject.buffers.length > 0) { + jsonObject.buffers[0].byteLength = binaryChunk.length; + delete jsonObject.buffers[0].uri; + } + + return { gltf: jsonObject, binaryChunk: binaryChunk }; + } + + function createGlbBuffer(gltf, binaryChunk) { + const textEncoder = new TextEncoder(); + const jsonBuffer = textEncoder.encode(JSON.stringify(gltf)); + const jsonPadding = (4 - (jsonBuffer.byteLength % 4)) % 4; + const paddedJson = new Uint8Array(jsonBuffer.byteLength + jsonPadding); + paddedJson.set(jsonBuffer); + if (jsonPadding > 0) { + paddedJson.fill(0x20, jsonBuffer.byteLength); + } + + let paddedBinary; + if (binaryChunk && binaryChunk.length > 0) { + const binPadding = (4 - (binaryChunk.length % 4)) % 4; + paddedBinary = new Uint8Array(binaryChunk.length + binPadding); + paddedBinary.set(binaryChunk); + if (binPadding > 0) { + paddedBinary.fill(0, binaryChunk.length); + } + } + + const hasBinaryChunk = !!paddedBinary; + const totalLength = + 12 + + 8 + + paddedJson.byteLength + + (hasBinaryChunk ? 8 + paddedBinary.byteLength : 0); + + const glbBuffer = new ArrayBuffer(totalLength); + const dataView = new DataView(glbBuffer); + let offset = 0; + + dataView.setUint32(offset, 0x46546c67, true); + offset += 4; + dataView.setUint32(offset, 2, true); + offset += 4; + dataView.setUint32(offset, totalLength, true); + offset += 4; + + dataView.setUint32(offset, paddedJson.byteLength, true); + offset += 4; + dataView.setUint32(offset, 0x4e4f534a, true); + offset += 4; + new Uint8Array(glbBuffer, offset, paddedJson.byteLength).set(paddedJson); + offset += paddedJson.byteLength; + + if (hasBinaryChunk) { + dataView.setUint32(offset, paddedBinary.byteLength, true); + offset += 4; + dataView.setUint32(offset, 0x004e4942, true); + offset += 4; + new Uint8Array(glbBuffer, offset, paddedBinary.byteLength).set( + paddedBinary, + ); + offset += paddedBinary.byteLength; + } + + return glbBuffer; + } + function getAttribute(attributes, semantic, setIndex) { const attributesLength = attributes.length; for (let i = 0; i < attributesLength; ++i) { @@ -4176,6 +4313,57 @@ describe( expect(clearcoatNormalTexture.texture.width).toBe(256); }); + it("loads model with BENTLEY_materials_point_style extension", async function () { + const gltfLoader = await loadGltf(pointStyleTestData); + + // The test model has 4 primitives with different materials. Let's verify they all exist. + const primitives = gltfLoader.components.nodes[0].primitives; + expect(primitives.length).toBe(4); + + // Check that pointDiameter was loaded correctly for each material. Each primitive has a different diameter. + expect(primitives[0].material.pointDiameter).toBe(5); + expect(primitives[1].material.pointDiameter).toBe(8); + expect(primitives[2].material.pointDiameter).toBe(14); + expect(primitives[3].material.pointDiameter).toBe(10); + }); + + it("ignores BENTLEY_materials_point_style with invalid negative diameter", async function () { + function modifyGltf(gltf) { + // Set an invalid negative diameter (diameters must be >0). + gltf.materials[0].extensions.BENTLEY_materials_point_style.diameter = + -5; + return gltf; + } + + const gltfLoader = await loadModifiedGltfAndTest( + pointStyleTestData, + undefined, + modifyGltf, + ); + + // The invalid negative diameter should be ignored; property should be undefined once the glTF is loaded. + const material = gltfLoader.components.nodes[0].primitives[0].material; + expect(material.pointDiameter).toBeUndefined(); + }); + + it("ignores BENTLEY_materials_point_style with non-integer diameter", async function () { + function modifyGltf(gltf) { + // Set an invalid non-integer diameter (diameters must be integers). + gltf.materials[0].extensions.BENTLEY_materials_point_style.diameter = 5.5; + return gltf; + } + + const gltfLoader = await loadModifiedGltfAndTest( + pointStyleTestData, + undefined, + modifyGltf, + ); + + // Invalid non-integer diameter should be ignored; property should be undefined once the glTF is loaded. + const material = gltfLoader.components.nodes[0].primitives[0].material; + expect(material.pointDiameter).toBeUndefined(); + }); + it("loads model with EXT_mesh_primitive_restart extension", async function () { const gltf = await Resource.fetchJson({ url: meshPrimitiveRestartTestData, @@ -4374,6 +4562,76 @@ describe( } }); + it("loads edge visibility material color override", async function () { + const gltfLoader = await loadModifiedGlbAndTest( + edgeVisibilityMaterialTestData, + undefined, + function (gltf) { + const primitive = gltf.meshes[0].primitives[0]; + const extension = + primitive.extensions.EXT_mesh_primitive_edge_visibility; + extension.material = 0; + + const material = gltf.materials[0]; + const pbr = + material.pbrMetallicRoughness ?? + (material.pbrMetallicRoughness = {}); + pbr.baseColorFactor = [0.2, 0.4, 0.6, 0.8]; + + return gltf; + }, + ); + + const primitive = gltfLoader.components.scene.nodes[0].primitives[0]; + const edgeVisibility = primitive.edgeVisibility; + expect(edgeVisibility).toBeDefined(); + expect(edgeVisibility.materialColor).toEqualEpsilon( + new Cartesian4(0.2, 0.4, 0.6, 0.8), + CesiumMath.EPSILON7, + ); + }); + + it("loads edge visibility line strings", async function () { + const gltfLoader = await loadModifiedGlbAndTest( + edgeVisibilityLineStringTestData, + undefined, + function (gltf) { + const primitive = gltf.meshes[0].primitives[0]; + primitive.extensions = primitive.extensions ?? Object.create(null); + primitive.extensions.EXT_mesh_primitive_edge_visibility = { + lineStrings: [ + { + indices: gltf.meshes[0].primitives[1].indices, + material: 0, + }, + ], + }; + + const material = gltf.materials[0]; + const pbr = + material.pbrMetallicRoughness ?? + (material.pbrMetallicRoughness = {}); + pbr.baseColorFactor = [1.0, 0.5, 0.0, 1.0]; + + return gltf; + }, + ); + + const primitive = gltfLoader.components.scene.nodes[0].primitives[0]; + const edgeVisibility = primitive.edgeVisibility; + expect(edgeVisibility).toBeDefined(); + expect(edgeVisibility.lineStrings).toBeDefined(); + + const lineStrings = edgeVisibility.lineStrings; + expect(lineStrings.length).toBe(1); + expect(lineStrings[0].indices.length).toBeGreaterThan(0); + expect(lineStrings[0].restartIndex).toBeDefined(); + expect(lineStrings[0].materialColor).toEqualEpsilon( + new Cartesian4(1.0, 0.5, 0.0, 1.0), + CesiumMath.EPSILON7, + ); + }); + it("validates edge visibility data loading", async function () { const gltfLoader = await loadGltf(edgeVisibilityTestData); const primitive = gltfLoader.components.scene.nodes[0].primitives[0]; @@ -4389,6 +4647,35 @@ describe( } }); + it("loads BENTLEY_materials_line_style extension", async function () { + const gltfLoader = await loadGltf(styledLines); + const components = gltfLoader.components; + const node = components.scene.nodes[0]; + const primitive = node.primitives[0]; + const material = primitive.material; + + expect(material).toBeDefined(); + expect(material.lineWidth).toBe(5); + expect(material.linePattern).toBe(61680); // 0xF0F0 + }); + + it("loads BENTLEY_materials_line_style with edge visibility", async function () { + const gltfLoader = await loadGltf(styledLines); + const components = gltfLoader.components; + const node = components.scene.nodes[0]; + const primitive = node.primitives[0]; + + // Verify edge visibility data is present + expect(primitive.edgeVisibility).toBeDefined(); + expect(primitive.edgeVisibility.visibility).toBeDefined(); + expect(primitive.edgeVisibility.silhouetteNormals).toBeDefined(); + + // Verify material line style properties + const material = primitive.material; + expect(material.lineWidth).toBe(5); + expect(material.linePattern).toBe(61680); // 0xF0F0 + }); + it("parses copyright field", function () { return loadGltf(boxWithCredits).then(function (gltfLoader) { const components = gltfLoader.components; diff --git a/packages/engine/Specs/Scene/Model/EdgeVisibilityPipelineStageDecodingSpec.js b/packages/engine/Specs/Scene/Model/EdgeVisibilityPipelineStageDecodingSpec.js index 482664be9df3..2ef32f67bde6 100644 --- a/packages/engine/Specs/Scene/Model/EdgeVisibilityPipelineStageDecodingSpec.js +++ b/packages/engine/Specs/Scene/Model/EdgeVisibilityPipelineStageDecodingSpec.js @@ -1,25 +1,43 @@ import { + Cartesian4, Buffer, BufferUsage, ComponentDatatype, IndexDatatype, PrimitiveType, + ResourceCache, ShaderBuilder, - ShaderDestination, VertexAttributeSemantic, } from "../../../index.js"; import createContext from "../../../../../Specs/createContext.js"; import EdgeVisibilityPipelineStage from "../../../Source/Scene/Model/EdgeVisibilityPipelineStage.js"; +import createScene from "../../../../../Specs/createScene.js"; describe("Scene/Model/EdgeVisibilityPipelineStage", function () { let context; + let scene; + const gltfLoaders = []; beforeAll(function () { context = createContext(); + scene = createScene(); }); afterAll(function () { context.destroyForSpecs(); + scene.destroyForSpecs(); + }); + + afterEach(function () { + const gltfLoadersLength = gltfLoaders.length; + for (let i = 0; i < gltfLoadersLength; ++i) { + const gltfLoader = gltfLoaders[i]; + if (!gltfLoader.isDestroyed()) { + gltfLoader.destroy(); + } + } + gltfLoaders.length = 0; + ResourceCache.clearForSpecs(); }); function createTestEdgeVisibilityData() { @@ -100,13 +118,6 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { function createMockRenderResources(primitive) { const shaderBuilder = new ShaderBuilder(); - // Pre-add the required function that EdgeVisibilityPipelineStage expects - shaderBuilder.addFunction( - "setDynamicVaryingsVS", - "void setDynamicVaryingsVS()\n{\n}", - ShaderDestination.VERTEX, - ); - return { shaderBuilder: shaderBuilder, uniformMap: {}, @@ -119,6 +130,7 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { function createMockFrameState() { return { context: context, + pixelRatio: 1.0, }; } @@ -134,8 +146,9 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { expect(renderResources.edgeGeometry).toBeDefined(); expect(renderResources.edgeGeometry.vertexArray).toBeDefined(); expect(renderResources.edgeGeometry.indexCount).toBeGreaterThan(0); + // Quad-based rendering uses TRIANGLES, not LINES expect(renderResources.edgeGeometry.primitiveType).toBe( - PrimitiveType.LINES, + PrimitiveType.TRIANGLES, ); }); @@ -211,11 +224,12 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { expect(renderResources.edgeGeometry).toBeDefined(); // Expected 3 unique visible edges: (0,1), (0,2), (2,3) - // Each edge creates 2 indices (line primitive), so indexCount should be 6 - expect(renderResources.edgeGeometry.indexCount).toBe(6); - expect(renderResources.edgeGeometry.indexCount % 2).toBe(0); // Even number for lines + // Quad-based rendering: each edge creates a quad (2 triangles = 6 indices) + // So 3 edges × 6 indices per edge = 18 indices total + expect(renderResources.edgeGeometry.indexCount).toBe(18); + expect(renderResources.edgeGeometry.indexCount % 6).toBe(0); // Multiple of 6 for quad triangles expect(renderResources.edgeGeometry.primitiveType).toBe( - PrimitiveType.LINES, + PrimitiveType.TRIANGLES, ); }); @@ -314,6 +328,36 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { expect(expectedEdges.size).toBe(3); }); + it("generates edge color attribute for material overrides and line strings", function () { + const primitive = createTestPrimitive(); + primitive.edgeVisibility.materialColor = new Cartesian4(0.2, 0.3, 0.4, 1.0); + primitive.edgeVisibility.lineStrings = [ + { + indices: new Uint16Array([0, 1, 65535, 1, 3]), + restartIndex: 65535, + materialColor: new Cartesian4(0.9, 0.1, 0.2, 1.0), + }, + ]; + + const renderResources = createMockRenderResources(primitive); + const frameState = createMockFrameState(); + + EdgeVisibilityPipelineStage.process(renderResources, primitive, frameState); + + expect(renderResources.edgeGeometry).toBeDefined(); + + const attributeLocations = renderResources.shaderBuilder.attributeLocations; + expect(attributeLocations.a_edgeColor).toBeDefined(); + + const vertexDefines = + renderResources.shaderBuilder._vertexShaderParts.defineLines; + expect(vertexDefines).toContain("HAS_EDGE_COLOR_ATTRIBUTE"); + + const attributes = + renderResources.edgeGeometry.vertexArray._attributes ?? []; + expect(attributes.length).toBeGreaterThan(5); + }); + it("sets up uniforms correctly", function () { const primitive = createTestPrimitive(); const renderResources = createMockRenderResources(primitive); @@ -337,15 +381,15 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { EdgeVisibilityPipelineStage.process(renderResources, primitive, frameState); - // Edge VAO (lines) + // Edge VAO (quad-based triangles) expect(renderResources.edgeGeometry).toBeDefined(); expect(renderResources.edgeGeometry.primitiveType).toBe( - PrimitiveType.LINES, + PrimitiveType.TRIANGLES, ); // With visibility pattern [2,0,1, 0,2,0] → 3 visible edges - // Each edge creates 2 vertices, so 6 vertices total - expect(renderResources.edgeGeometry.indexCount).toBe(6); // 3 edges × 2 vertices per edge + // Each edge creates a quad (4 vertices, 6 indices), so 18 indices total + expect(renderResources.edgeGeometry.indexCount).toBe(18); // 3 edges × 6 indices per quad }); it("validates edge VAO has 6 vertices for 3 visible edges", function () { @@ -368,6 +412,8 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { let silhouetteNormalAttribute = null; let faceNormalAAttribute = null; let faceNormalBAttribute = null; + let edgeOffsetAttribute = null; + let edgeOtherPosAttribute = null; for (let i = 0; i < attributes.length; i++) { const attr = attributes[i]; @@ -375,16 +421,22 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { // Position at location 0 positionAttribute = attr; } else if (attr.componentsPerAttribute === 1) { - // Edge type (float) - edgeTypeAttribute = attr; + // Edge type or edge offset (float) + if (!edgeTypeAttribute) { + edgeTypeAttribute = attr; + } else { + edgeOffsetAttribute = attr; + } } else if (attr.componentsPerAttribute === 3) { - // Normals (vec3) + // Normals or other position (vec3) if (!silhouetteNormalAttribute) { silhouetteNormalAttribute = attr; } else if (!faceNormalAAttribute) { faceNormalAAttribute = attr; } else if (!faceNormalBAttribute) { faceNormalBAttribute = attr; + } else { + edgeOtherPosAttribute = attr; } } } @@ -394,6 +446,8 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { expect(silhouetteNormalAttribute).toBeDefined(); expect(faceNormalAAttribute).toBeDefined(); expect(faceNormalBAttribute).toBeDefined(); + expect(edgeOffsetAttribute).toBeDefined(); + expect(edgeOtherPosAttribute).toBeDefined(); // Verify buffer properties expect(positionAttribute.componentsPerAttribute).toBe(3); // vec3 @@ -419,19 +473,14 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { // With our test data: // - 3 visible edges: (0,1)[HARD], (0,2)[SILHOUETTE], (2,3)[HARD] - // - Each edge has 2 vertices - // - Total: 6 vertices in edge domain + // - Each edge creates a quad (4 vertices, 6 indices) + // - Total: 18 indices in edge domain (3 edges × 6 indices per quad) // Verify index buffer expect(edgeVertexArray.indexBuffer).toBeDefined(); - expect(renderResources.edgeGeometry.indexCount).toBe(6); + expect(renderResources.edgeGeometry.indexCount).toBe(18); - // Expected vertex positions in edge domain: - // Edge 0: vertices (0,1) → positions: (0,0,0), (1,0,0) - // Edge 1: vertices (0,2) → positions: (0,0,0), (1,1,0) - // Edge 2: vertices (2,3) → positions: (1,1,0), (0,1,0) - - // The edge VAO creates a separate vertex domain with 6 vertices total + // The edge VAO creates quads with 4 vertices per edge const indexBuffer = edgeVertexArray.indexBuffer; expect(indexBuffer).toBeDefined(); }); @@ -459,7 +508,8 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { expect(silhouetteNormalBuffer).toBeDefined(); - expect(silhouetteNormalBuffer.sizeInBytes).toBe(6 * 3 * 4); + // Quad-based: 3 edges × 4 vertices per quad × 3 components × 4 bytes = 144 bytes + expect(silhouetteNormalBuffer.sizeInBytes).toBe(12 * 3 * 4); }); it("validates edge type VAO data values", function () { @@ -484,7 +534,8 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { expect(edgeTypeBuffer).toBeDefined(); - expect(edgeTypeBuffer.sizeInBytes).toBe(6 * 4); + // Quad-based: 3 edges × 4 vertices per quad × 1 component × 4 bytes = 48 bytes + expect(edgeTypeBuffer.sizeInBytes).toBe(12 * 4); }); it("validates edge position VAO data values", function () { @@ -508,6 +559,22 @@ describe("Scene/Model/EdgeVisibilityPipelineStage", function () { } expect(positionBuffer).toBeDefined(); - expect(positionBuffer.sizeInBytes).toBe(6 * 3 * 4); + // Quad-based: 3 edges × 4 vertices per quad × 3 components × 4 bytes = 144 bytes + expect(positionBuffer.sizeInBytes).toBe(12 * 3 * 4); + }); + + it("validates BENTLEY_materials_line_style support", function () { + const primitive = createTestPrimitive(); + const renderResources = createMockRenderResources(primitive); + const frameState = createMockFrameState(); + + EdgeVisibilityPipelineStage.process(renderResources, primitive, frameState); + + // Verify edge geometry was created with quad-based rendering + expect(renderResources.edgeGeometry).toBeDefined(); + expect(renderResources.edgeGeometry.primitiveType).toBe( + PrimitiveType.TRIANGLES, + ); + expect(renderResources.edgeGeometry.indexCount).toBe(18); // 3 edges × 6 indices per quad }); }); diff --git a/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js index 128eef15b282..2a96d4276103 100644 --- a/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js @@ -906,6 +906,109 @@ describe( u_testTexture: mockTexture, }); }); + + it("processes BENTLEY_materials_line_style with lineWidth", function () { + const renderResources = mockRenderResources(); + + const material = new ModelComponents.Material(); + material.lineWidth = 3.0; + + const primitive = new ModelComponents.Primitive(); + primitive.material = material; + + const frameState = { + context: scene.context, + pixelRatio: 2.0, + }; + + MaterialPipelineStage.process(renderResources, primitive, frameState); + + ShaderBuilderTester.expectHasVertexDefines( + renderResources.shaderBuilder, + ["HAS_LINE_STYLE", "HAS_LINE_WIDTH"], + ); + ShaderBuilderTester.expectHasFragmentDefines( + renderResources.shaderBuilder, + ["HAS_LINE_STYLE", "HAS_LINE_WIDTH", "USE_METALLIC_ROUGHNESS"], + ); + ShaderBuilderTester.expectHasVertexUniforms( + renderResources.shaderBuilder, + ["uniform float u_lineWidth;"], + ); + + expect(renderResources.uniformMap.u_lineWidth).toBeDefined(); + expect(renderResources.uniformMap.u_lineWidth()).toBe(6.0); // 3.0 * 2.0 pixelRatio + }); + + it("processes BENTLEY_materials_line_style with linePattern", function () { + const renderResources = mockRenderResources(); + + const material = new ModelComponents.Material(); + material.linePattern = 0xaaaa; // dotted pattern + + const primitive = new ModelComponents.Primitive(); + primitive.material = material; + + const frameState = { + context: scene.context, + pixelRatio: 1.0, + }; + + MaterialPipelineStage.process(renderResources, primitive, frameState); + + ShaderBuilderTester.expectHasFragmentDefines( + renderResources.shaderBuilder, + ["HAS_LINE_STYLE", "HAS_LINE_PATTERN", "USE_METALLIC_ROUGHNESS"], + ); + ShaderBuilderTester.expectHasFragmentUniforms( + renderResources.shaderBuilder, + ["uniform float u_linePattern;"], + ); + ShaderBuilderTester.expectHasVaryings(renderResources.shaderBuilder, [ + "float v_lineCoord;", + ]); + + expect(renderResources.uniformMap.u_linePattern).toBeDefined(); + expect(renderResources.uniformMap.u_linePattern()).toBe(0xaaaa); + }); + + it("processes BENTLEY_materials_line_style with both lineWidth and linePattern", function () { + const renderResources = mockRenderResources(); + + const material = new ModelComponents.Material(); + material.lineWidth = 2.5; + material.linePattern = 0xf0f0; // dashed pattern + + const primitive = new ModelComponents.Primitive(); + primitive.material = material; + + const frameState = { + context: scene.context, + pixelRatio: 1.5, + }; + + MaterialPipelineStage.process(renderResources, primitive, frameState); + + ShaderBuilderTester.expectHasVertexDefines( + renderResources.shaderBuilder, + ["HAS_LINE_STYLE", "HAS_LINE_WIDTH", "HAS_LINE_PATTERN"], + ); + ShaderBuilderTester.expectHasFragmentDefines( + renderResources.shaderBuilder, + [ + "HAS_LINE_STYLE", + "HAS_LINE_WIDTH", + "HAS_LINE_PATTERN", + "USE_METALLIC_ROUGHNESS", + ], + ); + + expect(renderResources.uniformMap.u_lineWidth).toBeDefined(); + expect(renderResources.uniformMap.u_lineWidth()).toBe(3.75); // 2.5 * 1.5 + + expect(renderResources.uniformMap.u_linePattern).toBeDefined(); + expect(renderResources.uniformMap.u_linePattern()).toBe(0xf0f0); + }); }, "WebGL", ); diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index 9d05e9c7d4ea..fea127f34867 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -2471,6 +2471,10 @@ describe( }, scene, ); + await pollToPromise(function () { + scene.renderForSpecs(); + return model._heightDirty === false; + }); expect(model._heightDirty).toBe(false); const terrainProvider = await CesiumTerrainProvider.fromUrl( "Data/CesiumTerrainTileJson/QuantizedMeshWithVertexNormals", diff --git a/packages/sandcastle/gallery/styled-gltf-lines-dev/index.html b/packages/sandcastle/gallery/styled-gltf-lines-dev/index.html new file mode 100644 index 000000000000..657fcd3fdf1c --- /dev/null +++ b/packages/sandcastle/gallery/styled-gltf-lines-dev/index.html @@ -0,0 +1,36 @@ + + + + + + + + + Styled glTF Lines + + + + + + +
+

Loading...

+
+ + + diff --git a/packages/sandcastle/gallery/styled-gltf-lines-dev/main.js b/packages/sandcastle/gallery/styled-gltf-lines-dev/main.js new file mode 100644 index 000000000000..f213ec197b14 --- /dev/null +++ b/packages/sandcastle/gallery/styled-gltf-lines-dev/main.js @@ -0,0 +1,43 @@ +import * as Cesium from "cesium"; + +const viewer = new Cesium.Viewer("cesiumContainer"); + +// The following .gltf file contains styled line data using the BENTLEY_materials_line_style extension +// combined with EXT_mesh_primitive_edge_visibility for edge rendering. +// The extension allows lines to have custom width (in screen pixels) and 16-bit dash patterns. +const modelURL = + "../../SampleData/models/StyledLines/BENTLEY_materials_line_style.gltf"; + +const height = 0.0; +const hpr = new Cesium.HeadingPitchRoll(0.0, 0.0, 0.0); +const origin = Cesium.Cartesian3.fromDegrees(0.0, 0.0, height); +const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(origin, hpr); + +try { + const model = viewer.scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: modelURL, + modelMatrix: modelMatrix, + }), + ); + + model.readyEvent.addEventListener(() => { + const camera = viewer.camera; + + // Zoom to model + const controller = viewer.scene.screenSpaceCameraController; + const r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near); + controller.minimumZoomDistance = r * 0.5; + + const center = model.boundingSphere.center; + const heading = Cesium.Math.toRadians(230.0); + const pitch = Cesium.Math.toRadians(-20.0); + camera.lookAt( + center, + new Cesium.HeadingPitchRange(heading, pitch, r * 2.0), + ); + camera.lookAtTransform(Cesium.Matrix4.IDENTITY); + }); +} catch (error) { + window.alert(`Error loading model: ${error}`); +} diff --git a/packages/sandcastle/gallery/styled-gltf-lines-dev/sandcastle.yaml b/packages/sandcastle/gallery/styled-gltf-lines-dev/sandcastle.yaml new file mode 100644 index 000000000000..6b41d049f494 --- /dev/null +++ b/packages/sandcastle/gallery/styled-gltf-lines-dev/sandcastle.yaml @@ -0,0 +1,7 @@ +legacyId: Styled glTF Lines.html +title: Styled glTF Lines - Dev +description: Use BENTLEY_materials_line_style to apply line width and pattern to glTF edges. +labels: + - Development +thumbnail: thumbnail.jpg +development: true diff --git a/packages/sandcastle/gallery/styled-gltf-lines-dev/thumbnail.jpg b/packages/sandcastle/gallery/styled-gltf-lines-dev/thumbnail.jpg new file mode 100644 index 000000000000..c35cb00a2002 Binary files /dev/null and b/packages/sandcastle/gallery/styled-gltf-lines-dev/thumbnail.jpg differ diff --git a/packages/sandcastle/gallery/styled-gltf-points-dev/index.html b/packages/sandcastle/gallery/styled-gltf-points-dev/index.html new file mode 100644 index 000000000000..abd8f4b3276d --- /dev/null +++ b/packages/sandcastle/gallery/styled-gltf-points-dev/index.html @@ -0,0 +1,36 @@ + + + + + + + + + Styled glTF Points + + + + + + +
+

Loading...

+
+ + + diff --git a/packages/sandcastle/gallery/styled-gltf-points-dev/main.js b/packages/sandcastle/gallery/styled-gltf-points-dev/main.js new file mode 100644 index 000000000000..f316829c513d --- /dev/null +++ b/packages/sandcastle/gallery/styled-gltf-points-dev/main.js @@ -0,0 +1,42 @@ +import * as Cesium from "cesium"; + +const viewer = new Cesium.Viewer("cesiumContainer"); + +// The following .gltf file contains styled point data using the BENTLEY_materials_point_style extension. +// The styled point data allows the points to have a variety of diameters. +const modelURL = + "../../SampleData/models/StyledPoints/points-r5-g8-b14-y10.gltf"; + +const height = 0.0; +const hpr = new Cesium.HeadingPitchRoll(0.0, 0.0, 0.0); +const origin = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height); +const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(origin, hpr); + +try { + const model = viewer.scene.primitives.add( + await Cesium.Model.fromGltfAsync({ + url: modelURL, + modelMatrix: modelMatrix, + }), + ); + + model.readyEvent.addEventListener(() => { + const camera = viewer.camera; + + // Zoom to model + const controller = viewer.scene.screenSpaceCameraController; + const r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near); + controller.minimumZoomDistance = r * 0.5; + + const center = model.boundingSphere.center; + const heading = Cesium.Math.toRadians(230.0); + const pitch = Cesium.Math.toRadians(-20.0); + camera.lookAt( + center, + new Cesium.HeadingPitchRange(heading, pitch, r * 2.0), + ); + camera.lookAtTransform(Cesium.Matrix4.IDENTITY); + }); +} catch (error) { + window.alert(`Error loading model: ${error}`); +} diff --git a/packages/sandcastle/gallery/styled-gltf-points-dev/sandcastle.yaml b/packages/sandcastle/gallery/styled-gltf-points-dev/sandcastle.yaml new file mode 100644 index 000000000000..568be84b77e7 --- /dev/null +++ b/packages/sandcastle/gallery/styled-gltf-points-dev/sandcastle.yaml @@ -0,0 +1,7 @@ +legacyId: Styled glTF Points.html +title: Styled glTF Points - Dev +description: Use BENTLEY_materials_point_style to apply width to glTF points. +labels: + - Development +thumbnail: thumbnail.jpg +development: true diff --git a/packages/sandcastle/gallery/styled-gltf-points-dev/thumbnail.jpg b/packages/sandcastle/gallery/styled-gltf-points-dev/thumbnail.jpg new file mode 100644 index 000000000000..f3cdb739642d Binary files /dev/null and b/packages/sandcastle/gallery/styled-gltf-points-dev/thumbnail.jpg differ