Clip.js 39 KB

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