智慧水务管理系统 - 精河县供水工程综合管理平台

Cesium3DTileBatchTable.js 36KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. import Cartesian2 from "../Core/Cartesian2.js";
  2. import Check from "../Core/Check.js";
  3. import clone from "../Core/clone.js";
  4. import Color from "../Core/Color.js";
  5. import combine from "../Core/combine.js";
  6. import defined from "../Core/defined.js";
  7. import deprecationWarning from "../Core/deprecationWarning.js";
  8. import destroyObject from "../Core/destroyObject.js";
  9. import DeveloperError from "../Core/DeveloperError.js";
  10. import CesiumMath from "../Core/Math.js";
  11. import RuntimeError from "../Core/RuntimeError.js";
  12. import ContextLimits from "../Renderer/ContextLimits.js";
  13. import DrawCommand from "../Renderer/DrawCommand.js";
  14. import Pass from "../Renderer/Pass.js";
  15. import RenderState from "../Renderer/RenderState.js";
  16. import ShaderSource from "../Renderer/ShaderSource.js";
  17. import BatchTexture from "./BatchTexture.js";
  18. import BatchTableHierarchy from "./BatchTableHierarchy.js";
  19. import BlendingState from "./BlendingState.js";
  20. import Cesium3DTileColorBlendMode from "./Cesium3DTileColorBlendMode.js";
  21. import CullFace from "./CullFace.js";
  22. import getBinaryAccessor from "./getBinaryAccessor.js";
  23. import StencilConstants from "./StencilConstants.js";
  24. import StencilFunction from "./StencilFunction.js";
  25. import StencilOperation from "./StencilOperation.js";
  26. import addAllToArray from "../Core/addAllToArray.js";
  27. const DEFAULT_COLOR_VALUE = BatchTexture.DEFAULT_COLOR_VALUE;
  28. const DEFAULT_SHOW_VALUE = BatchTexture.DEFAULT_SHOW_VALUE;
  29. /**
  30. * @private
  31. * @constructor
  32. */
  33. function Cesium3DTileBatchTable(
  34. content,
  35. featuresLength,
  36. batchTableJson,
  37. batchTableBinary,
  38. colorChangedCallback,
  39. ) {
  40. /**
  41. * @readonly
  42. */
  43. this.featuresLength = featuresLength;
  44. let extensions;
  45. if (defined(batchTableJson)) {
  46. extensions = batchTableJson.extensions;
  47. }
  48. this._extensions = extensions ?? {};
  49. const properties = initializeProperties(batchTableJson);
  50. this._properties = properties;
  51. this._batchTableHierarchy = initializeHierarchy(
  52. this,
  53. batchTableJson,
  54. batchTableBinary,
  55. );
  56. const binaryProperties = getBinaryProperties(
  57. featuresLength,
  58. properties,
  59. batchTableBinary,
  60. );
  61. this._binaryPropertiesByteLength =
  62. countBinaryPropertyMemory(binaryProperties);
  63. this._batchTableBinaryProperties = binaryProperties;
  64. this._content = content;
  65. this._batchTexture = new BatchTexture({
  66. featuresLength: featuresLength,
  67. colorChangedCallback: colorChangedCallback,
  68. owner: content,
  69. statistics: content.tileset.statistics,
  70. });
  71. }
  72. // This can be overridden for testing purposes
  73. Cesium3DTileBatchTable._deprecationWarning = deprecationWarning;
  74. Object.defineProperties(Cesium3DTileBatchTable.prototype, {
  75. /**
  76. * Size of the batch table, including the batch table hierarchy's binary
  77. * buffers and any binary properties. JSON data is not counted.
  78. *
  79. * @memberof Cesium3DTileBatchTable.prototype
  80. * @type {number}
  81. * @readonly
  82. * @private
  83. */
  84. batchTableByteLength: {
  85. get: function () {
  86. let totalByteLength = this._binaryPropertiesByteLength;
  87. if (defined(this._batchTableHierarchy)) {
  88. totalByteLength += this._batchTableHierarchy.byteLength;
  89. }
  90. totalByteLength += this._batchTexture.byteLength;
  91. return totalByteLength;
  92. },
  93. },
  94. });
  95. function initializeProperties(jsonHeader) {
  96. const properties = {};
  97. if (!defined(jsonHeader)) {
  98. return properties;
  99. }
  100. for (const propertyName in jsonHeader) {
  101. if (
  102. jsonHeader.hasOwnProperty(propertyName) &&
  103. propertyName !== "HIERARCHY" && // Deprecated HIERARCHY property
  104. propertyName !== "extensions" &&
  105. propertyName !== "extras"
  106. ) {
  107. properties[propertyName] = clone(jsonHeader[propertyName], true);
  108. }
  109. }
  110. return properties;
  111. }
  112. function initializeHierarchy(batchTable, jsonHeader, binaryBody) {
  113. if (!defined(jsonHeader)) {
  114. return;
  115. }
  116. let hierarchy = batchTable._extensions["3DTILES_batch_table_hierarchy"];
  117. const legacyHierarchy = jsonHeader.HIERARCHY;
  118. if (defined(legacyHierarchy)) {
  119. Cesium3DTileBatchTable._deprecationWarning(
  120. "batchTableHierarchyExtension",
  121. "The batch table HIERARCHY property has been moved to an extension. Use extensions.3DTILES_batch_table_hierarchy instead.",
  122. );
  123. batchTable._extensions["3DTILES_batch_table_hierarchy"] = legacyHierarchy;
  124. hierarchy = legacyHierarchy;
  125. }
  126. if (!defined(hierarchy)) {
  127. return;
  128. }
  129. return new BatchTableHierarchy({
  130. extension: hierarchy,
  131. binaryBody: binaryBody,
  132. });
  133. }
  134. function getBinaryProperties(featuresLength, properties, binaryBody) {
  135. let binaryProperties;
  136. for (const name in properties) {
  137. if (properties.hasOwnProperty(name)) {
  138. const property = properties[name];
  139. const byteOffset = property.byteOffset;
  140. if (defined(byteOffset)) {
  141. // This is a binary property
  142. const componentType = property.componentType;
  143. const type = property.type;
  144. if (!defined(componentType)) {
  145. throw new RuntimeError("componentType is required.");
  146. }
  147. if (!defined(type)) {
  148. throw new RuntimeError("type is required.");
  149. }
  150. if (!defined(binaryBody)) {
  151. throw new RuntimeError(
  152. `Property ${name} requires a batch table binary.`,
  153. );
  154. }
  155. const binaryAccessor = getBinaryAccessor(property);
  156. const componentCount = binaryAccessor.componentsPerAttribute;
  157. const classType = binaryAccessor.classType;
  158. const typedArray = binaryAccessor.createArrayBufferView(
  159. binaryBody.buffer,
  160. binaryBody.byteOffset + byteOffset,
  161. featuresLength,
  162. );
  163. if (!defined(binaryProperties)) {
  164. binaryProperties = {};
  165. }
  166. // Store any information needed to access the binary data, including the typed array,
  167. // componentCount (e.g. a VEC4 would be 4), and the type used to pack and unpack (e.g. Cartesian4).
  168. binaryProperties[name] = {
  169. typedArray: typedArray,
  170. componentCount: componentCount,
  171. type: classType,
  172. };
  173. }
  174. }
  175. }
  176. return binaryProperties;
  177. }
  178. function countBinaryPropertyMemory(binaryProperties) {
  179. if (!defined(binaryProperties)) {
  180. return 0;
  181. }
  182. let byteLength = 0;
  183. for (const name in binaryProperties) {
  184. if (binaryProperties.hasOwnProperty(name)) {
  185. byteLength += binaryProperties[name].typedArray.byteLength;
  186. }
  187. }
  188. return byteLength;
  189. }
  190. Cesium3DTileBatchTable.getBinaryProperties = function (
  191. featuresLength,
  192. batchTableJson,
  193. batchTableBinary,
  194. ) {
  195. return getBinaryProperties(featuresLength, batchTableJson, batchTableBinary);
  196. };
  197. Cesium3DTileBatchTable.prototype.setShow = function (batchId, show) {
  198. this._batchTexture.setShow(batchId, show);
  199. };
  200. Cesium3DTileBatchTable.prototype.setAllShow = function (show) {
  201. this._batchTexture.setAllShow(show);
  202. };
  203. Cesium3DTileBatchTable.prototype.getShow = function (batchId) {
  204. return this._batchTexture.getShow(batchId);
  205. };
  206. Cesium3DTileBatchTable.prototype.setColor = function (batchId, color) {
  207. this._batchTexture.setColor(batchId, color);
  208. };
  209. Cesium3DTileBatchTable.prototype.setAllColor = function (color) {
  210. this._batchTexture.setAllColor(color);
  211. };
  212. Cesium3DTileBatchTable.prototype.getColor = function (batchId, result) {
  213. return this._batchTexture.getColor(batchId, result);
  214. };
  215. Cesium3DTileBatchTable.prototype.getPickColor = function (batchId) {
  216. return this._batchTexture.getPickColor(batchId);
  217. };
  218. const scratchColor = new Color();
  219. Cesium3DTileBatchTable.prototype.applyStyle = function (style) {
  220. if (!defined(style)) {
  221. this.setAllColor(DEFAULT_COLOR_VALUE);
  222. this.setAllShow(DEFAULT_SHOW_VALUE);
  223. return;
  224. }
  225. const content = this._content;
  226. const length = this.featuresLength;
  227. for (let i = 0; i < length; ++i) {
  228. const feature = content.getFeature(i);
  229. const color = defined(style.color)
  230. ? (style.color.evaluateColor(feature, scratchColor) ??
  231. DEFAULT_COLOR_VALUE)
  232. : DEFAULT_COLOR_VALUE;
  233. const show = defined(style.show)
  234. ? (style.show.evaluate(feature) ?? DEFAULT_SHOW_VALUE)
  235. : DEFAULT_SHOW_VALUE;
  236. this.setColor(i, color);
  237. this.setShow(i, show);
  238. }
  239. };
  240. function getBinaryProperty(binaryProperty, index) {
  241. const typedArray = binaryProperty.typedArray;
  242. const componentCount = binaryProperty.componentCount;
  243. if (componentCount === 1) {
  244. return typedArray[index];
  245. }
  246. return binaryProperty.type.unpack(typedArray, index * componentCount);
  247. }
  248. function setBinaryProperty(binaryProperty, index, value) {
  249. const typedArray = binaryProperty.typedArray;
  250. const componentCount = binaryProperty.componentCount;
  251. if (componentCount === 1) {
  252. typedArray[index] = value;
  253. } else {
  254. binaryProperty.type.pack(value, typedArray, index * componentCount);
  255. }
  256. }
  257. function checkBatchId(batchId, featuresLength) {
  258. if (!defined(batchId) || batchId < 0 || batchId >= featuresLength) {
  259. throw new DeveloperError(
  260. `batchId is required and must be between zero and featuresLength - 1 (${featuresLength}` -
  261. +").",
  262. );
  263. }
  264. }
  265. Cesium3DTileBatchTable.prototype.isClass = function (batchId, className) {
  266. //>>includeStart('debug', pragmas.debug);
  267. checkBatchId(batchId, this.featuresLength);
  268. Check.typeOf.string("className", className);
  269. //>>includeEnd('debug');
  270. const hierarchy = this._batchTableHierarchy;
  271. if (!defined(hierarchy)) {
  272. return false;
  273. }
  274. return hierarchy.isClass(batchId, className);
  275. };
  276. Cesium3DTileBatchTable.prototype.isExactClass = function (batchId, className) {
  277. //>>includeStart('debug', pragmas.debug);
  278. Check.typeOf.string("className", className);
  279. //>>includeEnd('debug');
  280. return this.getExactClassName(batchId) === className;
  281. };
  282. Cesium3DTileBatchTable.prototype.getExactClassName = function (batchId) {
  283. //>>includeStart('debug', pragmas.debug);
  284. checkBatchId(batchId, this.featuresLength);
  285. //>>includeEnd('debug');
  286. const hierarchy = this._batchTableHierarchy;
  287. if (!defined(hierarchy)) {
  288. return undefined;
  289. }
  290. return hierarchy.getClassName(batchId);
  291. };
  292. Cesium3DTileBatchTable.prototype.hasProperty = function (batchId, name) {
  293. //>>includeStart('debug', pragmas.debug);
  294. checkBatchId(batchId, this.featuresLength);
  295. Check.typeOf.string("name", name);
  296. //>>includeEnd('debug');
  297. return (
  298. defined(this._properties[name]) ||
  299. (defined(this._batchTableHierarchy) &&
  300. this._batchTableHierarchy.hasProperty(batchId, name))
  301. );
  302. };
  303. /**
  304. * @private
  305. */
  306. Cesium3DTileBatchTable.prototype.hasPropertyBySemantic = function () {
  307. // Cesium 3D Tiles 1.0 formats do not have semantics
  308. return false;
  309. };
  310. Cesium3DTileBatchTable.prototype.getPropertyIds = function (batchId, results) {
  311. //>>includeStart('debug', pragmas.debug);
  312. checkBatchId(batchId, this.featuresLength);
  313. //>>includeEnd('debug');
  314. results = defined(results) ? results : [];
  315. results.length = 0;
  316. const scratchPropertyIds = Object.keys(this._properties);
  317. addAllToArray(results, scratchPropertyIds);
  318. if (defined(this._batchTableHierarchy)) {
  319. const propertyIds = this._batchTableHierarchy.getPropertyIds(
  320. batchId,
  321. scratchPropertyIds,
  322. );
  323. addAllToArray(results, propertyIds);
  324. }
  325. return results;
  326. };
  327. /**
  328. * @private
  329. */
  330. Cesium3DTileBatchTable.prototype.getPropertyBySemantic = function (
  331. batchId,
  332. name,
  333. ) {
  334. // Cesium 3D Tiles 1.0 formats do not have semantics
  335. return undefined;
  336. };
  337. Cesium3DTileBatchTable.prototype.getProperty = function (batchId, name) {
  338. //>>includeStart('debug', pragmas.debug);
  339. checkBatchId(batchId, this.featuresLength);
  340. Check.typeOf.string("name", name);
  341. //>>includeEnd('debug');
  342. if (defined(this._batchTableBinaryProperties)) {
  343. const binaryProperty = this._batchTableBinaryProperties[name];
  344. if (defined(binaryProperty)) {
  345. return getBinaryProperty(binaryProperty, batchId);
  346. }
  347. }
  348. const propertyValues = this._properties[name];
  349. if (defined(propertyValues)) {
  350. return clone(propertyValues[batchId], true);
  351. }
  352. if (defined(this._batchTableHierarchy)) {
  353. const hierarchyProperty = this._batchTableHierarchy.getProperty(
  354. batchId,
  355. name,
  356. );
  357. if (defined(hierarchyProperty)) {
  358. return hierarchyProperty;
  359. }
  360. }
  361. return undefined;
  362. };
  363. Cesium3DTileBatchTable.prototype.setProperty = function (batchId, name, value) {
  364. const featuresLength = this.featuresLength;
  365. //>>includeStart('debug', pragmas.debug);
  366. checkBatchId(batchId, featuresLength);
  367. Check.typeOf.string("name", name);
  368. //>>includeEnd('debug');
  369. if (defined(this._batchTableBinaryProperties)) {
  370. const binaryProperty = this._batchTableBinaryProperties[name];
  371. if (defined(binaryProperty)) {
  372. setBinaryProperty(binaryProperty, batchId, value);
  373. return;
  374. }
  375. }
  376. if (defined(this._batchTableHierarchy)) {
  377. if (this._batchTableHierarchy.setProperty(batchId, name, value)) {
  378. return;
  379. }
  380. }
  381. let propertyValues = this._properties[name];
  382. if (!defined(propertyValues)) {
  383. // Property does not exist. Create it.
  384. this._properties[name] = new Array(featuresLength);
  385. propertyValues = this._properties[name];
  386. }
  387. propertyValues[batchId] = clone(value, true);
  388. };
  389. function getGlslComputeSt(batchTable) {
  390. // GLSL batchId is zero-based: [0, featuresLength - 1]
  391. if (batchTable._batchTexture.textureDimensions.y === 1) {
  392. return (
  393. "uniform vec4 tile_textureStep; \n" +
  394. "vec2 computeSt(float batchId) \n" +
  395. "{ \n" +
  396. " float stepX = tile_textureStep.x; \n" +
  397. " float centerX = tile_textureStep.y; \n" +
  398. " return vec2(centerX + (batchId * stepX), 0.5); \n" +
  399. "} \n"
  400. );
  401. }
  402. return (
  403. "uniform vec4 tile_textureStep; \n" +
  404. "uniform vec2 tile_textureDimensions; \n" +
  405. "vec2 computeSt(float batchId) \n" +
  406. "{ \n" +
  407. " float stepX = tile_textureStep.x; \n" +
  408. " float centerX = tile_textureStep.y; \n" +
  409. " float stepY = tile_textureStep.z; \n" +
  410. " float centerY = tile_textureStep.w; \n" +
  411. " float xId = mod(batchId, tile_textureDimensions.x); \n" +
  412. " float yId = floor(batchId / tile_textureDimensions.x); \n" +
  413. " return vec2(centerX + (xId * stepX), centerY + (yId * stepY)); \n" +
  414. "} \n"
  415. );
  416. }
  417. Cesium3DTileBatchTable.prototype.getVertexShaderCallback = function (
  418. handleTranslucent,
  419. batchIdAttributeName,
  420. diffuseAttributeOrUniformName,
  421. ) {
  422. if (this.featuresLength === 0) {
  423. return;
  424. }
  425. const that = this;
  426. return function (source) {
  427. // If the color blend mode is HIGHLIGHT, the highlight color will always be applied in the fragment shader.
  428. // No need to apply the highlight color in the vertex shader as well.
  429. const renamedSource = modifyDiffuse(
  430. source,
  431. diffuseAttributeOrUniformName,
  432. false,
  433. );
  434. let newMain;
  435. if (ContextLimits.maximumVertexTextureImageUnits > 0) {
  436. // When VTF is supported, perform per-feature show/hide in the vertex shader
  437. newMain = "";
  438. if (handleTranslucent) {
  439. newMain += "uniform bool tile_translucentCommand; \n";
  440. }
  441. newMain +=
  442. `${
  443. "uniform sampler2D tile_batchTexture; \n" +
  444. "out vec4 tile_featureColor; \n" +
  445. "out vec2 tile_featureSt; \n" +
  446. "void main() \n" +
  447. "{ \n" +
  448. " vec2 st = computeSt("
  449. }${batchIdAttributeName}); \n` +
  450. ` vec4 featureProperties = texture(tile_batchTexture, st); \n` +
  451. ` tile_color(featureProperties); \n` +
  452. ` float show = ceil(featureProperties.a); \n` + // 0 - false, non-zero - true
  453. ` gl_Position *= show; \n`; // Per-feature show/hide
  454. if (handleTranslucent) {
  455. newMain +=
  456. " bool isStyleTranslucent = (featureProperties.a != 1.0); \n" +
  457. " if (czm_pass == czm_passTranslucent) \n" +
  458. " { \n" +
  459. " if (!isStyleTranslucent && !tile_translucentCommand) \n" + // Do not render opaque features in the translucent pass
  460. " { \n" +
  461. " gl_Position *= 0.0; \n" +
  462. " } \n" +
  463. " } \n" +
  464. " else \n" +
  465. " { \n" +
  466. " if (isStyleTranslucent) \n" + // Do not render translucent features in the opaque pass
  467. " { \n" +
  468. " gl_Position *= 0.0; \n" +
  469. " } \n" +
  470. " } \n";
  471. }
  472. newMain +=
  473. " tile_featureColor = featureProperties; \n" +
  474. " tile_featureSt = st; \n" +
  475. "}";
  476. } else {
  477. // When VTF is not supported, color blend mode MIX will look incorrect due to the feature's color not being available in the vertex shader
  478. newMain =
  479. `${
  480. "out vec2 tile_featureSt; \n" +
  481. "void main() \n" +
  482. "{ \n" +
  483. " tile_color(vec4(1.0)); \n" +
  484. " tile_featureSt = computeSt("
  485. }${batchIdAttributeName}); \n` + `}`;
  486. }
  487. return `${renamedSource}\n${getGlslComputeSt(that)}${newMain}`;
  488. };
  489. };
  490. function getDefaultShader(source, applyHighlight) {
  491. source = ShaderSource.replaceMain(source, "tile_main");
  492. if (!applyHighlight) {
  493. return (
  494. `${source}void tile_color(vec4 tile_featureColor) \n` +
  495. `{ \n` +
  496. ` tile_main(); \n` +
  497. `} \n`
  498. );
  499. }
  500. // The color blend mode is intended for the RGB channels so alpha is always just multiplied.
  501. // out_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight)
  502. return (
  503. `${source}uniform float tile_colorBlend; \n` +
  504. `void tile_color(vec4 tile_featureColor) \n` +
  505. `{ \n` +
  506. ` tile_main(); \n` +
  507. ` tile_featureColor = czm_gammaCorrect(tile_featureColor); \n` +
  508. ` out_FragColor.a *= tile_featureColor.a; \n` +
  509. ` float highlight = ceil(tile_colorBlend); \n` +
  510. ` out_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n` +
  511. `} \n`
  512. );
  513. }
  514. function replaceDiffuseTextureCalls(source, diffuseAttributeOrUniformName) {
  515. const functionCall = `texture(${diffuseAttributeOrUniformName}`;
  516. let fromIndex = 0;
  517. let startIndex = source.indexOf(functionCall, fromIndex);
  518. let endIndex;
  519. while (startIndex > -1) {
  520. let nestedLevel = 0;
  521. for (let i = startIndex; i < source.length; ++i) {
  522. const character = source.charAt(i);
  523. if (character === "(") {
  524. ++nestedLevel;
  525. } else if (character === ")") {
  526. --nestedLevel;
  527. if (nestedLevel === 0) {
  528. endIndex = i + 1;
  529. break;
  530. }
  531. }
  532. }
  533. const extractedFunction = source.slice(startIndex, endIndex);
  534. const replacedFunction = `tile_diffuse_final(${extractedFunction}, tile_diffuse)`;
  535. source =
  536. source.slice(0, startIndex) + replacedFunction + source.slice(endIndex);
  537. fromIndex = startIndex + replacedFunction.length;
  538. startIndex = source.indexOf(functionCall, fromIndex);
  539. }
  540. return source;
  541. }
  542. function modifyDiffuse(source, diffuseAttributeOrUniformName, applyHighlight) {
  543. // If the glTF does not specify the _3DTILESDIFFUSE semantic, return the default shader.
  544. // Otherwise if _3DTILESDIFFUSE is defined prefer the shader below that can switch the color mode at runtime.
  545. if (!defined(diffuseAttributeOrUniformName)) {
  546. return getDefaultShader(source, applyHighlight);
  547. }
  548. // Find the diffuse uniform. Examples matches:
  549. // uniform vec3 u_diffuseColor;
  550. // uniform sampler2D diffuseTexture;
  551. let regex = new RegExp(
  552. `(uniform|attribute|in)\\s+(vec[34]|sampler2D)\\s+${diffuseAttributeOrUniformName};`,
  553. );
  554. const uniformMatch = source.match(regex);
  555. if (!defined(uniformMatch)) {
  556. // Could not find uniform declaration of type vec3, vec4, or sampler2D
  557. return getDefaultShader(source, applyHighlight);
  558. }
  559. const declaration = uniformMatch[0];
  560. const type = uniformMatch[2];
  561. source = ShaderSource.replaceMain(source, "tile_main");
  562. source = source.replace(declaration, ""); // Remove uniform declaration for now so the replace below doesn't affect it
  563. // If the tile color is white, use the source color. This implies the feature has not been styled.
  564. // Highlight: tile_colorBlend is 0.0 and the source color is used
  565. // Replace: tile_colorBlend is 1.0 and the tile color is used
  566. // Mix: tile_colorBlend is between 0.0 and 1.0, causing the source color and tile color to mix
  567. const finalDiffuseFunction =
  568. "bool isWhite(vec3 color) \n" +
  569. "{ \n" +
  570. " return all(greaterThan(color, vec3(1.0 - czm_epsilon3))); \n" +
  571. "} \n" +
  572. "vec4 tile_diffuse_final(vec4 sourceDiffuse, vec4 tileDiffuse) \n" +
  573. "{ \n" +
  574. " vec4 blendDiffuse = mix(sourceDiffuse, tileDiffuse, tile_colorBlend); \n" +
  575. " vec4 diffuse = isWhite(tileDiffuse.rgb) ? sourceDiffuse : blendDiffuse; \n" +
  576. " return vec4(diffuse.rgb, sourceDiffuse.a); \n" +
  577. "} \n";
  578. // The color blend mode is intended for the RGB channels so alpha is always just multiplied.
  579. // out_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight)
  580. const highlight =
  581. " tile_featureColor = czm_gammaCorrect(tile_featureColor); \n" +
  582. " out_FragColor.a *= tile_featureColor.a; \n" +
  583. " float highlight = ceil(tile_colorBlend); \n" +
  584. " out_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n";
  585. let setColor;
  586. if (type === "vec3" || type === "vec4") {
  587. const sourceDiffuse =
  588. type === "vec3"
  589. ? `vec4(${diffuseAttributeOrUniformName}, 1.0)`
  590. : diffuseAttributeOrUniformName;
  591. const replaceDiffuse =
  592. type === "vec3" ? "tile_diffuse.xyz" : "tile_diffuse";
  593. regex = new RegExp(diffuseAttributeOrUniformName, "g");
  594. source = source.replace(regex, replaceDiffuse);
  595. setColor =
  596. ` vec4 source = ${sourceDiffuse}; \n` +
  597. ` tile_diffuse = tile_diffuse_final(source, tile_featureColor); \n` +
  598. ` tile_main(); \n`;
  599. } else if (type === "sampler2D") {
  600. // Handles any number of nested parentheses
  601. // E.g. texture(u_diffuse, uv)
  602. // E.g. texture(u_diffuse, computeUV(index))
  603. source = replaceDiffuseTextureCalls(source, diffuseAttributeOrUniformName);
  604. setColor =
  605. " tile_diffuse = tile_featureColor; \n" + " tile_main(); \n";
  606. }
  607. source =
  608. `${
  609. "uniform float tile_colorBlend; \n" + "vec4 tile_diffuse = vec4(1.0); \n"
  610. }${finalDiffuseFunction}${declaration}\n${source}\n` +
  611. `void tile_color(vec4 tile_featureColor) \n` +
  612. `{ \n${setColor}`;
  613. if (applyHighlight) {
  614. source += highlight;
  615. }
  616. source += "} \n";
  617. return source;
  618. }
  619. Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function (
  620. handleTranslucent,
  621. diffuseAttributeOrUniformName,
  622. hasPremultipliedAlpha,
  623. ) {
  624. if (this.featuresLength === 0) {
  625. return;
  626. }
  627. return function (source) {
  628. source = modifyDiffuse(source, diffuseAttributeOrUniformName, true);
  629. if (ContextLimits.maximumVertexTextureImageUnits > 0) {
  630. // When VTF is supported, per-feature show/hide already happened in the fragment shader
  631. source +=
  632. "uniform sampler2D tile_pickTexture; \n" +
  633. "in vec2 tile_featureSt; \n" +
  634. "in vec4 tile_featureColor; \n" +
  635. "void main() \n" +
  636. "{ \n" +
  637. " tile_color(tile_featureColor); \n";
  638. if (hasPremultipliedAlpha) {
  639. source += " out_FragColor.rgb *= out_FragColor.a; \n";
  640. }
  641. source += "}";
  642. } else {
  643. if (handleTranslucent) {
  644. source += "uniform bool tile_translucentCommand; \n";
  645. }
  646. source +=
  647. "uniform sampler2D tile_pickTexture; \n" +
  648. "uniform sampler2D tile_batchTexture; \n" +
  649. "in vec2 tile_featureSt; \n" +
  650. "void main() \n" +
  651. "{ \n" +
  652. " vec4 featureProperties = texture(tile_batchTexture, tile_featureSt); \n" +
  653. " if (featureProperties.a == 0.0) { \n" + // show: alpha == 0 - false, non-zeo - true
  654. " discard; \n" +
  655. " } \n";
  656. if (handleTranslucent) {
  657. source +=
  658. " bool isStyleTranslucent = (featureProperties.a != 1.0); \n" +
  659. " if (czm_pass == czm_passTranslucent) \n" +
  660. " { \n" +
  661. " if (!isStyleTranslucent && !tile_translucentCommand) \n" + // Do not render opaque features in the translucent pass
  662. " { \n" +
  663. " discard; \n" +
  664. " } \n" +
  665. " } \n" +
  666. " else \n" +
  667. " { \n" +
  668. " if (isStyleTranslucent) \n" + // Do not render translucent features in the opaque pass
  669. " { \n" +
  670. " discard; \n" +
  671. " } \n" +
  672. " } \n";
  673. }
  674. source += " tile_color(featureProperties); \n";
  675. if (hasPremultipliedAlpha) {
  676. source += " out_FragColor.rgb *= out_FragColor.a; \n";
  677. }
  678. source += "} \n";
  679. }
  680. return source;
  681. };
  682. };
  683. function getColorBlend(batchTable) {
  684. const tileset = batchTable._content.tileset;
  685. const colorBlendMode = tileset.colorBlendMode;
  686. const colorBlendAmount = tileset.colorBlendAmount;
  687. if (colorBlendMode === Cesium3DTileColorBlendMode.HIGHLIGHT) {
  688. return 0.0;
  689. }
  690. if (colorBlendMode === Cesium3DTileColorBlendMode.REPLACE) {
  691. return 1.0;
  692. }
  693. if (colorBlendMode === Cesium3DTileColorBlendMode.MIX) {
  694. // The value 0.0 is reserved for highlight, so clamp to just above 0.0.
  695. return CesiumMath.clamp(colorBlendAmount, CesiumMath.EPSILON4, 1.0);
  696. }
  697. //>>includeStart('debug', pragmas.debug);
  698. throw new DeveloperError(`Invalid color blend mode "${colorBlendMode}".`);
  699. //>>includeEnd('debug');
  700. }
  701. Cesium3DTileBatchTable.prototype.getUniformMapCallback = function () {
  702. if (this.featuresLength === 0) {
  703. return;
  704. }
  705. const that = this;
  706. return function (uniformMap) {
  707. const batchUniformMap = {
  708. tile_batchTexture: function () {
  709. // PERFORMANCE_IDEA: we could also use a custom shader that avoids the texture read.
  710. return (
  711. that._batchTexture.batchTexture ?? that._batchTexture.defaultTexture
  712. );
  713. },
  714. tile_textureDimensions: function () {
  715. return that._batchTexture.textureDimensions;
  716. },
  717. tile_textureStep: function () {
  718. return that._batchTexture.textureStep;
  719. },
  720. tile_colorBlend: function () {
  721. return getColorBlend(that);
  722. },
  723. tile_pickTexture: function () {
  724. return that._batchTexture.pickTexture;
  725. },
  726. };
  727. return combine(uniformMap, batchUniformMap);
  728. };
  729. };
  730. Cesium3DTileBatchTable.prototype.getPickId = function () {
  731. return "texture(tile_pickTexture, tile_featureSt)";
  732. };
  733. ///////////////////////////////////////////////////////////////////////////
  734. const StyleCommandsNeeded = {
  735. ALL_OPAQUE: 0,
  736. ALL_TRANSLUCENT: 1,
  737. OPAQUE_AND_TRANSLUCENT: 2,
  738. };
  739. Cesium3DTileBatchTable.prototype.addDerivedCommands = function (
  740. frameState,
  741. commandStart,
  742. ) {
  743. const commandList = frameState.commandList;
  744. const commandEnd = commandList.length;
  745. const tile = this._content._tile;
  746. const finalResolution = tile._finalResolution;
  747. const tileset = tile.tileset;
  748. const bivariateVisibilityTest =
  749. tileset.isSkippingLevelOfDetail &&
  750. tileset.hasMixedContent &&
  751. frameState.context.stencilBuffer;
  752. const styleCommandsNeeded = getStyleCommandsNeeded(this);
  753. for (let i = commandStart; i < commandEnd; ++i) {
  754. const command = commandList[i];
  755. if (command.pass === Pass.COMPUTE) {
  756. continue;
  757. }
  758. let derivedCommands = command.derivedCommands.tileset;
  759. if (!defined(derivedCommands) || command.dirty) {
  760. derivedCommands = {};
  761. command.derivedCommands.tileset = derivedCommands;
  762. derivedCommands.originalCommand = deriveCommand(command);
  763. command.dirty = false;
  764. }
  765. const originalCommand = derivedCommands.originalCommand;
  766. if (
  767. styleCommandsNeeded !== StyleCommandsNeeded.ALL_OPAQUE &&
  768. command.pass !== Pass.TRANSLUCENT
  769. ) {
  770. if (!defined(derivedCommands.translucent)) {
  771. derivedCommands.translucent = deriveTranslucentCommand(originalCommand);
  772. }
  773. }
  774. if (
  775. styleCommandsNeeded !== StyleCommandsNeeded.ALL_TRANSLUCENT &&
  776. command.pass !== Pass.TRANSLUCENT
  777. ) {
  778. if (!defined(derivedCommands.opaque)) {
  779. derivedCommands.opaque = deriveOpaqueCommand(originalCommand);
  780. }
  781. if (bivariateVisibilityTest) {
  782. if (!finalResolution) {
  783. if (!defined(derivedCommands.zback)) {
  784. derivedCommands.zback = deriveZBackfaceCommand(
  785. frameState.context,
  786. originalCommand,
  787. );
  788. }
  789. tileset._backfaceCommands.push(derivedCommands.zback);
  790. }
  791. if (
  792. !defined(derivedCommands.stencil) ||
  793. tile._selectionDepth !==
  794. getLastSelectionDepth(derivedCommands.stencil)
  795. ) {
  796. if (command.renderState.depthMask) {
  797. derivedCommands.stencil = deriveStencilCommand(
  798. originalCommand,
  799. tile._selectionDepth,
  800. );
  801. } else {
  802. // Ignore if tile does not write depth
  803. derivedCommands.stencil = derivedCommands.opaque;
  804. }
  805. }
  806. }
  807. }
  808. const opaqueCommand = bivariateVisibilityTest
  809. ? derivedCommands.stencil
  810. : derivedCommands.opaque;
  811. const translucentCommand = derivedCommands.translucent;
  812. // If the command was originally opaque:
  813. // * If the styling applied to the tile is all opaque, use the opaque command
  814. // (with one additional uniform needed for the shader).
  815. // * If the styling is all translucent, use new (cached) derived commands (front
  816. // and back faces) with a translucent render state.
  817. // * If the styling causes both opaque and translucent features in this tile,
  818. // then use both sets of commands.
  819. if (command.pass !== Pass.TRANSLUCENT) {
  820. if (styleCommandsNeeded === StyleCommandsNeeded.ALL_OPAQUE) {
  821. commandList[i] = opaqueCommand;
  822. }
  823. if (styleCommandsNeeded === StyleCommandsNeeded.ALL_TRANSLUCENT) {
  824. commandList[i] = translucentCommand;
  825. }
  826. if (styleCommandsNeeded === StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT) {
  827. // PERFORMANCE_IDEA: if the tile has multiple commands, we do not know what features are in what
  828. // commands so this case may be overkill.
  829. commandList[i] = opaqueCommand;
  830. commandList.push(translucentCommand);
  831. }
  832. } else {
  833. // Command was originally translucent so no need to derive new commands;
  834. // as of now, a style can't change an originally translucent feature to
  835. // opaque since the style's alpha is modulated, not a replacement. When
  836. // this changes, we need to derive new opaque commands here.
  837. commandList[i] = originalCommand;
  838. }
  839. }
  840. };
  841. function getStyleCommandsNeeded(batchTable) {
  842. const translucentFeaturesLength =
  843. batchTable._batchTexture.translucentFeaturesLength;
  844. if (translucentFeaturesLength === 0) {
  845. return StyleCommandsNeeded.ALL_OPAQUE;
  846. } else if (translucentFeaturesLength === batchTable.featuresLength) {
  847. return StyleCommandsNeeded.ALL_TRANSLUCENT;
  848. }
  849. return StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT;
  850. }
  851. function deriveCommand(command) {
  852. const derivedCommand = DrawCommand.shallowClone(command);
  853. // Add a uniform to indicate if the original command was translucent so
  854. // the shader knows not to cull vertices that were originally transparent
  855. // even though their style is opaque.
  856. const translucentCommand = derivedCommand.pass === Pass.TRANSLUCENT;
  857. derivedCommand.uniformMap = defined(derivedCommand.uniformMap)
  858. ? derivedCommand.uniformMap
  859. : {};
  860. derivedCommand.uniformMap.tile_translucentCommand = function () {
  861. return translucentCommand;
  862. };
  863. return derivedCommand;
  864. }
  865. function deriveTranslucentCommand(command) {
  866. const derivedCommand = DrawCommand.shallowClone(command);
  867. derivedCommand.pass = Pass.TRANSLUCENT;
  868. derivedCommand.renderState = getTranslucentRenderState(command.renderState);
  869. return derivedCommand;
  870. }
  871. function deriveOpaqueCommand(command) {
  872. const derivedCommand = DrawCommand.shallowClone(command);
  873. derivedCommand.renderState = getOpaqueRenderState(command.renderState);
  874. return derivedCommand;
  875. }
  876. function getLogDepthPolygonOffsetFragmentShaderProgram(context, shaderProgram) {
  877. let shader = context.shaderCache.getDerivedShaderProgram(
  878. shaderProgram,
  879. "zBackfaceLogDepth",
  880. );
  881. if (!defined(shader)) {
  882. const fs = shaderProgram.fragmentShaderSource.clone();
  883. fs.defines = defined(fs.defines) ? fs.defines.slice(0) : [];
  884. fs.defines.push("POLYGON_OFFSET");
  885. shader = context.shaderCache.createDerivedShaderProgram(
  886. shaderProgram,
  887. "zBackfaceLogDepth",
  888. {
  889. vertexShaderSource: shaderProgram.vertexShaderSource,
  890. fragmentShaderSource: fs,
  891. attributeLocations: shaderProgram._attributeLocations,
  892. },
  893. );
  894. }
  895. return shader;
  896. }
  897. function deriveZBackfaceCommand(context, command) {
  898. // Write just backface depth of unresolved tiles so resolved stenciled tiles do not appear in front
  899. const derivedCommand = DrawCommand.shallowClone(command);
  900. const rs = clone(derivedCommand.renderState, true);
  901. rs.cull.enabled = true;
  902. rs.cull.face = CullFace.FRONT;
  903. // Back faces do not need to write color.
  904. rs.colorMask = {
  905. red: false,
  906. green: false,
  907. blue: false,
  908. alpha: false,
  909. };
  910. // Push back face depth away from the camera so it is less likely that back faces and front faces of the same tile
  911. // intersect and overlap. This helps avoid flickering for very thin double-sided walls.
  912. rs.polygonOffset = {
  913. enabled: true,
  914. factor: 5.0,
  915. units: 5.0,
  916. };
  917. // Set the 3D Tiles bit
  918. rs.stencilTest = StencilConstants.setCesium3DTileBit();
  919. rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK;
  920. derivedCommand.renderState = RenderState.fromCache(rs);
  921. derivedCommand.castShadows = false;
  922. derivedCommand.receiveShadows = false;
  923. derivedCommand.uniformMap = clone(command.uniformMap);
  924. const polygonOffset = new Cartesian2(5.0, 5.0);
  925. derivedCommand.uniformMap.u_polygonOffset = function () {
  926. return polygonOffset;
  927. };
  928. // Make the log depth depth fragment write account for the polygon offset, too.
  929. // Otherwise, the back face commands will cause the higher resolution
  930. // tiles to disappear.
  931. derivedCommand.shaderProgram = getLogDepthPolygonOffsetFragmentShaderProgram(
  932. context,
  933. command.shaderProgram,
  934. );
  935. return derivedCommand;
  936. }
  937. function deriveStencilCommand(command, reference) {
  938. // Tiles only draw if their selection depth is >= the tile drawn already. They write their
  939. // selection depth to the stencil buffer to prevent ancestor tiles from drawing on top
  940. const derivedCommand = DrawCommand.shallowClone(command);
  941. const rs = clone(derivedCommand.renderState, true);
  942. // Stencil test is masked to the most significant 3 bits so the reference is shifted. Writes 0 for the terrain bit
  943. rs.stencilTest.enabled = true;
  944. rs.stencilTest.mask = StencilConstants.SKIP_LOD_MASK;
  945. rs.stencilTest.reference =
  946. StencilConstants.CESIUM_3D_TILE_MASK |
  947. (reference << StencilConstants.SKIP_LOD_BIT_SHIFT);
  948. rs.stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL;
  949. rs.stencilTest.frontOperation.zPass = StencilOperation.REPLACE;
  950. rs.stencilTest.backFunction = StencilFunction.GREATER_OR_EQUAL;
  951. rs.stencilTest.backOperation.zPass = StencilOperation.REPLACE;
  952. rs.stencilMask =
  953. StencilConstants.CESIUM_3D_TILE_MASK | StencilConstants.SKIP_LOD_MASK;
  954. derivedCommand.renderState = RenderState.fromCache(rs);
  955. return derivedCommand;
  956. }
  957. function getLastSelectionDepth(stencilCommand) {
  958. // Isolate the selection depth from the stencil reference.
  959. const reference = stencilCommand.renderState.stencilTest.reference;
  960. return (
  961. (reference & StencilConstants.SKIP_LOD_MASK) >>>
  962. StencilConstants.SKIP_LOD_BIT_SHIFT
  963. );
  964. }
  965. function getTranslucentRenderState(renderState) {
  966. const rs = clone(renderState, true);
  967. rs.cull.enabled = false;
  968. rs.depthTest.enabled = true;
  969. rs.depthMask = false;
  970. rs.blending = BlendingState.ALPHA_BLEND;
  971. rs.stencilTest = StencilConstants.setCesium3DTileBit();
  972. rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK;
  973. return RenderState.fromCache(rs);
  974. }
  975. function getOpaqueRenderState(renderState) {
  976. const rs = clone(renderState, true);
  977. rs.stencilTest = StencilConstants.setCesium3DTileBit();
  978. rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK;
  979. return RenderState.fromCache(rs);
  980. }
  981. Cesium3DTileBatchTable.prototype.update = function (tileset, frameState) {
  982. this._batchTexture.update(tileset, frameState);
  983. };
  984. Cesium3DTileBatchTable.prototype.isDestroyed = function () {
  985. return false;
  986. };
  987. Cesium3DTileBatchTable.prototype.destroy = function () {
  988. this._batchTexture = this._batchTexture && this._batchTexture.destroy();
  989. return destroyObject(this);
  990. };
  991. export default Cesium3DTileBatchTable;