export default class Frustum { constructor(options, viewer, viewer1) { this.options = { ...options } this.viewer = viewer this.viewer1 = viewer1 this.head = 0 this.pitch = 90 this.po = 0.00001 this.position = null this.hpr = null this.currentFrustumOutline = null this.frustum = null this.setInterval1 = null this.webrtc = null // 设置默认值 Frustum.setDefaultValue(this) this.create() } static setDefaultValue(that) { that.options.position = that.options.position || {} that.options.fov = that.options.fov || 30 that.options.aspectRatio = that.options.aspectRatio || 1 that.options.near = that.options.near || 1 that.options.far = that.options.far || 120 that.options.heading = that.options.heading || 0 that.options.pitch = that.options.pitch || 90 that.options.roll = that.options.roll || 0 that.options.show = that.options.show ?? true that.options.videoUrl = that.options.videoUrl || '' that.options.index = that.options.index || 0 that.options.arr = that.options.arr || [] that.options.normalHeight = that.options.normalHeight || 100 } // 初始化视锥体 create() { this.frustum = new Cesium.PerspectiveFrustum({ fov: Cesium.Math.toRadians(this.options.fov), aspectRatio: this.options.aspectRatio, near: this.options.near, far: this.options.far }) const { lng, lat, alt } = this.options.position const { heading, pitch, roll } = this.options this.position = Cesium.Cartesian3.fromDegrees( lng, lat, alt + this.options.normalHeight ) this.hpr = new Cesium.HeadingPitchRoll( Cesium.Math.toRadians(heading), Cesium.Math.toRadians(pitch), Cesium.Math.toRadians(roll) ) this.drawFrustumOutline() this.drawFrustumFilled() this.monitorKeyboard() this.updateFrustumSquareBase(40) this.syncHpr() if (this.options.videoUrl) { this.addVideoToFrustumTop2() } } // 监听键盘事件 monitorKeyboard() { const keyActions = { KeyQ: () => this.setIntervalhpr(-0.45), KeyE: () => this.setIntervalhpr(0.45), KeyB: () => this.setIntervalhprr(-0.45), KeyN: () => this.setIntervalhprr(0.45), KeyW: () => this.updateFrustumPosition('move', -0.00001), KeyS: () => this.updateFrustumPosition('move', 0.00001), KeyA: () => this.updateFrustumPosition('move', -0.00001, 0), KeyD: () => this.updateFrustumPosition('move', 0.00001, 0), KeyC: () => this.updateFrustumHeight(1), // 增加高度 KeyZ: () => this.updateFrustumHeight(-1) // 降低高度 } this.keydownHandler = event => { if (keyActions[event.code]) keyActions[event.code]() } this.keyupHandler = () => this.stopFrustumRotation() document.addEventListener('keydown', this.keydownHandler) document.addEventListener('keyup', this.keyupHandler) } // 渲染视频 addVideoToFrustumTop() { // 创建视频元素 const videoElement = document.createElement('video') videoElement.width = 640 videoElement.height = 360 videoElement.autoplay = true videoElement.loop = true videoElement.muted = true // videoElement.style.display = 'none'; // 隐藏视频元素 document.body.appendChild(videoElement) // 使用 flv.js 播放 FLV 视频 if (flvjs.isSupported()) { const flvPlayer = flvjs.createPlayer({ // url: 'http://zmkg.cqet.top:9991/live/2pUbcgTrly3mIDuxsDXN9h3hqcEKU6TlsV_YeIDyqHqXGzXafqWokXdU1q6j_S7hTCP7HynZQIsuNM6KQ5l-ag==.flv', type: 'flv', isLive: true, hasAudio: false, enableStashBuffer: true, // enableWorker: true, autoCleanupSourceBuffer: true, //自动清除缓存 url: this.options.videoUrl }) flvPlayer.attachMediaElement(videoElement) flvPlayer.load() flvPlayer.play() } else { console.error('FLV.js is not supported in this browser.') } const corners = this.computeFrustumCorners( this.frustum, this.position, this.hpr ) // 创建 PolygonGeometry 并应用视频作为纹理 const polygonHierarchy = new Cesium.PolygonHierarchy([ corners.bottomLeft, corners.bottomRight, corners.topRight, corners.topLeft ]) this.videoEntity = this.viewer.entities.add( new Cesium.Entity({ id: '22222222', show: true, polygon: { hierarchy: polygonHierarchy } }) ) videoElement.addEventListener('loadeddata', () => { this.videoEntity.polygon.material = videoElement // 确保视频纹理加载后再设置 }) } // 渲染视频 async addVideoToFrustumTop2() { // 创建视频元素 const videoElement = document.createElement('video') videoElement.width = 640 videoElement.height = 360 videoElement.autoplay = true videoElement.loop = true videoElement.muted = true // videoElement.style.display = 'none'; // 隐藏视频元素 document.body.appendChild(videoElement) await this.startPlay(videoElement, this.options.videoUrl) const corners = this.computeFrustumCorners( this.frustum, this.position, this.hpr ) // 创建 PolygonGeometry 并应用视频作为纹理 const polygonHierarchy = new Cesium.PolygonHierarchy([ corners.bottomLeft, corners.bottomRight, corners.topRight, corners.topLeft ]) this.videoEntity = this.viewer.entities.add( new Cesium.Entity({ id: '22222222', show: true, polygon: { hierarchy: polygonHierarchy } }) ) videoElement.addEventListener('loadeddata', () => { this.videoEntity.polygon.material = videoElement // 确保视频纹理加载后再设置 }) } async startPlay(element, url) { // Close existing SDK instance if any if (this.webrtc) { this.webrtc.close() } // Initialize a new SDK instance this.webrtc = new SrsRtcWhipWhepAsync() // Bind the video player to the SDK stream element.srcObject = this.webrtc.stream try { const session = await this.webrtc.play(url) console.log(session) // this.sessionId = session.sessionid // this.simulatorUrl = `${session.simulator}?drop=1&username=${session.sessionid}` } catch (error) { // console.error('Error playing stream:', error) this.webrtc.close() // this.playerVisible = false } } // 计算视锥体远裁剪面(大面)的四个角点 computeFrustumCorners(frustum, position, hpr) { const tanFov = Math.tan(frustum.fov * 0.5) const farHeight = frustum.far * tanFov const farWidth = farHeight * frustum.aspectRatio const topLeft = new Cesium.Cartesian3(-farWidth, farHeight, -frustum.far) const topRight = new Cesium.Cartesian3(farWidth, farHeight, -frustum.far) const bottomLeft = new Cesium.Cartesian3( -farWidth, -farHeight, -frustum.far ) const bottomRight = new Cesium.Cartesian3( farWidth, -farHeight, -frustum.far ) const transform = Cesium.Transforms.headingPitchRollToFixedFrame( position, hpr ) // console.log('transform111111111111111111111111', transform) return { topLeft: Cesium.Matrix4.multiplyByPoint( transform, topLeft, new Cesium.Cartesian3() ), topRight: Cesium.Matrix4.multiplyByPoint( transform, topRight, new Cesium.Cartesian3() ), bottomLeft: Cesium.Matrix4.multiplyByPoint( transform, bottomLeft, new Cesium.Cartesian3() ), bottomRight: Cesium.Matrix4.multiplyByPoint( transform, bottomRight, new Cesium.Cartesian3() ) } } // 封装的函数:更新 Polygon 面的位置 updatePolygonPosition() { const corners = this.computeFrustumCorners( this.frustum, this.position, this.hpr ) this.videoEntity.polygon.hierarchy = new Cesium.CallbackProperty(e => { return new Cesium.PolygonHierarchy([ corners.bottomLeft, corners.bottomRight, corners.topRight, corners.topLeft ]) }) } // 更新锥体底部为正方形的方法 updateFrustumSquareBase(value) { // 将输入值范围从 56 到 1 映射到面积范围 10000 到 100 const minArea = 100 // 最小面积 const maxArea = 10000 // 最大面积 // 映射公式(反转映射) const newArea = ((56 - value) / (56 - 1)) * (maxArea - minArea) + minArea // 确保aspectRatio保持为1(正方形) this.frustum.aspectRatio = 1 // 根据面积计算正方形边长 const sideLength = Math.sqrt(newArea) // 远平面距离 const far = this.frustum.far // 计算新的fov const fov = 2 * Math.atan(sideLength / (2 * far)) // 更新视锥体的fov this.frustum.fov = fov // 重新绘制视锥体轮廓和填充 this.drawFrustumOutline() this.drawFrustumFilled() this.syncHpr() } updateFrustumHeight(deltaHeight) { const cartographic = Cesium.Cartographic.fromCartesian(this.position) cartographic.height += deltaHeight // 更新高度 this.position = Cesium.Cartesian3.fromDegrees( Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude), cartographic.height ) this.options.position.alt = cartographic.height // this.options.arr[ // this.options.index // ] = this.viewer.scene.globe.ellipsoid.cartographicToCartesian(cartographic) this.syncHpr() this.drawFrustumOutline() // 重新绘制视锥体轮廓 this.drawFrustumFilled() } // 更新position变化后的视锥体属性 updatePositionHeight(p) { this.options.position = this.cartesian3Towgs84(p) this.syncHpr() this.drawFrustumOutline() // 重新绘制视锥体轮廓 this.drawFrustumFilled() } cartesian3Towgs84(cartesian) { var ellipsoid = this.viewer.scene.globe.ellipsoid var cartesian3 = new Cesium.Cartesian3( cartesian.x, cartesian.y, cartesian.z ) var cartographic = ellipsoid.cartesianToCartographic(cartesian3) var lat = Cesium.Math.toDegrees(cartographic.latitude) var lng = Cesium.Math.toDegrees(cartographic.longitude) var alt = cartographic.height < 0 ? 0 : cartographic.height return { lng: lng, lat: lat, alt: alt } } setIntervalhpr(num) { this.stopFrustumRotation() // 先停止当前的定时器 this.setInterval1 = setInterval(() => { this.head += num this.updateFrustumHPR(Cesium.Math.toRadians(this.head), this.pitch) }, 10) } setIntervalhprr(num) { this.stopFrustumRotation() // 先停止当前的定时器 this.setInterval1 = setInterval(() => { // 限制 pitch 在 [60, 180] 范围内 this.pitch = Math.max(60, Math.min(180, this.pitch + num)) this.updateFrustumHPR(this.head, Cesium.Math.toRadians(this.pitch)) }, 10) } // 停止视锥体旋转 stopFrustumRotation() { if (this.setInterval1) { clearInterval(this.setInterval1) this.setInterval1 = null } } // 新增:绘制填充的视锥体 drawFrustumFilled() { let that = this // console.log('that.options.show', that.options.show) const transform = Cesium.Transforms.headingPitchRollToFixedFrame( this.position, this.hpr ) const frustumGeometry = new Cesium.FrustumGeometry({ frustum: this.frustum, origin: Cesium.Matrix4.getTranslation(transform, new Cesium.Cartesian3()), orientation: Cesium.Quaternion.fromRotationMatrix( Cesium.Matrix4.getRotation(transform, new Cesium.Matrix3()) ) }) if (this.currentFrustumFilled) { this.viewer.scene.primitives.remove(this.currentFrustumFilled) } this.currentFrustumFilled = new Cesium.Primitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: frustumGeometry, attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.fromAlpha(Cesium.Color.YELLOW, 0.4) // 半透明黄色填充 ) } }), appearance: new Cesium.MaterialAppearance({ material: Cesium.Material.fromType('Color', { color: Cesium.Color.fromAlpha(Cesium.Color.YELLOW, 0.4) // 填充颜色 }), translucent: true }), asynchronous: false, // show: false show: that.options.show }) this.viewer.scene.primitives.add(this.currentFrustumFilled) } // 绘制视锥体轮廓 drawFrustumOutline() { let that = this // console.log('that.options.show', that.options.show) const transform = Cesium.Transforms.headingPitchRollToFixedFrame( this.position, this.hpr ) const frustumOutlineGeometry = new Cesium.FrustumOutlineGeometry({ frustum: this.frustum, origin: Cesium.Matrix4.getTranslation(transform, new Cesium.Cartesian3()), orientation: Cesium.Quaternion.fromRotationMatrix( Cesium.Matrix4.getRotation(transform, new Cesium.Matrix3()) ) }) if (this.currentFrustumOutline) { this.viewer.scene.primitives.remove(this.currentFrustumOutline) } this.currentFrustumOutline = new Cesium.Primitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: frustumOutlineGeometry, attributes: { color: Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.YELLOW ) } }), appearance: new Cesium.PolylineColorAppearance({ translucent: false }), asynchronous: false, show: that.options.show // show: false }) this.viewer.scene.primitives.add(this.currentFrustumOutline) } // 更新视锥体位置 updateFrustumPosition(type = 'move', p, deg = 90, flag = true) { if (type === 'move') { // eslint-disable-next-line no-undef const point = turf.point([ this.options.position.lng, this.options.position.lat ]) const degreesValue = Cesium.Math.toDegrees(this.hpr.heading) const bearing = degreesValue + deg const options = { units: 'degrees' } // eslint-disable-next-line no-undef const destination = turf.destination(point, p, bearing, options).geometry .coordinates this.position = Cesium.Cartesian3.fromDegrees( destination[0], destination[1], this.options.position.alt + this.options.normalHeight ) this.options.position.lng = destination[0] this.options.position.lat = destination[1] this.viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees( destination[0], destination[1], this.viewer.camera.positionCartographic.height ) }) } if (type === 'update') { this.position = p this.options.videoUrl && this.updatePolygonPosition() } if (flag) { this.syncHpr() this.updateFrustumAttributes() } } // 同步视角 syncHpr() { // console.log('this.viewer1', this.viewer1); if (this.viewer1) { const { lng, lat, alt } = this.options.position let pitch = -this.hpr.pitch - Cesium.Math.toRadians(-90.0) this.viewer1.camera.setView({ destination: Cesium.Cartesian3.fromDegrees( lng, lat, alt + this.options.normalHeight ), orientation: { heading: this.hpr.heading + Cesium.Math.toRadians(-90.0), pitch, roll: this.hpr.roll } }) } } // 更新视锥体的 HeadingPitchRoll updateFrustumHPR( h = this.head, p = this.pitch, r = 0, flag = true, type = '' ) { function degreesToRadians(degrees) { return (degrees * Math.PI) / 180.0 } if (type == 'alone') { this.hpr.heading = degreesToRadians(h) this.hpr.pitch = degreesToRadians(p) this.hpr.roll = degreesToRadians(r) } else { this.hpr.heading = Cesium.Math.negativePiToPi(h) this.hpr.pitch = Cesium.Math.negativePiToPi(p) this.hpr.roll = Cesium.Math.negativePiToPi(r) } if (flag) { this.syncHpr() this.updateFrustumAttributes() } } // 用于更新 updateFrustumAttributes() { let that = this // 检查 position 和 hpr 是否已初始化 if (!this.position || !this.hpr) { // eslint-disable-next-line no-console console.error('Position or HPR is not defined:', this.position, this.hpr) return } // 生成变换矩阵 const transform = Cesium.Transforms.headingPitchRollToFixedFrame( this.position, this.hpr ) if (!transform) { // eslint-disable-next-line no-console console.error('Transform generation failed.') return } try { // 准备轮廓几何体和外观 const outlineGeometry = new Cesium.FrustumOutlineGeometry({ frustum: this.frustum, origin: Cesium.Matrix4.getTranslation( transform, new Cesium.Cartesian3() ), orientation: Cesium.Quaternion.fromRotationMatrix( Cesium.Matrix4.getRotation(transform, new Cesium.Matrix3()) ) }) const outlineAppearance = new Cesium.PolylineColorAppearance({ translucent: false }) const outlineColor = Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.YELLOW ) // 准备填充几何体和外观 const filledGeometry = new Cesium.FrustumGeometry({ frustum: this.frustum, origin: Cesium.Matrix4.getTranslation( transform, new Cesium.Cartesian3() ), orientation: Cesium.Quaternion.fromRotationMatrix( Cesium.Matrix4.getRotation(transform, new Cesium.Matrix3()) ) }) const filledAppearance = new Cesium.MaterialAppearance({ material: Cesium.Material.fromType('Color', { color: Cesium.Color.YELLOW.withAlpha(0.5) }), translucent: true }) const filledColor = Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.RED.withAlpha(0.5) ) // 删除旧的 Primitive if (this.currentFrustumOutline) { this.viewer.scene.primitives.remove(this.currentFrustumOutline) } if (this.currentFrustumFilled) { this.viewer.scene.primitives.remove(this.currentFrustumFilled) } // 创建并添加新的轮廓 Primitive this.currentFrustumOutline = new Cesium.Primitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: outlineGeometry, attributes: { color: outlineColor } }), appearance: outlineAppearance, asynchronous: false, show: that.options.show }) this.viewer.scene.primitives.add(this.currentFrustumOutline) // 创建并添加新的填充 Primitive this.currentFrustumFilled = new Cesium.Primitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: filledGeometry, attributes: { color: filledColor } }), appearance: filledAppearance, asynchronous: false, show: that.options.show }) this.viewer.scene.primitives.add(this.currentFrustumFilled) } catch (error) { // eslint-disable-next-line no-console console.error('Error in drawFrustum:', error) } } // 调整视锥体的 near 和 far 平面 updateFrustumNearFar(newNear, newFar) { this.frustum.near = newNear this.frustum.far = newFar this.drawFrustumOutline() this.drawFrustumFilled() } // 调整视锥体的 fov updateFrustumFov(newFov) { this.frustum.fov = Cesium.Math.toRadians(newFov) this.drawFrustumOutline() this.drawFrustumFilled() } get show() { return this.options.show } set show(bool) { if (typeof bool === 'boolean') { this.options.show = bool this.currentFrustumOutline.show = bool this.currentFrustumFilled.show = bool } } remove() { document.removeEventListener('keydown', this.keydownHandler) document.removeEventListener('keyup', this.keyupHandler) if (this.currentFrustumFilled) { this.viewer.scene.primitives.remove(this.currentFrustumFilled) } if (this.currentFrustumOutline) { this.viewer.scene.primitives.remove(this.currentFrustumOutline) } if (this.videoEntity) { this.viewer.entities.remove(this.videoEntity) } } }