Clip.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  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. import SplitScreen from "../../utils/SplitScreen.js"
  9. import {ExtendView} from "../../../viewer/ExtendView.js";
  10. import Viewport from "../../viewer/Viewport.js";
  11. import {TextSprite} from "../../objects/TextSprite.js";
  12. import {ExtendPointCloudMaterial} from "../../../materials/ExtendPointCloudMaterial.js";
  13. import AxisViewer from "../../objects/tool/AxisViewer.js";
  14. const defaultBoxWidth = 16; //navvis: 10
  15. //navvis position: si {x: 0, y: 0, z: 0}
  16. const cameraProps = [
  17. {
  18. name : 'top',
  19. axis:["x","y"],
  20. direction : new THREE.Vector3(0,0,-1), //镜头朝向
  21. openCount:0,
  22. },
  23. {
  24. name : 'front',
  25. axis:["x","z"],
  26. direction : new THREE.Vector3(0,1,0),
  27. openCount:0,
  28. },
  29. {
  30. name : 'mainView',
  31. openCount:0,
  32. }
  33. ]
  34. var Clip = {
  35. bus : new THREE.EventDispatcher,
  36. selectedDatasets : [],
  37. changeCallback(force){
  38. if(this.activeViewName != 'mainView'){
  39. this.splitScreenTool.updateCameraOutOfModel() //更新相机位置
  40. }
  41. if(Potree.settings.isOfficial && this.showMap){
  42. let fun = ()=>{
  43. let pointclouds = this.getIntersectPointcloud()
  44. if( Common.getDifferenceSet(pointclouds,this.selectedDatasets).length){
  45. this.selectedDatasets = pointclouds
  46. //console.error('clipSelectedDatasets',selectedDatasets)
  47. this.bus.dispatchEvent({type:'updateSelectedDatasets', selectedDatasets:pointclouds.map(e=>e.dataset_id) })
  48. }
  49. }
  50. if(force)fun()
  51. else Common.intervalTool.isWaiting('clipSelectedDatasets', fun , 300)
  52. }
  53. },
  54. enter:function(){
  55. this.initViews()
  56. this.previousView = {
  57. position: viewer.images360.position,
  58. target: viewer.scene.view.getPivot(),
  59. displayMode : Potree.settings.displayMode,
  60. //---
  61. ifShowMarker : Potree.settings.ifShowMarker,
  62. }
  63. let pointcloud = this.getPointcloud()
  64. let bound = pointcloud.bound //只选取其中一个数据集的bound,而非整体,是因为担心两个数据集中间有空隙,于是刚好落在没有点云的地方。
  65. let boundSize = bound.getSize(new THREE.Vector3())
  66. let target = this.getTarget(bound.getCenter(new THREE.Vector3())); //navvis的位置xy是用相机位置 this.ViewService.mainView.getCamera().position 我觉得也可以用第一个漫游点的,或者最接近bound中心的漫游点
  67. let scale = new THREE.Vector3(defaultBoxWidth,defaultBoxWidth, boundSize.z)//z和navvis一样
  68. let eyeDir = viewer.scene.view.direction.clone().setZ(0/* -boundSize.z/3 */).multiplyScalar(-defaultBoxWidth) //为了使所在楼层不变,不修改z
  69. //let eyeDir = scale.clone().setZ(boundSize.z/3).multiplyScalar(1.3)
  70. let position = new THREE.Vector3().addVectors(target, eyeDir)
  71. Potree.settings.displayMode = 'showPointCloud'
  72. viewer.setView({
  73. position ,
  74. target,
  75. duration:300,
  76. callback:function(){
  77. }
  78. })
  79. //viewer.setControls(viewer.orbitControls);
  80. viewer.setLimitFar(false)
  81. //viewer.setClipState(false) //暂时关闭旧的clipping
  82. /* new AxisViewer(viewer.mainViewport, viewer.renderArea,{domStyle:{
  83. bottom: '2px',right: '20px', width:'80px',height:'80px'}
  84. }) */
  85. {
  86. this.box = new BoxVolume({
  87. clip:true
  88. })
  89. this.box.clipTask = ClipTask['SHOW_INSIDE_Big' /* "SHOW_INSIDE" */]
  90. this.box.showBox = false
  91. this.box.name = "ClipBox";
  92. this.box.position.copy(target)
  93. this.box.scale.copy(scale)
  94. viewer.controls.setTarget(this.box.position)//绕其旋转
  95. this.splitScreenTool.focusCenter = this.box.position
  96. //带动mapBox
  97. this.box.addEventListener('position_changed',e=>{
  98. if(this.showMap){
  99. this.mapBox.center.setX(this.box.position.x)
  100. this.mapBox.center.setY(this.box.position.y)
  101. this.mapBox.updatePoints()
  102. }
  103. this.changeCallback()
  104. })
  105. this.box.addEventListener('scale_changed',e=>{
  106. if(this.showMap){
  107. var scale = this.box.scale
  108. this.mapBox.updatePoints(scale)
  109. }
  110. this.changeCallback()
  111. })
  112. this.box.addEventListener('orientation_changed',e=>{
  113. if(this.showMap){
  114. this.mapBox.angle = this.box.rotation.z
  115. this.mapBox.rotateBar.rotation.z = this.mapBox.angle
  116. this.mapBox.updatePoints()
  117. }
  118. this.changeCallback()
  119. })
  120. viewer.scene.addVolume(this.box);
  121. }
  122. if(this.showMap){//map
  123. let boxRotateBack = ()=>{//不知道是不是这么写。 因为可能z的旋转不一定都在z
  124. this.box.rotation.x = 0;
  125. this.box.rotation.y = 0;
  126. }
  127. this.mapBox = new mapClipBox(target, scale)
  128. viewer.mapViewer.scene.add(this.mapBox)
  129. //带动box
  130. this.mapBox.addEventListener('repos',e=>{
  131. this.box.position.setX(this.mapBox.center.x)
  132. this.box.position.setY(this.mapBox.center.y)
  133. boxRotateBack()
  134. this.changeCallback()
  135. })
  136. this.mapBox.addEventListener('dragChange',e=>{
  137. var scale = this.mapBox.getScale()
  138. this.box.scale.setX(scale.x)
  139. this.box.scale.setY(scale.y)
  140. this.box.position.setX(this.mapBox.center.x)
  141. this.box.position.setY(this.mapBox.center.y)
  142. boxRotateBack()
  143. this.changeCallback()
  144. })
  145. this.mapBox.addEventListener('rotate',e=>{
  146. this.box.rotation.z = this.mapBox.angle
  147. boxRotateBack()
  148. this.changeCallback()
  149. })
  150. }
  151. this.switchView('mainView')
  152. {
  153. //viewer.setClipTask(ClipTask["SHOW_INSIDE"])
  154. }
  155. Potree.settings.unableNavigate = true
  156. Potree.settings.ifShowMarker = false
  157. Potree.Utils.updateVisible(viewer.measuringTool.scene, 'clipModel', false)
  158. //Potree.Utils.updateVisible(viewer.mapViewer.cursor, 'clipModel', false)//隐藏地图游标
  159. viewer.inputHandler.toggleSelection(this.box);
  160. viewer.inputHandler.fixSelection = true
  161. viewer.transformationTool.setModeEnable(['scale', 'translation', 'rotation'] )
  162. viewer.transformationTool.frame.material.color.set(Potree.config.clip.color)//navvis 15899953
  163. viewer.setPointStandardMat(true)
  164. if(this.showMap){
  165. let mapVisi = false
  166. this.events = {
  167. flyToPos : (e)=>{
  168. let dis = 2
  169. let target = e.position
  170. target = this.box.position
  171. position = e.position
  172. //为了方便缩放操作,直接使用box中心作为target
  173. let duration = e.duration == void 0 ? 1000 : e.duration
  174. viewer.scene.view.setView({position, duration, target})
  175. },
  176. mapVisiChange(e){
  177. mapVisi = e.visible
  178. let delay = 100 //因resize了camera需要时间更新projectionMatrix
  179. setTimeout(()=>{
  180. let boundingBox = Clip.box.boundingBox.clone().applyMatrix4(Clip.box.matrixWorld)
  181. if(mapVisi){//切换地图
  182. if(viewer.fpVisiDatasets.length == 0){//不会显示任何一个floorplan图,就要就近移动到一个可见数据集里
  183. let clouds = viewer.scene.pointclouds.filter(e=>e.visible)
  184. if(clouds.length == 0)clouds = viewer.scene.pointclouds
  185. let scores = clouds.map((e,i)=>{
  186. return [ -viewer.scene.view.position.distanceToSquared(e.bound2) , i]
  187. })
  188. scores = scores.sort((a,b)=> a[0] - b[0])
  189. let neareast = clouds[scores[0][1]]
  190. viewer.flyToDataset({ pointcloud : neareast, duration:0})
  191. }
  192. if(Clip.switchMapCount == 0 || !Potree.Utils.getPos2d(viewer.scene.view.position, viewer.mapViewer.viewports[0], viewer.mapViewer.renderArea).inSight
  193. || !Potree.Utils.isInsideFrustum(boundingBox, viewer.mapViewer.camera)){ //要使box框和游标都在屏幕内。因为游标是3d的当前位置,很可能是准备去框住的位置
  194. let bound = boundingBox.clone()
  195. bound.expandByPoint(viewer.scene.view.position)
  196. let size = bound.getSize(new THREE.Vector3)
  197. let center = bound.getCenter(new THREE.Vector3)
  198. let margin = viewer.mainViewport.resolution.clone().multiplyScalar(0.3)
  199. viewer.mapViewer.moveTo(center, size, 100, margin)
  200. }
  201. Clip.switchMapCount++
  202. //关于究竟是focus box还是dataset有点纠结,又或是两个的union。box和数据集可能离得很远,且无法确定当前想选择的数据集,且数据集可能无floorplan, 即使有可能也不展示……
  203. }else{//切换3d
  204. if(!Potree.Utils.isInsideFrustum(boundingBox, viewer.scene.getActiveCamera())){//屏幕上没有box的话
  205. viewer.focusOnObject({boundingBox}, 'boundingBox', 100 )
  206. }
  207. }
  208. },delay)
  209. }
  210. }
  211. this.switchMapCount = 0
  212. this.bus.addEventListener('flyToPos',this.events.flyToPos)
  213. viewer.mapViewer.addEventListener('forceVisible',this.events.mapVisiChange)
  214. }
  215. this.editing = true
  216. setTimeout(()=>{this.changeCallback(true)},1)
  217. },
  218. leave:function(){
  219. viewer.inputHandler.fixSelection = false
  220. viewer.scene.removeVolume(this.box);
  221. this.showMap && this.mapBox.dispose()
  222. //viewer.setControls(viewer.fpControls);
  223. this.switchView('mainView')
  224. Potree.settings.unableNavigate = false
  225. Potree.settings.ifShowMarker = this.previousView.ifShowMarker
  226. Potree.Utils.updateVisible(viewer.measuringTool.scene, 'clipModel', true)
  227. //Potree.Utils.updateVisible(viewer.mapViewer.cursor, 'clipModel', true)
  228. viewer.setView(this.previousView)
  229. viewer.setLimitFar(true)
  230. viewer.setPointStandardMat(false)
  231. //viewer.setClipState(true)
  232. viewer.controls.setTarget(null)
  233. this.splitScreenTool.focusCenter = null
  234. //viewer.mainViewport.axis.dispose()
  235. cameraProps.forEach(e=>e.openCount = 0)
  236. if(this.showMap){
  237. this.bus.removeEventListener('flyToPos',this.events.flyToPos)
  238. viewer.mapViewer.removeEventListener('forceVisible',this.events.mapVisiChange)
  239. this.events = null
  240. }
  241. this.editing = false
  242. },
  243. initViews(){
  244. if(this.views){
  245. this.views.top.pitch = -Math.PI
  246. this.views.top.yaw = 0
  247. this.views.front.pitch = 0
  248. this.views.front.yaw = 0 //--还原for bugID 44757
  249. return
  250. }
  251. this.views = {}
  252. this.cameras = {}
  253. this.orthoCamera = new THREE.OrthographicCamera(-100, 100, 100, 100, 0.01, 10000)
  254. this.orthoCamera.up.set(0,0,1)
  255. this.splitScreenTool = new SplitScreen
  256. this.targetPlane = viewer.mainViewport.targetPlane = new THREE.Plane()
  257. this.shiftTarget = viewer.mainViewport.shiftTarget = new THREE.Vector3 //project在targetPlane上的位置
  258. for(let i=0;i<2;i++){
  259. let prop = cameraProps[i];
  260. let view = new ExtendView()
  261. this.views[prop.name] = view
  262. this.cameras[prop.name] = this.orthoCamera
  263. view.name = prop.name
  264. view.direction = prop.direction
  265. }
  266. this.views.mainView = viewer.mainViewport.view
  267. this.cameras.mainView = viewer.mainViewport.camera
  268. },
  269. switchView(name ){//替换view和camera到mainViewport
  270. if(this.activeViewName == name)return
  271. let view = this.views[name]
  272. let camera = this.cameras[name]
  273. let prop = cameraProps.find(e=>e.name == name)
  274. let {boundSize, center, boundingBox} = viewer.bound
  275. this.lastViewName = this.activeViewName
  276. this.activeViewName = name
  277. let lastView = this.views[this.lastViewName]
  278. let lastCamera = this.cameras[this.lastViewName]
  279. viewer.mainViewport.view = view
  280. viewer.mainViewport.camera = camera
  281. if(lastCamera)lastView.zoom = lastCamera.zoom
  282. view.zoom && (camera.zoom = view.zoom)
  283. viewer.updateScreenSize({forceUpdateSize:true})//更新camera aspect left等
  284. view.applyToCamera(camera)
  285. let ifFocus
  286. let boxBound = Clip.box.boundingBox.clone().applyMatrix4(Clip.box.matrixWorld)
  287. if(!Potree.Utils.isInsideFrustum(boxBound, camera)){//屏幕上没有box的话
  288. ifFocus = true
  289. }
  290. if(name == 'mainView'){
  291. if(lastView){//2d->3d
  292. }
  293. if(prop.openCount == 0) ifFocus = false
  294. //viewer.fpControls.lockKey = false
  295. }else{
  296. this.targetPlane.setFromNormalAndCoplanarPoint( view.direction.clone(), center )
  297. this.targetPlane.projectPoint(view.position, this.shiftTarget ) //target转换到过模型中心的平面,以保证镜头一定在模型外
  298. //view.position.copy(this.splitScreenTool.getPosOutOfModel(viewer.mainViewport))
  299. //if(view.zoom)camera.zoom = view.zoom//恢复上次的zoom
  300. if(prop.openCount == 0){//至多执行一次
  301. ifFocus = true
  302. }
  303. if(this.lastViewName == 'mainView'){//3d->2d
  304. }else{
  305. }
  306. ifFocus || this.splitScreenTool.updateCameraOutOfModel() //更新相机位置
  307. //if(name == 'top') viewer.mainViewport.alignment = {rotate:true,translate:true};
  308. if(name == 'front'){
  309. //viewer.mainViewport.alignment = {translate:true, rotateSide:true, translateVec:new THREE.Vector3(0,0,1)}; //只能上下移动
  310. viewer.mainViewport.rotateSide = true
  311. }else{
  312. viewer.mainViewport.rotateSide = false
  313. }
  314. //viewer.fpControls.lockKey = true
  315. }
  316. ifFocus && this.focusOnObject(this.box)
  317. //首次到front最好能自动对准box的长边的角度上
  318. prop.openCount ++;
  319. },
  320. focusOnObject(box, duration=0){
  321. if(this.activeViewName == 'mainView'){
  322. viewer.focusOnObject({boundingBox:box.boundingBox.clone().applyMatrix4(box.matrixWorld)}, 'boundingBox', duration)
  323. }else{
  324. this.orthoMoveFit(box.position, {bound:box.boundingBox.clone().applyMatrix4(box.matrixWorld)}, duration)
  325. }
  326. },
  327. transform(type, axis, value ){//使用按键微调
  328. this.box[type][axis] += value;
  329. },
  330. setModeEnable(type, enable){
  331. let modes = []
  332. ;['scale', 'translation', 'rotation'].forEach(e=>{
  333. if(e == type){
  334. enable && modes.push(e)
  335. }else{
  336. viewer.transformationTool.modesEnabled[e] && modes.push(e)
  337. }
  338. })
  339. viewer.transformationTool.setModeEnable(modes)
  340. viewer.dispatchEvent('content_changed')
  341. },
  342. rotateSideCamera(angle){//侧视图绕模型中心水平旋转到的角度 angle: -180 ~ 180
  343. let diff = THREE.Math.degToRad(angle) - viewer.mainViewport.view.yaw
  344. this.splitScreenTool.rotateSideCamera(viewer.mainViewport, diff)
  345. },
  346. orthoMoveFit(pos, info, duration){
  347. var margin = {x:viewer.mainViewport.resolution.x*0.4, y:viewer.mainViewport.resolution.y*0.4}
  348. this.splitScreenTool.viewportFitBound(viewer.mainViewport, info.bound, pos, duration, margin )
  349. },
  350. getPointcloud:function(){ //找一个离当前最近的点云,且最好有漫游点
  351. let pointclouds = viewer.scene.pointclouds.filter(e=>e.panos.length>0)
  352. if(pointclouds.length == 0)pointclouds = viewer.scene.pointclouds;
  353. let result = Common.sortByScore(pointclouds,[],[e=>{
  354. let center = e.bound.getCenter(new THREE.Vector3)
  355. let size = e.bound.getSize(new THREE.Vector3).length() / 2
  356. let posToCenter = viewer.images360.position.distanceTo(center)
  357. return size / posToCenter
  358. }])
  359. return result[0].item
  360. },
  361. getTarget:function(boundCenter){//box位置。要找一个有点云的地方。方案1相机位置, 方案2接近相机的漫游点, 方案3接近中心的漫游点。选择方案2,因最大概率有点云
  362. var target = new THREE.Vector3()
  363. var cameraPos = viewer.images360.position;
  364. var pano = Common.find(viewer.images360.panos , [], [Images360.sortFunctions.floorDisSquaredToPoint(cameraPos)]);
  365. if(pano){
  366. target.copy(pano.position)
  367. target.setZ(boundCenter.z)
  368. }else{
  369. target.copy(boundCenter)
  370. }
  371. return target
  372. },
  373. /* switchMap:function(state){
  374. }, */
  375. download:function( ){
  376. if(this.getIntersectPointcloud().length == 0){
  377. return null
  378. }
  379. var visiPointclouds = viewer.scene.pointclouds.filter(e=> Potree.Utils.getObjVisiByReason(e, 'datasetSelection'))
  380. visiPointclouds.sort((a,b)=>{return a.dataset_id-b.dataset_id})//缓存需要固定排序好比较
  381. let data = {
  382. transformation_matrix: visiPointclouds.map((cloud)=>{
  383. let data = {
  384. id: cloud.dataset_id,
  385. matrix : this.getTransformationMatrix(cloud).elements, //剪裁大框
  386. visiMatrixes: cloud.material.clipBoxes_in.map(e=>this.getTransformationMatrix(cloud, e.inverse).elements), //若干个可见型小框(虽然现在用不到了,因为普通界面不展示这些剪裁区域)
  387. unVisiMatrixes: cloud.material.clipBoxes_out.map(e=>this.getTransformationMatrix(cloud, e.inverse).elements), //若干个不可见型小框
  388. modelMatrix:(new THREE.Matrix4).copy(cloud.transformMatrix).transpose().elements
  389. }
  390. return data
  391. }) ,
  392. aabb: "b-0.5 -0.5 -0.5 0.5 0.5 0.5" //剪裁空间( 所有点在乘上这个矩阵后, 还能落在 1 * 1 * 1的box内的点就是所裁剪的
  393. }
  394. return data
  395. },
  396. downloadNoCrop(){//不剪裁 下载整个点云
  397. var visiPointclouds = viewer.scene.pointclouds.filter(e=> Potree.Utils.getObjVisiByReason(e, 'datasetSelection'))
  398. visiPointclouds.sort((a,b)=>{return a.dataset_id-b.dataset_id})//缓存需要固定排序好比较
  399. let data = {
  400. transformation_matrix: visiPointclouds.map((cloud)=>{
  401. let data = {
  402. id: cloud.dataset_id,
  403. matrix : new THREE.Matrix4().elements, //固定值
  404. modelMatrix:(new THREE.Matrix4).copy(cloud.transformMatrix).transpose().elements
  405. }
  406. return data
  407. }) ,
  408. aabb: "b-12742000 -12742000 -12742000 12742000 12742000 12742000" //固定剪裁空间
  409. }
  410. console.log(data)
  411. return data
  412. },
  413. getTransformationMatrix:function(pointcloud, invMatrix) {//剪裁矩阵
  414. var invMatrix = invMatrix || this.box.matrixWorld.clone().invert()
  415. return (new THREE.Matrix4).multiplyMatrices(invMatrix, pointcloud.transformMatrix).transpose()
  416. },
  417. getIntersectPointcloud(){
  418. var intersect = (pointcloud)=>{
  419. if(pointcloud.intersectBox(this.box.matrixWorld))return true
  420. }
  421. return viewer.scene.pointclouds.filter(e=>intersect(e))
  422. },
  423. getRulerBound(){//坐标尺边界
  424. let camera = viewer.mainViewport.camera
  425. if(!camera.isOrthographicCamera)return
  426. let w = camera.right / camera.zoom //half
  427. let h = camera.top / camera.zoom
  428. let points = []
  429. var boundAtCamera = new THREE.Box3()
  430. Clip.box.children[0].geometry.vertices.forEach(e=>{ //模仿getPosWithFullBound
  431. let p = e.clone().applyMatrix4(Clip.box.matrixWorld)
  432. points.push(p)
  433. let p1 = p.clone().applyMatrix4(camera.matrixWorldInverse);
  434. boundAtCamera.expandByPoint(p1)
  435. })
  436. //需要找出clipbox的bound的左上角,它在标尺中是原点
  437. let ClipBoxLeftTop = new THREE.Vector2(boundAtCamera.min.x, boundAtCamera.max.y) //相对于相机的位置
  438. let camPos = new THREE.Vector2(-ClipBoxLeftTop.x, ClipBoxLeftTop.y)//由于ClipBoxLeftTop要变换到原点,所以相机位置就成了ClipBoxLeftTop的相反数, 但因是从上到下所以y再乘-1
  439. let bound_ = {
  440. left: camPos.x - w,
  441. right: camPos.x + w,
  442. bottom: camPos.y + h, //注意从上到下增大
  443. top: camPos.y - h,
  444. }
  445. //console.log(bound)
  446. return bound_
  447. },
  448. screenshot: async (rulerBound, rulerMargin, unitText='像素 : 米' )=>{ //测绘图下载。顶视图|侧视图
  449. return new Promise((resolve,reject)=>{
  450. if(Clip.screenshoting )return reject()
  451. let visiPointclouds = viewer.scene.pointclouds.filter(e=> Potree.Utils.getObjVisiByReason(e, 'datasetSelection'))
  452. let camera = viewer.mainViewport.camera
  453. //if(Clip.activeViewName == 'mainView')return reject()
  454. const maxWidth = 8192, minWidth = Potree.browser.urlHasValue('clipMinWidth',true) || 1024 //图片尺寸最大最小值。
  455. //Potree.settings.pointDensity = 'ultraHigh'
  456. viewer.inputHandler.deselectAll()
  457. Clip.box.visible = false
  458. Clip.screenshoting = true
  459. let material = new ExtendPointCloudMaterial
  460. material.pointSizeType = Potree.PointSizeType.FIXED //Potree.PointSizeType
  461. material.size = Potree.browser.urlHasValue('clipPointSize',true) || 1
  462. material.uniforms.minSize.value = 0.1
  463. material.activeAttributeName = 'rgba'
  464. //material.classification = pointcloud.material.classification;
  465. material.clipBoxBig_in = visiPointclouds[0].material.clipBoxBig_in
  466. material.shaderNeedsUpdate = true
  467. material.bigClipInBox = visiPointclouds[0].material.bigClipInBox
  468. let materials = visiPointclouds.map(e=>{
  469. let mat = e.material
  470. e.material = material
  471. return mat
  472. })
  473. //根据boundingBox尺寸来定图片尺寸
  474. let boundingBox = Clip.box.boundingBox.clone().applyMatrix4(Clip.box.matrixWorld)
  475. let points = []
  476. var bound = new THREE.Box3()
  477. Clip.box.children[0].geometry.vertices.forEach(e=>{ //模仿getPosWithFullBound
  478. let p = e.clone().applyMatrix4(Clip.box.matrixWorld)
  479. points.push(p)
  480. let p1 = p.clone().applyMatrix4(camera.matrixWorldInverse);
  481. bound.expandByPoint(p1)
  482. })
  483. let boundSize = bound.getSize(new THREE.Vector3)
  484. let minZoom = Potree.browser.urlHasValue('clipZoom',true) || 80 //1px = 1个点 = 1cm , 如果一个点都是1cm的话
  485. //zoom不宜过大或过小,过小点云重叠、画面模糊;过大点云有间隙,纹理就看不清
  486. //中间部分75-80为佳,文字100,边缘50. 最好支持调节 . 且当下载尺寸变大往往点云也多,可能冲破pointbudget限制
  487. //let text = `1 : ${(1 / camera.zoom).toFixed(4)}(像素 : 米)`
  488. let w = boundSize.x * minZoom
  489. let h = boundSize.y * minZoom
  490. let max = Math.max(w,h)
  491. let s = 1
  492. if(max > maxWidth){
  493. s = maxWidth/max
  494. }else if(max < minWidth){
  495. s = minWidth/max
  496. }
  497. w *= s, h *= s
  498. material.size *= s
  499. s != 1 && console.log('宽度缩放倍数',s)
  500. //虽然size>1就是浪费像素,但是太小了视觉效果有点差……可能因为fov不真实。 点云大就像是老式电视机虽然显示像素量少但感觉不出画面模糊,且边缘锐利。
  501. w = Math.round(w)
  502. h = Math.round(h)
  503. let focusObjectInfo = [{boundingBox, points}, 'boundingBox', 0, { dontChangeCamDir:true, dontMoveMap:true, boundScale:1 }]
  504. //分块渲染截图,最后拼合图片。需要使每一块的点数不超过pointBudget, 且尺寸不超过4096(会崩溃),过小的话一般不会超过pointBudget,除非是深度较大后排点云多。
  505. let r = THREE.Math.clamp(Math.sqrt(3.5e7 / boundSize.z) * s, 1024, 4096); //每多少米分一块。 推理 wc*hc = w / r * h / r = zoom * w * s * zoom * h * s / r^2 = xyz / V(常数,每多少立方米会超出pointBudget,就是括号里的数字,给大概值)
  506. //let r = THREE.Math.clamp(1024 / boundSize.z, 256, 4096);
  507. let wc = w / r //横向块数
  508. let hc = h / r //纵向块数
  509. let splitRenderInfo = (wc > 1 || hc > 1) && {wc: Math.ceil(wc), hc:Math.ceil(hc)} //因为结果需要化为整数所以可能不太理想, 分割数过多。 另外如果面积小但是因z过长而超出pointBudget没关系,因后排点云无需绘制
  510. console.log({wc,hc,w,h})
  511. let meterPerPixel = boundSize.x / w
  512. let text = `1 : ${(meterPerPixel).toFixed(4)}(${unitText})`
  513. let beforeScreenshot = ()=>{
  514. if(rulerBound){
  515. let ruler = Clip.getRulerBound()
  516. Object.assign(rulerBound,ruler)
  517. }
  518. }
  519. let {getImagePromise, finishPromise} = viewer.startScreenshot({ type: 'default', bgOpacity:0, focusObjectInfo, maxTimeForPointLoad : 3e5, pointDensity:'screenshot2', splitRenderInfo, beforeScreenshot }, w, h, 1 )
  520. finishPromise.done(({dataUrl}) => {
  521. viewer.inputHandler.toggleSelection(Clip.box);
  522. Clip.box.visible = true
  523. let img = new Image
  524. img.src = dataUrl
  525. img.onload = async ()=>{//加上标尺比例水印
  526. //固定文字大小和边距像素 //如果有一边过小,可能超出画面外,暂时不管这种可能
  527. const fontsize = math.linearClamp(w, [100, 700, 8000], [12, 20, 30])
  528. const marginSelf = {//img外的margin(标尺内)
  529. left : rulerBound ? 76 : 40,
  530. right : rulerBound ? 30 : 40,
  531. bottom : rulerBound ? 20 + fontsize*2 : 40 + fontsize*2,
  532. top : rulerBound ? 60 : 40,
  533. }
  534. //const Margin = rulerMargin + marginSelf
  535. //文字的边距
  536. let bottomRatioToImg = - Math.min(fontsize*2.5, rulerBound ? marginSelf.bottom : marginSelf.bottom*0.9 ) / h//- (marginSelf.bottom - fontsize)/ h //在img之下. text底部到img底部的距离
  537. //let leftRatioToImg = Margin / w
  538. let labelInfo = {
  539. bottomRatioToImg, horizonCenter:true,//leftRatioToImg,
  540. textColor: rulerBound ? {r: 0, g: 0, b: 0, a: 1} : { r: 255, g: 255, b: 255, a: 1 },
  541. textBorderColor : { r: 30, g: 30, b: 30, a: 1 },
  542. textBorderThick : rulerBound ? 0 : 1 ,
  543. fontsize,
  544. fontWeight: rulerBound ? 'normal':'Bold',
  545. outputCanvas : !!rulerBound,
  546. bgColor: rulerBound ? {r:255,g:255,b:255,a:255}:null
  547. }
  548. labelInfo.bgMargin = {//img外需要多少margin
  549. left: marginSelf.left + rulerMargin, //rulerMargin是在标尺外的margin
  550. bottom : marginSelf.bottom + rulerMargin,
  551. right: marginSelf.right + rulerMargin,
  552. top: marginSelf.top + rulerMargin
  553. }
  554. if(rulerBound){//因为margin 扩大bound
  555. rulerBound.left -= marginSelf.left * meterPerPixel
  556. rulerBound.right += marginSelf.right * meterPerPixel
  557. rulerBound.top -= marginSelf.top * meterPerPixel
  558. rulerBound.bottom += marginSelf.bottom * meterPerPixel
  559. }
  560. //console.log('topRatioToImg',topRatioToImg,'leftRatioToImg',leftRatioToImg,'fontsize', labelInfo.fontsize)
  561. let result = await Potree.Utils.imgAddText(img, text , labelInfo )
  562. //Common.downloadFile(finalDataUrl, 'screenshot11.png')
  563. visiPointclouds.forEach((e,i)=>e.material = materials[i])
  564. Clip.screenshoting = false
  565. viewer.viewports = [viewer.mainViewport]
  566. resolve(result)
  567. }
  568. })
  569. })
  570. /*
  571. 隐患:无法获知最大可加载的点的数量,也未知需要加载的点的数量。需要掌控好分块数量,避免因pointBudget限制造成周围的点稀疏。
  572. 加载点云时间过久
  573. */
  574. return finishPromise
  575. }
  576. }
  577. export {Clip}
  578. /*
  579. 裁剪点云时,2D界面显示全部平面图,按楼层切换显示。
  580. 所有点云下载后点云所带的本地坐标都是场景显示的local坐标,这样才能看起来一样。
  581. 且为了使之和坐标页面对应,坐标页面的本地坐标也是local,而非mesh_local.
  582. //保存box
  583. viewer.modules.Clip.box.matrix
  584. //恢复box
  585. let a = new THREE.Matrix4().fromArray([
  586. 14.189120116830964,
  587. 0,
  588. 0,
  589. 0,
  590. 0,
  591. 9.924790069368392,
  592. 0,
  593. 0,
  594. 0,
  595. 0,
  596. 0.8751009568437951,
  597. 0,
  598. -7.433819981633984,
  599. 1.528696446650445,
  600. 2.9412375879215977,
  601. 1
  602. ])
  603. a.decompose(viewer.modules.Clip.box.position,viewer.modules.Clip.box.quaternion,viewer.modules.Clip.box.scale)
  604. viewer.modules.Clip.rotateSideCamera(-93)
  605. */