SiteModel.js 44 KB

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