utils.js 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288
  1. import * as THREE from "../libs/three.js/build/three.module.js";
  2. import {XHRFactory} from "./XHRFactory.js";
  3. import {Volume} from "./objects/tool/Volume.js";
  4. import {Profile} from "./objects/tool/Profile.js";
  5. import {Measure} from "./objects/tool/Measure.js";
  6. import {PolygonClipVolume} from "./objects/tool/PolygonClipVolume.js";
  7. export class Utils {
  8. static async loadShapefileFeatures (file, callback) {
  9. let features = [];
  10. let handleFinish = () => {
  11. callback(features);
  12. };
  13. let source = await shapefile.open(file);
  14. while(true){
  15. let result = await source.read();
  16. if (result.done) {
  17. handleFinish();
  18. break;
  19. }
  20. if (result.value && result.value.type === 'Feature' && result.value.geometry !== undefined) {
  21. features.push(result.value);
  22. }
  23. }
  24. }
  25. static toString (value) {
  26. if (value.x != null) {
  27. return value.x.toFixed(2) + ', ' + value.y.toFixed(2) + ', ' + value.z.toFixed(2);
  28. } else {
  29. return '' + value + '';
  30. }
  31. }
  32. static normalizeURL (url) {
  33. let u = new URL(url);
  34. return u.protocol + '//' + u.hostname + u.pathname.replace(/\/+/g, '/');
  35. };
  36. static pathExists (url) {
  37. let req = XHRFactory.createXMLHttpRequest();
  38. req.open('GET', url, false);
  39. req.send(null);
  40. if (req.status !== 200) {
  41. return false;
  42. }
  43. return true;
  44. };
  45. static debugSphere(parent, position, scale, color){
  46. let geometry = new THREE.SphereGeometry(1, 8, 8);
  47. let material;
  48. if(color !== undefined){
  49. material = new THREE.MeshBasicMaterial({color: color});
  50. }else{
  51. material = new THREE.MeshNormalMaterial();
  52. }
  53. let sphere = new THREE.Mesh(geometry, material);
  54. sphere.position.copy(position);
  55. sphere.scale.set(scale, scale, scale);
  56. parent.add(sphere);
  57. return sphere;
  58. }
  59. static debugLine(parent, start, end, color){
  60. let material = new THREE.LineBasicMaterial({ color: color });
  61. let geometry = new THREE.Geometry();
  62. const p1 = new THREE.Vector3(0, 0, 0);
  63. const p2 = end.clone().sub(start);
  64. geometry.vertices.push(p1, p2);
  65. let tl = new THREE.Line( geometry, material );
  66. tl.position.copy(start);
  67. parent.add(tl);
  68. let line = {
  69. node: tl,
  70. set: (start, end) => {
  71. geometry.vertices[0].copy(start);
  72. geometry.vertices[1].copy(end);
  73. geometry.verticesNeedUpdate = true;
  74. },
  75. };
  76. return line;
  77. }
  78. static debugCircle(parent, center, radius, normal, color){
  79. let material = new THREE.LineBasicMaterial({ color: color });
  80. let geometry = new THREE.Geometry();
  81. let n = 32;
  82. for(let i = 0; i <= n; i++){
  83. let u0 = 2 * Math.PI * (i / n);
  84. let u1 = 2 * Math.PI * (i + 1) / n;
  85. let p0 = new THREE.Vector3(
  86. Math.cos(u0),
  87. Math.sin(u0),
  88. 0
  89. );
  90. let p1 = new THREE.Vector3(
  91. Math.cos(u1),
  92. Math.sin(u1),
  93. 0
  94. );
  95. geometry.vertices.push(p0, p1);
  96. }
  97. let tl = new THREE.Line( geometry, material );
  98. tl.position.copy(center);
  99. tl.scale.set(radius, radius, radius);
  100. parent.add(tl);
  101. }
  102. static debugBox(parent, box, transform = new THREE.Matrix4(), color = 0xFFFF00){
  103. let vertices = [
  104. [box.min.x, box.min.y, box.min.z],
  105. [box.min.x, box.min.y, box.max.z],
  106. [box.min.x, box.max.y, box.min.z],
  107. [box.min.x, box.max.y, box.max.z],
  108. [box.max.x, box.min.y, box.min.z],
  109. [box.max.x, box.min.y, box.max.z],
  110. [box.max.x, box.max.y, box.min.z],
  111. [box.max.x, box.max.y, box.max.z],
  112. ].map(v => new THREE.Vector3(...v));
  113. let edges = [
  114. [0, 4], [4, 5], [5, 1], [1, 0],
  115. [2, 6], [6, 7], [7, 3], [3, 2],
  116. [0, 2], [4, 6], [5, 7], [1, 3]
  117. ];
  118. let center = box.getCenter(new THREE.Vector3());
  119. let centroids = [
  120. {position: [box.min.x, center.y, center.z], color: 0xFF0000},
  121. {position: [box.max.x, center.y, center.z], color: 0x880000},
  122. {position: [center.x, box.min.y, center.z], color: 0x00FF00},
  123. {position: [center.x, box.max.y, center.z], color: 0x008800},
  124. {position: [center.x, center.y, box.min.z], color: 0x0000FF},
  125. {position: [center.x, center.y, box.max.z], color: 0x000088},
  126. ];
  127. for(let vertex of vertices){
  128. let pos = vertex.clone().applyMatrix4(transform);
  129. Utils.debugSphere(parent, pos, 0.1, 0xFF0000);
  130. }
  131. for(let edge of edges){
  132. let start = vertices[edge[0]].clone().applyMatrix4(transform);
  133. let end = vertices[edge[1]].clone().applyMatrix4(transform);
  134. Utils.debugLine(parent, start, end, color);
  135. }
  136. for(let centroid of centroids){
  137. let pos = new THREE.Vector3(...centroid.position).applyMatrix4(transform);
  138. Utils.debugSphere(parent, pos, 0.1, centroid.color);
  139. }
  140. }
  141. static debugPlane(parent, plane, size = 1, color = 0x0000FF){
  142. let planehelper = new THREE.PlaneHelper(plane, size, color);
  143. parent.add(planehelper);
  144. }
  145. /**
  146. * adapted from mhluska at https://github.com/mrdoob/three.js/issues/1561
  147. */
  148. static computeTransformedBoundingBox (box, transform) {
  149. let vertices = [
  150. new THREE.Vector3(box.min.x, box.min.y, box.min.z).applyMatrix4(transform),
  151. new THREE.Vector3(box.min.x, box.min.y, box.min.z).applyMatrix4(transform),
  152. new THREE.Vector3(box.max.x, box.min.y, box.min.z).applyMatrix4(transform),
  153. new THREE.Vector3(box.min.x, box.max.y, box.min.z).applyMatrix4(transform),
  154. new THREE.Vector3(box.min.x, box.min.y, box.max.z).applyMatrix4(transform),
  155. new THREE.Vector3(box.min.x, box.max.y, box.max.z).applyMatrix4(transform),
  156. new THREE.Vector3(box.max.x, box.max.y, box.min.z).applyMatrix4(transform),
  157. new THREE.Vector3(box.max.x, box.min.y, box.max.z).applyMatrix4(transform),
  158. new THREE.Vector3(box.max.x, box.max.y, box.max.z).applyMatrix4(transform)
  159. ];
  160. let boundingBox = new THREE.Box3();
  161. boundingBox.setFromPoints(vertices);
  162. return boundingBox;
  163. };
  164. /**
  165. * add separators to large numbers
  166. *
  167. * @param nStr
  168. * @returns
  169. */
  170. static addCommas (nStr) {
  171. nStr += '';
  172. let x = nStr.split('.');
  173. let x1 = x[0];
  174. let x2 = x.length > 1 ? '.' + x[1] : '';
  175. let rgx = /(\d+)(\d{3})/;
  176. while (rgx.test(x1)) {
  177. x1 = x1.replace(rgx, '$1' + ',' + '$2');
  178. }
  179. return x1 + x2;
  180. };
  181. static removeCommas (str) {
  182. return str.replace(/,/g, '');
  183. }
  184. /**
  185. * create worker from a string
  186. *
  187. * code from http://stackoverflow.com/questions/10343913/how-to-create-a-web-worker-from-a-string
  188. */
  189. static createWorker (code) {
  190. let blob = new Blob([code], {type: 'application/javascript'});
  191. let worker = new Worker(URL.createObjectURL(blob));
  192. return worker;
  193. };
  194. static moveTo(scene, endPosition, endTarget){
  195. let view = scene.view;
  196. let camera = scene.getActiveCamera();
  197. let animationDuration = 500;
  198. let easing = TWEEN.Easing.Quartic.Out;
  199. { // animate camera position
  200. let tween = new TWEEN.Tween(view.position).to(endPosition, animationDuration);
  201. tween.easing(easing);
  202. tween.start();
  203. }
  204. { // animate camera target
  205. let camTargetDistance = camera.position.distanceTo(endTarget);
  206. let target = new THREE.Vector3().addVectors(
  207. camera.position,
  208. camera.getWorldDirection(new THREE.Vector3()).clone().multiplyScalar(camTargetDistance)
  209. );
  210. let tween = new TWEEN.Tween(target).to(endTarget, animationDuration);
  211. tween.easing(easing);
  212. tween.onUpdate(() => {
  213. view.lookAt(target);
  214. });
  215. tween.onComplete(() => {
  216. view.lookAt(target);
  217. });
  218. tween.start();
  219. }
  220. }
  221. static loadSkybox (path) {
  222. let parent = new THREE.Object3D("skybox_root");
  223. let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 100000);
  224. if(!window.axisYup) camera.up.set(0, 0, 1);
  225. let scene = new THREE.Scene();
  226. let format = '.jpg';
  227. let urls = [
  228. path + 'px' + format, path + 'nx' + format,
  229. path + 'py' + format, path + 'ny' + format,
  230. path + 'pz' + format, path + 'nz' + format
  231. ];
  232. let materialArray = [];
  233. {
  234. for (let i = 0; i < 6; i++) {
  235. let material = new THREE.MeshBasicMaterial({
  236. map: null,
  237. side: THREE.BackSide,
  238. depthTest: false,
  239. depthWrite: false,
  240. color: 0x424556
  241. });
  242. materialArray.push(material);
  243. let loader = new THREE.TextureLoader();
  244. loader.load(urls[i],
  245. function loaded (texture) {
  246. material.map = texture;
  247. material.needsUpdate = true;
  248. material.color.setHex(0xffffff);
  249. }, function progress (xhr) {
  250. // console.log( (xhr.loaded / xhr.total * 100) + '% loaded' );
  251. }, function error (xhr) {
  252. console.log('An error happened', xhr);
  253. }
  254. );
  255. }
  256. }
  257. let skyGeometry = new THREE.CubeGeometry(700, 700, 700);
  258. let skybox = new THREE.Mesh(skyGeometry, materialArray);
  259. scene.add(skybox);
  260. scene.traverse(n => n.frustumCulled = false);
  261. // z up
  262. scene.rotation.x = Math.PI / 2;
  263. parent.children.push(camera);
  264. camera.parent = parent;
  265. return {camera, scene, parent};
  266. };
  267. static createGrid (width, length, spacing, color) {
  268. let material = new THREE.LineBasicMaterial({
  269. color: color || 0x888888
  270. });
  271. let geometry = new THREE.Geometry();
  272. for (let i = 0; i <= length; i++) {
  273. geometry.vertices.push(new THREE.Vector3(-(spacing * width) / 2, i * spacing - (spacing * length) / 2, 0));
  274. geometry.vertices.push(new THREE.Vector3(+(spacing * width) / 2, i * spacing - (spacing * length) / 2, 0));
  275. }
  276. for (let i = 0; i <= width; i++) {
  277. geometry.vertices.push(new THREE.Vector3(i * spacing - (spacing * width) / 2, -(spacing * length) / 2, 0));
  278. geometry.vertices.push(new THREE.Vector3(i * spacing - (spacing * width) / 2, +(spacing * length) / 2, 0));
  279. }
  280. let line = new THREE.LineSegments(geometry, material, THREE.LinePieces);
  281. line.receiveShadow = true;
  282. return line;
  283. }
  284. static createBackgroundTexture (width, height) {
  285. function gauss (x, y) {
  286. return (1 / (2 * Math.PI)) * Math.exp(-(x * x + y * y) / 2);
  287. };
  288. // map.magFilter = THREE.NearestFilter;
  289. let size = width * height;
  290. let data = new Uint8Array(3 * size);
  291. let chroma = [1, 1.5, 1.7];
  292. let max = gauss(0, 0);
  293. for (let x = 0; x < width; x++) {
  294. for (let y = 0; y < height; y++) {
  295. let u = 2 * (x / width) - 1;
  296. let v = 2 * (y / height) - 1;
  297. let i = x + width * y;
  298. let d = gauss(2 * u, 2 * v) / max;
  299. let r = (Math.random() + Math.random() + Math.random()) / 3;
  300. r = (d * 0.5 + 0.5) * r * 0.03;
  301. r = r * 0.4;
  302. // d = Math.pow(d, 0.6);
  303. data[3 * i + 0] = 255 * (d / 15 + 0.05 + r) * chroma[0];
  304. data[3 * i + 1] = 255 * (d / 15 + 0.05 + r) * chroma[1];
  305. data[3 * i + 2] = 255 * (d / 15 + 0.05 + r) * chroma[2];
  306. }
  307. }
  308. let texture = new THREE.DataTexture(data, width, height, THREE.RGBFormat);
  309. texture.needsUpdate = true;
  310. return texture;
  311. }
  312. static getMousePointCloudIntersection (viewport, mouse, pointer, camera, viewer, pointclouds, params = {}) {
  313. if(!pointclouds)return
  314. let renderer = viewer.renderer;
  315. let pickParams = {};
  316. if(params.pickClipped){
  317. pickParams.pickClipped = params.pickClipped;
  318. }
  319. if(viewport){ //转换到类似整个画面时
  320. pickParams.x = mouse.x / viewport.width;
  321. pickParams.y = renderer.domElement.clientHeight - mouse.y / viewport.height;
  322. }else{
  323. pickParams.x = mouse.x;
  324. pickParams.y = renderer.domElement.clientHeight - mouse.y;
  325. }
  326. let raycaster = new THREE.Raycaster();
  327. raycaster.setFromCamera(pointer, camera);
  328. let ray = raycaster.ray;
  329. let selectedPointcloud = null;
  330. let closestDistance = Infinity;
  331. let closestIntersection = null;
  332. let closestPoint = null;
  333. let density
  334. let sizeType
  335. if(params.isMeasuring){
  336. density = Potree.settings.pointDensity
  337. Potree.settings.pointDensity = 'magnifier'
  338. pointclouds.forEach(e=>{//因为全景模式的pointSizeType是fixed所以要还原下
  339. sizeType = e.material.pointSizeType
  340. e.material.pointSizeType = Potree.config.material.pointSizeType
  341. })
  342. Potree.updatePointClouds(pointclouds, camera, viewport.resolution );
  343. }
  344. for(let pointcloud of pointclouds){
  345. let point = pointcloud.pick(viewer, camera, ray, pickParams);
  346. viewport.afterRender
  347. if(!point){
  348. continue;
  349. }
  350. let distance = camera.position.distanceTo(point.position);
  351. if (distance < closestDistance) {
  352. closestDistance = distance;
  353. selectedPointcloud = pointcloud;
  354. closestIntersection = point.position;
  355. closestPoint = point;
  356. }
  357. }
  358. if(params.isMeasuring){
  359. Potree.settings.pointDensity = density
  360. pointclouds.forEach(e=>{
  361. e.material.pointSizeType = sizeType
  362. })
  363. }
  364. if (selectedPointcloud) {
  365. return {
  366. location: closestIntersection,
  367. distance: closestDistance,
  368. pointcloud: selectedPointcloud,
  369. point: closestPoint
  370. };
  371. } else {
  372. return null;
  373. }
  374. }
  375. static renderTargetToDataUrl(renderTarget, width, height, renderer, compressRatio = 0.7){
  376. let pixelCount = width * height;
  377. let buffer = new Uint8Array(4 * pixelCount);
  378. renderer.readRenderTargetPixels(renderTarget, 0, 0, width, height, buffer);
  379. var dataUrl = Potree.Utils.pixelsArrayToDataUrl(buffer, width, height, compressRatio)
  380. return dataUrl
  381. }
  382. static pixelsArrayToDataUrl(pixels, width, height, compressRatio = 0.7) {
  383. let canvas = document.createElement('canvas');
  384. canvas.width = width;
  385. canvas.height = height;
  386. let context = canvas.getContext('2d');
  387. pixels = new pixels.constructor(pixels);
  388. /* for (let i = 0; i < pixels.length; i++) {
  389. pixels[i * 4 + 3] = 255;
  390. } */
  391. // flip vertically
  392. let bytesPerLine = width * 4;
  393. for(let i = 0; i < parseInt(height / 2); i++){
  394. let j = height - i - 1;
  395. let lineI = pixels.slice(i * bytesPerLine, i * bytesPerLine + bytesPerLine);
  396. let lineJ = pixels.slice(j * bytesPerLine, j * bytesPerLine + bytesPerLine);
  397. pixels.set(lineJ, i * bytesPerLine);
  398. pixels.set(lineI, j * bytesPerLine);
  399. }
  400. let imageData = context.createImageData(width, height);
  401. imageData.data.set(pixels);
  402. context.putImageData(imageData, 0, 0);
  403. let dataURL = canvas.toDataURL(compressRatio);
  404. return dataURL;
  405. }
  406. static removeListeners(dispatcher, type){
  407. if (dispatcher._listeners === undefined) {
  408. return;
  409. }
  410. if (dispatcher._listeners[ type ]) {
  411. delete dispatcher._listeners[ type ];
  412. }
  413. }
  414. /* static mouseToRay(mouse, camera, width, height){
  415. let normalizedMouse = {
  416. x: (mouse.x / width) * 2 - 1,
  417. y: -(mouse.y / height) * 2 + 1
  418. };
  419. let vector = new THREE.Vector3(normalizedMouse.x, normalizedMouse.y, 0.5);
  420. let origin = camera.position.clone();
  421. vector.unproject(camera);
  422. let direction = new THREE.Vector3().subVectors(vector, origin).normalize();
  423. let ray = new THREE.Ray(origin, direction);
  424. return ray;
  425. } */
  426. static mouseToRay(pointer, camera ){
  427. let vector = new THREE.Vector3(pointer.x, pointer.y, 1);
  428. let origin = new THREE.Vector3(pointer.x, pointer.y, -1); //不能用camera.position,在orbitCamera时不准
  429. vector.unproject(camera);
  430. origin.unproject(camera);
  431. let direction = new THREE.Vector3().subVectors(vector, origin).normalize();
  432. let ray = new THREE.Ray(origin, direction);
  433. return ray;
  434. }
  435. static getPos2d(point, camera, dom, viewport){//获取一个三维坐标对应屏幕中的二维坐标
  436. var pos = point.clone().project(camera) //比之前hotspot的计算方式写得简单 project用于3转2(求法同shader); unproject用于2转3 :new r.Vector3(e.x, e.y, -1).unproject(this.camera);
  437. var x,y,left,top;
  438. x = (pos.x + 1) / 2 * dom.clientWidth * viewport.width;
  439. y = (1 - (pos.y + 1) / 2) * dom.clientHeight * viewport.height;
  440. left = viewport.left * dom.clientWidth;
  441. top = (1- viewport.bottom - viewport.height) * dom.clientHeight;
  442. var inSight = pos.x <= 1 && pos.x >= -1 //是否在屏幕中
  443. && pos.x <= 1 && pos.y >= -1
  444. return {
  445. pos: new THREE.Vector2(left+x,top+y) ,// 屏幕像素坐标
  446. vector: pos, //(范围 -1 ~ 1)
  447. trueSide : pos.z<1, //trueSide为false时,即使在屏幕范围内可见,也是反方向的另一个不可以被渲染的点 参见Tag.update
  448. inSight : inSight, //在屏幕范围内可见,
  449. posInViewport: new THREE.Vector2(x,y)
  450. };
  451. }
  452. static projectedRadius(radius, camera, distance, screenWidth, screenHeight){
  453. if(camera instanceof THREE.OrthographicCamera){
  454. return Utils.projectedRadiusOrtho(radius, camera.projectionMatrix, screenWidth, screenHeight);
  455. }else if(camera instanceof THREE.PerspectiveCamera){
  456. return Utils.projectedRadiusPerspective(radius, camera.fov * Math.PI / 180, distance, screenHeight);
  457. }else{
  458. throw new Error("invalid parameters");
  459. }
  460. }
  461. static projectedRadiusPerspective(radius, fov, distance, screenHeight) {
  462. let projFactor = (1 / Math.tan(fov / 2)) / distance;
  463. projFactor = projFactor * screenHeight / 2;
  464. return radius * projFactor;
  465. }
  466. static projectedRadiusOrtho(radius, proj, screenWidth, screenHeight) {
  467. let p1 = new THREE.Vector4(0);
  468. let p2 = new THREE.Vector4(radius);
  469. p1.applyMatrix4(proj);
  470. p2.applyMatrix4(proj);
  471. p1 = new THREE.Vector3(p1.x, p1.y, p1.z);
  472. p2 = new THREE.Vector3(p2.x, p2.y, p2.z);
  473. p1.x = (p1.x + 1.0) * 0.5 * screenWidth;
  474. p1.y = (p1.y + 1.0) * 0.5 * screenHeight;
  475. p2.x = (p2.x + 1.0) * 0.5 * screenWidth;
  476. p2.y = (p2.y + 1.0) * 0.5 * screenHeight;
  477. return p1.distanceTo(p2);
  478. }
  479. static topView(camera, node){
  480. camera.position.set(0, 1, 0);
  481. camera.rotation.set(-Math.PI / 2, 0, 0);
  482. camera.zoomTo(node, 1);
  483. }
  484. static frontView (camera, node) {
  485. camera.position.set(0, 0, 1);
  486. camera.rotation.set(0, 0, 0);
  487. camera.zoomTo(node, 1);
  488. }
  489. static leftView (camera, node) {
  490. camera.position.set(-1, 0, 0);
  491. camera.rotation.set(0, -Math.PI / 2, 0);
  492. camera.zoomTo(node, 1);
  493. }
  494. static rightView (camera, node) {
  495. camera.position.set(1, 0, 0);
  496. camera.rotation.set(0, Math.PI / 2, 0);
  497. camera.zoomTo(node, 1);
  498. }
  499. static findClosestGpsTime(target, viewer){
  500. const start = performance.now();
  501. const nodes = [];
  502. for(const pc of viewer.scene.pointclouds){
  503. nodes.push(pc.root);
  504. for(const child of pc.root.children){
  505. if(child){
  506. nodes.push(child);
  507. }
  508. }
  509. }
  510. let closestNode = null;
  511. let closestIndex = Infinity;
  512. let closestDistance = Infinity;
  513. let closestValue = 0;
  514. for(const node of nodes){
  515. const isOkay = node.geometryNode != null
  516. && node.geometryNode.geometry != null
  517. && node.sceneNode != null;
  518. if(!isOkay){
  519. continue;
  520. }
  521. let geometry = node.geometryNode.geometry;
  522. let gpsTime = geometry.attributes["gps-time"];
  523. let range = gpsTime.potree.range;
  524. for(let i = 0; i < gpsTime.array.length; i++){
  525. let value = gpsTime.array[i];
  526. value = value * (range[1] - range[0]) + range[0];
  527. const distance = Math.abs(target - value);
  528. if(distance < closestDistance){
  529. closestIndex = i;
  530. closestDistance = distance;
  531. closestValue = value;
  532. closestNode = node;
  533. //console.log("found a closer one: " + value);
  534. }
  535. }
  536. }
  537. const geometry = closestNode.geometryNode.geometry;
  538. const position = new THREE.Vector3(
  539. geometry.attributes.position.array[3 * closestIndex + 0],
  540. geometry.attributes.position.array[3 * closestIndex + 1],
  541. geometry.attributes.position.array[3 * closestIndex + 2],
  542. );
  543. position.applyMatrix4(closestNode.sceneNode.matrixWorld);
  544. const end = performance.now();
  545. const duration = (end - start);
  546. console.log(`duration: ${duration.toFixed(3)}ms`);
  547. return {
  548. node: closestNode,
  549. index: closestIndex,
  550. position: position,
  551. };
  552. }
  553. /**
  554. *
  555. * 0: no intersection
  556. * 1: intersection
  557. * 2: fully inside
  558. */
  559. static frustumSphereIntersection (frustum, sphere) {
  560. let planes = frustum.planes;
  561. let center = sphere.center;
  562. let negRadius = -sphere.radius;
  563. let minDistance = Number.MAX_VALUE;
  564. for (let i = 0; i < 6; i++) {
  565. let distance = planes[ i ].distanceToPoint(center);
  566. if (distance < negRadius) {
  567. return 0;
  568. }
  569. minDistance = Math.min(minDistance, distance);
  570. }
  571. return (minDistance >= sphere.radius) ? 2 : 1;
  572. }
  573. // code taken from three.js
  574. // ImageUtils - generateDataTexture()
  575. static generateDataTexture (width, height, color) {
  576. let size = width * height;
  577. let data = new Uint8Array(4 * width * height);
  578. let r = Math.floor(color.r * 255);
  579. let g = Math.floor(color.g * 255);
  580. let b = Math.floor(color.b * 255);
  581. for (let i = 0; i < size; i++) {
  582. data[ i * 3 ] = r;
  583. data[ i * 3 + 1 ] = g;
  584. data[ i * 3 + 2 ] = b;
  585. }
  586. let texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
  587. texture.needsUpdate = true;
  588. texture.magFilter = THREE.NearestFilter;
  589. return texture;
  590. }
  591. // from http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
  592. static getParameterByName (name) {
  593. name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
  594. let regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
  595. let results = regex.exec(document.location.search);
  596. return results === null ? null : decodeURIComponent(results[1].replace(/\+/g, ' '));
  597. }
  598. static setParameter (name, value) {
  599. // value = encodeURIComponent(value);
  600. name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
  601. let regex = new RegExp('([\\?&])(' + name + '=([^&#]*))');
  602. let results = regex.exec(document.location.search);
  603. let url = window.location.href;
  604. if (results === null) {
  605. if (window.location.search.length === 0) {
  606. url = url + '?';
  607. } else {
  608. url = url + '&';
  609. }
  610. url = url + name + '=' + value;
  611. } else {
  612. let newValue = name + '=' + value;
  613. url = url.replace(results[2], newValue);
  614. }
  615. window.history.replaceState({}, '', url);
  616. }
  617. static createChildAABB(aabb, index){
  618. let min = aabb.min.clone();
  619. let max = aabb.max.clone();
  620. let size = new THREE.Vector3().subVectors(max, min);
  621. if ((index & 0b0001) > 0) {
  622. min.z += size.z / 2;
  623. } else {
  624. max.z -= size.z / 2;
  625. }
  626. if ((index & 0b0010) > 0) {
  627. min.y += size.y / 2;
  628. } else {
  629. max.y -= size.y / 2;
  630. }
  631. if ((index & 0b0100) > 0) {
  632. min.x += size.x / 2;
  633. } else {
  634. max.x -= size.x / 2;
  635. }
  636. return new THREE.Box3(min, max);
  637. }
  638. // see https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript
  639. static clipboardCopy(text){
  640. let textArea = document.createElement("textarea");
  641. textArea.style.position = 'fixed';
  642. textArea.style.top = 0;
  643. textArea.style.left = 0;
  644. textArea.style.width = '2em';
  645. textArea.style.height = '2em';
  646. textArea.style.padding = 0;
  647. textArea.style.border = 'none';
  648. textArea.style.outline = 'none';
  649. textArea.style.boxShadow = 'none';
  650. textArea.style.background = 'transparent';
  651. textArea.value = text;
  652. document.body.appendChild(textArea);
  653. textArea.select();
  654. try {
  655. let success = document.execCommand('copy');
  656. if(success){
  657. console.log("copied text to clipboard");
  658. }else{
  659. console.log("copy to clipboard failed");
  660. }
  661. } catch (err) {
  662. console.log("error while trying to copy to clipboard");
  663. }
  664. document.body.removeChild(textArea);
  665. }
  666. static getMeasurementIcon(measurement){
  667. if (measurement instanceof Measure) {
  668. if (measurement.showDistances && !measurement.showArea && !measurement.showAngles) {
  669. return `${Potree.resourcePath}/icons/distance.svg`;
  670. } else if (measurement.showDistances && measurement.showArea && !measurement.showAngles) {
  671. return `${Potree.resourcePath}/icons/area.svg`;
  672. } else if (measurement.maxMarkers === 1) {
  673. return `${Potree.resourcePath}/icons/point.svg`;
  674. } else if (!measurement.showDistances && !measurement.showArea && measurement.showAngles) {
  675. return `${Potree.resourcePath}/icons/angle.png`;
  676. } else if (measurement.showHeight) {
  677. return `${Potree.resourcePath}/icons/height.svg`;
  678. } else {
  679. return `${Potree.resourcePath}/icons/distance.svg`;
  680. }
  681. } else if (measurement instanceof Profile) {
  682. return `${Potree.resourcePath}/icons/profile.svg`;
  683. } else if (measurement instanceof Volume) {
  684. return `${Potree.resourcePath}/icons/volume.svg`;
  685. } else if (measurement instanceof PolygonClipVolume) {
  686. return `${Potree.resourcePath}/icons/clip-polygon.svg`;
  687. }
  688. }
  689. static lineToLineIntersection(P0, P1, P2, P3){
  690. const P = [P0, P1, P2, P3];
  691. const d = (m, n, o, p) => {
  692. let result =
  693. (P[m].x - P[n].x) * (P[o].x - P[p].x)
  694. + (P[m].y - P[n].y) * (P[o].y - P[p].y)
  695. + (P[m].z - P[n].z) * (P[o].z - P[p].z);
  696. return result;
  697. };
  698. const mua = (d(0, 2, 3, 2) * d(3, 2, 1, 0) - d(0, 2, 1, 0) * d(3, 2, 3, 2))
  699. /**-----------------------------------------------------------------**/ /
  700. (d(1, 0, 1, 0) * d(3, 2, 3, 2) - d(3, 2, 1, 0) * d(3, 2, 1, 0));
  701. const mub = (d(0, 2, 3, 2) + mua * d(3, 2, 1, 0))
  702. /**--------------------------------------**/ /
  703. d(3, 2, 3, 2);
  704. const P01 = P1.clone().sub(P0);
  705. const P23 = P3.clone().sub(P2);
  706. const Pa = P0.clone().add(P01.multiplyScalar(mua));
  707. const Pb = P2.clone().add(P23.multiplyScalar(mub));
  708. const center = Pa.clone().add(Pb).multiplyScalar(0.5);
  709. return center;
  710. }
  711. static computeCircleCenter(A, B, C){
  712. const AB = B.clone().sub(A);
  713. const AC = C.clone().sub(A);
  714. const N = AC.clone().cross(AB).normalize();
  715. const ab_dir = AB.clone().cross(N).normalize();
  716. const ac_dir = AC.clone().cross(N).normalize();
  717. const ab_origin = A.clone().add(B).multiplyScalar(0.5);
  718. const ac_origin = A.clone().add(C).multiplyScalar(0.5);
  719. const P0 = ab_origin;
  720. const P1 = ab_origin.clone().add(ab_dir);
  721. const P2 = ac_origin;
  722. const P3 = ac_origin.clone().add(ac_dir);
  723. const center = Utils.lineToLineIntersection(P0, P1, P2, P3);
  724. return center;
  725. // Potree.Utils.debugLine(viewer.scene.scene, P0, P1, 0x00ff00);
  726. // Potree.Utils.debugLine(viewer.scene.scene, P2, P3, 0x0000ff);
  727. // Potree.Utils.debugSphere(viewer.scene.scene, center, 0.03, 0xff00ff);
  728. // const radius = center.distanceTo(A);
  729. // Potree.Utils.debugCircle(viewer.scene.scene, center, radius, new THREE.Vector3(0, 0, 1), 0xff00ff);
  730. }
  731. static getNorthVec(p1, distance, projection){
  732. if(projection){
  733. // if there is a projection, transform coordinates to WGS84
  734. // and compute angle to north there
  735. proj4.defs("pointcloud", projection);
  736. const transform = proj4("pointcloud", "WGS84");
  737. const llP1 = transform.forward(p1.toArray());
  738. let llP2 = transform.forward([p1.x, p1.y + distance]);
  739. const polarRadius = Math.sqrt((llP2[0] - llP1[0]) ** 2 + (llP2[1] - llP1[1]) ** 2);
  740. llP2 = [llP1[0], llP1[1] + polarRadius];
  741. const northVec = transform.inverse(llP2);
  742. return new THREE.Vector3(...northVec, p1.z).sub(p1);
  743. }else{
  744. // if there is no projection, assume [0, 1, 0] as north direction
  745. const vec = new THREE.Vector3(0, 1, 0).multiplyScalar(distance);
  746. return vec;
  747. }
  748. }
  749. static computeAzimuth(p1, p2, projection){
  750. let azimuth = 0;
  751. if(projection){
  752. // if there is a projection, transform coordinates to WGS84
  753. // and compute angle to north there
  754. let transform;
  755. if (projection.includes('EPSG')) {
  756. transform = proj4(projection, "WGS84");
  757. } else {
  758. proj4.defs("pointcloud", projection);
  759. transform = proj4("pointcloud", "WGS84");
  760. }
  761. const llP1 = transform.forward(p1.toArray());
  762. const llP2 = transform.forward(p2.toArray());
  763. const dir = [
  764. llP2[0] - llP1[0],
  765. llP2[1] - llP1[1],
  766. ];
  767. azimuth = Math.atan2(dir[1], dir[0]) - Math.PI / 2;
  768. }else{
  769. // if there is no projection, assume [0, 1, 0] as north direction
  770. const dir = [p2.x - p1.x, p2.y - p1.y];
  771. azimuth = Math.atan2(dir[1], dir[0]) - Math.PI / 2;
  772. }
  773. // make clockwise
  774. azimuth = -azimuth;
  775. return azimuth;
  776. }
  777. static async loadScript(url){
  778. return new Promise( resolve => {
  779. const element = document.getElementById(url);
  780. if(element){
  781. resolve();
  782. }else{
  783. const script = document.createElement("script");
  784. script.id = url;
  785. script.onload = () => {
  786. resolve();
  787. };
  788. script.src = url;
  789. document.body.appendChild(script);
  790. }
  791. });
  792. }
  793. static createSvgGradient(scheme){
  794. // this is what we are creating:
  795. //
  796. //<svg width="1em" height="3em" xmlns="http://www.w3.org/2000/svg">
  797. // <defs>
  798. // <linearGradient id="gradientID" gradientTransform="rotate(90)">
  799. // <stop offset="0%" stop-color="rgb(93, 78, 162)" />
  800. // ...
  801. // <stop offset="100%" stop-color="rgb(157, 0, 65)" />
  802. // </linearGradient>
  803. // </defs>
  804. //
  805. // <rect width="100%" height="100%" fill="url('#myGradient')" stroke="black" stroke-width="0.1em"/>
  806. //</svg>
  807. const gradientId = `${Math.random()}_${Date.now()}`;
  808. const svgn = "http://www.w3.org/2000/svg";
  809. const svg = document.createElementNS(svgn, "svg");
  810. svg.setAttributeNS(null, "width", "2em");
  811. svg.setAttributeNS(null, "height", "3em");
  812. { // <defs>
  813. const defs = document.createElementNS(svgn, "defs");
  814. const linearGradient = document.createElementNS(svgn, "linearGradient");
  815. linearGradient.setAttributeNS(null, "id", gradientId);
  816. linearGradient.setAttributeNS(null, "gradientTransform", "rotate(90)");
  817. for(let i = scheme.length - 1; i >= 0; i--){
  818. const stopVal = scheme[i];
  819. const percent = parseInt(100 - stopVal[0] * 100);
  820. const [r, g, b] = stopVal[1].toArray().map(v => parseInt(v * 255));
  821. const stop = document.createElementNS(svgn, "stop");
  822. stop.setAttributeNS(null, "offset", `${percent}%`);
  823. stop.setAttributeNS(null, "stop-color", `rgb(${r}, ${g}, ${b})`);
  824. linearGradient.appendChild(stop);
  825. }
  826. defs.appendChild(linearGradient);
  827. svg.appendChild(defs);
  828. }
  829. const rect = document.createElementNS(svgn, "rect");
  830. rect.setAttributeNS(null, "width", `100%`);
  831. rect.setAttributeNS(null, "height", `100%`);
  832. rect.setAttributeNS(null, "fill", `url("#${gradientId}")`);
  833. rect.setAttributeNS(null, "stroke", `black`);
  834. rect.setAttributeNS(null, "stroke-width", `0.1em`);
  835. svg.appendChild(rect);
  836. return svg;
  837. }
  838. static async waitAny(promises){
  839. return new Promise( (resolve) => {
  840. promises.map( promise => {
  841. promise.then( () => {
  842. resolve();
  843. });
  844. });
  845. });
  846. }
  847. }
  848. Utils.screenPass = new function () {
  849. this.screenScene = new THREE.Scene();
  850. this.screenQuad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2, 1));
  851. this.screenQuad.material.depthTest = true;
  852. this.screenQuad.material.depthWrite = true;
  853. this.screenQuad.material.transparent = true;
  854. this.screenScene.add(this.screenQuad);
  855. this.camera = new THREE.Camera();
  856. this.render = function (renderer, material, target) {
  857. this.screenQuad.material = material;
  858. if (typeof target === 'undefined') {
  859. renderer.render(this.screenScene, this.camera);
  860. } else {
  861. renderer.setRenderTarget(target)
  862. renderer.render(this.screenScene, this.camera);
  863. }
  864. };
  865. }();
  866. //add
  867. Utils.computePointcloudsBound = function(pointclouds){
  868. var boundingBox = new THREE.Box3();
  869. pointclouds.forEach(pointcloud=>{
  870. var boundingBox_ = pointcloud.pcoGeometry.tightBoundingBox.clone().applyMatrix4(pointcloud.matrixWorld)
  871. pointcloud.bound = boundingBox_
  872. boundingBox.union(boundingBox_)
  873. })
  874. var boundSize = boundingBox.getSize(new THREE.Vector3)
  875. var center = boundingBox.getCenter(new THREE.Vector3)
  876. return {boundSize, center, boundingBox}
  877. }
  878. Utils.convertScreenPositionToNDC = function(pointer, mouse, width, height) {
  879. return pointer = pointer || new THREE.Vector2,
  880. pointer.x = mouse.x / width * 2 - 1,
  881. pointer.y = 2 * -(mouse.y / height) + 1,
  882. pointer
  883. }
  884. Utils.getOrthoCameraMoveVec = function(pointerDelta, camera ){//获取当camera为Ortho型时 屏幕点1 到 屏幕点2 的三维距离
  885. let cameraViewWidth = camera.right / camera.zoom
  886. let cameraViewHeight = camera.top / camera.zoom
  887. let moveVec = new THREE.Vector3;
  888. moveVec.set( pointerDelta.x * cameraViewWidth , pointerDelta.y * cameraViewHeight , 0).applyQuaternion(camera.quaternion)
  889. return moveVec
  890. }
  891. Utils.VectorFactory = {
  892. fromArray : function(t) {
  893. if (t) {
  894. if (t.length < 2 || t.length > 3)
  895. console.error("Wrong number of ordinates for a point!");
  896. return 3 === t.length ? (new THREE.Vector3).fromArray(t) : (new THREE.Vector2).fromArray(t)
  897. }
  898. },
  899. fromArray3 : function(t) {
  900. if (t) {
  901. if (3 !== t.length)
  902. console.error("Wrong number of ordinates for a point!");
  903. return (new THREE.Vector3).fromArray(t)
  904. }
  905. },
  906. fromArray2 : function(t) {
  907. if (t) {
  908. if (2 !== t.length)
  909. console.error("Wrong number of ordinates for a point!");
  910. return (new THREE.Vector2).fromArray(t)
  911. }
  912. },
  913. toString : function(t) {
  914. return t.x.toFixed(8) + "," + t.y.toFixed(8) + "," + t.z.toFixed(3)
  915. }
  916. }
  917. Utils.QuaternionFactory = {
  918. rot90 : (new THREE.Quaternion).setFromAxisAngle(new THREE.Vector3(0,0,1), THREE.Math.degToRad(-90)),
  919. fromArray : function(t) {
  920. if (t) {
  921. if (4 !== t.length)
  922. console.error("Wrong number of ordinates for a quaternion!");
  923. return new THREE.Quaternion(t[1],t[2],t[3],t[0]).multiply(this.rot90)
  924. }
  925. }
  926. ,
  927. toArray : function(t) {
  928. if (t) {
  929. var e = t.clone().multiply(a).toArray();
  930. return [e[3], e[0], e[1], e[2]]
  931. }
  932. }
  933. ,
  934. fromLonLat : function(t) {
  935. if (t)
  936. return (new THREE.Quaternion).setFromEuler(new THREE.Euler(t.lon,t.lat,0))
  937. }
  938. ,
  939. toLonLat : function(t) {
  940. if (t) {
  941. var e = (new THREE.Euler).setFromQuaternion(t);
  942. return {
  943. lon: e.x,
  944. lat: e.y
  945. }
  946. }
  947. }
  948. }
  949. Utils.datasetPosTransform = function(o={}){
  950. let pointcloud = o.pointcloud || viewer.scene.pointclouds.find(e=>e.dataset_id == o.datasetId)
  951. if(pointcloud){
  952. let tranMatrix = o.fromDataset ? pointcloud.transformMatrix : pointcloud.transformInvMatrix
  953. return (new THREE.Vector3).copy(o.position).applyMatrix4(tranMatrix)
  954. }else{
  955. if(o.datasetId != void 0){
  956. console.error(`datasetPosTransform找不到datasetId为${o.datasetId}的数据集,请检查(热点?测量线?)数据`)
  957. //很可能是旧的热点,需要删除
  958. }
  959. }
  960. }
  961. Utils.datasetRotTransform = function(o={}){
  962. let pointcloud = o.pointcloud || viewer.scene.pointclouds.find(e=>e.dataset_id == o.datasetId)
  963. if(pointcloud){
  964. var matrix, newMatrix, result
  965. if(o.rotation){
  966. matrix = new THREE.Matrix4().makeRotationFromEuler(o.rotation)
  967. }else if(o.quaternion){
  968. matrix = new THREE.Matrix4().makeRotationFromQuaternion(o.quaternion)
  969. }else if(o.matrix){
  970. matrix = o.matrix.clone()
  971. }else{
  972. return
  973. }
  974. let rotateMatrix = o.fromDataset ? pointcloud.rotateMatrix : pointcloud.rotateInvMatrix
  975. newMatrix = new THREE.Matrix4().multiplyMatrices(rotateMatrix, matrix )
  976. if(o.getRotation){
  977. result = new THREE.Euler().setFromRotationMatrix(newMatrix)
  978. }else if(o.getQuaternion){
  979. result = new THREE.Quaternion().setFromRotationMatrix(newMatrix)
  980. }else if(o.getMatrix){
  981. result = newMatrix
  982. }
  983. return result
  984. }
  985. }