Files
sdk4.0/src/Obj/AirLine/frustum.js
2025-07-03 13:54:01 +08:00

674 lines
20 KiB
JavaScript
Raw 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.

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)
}
}
}