mirror of https://github.com/CesiumGS/cesium.git
490 lines
17 KiB
JavaScript
490 lines
17 KiB
JavaScript
import defined from "../../Core/defined.js";
|
|
import Cartesian2 from "../../Core/Cartesian2.js";
|
|
import Cartesian3 from "../../Core/Cartesian3.js";
|
|
import Matrix4 from "../../Core/Matrix4.js";
|
|
import Rectangle from "../../Core/Rectangle.js";
|
|
import Cartographic from "../../Core/Cartographic.js";
|
|
import BoundingRectangle from "../../Core/BoundingRectangle.js";
|
|
import ComponentDatatype from "../../Core/ComponentDatatype.js";
|
|
import Check from "../../Core/Check.js";
|
|
|
|
import AttributeType from "../AttributeType.js";
|
|
import ModelReader from "./ModelReader.js";
|
|
import VertexAttributeSemantic from "../VertexAttributeSemantic.js";
|
|
|
|
/**
|
|
* A class for computing the texture coordinates of imagery that is
|
|
* supposed to be mapped on a <code>ModelComponents.Primitive</code>.
|
|
*
|
|
* @private
|
|
*/
|
|
class ModelImageryMapping {
|
|
/**
|
|
* Creates a typed array that contains texture coordinates for
|
|
* the given <code>MappedPositions</code>, using the given
|
|
* projection.
|
|
*
|
|
* This will be a typed array that contains the texture coordinates
|
|
* that result from projecting the given positions with the given
|
|
* projection, and normalizing them to their bounding rectangle.
|
|
*
|
|
* @param {MappedPositions} mappedPositions The positions
|
|
* @param {MapProjection} projection The projection that should be used
|
|
* @returns {TypedArray} The result
|
|
*/
|
|
static createTextureCoordinatesForMappedPositions(
|
|
mappedPositions,
|
|
projection,
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("mappedPositions", mappedPositions);
|
|
Check.defined("projection", projection);
|
|
//>>includeEnd('debug');
|
|
|
|
const cartographicPositions = mappedPositions.cartographicPositions;
|
|
const cartographicBoundingRectangle =
|
|
mappedPositions.cartographicBoundingRectangle;
|
|
const numPositions = mappedPositions.numPositions;
|
|
return ModelImageryMapping._createTextureCoordinates(
|
|
cartographicPositions,
|
|
numPositions,
|
|
cartographicBoundingRectangle,
|
|
projection,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates a typed array that contains texture coordinates for
|
|
* a primitive with the given positions, using the given
|
|
* projection.
|
|
*
|
|
* This will be a typed array of size <code>numPositions*2</code>
|
|
* that contains the texture coordinates that result from
|
|
* projecting the given positions with the given projection,
|
|
* and normalizing them to the given bounding rectangle.
|
|
*
|
|
* @param {Iterable<Cartographic>} cartographicPositions The
|
|
* cartographic positions
|
|
* @param {number} numPositions The number of positions (vertices)
|
|
* @param {Rectangle} cartographicBoundingRectangle The bounding
|
|
* rectangle of the cartographic positions
|
|
* @param {MapProjection} projection The projection that should be used
|
|
* @returns {TypedArray} The result
|
|
* @private
|
|
*/
|
|
static _createTextureCoordinates(
|
|
cartographicPositions,
|
|
numPositions,
|
|
cartographicBoundingRectangle,
|
|
projection,
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("cartographicPositions", cartographicPositions);
|
|
Check.typeOf.number.greaterThanOrEquals("numPositions", numPositions, 0);
|
|
Check.defined(
|
|
"cartographicBoundingRectangle",
|
|
cartographicBoundingRectangle,
|
|
);
|
|
Check.defined("projection", projection);
|
|
//>>includeEnd('debug');
|
|
|
|
// Convert the bounding `Rectangle`(!) of the cartographic positions
|
|
// into a `BoundingRectangle`(!) using the given projection
|
|
const boundingRectangle = new BoundingRectangle();
|
|
BoundingRectangle.fromRectangle(
|
|
cartographicBoundingRectangle,
|
|
projection,
|
|
boundingRectangle,
|
|
);
|
|
|
|
// Compute the projected positions, using the given projection
|
|
const projectedPositions = ModelImageryMapping.createProjectedPositions(
|
|
cartographicPositions,
|
|
projection,
|
|
);
|
|
|
|
// Relativize the projected positions into the bounding rectangle
|
|
// to obtain texture coordinates
|
|
const texCoords = ModelImageryMapping.computeTexCoords(
|
|
projectedPositions,
|
|
boundingRectangle,
|
|
);
|
|
|
|
// Convert the texture coordinates into a typed array
|
|
const texCoordsTypedArray =
|
|
ModelImageryMapping.createTypedArrayFromCartesians2(
|
|
numPositions,
|
|
texCoords,
|
|
);
|
|
|
|
return texCoordsTypedArray;
|
|
}
|
|
|
|
/**
|
|
* Creates the `ModelComponents.Attribute` for the texture coordinates
|
|
* for a primitive
|
|
*
|
|
* This will create an attribute with
|
|
* - semantic: VertexAttributeSemantic.TEXCOORD
|
|
* - type: AttributeType.VEC2
|
|
* - count: mappedPositions.numPositions
|
|
* that contains the texture coordinates for the given vertex positions,
|
|
* after they are projected using the given projection, normalized to
|
|
* their bounding rectangle.
|
|
*
|
|
* @param {Iterable<Cartographic>} cartographicPositions The
|
|
* cartographic positions
|
|
* @param {number} numPositions The number of positions (vertices)
|
|
* @param {Rectangle} cartographicBoundingRectangle The bounding
|
|
* rectangle of the cartographic positions
|
|
* @param {MapProjection} projection The projection that should be used
|
|
* @returns {ModelComponents.Attribute} The new attribute
|
|
*/
|
|
static createTextureCoordinatesAttributeForMappedPositions(
|
|
mappedPositions,
|
|
projection,
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("mappedPositions", mappedPositions);
|
|
Check.defined("projection", projection);
|
|
//>>includeEnd('debug');
|
|
|
|
// Create the typed array that contains the texture coordinates
|
|
const texCoordsTypedArray =
|
|
ModelImageryMapping.createTextureCoordinatesForMappedPositions(
|
|
mappedPositions,
|
|
projection,
|
|
);
|
|
|
|
// Create an attribute from the texture coordinates typed array
|
|
const texCoordAttribute =
|
|
ModelImageryMapping.createTexCoordAttribute(texCoordsTypedArray);
|
|
|
|
return texCoordAttribute;
|
|
}
|
|
|
|
/**
|
|
* Create an iterable that provides the cartographic positions
|
|
* of the given POSITION attribute, based on the given ellipsoid
|
|
*
|
|
* @param {ModelComponents.Attribute} primitivePositionAttribute
|
|
* The "POSITION" attribute of the primitive.
|
|
* @param {Matrix4} primitivePositionTransform The full transform of the primitive
|
|
* @param {Elliposid} ellipsoid The ellipsoid that should be used
|
|
* @returns {Iterable<Cartographic>} The iterable over `Cartographic` objects
|
|
*/
|
|
static createCartographicPositions(
|
|
primitivePositionAttribute,
|
|
primitivePositionTransform,
|
|
ellipsoid,
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("primitivePositionAttribute", primitivePositionAttribute);
|
|
Check.defined("primitivePositionTransform", primitivePositionTransform);
|
|
Check.defined("ellipsoid", ellipsoid);
|
|
//>>includeEnd('debug');
|
|
|
|
// Extract the positions as a typed array
|
|
const typedArray = ModelReader.readAttributeAsTypedArray(
|
|
primitivePositionAttribute,
|
|
);
|
|
|
|
// Create an iterable over the positions
|
|
const type = primitivePositionAttribute.type;
|
|
const numComponents = AttributeType.getNumberOfComponents(type);
|
|
const positions =
|
|
ModelImageryMapping.createIterableCartesian3FromTypedArray(
|
|
typedArray,
|
|
numComponents,
|
|
);
|
|
|
|
// Compute the positions after they are transformed with the given matrix
|
|
const transformedPositions = ModelImageryMapping.transformCartesians3(
|
|
positions,
|
|
primitivePositionTransform,
|
|
);
|
|
|
|
// Compute the cartographic positions for the given ellipsoid
|
|
const cartographicPositions = ModelImageryMapping.transformToCartographic(
|
|
transformedPositions,
|
|
ellipsoid,
|
|
);
|
|
return cartographicPositions;
|
|
}
|
|
|
|
/**
|
|
* Creates an iterable over `Cartesian3` objects from the given
|
|
* typed array.
|
|
*
|
|
* The resulting iterable will always return the same `Cartesian3`
|
|
* object. Clients should not store and modify this object.
|
|
*
|
|
* @param {TypedArray} typedArray The typed array
|
|
* @param {number} stride The stride between to consecutive
|
|
* `Cartesian3` elements in the given array. Must be at least 3.
|
|
* @returns {Iterable<Cartesian3>} The iterable
|
|
*/
|
|
static createIterableCartesian3FromTypedArray(typedArray, stride) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("typedArray", typedArray);
|
|
Check.typeOf.number.greaterThanOrEquals("stride", stride, 3);
|
|
//>>includeEnd('debug');
|
|
|
|
const cartesian = new Cartesian3();
|
|
const numElements = typedArray.length / stride;
|
|
const result = {
|
|
[Symbol.iterator]: function* () {
|
|
for (let i = 0; i < numElements; i++) {
|
|
cartesian.x = typedArray[i * stride + 0];
|
|
cartesian.y = typedArray[i * stride + 1];
|
|
cartesian.z = typedArray[i * stride + 2];
|
|
yield cartesian;
|
|
}
|
|
},
|
|
};
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates a new iterable that applies the given mapper to the given iterable.
|
|
*
|
|
* @param {Iterable} iterable The input iterable
|
|
* @param {Function} mapper The mapper
|
|
* @returns {Iterable} The mapped iterable
|
|
*/
|
|
static map(iterable, mapper) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("iterable", iterable);
|
|
Check.defined("mapper", mapper);
|
|
//>>includeEnd('debug');
|
|
|
|
const result = {
|
|
[Symbol.iterator]: function* () {
|
|
for (const element of iterable) {
|
|
yield mapper(element);
|
|
}
|
|
},
|
|
};
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Computes the bounding rectangle of the given cartographic positions,
|
|
* stores it in the given result, and returns it.
|
|
*
|
|
* If the given result is `undefined`, a new rectangle will be created
|
|
* and returned.
|
|
*
|
|
* @param {Iterable<Cartographic>} cartographicPositions The cartographics
|
|
* @param {Rectangle} [result] The result
|
|
* @returns {Rectangle} The result
|
|
*/
|
|
static computeCartographicBoundingRectangle(cartographicPositions, result) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("cartographicPositions", cartographicPositions);
|
|
//>>includeEnd('debug');
|
|
|
|
if (!defined(result)) {
|
|
result = new Rectangle();
|
|
}
|
|
// One could store these directly in the result, but that would
|
|
// violate the constraint of the PI-related ranges..
|
|
let north = Number.NEGATIVE_INFINITY;
|
|
let south = Number.POSITIVE_INFINITY;
|
|
let east = Number.NEGATIVE_INFINITY;
|
|
let west = Number.POSITIVE_INFINITY;
|
|
for (const cartographicPosition of cartographicPositions) {
|
|
north = Math.max(north, cartographicPosition.latitude);
|
|
south = Math.min(south, cartographicPosition.latitude);
|
|
east = Math.max(east, cartographicPosition.longitude);
|
|
west = Math.min(west, cartographicPosition.longitude);
|
|
}
|
|
result.north = north;
|
|
result.south = south;
|
|
result.east = east;
|
|
result.west = west;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Creates a new iterable that provides `Cartesian3` objects that
|
|
* are created by transforming the `Cartesian3` objects of the
|
|
* given iterable with the given matrix.
|
|
*
|
|
* The resulting iterable will always return the same `Cartesian3`
|
|
* object. Clients should not store and modify this object.
|
|
*
|
|
* @param {Iterable<Cartesian3>} positions The positions
|
|
* @param {Matrix4} matrix The matrix
|
|
* @returns {Iterable<Cartesian3>} The transformed cartesians
|
|
*/
|
|
static transformCartesians3(positions, matrix) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("positions", positions);
|
|
Check.defined("matrix", matrix);
|
|
//>>includeEnd('debug');
|
|
|
|
const transformedPosition = new Cartesian3();
|
|
const transformedPositions = ModelImageryMapping.map(positions, (p) => {
|
|
Matrix4.multiplyByPoint(matrix, p, transformedPosition);
|
|
return transformedPosition;
|
|
});
|
|
return transformedPositions;
|
|
}
|
|
|
|
/**
|
|
* Creates a new iterable that provides `Cartographic` objects that
|
|
* are created by converting the given `Cartesian3` objects to
|
|
* cartographics, based on the given ellipsoid.
|
|
*
|
|
* The resulting iterable will always return the same `Cartographic`
|
|
* object. Clients should not store and modify this object.
|
|
*
|
|
* @param {Iterable<Cartesian3>} positions The positions
|
|
* @param {Ellipsoid} ellipsoid The ellipsoid
|
|
* @returns {Iterable<Cartographic>} The cartographic positions
|
|
*/
|
|
static transformToCartographic(positions, ellipsoid) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("positions", positions);
|
|
Check.defined("ellipsoid", ellipsoid);
|
|
//>>includeEnd('debug');
|
|
|
|
const cartographicPosition = new Cartographic();
|
|
const cartographicPositions = ModelImageryMapping.map(positions, (p) => {
|
|
// Note: This will not yield valid results for p=(0,0,0).
|
|
// But there is no sensible cartographic position for
|
|
// that, so simply accept the unspecified output here.
|
|
ellipsoid.cartesianToCartographic(p, cartographicPosition);
|
|
return cartographicPosition;
|
|
});
|
|
return cartographicPositions;
|
|
}
|
|
|
|
/**
|
|
* Creates an iterable over the results of applying the given projection
|
|
* to the given cartographic positions.
|
|
*
|
|
* The resulting iterable will always return the same `Cartesian3`
|
|
* object. Clients should not store and modify this object.
|
|
*
|
|
* @param {Iterable<Cartographic>} cartographicPositions The cartographic
|
|
* positions
|
|
* @param {MapProjection} projection The projection to use
|
|
* @returns {Iterable<Cartesian3>} The projected positions
|
|
*/
|
|
static createProjectedPositions(cartographicPositions, projection) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("cartographicPositions", cartographicPositions);
|
|
Check.defined("projection", projection);
|
|
//>>includeEnd('debug');
|
|
|
|
const projectedPosition = new Cartesian3();
|
|
const projectedPositions = ModelImageryMapping.map(
|
|
cartographicPositions,
|
|
(c) => {
|
|
projection.project(c, projectedPosition);
|
|
return projectedPosition;
|
|
},
|
|
);
|
|
return projectedPositions;
|
|
}
|
|
|
|
/**
|
|
* Computes the texture coordinates for the given positions, relative
|
|
* to the given bounding rectangle.
|
|
*
|
|
* This will make the x/y coordinates of the given cartesians relative
|
|
* to the given bounding rectangle and clamp them to [0,0]-[1,1].
|
|
*
|
|
* NOTE: This could be broken down into
|
|
* 1. mapping to 2D
|
|
* 2. relativizing for the bounding recangle
|
|
* 3. clamping to [0,0]-[1,1]
|
|
*
|
|
* @param {Iterable<Cartesian3>} positions The positions
|
|
* @param {BoundingRectangle} boundingRectangle The rectangle
|
|
* @returns {Iterable<Cartesian2>} The texture coordinates
|
|
*/
|
|
static computeTexCoords(positions, boundingRectangle) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("positions", positions);
|
|
Check.defined("boundingRectangle", boundingRectangle);
|
|
//>>includeEnd('debug');
|
|
|
|
const texCoord = new Cartesian2();
|
|
const invSizeX = 1.0 / boundingRectangle.width;
|
|
const invSizeY = 1.0 / boundingRectangle.height;
|
|
const texCoords = ModelImageryMapping.map(positions, (p) => {
|
|
const uRaw = (p.x - boundingRectangle.x) * invSizeX;
|
|
const vRaw = (p.y - boundingRectangle.y) * invSizeY;
|
|
const u = Math.min(Math.max(uRaw, 0.0), 1.0);
|
|
const v = Math.min(Math.max(vRaw, 0.0), 1.0);
|
|
texCoord.x = u;
|
|
texCoord.y = v;
|
|
return texCoord;
|
|
});
|
|
return texCoords;
|
|
}
|
|
|
|
/**
|
|
* Creates a new typed array from the given `Cartesian2` objects.
|
|
*
|
|
* @param {number} numElements The number of elements
|
|
* @param {Iterable<Cartesian2>} elements The elements
|
|
* @returns {TypedArray} The typed array
|
|
*/
|
|
static createTypedArrayFromCartesians2(numElements, elements) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("numElements", numElements, 0);
|
|
Check.defined("elements", elements);
|
|
//>>includeEnd('debug');
|
|
|
|
const typedArray = new Float32Array(numElements * 2);
|
|
let index = 0;
|
|
for (const element of elements) {
|
|
typedArray[index * 2 + 0] = element.x;
|
|
typedArray[index * 2 + 1] = element.y;
|
|
index++;
|
|
}
|
|
return typedArray;
|
|
}
|
|
|
|
/**
|
|
* Create a new texture coordinates attribute from the given data.
|
|
*
|
|
* This will create an attribute with
|
|
* - semantic: VertexAttributeSemantic.TEXCOORD
|
|
* - type: AttributeType.VEC2
|
|
* - count: texCoordsTypedArray.length / 2
|
|
* that contains the data from the given typed array.
|
|
*
|
|
* @param {TypedArray} texCoordsTypedArray The typed array
|
|
* @returns {ModelComponents.Attribute} The attribute
|
|
*/
|
|
static createTexCoordAttribute(texCoordsTypedArray) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("texCoordsTypedArray", texCoordsTypedArray);
|
|
//>>includeEnd('debug');
|
|
|
|
const texCoordAttribute = {
|
|
name: "Imagery Texture Coordinates",
|
|
semantic: VertexAttributeSemantic.TEXCOORD,
|
|
setIndex: 0,
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
type: AttributeType.VEC2,
|
|
normalized: false,
|
|
count: texCoordsTypedArray.length / 2,
|
|
min: undefined,
|
|
max: undefined,
|
|
constant: new Cartesian2(0, 0),
|
|
quantization: undefined,
|
|
typedArray: texCoordsTypedArray,
|
|
byteOffset: 0,
|
|
byteStride: undefined,
|
|
};
|
|
return texCoordAttribute;
|
|
}
|
|
}
|
|
export default ModelImageryMapping;
|