Cesium3DTileBatchTable.js 63 KB

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