ProfileRequest.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. import * as THREE from "../libs/three.js/build/three.module.js";
  2. import {Points} from "./Points.js";
  3. export class ProfileData {
  4. constructor (profile) {
  5. this.profile = profile;
  6. this.segments = [];
  7. this.boundingBox = new THREE.Box3();
  8. for (let i = 0; i < profile.points.length - 1; i++) {
  9. let start = profile.points[i];
  10. let end = profile.points[i + 1];
  11. let startGround = new THREE.Vector3(start.x, start.y, 0);
  12. let endGround = new THREE.Vector3(end.x, end.y, 0);
  13. let center = new THREE.Vector3().addVectors(endGround, startGround).multiplyScalar(0.5);
  14. let length = startGround.distanceTo(endGround);
  15. let side = new THREE.Vector3().subVectors(endGround, startGround).normalize();
  16. let up = new THREE.Vector3(0, 0, 1);
  17. let forward = new THREE.Vector3().crossVectors(side, up).normalize();
  18. let N = forward;
  19. let cutPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(N, startGround);
  20. let halfPlane = new THREE.Plane().setFromNormalAndCoplanarPoint(side, center);
  21. let segment = {
  22. start: start,
  23. end: end,
  24. cutPlane: cutPlane,
  25. halfPlane: halfPlane,
  26. length: length,
  27. points: new Points()
  28. };
  29. this.segments.push(segment);
  30. }
  31. }
  32. size () {
  33. let size = 0;
  34. for (let segment of this.segments) {
  35. size += segment.points.numPoints;
  36. }
  37. return size;
  38. }
  39. };
  40. export class ProfileRequest {
  41. constructor (pointcloud, profile, maxDepth, callback) {
  42. this.pointcloud = pointcloud;
  43. this.profile = profile;
  44. this.maxDepth = maxDepth || Number.MAX_VALUE;
  45. this.callback = callback;
  46. this.temporaryResult = new ProfileData(this.profile);
  47. this.pointsServed = 0;
  48. this.highestLevelServed = 0;
  49. this.priorityQueue = new BinaryHeap(function (x) { return 1 / x.weight; });
  50. this.initialize();
  51. }
  52. initialize () {
  53. this.priorityQueue.push({node: this.pointcloud.pcoGeometry.root, weight: Infinity});
  54. };
  55. // traverse the node and add intersecting descendants to queue
  56. traverse (node) {
  57. let stack = [];
  58. for (let i = 0; i < 8; i++) {
  59. let child = node.children[i];
  60. if (child && this.pointcloud.nodeIntersectsProfile(child, this.profile)) {
  61. stack.push(child);
  62. }
  63. }
  64. while (stack.length > 0) {
  65. let node = stack.pop();
  66. let weight = node.boundingSphere.radius;
  67. this.priorityQueue.push({node: node, weight: weight});
  68. // add children that intersect the cutting plane
  69. if (node.level < this.maxDepth) {
  70. for (let i = 0; i < 8; i++) {
  71. let child = node.children[i];
  72. if (child && this.pointcloud.nodeIntersectsProfile(child, this.profile)) {
  73. stack.push(child);
  74. }
  75. }
  76. }
  77. }
  78. }
  79. update(){
  80. if(!this.updateGeneratorInstance){
  81. this.updateGeneratorInstance = this.updateGenerator();
  82. }
  83. let result = this.updateGeneratorInstance.next();
  84. if(result.done){
  85. this.updateGeneratorInstance = null;
  86. }
  87. }
  88. * updateGenerator(){
  89. // load nodes in queue
  90. // if hierarchy expands, also load nodes from expanded hierarchy
  91. // once loaded, add data to this.points and remove node from queue
  92. // only evaluate 1-50 nodes per frame to maintain responsiveness
  93. let start = performance.now();
  94. let maxNodesPerUpdate = 1;
  95. let intersectedNodes = [];
  96. for (let i = 0; i < Math.min(maxNodesPerUpdate, this.priorityQueue.size()); i++) {
  97. let element = this.priorityQueue.pop();
  98. let node = element.node;
  99. if(node.level > this.maxDepth){
  100. continue;
  101. }
  102. if (node.loaded) {
  103. // add points to result
  104. intersectedNodes.push(node);
  105. exports.lru.touch(node);
  106. this.highestLevelServed = Math.max(node.getLevel(), this.highestLevelServed);
  107. var geom = node.pcoGeometry;
  108. var hierarchyStepSize = geom ? geom.hierarchyStepSize : 1;
  109. var doTraverse = node.getLevel() === 0 ||
  110. (node.level % hierarchyStepSize === 0 && node.hasChildren);
  111. if (doTraverse) {
  112. this.traverse(node);
  113. }
  114. } else {
  115. node.load();
  116. this.priorityQueue.push(element);
  117. }
  118. }
  119. if (intersectedNodes.length > 0) {
  120. for(let done of this.getPointsInsideProfile(intersectedNodes, this.temporaryResult)){
  121. if(!done){
  122. //console.log("updateGenerator yields");
  123. yield false;
  124. }
  125. }
  126. if (this.temporaryResult.size() > 100) {
  127. this.pointsServed += this.temporaryResult.size();
  128. this.callback.onProgress({request: this, points: this.temporaryResult});
  129. this.temporaryResult = new ProfileData(this.profile);
  130. }
  131. }
  132. if (this.priorityQueue.size() === 0) {
  133. // we're done! inform callback and remove from pending requests
  134. if (this.temporaryResult.size() > 0) {
  135. this.pointsServed += this.temporaryResult.size();
  136. this.callback.onProgress({request: this, points: this.temporaryResult});
  137. this.temporaryResult = new ProfileData(this.profile);
  138. }
  139. this.callback.onFinish({request: this});
  140. let index = this.pointcloud.profileRequests.indexOf(this);
  141. if (index >= 0) {
  142. this.pointcloud.profileRequests.splice(index, 1);
  143. }
  144. }
  145. yield true;
  146. };
  147. * getAccepted(numPoints, node, matrix, segment, segmentDir, points, totalMileage){
  148. let checkpoint = performance.now();
  149. let accepted = new Uint32Array(numPoints);
  150. let mileage = new Float64Array(numPoints);
  151. let acceptedPositions = new Float32Array(numPoints * 3);
  152. let numAccepted = 0;
  153. let pos = new THREE.Vector3();
  154. let svp = new THREE.Vector3();
  155. let view = new Float32Array(node.geometry.attributes.position.array);
  156. for (let i = 0; i < numPoints; i++) {
  157. pos.set(
  158. view[i * 3 + 0],
  159. view[i * 3 + 1],
  160. view[i * 3 + 2]);
  161. pos.applyMatrix4(matrix);
  162. let distance = Math.abs(segment.cutPlane.distanceToPoint(pos));
  163. let centerDistance = Math.abs(segment.halfPlane.distanceToPoint(pos));
  164. if (distance < this.profile.width / 2 && centerDistance < segment.length / 2) {
  165. svp.subVectors(pos, segment.start);
  166. let localMileage = segmentDir.dot(svp);
  167. accepted[numAccepted] = i;
  168. mileage[numAccepted] = localMileage + totalMileage;
  169. points.boundingBox.expandByPoint(pos);
  170. pos.sub(this.pointcloud.position);
  171. acceptedPositions[3 * numAccepted + 0] = pos.x;
  172. acceptedPositions[3 * numAccepted + 1] = pos.y;
  173. acceptedPositions[3 * numAccepted + 2] = pos.z;
  174. numAccepted++;
  175. }
  176. if((i % 1000) === 0){
  177. let duration = performance.now() - checkpoint;
  178. if(duration > 4){
  179. //console.log(`getAccepted yield after ${duration}ms`);
  180. yield false;
  181. checkpoint = performance.now();
  182. }
  183. }
  184. }
  185. accepted = accepted.subarray(0, numAccepted);
  186. mileage = mileage.subarray(0, numAccepted);
  187. acceptedPositions = acceptedPositions.subarray(0, numAccepted * 3);
  188. //let end = performance.now();
  189. //let duration = end - start;
  190. //console.log("accepted duration ", duration)
  191. //console.log(`getAccepted finished`);
  192. yield [accepted, mileage, acceptedPositions];
  193. }
  194. * getPointsInsideProfile(nodes, target){
  195. let checkpoint = performance.now();
  196. let totalMileage = 0;
  197. let pointsProcessed = 0;
  198. for (let segment of target.segments) {
  199. for (let node of nodes) {
  200. let numPoints = node.numPoints;
  201. let geometry = node.geometry;
  202. if(!numPoints){
  203. continue;
  204. }
  205. { // skip if current node doesn't intersect current segment
  206. let bbWorld = node.boundingBox.clone().applyMatrix4(this.pointcloud.matrixWorld);
  207. let bsWorld = bbWorld.getBoundingSphere(new THREE.Sphere());
  208. let start = new THREE.Vector3(segment.start.x, segment.start.y, bsWorld.center.z);
  209. let end = new THREE.Vector3(segment.end.x, segment.end.y, bsWorld.center.z);
  210. let closest = new THREE.Line3(start, end).closestPointToPoint(bsWorld.center, true, new THREE.Vector3());
  211. let distance = closest.distanceTo(bsWorld.center);
  212. let intersects = (distance < (bsWorld.radius + target.profile.width));
  213. if(!intersects){
  214. continue;
  215. }
  216. }
  217. //{// DEBUG
  218. // console.log(node.name);
  219. // let boxHelper = new Potree.Box3Helper(node.getBoundingBox());
  220. // boxHelper.matrixAutoUpdate = false;
  221. // boxHelper.matrix.copy(viewer.scene.pointclouds[0].matrixWorld);
  222. // viewer.scene.scene.add(boxHelper);
  223. //}
  224. let sv = new THREE.Vector3().subVectors(segment.end, segment.start).setZ(0);
  225. let segmentDir = sv.clone().normalize();
  226. let points = new Points();
  227. let nodeMatrix = new THREE.Matrix4().makeTranslation(...node.boundingBox.min.toArray());
  228. let matrix = new THREE.Matrix4().multiplyMatrices(
  229. this.pointcloud.matrixWorld, nodeMatrix);
  230. pointsProcessed = pointsProcessed + numPoints;
  231. let accepted = null;
  232. let mileage = null;
  233. let acceptedPositions = null;
  234. for(let result of this.getAccepted(numPoints, node, matrix, segment, segmentDir, points,totalMileage)){
  235. if(!result){
  236. let duration = performance.now() - checkpoint;
  237. //console.log(`getPointsInsideProfile yield after ${duration}ms`);
  238. yield false;
  239. checkpoint = performance.now();
  240. }else{
  241. [accepted, mileage, acceptedPositions] = result;
  242. }
  243. }
  244. let duration = performance.now() - checkpoint;
  245. if(duration > 4){
  246. //console.log(`getPointsInsideProfile yield after ${duration}ms`);
  247. yield false;
  248. checkpoint = performance.now();
  249. }
  250. points.data.position = acceptedPositions;
  251. let relevantAttributes = Object.keys(geometry.attributes).filter(a => !["position", "indices"].includes(a));
  252. for(let attributeName of relevantAttributes){
  253. let attribute = geometry.attributes[attributeName];
  254. let numElements = attribute.array.length / numPoints;
  255. if(numElements !== parseInt(numElements)){
  256. debugger;
  257. }
  258. let Type = attribute.array.constructor;
  259. let filteredBuffer = new Type(numElements * accepted.length);
  260. let source = attribute.array;
  261. let target = filteredBuffer;
  262. for(let i = 0; i < accepted.length; i++){
  263. let index = accepted[i];
  264. let start = index * numElements;
  265. let end = start + numElements;
  266. let sub = source.subarray(start, end);
  267. target.set(sub, i * numElements);
  268. }
  269. points.data[attributeName] = filteredBuffer;
  270. }
  271. points.data['mileage'] = mileage;
  272. points.numPoints = accepted.length;
  273. segment.points.add(points);
  274. }
  275. totalMileage += segment.length;
  276. }
  277. for (let segment of target.segments) {
  278. target.boundingBox.union(segment.points.boundingBox);
  279. }
  280. //console.log(`getPointsInsideProfile finished`);
  281. yield true;
  282. };
  283. finishLevelThenCancel () {
  284. if (this.cancelRequested) {
  285. return;
  286. }
  287. this.maxDepth = this.highestLevelServed;
  288. this.cancelRequested = true;
  289. //console.log(`maxDepth: ${this.maxDepth}`);
  290. };
  291. cancel () {
  292. this.callback.onCancel();
  293. this.priorityQueue = new BinaryHeap(function (x) { return 1 / x.weight; });
  294. let index = this.pointcloud.profileRequests.indexOf(this);
  295. if (index >= 0) {
  296. this.pointcloud.profileRequests.splice(index, 1);
  297. }
  298. };
  299. }