Clip.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. import * as THREE from "../../../../libs/three.js/build/three.module.js";
  2. import {BoxVolume} from '../../../utils/VolumeNew.js'
  3. import { ClipTask, ClipMethod} from "../../../defines.js"
  4. import {mapClipBox} from '../../objects/tool/mapClipBox.js'
  5. import Common from '../../utils/Common.js'
  6. import math from '../../utils/math.js'
  7. import {Images360} from '../panos/Images360.js'
  8. const defaultBoxWidth = 16; //navvis: 10
  9. //navvis position: si {x: 0, y: 0, z: 0}
  10. var Clip = {
  11. bus : new THREE.EventDispatcher,
  12. selectedDatasets : [],
  13. changeCallback(force){
  14. viewer.controls.setTarget(this.box.position)//绕其旋转
  15. if(Potree.settings.isOfficial){
  16. Common.intervalTool.isWaiting('clipSelectedDatasets', ()=>{ //延时update,防止卡顿
  17. let pointclouds = this.getIntersectPointcloud()
  18. if(force || Common.getDifferenceSet(pointclouds,this.selectedDatasets).length){
  19. this.selectedDatasets = pointclouds
  20. //console.error('clipSelectedDatasets',selectedDatasets)
  21. this.bus.dispatchEvent({type:'updateSelectedDatasets', selectedDatasets:pointclouds.map(e=>e.dataset_id) })
  22. force = false
  23. return true
  24. }
  25. }, 300)
  26. }
  27. },
  28. enter:function(){
  29. this.previousView = {
  30. position: viewer.images360.position,
  31. target: viewer.scene.view.getPivot(),
  32. displayMode : Potree.settings.displayMode,
  33. //---
  34. ifShowMarker : Potree.settings.ifShowMarker,
  35. }
  36. let pointcloud = this.getPointcloud()
  37. let bound = pointcloud.bound //只选取其中一个数据集的bound,而非整体,是因为担心两个数据集中间有空隙,于是刚好落在没有点云的地方。
  38. let boundSize = bound.getSize(new THREE.Vector3())
  39. let target = this.getTarget(bound.getCenter(new THREE.Vector3())); //navvis的位置xy是用相机位置 this.ViewService.mainView.getCamera().position 我觉得也可以用第一个漫游点的,或者最接近bound中心的漫游点
  40. let scale = new THREE.Vector3(defaultBoxWidth,defaultBoxWidth, boundSize.z)//z和navvis一样
  41. let eyeDir = viewer.scene.view.direction.clone().setZ(0/* -boundSize.z/3 */).multiplyScalar(-defaultBoxWidth) //为了使所在楼层不变,不修改z
  42. //let eyeDir = scale.clone().setZ(boundSize.z/3).multiplyScalar(1.3)
  43. let position = new THREE.Vector3().addVectors(target, eyeDir)
  44. Potree.settings.displayMode = 'showPointCloud'
  45. viewer.setView({
  46. position ,
  47. target,
  48. duration:300,
  49. callback:function(){
  50. }
  51. })
  52. //viewer.setControls(viewer.orbitControls);
  53. viewer.setLimitFar(false)
  54. //viewer.setClipState(false) //暂时关闭旧的clipping
  55. {
  56. this.box = new BoxVolume({
  57. clip:true
  58. })
  59. this.box.clipTask = ClipTask['SHOW_INSIDE_Big' /* "SHOW_INSIDE" */]
  60. this.box.showBox = false
  61. this.box.name = "ClipBox";
  62. this.box.position.copy(target)
  63. this.box.scale.copy(scale)
  64. //带动mapBox
  65. this.box.addEventListener('position_changed',e=>{
  66. this.mapBox.center.setX(this.box.position.x)
  67. this.mapBox.center.setY(this.box.position.y)
  68. this.mapBox.updatePoints()
  69. this.changeCallback()
  70. })
  71. this.box.addEventListener('scale_changed',e=>{
  72. var scale = this.box.scale
  73. this.mapBox.updatePoints(scale)
  74. this.changeCallback()
  75. })
  76. this.box.addEventListener('orientation_changed',e=>{
  77. this.mapBox.angle = this.box.rotation.z
  78. this.mapBox.rotateBar.rotation.z = this.mapBox.angle
  79. this.mapBox.updatePoints()
  80. this.changeCallback()
  81. })
  82. viewer.scene.addVolume(this.box);
  83. }
  84. {//map
  85. let boxRotateBack = ()=>{//不知道是不是这么写。 因为可能z的旋转不一定都在z
  86. this.box.rotation.x = 0;
  87. this.box.rotation.y = 0;
  88. }
  89. this.mapBox = new mapClipBox(target, scale)
  90. viewer.mapViewer.scene.add(this.mapBox)
  91. //带动box
  92. this.mapBox.addEventListener('repos',e=>{
  93. this.box.position.setX(this.mapBox.center.x)
  94. this.box.position.setY(this.mapBox.center.y)
  95. boxRotateBack()
  96. this.changeCallback()
  97. })
  98. this.mapBox.addEventListener('dragChange',e=>{
  99. var scale = this.mapBox.getScale()
  100. this.box.scale.setX(scale.x)
  101. this.box.scale.setY(scale.y)
  102. this.box.position.setX(this.mapBox.center.x)
  103. this.box.position.setY(this.mapBox.center.y)
  104. boxRotateBack()
  105. this.changeCallback()
  106. })
  107. this.mapBox.addEventListener('rotate',e=>{
  108. this.box.rotation.z = this.mapBox.angle
  109. boxRotateBack()
  110. this.changeCallback()
  111. })
  112. }
  113. {
  114. //viewer.setClipTask(ClipTask["SHOW_INSIDE"])
  115. }
  116. Potree.settings.unableNavigate = true
  117. Potree.settings.ifShowMarker = false
  118. viewer.updateVisible(viewer.measuringTool.scene, 'clipModel', false)
  119. //viewer.updateVisible(viewer.mapViewer.cursor, 'clipModel', false)//隐藏地图游标
  120. viewer.inputHandler.toggleSelection(this.box);
  121. viewer.inputHandler.fixSelection = true
  122. viewer.transformationTool.frame.material.color.set(Potree.config.clip.color)//navvis 15899953
  123. viewer.setPointStandardMat(true)
  124. {
  125. let mapVisi = false
  126. this.events = {
  127. flyToPos : (e)=>{
  128. let dis = 2
  129. let target = e.position
  130. //position = new THREE.Vector3().subVectors(target, this.scene.view.direction)
  131. //永远朝向框的中心
  132. /* let dir = new THREE.Vector3().subVectors(this.box.position, e.position).normalize()
  133. position = new THREE.Vector3().subVectors(target, dir) */
  134. target = this.box.position
  135. position = e.position
  136. //为了方便缩放操作,直接使用box中心作为target
  137. let duration = 1000
  138. viewer.scene.view.setView({position, duration, target})
  139. },
  140. mapVisiChange(e){
  141. mapVisi = e.visible
  142. let delay = 100 //因resize了camera需要时间更新projectionMatrix
  143. setTimeout(()=>{
  144. let boundingBox = Clip.box.boundingBox.clone().applyMatrix4(Clip.box.matrixWorld)
  145. if(mapVisi){//切换地图
  146. if(Clip.switchMapCount == 0 || !Potree.Utils.isInsideFrustum(boundingBox, viewer.mapViewer.camera)){
  147. let size = boundingBox.getSize(new THREE.Vector3)
  148. let margin = viewer.mainViewport.resolution.clone().multiplyScalar(0.3)
  149. viewer.mapViewer.moveTo(Clip.box.position, size, 100, margin)
  150. }
  151. Clip.switchMapCount++
  152. //关于究竟是focus box还是dataset有点纠结,又或是两个的union。box和数据集可能离得很远,且无法确定当前想选择的数据集,且数据集可能无floorplan, 即使有可能也不展示……
  153. }else{//切换3d
  154. if(!Potree.Utils.isInsideFrustum(boundingBox, viewer.scene.getActiveCamera())){//屏幕上没有box的话
  155. viewer.focusOnObject({boundingBox}, 'boundingBox', 100 )
  156. }
  157. }
  158. },delay)
  159. }
  160. }
  161. this.switchMapCount = 0
  162. this.bus.addEventListener('flyToPos',this.events.flyToPos)
  163. viewer.mapViewer.addEventListener('forceVisible',this.events.mapVisiChange)
  164. }
  165. this.editing = true
  166. setTimeout(()=>{this.changeCallback(true)},1)
  167. },
  168. leave:function(){
  169. viewer.inputHandler.fixSelection = false
  170. viewer.scene.removeVolume(this.box);
  171. this.mapBox.dispose()
  172. //viewer.setControls(viewer.fpControls);
  173. Potree.settings.unableNavigate = false
  174. Potree.settings.ifShowMarker = this.previousView.ifShowMarker
  175. viewer.updateVisible(viewer.measuringTool.scene, 'clipModel', true)
  176. //viewer.updateVisible(viewer.mapViewer.cursor, 'clipModel', true)
  177. viewer.setView(this.previousView)
  178. viewer.setLimitFar(true)
  179. viewer.setPointStandardMat(false)
  180. //viewer.setClipState(true)
  181. viewer.controls.setTarget(null)
  182. {
  183. this.bus.removeEventListener('flyToPos',this.events.flyToPos)
  184. viewer.mapViewer.removeEventListener('forceVisible',this.events.mapVisiChange)
  185. this.events = null
  186. }
  187. this.editing = false
  188. },
  189. getPointcloud:function(){ //找一个离当前最近的点云,且最好有漫游点
  190. let pointclouds = viewer.scene.pointclouds.filter(e=>e.panos.length>0)
  191. if(pointclouds.length == 0)pointclouds = viewer.scene.pointclouds;
  192. let result = Common.sortByScore(pointclouds,[],[e=>{
  193. let center = e.bound.getCenter(new THREE.Vector3)
  194. let size = e.bound.getSize(new THREE.Vector3).length() / 2
  195. let posToCenter = viewer.images360.position.distanceTo(center)
  196. return size / posToCenter
  197. }])
  198. return result[0].item
  199. },
  200. getTarget:function(boundCenter){//box位置。要找一个有点云的地方。方案1相机位置, 方案2接近相机的漫游点, 方案3接近中心的漫游点。选择方案2,因最大概率有点云
  201. var target = new THREE.Vector3()
  202. var cameraPos = viewer.images360.position;
  203. var pano = Common.find(viewer.images360.panos , [], [Images360.sortFunctions.floorDisSquaredToPoint(cameraPos)]);
  204. if(pano){
  205. target.copy(pano.position)
  206. target.setZ(boundCenter.z)
  207. }else{
  208. target.copy(boundCenter)
  209. }
  210. return target
  211. },
  212. /* switchMap:function(state){
  213. }, */
  214. download:function( ){
  215. if(this.getIntersectPointcloud().length == 0){
  216. return null
  217. }
  218. var visiPointclouds = viewer.scene.pointclouds.filter(e=> viewer.getObjVisiByReason(e, 'datasetSelection'))
  219. let data = {
  220. transformation_matrix: visiPointclouds.map((cloud)=>{
  221. let data = {
  222. id: cloud.dataset_id,
  223. matrix : this.getTransformationMatrix(cloud).elements, //剪裁大框
  224. VisiMatrixes: cloud.material.clipBoxes_in.map(e=>this.getTransformationMatrix(cloud, e.inverse).elements), //若干个可见型小框(虽然现在用不到了,因为普通界面不展示这些剪裁区域)
  225. UnVisiMatrixes: cloud.material.clipBoxes_out.map(e=>this.getTransformationMatrix(cloud, e.inverse).elements), //若干个不可见型小框
  226. modelMatrix:(new THREE.Matrix4).copy(cloud.transformMatrix).transpose().elements
  227. }
  228. return data
  229. }) ,
  230. aabb: "b-0.5 -0.5 -0.5 0.5 0.5 0.5" //剪裁空间( 所有点在乘上这个矩阵后, 还能落在 1 * 1 * 1的box内的点就是所裁剪的
  231. }
  232. return data
  233. //https://testlaser.4dkankan.com/indoor/t-ia44BhY/api/pointcloud/crop
  234. },
  235. downloadNoCrop(){//不剪裁 下载整个点云
  236. var visiPointclouds = viewer.scene.pointclouds.filter(e=> viewer.getObjVisiByReason(e, 'datasetSelection'))
  237. let data = {
  238. transformation_matrix: visiPointclouds.map((cloud)=>{
  239. let data = {
  240. id: cloud.dataset_id,
  241. matrix : new THREE.Matrix4().elements, //固定值
  242. modelMatrix:(new THREE.Matrix4).copy(cloud.transformMatrix).transpose().elements
  243. }
  244. return data
  245. }) ,
  246. aabb: "b-12742000 -12742000 -12742000 12742000 12742000 12742000" //固定剪裁空间
  247. }
  248. console.log(data)
  249. return data
  250. },
  251. getTransformationMatrix:function(pointcloud, invMatrix) {//剪裁矩阵
  252. var invMatrix = invMatrix || this.box.matrixWorld.clone().invert()
  253. return (new THREE.Matrix4).multiplyMatrices(invMatrix, pointcloud.transformMatrix).transpose()
  254. },
  255. /* getIntersectPointcloud(){
  256. var boxBound = new THREE.Box3(
  257. new THREE.Vector3(-0.5,-0.5,-0.5), new THREE.Vector3(0.5,0.5,0.5),
  258. ).applyMatrix4(this.box.matrixWorld) //large boundingbox
  259. let boxMatrixInverse = new THREE.Matrix4().copy(this.box.matrixWorld).invert();
  260. let boxPoints = [
  261. new THREE.Vector3(boxBound.min.x, boxBound.min.y,0),
  262. new THREE.Vector3(boxBound.max.x, boxBound.min.y,0),
  263. new THREE.Vector3(boxBound.max.x, boxBound.max.y,0),
  264. new THREE.Vector3(boxBound.min.x, boxBound.max.y,0)
  265. ]
  266. var intersect = (pointcloud)=>{
  267. if(!pointcloud.bound.intersectsBox(boxBound))return false
  268. //判断box和点云的tight bound是否相交(因为box可以任意旋转,且实在找不到三维中的立方体相交的函数,所以直接用boxBound)
  269. var points = pointcloud.getUnrotBoundPoint('all')
  270. let rings = math.getPolygonsMixedRings([points.slice(0,4), boxPoints] , true)
  271. //console.log(pointcloud.dataset_id, pointcloud.name, rings.length)
  272. if(rings.length > 1 )return false
  273. {//再用frustum和数据集的sphere相交试试,能排除一些错误
  274. let a = Potree.Utils.isIntersectBox(points, this.box.matrixWorld)
  275. if(!a){
  276. console.log('没能经过isInsideBox测试')
  277. }
  278. return a
  279. }
  280. return true
  281. }
  282. return viewer.scene.pointclouds.filter(e=>intersect(e))
  283. } */
  284. getIntersectPointcloud(){
  285. var intersect = (pointcloud)=>{
  286. if(pointcloud.intersectBox(this.box.matrixWorld))return true
  287. }
  288. return viewer.scene.pointclouds.filter(e=>intersect(e))
  289. }
  290. }
  291. export {Clip}
  292. /*
  293. 裁剪点云时,2D界面显示全部平面图,按楼层切换显示。
  294. */