Panorama.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. import * as THREE from "../../../libs/three.js/build/three.module.js";
  2. import {transitions, easing, lerp} from '../../utils/transitions.js'
  3. import TileUtils from './tile/TileUtils'
  4. import { PanoRendererEvents, PanoramaEvents, PanoSizeClass} from '../../defines'
  5. import math from '../../utils/math'
  6. import {TextSprite} from '../../objects/TextSprite'
  7. var texLoader = new THREE.TextureLoader()
  8. const labelProp = {
  9. sizeInfo: {minSize : 200 , maxSize : 250, nearBound : 0.8, farBound : 10},
  10. backgroundColor:{r: 255, g: 255, b: 255, a: 0.4 },
  11. textColor:{r: 0, g: 0, b: 0, a: 1 },
  12. borderRadius: 15,
  13. renderOrder:10
  14. }
  15. let standardMarkerMat
  16. let getMarerMat = function(){
  17. if(!standardMarkerMat) {
  18. let map = texLoader.load( Potree.resourcePath+'/textures/marker.png' )
  19. map.anisotropy = 4 // 各向异性过滤 .防止倾斜模糊
  20. standardMarkerMat = new THREE.MeshBasicMaterial({opacity:0.7, side: THREE.DoubleSide , map ,transparent:true, depthTest:false})//总是被点云遮住,所以depthTest:false
  21. }
  22. return standardMarkerMat.clone()
  23. }
  24. //显示全景图时marker没有被遮挡,如果需要,要换成depthBasicMaterial 或者直接把skybox的深度修改(拿到深度贴图后更如此)
  25. let planeGeo = new THREE.PlaneBufferGeometry(0.2,0.2);
  26. let sg = new THREE.SphereGeometry(0.1, 8, 8);
  27. let smHovered = new THREE.MeshBasicMaterial({/* side: THREE.BackSide, */color: 0xff0000});
  28. let sm = new THREE.MeshBasicMaterial({/* side: THREE.BackSide */});
  29. var rot90 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), Math.PI/2 ); //使用的是刚好适合全景图的,给cube贴图需要转90°
  30. //var rot90 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), -Math.PI/2 ); //4dkk->navvis
  31. //var rot901 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), -Math.PI/2 ); //整张球幕图要旋转下
  32. //rot90 = new THREE.Quaternion().multiplyQuaternions( rot901, rot90)
  33. var old = null;
  34. /*
  35. 转成四维看看的axis:
  36. var a = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), THREE.Math.degToRad(-90)) 因为四维的要绕y转90
  37. 这里的quaternion.multiply(a);
  38. 先乘再换顺序 w : q.w, x:q.x , y:-q.z, z:q.y
  39. */
  40. //暂时直接用4dkkconsole输出的数据
  41. class Panorama extends THREE.EventDispatcher{
  42. constructor(o, transform, images360){//file, time, longitude, latitude, altitude, course, pitch, roll
  43. super()
  44. this.id = o.id;
  45. this.images360 = images360
  46. this.transform = transform
  47. this.visible = true //for viewer updateVisible
  48. this.enabled = true//是否可以走
  49. this.originPosition = new THREE.Vector3().fromArray(o.dataset_location)
  50. this.originFloorPosition = new THREE.Vector3().fromArray(o.dataset_floor_location)
  51. this.originID = parseInt(o.file_id)//"file_id":"00022"对应是原本的4dkk的id --来自vision.txt
  52. this.pointcloud = viewer.scene.pointclouds.find(e=>e.dataset_id == o.dataset_id) || viewer.scene.pointclouds[0]
  53. this.pointcloud.panos.push(this)
  54. /* this.pointcloud.addEventListener('isVisible',(e)=>{
  55. var visible = viewer.getObjVisiByReason(this.pointcloud, 'datasetSelection')
  56. console.log('datasetVisi', visible)
  57. viewer.updateVisible(this, 'pointcloudVisi', visible)
  58. //e.reason == 'datasetSelection' && viewer.updateVisible(this, 'pointcloudVisi', e.visible)
  59. }) */
  60. this.addEventListener('isVisible',(e)=>{//是否显示该点的mesh(不显示也能走)
  61. this.marker.visible = e.visible
  62. Potree.settings.showPanoMesh && (this.mesh.visible = e.visible)
  63. if(e.reason == 'screenshot' || e.visible){
  64. this.label && (this.label.visible = e.visible)//截图时隐藏下
  65. }
  66. })
  67. //全景图和Cube的水平采样起始坐标相差90度
  68. /* if(from4dkk){
  69. var qua = o.dataset_orientation
  70. var quaternion = new THREE.Quaternion().fromArray(qua)
  71. quaternion = new THREE.Quaternion().multiplyQuaternions(quaternion, rot901);//整张球幕图要旋转下 因为在4dkk里转过,还原。如果是tiles的不用
  72. this.quaternion = new THREE.Quaternion(quaternion.x, -quaternion.z, quaternion.y, quaternion.w) //转化坐标
  73. }else{ */
  74. var qua = o.dataset_orientation
  75. qua = [qua[1], qua[2], qua[3], qua[0]]
  76. this.quaternion = new THREE.Quaternion().fromArray(qua)
  77. this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion
  78. this.quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion, rot90);//全景图和Cube的水平采样起始坐标相差90度,cubeTex转90度
  79. this.rotation4dkk = new THREE.Euler().setFromQuaternion(this.quaternion4dkk)
  80. this.rotation = new THREE.Euler().setFromQuaternion(this.quaternion)
  81. //}
  82. //this.quaternion1 = Potree.Utils.QuaternionFactory.fromArray(o.dataset_orientation)
  83. //同quaternion
  84. //let xy = this.transform.forward([this.longitude, this.latitude]);
  85. this.file = `https://4dkk.4dage.com/images/images${Potree.settings.number}/pan/high/${this.id}.jpg`
  86. this.build()
  87. this.transformByPointcloud() //初始化位移
  88. {//tile
  89. this.minimumTiledPanoLoaded = !1;
  90. this.highestPartialTileRenderOpCompleted = 0;
  91. this.highestFullTileRenderOpCompleted = 0;
  92. this.shouldRedrawOnBaseLoaded = !1;
  93. this.resolutionPromise = {}
  94. this.tiledPanoRenderTarget = null;
  95. this.zoomed = !1;
  96. images360.panoRenderer.addEventListener(PanoRendererEvents.TileRenderSuccess, this.onTileRendered.bind(this));
  97. images360.panoRenderer.addEventListener(PanoRendererEvents.PanoRenderComplete, this.onPanoRendered.bind(this));
  98. images360.panoRenderer.addEventListener(PanoRendererEvents.TileRenderFailure, this.onTileRenderFail.bind(this));
  99. images360.panoRenderer.addEventListener(PanoRendererEvents.UploadAttemptedForAllTiles, this.onUploadAttemptedForAllTiles.bind(this));
  100. }
  101. this.addEventListener('hoverOn', (e)=>{//from Map
  102. if(!e.byMainView){
  103. this.hoverOn(e)
  104. }
  105. })
  106. this.addEventListener('hoverOff', (e)=>{
  107. if(!e.byMainView){
  108. this.hoverOff(e)
  109. }
  110. })
  111. }
  112. setEnable(enable){//是否可以走
  113. viewer.updateVisible(this, 'isEnabled', enable) //令所有marker不可见
  114. this.enabled = enable
  115. //如果当前在全景模式且在这个点,需要切换显示吗? 目前用不到
  116. }
  117. build(){
  118. let mesh = new THREE.Mesh(sg, sm);
  119. mesh.scale.set(1, 1, 1);
  120. mesh.material.transparent = true;
  121. mesh.material.opacity = 0.75;
  122. mesh.pano = this;
  123. mesh.name = 'panoSphere'
  124. mesh.addEventListener('mouseover',(e)=>{
  125. mesh.material = smHovered
  126. })
  127. mesh.addEventListener('mouseleave',(e)=>{
  128. mesh.material = sm
  129. })
  130. mesh.addEventListener('click',(e)=>{
  131. this.images360.focusPano(this)
  132. })
  133. { // orientation
  134. //var {course, pitch, roll} = this;
  135. //mesh.quaternion.copy(this.quaternion)
  136. //add
  137. //var quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion, rot901);//改 为球目全
  138. //quaternion.premultiply(rot90)
  139. this.panoMatrix = new THREE.Matrix4().makeRotationFromQuaternion(this.quaternion)
  140. this.oriPanoMatrix = this.panoMatrix.clone()
  141. //console.log(this.quaternion)
  142. //this.quaternion = quaternion
  143. }
  144. this.mesh = mesh;
  145. if(!Potree.settings.showPanoMesh) mesh.visible = false
  146. let marker = new THREE.Mesh(planeGeo, getMarerMat() )
  147. marker.up.set(0,0,1)
  148. marker.lookAt(marker.up)
  149. marker.scale.set(2,2,2)
  150. this.marker = marker
  151. this.images360.node.add(mesh)
  152. this.images360.node.add(marker)
  153. Potree.settings.isTest && this.createTextLabel()
  154. }
  155. transformByPointcloud(){
  156. let position = this.originPosition.clone().applyMatrix4(this.pointcloud.transformMatrix);//也可以用datasetPosTransform算
  157. let floorPosition = this.originFloorPosition.clone().applyMatrix4(this.pointcloud.transformMatrix);
  158. this.setPosition(position, floorPosition)
  159. this.panoMatrix = new THREE.Matrix4().multiplyMatrices(this.pointcloud.rotateMatrix, this.oriPanoMatrix )
  160. //this.panoMatrix2 = Potree.Utils.datasetRotTransform({fromDataset:true, pointcloud:this.pointcloud, matrix:this.oriPanoMatrix, getMatrix:true}) //和上一行结果一样
  161. //quaternion也变下
  162. this.dispatchEvent('rePos')
  163. }
  164. setPosition(position, floorPosition){
  165. this.position = position
  166. this.floorPosition = floorPosition
  167. this.mesh.position.copy(this.position)
  168. this.marker.position.copy(this.floorPosition)
  169. this.marker.position.z+=0.1//会被点云遮住
  170. if(this.label){
  171. this.label.position.copy(this.floorPosition), this.label.position.z+=0.2
  172. this.label.update()
  173. }
  174. }
  175. hoverOn(e={}) {
  176. //console.log("hoverOn " + this.id )
  177. transitions.start(lerp.property(this.marker.material, "opacity", 1), 250)
  178. if(!e.byMap) this.dispatchEvent({type:'hoverOn', byMainView:true})
  179. }
  180. hoverOff(e={}){
  181. //console.log("hoverOff " + this.id )
  182. transitions.start(lerp.property(this.marker.material, "opacity", 0.5), 250)
  183. if(!e.byMap) this.dispatchEvent({type:'hoverOff', byMainView:true})
  184. }
  185. setZoomed(zoomed){
  186. this.zoomed = zoomed;
  187. Potree.settings.displayMode == 'showPanos' && this.updateSkyboxForZoomLevel(); //放大后换成zoomTarget贴图
  188. viewer.dispatchEvent({type:'panoSetZoom', zoomed})
  189. }
  190. enter(){
  191. this.setZoomed(!1),
  192. viewer.dispatchEvent({type:PanoramaEvents.Enter, oldPano:old, newPano:this } )
  193. old = this
  194. //console.log("enter pano "+ this.id)
  195. }
  196. exit(){
  197. /* if(this.tiled)
  198. { */
  199. this.clearWaitDeferreds();
  200. this.minimumTiledPanoLoaded = !1;
  201. this.tiledPanoRenderTarget = null;
  202. this.setZoomed(!1);
  203. this.images360.panoRenderer.deactivateTiledPano(this);
  204. this.highestPartialTileRenderOpCompleted = 0;
  205. this.highestFullTileRenderOpCompleted = 0;
  206. /*}
  207. else
  208. {
  209. this.solidSkybox.dispose();
  210. this.solidSkybox.loaded = !1;
  211. this.solidSkybox.version = 0;
  212. } */
  213. //console.log("exit pano "+ this.id)
  214. viewer.dispatchEvent({type:PanoramaEvents.Exit, pano:this});
  215. }
  216. updateSkyboxForZoomLevel(){
  217. if(this.minimumTiledPanoLoaded){
  218. this.images360.updateProjectedPanos();
  219. }
  220. }
  221. getSkyboxTexture(){
  222. if(this.minimumTiledPanoLoaded)
  223. {
  224. if(this.zoomed && this.images360.qualityManager.maxRenderTargetSize > this.images360.qualityManager.maxNavPanoSize)//change 如果放大后和不放大都是2k就不用这个
  225. {
  226. return this.images360.panoRenderer.zoomRenderTarget.texture;
  227. }
  228. else
  229. {
  230. this.tiledPanoRenderTarget.texture.mapping = THREE.UVMapping//add
  231. return this.tiledPanoRenderTarget.texture;
  232. }
  233. }
  234. else
  235. {
  236. return null;
  237. }
  238. }
  239. isLoaded(e){
  240. if (e && "string" == typeof e)
  241. console.error("Wrong panoSize given to Panorama.isLoaded(); a tiled pano uses PanoSizeClass");
  242. return !!this.minimumTiledPanoLoaded && (!e || this.highestFullTileRenderOpCompleted >= e)//改:原本是:this.highestPartialTileRenderOpCompleted >= e, 希望这代表全部加载完
  243. }
  244. getWaitDeferred(size){//获取不同size的tile贴图的promiss
  245. var t = this.resolutionPromise[this.id];
  246. t || (t = {}, this.resolutionPromise[this.id] = t);
  247. var i = t[size];
  248. return i || (i = {
  249. deferred: $.Deferred(),
  250. active: !1
  251. },
  252. t[size] = i),
  253. i
  254. }
  255. clearWaitDeferreds(){
  256. var e = this.resolutionPromise[this.id];
  257. e || (e = {},
  258. this.resolutionPromise[this.id] = e);
  259. for (var t in e)
  260. if (e.hasOwnProperty(t)) {
  261. var i = e[t];
  262. i.active = !1,
  263. i.deferred = $.Deferred()
  264. }
  265. }
  266. resetWaitDeferred(e){
  267. var t = this.getWaitDeferred(e);
  268. t.active = !1;
  269. t.deferred = $.Deferred();
  270. }
  271. onTileRendered(ev){
  272. ev.id === this.id && this.dispatchEvent({
  273. type:PanoramaEvents.TileLoaded,
  274. size:ev.panoSize, index:ev.tileIndex, count:ev.totalTiles
  275. });
  276. }
  277. onPanoRendered(ev) {
  278. if(ev.id === this.id)
  279. {
  280. this.minimumTiledPanoLoaded = !0;
  281. this.updateSkyboxForZoomLevel();//更新贴图 setProjected
  282. ev.panoSize > this.highestPartialTileRenderOpCompleted && (this.highestPartialTileRenderOpCompleted = ev.panoSize);//应该是更新最高获取到的Partial size
  283. ev.updateFullComplete && ev.panoSize > this.highestFullTileRenderOpCompleted && (this.highestFullTileRenderOpCompleted = ev.panoSize); //应该是更新最高获取到的Full size
  284. //this.dispatchEvent("load", ev.panoSize);
  285. viewer.ifAllLoaded( this);
  286. this.dispatchEvent({type:PanoramaEvents.LoadComplete, size:ev.panoSize, count:ev.totalTiles});
  287. }
  288. }
  289. onTileRenderFail(ev) {
  290. ev.id === this.id && this.dispatchEvent({type:PanoramaEvents.LoadFailed });
  291. }
  292. onUploadAttemptedForAllTiles(ev) {
  293. if (ev.id === this.id) {
  294. var n = this.images360.qualityManager.getPanoSize(PanoSizeClass.BASE);
  295. if(ev.panoSize === n && this.shouldRedrawOnBaseLoaded) //shouldRedrawOnBaseLoaded一直是false。在4dkk里只有初始点在quickstart后变为true。
  296. {
  297. this.shouldRedrawOnBaseLoaded = !1;
  298. this.panoRenderer.resetRenderStatus(this.id, !0, !1);
  299. this.panoRenderer.renderPanoTiles(this.id, null, !0, !0);
  300. }
  301. }
  302. }
  303. createTextLabel(){
  304. this.removeTextLabel()
  305. this.label = new TextSprite($.extend(
  306. labelProp, {text: this.id }) //{text: `id:${this.id}, dataset:${this.pointcloud.name}, 4dkkId:${this.originID}`}
  307. );
  308. this.images360.node.add(this.label);
  309. this.floorPosition && this.label.position.copy(this.floorPosition)
  310. }
  311. removeTextLabel(){
  312. if(this.label){
  313. this.label.parent.remove(this.label);
  314. }
  315. }
  316. };
  317. Panorama.prototype.loadTiledPano = function() {
  318. //var downloads = [] , t = [];
  319. var downloaded = {} , eventAdded = {}, latestPartialRequest = {}; //每个pano对应一组这些
  320. return function(size, dirs, fov, o, a, download) {
  321. var dir = dirs.datasetsLocal.find(e=>e.datasetId == this.pointcloud.dataset_id).direction;
  322. //var dir = dirs
  323. null !== o && void 0 !== o || (o = !0),
  324. null !== a && void 0 !== a || (a = !0);
  325. var l = this.getWaitDeferred(size)
  326. , c = l.deferred
  327. , h = null
  328. , u = null;
  329. fov && ("number" == typeof fov ? h = fov : (h = fov.hFov, u = fov.vFov))
  330. if (!this.isLoaded(size)) {
  331. //console.log('loadTiledPano', this.id, size, fov)
  332. if (!l.active) {
  333. l.active = !0
  334. let name = this.id + ":" + size
  335. downloaded[name] = downloaded[name] || []
  336. /*
  337. this.downloaded = downloaded
  338. this.latestPartialRequest = latestPartialRequest
  339. */
  340. latestPartialRequest[name] = null
  341. if (fov) {
  342. let tileArr = []//add
  343. var d = TileUtils.matchingTilesInDirection(this, size, dir, h, u, tileArr);
  344. latestPartialRequest[name] = tileArr
  345. downloaded[name].forEach((e)=>{
  346. let item = latestPartialRequest[name].find(a=>e.faceTileIndex == a.faceTileIndex && e.face == a.face)
  347. if(item){
  348. item.loaded = true
  349. }
  350. })
  351. if(!latestPartialRequest[name].some(e=>!e.loaded)){//所需要的全部加载成功
  352. //let total = TileUtils.getTileCountForSize(size)
  353. //this.onPanoRendered(this.id, size, total, !0);
  354. c.resolve(size/* , total */);
  355. this.resetWaitDeferred(size)
  356. //console.log('该部分早已经加载好了'+size, this.id)
  357. latestPartialRequest[name] = null
  358. }
  359. //console.log("Loading partial pano: " + this.id + " with " + d + " tiles")
  360. }
  361. if(!eventAdded[this.id]) {
  362. eventAdded[this.id] = !0
  363. this.addEventListener(PanoramaEvents.LoadComplete, function(ev/* e, t */) {//本次任务全部加载完毕
  364. //console.warn('点位(可能部分)下载完成 ', 'id:'+this.id, 'size:'+ev.size )
  365. var i = this.getWaitDeferred(ev.size).deferred;//"pending"为还未完成
  366. i && "pending" === i.state() && this.highestPartialTileRenderOpCompleted >= ev.size && (i.resolve(ev.size, ev.count),
  367. this.resetWaitDeferred(ev.size))//恢复active为false
  368. }.bind(this))
  369. this.addEventListener(PanoramaEvents.LoadFailed, function(ev) {
  370. var t = this.getWaitDeferred(e).deferred;
  371. t && "pending" === t.state() && this.highestPartialTileRenderOpCompleted >= ev.t && (t.reject(ev.t),
  372. this.resetWaitDeferred(ev.t))//恢复active为false
  373. }.bind(this))
  374. this.addEventListener(PanoramaEvents.TileLoaded, function(ev/* t, i, n */) {//每张加载完时
  375. //console.log('tileLoaded', 'id:'+this.id, 'size:'+ev.size, 'tileIndex:'+ev.index )
  376. let tileIndex = ev.index
  377. let total = ev.count
  378. let size = ev.size
  379. let name = this.id + ":" + size
  380. downloaded[name] = downloaded[name] || [] //不是所有的加载都是从loadTiledPano获取的所以会有未定义的情况
  381. let {faceTileIndex,face} = TileUtils.getTileLocation(size, tileIndex, {})
  382. downloaded[name].push({faceTileIndex,face})
  383. var r = this.getWaitDeferred(size).deferred;
  384. if (r && "pending" === r.state()) {
  385. r.notify(size, tileIndex, total);
  386. if(latestPartialRequest[name]){
  387. let item = latestPartialRequest[name].find(e=>e.faceTileIndex == faceTileIndex && e.face == face)
  388. item && (item.loaded = true )
  389. if(!latestPartialRequest[name].some(e=>!e.loaded)){//所需要的局部tiles全部加载成功
  390. this.onPanoRendered(this.id, size, total, !0); //onPanoRendered还会触发 PanoramaEvents.LoadComplete
  391. r.resolve(size, total);
  392. this.resetWaitDeferred(size)
  393. //console.log('该部分加载好了'+size, this.id)
  394. latestPartialRequest[name] = null
  395. }
  396. }
  397. }
  398. /* var r = this.getWaitDeferred(ev.size).deferred;
  399. if (r && "pending" === r.state()) {
  400. r.notify(ev.size, ev.index, ev.count);
  401. var o = downloads[this.id + ":" + ev.size];
  402. if(o){//如果有规定下载哪些tile,只需要下载这些tile则LoadComplete
  403. o.tileCount++
  404. if(o.tileCount === o.targetTileCount){//达到下载目标数
  405. this.onPanoRendered(this.id, ev.size, ev.count, !0);
  406. r.resolve(ev.size, ev.count);
  407. this.resetWaitDeferred(ev.size)
  408. }
  409. }
  410. } */
  411. }.bind(this))
  412. }
  413. }
  414. this.images360.tileDownloader.clearForceQueue(),
  415. this.images360.tileDownloader.forceQueueTilesForPano(this, size, dir, h, u, download)
  416. this.tiledPanoRenderTarget = this.images360.panoRenderer.activateTiledPano(this, this.images360.qualityManager.getMaxNavPanoSize(), o)
  417. this.images360.panoRenderer.renderPanoTiles(this.id, dirs, a)
  418. }else{
  419. //console.log('早已经全加载好了' +size, this.id)
  420. c.resolve(size)
  421. }
  422. return c.promise()
  423. }
  424. }()
  425. /*
  426. 经观察发现,navvis的也存在的问题是点云和全景有微小的偏差,导致远处的热点在全景和点云上看位置差别感大,比如一个在路上一个在天空上。
  427. */
  428. export default Panorama