All files / engine/Source/Scene MetadataPicking.js

80.85% Statements 76/94
91.66% Branches 44/48
100% Functions 8/8
82.22% Lines 74/90

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397                                            1x                                 1x         145x   75x   5x   3x   3x   3x   3x   3x   3x   41x   2x   4x                                             1x         133x 133x         127x 8x   119x                                                       1x         31x   31x 31x 31x 30x 30x 22x 22x   105x 105x         104x   21x   8x 8x         5x                                                                                 1x       15x         15x                         15x         12x                                     1x 23x     23x           10x   56x 12x   3x   3x   3x   1x   1x   1x                                 1x 23x     23x           10x   13x   3x   3x   3x   1x   1x   1x     1x                                           1x         9x         8x                 4x       4x       4x               8x                         8x       8x        
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import defined from "../Core/defined.js";
import Matrix2 from "../Core/Matrix2.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import RuntimeError from "../Core/RuntimeError.js";
import MetadataClassProperty from "./MetadataClassProperty.js";
import MetadataComponentType from "./MetadataComponentType.js";
import MetadataType from "./MetadataType.js";
 
/**
 * Utility functions for metadata picking.
 *
 * These are used by the `Picking.pickMetadata` function to decode
 * the metadata values that have been read from the frame buffer
 * into the actual metadata values, according to the structure
 * defined by the `MetadataClassProperty`.
 *
 * @private
 */
const MetadataPicking = {};
 
/**
 * Returns the value at the specified index of the given data view,
 * interpreting the data to have the given component type.
 *
 * @param {MetadataComponentType} componentType The `MetadataComponentType`
 * @param {DataView} dataView The data view
 * @param {number} index The index (byte offset)
 * @returns {number|bigint|undefined} The value
 * @throws RuntimeError If the given component type is not a valid
 * `MetadataComponentType`
 * @throws RangeError If reading the data from the given data view would
 * cause an out-of-bounds access
 *
 * @private
 */
MetadataPicking.decodeRawMetadataValue = function (
  componentType,
  dataView,
  index,
) {
  switch (componentType) {
    case MetadataComponentType.INT8:
      return dataView.getInt8(index);
    case MetadataComponentType.UINT8:
      return dataView.getUint8(index);
    case MetadataComponentType.INT16:
      return dataView.getInt16(index, true);
    case MetadataComponentType.UINT16:
      return dataView.getUint16(index, true);
    case MetadataComponentType.INT32:
      return dataView.getInt32(index, true);
    case MetadataComponentType.UINT32:
      return dataView.getUint32(index, true);
    case MetadataComponentType.INT64:
      return dataView.getBigInt64(index, true);
    case MetadataComponentType.UINT64:
      return dataView.getBigUint64(index, true);
    case MetadataComponentType.FLOAT32:
      return dataView.getFloat32(index, true);
    case MetadataComponentType.FLOAT64:
      return dataView.getFloat64(index, true);
  }
  throw new RuntimeError(`Invalid component type: ${componentType}`);
};
 
/**
 * Decodes one component of a metadata value with the given property type
 * from the given data view.
 *
 * This will decode one component (e.g. one entry of a SCALAR array,
 * or one component of a VEC2 element).
 *
 * This will apply normalization to the raw component value if the given
 * class property is 'normalized'.
 *
 * @param {MetadataClassProperty} classProperty The class property
 * @param {DataView} dataView The data view containing the raw metadata values
 * @param {number} dataViewOffset The byte offset within the data view from
 * which the component should be read
 * @returns {number|bigint|undefined} The metadata value component
 * @throws RuntimeError If the component of the given property is not
 * a valid `MetadataComponentType`
 * @throws RangeError If reading the data from the given data view would
 * cause an out-of-bounds access
 */
MetadataPicking.decodeRawMetadataValueComponent = function (
  classProperty,
  dataView,
  dataViewOffset,
) {
  const componentType = classProperty.componentType;
  const component = MetadataPicking.decodeRawMetadataValue(
    componentType,
    dataView,
    dataViewOffset,
  );
  if (classProperty.normalized) {
    return MetadataComponentType.normalize(component, componentType);
  }
  return component;
};
 
/**
 * Decodes one element of a metadata value with the given property type
 * from the given data view.
 *
 * When the given class property is vector- or matrix typed, then the
 * result will be an array, with a length that corresponds to the
 * number of vector- or matrix components.
 *
 * Otherwise, it will be a single value.
 *
 * In any case, the return value will be the "raw" value, which does
 * take into account normalization, but does NOT take into account
 * default/noData value handling.
 *
 * @param {MetadataClassProperty} classProperty The metadata class property
 * @param {DataView} dataView The data view containing the raw metadata values
 * @param {number} elementIndex The index of the element. This is the index
 * inside the array for array-typed properties, and 0 for non-array types.
 * @returns {number|number[]|bigint|bigint[]|undefined} The decoded metadata value element
 * @throws RuntimeError If the component of the given property is not
 * a valid `MetadataComponentType`
 * @throws RangeError If reading the data from the given data view would
 * cause an out-of-bounds access
 *
 */
