| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- import * as THREE from "../../../libs/three.js/build/three.module.js";
- //import "../../../libs/other/hls.js";
- import BasicMaterial from '../materials/BasicMaterial.js'
- import {TextSprite} from "./TextSprite.js";
- //import DepthBasicMaterial from "../materials/DepthBasicMaterial.js";
- import math from '@sdk/utils/math'
- /* import SecurityCamera from '../core/cameras/SecurityCamera'
- import SecurityControls from '../core/controls/SecurityControls' */
-
- import {transitions, easing, lerp} from '../utils/transitions.js'
- import CursorDeal from '../utils/CursorDeal.js'
- import FlvVideoPlayerBase from '../utils/media/FlvVideoPlayerBase'
- import H5VideoPlayerBase from '../utils/media/H5VideoPlayerBase'
-
- const planeGeometry = new THREE.PlaneGeometry(1,1)
- const testUrl = 'https://newcntv.qcloudcdn.com/asp/hls/4000/0303000a/3/default/d907ef756138493e9fe5b35b4ab69642/4000.m3u8'
-
- const videoPlayer = Potree.browser.nonsupportH5Video ? new FlvVideoPlayerBase() : new H5VideoPlayerBase()
- const normalColor = new THREE.Color(1, 1, 1)
- const hoverColor = new THREE.Color(2, 2, 2)
- const vec1 = new THREE.Vector3(),
- vec2 = new THREE.Vector3(),
- vec3 = new THREE.Vector3(),
- quat1 = new THREE.Quaternion(),
- quat2 = new THREE.Quaternion()
- let cameraModel, loadingCamModel
- //测试场景: http://192.168.0.25/mix3d/#/home/671 潘少 Aa123456 http://192.168.0.25/epg.html?m=YZL-jm-lrZcWxzPaPJ#/tag
- //接收4dkk的监控 暂不支持修改 可以直接加到模型上,注意所附模型旋转90度后角度才对。
- export default class Monitor extends THREE.Object3D{
- constructor(data, model){
- super()
- this.isMonitor = true
- this.external = external//全景时不换材质
- warnHls()
-
- data.video = testUrl
-
- this.data = data
- /* for(let i in data){
- this[i] = data[i]
- } */
-
- this.fov = data.data.fov
- this.cylinderNear = data.data.near || 0.03
- this.cylinderFar = data.data.far || 3
-
- model.add(this)
-
-
- let videoUrl = this.getVideoSrc()
- this.video = videoPlayer.getVideo(videoUrl ,this)
- if (data.video) {
- //console.error('createVideo', this.videoSrc, this.sid)
- /* this.video.onloadedmetadata = () => {
- this.video.canPlayed = true
- this.video.masters.forEach(v => { //一个video可以对应多个camera,因为它们链接一样
- v.dispatchEvent({ type: 'loadedmetadata' })
- })
- } */
- let canPlayed = e => {
- //this.player.$app.Camera.emit('SecurityCamera.videoActive', this.sid)
- this.videoActive = true
- //this.updateInfo(true)
- }
- if (this.video.canPlayed) {
- canPlayed()
- } else {
- this.addEventListener('loadedmetadata', canPlayed)
- }
- if (Hls.isSupported() && data.urlType == 1) {
- //似乎Hls不支持就无法播放
- let hls = new Hls()
- hls.loadSource(videoUrl)
- hls.attachMedia(this.video)
- hls.on(Hls.Events.ERROR, (event, data) =>{ console.log('HLS加载失败', data.name, event, data)})
- this.hlsVideoPlayer = hls
- }
- this.play() // ios需要
- this.pause()
- }
-
-
- this.obj3d = new THREE.Group()
- this.add(this.obj3d)
-
-
- // 投射体材质
- this.normalMat = new BasicMaterial({
- color: new THREE.Color(0x00c8af),
- transparent: true,
- opacity: 0.1,
- side: THREE.DoubleSide,
- depthTest: false,
- visible: !!data.showScope
- })
-
-
- // 监控视频材质
- this.videoMat = new BasicMaterial({
- map: new THREE.VideoTexture(this.video),
- side: THREE.DoubleSide,
- depthTest: false,
- transparent: true,
-
- })
- // 摄像头
- if (!cameraModel) {
- if(!loadingCamModel){
- loadingCamModel = true
- viewer.loadModel({fileType:'glb', url:`${Potree.resourcePath}/models/glb/monitor.glb`},(model)=>{
- cameraModel = model.children[0].children[0]
- cameraModel.geometry.translate(30, 50, -10)
- cameraModel.quaternion.setFromEuler(new THREE.Euler(Math.PI / 2+0.24, Math.PI, 0))
- cameraModel.name = 'cameraModel'
- console.log('load monitor glb', cameraModel.uuid)
- model.parent.remove(model)
- viewer.scene.monitors.forEach(e=>e.modelLoaded())
- loadingCamModel = false
- })
- }
- }else{
- this.modelLoaded()
- }
- this.updateAspect()
-
- if(data.showTitle){
- let group = new THREE.Shim.FollowRootObject(this) //透明有问题,只有放到overlayScene里渲染了 (大小好像还是跟随模型了
-
- this.titleLabel = new TextSprite({
- text: data.name,
- backgroundColor: { r: 255, g: 255, b: 255, a: 0 },
- textColor: { r: 255, g: 255, b: 255, a: 1 },
- textshadowColor: '#666',
- borderRadius: 2,
- fontsize: 34,
- renderOrder: 5,
- margin: { x: 12, y: 10 },
- sizeInfo: { scale: 0.4, nearBound: 3 },
- })
- this.titleLabel.sprite.material.depthTest = this.titleLabel.sprite.material.depthWrite = true
- this.titleLabel.position.set(0, -0.2, 0.1)
- group.add(this.titleLabel)
- viewer.scene.overlayScene.add(group)
- group.name = 'monitorLabel'
-
-
-
- this.addEventListener('isVisible',()=>{
- Potree.Utils.updateVisible(group,'follow',this.realVisible())
- })
- this.parent.addEventListener('isVisible',()=>{
- Potree.Utils.updateVisible(group,'follow',this.realVisible())
- })
- }
-
-
- {
- this.posOri = new THREE.Vector3(parseFloat(data.data['posOri-x']), parseFloat(data.data['posOri-y']), parseFloat(data.data['posOri-z'])),
- this.posOffset = new THREE.Vector3(parseFloat(data.data['posOffset-x']), parseFloat(data.data['posOffset-y']), parseFloat(data.data['posOffset-z'])),
-
- // data.position && this.position.copy(data.position)
- this.position.copy(this.posOri).add(this.posOffset)
- this.position.copy(Potree.Utils.tran4dkkVecInModel(this.position, model))
-
- // target的优先级大于rotation
- if (data.data.target) {
- this.target = Potree.Utils.tran4dkkVecInModel(data.data.target, model)
- this.lookAt(data.data.target)
- } else {
- data.data.rotation && this.quaternion.setFromEuler(data.data.rotation)
- this.target = new THREE.Vector3(0, 0, -1).applyQuaternion(this.quaternion).add(this.position)
- }
-
-
-
- this.roll = 0
-
-
- data.data.pitch && (this.pitch = data.data.pitch)
- data.data.yaw && (this.yaw = data.data.yaw)
- data.data.roll && this.setRoll(this.data.data.roll)
-
- if(model.props.baseRotation?.x == 0){ //该模型已经矫正,无需旋转90度,但是场景的数据需要,导致monitor和模型不匹配,需要再旋转
- this.quaternion.copy(Potree.math.convertQuaternion.YupToZup(this.quaternion))
- }
-
- }
-
- //this.updateInfo(true)
-
-
-
- this.events = {
- setSize: (width, height) => {
- if (this.isWatching) {
- this.updateAspect()
- }
- },
- update: ()=>{
- if(!this.video.paused){
- viewer.dispatchEvent('content_changed')
- }
- }
-
- }
- viewer.addEventListener('viewerResize', this.events.setSize )
- viewer.addEventListener('update', this.events.update )
-
- window.monitor = this
- Potree.Utils.setObjectLayers(this,'dontIntersect')
- Potree.Utils.setObjectLayers(this.cylinder.bottom,'monitor')
- this.cameraModel && Potree.Utils.setObjectLayers(this.cameraModel,'sceneObjects')
- }
-
- getVideoSrc(){
- if(this.data.urlType === 2){
- let a = this.parent.props.raw.surveillancePath.split('/'); //用户上传的文件
- a.pop()
- return a.join('/') + '/' + this.data.fileName
- }else{
- return this.data.playUrl
- }
- }
-
- modelLoaded(){
- this.cameraModel = cameraModel.clone()
- this.cameraModel.material = cameraModel.material.clone()
- this.obj3d.add(this.cameraModel)
-
-
-
- this.cameraModel.addEventListener('mouseover',()=>{
- CursorDeal.add('hoverMonitor')
- this.highlight(true)
- })
- this.cameraModel.addEventListener('mouseleave',()=>{
- CursorDeal.remove('hoverMonitor')
- this.highlight(false)
- })
-
- this.cameraModel.addEventListener('click',()=>{
- if(viewer.scene.monitors.some(e=>e.isWatching))return
- this.watch()
- })
- }
-
-
- watch(){
-
- if (!this.videoActive){
- console.log('monitorError src:', this.video.src)
- return viewer.dispatchEvent('monitorError' )
- }
-
-
- let camera = viewer.mainViewport.camera
- this.updateAspect()
- this.isWatching = true
- //this.target.set(0, 0, -1).applyQuaternion(this.quaternion).add(this.position)
- viewer.mainViewport.view.cancelFlying()
- this.oldState = {
- fov: camera.fov,
- position: camera.position.clone(),
- quaternion: camera.quaternion.clone()
- }
- this.data.showScope && this.showVideo(true)
- this.video.pause()//先显示出画面
- setTimeout(()=>{
- this.data.showScope || this.showVideo(true)
- viewer.mainViewport.cameraLayers = ['monitor'] //hide others
- viewer.scene.monitors.forEach(e=>Potree.Utils.updateVisible(e,'watch', e == this ))
- },800)
- viewer.mainViewport.view.setRotMode('free')
- viewer.mainViewport.view.setView({
- position: this.getWorldPosition(new THREE.Vector3),
- quaternion: this.getWorldQuaternion(new THREE.Quaternion),
- onUpdate:(percent)=>{
- camera.fov = this.fov * percent + this.oldState.fov * (1-percent)
- camera.updateProjectionMatrix()
- },
- callback:()=>{
- this.video.play()
- viewer.dispatchEvent({type:'watchMonitor',monitor:this})
- },
- duration:1500
- })
-
- viewer.renderArea.style['pointer-events'] = 'none';
- }
-
-
- leave(){// 停止观看监控
- let camera = viewer.mainViewport.camera
-
- this.isWatching = false
- this.cameraModel.material.color.copy(normalColor)
- viewer.mainViewport.view.cancelFlying()
- viewer.mainViewport.cameraLayers = null
- viewer.scene.monitors.forEach(e=>Potree.Utils.updateVisible(e,'watch', true ))
- this.data.showScope || this.showVideo(false)
- this.video.pause()
- viewer.mainViewport.view.setView({
- position: this.oldState.position,
- quaternion: this.oldState.quaternion,
-
- onUpdate:(percent)=>{
- camera.fov = this.fov * (1-percent) + this.oldState.fov * percent
- camera.updateProjectionMatrix()
- },
- callback:()=>{
- viewer.renderArea.style['pointer-events'] = '';
- this.data.showScope && this.showVideo(false)
- viewer.mainViewport.view.setRotMode('standard')
- },
- duration:1000
- })
- viewer.dispatchEvent({type:'exitWatchMonitor',monitor:this})
- }
-
-
-
- // 显示监控视频或显示投射体
- showVideo(isShow) {
- //console.warn('showVideo', this.info.sid, isShow )
-
- if (!this.videoActive) return
- if (isShow) {
- /* if (browser.detectIOS()) {
- this.player.domElement.addEventListener('touchstart', this.events.onDomElementTouchStart, true)
- } */
- this.play()
- this.normalMat.opacity = 0
- Potree.Utils.updateVisible(this.cylinder, 'watch', true, 1, 'add')
- this.cylinder.bottom.material = this.videoMat
- //this.cylinder.bottom.renderOrder = RenderOrder.monitorPlane
- } else {
- this.pause()
- this.normalMat.opacity = 0.08
- Potree.Utils.updateVisible(this.cylinder, 'watch', false, 1, 'cancel')
- this.cylinder.bottom.material = this.normalMat
- //this.cylinder.bottom.renderOrder = RenderOrder.visibleFloor
- }
-
- }
-
- play() {
- //console.log('play monitor', this.sid, this.videoSrc)
- this.shouldPlay = true
- if (Potree.browser.detectWeixin()) {
- //用微信平台的 WeixinJSBridge 越过 Autoplay Policy
- try {
- top.WeixinJSBridge &&
- top.WeixinJSBridge.invoke(
- 'getNetworkType',
- {},
- e => {
- this.video.play()
- },
- false
- )
- } catch (error) {
- this.video.play()
- }
- } else {
- this.video.play()
- if (this.video.paused) {
- console.log('播放不了')
- /* this.player.once('pointerStart', () => {
- this.play()
- }) */
- }
- }
- }
- pause() {
- this.shouldPlay = false
- this.video.pause()
- }
-
-
-
-
-
-
-
- dispose(){
-
- let index = viewer.scene.monitors.indexOf(this)
- if(index == -1)return
- viewer.scene.monitors.splice(index,1)
-
- viewer.removeEventListener("viewerResize", this.events.setSize )
- viewer.removeEventListener("update", this.events.update )
-
- transitions.cancelById('cameraHighlight_' + this.sid)
- /* this.tag.geometry.dispose()
- this.tag.material.dispose()
- this.cylinder.line.geometry.dispose()
- this.cylinder.line.material.dispose()
- this.cylinder.geometry.dispose() */
- viewer.removeModel(this)
- this.normalMat.dispose()
- this.videoMat.dispose()
-
- this.titleLabel?.dispose()
-
- this.hlsVideoPlayer && this.hlsVideoPlayer.destroy()
-
-
-
- }
-
-
- highlight(state) {//相机hover高亮
- if (this.hightlighted == state) return
- this.hightlighted = state
- transitions.cancelById('cameraHighlight_' + this.sid)
- transitions.start(lerp.color(this.cameraModel.material.color, state ? hoverColor : normalColor), 100, null, null, null, null, 'cameraHighlight_' + this.sid)
- }
-
-
-
-
-
-
- updateAspect() {
- //更新aspect且修改geometry
- let aspect = viewer.mainViewport.camera.aspect
- if (aspect != this.aspect) {
- this.aspect = aspect
- this.updateMesh()
- }
- }
-
-
-
-
-
-
-
- updateMesh() {
-
- let nearHeight, nearWidth, farHeight, farWidth
- nearHeight = Math.tan(THREE.MathUtils.degToRad(this.fov / 2)) * this.cylinderNear
- nearWidth = nearHeight * this.aspect //根据canvas比例调整视频面的比例,保持和canvas一致。
- farHeight = Math.tan(THREE.MathUtils.degToRad(this.fov / 2)) * this.cylinderFar
- farWidth = farHeight * this.aspect
- let firstBuild = !this.cylinder
- if(this.data.showScope){
- // 投射体(底面以外)
- let vertices = [],
- bottomVertices = []
- vertices.push(-nearWidth, nearHeight, -this.cylinderNear)
- vertices.push(nearWidth, nearHeight, -this.cylinderNear)
- vertices.push(nearWidth, -nearHeight, -this.cylinderNear)
- vertices.push(-nearWidth, -nearHeight, -this.cylinderNear)
- bottomVertices.push(-farWidth, farHeight, -this.cylinderFar)
- bottomVertices.push(farWidth, farHeight, -this.cylinderFar)
- bottomVertices.push(farWidth, -farHeight, -this.cylinderFar)
- bottomVertices.push(-farWidth, -farHeight, -this.cylinderFar)
- vertices = vertices.concat(bottomVertices)
-
- if (firstBuild) {
- let uvs = []
- uvs.push(0, 1, 1, 1, 1, 0, 0, 0)
- uvs.push(0, 1, 1, 1, 1, 0, 0, 0)
- let indexs = []
- indexs.push(0, 1, 3, 2, 3, 1)
- indexs.push(0, 1, 4, 5, 4, 1)
- indexs.push(1, 2, 5, 6, 5, 2)
- indexs.push(2, 3, 6, 7, 6, 3)
- indexs.push(3, 0, 7, 4, 7, 0)
- // indexs.push(4, 7, 5, 6, 5, 7)
- let cylinderGeo = new THREE.BufferGeometry()
- cylinderGeo.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2))
- cylinderGeo.setIndex(new THREE.BufferAttribute(new Uint16Array(indexs), 1))
- this.cylinder = new THREE.Mesh(cylinderGeo, this.normalMat)
- //if (this.control.player.$app.config.mobile) this.cylinder.rotateZ(-Math.PI / 2) //用户自己旋转屏幕吧
- this.obj3d.add(this.cylinder)
- }
- this.cylinder.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3))
- }else if(!this.cylinder){
- this.cylinder = new THREE.Group()
- this.obj3d.add(this.cylinder)
- }
- // 投射体底面 (视频)
- const bottomGeo = new THREE.PlaneGeometry(farWidth * 2, farHeight * 2)
- if (firstBuild) {
- this.cylinder.bottom = new THREE.Mesh(bottomGeo, this.normalMat)
- this.cylinder.add(this.cylinder.bottom)
- } else {
- this.cylinder.bottom.geometry.dispose()
- this.cylinder.bottom.geometry = bottomGeo
- }
- this.cylinder.bottom.position.set(0, 0, this.cylinderNear - this.cylinderFar)
- if(this.data.showScope){
- // 投射体线框
- if (firstBuild) {
- this.cylinder.line = new THREE.LineSegments(new THREE.EdgesGeometry(this.cylinder.geometry), new THREE.LineBasicMaterial({ color: 0xffffff, opacity: 0.6, transparent: true }))
- this.cylinder.add(this.cylinder.line)
- } else {
- this.cylinder.line.geometry.dispose()
- this.cylinder.line.geometry = new THREE.EdgesGeometry(this.cylinder.geometry)
- }
- }
- }
-
- set pitch(pitch) {
- pitch = Math.min(Math.max(pitch, -89.9), 89.9) // 防止万向锁
- let yaw = this.yaw < 0 ? this.yaw + 360 : this.yaw
- let upDir = vec1.set(0, 1, 0)
- let rightDir = vec2.set(1, 0, 0) //.applyQuaternion(this.quaternion)
- let yawQuat = quat1.setFromAxisAngle(upDir, THREE.MathUtils.degToRad(yaw))
- let pitchQuat = quat2.setFromAxisAngle(rightDir, THREE.MathUtils.degToRad(pitch))
- this.quaternion.multiplyQuaternions(yawQuat, pitchQuat)
- //this.updateTarget()
- }
-
- set yaw(yaw) {
- let pitch = this.pitch // 要先计算pitch,防止窜数据
- let upDir = vec1.set(0, 1, 0)
- let rightDir = vec2.set(1, 0, 0)
- let yawQuat = quat1.setFromAxisAngle(upDir, THREE.MathUtils.degToRad(yaw))
- let pitchQuat = quat2.setFromAxisAngle(rightDir, THREE.MathUtils.degToRad(pitch))
- this.quaternion.multiplyQuaternions(yawQuat, pitchQuat)
- //this.updateTarget()
- }
-
- // 横滚角
- setRoll(angle) {//因加在子物体上,非0时播放视频是歪的(4dkk也这样)
- this.roll = angle % 360
- this.obj3d.quaternion.setFromAxisAngle(vec1.set(0, 0, -1) , THREE.MathUtils.degToRad(angle))
- }
- get pitch() {// 上下转角
- return this.data.data.pitch //xzw改
- let lookAt = vec1.copy(this.cylinder.bottom.position).applyQuaternion(this.quaternion)
- let projectVec = vec2.copy(lookAt).projectOnPlane(vec3.set(0, 1, 0))
- let pitch = THREE.MathUtils.radToDeg(lookAt.angleTo(projectVec) * Math.sign(lookAt.y)) % 180
- if (pitch > 90) pitch = 90 - pitch
- return pitch
- }
- get yaw() {// 左右转角
- return this.data.data.yaw //xzw改 原先的计算不对,0变180
- let lookAt = vec1.copy(this.cylinder.bottom.position).applyQuaternion(this.quaternion)
- let lookAtXZ = lookAt.setY(0)
- let frontDir = vec2.set(0, 0, 1)
- let frontCross = vec3.set(1, 0, 0)
- let angle = (THREE.MathUtils.radToDeg(lookAtXZ.angleTo(frontDir) * Math.sign(lookAtXZ.dot(frontCross))) + 180) % 360
- if (angle > 180) angle = angle - 360
- return angle
- }
-
- }
- function warnHls(){
- if (!window.Hls) {
- console.error('没加载Hls.js文件!')
- }
- if (!window.Hls?.isSupported()) { //在融合页面导入hls文件
- console.error('Hls is not Supported, 部分监控视频可能不支持') //iphoneX不支持 小米Civi 1S支持。
- }
- }
- /*
- viewer.addMonitor(undefined, viewer.objs.children[0])
- viewer.scene.monitors[0].leave()
- */
|