SiteModel.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280
  1. import * as THREE from "../../../../libs/three.js/build/three.module.js";
  2. import SplitScreen4Views from "../../utils/SplitScreen4Views.js"
  3. import {BuildingBox} from "./BuildingBox.js"
  4. import Common from "../../utils/Common.js";
  5. import {Images360} from '../panos/Images360.js'
  6. import {KeyCodes} from '../../../KeyCodes.js'
  7. import {config } from "../../settings.js";
  8. import math from "../../utils/math.js";
  9. const minFloorHeight = 0.5
  10. const ifDrawDatasetBound = true //显示一下数据集的tightBound线框
  11. const minMarkers = 3
  12. const Limit = {zMin:-config.map.cameraHeight, zMax:config.map.cameraHeight,} //不能超过camera的高度,为了对称所以也限制了最低
  13. var SiteModel = {
  14. bus: new THREE.EventDispatcher(),
  15. entities:[], //所有实体
  16. buildings:[], //所有建筑父集
  17. meshGroup: new THREE.Object3D,
  18. inEntity : null,
  19. lastPos: new THREE.Vector3(Infinity,Infinity,Infinity),
  20. init: function(){
  21. viewer.scene.scene.add(this.meshGroup)
  22. this.meshGroup.name = 'siteModel'
  23. this.SplitScreen = SplitScreen4Views
  24. if(Potree.settings.editType == 'pano'){
  25. return
  26. }
  27. this.createHeightPull();
  28. if(Potree.settings.isTest && ifDrawDatasetBound){
  29. viewer.addEventListener('allLoaded',()=>{
  30. viewer.scene.pointclouds.forEach(pointcloud=>{
  31. let boxPoints = pointcloud.getUnrotBoundPoint();
  32. let boundingBox = new BuildingBox({
  33. name: '数据集tightBound_'+pointcloud.dataset_id,
  34. points: boxPoints,
  35. buildType : 'dataset',
  36. zMax: pointcloud.bound.max.z,
  37. zMin: pointcloud.bound.min.z,
  38. ifDraw:true
  39. })
  40. this.meshGroup.add(boundingBox)
  41. //boundingBox.markers.forEach(e=>e.visible = false)
  42. })
  43. })
  44. }
  45. if(Potree.settings.isOfficial){
  46. viewer.addEventListener('camera_changed', e => {
  47. if(e.changeInfo && e.changeInfo.positionChanged){
  48. this.updateEntityAt()
  49. }
  50. })
  51. }
  52. {
  53. let pressDelete = (e)=>{
  54. if(e.keyCode == KeyCodes.BACKSPACE || e.keyCode == KeyCodes.DELETE){
  55. if(this.selectedMarker){
  56. let entity = this.selectedMarker.parent
  57. let index = entity.markers.indexOf(this.selectedMarker)
  58. entity.removeMarker(index)
  59. if(entity.points.length<2){//删到只剩一个点时重新画(如果是hole的点,直接删除hole吧?)
  60. this.startInsertion('resume',entity)
  61. }
  62. }
  63. }
  64. }
  65. viewer.inputHandler.addEventListener('keydown', pressDelete)
  66. }
  67. },
  68. updateEntityAt(force){
  69. //if(!this.entities.length || this.editing) return //编辑时也要根据位置显示不同楼层的漫游点与cad
  70. let fun = ()=>{ //延时update,防止卡顿
  71. let currPos = viewer.mainViewport.view.position
  72. //if(force || !currPos.equals(this.lastPos) ){
  73. //console.log('currPos ', currPos.toArray())
  74. this.lastPos.copy(currPos)
  75. let entity;
  76. let searchPos = Potree.settings.displayMode == 'showPanos' ? viewer.images360.currentPano : currPos
  77. entity = this.pointInWhichEntity(searchPos, 'room');
  78. if(force || this.inEntity != entity ){
  79. let oldEntity = this.inEntity
  80. this.inEntity = entity
  81. //console.log('buildingChange', entity)
  82. this.bus.dispatchEvent({type:'buildingChange',entity})
  83. //this.updatePanosVisible(oldEntity, this.inEntity)
  84. let lastFloor = this.currentFloor //oldEntity ? oldEntity.buildType == 'floor' ? oldEntity : oldEntity.buildType == 'room' ? oldEntity.buildParent : null : null; //基本只会是floor或room
  85. let currentFloor = entity ? entity.buildType == 'floor' ? entity : entity.buildType == 'room' ? entity.buildParent : null : null; //基本只会是floor或room
  86. if(force || currentFloor != lastFloor){
  87. //console.log('改变了floor',lastFloor,currentFloor)
  88. this.currentFloor = currentFloor
  89. this.bus.dispatchEvent({type:'FloorChange',currentFloor})
  90. }
  91. }
  92. force = false
  93. return true
  94. //}
  95. }
  96. if(force)fun()
  97. else Common.intervalTool.isWaiting('sitemodelCameraInterval', fun , 500)
  98. },
  99. enter:function(){
  100. Potree.Log('sitemodel enter')
  101. this.clear() //确保全部清空
  102. this.editing = true
  103. //this.updatePanosVisible(null, null, true)//show all
  104. viewer.updateFpVisiDatasets()
  105. let mapViewport = viewer.mapViewer.viewports[0]
  106. this.SplitScreen.split({siteModel:true/* , viewports:[{name:'Top',viewport : mapViewport }] */})
  107. viewer.viewports.forEach(e=>{
  108. if(e.name != 'mapViewport'){
  109. e.layersAdd('siteModelMapUnvisi')
  110. }
  111. if(e.name == 'right' || e.name == 'back'){
  112. e.layersAdd('siteModeSideVisi')
  113. }
  114. })
  115. viewer.images360.panos.forEach(pano=>{
  116. viewer.setObjectLayers(pano.marker, 'siteModelMapUnvisi' )
  117. })
  118. mapViewport.layersAdd('siteModeOnlyMapVisi') //只有mapViewport能看到marker
  119. },
  120. leave:function(){
  121. Potree.Log('sitemodel leave')
  122. let mapViewport = viewer.mapViewer.viewports[0]
  123. this.SplitScreen.recover()
  124. viewer.viewports.forEach(e=>{
  125. if(e.name != 'mapViewport'){
  126. e.layersRemove('siteModelMapUnvisi')
  127. }
  128. if(e.name == 'right' || e.name == 'back'){
  129. e.layersRemove('siteModeSideVisi')
  130. }
  131. })
  132. viewer.images360.panos.forEach(pano=>{
  133. viewer.setObjectLayers(pano.marker, 'sceneObjects' )
  134. })
  135. mapViewport.layersRemove('siteModeOnlyMapVisi')
  136. this.clear()
  137. this.editing = false
  138. this.updateEntityAt(true)
  139. viewer.updateFpVisiDatasets()
  140. } ,
  141. addFloor:function(parent, dirType, sid, name){//dirType:'top'|'bottom'在上方建还是下方。如果建筑中没有楼层,默认在基底建一个
  142. let buildType = 'floor'
  143. let zMin, zMax
  144. if(parent.buildChildren.length == 0){
  145. zMin = parent.zMin
  146. zMax = zMin + Potree.config.siteModel.floorHeightDefault
  147. }else{
  148. if(dirType == 'bottom'){
  149. //var btm = Common.find(parent.buildChildren,null,[e=>e.zMin])
  150. var btm = parent.buildChildren[0]
  151. zMax = btm.zMin
  152. zMin = zMax - Potree.config.siteModel.floorHeightDefault
  153. }else{
  154. //var top = Common.find(parent.buildChildren,null,[e=>e.zMax])
  155. var top = parent.buildChildren[parent.buildChildren.length - 1]
  156. zMin = top.zMax
  157. zMax = zMin + Potree.config.siteModel.floorHeightDefault
  158. }
  159. }
  160. let prop = {
  161. buildType,
  162. //name : Potree.config.siteModel.names[buildType],
  163. zMin,
  164. zMax,
  165. buildParent:parent,
  166. sid, name,
  167. ifDraw:true
  168. }
  169. var floor = new BuildingBox(prop);
  170. /* parent.buildChildren.push(floor)
  171. this.meshGroup.add(floor);
  172. this.entities.push(floor) */
  173. floor.update()
  174. this.addEntity(floor,parent)
  175. //this.selectEntity(floor)
  176. if(this.selected == parent){//重新选择下,为了显示新楼层线框
  177. parent.unselect()
  178. parent.select()
  179. }
  180. return floor
  181. },
  182. startInsertion:function(buildType, parent, sid, name, callback, cancelFun){
  183. let zMin, zMax, entity, resume
  184. let mapViewport = viewer.mapViewer.viewports[0]
  185. if(buildType == 'resume'){//继续画(使用最后一个点或者新加的点)
  186. resume = true
  187. entity = parent
  188. buildType = parent.buildType
  189. //删除原先所有的点,因为它们已经添加了事件,会很麻烦:
  190. entity.reDraw(0)
  191. entity.isNew = true //当作新的来画
  192. }
  193. if(!resume){
  194. if(buildType == 'hole' || buildType == 'room'){
  195. zMin = parent.zMin
  196. zMax = parent.zMax
  197. }else if(buildType == 'building'){
  198. parent = null
  199. zMin = viewer.bound.boundingBox.min.z
  200. zMax = viewer.bound.boundingBox.min.z
  201. }
  202. if(buildType == 'hole'){
  203. entity = parent.addHole()
  204. entity.isNew = true
  205. this.selectEntity(parent)
  206. entity.select()
  207. console.log('挖洞 ',entity.uuid)
  208. }else{
  209. let prop = {
  210. buildType,
  211. //name : Potree.config.siteModel.names[buildType],//'building',
  212. zMin,
  213. zMax,
  214. buildParent:parent,
  215. sid, name,
  216. ifDraw:true
  217. }
  218. entity = new BuildingBox(prop);
  219. entity.isNew = true
  220. this.selectEntity(entity)
  221. }
  222. this.addEntity(entity, parent)
  223. }
  224. let timer;
  225. let endDragFun = (e) => {
  226. if (e.button == THREE.MOUSE.LEFT ) {
  227. var marker = entity.addMarker({point:entity.points[entity.points.length - 1].clone()})
  228. //entity.editStateChange(true) //重新激活reticule状态
  229. entity.continueDrag(marker, e)
  230. } else if (e.button === THREE.MOUSE.RIGHT ) {
  231. if(e.pressDistance < Potree.config.clickMaxDragDis )end(e);//非拖拽的话
  232. else entity.continueDrag(null, e/* .drag.object */)
  233. }
  234. };
  235. let finish = ()=>{//结束绘画
  236. viewer.removeEventListener('cancel_insertions', Exit);
  237. entity.removeEventListener('unselect', Exit);
  238. clearTimeout(timer)
  239. entity.editStateChange(false)
  240. //pressExit && viewer.inputHandler.removeEventListener('keydown', pressExit);
  241. callback && callback(entity)
  242. }
  243. let end = (e={}) => {//尝试结束
  244. /* 退出的三种形式:
  245. 1 普通:如果大于三个marker,结束且保留;否则重新画。()
  246. 2 删除:直接结束且删除。(remove)
  247. 3 结束:如果大于三个marker,结束且保留;否则结束且删除。 (finish)
  248. 4 保留:无论几个marker,都保留着,结束。(remain)
  249. */
  250. if(e.remove){
  251. finish()
  252. return this.removeEntity(entity)
  253. }
  254. if(!e.remain && !e.finish && !e.remove && entity.markers.length<=minMarkers){//右键 当个数不够时取消
  255. //重新开始画
  256. entity.reDraw(1)
  257. viewer.updateVisible(entity.markers[0],'unMove',false);
  258. var f = ()=>{
  259. viewer.updateVisible(entity.markers[0],'unMove',true);
  260. entity.removeEventListener('dragChange',f)
  261. }
  262. entity.addEventListener('dragChange',f)
  263. //console.log('waitcontinue')
  264. entity.continueDrag(entity.markers[0], e)
  265. return
  266. }
  267. finish()
  268. if (e.remain || !e.remove && entity.markers.length > 3) {//保留
  269. entity.removeMarker(entity.points.length - 1);
  270. entity.markers.forEach(marker=>{marker.dispatchEvent('addHoverEvent') })
  271. if(buildType == 'room'){
  272. this.fitPullBox()
  273. }
  274. entity.isNew = false
  275. entity.addMidMarkers()
  276. }else{
  277. this.removeEntity(entity) //直接删除没画好的,比较简单。这样就不用担心旧的continueDrag仍旧触发了
  278. }
  279. return entity
  280. };
  281. let Exit = (e)=>{ //强制结束
  282. entity.removeEventListener('unselect', Exit);
  283. if(viewer.inputHandler.drag){//还未触发drop的话
  284. viewer.inputHandler.drag.object.dispatchEvent({
  285. type: 'drop',
  286. drag: viewer.inputHandler.drag,
  287. viewer: viewer,
  288. pressDistance:0,
  289. button : THREE.MOUSE.RIGHT
  290. });
  291. viewer.inputHandler.drag = null
  292. }else{
  293. end({remain:true})
  294. }
  295. viewer.inputHandler.drag = null
  296. }
  297. viewer.dispatchEvent( 'cancel_insertions' );//取消之前的
  298. viewer.addEventListener('cancel_insertions', Exit);
  299. entity.addEventListener('unselect', Exit);
  300. var marker = entity.addMarker({point:new THREE.Vector3(0, 0, 0)})
  301. viewer.updateVisible(marker,'unMove',false);//这时候的位置是假的(0,0,0)所以先不可见
  302. var f = ()=>{
  303. viewer.updateVisible(marker,'unMove',true);
  304. entity.removeEventListener('dragChange',f)
  305. }
  306. entity.addEventListener('dragChange',f)
  307. marker.isDragging = true
  308. viewer.inputHandler.startDragging(marker , {dragViewport:mapViewport, endDragFun, notPressMouse:true} ); //notPressMouse代表不是通过按下鼠标来拖拽. dragViewport指定了只能在地图上拖拽
  309. return entity;
  310. },
  311. getPreDealData(points, zMin, zMax, initial, buildType, parent){
  312. /* if( buildType == 'building' ){
  313. zMax = zMin //强制变得一样,作为基底。如果有必要,保存时再算真实的zMax。目前zMin没有保存所以数据是错的,会直接根据floor计算
  314. } */
  315. var bound = viewer.bound.boundingBox
  316. if(buildType == 'building' && initial){//初始数据错的,要自己建(只有一个building和floor) 原posIsLonlat
  317. console.log('空间模型未编辑过, 初始化了一个')
  318. points = [
  319. new THREE.Vector3(bound.min.x, bound.min.y,0),
  320. new THREE.Vector3(bound.max.x, bound.min.y,0),
  321. new THREE.Vector3(bound.max.x, bound.max.y,0),
  322. new THREE.Vector3(bound.min.x, bound.max.y,0),
  323. ]
  324. zMin = bound.min.z
  325. zMax = bound.max.z
  326. /* points = points.map(e=>{
  327. return viewer.transform.lonlatToLocal.forward(e)
  328. }) */
  329. }else{//相对于初始数据集的模型内坐标
  330. points = points.map(e=> this.transform(e, 'fromDataset'))
  331. if(buildType == 'floor' && initial){
  332. zMin = bound.min.z
  333. zMax = bound.max.z
  334. }
  335. }
  336. return {points, zMax, zMin }
  337. },
  338. resetFromData:function(entity, points=[], holes=[], zMin, zMax ){
  339. var {points, zMax, zMin} = this.getPreDealData(points, zMin, zMax , this.autoBuild , entity.buildType, entity.buildParent )
  340. if(entity.buildType != 'floor' )entity.points = points
  341. if(entity.buildType == 'room'){
  342. entity.zMin = zMin
  343. entity.zMax = zMax
  344. }else if(entity.buildType == 'floor'){//改楼高
  345. let height = zMax - zMin
  346. let zMax2 = entity.zMin + height
  347. SiteModel.changeZ(entity, 'zMax', zMax2)
  348. }
  349. {
  350. //删除旧的holes重新添加
  351. let holesOld = entity.holes
  352. holesOld.forEach(e=>{
  353. entity.removeHole(e)
  354. })
  355. holes.forEach(points =>{
  356. let ps = points.map(e=> this.transform(e, 'fromDataset'))
  357. let hole = entity.addHole(ps)
  358. hole.addMidMarkers()
  359. })
  360. }
  361. entity.update()
  362. return entity
  363. }
  364. ,
  365. createFromData:function( buildType, parent ,sid, name, points=[], holes=[], zMin, zMax, initial,panos_,flagPano){
  366. if(buildType != 'building' && buildType != 'floor' && buildType != 'room' ) return
  367. var {points, zMax, zMin} = this.getPreDealData(points, zMin, zMax , initial, buildType, parent )
  368. let panos = []
  369. {
  370. if(panos_){
  371. panos_.forEach(sid=>{
  372. let pano = viewer.images360.getPano(sid,'sid')
  373. if(pano)panos.push(pano)
  374. })
  375. }
  376. flagPano = flagPano != void 0 ? viewer.images360.getPano(flagPano,'sid') : null ; //最中心的pano 或者 最靠近该实体的pano(当panos为空时)
  377. if(!this.editing && buildType == 'floor' && !flagPano){//没有的话可能是自动添加的floor,直接用parent的吧
  378. panos = parent.panos;
  379. flagPano = parent.flagPano;
  380. }
  381. }
  382. let prop = {
  383. buildType,
  384. points,
  385. name,
  386. sid,
  387. zMin,
  388. zMax,
  389. buildParent:parent,
  390. ifDraw:this.editing || Potree.settings.drawEntityData,
  391. panos, flagPano,
  392. autoBuild : initial
  393. }
  394. let entity = new BuildingBox(prop)
  395. SiteModel.addEntity(entity, parent )
  396. if(this.editing){
  397. if(buildType == 'building'|| buildType == 'room'){
  398. entity.addMidMarkers()
  399. }
  400. }
  401. holes.forEach(points =>{
  402. let ps = points.map(e=> this.transform(e, 'fromDataset'))
  403. let hole = entity.addHole(ps)
  404. this.editing && hole.addMidMarkers()
  405. })
  406. /* if(buildType == 'floor'){
  407. this.updateBuildingZ(parent)
  408. } */
  409. return entity
  410. },
  411. transform:function(pos, type){
  412. if(Potree.settings.editType == 'pano'){ // 模型不经转换
  413. return new THREE.Vector3().copy(pos).setZ(0);
  414. }
  415. if(type == 'toDataset'){
  416. let point = Potree.Utils.datasetPosTransform({ toDataset: true, position: pos.clone(), datasetId: Potree.settings.originDatasetId })
  417. return new THREE.Vector2().copy(point)
  418. }else{
  419. let position = new THREE.Vector3().copy(pos).setZ(0)
  420. return Potree.Utils.datasetPosTransform({ fromDataset: true, position, datasetId: Potree.settings.originDatasetId })
  421. }
  422. },
  423. addEntity:function(entity, parent){
  424. if(entity.buildType != 'hole'){
  425. this.meshGroup.add(entity);
  426. this.entities.push(entity)
  427. if(entity.buildType == 'building'){
  428. this.buildings.push(entity)
  429. }else{
  430. parent.buildChildren.push(entity)
  431. }
  432. }
  433. if(entity.buildType == 'room'){
  434. entity.addEventListener('marker_dropped',()=>{
  435. this.fitPullBox()
  436. })
  437. }else if(entity.buildType == 'floor'){
  438. this.updateBuildingZ(parent)
  439. parent.dispatchEvent({type:'addFloor'})
  440. }
  441. {//仅能存在一个marker被选中。选中的点可以被删除
  442. entity.addEventListener('clickMarker', (e)=>{
  443. if(this.selectedMarker == e.marker){
  444. this.selectedMarker.dispatchEvent({type:'clickSelect',state:false})
  445. this.selectedMarker = null
  446. }else{
  447. if(this.selectedMarker){
  448. this.selectedMarker.dispatchEvent({type:'clickSelect',state:false})
  449. }
  450. this.selectedMarker = e.marker
  451. this.selectedMarker.dispatchEvent({type:'clickSelect',state:true})
  452. }
  453. })
  454. entity.addEventListener('removeMarker', (e)=>{
  455. if(this.selectedMarker == e.marker){
  456. this.selectedMarker = null
  457. }
  458. })
  459. let unselect = (e)=>{//取消选中实体或删除后
  460. if(this.selectedMarker && entity.markers.includes(this.selectedMarker)){
  461. this.selectedMarker.dispatchEvent({type:'clickSelect',state:false})
  462. this.selectedMarker = null
  463. }
  464. }
  465. entity.addEventListener('dispose', unselect)
  466. entity.addEventListener('unselect', unselect)
  467. }
  468. //console.log('添加实体:', entity.buildType, entity.sid, entity.uuid)
  469. },
  470. removeEntity : function(entity){
  471. if(!this.entities.includes(entity))return
  472. console.log('删除实体:', entity.buildType, entity.sid)
  473. if(this.selected == entity){
  474. this.height_pull_box.visible = false
  475. this.selectEntity(null)
  476. }
  477. if(entity.buildType == 'building'){
  478. var index = this.buildings.indexOf(entity);
  479. if(index>-1){
  480. this.buildings.splice(index,1)
  481. }
  482. }else{
  483. var index = entity.buildParent.buildChildren.indexOf(entity);
  484. if(index>-1){
  485. entity.buildParent.buildChildren.splice(index,1)
  486. }
  487. }
  488. var index = this.entities.indexOf(entity);
  489. if(index>-1){
  490. this.entities.splice(index,1)
  491. }
  492. entity.dispose()
  493. let buildChildren = entity.buildChildren.slice()
  494. buildChildren.forEach(e=>this.removeEntity(e))
  495. },
  496. updateBuildingZ:function(building){
  497. building.buildChildren = building.buildChildren.sort((e,a)=>e.zMin-a.zMin)//从低到高排序
  498. building.zMin = building.zMax = building.buildChildren[0].zMin //基底高度
  499. //building.zMax = building.buildChildren[building.buildChildren.length-1].zMax
  500. if(this.editing) building.update({dontUpdateChildren:true})
  501. building.dispatchEvent('updateBuildingZ')
  502. },
  503. selectEntity : function(entity, state=true){
  504. if(state === false){
  505. entity.unselect()
  506. if(this.selected == entity)this.selected = null
  507. return
  508. }
  509. if(this.selected == entity || entity && entity.buildType == 'hole')return
  510. //this.buildings.forEach(e=>e.unselect())
  511. this.selected && this.selected.unselect()
  512. this.height_pull_box.visible = false
  513. if(entity){
  514. entity.select()
  515. }
  516. this.selected = entity
  517. if(entity && (entity.buildType == 'floor' || entity.buildType == 'room' )){
  518. this.height_pull_box.visible = true
  519. this.fitPullBox()
  520. }
  521. if(entity && !entity.isNew && (entity.buildType == 'building' || entity.buildType == 'room' ) && entity.points.length<2){
  522. this.startInsertion('resume',entity) //继续画
  523. }
  524. },
  525. fitPullBox: function(){ //自适应拖拽楼层的pullMesh
  526. if(!this.selected || this.selected.buildType!= 'floor' && this.selected.buildType!= 'room')return
  527. let bound = new THREE.Box3();
  528. bound.expandByObject(this.selected.box)
  529. let center = bound.getCenter(new THREE.Vector3() )
  530. let size = bound.getSize(new THREE.Vector3() )
  531. this.height_pull_box.scale.copy(size)
  532. this.height_pull_box.position.copy(center)
  533. },
  534. changeZ:function(entity, dirType, value){ // floor or room 修改zMin or zMax
  535. let max, min //limit
  536. if(entity.buildType == 'floor'){//楼层
  537. let index = entity.buildParent.buildChildren.indexOf(entity)
  538. if(dirType == 'zMax'){
  539. let upper = entity.buildParent.buildChildren[index+1];
  540. entity.zMax = Math.min(Limit.zMax, value)
  541. min = entity.zMin + minFloorHeight
  542. if(entity.zMax < min){
  543. entity.zMax = min
  544. }else{
  545. if(upper){
  546. max = upper.zMax - minFloorHeight;
  547. if(entity.zMax > max){
  548. entity.zMax = max
  549. }
  550. }
  551. }
  552. if(upper){
  553. upper.zMin = entity.zMax
  554. upper.update()
  555. upper.dispatchEvent({type:'changeHeight'})
  556. }
  557. }else{
  558. let lower = entity.buildParent.buildChildren[index-1];
  559. entity.zMin = Math.max(Limit.zMin, value)
  560. max = entity.zMax - minFloorHeight
  561. if(entity.zMin > max){
  562. entity.zMin = max
  563. }else{
  564. if(lower){
  565. min = lower.zMin + minFloorHeight;
  566. if(entity.zMin < min){
  567. entity.zMin = min
  568. }
  569. }
  570. }
  571. if(lower){
  572. lower.zMax = entity.zMin
  573. lower.update()
  574. lower.dispatchEvent({type:'changeHeight'})
  575. }
  576. if(index == 0)this.updateBuildingZ(entity.buildParent)
  577. }
  578. }else if(entity.buildType == 'room'){//房间
  579. //按照navvis的是不一定限制在当前楼层,只要高度不超过当前楼层即可。
  580. let maxHeight = entity.buildParent.zMax - entity.buildParent.zMin
  581. if(dirType == 'zMax'){
  582. min = entity.zMin + minFloorHeight
  583. max = entity.zMin + maxHeight
  584. entity.zMax = THREE.Math.clamp(value, min, max);
  585. }else{
  586. min = entity.zMax - maxHeight
  587. max = entity.zMax - minFloorHeight
  588. entity.zMin = THREE.Math.clamp(value, min, max);
  589. }
  590. }
  591. entity.update()
  592. entity.dispatchEvent({type:'changeHeight'})
  593. //this.selected.emit('update')
  594. this.fitPullBox()
  595. },
  596. createHeightPull:function(){ //拖拽楼层的bounding box
  597. let boxGeo = new THREE.BoxBufferGeometry( 1, 1, 1/4 )
  598. let boxMat = new THREE.MeshBasicMaterial({
  599. color:"#F00",
  600. opacity:0,
  601. transparent:true,
  602. depthTest:false,
  603. side:2
  604. })
  605. let height_pull_box_up = new THREE.Mesh(boxGeo,boxMat)
  606. let height_pull_box_down = new THREE.Mesh(boxGeo,boxMat)
  607. height_pull_box_up.name = 'height_pull_box_up';
  608. height_pull_box_down.name = 'height_pull_box_down';
  609. this.height_pull_box = new THREE.Object3D();
  610. this.height_pull_box.name = 'height_pull_box'
  611. this.height_pull_box.add(height_pull_box_up)
  612. this.height_pull_box.add(height_pull_box_down)
  613. this.height_pull_box.visible = false
  614. this.meshGroup.add(this.height_pull_box)
  615. height_pull_box_up.position.set(0,0,1/2/* 3/8 */)
  616. height_pull_box_down.position.set(0,0,-1/2/* -3/8 */)
  617. viewer.setObjectLayers(this.height_pull_box, 'siteModeSideVisi' )
  618. let mouseover = (e)=>{
  619. viewer.dispatchEvent({
  620. type : "CursorChange", action : "add", name:"siteModelFloorDrag"
  621. })
  622. }
  623. let mouseleave = (e)=>{
  624. viewer.dispatchEvent({
  625. type : "CursorChange", action : "remove", name:"siteModelFloorDrag"
  626. })
  627. }
  628. let firstZ, firstIntersect;
  629. let drag = (e)=>{
  630. var intersect = e.intersect.orthoIntersect //不要点云的intersect,只要orthocamera算出的平面intersect
  631. if(firstIntersect != void 0){
  632. let moveZ = intersect.z - firstIntersect
  633. if(this.selected.buildType == 'floor'){//楼层
  634. //限制高度不能超过上下
  635. if(e.target == height_pull_box_up){
  636. if(firstZ == void 0)firstZ = this.selected.zMax
  637. this.changeZ(this.selected, 'zMax', firstZ + moveZ)
  638. }else{
  639. if(firstZ == void 0)firstZ = this.selected.zMin
  640. this.changeZ(this.selected, 'zMin', firstZ + moveZ)
  641. }
  642. }else if(this.selected.buildType == 'room'){//房屋
  643. if(e.target == height_pull_box_up){
  644. if(firstZ == void 0)firstZ = this.selected.zMax
  645. this.changeZ(this.selected, 'zMax', firstZ + moveZ)
  646. }else{
  647. if(firstZ == void 0)firstZ = this.selected.zMin
  648. this.changeZ(this.selected, 'zMin', firstZ + moveZ)
  649. }
  650. }
  651. }else{
  652. firstIntersect = intersect.z
  653. }
  654. }
  655. let drop = (e)=>{
  656. firstZ = firstIntersect = null
  657. }
  658. height_pull_box_up.addEventListener('mousemove',mouseover)
  659. height_pull_box_down.addEventListener('mousemove',mouseover)
  660. height_pull_box_up.addEventListener('mouseleave',mouseleave)
  661. height_pull_box_down.addEventListener('mouseleave',mouseleave)
  662. height_pull_box_up.addEventListener('drag',drag)
  663. height_pull_box_down.addEventListener('drag',drag)
  664. height_pull_box_up.addEventListener('drop',drop)
  665. height_pull_box_down.addEventListener('drop',drop)
  666. },
  667. pointInWhichEntity(location, buildType, ifIgnoreHole){//buildType是要找的建筑类型
  668. //location 可以是pano或者坐标
  669. //由于房间可能在building外,所以房间要另外单独识别。
  670. let lastResult; //最接近的上一层结果,如果没有result返回这个
  671. let result
  672. let level = {
  673. building: 0, floor: 1, room: 2
  674. }
  675. let traverse = (parent, buildType)=>{//返回第一个符合标准的实体
  676. let contains;
  677. if(location instanceof THREE.Vector3){
  678. contains = parent.ifContainsPoint(location)
  679. }else{//is pano
  680. contains = parent.panos.includes(location)
  681. }
  682. if(contains){
  683. if(!lastResult || level[lastResult.buildType] < level[parent.buildType] )lastResult = parent
  684. if(parent.buildType == buildType){
  685. return parent
  686. }else{
  687. for(let i=0,len=parent.buildChildren.length; i<len; i++){
  688. let result1 = traverse(parent.buildChildren[i])
  689. if(result1) return result1
  690. }
  691. }
  692. }
  693. }
  694. //因为建筑可能重叠,所以需要先找到最接近其中心的建筑物
  695. result = Common.sortByScore(this.buildings, [(building)=>{
  696. return traverse(building, 'building') //在building中
  697. }], [(building)=>{ //写法类似pointInWhichPointcloud
  698. let boundingBox = building.getBound()
  699. let center = boundingBox.getCenter(new THREE.Vector3())
  700. let position = location instanceof THREE.Vector3 ? location : location.position
  701. let dis = position.distanceTo(center)
  702. let size = boundingBox.getSize(new THREE.Vector3())
  703. let length = size.length() / 2;
  704. return length / dis
  705. }]);
  706. let building = result && result[0] && result[0].score > 1 && result[0].item
  707. if(buildType == 'building' || !building)return building
  708. result = traverse(building, buildType)
  709. /* if(!result && buildType == 'room'){//如果要找的是room, 且按刚才的顺序找不到的话,就单独从所有rooms中找一遍。因为room可能不在floor和building内。
  710. let rooms = this.entities.filter(e=>e.buildType == 'room');
  711. result = rooms.find(e=>e.ifContainsPoint(position))
  712. }*/
  713. //虽然房间可以画到上级之外,但是为了方便起见,假定房间绝对在楼层之内。找不到的话要调整空间模型了。
  714. return result || lastResult
  715. }
  716. ,
  717. findPanos: function(){
  718. {
  719. this.entities.forEach(entity=>{
  720. //清空:
  721. entity.panos = []
  722. entity.flagPano = null
  723. viewer.images360.panos.forEach(pano=>{
  724. if(entity.ifContainsPoint(pano.position)){
  725. entity.panos.push(pano)
  726. }
  727. })
  728. })
  729. }
  730. /* viewer.images360.panos.forEach(pano=>{ //一个漫游点只对应一个实体的话
  731. let result = this.pointInWhichEntity(pano.position, 'room');
  732. {//get panos for every entities
  733. let entity = result
  734. while(entity){
  735. entity.panos.push(pano);
  736. entity = entity.buildParent
  737. }
  738. }
  739. }) */
  740. {//search center pano
  741. this.entities.forEach(entity=>{
  742. let panos = entity.panos
  743. if(panos.length == 0)return
  744. let bound = entity.getBound();
  745. let center = bound.getCenter(new THREE.Vector3)
  746. let request = []
  747. let rank = [
  748. Images360.scoreFunctions.distanceSquared({position: center})
  749. ]
  750. //let panos = entity.panos && entity.panos.length ? entity.panos : viewer.images360.panos //entity没有panos的话,就扩大到所有panos
  751. let r = Common.sortByScore(panos, request, rank);
  752. if(r && r.length){
  753. entity.flagPano = r[0].item
  754. }else{
  755. console.error('no flagPano??')
  756. }
  757. })
  758. }
  759. }
  760. ,
  761. findEntityForDataset:function(){//为每一个数据集寻找它所属的最小实体
  762. /* var entities = this.entities.filter(e=>e.buildType == 'room' || e.buildType == 'floor' && e.buildChildren.length == 0)
  763. viewer.scene.pointclouds.forEach(pointcloud=>{
  764. let cloudVolume = pointcloud.getVolume()
  765. let scores = []
  766. entities.forEach(entity=>{
  767. let volume = entity.intersectPointcloudVolume(pointcloud)
  768. //注:默认已经findPanos过
  769. let panos = entity.panos.filter(e=>pointcloud.panos.includes(e));
  770. let panoCount = panos.length
  771. let score = volume / cloudVolume + panoCount / pointcloud.panos.length
  772. scores.push({entity, volume, panoCount, score})
  773. })
  774. scores.sort((a,b)=>{ return b.score-a.score })
  775. if(scores.length == 0 || scores[0].volume/cloudVolume < 0.0001 && scores[0].volume < 3 ){//如果约等于0
  776. pointcloud.belongToEntity = null
  777. }else{
  778. pointcloud.belongToEntity = scores[0].entity;
  779. }
  780. }) */
  781. let getScores = (pointcloud, entities, cloudVolume)=>{
  782. let scores = []
  783. entities.forEach(entity=>{
  784. let volume = entity.intersectPointcloudVolume(pointcloud)
  785. //注:默认已经findPanos过
  786. let panos = entity.panos.filter(e=>pointcloud.panos.includes(e));
  787. let panoCount = panos.length
  788. let score = volume / cloudVolume
  789. if(pointcloud.panos.length > 0){
  790. score += panoCount / pointcloud.panos.length
  791. }else{
  792. //score += 1
  793. }
  794. scores.push({entity, volume, panoCount, score})
  795. })
  796. scores.sort((a,b)=>{ return b.score-a.score })
  797. return scores
  798. }
  799. viewer.scene.pointclouds.forEach(pointcloud=>{ //先判断父级,如果父集不通过就不判断子级。
  800. let cloudVolume = pointcloud.getVolume()
  801. let entities = this.buildings
  802. while(1){
  803. let scores = getScores(pointcloud, entities, cloudVolume)
  804. if(scores.length == 0 || scores[0].volume/cloudVolume < 0.0001 && scores[0].volume < 3 ){//如果约等于0
  805. if(scores[0] && scores[0].entity.buildType == 'room'){
  806. pointcloud.belongToEntity = scores[0].entity; //当该数据集在楼层中,但不在楼层中的任意一个房间时,任意挂载在其中一个房间上。
  807. break
  808. }
  809. pointcloud.belongToEntity = null
  810. break;
  811. }else{
  812. entities = scores[0].entity.buildChildren
  813. if(entities.length == 0){
  814. pointcloud.belongToEntity = scores[0].entity;
  815. break;
  816. }
  817. }
  818. }
  819. })
  820. /*
  821. 旧版:
  822. 只需要考虑 floor 和 room, 因为building的只有一个基底没高度
  823. floor 和 room 在空间中没有完全的从属关系,因为room可以超出floor之外。所以直接混在一起来查找,但要排除有房间的楼层。
  824. (现在改为层层递进查找,否则数据集包含entity多的,会直接挂载到体积最大的房间里,即使看起来主体点云并不在该房间)
  825. 有的数据集虽然很高,但只有近地面的部分才是主体,这部分一般含有全部漫游点。为了防止上层的实体因体积较大而分数高,就把包含漫游点的个数也加入考虑。
  826. 重叠体积大、且包含漫游点最多的最小实体将会拥有该点云。
  827. 期望: 最好不挂载到最小子级,因为现在有房间都到房间里了。
  828. */
  829. }
  830. ,
  831. clear:function(){//清空
  832. /* entities:[], //所有实体
  833. buildings:[], //所有建筑父集
  834. meshGroup: new THREE.Object3D, */
  835. this.selectEntity(null)
  836. let length = this.entities.length;
  837. for(let i=0;i<length;i++){
  838. this.entities[i].dispose()
  839. }
  840. this.entities = []
  841. this.buildings = []
  842. this.inEntity = null
  843. }
  844. ,
  845. gotoEntity(id, isNearBy, duration=1000) {
  846. var entity = this.entities.find(e => e.sid == id)
  847. let aimPano
  848. if (!entity) {
  849. return console.error('没找到entity ')
  850. }
  851. if(Potree.settings.displayMode == 'showPanos'){
  852. if (isNearBy && entity.panos.length) {
  853. if(entity.panos.includes(viewer.images360.currentPano)) return 'posNoChange' //已在当前实体中
  854. let position = viewer.scene.getActiveCamera().position
  855. let request = []
  856. let rank = [Images360.scoreFunctions.distanceSquared({ position })]
  857. let r = Common.sortByScore(entity.panos, request, rank)
  858. aimPano = r[0].item
  859. } else {
  860. if (!entity.flagPano) {
  861. return console.log('没有flagPano')
  862. }
  863. aimPano = entity.flagPano
  864. }
  865. if(aimPano == viewer.images360.currentPano) return 'posNoChange'
  866. viewer.images360.flyToPano(aimPano)
  867. }else{
  868. if(isNearBy && entity.ifContainsPoint(viewer.images360.position) ){
  869. return 'posNoChange' //已在当前实体中
  870. }
  871. let boundingBox = entity.getBound()
  872. let position = boundingBox.getCenter(new THREE.Vector3()) //中心点不一定在entity中,比如半环形建筑(所以要不要改成到漫游点呢)
  873. if(viewer.modules.Clip && viewer.modules.Clip.editing){
  874. viewer.modules.Clip.bus.dispatchEvent({type:'flyToPos', position})
  875. }else{
  876. if(math.closeTo(position, viewer.images360.position)) return 'posNoChange'
  877. let size = boundingBox.getSize(new THREE.Vector3())
  878. viewer.scene.view.setView({position, duration})
  879. }
  880. viewer.mapViewer.moveTo(position, size, duration)
  881. }
  882. return true
  883. },
  884. focusEntity(id){
  885. var entity = this.entities.find(e => e.sid == id)
  886. let boundingBox = entity.getBound()
  887. //let boundSize = boundingBox.getSize(new THREE.Vector3())
  888. let center = boundingBox.getCenter(new THREE.Vector3())
  889. this.SplitScreen.focusOnObject(boundingBox, center)
  890. this.gotoEntity(id, false, 0)
  891. },
  892. removeIlligalArchi(){//删除marker数量小于3个的建筑,当保存时
  893. let needDelete = []
  894. this.entities.forEach(e=>{
  895. if(e.points.length<3){
  896. needDelete.push(e)
  897. }
  898. })
  899. needDelete.forEach(e=>this.removeEntity(e))
  900. },
  901. }
  902. /*
  903. 规则
  904. 层级:
  905. type 中文名 改动范围 其他
  906. BUILDING 建筑 xy mesh由自己的基底以及所有floor的组成。如果删除所有floor,就剩一个平面。故而zMin == zMax
  907. FLOOR 楼层 z(xy未解锁) 点击楼层时房间也会显示,而建筑的其他楼层不显示线框,会显示面. 拖拽高度实际是拖拽楼层间的分界线,楼层之间不会有缝隙
  908. ROOM 房间 xyz(xy可加锁) 可能超出楼层外,因为楼层拖拽时房间没变。所以建筑不一定包容房间。
  909. CUSTOM 自定义(现作房间) xyz
  910. ( xy范绘制时不能超出父级外轮廓,但父级编辑时可以进入子级轮廓。只要能解锁轮廓的都能切洞)
  911. floor输入高度数值的限制和拖拽是一样的,相当于调节其zMax, 且不能超出其上层的zMax。
  912. navvis弊端:
  913. 空间模型不会随着数据集移动而移动 (可以做成跟随,但是如果一个建筑对应多个数据集,那只能跟序号在前的数据集走)
  914. 建筑点修改后,房间可能飘出建筑外的。 楼层高度修改后也是。
  915. 调整高度时,看不到相邻的楼层界限,导致拖不动时像bug。
  916. 调整高度时,侧面看有的重叠的部分比较高亮,感觉是冗余信息?有点乱
  917. 问题:
  918. 磨砂材质
  919. 没有阴影,可directionallight 加了呀
  920. 暂定一个数据集只属于一个实体(从最小的找起)
  921. https://testlaser.4dkankan.com/indoor/t-8KbK1JjubE/api/site_model
  922. 删点全删光了要删实体吗
  923. */
  924. export {SiteModel}