Monitor.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. import * as THREE from "../../../libs/three.js/build/three.module.js";
  2. //import "../../../libs/other/hls.js";
  3. import BasicMaterial from '../materials/BasicMaterial.js'
  4. import {TextSprite} from "./TextSprite.js";
  5. //import DepthBasicMaterial from "../materials/DepthBasicMaterial.js";
  6. import math from '@sdk/utils/math'
  7. /* import SecurityCamera from '../core/cameras/SecurityCamera'
  8. import SecurityControls from '../core/controls/SecurityControls' */
  9. import {transitions, easing, lerp} from '../utils/transitions.js'
  10. import CursorDeal from '../utils/CursorDeal.js'
  11. import FlvVideoPlayerBase from '../utils/media/FlvVideoPlayerBase'
  12. import H5VideoPlayerBase from '../utils/media/H5VideoPlayerBase'
  13. const planeGeometry = new THREE.PlaneGeometry(1,1)
  14. const testUrl = 'https://newcntv.qcloudcdn.com/asp/hls/4000/0303000a/3/default/d907ef756138493e9fe5b35b4ab69642/4000.m3u8'
  15. const videoPlayer = Potree.browser.nonsupportH5Video ? new FlvVideoPlayerBase() : new H5VideoPlayerBase()
  16. const normalColor = new THREE.Color(1, 1, 1)
  17. const hoverColor = new THREE.Color(2, 2, 2)
  18. const vec1 = new THREE.Vector3(),
  19. vec2 = new THREE.Vector3(),
  20. vec3 = new THREE.Vector3(),
  21. quat1 = new THREE.Quaternion(),
  22. quat2 = new THREE.Quaternion()
  23. let cameraModel, loadingCamModel
  24. //测试场景: http://192.168.0.25/mix3d/#/home/671 潘少 Aa123456 http://192.168.0.25/epg.html?m=YZL-jm-lrZcWxzPaPJ#/tag
  25. //接收4dkk的监控 暂不支持修改 可以直接加到模型上,注意所附模型旋转90度后角度才对。
  26. export default class Monitor extends THREE.Object3D{
  27. constructor(data, model){
  28. super()
  29. this.isMonitor = true
  30. this.external = external//全景时不换材质
  31. warnHls()
  32. data.video = testUrl
  33. this.data = data
  34. /* for(let i in data){
  35. this[i] = data[i]
  36. } */
  37. this.fov = data.data.fov
  38. this.cylinderNear = data.data.near || 0.03
  39. this.cylinderFar = data.data.far || 3
  40. model.add(this)
  41. let videoUrl = this.getVideoSrc()
  42. this.video = videoPlayer.getVideo(videoUrl ,this)
  43. if (data.video) {
  44. //console.error('createVideo', this.videoSrc, this.sid)
  45. /* this.video.onloadedmetadata = () => {
  46. this.video.canPlayed = true
  47. this.video.masters.forEach(v => { //一个video可以对应多个camera,因为它们链接一样
  48. v.dispatchEvent({ type: 'loadedmetadata' })
  49. })
  50. } */
  51. let canPlayed = e => {
  52. //this.player.$app.Camera.emit('SecurityCamera.videoActive', this.sid)
  53. this.videoActive = true
  54. //this.updateInfo(true)
  55. }
  56. if (this.video.canPlayed) {
  57. canPlayed()
  58. } else {
  59. this.addEventListener('loadedmetadata', canPlayed)
  60. }
  61. if (Hls.isSupported() && data.urlType == 1) {
  62. //似乎Hls不支持就无法播放
  63. let hls = new Hls()
  64. hls.loadSource(videoUrl)
  65. hls.attachMedia(this.video)
  66. hls.on(Hls.Events.ERROR, (event, data) =>{ console.log('HLS加载失败', data.name, event, data)})
  67. this.hlsVideoPlayer = hls
  68. }
  69. this.play() // ios需要
  70. this.pause()
  71. }
  72. this.obj3d = new THREE.Group()
  73. this.add(this.obj3d)
  74. // 投射体材质
  75. this.normalMat = new BasicMaterial({
  76. color: new THREE.Color(0x00c8af),
  77. transparent: true,
  78. opacity: 0.1,
  79. side: THREE.DoubleSide,
  80. depthTest: false,
  81. visible: !!data.showScope
  82. })
  83. // 监控视频材质
  84. this.videoMat = new BasicMaterial({
  85. map: new THREE.VideoTexture(this.video),
  86. side: THREE.DoubleSide,
  87. depthTest: false,
  88. transparent: true,
  89. })
  90. // 摄像头
  91. if (!cameraModel) {
  92. if(!loadingCamModel){
  93. loadingCamModel = true
  94. viewer.loadModel({fileType:'glb', url:`${Potree.resourcePath}/models/glb/monitor.glb`},(model)=>{
  95. cameraModel = model.children[0].children[0]
  96. cameraModel.geometry.translate(30, 50, -10)
  97. cameraModel.quaternion.setFromEuler(new THREE.Euler(Math.PI / 2+0.24, Math.PI, 0))
  98. cameraModel.name = 'cameraModel'
  99. console.log('load monitor glb', cameraModel.uuid)
  100. model.parent.remove(model)
  101. viewer.scene.monitors.forEach(e=>e.modelLoaded())
  102. loadingCamModel = false
  103. })
  104. }
  105. }else{
  106. this.modelLoaded()
  107. }
  108. this.updateAspect()
  109. if(data.showTitle){
  110. let group = new THREE.Shim.FollowRootObject(this) //透明有问题,只有放到overlayScene里渲染了 (大小好像还是跟随模型了
  111. this.titleLabel = new TextSprite({
  112. text: data.name,
  113. backgroundColor: { r: 255, g: 255, b: 255, a: 0 },
  114. textColor: { r: 255, g: 255, b: 255, a: 1 },
  115. textshadowColor: '#666',
  116. borderRadius: 2,
  117. fontsize: 34,
  118. renderOrder: 5,
  119. margin: { x: 12, y: 10 },
  120. sizeInfo: { scale: 0.4, nearBound: 3 },
  121. })
  122. this.titleLabel.sprite.material.depthTest = this.titleLabel.sprite.material.depthWrite = true
  123. this.titleLabel.position.set(0, -0.2, 0.1)
  124. group.add(this.titleLabel)
  125. viewer.scene.overlayScene.add(group)
  126. group.name = 'monitorLabel'
  127. this.addEventListener('isVisible',()=>{
  128. Potree.Utils.updateVisible(group,'follow',this.realVisible())
  129. })
  130. this.parent.addEventListener('isVisible',()=>{
  131. Potree.Utils.updateVisible(group,'follow',this.realVisible())
  132. })
  133. }
  134. {
  135. this.posOri = new THREE.Vector3(parseFloat(data.data['posOri-x']), parseFloat(data.data['posOri-y']), parseFloat(data.data['posOri-z'])),
  136. this.posOffset = new THREE.Vector3(parseFloat(data.data['posOffset-x']), parseFloat(data.data['posOffset-y']), parseFloat(data.data['posOffset-z'])),
  137. // data.position && this.position.copy(data.position)
  138. this.position.copy(this.posOri).add(this.posOffset)
  139. this.position.copy(Potree.Utils.tran4dkkVecInModel(this.position, model))
  140. // target的优先级大于rotation
  141. if (data.data.target) {
  142. this.target = Potree.Utils.tran4dkkVecInModel(data.data.target, model)
  143. this.lookAt(data.data.target)
  144. } else {
  145. data.data.rotation && this.quaternion.setFromEuler(data.data.rotation)
  146. this.target = new THREE.Vector3(0, 0, -1).applyQuaternion(this.quaternion).add(this.position)
  147. }
  148. this.roll = 0
  149. data.data.pitch && (this.pitch = data.data.pitch)
  150. data.data.yaw && (this.yaw = data.data.yaw)
  151. data.data.roll && this.setRoll(this.data.data.roll)
  152. if(model.props.baseRotation?.x == 0){ //该模型已经矫正,无需旋转90度,但是场景的数据需要,导致monitor和模型不匹配,需要再旋转
  153. this.quaternion.copy(Potree.math.convertQuaternion.YupToZup(this.quaternion))
  154. }
  155. }
  156. //this.updateInfo(true)
  157. this.events = {
  158. setSize: (width, height) => {
  159. if (this.isWatching) {
  160. this.updateAspect()
  161. }
  162. },
  163. update: ()=>{
  164. if(!this.video.paused){
  165. viewer.dispatchEvent('content_changed')
  166. }
  167. }
  168. }
  169. viewer.addEventListener('viewerResize', this.events.setSize )
  170. viewer.addEventListener('update', this.events.update )
  171. window.monitor = this
  172. Potree.Utils.setObjectLayers(this,'dontIntersect')
  173. Potree.Utils.setObjectLayers(this.cylinder.bottom,'monitor')
  174. this.cameraModel && Potree.Utils.setObjectLayers(this.cameraModel,'sceneObjects')
  175. }
  176. getVideoSrc(){
  177. if(this.data.urlType === 2){
  178. let a = this.parent.props.raw.surveillancePath.split('/'); //用户上传的文件
  179. a.pop()
  180. return a.join('/') + '/' + this.data.fileName
  181. }else{
  182. return this.data.playUrl
  183. }
  184. }
  185. modelLoaded(){
  186. this.cameraModel = cameraModel.clone()
  187. this.cameraModel.material = cameraModel.material.clone()
  188. this.obj3d.add(this.cameraModel)
  189. this.cameraModel.addEventListener('mouseover',()=>{
  190. CursorDeal.add('hoverMonitor')
  191. this.highlight(true)
  192. })
  193. this.cameraModel.addEventListener('mouseleave',()=>{
  194. CursorDeal.remove('hoverMonitor')
  195. this.highlight(false)
  196. })
  197. this.cameraModel.addEventListener('click',()=>{
  198. if(viewer.scene.monitors.some(e=>e.isWatching))return
  199. this.watch()
  200. })
  201. }
  202. watch(){
  203. if (!this.videoActive){
  204. console.log('monitorError src:', this.video.src)
  205. return viewer.dispatchEvent('monitorError' )
  206. }
  207. let camera = viewer.mainViewport.camera
  208. this.updateAspect()
  209. this.isWatching = true
  210. //this.target.set(0, 0, -1).applyQuaternion(this.quaternion).add(this.position)
  211. viewer.mainViewport.view.cancelFlying()
  212. this.oldState = {
  213. fov: camera.fov,
  214. position: camera.position.clone(),
  215. quaternion: camera.quaternion.clone()
  216. }
  217. this.data.showScope && this.showVideo(true)
  218. this.video.pause()//先显示出画面
  219. setTimeout(()=>{
  220. this.data.showScope || this.showVideo(true)
  221. viewer.mainViewport.cameraLayers = ['monitor'] //hide others
  222. viewer.scene.monitors.forEach(e=>Potree.Utils.updateVisible(e,'watch', e == this ))
  223. },800)
  224. viewer.mainViewport.view.setRotMode('free')
  225. viewer.mainViewport.view.setView({
  226. position: this.getWorldPosition(new THREE.Vector3),
  227. quaternion: this.getWorldQuaternion(new THREE.Quaternion),
  228. onUpdate:(percent)=>{
  229. camera.fov = this.fov * percent + this.oldState.fov * (1-percent)
  230. camera.updateProjectionMatrix()
  231. },
  232. callback:()=>{
  233. this.video.play()
  234. viewer.dispatchEvent({type:'watchMonitor',monitor:this})
  235. },
  236. duration:1500
  237. })
  238. viewer.renderArea.style['pointer-events'] = 'none';
  239. }
  240. leave(){// 停止观看监控
  241. let camera = viewer.mainViewport.camera
  242. this.isWatching = false
  243. this.cameraModel.material.color.copy(normalColor)
  244. viewer.mainViewport.view.cancelFlying()
  245. viewer.mainViewport.cameraLayers = null
  246. viewer.scene.monitors.forEach(e=>Potree.Utils.updateVisible(e,'watch', true ))
  247. this.data.showScope || this.showVideo(false)
  248. this.video.pause()
  249. viewer.mainViewport.view.setView({
  250. position: this.oldState.position,
  251. quaternion: this.oldState.quaternion,
  252. onUpdate:(percent)=>{
  253. camera.fov = this.fov * (1-percent) + this.oldState.fov * percent
  254. camera.updateProjectionMatrix()
  255. },
  256. callback:()=>{
  257. viewer.renderArea.style['pointer-events'] = '';
  258. this.data.showScope && this.showVideo(false)
  259. viewer.mainViewport.view.setRotMode('standard')
  260. },
  261. duration:1000
  262. })
  263. viewer.dispatchEvent({type:'exitWatchMonitor',monitor:this})
  264. }
  265. // 显示监控视频或显示投射体
  266. showVideo(isShow) {
  267. //console.warn('showVideo', this.info.sid, isShow )
  268. if (!this.videoActive) return
  269. if (isShow) {
  270. /* if (browser.detectIOS()) {
  271. this.player.domElement.addEventListener('touchstart', this.events.onDomElementTouchStart, true)
  272. } */
  273. this.play()
  274. this.normalMat.opacity = 0
  275. Potree.Utils.updateVisible(this.cylinder, 'watch', true, 1, 'add')
  276. this.cylinder.bottom.material = this.videoMat
  277. //this.cylinder.bottom.renderOrder = RenderOrder.monitorPlane
  278. } else {
  279. this.pause()
  280. this.normalMat.opacity = 0.08
  281. Potree.Utils.updateVisible(this.cylinder, 'watch', false, 1, 'cancel')
  282. this.cylinder.bottom.material = this.normalMat
  283. //this.cylinder.bottom.renderOrder = RenderOrder.visibleFloor
  284. }
  285. }
  286. play() {
  287. //console.log('play monitor', this.sid, this.videoSrc)
  288. this.shouldPlay = true
  289. if (Potree.browser.detectWeixin()) {
  290. //用微信平台的 WeixinJSBridge 越过 Autoplay Policy
  291. try {
  292. top.WeixinJSBridge &&
  293. top.WeixinJSBridge.invoke(
  294. 'getNetworkType',
  295. {},
  296. e => {
  297. this.video.play()
  298. },
  299. false
  300. )
  301. } catch (error) {
  302. this.video.play()
  303. }
  304. } else {
  305. this.video.play()
  306. if (this.video.paused) {
  307. console.log('播放不了')
  308. /* this.player.once('pointerStart', () => {
  309. this.play()
  310. }) */
  311. }
  312. }
  313. }
  314. pause() {
  315. this.shouldPlay = false
  316. this.video.pause()
  317. }
  318. dispose(){
  319. let index = viewer.scene.monitors.indexOf(this)
  320. if(index == -1)return
  321. viewer.scene.monitors.splice(index,1)
  322. viewer.removeEventListener("viewerResize", this.events.setSize )
  323. viewer.removeEventListener("update", this.events.update )
  324. transitions.cancelById('cameraHighlight_' + this.sid)
  325. /* this.tag.geometry.dispose()
  326. this.tag.material.dispose()
  327. this.cylinder.line.geometry.dispose()
  328. this.cylinder.line.material.dispose()
  329. this.cylinder.geometry.dispose() */
  330. viewer.removeModel(this)
  331. this.normalMat.dispose()
  332. this.videoMat.dispose()
  333. this.titleLabel?.dispose()
  334. this.hlsVideoPlayer && this.hlsVideoPlayer.destroy()
  335. }
  336. highlight(state) {//相机hover高亮
  337. if (this.hightlighted == state) return
  338. this.hightlighted = state
  339. transitions.cancelById('cameraHighlight_' + this.sid)
  340. transitions.start(lerp.color(this.cameraModel.material.color, state ? hoverColor : normalColor), 100, null, null, null, null, 'cameraHighlight_' + this.sid)
  341. }
  342. updateAspect() {
  343. //更新aspect且修改geometry
  344. let aspect = viewer.mainViewport.camera.aspect
  345. if (aspect != this.aspect) {
  346. this.aspect = aspect
  347. this.updateMesh()
  348. }
  349. }
  350. updateMesh() {
  351. let nearHeight, nearWidth, farHeight, farWidth
  352. nearHeight = Math.tan(THREE.MathUtils.degToRad(this.fov / 2)) * this.cylinderNear
  353. nearWidth = nearHeight * this.aspect //根据canvas比例调整视频面的比例,保持和canvas一致。
  354. farHeight = Math.tan(THREE.MathUtils.degToRad(this.fov / 2)) * this.cylinderFar
  355. farWidth = farHeight * this.aspect
  356. let firstBuild = !this.cylinder
  357. if(this.data.showScope){
  358. // 投射体(底面以外)
  359. let vertices = [],
  360. bottomVertices = []
  361. vertices.push(-nearWidth, nearHeight, -this.cylinderNear)
  362. vertices.push(nearWidth, nearHeight, -this.cylinderNear)
  363. vertices.push(nearWidth, -nearHeight, -this.cylinderNear)
  364. vertices.push(-nearWidth, -nearHeight, -this.cylinderNear)
  365. bottomVertices.push(-farWidth, farHeight, -this.cylinderFar)
  366. bottomVertices.push(farWidth, farHeight, -this.cylinderFar)
  367. bottomVertices.push(farWidth, -farHeight, -this.cylinderFar)
  368. bottomVertices.push(-farWidth, -farHeight, -this.cylinderFar)
  369. vertices = vertices.concat(bottomVertices)
  370. if (firstBuild) {
  371. let uvs = []
  372. uvs.push(0, 1, 1, 1, 1, 0, 0, 0)
  373. uvs.push(0, 1, 1, 1, 1, 0, 0, 0)
  374. let indexs = []
  375. indexs.push(0, 1, 3, 2, 3, 1)
  376. indexs.push(0, 1, 4, 5, 4, 1)
  377. indexs.push(1, 2, 5, 6, 5, 2)
  378. indexs.push(2, 3, 6, 7, 6, 3)
  379. indexs.push(3, 0, 7, 4, 7, 0)
  380. // indexs.push(4, 7, 5, 6, 5, 7)
  381. let cylinderGeo = new THREE.BufferGeometry()
  382. cylinderGeo.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2))
  383. cylinderGeo.setIndex(new THREE.BufferAttribute(new Uint16Array(indexs), 1))
  384. this.cylinder = new THREE.Mesh(cylinderGeo, this.normalMat)
  385. //if (this.control.player.$app.config.mobile) this.cylinder.rotateZ(-Math.PI / 2) //用户自己旋转屏幕吧
  386. this.obj3d.add(this.cylinder)
  387. }
  388. this.cylinder.geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3))
  389. }else if(!this.cylinder){
  390. this.cylinder = new THREE.Group()
  391. this.obj3d.add(this.cylinder)
  392. }
  393. // 投射体底面 (视频)
  394. const bottomGeo = new THREE.PlaneGeometry(farWidth * 2, farHeight * 2)
  395. if (firstBuild) {
  396. this.cylinder.bottom = new THREE.Mesh(bottomGeo, this.normalMat)
  397. this.cylinder.add(this.cylinder.bottom)
  398. } else {
  399. this.cylinder.bottom.geometry.dispose()
  400. this.cylinder.bottom.geometry = bottomGeo
  401. }
  402. this.cylinder.bottom.position.set(0, 0, this.cylinderNear - this.cylinderFar)
  403. if(this.data.showScope){
  404. // 投射体线框
  405. if (firstBuild) {
  406. this.cylinder.line = new THREE.LineSegments(new THREE.EdgesGeometry(this.cylinder.geometry), new THREE.LineBasicMaterial({ color: 0xffffff, opacity: 0.6, transparent: true }))
  407. this.cylinder.add(this.cylinder.line)
  408. } else {
  409. this.cylinder.line.geometry.dispose()
  410. this.cylinder.line.geometry = new THREE.EdgesGeometry(this.cylinder.geometry)
  411. }
  412. }
  413. }
  414. set pitch(pitch) {
  415. pitch = Math.min(Math.max(pitch, -89.9), 89.9) // 防止万向锁
  416. let yaw = this.yaw < 0 ? this.yaw + 360 : this.yaw
  417. let upDir = vec1.set(0, 1, 0)
  418. let rightDir = vec2.set(1, 0, 0) //.applyQuaternion(this.quaternion)
  419. let yawQuat = quat1.setFromAxisAngle(upDir, THREE.MathUtils.degToRad(yaw))
  420. let pitchQuat = quat2.setFromAxisAngle(rightDir, THREE.MathUtils.degToRad(pitch))
  421. this.quaternion.multiplyQuaternions(yawQuat, pitchQuat)
  422. //this.updateTarget()
  423. }
  424. set yaw(yaw) {
  425. let pitch = this.pitch // 要先计算pitch,防止窜数据
  426. let upDir = vec1.set(0, 1, 0)
  427. let rightDir = vec2.set(1, 0, 0)
  428. let yawQuat = quat1.setFromAxisAngle(upDir, THREE.MathUtils.degToRad(yaw))
  429. let pitchQuat = quat2.setFromAxisAngle(rightDir, THREE.MathUtils.degToRad(pitch))
  430. this.quaternion.multiplyQuaternions(yawQuat, pitchQuat)
  431. //this.updateTarget()
  432. }
  433. // 横滚角
  434. setRoll(angle) {//因加在子物体上,非0时播放视频是歪的(4dkk也这样)
  435. this.roll = angle % 360
  436. this.obj3d.quaternion.setFromAxisAngle(vec1.set(0, 0, -1) , THREE.MathUtils.degToRad(angle))
  437. }
  438. get pitch() {// 上下转角
  439. return this.data.data.pitch //xzw改
  440. let lookAt = vec1.copy(this.cylinder.bottom.position).applyQuaternion(this.quaternion)
  441. let projectVec = vec2.copy(lookAt).projectOnPlane(vec3.set(0, 1, 0))
  442. let pitch = THREE.MathUtils.radToDeg(lookAt.angleTo(projectVec) * Math.sign(lookAt.y)) % 180
  443. if (pitch > 90) pitch = 90 - pitch
  444. return pitch
  445. }
  446. get yaw() {// 左右转角
  447. return this.data.data.yaw //xzw改 原先的计算不对,0变180
  448. let lookAt = vec1.copy(this.cylinder.bottom.position).applyQuaternion(this.quaternion)
  449. let lookAtXZ = lookAt.setY(0)
  450. let frontDir = vec2.set(0, 0, 1)
  451. let frontCross = vec3.set(1, 0, 0)
  452. let angle = (THREE.MathUtils.radToDeg(lookAtXZ.angleTo(frontDir) * Math.sign(lookAtXZ.dot(frontCross))) + 180) % 360
  453. if (angle > 180) angle = angle - 360
  454. return angle
  455. }
  456. }
  457. function warnHls(){
  458. if (!window.Hls) {
  459. console.error('没加载Hls.js文件!')
  460. }
  461. if (!window.Hls?.isSupported()) { //在融合页面导入hls文件
  462. console.error('Hls is not Supported, 部分监控视频可能不支持') //iphoneX不支持 小米Civi 1S支持。
  463. }
  464. }
  465. /*
  466. viewer.addMonitor(undefined, viewer.objs.children[0])
  467. viewer.scene.monitors[0].leave()
  468. */