@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,175 lines (1,076 loc) • 37 kB
JavaScript
import defined from "../Core/defined.js";
import BoundingSphere from "../Core/BoundingSphere.js";
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartographic from "../Core/Cartographic.js";
import Check from "../Core/Check.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import CesiumMath from "../Core/Math.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
import Rectangle from "../Core/Rectangle.js";
import Transforms from "../Core/Transforms.js";
/**
* An ellipsoid {@link VoxelShape}.
*
* @alias VoxelEllipsoidShape
* @constructor
*
* @see VoxelShape
* @see VoxelBoxShape
* @see VoxelCylinderShape
* @see VoxelShapeType
*
* @private
*/
function VoxelEllipsoidShape() {
this._orientedBoundingBox = new OrientedBoundingBox();
this._boundingSphere = new BoundingSphere();
this._boundTransform = new Matrix4();
this._shapeTransform = new Matrix4();
/**
* @type {Rectangle}
* @private
*/
this._rectangle = new Rectangle();
/**
* @type {number}
* @private
*/
this._minimumHeight = VoxelEllipsoidShape.DefaultMinBounds.z;
/**
* @type {number}
* @private
*/
this._maximumHeight = VoxelEllipsoidShape.DefaultMaxBounds.z;
/**
* @type {Ellipsoid}
* @private
*/
this._ellipsoid = new Ellipsoid();
/**
* @type {Cartesian3}
* @private
*/
this._translation = new Cartesian3();
/**
* @type {Matrix3}
* @private
*/
this._rotation = new Matrix3();
this._shaderUniforms = {
cameraPositionCartographic: new Cartesian3(),
ellipsoidEcToEastNorthUp: new Matrix3(),
ellipsoidRadii: new Cartesian3(),
eccentricitySquared: 0.0,
evoluteScale: new Cartesian2(),
ellipsoidCurvatureAtLatitude: new Cartesian2(),
ellipsoidInverseRadiiSquared: new Cartesian3(),
ellipsoidRenderLongitudeMinMax: new Cartesian2(),
ellipsoidShapeUvLongitudeRangeOrigin: 0.0,
ellipsoidShapeUvLongitudeMinMaxMid: new Cartesian3(),
ellipsoidLocalToShapeUvLongitude: new Cartesian2(),
ellipsoidLocalToShapeUvLatitude: new Cartesian2(),
ellipsoidRenderLatitudeSinMinMax: new Cartesian2(),
ellipsoidInverseHeightDifference: 0.0,
clipMinMaxHeight: new Cartesian2(),
};
this._shaderDefines = {
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY: undefined,
ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE: undefined,
ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF: undefined,
ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF: undefined,
ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE: undefined,
ELLIPSOID_INTERSECTION_INDEX_LONGITUDE: undefined,
ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX: undefined,
ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN: undefined,
ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX: undefined,
ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN: undefined,
};
this._shaderMaximumIntersectionsLength = 0; // not known until update
}
Object.defineProperties(VoxelEllipsoidShape.prototype, {
/**
* An oriented bounding box containing the bounded shape.
*
* @memberof VoxelEllipsoidShape.prototype
* @type {OrientedBoundingBox}
* @readonly
* @private
*/
orientedBoundingBox: {
get: function () {
return this._orientedBoundingBox;
},
},
/**
* A bounding sphere containing the bounded shape.
*
* @memberof VoxelEllipsoidShape.prototype
* @type {BoundingSphere}
* @readonly
* @private
*/
boundingSphere: {
get: function () {
return this._boundingSphere;
},
},
/**
* A transformation matrix containing the bounded shape.
*
* @memberof VoxelEllipsoidShape.prototype
* @type {Matrix4}
* @readonly
* @private
*/
boundTransform: {
get: function () {
return this._boundTransform;
},
},
/**
* A transformation matrix containing the shape, ignoring the bounds.
*
* @memberof VoxelEllipsoidShape.prototype
* @type {Matrix4}
* @readonly
* @private
*/
shapeTransform: {
get: function () {
return this._shapeTransform;
},
},
/**
* @memberof VoxelEllipsoidShape.prototype
* @type {Object<string, any>}
* @readonly
* @private
*/
shaderUniforms: {
get: function () {
return this._shaderUniforms;
},
},
/**
* @memberof VoxelEllipsoidShape.prototype
* @type {Object<string, any>}
* @readonly
* @private
*/
shaderDefines: {
get: function () {
return this._shaderDefines;
},
},
/**
* The maximum number of intersections against the shape for any ray direction.
* @memberof VoxelEllipsoidShape.prototype
* @type {number}
* @readonly
* @private
*/
shaderMaximumIntersectionsLength: {
get: function () {
return this._shaderMaximumIntersectionsLength;
},
},
});
const scratchActualMinBounds = new Cartesian3();
const scratchShapeMinBounds = new Cartesian3();
const scratchShapeMaxBounds = new Cartesian3();
const scratchClipMinBounds = new Cartesian3();
const scratchClipMaxBounds = new Cartesian3();
const scratchRenderMinBounds = new Cartesian3();
const scratchRenderMaxBounds = new Cartesian3();
const scratchScale = new Cartesian3();
const scratchShapeOuterExtent = new Cartesian3();
const scratchRenderOuterExtent = new Cartesian3();
const scratchRenderRectangle = new Rectangle();
/**
* Update the shape's state.
* @private
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
* @param {Cartesian3} [clipMinBounds=VoxelEllipsoidShape.DefaultMinBounds] The minimum clip bounds.
* @param {Cartesian3} [clipMaxBounds=VoxelEllipsoidShape.DefaultMaxBounds] The maximum clip bounds.
* @returns {boolean} Whether the shape is visible.
*/
VoxelEllipsoidShape.prototype.update = function (
modelMatrix,
minBounds,
maxBounds,
clipMinBounds,
clipMaxBounds,
) {
const { DefaultMinBounds, DefaultMaxBounds } = VoxelEllipsoidShape;
clipMinBounds = clipMinBounds ?? DefaultMinBounds;
clipMaxBounds = clipMaxBounds ?? DefaultMaxBounds;
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("modelMatrix", modelMatrix);
Check.typeOf.object("minBounds", minBounds);
Check.typeOf.object("maxBounds", maxBounds);
//>>includeEnd('debug');
const epsilonZeroScale = CesiumMath.EPSILON10;
const epsilonLongitudeDiscontinuity = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
const epsilonLongitude = CesiumMath.EPSILON10;
const epsilonLatitude = CesiumMath.EPSILON10;
const epsilonLatitudeFlat = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
// Don't let the height go below the center of the ellipsoid.
const radii = Matrix4.getScale(modelMatrix, scratchScale);
const actualMinBounds = Cartesian3.clone(
DefaultMinBounds,
scratchActualMinBounds,
);
actualMinBounds.z = -Cartesian3.minimumComponent(radii);
const shapeMinBounds = Cartesian3.clamp(
minBounds,
actualMinBounds,
DefaultMaxBounds,
scratchShapeMinBounds,
);
const shapeMaxBounds = Cartesian3.clamp(
maxBounds,
actualMinBounds,
DefaultMaxBounds,
scratchShapeMaxBounds,
);
const clampedClipMinBounds = Cartesian3.clamp(
clipMinBounds,
actualMinBounds,
DefaultMaxBounds,
scratchClipMinBounds,
);
const clampedClipMaxBounds = Cartesian3.clamp(
clipMaxBounds,
actualMinBounds,
DefaultMaxBounds,
scratchClipMaxBounds,
);
const renderMinBounds = Cartesian3.maximumByComponent(
shapeMinBounds,
clampedClipMinBounds,
scratchRenderMinBounds,
);
const renderMaxBounds = Cartesian3.minimumByComponent(
shapeMaxBounds,
clampedClipMaxBounds,
scratchRenderMaxBounds,
);
// Compute the farthest a point can be from the center of the ellipsoid.
const shapeOuterExtent = Cartesian3.add(
radii,
Cartesian3.fromElements(
shapeMaxBounds.z,
shapeMaxBounds.z,
shapeMaxBounds.z,
scratchShapeOuterExtent,
),
scratchShapeOuterExtent,
);
const renderOuterExtent = Cartesian3.add(
radii,
Cartesian3.fromElements(
renderMaxBounds.z,
renderMaxBounds.z,
renderMaxBounds.z,
scratchRenderOuterExtent,
),
scratchRenderOuterExtent,
);
// Exit early if the shape is not visible.
// Note that minLongitude may be greater than maxLongitude when crossing the 180th meridian.
if (
renderMinBounds.y > renderMaxBounds.y ||
renderMinBounds.y === DefaultMaxBounds.y ||
renderMaxBounds.y === DefaultMinBounds.y ||
renderMinBounds.z > renderMaxBounds.z ||
CesiumMath.equalsEpsilon(
renderOuterExtent,
Cartesian3.ZERO,
undefined,
epsilonZeroScale,
)
) {
return false;
}
this._rectangle = Rectangle.fromRadians(
shapeMinBounds.x,
shapeMinBounds.y,
shapeMaxBounds.x,
shapeMaxBounds.y,
);
this._translation = Matrix4.getTranslation(modelMatrix, this._translation);
this._rotation = Matrix4.getRotation(modelMatrix, this._rotation);
this._ellipsoid = Ellipsoid.fromCartesian3(radii, this._ellipsoid);
this._minimumHeight = shapeMinBounds.z;
this._maximumHeight = shapeMaxBounds.z;
const renderRectangle = Rectangle.fromRadians(
renderMinBounds.x,
renderMinBounds.y,
renderMaxBounds.x,
renderMaxBounds.y,
scratchRenderRectangle,
);
this._orientedBoundingBox = getEllipsoidChunkObb(
renderRectangle,
renderMinBounds.z,
renderMaxBounds.z,
this._ellipsoid,
this._translation,
this._rotation,
this._orientedBoundingBox,
);
this._shapeTransform = Matrix4.fromRotationTranslation(
this._rotation,
this._translation,
this._shapeTransform,
);
this._boundTransform = Matrix4.fromRotationTranslation(
this._orientedBoundingBox.halfAxes,
this._orientedBoundingBox.center,
this._boundTransform,
);
this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(
this._orientedBoundingBox,
this._boundingSphere,
);
// Longitude
const defaultLongitudeRange = DefaultMaxBounds.x - DefaultMinBounds.x;
const defaultLongitudeRangeHalf = 0.5 * defaultLongitudeRange;
const renderIsLongitudeReversed = renderMaxBounds.x < renderMinBounds.x;
const renderLongitudeRange =
renderMaxBounds.x -
renderMinBounds.x +
renderIsLongitudeReversed * defaultLongitudeRange;
const renderIsLongitudeRangeZero = renderLongitudeRange <= epsilonLongitude;
const renderIsLongitudeRangeUnderHalf =
renderLongitudeRange >= defaultLongitudeRangeHalf - epsilonLongitude &&
renderLongitudeRange < defaultLongitudeRange - epsilonLongitude;
const renderIsLongitudeRangeOverHalf =
renderLongitudeRange > epsilonLongitude &&
renderLongitudeRange < defaultLongitudeRangeHalf - epsilonLongitude;
const renderHasLongitude =
renderIsLongitudeRangeZero ||
renderIsLongitudeRangeUnderHalf ||
renderIsLongitudeRangeOverHalf;
const shapeIsLongitudeReversed = shapeMaxBounds.x < shapeMinBounds.x;
const shapeLongitudeRange =
shapeMaxBounds.x -
shapeMinBounds.x +
shapeIsLongitudeReversed * defaultLongitudeRange;
const shapeIsLongitudeRangeUnderHalf =
shapeLongitudeRange > defaultLongitudeRangeHalf + epsilonLongitude &&
shapeLongitudeRange < defaultLongitudeRange - epsilonLongitude;
const shapeIsLongitudeRangeHalf =
shapeLongitudeRange >= defaultLongitudeRangeHalf - epsilonLongitude &&
shapeLongitudeRange <= defaultLongitudeRangeHalf + epsilonLongitude;
const shapeIsLongitudeRangeOverHalf =
shapeLongitudeRange < defaultLongitudeRangeHalf - epsilonLongitude;
const shapeHasLongitude =
shapeIsLongitudeRangeUnderHalf ||
shapeIsLongitudeRangeHalf ||
shapeIsLongitudeRangeOverHalf;
// Latitude
const renderIsLatitudeMaxUnderHalf = renderMaxBounds.y < -epsilonLatitudeFlat;
const renderIsLatitudeMaxHalf =
renderMaxBounds.y >= -epsilonLatitudeFlat &&
renderMaxBounds.y <= +epsilonLatitudeFlat;
const renderIsLatitudeMaxOverHalf =
renderMaxBounds.y > +epsilonLatitudeFlat &&
renderMaxBounds.y < DefaultMaxBounds.y - epsilonLatitude;
const renderHasLatitudeMax =
renderIsLatitudeMaxUnderHalf ||
renderIsLatitudeMaxHalf ||
renderIsLatitudeMaxOverHalf;
const renderIsLatitudeMinUnderHalf =
renderMinBounds.y > DefaultMinBounds.y + epsilonLatitude &&
renderMinBounds.y < -epsilonLatitudeFlat;
const renderIsLatitudeMinHalf =
renderMinBounds.y >= -epsilonLatitudeFlat &&
renderMinBounds.y <= +epsilonLatitudeFlat;
const renderIsLatitudeMinOverHalf = renderMinBounds.y > +epsilonLatitudeFlat;
const renderHasLatitudeMin =
renderIsLatitudeMinUnderHalf ||
renderIsLatitudeMinHalf ||
renderIsLatitudeMinOverHalf;
const renderHasLatitude = renderHasLatitudeMax || renderHasLatitudeMin;
const shapeLatitudeRange = shapeMaxBounds.y - shapeMinBounds.y;
const shapeIsLatitudeMaxUnderHalf = shapeMaxBounds.y < -epsilonLatitudeFlat;
const shapeIsLatitudeMaxHalf =
shapeMaxBounds.y >= -epsilonLatitudeFlat &&
shapeMaxBounds.y <= +epsilonLatitudeFlat;
const shapeIsLatitudeMaxOverHalf =
shapeMaxBounds.y > +epsilonLatitudeFlat &&
shapeMaxBounds.y < DefaultMaxBounds.y - epsilonLatitude;
const shapeHasLatitudeMax =
shapeIsLatitudeMaxUnderHalf ||
shapeIsLatitudeMaxHalf ||
shapeIsLatitudeMaxOverHalf;
const shapeIsLatitudeMinUnderHalf =
shapeMinBounds.y > DefaultMinBounds.y + epsilonLatitude &&
shapeMinBounds.y < -epsilonLatitudeFlat;
const shapeIsLatitudeMinHalf =
shapeMinBounds.y >= -epsilonLatitudeFlat &&
shapeMinBounds.y <= +epsilonLatitudeFlat;
const shapeIsLatitudeMinOverHalf = shapeMinBounds.y > +epsilonLatitudeFlat;
const shapeHasLatitudeMin =
shapeIsLatitudeMinUnderHalf ||
shapeIsLatitudeMinHalf ||
shapeIsLatitudeMinOverHalf;
const shapeHasLatitude = shapeHasLatitudeMax || shapeHasLatitudeMin;
const shaderUniforms = this._shaderUniforms;
const shaderDefines = this._shaderDefines;
// To keep things simple, clear the defines every time
for (const key in shaderDefines) {
if (shaderDefines.hasOwnProperty(key)) {
shaderDefines[key] = undefined;
}
}
shaderUniforms.ellipsoidRadii = Cartesian3.clone(
shapeOuterExtent,
shaderUniforms.ellipsoidRadii,
);
const { x: radiiX, z: radiiZ } = shaderUniforms.ellipsoidRadii;
const axisRatio = radiiZ / radiiX;
shaderUniforms.eccentricitySquared = 1.0 - axisRatio * axisRatio;
shaderUniforms.evoluteScale = Cartesian2.fromElements(
(radiiX * radiiX - radiiZ * radiiZ) / radiiX,
(radiiZ * radiiZ - radiiX * radiiX) / radiiZ,
shaderUniforms.evoluteScale,
);
// Used to compute geodetic surface normal.
shaderUniforms.ellipsoidInverseRadiiSquared = Cartesian3.divideComponents(
Cartesian3.ONE,
Cartesian3.multiplyComponents(
shaderUniforms.ellipsoidRadii,
shaderUniforms.ellipsoidRadii,
shaderUniforms.ellipsoidInverseRadiiSquared,
),
shaderUniforms.ellipsoidInverseRadiiSquared,
);
// Keep track of how many intersections there are going to be.
let intersectionCount = 0;
// Intersects outer and inner ellipsoid for the max and min height.
shaderDefines["ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX"] = intersectionCount;
intersectionCount += 1;
shaderDefines["ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN"] = intersectionCount;
intersectionCount += 1;
shaderUniforms.clipMinMaxHeight = Cartesian2.fromElements(
renderMinBounds.z - shapeMaxBounds.z,
renderMaxBounds.z - shapeMaxBounds.z,
shaderUniforms.clipMinMaxHeight,
);
// The percent of space that is between the inner and outer ellipsoid.
const thickness = shapeMaxBounds.z - shapeMinBounds.z;
shaderUniforms.ellipsoidInverseHeightDifference = 1.0 / thickness;
if (shapeMinBounds.z === shapeMaxBounds.z) {
shaderUniforms.ellipsoidInverseHeightDifference = 0.0;
}
// Intersects a wedge for the min and max longitude.
if (renderHasLongitude) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE"] = true;
shaderDefines["ELLIPSOID_INTERSECTION_INDEX_LONGITUDE"] = intersectionCount;
if (renderIsLongitudeRangeUnderHalf) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF"] =
true;
intersectionCount += 1;
} else if (renderIsLongitudeRangeOverHalf) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF"] =
true;
intersectionCount += 2;
} else if (renderIsLongitudeRangeZero) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO"] =
true;
intersectionCount += 2;
}
shaderUniforms.ellipsoidRenderLongitudeMinMax = Cartesian2.fromElements(
renderMinBounds.x,
renderMaxBounds.x,
shaderUniforms.ellipsoidRenderLongitudeMinMax,
);
}
if (shapeHasLongitude) {
shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE"] = true;
const uvShapeMinLongitude =
(shapeMinBounds.x - DefaultMinBounds.x) / defaultLongitudeRange;
const uvShapeMaxLongitude =
(shapeMaxBounds.x - DefaultMinBounds.x) / defaultLongitudeRange;
const uvLongitudeRangeZero =
1.0 - shapeLongitudeRange / defaultLongitudeRange;
// Translate the origin of UV angles (in [0,1]) to the center of the unoccupied space
const uvLongitudeRangeOrigin =
(uvShapeMaxLongitude + 0.5 * uvLongitudeRangeZero) % 1.0;
shaderUniforms.ellipsoidShapeUvLongitudeRangeOrigin =
uvLongitudeRangeOrigin;
const shapeIsLongitudeReversed = shapeMaxBounds.x < shapeMinBounds.x;
if (shapeIsLongitudeReversed) {
shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED"] =
true;
}
if (shapeLongitudeRange <= epsilonLongitude) {
shaderUniforms.ellipsoidLocalToShapeUvLongitude = Cartesian2.fromElements(
0.0,
1.0,
shaderUniforms.ellipsoidLocalToShapeUvLongitude,
);
} else {
const scale = defaultLongitudeRange / shapeLongitudeRange;
const shiftedMinLongitude = uvShapeMinLongitude - uvLongitudeRangeOrigin;
const offset =
-scale * (shiftedMinLongitude - Math.floor(shiftedMinLongitude));
shaderUniforms.ellipsoidLocalToShapeUvLongitude = Cartesian2.fromElements(
scale,
offset,
shaderUniforms.ellipsoidLocalToShapeUvLongitude,
);
}
}
if (renderHasLongitude) {
const renderIsMinLongitudeDiscontinuity = CesiumMath.equalsEpsilon(
renderMinBounds.x,
DefaultMinBounds.x,
undefined,
epsilonLongitudeDiscontinuity,
);
const renderIsMaxLongitudeDiscontinuity = CesiumMath.equalsEpsilon(
renderMaxBounds.x,
DefaultMaxBounds.x,
undefined,
epsilonLongitudeDiscontinuity,
);
if (renderIsMinLongitudeDiscontinuity) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY"] =
true;
}
if (renderIsMaxLongitudeDiscontinuity) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY"] =
true;
}
const uvShapeMinLongitude =
(shapeMinBounds.x - DefaultMinBounds.x) / defaultLongitudeRange;
const uvShapeMaxLongitude =
(shapeMaxBounds.x - DefaultMinBounds.x) / defaultLongitudeRange;
const uvRenderMaxLongitude =
(renderMaxBounds.x - DefaultMinBounds.x) / defaultLongitudeRange;
const uvRenderLongitudeRangeZero =
1.0 - renderLongitudeRange / defaultLongitudeRange;
const uvRenderLongitudeRangeZeroMid =
(uvRenderMaxLongitude + 0.5 * uvRenderLongitudeRangeZero) % 1.0;
shaderUniforms.ellipsoidShapeUvLongitudeMinMaxMid = Cartesian3.fromElements(
uvShapeMinLongitude,
uvShapeMaxLongitude,
uvRenderLongitudeRangeZeroMid,
shaderUniforms.ellipsoidShapeUvLongitudeMinMaxMid,
);
}
if (renderHasLatitude) {
// Intersects a cone for min latitude
if (renderHasLatitudeMin) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN"] = true;
shaderDefines["ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN"] =
intersectionCount;
if (renderIsLatitudeMinUnderHalf) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF"] =
true;
intersectionCount += 1;
} else if (renderIsLatitudeMinHalf) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF"] =
true;
intersectionCount += 1;
} else if (renderIsLatitudeMinOverHalf) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF"] =
true;
intersectionCount += 2;
}
}
// Intersects a cone for max latitude
if (renderHasLatitudeMax) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX"] = true;
shaderDefines["ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX"] =
intersectionCount;
if (renderIsLatitudeMaxUnderHalf) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF"] =
true;
intersectionCount += 2;
} else if (renderIsLatitudeMaxHalf) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF"] =
true;
intersectionCount += 1;
} else if (renderIsLatitudeMaxOverHalf) {
shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF"] =
true;
intersectionCount += 1;
}
}
shaderUniforms.ellipsoidRenderLatitudeSinMinMax = Cartesian2.fromElements(
Math.sin(renderMinBounds.y),
Math.sin(renderMaxBounds.y),
shaderUniforms.ellipsoidRenderLatitudeSinMinMax,
);
}
if (shapeHasLatitude) {
shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE"] = true;
if (shapeLatitudeRange < epsilonLatitude) {
shaderUniforms.ellipsoidLocalToShapeUvLatitude = Cartesian2.fromElements(
0.0,
1.0,
shaderUniforms.ellipsoidLocalToShapeUvLatitude,
);
} else {
const defaultLatitudeRange = DefaultMaxBounds.y - DefaultMinBounds.y;
const scale = defaultLatitudeRange / shapeLatitudeRange;
const offset =
(DefaultMinBounds.y - shapeMinBounds.y) / shapeLatitudeRange;
shaderUniforms.ellipsoidLocalToShapeUvLatitude = Cartesian2.fromElements(
scale,
offset,
shaderUniforms.ellipsoidLocalToShapeUvLatitude,
);
}
}
this._shaderMaximumIntersectionsLength = intersectionCount;
return true;
};
const scratchCameraPositionCartographic = new Cartographic();
const surfacePositionScratch = new Cartesian3();
const enuTransformScratch = new Matrix4();
const enuRotationScratch = new Matrix3();
/**
* Update any view-dependent transforms.
* @private
* @param {FrameState} frameState The frame state.
*/
VoxelEllipsoidShape.prototype.updateViewTransforms = function (frameState) {
const shaderUniforms = this._shaderUniforms;
const ellipsoid = this._ellipsoid;
// TODO: incorporate modelMatrix or shapeTransform here?
const cameraWC = frameState.camera.positionWC;
const cameraPositionCartographic = ellipsoid.cartesianToCartographic(
cameraWC,
scratchCameraPositionCartographic,
);
Cartesian3.fromElements(
cameraPositionCartographic.longitude,
cameraPositionCartographic.latitude,
cameraPositionCartographic.height,
shaderUniforms.cameraPositionCartographic,
);
// TODO: incorporate modelMatrix here?
const surfacePosition = Cartesian3.fromRadians(
cameraPositionCartographic.longitude,
cameraPositionCartographic.latitude,
0.0,
ellipsoid,
surfacePositionScratch,
);
shaderUniforms.ellipsoidCurvatureAtLatitude = ellipsoid.getLocalCurvature(
surfacePosition,
shaderUniforms.ellipsoidCurvatureAtLatitude,
);
const enuToWorld = Transforms.eastNorthUpToFixedFrame(
surfacePosition,
ellipsoid,
enuTransformScratch,
);
const rotateEnuToWorld = Matrix4.getRotation(enuToWorld, enuRotationScratch);
const rotateWorldToView = frameState.context.uniformState.viewRotation;
const rotateEnuToView = Matrix3.multiply(
rotateWorldToView,
rotateEnuToWorld,
enuRotationScratch,
);
// Inverse is the transpose since it's a pure rotation.
shaderUniforms.ellipsoidEcToEastNorthUp = Matrix3.transpose(
rotateEnuToView,
shaderUniforms.ellipsoidEcToEastNorthUp,
);
};
const scratchRectangle = new Rectangle();
/**
* Computes an oriented bounding box for a specified tile.
* @private
* @param {number} tileLevel The tile's level.
* @param {number} tileX The tile's x coordinate.
* @param {number} tileY The tile's y coordinate.
* @param {number} tileZ The tile's z coordinate.
* @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile
* @returns {OrientedBoundingBox} The oriented bounding box.
*/
VoxelEllipsoidShape.prototype.computeOrientedBoundingBoxForTile = function (
tileLevel,
tileX,
tileY,
tileZ,
result,
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number("tileLevel", tileLevel);
Check.typeOf.number("tileX", tileX);
Check.typeOf.number("tileY", tileY);
Check.typeOf.number("tileZ", tileZ);
Check.typeOf.object("result", result);
//>>includeEnd('debug');
const sizeAtLevel = 1.0 / Math.pow(2.0, tileLevel);
const minLongitudeLerp = tileX * sizeAtLevel;
const maxLongitudeLerp = (tileX + 1) * sizeAtLevel;
const minLatitudeLerp = tileY * sizeAtLevel;
const maxLatitudeLerp = (tileY + 1) * sizeAtLevel;
const minHeightLerp = tileZ * sizeAtLevel;
const maxHeightLerp = (tileZ + 1) * sizeAtLevel;
const rectangle = Rectangle.subsection(
this._rectangle,
minLongitudeLerp,
minLatitudeLerp,
maxLongitudeLerp,
maxLatitudeLerp,
scratchRectangle,
);
const minHeight = CesiumMath.lerp(
this._minimumHeight,
this._maximumHeight,
minHeightLerp,
);
const maxHeight = CesiumMath.lerp(
this._minimumHeight,
this._maximumHeight,
maxHeightLerp,
);
return getEllipsoidChunkObb(
rectangle,
minHeight,
maxHeight,
this._ellipsoid,
this._translation,
this._rotation,
result,
);
};
const scratchQuadrantPosition = new Cartesian2();
const scratchInverseRadii = new Cartesian2();
const scratchEllipseTrigs = new Cartesian2();
const scratchEllipseGuess = new Cartesian2();
const scratchEvolute = new Cartesian2();
const scratchQ = new Cartesian2();
/**
* Find the nearest point on an ellipse and its radius.
* @param {Cartesian2} position
* @param {Cartesian2} radii
* @param {Cartesian2} evoluteScale
* @param {Cartesian3} result The Cartesian3 to store the result in. .x and .y components contain the nearest point on the ellipse, .z contains the local radius of curvature.
* @returns {Cartesian3} The nearest point on the ellipse and its radius.
* @private
*/
function nearestPointAndRadiusOnEllipse(position, radii, evoluteScale, result) {
// Map to the first quadrant
const p = Cartesian2.abs(position, scratchQuadrantPosition);
const inverseRadii = Cartesian2.fromElements(
1.0 / radii.x,
1.0 / radii.y,
scratchInverseRadii,
);
// We describe the ellipse parametrically: v = radii * vec2(cos(t), sin(t))
// but store the cos and sin of t in a vec2 for efficiency.
// Initial guess: t = pi/4
let tTrigs = Cartesian2.fromElements(
Math.SQRT1_2,
Math.SQRT1_2,
scratchEllipseTrigs,
);
// TODO: too much duplication. Move v and evolute declarations inside loop?
// Initial guess of point on ellipsoid
let v = Cartesian2.multiplyComponents(radii, tTrigs, scratchEllipseGuess);
// Center of curvature of the ellipse at v
let evolute = Cartesian2.fromElements(
evoluteScale.x * tTrigs.x * tTrigs.x * tTrigs.x,
evoluteScale.y * tTrigs.y * tTrigs.y * tTrigs.y,
scratchEvolute,
);
for (let i = 0; i < 3; ++i) {
// Find the (approximate) intersection of p - evolute with the ellipsoid.
const distance = Cartesian2.magnitude(
Cartesian2.subtract(v, evolute, scratchQ),
);
const direction = Cartesian2.normalize(
Cartesian2.subtract(p, evolute, scratchQ),
scratchQ,
);
const q = Cartesian2.multiplyByScalar(direction, distance, scratchQ);
// Update the estimate of t
tTrigs = Cartesian2.multiplyComponents(
Cartesian2.add(q, evolute, scratchEllipseTrigs),
inverseRadii,
scratchEllipseTrigs,
);
tTrigs = Cartesian2.normalize(
Cartesian2.clamp(
tTrigs,
Cartesian2.ZERO,
Cartesian2.ONE,
scratchEllipseTrigs,
),
scratchEllipseTrigs,
);
v = Cartesian2.multiplyComponents(radii, tTrigs, scratchEllipseGuess);
evolute = Cartesian2.fromElements(
evoluteScale.x * tTrigs.x * tTrigs.x * tTrigs.x,
evoluteScale.y * tTrigs.y * tTrigs.y * tTrigs.y,
scratchEvolute,
);
}
// Map back to the original quadrant
return Cartesian3.fromElements(
Math.sign(position.x) * v.x,
Math.sign(position.y) * v.y,
Cartesian2.magnitude(Cartesian2.subtract(v, evolute, scratchQ)),
result,
);
}
const scratchEllipseRadii = new Cartesian2();
const scratchEllipsePosition = new Cartesian2();
const scratchSurfacePointAndRadius = new Cartesian3();
const scratchNormal2d = new Cartesian2();
/**
* Convert a UV coordinate to the shape's UV space.
* @private
* @param {Cartesian3} positionLocal The local position to convert.
* @param {Cartesian3} result The Cartesian3 to store the result in.
* @returns {Cartesian3} The converted UV coordinate.
*/
VoxelEllipsoidShape.prototype.convertLocalToShapeUvSpace = function (
positionLocal,
result,
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("positionLocal", positionLocal);
Check.typeOf.object("result", result);
//>>includeEnd('debug');
let longitude = Math.atan2(positionLocal.y, positionLocal.x);
const {
ellipsoidRadii,
evoluteScale,
ellipsoidInverseRadiiSquared,
ellipsoidInverseHeightDifference,
ellipsoidShapeUvLongitudeRangeOrigin,
ellipsoidLocalToShapeUvLongitude,
ellipsoidLocalToShapeUvLatitude,
} = this._shaderUniforms;
const distanceFromZAxis = Math.hypot(positionLocal.x, positionLocal.y);
const posEllipse = Cartesian2.fromElements(
distanceFromZAxis,
positionLocal.z,
scratchEllipsePosition,
);
const surfacePointAndRadius = nearestPointAndRadiusOnEllipse(
posEllipse,
Cartesian2.fromElements(
ellipsoidRadii.x,
ellipsoidRadii.z,
scratchEllipseRadii,
),
evoluteScale,
scratchSurfacePointAndRadius,
);
const normal2d = Cartesian2.normalize(
Cartesian2.fromElements(
surfacePointAndRadius.x * ellipsoidInverseRadiiSquared.x,
surfacePointAndRadius.y * ellipsoidInverseRadiiSquared.z,
scratchNormal2d,
),
scratchNormal2d,
);
let latitude = Math.atan2(normal2d.y, normal2d.x);
const heightSign =
Cartesian2.magnitude(posEllipse) <
Cartesian2.magnitude(surfacePointAndRadius)
? -1.0
: 1.0;
const heightVector = Cartesian2.subtract(
posEllipse,
surfacePointAndRadius,
scratchEllipsePosition,
);
let height = heightSign * Cartesian2.magnitude(heightVector);
const {
ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE,
ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE,
} = this._shaderDefines;
longitude = (longitude + Math.PI) / (2.0 * Math.PI);
if (defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)) {
longitude -= ellipsoidShapeUvLongitudeRangeOrigin;
longitude = longitude - Math.floor(longitude);
// Scale and shift so [0, 1] covers the occupied space.
longitude =
longitude * ellipsoidLocalToShapeUvLongitude.x +
ellipsoidLocalToShapeUvLongitude.y;
}
latitude = (latitude + Math.PI / 2.0) / Math.PI;
if (defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)) {
// Scale and shift so [0, 1] covers the occupied space.
latitude =
latitude * ellipsoidLocalToShapeUvLatitude.x +
ellipsoidLocalToShapeUvLatitude.y;
}
height = 1.0 + height * ellipsoidInverseHeightDifference;
return Cartesian3.fromElements(longitude, latitude, height, result);
};
const sampleSizeScratch = new Cartesian3();
const scratchTileMinBounds = new Cartesian3();
const scratchTileMaxBounds = new Cartesian3();
/**
* Computes an oriented bounding box for a specified sample within a specified tile.
* @private
* @param {SpatialNode} spatialNode The spatial node containing the sample
* @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
* @param {Cartesian3} tileUv The sample coordinate within the tile
* @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified sample
* @returns {OrientedBoundingBox} The oriented bounding box.
*/
VoxelEllipsoidShape.prototype.computeOrientedBoundingBoxForSample = function (
spatialNode,
tileDimensions,
tileUv,
result,
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("spatialNode", spatialNode);
Check.typeOf.object("tileDimensions", tileDimensions);
Check.typeOf.object("tileUv", tileUv);
Check.typeOf.object("result", result);
//>>includeEnd('debug');
const tileSizeAtLevel = 1.0 / Math.pow(2.0, spatialNode.level);
const sampleSize = Cartesian3.divideComponents(
Cartesian3.ONE,
tileDimensions,
sampleSizeScratch,
);
const sampleSizeAtLevel = Cartesian3.multiplyByScalar(
sampleSize,
tileSizeAtLevel,
sampleSizeScratch,
);
const minLerp = Cartesian3.multiplyByScalar(
Cartesian3.fromElements(
spatialNode.x + tileUv.x,
spatialNode.y + tileUv.y,
spatialNode.z + tileUv.z,
scratchTileMinBounds,
),
tileSizeAtLevel,
scratchTileMinBounds,
);
const maxLerp = Cartesian3.add(
minLerp,
sampleSizeAtLevel,
scratchTileMaxBounds,
);
const rectangle = Rectangle.subsection(
this._rectangle,
minLerp.x,
minLerp.y,
maxLerp.x,
maxLerp.y,
scratchRectangle,
);
const minHeight = CesiumMath.lerp(
this._minimumHeight,
this._maximumHeight,
minLerp.z,
);
const maxHeight = CesiumMath.lerp(
this._minimumHeight,
this._maximumHeight,
maxLerp.z,
);
return getEllipsoidChunkObb(
rectangle,
minHeight,
maxHeight,
this._ellipsoid,
this._translation,
this._rotation,
result,
);
};
/**
* Computes an {@link OrientedBoundingBox} for a subregion of the shape.
*
* @function
*
* @param {Rectangle} rectangle The rectangle.
* @param {number} minHeight The minimumZ.
* @param {number} maxHeight The maximumZ.
* @param {Ellipsoid} ellipsoid The ellipsoid.
* @param {Cartesian3} translation The translation applied to the shape
* @param {Matrix3} rotation The rotation applied to the shape
* @param {OrientedBoundingBox} result The object onto which to store the result.
* @returns {OrientedBoundingBox} The oriented bounding box that contains this subregion.
*
* @private
*/
function getEllipsoidChunkObb(
rectangle,
minHeight,
maxHeight,
ellipsoid,
translation,
rotation,
result,
) {
result = OrientedBoundingBox.fromRectangle(
rectangle,
minHeight,
maxHeight,
ellipsoid,
result,
);
result.center = Cartesian3.add(result.center, translation, result.center);
result.halfAxes = Matrix3.multiply(
result.halfAxes,
rotation,
result.halfAxes,
);
return result;
}
/**
* Defines the minimum bounds of the shape. Corresponds to minimum longitude, latitude, height.
* @private
* @type {Cartesian3}
* @constant
* @readonly
*/
VoxelEllipsoidShape.DefaultMinBounds = Object.freeze(
new Cartesian3(
-CesiumMath.PI,
-CesiumMath.PI_OVER_TWO,
-Ellipsoid.WGS84.minimumRadius,
),
);
/**
* Defines the maximum bounds of the shape. Corresponds to maximum longitude, latitude, height.
* @private
* @type {Cartesian3}
* @constant
* @readonly
*/
VoxelEllipsoidShape.DefaultMaxBounds = Object.freeze(
new Cartesian3(
CesiumMath.PI,
CesiumMath.PI_OVER_TWO,
10.0 * Ellipsoid.WGS84.maximumRadius,
),
);
export default VoxelEllipsoidShape;