profile.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106
  1. import * as THREE from "../../libs/three.js/build/three.module.js";
  2. import {Utils} from "../utils.js";
  3. import {Points} from "../Points.js";
  4. import {CSVExporter} from "../exporter/CSVExporter.js";
  5. import {LASExporter} from "../exporter/LASExporter.js";
  6. import { EventDispatcher } from "../EventDispatcher.js";
  7. import {PointCloudTree} from "../PointCloudTree.js";
  8. import {Renderer} from "../PotreeRenderer.js";
  9. import {PointCloudMaterial} from "../materials/PointCloudMaterial.js";
  10. import {PointSizeType} from "../defines.js";
  11. function copyMaterial(source, target){
  12. for(let name of Object.keys(target.uniforms)){
  13. target.uniforms[name].value = source.uniforms[name].value;
  14. }
  15. target.gradientTexture = source.gradientTexture;
  16. target.visibleNodesTexture = source.visibleNodesTexture;
  17. target.classificationTexture = source.classificationTexture;
  18. target.matcapTexture = source.matcapTexture;
  19. target.activeAttributeName = source.activeAttributeName;
  20. target.ranges = source.ranges;
  21. //target.updateShaderSource();
  22. }
  23. class Batch{
  24. constructor(geometry, material){
  25. this.geometry = geometry;
  26. this.material = material;
  27. this.sceneNode = new THREE.Points(geometry, material);
  28. this.geometryNode = {
  29. estimatedSpacing: 1.0,
  30. geometry: geometry,
  31. };
  32. }
  33. getLevel(){
  34. return 0;
  35. }
  36. }
  37. class ProfileFakeOctree extends PointCloudTree{
  38. constructor(octree){
  39. super();
  40. this.trueOctree = octree;
  41. this.pcoGeometry = octree.pcoGeometry;
  42. this.points = [];
  43. this.visibleNodes = [];
  44. //this.material = this.trueOctree.material;
  45. this.material = new PointCloudMaterial();
  46. //this.material.copy(this.trueOctree.material);
  47. copyMaterial(this.trueOctree.material, this.material);
  48. this.material.pointSizeType = PointSizeType.FIXED;
  49. this.batchSize = 100 * 1000;
  50. this.currentBatch = null
  51. }
  52. getAttribute(name){
  53. return this.trueOctree.getAttribute(name);
  54. }
  55. dispose(){
  56. for(let node of this.visibleNodes){
  57. node.geometry.dispose();
  58. }
  59. this.visibleNodes = [];
  60. this.currentBatch = null;
  61. this.points = [];
  62. }
  63. addPoints(data){
  64. // since each call to addPoints can deliver very very few points,
  65. // we're going to batch them into larger buffers for efficiency.
  66. if(this.currentBatch === null){
  67. this.currentBatch = this.createNewBatch(data);
  68. }
  69. this.points.push(data);
  70. let updateRange = {
  71. start: this.currentBatch.geometry.drawRange.count,
  72. count: 0
  73. };
  74. let projectedBox = new THREE.Box3();
  75. let truePos = new THREE.Vector3();
  76. for(let i = 0; i < data.numPoints; i++){
  77. if(updateRange.start + updateRange.count >= this.batchSize){
  78. // current batch full, start new batch
  79. for(let key of Object.keys(this.currentBatch.geometry.attributes)){
  80. let attribute = this.currentBatch.geometry.attributes[key];
  81. attribute.updateRange.offset = updateRange.start;
  82. attribute.updateRange.count = updateRange.count;
  83. attribute.needsUpdate = true;
  84. }
  85. this.currentBatch.geometry.computeBoundingBox();
  86. this.currentBatch.geometry.computeBoundingSphere();
  87. this.currentBatch = this.createNewBatch(data);
  88. updateRange = {
  89. start: 0,
  90. count: 0
  91. };
  92. }
  93. truePos.set(
  94. data.data.position[3 * i + 0] + this.trueOctree.position.x,
  95. data.data.position[3 * i + 1] + this.trueOctree.position.y,
  96. data.data.position[3 * i + 2] + this.trueOctree.position.z,
  97. );
  98. let x = data.data.mileage[i];
  99. let y = 0;
  100. let z = truePos.z;
  101. projectedBox.expandByPoint(new THREE.Vector3(x, y, z));
  102. let index = updateRange.start + updateRange.count;
  103. let geometry = this.currentBatch.geometry;
  104. for(let attributeName of Object.keys(data.data)){
  105. let source = data.data[attributeName];
  106. let target = geometry.attributes[attributeName];
  107. let numElements = target.itemSize;
  108. for(let item = 0; item < numElements; item++){
  109. target.array[numElements * index + item] = source[numElements * i + item];
  110. }
  111. }
  112. {
  113. let position = geometry.attributes.position;
  114. position.array[3 * index + 0] = x;
  115. position.array[3 * index + 1] = y;
  116. position.array[3 * index + 2] = z;
  117. }
  118. updateRange.count++;
  119. this.currentBatch.geometry.drawRange.count++;
  120. }
  121. for(let key of Object.keys(this.currentBatch.geometry.attributes)){
  122. let attribute = this.currentBatch.geometry.attributes[key];
  123. attribute.updateRange.offset = updateRange.start;
  124. attribute.updateRange.count = updateRange.count;
  125. attribute.needsUpdate = true;
  126. }
  127. data.projectedBox = projectedBox;
  128. this.projectedBox = this.points.reduce( (a, i) => a.union(i.projectedBox), new THREE.Box3());
  129. }
  130. createNewBatch(data){
  131. let geometry = new THREE.BufferGeometry();
  132. // create new batches with batch_size elements of the same type as the attribute
  133. for(let attributeName of Object.keys(data.data)){
  134. let buffer = data.data[attributeName];
  135. let numElements = buffer.length / data.numPoints; // 3 for pos, 4 for col, 1 for scalars
  136. let constructor = buffer.constructor;
  137. let normalized = false;
  138. if(this.trueOctree.root.sceneNode){
  139. if(this.trueOctree.root.sceneNode.geometry.attributes[attributeName]){
  140. normalized = this.trueOctree.root.sceneNode.geometry.attributes[attributeName].normalized;
  141. }
  142. }
  143. let batchBuffer = new constructor(numElements * this.batchSize);
  144. let bufferAttribute = new THREE.BufferAttribute(batchBuffer, numElements, normalized);
  145. bufferAttribute.potree = {
  146. range: [0, 1],
  147. };
  148. geometry.setAttribute(attributeName, bufferAttribute);
  149. }
  150. geometry.drawRange.start = 0;
  151. geometry.drawRange.count = 0;
  152. let batch = new Batch(geometry, this.material);
  153. this.visibleNodes.push(batch);
  154. return batch;
  155. }
  156. computeVisibilityTextureData(){
  157. let data = new Uint8Array(this.visibleNodes.length * 4);
  158. let offsets = new Map();
  159. for(let i = 0; i < this.visibleNodes.length; i++){
  160. let node = this.visibleNodes[i];
  161. offsets[node] = i;
  162. }
  163. return {
  164. data: data,
  165. offsets: offsets,
  166. };
  167. }
  168. }
  169. export class ProfileWindow extends EventDispatcher {
  170. constructor (viewer) {
  171. super();
  172. this.viewer = viewer;
  173. this.elRoot = $('#profile_window');
  174. this.renderArea = this.elRoot.find('#profileCanvasContainer');
  175. this.svg = d3.select('svg#profileSVG');
  176. this.mouseIsDown = false;
  177. this.projectedBox = new THREE.Box3();
  178. this.pointclouds = new Map();
  179. this.numPoints = 0;
  180. this.lastAddPointsTimestamp = undefined;
  181. this.mouse = new THREE.Vector2(0, 0);
  182. this.scale = new THREE.Vector3(1, 1, 1);
  183. this.autoFitEnabled = true; // completely disable/enable
  184. this.autoFit = false; // internal
  185. let cwIcon = `${exports.resourcePath}/icons/arrow_cw.svg`;
  186. $('#potree_profile_rotate_cw').attr('src', cwIcon);
  187. let ccwIcon = `${exports.resourcePath}/icons/arrow_ccw.svg`;
  188. $('#potree_profile_rotate_ccw').attr('src', ccwIcon);
  189. let forwardIcon = `${exports.resourcePath}/icons/arrow_up.svg`;
  190. $('#potree_profile_move_forward').attr('src', forwardIcon);
  191. let backwardIcon = `${exports.resourcePath}/icons/arrow_down.svg`;
  192. $('#potree_profile_move_backward').attr('src', backwardIcon);
  193. let csvIcon = `${exports.resourcePath}/icons/file_csv_2d.svg`;
  194. $('#potree_download_csv_icon').attr('src', csvIcon);
  195. let lasIcon = `${exports.resourcePath}/icons/file_las_3d.svg`;
  196. $('#potree_download_las_icon').attr('src', lasIcon);
  197. let closeIcon = `${exports.resourcePath}/icons/close.svg`;
  198. $('#closeProfileContainer').attr("src", closeIcon);
  199. this.initTHREE();
  200. this.initSVG();
  201. this.initListeners();
  202. this.pRenderer = new Renderer(this.renderer);
  203. this.elRoot.i18n();
  204. }
  205. initListeners () {
  206. $(window).resize(() => {
  207. if (this.enabled) {
  208. this.render();
  209. }
  210. });
  211. this.renderArea.mousedown(e => {
  212. this.mouseIsDown = true;
  213. });
  214. this.renderArea.mouseup(e => {
  215. this.mouseIsDown = false;
  216. });
  217. let viewerPickSphereSizeHandler = () => {
  218. let camera = this.viewer.scene.getActiveCamera();
  219. let domElement = this.viewer.renderer.domElement;
  220. let distance = this.viewerPickSphere.position.distanceTo(camera.position);
  221. let pr = Utils.projectedRadius(1, camera, distance, domElement.clientWidth, domElement.clientHeight);
  222. let scale = (10 / pr);
  223. this.viewerPickSphere.scale.set(scale, scale, scale);
  224. };
  225. this.renderArea.mousemove(e => {
  226. if (this.pointclouds.size === 0) {
  227. return;
  228. }
  229. let rect = this.renderArea[0].getBoundingClientRect();
  230. let x = e.clientX - rect.left;
  231. let y = e.clientY - rect.top;
  232. let newMouse = new THREE.Vector2(x, y);
  233. if (this.mouseIsDown) {
  234. // DRAG
  235. this.autoFit = false;
  236. this.lastDrag = new Date().getTime();
  237. let cPos = [this.scaleX.invert(this.mouse.x), this.scaleY.invert(this.mouse.y)];
  238. let ncPos = [this.scaleX.invert(newMouse.x), this.scaleY.invert(newMouse.y)];
  239. this.camera.position.x -= ncPos[0] - cPos[0];
  240. this.camera.position.z -= ncPos[1] - cPos[1];
  241. this.render();
  242. } else if (this.pointclouds.size > 0) {
  243. // FIND HOVERED POINT
  244. let radius = Math.abs(this.scaleX.invert(0) - this.scaleX.invert(40));
  245. let mileage = this.scaleX.invert(newMouse.x);
  246. let elevation = this.scaleY.invert(newMouse.y);
  247. let closest = this.selectPoint(mileage, elevation, radius);
  248. if (closest) {
  249. let point = closest.point;
  250. let position = new Float64Array([
  251. point.position[0] + closest.pointcloud.position.x,
  252. point.position[1] + closest.pointcloud.position.y,
  253. point.position[2] + closest.pointcloud.position.z
  254. ]);
  255. this.elRoot.find('#profileSelectionProperties').fadeIn(200);
  256. this.pickSphere.visible = true;
  257. this.pickSphere.scale.set(0.5 * radius, 0.5 * radius, 0.5 * radius);
  258. this.pickSphere.position.set(point.mileage, 0, position[2]);
  259. this.viewerPickSphere.position.set(...position);
  260. if(!this.viewer.scene.scene.children.includes(this.viewerPickSphere)){
  261. this.viewer.scene.scene.add(this.viewerPickSphere);
  262. if(!this.viewer.hasEventListener("update", viewerPickSphereSizeHandler)){
  263. this.viewer.addEventListener("update", viewerPickSphereSizeHandler);
  264. }
  265. }
  266. let info = this.elRoot.find('#profileSelectionProperties');
  267. let html = '<table>';
  268. for (let attributeName of Object.keys(point)) {
  269. let value = point[attributeName];
  270. let attribute = closest.pointcloud.getAttribute(attributeName);
  271. let transform = value => value;
  272. if(attribute && attribute.type.size > 4){
  273. let range = attribute.initialRange;
  274. let scale = 1 / (range[1] - range[0]);
  275. let offset = range[0];
  276. transform = value => value / scale + offset;
  277. }
  278. if (attributeName === 'position') {
  279. let values = [...position].map(v => Utils.addCommas(v.toFixed(3)));
  280. html += `
  281. <tr>
  282. <td>x</td>
  283. <td>${values[0]}</td>
  284. </tr>
  285. <tr>
  286. <td>y</td>
  287. <td>${values[1]}</td>
  288. </tr>
  289. <tr>
  290. <td>z</td>
  291. <td>${values[2]}</td>
  292. </tr>`;
  293. } else if (attributeName === 'rgba') {
  294. html += `
  295. <tr>
  296. <td>${attributeName}</td>
  297. <td>${value.join(', ')}</td>
  298. </tr>`;
  299. } else if (attributeName === 'normal') {
  300. continue;
  301. } else if (attributeName === 'mileage') {
  302. html += `
  303. <tr>
  304. <td>${attributeName}</td>
  305. <td>${value.toFixed(3)}</td>
  306. </tr>`;
  307. } else {
  308. html += `
  309. <tr>
  310. <td>${attributeName}</td>
  311. <td>${transform(value)}</td>
  312. </tr>`;
  313. }
  314. }
  315. html += '</table>';
  316. info.html(html);
  317. this.selectedPoint = point;
  318. } else {
  319. // this.pickSphere.visible = false;
  320. // this.selectedPoint = null;
  321. this.viewer.scene.scene.add(this.viewerPickSphere);
  322. let index = this.viewer.scene.scene.children.indexOf(this.viewerPickSphere);
  323. if(index >= 0){
  324. this.viewer.scene.scene.children.splice(index, 1);
  325. }
  326. this.viewer.removeEventListener("update", viewerPickSphereSizeHandler);
  327. }
  328. this.render();
  329. }
  330. this.mouse.copy(newMouse);
  331. });
  332. let onWheel = e => {
  333. this.autoFit = false;
  334. let delta = 0;
  335. if (e.wheelDelta !== undefined) { // WebKit / Opera / Explorer 9
  336. delta = e.wheelDelta;
  337. } else if (e.detail !== undefined) { // Firefox
  338. delta = -e.detail;
  339. }
  340. let ndelta = Math.sign(delta);
  341. let cPos = [this.scaleX.invert(this.mouse.x), this.scaleY.invert(this.mouse.y)];
  342. if (ndelta > 0) {
  343. // + 10%
  344. this.scale.multiplyScalar(1.1);
  345. } else {
  346. // - 10%
  347. this.scale.multiplyScalar(100 / 110);
  348. }
  349. this.updateScales();
  350. let ncPos = [this.scaleX.invert(this.mouse.x), this.scaleY.invert(this.mouse.y)];
  351. this.camera.position.x -= ncPos[0] - cPos[0];
  352. this.camera.position.z -= ncPos[1] - cPos[1];
  353. this.render();
  354. this.updateScales();
  355. };
  356. $(this.renderArea)[0].addEventListener('mousewheel', onWheel, false);
  357. $(this.renderArea)[0].addEventListener('DOMMouseScroll', onWheel, false); // Firefox
  358. $('#closeProfileContainer').click(() => {
  359. this.hide();
  360. });
  361. let getProfilePoints = () => {
  362. let points = new Points();
  363. for(let [pointcloud, entry] of this.pointclouds){
  364. for(let pointSet of entry.points){
  365. let originPos = pointSet.data.position;
  366. let trueElevationPosition = new Float32Array(originPos);
  367. for(let i = 0; i < pointSet.numPoints; i++){
  368. trueElevationPosition[3 * i + 2] += pointcloud.position.z;
  369. }
  370. pointSet.data.position = trueElevationPosition;
  371. points.add(pointSet);
  372. pointSet.data.position = originPos;
  373. }
  374. }
  375. return points;
  376. };
  377. $('#potree_download_csv_icon').click(() => {
  378. let points = getProfilePoints();
  379. let string = CSVExporter.toString(points);
  380. let blob = new Blob([string], {type: "text/string"});
  381. $('#potree_download_profile_ortho_link').attr('href', URL.createObjectURL(blob));
  382. });
  383. $('#potree_download_las_icon').click(() => {
  384. let points = getProfilePoints();
  385. let buffer = LASExporter.toLAS(points);
  386. let blob = new Blob([buffer], {type: "application/octet-binary"});
  387. $('#potree_download_profile_link').attr('href', URL.createObjectURL(blob));
  388. });
  389. }
  390. selectPoint (mileage, elevation, radius) {
  391. let closest = {
  392. distance: Infinity,
  393. pointcloud: null,
  394. points: null,
  395. index: null
  396. };
  397. let pointBox = new THREE.Box2(
  398. new THREE.Vector2(mileage - radius, elevation - radius),
  399. new THREE.Vector2(mileage + radius, elevation + radius));
  400. let numTested = 0;
  401. let numSkipped = 0;
  402. let numTestedPoints = 0;
  403. let numSkippedPoints = 0;
  404. for (let [pointcloud, entry] of this.pointclouds) {
  405. for(let points of entry.points){
  406. let collisionBox = new THREE.Box2(
  407. new THREE.Vector2(points.projectedBox.min.x, points.projectedBox.min.z),
  408. new THREE.Vector2(points.projectedBox.max.x, points.projectedBox.max.z)
  409. );
  410. let intersects = collisionBox.intersectsBox(pointBox);
  411. if(!intersects){
  412. numSkipped++;
  413. numSkippedPoints += points.numPoints;
  414. continue;
  415. }
  416. numTested++;
  417. numTestedPoints += points.numPoints
  418. for (let i = 0; i < points.numPoints; i++) {
  419. let m = points.data.mileage[i] - mileage;
  420. let e = points.data.position[3 * i + 2] - elevation + pointcloud.position.z;
  421. let r = Math.sqrt(m * m + e * e);
  422. const withinDistance = r < radius && r < closest.distance;
  423. let unfilteredClass = true;
  424. if(points.data.classification){
  425. const classification = pointcloud.material.classification;
  426. const pointClassID = points.data.classification[i];
  427. const pointClassValue = classification[pointClassID];
  428. if(pointClassValue && (!pointClassValue.visible || pointClassValue.color.w === 0)){
  429. unfilteredClass = false;
  430. }
  431. }
  432. if (withinDistance && unfilteredClass) {
  433. closest = {
  434. distance: r,
  435. pointcloud: pointcloud,
  436. points: points,
  437. index: i
  438. };
  439. }
  440. }
  441. }
  442. }
  443. //console.log(`nodes: ${numTested}, ${numSkipped} || points: ${numTestedPoints}, ${numSkippedPoints}`);
  444. if (closest.distance < Infinity) {
  445. let points = closest.points;
  446. let point = {};
  447. let attributes = Object.keys(points.data);
  448. for (let attribute of attributes) {
  449. let attributeData = points.data[attribute];
  450. let itemSize = attributeData.length / points.numPoints;
  451. let value = attributeData.subarray(itemSize * closest.index, itemSize * closest.index + itemSize);
  452. if (value.length === 1) {
  453. point[attribute] = value[0];
  454. } else {
  455. point[attribute] = value;
  456. }
  457. }
  458. closest.point = point;
  459. return closest;
  460. } else {
  461. return null;
  462. }
  463. }
  464. initTHREE () {
  465. this.renderer = new THREE.WebGLRenderer({alpha: true, premultipliedAlpha: false});
  466. this.renderer.setClearColor(0x000000, 0);
  467. this.renderer.setSize(10, 10);
  468. this.renderer.autoClear = false;
  469. this.renderArea.append($(this.renderer.domElement));
  470. this.renderer.domElement.tabIndex = '2222';
  471. $(this.renderer.domElement).css('width', '100%');
  472. $(this.renderer.domElement).css('height', '100%');
  473. {
  474. let gl = this.renderer.getContext();
  475. if(gl.createVertexArray == null){
  476. let extVAO = gl.getExtension('OES_vertex_array_object');
  477. if(!extVAO){
  478. throw new Error("OES_vertex_array_object extension not supported");
  479. }
  480. gl.createVertexArray = extVAO.createVertexArrayOES.bind(extVAO);
  481. gl.bindVertexArray = extVAO.bindVertexArrayOES.bind(extVAO);
  482. }
  483. }
  484. this.camera = new THREE.OrthographicCamera(-1000, 1000, 1000, -1000, -1000, 1000);
  485. this.camera.up.set(0, 0, 1);
  486. this.camera.rotation.order = "ZXY";
  487. this.camera.rotation.x = Math.PI / 2.0;
  488. this.scene = new THREE.Scene();
  489. this.profileScene = new THREE.Scene();
  490. let sg = new THREE.SphereGeometry(1, 16, 16);
  491. let sm = new THREE.MeshNormalMaterial();
  492. this.pickSphere = new THREE.Mesh(sg, sm);
  493. this.scene.add(this.pickSphere);
  494. this.viewerPickSphere = new THREE.Mesh(sg, sm);
  495. }
  496. initSVG () {
  497. let width = this.renderArea[0].clientWidth;
  498. let height = this.renderArea[0].clientHeight;
  499. let marginLeft = this.renderArea[0].offsetLeft;
  500. this.svg.selectAll('*').remove();
  501. this.scaleX = d3.scale.linear()
  502. .domain([this.camera.left + this.camera.position.x, this.camera.right + this.camera.position.x])
  503. .range([0, width]);
  504. this.scaleY = d3.scale.linear()
  505. .domain([this.camera.bottom + this.camera.position.z, this.camera.top + this.camera.position.z])
  506. .range([height, 0]);
  507. this.xAxis = d3.svg.axis()
  508. .scale(this.scaleX)
  509. .orient('bottom')
  510. .innerTickSize(-height)
  511. .outerTickSize(1)
  512. .tickPadding(10)
  513. .ticks(width / 50);
  514. this.yAxis = d3.svg.axis()
  515. .scale(this.scaleY)
  516. .orient('left')
  517. .innerTickSize(-width)
  518. .outerTickSize(1)
  519. .tickPadding(10)
  520. .ticks(height / 20);
  521. this.elXAxis = this.svg.append('g')
  522. .attr('class', 'x axis')
  523. .attr('transform', `translate(${marginLeft}, ${height})`)
  524. .call(this.xAxis);
  525. this.elYAxis = this.svg.append('g')
  526. .attr('class', 'y axis')
  527. .attr('transform', `translate(${marginLeft}, 0)`)
  528. .call(this.yAxis);
  529. }
  530. addPoints (pointcloud, points) {
  531. if(points.numPoints === 0){
  532. return;
  533. }
  534. let entry = this.pointclouds.get(pointcloud);
  535. if(!entry){
  536. entry = new ProfileFakeOctree(pointcloud);
  537. this.pointclouds.set(pointcloud, entry);
  538. this.profileScene.add(entry);
  539. let materialChanged = () => {
  540. this.render();
  541. };
  542. materialChanged();
  543. pointcloud.material.addEventListener('material_property_changed', materialChanged);
  544. this.addEventListener("on_reset_once", () => {
  545. pointcloud.material.removeEventListener('material_property_changed', materialChanged);
  546. });
  547. }
  548. entry.addPoints(points);
  549. this.projectedBox.union(entry.projectedBox);
  550. if (this.autoFit && this.autoFitEnabled) {
  551. let width = this.renderArea[0].clientWidth;
  552. let height = this.renderArea[0].clientHeight;
  553. let size = this.projectedBox.getSize(new THREE.Vector3());
  554. let sx = width / size.x;
  555. let sy = height / size.z;
  556. let scale = Math.min(sx, sy);
  557. let center = this.projectedBox.getCenter(new THREE.Vector3());
  558. this.scale.set(scale, scale, 1);
  559. this.camera.position.copy(center);
  560. //console.log("camera: ", this.camera.position.toArray().join(", "));
  561. }
  562. //console.log(entry);
  563. this.render();
  564. let numPoints = 0;
  565. for (let [key, value] of this.pointclouds.entries()) {
  566. numPoints += value.points.reduce( (a, i) => a + i.numPoints, 0);
  567. }
  568. $(`#profile_num_points`).html(Utils.addCommas(numPoints));
  569. }
  570. reset () {
  571. this.lastReset = new Date().getTime();
  572. this.dispatchEvent({type: "on_reset_once"});
  573. this.removeEventListeners("on_reset_once");
  574. this.autoFit = true;
  575. this.projectedBox = new THREE.Box3();
  576. for(let [key, entry] of this.pointclouds){
  577. entry.dispose();
  578. }
  579. this.pointclouds.clear();
  580. this.mouseIsDown = false;
  581. this.mouse.set(0, 0);
  582. if(this.autoFitEnabled){
  583. this.scale.set(1, 1, 1);
  584. }
  585. this.pickSphere.visible = false;
  586. this.elRoot.find('#profileSelectionProperties').hide();
  587. this.render();
  588. }
  589. show () {
  590. this.elRoot.fadeIn();
  591. this.enabled = true;
  592. }
  593. hide () {
  594. this.elRoot.fadeOut();
  595. this.enabled = false;
  596. }
  597. updateScales () {
  598. let width = this.renderArea[0].clientWidth;
  599. let height = this.renderArea[0].clientHeight;
  600. let left = (-width / 2) / this.scale.x;
  601. let right = (+width / 2) / this.scale.x;
  602. let top = (+height / 2) / this.scale.y;
  603. let bottom = (-height / 2) / this.scale.y;
  604. this.camera.left = left;
  605. this.camera.right = right;
  606. this.camera.top = top;
  607. this.camera.bottom = bottom;
  608. this.camera.updateProjectionMatrix();
  609. this.scaleX.domain([this.camera.left + this.camera.position.x, this.camera.right + this.camera.position.x])
  610. .range([0, width]);
  611. this.scaleY.domain([this.camera.bottom + this.camera.position.z, this.camera.top + this.camera.position.z])
  612. .range([height, 0]);
  613. let marginLeft = this.renderArea[0].offsetLeft;
  614. this.xAxis.scale(this.scaleX)
  615. .orient('bottom')
  616. .innerTickSize(-height)
  617. .outerTickSize(1)
  618. .tickPadding(10)
  619. .ticks(width / 50);
  620. this.yAxis.scale(this.scaleY)
  621. .orient('left')
  622. .innerTickSize(-width)
  623. .outerTickSize(1)
  624. .tickPadding(10)
  625. .ticks(height / 20);
  626. this.elXAxis
  627. .attr('transform', `translate(${marginLeft}, ${height})`)
  628. .call(this.xAxis);
  629. this.elYAxis
  630. .attr('transform', `translate(${marginLeft}, 0)`)
  631. .call(this.yAxis);
  632. }
  633. requestScaleUpdate(){
  634. let threshold = 100;
  635. let allowUpdate = ((this.lastReset === undefined) || (this.lastScaleUpdate === undefined))
  636. || ((new Date().getTime() - this.lastReset) > threshold && (new Date().getTime() - this.lastScaleUpdate) > threshold);
  637. if(allowUpdate){
  638. this.updateScales();
  639. this.lastScaleUpdate = new Date().getTime();
  640. this.scaleUpdatePending = false;
  641. }else if(!this.scaleUpdatePending) {
  642. setTimeout(this.requestScaleUpdate.bind(this), 100);
  643. this.scaleUpdatePending = true;
  644. }
  645. }
  646. render () {
  647. let width = this.renderArea[0].clientWidth;
  648. let height = this.renderArea[0].clientHeight;
  649. let {renderer, pRenderer, camera, profileScene, scene} = this;
  650. let {scaleX, pickSphere} = this;
  651. renderer.setSize(width, height);
  652. renderer.setClearColor(0x000000, 0);
  653. renderer.clear(true, true, false);
  654. for(let pointcloud of this.pointclouds.keys()){
  655. let source = pointcloud.material;
  656. let target = this.pointclouds.get(pointcloud).material;
  657. copyMaterial(source, target);
  658. target.size = 2;
  659. }
  660. pRenderer.render(profileScene, camera, null);
  661. let radius = Math.abs(scaleX.invert(0) - scaleX.invert(5));
  662. if (radius === 0) {
  663. pickSphere.visible = false;
  664. } else {
  665. pickSphere.scale.set(radius, radius, radius);
  666. pickSphere.visible = true;
  667. }
  668. renderer.render(scene, camera);
  669. this.requestScaleUpdate();
  670. }
  671. };
  672. export class ProfileWindowController {
  673. constructor (viewer) {
  674. this.viewer = viewer;
  675. this.profileWindow = viewer.profileWindow;
  676. this.profile = null;
  677. this.numPoints = 0;
  678. this.threshold = 60 * 1000;
  679. this.rotateAmount = 10;
  680. this.scheduledRecomputeTime = null;
  681. this.enabled = true;
  682. this.requests = [];
  683. this._recompute = () => { this.recompute(); };
  684. this.viewer.addEventListener("scene_changed", e => {
  685. e.oldScene.removeEventListener("pointcloud_added", this._recompute);
  686. e.scene.addEventListener("pointcloud_added", this._recompute);
  687. });
  688. this.viewer.scene.addEventListener("pointcloud_added", this._recompute);
  689. $("#potree_profile_rotate_amount").val(parseInt(this.rotateAmount));
  690. $("#potree_profile_rotate_amount").on("input", (e) => {
  691. const str = $("#potree_profile_rotate_amount").val();
  692. if(!isNaN(str)){
  693. const value = parseFloat(str);
  694. this.rotateAmount = value;
  695. $("#potree_profile_rotate_amount").css("background-color", "")
  696. }else{
  697. $("#potree_profile_rotate_amount").css("background-color", "#ff9999")
  698. }
  699. });
  700. const rotate = (radians) => {
  701. const profile = this.profile;
  702. const points = profile.points;
  703. const start = points[0];
  704. const end = points[points.length - 1];
  705. const center = start.clone().add(end).multiplyScalar(0.5);
  706. const mMoveOrigin = new THREE.Matrix4().makeTranslation(-center.x, -center.y, -center.z);
  707. const mRotate = new THREE.Matrix4().makeRotationZ(radians);
  708. const mMoveBack = new THREE.Matrix4().makeTranslation(center.x, center.y, center.z);
  709. //const transform = mMoveOrigin.multiply(mRotate).multiply(mMoveBack);
  710. const transform = mMoveBack.multiply(mRotate).multiply(mMoveOrigin);
  711. const rotatedPoints = points.map( point => point.clone().applyMatrix4(transform) );
  712. this.profileWindow.autoFitEnabled = false;
  713. for(let i = 0; i < points.length; i++){
  714. profile.setPosition(i, rotatedPoints[i]);
  715. }
  716. }
  717. $("#potree_profile_rotate_cw").click( () => {
  718. const radians = THREE.Math.degToRad(this.rotateAmount);
  719. rotate(-radians);
  720. });
  721. $("#potree_profile_rotate_ccw").click( () => {
  722. const radians = THREE.Math.degToRad(this.rotateAmount);
  723. rotate(radians);
  724. });
  725. $("#potree_profile_move_forward").click( () => {
  726. const profile = this.profile;
  727. const points = profile.points;
  728. const start = points[0];
  729. const end = points[points.length - 1];
  730. const dir = end.clone().sub(start).normalize();
  731. const up = new THREE.Vector3(0, 0, 1);
  732. const forward = up.cross(dir);
  733. const move = forward.clone().multiplyScalar(profile.width / 2);
  734. this.profileWindow.autoFitEnabled = false;
  735. for(let i = 0; i < points.length; i++){
  736. profile.setPosition(i, points[i].clone().add(move));
  737. }
  738. });
  739. $("#potree_profile_move_backward").click( () => {
  740. const profile = this.profile;
  741. const points = profile.points;
  742. const start = points[0];
  743. const end = points[points.length - 1];
  744. const dir = end.clone().sub(start).normalize();
  745. const up = new THREE.Vector3(0, 0, 1);
  746. const forward = up.cross(dir);
  747. const move = forward.clone().multiplyScalar(-profile.width / 2);
  748. this.profileWindow.autoFitEnabled = false;
  749. for(let i = 0; i < points.length; i++){
  750. profile.setPosition(i, points[i].clone().add(move));
  751. }
  752. });
  753. }
  754. setProfile (profile) {
  755. if (this.profile !== null && this.profile !== profile) {
  756. this.profile.removeEventListener('marker_moved', this._recompute);
  757. this.profile.removeEventListener('marker_added', this._recompute);
  758. this.profile.removeEventListener('marker_removed', this._recompute);
  759. this.profile.removeEventListener('width_changed', this._recompute);
  760. }
  761. this.profile = profile;
  762. {
  763. this.profile.addEventListener('marker_moved', this._recompute);
  764. this.profile.addEventListener('marker_added', this._recompute);
  765. this.profile.addEventListener('marker_removed', this._recompute);
  766. this.profile.addEventListener('width_changed', this._recompute);
  767. }
  768. this.recompute();
  769. }
  770. reset () {
  771. this.profileWindow.reset();
  772. this.numPoints = 0;
  773. if (this.profile) {
  774. for (let request of this.requests) {
  775. request.cancel();
  776. }
  777. }
  778. }
  779. progressHandler (pointcloud, progress) {
  780. for (let segment of progress.segments) {
  781. this.profileWindow.addPoints(pointcloud, segment.points);
  782. this.numPoints += segment.points.numPoints;
  783. }
  784. }
  785. cancel () {
  786. for (let request of this.requests) {
  787. request.cancel();
  788. // request.finishLevelThenCancel();
  789. }
  790. this.requests = [];
  791. };
  792. finishLevelThenCancel(){
  793. for (let request of this.requests) {
  794. request.finishLevelThenCancel();
  795. }
  796. this.requests = [];
  797. }
  798. recompute () {
  799. if (!this.profile) {
  800. return;
  801. }
  802. if (this.scheduledRecomputeTime !== null && this.scheduledRecomputeTime > new Date().getTime()) {
  803. return;
  804. } else {
  805. this.scheduledRecomputeTime = new Date().getTime() + 100;
  806. }
  807. this.scheduledRecomputeTime = null;
  808. this.reset();
  809. for (let pointcloud of this.viewer.scene.pointclouds.filter(p => p.visible)) {
  810. let request = pointcloud.getPointsInProfile(this.profile, null, {
  811. 'onProgress': (event) => {
  812. if (!this.enabled) {
  813. return;
  814. }
  815. this.progressHandler(pointcloud, event.points);
  816. if (this.numPoints > this.threshold) {
  817. this.finishLevelThenCancel();
  818. }
  819. },
  820. 'onFinish': (event) => {
  821. if (!this.enabled) {
  822. }
  823. },
  824. 'onCancel': () => {
  825. if (!this.enabled) {
  826. }
  827. }
  828. });
  829. this.requests.push(request);
  830. }
  831. }
  832. };