Panorama.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. import * as THREE from "../../../../libs/three.js/build/three.module.js";
  2. import {transitions, easing, lerp} from '../../utils/transitions.js'
  3. import math from '../../utils/math.js'
  4. import {TextSprite} from '../../objects/TextSprite.js'
  5. import DepthBasicMaterial from "../../materials/DepthBasicMaterial.js";
  6. let { PanoramaEvents } = Potree.defines
  7. var texLoader = new THREE.TextureLoader()
  8. const markerOpacitys ={
  9. default : 0.5,
  10. hovered : 1,
  11. }
  12. const labelProp = {
  13. sizeInfo: {minSize : 200 , maxSize : 250, nearBound : 0.8, farBound : 10},
  14. backgroundColor:{r: 255, g: 255, b: 255, a: 0.4 },
  15. textColor:{r: 0, g: 0, b: 0, a: 1 },
  16. borderRadius: 15,
  17. renderOrder:10,
  18. useDepth:true,
  19. clipDistance: 30, maxClipFactor:0.3, occlusionDistance:3,
  20. }
  21. const labelProp2 = {
  22. //sizeInfo: {minSize : 200 , maxSize : 250, nearBound : 0.8, farBound : 10},
  23. backgroundColor:{r: 255, g: 255, b: 255, a: 0 },
  24. textColor:{r:255 , g: 255, b: 255, a: 1 },
  25. textBorderColor:{r:30 , g:30, b: 30, a: 1 },
  26. textBorderThick:3,
  27. dontFixOrient:true,
  28. renderOrder:10,
  29. fontsize:30,
  30. }
  31. let markerTex
  32. //显示全景图时marker没有被遮挡,如果需要,要换成depthBasicMaterial 或者直接把skybox的深度修改(拿到深度贴图后更如此)
  33. let planeGeo = new THREE.PlaneBufferGeometry(0.2,0.2);
  34. let sg = new THREE.SphereGeometry(0.1, 8, 8);
  35. let smHovered = new THREE.MeshBasicMaterial({/* side: THREE.BackSide, */color: 0xff0000});
  36. let sm = new THREE.MeshBasicMaterial({/* side: THREE.BackSide */});
  37. var rot90 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), Math.PI/2 ); //使用的是刚好适合全景图的,给cube贴图需要转90°
  38. //var rot90 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1,0,0), -Math.PI/2 ); //4dkk->navvis
  39. //var rot901 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,1,0), -Math.PI/2 ); //整张球幕图要旋转下
  40. //rot90 = new THREE.Quaternion().multiplyQuaternions( rot901, rot90)
  41. var old = null;
  42. /*
  43. 转成四维看看的axis:
  44. var a = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), THREE.Math.degToRad(-90)) 因为四维的要绕y转90
  45. 这里的quaternion.multiply(a);
  46. 先乘再换顺序 w : q.w, x:q.x , y:-q.z, z:q.y
  47. */
  48. //暂时直接用4dkkconsole输出的数据
  49. class Panorama extends THREE.EventDispatcher{
  50. constructor(o, images360){//file, time, longitude, latitude, altitude, course, pitch, roll
  51. super()
  52. this.id = o.id; //唯一标识
  53. this.images360 = images360
  54. this.visible = true //for updateVisible
  55. this.enabled = true//是否可以走
  56. this.addEventListener('isVisible',(e)=>{//是否显示该点的mesh(不显示也能走)
  57. //console.log('pano isVisible', this.id, e.visible)
  58. Potree.Utils.updateVisible(this.marker, 'panoVisi', e.visible)
  59. Potree.settings.showPanoMesh && (this.mesh.visible = e.visible)
  60. if(e.reason == 'screenshot' || e.visible){
  61. this.label && (this.label.visible = e.visible)//截图时隐藏下
  62. }
  63. this.label2 && Potree.Utils.updateVisible(this.label2, 'panoVisi', e.visible)
  64. })
  65. /*
  66. 漫游点可见性:旧
  67. level reason 类型
  68. 2(最高)buildingChange(不在此楼层) unvisible
  69. 1 modeIsShowPanos(漫游模式) visible //不记得为什么加这个了,所以重写
  70. 0 pointcloudVisi(隐藏了数据集) unvisible
  71. */
  72. /*
  73. 漫游点可见性:新
  74. level reason 类型
  75. 2(最高)buildingChange(不在此楼层) unvisible
  76. 1 ifShowMarker(marker显示开关) unvisible
  77. 0 pointcloudVisi(隐藏了数据集) unvisible
  78. */
  79. this.panosData = o
  80. this.originPosition = new THREE.Vector3().copy(o.pose.translation)
  81. this.originFloorPosition = new THREE.Vector3().copy(o.puck)
  82. this.originID = parseInt(o.id)// uuid "file_id":"00022"对应是原本的4dkk的id --来自vision.txt
  83. this.pointcloud = viewer.scene.pointclouds[0]
  84. this.pointcloud.panos.push(this)
  85. //this.sid = this.pointcloud.sceneCode + '|' + this.originID //不会更改的标记
  86. this.sid = this.originID //不会更改的标记
  87. //全景图和Cube的水平采样起始坐标相差90度
  88. /* if(from4dkk){
  89. var qua = o.dataset_orientation
  90. var quaternion = new THREE.Quaternion().fromArray(qua)
  91. quaternion = new THREE.Quaternion().multiplyQuaternions(quaternion, rot901);//整张球幕图要旋转下 因为在4dkk里转过,还原。如果是tiles的不用
  92. this.quaternion = new THREE.Quaternion(quaternion.x, -quaternion.z, quaternion.y, quaternion.w) //转化坐标
  93. }else{ */
  94. this.quaternion = new THREE.Quaternion().copy(o.pose.rotation)
  95. this.quaternion4dkk = math.convertVisionQuaternion(this.quaternion)//4dkk内使用的quaternion
  96. this.quaternion2 = this.quaternion.clone()
  97. //this.quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion, rot90);//全景图和Cube的水平采样起始坐标相差90度,cubeTex转90度
  98. this.rotation4dkk = new THREE.Euler().setFromQuaternion(this.quaternion4dkk)
  99. //}
  100. //this.quaternion1 = Potree.Utils.QuaternionFactory.fromArray(o.dataset_orientation)
  101. //同quaternion
  102. //let xy = this.transform.forward([this.longitude, this.latitude]);
  103. //this.file = `https://4dkk.4dage.com/images/images${Potree.settings.number}/pan/high/${this.id}.jpg`
  104. this.neighbours = [];
  105. this.rotation = new THREE.Euler().setFromQuaternion(this.quaternion)
  106. this.build()
  107. this.setPosition(this.originPosition, this.originFloorPosition )//this.transformByPointcloud() //初始化位移
  108. this.addEventListener('hoverOn', (e)=>{//from Map
  109. if(!e.byMainView){
  110. this.hoverOn(e)
  111. }
  112. })
  113. this.addEventListener('hoverOff', (e)=>{
  114. if(!e.byMainView){
  115. this.hoverOff(e)
  116. }
  117. })
  118. }
  119. get noNeighbour(){//是否绝对到不到的孤立点
  120. for(let i=0,j=this.images360.panos.length; i<j; i++){
  121. if(this.images360.neighbourMap[this.id][i] !== false ){
  122. return false
  123. }
  124. }
  125. return true
  126. //return this.neighbours.length == 0
  127. }
  128. setEnable(enable){//是否可以走
  129. Potree.Utils.updateVisible(this, 'isEnabled', enable) //令所有marker不可见
  130. this.enabled = enable
  131. //如果当前在全景模式且在这个点,需要切换显示吗? 目前用不到
  132. }
  133. waitForLoad(){
  134. viewer.waitForLoad(this, ()=>{//发送loading
  135. return this.depthTex && this.skyboxTex
  136. });
  137. }
  138. loadTex(){
  139. if(this.skyboxTex || this.texLoading)return
  140. this.texLoading = true
  141. let src = `${Potree.settings.urls.prefix1}/images/${this.originID}.jpg` //`server\test\SS-t-P1d6CwREny2\${this.id}.jpg` //`${Potree.settings.urls.prefix1}/${Potree.settings.webSite}/${this.pointcloud.sceneCode}/data/${this.pointcloud.sceneCode}/depthmap/${this.originID}.png`
  142. //console.log('开始下载depthImg', this.id)
  143. let startLoad = (src)=>{
  144. let texture = texLoader.load( src, ()=>{
  145. this.skyboxTex = texture
  146. this.dispatchEvent({type:'loadedTex', loaded:true})
  147. this.depthTexLoading = false
  148. //viewer.dispatchEvent('content_changed')
  149. },null,(e)=>{//error
  150. console.error('loadTex失败, 数据集sceneCode'+ this.pointcloud.sceneCode, this.id )
  151. this.dispatchEvent({type:'loadedTex', })
  152. });
  153. texture.wrapS = THREE.RepeatWrapping;
  154. texture.flipY = false
  155. texture.magFilter = THREE.LinearFilter
  156. texture.minFilter = THREE.LinearFilter //防止边缘竖线
  157. texture.generateMipmaps = false
  158. }
  159. Potree.getRealUrl(src, startLoad)
  160. }
  161. loadDepthImg(){
  162. if(!this.pointcloud.hasDepthTex || this.depthTex || this.depthTexLoading)return
  163. this.depthTexLoading = true
  164. let src = //Potree.settings.number == 'SS-t-7DUfWAUZ3V' ? `${Potree.scriptPath}/data/${Potree.settings.number}/depthMap/${this.originID}.png` :
  165. //`${Potree.settings.urls.prefix1}/${Potree.settings.webSite}/${this.pointcloud.sceneCode}/data/${this.pointcloud.sceneCode}/depthmap/${this.originID}.png`
  166. `${Potree.settings.urls.prefix1}/depthmap/${this.originID}.png`
  167. //console.log('开始下载depthImg', this.id)
  168. let startLoad = (src)=>{
  169. let texture = texLoader.load( src, ()=>{
  170. this.depthTex = texture
  171. this.dispatchEvent({type:'loadedDepthImg', pano:this, loaded:true})
  172. this.depthTexLoading = false
  173. this.images360.updateDepthTex(this)
  174. //viewer.dispatchEvent('content_changed')
  175. },null,(e)=>{//error
  176. console.error('loadDepthImg失败, 数据集sceneCode'+ this.pointcloud.sceneCode, this.id )
  177. this.pointcloud.hasDepthTex = false
  178. this.dispatchEvent({type:'loadedDepthImg', pano:this, })
  179. });
  180. texture.wrapS = THREE.RepeatWrapping;
  181. texture.flipY = false
  182. texture.magFilter = THREE.LinearFilter
  183. texture.minFilter = THREE.LinearFilter
  184. texture.generateMipmaps = false
  185. }
  186. Potree.getRealUrl(src, startLoad)
  187. }
  188. build(){
  189. { // orientation
  190. //add
  191. //var quaternion = new THREE.Quaternion().multiplyQuaternions(this.quaternion, rot901);//改 为球目全
  192. //quaternion.premultiply(rot90)
  193. this.panoMatrix = new THREE.Matrix4().makeRotationFromQuaternion(this.quaternion)
  194. //this.oriPanoMatrix = this.panoMatrix.clone()
  195. //if(this.quaternion2)this.oriPanoMatrix2 = new THREE.Matrix4().makeRotationFromQuaternion(this.quaternion2)
  196. //补:全景图下和原来的一样
  197. this.panoMatrix2 = this.panoMatrix
  198. this.panoMatrix2Inverse = this.panoMatrix2.clone().invert();
  199. }
  200. let marker = new THREE.Mesh(planeGeo, this.getMarkerMat() )
  201. marker.name = 'marker_'+this.id
  202. marker.up.set(0,0,1)
  203. marker.lookAt(marker.up)
  204. marker.scale.set(2,2,2)
  205. this.addEventListener('changeMarkerTex',(e)=>{
  206. marker.material.map = markerTex[e.name]
  207. })
  208. this.marker = marker
  209. this.images360.node.add(marker)
  210. Potree.settings.isTest && this.addLabel()
  211. //this.addLabel2()
  212. marker.addEventListener('mouseover', this.hoverOn.bind(this));
  213. marker.addEventListener('mouseleave', this.hoverOff.bind(this));
  214. }
  215. /* transformByPointcloud(){
  216. let position = this.originPosition.clone().applyMatrix4(this.pointcloud.transformMatrix);//也可以用datasetPosTransform算
  217. let floorPosition = this.originFloorPosition.clone().applyMatrix4(this.pointcloud.transformMatrix);
  218. this.setPosition(position, floorPosition)
  219. this.panoMatrix = new THREE.Matrix4().multiplyMatrices(this.pointcloud.rotateMatrix, this.oriPanoMatrix )
  220. //this.panoMatrix2 = Potree.Utils.datasetRotTransform({fromDataset:true, pointcloud:this.pointcloud, matrix:this.oriPanoMatrix, getMatrix:true}) //和上一行结果一样
  221. //quaternion也变下
  222. if(this.oriPanoMatrix2){
  223. this.panoMatrix2 = new THREE.Matrix4().multiplyMatrices(this.pointcloud.rotateMatrix, this.oriPanoMatrix2 )//供DepthImageSampler使用
  224. this.panoMatrix2Inverse = this.panoMatrix2.clone().invert();
  225. }
  226. this.dispatchEvent('rePos')
  227. } */
  228. setPosition(position, floorPosition){
  229. this.position = position
  230. this.floorPosition = floorPosition
  231. //this.mesh.position.copy(this.position)
  232. this.marker.position.copy(this.floorPosition)
  233. this.marker.position.z+=0.04//会被点云遮住
  234. if(this.label){
  235. if(Potree.settings.editType == 'pano'){
  236. this.label.position.copy(this.position)
  237. }else{
  238. this.label.position.copy(this.floorPosition)
  239. }
  240. this.label.position.z+=0.14
  241. this.label.update()
  242. }
  243. if(this.label2){
  244. if(Potree.settings.editType == 'pano'){
  245. this.label2.position.copy(this.position)
  246. }else{
  247. this.label2.position.copy(this.floorPosition)
  248. }
  249. this.label2.position.copy(this.marker.position)
  250. this.label2.update()
  251. }
  252. }
  253. getMarkerMat(){
  254. if(!markerTex) {
  255. markerTex = {
  256. default:texLoader.load( Potree.resourcePath+'/textures/marker.png' ),
  257. ring:texLoader.load( Potree.resourcePath+'/textures/marker2.png' )
  258. }
  259. markerTex.default.anisotropy = 4 // 各向异性过滤 .防止倾斜模糊
  260. markerTex.ring.anisotropy = 4
  261. //有可能被点云遮住吗。
  262. }
  263. return new DepthBasicMaterial({opacity: markerOpacitys.default, side: THREE.DoubleSide , map:markerTex.default ,transparent:true,
  264. clipDistance: 2, occlusionDistance:1, //不能设置太短,因为过渡时深度不准确
  265. useDepth: !!(Potree.settings.useDepthTex && this.pointcloud.hasDepthTex),
  266. autoDepthTest:true
  267. //改为DepthBasicMaterial是因为原Basic的材质在有深度图时过渡会先隐藏后出现。 注:没有深度图时全景模式的marker无法遮挡
  268. })
  269. }
  270. hoverOn(e={}) {
  271. //console.log("hoverOn " + this.id )
  272. transitions.start(lerp.property(this.marker.material, "opacity", markerOpacitys.hovered,()=>{
  273. viewer.dispatchEvent('content_changed')
  274. }), this.marker.visible ? 250 : 0)
  275. if(!e.byMap) this.dispatchEvent({type:'hoverOn', byMainView:true})
  276. if(!e.byImages360) this.images360.dispatchEvent({type:'markerHover', hovered:true, pano:this})
  277. }
  278. hoverOff(e={}){
  279. //console.log("hoverOff " + this.id )
  280. transitions.start(lerp.property(this.marker.material, "opacity", markerOpacitys.default,()=>{
  281. viewer.dispatchEvent('content_changed')
  282. }), this.marker.visible ? 250 : 0)
  283. if(!e.byMap) this.dispatchEvent({type:'hoverOff', byMainView:true})
  284. if(!e.byImages360) this.images360.dispatchEvent({type:'markerHover', hovered:false, pano:this})
  285. }
  286. enter(){
  287. this.entered = true
  288. viewer.dispatchEvent({type:PanoramaEvents.Enter, oldPano:old, newPano:this } )
  289. old = this
  290. //console.log("enter pano "+ this.id)
  291. }
  292. exit(){
  293. this.skyboxTex && this.skyboxTex.dispose()
  294. this.depthTex && this.depthTex.dispose() //贴图不使用后先dispose,下次到该点时会自动还原
  295. this.entered = false //add
  296. viewer.dispatchEvent({type:PanoramaEvents.Exit, pano:this});
  297. }
  298. addLabel(){
  299. this.removeTextLabel()
  300. this.label = new TextSprite(Object.assign({},
  301. labelProp, {text: this.id + "("+this.originID+")"}) //{text: `id:${this.id}, dataset:${this.pointcloud.name}, 4dkkId:${this.originID}`}
  302. );
  303. this.images360.node.add(this.label);
  304. this.floorPosition && this.label.position.copy(this.floorPosition)
  305. }
  306. removeTextLabel(){
  307. if(this.label){
  308. this.label.parent.remove(this.label);
  309. }
  310. }
  311. getCeilHeight(){//天花板高度值 (假设不存在depth为0的点,所有为0的要么是在盲区,要么是无穷远。)
  312. if(this.ceilZ == void 0){
  313. const depthTiming = Potree.timeCollect.depthSampler.median //pc firefox达到4. chrome为0.01
  314. //用三个间隔120度散开,和中心垂直线成一定夹角的三个向量去求 最高高度 (不求平均的原因:万一是0不好算)
  315. let rotMat = new THREE.Matrix4().makeRotationX((Potree.config.depthTexUVyLimit+0.01)*Math.PI)// 角度不能小于天花板中空的半径
  316. let dirs = [new THREE.Vector3(0,0,1).applyMatrix4(rotMat)];
  317. if(depthTiming < 1){
  318. let rotMat1 = new THREE.Matrix4().makeRotationZ(Math.PI*2 / 3);
  319. dirs.push(dirs[0].clone().applyMatrix4(rotMat1))
  320. }
  321. if(depthTiming < 0.3){
  322. let rotMat2 = new THREE.Matrix4().makeRotationZ(-Math.PI*2 / 3);
  323. dirs.push(dirs[0].clone().applyMatrix4(rotMat2));
  324. }
  325. let zs = dirs.map(dir_=>{
  326. let dir = dir_.clone().applyMatrix4(this.panoMatrix2) //pano不一定是垂直的, 需要把之前的dirInPano先转成真实的dir,防止超出角度限制
  327. let intersect = viewer.images360.getIntersect(this, dir)
  328. let z = intersect ? intersect.location.z : Infinity/* this.position.z+skyHeight */ //没有intersect代表可能是天空
  329. return z
  330. })
  331. zs.sort((a,b)=>{return b-a});//得最大值 (不用中位数的原因:在屋檐处,如果仅有一个intersect是天空,因到了室外所以也用天空高度)
  332. this.ceilZ = zs[0]
  333. let min = this.position.z + 1 // 防止意外太低
  334. this.ceilZ = Math.max(min, this.ceilZ)
  335. //console.log(this.id, 'ceilZ:', this.ceilZ )
  336. }
  337. return this.ceilZ
  338. }
  339. };
  340. export default Panorama