Cesium3DTileBatchTable.js 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720
  1. import arrayFill from "../Core/arrayFill.js";
  2. import Cartesian2 from "../Core/Cartesian2.js";
  3. import Cartesian4 from "../Core/Cartesian4.js";
  4. import Check from "../Core/Check.js";
  5. import clone from "../Core/clone.js";
  6. import Color from "../Core/Color.js";
  7. import combine from "../Core/combine.js";
  8. import ComponentDatatype from "../Core/ComponentDatatype.js";
  9. import defaultValue from "../Core/defaultValue.js";
  10. import defined from "../Core/defined.js";
  11. import deprecationWarning from "../Core/deprecationWarning.js";
  12. import destroyObject from "../Core/destroyObject.js";
  13. import DeveloperError from "../Core/DeveloperError.js";
  14. import CesiumMath from "../Core/Math.js";
  15. import PixelFormat from "../Core/PixelFormat.js";
  16. import RuntimeError from "../Core/RuntimeError.js";
  17. import ContextLimits from "../Renderer/ContextLimits.js";
  18. import DrawCommand from "../Renderer/DrawCommand.js";
  19. import Pass from "../Renderer/Pass.js";
  20. import PixelDatatype from "../Renderer/PixelDatatype.js";
  21. import RenderState from "../Renderer/RenderState.js";
  22. import Sampler from "../Renderer/Sampler.js";
  23. import ShaderSource from "../Renderer/ShaderSource.js";
  24. import Texture from "../Renderer/Texture.js";
  25. import AttributeType from "./AttributeType.js";
  26. import BlendingState from "./BlendingState.js";
  27. import Cesium3DTileColorBlendMode from "./Cesium3DTileColorBlendMode.js";
  28. import CullFace from "./CullFace.js";
  29. import getBinaryAccessor from "./getBinaryAccessor.js";
  30. import StencilConstants from "./StencilConstants.js";
  31. import StencilFunction from "./StencilFunction.js";
  32. import StencilOperation from "./StencilOperation.js";
  33. var DEFAULT_COLOR_VALUE = Color.WHITE;
  34. var DEFAULT_SHOW_VALUE = true;
  35. /**
  36. * @private
  37. * @constructor
  38. */
  39. function Cesium3DTileBatchTable(
  40. content,
  41. featuresLength,
  42. batchTableJson,
  43. batchTableBinary,
  44. colorChangedCallback
  45. ) {
  46. /**
  47. * @readonly
  48. */
  49. this.featuresLength = featuresLength;
  50. this._translucentFeaturesLength = 0; // Number of features in the tile that are translucent
  51. var extensions;
  52. if (defined(batchTableJson)) {
  53. extensions = batchTableJson.extensions;
  54. }
  55. this._extensions = defaultValue(extensions, {});
  56. var properties = initializeProperties(batchTableJson);
  57. this._properties = properties;
  58. this._batchTableHierarchy = initializeHierarchy(
  59. this,
  60. batchTableJson,
  61. batchTableBinary
  62. );
  63. this._batchTableBinaryProperties = getBinaryProperties(
  64. featuresLength,
  65. properties,
  66. batchTableBinary
  67. );
  68. // PERFORMANCE_IDEA: These parallel arrays probably generate cache misses in get/set color/show
  69. // and use A LOT of memory. How can we use less memory?
  70. this._showAlphaProperties = undefined; // [Show (0 or 255), Alpha (0 to 255)] property for each feature
  71. this._batchValues = undefined; // Per-feature RGBA (A is based on the color's alpha and feature's show property)
  72. this._batchValuesDirty = false;
  73. this._batchTexture = undefined;
  74. this._defaultTexture = undefined;
  75. this._pickTexture = undefined;
  76. this._pickIds = [];
  77. this._content = content;
  78. this._colorChangedCallback = colorChangedCallback;
  79. // Dimensions for batch and pick textures
  80. var textureDimensions;
  81. var textureStep;
  82. if (featuresLength > 0) {
  83. // PERFORMANCE_IDEA: this can waste memory in the last row in the uncommon case
  84. // when more than one row is needed (e.g., > 16K features in one tile)
  85. var width = Math.min(featuresLength, ContextLimits.maximumTextureSize);
  86. var height = Math.ceil(featuresLength / ContextLimits.maximumTextureSize);
  87. var stepX = 1.0 / width;
  88. var centerX = stepX * 0.5;
  89. var stepY = 1.0 / height;
  90. var centerY = stepY * 0.5;
  91. textureDimensions = new Cartesian2(width, height);
  92. textureStep = new Cartesian4(stepX, centerX, stepY, centerY);
  93. }
  94. this._textureDimensions = textureDimensions;
  95. this._textureStep = textureStep;
  96. }
  97. // This can be overridden for testing purposes
  98. Cesium3DTileBatchTable._deprecationWarning = deprecationWarning;
  99. Object.defineProperties(Cesium3DTileBatchTable.prototype, {
  100. memorySizeInBytes: {
  101. get: function () {
  102. var memory = 0;
  103. if (defined(this._pickTexture)) {
  104. memory += this._pickTexture.sizeInBytes;
  105. }
  106. if (defined(this._batchTexture)) {
  107. memory += this._batchTexture.sizeInBytes;
  108. }
  109. return memory;
  110. },
  111. },
  112. });
  113. function initializeProperties(jsonHeader) {
  114. var properties = {};
  115. if (!defined(jsonHeader)) {
  116. return properties;
  117. }
  118. for (var propertyName in jsonHeader) {
  119. if (
  120. jsonHeader.hasOwnProperty(propertyName) &&
  121. propertyName !== "HIERARCHY" && // Deprecated HIERARCHY property
  122. propertyName !== "extensions" &&
  123. propertyName !== "extras"
  124. ) {
  125. properties[propertyName] = clone(jsonHeader[propertyName], true);
  126. }
  127. }
  128. return properties;
  129. }
  130. function initializeHierarchy(batchTable, jsonHeader, binaryBody) {
  131. if (!defined(jsonHeader)) {
  132. return;
  133. }
  134. var hierarchy = batchTable._extensions["3DTILES_batch_table_hierarchy"];
  135. var legacyHierarchy = jsonHeader.HIERARCHY;
  136. if (defined(legacyHierarchy)) {
  137. Cesium3DTileBatchTable._deprecationWarning(
  138. "batchTableHierarchyExtension",
  139. "The batch table HIERARCHY property has been moved to an extension. Use extensions.3DTILES_batch_table_hierarchy instead."
  140. );
  141. batchTable._extensions["3DTILES_batch_table_hierarchy"] = legacyHierarchy;
  142. hierarchy = legacyHierarchy;
  143. }
  144. if (!defined(hierarchy)) {
  145. return;
  146. }
  147. return initializeHierarchyValues(hierarchy, binaryBody);
  148. }
  149. function initializeHierarchyValues(hierarchyJson, binaryBody) {
  150. var i;
  151. var classId;
  152. var binaryAccessor;
  153. var instancesLength = hierarchyJson.instancesLength;
  154. var classes = hierarchyJson.classes;
  155. var classIds = hierarchyJson.classIds;
  156. var parentCounts = hierarchyJson.parentCounts;
  157. var parentIds = hierarchyJson.parentIds;
  158. var parentIdsLength = instancesLength;
  159. if (defined(classIds.byteOffset)) {
  160. classIds.componentType = defaultValue(
  161. classIds.componentType,
  162. ComponentDatatype.UNSIGNED_SHORT
  163. );
  164. classIds.type = AttributeType.SCALAR;
  165. binaryAccessor = getBinaryAccessor(classIds);
  166. classIds = binaryAccessor.createArrayBufferView(
  167. binaryBody.buffer,
  168. binaryBody.byteOffset + classIds.byteOffset,
  169. instancesLength
  170. );
  171. }
  172. var parentIndexes;
  173. if (defined(parentCounts)) {
  174. if (defined(parentCounts.byteOffset)) {
  175. parentCounts.componentType = defaultValue(
  176. parentCounts.componentType,
  177. ComponentDatatype.UNSIGNED_SHORT
  178. );
  179. parentCounts.type = AttributeType.SCALAR;
  180. binaryAccessor = getBinaryAccessor(parentCounts);
  181. parentCounts = binaryAccessor.createArrayBufferView(
  182. binaryBody.buffer,
  183. binaryBody.byteOffset + parentCounts.byteOffset,
  184. instancesLength
  185. );
  186. }
  187. parentIndexes = new Uint16Array(instancesLength);
  188. parentIdsLength = 0;
  189. for (i = 0; i < instancesLength; ++i) {
  190. parentIndexes[i] = parentIdsLength;
  191. parentIdsLength += parentCounts[i];
  192. }
  193. }
  194. if (defined(parentIds) && defined(parentIds.byteOffset)) {
  195. parentIds.componentType = defaultValue(
  196. parentIds.componentType,
  197. ComponentDatatype.UNSIGNED_SHORT
  198. );
  199. parentIds.type = AttributeType.SCALAR;
  200. binaryAccessor = getBinaryAccessor(parentIds);
  201. parentIds = binaryAccessor.createArrayBufferView(
  202. binaryBody.buffer,
  203. binaryBody.byteOffset + parentIds.byteOffset,
  204. parentIdsLength
  205. );
  206. }
  207. var classesLength = classes.length;
  208. for (i = 0; i < classesLength; ++i) {
  209. var classInstancesLength = classes[i].length;
  210. var properties = classes[i].instances;
  211. var binaryProperties = getBinaryProperties(
  212. classInstancesLength,
  213. properties,
  214. binaryBody
  215. );
  216. classes[i].instances = combine(binaryProperties, properties);
  217. }
  218. var classCounts = arrayFill(new Array(classesLength), 0);
  219. var classIndexes = new Uint16Array(instancesLength);
  220. for (i = 0; i < instancesLength; ++i) {
  221. classId = classIds[i];
  222. classIndexes[i] = classCounts[classId];
  223. ++classCounts[classId];
  224. }
  225. var hierarchy = {
  226. classes: classes,
  227. classIds: classIds,
  228. classIndexes: classIndexes,
  229. parentCounts: parentCounts,
  230. parentIndexes: parentIndexes,
  231. parentIds: parentIds,
  232. };
  233. //>>includeStart('debug', pragmas.debug);
  234. validateHierarchy(hierarchy);
  235. //>>includeEnd('debug');
  236. return hierarchy;
  237. }
  238. //>>includeStart('debug', pragmas.debug);
  239. var scratchValidateStack = [];
  240. function validateHierarchy(hierarchy) {
  241. var stack = scratchValidateStack;
  242. stack.length = 0;
  243. var classIds = hierarchy.classIds;
  244. var instancesLength = classIds.length;
  245. for (var i = 0; i < instancesLength; ++i) {
  246. validateInstance(hierarchy, i, stack);
  247. }
  248. }
  249. function validateInstance(hierarchy, instanceIndex, stack) {
  250. var parentCounts = hierarchy.parentCounts;
  251. var parentIds = hierarchy.parentIds;
  252. var parentIndexes = hierarchy.parentIndexes;
  253. var classIds = hierarchy.classIds;
  254. var instancesLength = classIds.length;
  255. if (!defined(parentIds)) {
  256. // No need to validate if there are no parents
  257. return;
  258. }
  259. if (instanceIndex >= instancesLength) {
  260. throw new DeveloperError(
  261. "Parent index " +
  262. instanceIndex +
  263. " exceeds the total number of instances: " +
  264. instancesLength
  265. );
  266. }
  267. if (stack.indexOf(instanceIndex) > -1) {
  268. throw new DeveloperError(
  269. "Circular dependency detected in the batch table hierarchy."
  270. );
  271. }
  272. stack.push(instanceIndex);
  273. var parentCount = defined(parentCounts) ? parentCounts[instanceIndex] : 1;
  274. var parentIndex = defined(parentCounts)
  275. ? parentIndexes[instanceIndex]
  276. : instanceIndex;
  277. for (var i = 0; i < parentCount; ++i) {
  278. var parentId = parentIds[parentIndex + i];
  279. // Stop the traversal when the instance has no parent (its parentId equals itself), else continue the traversal.
  280. if (parentId !== instanceIndex) {
  281. validateInstance(hierarchy, parentId, stack);
  282. }
  283. }
  284. stack.pop(instanceIndex);
  285. }
  286. //>>includeEnd('debug');
  287. function getBinaryProperties(featuresLength, properties, binaryBody) {
  288. var binaryProperties;
  289. for (var name in properties) {
  290. if (properties.hasOwnProperty(name)) {
  291. var property = properties[name];
  292. var byteOffset = property.byteOffset;
  293. if (defined(byteOffset)) {
  294. // This is a binary property
  295. var componentType = property.componentType;
  296. var type = property.type;
  297. if (!defined(componentType)) {
  298. throw new RuntimeError("componentType is required.");
  299. }
  300. if (!defined(type)) {
  301. throw new RuntimeError("type is required.");
  302. }
  303. if (!defined(binaryBody)) {
  304. throw new RuntimeError(
  305. "Property " + name + " requires a batch table binary."
  306. );
  307. }
  308. var binaryAccessor = getBinaryAccessor(property);
  309. var componentCount = binaryAccessor.componentsPerAttribute;
  310. var classType = binaryAccessor.classType;
  311. var typedArray = binaryAccessor.createArrayBufferView(
  312. binaryBody.buffer,
  313. binaryBody.byteOffset + byteOffset,
  314. featuresLength
  315. );
  316. if (!defined(binaryProperties)) {
  317. binaryProperties = {};
  318. }
  319. // Store any information needed to access the binary data, including the typed array,
  320. // componentCount (e.g. a VEC4 would be 4), and the type used to pack and unpack (e.g. Cartesian4).
  321. binaryProperties[name] = {
  322. typedArray: typedArray,
  323. componentCount: componentCount,
  324. type: classType,
  325. };
  326. }
  327. }
  328. }
  329. return binaryProperties;
  330. }
  331. Cesium3DTileBatchTable.getBinaryProperties = function (
  332. featuresLength,
  333. batchTableJson,
  334. batchTableBinary
  335. ) {
  336. return getBinaryProperties(featuresLength, batchTableJson, batchTableBinary);
  337. };
  338. function getByteLength(batchTable) {
  339. var dimensions = batchTable._textureDimensions;
  340. return dimensions.x * dimensions.y * 4;
  341. }
  342. function getBatchValues(batchTable) {
  343. if (!defined(batchTable._batchValues)) {
  344. // Default batch texture to RGBA = 255: white highlight (RGB) and show/alpha = true/255 (A).
  345. var byteLength = getByteLength(batchTable);
  346. var bytes = new Uint8Array(byteLength);
  347. arrayFill(bytes, 255);
  348. batchTable._batchValues = bytes;
  349. }
  350. return batchTable._batchValues;
  351. }
  352. function getShowAlphaProperties(batchTable) {
  353. if (!defined(batchTable._showAlphaProperties)) {
  354. var byteLength = 2 * batchTable.featuresLength;
  355. var bytes = new Uint8Array(byteLength);
  356. // [Show = true, Alpha = 255]
  357. arrayFill(bytes, 255);
  358. batchTable._showAlphaProperties = bytes;
  359. }
  360. return batchTable._showAlphaProperties;
  361. }
  362. function checkBatchId(batchId, featuresLength) {
  363. if (!defined(batchId) || batchId < 0 || batchId > featuresLength) {
  364. throw new DeveloperError(
  365. "batchId is required and between zero and featuresLength - 1 (" +
  366. featuresLength -
  367. +")."
  368. );
  369. }
  370. }
  371. Cesium3DTileBatchTable.prototype.setShow = function (batchId, show) {
  372. //>>includeStart('debug', pragmas.debug);
  373. checkBatchId(batchId, this.featuresLength);
  374. Check.typeOf.bool("show", show);
  375. //>>includeEnd('debug');
  376. if (show && !defined(this._showAlphaProperties)) {
  377. // Avoid allocating since the default is show = true
  378. return;
  379. }
  380. var showAlphaProperties = getShowAlphaProperties(this);
  381. var propertyOffset = batchId * 2;
  382. var newShow = show ? 255 : 0;
  383. if (showAlphaProperties[propertyOffset] !== newShow) {
  384. showAlphaProperties[propertyOffset] = newShow;
  385. var batchValues = getBatchValues(this);
  386. // Compute alpha used in the shader based on show and color.alpha properties
  387. var offset = batchId * 4 + 3;
  388. batchValues[offset] = show ? showAlphaProperties[propertyOffset + 1] : 0;
  389. this._batchValuesDirty = true;
  390. }
  391. };
  392. Cesium3DTileBatchTable.prototype.setAllShow = function (show) {
  393. //>>includeStart('debug', pragmas.debug);
  394. Check.typeOf.bool("show", show);
  395. //>>includeEnd('debug');
  396. var featuresLength = this.featuresLength;
  397. for (var i = 0; i < featuresLength; ++i) {
  398. this.setShow(i, show);
  399. }
  400. };
  401. Cesium3DTileBatchTable.prototype.getShow = function (batchId) {
  402. //>>includeStart('debug', pragmas.debug);
  403. checkBatchId(batchId, this.featuresLength);
  404. //>>includeEnd('debug');
  405. if (!defined(this._showAlphaProperties)) {
  406. // Avoid allocating since the default is show = true
  407. return true;
  408. }
  409. var offset = batchId * 2;
  410. return this._showAlphaProperties[offset] === 255;
  411. };
  412. var scratchColorBytes = new Array(4);
  413. Cesium3DTileBatchTable.prototype.setColor = function (batchId, color) {
  414. //>>includeStart('debug', pragmas.debug);
  415. checkBatchId(batchId, this.featuresLength);
  416. Check.typeOf.object("color", color);
  417. //>>includeEnd('debug');
  418. if (Color.equals(color, DEFAULT_COLOR_VALUE) && !defined(this._batchValues)) {
  419. // Avoid allocating since the default is white
  420. return;
  421. }
  422. var newColor = color.toBytes(scratchColorBytes);
  423. var newAlpha = newColor[3];
  424. var batchValues = getBatchValues(this);
  425. var offset = batchId * 4;
  426. var showAlphaProperties = getShowAlphaProperties(this);
  427. var propertyOffset = batchId * 2;
  428. if (
  429. batchValues[offset] !== newColor[0] ||
  430. batchValues[offset + 1] !== newColor[1] ||
  431. batchValues[offset + 2] !== newColor[2] ||
  432. showAlphaProperties[propertyOffset + 1] !== newAlpha
  433. ) {
  434. batchValues[offset] = newColor[0];
  435. batchValues[offset + 1] = newColor[1];
  436. batchValues[offset + 2] = newColor[2];
  437. var wasTranslucent = showAlphaProperties[propertyOffset + 1] !== 255;
  438. // Compute alpha used in the shader based on show and color.alpha properties
  439. var show = showAlphaProperties[propertyOffset] !== 0;
  440. batchValues[offset + 3] = show ? newAlpha : 0;
  441. showAlphaProperties[propertyOffset + 1] = newAlpha;
  442. // Track number of translucent features so we know if this tile needs
  443. // opaque commands, translucent commands, or both for rendering.
  444. var isTranslucent = newAlpha !== 255;
  445. if (isTranslucent && !wasTranslucent) {
  446. ++this._translucentFeaturesLength;
  447. } else if (!isTranslucent && wasTranslucent) {
  448. --this._translucentFeaturesLength;
  449. }
  450. this._batchValuesDirty = true;
  451. if (defined(this._colorChangedCallback)) {
  452. this._colorChangedCallback(batchId, color);
  453. }
  454. }
  455. };
  456. Cesium3DTileBatchTable.prototype.setAllColor = function (color) {
  457. //>>includeStart('debug', pragmas.debug);
  458. Check.typeOf.object("color", color);
  459. //>>includeEnd('debug');
  460. var featuresLength = this.featuresLength;
  461. for (var i = 0; i < featuresLength; ++i) {
  462. this.setColor(i, color);
  463. }
  464. };
  465. Cesium3DTileBatchTable.prototype.getColor = function (batchId, result) {
  466. //>>includeStart('debug', pragmas.debug);
  467. checkBatchId(batchId, this.featuresLength);
  468. Check.typeOf.object("result", result);
  469. //>>includeEnd('debug');
  470. if (!defined(this._batchValues)) {
  471. return Color.clone(DEFAULT_COLOR_VALUE, result);
  472. }
  473. var batchValues = this._batchValues;
  474. var offset = batchId * 4;
  475. var showAlphaProperties = this._showAlphaProperties;
  476. var propertyOffset = batchId * 2;
  477. return Color.fromBytes(
  478. batchValues[offset],
  479. batchValues[offset + 1],
  480. batchValues[offset + 2],
  481. showAlphaProperties[propertyOffset + 1],
  482. result
  483. );
  484. };
  485. Cesium3DTileBatchTable.prototype.getPickColor = function (batchId) {
  486. //>>includeStart('debug', pragmas.debug);
  487. checkBatchId(batchId, this.featuresLength);
  488. //>>includeEnd('debug');
  489. return this._pickIds[batchId];
  490. };
  491. var scratchColor = new Color();
  492. Cesium3DTileBatchTable.prototype.applyStyle = function (style) {
  493. if (!defined(style)) {
  494. this.setAllColor(DEFAULT_COLOR_VALUE);
  495. this.setAllShow(DEFAULT_SHOW_VALUE);
  496. return;
  497. }
  498. var content = this._content;
  499. var length = this.featuresLength;
  500. for (var i = 0; i < length; ++i) {
  501. var feature = content.getFeature(i);
  502. var color = defined(style.color)
  503. ? style.color.evaluateColor(feature, scratchColor)
  504. : DEFAULT_COLOR_VALUE;
  505. var show = defined(style.show)
  506. ? style.show.evaluate(feature)
  507. : DEFAULT_SHOW_VALUE;
  508. this.setColor(i, color);
  509. this.setShow(i, show);
  510. }
  511. };
  512. function getBinaryProperty(binaryProperty, index) {
  513. var typedArray = binaryProperty.typedArray;
  514. var componentCount = binaryProperty.componentCount;
  515. if (componentCount === 1) {
  516. return typedArray[index];
  517. }
  518. return binaryProperty.type.unpack(typedArray, index * componentCount);
  519. }
  520. function setBinaryProperty(binaryProperty, index, value) {
  521. var typedArray = binaryProperty.typedArray;
  522. var componentCount = binaryProperty.componentCount;
  523. if (componentCount === 1) {
  524. typedArray[index] = value;
  525. } else {
  526. binaryProperty.type.pack(value, typedArray, index * componentCount);
  527. }
  528. }
  529. // The size of this array equals the maximum instance count among all loaded tiles, which has the potential to be large.
  530. var scratchVisited = [];
  531. var scratchStack = [];
  532. var marker = 0;
  533. function traverseHierarchyMultipleParents(
  534. hierarchy,
  535. instanceIndex,
  536. endConditionCallback
  537. ) {
  538. var classIds = hierarchy.classIds;
  539. var parentCounts = hierarchy.parentCounts;
  540. var parentIds = hierarchy.parentIds;
  541. var parentIndexes = hierarchy.parentIndexes;
  542. var instancesLength = classIds.length;
  543. // Ignore instances that have already been visited. This occurs in diamond inheritance situations.
  544. // Use a marker value to indicate that an instance has been visited, which increments with each run.
  545. // This is more efficient than clearing the visited array every time.
  546. var visited = scratchVisited;
  547. visited.length = Math.max(visited.length, instancesLength);
  548. var visitedMarker = ++marker;
  549. var stack = scratchStack;
  550. stack.length = 0;
  551. stack.push(instanceIndex);
  552. while (stack.length > 0) {
  553. instanceIndex = stack.pop();
  554. if (visited[instanceIndex] === visitedMarker) {
  555. // This instance has already been visited, stop traversal
  556. continue;
  557. }
  558. visited[instanceIndex] = visitedMarker;
  559. var result = endConditionCallback(hierarchy, instanceIndex);
  560. if (defined(result)) {
  561. // The end condition was met, stop the traversal and return the result
  562. return result;
  563. }
  564. var parentCount = parentCounts[instanceIndex];
  565. var parentIndex = parentIndexes[instanceIndex];
  566. for (var i = 0; i < parentCount; ++i) {
  567. var parentId = parentIds[parentIndex + i];
  568. // Stop the traversal when the instance has no parent (its parentId equals itself)
  569. // else add the parent to the stack to continue the traversal.
  570. if (parentId !== instanceIndex) {
  571. stack.push(parentId);
  572. }
  573. }
  574. }
  575. }
  576. function traverseHierarchySingleParent(
  577. hierarchy,
  578. instanceIndex,
  579. endConditionCallback
  580. ) {
  581. var hasParent = true;
  582. while (hasParent) {
  583. var result = endConditionCallback(hierarchy, instanceIndex);
  584. if (defined(result)) {
  585. // The end condition was met, stop the traversal and return the result
  586. return result;
  587. }
  588. var parentId = hierarchy.parentIds[instanceIndex];
  589. hasParent = parentId !== instanceIndex;
  590. instanceIndex = parentId;
  591. }
  592. }
  593. function traverseHierarchy(hierarchy, instanceIndex, endConditionCallback) {
  594. // Traverse over the hierarchy and process each instance with the endConditionCallback.
  595. // When the endConditionCallback returns a value, the traversal stops and that value is returned.
  596. var parentCounts = hierarchy.parentCounts;
  597. var parentIds = hierarchy.parentIds;
  598. if (!defined(parentIds)) {
  599. return endConditionCallback(hierarchy, instanceIndex);
  600. } else if (defined(parentCounts)) {
  601. return traverseHierarchyMultipleParents(
  602. hierarchy,
  603. instanceIndex,
  604. endConditionCallback
  605. );
  606. }
  607. return traverseHierarchySingleParent(
  608. hierarchy,
  609. instanceIndex,
  610. endConditionCallback
  611. );
  612. }
  613. function hasPropertyInHierarchy(batchTable, batchId, name) {
  614. var hierarchy = batchTable._batchTableHierarchy;
  615. var result = traverseHierarchy(hierarchy, batchId, function (
  616. hierarchy,
  617. instanceIndex
  618. ) {
  619. var classId = hierarchy.classIds[instanceIndex];
  620. var instances = hierarchy.classes[classId].instances;
  621. if (defined(instances[name])) {
  622. return true;
  623. }
  624. });
  625. return defined(result);
  626. }
  627. function getPropertyNamesInHierarchy(batchTable, batchId, results) {
  628. var hierarchy = batchTable._batchTableHierarchy;
  629. traverseHierarchy(hierarchy, batchId, function (hierarchy, instanceIndex) {
  630. var classId = hierarchy.classIds[instanceIndex];
  631. var instances = hierarchy.classes[classId].instances;
  632. for (var name in instances) {
  633. if (instances.hasOwnProperty(name)) {
  634. if (results.indexOf(name) === -1) {
  635. results.push(name);
  636. }
  637. }
  638. }
  639. });
  640. }
  641. function getHierarchyProperty(batchTable, batchId, name) {
  642. var hierarchy = batchTable._batchTableHierarchy;
  643. return traverseHierarchy(hierarchy, batchId, function (
  644. hierarchy,
  645. instanceIndex
  646. ) {
  647. var classId = hierarchy.classIds[instanceIndex];
  648. var instanceClass = hierarchy.classes[classId];
  649. var indexInClass = hierarchy.classIndexes[instanceIndex];
  650. var propertyValues = instanceClass.instances[name];
  651. if (defined(propertyValues)) {
  652. if (defined(propertyValues.typedArray)) {
  653. return getBinaryProperty(propertyValues, indexInClass);
  654. }
  655. return clone(propertyValues[indexInClass], true);
  656. }
  657. });
  658. }
  659. function setHierarchyProperty(batchTable, batchId, name, value) {
  660. var hierarchy = batchTable._batchTableHierarchy;
  661. var result = traverseHierarchy(hierarchy, batchId, function (
  662. hierarchy,
  663. instanceIndex
  664. ) {
  665. var classId = hierarchy.classIds[instanceIndex];
  666. var instanceClass = hierarchy.classes[classId];
  667. var indexInClass = hierarchy.classIndexes[instanceIndex];
  668. var propertyValues = instanceClass.instances[name];
  669. if (defined(propertyValues)) {
  670. //>>includeStart('debug', pragmas.debug);
  671. if (instanceIndex !== batchId) {
  672. throw new DeveloperError(
  673. 'Inherited property "' + name + '" is read-only.'
  674. );
  675. }
  676. //>>includeEnd('debug');
  677. if (defined(propertyValues.typedArray)) {
  678. setBinaryProperty(propertyValues, indexInClass, value);
  679. } else {
  680. propertyValues[indexInClass] = clone(value, true);
  681. }
  682. return true;
  683. }
  684. });
  685. return defined(result);
  686. }
  687. Cesium3DTileBatchTable.prototype.isClass = function (batchId, className) {
  688. //>>includeStart('debug', pragmas.debug);
  689. checkBatchId(batchId, this.featuresLength);
  690. Check.typeOf.string("className", className);
  691. //>>includeEnd('debug');
  692. // PERFORMANCE_IDEA : cache results in the ancestor classes to speed up this check if this area becomes a hotspot
  693. var hierarchy = this._batchTableHierarchy;
  694. if (!defined(hierarchy)) {
  695. return false;
  696. }
  697. // PERFORMANCE_IDEA : treat class names as integers for faster comparisons
  698. var result = traverseHierarchy(hierarchy, batchId, function (
  699. hierarchy,
  700. instanceIndex
  701. ) {
  702. var classId = hierarchy.classIds[instanceIndex];
  703. var instanceClass = hierarchy.classes[classId];
  704. if (instanceClass.name === className) {
  705. return true;
  706. }
  707. });
  708. return defined(result);
  709. };
  710. Cesium3DTileBatchTable.prototype.isExactClass = function (batchId, className) {
  711. //>>includeStart('debug', pragmas.debug);
  712. Check.typeOf.string("className", className);
  713. //>>includeEnd('debug');
  714. return this.getExactClassName(batchId) === className;
  715. };
  716. Cesium3DTileBatchTable.prototype.getExactClassName = function (batchId) {
  717. //>>includeStart('debug', pragmas.debug);
  718. checkBatchId(batchId, this.featuresLength);
  719. //>>includeEnd('debug');
  720. var hierarchy = this._batchTableHierarchy;
  721. if (!defined(hierarchy)) {
  722. return undefined;
  723. }
  724. var classId = hierarchy.classIds[batchId];
  725. var instanceClass = hierarchy.classes[classId];
  726. return instanceClass.name;
  727. };
  728. Cesium3DTileBatchTable.prototype.hasProperty = function (batchId, name) {
  729. //>>includeStart('debug', pragmas.debug);
  730. checkBatchId(batchId, this.featuresLength);
  731. Check.typeOf.string("name", name);
  732. //>>includeEnd('debug');
  733. return (
  734. defined(this._properties[name]) ||
  735. (defined(this._batchTableHierarchy) &&
  736. hasPropertyInHierarchy(this, batchId, name))
  737. );
  738. };
  739. Cesium3DTileBatchTable.prototype.getPropertyNames = function (
  740. batchId,
  741. results
  742. ) {
  743. //>>includeStart('debug', pragmas.debug);
  744. checkBatchId(batchId, this.featuresLength);
  745. //>>includeEnd('debug');
  746. results = defined(results) ? results : [];
  747. results.length = 0;
  748. var propertyNames = Object.keys(this._properties);
  749. results.push.apply(results, propertyNames);
  750. if (defined(this._batchTableHierarchy)) {
  751. getPropertyNamesInHierarchy(this, batchId, results);
  752. }
  753. return results;
  754. };
  755. Cesium3DTileBatchTable.prototype.getProperty = function (batchId, name) {
  756. //>>includeStart('debug', pragmas.debug);
  757. checkBatchId(batchId, this.featuresLength);
  758. Check.typeOf.string("name", name);
  759. //>>includeEnd('debug');
  760. if (defined(this._batchTableBinaryProperties)) {
  761. var binaryProperty = this._batchTableBinaryProperties[name];
  762. if (defined(binaryProperty)) {
  763. return getBinaryProperty(binaryProperty, batchId);
  764. }
  765. }
  766. var propertyValues = this._properties[name];
  767. if (defined(propertyValues)) {
  768. return clone(propertyValues[batchId], true);
  769. }
  770. if (defined(this._batchTableHierarchy)) {
  771. var hierarchyProperty = getHierarchyProperty(this, batchId, name);
  772. if (defined(hierarchyProperty)) {
  773. return hierarchyProperty;
  774. }
  775. }
  776. return undefined;
  777. };
  778. Cesium3DTileBatchTable.prototype.setProperty = function (batchId, name, value) {
  779. var featuresLength = this.featuresLength;
  780. //>>includeStart('debug', pragmas.debug);
  781. checkBatchId(batchId, featuresLength);
  782. Check.typeOf.string("name", name);
  783. //>>includeEnd('debug');
  784. if (defined(this._batchTableBinaryProperties)) {
  785. var binaryProperty = this._batchTableBinaryProperties[name];
  786. if (defined(binaryProperty)) {
  787. setBinaryProperty(binaryProperty, batchId, value);
  788. return;
  789. }
  790. }
  791. if (defined(this._batchTableHierarchy)) {
  792. if (setHierarchyProperty(this, batchId, name, value)) {
  793. return;
  794. }
  795. }
  796. var propertyValues = this._properties[name];
  797. if (!defined(propertyValues)) {
  798. // Property does not exist. Create it.
  799. this._properties[name] = new Array(featuresLength);
  800. propertyValues = this._properties[name];
  801. }
  802. propertyValues[batchId] = clone(value, true);
  803. };
  804. function getGlslComputeSt(batchTable) {
  805. // GLSL batchId is zero-based: [0, featuresLength - 1]
  806. if (batchTable._textureDimensions.y === 1) {
  807. return (
  808. "uniform vec4 tile_textureStep; \n" +
  809. "vec2 computeSt(float batchId) \n" +
  810. "{ \n" +
  811. " float stepX = tile_textureStep.x; \n" +
  812. " float centerX = tile_textureStep.y; \n" +
  813. " return vec2(centerX + (batchId * stepX), 0.5); \n" +
  814. "} \n"
  815. );
  816. }
  817. return (
  818. "uniform vec4 tile_textureStep; \n" +
  819. "uniform vec2 tile_textureDimensions; \n" +
  820. "vec2 computeSt(float batchId) \n" +
  821. "{ \n" +
  822. " float stepX = tile_textureStep.x; \n" +
  823. " float centerX = tile_textureStep.y; \n" +
  824. " float stepY = tile_textureStep.z; \n" +
  825. " float centerY = tile_textureStep.w; \n" +
  826. " float xId = mod(batchId, tile_textureDimensions.x); \n" +
  827. " float yId = floor(batchId / tile_textureDimensions.x); \n" +
  828. " return vec2(centerX + (xId * stepX), centerY + (yId * stepY)); \n" +
  829. "} \n"
  830. );
  831. }
  832. Cesium3DTileBatchTable.prototype.getVertexShaderCallback = function (
  833. handleTranslucent,
  834. batchIdAttributeName,
  835. diffuseAttributeOrUniformName
  836. ) {
  837. if (this.featuresLength === 0) {
  838. return;
  839. }
  840. var that = this;
  841. return function (source) {
  842. // If the color blend mode is HIGHLIGHT, the highlight color will always be applied in the fragment shader.
  843. // No need to apply the highlight color in the vertex shader as well.
  844. var renamedSource = modifyDiffuse(
  845. source,
  846. diffuseAttributeOrUniformName,
  847. false
  848. );
  849. var newMain;
  850. if (ContextLimits.maximumVertexTextureImageUnits > 0) {
  851. // When VTF is supported, perform per-feature show/hide in the vertex shader
  852. newMain = "";
  853. if (handleTranslucent) {
  854. newMain += "uniform bool tile_translucentCommand; \n";
  855. }
  856. newMain +=
  857. "uniform sampler2D tile_batchTexture; \n" +
  858. "varying vec4 tile_featureColor; \n" +
  859. "varying vec2 tile_featureSt; \n" +
  860. "void main() \n" +
  861. "{ \n" +
  862. " vec2 st = computeSt(" +
  863. batchIdAttributeName +
  864. "); \n" +
  865. " vec4 featureProperties = texture2D(tile_batchTexture, st); \n" +
  866. " tile_color(featureProperties); \n" +
  867. " float show = ceil(featureProperties.a); \n" + // 0 - false, non-zeo - true
  868. " gl_Position *= show; \n"; // Per-feature show/hide
  869. if (handleTranslucent) {
  870. newMain +=
  871. " bool isStyleTranslucent = (featureProperties.a != 1.0); \n" +
  872. " if (czm_pass == czm_passTranslucent) \n" +
  873. " { \n" +
  874. " if (!isStyleTranslucent && !tile_translucentCommand) \n" + // Do not render opaque features in the translucent pass
  875. " { \n" +
  876. " gl_Position *= 0.0; \n" +
  877. " } \n" +
  878. " } \n" +
  879. " else \n" +
  880. " { \n" +
  881. " if (isStyleTranslucent) \n" + // Do not render translucent features in the opaque pass
  882. " { \n" +
  883. " gl_Position *= 0.0; \n" +
  884. " } \n" +
  885. " } \n";
  886. }
  887. newMain +=
  888. " tile_featureColor = featureProperties; \n" +
  889. " tile_featureSt = st; \n" +
  890. "}";
  891. } else {
  892. // 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
  893. newMain =
  894. "varying vec2 tile_featureSt; \n" +
  895. "void main() \n" +
  896. "{ \n" +
  897. " tile_color(vec4(1.0)); \n" +
  898. " tile_featureSt = computeSt(" +
  899. batchIdAttributeName +
  900. "); \n" +
  901. "}";
  902. }
  903. return renamedSource + "\n" + getGlslComputeSt(that) + newMain;
  904. };
  905. };
  906. function getDefaultShader(source, applyHighlight) {
  907. source = ShaderSource.replaceMain(source, "tile_main");
  908. if (!applyHighlight) {
  909. return (
  910. source +
  911. "void tile_color(vec4 tile_featureColor) \n" +
  912. "{ \n" +
  913. " tile_main(); \n" +
  914. "} \n"
  915. );
  916. }
  917. // The color blend mode is intended for the RGB channels so alpha is always just multiplied.
  918. // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight)
  919. return (
  920. source +
  921. "uniform float tile_colorBlend; \n" +
  922. "void tile_color(vec4 tile_featureColor) \n" +
  923. "{ \n" +
  924. " tile_main(); \n" +
  925. " tile_featureColor = czm_gammaCorrect(tile_featureColor); \n" +
  926. " gl_FragColor.a *= tile_featureColor.a; \n" +
  927. " float highlight = ceil(tile_colorBlend); \n" +
  928. " gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n" +
  929. "} \n"
  930. );
  931. }
  932. function replaceDiffuseTextureCalls(source, diffuseAttributeOrUniformName) {
  933. var functionCall = "texture2D(" + diffuseAttributeOrUniformName;
  934. var fromIndex = 0;
  935. var startIndex = source.indexOf(functionCall, fromIndex);
  936. var endIndex;
  937. while (startIndex > -1) {
  938. var nestedLevel = 0;
  939. for (var i = startIndex; i < source.length; ++i) {
  940. var character = source.charAt(i);
  941. if (character === "(") {
  942. ++nestedLevel;
  943. } else if (character === ")") {
  944. --nestedLevel;
  945. if (nestedLevel === 0) {
  946. endIndex = i + 1;
  947. break;
  948. }
  949. }
  950. }
  951. var extractedFunction = source.slice(startIndex, endIndex);
  952. var replacedFunction =
  953. "tile_diffuse_final(" + extractedFunction + ", tile_diffuse)";
  954. source =
  955. source.slice(0, startIndex) + replacedFunction + source.slice(endIndex);
  956. fromIndex = startIndex + replacedFunction.length;
  957. startIndex = source.indexOf(functionCall, fromIndex);
  958. }
  959. return source;
  960. }
  961. function modifyDiffuse(source, diffuseAttributeOrUniformName, applyHighlight) {
  962. // If the glTF does not specify the _3DTILESDIFFUSE semantic, return the default shader.
  963. // Otherwise if _3DTILESDIFFUSE is defined prefer the shader below that can switch the color mode at runtime.
  964. if (!defined(diffuseAttributeOrUniformName)) {
  965. return getDefaultShader(source, applyHighlight);
  966. }
  967. // Find the diffuse uniform. Examples matches:
  968. // uniform vec3 u_diffuseColor;
  969. // uniform sampler2D diffuseTexture;
  970. var regex = new RegExp(
  971. "(uniform|attribute|in)\\s+(vec[34]|sampler2D)\\s+" +
  972. diffuseAttributeOrUniformName +
  973. ";"
  974. );
  975. var uniformMatch = source.match(regex);
  976. if (!defined(uniformMatch)) {
  977. // Could not find uniform declaration of type vec3, vec4, or sampler2D
  978. return getDefaultShader(source, applyHighlight);
  979. }
  980. var declaration = uniformMatch[0];
  981. var type = uniformMatch[2];
  982. source = ShaderSource.replaceMain(source, "tile_main");
  983. source = source.replace(declaration, ""); // Remove uniform declaration for now so the replace below doesn't affect it
  984. // If the tile color is white, use the source color. This implies the feature has not been styled.
  985. // Highlight: tile_colorBlend is 0.0 and the source color is used
  986. // Replace: tile_colorBlend is 1.0 and the tile color is used
  987. // Mix: tile_colorBlend is between 0.0 and 1.0, causing the source color and tile color to mix
  988. var finalDiffuseFunction =
  989. "bool isWhite(vec3 color) \n" +
  990. "{ \n" +
  991. " return all(greaterThan(color, vec3(1.0 - czm_epsilon3))); \n" +
  992. "} \n" +
  993. "vec4 tile_diffuse_final(vec4 sourceDiffuse, vec4 tileDiffuse) \n" +
  994. "{ \n" +
  995. " vec4 blendDiffuse = mix(sourceDiffuse, tileDiffuse, tile_colorBlend); \n" +
  996. " vec4 diffuse = isWhite(tileDiffuse.rgb) ? sourceDiffuse : blendDiffuse; \n" +
  997. " return vec4(diffuse.rgb, sourceDiffuse.a); \n" +
  998. "} \n";
  999. // The color blend mode is intended for the RGB channels so alpha is always just multiplied.
  1000. // gl_FragColor is multiplied by the tile color only when tile_colorBlend is 0.0 (highlight)
  1001. var highlight =
  1002. " tile_featureColor = czm_gammaCorrect(tile_featureColor); \n" +
  1003. " gl_FragColor.a *= tile_featureColor.a; \n" +
  1004. " float highlight = ceil(tile_colorBlend); \n" +
  1005. " gl_FragColor.rgb *= mix(tile_featureColor.rgb, vec3(1.0), highlight); \n";
  1006. var setColor;
  1007. if (type === "vec3" || type === "vec4") {
  1008. var sourceDiffuse =
  1009. type === "vec3"
  1010. ? "vec4(" + diffuseAttributeOrUniformName + ", 1.0)"
  1011. : diffuseAttributeOrUniformName;
  1012. var replaceDiffuse = type === "vec3" ? "tile_diffuse.xyz" : "tile_diffuse";
  1013. regex = new RegExp(diffuseAttributeOrUniformName, "g");
  1014. source = source.replace(regex, replaceDiffuse);
  1015. setColor =
  1016. " vec4 source = " +
  1017. sourceDiffuse +
  1018. "; \n" +
  1019. " tile_diffuse = tile_diffuse_final(source, tile_featureColor); \n" +
  1020. " tile_main(); \n";
  1021. } else if (type === "sampler2D") {
  1022. // Handles any number of nested parentheses
  1023. // E.g. texture2D(u_diffuse, uv)
  1024. // E.g. texture2D(u_diffuse, computeUV(index))
  1025. source = replaceDiffuseTextureCalls(source, diffuseAttributeOrUniformName);
  1026. setColor =
  1027. " tile_diffuse = tile_featureColor; \n" + " tile_main(); \n";
  1028. }
  1029. source =
  1030. "uniform float tile_colorBlend; \n" +
  1031. "vec4 tile_diffuse = vec4(1.0); \n" +
  1032. finalDiffuseFunction +
  1033. declaration +
  1034. "\n" +
  1035. source +
  1036. "\n" +
  1037. "void tile_color(vec4 tile_featureColor) \n" +
  1038. "{ \n" +
  1039. setColor;
  1040. if (applyHighlight) {
  1041. source += highlight;
  1042. }
  1043. source += "} \n";
  1044. return source;
  1045. }
  1046. Cesium3DTileBatchTable.prototype.getFragmentShaderCallback = function (
  1047. handleTranslucent,
  1048. diffuseAttributeOrUniformName
  1049. ) {
  1050. if (this.featuresLength === 0) {
  1051. return;
  1052. }
  1053. return function (source) {
  1054. source = modifyDiffuse(source, diffuseAttributeOrUniformName, true);
  1055. if (ContextLimits.maximumVertexTextureImageUnits > 0) {
  1056. // When VTF is supported, per-feature show/hide already happened in the fragment shader
  1057. source +=
  1058. "uniform sampler2D tile_pickTexture; \n" +
  1059. "varying vec2 tile_featureSt; \n" +
  1060. "varying vec4 tile_featureColor; \n" +
  1061. "void main() \n" +
  1062. "{ \n" +
  1063. " tile_color(tile_featureColor); \n" +
  1064. "}";
  1065. } else {
  1066. if (handleTranslucent) {
  1067. source += "uniform bool tile_translucentCommand; \n";
  1068. }
  1069. source +=
  1070. "uniform sampler2D tile_pickTexture; \n" +
  1071. "uniform sampler2D tile_batchTexture; \n" +
  1072. "varying vec2 tile_featureSt; \n" +
  1073. "void main() \n" +
  1074. "{ \n" +
  1075. " vec4 featureProperties = texture2D(tile_batchTexture, tile_featureSt); \n" +
  1076. " if (featureProperties.a == 0.0) { \n" + // show: alpha == 0 - false, non-zeo - true
  1077. " discard; \n" +
  1078. " } \n";
  1079. if (handleTranslucent) {
  1080. source +=
  1081. " bool isStyleTranslucent = (featureProperties.a != 1.0); \n" +
  1082. " if (czm_pass == czm_passTranslucent) \n" +
  1083. " { \n" +
  1084. " if (!isStyleTranslucent && !tile_translucentCommand) \n" + // Do not render opaque features in the translucent pass
  1085. " { \n" +
  1086. " discard; \n" +
  1087. " } \n" +
  1088. " } \n" +
  1089. " else \n" +
  1090. " { \n" +
  1091. " if (isStyleTranslucent) \n" + // Do not render translucent features in the opaque pass
  1092. " { \n" +
  1093. " discard; \n" +
  1094. " } \n" +
  1095. " } \n";
  1096. }
  1097. source += " tile_color(featureProperties); \n" + "} \n";
  1098. }
  1099. return source;
  1100. };
  1101. };
  1102. Cesium3DTileBatchTable.prototype.getClassificationFragmentShaderCallback = function () {
  1103. if (this.featuresLength === 0) {
  1104. return;
  1105. }
  1106. return function (source) {
  1107. source = ShaderSource.replaceMain(source, "tile_main");
  1108. if (ContextLimits.maximumVertexTextureImageUnits > 0) {
  1109. // When VTF is supported, per-feature show/hide already happened in the fragment shader
  1110. source +=
  1111. "uniform sampler2D tile_pickTexture;\n" +
  1112. "varying vec2 tile_featureSt; \n" +
  1113. "varying vec4 tile_featureColor; \n" +
  1114. "void main() \n" +
  1115. "{ \n" +
  1116. " tile_main(); \n" +
  1117. " gl_FragColor = tile_featureColor; \n" +
  1118. "}";
  1119. } else {
  1120. source +=
  1121. "uniform sampler2D tile_batchTexture; \n" +
  1122. "uniform sampler2D tile_pickTexture;\n" +
  1123. "varying vec2 tile_featureSt; \n" +
  1124. "void main() \n" +
  1125. "{ \n" +
  1126. " tile_main(); \n" +
  1127. " vec4 featureProperties = texture2D(tile_batchTexture, tile_featureSt); \n" +
  1128. " if (featureProperties.a == 0.0) { \n" + // show: alpha == 0 - false, non-zeo - true
  1129. " discard; \n" +
  1130. " } \n" +
  1131. " gl_FragColor = featureProperties; \n" +
  1132. "} \n";
  1133. }
  1134. return source;
  1135. };
  1136. };
  1137. function getColorBlend(batchTable) {
  1138. var tileset = batchTable._content.tileset;
  1139. var colorBlendMode = tileset.colorBlendMode;
  1140. var colorBlendAmount = tileset.colorBlendAmount;
  1141. if (colorBlendMode === Cesium3DTileColorBlendMode.HIGHLIGHT) {
  1142. return 0.0;
  1143. }
  1144. if (colorBlendMode === Cesium3DTileColorBlendMode.REPLACE) {
  1145. return 1.0;
  1146. }
  1147. if (colorBlendMode === Cesium3DTileColorBlendMode.MIX) {
  1148. // The value 0.0 is reserved for highlight, so clamp to just above 0.0.
  1149. return CesiumMath.clamp(colorBlendAmount, CesiumMath.EPSILON4, 1.0);
  1150. }
  1151. //>>includeStart('debug', pragmas.debug);
  1152. throw new DeveloperError(
  1153. 'Invalid color blend mode "' + colorBlendMode + '".'
  1154. );
  1155. //>>includeEnd('debug');
  1156. }
  1157. Cesium3DTileBatchTable.prototype.getUniformMapCallback = function () {
  1158. if (this.featuresLength === 0) {
  1159. return;
  1160. }
  1161. var that = this;
  1162. return function (uniformMap) {
  1163. var batchUniformMap = {
  1164. tile_batchTexture: function () {
  1165. // PERFORMANCE_IDEA: we could also use a custom shader that avoids the texture read.
  1166. return defaultValue(that._batchTexture, that._defaultTexture);
  1167. },
  1168. tile_textureDimensions: function () {
  1169. return that._textureDimensions;
  1170. },
  1171. tile_textureStep: function () {
  1172. return that._textureStep;
  1173. },
  1174. tile_colorBlend: function () {
  1175. return getColorBlend(that);
  1176. },
  1177. tile_pickTexture: function () {
  1178. return that._pickTexture;
  1179. },
  1180. };
  1181. return combine(uniformMap, batchUniformMap);
  1182. };
  1183. };
  1184. Cesium3DTileBatchTable.prototype.getPickId = function () {
  1185. return "texture2D(tile_pickTexture, tile_featureSt)";
  1186. };
  1187. ///////////////////////////////////////////////////////////////////////////
  1188. var StyleCommandsNeeded = {
  1189. ALL_OPAQUE: 0,
  1190. ALL_TRANSLUCENT: 1,
  1191. OPAQUE_AND_TRANSLUCENT: 2,
  1192. };
  1193. Cesium3DTileBatchTable.prototype.addDerivedCommands = function (
  1194. frameState,
  1195. commandStart
  1196. ) {
  1197. var commandList = frameState.commandList;
  1198. var commandEnd = commandList.length;
  1199. var tile = this._content._tile;
  1200. var finalResolution = tile._finalResolution;
  1201. var tileset = tile.tileset;
  1202. var bivariateVisibilityTest =
  1203. tileset._skipLevelOfDetail &&
  1204. tileset._hasMixedContent &&
  1205. frameState.context.stencilBuffer;
  1206. var styleCommandsNeeded = getStyleCommandsNeeded(this);
  1207. for (var i = commandStart; i < commandEnd; ++i) {
  1208. var command = commandList[i];
  1209. var derivedCommands = command.derivedCommands.tileset;
  1210. if (!defined(derivedCommands) || command.dirty) {
  1211. derivedCommands = {};
  1212. command.derivedCommands.tileset = derivedCommands;
  1213. derivedCommands.originalCommand = deriveCommand(command);
  1214. command.dirty = false;
  1215. }
  1216. var originalCommand = derivedCommands.originalCommand;
  1217. if (
  1218. styleCommandsNeeded !== StyleCommandsNeeded.ALL_OPAQUE &&
  1219. command.pass !== Pass.TRANSLUCENT
  1220. ) {
  1221. if (!defined(derivedCommands.translucent)) {
  1222. derivedCommands.translucent = deriveTranslucentCommand(originalCommand);
  1223. }
  1224. }
  1225. if (
  1226. styleCommandsNeeded !== StyleCommandsNeeded.ALL_TRANSLUCENT &&
  1227. command.pass !== Pass.TRANSLUCENT
  1228. ) {
  1229. if (!defined(derivedCommands.opaque)) {
  1230. derivedCommands.opaque = deriveOpaqueCommand(originalCommand);
  1231. }
  1232. if (bivariateVisibilityTest) {
  1233. if (!finalResolution) {
  1234. if (!defined(derivedCommands.zback)) {
  1235. derivedCommands.zback = deriveZBackfaceCommand(
  1236. frameState.context,
  1237. originalCommand
  1238. );
  1239. }
  1240. tileset._backfaceCommands.push(derivedCommands.zback);
  1241. }
  1242. if (
  1243. !defined(derivedCommands.stencil) ||
  1244. tile._selectionDepth !==
  1245. getLastSelectionDepth(derivedCommands.stencil)
  1246. ) {
  1247. if (command.renderState.depthMask) {
  1248. derivedCommands.stencil = deriveStencilCommand(
  1249. originalCommand,
  1250. tile._selectionDepth
  1251. );
  1252. } else {
  1253. // Ignore if tile does not write depth
  1254. derivedCommands.stencil = derivedCommands.opaque;
  1255. }
  1256. }
  1257. }
  1258. }
  1259. var opaqueCommand = bivariateVisibilityTest
  1260. ? derivedCommands.stencil
  1261. : derivedCommands.opaque;
  1262. var translucentCommand = derivedCommands.translucent;
  1263. // If the command was originally opaque:
  1264. // * If the styling applied to the tile is all opaque, use the opaque command
  1265. // (with one additional uniform needed for the shader).
  1266. // * If the styling is all translucent, use new (cached) derived commands (front
  1267. // and back faces) with a translucent render state.
  1268. // * If the styling causes both opaque and translucent features in this tile,
  1269. // then use both sets of commands.
  1270. if (command.pass !== Pass.TRANSLUCENT) {
  1271. if (styleCommandsNeeded === StyleCommandsNeeded.ALL_OPAQUE) {
  1272. commandList[i] = opaqueCommand;
  1273. }
  1274. if (styleCommandsNeeded === StyleCommandsNeeded.ALL_TRANSLUCENT) {
  1275. commandList[i] = translucentCommand;
  1276. }
  1277. if (styleCommandsNeeded === StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT) {
  1278. // PERFORMANCE_IDEA: if the tile has multiple commands, we do not know what features are in what
  1279. // commands so this case may be overkill.
  1280. commandList[i] = opaqueCommand;
  1281. commandList.push(translucentCommand);
  1282. }
  1283. } else {
  1284. // Command was originally translucent so no need to derive new commands;
  1285. // as of now, a style can't change an originally translucent feature to
  1286. // opaque since the style's alpha is modulated, not a replacement. When
  1287. // this changes, we need to derive new opaque commands here.
  1288. commandList[i] = originalCommand;
  1289. }
  1290. }
  1291. };
  1292. function getStyleCommandsNeeded(batchTable) {
  1293. var translucentFeaturesLength = batchTable._translucentFeaturesLength;
  1294. if (translucentFeaturesLength === 0) {
  1295. return StyleCommandsNeeded.ALL_OPAQUE;
  1296. } else if (translucentFeaturesLength === batchTable.featuresLength) {
  1297. return StyleCommandsNeeded.ALL_TRANSLUCENT;
  1298. }
  1299. return StyleCommandsNeeded.OPAQUE_AND_TRANSLUCENT;
  1300. }
  1301. function deriveCommand(command) {
  1302. var derivedCommand = DrawCommand.shallowClone(command);
  1303. // Add a uniform to indicate if the original command was translucent so
  1304. // the shader knows not to cull vertices that were originally transparent
  1305. // even though their style is opaque.
  1306. var translucentCommand = derivedCommand.pass === Pass.TRANSLUCENT;
  1307. derivedCommand.uniformMap = defined(derivedCommand.uniformMap)
  1308. ? derivedCommand.uniformMap
  1309. : {};
  1310. derivedCommand.uniformMap.tile_translucentCommand = function () {
  1311. return translucentCommand;
  1312. };
  1313. return derivedCommand;
  1314. }
  1315. function deriveTranslucentCommand(command) {
  1316. var derivedCommand = DrawCommand.shallowClone(command);
  1317. derivedCommand.pass = Pass.TRANSLUCENT;
  1318. derivedCommand.renderState = getTranslucentRenderState(command.renderState);
  1319. return derivedCommand;
  1320. }
  1321. function deriveOpaqueCommand(command) {
  1322. var derivedCommand = DrawCommand.shallowClone(command);
  1323. derivedCommand.renderState = getOpaqueRenderState(command.renderState);
  1324. return derivedCommand;
  1325. }
  1326. function getLogDepthPolygonOffsetFragmentShaderProgram(context, shaderProgram) {
  1327. var shader = context.shaderCache.getDerivedShaderProgram(
  1328. shaderProgram,
  1329. "zBackfaceLogDepth"
  1330. );
  1331. if (!defined(shader)) {
  1332. var fs = shaderProgram.fragmentShaderSource.clone();
  1333. fs.defines = defined(fs.defines) ? fs.defines.slice(0) : [];
  1334. fs.defines.push("POLYGON_OFFSET");
  1335. fs.sources.unshift(
  1336. "#ifdef GL_OES_standard_derivatives\n#extension GL_OES_standard_derivatives : enable\n#endif\n"
  1337. );
  1338. shader = context.shaderCache.createDerivedShaderProgram(
  1339. shaderProgram,
  1340. "zBackfaceLogDepth",
  1341. {
  1342. vertexShaderSource: shaderProgram.vertexShaderSource,
  1343. fragmentShaderSource: fs,
  1344. attributeLocations: shaderProgram._attributeLocations,
  1345. }
  1346. );
  1347. }
  1348. return shader;
  1349. }
  1350. function deriveZBackfaceCommand(context, command) {
  1351. // Write just backface depth of unresolved tiles so resolved stenciled tiles do not appear in front
  1352. var derivedCommand = DrawCommand.shallowClone(command);
  1353. var rs = clone(derivedCommand.renderState, true);
  1354. rs.cull.enabled = true;
  1355. rs.cull.face = CullFace.FRONT;
  1356. // Back faces do not need to write color.
  1357. rs.colorMask = {
  1358. red: false,
  1359. green: false,
  1360. blue: false,
  1361. alpha: false,
  1362. };
  1363. // Push back face depth away from the camera so it is less likely that back faces and front faces of the same tile
  1364. // intersect and overlap. This helps avoid flickering for very thin double-sided walls.
  1365. rs.polygonOffset = {
  1366. enabled: true,
  1367. factor: 5.0,
  1368. units: 5.0,
  1369. };
  1370. // Set the 3D Tiles bit
  1371. rs.stencilTest = StencilConstants.setCesium3DTileBit();
  1372. rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK;
  1373. derivedCommand.renderState = RenderState.fromCache(rs);
  1374. derivedCommand.castShadows = false;
  1375. derivedCommand.receiveShadows = false;
  1376. derivedCommand.uniformMap = clone(command.uniformMap);
  1377. var polygonOffset = new Cartesian2(5.0, 5.0);
  1378. derivedCommand.uniformMap.u_polygonOffset = function () {
  1379. return polygonOffset;
  1380. };
  1381. // Make the log depth depth fragment write account for the polygon offset, too.
  1382. // Otherwise, the back face commands will cause the higher resolution
  1383. // tiles to disappear.
  1384. derivedCommand.shaderProgram = getLogDepthPolygonOffsetFragmentShaderProgram(
  1385. context,
  1386. command.shaderProgram
  1387. );
  1388. return derivedCommand;
  1389. }
  1390. function deriveStencilCommand(command, reference) {
  1391. // Tiles only draw if their selection depth is >= the tile drawn already. They write their
  1392. // selection depth to the stencil buffer to prevent ancestor tiles from drawing on top
  1393. var derivedCommand = DrawCommand.shallowClone(command);
  1394. var rs = clone(derivedCommand.renderState, true);
  1395. // Stencil test is masked to the most significant 3 bits so the reference is shifted. Writes 0 for the terrain bit
  1396. rs.stencilTest.enabled = true;
  1397. rs.stencilTest.mask = StencilConstants.SKIP_LOD_MASK;
  1398. rs.stencilTest.reference =
  1399. StencilConstants.CESIUM_3D_TILE_MASK |
  1400. (reference << StencilConstants.SKIP_LOD_BIT_SHIFT);
  1401. rs.stencilTest.frontFunction = StencilFunction.GREATER_OR_EQUAL;
  1402. rs.stencilTest.frontOperation.zPass = StencilOperation.REPLACE;
  1403. rs.stencilTest.backFunction = StencilFunction.GREATER_OR_EQUAL;
  1404. rs.stencilTest.backOperation.zPass = StencilOperation.REPLACE;
  1405. rs.stencilMask =
  1406. StencilConstants.CESIUM_3D_TILE_MASK | StencilConstants.SKIP_LOD_MASK;
  1407. derivedCommand.renderState = RenderState.fromCache(rs);
  1408. return derivedCommand;
  1409. }
  1410. function getLastSelectionDepth(stencilCommand) {
  1411. // Isolate the selection depth from the stencil reference.
  1412. var reference = stencilCommand.renderState.stencilTest.reference;
  1413. return (
  1414. (reference & StencilConstants.SKIP_LOD_MASK) >>>
  1415. StencilConstants.SKIP_LOD_BIT_SHIFT
  1416. );
  1417. }
  1418. function getTranslucentRenderState(renderState) {
  1419. var rs = clone(renderState, true);
  1420. rs.cull.enabled = false;
  1421. rs.depthTest.enabled = true;
  1422. rs.depthMask = false;
  1423. rs.blending = BlendingState.ALPHA_BLEND;
  1424. return RenderState.fromCache(rs);
  1425. }
  1426. function getOpaqueRenderState(renderState) {
  1427. var rs = clone(renderState, true);
  1428. rs.stencilTest = StencilConstants.setCesium3DTileBit();
  1429. rs.stencilMask = StencilConstants.CESIUM_3D_TILE_MASK;
  1430. return RenderState.fromCache(rs);
  1431. }
  1432. ///////////////////////////////////////////////////////////////////////////
  1433. function createTexture(batchTable, context, bytes) {
  1434. var dimensions = batchTable._textureDimensions;
  1435. return new Texture({
  1436. context: context,
  1437. pixelFormat: PixelFormat.RGBA,
  1438. pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
  1439. source: {
  1440. width: dimensions.x,
  1441. height: dimensions.y,
  1442. arrayBufferView: bytes,
  1443. },
  1444. flipY: false,
  1445. sampler: Sampler.NEAREST,
  1446. });
  1447. }
  1448. function createPickTexture(batchTable, context) {
  1449. var featuresLength = batchTable.featuresLength;
  1450. if (!defined(batchTable._pickTexture) && featuresLength > 0) {
  1451. var pickIds = batchTable._pickIds;
  1452. var byteLength = getByteLength(batchTable);
  1453. var bytes = new Uint8Array(byteLength);
  1454. var content = batchTable._content;
  1455. // PERFORMANCE_IDEA: we could skip the pick texture completely by allocating
  1456. // a continuous range of pickIds and then converting the base pickId + batchId
  1457. // to RGBA in the shader. The only consider is precision issues, which might
  1458. // not be an issue in WebGL 2.
  1459. for (var i = 0; i < featuresLength; ++i) {
  1460. var pickId = context.createPickId(content.getFeature(i));
  1461. pickIds.push(pickId);
  1462. var pickColor = pickId.color;
  1463. var offset = i * 4;
  1464. bytes[offset] = Color.floatToByte(pickColor.red);
  1465. bytes[offset + 1] = Color.floatToByte(pickColor.green);
  1466. bytes[offset + 2] = Color.floatToByte(pickColor.blue);
  1467. bytes[offset + 3] = Color.floatToByte(pickColor.alpha);
  1468. }
  1469. batchTable._pickTexture = createTexture(batchTable, context, bytes);
  1470. content.tileset._statistics.batchTableByteLength +=
  1471. batchTable._pickTexture.sizeInBytes;
  1472. }
  1473. }
  1474. function updateBatchTexture(batchTable) {
  1475. var dimensions = batchTable._textureDimensions;
  1476. // PERFORMANCE_IDEA: Instead of rewriting the entire texture, use fine-grained
  1477. // texture updates when less than, for example, 10%, of the values changed. Or
  1478. // even just optimize the common case when one feature show/color changed.
  1479. batchTable._batchTexture.copyFrom({
  1480. width: dimensions.x,
  1481. height: dimensions.y,
  1482. arrayBufferView: batchTable._batchValues,
  1483. });
  1484. }
  1485. Cesium3DTileBatchTable.prototype.update = function (tileset, frameState) {
  1486. var context = frameState.context;
  1487. this._defaultTexture = context.defaultTexture;
  1488. var passes = frameState.passes;
  1489. if (passes.pick || passes.postProcess) {
  1490. createPickTexture(this, context);
  1491. }
  1492. if (this._batchValuesDirty) {
  1493. this._batchValuesDirty = false;
  1494. // Create batch texture on-demand
  1495. if (!defined(this._batchTexture)) {
  1496. this._batchTexture = createTexture(this, context, this._batchValues);
  1497. tileset._statistics.batchTableByteLength += this._batchTexture.sizeInBytes;
  1498. }
  1499. updateBatchTexture(this); // Apply per-feature show/color updates
  1500. }
  1501. };
  1502. Cesium3DTileBatchTable.prototype.isDestroyed = function () {
  1503. return false;
  1504. };
  1505. Cesium3DTileBatchTable.prototype.destroy = function () {
  1506. this._batchTexture = this._batchTexture && this._batchTexture.destroy();
  1507. this._pickTexture = this._pickTexture && this._pickTexture.destroy();
  1508. var pickIds = this._pickIds;
  1509. var length = pickIds.length;
  1510. for (var i = 0; i < length; ++i) {
  1511. pickIds[i].destroy();
  1512. }
  1513. return destroyObject(this);
  1514. };
  1515. export default Cesium3DTileBatchTable;