panoEditor.js 48 KB

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