utils.js 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099
  1. import * as THREE from "../libs/three.js/build/three.module.js";
  2. import {XHRFactory} from "./XHRFactory.js";
  3. import {Volume} from "./utils/Volume.js";
  4. import {Profile} from "./utils/Profile.js";
  5. import {Measure} from "./utils/Measure.js";
  6. import {PolygonClipVolume} from "./utils/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. };//感觉就是bound.applyMatrix4(transform)
  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. 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 (mouse, camera, viewer, pointclouds, params = {}) {
  313. let renderer = viewer.renderer;
  314. let nmouse = {
  315. x: (mouse.x / renderer.domElement.clientWidth) * 2 - 1,
  316. y: -(mouse.y / renderer.domElement.clientHeight) * 2 + 1
  317. };
  318. let pickParams = {};
  319. if(params.pickClipped){
  320. pickParams.pickClipped = params.pickClipped;
  321. }
  322. pickParams.x = mouse.x;
  323. pickParams.y = renderer.domElement.clientHeight - mouse.y;
  324. let raycaster = new THREE.Raycaster();
  325. raycaster.setFromCamera(nmouse, camera);
  326. let ray = raycaster.ray;
  327. let selectedPointcloud = null;
  328. let closestDistance = Infinity;
  329. let closestIntersection = null;
  330. let closestPoint = null;
  331. for(let pointcloud of pointclouds){
  332. let point = pointcloud.pick(viewer, camera, ray, pickParams);
  333. if(!point){
  334. continue;
  335. }
  336. let distance = camera.position.distanceTo(point.position);
  337. if (distance < closestDistance) {
  338. closestDistance = distance;
  339. selectedPointcloud = pointcloud;
  340. closestIntersection = point.position;
  341. closestPoint = point;
  342. }
  343. }
  344. if (selectedPointcloud) {
  345. return {
  346. location: closestIntersection,
  347. distance: closestDistance,
  348. pointcloud: selectedPointcloud,
  349. point: closestPoint
  350. };
  351. } else {
  352. return null;
  353. }
  354. }
  355. static pixelsArrayToImage (pixels, width, height) {
  356. let canvas = document.createElement('canvas');
  357. canvas.width = width;
  358. canvas.height = height;
  359. let context = canvas.getContext('2d');
  360. pixels = new pixels.constructor(pixels);
  361. for (let i = 0; i < pixels.length; i++) {
  362. pixels[i * 4 + 3] = 255;
  363. }
  364. let imageData = context.createImageData(width, height);
  365. imageData.data.set(pixels);
  366. context.putImageData(imageData, 0, 0);
  367. let img = new Image();
  368. img.src = canvas.toDataURL();
  369. // img.style.transform = "scaleY(-1)";
  370. return img;
  371. }
  372. static pixelsArrayToDataUrl(pixels, width, height) {
  373. let canvas = document.createElement('canvas');
  374. canvas.width = width;
  375. canvas.height = height;
  376. let context = canvas.getContext('2d');
  377. pixels = new pixels.constructor(pixels);
  378. for (let i = 0; i < pixels.length; i++) {
  379. pixels[i * 4 + 3] = 255;
  380. }
  381. let imageData = context.createImageData(width, height);
  382. imageData.data.set(pixels);
  383. context.putImageData(imageData, 0, 0);
  384. let dataURL = canvas.toDataURL();
  385. return dataURL;
  386. }
  387. static pixelsArrayToCanvas(pixels, width, height){
  388. let canvas = document.createElement('canvas');
  389. canvas.width = width;
  390. canvas.height = height;
  391. let context = canvas.getContext('2d');
  392. pixels = new pixels.constructor(pixels);
  393. //for (let i = 0; i < pixels.length; i++) {
  394. // pixels[i * 4 + 3] = 255;
  395. //}
  396. // flip vertically
  397. let bytesPerLine = width * 4;
  398. for(let i = 0; i < parseInt(height / 2); i++){
  399. let j = height - i - 1;
  400. let lineI = pixels.slice(i * bytesPerLine, i * bytesPerLine + bytesPerLine);
  401. let lineJ = pixels.slice(j * bytesPerLine, j * bytesPerLine + bytesPerLine);
  402. pixels.set(lineJ, i * bytesPerLine);
  403. pixels.set(lineI, j * bytesPerLine);
  404. }
  405. let imageData = context.createImageData(width, height);
  406. imageData.data.set(pixels);
  407. context.putImageData(imageData, 0, 0);
  408. return canvas;
  409. }
  410. static removeListeners(dispatcher, type){
  411. if (dispatcher._listeners === undefined) {
  412. return;
  413. }
  414. if (dispatcher._listeners[ type ]) {
  415. delete dispatcher._listeners[ type ];
  416. }
  417. }
  418. static mouseToRay(mouse, camera, width, height){
  419. let normalizedMouse = {
  420. x: (mouse.x / width) * 2 - 1,
  421. y: -(mouse.y / height) * 2 + 1
  422. };
  423. let vector = new THREE.Vector3(normalizedMouse.x, normalizedMouse.y, 0.5);
  424. let origin = camera.position.clone();
  425. vector.unproject(camera);
  426. let direction = new THREE.Vector3().subVectors(vector, origin).normalize();
  427. let ray = new THREE.Ray(origin, direction);
  428. return ray;
  429. }
  430. static projectedRadius(radius, camera, distance, screenWidth, screenHeight){
  431. if(camera instanceof THREE.OrthographicCamera){
  432. return Utils.projectedRadiusOrtho(radius, camera.projectionMatrix, screenWidth, screenHeight);
  433. }else if(camera instanceof THREE.PerspectiveCamera){
  434. return Utils.projectedRadiusPerspective(radius, camera.fov * Math.PI / 180, distance, screenHeight);
  435. }else{
  436. throw new Error("invalid parameters");
  437. }
  438. }
  439. static projectedRadiusPerspective(radius, fov, distance, screenHeight) {
  440. let projFactor = (1 / Math.tan(fov / 2)) / distance;
  441. projFactor = projFactor * screenHeight / 2;
  442. return radius * projFactor;
  443. }
  444. static projectedRadiusOrtho(radius, proj, screenWidth, screenHeight) {
  445. let p1 = new THREE.Vector4(0);
  446. let p2 = new THREE.Vector4(radius);
  447. p1.applyMatrix4(proj);
  448. p2.applyMatrix4(proj);
  449. p1 = new THREE.Vector3(p1.x, p1.y, p1.z);
  450. p2 = new THREE.Vector3(p2.x, p2.y, p2.z);
  451. p1.x = (p1.x + 1.0) * 0.5 * screenWidth;
  452. p1.y = (p1.y + 1.0) * 0.5 * screenHeight;
  453. p2.x = (p2.x + 1.0) * 0.5 * screenWidth;
  454. p2.y = (p2.y + 1.0) * 0.5 * screenHeight;
  455. return p1.distanceTo(p2);
  456. }
  457. static topView(camera, node){
  458. camera.position.set(0, 1, 0);
  459. camera.rotation.set(-Math.PI / 2, 0, 0);
  460. camera.zoomTo(node, 1);
  461. }
  462. static frontView (camera, node) {
  463. camera.position.set(0, 0, 1);
  464. camera.rotation.set(0, 0, 0);
  465. camera.zoomTo(node, 1);
  466. }
  467. static leftView (camera, node) {
  468. camera.position.set(-1, 0, 0);
  469. camera.rotation.set(0, -Math.PI / 2, 0);
  470. camera.zoomTo(node, 1);
  471. }
  472. static rightView (camera, node) {
  473. camera.position.set(1, 0, 0);
  474. camera.rotation.set(0, Math.PI / 2, 0);
  475. camera.zoomTo(node, 1);
  476. }
  477. static findClosestGpsTime(target, viewer){
  478. const start = performance.now();
  479. const nodes = [];
  480. for(const pc of viewer.scene.pointclouds){
  481. nodes.push(pc.root);
  482. for(const child of pc.root.children){
  483. if(child){
  484. nodes.push(child);
  485. }
  486. }
  487. }
  488. let closestNode = null;
  489. let closestIndex = Infinity;
  490. let closestDistance = Infinity;
  491. let closestValue = 0;
  492. for(const node of nodes){
  493. const isOkay = node.geometryNode != null
  494. && node.geometryNode.geometry != null
  495. && node.sceneNode != null;
  496. if(!isOkay){
  497. continue;
  498. }
  499. let geometry = node.geometryNode.geometry;
  500. let gpsTime = geometry.attributes["gps-time"];
  501. let range = gpsTime.potree.range;
  502. for(let i = 0; i < gpsTime.array.length; i++){
  503. let value = gpsTime.array[i];
  504. value = value * (range[1] - range[0]) + range[0];
  505. const distance = Math.abs(target - value);
  506. if(distance < closestDistance){
  507. closestIndex = i;
  508. closestDistance = distance;
  509. closestValue = value;
  510. closestNode = node;
  511. //console.log("found a closer one: " + value);
  512. }
  513. }
  514. }
  515. const geometry = closestNode.geometryNode.geometry;
  516. const position = new THREE.Vector3(
  517. geometry.attributes.position.array[3 * closestIndex + 0],
  518. geometry.attributes.position.array[3 * closestIndex + 1],
  519. geometry.attributes.position.array[3 * closestIndex + 2],
  520. );
  521. position.applyMatrix4(closestNode.sceneNode.matrixWorld);
  522. const end = performance.now();
  523. const duration = (end - start);
  524. console.log(`duration: ${duration.toFixed(3)}ms`);
  525. return {
  526. node: closestNode,
  527. index: closestIndex,
  528. position: position,
  529. };
  530. }
  531. /**
  532. *
  533. * 0: no intersection
  534. * 1: intersection
  535. * 2: fully inside
  536. */
  537. static frustumSphereIntersection (frustum, sphere) {
  538. let planes = frustum.planes;
  539. let center = sphere.center;
  540. let negRadius = -sphere.radius;
  541. let minDistance = Number.MAX_VALUE;
  542. for (let i = 0; i < 6; i++) {
  543. let distance = planes[ i ].distanceToPoint(center);
  544. if (distance < negRadius) {
  545. return 0;
  546. }
  547. minDistance = Math.min(minDistance, distance);
  548. }
  549. return (minDistance >= sphere.radius) ? 2 : 1;
  550. }
  551. // code taken from three.js
  552. // ImageUtils - generateDataTexture()
  553. static generateDataTexture (width, height, color) {
  554. let size = width * height;
  555. let data = new Uint8Array(4 * width * height);
  556. let r = Math.floor(color.r * 255);
  557. let g = Math.floor(color.g * 255);
  558. let b = Math.floor(color.b * 255);
  559. for (let i = 0; i < size; i++) {
  560. data[ i * 3 ] = r;
  561. data[ i * 3 + 1 ] = g;
  562. data[ i * 3 + 2 ] = b;
  563. }
  564. let texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
  565. texture.needsUpdate = true;
  566. texture.magFilter = THREE.NearestFilter;
  567. return texture;
  568. }
  569. // from http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
  570. static getParameterByName (name) {
  571. name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
  572. let regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
  573. let results = regex.exec(document.location.search);
  574. return results === null ? null : decodeURIComponent(results[1].replace(/\+/g, ' '));
  575. }
  576. static setParameter (name, value) {
  577. // value = encodeURIComponent(value);
  578. name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
  579. let regex = new RegExp('([\\?&])(' + name + '=([^&#]*))');
  580. let results = regex.exec(document.location.search);
  581. let url = window.location.href;
  582. if (results === null) {
  583. if (window.location.search.length === 0) {
  584. url = url + '?';
  585. } else {
  586. url = url + '&';
  587. }
  588. url = url + name + '=' + value;
  589. } else {
  590. let newValue = name + '=' + value;
  591. url = url.replace(results[2], newValue);
  592. }
  593. window.history.replaceState({}, '', url);
  594. }
  595. static createChildAABB(aabb, index){
  596. let min = aabb.min.clone();
  597. let max = aabb.max.clone();
  598. let size = new THREE.Vector3().subVectors(max, min);
  599. if ((index & 0b0001) > 0) {
  600. min.z += size.z / 2;
  601. } else {
  602. max.z -= size.z / 2;
  603. }
  604. if ((index & 0b0010) > 0) {
  605. min.y += size.y / 2;
  606. } else {
  607. max.y -= size.y / 2;
  608. }
  609. if ((index & 0b0100) > 0) {
  610. min.x += size.x / 2;
  611. } else {
  612. max.x -= size.x / 2;
  613. }
  614. return new THREE.Box3(min, max);
  615. }
  616. // see https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript
  617. static clipboardCopy(text){
  618. let textArea = document.createElement("textarea");
  619. textArea.style.position = 'fixed';
  620. textArea.style.top = 0;
  621. textArea.style.left = 0;
  622. textArea.style.width = '2em';
  623. textArea.style.height = '2em';
  624. textArea.style.padding = 0;
  625. textArea.style.border = 'none';
  626. textArea.style.outline = 'none';
  627. textArea.style.boxShadow = 'none';
  628. textArea.style.background = 'transparent';
  629. textArea.value = text;
  630. document.body.appendChild(textArea);
  631. textArea.select();
  632. try {
  633. let success = document.execCommand('copy');
  634. if(success){
  635. console.log("copied text to clipboard");
  636. }else{
  637. console.log("copy to clipboard failed");
  638. }
  639. } catch (err) {
  640. console.log("error while trying to copy to clipboard");
  641. }
  642. document.body.removeChild(textArea);
  643. }
  644. static getMeasurementIcon(measurement){
  645. if (measurement instanceof Measure) {
  646. if (measurement.showDistances && !measurement.showArea && !measurement.showAngles) {
  647. return `${Potree.resourcePath}/icons/distance.svg`;
  648. } else if (measurement.showDistances && measurement.showArea && !measurement.showAngles) {
  649. return `${Potree.resourcePath}/icons/area.svg`;
  650. } else if (measurement.maxMarkers === 1) {
  651. return `${Potree.resourcePath}/icons/point.svg`;
  652. } else if (!measurement.showDistances && !measurement.showArea && measurement.showAngles) {
  653. return `${Potree.resourcePath}/icons/angle.png`;
  654. } else if (measurement.showHeight) {
  655. return `${Potree.resourcePath}/icons/height.svg`;
  656. } else {
  657. return `${Potree.resourcePath}/icons/distance.svg`;
  658. }
  659. } else if (measurement instanceof Profile) {
  660. return `${Potree.resourcePath}/icons/profile.svg`;
  661. } else if (measurement instanceof Volume) {
  662. return `${Potree.resourcePath}/icons/volume.svg`;
  663. } else if (measurement instanceof PolygonClipVolume) {
  664. return `${Potree.resourcePath}/icons/clip-polygon.svg`;
  665. }
  666. }
  667. static lineToLineIntersection(P0, P1, P2, P3){
  668. const P = [P0, P1, P2, P3];
  669. const d = (m, n, o, p) => {
  670. let result =
  671. (P[m].x - P[n].x) * (P[o].x - P[p].x)
  672. + (P[m].y - P[n].y) * (P[o].y - P[p].y)
  673. + (P[m].z - P[n].z) * (P[o].z - P[p].z);
  674. return result;
  675. };
  676. const mua = (d(0, 2, 3, 2) * d(3, 2, 1, 0) - d(0, 2, 1, 0) * d(3, 2, 3, 2))
  677. /**-----------------------------------------------------------------**/ /
  678. (d(1, 0, 1, 0) * d(3, 2, 3, 2) - d(3, 2, 1, 0) * d(3, 2, 1, 0));
  679. const mub = (d(0, 2, 3, 2) + mua * d(3, 2, 1, 0))
  680. /**--------------------------------------**/ /
  681. d(3, 2, 3, 2);
  682. const P01 = P1.clone().sub(P0);
  683. const P23 = P3.clone().sub(P2);
  684. const Pa = P0.clone().add(P01.multiplyScalar(mua));
  685. const Pb = P2.clone().add(P23.multiplyScalar(mub));
  686. const center = Pa.clone().add(Pb).multiplyScalar(0.5);
  687. return center;
  688. }
  689. static computeCircleCenter(A, B, C){
  690. const AB = B.clone().sub(A);
  691. const AC = C.clone().sub(A);
  692. const N = AC.clone().cross(AB).normalize();
  693. const ab_dir = AB.clone().cross(N).normalize();
  694. const ac_dir = AC.clone().cross(N).normalize();
  695. const ab_origin = A.clone().add(B).multiplyScalar(0.5);
  696. const ac_origin = A.clone().add(C).multiplyScalar(0.5);
  697. const P0 = ab_origin;
  698. const P1 = ab_origin.clone().add(ab_dir);
  699. const P2 = ac_origin;
  700. const P3 = ac_origin.clone().add(ac_dir);
  701. const center = Utils.lineToLineIntersection(P0, P1, P2, P3);
  702. return center;
  703. // Potree.Utils.debugLine(viewer.scene.scene, P0, P1, 0x00ff00);
  704. // Potree.Utils.debugLine(viewer.scene.scene, P2, P3, 0x0000ff);
  705. // Potree.Utils.debugSphere(viewer.scene.scene, center, 0.03, 0xff00ff);
  706. // const radius = center.distanceTo(A);
  707. // Potree.Utils.debugCircle(viewer.scene.scene, center, radius, new THREE.Vector3(0, 0, 1), 0xff00ff);
  708. }
  709. static getNorthVec(p1, distance, projection){
  710. if(projection){
  711. // if there is a projection, transform coordinates to WGS84
  712. // and compute angle to north there
  713. proj4.defs("pointcloud", projection);
  714. const transform = proj4("pointcloud", "WGS84");
  715. const llP1 = transform.forward(p1.toArray());
  716. let llP2 = transform.forward([p1.x, p1.y + distance]);
  717. const polarRadius = Math.sqrt((llP2[0] - llP1[0]) ** 2 + (llP2[1] - llP1[1]) ** 2);
  718. llP2 = [llP1[0], llP1[1] + polarRadius];
  719. const northVec = transform.inverse(llP2);
  720. return new THREE.Vector3(...northVec, p1.z).sub(p1);
  721. }else{
  722. // if there is no projection, assume [0, 1, 0] as north direction
  723. const vec = new THREE.Vector3(0, 1, 0).multiplyScalar(distance);
  724. return vec;
  725. }
  726. }
  727. static computeAzimuth(p1, p2, projection){
  728. let azimuth = 0;
  729. if(projection){
  730. // if there is a projection, transform coordinates to WGS84
  731. // and compute angle to north there
  732. let transform;
  733. if (projection.includes('EPSG')) {
  734. transform = proj4(projection, "WGS84");
  735. } else {
  736. proj4.defs("pointcloud", projection);
  737. transform = proj4("pointcloud", "WGS84");
  738. }
  739. const llP1 = transform.forward(p1.toArray());
  740. const llP2 = transform.forward(p2.toArray());
  741. const dir = [
  742. llP2[0] - llP1[0],
  743. llP2[1] - llP1[1],
  744. ];
  745. azimuth = Math.atan2(dir[1], dir[0]) - Math.PI / 2;
  746. }else{
  747. // if there is no projection, assume [0, 1, 0] as north direction
  748. const dir = [p2.x - p1.x, p2.y - p1.y];
  749. azimuth = Math.atan2(dir[1], dir[0]) - Math.PI / 2;
  750. }
  751. // make clockwise
  752. azimuth = -azimuth;
  753. return azimuth;
  754. }
  755. static async loadScript(url){
  756. return new Promise( resolve => {
  757. const element = document.getElementById(url);
  758. if(element){
  759. resolve();
  760. }else{
  761. const script = document.createElement("script");
  762. script.id = url;
  763. script.onload = () => {
  764. resolve();
  765. };
  766. script.src = url;
  767. document.body.appendChild(script);
  768. }
  769. });
  770. }
  771. static createSvgGradient(scheme){
  772. // this is what we are creating:
  773. //
  774. //<svg width="1em" height="3em" xmlns="http://www.w3.org/2000/svg">
  775. // <defs>
  776. // <linearGradient id="gradientID" gradientTransform="rotate(90)">
  777. // <stop offset="0%" stop-color="rgb(93, 78, 162)" />
  778. // ...
  779. // <stop offset="100%" stop-color="rgb(157, 0, 65)" />
  780. // </linearGradient>
  781. // </defs>
  782. //
  783. // <rect width="100%" height="100%" fill="url('#myGradient')" stroke="black" stroke-width="0.1em"/>
  784. //</svg>
  785. const gradientId = `${Math.random()}_${Date.now()}`;
  786. const svgn = "http://www.w3.org/2000/svg";
  787. const svg = document.createElementNS(svgn, "svg");
  788. svg.setAttributeNS(null, "width", "2em");
  789. svg.setAttributeNS(null, "height", "3em");
  790. { // <defs>
  791. const defs = document.createElementNS(svgn, "defs");
  792. const linearGradient = document.createElementNS(svgn, "linearGradient");
  793. linearGradient.setAttributeNS(null, "id", gradientId);
  794. linearGradient.setAttributeNS(null, "gradientTransform", "rotate(90)");
  795. for(let i = scheme.length - 1; i >= 0; i--){
  796. const stopVal = scheme[i];
  797. const percent = parseInt(100 - stopVal[0] * 100);
  798. const [r, g, b] = stopVal[1].toArray().map(v => parseInt(v * 255));
  799. const stop = document.createElementNS(svgn, "stop");
  800. stop.setAttributeNS(null, "offset", `${percent}%`);
  801. stop.setAttributeNS(null, "stop-color", `rgb(${r}, ${g}, ${b})`);
  802. linearGradient.appendChild(stop);
  803. }
  804. defs.appendChild(linearGradient);
  805. svg.appendChild(defs);
  806. }
  807. const rect = document.createElementNS(svgn, "rect");
  808. rect.setAttributeNS(null, "width", `100%`);
  809. rect.setAttributeNS(null, "height", `100%`);
  810. rect.setAttributeNS(null, "fill", `url("#${gradientId}")`);
  811. rect.setAttributeNS(null, "stroke", `black`);
  812. rect.setAttributeNS(null, "stroke-width", `0.1em`);
  813. svg.appendChild(rect);
  814. return svg;
  815. }
  816. static async waitAny(promises){
  817. return new Promise( (resolve) => {
  818. promises.map( promise => {
  819. promise.then( () => {
  820. resolve();
  821. });
  822. });
  823. });
  824. }
  825. }
  826. Utils.screenPass = new function () {
  827. this.screenScene = new THREE.Scene();
  828. this.screenQuad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2, 1));
  829. this.screenQuad.material.depthTest = true;
  830. this.screenQuad.material.depthWrite = true;
  831. this.screenQuad.material.transparent = true;
  832. this.screenScene.add(this.screenQuad);
  833. this.camera = new THREE.Camera();
  834. this.render = function (renderer, material, target) {
  835. this.screenQuad.material = material;
  836. if (typeof target === 'undefined') {
  837. renderer.render(this.screenScene, this.camera);
  838. } else {
  839. renderer.render(this.screenScene, this.camera, target);
  840. }
  841. };
  842. }();