utils.js 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250
  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. };
  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. let renderer = viewer.renderer;
  314. let pickParams = {};
  315. if(params.pickClipped){
  316. pickParams.pickClipped = params.pickClipped;
  317. }
  318. if(viewport){ //转换到类似整个画面时
  319. pickParams.x = mouse.x / viewport.width;
  320. pickParams.y = renderer.domElement.clientHeight - mouse.y / viewport.height;
  321. }else{
  322. pickParams.x = mouse.x;
  323. pickParams.y = renderer.domElement.clientHeight - mouse.y;
  324. }
  325. let raycaster = new THREE.Raycaster();
  326. raycaster.setFromCamera(pointer, camera);
  327. let ray = raycaster.ray;
  328. let selectedPointcloud = null;
  329. let closestDistance = Infinity;
  330. let closestIntersection = null;
  331. let closestPoint = null;
  332. for(let pointcloud of pointclouds){
  333. let point = pointcloud.pick(viewer, camera, ray, pickParams);
  334. if(!point){
  335. continue;
  336. }
  337. let distance = camera.position.distanceTo(point.position);
  338. if (distance < closestDistance) {
  339. closestDistance = distance;
  340. selectedPointcloud = pointcloud;
  341. closestIntersection = point.position;
  342. closestPoint = point;
  343. }
  344. }
  345. if (selectedPointcloud) {
  346. return {
  347. location: closestIntersection,
  348. distance: closestDistance,
  349. pointcloud: selectedPointcloud,
  350. point: closestPoint
  351. };
  352. } else {
  353. return null;
  354. }
  355. }
  356. /* static pixelsArrayToImage (pixels, width, height) {
  357. let canvas = document.createElement('canvas');
  358. canvas.width = width;
  359. canvas.height = height;
  360. let context = canvas.getContext('2d');
  361. pixels = new pixels.constructor(pixels);
  362. for (let i = 0; i < pixels.length; i++) {
  363. pixels[i * 4 + 3] = 255;
  364. }
  365. let imageData = context.createImageData(width, height);
  366. imageData.data.set(pixels);
  367. context.putImageData(imageData, 0, 0);
  368. let img = new Image();
  369. img.src = canvas.toDataURL();
  370. // img.style.transform = "scaleY(-1)";
  371. return img;
  372. } */
  373. static pixelsArrayToDataUrl(pixels, width, height, compressRatio = 0.7) {
  374. let canvas = document.createElement('canvas');
  375. canvas.width = width;
  376. canvas.height = height;
  377. let context = canvas.getContext('2d');
  378. pixels = new pixels.constructor(pixels);
  379. /* for (let i = 0; i < pixels.length; i++) {
  380. pixels[i * 4 + 3] = 255;
  381. } */
  382. // flip vertically
  383. let bytesPerLine = width * 4;
  384. for(let i = 0; i < parseInt(height / 2); i++){
  385. let j = height - i - 1;
  386. let lineI = pixels.slice(i * bytesPerLine, i * bytesPerLine + bytesPerLine);
  387. let lineJ = pixels.slice(j * bytesPerLine, j * bytesPerLine + bytesPerLine);
  388. pixels.set(lineJ, i * bytesPerLine);
  389. pixels.set(lineI, j * bytesPerLine);
  390. }
  391. let imageData = context.createImageData(width, height);
  392. imageData.data.set(pixels);
  393. context.putImageData(imageData, 0, 0);
  394. let dataURL = canvas.toDataURL(compressRatio);
  395. return dataURL;
  396. }
  397. /* static pixelsArrayToCanvas(pixels, width, height){
  398. let canvas = document.createElement('canvas');
  399. canvas.width = width;
  400. canvas.height = height;
  401. let context = canvas.getContext('2d');
  402. pixels = new pixels.constructor(pixels);
  403. //for (let i = 0; i < pixels.length; i++) {
  404. // pixels[i * 4 + 3] = 255;
  405. //}
  406. // flip vertically
  407. let bytesPerLine = width * 4;
  408. for(let i = 0; i < parseInt(height / 2); i++){
  409. let j = height - i - 1;
  410. let lineI = pixels.slice(i * bytesPerLine, i * bytesPerLine + bytesPerLine);
  411. let lineJ = pixels.slice(j * bytesPerLine, j * bytesPerLine + bytesPerLine);
  412. pixels.set(lineJ, i * bytesPerLine);
  413. pixels.set(lineI, j * bytesPerLine);
  414. }
  415. let imageData = context.createImageData(width, height);
  416. imageData.data.set(pixels);
  417. context.putImageData(imageData, 0, 0);
  418. return canvas;
  419. } */
  420. static removeListeners(dispatcher, type){
  421. if (dispatcher._listeners === undefined) {
  422. return;
  423. }
  424. if (dispatcher._listeners[ type ]) {
  425. delete dispatcher._listeners[ type ];
  426. }
  427. }
  428. /* static mouseToRay(mouse, camera, width, height){
  429. let normalizedMouse = {
  430. x: (mouse.x / width) * 2 - 1,
  431. y: -(mouse.y / height) * 2 + 1
  432. };
  433. let vector = new THREE.Vector3(normalizedMouse.x, normalizedMouse.y, 0.5);
  434. let origin = camera.position.clone();
  435. vector.unproject(camera);
  436. let direction = new THREE.Vector3().subVectors(vector, origin).normalize();
  437. let ray = new THREE.Ray(origin, direction);
  438. return ray;
  439. } */
  440. static mouseToRay(pointer, camera ){
  441. let vector = new THREE.Vector3(pointer.x, pointer.y, 1);
  442. let origin = new THREE.Vector3(pointer.x, pointer.y, -1); //不能用camera.position,在orbitCamera时不准
  443. vector.unproject(camera);
  444. origin.unproject(camera);
  445. let direction = new THREE.Vector3().subVectors(vector, origin).normalize();
  446. let ray = new THREE.Ray(origin, direction);
  447. return ray;
  448. }
  449. static getPos2d(point, camera, dom, viewport){//获取一个三维坐标对应屏幕中的二维坐标
  450. 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);
  451. var x,y,left,top;
  452. x = (pos.x + 1) / 2 * dom.clientWidth * viewport.width;
  453. y = (1 - (pos.y + 1) / 2) * dom.clientHeight * viewport.height;
  454. left = viewport.left * dom.clientWidth;
  455. top = (1- viewport.bottom - viewport.height) * dom.clientHeight;
  456. var inSight = pos.x <= 1 && pos.x >= -1 //是否在屏幕中
  457. && pos.x <= 1 && pos.y >= -1
  458. return {
  459. pos: new THREE.Vector2(left+x,top+y) ,// 屏幕像素坐标
  460. vector: pos, //(范围 -1 ~ 1)
  461. trueSide : pos.z<1, //trueSide为false时,即使在屏幕范围内可见,也是反方向的另一个不可以被渲染的点 参见Tag.update
  462. inSight : inSight, //在屏幕范围内可见,
  463. posInViewport: new THREE.Vector2(x,y)
  464. };
  465. }
  466. static projectedRadius(radius, camera, distance, screenWidth, screenHeight){
  467. if(camera instanceof THREE.OrthographicCamera){
  468. return Utils.projectedRadiusOrtho(radius, camera.projectionMatrix, screenWidth, screenHeight);
  469. }else if(camera instanceof THREE.PerspectiveCamera){
  470. return Utils.projectedRadiusPerspective(radius, camera.fov * Math.PI / 180, distance, screenHeight);
  471. }else{
  472. throw new Error("invalid parameters");
  473. }
  474. }
  475. static projectedRadiusPerspective(radius, fov, distance, screenHeight) {
  476. let projFactor = (1 / Math.tan(fov / 2)) / distance;
  477. projFactor = projFactor * screenHeight / 2;
  478. return radius * projFactor;
  479. }
  480. static projectedRadiusOrtho(radius, proj, screenWidth, screenHeight) {
  481. let p1 = new THREE.Vector4(0);
  482. let p2 = new THREE.Vector4(radius);
  483. p1.applyMatrix4(proj);
  484. p2.applyMatrix4(proj);
  485. p1 = new THREE.Vector3(p1.x, p1.y, p1.z);
  486. p2 = new THREE.Vector3(p2.x, p2.y, p2.z);
  487. p1.x = (p1.x + 1.0) * 0.5 * screenWidth;
  488. p1.y = (p1.y + 1.0) * 0.5 * screenHeight;
  489. p2.x = (p2.x + 1.0) * 0.5 * screenWidth;
  490. p2.y = (p2.y + 1.0) * 0.5 * screenHeight;
  491. return p1.distanceTo(p2);
  492. }
  493. static topView(camera, node){
  494. camera.position.set(0, 1, 0);
  495. camera.rotation.set(-Math.PI / 2, 0, 0);
  496. camera.zoomTo(node, 1);
  497. }
  498. static frontView (camera, node) {
  499. camera.position.set(0, 0, 1);
  500. camera.rotation.set(0, 0, 0);
  501. camera.zoomTo(node, 1);
  502. }
  503. static leftView (camera, node) {
  504. camera.position.set(-1, 0, 0);
  505. camera.rotation.set(0, -Math.PI / 2, 0);
  506. camera.zoomTo(node, 1);
  507. }
  508. static rightView (camera, node) {
  509. camera.position.set(1, 0, 0);
  510. camera.rotation.set(0, Math.PI / 2, 0);
  511. camera.zoomTo(node, 1);
  512. }
  513. static findClosestGpsTime(target, viewer){
  514. const start = performance.now();
  515. const nodes = [];
  516. for(const pc of viewer.scene.pointclouds){
  517. nodes.push(pc.root);
  518. for(const child of pc.root.children){
  519. if(child){
  520. nodes.push(child);
  521. }
  522. }
  523. }
  524. let closestNode = null;
  525. let closestIndex = Infinity;
  526. let closestDistance = Infinity;
  527. let closestValue = 0;
  528. for(const node of nodes){
  529. const isOkay = node.geometryNode != null
  530. && node.geometryNode.geometry != null
  531. && node.sceneNode != null;
  532. if(!isOkay){
  533. continue;
  534. }
  535. let geometry = node.geometryNode.geometry;
  536. let gpsTime = geometry.attributes["gps-time"];
  537. let range = gpsTime.potree.range;
  538. for(let i = 0; i < gpsTime.array.length; i++){
  539. let value = gpsTime.array[i];
  540. value = value * (range[1] - range[0]) + range[0];
  541. const distance = Math.abs(target - value);
  542. if(distance < closestDistance){
  543. closestIndex = i;
  544. closestDistance = distance;
  545. closestValue = value;
  546. closestNode = node;
  547. //console.log("found a closer one: " + value);
  548. }
  549. }
  550. }
  551. const geometry = closestNode.geometryNode.geometry;
  552. const position = new THREE.Vector3(
  553. geometry.attributes.position.array[3 * closestIndex + 0],
  554. geometry.attributes.position.array[3 * closestIndex + 1],
  555. geometry.attributes.position.array[3 * closestIndex + 2],
  556. );
  557. position.applyMatrix4(closestNode.sceneNode.matrixWorld);
  558. const end = performance.now();
  559. const duration = (end - start);
  560. console.log(`duration: ${duration.toFixed(3)}ms`);
  561. return {
  562. node: closestNode,
  563. index: closestIndex,
  564. position: position,
  565. };
  566. }
  567. /**
  568. *
  569. * 0: no intersection
  570. * 1: intersection
  571. * 2: fully inside
  572. */
  573. static frustumSphereIntersection (frustum, sphere) {
  574. let planes = frustum.planes;
  575. let center = sphere.center;
  576. let negRadius = -sphere.radius;
  577. let minDistance = Number.MAX_VALUE;
  578. for (let i = 0; i < 6; i++) {
  579. let distance = planes[ i ].distanceToPoint(center);
  580. if (distance < negRadius) {
  581. return 0;
  582. }
  583. minDistance = Math.min(minDistance, distance);
  584. }
  585. return (minDistance >= sphere.radius) ? 2 : 1;
  586. }
  587. // code taken from three.js
  588. // ImageUtils - generateDataTexture()
  589. static generateDataTexture (width, height, color) {
  590. let size = width * height;
  591. let data = new Uint8Array(4 * width * height);
  592. let r = Math.floor(color.r * 255);
  593. let g = Math.floor(color.g * 255);
  594. let b = Math.floor(color.b * 255);
  595. for (let i = 0; i < size; i++) {
  596. data[ i * 3 ] = r;
  597. data[ i * 3 + 1 ] = g;
  598. data[ i * 3 + 2 ] = b;
  599. }
  600. let texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
  601. texture.needsUpdate = true;
  602. texture.magFilter = THREE.NearestFilter;
  603. return texture;
  604. }
  605. // from http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
  606. static getParameterByName (name) {
  607. name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
  608. let regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
  609. let results = regex.exec(document.location.search);
  610. return results === null ? null : decodeURIComponent(results[1].replace(/\+/g, ' '));
  611. }
  612. static setParameter (name, value) {
  613. // value = encodeURIComponent(value);
  614. name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
  615. let regex = new RegExp('([\\?&])(' + name + '=([^&#]*))');
  616. let results = regex.exec(document.location.search);
  617. let url = window.location.href;
  618. if (results === null) {
  619. if (window.location.search.length === 0) {
  620. url = url + '?';
  621. } else {
  622. url = url + '&';
  623. }
  624. url = url + name + '=' + value;
  625. } else {
  626. let newValue = name + '=' + value;
  627. url = url.replace(results[2], newValue);
  628. }
  629. window.history.replaceState({}, '', url);
  630. }
  631. static createChildAABB(aabb, index){
  632. let min = aabb.min.clone();
  633. let max = aabb.max.clone();
  634. let size = new THREE.Vector3().subVectors(max, min);
  635. if ((index & 0b0001) > 0) {
  636. min.z += size.z / 2;
  637. } else {
  638. max.z -= size.z / 2;
  639. }
  640. if ((index & 0b0010) > 0) {
  641. min.y += size.y / 2;
  642. } else {
  643. max.y -= size.y / 2;
  644. }
  645. if ((index & 0b0100) > 0) {
  646. min.x += size.x / 2;
  647. } else {
  648. max.x -= size.x / 2;
  649. }
  650. return new THREE.Box3(min, max);
  651. }
  652. // see https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript
  653. static clipboardCopy(text){
  654. let textArea = document.createElement("textarea");
  655. textArea.style.position = 'fixed';
  656. textArea.style.top = 0;
  657. textArea.style.left = 0;
  658. textArea.style.width = '2em';
  659. textArea.style.height = '2em';
  660. textArea.style.padding = 0;
  661. textArea.style.border = 'none';
  662. textArea.style.outline = 'none';
  663. textArea.style.boxShadow = 'none';
  664. textArea.style.background = 'transparent';
  665. textArea.value = text;
  666. document.body.appendChild(textArea);
  667. textArea.select();
  668. try {
  669. let success = document.execCommand('copy');
  670. if(success){
  671. console.log("copied text to clipboard");
  672. }else{
  673. console.log("copy to clipboard failed");
  674. }
  675. } catch (err) {
  676. console.log("error while trying to copy to clipboard");
  677. }
  678. document.body.removeChild(textArea);
  679. }
  680. static getMeasurementIcon(measurement){
  681. if (measurement instanceof Measure) {
  682. if (measurement.showDistances && !measurement.showArea && !measurement.showAngles) {
  683. return `${Potree.resourcePath}/icons/distance.svg`;
  684. } else if (measurement.showDistances && measurement.showArea && !measurement.showAngles) {
  685. return `${Potree.resourcePath}/icons/area.svg`;
  686. } else if (measurement.maxMarkers === 1) {
  687. return `${Potree.resourcePath}/icons/point.svg`;
  688. } else if (!measurement.showDistances && !measurement.showArea && measurement.showAngles) {
  689. return `${Potree.resourcePath}/icons/angle.png`;
  690. } else if (measurement.showHeight) {
  691. return `${Potree.resourcePath}/icons/height.svg`;
  692. } else {
  693. return `${Potree.resourcePath}/icons/distance.svg`;
  694. }
  695. } else if (measurement instanceof Profile) {
  696. return `${Potree.resourcePath}/icons/profile.svg`;
  697. } else if (measurement instanceof Volume) {
  698. return `${Potree.resourcePath}/icons/volume.svg`;
  699. } else if (measurement instanceof PolygonClipVolume) {
  700. return `${Potree.resourcePath}/icons/clip-polygon.svg`;
  701. }
  702. }
  703. static lineToLineIntersection(P0, P1, P2, P3){
  704. const P = [P0, P1, P2, P3];
  705. const d = (m, n, o, p) => {
  706. let result =
  707. (P[m].x - P[n].x) * (P[o].x - P[p].x)
  708. + (P[m].y - P[n].y) * (P[o].y - P[p].y)
  709. + (P[m].z - P[n].z) * (P[o].z - P[p].z);
  710. return result;
  711. };
  712. const mua = (d(0, 2, 3, 2) * d(3, 2, 1, 0) - d(0, 2, 1, 0) * d(3, 2, 3, 2))
  713. /**-----------------------------------------------------------------**/ /
  714. (d(1, 0, 1, 0) * d(3, 2, 3, 2) - d(3, 2, 1, 0) * d(3, 2, 1, 0));
  715. const mub = (d(0, 2, 3, 2) + mua * d(3, 2, 1, 0))
  716. /**--------------------------------------**/ /
  717. d(3, 2, 3, 2);
  718. const P01 = P1.clone().sub(P0);
  719. const P23 = P3.clone().sub(P2);
  720. const Pa = P0.clone().add(P01.multiplyScalar(mua));
  721. const Pb = P2.clone().add(P23.multiplyScalar(mub));
  722. const center = Pa.clone().add(Pb).multiplyScalar(0.5);
  723. return center;
  724. }
  725. static computeCircleCenter(A, B, C){
  726. const AB = B.clone().sub(A);
  727. const AC = C.clone().sub(A);
  728. const N = AC.clone().cross(AB).normalize();
  729. const ab_dir = AB.clone().cross(N).normalize();
  730. const ac_dir = AC.clone().cross(N).normalize();
  731. const ab_origin = A.clone().add(B).multiplyScalar(0.5);
  732. const ac_origin = A.clone().add(C).multiplyScalar(0.5);
  733. const P0 = ab_origin;
  734. const P1 = ab_origin.clone().add(ab_dir);
  735. const P2 = ac_origin;
  736. const P3 = ac_origin.clone().add(ac_dir);
  737. const center = Utils.lineToLineIntersection(P0, P1, P2, P3);
  738. return center;
  739. // Potree.Utils.debugLine(viewer.scene.scene, P0, P1, 0x00ff00);
  740. // Potree.Utils.debugLine(viewer.scene.scene, P2, P3, 0x0000ff);
  741. // Potree.Utils.debugSphere(viewer.scene.scene, center, 0.03, 0xff00ff);
  742. // const radius = center.distanceTo(A);
  743. // Potree.Utils.debugCircle(viewer.scene.scene, center, radius, new THREE.Vector3(0, 0, 1), 0xff00ff);
  744. }
  745. static getNorthVec(p1, distance, projection){
  746. if(projection){
  747. // if there is a projection, transform coordinates to WGS84
  748. // and compute angle to north there
  749. proj4.defs("pointcloud", projection);
  750. const transform = proj4("pointcloud", "WGS84");
  751. const llP1 = transform.forward(p1.toArray());
  752. let llP2 = transform.forward([p1.x, p1.y + distance]);
  753. const polarRadius = Math.sqrt((llP2[0] - llP1[0]) ** 2 + (llP2[1] - llP1[1]) ** 2);
  754. llP2 = [llP1[0], llP1[1] + polarRadius];
  755. const northVec = transform.inverse(llP2);
  756. return new THREE.Vector3(...northVec, p1.z).sub(p1);
  757. }else{
  758. // if there is no projection, assume [0, 1, 0] as north direction
  759. const vec = new THREE.Vector3(0, 1, 0).multiplyScalar(distance);
  760. return vec;
  761. }
  762. }
  763. static computeAzimuth(p1, p2, projection){
  764. let azimuth = 0;
  765. if(projection){
  766. // if there is a projection, transform coordinates to WGS84
  767. // and compute angle to north there
  768. let transform;
  769. if (projection.includes('EPSG')) {
  770. transform = proj4(projection, "WGS84");
  771. } else {
  772. proj4.defs("pointcloud", projection);
  773. transform = proj4("pointcloud", "WGS84");
  774. }
  775. const llP1 = transform.forward(p1.toArray());
  776. const llP2 = transform.forward(p2.toArray());
  777. const dir = [
  778. llP2[0] - llP1[0],
  779. llP2[1] - llP1[1],
  780. ];
  781. azimuth = Math.atan2(dir[1], dir[0]) - Math.PI / 2;
  782. }else{
  783. // if there is no projection, assume [0, 1, 0] as north direction
  784. const dir = [p2.x - p1.x, p2.y - p1.y];
  785. azimuth = Math.atan2(dir[1], dir[0]) - Math.PI / 2;
  786. }
  787. // make clockwise
  788. azimuth = -azimuth;
  789. return azimuth;
  790. }
  791. static async loadScript(url){
  792. return new Promise( resolve => {
  793. const element = document.getElementById(url);
  794. if(element){
  795. resolve();
  796. }else{
  797. const script = document.createElement("script");
  798. script.id = url;
  799. script.onload = () => {
  800. resolve();
  801. };
  802. script.src = url;
  803. document.body.appendChild(script);
  804. }
  805. });
  806. }
  807. static createSvgGradient(scheme){
  808. // this is what we are creating:
  809. //
  810. //<svg width="1em" height="3em" xmlns="http://www.w3.org/2000/svg">
  811. // <defs>
  812. // <linearGradient id="gradientID" gradientTransform="rotate(90)">
  813. // <stop offset="0%" stop-color="rgb(93, 78, 162)" />
  814. // ...
  815. // <stop offset="100%" stop-color="rgb(157, 0, 65)" />
  816. // </linearGradient>
  817. // </defs>
  818. //
  819. // <rect width="100%" height="100%" fill="url('#myGradient')" stroke="black" stroke-width="0.1em"/>
  820. //</svg>
  821. const gradientId = `${Math.random()}_${Date.now()}`;
  822. const svgn = "http://www.w3.org/2000/svg";
  823. const svg = document.createElementNS(svgn, "svg");
  824. svg.setAttributeNS(null, "width", "2em");
  825. svg.setAttributeNS(null, "height", "3em");
  826. { // <defs>
  827. const defs = document.createElementNS(svgn, "defs");
  828. const linearGradient = document.createElementNS(svgn, "linearGradient");
  829. linearGradient.setAttributeNS(null, "id", gradientId);
  830. linearGradient.setAttributeNS(null, "gradientTransform", "rotate(90)");
  831. for(let i = scheme.length - 1; i >= 0; i--){
  832. const stopVal = scheme[i];
  833. const percent = parseInt(100 - stopVal[0] * 100);
  834. const [r, g, b] = stopVal[1].toArray().map(v => parseInt(v * 255));
  835. const stop = document.createElementNS(svgn, "stop");
  836. stop.setAttributeNS(null, "offset", `${percent}%`);
  837. stop.setAttributeNS(null, "stop-color", `rgb(${r}, ${g}, ${b})`);
  838. linearGradient.appendChild(stop);
  839. }
  840. defs.appendChild(linearGradient);
  841. svg.appendChild(defs);
  842. }
  843. const rect = document.createElementNS(svgn, "rect");
  844. rect.setAttributeNS(null, "width", `100%`);
  845. rect.setAttributeNS(null, "height", `100%`);
  846. rect.setAttributeNS(null, "fill", `url("#${gradientId}")`);
  847. rect.setAttributeNS(null, "stroke", `black`);
  848. rect.setAttributeNS(null, "stroke-width", `0.1em`);
  849. svg.appendChild(rect);
  850. return svg;
  851. }
  852. static async waitAny(promises){
  853. return new Promise( (resolve) => {
  854. promises.map( promise => {
  855. promise.then( () => {
  856. resolve();
  857. });
  858. });
  859. });
  860. }
  861. }
  862. Utils.screenPass = new function () {
  863. this.screenScene = new THREE.Scene();
  864. this.screenQuad = new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2, 1));
  865. this.screenQuad.material.depthTest = true;
  866. this.screenQuad.material.depthWrite = true;
  867. this.screenQuad.material.transparent = true;
  868. this.screenScene.add(this.screenQuad);
  869. this.camera = new THREE.Camera();
  870. this.render = function (renderer, material, target) {
  871. this.screenQuad.material = material;
  872. if (typeof target === 'undefined') {
  873. renderer.render(this.screenScene, this.camera);
  874. } else {
  875. renderer.setRenderTarget(target)
  876. renderer.render(this.screenScene, this.camera);
  877. }
  878. };
  879. }();
  880. //add
  881. Utils.computePointcloudsBound = function(pointclouds){
  882. var boundingBox = new THREE.Box3();
  883. pointclouds.forEach(pointcloud=>{
  884. var boundingBox_ = pointcloud.pcoGeometry.tightBoundingBox.clone().applyMatrix4(pointcloud.matrixWorld)
  885. pointcloud.bound = boundingBox_
  886. boundingBox.union(boundingBox_)
  887. })
  888. var boundSize = boundingBox.getSize(new THREE.Vector3)
  889. var center = boundingBox.getCenter(new THREE.Vector3)
  890. return {boundSize, center, boundingBox}
  891. }
  892. Utils.convertScreenPositionToNDC = function(pointer, mouse, width, height) {
  893. return pointer = pointer || new THREE.Vector2,
  894. pointer.x = mouse.x / width * 2 - 1,
  895. pointer.y = 2 * -(mouse.y / height) + 1,
  896. pointer
  897. }
  898. Utils.getOrthoCameraMoveVec = function(pointerDelta, camera ){//获取当camera为Ortho型时 屏幕点1 到 屏幕点2 的三维距离
  899. let cameraViewWidth = camera.right / camera.zoom
  900. let cameraViewHeight = camera.top / camera.zoom
  901. let moveVec = new THREE.Vector3;
  902. moveVec.set( pointerDelta.x * cameraViewWidth , pointerDelta.y * cameraViewHeight , 0).applyQuaternion(camera.quaternion)
  903. return moveVec
  904. }
  905. Utils.VectorFactory = {
  906. fromArray : function(t) {
  907. if (t) {
  908. if (t.length < 2 || t.length > 3)
  909. console.error("Wrong number of ordinates for a point!");
  910. return 3 === t.length ? (new THREE.Vector3).fromArray(t) : (new THREE.Vector2).fromArray(t)
  911. }
  912. },
  913. fromArray3 : function(t) {
  914. if (t) {
  915. if (3 !== t.length)
  916. console.error("Wrong number of ordinates for a point!");
  917. return (new THREE.Vector3).fromArray(t)
  918. }
  919. },
  920. fromArray2 : function(t) {
  921. if (t) {
  922. if (2 !== t.length)
  923. console.error("Wrong number of ordinates for a point!");
  924. return (new THREE.Vector2).fromArray(t)
  925. }
  926. },
  927. toString : function(t) {
  928. return t.x.toFixed(8) + "," + t.y.toFixed(8) + "," + t.z.toFixed(3)
  929. }
  930. }
  931. Utils.QuaternionFactory = {
  932. rot90 : (new THREE.Quaternion).setFromAxisAngle(new THREE.Vector3(0,0,1), THREE.Math.degToRad(-90)),
  933. fromArray : function(t) {
  934. if (t) {
  935. if (4 !== t.length)
  936. console.error("Wrong number of ordinates for a quaternion!");
  937. return new THREE.Quaternion(t[1],t[2],t[3],t[0]).multiply(this.rot90)
  938. }
  939. }
  940. ,
  941. toArray : function(t) {
  942. if (t) {
  943. var e = t.clone().multiply(a).toArray();
  944. return [e[3], e[0], e[1], e[2]]
  945. }
  946. }
  947. ,
  948. fromLonLat : function(t) {
  949. if (t)
  950. return (new THREE.Quaternion).setFromEuler(new THREE.Euler(t.lon,t.lat,0))
  951. }
  952. ,
  953. toLonLat : function(t) {
  954. if (t) {
  955. var e = (new THREE.Euler).setFromQuaternion(t);
  956. return {
  957. lon: e.x,
  958. lat: e.y
  959. }
  960. }
  961. }
  962. }