MetadataPicking.decodeRawMetadataValueElement = function (
  classProperty,
  dataView,
  elementIndex,
) {
  const componentType = classProperty.componentType;
  const componentSizeInBytes =
    MetadataComponentType.getSizeInBytes(componentType);
  const type = classProperty.type;
  const componentCount = MetadataType.getComponentCount(type);
  const elementSizeInBytes = componentSizeInBytes * componentCount;
  if (componentCount > 1) {
    const result = Array(componentCount);
    for (let i = 0; i < componentCount; i++) {
      const offset =
        elementIndex * elementSizeInBytes + i * componentSizeInBytes;
      const component = MetadataPicking.decodeRawMetadataValueComponent(
        classProperty,
        dataView,
        offset,
      );
      result[i] = component;
    }
    return result;
  }
  const offset = elementIndex * elementSizeInBytes;
  const result = MetadataPicking.decodeRawMetadataValueComponent(
    classProperty,
    dataView,
    offset,
  );
  return result;
};
 
/**
 * Decode the given raw values into the raw (array-based) form of
 * a metadata property value.
 *
 * (For decoding to types like `CartesianN`, the `decodeMetadataValues`
 * function can be used)
 *
 * The given values are a `Uint8Array` containing the RGBA
 * values that have been read from the metadata picking
 * frame buffer. They are assumed to contain the value for
 * the given class property, as encoded by the
 * `MetadataPickingPipelineStage` for metadata picking.
 *
 * When the given class property is an array, then (it has to be
 * a fixed-length array, and) the result will be an array with
 * the respective length.
 *
 * When the given class property is vector- or matrix typed,
 * then the result will be an array, with a length that corresponds
 * to the number of vector- or matrix components.
 *
 * (The case that the property is an array of vector- or matrix
 * elements is not supported on the side of the general metadata
 * shader infrastructure, but handled here nevertheless. For such
 * an input, the result would be an array of arrays, with each
 * element representing one of the vectors or matrices).
 *
 * In any case, the return value will be the "raw" value, which does
 * take into account normalization, but does NOT take into account
 * any offset/scale, or default/noData value handling.
 *
 * @param {MetadataClassProperty} classProperty The `MetadataClassProperty`
 * @param {Uint8Array} rawPixelValues The raw values
 * @returns {number|bigint|number[]|bigint[]|undefined} The value
 * @throws RuntimeError If the class property has an invalid component type
 *
 * @private
 */
MetadataPicking.decodeRawMetadataValues = function (
  classProperty,
  rawPixelValues,
) {
  const dataView = new DataView(
    rawPixelValues.buffer,
    rawPixelValues.byteOffset,
    rawPixelValues.byteLength,
  );
  Iif (classProperty.isArray) {
    const arrayLength = classProperty.arrayLength;
    const result = Array(arrayLength);
    for (let i = 0; i < arrayLength; i++) {
      const element = MetadataPicking.decodeRawMetadataValueElement(
        classProperty,
        dataView,
        i,
      );
      result[i] = element;
    }
    return result;
  }
  const result = MetadataPicking.decodeRawMetadataValueElement(
    classProperty,
    dataView,
    0,
  );
  return result;
};
 
/**
 * Converts the given type into an object representation where appropriate.
 *
 * When the given type is `SCALAR`, `STRING`, `BOOLEAN`, or `ENUM`, or
 * when the given value is `undefined`, then the given value will be
 * returned.
 *
 * Otherwise, for the `VECn/MATn` types, the given value is assumed to be
 * a numeric array, and is converted into the matching `CartesianN/MatrixN`
 * value.
 *
 * @param {string} type The `ClassProperty` type
 * @param {number|bigint|number[]|bigint[]|undefined} value The input value
 * @returns {undefined|number|bigint|string|boolean|Cartesian2|Cartesian3|Cartesian4|Matrix2|Matrix3|Matrix4} The object representation
 * @throws RuntimeError If the type is not a valid `MetadataType`
 */
