// * 创建视锥 import * as Cesium from 'cesium' function computeFarCenter (position, hpr, far) { const quaternion = Cesium.Transforms.headingPitchRollQuaternion(position, hpr) const rotMat = Cesium.Matrix3.fromQuaternion(quaternion) const dirLocal = new Cesium.Cartesian3(0, 0, far) const dirWorld = Cesium.Matrix3.multiplyByVector(rotMat, dirLocal, new Cesium.Cartesian3()) return Cesium.Cartesian3.add(position, dirWorld, new Cesium.Cartesian3()) } /* eslint-disable new-cap */ /* eslint-disable no-undef */ export default class CreateFrustum { constructor(viewer, options) { this._isShowVideoPlan = options.isShowVideoPlan || false this.position = options.position this._viewer = viewer this.fov = options.fov || 0 this.near = options.near || 0.01 this.far = options.far || 0 this.heading = options.heading || 0 this.pitch = options.pitch || 0 this.roll = options.roll || 0 this.width = options.width this.height = options.height this.videoScreen = null this.cameraPosition = Cesium.Cartesian3.fromDegrees( this.position.longitude, this.position.latitude, this.position.altitude ) this.hpr = Cesium.HeadingPitchRoll.fromDegrees(this.heading, this.pitch, this.roll) this.orientation = Cesium.Transforms.headingPitchRollQuaternion(this.cameraPosition, this.hpr) this._entityVideoDataSource = null this._entityVideoDataSource = new Cesium.CustomDataSource('entityVideoDataSource') this._viewer.dataSources.add(this._entityVideoDataSource) this._primitiveCollection = null this._primitiveCollection = new Cesium.PrimitiveCollection() this._viewer.scene.primitives.add(this._primitiveCollection) this.add() } // 创建视锥体和轮廓线 add () { this.clear() this._viewer.scene.globe.depthTestAgainstTerrain = true this.addFrustum() this.addOutline() this.addCenterline() } // 创建视锥体 addFrustum () { const frustum = new Cesium.PerspectiveFrustum({ // 查看的视场角,绕Z轴旋转,以弧度方式输入 fov: Cesium.Math.toRadians(this.fov), // 视锥体的宽度/高度 aspectRatio: this.width / this.height, // 近面距视点的距离 near: this.near, // 远面距视点的距离 far: this.far, }) const frustumGeometry = new Cesium.FrustumGeometry({ frustum: frustum, origin: this.cameraPosition, orientation: this.orientation, vertexFormat: Cesium.VertexFormat.POSITION_ONLY, }) const instance = new Cesium.GeometryInstance({ geometry: frustumGeometry, attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.fromBytes(0, 213, 144, 20) ), }, }) const primitive = new Cesium.Primitive({ geometryInstances: instance, releaseGeometryInstances: false, appearance: new Cesium.PerInstanceColorAppearance({ closed: true, flat: true, translucent: true }), asynchronous: false, }) this._primitiveCollection.add(primitive) // if (this.roll > 135) { // this.initVideoPolygon(frustumGeometry) // } } // 创建轮廓线 addOutline () { const frustum = new Cesium.PerspectiveFrustum({ // 查看的视场角度,绕Z轴旋转,以弧度方式输入 // The angle of the field of view (FOV), in radians. // This angle will be used as the horizontal FOV if the width is greater than the height, otherwise it will be the vertical FOV. fov: Cesium.Math.toRadians(this.fov), // 视锥体的宽度/高度 aspectRatio: this.width / this.height, // 近面距视点的距离 near: this.near, // 远面距视点的距离 far: this.far, }) const frustumGeometry = new Cesium.FrustumOutlineGeometry({ frustum: frustum, origin: this.cameraPosition, orientation: this.orientation, vertexFormat: Cesium.VertexFormat.POSITION_ONLY, }) const instance = new Cesium.GeometryInstance({ geometry: frustumGeometry, attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.fromBytes(0, 213, 144, 255) ), }, }) const primitive = new Cesium.Primitive({ geometryInstances: instance, appearance: new Cesium.PerInstanceColorAppearance({ closed: true, flat: true, translucent: true }), asynchronous: false, }) this._primitiveCollection.add(primitive) } addCenterline () { let matrix = Cesium.Matrix3.fromQuaternion( this.orientation, new Cesium.Matrix3() ) let right1 = Cesium.Matrix3.getColumn(matrix, 0, new Cesium.Cartesian3()) let up1 = Cesium.Matrix3.getColumn(matrix, 1, new Cesium.Cartesian3()) let direction = Cesium.Matrix3.getColumn(matrix, 2, new Cesium.Cartesian3()) let frontDirect = Cesium.Cartesian3.multiplyByScalar( direction, this.far, new Cesium.Cartesian3() ) let bottomCenter = Cesium.Cartesian3.add(this.cameraPosition, frontDirect, new Cesium.Cartesian3()) // 创建Entity来显示中心线 this._entityVideoDataSource?.entities.add({ name: 'Center Line of Frustum', polyline: { positions: [this.cameraPosition, bottomCenter], width: 1, // material: Cesium.Color.RED.withAlpha(0.5) material: new Cesium.PolylineDashMaterialProperty({ color: Cesium.Color.fromBytes(0, 213, 144, 255), dashLength: 30.0, gapColor: Cesium.Color.TRANSPARENT, gapWidth: 1.0, }), }, }) const ray = new Cesium.Ray() ray.origin = this.cameraPosition // 计算方向向量(终点 - 起点)并归一化 ray.direction = Cesium.Cartesian3.normalize( Cesium.Cartesian3.subtract(bottomCenter, this.cameraPosition, new Cesium.Cartesian3()), new Cesium.Cartesian3() ) const pickPosition = this._viewer.scene.globe.pick(ray, this._viewer.scene) let distance = null if (pickPosition) { // 计算交点与A、B的距离 distance = Cesium.Cartesian3.distance(this.cameraPosition, pickPosition) } // 视椎体投放视频 // if (this.roll <= 135) { // this._isShowVideoPlan && this.initVideoPlan(distance) // } } initVideoPlan (distance = null) { const element = document.querySelector('.video-player') if (element === '') return let curFar = distance != null && distance < this.far ? distance - 10 : this.far let width = curFar * Math.tan(Cesium.Math.toRadians(this.fov) * 0.5) * 2 let height = width / (this.width / this.height) this._entityVideoDataSource.entities.add({ position: new Cesium.CallbackProperty(() => { return computeFarCenter(this.cameraPosition, this.hpr, curFar) }, false), orientation: new Cesium.CallbackProperty(() => { return this.orientation }, false), plane: { plane: new Cesium.Plane(new Cesium.Cartesian3(0, 0, -1), 0), dimensions: new Cesium.Cartesian2(width, height), material: element, }, }) } initVideoPolygon (frustumGeometry) { const element = document.querySelector('.video-player') if (element === '') return // 取出视锥体的顶点坐标 const geometry = Cesium.FrustumOutlineGeometry.createGeometry(frustumGeometry) const result = [] for (let i = 0; i < geometry.attributes.position.values.length; i += 3) { result.push(Array.from(geometry.attributes.position.values).slice(i, i + 3)) } // 将顶点分为近面(前4个)和远面(后4个) const nearVertices = result.slice(0, 4) const farVertices = result.slice(4, 8) let videoPolygon = [] let videoIntersectionPolygon = [] // 处理四条棱线(近面顶点到远面对应顶点) for (let i = 0; i < 4; i++) { const nearVertex = new Cesium.Cartesian3(...nearVertices[i]) const farVertex = new Cesium.Cartesian3(...farVertices[i]) const ray = new Cesium.Ray() ray.origin = nearVertex // 计算方向向量(终点 - 起点)并归一化 ray.direction = Cesium.Cartesian3.normalize( Cesium.Cartesian3.subtract(farVertex, nearVertex, new Cesium.Cartesian3()), new Cesium.Cartesian3() ) // 检测射线与地球表面交点 const pickPosition = this._viewer.scene.globe.pick(ray, this._viewer.scene) // 有交点则使用交点,否则使用远面顶点 if (pickPosition) { videoIntersectionPolygon.push(pickPosition) } videoPolygon.push(farVertex) } // 获得视锥体和地面的交点,构成平面,并使用video标签做为材质 if (videoPolygon.length === 4) { if (videoIntersectionPolygon.length < 4) { this._entityVideoDataSource.entities.add({ polygon: { hierarchy: new Cesium.PolygonHierarchy(videoPolygon), material: element, stRotation: +Cesium.Math.toRadians(this.heading), perPositionHeight: true, heightReference: Cesium.HeightReference.NONE }, orientation: new Cesium.CallbackProperty(() => { return this.orientation }, false), }) } else { this._entityVideoDataSource.entities.add({ polygon: { hierarchy: new Cesium.PolygonHierarchy(videoIntersectionPolygon), material: element, stRotation: Cesium.Math.toRadians(135), perPositionHeight: false, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND }, // orientation: new Cesium.CallbackProperty(() => { // return this.orientation // }, false), }) } } } // 清除视锥体和轮廓线 clear () { if (this._viewer) { this._viewer.scene.globe.depthTestAgainstTerrain = false if (this._primitiveCollection) { this._primitiveCollection.removeAll() } if (this._entityVideoDataSource) { this._entityVideoDataSource?.entities.removeAll() } } } destroyed () { if (this._viewer) { this._viewer.scene.globe.depthTestAgainstTerrain = false if (this._primitiveCollection) { this._primitiveCollection.removeAll() this._viewer.scene.primitives.remove(this._primitiveCollection) this._primitiveCollection = null } if (this._entityVideoDataSource) { this._entityVideoDataSource?.entities.removeAll() this._viewer.dataSources.remove(this._entityVideoDataSource, true) this._entityVideoDataSource = null } } } UpdateWidth (value) { this.width = value this.add() } UpdateHeight (value) { this.height = value this.add() } UpdateFov (value) { this.fov = value this.add() } UpdateNear (value) { this.near = value this.add() } UpdateFar (value) { this.far = value this.add() } UpdateHeading (value) { this.heading = value this.add() } UpdatePitch (value) { this.pitch = value this.add() } UpdateRoll (value) { this.roll = value this.add() } UpdataPosition (position) { if (!position) return this.position = position this.add() } // 更新视锥体的姿态 Update (position, headingPitchRoll) { this.position = Cesium.Cartesian3.fromDegrees( position.longitude, position.latitude, position.altitude ) this.orientation = Cesium.Transforms.headingPitchRollQuaternion( this.position, new Cesium.HeadingPitchRoll.fromDegrees( headingPitchRoll.heading, headingPitchRoll.pitch, headingPitchRoll.roll ) ) this.add() } }