Files
sdk4.0/src/Obj/AirLine/frustum.js

674 lines
20 KiB
JavaScript
Raw Normal View History

2025-07-03 13:54:01 +08:00
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)
}
}
}