Alignment.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  1. import * as THREE from "../../../../libs/three.js/build/three.module.js";
  2. import SplitScreen4Views from "../../utils/SplitScreen4Views.js"
  3. import math from "../../utils/math.js"
  4. import History from "../../utils/History.js"
  5. import {TransformControls} from "../../objects/tool/TransformControls.js";
  6. import CursorDeal from "../../utils/CursorDeal.js";
  7. var Alignment = {
  8. SplitScreen: SplitScreen4Views,
  9. handleState:null, //操作状态 'translate'|'rotate'
  10. bus: new THREE.EventDispatcher(),
  11. selectedClouds:[],
  12. /* prepareRecord : true,
  13. writeToHistory(pointclouds){
  14. if(!this.prepareRecord)return;
  15. this.prepareRecord = false
  16. let content = this.getTemp(pointclouds)
  17. this.history.writeIn(content)
  18. },
  19. undo(){//撤销一步
  20. let last = this.history.pop();
  21. last && last.forEach(item=>{
  22. this.applyTemp(item)
  23. })
  24. }, */
  25. applyTemp(item){
  26. var pointcloud = viewer.scene.pointclouds.find(p=>p.dataset_id+p.name == item.sid)
  27. pointcloud.orientationUser = item.orientationUser
  28. pointcloud.translateUser = item.translateUser
  29. this.setMatrix( pointcloud )
  30. },
  31. getTemp(pointclouds){//记录最近一次保存后的状态,便于恢复
  32. pointclouds = pointclouds || viewer.scene.pointclouds
  33. return pointclouds.map(e=>{
  34. return {
  35. sid : e.dataset_id+e.name,
  36. orientationUser : typeof(e.orientationUser) == 'number' ? e.orientationUser : e.orientationUser.clone() ,
  37. translateUser : e.translateUser.clone(),
  38. }
  39. } )
  40. },
  41. getDatasetQuaternion(pointcloud){
  42. let quaternion
  43. if(typeof(pointcloud.orientationUser) == 'number' ){
  44. quaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), pointcloud.orientationUser)
  45. }else{
  46. quaternion = pointcloud.orientationUser
  47. }
  48. return quaternion
  49. },
  50. getDatasetOrient(pointcloud){
  51. let orientation
  52. if(typeof(pointcloud.orientationUser) == 'number' ){
  53. orientation = pointcloud.orientationUser
  54. }else{
  55. orientation = math.getYawPitchByQua(pointcloud.orientationUser)
  56. }
  57. return orientation
  58. },
  59. init:function(){
  60. let transfromInfo, sideRotHovered
  61. this.history = new History({
  62. applyData: (data)=>{
  63. data.forEach(item=>{
  64. Alignment.applyTemp(item)
  65. })
  66. return true
  67. },
  68. getData:(pointclouds)=>{
  69. return Alignment.getTemp(pointclouds)
  70. }
  71. })
  72. {//侧视图旋转
  73. this.transformControls = new TransformControls(viewer.mainViewport.camera, viewer.renderArea,{
  74. //dontHideWhenFaceCamera: true,
  75. rotFullCircle:true
  76. })
  77. this.transformControls.name = 'Alignment-transformControls'
  78. this.transformControls.setSize(0.5)
  79. viewer.scene.scene.add(this.transformControls)
  80. this.transformControls.setMode('rotate')
  81. this.transformControls.setRotateMethod(2)
  82. this.transformControls._gizmo.hideAxis = {rotate:['x','z','y'] }
  83. this.fakeMarkerForTran = new THREE.Mesh(new THREE.BoxBufferGeometry(0.3,0.3,0.3) , new THREE.MeshBasicMaterial({
  84. color:"#F00", opacity:0.9, transparent:true, visible:false
  85. }));//一个看不见的mesh,只是为了让transformControls移动点云
  86. this.fakeMarkerForTran.name = 'fakeMarkerForTran'
  87. viewer.scene.scene.add(this.fakeMarkerForTran)
  88. let afterMoveCtl = (type)=>{
  89. if(Alignment.afterMoveCtl){
  90. Alignment.afterMoveCtl(type)
  91. }else{
  92. if(type == 'position'){
  93. let moveVec = new THREE.Vector3().subVectors(this.fakeMarkerForTran.position, this.fakeMarkerForTran.oldState.position)
  94. Alignment.translate(this.selectedClouds[0], moveVec)
  95. }else{
  96. let center = this.selectedClouds[0].translateUser;
  97. let diffQua = new THREE.Quaternion().multiplyQuaternions(this.fakeMarkerForTran.quaternion, this.fakeMarkerForTran.oldState.quaternion.clone().invert())
  98. CursorDeal.add('rotatePointcloud')
  99. //Alignment.rotateAround(center, this.selectedClouds, diffQua)
  100. Alignment.rotate(this.selectedClouds[0], null, diffQua)
  101. }
  102. }
  103. this.fakeMarkerForTran.oldState = {
  104. position: this.fakeMarkerForTran.position.clone(),
  105. quaternion: this.fakeMarkerForTran.quaternion.clone(),
  106. }
  107. Alignment.history.beforeChange(this.selectedClouds)
  108. }
  109. this.fakeMarkerForTran.addEventListener('position_changed', afterMoveCtl.bind(this,'position'))
  110. this.fakeMarkerForTran.addEventListener("rotation_changed", afterMoveCtl.bind(this,'rotation') )
  111. this.transformControls.addEventListener('mouseUp',()=>{
  112. Alignment.history.afterChange(this.selectedClouds)
  113. })
  114. this.transformControls.addEventListener('axisHoveredChange',(e)=>{
  115. if(e.axis && e.mode == 'rotate'){
  116. CursorDeal.add('rotatePointcloud'),
  117. sideRotHovered = true
  118. }else{
  119. CursorDeal.remove('rotatePointcloud')
  120. sideRotHovered = false
  121. }
  122. })
  123. this.history.addEventListener('undo',()=>{
  124. this.updateFakeMarker()
  125. })
  126. }
  127. viewer.fpControls.addEventListener("transformPointcloud",(e)=>{
  128. if(e.pointclouds[0].dataset_id == Potree.settings.originDatasetId && Potree.settings.editType != 'pano'){//禁止手动移动初始数据集
  129. return this.bus.dispatchEvent('forbitMoveOriginDataset')
  130. }
  131. this.history.beforeChange(e.pointclouds)
  132. //this.writeToHistory( e.pointclouds )
  133. if(!transfromInfo){
  134. transfromInfo = {pointclouds:e.pointclouds}
  135. }
  136. if(this.handleState == 'translate'){
  137. e.pointclouds.forEach(cloud=>Alignment.translate(cloud, e.moveVec))
  138. //this.updateFakeMarker()
  139. }else if(this.handleState == 'rotate'){
  140. //if(Potree.settings.editType == 'pano'){
  141. //旋转中心是intersectStart的版本
  142. /* let center = e.intersectStart //旋转中心是mousedown的位置
  143. if(e.intersect.equals(center))return
  144. if(!transfromInfo){
  145. transfromInfo = {
  146. orientationUser : e.pointclouds[0].orientationUser,
  147. //vecStart : e.moveVec, // 首次移动向量 只有八个方向,精度太小,所以延迟
  148. pointclouds: e.pointclouds
  149. }
  150. this.bus.dispatchEvent({type:'rotateStart', startPoint:center})
  151. return
  152. }else if(!transfromInfo.vecStart){
  153. let vec = new THREE.Vector3().subVectors(e.intersect, center).setZ(0)
  154. if(vec.length() * e.camera.zoom > 30){ //在屏幕上距离初始点有一定距离后开始算
  155. //console.log('moveVec',vec)
  156. transfromInfo.vecStart = vec
  157. } */
  158. let center = e.pointclouds[0].translateUser //旋转中心是第一个点云的位置
  159. if(e.intersect.equals(center))return
  160. if(!transfromInfo.vecStart){
  161. //transfromInfo.orientationUser = e.pointclouds[0].orientationUser
  162. transfromInfo.vecStart = new THREE.Vector3().subVectors(e.intersectStart, center).setZ(0)
  163. transfromInfo.lastQua = new THREE.Quaternion()
  164. }else{
  165. let vec = new THREE.Vector3().subVectors(e.intersect, center).setZ(0)
  166. let angle = math.getAngle(transfromInfo.vecStart,vec,'z')
  167. //let diffAngle = transfromInfo.orientationUser + angle - transfromInfo.pointclouds[0].orientationUser
  168. let diffQuaFromStart = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,1), angle)
  169. let diffQua = new THREE.Quaternion().multiplyQuaternions(diffQuaFromStart, transfromInfo.lastQua.clone().invert())
  170. transfromInfo.lastQua.copy(diffQuaFromStart)
  171. transfromInfo.pointclouds.forEach(cloud=>{
  172. /* let centerNoTranfrom = Potree.Utils.datasetPosTransform({ toDataset: true, pointcloud:cloud, position: center }) //中心点在数据集中的位置
  173. Alignment.rotate(cloud, null, diffAngle)
  174. let centerNow = Potree.Utils.datasetPosTransform({ fromDataset: true, pointcloud:cloud, position: centerNoTranfrom }) //中心点的现在位置
  175. let shift = new THREE.Vector3().subVectors( center, centerNow); //偏移量
  176. Alignment.translate(cloud,shift) //使center还保留在原位
  177. //let centerNow1 = Potree.Utils.datasetPosTransform({ fromDataset: true, pointcloud:transfromInfo.pointcloud, position: centerNoTranfrom }) //中心点的现在位置
  178. */
  179. Alignment.rotateAround(center, cloud, diffQua)
  180. })
  181. }
  182. //this.bus.dispatchEvent({type:'rotate', endPoint: e.intersect})
  183. /* }else{ //旧版数据集校准只转z轴
  184. let center = e.pointclouds[0].translateUser //移动到的位置就是中心
  185. if(e.intersect.equals(center))return
  186. if(!transfromInfo.vecStart){
  187. transfromInfo.orientationUser = e.pointclouds[0].orientationUser
  188. transfromInfo.vecStart = new THREE.Vector3().subVectors(e.intersectStart, center).setZ(0)
  189. }else{
  190. let vec = new THREE.Vector3().subVectors(e.intersect, center).setZ(0)
  191. let angle = math.getAngle(transfromInfo.vecStart,vec,'z')
  192. let diffAngle = transfromInfo.orientationUser + angle - transfromInfo.pointclouds[0].orientationUser
  193. Alignment.rotate(transfromInfo.pointclouds[0], null, diffAngle)
  194. }
  195. } */
  196. }
  197. })
  198. viewer.fpControls.addEventListener("end",(e)=>{
  199. transfromInfo && this.history.afterChange(transfromInfo.pointclouds )
  200. transfromInfo = null
  201. })
  202. // cursor:
  203. let updateCursor = (e)=>{
  204. if(e.drag || !this.editing)return //仅在鼠标不按下时更新:
  205. let handleState = Alignment.handleState
  206. if(e.hoverViewport.alignment && handleState && e.hoverViewport.alignment[handleState]){
  207. if(handleState == 'translate'){
  208. if( e.intersect && e.intersect.location ){
  209. CursorDeal.add('movePointcloud')
  210. }else{
  211. CursorDeal.remove('movePointcloud')
  212. }
  213. }else if(handleState == 'rotate'){
  214. if( e.intersect && e.intersect.location ){
  215. CursorDeal.add('rotatePointcloud')
  216. }else{
  217. CursorDeal.remove('rotatePointcloud')
  218. }
  219. }
  220. }else{
  221. //清空:
  222. CursorDeal.remove('movePointcloud')
  223. if(!sideRotHovered) CursorDeal.remove('rotatePointcloud')
  224. }
  225. }
  226. viewer.addEventListener('global_mousemove',updateCursor)
  227. viewer.addEventListener('global_drop',updateCursor)//拖拽结束
  228. viewer.addEventListener('updateModelBound', (e)=>{
  229. if(this.editing){
  230. this.SplitScreen.updateCameraOutOfModel()
  231. }
  232. })
  233. },
  234. setMatrix : function(pointcloud){
  235. var vec1 = pointcloud.position //position为数据集内部的偏移,在navvis中对应的是dataset.pointCloudSceneNode的children[0].position
  236. var vec2 = pointcloud.translateUser
  237. var pos1Matrix = new THREE.Matrix4().setPosition(vec1);//先移动到点云本身应该在的初始位置(在4dkk里和其他应用中都是在这个位置的,也能和漫游点对应上)
  238. var rotMatrix = typeof(pointcloud.orientationUser ) == 'number' ? //再旋转
  239. new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), pointcloud.orientationUser ) :
  240. new THREE.Matrix4().makeRotationFromQuaternion(pointcloud.orientationUser)
  241. var pos2Matrix = new THREE.Matrix4().setPosition(vec2);//最后是平移
  242. var matrix = new THREE.Matrix4().multiplyMatrices(pos2Matrix, rotMatrix);
  243. pointcloud.transformMatrix = matrix.clone();//为该数据集的变化矩阵。 对应navvis的m2w_
  244. pointcloud.transformInvMatrix.copy(pointcloud.transformMatrix).invert()
  245. pointcloud.rotateMatrix = rotMatrix
  246. pointcloud.rotateInvMatrix.copy(rotMatrix).invert()
  247. pointcloud.panos.forEach(e=>e.transformByPointcloud())
  248. matrix = new THREE.Matrix4().multiplyMatrices(matrix, pos1Matrix);
  249. //matrix.premultiply(viewer.scene.scene.matrix);////////////////////////add
  250. pointcloud.matrix = matrix;
  251. //pointcloud.matrixWorldNeedsUpdate = true //更新matrixWorld (非计算,直接赋值)
  252. pointcloud.updateMatrixWorld(true)
  253. if(this.editing){
  254. Alignment.changeCallBack && Alignment.changeCallBack();
  255. }
  256. if(pointcloud.spriteNodeRoot){
  257. pointcloud.spriteNodeRoot.matrixWorld.copy(pointcloud.matrixWorld)//.multiplyMatrices(pointcloud.matrixWorld, pointcloud.matrixWorld);
  258. }
  259. viewer.boundNeedUpdate = true
  260. //pointcloud.updateBound()
  261. pointcloud.getPanosBound()
  262. viewer.dispatchEvent('content_changed')
  263. },
  264. rotateAround(center, pointcloud, angle, axis){//绕center点水平转动
  265. let vec1 = new THREE.Vector3().subVectors(pointcloud.translateUser, center);
  266. let rotMatrix = typeof(angle) == 'number' ?
  267. new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0,0,1), angle) :
  268. new THREE.Matrix4().makeRotationFromQuaternion(angle)
  269. let vec2 = vec1.clone().applyMatrix4(rotMatrix) //将到旋转中心的偏差也转动
  270. let vec3 = new THREE.Vector3().subVectors(vec2,vec1); //这个就是多出来的一步translateUser
  271. this.rotate(pointcloud, null, angle)
  272. this.translate(pointcloud, vec3)
  273. //绕点转动就是比普通转动多一步移动到相对center的某个位置。 1 初始点云移动到自己的position; 2 移动一个vec1 3绕原点旋转 4再移动一个原本的translateUser。 绘制出来后发现移动量就是第二步vec旋转后的偏移
  274. },
  275. rotate:function(pointcloud, deg, angle, axis){//绕各自中心水平转动(各自的position) 假设点云位移position后0,0,0就是它的中心了(根据navvis观察这样做是绕同一个点旋转的)
  276. let qua
  277. if(deg || typeof(angle) == 'number'){
  278. angle = angle != void 0 ? angle : THREE.Math.degToRad(deg) //正逆负顺
  279. }else if(angle instanceof Array){//when init
  280. qua = new THREE.Quaternion().fromArray(angle)
  281. }else{
  282. qua = angle
  283. }
  284. if(typeof(pointcloud.orientationUser) == 'number' ){
  285. //pointcloud.orientationUser += angle
  286. pointcloud.orientationUser = this.getDatasetQuaternion(pointcloud)
  287. }
  288. if(!qua){
  289. qua = new THREE.Quaternion().setFromAxisAngle(axis || new THREE.Vector3(0,0,1), angle)
  290. }
  291. pointcloud.orientationUser.premultiply(qua)
  292. //新版全部使用qua
  293. Alignment.setMatrix(pointcloud)
  294. },
  295. translate:function(pointcloud, vec){
  296. pointcloud.translateUser.add(vec)
  297. Alignment.setMatrix(pointcloud)
  298. },
  299. enter:function(){
  300. //this.saveTemp()
  301. this.originData = this.getTemp()
  302. this.SplitScreen.split({alignment:true})
  303. /* viewer.images360.panos.forEach(pano=>{
  304. Potree.Utils.updateVisible(pano.mapMarker, 'split4Screens', false)
  305. }) */
  306. viewer.viewports.find(e=>e.name == 'mapViewport').alignment = {rotate:true,translate:true};
  307. viewer.viewports.find(e=>e.name == 'right').alignment = {translate:true, translateVec:new THREE.Vector3(0,0,1)}; //只能上下移动
  308. viewer.viewports.find(e=>e.name == 'back').alignment = {translate:true, translateVec:new THREE.Vector3(0,0,1)}; //只能上下移动
  309. viewer.viewports.forEach(e=>{
  310. if(e.name == 'right' || e.name == 'back' ){
  311. e.layersAdd('sideVisi')
  312. /* if(e.name == 'right') e.layersAdd('layer1')
  313. else if(e.name == 'back') e.layersAdd('layer2') */
  314. }
  315. })
  316. Potree.Utils.setObjectLayers(this.transformControls, 'sideVisi' )
  317. /* Potree.Utils.setObjectLayers(this.transformControls, 'layer1' )
  318. Potree.Utils.setObjectLayers(this.transformControls, 'layer2' ) */
  319. {
  320. /* viewer.viewports.forEach(e=>{
  321. let oldFun = e.beforeRender
  322. e.beforeRender = function(){
  323. oldFun.bind(this)()
  324. if(e.name == 'right'){
  325. Alignment.transformControls._gizmo.hideAxis = {rotate:['e','z','y'] }
  326. }else if(e.name == 'back'){
  327. Alignment.transformControls._gizmo.hideAxis = {rotate:['e','z','x'] }
  328. }
  329. }
  330. }) */
  331. /* viewer.viewports.find(e=>e.name == 'right').beforeRender = function(){
  332. oldFun.bind(this)()
  333. if(this.name == right)
  334. }
  335. viewer.viewports.find(e=>e.name == 'back').beforeRender = (e)=>{
  336. old2()
  337. this.transformControls._gizmo.hideAxis = {rotate:['e','z','x'] }
  338. } */
  339. }
  340. this.editing = true
  341. viewer.updateFpVisiDatasets()
  342. },
  343. leave:function(){
  344. this.switchHandle(null)
  345. /* this.originData.forEach(e=>{//恢复
  346. var pointcloud = viewer.scene.pointclouds.find(p=>p.dataset_id == e.id)
  347. this.translate(pointcloud, new THREE.Vector3().subVectors(e.translateUser , pointcloud.translateUser))
  348. this.rotate(pointcloud, null, e.orientationUser - pointcloud.orientationUser)
  349. }) */
  350. this.originData.forEach(e=>{//恢复
  351. this.applyTemp(e)
  352. })
  353. this.SplitScreen.recover()
  354. /* viewer.images360.panos.forEach(pano=>{
  355. Potree.Utils.updateVisible(pano.mapMarker, 'split4Screens', true)
  356. }) */
  357. this.editing = false
  358. this.history.clear()
  359. viewer.updateFpVisiDatasets()
  360. CursorDeal.remove('movePointcloud')
  361. CursorDeal.remove('rotatePointcloud')
  362. }
  363. ,
  364. switchHandle:function(state){
  365. console.log('switchHandle',state)
  366. this.handleState = state
  367. //清空:
  368. CursorDeal.remove('movePointcloud')
  369. CursorDeal.remove('rotatePointcloud')
  370. this.bus.dispatchEvent({type:'switchHandle' , state })
  371. this.updateCtlDisplay()
  372. },
  373. updateCtlDisplay(){//仅数据集校准执行
  374. if(!this.editing)return
  375. let selected = this.handleState == 'rotate' && this.selectedClouds.length > 0
  376. if(selected){
  377. this.updateFakeMarker()
  378. }else{
  379. this.transformControls.detach()
  380. }
  381. },
  382. updateFakeMarker(){
  383. this.transformControls.attach(this.fakeMarkerForTran)
  384. let position = this.selectedClouds[0].translateUser
  385. let quaternion = this.getDatasetQuaternion(this.selectedClouds[0])
  386. this.fakeMarkerForTran.position.copy(position)
  387. this.fakeMarkerForTran.quaternion.copy(quaternion)
  388. this.fakeMarkerForTran.oldState = {
  389. position: position.clone(),
  390. quaternion: quaternion.clone()
  391. }
  392. }
  393. ,
  394. save: function(){//保存所有数据集的位置和旋转
  395. let callback = ()=>{//保存成功后
  396. this.originData = this.getTemp() //this.saveTemp();
  397. //需要修改 测量线的position。漫游点已经实时修改了
  398. viewer.scene.measurements.forEach(e=>e.transformByPointcloud())
  399. viewer.images360.updateCube(viewer.bound)
  400. }
  401. let initialPointcloud = viewer.scene.pointclouds.find(e=>e.dataset_id == Potree.settings.originDatasetId)
  402. var data = viewer.scene.pointclouds.map(e=>{
  403. let pos = viewer.transform.lonlatToLocal.inverse(e.translateUser.clone())
  404. let data = {
  405. id: e.dataset_id,
  406. quaternion: this.getDatasetQuaternion(e).toArray(),
  407. orientation : this.getDatasetOrient(e).yaw,
  408. location:[pos.x, pos.y, pos.z + initialPointcloud.datasetData.location[2]],
  409. //transformMatrix: e.transformMatrix.elements,
  410. }
  411. return data
  412. })
  413. //data = JSON.stringify(data)
  414. //test: 退出后保留结果
  415. if(!Potree.settings.isOfficial){
  416. callback()
  417. }
  418. return {data, callback}
  419. }
  420. }
  421. /*
  422. 关于控制点:
  423. 两个控制点只能打在同一个数据集上。传输这两个点的4dkk中的本地坐标和经纬度,后台算出该数据集的旋转平移,
  424. 然后其他数据集绕该数据集旋转,并保持相对位置不变。
  425. 关于cloud.js
  426. tightBoundingBox规定了整个点云应该放置后的边界范围。 boundingBox则是在其基础上变为正方体。
  427. 为了达成这个目标,需要将点云进行位移。在没有位移前,点云boundingBox的min是0,0,0(如果没出错的话)
  428. 所以要令min变为tightBoundingBox.min, 需要将点云position设置为 tightBoundingBox.min 。
  429. 设置完以后整体点云的旋转中心为0,0,0。如果不出错的话0,0,0大概在点云中央附近。
  430. 在添加经纬度信息后,非初始数据集又会有一个位置偏移,其旋转中心也跟着一起偏移,如偏移了 1,0,0,则旋转中心就是 1,0,0
  431. 但是,有的cloud.js的tightBoundingBox使用的是大地坐标,需要转成普通值
  432. */
  433. export {Alignment}