PointCloudArena4D.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. import * as THREE from "../../libs/three.js/build/three.module.js";
  2. import {PointCloudTree, PointCloudTreeNode} from "../PointCloudTree.js";
  3. import {PointCloudMaterial} from "../materials/PointCloudMaterial.js";
  4. import {PointSizeType, ClipTask, TreeType} from "../defines.js";
  5. import {Utils} from "../utils.js";
  6. export class PointCloudArena4DNode extends PointCloudTreeNode {
  7. constructor () {
  8. super();
  9. this.left = null;
  10. this.right = null;
  11. this.sceneNode = null;
  12. this.kdtree = null;
  13. }
  14. getNumPoints () {
  15. return this.geometryNode.numPoints;
  16. }
  17. isLoaded () {
  18. return true;
  19. }
  20. isTreeNode () {
  21. return true;
  22. }
  23. isGeometryNode () {
  24. return false;
  25. }
  26. getLevel () {
  27. return this.geometryNode.level;
  28. }
  29. getBoundingSphere () {
  30. return this.geometryNode.boundingSphere;
  31. }
  32. getBoundingBox () {
  33. return this.geometryNode.boundingBox;
  34. }
  35. toTreeNode (child) {
  36. let geometryNode = null;
  37. if (this.left === child) {
  38. geometryNode = this.left;
  39. } else if (this.right === child) {
  40. geometryNode = this.right;
  41. }
  42. if (!geometryNode.loaded) {
  43. return;
  44. }
  45. let node = new PointCloudArena4DNode();
  46. let sceneNode = THREE.PointCloud(geometryNode.geometry, this.kdtree.material);
  47. sceneNode.visible = false;
  48. node.kdtree = this.kdtree;
  49. node.geometryNode = geometryNode;
  50. node.sceneNode = sceneNode;
  51. node.parent = this;
  52. node.left = this.geometryNode.left;
  53. node.right = this.geometryNode.right;
  54. }
  55. getChildren () {
  56. let children = [];
  57. if (this.left) {
  58. children.push(this.left);
  59. }
  60. if (this.right) {
  61. children.push(this.right);
  62. }
  63. return children;
  64. }
  65. };
  66. export class PointCloudArena4D extends PointCloudTree{
  67. constructor (geometry) {
  68. super();
  69. this.root = null;
  70. if (geometry.root) {
  71. this.root = geometry.root;
  72. } else {
  73. geometry.addEventListener('hierarchy_loaded', () => {
  74. this.root = geometry.root;
  75. });
  76. }
  77. this.visiblePointsTarget = 2 * 1000 * 1000;
  78. this.minimumNodePixelSize = 150;
  79. this.position.sub(geometry.offset);
  80. this.updateMatrix();
  81. this.numVisibleNodes = 0;
  82. this.numVisiblePoints = 0;
  83. this.boundingBoxNodes = [];
  84. this.loadQueue = [];
  85. this.visibleNodes = [];
  86. this.pcoGeometry = geometry;
  87. this.boundingBox = this.pcoGeometry.boundingBox;
  88. this.boundingSphere = this.pcoGeometry.boundingSphere;
  89. this.material = new PointCloudMaterial({vertexColors: THREE.VertexColors, size: 0.05, treeType: TreeType.KDTREE});
  90. this.material.sizeType = PointSizeType.ATTENUATED;
  91. this.material.size = 0.05;
  92. this.profileRequests = [];
  93. this.name = '';
  94. }
  95. getBoundingBoxWorld () {
  96. this.updateMatrixWorld(true);
  97. let box = this.boundingBox;
  98. let transform = this.matrixWorld;
  99. let tBox = Utils.computeTransformedBoundingBox(box, transform);
  100. return tBox;
  101. };
  102. setName (name) {
  103. if (this.name !== name) {
  104. this.name = name;
  105. this.dispatchEvent({type: 'name_changed', name: name, pointcloud: this});
  106. }
  107. }
  108. getName () {
  109. return this.name;
  110. }
  111. getLevel () {
  112. return this.level;
  113. }
  114. toTreeNode (geometryNode, parent) {
  115. let node = new PointCloudArena4DNode();
  116. let sceneNode = new THREE.Points(geometryNode.geometry, this.material);
  117. sceneNode.frustumCulled = false;
  118. sceneNode.onBeforeRender = (_this, scene, camera, geometry, material, group) => {
  119. if (material.program) {
  120. _this.getContext().useProgram(material.program.program);
  121. if (material.program.getUniforms().map.level) {
  122. let level = geometryNode.getLevel();
  123. material.uniforms.level.value = level;
  124. material.program.getUniforms().map.level.setValue(_this.getContext(), level);
  125. }
  126. if (this.visibleNodeTextureOffsets && material.program.getUniforms().map.vnStart) {
  127. let vnStart = this.visibleNodeTextureOffsets.get(node);
  128. material.uniforms.vnStart.value = vnStart;
  129. material.program.getUniforms().map.vnStart.setValue(_this.getContext(), vnStart);
  130. }
  131. if (material.program.getUniforms().map.pcIndex) {
  132. let i = node.pcIndex ? node.pcIndex : this.visibleNodes.indexOf(node);
  133. material.uniforms.pcIndex.value = i;
  134. material.program.getUniforms().map.pcIndex.setValue(_this.getContext(), i);
  135. }
  136. }
  137. };
  138. node.geometryNode = geometryNode;
  139. node.sceneNode = sceneNode;
  140. node.pointcloud = this;
  141. node.left = geometryNode.left;
  142. node.right = geometryNode.right;
  143. if (!parent) {
  144. this.root = node;
  145. this.add(sceneNode);
  146. } else {
  147. parent.sceneNode.add(sceneNode);
  148. if (parent.left === geometryNode) {
  149. parent.left = node;
  150. } else if (parent.right === geometryNode) {
  151. parent.right = node;
  152. }
  153. }
  154. let disposeListener = function () {
  155. parent.sceneNode.remove(node.sceneNode);
  156. if (parent.left === node) {
  157. parent.left = geometryNode;
  158. } else if (parent.right === node) {
  159. parent.right = geometryNode;
  160. }
  161. };
  162. geometryNode.oneTimeDisposeHandlers.push(disposeListener);
  163. return node;
  164. }
  165. updateMaterial (material, visibleNodes, camera, renderer) {
  166. material.fov = camera.fov * (Math.PI / 180);
  167. material.screenWidth = renderer.domElement.clientWidth;
  168. material.screenHeight = renderer.domElement.clientHeight;
  169. material.spacing = this.pcoGeometry.spacing;
  170. material.near = camera.near;
  171. material.far = camera.far;
  172. // reduce shader source updates by setting maxLevel slightly higher than actually necessary
  173. if (this.maxLevel > material.levels) {
  174. material.levels = this.maxLevel + 2;
  175. }
  176. // material.uniforms.octreeSize.value = this.boundingBox.size().x;
  177. let bbSize = this.boundingBox.getSize(new THREE.Vector3());
  178. material.bbSize = [bbSize.x, bbSize.y, bbSize.z];
  179. }
  180. updateVisibleBounds () {
  181. }
  182. hideDescendants (object) {
  183. let stack = [];
  184. for (let i = 0; i < object.children.length; i++) {
  185. let child = object.children[i];
  186. if (child.visible) {
  187. stack.push(child);
  188. }
  189. }
  190. while (stack.length > 0) {
  191. let child = stack.shift();
  192. child.visible = false;
  193. if (child.boundingBoxNode) {
  194. child.boundingBoxNode.visible = false;
  195. }
  196. for (let i = 0; i < child.children.length; i++) {
  197. let childOfChild = child.children[i];
  198. if (childOfChild.visible) {
  199. stack.push(childOfChild);
  200. }
  201. }
  202. }
  203. }
  204. updateMatrixWorld (force) {
  205. // node.matrixWorld.multiplyMatrices( node.parent.matrixWorld, node.matrix );
  206. if (this.matrixAutoUpdate === true) this.updateMatrix();
  207. if (this.matrixWorldNeedsUpdate === true || force === true) {
  208. if (this.parent === undefined) {
  209. this.matrixWorld.copy(this.matrix);
  210. } else {
  211. this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix);
  212. }
  213. this.matrixWorldNeedsUpdate = false;
  214. force = true;
  215. }
  216. }
  217. nodesOnRay (nodes, ray) {
  218. let nodesOnRay = [];
  219. let _ray = ray.clone();
  220. for (let i = 0; i < nodes.length; i++) {
  221. let node = nodes[i];
  222. let sphere = node.getBoundingSphere().clone().applyMatrix4(node.sceneNode.matrixWorld);
  223. // TODO Unused: let box = node.getBoundingBox().clone().applyMatrix4(node.sceneNode.matrixWorld);
  224. if (_ray.intersectsSphere(sphere)) {
  225. nodesOnRay.push(node);
  226. }
  227. // if(_ray.isIntersectionBox(box)){
  228. // nodesOnRay.push(node);
  229. // }
  230. }
  231. return nodesOnRay;
  232. }
  233. pick(viewer, camera, ray, params = {}){
  234. let renderer = viewer.renderer;
  235. let pRenderer = viewer.pRenderer;
  236. performance.mark("pick-start");
  237. let getVal = (a, b) => a !== undefined ? a : b;
  238. let pickWindowSize = getVal(params.pickWindowSize, 17);
  239. let pickOutsideClipRegion = getVal(params.pickOutsideClipRegion, false);
  240. let size = renderer.getSize(new THREE.Vector2());
  241. let width = Math.ceil(getVal(params.width, size.width));
  242. let height = Math.ceil(getVal(params.height, size.height));
  243. let pointSizeType = getVal(params.pointSizeType, this.material.pointSizeType);
  244. let pointSize = getVal(params.pointSize, this.material.size);
  245. let nodes = this.nodesOnRay(this.visibleNodes, ray);
  246. if (nodes.length === 0) {
  247. return null;
  248. }
  249. if (!this.pickState) {
  250. let scene = new THREE.Scene();
  251. let material = new PointCloudMaterial();
  252. material.activeAttributeName = "indices";
  253. let renderTarget = new THREE.WebGLRenderTarget(
  254. 1, 1,
  255. { minFilter: THREE.LinearFilter,
  256. magFilter: THREE.NearestFilter,
  257. format: THREE.RGBAFormat }
  258. );
  259. this.pickState = {
  260. renderTarget: renderTarget,
  261. material: material,
  262. scene: scene
  263. };
  264. };
  265. let pickState = this.pickState;
  266. let pickMaterial = pickState.material;
  267. { // update pick material
  268. pickMaterial.pointSizeType = pointSizeType;
  269. pickMaterial.shape = this.material.shape;
  270. pickMaterial.size = pointSize;
  271. pickMaterial.uniforms.minSize.value = this.material.uniforms.minSize.value;
  272. pickMaterial.uniforms.maxSize.value = this.material.uniforms.maxSize.value;
  273. pickMaterial.classification = this.material.classification;
  274. if(params.pickClipped){
  275. pickMaterial.clipBoxes = this.material.clipBoxes;
  276. if(this.material.clipTask === ClipTask.HIGHLIGHT){
  277. pickMaterial.clipTask = ClipTask.NONE;
  278. }else{
  279. pickMaterial.clipTask = this.material.clipTask;
  280. }
  281. }else{
  282. pickMaterial.clipBoxes = [];
  283. }
  284. this.updateMaterial(pickMaterial, nodes, camera, renderer);
  285. }
  286. pickState.renderTarget.setSize(width, height);
  287. let pixelPos = new THREE.Vector2(params.x, params.y);
  288. let gl = renderer.getContext();
  289. gl.enable(gl.SCISSOR_TEST);
  290. gl.scissor(
  291. parseInt(pixelPos.x - (pickWindowSize - 1) / 2),
  292. parseInt(pixelPos.y - (pickWindowSize - 1) / 2),
  293. parseInt(pickWindowSize), parseInt(pickWindowSize));
  294. renderer.state.buffers.depth.setTest(pickMaterial.depthTest);
  295. renderer.state.buffers.depth.setMask(pickMaterial.depthWrite);
  296. renderer.state.setBlending(THREE.NoBlending);
  297. renderer.clearTarget(pickState.renderTarget, true, true, true);
  298. { // RENDER
  299. renderer.setRenderTarget(pickState.renderTarget);
  300. gl.clearColor(0, 0, 0, 0);
  301. renderer.clearTarget( pickState.renderTarget, true, true, true );
  302. let tmp = this.material;
  303. this.material = pickMaterial;
  304. pRenderer.renderOctree(this, nodes, camera, pickState.renderTarget);
  305. this.material = tmp;
  306. }
  307. let clamp = (number, min, max) => Math.min(Math.max(min, number), max);
  308. let x = parseInt(clamp(pixelPos.x - (pickWindowSize - 1) / 2, 0, width));
  309. let y = parseInt(clamp(pixelPos.y - (pickWindowSize - 1) / 2, 0, height));
  310. let w = parseInt(Math.min(x + pickWindowSize, width) - x);
  311. let h = parseInt(Math.min(y + pickWindowSize, height) - y);
  312. let pixelCount = w * h;
  313. let buffer = new Uint8Array(4 * pixelCount);
  314. gl.readPixels(x, y, pickWindowSize, pickWindowSize, gl.RGBA, gl.UNSIGNED_BYTE, buffer);
  315. renderer.setRenderTarget(null);
  316. renderer.state.reset();
  317. renderer.setScissorTest(false);
  318. gl.disable(gl.SCISSOR_TEST);
  319. let pixels = buffer;
  320. let ibuffer = new Uint32Array(buffer.buffer);
  321. // find closest hit inside pixelWindow boundaries
  322. let min = Number.MAX_VALUE;
  323. let hits = [];
  324. for (let u = 0; u < pickWindowSize; u++) {
  325. for (let v = 0; v < pickWindowSize; v++) {
  326. let offset = (u + v * pickWindowSize);
  327. let distance = Math.pow(u - (pickWindowSize - 1) / 2, 2) + Math.pow(v - (pickWindowSize - 1) / 2, 2);
  328. let pcIndex = pixels[4 * offset + 3];
  329. pixels[4 * offset + 3] = 0;
  330. let pIndex = ibuffer[offset];
  331. if(!(pcIndex === 0 && pIndex === 0) && (pcIndex !== undefined) && (pIndex !== undefined)){
  332. let hit = {
  333. pIndex: pIndex,
  334. pcIndex: pcIndex,
  335. distanceToCenter: distance
  336. };
  337. if(params.all){
  338. hits.push(hit);
  339. }else{
  340. if(hits.length > 0){
  341. if(distance < hits[0].distanceToCenter){
  342. hits[0] = hit;
  343. }
  344. }else{
  345. hits.push(hit);
  346. }
  347. }
  348. }
  349. }
  350. }
  351. for(let hit of hits){
  352. let point = {};
  353. if (!nodes[hit.pcIndex]) {
  354. return null;
  355. }
  356. let node = nodes[hit.pcIndex];
  357. let pc = node.sceneNode;
  358. let geometry = node.geometryNode.geometry;
  359. for(let attributeName in geometry.attributes){
  360. let attribute = geometry.attributes[attributeName];
  361. if (attributeName === 'position') {
  362. let x = attribute.array[3 * hit.pIndex + 0];
  363. let y = attribute.array[3 * hit.pIndex + 1];
  364. let z = attribute.array[3 * hit.pIndex + 2];
  365. let position = new THREE.Vector3(x, y, z);
  366. position.applyMatrix4(pc.matrixWorld);
  367. point[attributeName] = position;
  368. } else if (attributeName === 'indices') {
  369. } else {
  370. //if (values.itemSize === 1) {
  371. // point[attribute.name] = values.array[hit.pIndex];
  372. //} else {
  373. // let value = [];
  374. // for (let j = 0; j < values.itemSize; j++) {
  375. // value.push(values.array[values.itemSize * hit.pIndex + j]);
  376. // }
  377. // point[attribute.name] = value;
  378. //}
  379. }
  380. }
  381. hit.point = point;
  382. }
  383. performance.mark("pick-end");
  384. performance.measure("pick", "pick-start", "pick-end");
  385. if(params.all){
  386. return hits.map(hit => hit.point);
  387. }else{
  388. if(hits.length === 0){
  389. return null;
  390. }else{
  391. return hits[0].point;
  392. }
  393. }
  394. }
  395. computeVisibilityTextureData(nodes){
  396. if(exports.measureTimings) performance.mark("computeVisibilityTextureData-start");
  397. let data = new Uint8Array(nodes.length * 3);
  398. let visibleNodeTextureOffsets = new Map();
  399. // copy array
  400. nodes = nodes.slice();
  401. // sort by level and number
  402. let sort = function (a, b) {
  403. let la = a.geometryNode.level;
  404. let lb = b.geometryNode.level;
  405. let na = a.geometryNode.number;
  406. let nb = b.geometryNode.number;
  407. if (la !== lb) return la - lb;
  408. if (na < nb) return -1;
  409. if (na > nb) return 1;
  410. return 0;
  411. };
  412. nodes.sort(sort);
  413. let visibleNodeNames = [];
  414. for (let i = 0; i < nodes.length; i++) {
  415. visibleNodeNames.push(nodes[i].geometryNode.number);
  416. }
  417. for (let i = 0; i < nodes.length; i++) {
  418. let node = nodes[i];
  419. visibleNodeTextureOffsets.set(node, i);
  420. let b1 = 0; // children
  421. let b2 = 0; // offset to first child
  422. let b3 = 0; // split
  423. if (node.geometryNode.left && visibleNodeNames.indexOf(node.geometryNode.left.number) > 0) {
  424. b1 += 1;
  425. b2 = visibleNodeNames.indexOf(node.geometryNode.left.number) - i;
  426. }
  427. if (node.geometryNode.right && visibleNodeNames.indexOf(node.geometryNode.right.number) > 0) {
  428. b1 += 2;
  429. b2 = (b2 === 0) ? visibleNodeNames.indexOf(node.geometryNode.right.number) - i : b2;
  430. }
  431. if (node.geometryNode.split === 'X') {
  432. b3 = 1;
  433. } else if (node.geometryNode.split === 'Y') {
  434. b3 = 2;
  435. } else if (node.geometryNode.split === 'Z') {
  436. b3 = 4;
  437. }
  438. data[i * 3 + 0] = b1;
  439. data[i * 3 + 1] = b2;
  440. data[i * 3 + 2] = b3;
  441. }
  442. if(exports.measureTimings){
  443. performance.mark("computeVisibilityTextureData-end");
  444. performance.measure("render.computeVisibilityTextureData", "computeVisibilityTextureData-start", "computeVisibilityTextureData-end");
  445. }
  446. return {
  447. data: data,
  448. offsets: visibleNodeTextureOffsets
  449. };
  450. }
  451. get progress () {
  452. if (this.pcoGeometry.root) {
  453. return exports.numNodesLoading > 0 ? 0 : 1;
  454. } else {
  455. return 0;
  456. }
  457. }
  458. };