panoEditor.js 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299
  1. import * as THREE from "../../../../libs/three.js/build/three.module.js";
  2. import math from "../../utils/math.js"
  3. import Common from '../../utils/Common.js'
  4. import {LineDraw, MeshDraw} from "../../utils/DrawUtil.js";
  5. import {ExtendView} from "../../../viewer/ExtendView.js";
  6. import Viewport from "../../viewer/Viewport.js";
  7. import Sprite from "../../objects/Sprite.js";
  8. import {transitions, easing, lerp} from '../../utils/transitions.js'
  9. import {TransformControls} from "../../objects/tool/TransformControls.js";
  10. import SplitScreen from "../../utils/SplitScreen.js"
  11. import DepthBasicMaterial from "../../materials/DepthBasicMaterial.js";
  12. let clickPanoToDisLink = false;//是否在编辑漫游点连接时,通过点击漫游点能断开连接
  13. let images360, Alignment, SiteModel
  14. const texLoader = new THREE.TextureLoader()
  15. texLoader.crossOrigin = "anonymous"
  16. const rotQua = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), Math.PI)
  17. const lineMats = {}
  18. const circleMats = {}
  19. const renderOrders = {
  20. circleSelected:3,
  21. circle:2,
  22. line:1,
  23. }
  24. const pointColor = {
  25. /* selected:"#c80",
  26. default:'#1ac' */
  27. selected:"#c60",
  28. default:'#17c'
  29. }
  30. const opacitys = {
  31. default:1.3,
  32. selected: 2.0
  33. }
  34. const cameraProps = [
  35. {
  36. name : 'top',
  37. axis:["x","y"],
  38. direction : new THREE.Vector3(0,0,-1), //镜头朝向
  39. openCount:0,
  40. },
  41. {
  42. name : 'right',
  43. axis:["y","z"],
  44. direction : new THREE.Vector3(1,0,0),
  45. openCount:0,
  46. }
  47. ]
  48. class PanoEditor extends THREE.EventDispatcher{
  49. constructor(){
  50. super()
  51. this.panoGroup = [], //分组
  52. this.viewports = {},
  53. this.panoLink = {},
  54. this.panoMeshs = new THREE.Object3D,
  55. this.lineMeshes = new THREE.Object3D
  56. this.views = {}
  57. this.cameras = {}
  58. this.orthoCamera = new THREE.OrthographicCamera(-100, 100, 100, 100, 0.01, 10000)
  59. this.orthoCamera.up.set(0,0,1)
  60. this.selectedPano;
  61. this.selectedGroup;
  62. this.operation;
  63. this.visiblePanos = []
  64. }
  65. init(){
  66. {//init lineMats
  67. lineMats.default = LineDraw.createFatLineMat({
  68. color: '#eeeeee',
  69. lineWidth: 2,
  70. depthTest:false
  71. })
  72. lineMats.hovered = LineDraw.createFatLineMat({
  73. color: '#00c8af',
  74. lineWidth: 2,
  75. depthTest:false
  76. })
  77. lineMats.selected = LineDraw.createFatLineMat({
  78. color: '#00c8af',
  79. lineWidth: 3,
  80. depthTest:false
  81. })
  82. }
  83. this.initViews()
  84. viewer.addEventListener('allLoaded',()=>{
  85. images360 = viewer.images360
  86. Alignment = viewer.modules.Alignment
  87. SiteModel = viewer.modules.SiteModel
  88. this.panoMeshs.name = 'panoMeshs'
  89. viewer.scene.scene.add(this.panoMeshs)
  90. this.lineMeshes.name = 'lineMeshes'
  91. viewer.scene.scene.add(this.lineMeshes)
  92. {
  93. this.transformControls = new TransformControls(viewer.mainViewport.camera, viewer.renderArea,{
  94. dontHideWhenFaceCamera: true,
  95. rotFullCircle:true
  96. });
  97. this.transformControls.setSize(1.5)
  98. viewer.scene.scene.add(this.transformControls)
  99. this.transformControls._gizmo.hideAxis = {translate:['x','y'], rotate:['x','y','e'] }
  100. this.transformControls.setRotateMethod(2)
  101. this.fakeMarkerForTran = new THREE.Mesh(new THREE.BoxBufferGeometry(0.3,0.3,0.3) , new THREE.MeshBasicMaterial({
  102. color:"#FFFFFF", opacity:0.4, transparent:true, visible:false
  103. }));//一个看不见的mesh,只是为了让transformControls移动点云
  104. viewer.scene.scene.add(this.fakeMarkerForTran)
  105. let afterMoveCircle = (type)=>{
  106. if(type == 'position'){
  107. let moveVec = new THREE.Vector3().subVectors(this.fakeMarkerForTran.position, this.fakeMarkerForTran.oldState.position)
  108. this.selectedClouds.forEach(cloud=>Alignment.translate(cloud, moveVec))
  109. }else{
  110. let center = this.selectedPano.position;
  111. let forward = new THREE.Vector3(0,1,0);
  112. let vec1 = forward.clone().applyQuaternion(this.fakeMarkerForTran.oldState.quaternion)
  113. let vec2 = forward.clone().applyQuaternion(this.fakeMarkerForTran.quaternion)
  114. let diffAngle = math.getAngle(vec1,vec2,'z')
  115. this.selectedClouds.forEach(cloud=>{
  116. Alignment.rotateAround(center, cloud, null, diffAngle)
  117. })
  118. }
  119. this.fakeMarkerForTran.oldState = {
  120. position: this.fakeMarkerForTran.position.clone(),
  121. quaternion: this.fakeMarkerForTran.quaternion.clone(),
  122. }
  123. Alignment.writeToHistory( this.selectedClouds )
  124. }
  125. this.fakeMarkerForTran.addEventListener('position_changed', afterMoveCircle.bind(this,'position'))
  126. this.fakeMarkerForTran.addEventListener("rotation_changed", afterMoveCircle.bind(this,'rotation') )
  127. this.transformControls.addEventListener('transform_end',()=>{
  128. Alignment.prepareRecord = true
  129. })
  130. Alignment.history.addEventListener('undo',()=>{
  131. this.updateTranCtl()
  132. })
  133. }
  134. this.initPanoLink()
  135. this.addPanoMesh()
  136. viewer.scene.pointclouds.forEach(e=>{
  137. e.material.color = pointColor.default
  138. })
  139. this.switchView('top')
  140. SiteModel.bus.addEventListener('initDataDone',()=>{
  141. let floor = SiteModel.entities.find(e=>e.buildType == 'floor' && e.panos.length) //选择有漫游点的一层
  142. if(!floor){
  143. floor = 'all' //SiteModel.entities.find(e=>e.buildType == 'floor')
  144. console.log('没有一层有漫游点?!')
  145. }
  146. this.gotoFloor(floor)
  147. })
  148. Alignment.bus.addEventListener('switchHandle', this.updateCursor.bind(this))
  149. viewer.addEventListener('global_click',(e)=>{
  150. if(e.button === THREE.MOUSE.RIGHT){//取消旋转和平移
  151. console.log('right click',e)
  152. this.setLinkOperateState('addLink',false)
  153. this.setLinkOperateState('removeLink',false)
  154. }else if(this.clickToZoomInEnabled){
  155. if(this.activeViewName == 'mainView'){
  156. viewer.controls.zoomToLocation(e.mouse)
  157. }else{
  158. this.zoomIn(e.intersect.orthoIntersect, e.pointer)
  159. }
  160. this.setZoomInState(false)
  161. }
  162. })
  163. /* {//旋转时的辅助线--绕某个点旋转的版本
  164. this.rotGuideLine = LineDraw.createLine([], {color:'#aaffee'})
  165. this.rotGuideLine.visible = false
  166. this.rotGuideLine.name = 'rotGuideLine'
  167. this.rotGuideLine.renderOrder = renderOrders.line
  168. viewer.scene.scene.add(this.rotGuideLine)
  169. let startPoint
  170. Alignment.bus.addEventListener('rotateStart', (e)=>{
  171. startPoint = e.startPoint
  172. })
  173. Alignment.bus.addEventListener('rotate', (e)=>{
  174. LineDraw.updateLine(this.rotGuideLine, [startPoint, e.endPoint] )
  175. this.rotGuideLine.visible = true
  176. })
  177. viewer.fpControls.addEventListener("end",(e)=>{
  178. startPoint = null
  179. this.rotGuideLine.visible = false
  180. })
  181. } */
  182. {//连接时的辅助线
  183. this.linkGuideLine = LineDraw.createLine([], {color:'#aaa', deshed:true, dashSize:0.1,gapSize:0.1,})
  184. this.linkGuideLine.visible = false
  185. this.linkGuideLine.name = 'linkGuideLine'
  186. viewer.scene.scene.add(this.linkGuideLine)
  187. this.linkGuideLine.renderOrder = renderOrders.line
  188. let update = (e)=>{
  189. if(this.operation != 'addLink' || this.activeViewName != 'top' && this.activeViewName != 'mainView' ||!this.selectedPano){
  190. return this.linkGuideLine.visible = false
  191. }
  192. let endPos
  193. if(this.activeViewName == 'top' ){
  194. endPos = e.intersect.orthoIntersect.clone().setZ(this.selectedPano.position.z)
  195. }else if(this.activeViewName == 'mainView' ){
  196. if(!e.intersect.point)return
  197. endPos = e.intersect.point.position
  198. }
  199. LineDraw.updateLine(this.linkGuideLine, [this.selectedPano.position, endPos] )
  200. this.linkGuideLine.visible = true
  201. }
  202. viewer.addEventListener('global_mousemove', (e)=>{
  203. update(e)
  204. })
  205. this.addEventListener('updateLinkGuideLine', update)
  206. //为何打开调试时移动很卡
  207. }
  208. /*
  209. viewer.inputHandler.addEventListener('keydown', (e)=>{
  210. if(e.event.key == "r" ){
  211. this.setTranMode('rotate')
  212. }else if(e.event.key == "t"){
  213. this.setTranMode('translate')
  214. }
  215. }) */
  216. })
  217. }
  218. setTranMode(mode){//rotate or translate
  219. this.tranMode = mode
  220. if(this.activeViewName == 'mainView'){
  221. mode && this.transformControls.setMode(mode)
  222. this.updateTranCtl()
  223. }else{
  224. Alignment.switchHandle(mode)
  225. }
  226. }
  227. updateTranCtl(){// 设置3D页面的transformControls相关
  228. if(!this.tranMode || !this.selectedPano || this.activeViewName != 'mainView' ) {
  229. return this.transformControls.detach()
  230. }else if(this.checkIfAllLinked({group:this.selectedGroup})){
  231. this.dispatchEvent('needToDisConnect')
  232. return this.transformControls.detach()
  233. }
  234. this.transformControls.attach(this.fakeMarkerForTran)
  235. let {position, quaternion} = this.getPanoPose(this.selectedPano);
  236. this.fakeMarkerForTran.position.copy(position)
  237. this.fakeMarkerForTran.quaternion.copy(quaternion)
  238. this.fakeMarkerForTran.oldState = {
  239. position: position.clone(),
  240. quaternion: quaternion.clone(),
  241. }
  242. }
  243. //////////////////////////////////
  244. initViews(){
  245. this.splitScreenTool = new SplitScreen
  246. this.targetPlane = viewer.mainViewport.targetPlane = new THREE.Plane()
  247. this.shiftTarget = viewer.mainViewport.shiftTarget = new THREE.Vector3 //project在targetPlane上的位置
  248. for(let i=0;i<2;i++){
  249. let prop = cameraProps[i];
  250. let view = new ExtendView()
  251. this.views[prop.name] = view
  252. this.cameras[prop.name] = this.orthoCamera
  253. view.direction = prop.direction
  254. }
  255. this.views.mainView = viewer.mainViewport.view
  256. this.cameras.mainView = viewer.mainViewport.camera
  257. }
  258. switchView(name){//替换view和camera到mainViewport
  259. let view = this.views[name]
  260. let camera = this.cameras[name]
  261. let prop = cameraProps.find(e=>e.name == name)
  262. let {boundSize, center} = viewer.bound
  263. this.lastViewName = this.activeViewName
  264. this.activeViewName = name
  265. let lastView = this.views[this.lastViewName]
  266. let lastCamera = this.cameras[this.lastViewName]
  267. viewer.mainViewport.view = view
  268. viewer.mainViewport.camera = camera
  269. if(lastCamera)lastView.zoom = lastCamera.zoom
  270. this.targetPlane.setFromNormalAndCoplanarPoint( view.direction.clone(), center )
  271. this.targetPlane.projectPoint(view.position, this.shiftTarget ) //target转换到过模型中心的平面,以保证镜头一定在模型外
  272. view.position.copy(this.splitScreenTool.getPosOutOfModel(viewer.mainViewport))
  273. if(view.zoom)camera.zoom = view.zoom//恢复上次的zoom
  274. viewer.updateScreenSize({forceUpdateSize:true})//更新camera aspect left等
  275. this.updateCursor()
  276. if(name == 'mainView'){
  277. viewer.mainViewport.alignment = null
  278. viewer.scene.pointclouds.forEach(e=>{
  279. e.material.activeAttributeName = 'rgba'
  280. e.material.useFilterByNormal = false
  281. e.changePointOpacity(1,true)
  282. })
  283. viewer.updateVisible(viewer.reticule, 'force', true)
  284. if(lastView){//2d->3d
  285. view.copy(lastView)
  286. let direction = view.direction
  287. let panos = images360.panos.filter(e=>e.circle.visible)
  288. let nearestPano = Common.sortByScore(panos , [], [(pano)=>{
  289. let vec = new THREE.Vector3().subVectors(pano.position, view.position);
  290. return -vec.dot(direction);
  291. }], true);
  292. //console.log('最近',nearestPano )
  293. if(nearestPano && nearestPano[0] ){ //尽量不变画面范围,使pano点保持原位,转换到mainView
  294. let halfHeight = lastCamera.top/lastCamera.zoom
  295. let dis = halfHeight / Math.tan( THREE.Math.degToRad(camera.fov/2))
  296. view.position.add(direction.clone().multiplyScalar(-nearestPano[0].score - dis))
  297. //console.log('getCloser', -nearestPano[0].score - dis)
  298. this.lastDisToPano = dis //记录一下
  299. }
  300. }
  301. viewer.fpControls.lockKey = false
  302. }else{
  303. if(this.lastViewName == 'mainView'){//3d->2d
  304. let direction = lastView.direction
  305. let panos = images360.panos.filter(e=>e.circle.visible)
  306. //尽量靠近画布中心,且距离相机较近
  307. let nearestPano = Common.sortByScore(panos , [], [(pano)=>{
  308. let vec = new THREE.Vector3().subVectors(pano.position, lastView.position);
  309. let dis = vec.dot(direction);
  310. return dis < 0 ? dis * 10 : - dis
  311. },(pano)=>{
  312. let vec = new THREE.Vector3().subVectors(pano.position, lastView.position);
  313. let angle = vec.angleTo(direction)
  314. return - angle * 70
  315. }], true);
  316. //目前还存在的问题就是不知selectedPano和最近点的取舍
  317. //console.log('panos',nearestPano )
  318. if(nearestPano && nearestPano[0] ){
  319. //console.log('nearestPano',nearestPano[0].item.id )
  320. let pos1 = nearestPano[0].item.position.clone()
  321. let pos2 = pos1.clone()
  322. let dis = new THREE.Vector3().subVectors(nearestPano[0].item.position, lastView.position).dot(direction) //-nearestPano[0].score
  323. //根据2d->3d的式子逆求zoom
  324. let halfHeight = Math.abs(dis) * Math.tan( THREE.Math.degToRad(lastCamera.fov/2))
  325. camera.zoom = camera.top / halfHeight
  326. camera.updateProjectionMatrix()
  327. if(name == 'right'){//侧视图
  328. view.direction = direction.clone().setZ(0) //水平方向设定为3d的方向
  329. this.targetPlane.setFromNormalAndCoplanarPoint( view.direction.clone(), center )
  330. this.targetPlane.projectPoint(view.position, this.shiftTarget ) //target转换到过模型中心的平面,以保证镜头一定在模型外
  331. view.position.copy(this.splitScreenTool.getPosOutOfModel(viewer.mainViewport))
  332. }
  333. view.applyToCamera(camera)//update
  334. pos1.project(lastCamera)
  335. pos2.project(camera)
  336. //目标是找到画面上最接近中心的一点(最好是漫游点,不然就是点云),让其在转换画面后在画面上的位置不变。万一找到的点不在屏幕中(比如当屏幕中没点云时),就默认让那个点移动到屏幕中央,也就是假设当前它pos1在屏幕中央位置。
  337. //
  338. if(pos1.z>1){
  339. console.warn('选取的点在相机背后了!?')
  340. }
  341. //如果最近点超出屏幕范围 (-1,1), 最好将其拉到边缘,甚至居中 。这样屏幕上就不会没有漫游点了
  342. let bound = 0.9
  343. pos1.x = THREE.Math.clamp(pos1.x, -bound, bound)
  344. pos1.y = THREE.Math.clamp(pos1.y, -bound, bound)
  345. let vecOnscreen = new THREE.Vector3().subVectors(pos1,pos2)
  346. let moveVec = Potree.Utils.getOrthoCameraMoveVec(vecOnscreen, camera )
  347. //console.log('pos1', pos1)
  348. view.position.sub(moveVec)
  349. }
  350. }else{
  351. if(prop.openCount == 0){//至多执行一次
  352. this.viewportFitBound(name, boundSize, center)
  353. }
  354. }
  355. prop.openCount ++;
  356. viewer.scene.pointclouds.forEach(e=>{
  357. e.material.activeAttributeName = 'color'
  358. e.material.useFilterByNormal = true //defines : use_filter_by_normal attenuated_opacity
  359. if(this.selectedPano && this.selectedClouds.includes(e) /* this.selectedPano.pointcloud == e */){
  360. e.changePointOpacity(opacitys.selected,true)
  361. }else{
  362. e.changePointOpacity(opacitys.default,true)
  363. }
  364. })
  365. viewer.updateVisible(viewer.reticule, 'force', false)
  366. if(name == 'top') viewer.mainViewport.alignment = {rotate:true,translate:true};
  367. if(name == 'right'){
  368. viewer.mainViewport.alignment = {translate:true, rotateSide:true, translateVec:new THREE.Vector3(0,0,1)}; //只能上下移动
  369. viewer.mainViewport.rotateSide = true
  370. }else{
  371. viewer.mainViewport.rotateSide = false
  372. }
  373. viewer.fpControls.lockKey = true
  374. }
  375. this.updateTranCtl()
  376. this.setTranMode(this.tranMode) // update
  377. this.setZoomInState(false) //取消放大模式
  378. this.updatePointLevels()
  379. }
  380. viewportFitBound(){ //使一个viewport聚焦在某个范围
  381. if(viewer.mainViewport.resolution.x == 0 || viewer.mainViewport.resolution.y == 0){
  382. return setTimeout(()=>{
  383. this.viewportFitBound()
  384. },10)
  385. }
  386. this.gotoFloor(this.currentFloor, true, 0, null, true)
  387. }
  388. rotateSideCamera(angle){//侧视图绕模型中心水平旋转
  389. this.splitScreenTool.rotateSideCamera(viewer.mainViewport, angle)
  390. }
  391. zoomIn(intersect, pointer){
  392. let camera = viewer.mainViewport.camera
  393. let endZoom = 200
  394. //this.orthoMoveFit(intersect, {endZoom:viewer.mainViewport.camera.zoom < aimZoom ? aimZoom : null} , 300)
  395. let startZoom = camera.zoom
  396. if(startZoom >= endZoom){return}
  397. viewer.mainViewport.view.zoomOrthoCamera(camera, endZoom, pointer, 300)
  398. }
  399. orthoMoveFit(pos, info, duration){
  400. var margin = {x:200, y:230}
  401. this.splitScreenTool.viewportFitBound(viewer.mainViewport, info.bound, pos, duration, margin )
  402. }
  403. setZoomInState(state, informinformBy2d){//是否点击后可放大
  404. //if(state && this.activeViewName == 'mainView')return console.log('3D不可放大')
  405. this.clickToZoomInEnabled = !!state
  406. if(state){
  407. viewer.dispatchEvent({type : "CursorChange", action : "add", name:"zoomInCloud"} )
  408. }else{
  409. viewer.dispatchEvent({type : "CursorChange", action : "remove", name:"zoomInCloud" })
  410. }
  411. if(!state && !informinformBy2d){
  412. this.dispatchEvent({type:'operationCancel', operation: 'zoomIn'})
  413. }
  414. }
  415. gotoFloor(floor, force, duration = 600, informBy2d, fitBound){// 选择不同楼层, 切换点位显示。 'all'为全部显示
  416. floor = floor || 'all'
  417. if(this.currentFloor == floor && !force)return
  418. if(this.currentFloor != floor){//如果楼层没变,不修改可视
  419. //let pointclouds = viewer.findPointcloudsAtFloor(floor)
  420. let panos = floor == 'all' ? viewer.images360.panos : floor.panos
  421. viewer.images360.panos.forEach(pano=>{
  422. let v = panos.includes(pano)
  423. this.switchPanoVisible(pano,v)
  424. })
  425. }
  426. this.updateLinesVisible()
  427. //切换楼层时清空选择状态
  428. if(this.selectedPano && floor != 'all' && !floor.panos.includes(this.selectedPano)){
  429. this.selectedPano.circle.dispatchEvent('click')
  430. }
  431. if(this.selectedLine){
  432. this.selectedLine.dispatchEvent('click')
  433. }
  434. let bound, center
  435. if(floor == 'all'){
  436. bound = viewer.images360.bound.bounding
  437. center = viewer.images360.bound.center
  438. }else{
  439. bound = this.getPanosBound(floor)
  440. center = bound.getCenter(new THREE.Vector3())
  441. if(floor.panos.length == 0)console.log(floor.name, 'floor无漫游点' )
  442. }
  443. if(this.activeViewName != 'mainView' ){
  444. fitBound && this.orthoMoveFit(center, {bound}, duration)
  445. }else if(this.activeViewName == 'mainView'){
  446. if(floor != 'all'){ //切换一下位置,因为原处点云会消失
  447. viewer.scene.view.setView({position:center, duration })
  448. }
  449. }
  450. this.currentFloor = floor
  451. if(!informBy2d){
  452. this.dispatchEvent({type:'changeFloor', floor})
  453. }
  454. }
  455. getPanosBound(floor){
  456. if(!floor.panosBound){
  457. if(floor.panos.length == 0){
  458. floor.panosBound = viewer.images360.bound.bounding.clone()
  459. }else{
  460. let minSize = new THREE.Vector3(5,5,5)
  461. let bound = math.getBoundByPoints(floor.panos.map(e=>e.position), minSize)
  462. floor.panosBound = bound.bounding
  463. }
  464. }
  465. return floor.panosBound
  466. }
  467. switchPanoVisible(pano, v, informBy2d){
  468. pano.circle.visible = v
  469. viewer.updateVisible(pano, 'panoEditor', v)
  470. viewer.updateVisible(pano.pointcloud, 'panoEditor', v)
  471. if(v){
  472. this.visiblePanos.includes(pano) || this.visiblePanos.push(pano)
  473. }else{
  474. let index = this.visiblePanos.indexOf(pano);
  475. index>-1 && this.visiblePanos.splice(index,1)
  476. }
  477. if(informBy2d){
  478. this.updateLinesVisible()
  479. }
  480. informBy2d || this.dispatchEvent({type:"switchPanoVisible", pano, v})
  481. this.updatePointLevels()
  482. }
  483. updateLinesVisible(){
  484. this.lineMeshes.children.forEach(line=>{
  485. let names = line.name.split('-')
  486. var pano0 = images360.getPano(names[0])
  487. var pano1 = images360.getPano(names[1])
  488. line.visible = this.visiblePanos.includes(pano0) || this.visiblePanos.includes(pano1)
  489. })
  490. }
  491. updateCursor(){
  492. let cursor
  493. if(this.activeViewName == 'mainView' || !this.selectedPano){
  494. cursor = null
  495. }else{
  496. cursor = Alignment.handleState
  497. }
  498. if(cursor == 'rotate'){
  499. viewer.dispatchEvent({
  500. type : "CursorChange", action : "add", name:"rotatePointcloud"
  501. })
  502. viewer.dispatchEvent({
  503. type : "CursorChange", action : "remove", name:"movePointcloud"
  504. })
  505. }else if(cursor == 'translate'){
  506. viewer.dispatchEvent({
  507. type : "CursorChange", action : "add", name:"movePointcloud"
  508. })
  509. viewer.dispatchEvent({
  510. type : "CursorChange", action : "remove", name:"rotatePointcloud"
  511. })
  512. }else{
  513. viewer.dispatchEvent({
  514. type : "CursorChange", action : "remove", name:"movePointcloud"
  515. })
  516. viewer.dispatchEvent({
  517. type : "CursorChange", action : "remove", name:"rotatePointcloud"
  518. })
  519. }
  520. //this.cursorState = cursor
  521. }
  522. setLinkOperateState(name, state, informinformBy2d){
  523. if(state && name == this.operation || !state && name != this.operation)return
  524. let old = this.operation
  525. this.operation = state ? name : null
  526. if(this.operation == 'removeLink'){
  527. if(this.selectedLine){
  528. this.selectedLine.dispatchEvent('click')//删除
  529. }
  530. if(this.selectedPano && clickPanoToDisLink){
  531. this.selectedPano.circle.dispatchEvent('click')//删除
  532. }
  533. }
  534. if(this.operation != 'addLink'){
  535. this.linkGuideLine.visible = false
  536. }
  537. if(!state && !informinformBy2d){
  538. this.dispatchEvent({type: "operationCancel", operation: old})
  539. }
  540. if(this.operation == 'addLink'){
  541. viewer.dispatchEvent({type : "CursorChange", action : "add", name:"connectPano"} )
  542. }else{
  543. viewer.dispatchEvent({type : "CursorChange", action : "remove", name:"connectPano"} )
  544. }
  545. if(this.operation == 'removeLink'){
  546. viewer.dispatchEvent({type : "CursorChange", action : "add", name:"disconnectPano"} )
  547. }else{
  548. viewer.dispatchEvent({type : "CursorChange", action : "remove", name:"disconnectPano"} )
  549. }
  550. }
  551. /////////////////////////////////
  552. initPanoLink(){
  553. images360.panos.forEach((pano)=>{
  554. this.panoLink[pano.id] = {}
  555. })
  556. images360.panos.forEach((pano)=>{
  557. pano.visibles.forEach(index=>{//visibles中存的是下标!
  558. this.linkChange(pano, images360.getPano(index,'index'), 'add')
  559. })
  560. })
  561. console.log('panoLink',this.panoLink)
  562. }
  563. groupChange(pano0, pano1, type){//修改group (type == 'remove'时,pano1可以为空)
  564. if(type == 'add'){
  565. Common.pushToGroupAuto([pano0, pano1], this.panoGroup )
  566. }else{
  567. let atGroup = this.panoGroup.find(e=>e.includes(pano0) && (e.includes(pano1) || !pano1));//所在组
  568. if(!atGroup){
  569. if(pano1){
  570. console.log('这两个pano原本就不在一个组', pano0.id, pano1.id)
  571. }else{
  572. console.log('pano0不在任何组', pano0)
  573. }
  574. return
  575. }
  576. //断开连接时,因为组内没有其他成员的连接信息,所以需要清除整组,并将剩余的一个个重新连接
  577. this.panoGroup.splice(this.panoGroup.indexOf(atGroup),1) //删除
  578. atGroup.forEach(pano=>{//然后再重新生成这两个和组的关系,各自分组
  579. if(pano == pano0 || pano == pano1)return
  580. for(let id in this.panoLink[pano.id]){
  581. if(this.panoLink[pano.id][id]){
  582. let pano_ = images360.getPano(id)
  583. Common.pushToGroupAuto([pano, pano_], this.panoGroup )
  584. }
  585. }
  586. })
  587. }
  588. }
  589. linkChange(pano0, pano1, type){//修改link (type == 'remove'时,pano1可以为空)
  590. let temp = []
  591. if(type == 'add'){
  592. if(!pano1)return console.error('不支持add时pano1为空')
  593. this.panoLink[pano0.id][pano1.id] = this.panoLink[pano0.id][pano1.id] || {}
  594. this.panoLink[pano1.id][pano0.id] = this.panoLink[pano1.id][pano0.id] || {}
  595. }else{
  596. if(!pano1){
  597. for(let id in this.panoLink[pano0.id]){
  598. if(this.panoLink[pano0.id][id]){
  599. this.panoLink[id][pano0.id] = false
  600. temp.push(id)
  601. }
  602. }
  603. this.panoLink[pano0.id] = {} //全部断连
  604. }else{
  605. this.panoLink[pano0.id][pano1.id] = false
  606. this.panoLink[pano1.id][pano0.id] = false
  607. }
  608. }
  609. if(!pano1){ //全部断连
  610. temp.forEach(id=>{
  611. this.lineChange(pano0, images360.getPano(id) , type)
  612. })
  613. }else{
  614. this.lineChange(pano0, pano1, type)
  615. }
  616. this.groupChange(pano0, pano1, type)
  617. //this.updateSelectGroup()
  618. this.selectPano(this.selectedPano, false,true) //更新选中点云显示
  619. }
  620. lineChange(pano0, pano1, type){//修改line
  621. if(type == 'add'){
  622. if(this.panoLink[pano0.id][pano1.id].line) return
  623. let line = LineDraw.createFatLine([pano0.position, pano1.position], {material:lineMats.default})
  624. line.name = `${pano0.id}-${pano1.id}`
  625. line.renderOrder = line.pickOrder = renderOrders.line
  626. this.lineMeshes.add(line)
  627. this.panoLink[pano0.id][pano1.id].line = this.panoLink[pano1.id][pano0.id].line = line
  628. line.addEventListener('mouseover', ()=>{
  629. if(this.clickToZoomInEnabled)return
  630. //if(this.activeViewName == 'mainView')return
  631. if(this.selectedLine != line)line.material = lineMats.hovered
  632. viewer.dispatchEvent({
  633. type : "CursorChange", action : "add", name:"hoverLine"
  634. });
  635. });
  636. line.addEventListener('mouseleave', ()=>{
  637. if(this.clickToZoomInEnabled)return
  638. //if(this.activeViewName == 'mainView')return
  639. if(this.selectedLine != line)line.material = lineMats.default
  640. viewer.dispatchEvent({
  641. type : "CursorChange", action : "remove", name:"hoverLine"
  642. });
  643. });
  644. line.addEventListener('click', (e)=>{
  645. if(this.clickToZoomInEnabled)return
  646. //if(this.activeViewName == 'mainView')return
  647. if(this.operation == 'removeLink'){
  648. if(this.selectedLine == line) this.selectLine(null)
  649. return this.linkChange(pano0, pano1, 'remove')
  650. }
  651. this.selectLine(line)
  652. })
  653. }else{
  654. let line = this.lineMeshes.children.find(e=>e.name == `${pano0.id}-${pano1.id}` || e.name == `${pano1.id}-${pano0.id}` )
  655. if(line){
  656. this.lineMeshes.remove(line)
  657. line.geometry.dispose()
  658. }
  659. }
  660. }
  661. selectLine(line){
  662. if(this.selectedLine == line)return
  663. if(this.selectedLine){
  664. this.selectedLine.material = lineMats.default;
  665. }
  666. if(line){
  667. line.material = lineMats.selected
  668. }
  669. this.selectedLine = line
  670. }
  671. addPanoMesh(){
  672. let map = texLoader.load(Potree.resourcePath+'/textures/correct_n.png' )
  673. /* circleMats.default_normal = new THREE.MeshBasicMaterial({
  674. map,
  675. color: 0xffffff,
  676. transparent: true,
  677. depthTest: false,
  678. depthWrite: false,
  679. }) */
  680. window.circleMats = circleMats
  681. circleMats.default_normal = new DepthBasicMaterial({
  682. map,
  683. color: 0xffffff,
  684. transparent: true,
  685. /* depthTest: false,
  686. depthWrite: false, */
  687. clipDistance : 5,//消失距离
  688. occlusionDistance: 2,//变为backColor距离
  689. useDepth:true,
  690. maxClipFactor: 0.6,
  691. backColor: 0x33ffdd,
  692. })
  693. circleMats.default_rtk_on = circleMats.default_normal.clone();
  694. circleMats.default_rtk_on.map = texLoader.load(Potree.resourcePath+'/textures/rtk-y-n.png' )
  695. circleMats.default_rtk_off = circleMats.default_normal.clone();
  696. circleMats.default_rtk_off.map = texLoader.load(Potree.resourcePath+'/textures/rtk-f-n.png' )
  697. circleMats.selected_normal = circleMats.default_normal.clone();
  698. circleMats.selected_normal.map = texLoader.load(Potree.resourcePath+'/textures/correct_s.png' )
  699. circleMats.selected_normal.useDepth = false;
  700. circleMats.selected_rtk_on = circleMats.selected_normal.clone();
  701. circleMats.selected_rtk_on.map = texLoader.load(Potree.resourcePath+'/textures/rtk-y-s.png' )
  702. circleMats.selected_rtk_off = circleMats.selected_normal.clone();
  703. circleMats.selected_rtk_off.map = texLoader.load(Potree.resourcePath+'/textures/rtk-f-s.png' )
  704. circleMats.hovered_normal = circleMats.default_normal.clone();
  705. circleMats.hovered_normal.color.set(0x00ff00)
  706. circleMats.hovered_normal.useDepth = false
  707. circleMats.hovered_rtk_on = circleMats.default_rtk_on.clone();
  708. circleMats.hovered_rtk_on.color.set(0x00ff00)
  709. circleMats.hovered_rtk_on.useDepth = false
  710. circleMats.hovered_rtk_off = circleMats.default_rtk_off.clone();
  711. circleMats.hovered_rtk_off.color.set(0x00ff00)
  712. circleMats.hovered_rtk_off.useDepth = false
  713. let setPos = (circle)=>{
  714. circle.position.copy(circle.pano.position)
  715. for(let id in this.panoLink[circle.pano.id]){
  716. let linkInfo = this.panoLink[circle.pano.id][id]
  717. if(linkInfo){
  718. LineDraw.updateLine(linkInfo.line, [circle.pano.position, images360.getPano(id).position] )
  719. }
  720. }
  721. circle.update() //update sprite Matrix
  722. }
  723. images360.panos.forEach(pano=>{
  724. var circle = new Sprite({mat: circleMats['default' + '_'+ this.getPanoRtkState(pano) ] , sizeInfo:{
  725. minSize : 50 , maxSize : 120, nearBound : 2, farBound : 10,
  726. },
  727. renderOrder : renderOrders.circle,
  728. pickOrder: renderOrders.circle
  729. })
  730. circle.pickDontCheckDis = true
  731. circle.name = 'panoCircle'
  732. circle.sid = pano.id
  733. circle.pano = pano;
  734. pano.circle = circle;
  735. this.panoMeshs.add(circle)
  736. setPos(circle)
  737. pano.addEventListener('rePos', setPos.bind(this,circle))
  738. let drag = ()=>{
  739. /* if(this.activeViewName == 'mainView' && this.tranMode == 'translate'){//如果3d页不禁止xy的话,这段打开
  740. this.transformControls.dispatchEvent('dragging')//触发拖拽
  741. return
  742. } */
  743. if(this.tranMode != 'translate' || this.activeViewName == 'mainView')return
  744. this.selectPano(circle.pano) //为了方便拖拽点云,拖动circle就直接选中
  745. viewer.inputHandler.drag.object = null //取消拖拽状态,否则不触发点云拖动
  746. }
  747. circle.addEventListener('drag', drag)
  748. circle.addEventListener('mouseover', ()=>{
  749. this.hoverPano(pano,true)
  750. })
  751. circle.addEventListener('mouseleave', ()=>{
  752. this.hoverPano(pano,false)
  753. })
  754. circle.addEventListener('click', ()=>{
  755. //if(this.activeViewName == 'mainView')return
  756. if(this.clickToZoomInEnabled)return
  757. if(clickPanoToDisLink && this.operation == 'removeLink'){
  758. this.linkChange(pano, null, 'remove') //删除所有连接
  759. }
  760. if(this.selectedPano == circle.pano) return this.selectPano(null)
  761. if(this.operation == 'addLink' && this.selectedPano){
  762. this.linkChange(this.selectedPano, circle.pano, 'add')
  763. //this.setLinkOperateState('addLink',false)
  764. return
  765. }
  766. //if(this.operation == 'removeLink' && this.selectedPano){ //和选择中心点冲突
  767. // this.linkChange(this.selectedPano, circle.pano, 'remove')
  768. // //this.setLinkOperateState('removeLink',false)
  769. // return
  770. // }
  771. this.selectPano(circle.pano)
  772. })
  773. })
  774. }
  775. hoverPano(pano, state){
  776. if(this.clickToZoomInEnabled)return
  777. if(pano && state){ //在hover一个pano之前,一定会先取消已经hover的pano, 最多存在一个hovered的pano
  778. if(this.hoveredPano == pano)return
  779. if(this.hoveredPano){
  780. this.hoverPano(this.hoveredPano,false)
  781. }
  782. this.hoveredPano = pano
  783. pano.hovered = true
  784. if(/* this.activeViewName == 'mainView' || */Alignment.handleState && this.selectedPano && this.selectedPano == pano)return
  785. if(this.operation != 'addLink' || !this.selectedPano || this.selectedPano == pano){ // this.selectedPano == pano?
  786. viewer.dispatchEvent({
  787. type : "CursorChange", action : "add", name:"hoverPano"
  788. });
  789. }
  790. if(this.selectedPano != pano) pano.circle.material = circleMats['hovered' + '_'+ this.getPanoRtkState(pano) ]
  791. }else if(pano && !state){//unhover
  792. if(this.hoveredPano != pano)return
  793. pano.hovered = false
  794. viewer.dispatchEvent({
  795. type : "CursorChange", action : "remove", name:"hoverPano"
  796. });
  797. if(this.selectedPano != pano) pano.circle.material = circleMats['default' + '_'+ this.getPanoRtkState(pano) ]
  798. this.hoveredPano = null;
  799. }else{//unhover any
  800. if(this.hoveredPano){
  801. this.hoverPano(this.hoveredPano, false)
  802. }
  803. }
  804. }
  805. selectPano(pano, informinformBy2d, force){
  806. if(this.selectedPano == pano && !force)return
  807. let lastSeletedPano = this.selectedPano
  808. if(this.selectedPano){
  809. this.selectedPano.circle.material = circleMats['default' + '_'+ this.getPanoRtkState(this.selectedPano) ]
  810. this.selectedPano.circle.renderOrder = renderOrders.circle
  811. if(this.activeViewName == 'mainView'){
  812. }else{
  813. this.selectedClouds.forEach(e=>{
  814. e.changePointOpacity(opacitys.default,true)
  815. e.material.color = pointColor.default;
  816. })
  817. }
  818. }
  819. this.selectedPano = pano || null
  820. this.updateSelectGroup();
  821. if(pano){
  822. this.selectedPano.circle.material = circleMats['selected' + '_'+ this.getPanoRtkState(this.selectedPano) ]
  823. this.selectedPano.circle.renderOrder = this.selectedPano.circle.pickOrder = renderOrders.circleSelected //侧视图能显示在最前
  824. if(this.activeViewName == 'mainView'){
  825. }else{
  826. this.selectedClouds.forEach(e=>{
  827. e.changePointOpacity(opacitys.selected,true)
  828. e.material.color = pointColor.selected;
  829. })
  830. }
  831. {//自动切换楼层
  832. let atFloor = SiteModel.entities.find(e=>e.buildType == 'floor' && e.panos.includes(pano))
  833. if(!atFloor){
  834. atFloor = 'all'
  835. }else{
  836. }
  837. this.gotoFloor(atFloor, false, 600 )
  838. }
  839. }
  840. this.updateCursor()
  841. this.updateTranCtl()
  842. if(informinformBy2d){
  843. if(this.selectedPano){
  844. if(this.activeViewName == 'mainView'){ //平移,focus选中的pano
  845. let distance = this.lastDisToPano || 5;
  846. if(lastSeletedPano){
  847. distance = viewer.mainViewport.camera.position.distanceTo(lastSeletedPano.position)
  848. }
  849. viewer.focusOnObject({ position:this.selectedPano.position}, 'point', null, {distance })
  850. }else{
  851. this.orthoMoveFit(this.selectedPano.position, {}, 500)
  852. }
  853. }
  854. }else{
  855. this.dispatchEvent({type:'panoSelect', pano })
  856. }
  857. }
  858. updatePointLevels(){
  859. let percent = 1
  860. if(this.activeViewName == 'mainView'){
  861. //假设每个pointcloud所带的点个数大致相同,那么当可见点云个数越多,所能展示的level越低,否则因总个数超过budget的话密度会参差不齐。
  862. let visiCount = viewer.scene.pointclouds.filter(e=>e.visible).length
  863. let maxCount = 70, minCount = 1, minPer = 0.4, maxPer = 1
  864. percent = maxPer - ( maxPer - minPer) * THREE.Math.clamp((visiCount - minCount) / (maxCount - minCount),0,1) //dis2d越大,角度要越小
  865. //pointcloud.changePointSize()
  866. //console.log('updatePointLevels', percent, visiCount)
  867. }else{
  868. percent = null
  869. }
  870. Potree.settings.UserDensityPercent = percent
  871. viewer.setPointLevels()
  872. }
  873. getPanoRtkState(pano){
  874. return pano.panosData.has_rtk ? pano.rtkState ? 'rtk_on' : 'rtk_off' : 'normal'
  875. }
  876. setPanoRtkState(pano,state){
  877. pano.rtkState = state
  878. pano.circle.material = circleMats[(this.selectedPano == pano ? 'selected' : 'default') + '_'+ this.getPanoRtkState(pano) ]
  879. }
  880. updateSelectGroup(){//更新选中的组
  881. this.selectedGroup = this.panoGroup.find(e=>e.includes(this.selectedPano))
  882. if(this.selectedGroup){
  883. this.selectedGroup = [this.selectedPano, ...this.selectedGroup.filter(e=>e != this.selectedPano)];//将选中的放第一个,便于旋转时绕其旋转。
  884. }
  885. this.selectedClouds = this.selectedPano ? (this.selectedGroup || [this.selectedPano]).map(e=>e.pointcloud) : []
  886. }
  887. checkIfCanSave(){//如果未全部相连,不能保存
  888. for(let datasetId in Potree.settings.datasetsPanos ) {
  889. if(!this.checkIfAllLinked({datasetId})){
  890. console.log('没有全部连通,不能保存。其中一个:', datasetId)
  891. return
  892. }
  893. }
  894. return true
  895. }
  896. checkIfAllLinked(o){//某个(or组所在的)数据集是否全部连通
  897. let datasetId, group
  898. if(o.group){
  899. group = o.group
  900. let pano = o.group[0]
  901. if(!pano)return //会有没有漫游点的点云来编辑吗
  902. datasetId = pano.pointcloud.dataset_id
  903. }else if(o.datasetId){
  904. datasetId = o.datasetId
  905. group = this.panoGroup.find(panos=>panos[0].pointcloud.dataset_id == datasetId )
  906. if(!group)return //要找的数据集的pano全部都孤立了
  907. }
  908. if(!datasetId)return
  909. let panos = Potree.settings.datasetsPanos[datasetId].panos
  910. return panos.length == group.length
  911. }
  912. getPanoPose(pano){
  913. let pose = {
  914. position: pano.position.clone(),
  915. quaternion: new THREE.Quaternion().setFromRotationMatrix(pano.panoMatrix).premultiply(rotQua) ,
  916. }
  917. return pose
  918. }
  919. exportSavingData(){//输出漫游点新的坐标和朝向、以及连接信息
  920. let sweepLocations = {}
  921. for(let datasetId in Potree.settings.datasetsPanos ) {
  922. let {panos} = Potree.settings.datasetsPanos[datasetId]
  923. let data = panos.map(pano=>{
  924. let visibles = []
  925. for(let id in this.panoLink[pano.id]){
  926. if(this.panoLink[pano.id][id]){
  927. visibles.push(viewer.images360.getPano(id).index)
  928. }
  929. }
  930. let {position, quaternion} = this.getPanoPose(pano);
  931. return Object.assign({}, pano.panosData, {
  932. uuid: pano.uuid,
  933. /* pose:{
  934. translation: dealData(pano.position.clone() ),
  935. rotation: dealData(new THREE.Quaternion().setFromRotationMatrix(pano.panoMatrix).premultiply(rotQua) ),
  936. }, */
  937. pose : {
  938. translation : dealData(position),
  939. rotation : dealData(quaternion)
  940. },
  941. visibles,
  942. use_rtk : !!pano.rtkState
  943. //subgroup: 0,group: 1, "id_view":..
  944. })
  945. })
  946. sweepLocations[datasetId] = {sweepLocations:data}
  947. }
  948. /* this.lineMeshes.children.forEach(e=>{//从line中搜集连接信息,而不从linkInfo,这样visibles不会重复一次
  949. let names = e.name.split('-') //是不是该转成数字
  950. var pano0 = names[0]
  951. var pano1 = names[1]
  952. sweepLocations.find(s=>s.uuid == pano0).visibles.push(pano1)
  953. }) */
  954. function dealData(value){
  955. let v = math.toPrecision(value, 6)
  956. if(v instanceof THREE.Quaternion){
  957. return {x:v.x, y:v.y, z:v.z, w:v.w}
  958. }else if(v instanceof THREE.Vector3){
  959. return {x:v.x, y:v.y, z:v.z}
  960. }
  961. }
  962. console.log(sweepLocations)
  963. return sweepLocations
  964. }
  965. }
  966. /*
  967. 不同数据集之间不能连线
  968. 不同楼层可能也不能
  969. 如果楼层在不同建筑物怎么办? 楼层切换按钮只能在一个建筑内切换。
  970. 全部相连时不能移动和旋转
  971. 如果未全部相连,不能保存
  972. */
  973. export default new PanoEditor()