You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
934 lines
30 KiB
JavaScript
934 lines
30 KiB
JavaScript
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 ComponentDatatype from "../Core/ComponentDatatype.js";
|
|
import defaultValue from "../Core/defaultValue.js";
|
|
import defined from "../Core/defined.js";
|
|
import EncodedCartesian3 from "../Core/EncodedCartesian3.js";
|
|
import GeometryInstanceAttribute from "../Core/GeometryInstanceAttribute.js";
|
|
import CesiumMath from "../Core/Math.js";
|
|
import Matrix4 from "../Core/Matrix4.js";
|
|
import Rectangle from "../Core/Rectangle.js";
|
|
import Transforms from "../Core/Transforms.js";
|
|
import ShaderSource from "../Renderer/ShaderSource.js";
|
|
import PerInstanceColorAppearance from "../Scene/PerInstanceColorAppearance.js";
|
|
import ShadowVolumeAppearanceFS from "../Shaders/ShadowVolumeAppearanceFS.js";
|
|
|
|
/**
|
|
* Creates shaders for a ClassificationPrimitive to use a given Appearance, as well as for picking.
|
|
*
|
|
* @param {Boolean} extentsCulling Discard fragments outside the instance's texture coordinate extents.
|
|
* @param {Boolean} planarExtents If true, texture coordinates will be computed using planes instead of spherical coordinates.
|
|
* @param {Appearance} appearance An Appearance to be used with a ClassificationPrimitive via GroundPrimitive.
|
|
* @private
|
|
*/
|
|
function ShadowVolumeAppearance(extentsCulling, planarExtents, appearance) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.bool("extentsCulling", extentsCulling);
|
|
Check.typeOf.bool("planarExtents", planarExtents);
|
|
Check.typeOf.object("appearance", appearance);
|
|
//>>includeEnd('debug');
|
|
|
|
this._projectionExtentDefines = {
|
|
eastMostYhighDefine: "",
|
|
eastMostYlowDefine: "",
|
|
westMostYhighDefine: "",
|
|
westMostYlowDefine: "",
|
|
};
|
|
|
|
// Compute shader dependencies
|
|
var colorShaderDependencies = new ShaderDependencies();
|
|
colorShaderDependencies.requiresTextureCoordinates = extentsCulling;
|
|
colorShaderDependencies.requiresEC = !appearance.flat;
|
|
|
|
var pickShaderDependencies = new ShaderDependencies();
|
|
pickShaderDependencies.requiresTextureCoordinates = extentsCulling;
|
|
|
|
if (appearance instanceof PerInstanceColorAppearance) {
|
|
// PerInstanceColorAppearance doesn't have material.shaderSource, instead it has its own vertex and fragment shaders
|
|
colorShaderDependencies.requiresNormalEC = !appearance.flat;
|
|
} else {
|
|
// Scan material source for what hookups are needed. Assume czm_materialInput materialInput.
|
|
var materialShaderSource =
|
|
appearance.material.shaderSource + "\n" + appearance.fragmentShaderSource;
|
|
|
|
colorShaderDependencies.normalEC =
|
|
materialShaderSource.indexOf("materialInput.normalEC") !== -1 ||
|
|
materialShaderSource.indexOf("czm_getDefaultMaterial") !== -1;
|
|
colorShaderDependencies.positionToEyeEC =
|
|
materialShaderSource.indexOf("materialInput.positionToEyeEC") !== -1;
|
|
colorShaderDependencies.tangentToEyeMatrix =
|
|
materialShaderSource.indexOf("materialInput.tangentToEyeMatrix") !== -1;
|
|
colorShaderDependencies.st =
|
|
materialShaderSource.indexOf("materialInput.st") !== -1;
|
|
}
|
|
|
|
this._colorShaderDependencies = colorShaderDependencies;
|
|
this._pickShaderDependencies = pickShaderDependencies;
|
|
this._appearance = appearance;
|
|
this._extentsCulling = extentsCulling;
|
|
this._planarExtents = planarExtents;
|
|
}
|
|
|
|
/**
|
|
* Create the fragment shader for a ClassificationPrimitive's color pass when rendering for color.
|
|
*
|
|
* @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D.
|
|
* @returns {ShaderSource} Shader source for the fragment shader.
|
|
*/
|
|
ShadowVolumeAppearance.prototype.createFragmentShader = function (
|
|
columbusView2D
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.bool("columbusView2D", columbusView2D);
|
|
//>>includeEnd('debug');
|
|
|
|
var appearance = this._appearance;
|
|
var dependencies = this._colorShaderDependencies;
|
|
|
|
var defines = [];
|
|
if (!columbusView2D && !this._planarExtents) {
|
|
defines.push("SPHERICAL");
|
|
}
|
|
if (dependencies.requiresEC) {
|
|
defines.push("REQUIRES_EC");
|
|
}
|
|
if (dependencies.requiresWC) {
|
|
defines.push("REQUIRES_WC");
|
|
}
|
|
if (dependencies.requiresTextureCoordinates) {
|
|
defines.push("TEXTURE_COORDINATES");
|
|
}
|
|
if (this._extentsCulling) {
|
|
defines.push("CULL_FRAGMENTS");
|
|
}
|
|
if (dependencies.requiresNormalEC) {
|
|
defines.push("NORMAL_EC");
|
|
}
|
|
if (appearance instanceof PerInstanceColorAppearance) {
|
|
defines.push("PER_INSTANCE_COLOR");
|
|
}
|
|
|
|
// Material inputs. Use of parameters in the material is different
|
|
// from requirement of the parameters in the overall shader, for example,
|
|
// texture coordinates may be used for fragment culling but not for the material itself.
|
|
if (dependencies.normalEC) {
|
|
defines.push("USES_NORMAL_EC");
|
|
}
|
|
if (dependencies.positionToEyeEC) {
|
|
defines.push("USES_POSITION_TO_EYE_EC");
|
|
}
|
|
if (dependencies.tangentToEyeMatrix) {
|
|
defines.push("USES_TANGENT_TO_EYE");
|
|
}
|
|
if (dependencies.st) {
|
|
defines.push("USES_ST");
|
|
}
|
|
|
|
if (appearance.flat) {
|
|
defines.push("FLAT");
|
|
}
|
|
|
|
var materialSource = "";
|
|
if (!(appearance instanceof PerInstanceColorAppearance)) {
|
|
materialSource = appearance.material.shaderSource;
|
|
}
|
|
|
|
return new ShaderSource({
|
|
defines: defines,
|
|
sources: [materialSource, ShadowVolumeAppearanceFS],
|
|
});
|
|
};
|
|
|
|
ShadowVolumeAppearance.prototype.createPickFragmentShader = function (
|
|
columbusView2D
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.bool("columbusView2D", columbusView2D);
|
|
//>>includeEnd('debug');
|
|
|
|
var dependencies = this._pickShaderDependencies;
|
|
|
|
var defines = ["PICK"];
|
|
if (!columbusView2D && !this._planarExtents) {
|
|
defines.push("SPHERICAL");
|
|
}
|
|
if (dependencies.requiresEC) {
|
|
defines.push("REQUIRES_EC");
|
|
}
|
|
if (dependencies.requiresWC) {
|
|
defines.push("REQUIRES_WC");
|
|
}
|
|
if (dependencies.requiresTextureCoordinates) {
|
|
defines.push("TEXTURE_COORDINATES");
|
|
}
|
|
if (this._extentsCulling) {
|
|
defines.push("CULL_FRAGMENTS");
|
|
}
|
|
return new ShaderSource({
|
|
defines: defines,
|
|
sources: [ShadowVolumeAppearanceFS],
|
|
pickColorQualifier: "varying",
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Create the vertex shader for a ClassificationPrimitive's color pass on the final of 3 shadow volume passes
|
|
*
|
|
* @param {String[]} defines External defines to pass to the vertex shader.
|
|
* @param {String} vertexShaderSource ShadowVolumeAppearanceVS with any required modifications for computing position.
|
|
* @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D.
|
|
* @param {MapProjection} mapProjection Current scene's map projection.
|
|
* @returns {String} Shader source for the vertex shader.
|
|
*/
|
|
ShadowVolumeAppearance.prototype.createVertexShader = function (
|
|
defines,
|
|
vertexShaderSource,
|
|
columbusView2D,
|
|
mapProjection
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("defines", defines);
|
|
Check.typeOf.string("vertexShaderSource", vertexShaderSource);
|
|
Check.typeOf.bool("columbusView2D", columbusView2D);
|
|
Check.defined("mapProjection", mapProjection);
|
|
//>>includeEnd('debug');
|
|
return createShadowVolumeAppearanceVS(
|
|
this._colorShaderDependencies,
|
|
this._planarExtents,
|
|
columbusView2D,
|
|
defines,
|
|
vertexShaderSource,
|
|
this._appearance,
|
|
mapProjection,
|
|
this._projectionExtentDefines
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Create the vertex shader for a ClassificationPrimitive's pick pass on the final of 3 shadow volume passes
|
|
*
|
|
* @param {String[]} defines External defines to pass to the vertex shader.
|
|
* @param {String} vertexShaderSource ShadowVolumeAppearanceVS with any required modifications for computing position and picking.
|
|
* @param {Boolean} columbusView2D Whether the shader will be used for Columbus View or 2D.
|
|
* @param {MapProjection} mapProjection Current scene's map projection.
|
|
* @returns {String} Shader source for the vertex shader.
|
|
*/
|
|
ShadowVolumeAppearance.prototype.createPickVertexShader = function (
|
|
defines,
|
|
vertexShaderSource,
|
|
columbusView2D,
|
|
mapProjection
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("defines", defines);
|
|
Check.typeOf.string("vertexShaderSource", vertexShaderSource);
|
|
Check.typeOf.bool("columbusView2D", columbusView2D);
|
|
Check.defined("mapProjection", mapProjection);
|
|
//>>includeEnd('debug');
|
|
return createShadowVolumeAppearanceVS(
|
|
this._pickShaderDependencies,
|
|
this._planarExtents,
|
|
columbusView2D,
|
|
defines,
|
|
vertexShaderSource,
|
|
undefined,
|
|
mapProjection,
|
|
this._projectionExtentDefines
|
|
);
|
|
};
|
|
|
|
var longitudeExtentsCartesianScratch = new Cartesian3();
|
|
var longitudeExtentsCartographicScratch = new Cartographic();
|
|
var longitudeExtentsEncodeScratch = {
|
|
high: 0.0,
|
|
low: 0.0,
|
|
};
|
|
function createShadowVolumeAppearanceVS(
|
|
shaderDependencies,
|
|
planarExtents,
|
|
columbusView2D,
|
|
defines,
|
|
vertexShaderSource,
|
|
appearance,
|
|
mapProjection,
|
|
projectionExtentDefines
|
|
) {
|
|
var allDefines = defines.slice();
|
|
|
|
if (projectionExtentDefines.eastMostYhighDefine === "") {
|
|
var eastMostCartographic = longitudeExtentsCartographicScratch;
|
|
eastMostCartographic.longitude = CesiumMath.PI;
|
|
eastMostCartographic.latitude = 0.0;
|
|
eastMostCartographic.height = 0.0;
|
|
var eastMostCartesian = mapProjection.project(
|
|
eastMostCartographic,
|
|
longitudeExtentsCartesianScratch
|
|
);
|
|
var encoded = EncodedCartesian3.encode(
|
|
eastMostCartesian.x,
|
|
longitudeExtentsEncodeScratch
|
|
);
|
|
projectionExtentDefines.eastMostYhighDefine =
|
|
"EAST_MOST_X_HIGH " +
|
|
encoded.high.toFixed((encoded.high + "").length + 1);
|
|
projectionExtentDefines.eastMostYlowDefine =
|
|
"EAST_MOST_X_LOW " + encoded.low.toFixed((encoded.low + "").length + 1);
|
|
|
|
var westMostCartographic = longitudeExtentsCartographicScratch;
|
|
westMostCartographic.longitude = -CesiumMath.PI;
|
|
westMostCartographic.latitude = 0.0;
|
|
westMostCartographic.height = 0.0;
|
|
var westMostCartesian = mapProjection.project(
|
|
westMostCartographic,
|
|
longitudeExtentsCartesianScratch
|
|
);
|
|
encoded = EncodedCartesian3.encode(
|
|
westMostCartesian.x,
|
|
longitudeExtentsEncodeScratch
|
|
);
|
|
projectionExtentDefines.westMostYhighDefine =
|
|
"WEST_MOST_X_HIGH " +
|
|
encoded.high.toFixed((encoded.high + "").length + 1);
|
|
projectionExtentDefines.westMostYlowDefine =
|
|
"WEST_MOST_X_LOW " + encoded.low.toFixed((encoded.low + "").length + 1);
|
|
}
|
|
|
|
if (columbusView2D) {
|
|
allDefines.push(projectionExtentDefines.eastMostYhighDefine);
|
|
allDefines.push(projectionExtentDefines.eastMostYlowDefine);
|
|
allDefines.push(projectionExtentDefines.westMostYhighDefine);
|
|
allDefines.push(projectionExtentDefines.westMostYlowDefine);
|
|
}
|
|
|
|
if (defined(appearance) && appearance instanceof PerInstanceColorAppearance) {
|
|
allDefines.push("PER_INSTANCE_COLOR");
|
|
}
|
|
if (shaderDependencies.requiresTextureCoordinates) {
|
|
allDefines.push("TEXTURE_COORDINATES");
|
|
if (!(planarExtents || columbusView2D)) {
|
|
allDefines.push("SPHERICAL");
|
|
}
|
|
if (columbusView2D) {
|
|
allDefines.push("COLUMBUS_VIEW_2D");
|
|
}
|
|
}
|
|
|
|
return new ShaderSource({
|
|
defines: allDefines,
|
|
sources: [vertexShaderSource],
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Tracks shader dependencies.
|
|
* @private
|
|
*/
|
|
function ShaderDependencies() {
|
|
this._requiresEC = false;
|
|
this._requiresWC = false; // depends on eye coordinates, needed for material and for phong
|
|
this._requiresNormalEC = false; // depends on eye coordinates, needed for material
|
|
this._requiresTextureCoordinates = false; // depends on world coordinates, needed for material and for culling
|
|
|
|
this._usesNormalEC = false;
|
|
this._usesPositionToEyeEC = false;
|
|
this._usesTangentToEyeMat = false;
|
|
this._usesSt = false;
|
|
}
|
|
|
|
Object.defineProperties(ShaderDependencies.prototype, {
|
|
// Set when assessing final shading (flat vs. phong) and culling using computed texture coordinates
|
|
requiresEC: {
|
|
get: function () {
|
|
return this._requiresEC;
|
|
},
|
|
set: function (value) {
|
|
this._requiresEC = value || this._requiresEC;
|
|
},
|
|
},
|
|
requiresWC: {
|
|
get: function () {
|
|
return this._requiresWC;
|
|
},
|
|
set: function (value) {
|
|
this._requiresWC = value || this._requiresWC;
|
|
this.requiresEC = this._requiresWC;
|
|
},
|
|
},
|
|
requiresNormalEC: {
|
|
get: function () {
|
|
return this._requiresNormalEC;
|
|
},
|
|
set: function (value) {
|
|
this._requiresNormalEC = value || this._requiresNormalEC;
|
|
this.requiresEC = this._requiresNormalEC;
|
|
},
|
|
},
|
|
requiresTextureCoordinates: {
|
|
get: function () {
|
|
return this._requiresTextureCoordinates;
|
|
},
|
|
set: function (value) {
|
|
this._requiresTextureCoordinates =
|
|
value || this._requiresTextureCoordinates;
|
|
this.requiresWC = this._requiresTextureCoordinates;
|
|
},
|
|
},
|
|
// Get/Set when assessing material hookups
|
|
normalEC: {
|
|
set: function (value) {
|
|
this.requiresNormalEC = value;
|
|
this._usesNormalEC = value;
|
|
},
|
|
get: function () {
|
|
return this._usesNormalEC;
|
|
},
|
|
},
|
|
tangentToEyeMatrix: {
|
|
set: function (value) {
|
|
this.requiresWC = value;
|
|
this.requiresNormalEC = value;
|
|
this._usesTangentToEyeMat = value;
|
|
},
|
|
get: function () {
|
|
return this._usesTangentToEyeMat;
|
|
},
|
|
},
|
|
positionToEyeEC: {
|
|
set: function (value) {
|
|
this.requiresEC = value;
|
|
this._usesPositionToEyeEC = value;
|
|
},
|
|
get: function () {
|
|
return this._usesPositionToEyeEC;
|
|
},
|
|
},
|
|
st: {
|
|
set: function (value) {
|
|
this.requiresTextureCoordinates = value;
|
|
this._usesSt = value;
|
|
},
|
|
get: function () {
|
|
return this._usesSt;
|
|
},
|
|
},
|
|
});
|
|
|
|
function pointLineDistance(point1, point2, point) {
|
|
return (
|
|
Math.abs(
|
|
(point2.y - point1.y) * point.x -
|
|
(point2.x - point1.x) * point.y +
|
|
point2.x * point1.y -
|
|
point2.y * point1.x
|
|
) / Cartesian2.distance(point2, point1)
|
|
);
|
|
}
|
|
|
|
var points2DScratch = [
|
|
new Cartesian2(),
|
|
new Cartesian2(),
|
|
new Cartesian2(),
|
|
new Cartesian2(),
|
|
];
|
|
|
|
// textureCoordinateRotationPoints form 2 lines in the computed UV space that remap to desired texture coordinates.
|
|
// This allows simulation of baked texture coordinates for EllipseGeometry, RectangleGeometry, and PolygonGeometry.
|
|
function addTextureCoordinateRotationAttributes(
|
|
attributes,
|
|
textureCoordinateRotationPoints
|
|
) {
|
|
var points2D = points2DScratch;
|
|
|
|
var minXYCorner = Cartesian2.unpack(
|
|
textureCoordinateRotationPoints,
|
|
0,
|
|
points2D[0]
|
|
);
|
|
var maxYCorner = Cartesian2.unpack(
|
|
textureCoordinateRotationPoints,
|
|
2,
|
|
points2D[1]
|
|
);
|
|
var maxXCorner = Cartesian2.unpack(
|
|
textureCoordinateRotationPoints,
|
|
4,
|
|
points2D[2]
|
|
);
|
|
|
|
attributes.uMaxVmax = new GeometryInstanceAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 4,
|
|
normalize: false,
|
|
value: [maxYCorner.x, maxYCorner.y, maxXCorner.x, maxXCorner.y],
|
|
});
|
|
|
|
var inverseExtentX =
|
|
1.0 / pointLineDistance(minXYCorner, maxYCorner, maxXCorner);
|
|
var inverseExtentY =
|
|
1.0 / pointLineDistance(minXYCorner, maxXCorner, maxYCorner);
|
|
|
|
attributes.uvMinAndExtents = new GeometryInstanceAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 4,
|
|
normalize: false,
|
|
value: [minXYCorner.x, minXYCorner.y, inverseExtentX, inverseExtentY],
|
|
});
|
|
}
|
|
|
|
var cartographicScratch = new Cartographic();
|
|
var cornerScratch = new Cartesian3();
|
|
var northWestScratch = new Cartesian3();
|
|
var southEastScratch = new Cartesian3();
|
|
var highLowScratch = { high: 0.0, low: 0.0 };
|
|
function add2DTextureCoordinateAttributes(rectangle, projection, attributes) {
|
|
// Compute corner positions in double precision
|
|
var carto = cartographicScratch;
|
|
carto.height = 0.0;
|
|
|
|
carto.longitude = rectangle.west;
|
|
carto.latitude = rectangle.south;
|
|
|
|
var southWestCorner = projection.project(carto, cornerScratch);
|
|
|
|
carto.latitude = rectangle.north;
|
|
var northWest = projection.project(carto, northWestScratch);
|
|
|
|
carto.longitude = rectangle.east;
|
|
carto.latitude = rectangle.south;
|
|
var southEast = projection.project(carto, southEastScratch);
|
|
|
|
// Since these positions are all in the 2D plane, there's a lot of zeros
|
|
// and a lot of repetition. So we only need to encode 4 values.
|
|
// Encode:
|
|
// x: x value for southWestCorner
|
|
// y: y value for southWestCorner
|
|
// z: y value for northWest
|
|
// w: x value for southEast
|
|
|
|
var valuesHigh = [0, 0, 0, 0];
|
|
var valuesLow = [0, 0, 0, 0];
|
|
var encoded = EncodedCartesian3.encode(southWestCorner.x, highLowScratch);
|
|
valuesHigh[0] = encoded.high;
|
|
valuesLow[0] = encoded.low;
|
|
|
|
encoded = EncodedCartesian3.encode(southWestCorner.y, highLowScratch);
|
|
valuesHigh[1] = encoded.high;
|
|
valuesLow[1] = encoded.low;
|
|
|
|
encoded = EncodedCartesian3.encode(northWest.y, highLowScratch);
|
|
valuesHigh[2] = encoded.high;
|
|
valuesLow[2] = encoded.low;
|
|
|
|
encoded = EncodedCartesian3.encode(southEast.x, highLowScratch);
|
|
valuesHigh[3] = encoded.high;
|
|
valuesLow[3] = encoded.low;
|
|
|
|
attributes.planes2D_HIGH = new GeometryInstanceAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 4,
|
|
normalize: false,
|
|
value: valuesHigh,
|
|
});
|
|
|
|
attributes.planes2D_LOW = new GeometryInstanceAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 4,
|
|
normalize: false,
|
|
value: valuesLow,
|
|
});
|
|
}
|
|
|
|
var enuMatrixScratch = new Matrix4();
|
|
var inverseEnuScratch = new Matrix4();
|
|
var rectanglePointCartesianScratch = new Cartesian3();
|
|
var rectangleCenterScratch = new Cartographic();
|
|
var pointsCartographicScratch = [
|
|
new Cartographic(),
|
|
new Cartographic(),
|
|
new Cartographic(),
|
|
new Cartographic(),
|
|
new Cartographic(),
|
|
new Cartographic(),
|
|
new Cartographic(),
|
|
new Cartographic(),
|
|
];
|
|
/**
|
|
* When computing planes to bound the rectangle,
|
|
* need to factor in "bulge" and other distortion.
|
|
* Flatten the ellipsoid-centered corners and edge-centers of the rectangle
|
|
* into the plane of the local ENU system, compute bounds in 2D, and
|
|
* project back to ellipsoid-centered.
|
|
*
|
|
* @private
|
|
*/
|
|
function computeRectangleBounds(
|
|
rectangle,
|
|
ellipsoid,
|
|
height,
|
|
southWestCornerResult,
|
|
eastVectorResult,
|
|
northVectorResult
|
|
) {
|
|
// Compute center of rectangle
|
|
var centerCartographic = Rectangle.center(rectangle, rectangleCenterScratch);
|
|
centerCartographic.height = height;
|
|
var centerCartesian = Cartographic.toCartesian(
|
|
centerCartographic,
|
|
ellipsoid,
|
|
rectanglePointCartesianScratch
|
|
);
|
|
var enuMatrix = Transforms.eastNorthUpToFixedFrame(
|
|
centerCartesian,
|
|
ellipsoid,
|
|
enuMatrixScratch
|
|
);
|
|
var inverseEnu = Matrix4.inverse(enuMatrix, inverseEnuScratch);
|
|
|
|
var west = rectangle.west;
|
|
var east = rectangle.east;
|
|
var north = rectangle.north;
|
|
var south = rectangle.south;
|
|
|
|
var cartographics = pointsCartographicScratch;
|
|
cartographics[0].latitude = south;
|
|
cartographics[0].longitude = west;
|
|
cartographics[1].latitude = north;
|
|
cartographics[1].longitude = west;
|
|
cartographics[2].latitude = north;
|
|
cartographics[2].longitude = east;
|
|
cartographics[3].latitude = south;
|
|
cartographics[3].longitude = east;
|
|
|
|
var longitudeCenter = (west + east) * 0.5;
|
|
var latitudeCenter = (north + south) * 0.5;
|
|
|
|
cartographics[4].latitude = south;
|
|
cartographics[4].longitude = longitudeCenter;
|
|
cartographics[5].latitude = north;
|
|
cartographics[5].longitude = longitudeCenter;
|
|
cartographics[6].latitude = latitudeCenter;
|
|
cartographics[6].longitude = west;
|
|
cartographics[7].latitude = latitudeCenter;
|
|
cartographics[7].longitude = east;
|
|
|
|
var minX = Number.POSITIVE_INFINITY;
|
|
var maxX = Number.NEGATIVE_INFINITY;
|
|
var minY = Number.POSITIVE_INFINITY;
|
|
var maxY = Number.NEGATIVE_INFINITY;
|
|
for (var i = 0; i < 8; i++) {
|
|
cartographics[i].height = height;
|
|
var pointCartesian = Cartographic.toCartesian(
|
|
cartographics[i],
|
|
ellipsoid,
|
|
rectanglePointCartesianScratch
|
|
);
|
|
Matrix4.multiplyByPoint(inverseEnu, pointCartesian, pointCartesian);
|
|
pointCartesian.z = 0.0; // flatten into XY plane of ENU coordinate system
|
|
minX = Math.min(minX, pointCartesian.x);
|
|
maxX = Math.max(maxX, pointCartesian.x);
|
|
minY = Math.min(minY, pointCartesian.y);
|
|
maxY = Math.max(maxY, pointCartesian.y);
|
|
}
|
|
|
|
var southWestCorner = southWestCornerResult;
|
|
southWestCorner.x = minX;
|
|
southWestCorner.y = minY;
|
|
southWestCorner.z = 0.0;
|
|
Matrix4.multiplyByPoint(enuMatrix, southWestCorner, southWestCorner);
|
|
|
|
var southEastCorner = eastVectorResult;
|
|
southEastCorner.x = maxX;
|
|
southEastCorner.y = minY;
|
|
southEastCorner.z = 0.0;
|
|
Matrix4.multiplyByPoint(enuMatrix, southEastCorner, southEastCorner);
|
|
// make eastward vector
|
|
Cartesian3.subtract(southEastCorner, southWestCorner, eastVectorResult);
|
|
|
|
var northWestCorner = northVectorResult;
|
|
northWestCorner.x = minX;
|
|
northWestCorner.y = maxY;
|
|
northWestCorner.z = 0.0;
|
|
Matrix4.multiplyByPoint(enuMatrix, northWestCorner, northWestCorner);
|
|
// make eastward vector
|
|
Cartesian3.subtract(northWestCorner, southWestCorner, northVectorResult);
|
|
}
|
|
|
|
var eastwardScratch = new Cartesian3();
|
|
var northwardScratch = new Cartesian3();
|
|
var encodeScratch = new EncodedCartesian3();
|
|
/**
|
|
* Gets an attributes object containing:
|
|
* - 3 high-precision points as 6 GeometryInstanceAttributes. These points are used to compute eye-space planes.
|
|
* - 1 texture coordinate rotation GeometryInstanceAttributes
|
|
* - 2 GeometryInstanceAttributes used to compute high-precision points in 2D and Columbus View.
|
|
* These points are used to compute eye-space planes like above.
|
|
*
|
|
* Used to compute texture coordinates for small-area ClassificationPrimitives with materials or multiple non-overlapping instances.
|
|
*
|
|
* @see ShadowVolumeAppearance
|
|
* @private
|
|
*
|
|
* @param {Rectangle} boundingRectangle Rectangle object that the points will approximately bound
|
|
* @param {Number[]} textureCoordinateRotationPoints Points in the computed texture coordinate system for remapping texture coordinates
|
|
* @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates
|
|
* @param {MapProjection} projection The MapProjection used for 2D and Columbus View.
|
|
* @param {Number} [height=0] The maximum height for the shadow volume.
|
|
* @returns {Object} An attributes dictionary containing planar texture coordinate attributes.
|
|
*/
|
|
ShadowVolumeAppearance.getPlanarTextureCoordinateAttributes = function (
|
|
boundingRectangle,
|
|
textureCoordinateRotationPoints,
|
|
ellipsoid,
|
|
projection,
|
|
height
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.object("boundingRectangle", boundingRectangle);
|
|
Check.defined(
|
|
"textureCoordinateRotationPoints",
|
|
textureCoordinateRotationPoints
|
|
);
|
|
Check.typeOf.object("ellipsoid", ellipsoid);
|
|
Check.typeOf.object("projection", projection);
|
|
//>>includeEnd('debug');
|
|
|
|
var corner = cornerScratch;
|
|
var eastward = eastwardScratch;
|
|
var northward = northwardScratch;
|
|
computeRectangleBounds(
|
|
boundingRectangle,
|
|
ellipsoid,
|
|
defaultValue(height, 0.0),
|
|
corner,
|
|
eastward,
|
|
northward
|
|
);
|
|
|
|
var attributes = {};
|
|
addTextureCoordinateRotationAttributes(
|
|
attributes,
|
|
textureCoordinateRotationPoints
|
|
);
|
|
|
|
var encoded = EncodedCartesian3.fromCartesian(corner, encodeScratch);
|
|
|
|
attributes.southWest_HIGH = new GeometryInstanceAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 3,
|
|
normalize: false,
|
|
value: Cartesian3.pack(encoded.high, [0, 0, 0]),
|
|
});
|
|
attributes.southWest_LOW = new GeometryInstanceAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 3,
|
|
normalize: false,
|
|
value: Cartesian3.pack(encoded.low, [0, 0, 0]),
|
|
});
|
|
attributes.eastward = new GeometryInstanceAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 3,
|
|
normalize: false,
|
|
value: Cartesian3.pack(eastward, [0, 0, 0]),
|
|
});
|
|
attributes.northward = new GeometryInstanceAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 3,
|
|
normalize: false,
|
|
value: Cartesian3.pack(northward, [0, 0, 0]),
|
|
});
|
|
|
|
add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes);
|
|
return attributes;
|
|
};
|
|
|
|
var spherePointScratch = new Cartesian3();
|
|
function latLongToSpherical(latitude, longitude, ellipsoid, result) {
|
|
var cartographic = cartographicScratch;
|
|
cartographic.latitude = latitude;
|
|
cartographic.longitude = longitude;
|
|
cartographic.height = 0.0;
|
|
|
|
var spherePoint = Cartographic.toCartesian(
|
|
cartographic,
|
|
ellipsoid,
|
|
spherePointScratch
|
|
);
|
|
|
|
// Project into plane with vertical for latitude
|
|
var magXY = Math.sqrt(
|
|
spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y
|
|
);
|
|
|
|
// Use fastApproximateAtan2 for alignment with shader
|
|
var sphereLatitude = CesiumMath.fastApproximateAtan2(magXY, spherePoint.z);
|
|
var sphereLongitude = CesiumMath.fastApproximateAtan2(
|
|
spherePoint.x,
|
|
spherePoint.y
|
|
);
|
|
|
|
result.x = sphereLatitude;
|
|
result.y = sphereLongitude;
|
|
|
|
return result;
|
|
}
|
|
|
|
var sphericalScratch = new Cartesian2();
|
|
/**
|
|
* Gets an attributes object containing:
|
|
* - the southwest corner of a rectangular area in spherical coordinates, as well as the inverse of the latitude/longitude range.
|
|
* These are computed using the same atan2 approximation used in the shader.
|
|
* - 1 texture coordinate rotation GeometryInstanceAttributes
|
|
* - 2 GeometryInstanceAttributes used to compute high-precision points in 2D and Columbus View.
|
|
* These points are used to compute eye-space planes like above.
|
|
*
|
|
* Used when computing texture coordinates for large-area ClassificationPrimitives with materials or
|
|
* multiple non-overlapping instances.
|
|
* @see ShadowVolumeAppearance
|
|
* @private
|
|
*
|
|
* @param {Rectangle} boundingRectangle Rectangle object that the spherical extents will approximately bound
|
|
* @param {Number[]} textureCoordinateRotationPoints Points in the computed texture coordinate system for remapping texture coordinates
|
|
* @param {Ellipsoid} ellipsoid Ellipsoid for converting Rectangle points to world coordinates
|
|
* @param {MapProjection} projection The MapProjection used for 2D and Columbus View.
|
|
* @returns {Object} An attributes dictionary containing spherical texture coordinate attributes.
|
|
*/
|
|
ShadowVolumeAppearance.getSphericalExtentGeometryInstanceAttributes = function (
|
|
boundingRectangle,
|
|
textureCoordinateRotationPoints,
|
|
ellipsoid,
|
|
projection
|
|
) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.object("boundingRectangle", boundingRectangle);
|
|
Check.defined(
|
|
"textureCoordinateRotationPoints",
|
|
textureCoordinateRotationPoints
|
|
);
|
|
Check.typeOf.object("ellipsoid", ellipsoid);
|
|
Check.typeOf.object("projection", projection);
|
|
//>>includeEnd('debug');
|
|
|
|
// rectangle cartographic coords !== spherical because it's on an ellipsoid
|
|
var southWestExtents = latLongToSpherical(
|
|
boundingRectangle.south,
|
|
boundingRectangle.west,
|
|
ellipsoid,
|
|
sphericalScratch
|
|
);
|
|
|
|
var south = southWestExtents.x;
|
|
var west = southWestExtents.y;
|
|
|
|
var northEastExtents = latLongToSpherical(
|
|
boundingRectangle.north,
|
|
boundingRectangle.east,
|
|
ellipsoid,
|
|
sphericalScratch
|
|
);
|
|
var north = northEastExtents.x;
|
|
var east = northEastExtents.y;
|
|
|
|
// If the bounding rectangle crosses the IDL, rotate the spherical extents so the cross no longer happens.
|
|
// This rotation must happen in the shader too.
|
|
var rotationRadians = 0.0;
|
|
if (west > east) {
|
|
rotationRadians = CesiumMath.PI - west;
|
|
west = -CesiumMath.PI;
|
|
east += rotationRadians;
|
|
}
|
|
|
|
// Slightly pad extents to avoid floating point error when fragment culling at edges.
|
|
south -= CesiumMath.EPSILON5;
|
|
west -= CesiumMath.EPSILON5;
|
|
north += CesiumMath.EPSILON5;
|
|
east += CesiumMath.EPSILON5;
|
|
|
|
var longitudeRangeInverse = 1.0 / (east - west);
|
|
var latitudeRangeInverse = 1.0 / (north - south);
|
|
|
|
var attributes = {
|
|
sphericalExtents: new GeometryInstanceAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 4,
|
|
normalize: false,
|
|
value: [south, west, latitudeRangeInverse, longitudeRangeInverse],
|
|
}),
|
|
longitudeRotation: new GeometryInstanceAttribute({
|
|
componentDatatype: ComponentDatatype.FLOAT,
|
|
componentsPerAttribute: 1,
|
|
normalize: false,
|
|
value: [rotationRadians],
|
|
}),
|
|
};
|
|
|
|
addTextureCoordinateRotationAttributes(
|
|
attributes,
|
|
textureCoordinateRotationPoints
|
|
);
|
|
add2DTextureCoordinateAttributes(boundingRectangle, projection, attributes);
|
|
return attributes;
|
|
};
|
|
|
|
ShadowVolumeAppearance.hasAttributesForTextureCoordinatePlanes = function (
|
|
attributes
|
|
) {
|
|
return (
|
|
defined(attributes.southWest_HIGH) &&
|
|
defined(attributes.southWest_LOW) &&
|
|
defined(attributes.northward) &&
|
|
defined(attributes.eastward) &&
|
|
defined(attributes.planes2D_HIGH) &&
|
|
defined(attributes.planes2D_LOW) &&
|
|
defined(attributes.uMaxVmax) &&
|
|
defined(attributes.uvMinAndExtents)
|
|
);
|
|
};
|
|
|
|
ShadowVolumeAppearance.hasAttributesForSphericalExtents = function (
|
|
attributes
|
|
) {
|
|
return (
|
|
defined(attributes.sphericalExtents) &&
|
|
defined(attributes.longitudeRotation) &&
|
|
defined(attributes.planes2D_HIGH) &&
|
|
defined(attributes.planes2D_LOW) &&
|
|
defined(attributes.uMaxVmax) &&
|
|
defined(attributes.uvMinAndExtents)
|
|
);
|
|
};
|
|
|
|
function shouldUseSpherical(rectangle) {
|
|
return (
|
|
Math.max(rectangle.width, rectangle.height) >
|
|
ShadowVolumeAppearance.MAX_WIDTH_FOR_PLANAR_EXTENTS
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Computes whether the given rectangle is wide enough that texture coordinates
|
|
* over its area should be computed using spherical extents instead of distance to planes.
|
|
*
|
|
* @param {Rectangle} rectangle A rectangle
|
|
* @private
|
|
*/
|
|
ShadowVolumeAppearance.shouldUseSphericalCoordinates = function (rectangle) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.object("rectangle", rectangle);
|
|
//>>includeEnd('debug');
|
|
|
|
return shouldUseSpherical(rectangle);
|
|
};
|
|
|
|
/**
|
|
* Texture coordinates for ground primitives are computed either using spherical coordinates for large areas or
|
|
* using distance from planes for small areas.
|
|
*
|
|
* @type {Number}
|
|
* @constant
|
|
* @private
|
|
*/
|
|
ShadowVolumeAppearance.MAX_WIDTH_FOR_PLANAR_EXTENTS = CesiumMath.toRadians(1.0);
|
|
export default ShadowVolumeAppearance;
|