Cesium-Examples/examples/cesiumEx/viewshed.js
2025-06-22 15:06:26 +08:00

523 lines
22 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 可视域分析。
*
* @author Helsing
* @date 2020/08/28
* @alias viewshed
* @class
* @param {Cesium.Viewer} viewer Cesium三维视窗。
* @param {Object} options 选项。
* @param {Cesium.Cartesian3} options.viewPosition 观测点位置。
* @param {Cesium.Cartesian3} options.viewPositionEnd 最远观测点位置(如果设置了观测距离,这个属性可以不设置)。
* @param {Number} options.viewDistance 观测距离(单位`米`默认值100
* @param {Number} options.viewHeading 航向角(单位`度`默认值0
* @param {Number} options.viewPitch 俯仰角(单位`度`默认值0
* @param {Number} options.horizontalViewAngle 可视域水平夹角(单位`度`默认值90
* @param {Number} options.verticalViewAngle 可视域垂直夹角(单位`度`默认值60
* @param {Cesium.Color} options.visibleAreaColor 可视区域颜色(默认值`绿色`)。
* @param {Cesium.Color} options.invisibleAreaColor 不可视区域颜色(默认值`红色`)。
* @param {Boolean} options.enabled 阴影贴图是否可用。
* @param {Boolean} options.softShadows 是否启用柔和阴影。
* @param {Boolean} options.size 每个阴影贴图的大小。
*/
class viewshed extends analyser {
constructor(viewer, options) {
super(viewer);
this.viewer = viewer;
this.viewPosition = options.viewPosition;
this.viewPositionEnd = options.viewPositionEnd;
this.viewDistance = this.viewPositionEnd ? Cesium.Cartesian3.distance(this.viewPosition, this.viewPositionEnd) : (options.viewDistance || 100.0);
this.viewHeading = this.viewPositionEnd ? getHeading(this.viewPosition, this.viewPositionEnd) : (options.viewHeading || 0.0);
this.viewPitch = this.viewPositionEnd ? getPitch(this.viewPosition, this.viewPositionEnd) : (options.viewPitch || 0.0);
this.horizontalViewAngle = options.horizontalViewAngle || 90.0;
this.verticalViewAngle = options.verticalViewAngle || 60.0;
this.visibleAreaColor = options.visibleAreaColor || Cesium.Color.GREEN;
this.invisibleAreaColor = options.invisibleAreaColor || Cesium.Color.RED;
this.enabled = (typeof options.enabled === "boolean") ? options.enabled : true;
this.softShadows = (typeof options.softShadows === "boolean") ? options.softShadows : true;
this.size = options.size || 2048;
this.options = options;
this.id = Cesium.createGuid();
this._resultTip = this.viewer.entities.add({
id: this.id,
label: {
fillColor: Cesium.Color.YELLOW,
showBackground: true,
font: '14px monospace',
horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -10)
}
});
this.posArray = [];
this._markers = [];
this.state = this.BEYONANALYSER_STATE.PREPARE;
this.action();
}
action() {
let _self = this;
var ellipsoid = this.viewer.scene.globe.ellipsoid;
_self.handler.setInputAction(function (movement) {
var cartesian = latlng.getCurrentMousePosition(_self.viewer.scene, movement.position);
if (_self._markers.length == 0) {
var temp1 = Cesium.Cartographic.fromCartesian( cartesian );
var h1 ;
if (_self.options.qdOffset) {
h1 = temp1.height + _self.options.qdOffset
}else{
h1 = temp1.height + 1;
}
var cartographictemp = Cesium.Cartographic.fromDegrees( temp1.longitude / Math.PI * 180, temp1.latitude / Math.PI * 180, h1);
cartesian = ellipsoid.cartographicToCartesian(cartographictemp);
} else if (_self._markers.length == 1) {
var temp1 = Cesium.Cartographic.fromCartesian( cartesian );
var h1 ;
if (_self.options.zdOffset) {
h1 = temp1.height + _self.options.qdOffset
}else{
h1 = temp1.height + 1;
}
var cartographictemp = Cesium.Cartographic.fromDegrees( temp1.longitude / Math.PI * 180, temp1.latitude / Math.PI * 180, h1);
cartesian = ellipsoid.cartographicToCartesian(cartographictemp);
}
if (!cartesian) {
return;
}
_self.posArray.push(cartesian);
if (_self._markers.length == 0) {
var startSphere = _self.viewer.entities.add({
position: cartesian,
ellipsoid: {
radii: new Cesium.Cartesian3(1.0, 1.0, 1.0),
material: Cesium.Color.BLUE
},
label: {
text: "视线起点",
fillColor: Cesium.Color.YELLOW,
pixelOffset: {
x: 0,
y: -20
},
scale: 0.5
}
});
_self._markers.push(startSphere);
_self.state = _self.BEYONANALYSER_STATE.OPERATING;
} else if (_self._markers.length == 1) {
var redSphere = _self.viewer.entities.add({
position: cartesian,
ellipsoid: {
radii: new Cesium.Cartesian3(1.0, 1.0, 1.0),
material: Cesium.Color.RED
},
label: {
text: "视线终点",
fillColor: Cesium.Color.YELLOW,
pixelOffset: {
x: 0,
y: -20
},
scale: 0.5
}
});
_self._markers.push(redSphere);
_self.viewPosition = _self.posArray[0];
_self.viewPositionEnd = cartesian;
_self.viewDistance = _self.viewPositionEnd ? Cesium.Cartesian3.distance(_self.viewPosition, _self.viewPositionEnd) : (_self.options.viewDistance || 100.0);
_self.viewHeading = _self.viewPositionEnd ? getHeading(_self.viewPosition, _self.viewPositionEnd) : (_self.options.viewHeading || 0.0);
_self.viewPitch = _self.viewPositionEnd ? getPitch(_self.viewPosition, _self.viewPositionEnd) : (_self.options.viewPitch || 0.0);
_self.state = _self.BEYONANALYSER_STATE.END;
_self.handler.destroy();
_self.handler = null;
_self.remove();
_self.update();
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//移动
var info;
_self.handler.setInputAction(function (movement) {
var cartesian = _self.viewer.scene.pickPosition(movement.endPosition);
if (_self.state === _self.BEYONANALYSER_STATE.PREPARE) {
info = '点击设定起点';
_self.showTip(_self._resultTip, true, cartesian, info);
} else if (_self.state === _self.BEYONANALYSER_STATE.OPERATING) {
info = '点击设定终点';
_self.showTip(_self._resultTip, true, cartesian, info);
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
}
remove() {
if (this._markers.length == 0) {
return false;
}
for (let index = 0; index < this._markers.length; index++) {
var element = this._markers[index];
this.viewer.entities.remove(element);
}
this._markers.length = 0;
this.viewer.entities.remove(this._resultTip);
this._resultTip = undefined;
}
add() {
this.createLightCamera();
this.createShadowMap();
this.createPostStage();
this.drawFrustumOutline();
this.drawSketch();
}
update() {
this.clear();
this.add();
}
clear() {
if (this.sketch) {
this.viewer.entities.removeById(this.sketch.id);
this.sketch = null;
}
if (this.frustumOutline) {
this.frustumOutline.destroy();
this.frustumOutline = null;
}
if (this.postStage) {
this.viewer.scene.postProcessStages.remove(this.postStage);
this.postStage = null;
}
}
//创建相机
createLightCamera() {
this.lightCamera = new Cesium.Camera(this.viewer.scene);
this.lightCamera.position = this.viewPosition;
// if (this.viewPositionEnd) {
// let direction = Cesium.Cartesian3.normalize(Cesium.Cartesian3.subtract(this.viewPositionEnd, this.viewPosition, new Cesium.Cartesian3()), new Cesium.Cartesian3());
// this.lightCamera.direction = direction; // direction是相机面向的方向
// }
this.lightCamera.frustum.near = this.viewDistance * 0.001;
this.lightCamera.frustum.far = this.viewDistance;
const hr = Cesium.Math.toRadians(this.horizontalViewAngle);
const vr = Cesium.Math.toRadians(this.verticalViewAngle);
const aspectRatio =
(this.viewDistance * Math.tan(hr / 2) * 2) /
(this.viewDistance * Math.tan(vr / 2) * 2);
this.lightCamera.frustum.aspectRatio = aspectRatio;
if (hr > vr) {
this.lightCamera.frustum.fov = hr;
} else {
this.lightCamera.frustum.fov = vr;
}
this.lightCamera.setView({
destination: this.viewPosition,
orientation: {
heading: Cesium.Math.toRadians(this.viewHeading || 0),
pitch: Cesium.Math.toRadians(this.viewPitch || 0),
roll: 0
}
});
}
//创建阴影贴图
createShadowMap() {
this.shadowMap = new Cesium.ShadowMap({
context: (this.viewer.scene).context,
lightCamera: this.lightCamera,
enabled: this.enabled,
isPointLight: true,
pointLightRadius: this.viewDistance,
cascadesEnabled: false,
size: this.size,
softShadows: this.softShadows,
normalOffset: false,
fromLightSource: false,
});
this.viewer.scene.shadowMap = this.shadowMap;
// 启用地形阴影
this.viewer.scene.globe.shadows = Cesium.ShadowMode.ENABLED;
}
//创建PostStage
createPostStage() {
const fs = `
#define USE_CUBE_MAP_SHADOW true
uniform sampler2D colorTexture;
uniform sampler2D depthTexture;
varying vec2 v_textureCoordinates;
uniform mat4 camera_projection_matrix;
uniform mat4 camera_view_matrix;
uniform samplerCube shadowMap_textureCube;
uniform mat4 shadowMap_matrix;
uniform vec4 shadowMap_lightPositionEC;
uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness;
uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth;
uniform float helsing_viewDistance;
uniform vec4 helsing_visibleAreaColor;
uniform vec4 helsing_invisibleAreaColor;
struct zx_shadowParameters
{
vec3 texCoords;
float depthBias;
float depth;
float nDotL;
vec2 texelStepSize;
float normalShadingSmooth;
float darkness;
};
float czm_shadowVisibility(samplerCube shadowMap, zx_shadowParameters shadowParameters)
{
float depthBias = shadowParameters.depthBias;
float depth = shadowParameters.depth;
float nDotL = shadowParameters.nDotL;
float normalShadingSmooth = shadowParameters.normalShadingSmooth;
float darkness = shadowParameters.darkness;
vec3 uvw = shadowParameters.texCoords;
depth -= depthBias;
float visibility = czm_shadowDepthCompare(shadowMap, uvw, depth);
return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);
}
vec4 getPositionEC(){
return czm_windowToEyeCoordinates(gl_FragCoord);
}
vec3 getNormalEC(){
return vec3(1.);
}
vec4 toEye( vec2 uv, float depth){
vec2 xy=vec2((uv.x*2.-1.),(uv.y*2.-1.));
vec4 posInCamera=czm_inverseProjection*vec4(xy,depth,1.);
posInCamera=posInCamera/posInCamera.w;
return posInCamera;
}
vec3 pointProjectOnPlane( vec3 planeNormal, vec3 planeOrigin, vec3 point){
vec3 v01=point-planeOrigin;
float d=dot(planeNormal,v01);
return(point-planeNormal*d);
}
float getDepth( vec4 depth){
float z_window=czm_unpackDepth(depth);
z_window=czm_reverseLogDepth(z_window);
float n_range=czm_depthRange.near;
float f_range=czm_depthRange.far;
return(2.*z_window-n_range-f_range)/(f_range-n_range);
}
float shadow( vec4 positionEC){
vec3 normalEC=getNormalEC();
zx_shadowParameters shadowParameters;
shadowParameters.texelStepSize=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy;
shadowParameters.depthBias=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z;
shadowParameters.normalShadingSmooth=shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w;
shadowParameters.darkness=shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w;
vec3 directionEC=positionEC.xyz-shadowMap_lightPositionEC.xyz;
float distance=length(directionEC);
directionEC=normalize(directionEC);
float radius=shadowMap_lightPositionEC.w;
if(distance>radius)
{
return 2.0;
}
vec3 directionWC=czm_inverseViewRotation*directionEC;
shadowParameters.depth=distance/radius-0.0003;
shadowParameters.nDotL=clamp(dot(normalEC,-directionEC),0.,1.);
shadowParameters.texCoords=directionWC;
float visibility=czm_shadowVisibility(shadowMap_textureCube,shadowParameters);
return visibility;
}
bool visible( vec4 result)
{
result.x/=result.w;
result.y/=result.w;
result.z/=result.w;
return result.x>=-1.&&result.x<=1.
&&result.y>=-1.&&result.y<=1.
&&result.z>=-1.&&result.z<=1.;
}
void main(){
// 釉色 = 结构二维(颜色纹理, 纹理坐标)
gl_FragColor = texture2D(colorTexture, v_textureCoordinates);
// 深度 = 获取深度(结构二维(深度纹理, 纹理坐标))
float depth = getDepth(texture2D(depthTexture, v_textureCoordinates));
// 视角 = (纹理坐标, 深度)
vec4 viewPos = toEye(v_textureCoordinates, depth);
// 世界坐标
vec4 wordPos = czm_inverseView * viewPos;
// 虚拟相机中坐标
vec4 vcPos = camera_view_matrix * wordPos;
float near = .001 * helsing_viewDistance;
float dis = length(vcPos.xyz);
if(dis > near && dis < helsing_viewDistance){
// 透视投影
vec4 posInEye = camera_projection_matrix * vcPos;
// 可视区颜色
// vec4 helsing_visibleAreaColor=vec4(0.,1.,0.,.5);
// vec4 helsing_invisibleAreaColor=vec4(1.,0.,0.,.5);
if(visible(posInEye)){
float vis = shadow(viewPos);
if(vis > 0.3){
gl_FragColor = mix(gl_FragColor,helsing_visibleAreaColor,.5);
} else{
gl_FragColor = mix(gl_FragColor,helsing_invisibleAreaColor,.5);
}
}
}
}`;
const postStage = new Cesium.PostProcessStage({
fragmentShader: fs,
uniforms: {
shadowMap_textureCube: () => {
this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
return Reflect.get(this.shadowMap, "_shadowMapTexture");
},
shadowMap_matrix: () => {
this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
return Reflect.get(this.shadowMap, "_shadowMapMatrix");
},
shadowMap_lightPositionEC: () => {
this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
return Reflect.get(this.shadowMap, "_lightPositionEC");
},
shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness: () => {
this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
const bias = this.shadowMap._pointBias;
return Cesium.Cartesian4.fromElements(
bias.normalOffsetScale,
this.shadowMap._distance,
this.shadowMap.maximumDistance,
0.0,
new Cesium.Cartesian4()
);
},
shadowMap_texelSizeDepthBiasAndNormalShadingSmooth: () => {
this.shadowMap.update(Reflect.get(this.viewer.scene, "_frameState"));
const bias = this.shadowMap._pointBias;
const scratchTexelStepSize = new Cesium.Cartesian2();
const texelStepSize = scratchTexelStepSize;
texelStepSize.x = 1.0 / this.shadowMap._textureSize.x;
texelStepSize.y = 1.0 / this.shadowMap._textureSize.y;
return Cesium.Cartesian4.fromElements(
texelStepSize.x,
texelStepSize.y,
bias.depthBias,
bias.normalShadingSmooth,
new Cesium.Cartesian4()
);
},
camera_projection_matrix: this.lightCamera.frustum.projectionMatrix,
camera_view_matrix: this.lightCamera.viewMatrix,
helsing_viewDistance: () => {
return this.viewDistance;
},
helsing_visibleAreaColor: this.visibleAreaColor,
helsing_invisibleAreaColor: this.invisibleAreaColor,
}
});
this.postStage = this.viewer.scene.postProcessStages.add(postStage);
}
//创建视锥线
drawFrustumOutline() {
const scratchRight = new Cesium.Cartesian3();
const scratchRotation = new Cesium.Matrix3();
const scratchOrientation = new Cesium.Quaternion();
const position = this.lightCamera.positionWC;
const direction = this.lightCamera.directionWC;
const up = this.lightCamera.upWC;
let right = this.lightCamera.rightWC;
right = Cesium.Cartesian3.negate(right, scratchRight);
let rotation = scratchRotation;
Cesium.Matrix3.setColumn(rotation, 0, right, rotation);
Cesium.Matrix3.setColumn(rotation, 1, up, rotation);
Cesium.Matrix3.setColumn(rotation, 2, direction, rotation);
let orientation = Cesium.Quaternion.fromRotationMatrix(rotation, scratchOrientation);
let instance = new Cesium.GeometryInstance({
geometry: new Cesium.FrustumOutlineGeometry({
frustum: this.lightCamera.frustum,
origin: this.viewPosition,
orientation: orientation
}),
id: Math.random().toString(36).substr(2),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(
Cesium.Color.YELLOWGREEN //new Cesium.Color(0.0, 1.0, 0.0, 1.0)
),
show: new Cesium.ShowGeometryInstanceAttribute(true)
}
});
this.frustumOutline = this.viewer.scene.primitives.add(
new Cesium.Primitive({
geometryInstances: [instance],
appearance: new Cesium.PerInstanceColorAppearance({
flat: true,
translucent: false
})
})
);
}
//创建视网
drawSketch() {
this.sketch = this.viewer.entities.add({
name: 'sketch',
position: this.viewPosition,
orientation: Cesium.Transforms.headingPitchRollQuaternion(
this.viewPosition,
Cesium.HeadingPitchRoll.fromDegrees(this.viewHeading - this.horizontalViewAngle, this.viewPitch, 0.0)
),
ellipsoid: {
radii: new Cesium.Cartesian3(
this.viewDistance,
this.viewDistance,
this.viewDistance
),
// innerRadii: new Cesium.Cartesian3(2.0, 2.0, 2.0),
minimumClock: Cesium.Math.toRadians(-this.horizontalViewAngle / 2),
maximumClock: Cesium.Math.toRadians(this.horizontalViewAngle / 2),
minimumCone: Cesium.Math.toRadians(this.verticalViewAngle + 7.75),
maximumCone: Cesium.Math.toRadians(180 - this.verticalViewAngle - 7.75),
fill: false,
outline: true,
subdivisions: 256,
stackPartitions: 64,
slicePartitions: 64,
outlineColor: Cesium.Color.YELLOWGREEN
}
});
}
}
//获取偏航角
function getHeading(fromPosition, toPosition) {
let finalPosition = new Cesium.Cartesian3();
let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
Cesium.Matrix4.inverse(matrix4, matrix4);
Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
Cesium.Cartesian3.normalize(finalPosition, finalPosition);
return Cesium.Math.toDegrees(Math.atan2(finalPosition.x, finalPosition.y));
}
//获取俯仰角
function getPitch(fromPosition, toPosition) {
let finalPosition = new Cesium.Cartesian3();
let matrix4 = Cesium.Transforms.eastNorthUpToFixedFrame(fromPosition);
Cesium.Matrix4.inverse(matrix4, matrix4);
Cesium.Matrix4.multiplyByPoint(matrix4, toPosition, finalPosition);
Cesium.Cartesian3.normalize(finalPosition, finalPosition);
return Cesium.Math.toDegrees(Math.asin(finalPosition.z));
}