| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 | import Cartesian2 from "../Core/Cartesian2.js";import Cartesian3 from "../Core/Cartesian3.js";import ComponentDatatype from "../Core/ComponentDatatype.js";import ContextLimits from "../Renderer/ContextLimits.js";import defaultValue from "../Core/defaultValue.js";import defined from "../Core/defined.js";import destroyObject from "../Core/destroyObject.js";import DeveloperError from "../Core/DeveloperError.js";import CesiumMath from "../Core/Math.js";import MetadataComponentType from "./MetadataComponentType.js";import PixelDatatype from "../Renderer/PixelDatatype.js";import PixelFormat from "../Core/PixelFormat.js";import RuntimeError from "../Core/RuntimeError.js";import Sampler from "../Renderer/Sampler.js";import Texture from "../Renderer/Texture.js";import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js";import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";import TextureWrap from "../Renderer/TextureWrap.js";/** * @alias Megatexture * @constructor * * @param {Context} context * @param {Cartesian3} dimensions * @param {number} channelCount * @param {MetadataComponentType} componentType * @param {number} [textureMemoryByteLength] * * @private */function Megatexture(  context,  dimensions,  channelCount,  componentType,  textureMemoryByteLength) {  // TODO there are a lot of texture packing rules, see https://github.com/CesiumGS/cesium/issues/9572  // Unsigned short textures not allowed in webgl 1, so treat as float  if (componentType === MetadataComponentType.UNSIGNED_SHORT) {    componentType = MetadataComponentType.FLOAT32;  }  const supportsFloatingPointTexture = context.floatingPointTexture;  if (    componentType === MetadataComponentType.FLOAT32 &&    !supportsFloatingPointTexture  ) {    throw new RuntimeError("Floating point texture not supported");  }  // TODO support more  let pixelType;  if (    componentType === MetadataComponentType.FLOAT32 ||    componentType === MetadataComponentType.FLOAT64  ) {    pixelType = PixelDatatype.FLOAT;  } else if (componentType === MetadataComponentType.UINT8) {    pixelType = PixelDatatype.UNSIGNED_BYTE;  }  let pixelFormat;  if (channelCount === 1) {    pixelFormat = context.webgl2 ? PixelFormat.RED : PixelFormat.LUMINANCE;  } else if (channelCount === 2) {    pixelFormat = context.webgl2 ? PixelFormat.RG : PixelFormat.LUMINANCE_ALPHA;  } else if (channelCount === 3) {    pixelFormat = PixelFormat.RGB;  } else if (channelCount === 4) {    pixelFormat = PixelFormat.RGBA;  }  const maximumTextureMemoryByteLength = 512 * 1024 * 1024;  const defaultTextureMemoryByteLength = 128 * 1024 * 1024;  textureMemoryByteLength = Math.min(    defaultValue(textureMemoryByteLength, defaultTextureMemoryByteLength),    maximumTextureMemoryByteLength  );  const maximumTextureDimensionContext = ContextLimits.maximumTextureSize;  const componentTypeByteLength = MetadataComponentType.getSizeInBytes(    componentType  );  const texelCount = Math.floor(    textureMemoryByteLength / (channelCount * componentTypeByteLength)  );  const textureDimension = Math.min(    maximumTextureDimensionContext,    CesiumMath.previousPowerOfTwo(Math.floor(Math.sqrt(texelCount)))  );  const sliceCountPerRegionX = Math.ceil(Math.sqrt(dimensions.x));  const sliceCountPerRegionY = Math.ceil(dimensions.z / sliceCountPerRegionX);  const voxelCountPerRegionX = sliceCountPerRegionX * dimensions.x;  const voxelCountPerRegionY = sliceCountPerRegionY * dimensions.y;  const regionCountPerMegatextureX = Math.floor(    textureDimension / voxelCountPerRegionX  );  const regionCountPerMegatextureY = Math.floor(    textureDimension / voxelCountPerRegionY  );  if (regionCountPerMegatextureX === 0 || regionCountPerMegatextureY === 0) {    throw new RuntimeError("Tileset is too large to fit into megatexture");  }  /**   * @type {number}   * @readonly   */  this.channelCount = channelCount;  /**   * @type {MetadataComponentType}   * @readonly   */  this.componentType = componentType;  /**   * @type {Cartesian3}   * @readonly   */  this.voxelCountPerTile = Cartesian3.clone(dimensions, new Cartesian3());  /**   * @type {number}   * @readonly   */  this.maximumTileCount =    regionCountPerMegatextureX * regionCountPerMegatextureY;  /**   * @type {Cartesian2}   * @readonly   */  this.regionCountPerMegatexture = new Cartesian2(    regionCountPerMegatextureX,    regionCountPerMegatextureY  );  /**   * @type {Cartesian2}   * @readonly   */  this.voxelCountPerRegion = new Cartesian2(    voxelCountPerRegionX,    voxelCountPerRegionY  );  /**   * @type {Cartesian2}   * @readonly   */  this.sliceCountPerRegion = new Cartesian2(    sliceCountPerRegionX,    sliceCountPerRegionY  );  /**   * @type {Cartesian2}   * @readonly   */  this.voxelSizeUv = new Cartesian2(    1.0 / textureDimension,    1.0 / textureDimension  );  /**   * @type {Cartesian2}   * @readonly   */  this.sliceSizeUv = new Cartesian2(    dimensions.x / textureDimension,    dimensions.y / textureDimension  );  /**   * @type {Cartesian2}   * @readonly   */  this.regionSizeUv = new Cartesian2(    voxelCountPerRegionX / textureDimension,    voxelCountPerRegionY / textureDimension  );  /**   * @type {Texture}   * @readonly   */  this.texture = new Texture({    context: context,    pixelFormat: pixelFormat,    pixelDatatype: pixelType,    flipY: false,    width: textureDimension,    height: textureDimension,    sampler: new Sampler({      wrapS: TextureWrap.CLAMP_TO_EDGE,      wrapT: TextureWrap.CLAMP_TO_EDGE,      minificationFilter: TextureMinificationFilter.LINEAR,      magnificationFilter: TextureMagnificationFilter.LINEAR,    }),  });  const componentDatatype = MetadataComponentType.toComponentDatatype(    componentType  );  /**   * @type {Array}   */  this.tileVoxelDataTemp = ComponentDatatype.createTypedArray(    componentDatatype,    voxelCountPerRegionX * voxelCountPerRegionY * channelCount  );  /**   * @type {MegatextureNode[]}   * @readonly   */  this.nodes = new Array(this.maximumTileCount);  for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {    this.nodes[tileIndex] = new MegatextureNode(tileIndex);  }  for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {    const node = this.nodes[tileIndex];    node.previousNode = tileIndex > 0 ? this.nodes[tileIndex - 1] : undefined;    node.nextNode =      tileIndex < this.maximumTileCount - 1        ? this.nodes[tileIndex + 1]        : undefined;  }  /**   * @type {MegatextureNode}   * @readonly   */  this.occupiedList = undefined;  /**   * @type {MegatextureNode}   * @readonly   */  this.emptyList = this.nodes[0];  /**   * @type {number}   * @readonly   */  this.occupiedCount = 0;}/** * @alias MegatextureNode * @constructor * * @param {number} index * * @private */function MegatextureNode(index) {  /**   * @type {number}   */  this.index = index;  /**   * @type {MegatextureNode}   */  this.nextNode = undefined;  /**   * @type {MegatextureNode}   */  this.previousNode = undefined;}/** * @param {Array} data * @returns {number} */Megatexture.prototype.add = function (data) {  if (this.isFull()) {    throw new DeveloperError("Trying to add when there are no empty spots");  }  // remove head of empty list  const node = this.emptyList;  this.emptyList = this.emptyList.nextNode;  if (defined(this.emptyList)) {    this.emptyList.previousNode = undefined;  }  // make head of occupied list  node.nextNode = this.occupiedList;  if (defined(node.nextNode)) {    node.nextNode.previousNode = node;  }  this.occupiedList = node;  const index = node.index;  this.writeDataToTexture(index, data);  this.occupiedCount++;  return index;};/** * @param {number} index */Megatexture.prototype.remove = function (index) {  if (index < 0 || index >= this.maximumTileCount) {    throw new DeveloperError("Megatexture index out of bounds");  }  // remove from list  const node = this.nodes[index];  if (defined(node.previousNode)) {    node.previousNode.nextNode = node.nextNode;  }  if (defined(node.nextNode)) {    node.nextNode.previousNode = node.previousNode;  }  // make head of empty list  node.nextNode = this.emptyList;  if (defined(node.nextNode)) {    node.nextNode.previousNode = node;  }  node.previousNode = undefined;  this.emptyList = node;  this.occupiedCount--;};/** * @returns {boolean} */Megatexture.prototype.isFull = function () {  return this.emptyList === undefined;};/** * @param {number} tileCount * @param {Cartesian3} dimensions * @param {number} channelCount number of channels in the metadata. Must be 1 to 4. * @param {MetadataComponentType} componentType * @returns {number} */Megatexture.getApproximateTextureMemoryByteLength = function (  tileCount,  dimensions,  channelCount,  componentType) {  // TODO there's a lot of code duplicate with Megatexture constructor  // Unsigned short textures not allowed in webgl 1, so treat as float  if (componentType === MetadataComponentType.UNSIGNED_SHORT) {    componentType = MetadataComponentType.FLOAT32;  }  const datatypeSizeInBytes = MetadataComponentType.getSizeInBytes(    componentType  );  const voxelCountTotal =    tileCount * dimensions.x * dimensions.y * dimensions.z;  const sliceCountPerRegionX = Math.ceil(Math.sqrt(dimensions.z));  const sliceCountPerRegionY = Math.ceil(dimensions.z / sliceCountPerRegionX);  const voxelCountPerRegionX = sliceCountPerRegionX * dimensions.x;  const voxelCountPerRegionY = sliceCountPerRegionY * dimensions.y;  // Find the power of two that can fit all tile data, accounting for slices.  // There's probably a non-iterative solution for this, but this is good enough for now.  let textureDimension = CesiumMath.previousPowerOfTwo(    Math.floor(Math.sqrt(voxelCountTotal))  );  for (;;) {    const regionCountX = Math.floor(textureDimension / voxelCountPerRegionX);    const regionCountY = Math.floor(textureDimension / voxelCountPerRegionY);    const regionCount = regionCountX * regionCountY;    if (regionCount >= tileCount) {      break;    } else {      textureDimension *= 2;    }  }  const textureMemoryByteLength =    textureDimension * textureDimension * channelCount * datatypeSizeInBytes;  return textureMemoryByteLength;};/** * @param {number} index * @param {Float32Array|Uint16Array|Uint8Array} data */Megatexture.prototype.writeDataToTexture = function (index, data) {  // Unsigned short textures not allowed in webgl 1, so treat as float  const tileData =    data.constructor === Uint16Array ? new Float32Array(data) : data;  const voxelDimensionsPerTile = this.voxelCountPerTile;  const sliceDimensionsPerRegion = this.sliceCountPerRegion;  const voxelDimensionsPerRegion = this.voxelCountPerRegion;  const channelCount = this.channelCount;  const tileVoxelData = this.tileVoxelDataTemp;  for (let z = 0; z < voxelDimensionsPerTile.z; z++) {    const sliceVoxelOffsetX =      (z % sliceDimensionsPerRegion.x) * voxelDimensionsPerTile.x;    const sliceVoxelOffsetY =      Math.floor(z / sliceDimensionsPerRegion.x) * voxelDimensionsPerTile.y;    for (let y = 0; y < voxelDimensionsPerTile.y; y++) {      for (let x = 0; x < voxelDimensionsPerTile.x; x++) {        const readIndex =          z * voxelDimensionsPerTile.y * voxelDimensionsPerTile.x +          y * voxelDimensionsPerTile.x +          x;        const writeIndex =          (sliceVoxelOffsetY + y) * voxelDimensionsPerRegion.x +          (sliceVoxelOffsetX + x);        for (let c = 0; c < channelCount; c++) {          tileVoxelData[writeIndex * channelCount + c] =            tileData[readIndex * channelCount + c];        }      }    }  }  const regionDimensionsPerMegatexture = this.regionCountPerMegatexture;  const voxelWidth = voxelDimensionsPerRegion.x;  const voxelHeight = voxelDimensionsPerRegion.y;  const voxelOffsetX =    (index % regionDimensionsPerMegatexture.x) * voxelDimensionsPerRegion.x;  const voxelOffsetY =    Math.floor(index / regionDimensionsPerMegatexture.x) *    voxelDimensionsPerRegion.y;  const source = {    arrayBufferView: tileVoxelData,    width: voxelWidth,    height: voxelHeight,  };  const copyOptions = {    source: source,    xOffset: voxelOffsetX,    yOffset: voxelOffsetY,  };  this.texture.copyFrom(copyOptions);};/** * Returns true if this object was destroyed; otherwise, false. * <br /><br /> * If this object was destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. * * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>. * * @see Megatexture#destroy */Megatexture.prototype.isDestroyed = function () {  return false;};/** * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic * release of WebGL resources, instead of relying on the garbage collector to destroy this object. * <br /><br /> * Once an object is destroyed, it should not be used; calling any function other than * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore, * assign the return value (<code>undefined</code>) to the object as done in the example. * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * @see Megatexture#isDestroyed * * @example * megatexture = megatexture && megatexture.destroy(); */Megatexture.prototype.destroy = function () {  this.texture = this.texture && this.texture.destroy();  return destroyObject(this);};export default Megatexture;
 |