MetadataPicking.convertToObjectType = function (type, value) {
  Iif (!defined(value)) {
    return value;
  }
  if (
    type === MetadataType.SCALAR ||
    type === MetadataType.STRING ||
    type === MetadataType.BOOLEAN ||
    type === MetadataType.ENUM
  ) {
    return value;
  }
  const numbers = value.map((n) => Number(n));
  switch (type) {
    case MetadataType.VEC2:
      return Cartesian2.unpack(numbers, 0, new Cartesian2());
    case MetadataType.VEC3:
      return Cartesian3.unpack(numbers, 0, new Cartesian3());
    case MetadataType.VEC4:
      return Cartesian4.unpack(numbers, 0, new Cartesian4());
    case MetadataType.MAT2:
      return Matrix2.unpack(numbers, 0, new Matrix2());
    case MetadataType.MAT3:
      return Matrix3.unpack(numbers, 0, new Matrix3());
    case MetadataType.MAT4:
      return Matrix4.unpack(numbers, 0, new Matrix4());
  }
  // Should never happen:
  throw new RuntimeError(`Invalid metadata object type: ${type}`);
};
 
/**
 * Converts the given type into a raw value or array representation.
 *
 * For `VECn/MATn` types, the given value is converted into an array.
 * For other types, the value is returned directly
 *
 * @param {string} type The `ClassProperty` type
 * @param {undefined|number|bigint|string|boolean|Cartesian2|Cartesian3|Cartesian4|Matrix2|Matrix3|Matrix4} value The input value
 * @returns {undefined|number|bigint|string|boolean|number[]} The array representation
 * @throws RuntimeError If the type is not a valid `MetadataType`
 */
MetadataPicking.convertFromObjectType = function (type, value) {
  Iif (!defined(value)) {
    return value;
  }
  if (
    type === MetadataType.SCALAR ||
    type === MetadataType.STRING ||
    type === MetadataType.BOOLEAN ||
    type === MetadataType.ENUM
  ) {
    return value;
  }
  switch (type) {
    case MetadataType.VEC2:
      return Cartesian2.pack(value, Array(2));
    case MetadataType.VEC3:
      return Cartesian3.pack(value, Array(3));
    case MetadataType.VEC4:
      return Cartesian4.pack(value, Array(4));
    case MetadataType.MAT2:
      return Matrix2.pack(value, Array(4));
    case MetadataType.MAT3:
      return Matrix3.pack(value, Array(9));
    case MetadataType.MAT4:
      return Matrix4.pack(value, Array(16));
  }
  // Should never happen:
  throw new RuntimeError(`Invalid metadata object type: ${type}`);
};
 
/**
 * Decode the given raw values into a metadata property value.
 *
 * This applies the value transform (offset/scale) to the result
 * of `decodeRawMetadataValues`, and converts this from array-based
 * types into object types like `CartesianN`.
 *
 * @param {MetadataClassProperty} classProperty The `MetadataClassProperty`
 * @param {object} metadataProperty The
 * `PropertyTextureProperty` or `PropertyAttributeProperty`
 * @param {Uint8Array} rawPixelValues The raw values
 * @returns {MetadataValue} The value
 * @throws RuntimeError If the class property has an invalid type
 * or component type
 * @throws RangeError If the given pixel values do not have sufficient
 * size to contain the expected value type
 *
 * @private
 */
MetadataPicking.decodeMetadataValues = function (
  classProperty,
  metadataProperty,
  rawPixelValues,
) {
  let arrayBasedResult = MetadataPicking.decodeRawMetadataValues(
    classProperty,
    rawPixelValues,
  );
 
  if (metadataProperty.hasValueTransform) {
    // In the MetadataClassProperty, these offset/scale are always in
    // their array-based form (e.g. a number[3] for `VEC3`). But for
    // the PropertyTextureProperty and PropertyAttributeProperty,
    // the type of the offset/scale is defined to be
    // number|Cartesian2|Cartesian3|Cartesian4|Matrix2|Matrix3|Matrix4
    // So these types are converted into their array-based form here, before
    // applying them with `MetadataClassProperty.valueTransformInPlace`
 
    const offset = MetadataPicking.convertFromObjectType(
      classProperty.type,
      metadataProperty.offset,
    );
    const scale = MetadataPicking.convertFromObjectType(
      classProperty.type,
      metadataProperty.scale,
    );
    arrayBasedResult = MetadataClassProperty.valueTransformInPlace(
      arrayBasedResult,
      offset,
      scale,
      MetadataComponentType.applyValueTransform,
    );
  }
 
  Iif (classProperty.isArray) {
    const arrayLength = classProperty.arrayLength;
    const result = Array(arrayLength);
    for (let i = 0; i < arrayLength; i++) {
      const arrayBasedValue = arrayBasedResult[i];
      const objectBasedValue = MetadataPicking.convertToObjectType(
        classProperty.type,
        arrayBasedValue,
      );
      result[i] = objectBasedValue;
    }
    return result;
  }
  const objectResult = MetadataPicking.convertToObjectType(
    classProperty.type,
    arrayBasedResult,
  );
  return objectResult;
};
 
export default Object.freeze(MetadataPicking);