├── .gitignore ├── .vscode ├── keybindings.json ├── launch.json └── settings.json ├── AHN5.html ├── LICENSE ├── README.md ├── ca13.html ├── extra_materials_trimble_sitn.html ├── gare.html ├── heidentor.html ├── image_360_test.html ├── index.html ├── libs ├── BinaryHeap │ └── BinaryHeap.js ├── brotli │ ├── BUILD │ ├── LICENSE │ ├── WORKSPACE │ ├── decode.js │ ├── decode.min.js │ ├── decode_test.js │ └── polyfill.js ├── dat.gui │ ├── dat.gui.css │ ├── dat.gui.module.js │ └── dat.gui.module.js.map ├── gl-matrix.js ├── glslang │ ├── glslang.js │ └── glslang.wasm ├── json5 │ ├── LICENSE.md.txt │ └── json5.js ├── laz-perf-old │ ├── LICENSE_lazperf.txt │ ├── LICENSE_plasio.txt │ ├── js │ │ └── laslaz.js │ └── workers │ │ ├── laz-loader-worker.js │ │ └── laz-perf.js ├── laz-perf │ ├── laz-perf.js │ └── laz-perf.wasm ├── proj4js │ ├── .gitignore │ ├── AUTHORS │ ├── LICENSE.md │ ├── README.md │ ├── REFERENCES.md │ ├── proj4-src.js │ └── proj4.js ├── range-select │ └── RangeSelect.js ├── stats.js │ ├── LICENSE │ ├── README.md │ ├── stats.js │ ├── stats.min.js │ └── stats.module.js └── tween │ ├── LICENSE │ ├── README.md │ └── tween.esm.js ├── lidar_mobile_t1.laz.html ├── potree3.html ├── prototyping ├── RangeSelect.html ├── RangeSelect.js ├── converter_3.html ├── csSubsample.js ├── csSubsampleAveraged.js ├── encoding │ ├── encoding.mjs │ ├── evaluate.mjs │ ├── json5.mjs │ ├── metadata.json │ ├── r.csv │ └── r0.csv ├── encoding_2 │ ├── encoding.mjs │ ├── index.html │ └── json5.mjs ├── proto.js └── prototyping.html ├── resources ├── F ├── images │ ├── 360.png │ ├── 360.svg │ └── background.jpg ├── models │ └── stanford_bunny_reduced.obj └── potree.css ├── src ├── InputHandler.js ├── Potree.js ├── core │ ├── Geometry.js │ ├── RenderTarget.js │ └── StandardMaterial.js ├── defines.js ├── init.js ├── interaction │ └── measure.js ├── math │ ├── Box3.js │ ├── Frustum.js │ ├── Line3.js │ ├── Matrix4.js │ ├── PMath.js │ ├── Plane.js │ ├── Ray.js │ ├── Sphere.js │ ├── Vector3.js │ ├── Vector4.js │ └── math.js ├── misc │ ├── EventDispatcher.js │ ├── GLBLoader.js │ ├── GLBLoaderWorker.js │ ├── Gradients.js │ ├── OBJLoader.js │ ├── WorkerPool.js │ ├── glbvoxels │ │ └── GlbVoxelLoader.js │ └── potree3loader │ │ ├── renderVoxelsLOD.js │ │ ├── renderVoxelsLOD_points.js │ │ └── renderVoxelsLOD_quads.js ├── modules │ ├── 3DTiles │ │ ├── 3DTiles.js │ │ ├── 3DTiles.wgsl │ │ └── 3DTilesLoader.js │ ├── LasLoader │ │ └── LasLoader.js │ ├── attributes │ │ └── mappings.js │ ├── drawCommands │ │ ├── renderBoundingBoxes.js │ │ ├── renderBoundingSpheres.js │ │ ├── renderBoxes.js │ │ ├── renderLines copy.js │ │ ├── renderLines.js │ │ ├── renderMeshes.js │ │ ├── renderPoints.js │ │ ├── renderQuads.js │ │ ├── renderSpheres.js │ │ └── renderVoxels.js │ ├── geometries │ │ ├── cube.js │ │ ├── points.js │ │ ├── sphere.js │ │ └── wave.js │ ├── gui_dat │ │ ├── gui.js │ │ └── gui_compute_demo.js │ ├── lines │ │ ├── Lines.js │ │ └── render.js │ ├── mesh │ │ ├── Mesh.js │ │ ├── NormalMaterial.js │ │ ├── PhongMaterial.js │ │ ├── TriangleColorMaterial.js │ │ ├── WireframeMaterial.js │ │ └── renderMesh.js │ ├── parametric_functions │ │ └── ParametricFunction.js │ ├── points │ │ └── Points.js │ ├── progressive_loader │ │ ├── LasDecoder_worker.js │ │ ├── ProgressiveLoader.js │ │ ├── ProgressivePointCloud.js │ │ └── progressive_worker.js │ ├── quads │ │ ├── Quads.js │ │ └── renderQuads.js │ ├── sidebar │ │ ├── icons │ │ │ ├── LICENSE │ │ │ ├── circle.svg │ │ │ ├── distance.svg │ │ │ ├── dotdotdot.svg │ │ │ ├── height.svg │ │ │ ├── help.svg │ │ │ ├── home.svg │ │ │ ├── material.svg │ │ │ ├── measure.svg │ │ │ ├── point.svg │ │ │ ├── profile.svg │ │ │ └── rgb.svg │ │ ├── panel_appearance.js │ │ ├── panel_attributes.js │ │ ├── panel_hovered.js │ │ ├── panel_infos.js │ │ ├── panel_measurements.js │ │ ├── panel_scene.js │ │ ├── sidebar.css.js │ │ └── sidebar.js │ └── toolbar │ │ ├── icons │ │ ├── circle.svg │ │ ├── distance.svg │ │ ├── dotdotdot.svg │ │ ├── point.svg │ │ ├── profile.svg │ │ └── rgb.svg │ │ ├── toolbar.css.js │ │ ├── toolbar.html │ │ └── toolbar.js ├── navigation │ ├── OrbitControls.js │ ├── PotreeControls.js │ └── StationaryControls.js ├── potree │ ├── ChunkedBuffer.js │ ├── EDL.js │ ├── LRU.js │ ├── PointAttributes.js │ ├── PointCloudMaterial.js │ ├── arbitrary_attributes │ │ ├── renderPoints_arbitrary_attributes.js │ │ └── sh_scalar.js │ ├── dilate.js │ ├── hqs_normalize.js │ ├── images │ │ ├── Images360.js │ │ └── SphereMap.js │ ├── octree │ │ ├── PointCloudOctree.js │ │ ├── PointCloudOctreeNode.js │ │ ├── loader │ │ │ ├── CopcLoader.js │ │ │ ├── DecoderWorker_Potree2Batch.js │ │ │ ├── DecoderWorker_brotli.js │ │ │ ├── DecoderWorker_copc.js │ │ │ ├── DecoderWorker_default.js │ │ │ ├── DecoderWorker_points.js │ │ │ └── PotreeLoader.js │ │ ├── loader_v3 │ │ │ ├── DecoderWorker.js │ │ │ ├── DecoderWorker_points.js │ │ │ ├── DecoderWorker_voxels.js │ │ │ ├── Potree3Loader.js │ │ │ ├── loadPoints.js │ │ │ └── loadVoxels.js │ │ ├── mappings │ │ │ └── map_rgb.js │ │ ├── materials │ │ │ ├── mat_elevation.js │ │ │ └── mat_rgb.js │ │ ├── octree.wgsl │ │ └── pipelineGenerator.js │ ├── renderPointsOctree.js │ ├── renderPointsOctreeBundledVBO.js │ ├── renderQuads.js │ └── renderQuadsOctree.js ├── prototyping │ ├── LoadWorker.js │ ├── cube.js │ ├── generate_lod.js │ ├── generate_voxels.js │ ├── generate_voxels_compute │ │ ├── chunking.js │ │ ├── common.js │ │ ├── downsampling.js │ │ ├── generate_voxels_compute.js │ │ ├── remove_indices.js │ │ ├── renderVoxels.js │ │ ├── renderVoxelsLOD.js │ │ └── transferTriangles.js │ ├── generate_voxels_compute_2021.08.18 │ │ ├── chunking.js │ │ ├── common.js │ │ ├── downsampling.js │ │ ├── generate_voxels_compute.js │ │ └── renderVoxels.js │ ├── generate_voxels_compute_2021.08.28 │ │ ├── common.js │ │ ├── downsampling.js │ │ ├── generate_voxels_compute.js │ │ ├── renderVoxels.js │ │ └── renderVoxelsLOD.js │ ├── generate_voxels_compute_2021.09.03 │ │ ├── chunking.js │ │ ├── common.js │ │ ├── downsampling.js │ │ ├── generate_voxels_compute.js │ │ ├── remove_indices.js │ │ ├── renderVoxels.js │ │ ├── renderVoxelsLOD.js │ │ └── transferTriangles.js │ ├── generate_voxels_gpu copy.js │ ├── generate_voxels_gpu.js │ ├── generate_voxels_gpu_render.js │ ├── glbloader │ │ └── loadGLB.js │ ├── lasinfo.mjs │ ├── load.html │ ├── rect.js │ ├── renderPoints.js │ ├── shaders.js │ ├── simplify.js │ ├── simplify_0.js │ └── textures.js ├── renderer │ ├── Renderer.js │ ├── Timer.js │ ├── fillBuffer.js │ ├── readPixels.js │ └── writeBuffer.js ├── scene │ ├── Camera.js │ ├── PointLight.js │ ├── Scene.js │ └── SceneNode.js └── utils.js └── tools ├── ply2potree2.mjs └── shuffleLas.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | pointclouds/test/ 2 | nocommit/ 3 | temp/ 4 | resources/pointclouds/heidentor/hierarchy.bin 5 | resources/pointclouds/heidentor/metadata.json 6 | resources/pointclouds/heidentor/octree.bin 7 | resources/pointclouds/eclepens/hierarchy.bin 8 | resources/pointclouds/eclepens/metadata.json 9 | resources/pointclouds/eclepens/octree.bin 10 | resources/pointclouds/CA13/hierarchy.bin 11 | resources/pointclouds/CA13/metadata.json 12 | resources/pointclouds/CA13/octree.bin 13 | src/prototyping/laslaz/bunny_small.laz 14 | src/prototyping/laslaz/ot_35120B4310A_1.laz 15 | test/dragon_vrip.obj 16 | test/dragon_vrip.ply 17 | test/vr_controller_vive_1_5.obj 18 | src/prototyping/laslaz/heidentor.laz 19 | resources/models/ruins.glb 20 | resources/models/anita_mui.glb 21 | resources/models/australia_gas.glb 22 | resources/models/lausanne_wood_thing.glb 23 | resources/models/lion.glb 24 | resources/models/xyzrgb_dragon.ply 25 | resources/pointclouds/ca13_sample/hierarchy.bin 26 | resources/pointclouds/ca13_sample/metadata.json 27 | resources/pointclouds/ca13_sample/octree.bin 28 | resources/pointclouds/66_73/hierarchy.bin 29 | resources/pointclouds/66_73/metadata.json 30 | resources/pointclouds/66_73/octree.bin 31 | resources/models/vcity_sitn.glb 32 | prototyping/lion.las 33 | prototyping/color.bin 34 | prototyping/positionf32.bin 35 | prototyping/ot_35120D7323B_1.las 36 | prototyping/ot_35120D7323B_1.laz 37 | prototyping/artifacts/* 38 | resources/pointclouds/ot_35120C7102A_1/hierarchy.bin 39 | resources/pointclouds/ot_35120C7102A_1/metadata.json 40 | resources/pointclouds/ot_35120C7102A_1/octree.bin 41 | resources/pointclouds/octree_cuda/hierarchy.bin 42 | resources/pointclouds/octree_cuda/metadata.json 43 | resources/pointclouds/octree_cuda/octree.bin 44 | resources/models/australia_gas_30M.glb 45 | resources/models/sitnmesh.glb 46 | resources/models/Big5M.glb 47 | autzen-classified.copc.laz 48 | las14_extra_attributes.las 49 | sofi.copc.laz 50 | resources/pointclouds/aerial_lidar2019_2524_1197_laz14_dip/ 51 | resources/pointclouds/las14_extra_attributes/ 52 | resources/pointclouds/lifeboat/ 53 | resources/pointclouds/lifeboat_potree2/ 54 | resources/G 55 | prototyping/encoding/encoded_i32.bin 56 | prototyping/encoding/encoded_u8_l2.7z 57 | prototyping/encoding/encoded_u8_l2.bin 58 | prototyping/encoding/encoded_u8.bin 59 | prototyping/encoding/encoded.bin 60 | prototyping/encoding/r075.7z 61 | prototyping/encoding/r075.batch 62 | prototyping/encoding_2/three.js 63 | # prototyping/encoding/r0.csv 64 | # prototyping/encoding/r.csv 65 | prototyping/encoding_2/buffer_diff_sao.7z 66 | prototyping/encoding_2/buffer_diff_sao.bin 67 | prototyping/encoding_2/buffer_diff_sao.brotli 68 | prototyping/encoding_2/buffer_diff.7z 69 | prototyping/encoding_2/buffer_diff.bin 70 | prototyping/encoding_2/buffer_interleaved_u8.7z 71 | prototyping/encoding_2/buffer_interleaved_u8.bin 72 | prototyping/encoding_2/buffer_r.7z 73 | prototyping/encoding_2/buffer_r.bin 74 | prototyping/encoding_2/buffer_soa_u8.7z 75 | prototyping/encoding_2/buffer_soa_u8.bin 76 | prototyping/encoding_2/G 77 | resources/Drive3/ 78 | resources/pointclouds/retz_potree3/ 79 | resources/pointclouds/mozartplatz/ 80 | resources/E 81 | resources/pointclouds/AHN5_2023_C_01DZ2.potree 82 | resources/pointclouds/eclepens_reference.potree 83 | resources/pointclouds/eclepens.potree 84 | resources/pointclouds/lion.potree 85 | resources/pointclouds/meroe.potree 86 | resources/pointclouds/sitn_subset.potree 87 | resources/pointclouds/morro_bay.potree 88 | resources/pointclouds/ot_35120A4202A_1_1.potree 89 | resources/test/coordinates_new.txt 90 | resources/test/coordinates.txt 91 | -------------------------------------------------------------------------------- /.vscode/keybindings.json: -------------------------------------------------------------------------------- 1 | // Place your key bindings in this file to overwrite the defaults 2 | [ 3 | { 4 | "key": "ctrl+l", 5 | "command": "editor.action.deleteLines", 6 | "when": "editorTextFocus && !editorReadonly" 7 | },{ 8 | "key": "ctrl+shift+i", 9 | "command": "editor.action.toggleRenderWhitespace" 10 | },{ 11 | "key": "ctrl+d", 12 | "command": "editor.action.copyLinesDownAction" 13 | },{ 14 | "key": "alt+2", 15 | "command": "type", 16 | "args": { 17 | "text": "`" 18 | } 19 | } 20 | ] -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Program", 9 | "program": "${workspaceFolder}/prototyping/encoding_2/encoding.mjs", 10 | "request": "launch", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "type": "node" 15 | }, 16 | { 17 | "type": "node", 18 | "request": "launch", 19 | "name": "Launch Program", 20 | "program": "${workspaceFolder}\\tools\\shuffleLas.mjs" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.editor.enablePreview": false, 3 | "files.associations": { 4 | "*.cs": "cpp", 5 | "*.wgsl": "cpp", 6 | "*.vs": "cpp", 7 | "*.fs": "cpp", 8 | "random": "cpp", 9 | "chrono": "cpp", 10 | "functional": "cpp", 11 | "optional": "cpp", 12 | "system_error": "cpp", 13 | "type_traits": "cpp", 14 | "xlocmon": "cpp", 15 | "xlocnum": "cpp", 16 | "xtr1common": "cpp", 17 | "array": "cpp", 18 | "istream": "cpp", 19 | "utility": "cpp", 20 | "format": "cpp", 21 | "memory": "cpp", 22 | "tuple": "cpp", 23 | "xlocmes": "cpp", 24 | "xloctime": "cpp", 25 | "xutility": "cpp" 26 | }, 27 | "files.trimTrailingWhitespace": false, 28 | "editor.fontSize": 28, 29 | "editor.autoIndent": "advanced", 30 | "editor.detectIndentation": false, 31 | "editor.insertSpaces": false, 32 | "editor.minimap.enabled": false, 33 | "editor.autoClosingBrackets": "never", 34 | "editor.formatOnType": false, 35 | "editor.acceptSuggestionOnEnter": "off", 36 | "editor.acceptSuggestionOnCommitCharacter": false, 37 | "editor.mouseWheelZoom": true, 38 | "editor.renderWhitespace": "all", 39 | "C_Cpp.errorSquiggles": "Disabled", 40 | "editor.autoClosingQuotes": "never", 41 | "editor.autoSurround": "never", 42 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | * Playing around with WebGPU and learning how to rewrite [Potree](https://github.com/potree/potree/) with it. 4 | * Tested with Chrome Version 92. 5 | * Online demo: https://potree.org/temporary/potree2_ca13/ 6 | 7 | 8 | 9 | Contact: mschuetz@potree.org 10 | 11 | # References 12 | 13 | ## own 14 | 15 | * [Rendering point clouds with compute shaders](https://github.com/m-schuetz/compute_rasterizer)
16 | In some cases, compute shaders can render point clouds multiply times faster than native primitives like GL_POINTS or "point-list". 17 | * [Fast Out-of-Core Octree Generation for Massive Point Clouds](https://www.cg.tuwien.ac.at/research/publications/2020/SCHUETZ-2020-MPC/)
18 | Generating LOD structures can be slow and time consuming. Using hierarchical counting sort, we can efficiently organize the input into small batches that can be processed in parallel. 19 | * [Potree: Rendering Large Point Clouds in Web Browsers](https://www.cg.tuwien.ac.at/research/publications/2016/SCHUETZ-2016-POT/)
20 | Rendering billions of points in web browsers using an octree acceleration structure. 21 | 22 | 23 | ## more 24 | 25 | * [QSplat](http://graphics.stanford.edu/papers/qsplat/)
26 | First approach to render large point clouds (or meshes transformed to point clouds) in real-time using a bounding-sphere hierarchy. 27 | * [Layered Point Clouds](http://publications.crs4.it/pubdocs/2004/GM04c/spbg04-lpc.pdf)
28 | Basis of most commonly used point cloud acceleration structures, including Potree, Entwine, Arena4D, etc. The key feature is a GPU-friendly data structure that stores subsamples comprising thousands of points in each node. 29 | * [High-Quality Surface Splatting on Today’s GPUs](https://www.graphics.rwth-aachen.de/media/papers/splatting1.pdf)
30 | Anti-Aliasing for point clouds by blending overlapping fragments within a certain range together. Results are similar to mip mapping or anisotropic filtering for textured meshes, which don't work for point clouds because they are colored by vertex rather than texture. 31 | * ["A GPGPU-based Pipeline for Accelerated Rendering of Point Clouds."](https://core.ac.uk/download/pdf/295554194.pdf)
32 | Compute shader rendering using OpenCL. 33 | * ["View-warped Multi-view Soft Shadowing for Local Area Lights"](http://jcgt.org/published/0007/03/01/)
34 | Uses compute shaders for point-based depth map rendering. 35 | 36 | # Further Credits and Resources 37 | 38 | * [WebGPU Samples](http://austin-eng.com/webgpu-samples/?wgsl=1#animometer). Github: [austinEng/webgpu-samples](https://github.com/austinEng/webgpu-samples) 39 | * [WebGPU x-mas card](http://trierlab.com/VClab/webtek/xmas/) 40 | 41 | # LICENSE 42 | 43 | This prototype of Potree 2 uses the AGPL license: https://www.gnu.org/licenses/agpl-3.0.en.html. -------------------------------------------------------------------------------- /libs/brotli/BUILD: -------------------------------------------------------------------------------- 1 | package( 2 | default_visibility = ["//visibility:public"], 3 | ) 4 | 5 | licenses(["notice"]) # MIT 6 | 7 | load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library") 8 | 9 | # Not a real polyfill. Do NOT use for anything, but tests. 10 | closure_js_library( 11 | name = "polyfill", 12 | srcs = ["polyfill.js"], 13 | suppress = [ 14 | "JSC_INVALID_OPERAND_TYPE", 15 | "JSC_MISSING_JSDOC", 16 | "JSC_STRICT_INEXISTENT_PROPERTY", 17 | "JSC_TYPE_MISMATCH", 18 | "JSC_UNKNOWN_EXPR_TYPE", 19 | ], 20 | ) 21 | 22 | # Do NOT use this artifact; it is for test purposes only. 23 | closure_js_library( 24 | name = "decode", 25 | srcs = ["decode.js"], 26 | suppress = [ 27 | "JSC_DUP_VAR_DECLARATION", 28 | "JSC_USELESS_BLOCK", 29 | ], 30 | deps = [":polyfill"], 31 | ) 32 | 33 | load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_test") 34 | 35 | closure_js_test( 36 | name = "all_tests", 37 | srcs = ["decode_test.js"], 38 | deps = [ 39 | ":decode", 40 | ":polyfill", 41 | "@io_bazel_rules_closure//closure/library:testing", 42 | ], 43 | ) 44 | -------------------------------------------------------------------------------- /libs/brotli/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /libs/brotli/WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "org_brotli_js") 2 | 3 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") 4 | 5 | git_repository( 6 | name = "io_bazel_rules_closure", 7 | commit = "29ec97e7c85d607ba9e41cab3993fbb13f812c4b", 8 | remote = "https://github.com/bazelbuild/rules_closure.git", 9 | ) 10 | 11 | load("@io_bazel_rules_closure//closure:defs.bzl", "closure_repositories") 12 | closure_repositories() 13 | -------------------------------------------------------------------------------- /libs/brotli/decode_test.js: -------------------------------------------------------------------------------- 1 | goog.require('goog.testing.asserts'); 2 | goog.require('goog.testing.jsunit'); 3 | 4 | /** 5 | * @param {!Int8Array} bytes 6 | * @return {string} 7 | */ 8 | function bytesToString(bytes) { 9 | return String.fromCharCode.apply(null, new Uint16Array(bytes)); 10 | } 11 | 12 | function testMetadata() { 13 | assertEquals("", bytesToString(BrotliDecode(Int8Array.from([1, 11, 0, 42, 3])))); 14 | } 15 | 16 | function testEmpty() { 17 | assertEquals("", bytesToString(BrotliDecode(Int8Array.from([6])))); 18 | assertEquals("", bytesToString(BrotliDecode(Int8Array.from([0x81, 1])))); 19 | } 20 | 21 | function testBaseDictWord() { 22 | var input = Int8Array.from([ 23 | 0x1b, 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0xe3, 0xb4, 0x0d, 0x00, 0x00, 24 | 0x07, 0x5b, 0x26, 0x31, 0x40, 0x02, 0x00, 0xe0, 0x4e, 0x1b, 0x41, 0x02 25 | ]); 26 | /** @type {!Int8Array} */ 27 | var output = BrotliDecode(input); 28 | assertEquals("time", bytesToString(output)); 29 | } 30 | 31 | function testBlockCountMessage() { 32 | var input = Int8Array.from([ 33 | 0x1b, 0x0b, 0x00, 0x11, 0x01, 0x8c, 0xc1, 0xc5, 0x0d, 0x08, 0x00, 0x22, 34 | 0x65, 0xe1, 0xfc, 0xfd, 0x22, 0x2c, 0xc4, 0x00, 0x00, 0x38, 0xd8, 0x32, 35 | 0x89, 0x01, 0x12, 0x00, 0x00, 0x77, 0xda, 0x04, 0x10, 0x42, 0x00, 0x00, 0x00 36 | ]); 37 | /** @type {!Int8Array} */ 38 | var output = BrotliDecode(input); 39 | assertEquals("aabbaaaaabab", bytesToString(output)); 40 | } 41 | 42 | function testCompressedUncompressedShortCompressedSmallWindow() { 43 | var input = Int8Array.from([ 44 | 0x21, 0xf4, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xa7, 0x6d, 0x00, 0x00, 45 | 0x38, 0xd8, 0x32, 0x89, 0x01, 0x12, 0x00, 0x00, 0x77, 0xda, 0x34, 0x7b, 46 | 0xdb, 0x50, 0x80, 0x02, 0x80, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x31, 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x4e, 0xdb, 0x00, 0x00, 0x70, 0xb0, 48 | 0x65, 0x12, 0x03, 0x24, 0x00, 0x00, 0xee, 0xb4, 0x11, 0x24, 0x00 49 | ]); 50 | /** @type {!Int8Array} */ 51 | var output = BrotliDecode(input); 52 | assertEquals( 53 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 54 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 55 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 56 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 57 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 58 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 59 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 60 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 61 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 62 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 63 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 64 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 65 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 66 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 67 | "aaaaaaaaaaaaaabbbbbbbbbb", bytesToString(output)); 68 | } 69 | 70 | function testIntactDistanceRingBuffer0() { 71 | var input = Int8Array.from([ 72 | 0x1b, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x80, 0xe3, 0xb4, 0x0d, 0x00, 0x00, 73 | 0x07, 0x5b, 0x26, 0x31, 0x40, 0x02, 0x00, 0xe0, 0x4e, 0x1b, 0xa1, 0x80, 74 | 0x20, 0x00 75 | ]); 76 | /** @type {!Int8Array} */ 77 | var output = BrotliDecode(input); 78 | assertEquals("himselfself", bytesToString(output)); 79 | } 80 | -------------------------------------------------------------------------------- /libs/brotli/polyfill.js: -------------------------------------------------------------------------------- 1 | if (!Int32Array.__proto__.from) { 2 | Object.defineProperty(Int32Array.__proto__, 'from', { 3 | value: function(obj) { 4 | obj = Object(obj); 5 | if (!obj['length']) { 6 | return new this(0); 7 | } 8 | var typed_array = new this(obj.length); 9 | for(var i = 0; i < typed_array.length; i++) { 10 | typed_array[i] = obj[i]; 11 | } 12 | return typed_array; 13 | } 14 | }); 15 | } 16 | 17 | if (!Array.prototype.copyWithin) { 18 | Array.prototype.copyWithin = function(target, start, end) { 19 | var O = Object(this); 20 | var len = O.length >>> 0; 21 | var to = target | 0; 22 | var from = start | 0; 23 | var count = Math.min(Math.min(end | 0, len) - from, len - to); 24 | var direction = 1; 25 | if (from < to && to < (from + count)) { 26 | direction = -1; 27 | from += count - 1; 28 | to += count - 1; 29 | } 30 | while (count > 0) { 31 | O[to] = O[from]; 32 | from += direction; 33 | to += direction; 34 | count--; 35 | } 36 | return O; 37 | }; 38 | } 39 | 40 | if (!Array.prototype.fill) { 41 | Object.defineProperty(Array.prototype, 'fill', { 42 | value: function(value, start, end) { 43 | end = end | 0; 44 | var O = Object(this); 45 | var k = start | 0; 46 | while (k < end) { 47 | O[k] = value; 48 | k++; 49 | } 50 | return O; 51 | } 52 | }); 53 | } 54 | 55 | if (!Int8Array.prototype.copyWithin) { 56 | Int8Array.prototype.copyWithin = Array.prototype.copyWithin; 57 | } 58 | 59 | if (!Int8Array.prototype.fill) { 60 | Int8Array.prototype.fill = Array.prototype.fill; 61 | } 62 | 63 | if (!Int32Array.prototype.fill) { 64 | Int32Array.prototype.fill = Array.prototype.fill; 65 | } 66 | -------------------------------------------------------------------------------- /libs/glslang/glslang.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-schuetz/Potree2/b00172bddfb37db79d7734ed99b69e2a1a9e9e09/libs/glslang/glslang.wasm -------------------------------------------------------------------------------- /libs/json5/LICENSE.md.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2012-2018 Aseem Kishore, and [others]. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | [others]: https://github.com/json5/json5/contributors 24 | -------------------------------------------------------------------------------- /libs/laz-perf-old/LICENSE_plasio.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Uday Verma, uday.karan@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /libs/laz-perf/laz-perf.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-schuetz/Potree2/b00172bddfb37db79d7734ed99b69e2a1a9e9e09/libs/laz-perf/laz-perf.wasm -------------------------------------------------------------------------------- /libs/proj4js/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | node_modules 3 | .c9revisions 4 | coverage 5 | projs.js 6 | .DS_STORE 7 | dist 8 | -------------------------------------------------------------------------------- /libs/proj4js/AUTHORS: -------------------------------------------------------------------------------- 1 | Mike Adair 2 | Richard Greenwood 3 | Calvin Metcalf 4 | Richard Marsden (http://www.winwaed.com) 5 | #credit for 6 | #src/projCode/gnom.js 7 | #src/projCode/cea.js 8 | T. Mittan 9 | #credit for 10 | #src/projCode/eqdc.js 11 | #src/projCode/equi.js 12 | #src/projCode/merc.js 13 | #src/projCode/mill.js 14 | #src/projCode/omerc.js 15 | #src/projCode/ortho.js 16 | #src/projCode/poly.js 17 | #src/projCode/poly.js 18 | D. Steinwand 19 | #credit for 20 | #src/projCode/merc.js 21 | #src/projCode/laea.js 22 | #src/projCode/moll.js 23 | S. Nelson 24 | #credit for 25 | #src/projCode/moll.js -------------------------------------------------------------------------------- /libs/proj4js/LICENSE.md: -------------------------------------------------------------------------------- 1 | ## Proj4js -- Javascript reprojection library. 2 | 3 | Authors: 4 | - Mike Adair madairATdmsolutions.ca 5 | - Richard Greenwood richATgreenwoodmap.com 6 | - Didier Richard didier.richardATign.fr 7 | - Stephen Irons stephen.ironsATclear.net.nz 8 | - Olivier Terral oterralATgmail.com 9 | - Calvin Metcalf cmetcalfATappgeo.com 10 | 11 | Copyright (c) 2014, Mike Adair, Richard Greenwood, Didier Richard, Stephen Irons, Olivier Terral and Calvin Metcalf 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a 14 | copy of this software and associated documentation files (the "Software"), 15 | to deal in the Software without restriction, including without limitation 16 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 17 | and/or sell copies of the Software, and to permit persons to whom the 18 | Software is furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included 21 | in all copies or substantial portions of the Software. 22 | 23 | _THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 24 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 26 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 29 | DEALINGS IN THE SOFTWARE._ 30 | -------------------------------------------------------------------------------- /libs/proj4js/REFERENCES.md: -------------------------------------------------------------------------------- 1 | 1. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological Survey 2 | Professional Paper 1395 (Supersedes USGS Bulletin 1532), 3 | United States Government Printing Office, Washington D.C., 1987. 4 | Accessed: 2016-05-09. https://pubs.er.usgs.gov/publication/pp1395 5 | 2. Snyder, John P. and Voxland, Philip M., "An Album of Map Projections", 6 | U.S. Geological Survey Professional Paper 1453 , 7 | United State Government Printing Office, Washington D.C., 1989. 8 | Accessed: 2016-05-09. https://pubs.er.usgs.gov/publication/pp1453 9 | 3. "Cartographic Projection Procedures for the UNIX Environment- 10 | A User's Manual" by Gerald I. Evenden, 11 | USGS Open File Report 90-284 and Release 4 Interim Reports (2003). 12 | Accessed: 2016-06-09. http://www2.bren.ucsb.edu/~frew/ESM264/private/proj_manual.pdf 13 | 4. Snyder, John P., "Flattening the Earth - 14 | Two Thousand Years of Map Projections", Univ. Chicago Press, 1993 15 | 5. Wolfram Mathworld "Gnomonic Projection" 16 | http://mathworld.wolfram.com/GnomonicProjection.html 17 | Accessed: 12th November 2009 18 | 6. "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder, 19 | The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355. 20 | 7. Snyder, John P., "Map Projections--A Working Manual", U.S. Geological 21 | Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United 22 | State Government Printing Office, Washington D.C., 1987. 23 | Access date 2016-05-09. https://pubs.er.usgs.gov/publication/pp1395 24 | 8. "Software Documentation for GCTP General Cartographic Transformation 25 | Package", U.S. Geological Survey National Mapping Division, May 1982. 26 | 9. Department of Land and Survey Technical Circular 1973/32 27 | http://www.linz.govt.nz/docs/miscellaneous/nz-map-definition.pdf 28 | 10. OSG Technical Report 4.1 29 | http://www.linz.govt.nz/docs/miscellaneous/nzmg.pdf 30 | 11. Formules et constantes pour le Calcul pour la 31 | projection cylindrique conforme à axe oblique et pour la transformation entre 32 | des systèmes de référence. 33 | http://www.swisstopo.admin.ch/internet/swisstopo/fr/home/topics/survey/sys/refsys/switzerland.parsysrelated1.31216.downloadList.77004.DownloadFile.tmp/swissprojectionfr.pdf 34 | -------------------------------------------------------------------------------- /libs/stats.js/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009-2016 stats.js authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /libs/stats.js/README.md: -------------------------------------------------------------------------------- 1 | stats.js 2 | ======== 3 | 4 | #### JavaScript Performance Monitor #### 5 | 6 | This class provides a simple info box that will help you monitor your code performance. 7 | 8 | * **FPS** Frames rendered in the last second. The higher the number the better. 9 | * **MS** Milliseconds needed to render a frame. The lower the number the better. 10 | * **MB** MBytes of allocated memory. (Run Chrome with `--enable-precise-memory-info`) 11 | * **CUSTOM** User-defined panel support. 12 | 13 | 14 | ### Screenshots ### 15 | 16 | ![fps.png](https://raw.githubusercontent.com/mrdoob/stats.js/master/files/fps.png) 17 | ![ms.png](https://raw.githubusercontent.com/mrdoob/stats.js/master/files/ms.png) 18 | ![mb.png](https://raw.githubusercontent.com/mrdoob/stats.js/master/files/mb.png) 19 | ![custom.png](https://raw.githubusercontent.com/mrdoob/stats.js/master/files/custom.png) 20 | 21 | 22 | ### Installation ### 23 | ```bash 24 | npm install stats.js 25 | ``` 26 | 27 | ### Usage ### 28 | 29 | ```javascript 30 | var stats = new Stats(); 31 | stats.showPanel( 1 ); // 0: fps, 1: ms, 2: mb, 3+: custom 32 | document.body.appendChild( stats.dom ); 33 | 34 | function animate() { 35 | 36 | stats.begin(); 37 | 38 | // monitored code goes here 39 | 40 | stats.end(); 41 | 42 | requestAnimationFrame( animate ); 43 | 44 | } 45 | 46 | requestAnimationFrame( animate ); 47 | ``` 48 | 49 | 50 | ### Bookmarklet ### 51 | 52 | You can add this code to any page using the following bookmarklet: 53 | 54 | ```javascript 55 | javascript:(function(){var script=document.createElement('script');script.onload=function(){var stats=new Stats();document.body.appendChild(stats.dom);requestAnimationFrame(function loop(){stats.update();requestAnimationFrame(loop)});};script.src='//mrdoob.github.io/stats.js/build/stats.min.js';document.head.appendChild(script);})() 56 | ``` 57 | -------------------------------------------------------------------------------- /libs/stats.js/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | (function(f,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"===typeof define&&define.amd?define(e):f.Stats=e()})(this,function(){var f=function(){function e(a){c.appendChild(a.dom);return a}function u(a){for(var d=0;d=g+1E3&&(r.update(1E3*a/(c-g),100),g=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/ 4 | 1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){k=this.end()},domElement:c,setMode:u}};f.Panel=function(e,f,l){var c=Infinity,k=0,g=Math.round,a=g(window.devicePixelRatio||1),r=80*a,h=48*a,t=3*a,v=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=h;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,h);b.fillStyle=f;b.fillText(e,t,v); 5 | b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(h,w){c=Math.min(c,h);k=Math.max(k,h);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=f;b.fillText(g(h)+" "+e+" ("+g(c)+"-"+g(k)+")",t,v);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,g((1-h/w)*p))}}};return f}); 6 | -------------------------------------------------------------------------------- /libs/tween/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2012 Tween.js authors. 4 | 5 | Easing equations Copyright (c) 2001 Robert Penner http://robertpenner.com/easing/ 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. -------------------------------------------------------------------------------- /prototyping/RangeSelect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | 36 | 37 | 38 | 55 | 56 | 57 | 58 | Normal Slider 59 | 60 | 61 | 62 | Range Slider 63 | 64 | 65 | 66 | 67 | 68 | 69 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /prototyping/RangeSelect.js: -------------------------------------------------------------------------------- 1 | 2 | // see https://stackoverflow.com/a/31083391/913630 3 | 4 | export class RangeSelect extends HTMLElement{ 5 | constructor(){ 6 | super(); 7 | 8 | this.style.position = "relative"; 9 | this.style.width = "100%"; 10 | this.style.userSelect = "none"; 11 | 12 | this.range = [0, 100]; 13 | this.value = [10, 60]; 14 | 15 | this.innerHTML = ` 16 | 69 | 70 | 71 | 72 | 73 | 74 | `; 75 | 76 | this.elSliders = this.querySelectorAll("input"); 77 | this.elBackground = this.querySelector(`.background`); 78 | this.elSelected = this.querySelector(`.selected`); 79 | 80 | this.elSliders[0].addEventListener("input", this.onInput.bind(this)); 81 | this.elSliders[1].addEventListener("input", this.onInput.bind(this)); 82 | 83 | this.onInput(); 84 | } 85 | 86 | setRange(min, max){ 87 | this.range = [min, max]; 88 | 89 | this.elSliders[0].min = min; 90 | this.elSliders[0].max = max; 91 | this.elSliders[1].min = min; 92 | this.elSliders[1].max = max; 93 | } 94 | 95 | setValue(min, max){ 96 | this.value = [min, max]; 97 | 98 | this.elSliders[0].value = min; 99 | this.elSliders[1].value = max; 100 | } 101 | 102 | onInput(event){ 103 | 104 | let values = [this.elSliders[0].value, this.elSliders[1].value]; 105 | let min = Math.min(...values); 106 | let max = Math.max(...values); 107 | 108 | let rangeSize = this.range[1] - this.range[0]; 109 | let u_min = (min - this.range[0]) / rangeSize; 110 | let u_max = (max - this.range[0]) / rangeSize; 111 | 112 | this.elSelected.style.left = `${100 * u_min}%`; 113 | this.elSelected.style.width = `${100 * (u_max - u_min)}%`; 114 | 115 | this.value = [min, max]; 116 | 117 | if(event){ 118 | event.stopPropagation(); 119 | } 120 | 121 | this.dispatchEvent(new InputEvent("input")); 122 | 123 | return false; 124 | } 125 | } 126 | 127 | customElements.define('range-select', RangeSelect); -------------------------------------------------------------------------------- /prototyping/encoding/evaluate.mjs: -------------------------------------------------------------------------------- 1 | 2 | import * as fs from 'fs'; 3 | const fsp = fs.promises; 4 | import JSON5 from './json5.mjs'; 5 | 6 | const path = "./results_ca21_bunds.json"; 7 | // const path = "./results_retz.json"; 8 | // const path = "./results_palmyra.json"; 9 | // const path = "./results_saint_roman.json"; 10 | 11 | // returns benchmark result with median total duration from group 12 | function getMedian(group){ 13 | group.sort( (a, b) => a["duration_total"] - b["duration_total"]); 14 | 15 | let medianIndex = Math.floor(group.length / 2); 16 | 17 | return group[medianIndex]; 18 | } 19 | 20 | let txtJson = (await fsp.readFile(path)).toString(); 21 | let json = JSON5.parse(txtJson); 22 | 23 | let benchmarks = json.benchmarks; 24 | 25 | { // some sanity checks 26 | let setDevice = new Set(benchmarks.map(b => b.device)) 27 | let setNumPoints = new Set(benchmarks.map(b => b.points)) 28 | 29 | let abort = (msg) => { 30 | console.error(msg); 31 | process.exit(1); 32 | }; 33 | 34 | if(setDevice.size !== 1) abort("results from different cuda devices. expecting result file to originate from a single device."); 35 | if(setNumPoints.size !== 1) abort("results with different point count. expected results originate from single point cloud"); 36 | } 37 | 38 | console.log("#######################"); 39 | console.log("## BENCHMARK RESULTS ##"); 40 | console.log("#######################"); 41 | 42 | console.log(`reporting results from median time of each strategy`); 43 | console.log(`device: ${benchmarks[0].device}`); 44 | console.log(`#points: ${benchmarks[0].points.toLocaleString()}`); 45 | console.log(); 46 | 47 | let groups = new Map(); 48 | let strategies = []; 49 | 50 | // group by sampling strategy 51 | for(let benchmark of benchmarks){ 52 | if(!groups.has(benchmark.strategy)){ 53 | groups.set(benchmark.strategy, []); 54 | strategies.push(benchmark.strategy); 55 | } 56 | 57 | groups.get(benchmark.strategy).push(benchmark); 58 | } 59 | 60 | for(let strategy of strategies){ 61 | console.log(`## ${strategy}`); 62 | 63 | let group = groups.get(strategy); 64 | 65 | let median = getMedian(group); 66 | 67 | let strd_total = median.duration_total.toFixed(1).padStart(6); 68 | let strd_split = median.duration_split.toFixed(1).padStart(6); 69 | let strd_voxel = median.duration_voxelize.toFixed(1).padStart(6); 70 | let str_throughput = median.throughput.toFixed(1).padStart(6); 71 | 72 | console.log(`duration[split]: ${strd_split} ms`); 73 | console.log(`duration[voxelize]: ${strd_voxel} ms`); 74 | console.log(`duration[total]: ${strd_total} ms`); 75 | console.log(`throughput ${str_throughput} M points/s`); 76 | console.log(); 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /prototyping/encoding_2/encoding.mjs: -------------------------------------------------------------------------------- 1 | 2 | import {promises as fsp} from "fs"; 3 | import JSON5 from './json5.mjs'; 4 | 5 | 6 | let path = "g:/temp/retz/octree.bin"; 7 | let file = await fsp.open(path); 8 | 9 | let byteOffset = 2806623798; 10 | let byteSize = 219336; 11 | let numVoxels = 18278; 12 | 13 | let buffer = Buffer.alloc(byteSize); 14 | file.read(buffer, 0, byteSize, byteOffset); 15 | 16 | let buffer_interleaved_u8 = Buffer.alloc(3 * numVoxels); 17 | for(let i = 0; i < numVoxels; i++){ 18 | let r = buffer.readUint8(3 * numVoxels + 3 * i + 0); 19 | let g = buffer.readUint8(3 * numVoxels + 3 * i + 1); 20 | let b = buffer.readUint8(3 * numVoxels + 3 * i + 2); 21 | 22 | buffer_interleaved_u8.writeUInt8(r, 3 * i + 0); 23 | buffer_interleaved_u8.writeUInt8(g, 3 * i + 1); 24 | buffer_interleaved_u8.writeUInt8(b, 3 * i + 2); 25 | } 26 | 27 | let buffer_soa_u8 = Buffer.alloc(3 * numVoxels); 28 | for(let i = 0; i < numVoxels; i++){ 29 | let r = buffer.readUint8(3 * numVoxels + 3 * i + 0); 30 | let g = buffer.readUint8(3 * numVoxels + 3 * i + 1); 31 | let b = buffer.readUint8(3 * numVoxels + 3 * i + 2); 32 | 33 | buffer_soa_u8.writeUInt8(r, 0 * numVoxels + i); 34 | buffer_soa_u8.writeUInt8(g, 1 * numVoxels + i); 35 | buffer_soa_u8.writeUInt8(b, 2 * numVoxels + i); 36 | } 37 | 38 | let buffer_diff = Buffer.alloc(3 * numVoxels); 39 | let buffer_diff_sao = Buffer.alloc(3 * numVoxels); 40 | for(let i = 0; i < numVoxels; i++){ 41 | 42 | let r = buffer.readUint8(3 * numVoxels + 3 * i + 0); 43 | let g = buffer.readUint8(3 * numVoxels + 3 * i + 1); 44 | let b = buffer.readUint8(3 * numVoxels + 3 * i + 2); 45 | 46 | if(i === 0){ 47 | buffer_diff.writeUInt8(r, 3 * i + 0); 48 | buffer_diff.writeUInt8(g, 3 * i + 1); 49 | buffer_diff.writeUInt8(b, 3 * i + 2); 50 | 51 | buffer_diff_sao.writeUInt8(r, 0 * numVoxels + i); 52 | buffer_diff_sao.writeUInt8(g, 1 * numVoxels + i); 53 | buffer_diff_sao.writeUInt8(b, 2 * numVoxels + i); 54 | 55 | }else{ 56 | // previous rgb 57 | let rp = buffer.readUint8(3 * numVoxels + 3 * i + 0 - 3); 58 | let gp = buffer.readUint8(3 * numVoxels + 3 * i + 1 - 3); 59 | let bp = buffer.readUint8(3 * numVoxels + 3 * i + 2 - 3); 60 | 61 | let diff_r = (r - rp + 256) % 256; 62 | let diff_g = (g - gp + 256) % 256; 63 | let diff_b = (b - bp + 256) % 256; 64 | 65 | buffer_diff.writeUInt8(diff_r, 3 * i + 0); 66 | buffer_diff.writeUInt8(diff_g, 3 * i + 1); 67 | buffer_diff.writeUInt8(diff_b, 3 * i + 2); 68 | 69 | buffer_diff_sao.writeUInt8(r, 0 * numVoxels + i); 70 | buffer_diff_sao.writeUInt8(g, 1 * numVoxels + i); 71 | buffer_diff_sao.writeUInt8(b, 2 * numVoxels + i); 72 | } 73 | } 74 | 75 | let buffer_r = Buffer.alloc(numVoxels); 76 | for(let i = 0; i < numVoxels; i++){ 77 | let r = buffer.readUint8(3 * numVoxels + 3 * i + 0); 78 | 79 | buffer_r.writeUInt8(r, i); 80 | } 81 | 82 | fsp.writeFile("./buffer_interleaved_u8.bin", buffer_interleaved_u8); 83 | fsp.writeFile("./buffer_soa_u8.bin", buffer_soa_u8); 84 | fsp.writeFile("./buffer_diff.bin", buffer_diff); 85 | fsp.writeFile("./buffer_diff_sao.bin", buffer_diff_sao); 86 | fsp.writeFile("./buffer_r.bin", buffer_r); 87 | 88 | 89 | 90 | for(let i = 0; i < 16; i++){ 91 | let r = buffer.readUint8(3 * numVoxels + 3 * i + 0); 92 | let g = buffer.readUint8(3 * numVoxels + 3 * i + 1); 93 | let b = buffer.readUint8(3 * numVoxels + 3 * i + 2); 94 | 95 | console.log({r, g, b}); 96 | } -------------------------------------------------------------------------------- /prototyping/proto.js: -------------------------------------------------------------------------------- 1 | import {LasLoader, Header} from "LasLoader"; 2 | import {Vector3} from "potree"; 3 | 4 | export function splitLasfile(las){ 5 | let maxBatchSize = 10_000; 6 | 7 | let batches = []; 8 | let batch = null; 9 | for(let i = 0; i < las.header.numPoints; i++){ 10 | 11 | if(batch == null || batch.header.numPoints >= maxBatchSize){ 12 | // new batch 13 | 14 | let newBatchSize = Math.min(las.header.numPoints - i, maxBatchSize); 15 | let header = new Header(); 16 | header.numPoints = 0; 17 | header.scale = las.header.scale.clone(); 18 | header.offset = las.header.scale.clone(); 19 | header.min = new Vector3(Infinity, Infinity, Infinity); 20 | header.max = new Vector3(-Infinity, -Infinity, -Infinity); 21 | 22 | let positionf32 = new Float32Array(3 * newBatchSize); 23 | let color = new Uint8Array(4 * newBatchSize); 24 | let buffers = {positionf32, color}; 25 | 26 | batch = {header, buffers}; 27 | 28 | batches.push(batch); 29 | } 30 | 31 | let j = batch.header.numPoints; 32 | 33 | let x = las.buffers.positionf32[3 * i + 0] 34 | let y = las.buffers.positionf32[3 * i + 1] 35 | let z = las.buffers.positionf32[3 * i + 2] 36 | 37 | batch.header.min.x = Math.min(batch.header.min.x, x); 38 | batch.header.min.y = Math.min(batch.header.min.y, y); 39 | batch.header.min.z = Math.min(batch.header.min.z, z); 40 | batch.header.max.x = Math.max(batch.header.max.x, x); 41 | batch.header.max.y = Math.max(batch.header.max.y, y); 42 | batch.header.max.z = Math.max(batch.header.max.z, z); 43 | 44 | batch.buffers.positionf32[3 * j + 0] = x; 45 | batch.buffers.positionf32[3 * j + 1] = y; 46 | batch.buffers.positionf32[3 * j + 2] = z; 47 | 48 | batch.buffers.color[4 * j + 0] = las.buffers.color[4 * i + 0]; 49 | batch.buffers.color[4 * j + 1] = las.buffers.color[4 * i + 1]; 50 | batch.buffers.color[4 * j + 2] = las.buffers.color[4 * i + 2]; 51 | batch.buffers.color[4 * j + 3] = las.buffers.color[4 * i + 3]; 52 | 53 | batch.header.numPoints++; 54 | } 55 | 56 | return batches; 57 | } 58 | 59 | 60 | export function randomSelection(las){ 61 | let sampleSize = 100_000; 62 | 63 | let header = new Header(); 64 | header.numPoints = sampleSize; 65 | header.scale = las.header.scale.clone(); 66 | header.offset = las.header.scale.clone(); 67 | header.min = las.header.min.clone(); 68 | header.max = las.header.max.clone(); 69 | 70 | let positionf32 = new Float32Array(3 * sampleSize); 71 | let color = new Uint8Array(4 * sampleSize); 72 | let buffers = {positionf32, color}; 73 | 74 | let batch = {header, buffers}; 75 | 76 | for(let i = 0; i < sampleSize; i++){ 77 | 78 | let sourceIndex = Math.floor(Math.random() * las.header.numPoints); 79 | 80 | let x = las.buffers.positionf32[3 * sourceIndex + 0] 81 | let y = las.buffers.positionf32[3 * sourceIndex + 1] 82 | let z = las.buffers.positionf32[3 * sourceIndex + 2] 83 | 84 | batch.buffers.positionf32[3 * i + 0] = x; 85 | batch.buffers.positionf32[3 * i + 1] = y; 86 | batch.buffers.positionf32[3 * i + 2] = z; 87 | 88 | batch.buffers.color[4 * i + 0] = las.buffers.color[4 * sourceIndex + 0]; 89 | batch.buffers.color[4 * i + 1] = las.buffers.color[4 * sourceIndex + 1]; 90 | batch.buffers.color[4 * i + 2] = las.buffers.color[4 * sourceIndex + 2]; 91 | batch.buffers.color[4 * i + 3] = las.buffers.color[4 * sourceIndex + 3]; 92 | } 93 | 94 | return batch; 95 | } -------------------------------------------------------------------------------- /prototyping/prototyping.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 29 | 30 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /resources/F: -------------------------------------------------------------------------------- 1 | F:/ -------------------------------------------------------------------------------- /resources/images/360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-schuetz/Potree2/b00172bddfb37db79d7734ed99b69e2a1a9e9e09/resources/images/360.png -------------------------------------------------------------------------------- /resources/images/360.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 59 | 66 | 360° 77 | 78 | 79 | -------------------------------------------------------------------------------- /resources/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-schuetz/Potree2/b00172bddfb37db79d7734ed99b69e2a1a9e9e09/resources/images/background.jpg -------------------------------------------------------------------------------- /resources/potree.css: -------------------------------------------------------------------------------- 1 | 2 | canvas{ 3 | image-rendering: pixelated; 4 | display: flex; 5 | position: absolute; 6 | height: 100%; 7 | } 8 | 9 | td{ 10 | padding: 2px 10px 0px 0px; 11 | } 12 | 13 | #big_message{ 14 | position: absolute; 15 | left: calc(50%); 16 | bottom: 10px; 17 | color: white; 18 | text-shadow: 4px 4px 4px black; 19 | font-size: 4em; 20 | font-weight: bold; 21 | font-family: "Consolas"; 22 | /* from https://stackoverflow.com/a/14249403/913630 */ 23 | text-shadow: 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black; 24 | } 25 | 26 | #msg_dbg{ 27 | position: absolute; 28 | left: 10px; 29 | bottom: 10px; 30 | color: white; 31 | text-shadow: 4px 4px 4px black; 32 | font-size: 1em; 33 | font-weight: bold; 34 | font-family: "Consolas"; 35 | /* from https://stackoverflow.com/a/14249403/913630 */ 36 | text-shadow: 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black; 37 | } 38 | 39 | #msg_resources{ 40 | position: absolute; 41 | left: 1em; 42 | top: 10px; 43 | color: white; 44 | text-shadow: 4px 4px 4px black; 45 | font-size: 1em; 46 | font-weight: bold; 47 | font-family: "Consolas"; 48 | /* from https://stackoverflow.com/a/14249403/913630 */ 49 | text-shadow: 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black, 0 0 4px black; 50 | } 51 | 52 | .textButton { 53 | background: none; 54 | border: none; 55 | margin: 0; 56 | padding: 0; 57 | cursor: pointer; 58 | } -------------------------------------------------------------------------------- /src/core/Geometry.js: -------------------------------------------------------------------------------- 1 | 2 | import {Box3} from "potree"; 3 | 4 | let g_counter = 0; 5 | 6 | export class Geometry{ 7 | 8 | constructor({buffers, indices, numElements} = {}){ 9 | this.buffers = buffers ?? []; 10 | this.indices = indices ?? null; 11 | this.numElements = numElements ?? 0; 12 | this.boundingBox = new Box3(); 13 | this.numPoints = 0; 14 | this.numVoxels = 0; 15 | 16 | this.id = g_counter; 17 | g_counter++; 18 | } 19 | 20 | findBuffer(name){ 21 | return this.buffers.find(buffer => buffer.name === name)?.buffer; 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/core/RenderTarget.js: -------------------------------------------------------------------------------- 1 | 2 | export class RenderTarget{ 3 | 4 | constructor(renderer, params = {}){ 5 | this.colorAttachments = []; 6 | this.depth = null; 7 | this.size = params.size ?? [128, 128]; 8 | this.renderer = renderer; 9 | this.version = 0; 10 | 11 | { // COLOR ATTACHMENTS 12 | let descriptors = params.colorDescriptors ?? [{ 13 | size: this.size, 14 | format: "r32uint", 15 | usage: GPUTextureUsage.TEXTURE_BINDING 16 | | GPUTextureUsage.COPY_SRC 17 | | GPUTextureUsage.COPY_DST 18 | | GPUTextureUsage.RENDER_ATTACHMENT, 19 | }]; 20 | 21 | for(let descriptor of descriptors){ 22 | let texture = renderer.device.createTexture(descriptor); 23 | 24 | this.colorAttachments.push({descriptor, texture}); 25 | } 26 | } 27 | 28 | { // DEPTH ATTACHMENT 29 | let descriptor = params.depthDescriptor ?? { 30 | size: this.size, 31 | format: "depth32float", 32 | usage: GPUTextureUsage.TEXTURE_BINDING 33 | | GPUTextureUsage.COPY_SRC 34 | | GPUTextureUsage.COPY_DST 35 | | GPUTextureUsage.RENDER_ATTACHMENT, 36 | }; 37 | 38 | let texture = renderer.device.createTexture(descriptor); 39 | 40 | this.depth = {descriptor, texture}; 41 | } 42 | } 43 | 44 | // static create(descriptor){ 45 | // let instance = Object.create(RenderTarget); 46 | 47 | // return instance; 48 | // } 49 | 50 | setSize(width, height){ 51 | 52 | let resized = this.size[0] !== width || this.size[1] !== height; 53 | 54 | if(resized){ 55 | 56 | this.size = [width, height]; 57 | 58 | // resize color attachments 59 | for(let attachment of this.colorAttachments){ 60 | attachment.texture.destroy(); 61 | 62 | let desc = attachment.descriptor; 63 | desc.size = [...this.size, 1]; 64 | 65 | attachment.texture = this.renderer.device.createTexture(desc); 66 | } 67 | 68 | { // resize depth attachment 69 | let attachment = this.depth; 70 | attachment.texture.destroy(); 71 | 72 | let desc = attachment.descriptor; 73 | desc.size = [...this.size, 1]; 74 | 75 | attachment.texture = this.renderer.device.createTexture(desc); 76 | } 77 | 78 | this.version++; 79 | } 80 | 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /src/core/StandardMaterial.js: -------------------------------------------------------------------------------- 1 | 2 | export class StandardMaterial{ 3 | 4 | constructor(){ 5 | this.texture = null; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/defines.js: -------------------------------------------------------------------------------- 1 | 2 | export const KeyCodes = { 3 | 4 | LEFT: 37, 5 | UP: 38, 6 | RIGHT: 39, 7 | BOTTOM: 40, 8 | DELETE: 46, 9 | 10 | A: 'A'.charCodeAt(0), 11 | S: 'S'.charCodeAt(0), 12 | D: 'D'.charCodeAt(0), 13 | W: 'W'.charCodeAt(0), 14 | Q: 'Q'.charCodeAt(0), 15 | E: 'E'.charCodeAt(0), 16 | R: 'R'.charCodeAt(0), 17 | F: 'F'.charCodeAt(0) 18 | 19 | }; 20 | 21 | export const MouseCodes = { 22 | LEFT: 0, 23 | MIDDLE: 1, 24 | RIGHT: 2, 25 | }; -------------------------------------------------------------------------------- /src/math/Box3.js: -------------------------------------------------------------------------------- 1 | // 2 | // Adapted from three.js 3 | // license: MIT (https://github.com/mrdoob/three.js/blob/dev/LICENSE) 4 | // url: https://github.com/mrdoob/three.js/blob/dev/src/math/Box3.js 5 | // 6 | 7 | import {Vector3} from "./Vector3.js"; 8 | 9 | export class Box3{ 10 | 11 | constructor(min, max){ 12 | this.min = min ?? new Vector3(+Infinity, +Infinity, +Infinity); 13 | this.max = max ?? new Vector3(-Infinity, -Infinity, -Infinity); 14 | } 15 | 16 | clone(){ 17 | return new Box3( 18 | this.min.clone(), 19 | this.max.clone() 20 | ); 21 | } 22 | 23 | copy(box){ 24 | this.min.copy(box.min); 25 | this.max.copy(box.max); 26 | } 27 | 28 | size(){ 29 | return this.max.clone().sub(this.min); 30 | } 31 | 32 | containsPoint(point){ 33 | if(point.x < this.min.x || point.x > this.max.x) return false; 34 | if(point.y < this.min.y || point.y > this.max.y) return false; 35 | if(point.z < this.min.z || point.z > this.max.z) return false; 36 | 37 | return true; 38 | } 39 | 40 | center(){ 41 | return this.min.clone().add(this.max).multiplyScalar(0.5); 42 | } 43 | 44 | cube(){ 45 | let cubeSize = Math.max(...this.size().toArray()); 46 | let min = this.min.clone(); 47 | let max = this.min.clone().addScalar(cubeSize); 48 | let cube = new Box3(min, max); 49 | 50 | return cube; 51 | } 52 | 53 | expandByXYZ(x, y, z){ 54 | this.min.x = Math.min(this.min.x, x); 55 | this.min.y = Math.min(this.min.y, y); 56 | this.min.z = Math.min(this.min.z, z); 57 | 58 | this.max.x = Math.max(this.max.x, x); 59 | this.max.y = Math.max(this.max.y, y); 60 | this.max.z = Math.max(this.max.z, z); 61 | } 62 | 63 | expandByPoint(point){ 64 | this.min.x = Math.min(this.min.x, point.x); 65 | this.min.y = Math.min(this.min.y, point.y); 66 | this.min.z = Math.min(this.min.z, point.z); 67 | 68 | this.max.x = Math.max(this.max.x, point.x); 69 | this.max.y = Math.max(this.max.y, point.y); 70 | this.max.z = Math.max(this.max.z, point.z); 71 | } 72 | 73 | expandByBox(box){ 74 | this.expandByPoint(box.min); 75 | this.expandByPoint(box.max); 76 | } 77 | 78 | applyMatrix4(matrix){ 79 | 80 | let {min, max} = this; 81 | 82 | let points = [ 83 | new Vector3(min.x, min.y, min.z), 84 | new Vector3(min.x, min.y, max.z), 85 | new Vector3(min.x, max.y, min.z), 86 | new Vector3(min.x, max.y, max.z), 87 | new Vector3(max.x, min.y, min.z), 88 | new Vector3(max.x, min.y, max.z), 89 | new Vector3(max.x, max.y, min.z), 90 | new Vector3(max.x, max.y, max.z), 91 | ]; 92 | 93 | let newBox = new Box3(); 94 | 95 | for(let point of points){ 96 | let projected = point.applyMatrix4(matrix); 97 | newBox.expandByPoint(projected); 98 | } 99 | 100 | this.min.copy(newBox.min); 101 | this.max.copy(newBox.max); 102 | 103 | return this; 104 | } 105 | 106 | isFinite(){ 107 | return this.min.isFinite() && this.max.isFinite(); 108 | } 109 | 110 | }; -------------------------------------------------------------------------------- /src/math/Frustum.js: -------------------------------------------------------------------------------- 1 | // 2 | // Adapted from three.js 3 | // license: MIT (https://github.com/mrdoob/three.js/blob/dev/LICENSE) 4 | // url: https://github.com/mrdoob/three.js/blob/dev/src/math/Frustum.js 5 | // 6 | 7 | import {Plane} from "./Plane.js"; 8 | import {Vector3} from "./Vector3.js"; 9 | 10 | const _vector = new Vector3(); 11 | 12 | export class Frustum{ 13 | 14 | constructor( p0, p1, p2, p3, p4, p5 ) { 15 | 16 | this.planes = [ 17 | 18 | ( p0 !== undefined ) ? p0 : new Plane(), 19 | ( p1 !== undefined ) ? p1 : new Plane(), 20 | ( p2 !== undefined ) ? p2 : new Plane(), 21 | ( p3 !== undefined ) ? p3 : new Plane(), 22 | ( p4 !== undefined ) ? p4 : new Plane(), 23 | ( p5 !== undefined ) ? p5 : new Plane() 24 | 25 | ]; 26 | } 27 | 28 | setFromMatrix(m){ 29 | 30 | let planes = this.planes; 31 | let me = m.elements; 32 | let me0 = me[ 0 ] , me1 = me[ 1 ] , me2 = me[ 2 ] , me3 = me[ 3 ]; 33 | let me4 = me[ 4 ] , me5 = me[ 5 ] , me6 = me[ 6 ] , me7 = me[ 7 ]; 34 | let me8 = me[ 8 ] , me9 = me[ 9 ] , me10 = me[ 10 ], me11 = me[ 11 ]; 35 | let me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; 36 | 37 | // see https://www8.cs.umu.se/kurser/5DV051/HT12/lab/plane_extraction.pdf 38 | planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); // right 39 | planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); // left 40 | planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); // bottom 41 | planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); // top 42 | planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); // far 43 | planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); // near 44 | 45 | // planes[ 5 ].setComponents( me2, me6, me10, me14 ).normalize(); 46 | 47 | 48 | return this; 49 | 50 | } 51 | 52 | intersectsSphere(sphere) { 53 | 54 | const planes = this.planes; 55 | const center = sphere.center; 56 | const negRadius = - sphere.radius; 57 | 58 | for ( let i = 0; i < 6; i ++ ) { 59 | 60 | const distance = planes[ i ].distanceToPoint( center ); 61 | 62 | if ( distance < negRadius ) { 63 | 64 | return false; 65 | 66 | } 67 | 68 | } 69 | 70 | return true; 71 | 72 | } 73 | 74 | intersectsBox( box ) { 75 | 76 | const planes = this.planes; 77 | 78 | for ( let i = 0; i < 6; i ++ ) 79 | // for ( let i of [0, 1, 2, 3, 5]) 80 | // for ( let i of [0]) 81 | { 82 | 83 | const plane = planes[ i ]; 84 | 85 | // corner at max distance 86 | 87 | _vector.x = plane.normal.x > 0 ? box.max.x : box.min.x; 88 | _vector.y = plane.normal.y > 0 ? box.max.y : box.min.y; 89 | _vector.z = plane.normal.z > 0 ? box.max.z : box.min.z; 90 | 91 | if ( plane.distanceToPoint( _vector ) < 0 ) { 92 | 93 | return false; 94 | 95 | } 96 | 97 | } 98 | 99 | return true; 100 | 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /src/math/Line3.js: -------------------------------------------------------------------------------- 1 | // 2 | // Adapted from three.js 3 | // license: MIT (https://github.com/mrdoob/three.js/blob/dev/LICENSE) 4 | // url: https://github.com/mrdoob/three.js/blob/dev/src/math/Line3.js 5 | // 6 | 7 | import {Vector3} from "./Vector3.js"; 8 | 9 | const _startP = new Vector3(); 10 | const _startEnd = new Vector3(); 11 | 12 | export class Line3{ 13 | 14 | constructor(start, end){ 15 | this.start = start ?? new Vector3(); 16 | this.end = end ?? new Vector3(); 17 | } 18 | 19 | closestPointToPointParameter( point, clampToLine ) { 20 | 21 | _startP.subVectors( point, this.start ); 22 | _startEnd.subVectors( this.end, this.start ); 23 | 24 | const startEnd2 = _startEnd.dot( _startEnd ); 25 | const startEnd_startP = _startEnd.dot( _startP ); 26 | 27 | let t = startEnd_startP / startEnd2; 28 | 29 | if ( clampToLine ) { 30 | 31 | t = MathUtils.clamp( t, 0, 1 ); 32 | 33 | } 34 | 35 | return t; 36 | 37 | } 38 | 39 | closestPointToPoint(point, clampToLine) { 40 | const t = this.closestPointToPointParameter( point, clampToLine ); 41 | 42 | return this.delta( target ).multiplyScalar( t ).add( this.start ); 43 | } 44 | 45 | delta( target ) { 46 | return target.subVectors( this.end, this.start ); 47 | } 48 | 49 | at(t, target){ 50 | return this.delta( target ).multiplyScalar( t ).add( this.start ); 51 | } 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/math/PMath.js: -------------------------------------------------------------------------------- 1 | // 2 | // Adapted from three.js 3 | // license: MIT (https://github.com/mrdoob/three.js/blob/dev/LICENSE) 4 | // url: https://github.com/mrdoob/three.js/blob/dev/src/math 5 | // 6 | 7 | const π = Math.PI; 8 | 9 | export function toRadians(degrees){ 10 | return π * degrees / 180; 11 | } 12 | 13 | export function toDegrees(radians){ 14 | return 180 * radians / π; 15 | } 16 | 17 | export function ceilN(value, N){ 18 | return value + (N - value % N); 19 | }; 20 | 21 | export function clamp(value, min, max){ 22 | if(value < min) return min; 23 | if(value > max) return max; 24 | 25 | return value; 26 | }; -------------------------------------------------------------------------------- /src/math/Plane.js: -------------------------------------------------------------------------------- 1 | 2 | import {Vector3} from "./Vector3.js"; 3 | 4 | export class Plane{ 5 | 6 | constructor( normal, constant ) { 7 | 8 | // normal is assumed to be normalized 9 | 10 | this.normal = normal ?? new Vector3( 1, 0, 0 ); 11 | this.constant = constant ?? 0; 12 | 13 | } 14 | 15 | setComponents( x, y, z, w ) { 16 | 17 | this.normal.set( x, y, z ); 18 | this.constant = w; 19 | 20 | return this; 21 | 22 | } 23 | 24 | distanceToPoint( point ) { 25 | 26 | return this.normal.dot( point ) + this.constant; 27 | 28 | } 29 | 30 | normalize() { 31 | 32 | // Note: will lead to a divide by zero if the plane is invalid. 33 | 34 | const inverseNormalLength = 1.0 / this.normal.length(); 35 | this.normal.multiplyScalar( inverseNormalLength ); 36 | this.constant *= inverseNormalLength; 37 | 38 | return this; 39 | } 40 | 41 | }; -------------------------------------------------------------------------------- /src/math/Ray.js: -------------------------------------------------------------------------------- 1 | // 2 | // Adapted from three.js 3 | // license: MIT (https://github.com/mrdoob/three.js/blob/dev/LICENSE) 4 | // url: https://github.com/mrdoob/three.js/blob/dev/src/math/Ray.js 5 | // 6 | 7 | import {Vector3} from "./Vector3.js"; 8 | 9 | const _vector = new Vector3(); 10 | const _segCenter = new Vector3(); 11 | const _segDir = new Vector3(); 12 | const _diff = new Vector3(); 13 | 14 | const _edge1 = new Vector3(); 15 | const _edge2 = new Vector3(); 16 | const _normal = new Vector3(); 17 | 18 | export class Ray{ 19 | 20 | constructor(origin, direction){ 21 | this.origin = origin ?? new Vector3(); 22 | this.direction = direction ?? new Vector3( 0, 0, - 1 ); 23 | } 24 | 25 | distanceToPoint( point ) { 26 | return Math.sqrt( this.distanceSqToPoint( point ) ); 27 | } 28 | 29 | distanceSqToPoint( point ) { 30 | 31 | const directionDistance = _vector.subVectors( point, this.origin ).dot( this.direction ); 32 | 33 | // point behind the ray 34 | if ( directionDistance < 0 ) { 35 | 36 | return this.origin.distanceToSquared( point ); 37 | 38 | } 39 | 40 | _vector.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); 41 | 42 | return _vector.distanceToSquared( point ); 43 | 44 | } 45 | 46 | closestPointToPoint( point ) { 47 | 48 | let target = new Vector3(); 49 | 50 | target.subVectors( point, this.origin ); 51 | 52 | const directionDistance = target.dot( this.direction ); 53 | 54 | if ( directionDistance < 0 ) { 55 | 56 | return target.copy( this.origin ); 57 | 58 | } 59 | 60 | return target.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); 61 | 62 | } 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/math/Sphere.js: -------------------------------------------------------------------------------- 1 | // 2 | // Adapted from three.js 3 | // license: MIT (https://github.com/mrdoob/three.js/blob/dev/LICENSE) 4 | // url: https://github.com/mrdoob/three.js/blob/dev/src/math/Sphere.js 5 | // 6 | 7 | import {Vector3} from "./Vector3.js"; 8 | 9 | export class Sphere { 10 | 11 | constructor( center = new Vector3(), radius = - 1 ) { 12 | 13 | this.center = center; 14 | this.radius = radius; 15 | 16 | } 17 | 18 | set( center, radius ) { 19 | 20 | this.center.copy( center ); 21 | this.radius = radius; 22 | 23 | return this; 24 | 25 | } 26 | 27 | fromBox(box){ 28 | this.center.x = 0.5 * (box.min.x + box.max.x); 29 | this.center.y = 0.5 * (box.min.y + box.max.y); 30 | this.center.z = 0.5 * (box.min.z + box.max.z); 31 | 32 | let dx = box.max.x - box.min.x; 33 | let dy = box.max.y - box.min.y; 34 | let dz = box.max.z - box.min.z; 35 | 36 | this.radius = 0.5 * Math.sqrt(dx * dx + dy * dy + dz * dz); 37 | } 38 | 39 | copy( sphere ) { 40 | 41 | this.center.copy( sphere.center ); 42 | this.radius = sphere.radius; 43 | 44 | return this; 45 | 46 | } 47 | 48 | containsPoint( point ) { 49 | 50 | return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); 51 | 52 | } 53 | 54 | distanceToPoint( point ) { 55 | 56 | return ( point.distanceTo( this.center ) - this.radius ); 57 | 58 | } 59 | 60 | intersectsSphere( sphere ) { 61 | 62 | const radiusSum = this.radius + sphere.radius; 63 | 64 | return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); 65 | 66 | } 67 | 68 | applyMatrix4( matrix ) { 69 | 70 | this.center.applyMatrix4( matrix ); 71 | this.radius = this.radius * matrix.getMaxScaleOnAxis(); 72 | 73 | return this; 74 | 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/math/Vector3.js: -------------------------------------------------------------------------------- 1 | // 2 | // Adapted from three.js 3 | // license: MIT (https://github.com/mrdoob/three.js/blob/dev/LICENSE) 4 | // url: https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js 5 | // 6 | 7 | export class Vector3{ 8 | 9 | constructor(x, y, z){ 10 | this.x = x ?? 0; 11 | this.y = y ?? 0; 12 | this.z = z ?? 0; 13 | } 14 | 15 | set(x, y, z){ 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | 20 | return this; 21 | } 22 | 23 | copy(b){ 24 | this.x = b.x; 25 | this.y = b.y; 26 | this.z = b.z; 27 | 28 | return this; 29 | } 30 | 31 | multiplyScalar(s){ 32 | this.x = this.x * s; 33 | this.y = this.y * s; 34 | this.z = this.z * s; 35 | 36 | return this; 37 | } 38 | 39 | divideScalar(s){ 40 | this.x = this.x / s; 41 | this.y = this.y / s; 42 | this.z = this.z / s; 43 | 44 | return this; 45 | } 46 | 47 | add(b){ 48 | this.x = this.x + b.x; 49 | this.y = this.y + b.y; 50 | this.z = this.z + b.z; 51 | 52 | return this; 53 | } 54 | 55 | addScalar(s){ 56 | this.x = this.x + s; 57 | this.y = this.y + s; 58 | this.z = this.z + s; 59 | 60 | return this; 61 | } 62 | 63 | sub(b){ 64 | this.x = this.x - b.x; 65 | this.y = this.y - b.y; 66 | this.z = this.z - b.z; 67 | 68 | return this; 69 | } 70 | 71 | subScalar(s){ 72 | this.x = this.x - s; 73 | this.y = this.y - s; 74 | this.z = this.z - s; 75 | 76 | return this; 77 | } 78 | 79 | subVectors( a, b ) { 80 | 81 | this.x = a.x - b.x; 82 | this.y = a.y - b.y; 83 | this.z = a.z - b.z; 84 | 85 | return this; 86 | } 87 | 88 | cross(v) { 89 | return this.crossVectors( this, v ); 90 | } 91 | 92 | crossVectors( a, b ) { 93 | 94 | const ax = a.x, ay = a.y, az = a.z; 95 | const bx = b.x, by = b.y, bz = b.z; 96 | 97 | this.x = ay * bz - az * by; 98 | this.y = az * bx - ax * bz; 99 | this.z = ax * by - ay * bx; 100 | 101 | return this; 102 | } 103 | 104 | dot( v ) { 105 | return this.x * v.x + this.y * v.y + this.z * v.z; 106 | } 107 | 108 | distanceTo( v ) { 109 | return Math.sqrt( this.distanceToSquared( v ) ); 110 | } 111 | 112 | distanceToSquared( v ) { 113 | const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; 114 | 115 | return dx * dx + dy * dy + dz * dz; 116 | } 117 | 118 | clone(){ 119 | return new Vector3(this.x, this.y, this.z); 120 | } 121 | 122 | applyMatrix4(m){ 123 | const x = this.x, y = this.y, z = this.z; 124 | const e = m.elements; 125 | 126 | const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); 127 | 128 | this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; 129 | this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; 130 | this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; 131 | 132 | return this; 133 | } 134 | 135 | length() { 136 | return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); 137 | } 138 | 139 | lengthSq() { 140 | return this.x * this.x + this.y * this.y + this.z * this.z; 141 | } 142 | 143 | normalize(){ 144 | let l = this.length(); 145 | 146 | this.x = this.x / l; 147 | this.y = this.y / l; 148 | this.z = this.z / l; 149 | 150 | return this; 151 | } 152 | 153 | toString(precision){ 154 | if(precision != null){ 155 | return `${this.x.toFixed(precision)}, ${this.y.toFixed(precision)}, ${this.z.toFixed(precision)}`; 156 | }else{ 157 | return `${this.x}, ${this.y}, ${this.z}`; 158 | } 159 | } 160 | 161 | toArray(){ 162 | return [this.x, this.y, this.z]; 163 | } 164 | 165 | isFinite(){ 166 | let {x, y, z} = this; 167 | 168 | return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z); 169 | } 170 | 171 | }; -------------------------------------------------------------------------------- /src/math/Vector4.js: -------------------------------------------------------------------------------- 1 | // 2 | // Adapted from three.js 3 | // license: MIT (https://github.com/mrdoob/three.js/blob/dev/LICENSE) 4 | // url: https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js 5 | // 6 | 7 | export class Vector4{ 8 | 9 | constructor(x, y, z, w){ 10 | this.x = x ?? 0; 11 | this.y = y ?? 0; 12 | this.z = z ?? 0; 13 | this.w = w ?? 0; 14 | } 15 | 16 | set(x, y, z, w){ 17 | this.x = x; 18 | this.y = y; 19 | this.z = z; 20 | this.w = w; 21 | 22 | return this; 23 | } 24 | 25 | copy(b){ 26 | this.x = b.x; 27 | this.y = b.y; 28 | this.z = b.z; 29 | this.w = b.w; 30 | 31 | return this; 32 | } 33 | 34 | multiplyScalar(s){ 35 | this.x = this.x * s; 36 | this.y = this.y * s; 37 | this.z = this.z * s; 38 | this.w = this.w * s; 39 | 40 | return this; 41 | } 42 | 43 | divideScalar(s){ 44 | this.x = this.x / s; 45 | this.y = this.y / s; 46 | this.z = this.z / s; 47 | this.w = this.w / s; 48 | 49 | return this; 50 | } 51 | 52 | add(b){ 53 | this.x = this.x + b.x; 54 | this.y = this.y + b.y; 55 | this.z = this.z + b.z; 56 | this.w = this.w + b.w; 57 | 58 | return this; 59 | } 60 | 61 | addScalar(s){ 62 | this.x = this.x + s; 63 | this.y = this.y + s; 64 | this.z = this.z + s; 65 | this.w = this.w + s; 66 | 67 | return this; 68 | } 69 | 70 | sub(b){ 71 | this.x = this.x - b.x; 72 | this.y = this.y - b.y; 73 | this.z = this.z - b.z; 74 | this.w = this.w - b.w; 75 | 76 | return this; 77 | } 78 | 79 | subScalar(s){ 80 | this.x = this.x - s; 81 | this.y = this.y - s; 82 | this.z = this.z - s; 83 | this.w = this.w - s; 84 | 85 | return this; 86 | } 87 | 88 | subVectors( a, b ) { 89 | 90 | this.x = a.x - b.x; 91 | this.y = a.y - b.y; 92 | this.z = a.z - b.z; 93 | this.w = a.w - b.w; 94 | 95 | return this; 96 | } 97 | 98 | cross(v) { 99 | return this.crossVectors( this, v ); 100 | } 101 | 102 | // crossVectors(a, b) { 103 | 104 | // const ax = a.x, ay = a.y, az = a.z; 105 | // const bx = b.x, by = b.y, bz = b.z; 106 | 107 | // this.x = ay * bz - az * by; 108 | // this.y = az * bx - ax * bz; 109 | // this.z = ax * by - ay * bx; 110 | 111 | // return this; 112 | // } 113 | 114 | dot( v ) { 115 | return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; 116 | } 117 | 118 | distanceTo( v ) { 119 | return Math.sqrt( this.distanceToSquared( v ) ); 120 | } 121 | 122 | distanceToSquared( v ) { 123 | const dx = this.x - v.x; 124 | const dy = this.y - v.y; 125 | const dz = this.z - v.z; 126 | const dw = this.w - v.w; 127 | 128 | return dx * dx + dy * dy + dz * dz + dw * dw; 129 | } 130 | 131 | clone(){ 132 | return new Vector4(this.x, this.y, this.z, this.w); 133 | } 134 | 135 | applyMatrix4(m){ 136 | const {x, y, z, w} = this; 137 | const e = m.elements; 138 | 139 | this.x = e[0] * x + e[4] * y + e[ 8] * z + e[12] * w; 140 | this.y = e[1] * x + e[5] * y + e[ 9] * z + e[13] * w; 141 | this.z = e[2] * x + e[6] * y + e[10] * z + e[14] * w; 142 | this.w = e[3] * x + e[7] * y + e[11] * z + e[15] * w; 143 | 144 | return this; 145 | } 146 | 147 | length() { 148 | return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); 149 | } 150 | 151 | lengthSq() { 152 | return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; 153 | } 154 | 155 | normalize(){ 156 | let l = this.length(); 157 | 158 | this.x = this.x / l; 159 | this.y = this.y / l; 160 | this.z = this.z / l; 161 | this.w = this.w / l; 162 | 163 | return this; 164 | } 165 | 166 | toString(precision){ 167 | if(precision != null){ 168 | return `${this.x.toFixed(precision)}, ${this.y.toFixed(precision)}, ${this.z.toFixed(precision)}, ${this.w.toFixed(precision)}`; 169 | }else{ 170 | return `${this.x}, ${this.y}, ${this.z}, ${this.w}`; 171 | } 172 | } 173 | 174 | toArray(){ 175 | return [this.x, this.y, this.z, this.w]; 176 | } 177 | 178 | isFinite(){ 179 | let {x, y, z, w} = this; 180 | 181 | return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) && Number.isFinite(w); 182 | } 183 | 184 | }; -------------------------------------------------------------------------------- /src/math/math.js: -------------------------------------------------------------------------------- 1 | import { Vector3 } from "./Vector3.js"; 2 | 3 | export * from "./Box3.js"; 4 | export * from "./Frustum.js"; 5 | export * from "./Line3.js"; 6 | export * from "./Matrix4.js"; 7 | export * from "./Plane.js"; 8 | export * from "./Sphere.js"; 9 | export * from "./Ray.js"; 10 | export * from "./Vector3.js"; 11 | export * from "./Vector4.js"; 12 | export * from "./PMath.js"; 13 | 14 | const _v0 = new Vector3(); 15 | 16 | // from https://github.com/mrdoob/three.js/blob/dev/src/math/Triangle.js 17 | export function computeNormal(a, b, c){ 18 | 19 | let target = new Vector3(); 20 | 21 | target.subVectors( c, b ); 22 | _v0.subVectors( a, b ); 23 | target.cross( _v0 ); 24 | 25 | const targetLengthSq = target.lengthSq(); 26 | 27 | if ( targetLengthSq > 0 ) { 28 | return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) ); 29 | } 30 | 31 | return target.set( 0, 0, 0 ); 32 | 33 | } 34 | 35 | const π = Math.PI; 36 | 37 | export function toRadians(degrees){ 38 | return π * degrees / 180; 39 | } 40 | 41 | export function toDegrees(radians){ 42 | return 180 * radians / π; 43 | } 44 | 45 | export function ceilN(value, N){ 46 | return value + (N - value % N); 47 | }; 48 | 49 | export function clamp(value, min, max){ 50 | if(value < min) return min; 51 | if(value > max) return max; 52 | 53 | return value; 54 | }; -------------------------------------------------------------------------------- /src/misc/EventDispatcher.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export class EventDispatcher{ 4 | 5 | constructor(){ 6 | this.listeners = new Map(); 7 | } 8 | 9 | addEventListener(name, callback){ 10 | 11 | let list = this.listeners.get(name); 12 | 13 | if(!list){ 14 | list = []; 15 | 16 | this.listeners.set(name, list); 17 | } 18 | 19 | list.push(callback); 20 | 21 | } 22 | 23 | add(name, callback){ 24 | this.addEventListener(name, callback); 25 | } 26 | 27 | removeEventListener(name, callback){ 28 | throw "not implemented"; 29 | } 30 | 31 | removeAll(){ 32 | this.listeners = new Map(); 33 | } 34 | 35 | dispatch(name, data){ 36 | let list = this.listeners.get(name); 37 | 38 | if(!list){ 39 | return; 40 | } 41 | 42 | let containsOneTimeEvent = false; 43 | for(let callback of list){ 44 | callback(data); 45 | 46 | containsOneTimeEvent = containsOneTimeEvent || (callback.isOneTimeEvent === true); 47 | } 48 | 49 | if(containsOneTimeEvent){ 50 | let prunedList = list.filter(callback => !callback.isOneTimeEvent); 51 | this.listeners.set(name, prunedList); 52 | } 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/misc/GLBLoader.js: -------------------------------------------------------------------------------- 1 | 2 | import {Geometry, SceneNode, Mesh} from "potree"; 3 | import {Box3, Vector3} from "potree"; 4 | import {PhongMaterial, ColorMode} from "potree"; 5 | import {NormalMaterial, WireframeMaterial} from "potree"; 6 | 7 | let tmpCanvas = null; 8 | let tmpContext = null; 9 | function getTmpContext(){ 10 | 11 | if(tmpCanvas === null){ 12 | tmpCanvas = document.createElement('canvas'); 13 | tmpCanvas.width = 8192; 14 | tmpCanvas.height = 8192; 15 | tmpContext = tmpCanvas.getContext('2d'); 16 | } 17 | 18 | return tmpContext; 19 | } 20 | 21 | export function load(url, callbacks){ 22 | 23 | let workerPath = "./src/misc/GLBLoaderWorker.js"; 24 | let worker = new Worker(workerPath, {type: "module"}); 25 | 26 | let root = new SceneNode("glb root"); 27 | 28 | let images = new Map(); 29 | 30 | let image_loaded = (e) => { 31 | images.set(e.data.imageRef, e.data.imageBitmap); 32 | }; 33 | 34 | let mesh_batch_loaded = (e) => { 35 | 36 | let imageBitmap = null; 37 | if(images.has(e.data.imageRef)){ 38 | imageBitmap = images.get(e.data.imageRef); 39 | } 40 | 41 | let geometryData = e.data.geometry; 42 | 43 | let geometry = new Geometry(); 44 | geometry.buffers = geometryData.buffers; 45 | geometry.indices = geometryData.indices; 46 | geometry.numElements = geometryData.numElements; 47 | geometry.boundingBox.min.copy(geometryData.boundingBox.min); 48 | geometry.boundingBox.max.copy(geometryData.boundingBox.max); 49 | 50 | let mesh = new Mesh("glb mesh", geometry); 51 | 52 | if(imageBitmap){ 53 | mesh.material = new PhongMaterial(); 54 | mesh.material.image = imageBitmap; 55 | mesh.material.colorMode = ColorMode.TEXTURE; 56 | mesh.material.imageBuffer = e.data.imageBuffer; 57 | }else{ 58 | mesh.material = new PhongMaterial(); 59 | mesh.material.image = null; 60 | mesh.material.colorMode = ColorMode.VERTEX_COLOR; 61 | } 62 | 63 | root.children.push(mesh); 64 | 65 | if(root.children.length === 1){ 66 | callbacks.onStart(root); 67 | callbacks.onNode(mesh); 68 | }else{ 69 | callbacks.onNode(mesh); 70 | } 71 | }; 72 | 73 | 74 | 75 | worker.onmessage = (e) => { 76 | 77 | if(e.data.type === "mesh_batch_loaded"){ 78 | mesh_batch_loaded(e); 79 | }else if(e.data.type === "image_loaded"){ 80 | image_loaded(e); 81 | } 82 | 83 | }; 84 | 85 | let absoluteUrl = new URL(url, document.baseURI).href; 86 | worker.postMessage({url: absoluteUrl}); 87 | 88 | 89 | }; 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/misc/Gradients.js: -------------------------------------------------------------------------------- 1 | 2 | // see https://colorbrewer2.org/#type=diverging&scheme=Spectral&n=11 3 | 4 | export class Gradient{ 5 | 6 | constructor(steps){ 7 | this.steps = steps; 8 | } 9 | 10 | get(u){ 11 | 12 | let n = u * (this.steps.length - 1); 13 | let v = n % 1; 14 | 15 | let i = Math.floor(n) % this.steps.length; 16 | let j = Math.ceil(n) % this.steps.length; 17 | 18 | let a = this.steps[i]; 19 | let b = this.steps[j]; 20 | 21 | let color = [ 22 | (1 - v) * a[0] + v * b[0], 23 | (1 - v) * a[1] + v * b[1], 24 | (1 - v) * a[2] + v * b[2], 25 | ]; 26 | 27 | return color; 28 | } 29 | 30 | } 31 | 32 | export let SPECTRAL = new Gradient([ 33 | [ 94, 79, 162, 255], 34 | [ 50, 136, 189, 255], 35 | [102, 194, 165, 255], 36 | [171, 221, 164, 255], 37 | [230, 245, 152, 255], 38 | [255, 255, 191, 255], 39 | [254, 224, 139, 255], 40 | [253, 174, 97, 255], 41 | [244, 109, 67, 255], 42 | [213, 62, 79, 255], 43 | [158, 1, 66, 255], 44 | ]); 45 | 46 | export let PLASMA = new Gradient([ 47 | [255 * 0.241, 255 * 0.015, 255 * 0.610, 255], 48 | [255 * 0.387, 255 * 0.001, 255 * 0.654, 255], 49 | [255 * 0.524, 255 * 0.025, 255 * 0.653, 255], 50 | [255 * 0.651, 255 * 0.125, 255 * 0.596, 255], 51 | [255 * 0.752, 255 * 0.227, 255 * 0.513, 255], 52 | [255 * 0.837, 255 * 0.329, 255 * 0.431, 255], 53 | [255 * 0.907, 255 * 0.435, 255 * 0.353, 255], 54 | [255 * 0.963, 255 * 0.554, 255 * 0.272, 255], 55 | [255 * 0.992, 255 * 0.681, 255 * 0.195, 255], 56 | [255 * 0.987, 255 * 0.822, 255 * 0.144, 255], 57 | [255 * 0.940, 255 * 0.975, 255 * 0.130, 255], 58 | ]); 59 | 60 | export let VIRIDIS = new Gradient([ 61 | [255 * 0.267, 255 * 0.005, 255 * 0.329, 255], 62 | [255 * 0.283, 255 * 0.141, 255 * 0.458, 255], 63 | [255 * 0.254, 255 * 0.265, 255 * 0.530, 255], 64 | [255 * 0.207, 255 * 0.372, 255 * 0.553, 255], 65 | [255 * 0.164, 255 * 0.471, 255 * 0.558, 255], 66 | [255 * 0.128, 255 * 0.567, 255 * 0.551, 255], 67 | [255 * 0.135, 255 * 0.659, 255 * 0.518, 255], 68 | [255 * 0.267, 255 * 0.749, 255 * 0.441, 255], 69 | [255 * 0.478, 255 * 0.821, 255 * 0.318, 255], 70 | [255 * 0.741, 255 * 0.873, 255 * 0.150, 255], 71 | [255 * 0.993, 255 * 0.906, 255 * 0.144, 255], 72 | ]); 73 | 74 | export let INFERNO = new Gradient([ 75 | [255 * 0.077, 255 * 0.042, 255 * 0.206, 255], 76 | [255 * 0.225, 255 * 0.036, 255 * 0.388, 255], 77 | [255 * 0.373, 255 * 0.074, 255 * 0.432, 255], 78 | [255 * 0.522, 255 * 0.128, 255 * 0.420, 255], 79 | [255 * 0.665, 255 * 0.182, 255 * 0.370, 255], 80 | [255 * 0.797, 255 * 0.255, 255 * 0.287, 255], 81 | [255 * 0.902, 255 * 0.364, 255 * 0.184, 255], 82 | [255 * 0.969, 255 * 0.516, 255 * 0.063, 255], 83 | [255 * 0.988, 255 * 0.683, 255 * 0.072, 255], 84 | [255 * 0.961, 255 * 0.859, 255 * 0.298, 255], 85 | [255 * 0.988, 255 * 0.998, 255 * 0.645, 255], 86 | ]); 87 | 88 | export let GRAYSCALE = new Gradient([ 89 | [ 0, 0, 0, 255], 90 | [255, 255, 255, 255], 91 | ]); 92 | 93 | export let TURBO = new Gradient([ 94 | [255 * 0.18995, 255 * 0.07176, 255 * 0.23217, 255], 95 | [255 * 0.25107, 255 * 0.25237, 255 * 0.63374, 255], 96 | [255 * 0.27628, 255 * 0.42118, 255 * 0.89123, 255], 97 | [255 * 0.25862, 255 * 0.57958, 255 * 0.99876, 255], 98 | [255 * 0.15844, 255 * 0.73551, 255 * 0.92305, 255], 99 | [255 * 0.09267, 255 * 0.86554, 255 * 0.76230, 255], 100 | [255 * 0.19659, 255 * 0.94901, 255 * 0.59466, 255], 101 | [255 * 0.42778, 255 * 0.99419, 255 * 0.38575, 255], 102 | [255 * 0.64362, 255 * 0.98999, 255 * 0.23356, 255], 103 | [255 * 0.80473, 255 * 0.92452, 255 * 0.20459, 255], 104 | [255 * 0.93301, 255 * 0.81236, 255 * 0.22667, 255], 105 | [255 * 0.99314, 255 * 0.67408, 255 * 0.20348, 255], 106 | [255 * 0.98360, 255 * 0.49291, 255 * 0.12849, 255], 107 | [255 * 0.92105, 255 * 0.31489, 255 * 0.05475, 255], 108 | [255 * 0.81608, 255 * 0.18462, 255 * 0.01809, 255], 109 | [255 * 0.66449, 255 * 0.08436, 255 * 0.00424, 255], 110 | ]); 111 | 112 | export const Gradients = { 113 | SPECTRAL, PLASMA, VIRIDIS, INFERNO, GRAYSCALE, TURBO 114 | }; -------------------------------------------------------------------------------- /src/misc/WorkerPool.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | class WorkerList{ 4 | 5 | constructor(){ 6 | this.count = 0; 7 | this.list = []; 8 | } 9 | 10 | } 11 | 12 | let workers = new Map(); 13 | 14 | export class WorkerPool{ 15 | constructor(){ 16 | 17 | } 18 | 19 | static getWorker(url, params){ 20 | if (!workers.has(url)){ 21 | workers.set(url, new WorkerList()); 22 | } 23 | 24 | if (workers.get(url).list.length === 0){ 25 | let worker = new Worker(url, params); 26 | workers.get(url).list.push(worker); 27 | workers.get(url).count++; 28 | } 29 | 30 | let worker = workers.get(url).list.pop(); 31 | 32 | return worker; 33 | } 34 | 35 | static getWorkerCount(url){ 36 | if (!workers.has(url)){ 37 | return 0; 38 | }else{ 39 | return workers.get(url).count; 40 | } 41 | } 42 | 43 | static getAvailableWorkerCount(url){ 44 | if (!workers.has(url)){ 45 | return Infinity; 46 | }else{ 47 | return workers.get(url).list.length; 48 | } 49 | } 50 | 51 | static returnWorker(url, worker){ 52 | workers.get(url).list.push(worker); 53 | } 54 | 55 | }; -------------------------------------------------------------------------------- /src/modules/LasLoader/LasLoader.js: -------------------------------------------------------------------------------- 1 | 2 | import {Vector3} from "potree"; 3 | 4 | export class Header{ 5 | 6 | constructor(){ 7 | this.versionMajor = 0; 8 | this.versionMinor = 0; 9 | this.headerSize = 0; 10 | this.offsetToPointData = 0; 11 | this.format = 0; 12 | this.recordLength = 0; 13 | this.numPoints = 0; 14 | this.scale = new Vector3(); 15 | this.offset = new Vector3(); 16 | this.min = new Vector3(); 17 | this.max = new Vector3(); 18 | } 19 | 20 | }; 21 | 22 | function parseHeader(view){ 23 | let header = new Header(); 24 | 25 | header.versionMajor = view.getUint8(24); 26 | header.versionMinor = view.getUint8(25); 27 | header.headerSize = view.getUint16(94, true); 28 | header.offsetToPointData = view.getUint32(96, true); 29 | header.format = view.getUint8(104); 30 | header.recordLength = view.getUint16(105, true); 31 | 32 | if(header.versionMajor == 1 && header.versionMinor < 4){ 33 | header.numPoints = view.getUint32(107, true); 34 | }else{ 35 | header.numPoints = Number(view.getBigUint64(247, true)); 36 | } 37 | 38 | header.scale.x = view.getFloat64(131, true); 39 | header.scale.y = view.getFloat64(139, true); 40 | header.scale.z = view.getFloat64(147, true); 41 | 42 | header.offset.x = view.getFloat64(155, true); 43 | header.offset.y = view.getFloat64(163, true); 44 | header.offset.z = view.getFloat64(171, true); 45 | 46 | header.min.x = view.getFloat64(187, true); 47 | header.min.y = view.getFloat64(203, true); 48 | header.min.z = view.getFloat64(219, true); 49 | 50 | header.max.x = view.getFloat64(179, true); 51 | header.max.y = view.getFloat64(195, true); 52 | header.max.z = view.getFloat64(211, true); 53 | 54 | 55 | return header; 56 | } 57 | 58 | async function load(path){ 59 | 60 | let response = await fetch(path); 61 | let buffer = await response.arrayBuffer(); 62 | let view = new DataView(buffer); 63 | 64 | let header = parseHeader(view); 65 | 66 | let {scale, offset} = header; 67 | 68 | let position = new Float64Array(3 * header.numPoints); 69 | let color = new Uint8Array(4 * header.numPoints); 70 | 71 | for(let i = 0; i < header.numPoints; i++){ 72 | let pointOffset = header.offsetToPointData + i * header.recordLength; 73 | 74 | let X = view.getInt32(pointOffset + 0, true); 75 | let Y = view.getInt32(pointOffset + 4, true); 76 | let Z = view.getInt32(pointOffset + 8, true); 77 | 78 | let x = (X * scale.x) + offset.x; 79 | let y = (Y * scale.y) + offset.y; 80 | let z = (Z * scale.z) + offset.z; 81 | 82 | position[3 * i + 0] = x; 83 | position[3 * i + 1] = y; 84 | position[3 * i + 2] = z; 85 | 86 | let [r, g, b] = [255, 0, 0]; 87 | 88 | if(header.format === 3){ 89 | r = view.getUint16(pointOffset + 28, true); 90 | g = view.getUint16(pointOffset + 30, true); 91 | b = view.getUint16(pointOffset + 32, true); 92 | 93 | r = r > 255 ? r / 256 : r; 94 | g = g > 255 ? g / 256 : g; 95 | b = b > 255 ? b / 256 : b; 96 | } 97 | 98 | color[4 * i + 0] = r; 99 | color[4 * i + 1] = g; 100 | color[4 * i + 2] = b; 101 | color[4 * i + 3] = 255; 102 | } 103 | 104 | let positionf32 = new Float32Array(position); 105 | 106 | let buffers = {position, positionf32, color}; 107 | 108 | return {header, buffers}; 109 | } 110 | 111 | export const LasLoader = { 112 | load 113 | }; -------------------------------------------------------------------------------- /src/modules/geometries/points.js: -------------------------------------------------------------------------------- 1 | 2 | import {Geometry, Points} from "potree"; 3 | 4 | const {sin, cos} = Math; 5 | 6 | export function createPointsData(cells = 1_000){ 7 | let n = cells * cells; 8 | let position = new Float32Array(3 * n); 9 | let color = new Uint8Array(4 * n); 10 | let k = 0; 11 | for(let i = 0; i < cells; i++){ 12 | for(let j = 0; j < cells; j++){ 13 | 14 | let u = 2.0 * (i / cells) - 1.0; 15 | let v = 2.0 * (j / cells) - 1.0; 16 | let d = u * u + v * v; 17 | 18 | let x = 2 * u; 19 | let y = 2 * v; 20 | let z = cos(3 * u) * cos(3 * v) * Math.ceil(1 - d, 0); 21 | 22 | if(d >= 1){ 23 | x = x / d; 24 | y = y / d; 25 | z = -1; 26 | } 27 | 28 | position[3 * k + 0] = x; 29 | position[3 * k + 1] = y; 30 | position[3 * k + 2] = z; 31 | 32 | let b = (z / 2 + 0.5) * 0.8 + 0.2; 33 | 34 | color[4 * k + 0] = b * 255; 35 | color[4 * k + 1] = b * 50; 36 | color[4 * k + 2] = b * 190; 37 | 38 | // color[4 * k + 0] = b * 255 * (u + 1) / 2; 39 | // color[4 * k + 1] = b * 255 * (v + 1) / 2; 40 | // color[4 * k + 2] = b * 100 * (z + 1) / 2; 41 | color[4 * k + 3] = 255; 42 | 43 | if((Math.floor(60 * (z / 2 + 0.5)) % 10) == 0){ 44 | color[4 * k + 0] = 0; 45 | color[4 * k + 1] = 0; 46 | color[4 * k + 2] = 0; 47 | } 48 | 49 | 50 | k++; 51 | } 52 | } 53 | 54 | 55 | let numElements = n; 56 | let buffers = [ 57 | { 58 | name: "position", 59 | buffer: position, 60 | },{ 61 | name: "rgba", 62 | buffer: color, 63 | } 64 | ]; 65 | let geometry = new Geometry({numElements, buffers}); 66 | let points = new Points(); 67 | points.geometry = geometry; 68 | 69 | return points; 70 | } 71 | 72 | export function createPointsSphere(n = 1_000){ 73 | 74 | let position = new Float32Array(3 * n); 75 | let color = new Uint8Array(4 * n); 76 | for(let i = 0; i < n; i++){ 77 | 78 | let x = 2 * Math.random() - 1; 79 | let y = 2 * Math.random() - 1; 80 | let z = 2 * Math.random() - 1; 81 | 82 | let d = Math.sqrt(x ** 2 + y ** 2 + z ** 2); 83 | x = x / d; 84 | y = y / d; 85 | z = z / d; 86 | 87 | position[3 * i + 0] = x; 88 | position[3 * i + 1] = y; 89 | position[3 * i + 2] = z; 90 | 91 | color[4 * i + 0] = 255 * (z / 2 + 0.5); 92 | color[4 * i + 1] = 0; 93 | color[4 * i + 2] = 0; 94 | color[4 * i + 3] = 255; 95 | } 96 | 97 | 98 | let numElements = n; 99 | let buffers = [ 100 | { 101 | name: "position", 102 | buffer: position, 103 | },{ 104 | name: "rgba", 105 | buffer: color, 106 | } 107 | ]; 108 | let geometry = new Geometry({numElements, buffers}); 109 | let points = new Points(); 110 | points.geometry = geometry; 111 | 112 | return points; 113 | } -------------------------------------------------------------------------------- /src/modules/geometries/sphere.js: -------------------------------------------------------------------------------- 1 | import {Vector3, computeNormal} from "../../math/math.js" 2 | import {Geometry} from "../../core/Geometry.js"; 3 | 4 | const π = Math.PI; 5 | const {sin, cos} = Math; 6 | 7 | 8 | let numElements = 0; 9 | let buffers = []; 10 | new Geometry({numElements, buffers}); 11 | 12 | // after http://www.songho.ca/opengl/gl_sphere.html 13 | export function createSphere(detail){ 14 | 15 | let stackCount = detail; 16 | let sectorCount = detail; 17 | 18 | let sectorStep = 2 * π / stackCount; 19 | let stackStep = π / stackCount; 20 | 21 | let radius = 1; 22 | 23 | let n = (stackCount + 1) * (sectorCount + 1); 24 | 25 | let buf_position = new Float32Array(3 * n); 26 | let buf_color = new Float32Array(3 * n); 27 | let buf_uv = new Float32Array(2 * n); 28 | let buf_normal = new Float32Array(3 * n); 29 | 30 | let counter = 0; 31 | for(let i = 0; i <= stackCount; i++){ 32 | 33 | let stackAngle = π / 2 - i * stackStep; 34 | let xy = radius * cos(stackAngle); 35 | let z = radius * sin(stackAngle); 36 | 37 | for(let j = 0; j <= sectorCount; j++){ 38 | 39 | let sectorAngle = j * sectorStep; 40 | 41 | let x = xy * cos(sectorAngle); 42 | let y = xy * sin(sectorAngle); 43 | 44 | buf_position[3 * counter + 0] = x; 45 | buf_position[3 * counter + 1] = y; 46 | buf_position[3 * counter + 2] = z; 47 | 48 | buf_normal[3 * counter + 0] = x; 49 | buf_normal[3 * counter + 1] = y; 50 | buf_normal[3 * counter + 2] = z; 51 | 52 | let u = j / sectorCount; 53 | let v = i / stackCount; 54 | 55 | buf_uv[2 * counter + 0] = u; 56 | buf_uv[2 * counter + 1] = v; 57 | 58 | buf_color[3 * counter + 0] = x; 59 | buf_color[3 * counter + 1] = y; 60 | buf_color[3 * counter + 2] = z; 61 | 62 | counter++; 63 | } 64 | } 65 | 66 | let buffers = [ 67 | {name: "position", buffer: buf_position}, 68 | {name: "color", buffer: buf_color}, 69 | {name: "uv", buffer: buf_uv}, 70 | {name: "normal", buffer: buf_normal}, 71 | ]; 72 | 73 | let maxIndices = 2 * 3 * stackCount * sectorCount; 74 | let indices = new Uint32Array(maxIndices); 75 | let indexCounter = 0; 76 | 77 | for(let i = 0; i < stackCount; i++){ 78 | 79 | let k1 = i * (sectorCount + 1); 80 | let k2 = k1 + sectorCount + 1; 81 | 82 | for(let j = 0; j < sectorCount; j++, k1++, k2++){ 83 | 84 | if(i !== 0){ 85 | indices[3 * indexCounter + 0] = k1; 86 | indices[3 * indexCounter + 1] = k2; 87 | indices[3 * indexCounter + 2] = k1 + 1; 88 | indexCounter++; 89 | } 90 | 91 | if(i !== (stackCount - 1)){ 92 | indices[3 * indexCounter + 0] = k1 + 1; 93 | indices[3 * indexCounter + 1] = k2; 94 | indices[3 * indexCounter + 2] = k2 + 1; 95 | indexCounter++; 96 | } 97 | 98 | } 99 | } 100 | 101 | let clampedIndices = new Uint32Array(indices.buffer, 0, 3 * indexCounter); 102 | 103 | let geometry = new Geometry({ 104 | numElements: counter, 105 | buffers: buffers, 106 | indices: clampedIndices, 107 | }); 108 | 109 | return geometry; 110 | } 111 | 112 | 113 | export const sphere = createSphere(128); 114 | 115 | -------------------------------------------------------------------------------- /src/modules/geometries/wave.js: -------------------------------------------------------------------------------- 1 | 2 | import {Vector3, computeNormal} from "../../math/math.js 3 | 4 | export function createWave(){ 5 | 6 | let n = 500; 7 | 8 | let sample = (u, v) => { 9 | 10 | let x = u; 11 | let y = v; 12 | let z = Math.sin(u) * Math.cos(v); 13 | 14 | return new Vector3(x, y, z); 15 | }; 16 | 17 | let numTriangles = n * n * 2; 18 | let numVertices = numTriangles * 3; 19 | 20 | let positions = new Float32Array(3 * numVertices); 21 | let colors = new Float32Array(4 * numVertices); 22 | let uvs = new Float32Array(2 * numVertices); 23 | let normals = new Float32Array(3 * numVertices); 24 | 25 | let k = 0; 26 | let addVertex = (pos, col, uv, normal) => { 27 | 28 | positions[3 * k + 0] = pos.x; 29 | positions[3 * k + 1] = pos.y; 30 | positions[3 * k + 2] = pos.z; 31 | 32 | colors[4 * k + 0] = col[0]; 33 | colors[4 * k + 1] = col[1]; 34 | colors[4 * k + 2] = col[2]; 35 | colors[4 * k + 3] = col[3]; 36 | 37 | uvs[2 * k + 0] = uv[0]; 38 | uvs[2 * k + 1] = uv[1]; 39 | 40 | normals[3 * k + 0] = normal.x; 41 | normals[3 * k + 1] = normal.y; 42 | normals[3 * k + 2] = normal.z; 43 | 44 | k++; 45 | }; 46 | 47 | for(let i = 0; i < n; i++){ 48 | for(let j = 0; j < n; j++){ 49 | 50 | let u0 = 10 * Math.PI * ((i + 0) / n) - 5 * Math.PI; 51 | let u1 = 10 * Math.PI * ((i + 1) / n) - 5 * Math.PI; 52 | let v0 = 10 * Math.PI * ((j + 0) / n) - 5 * Math.PI; 53 | let v1 = 10 * Math.PI * ((j + 1) / n) - 5 * Math.PI; 54 | 55 | let p0 = sample(u0, v0); 56 | let p1 = sample(u1, v0); 57 | let p2 = sample(u1, v1); 58 | let p3 = sample(u0, v1); 59 | 60 | let n1 = computeNormal(p0, p1, p2); 61 | let n2 = computeNormal(p0, p2, p3); 62 | 63 | addVertex(p0, [0.2 + 0.8 * p0.z, 0.2, 0.2, 0.0], [u0, v0], n1); 64 | addVertex(p1, [0.2 + 0.8 * p1.z, 0.2, 0.2, 0.0], [u1, v0], n1); 65 | addVertex(p2, [0.2 + 0.8 * p2.z, 0.2, 0.2, 0.0], [u1, v1], n1); 66 | addVertex(p0, [0.2 + 0.8 * p0.z, 0.2, 0.2, 0.0], [u0, v0], n2); 67 | addVertex(p2, [0.2 + 0.8 * p2.z, 0.2, 0.2, 0.0], [u1, v1], n2); 68 | addVertex(p3, [0.2 + 0.8 * p3.z, 0.2, 0.2, 0.0], [u0, v1], n2); 69 | } 70 | } 71 | 72 | let geometry = { 73 | vertexCount: numVertices, 74 | triangleCount: numTriangles, 75 | buffers: [ 76 | {name: "position", buffer: positions}, 77 | {name: "color", buffer: colors}, 78 | {name: "uv", buffer: uvs}, 79 | {name: "normal", buffer: normals}, 80 | ], 81 | }; 82 | 83 | return geometry; 84 | 85 | } -------------------------------------------------------------------------------- /src/modules/gui_dat/gui.js: -------------------------------------------------------------------------------- 1 | 2 | import * as dat from "dat.gui"; 3 | import {Potree} from "potree"; 4 | 5 | let gui = null; 6 | let guiContent = { 7 | 8 | // INFOS 9 | "#voxels": "", 10 | "#points": "", 11 | "#nodes": "", 12 | "fps": "0", 13 | "duration(update)": "0", 14 | "cam.pos": "", 15 | "cam.target": "", 16 | "cam.dir": "", 17 | 18 | 19 | // INPUT 20 | "show bounding box": false, 21 | "use compute": false, 22 | "dilate": true, 23 | "Eye-Dome-Lighting": false, 24 | "High-Quality": false, 25 | "point budget (M)": 4, 26 | "point size": 3, 27 | "update": true, 28 | "debug": 0.5, 29 | 30 | // COLOR ADJUSTMENT 31 | "scalar min": 0, 32 | "scalar max": 2 ** 16, 33 | "gamma": 1, 34 | "brightness": 0, 35 | "contrast": 0, 36 | }; 37 | 38 | // window.guiContent = guiContent; 39 | let guiAttributes = null; 40 | let guiScalarMin = null; 41 | let guiScalarMax = null; 42 | 43 | export function initGUI(potree){ 44 | 45 | gui = new dat.GUI(); 46 | window.gui = gui; 47 | window.guiContent = guiContent; 48 | 49 | { 50 | let stats = gui.addFolder("stats"); 51 | stats.open(); 52 | // stats.add(guiContent, "#voxels").listen(); 53 | stats.add(guiContent, "#points").listen(); 54 | stats.add(guiContent, "#nodes").listen(); 55 | stats.add(guiContent, "fps").listen(); 56 | stats.add(guiContent, "duration(update)").listen(); 57 | stats.add(guiContent, "cam.pos").listen(); 58 | stats.add(guiContent, "cam.target").listen(); 59 | stats.add(guiContent, "cam.dir").listen(); 60 | } 61 | 62 | { 63 | let input = gui.addFolder("input"); 64 | input.open(); 65 | 66 | // input.add(guiContent, "mode", [ 67 | // "pixels", 68 | // "dilate", 69 | // "HQS", 70 | // ]); 71 | // input.add(guiContent, "use compute").listen(); 72 | input.add(guiContent, "dilate").listen(); 73 | input.add(guiContent, "Eye-Dome-Lighting").listen(); 74 | input.add(guiContent, "High-Quality").listen(); 75 | input.add(guiContent, "show bounding box").listen(); 76 | input.add(guiContent, "update").listen(); 77 | // guiAttributes = input.add(guiContent, "attribute", ["rgba", "intensity"]).listen(); 78 | // window.guiAttributes = guiAttributes; 79 | 80 | // slider 81 | input.add(guiContent, 'point budget (M)', 0.01, 5).listen(); 82 | input.add(guiContent, 'point size', 1, 5); 83 | input.add(guiContent, 'debug', 0, 1); 84 | } 85 | 86 | // { 87 | // let input = gui.addFolder("Color Adjustments"); 88 | // input.open(); 89 | 90 | // guiScalarMin = input.add(guiContent, 'scalar min', 0, 2 ** 16).listen(); 91 | // guiScalarMax = input.add(guiContent, 'scalar max', 0, 2 ** 16).listen(); 92 | // input.add(guiContent, 'gamma', 0, 2).listen(); 93 | // input.add(guiContent, 'brightness', -1, 1).listen(); 94 | // input.add(guiContent, 'contrast', -1, 1).listen(); 95 | // } 96 | 97 | 98 | potree.addEventListener("update", () => { 99 | 100 | let state = Potree.state; 101 | 102 | guiContent["fps"] = state.fps.toLocaleString(); 103 | guiContent["#voxels"] = state.numVoxels.toLocaleString(); 104 | guiContent["#points"] = state.numPoints.toLocaleString(); 105 | guiContent["#nodes"] = state.numNodes.toLocaleString(); 106 | guiContent["cam.pos"] = state.camPos; 107 | guiContent["cam.target"] = state.camTarget; 108 | guiContent["cam.dir"] = state.camDir; 109 | 110 | // Potree.settings.mode = guiContent["mode"]; 111 | Potree.settings.useCompute = guiContent["use compute"]; 112 | // Potree.settings.dilateEnabled = guiContent["dilate"]; 113 | // Potree.settings.attribute = guiContent["attribute"]; 114 | // Potree.settings.pointBudget = guiContent["point budget (M)"] * 1_000_000; 115 | Potree.settings.pointSize = guiContent["point size"]; 116 | // Potree.settings.edlEnabled = guiContent["Eye-Dome-Lighting"]; 117 | // Potree.settings.hqsEnabled = guiContent["High-Quality"]; 118 | // Potree.settings.updateEnabled = guiContent["update"]; 119 | // Potree.settings.showBoundingBox = guiContent["show bounding box"]; 120 | Potree.settings.debugU = guiContent["debug"]; 121 | 122 | 123 | }); 124 | 125 | gui.close() 126 | 127 | } -------------------------------------------------------------------------------- /src/modules/gui_dat/gui_compute_demo.js: -------------------------------------------------------------------------------- 1 | 2 | import * as dat from "dat.gui"; 3 | import {Potree} from "potree"; 4 | 5 | let gui = null; 6 | let guiContent = { 7 | 8 | // INFOS 9 | "#points": "0", 10 | "#nodes": "0", 11 | "fps": "0", 12 | "duration(update)": "0", 13 | "cam.pos": "", 14 | "cam.target": "", 15 | "cam.dir": "", 16 | 17 | 18 | // INPUT 19 | // "show bounding box": false, 20 | // "mode": "pixels", 21 | // "mode": "dilate", 22 | "use compute": true, 23 | "dilate": true, 24 | "Eye-Dome-Lighting": true, 25 | "High-Quality": true, 26 | // "mode": "HQS", 27 | "attribute": "rgba", 28 | "point budget (M)": 4, 29 | "point size": 3, 30 | "update": true, 31 | 32 | // COLOR ADJUSTMENT 33 | "scalar min": 0, 34 | "scalar max": 2 ** 16, 35 | "gamma": 1, 36 | "brightness": 0, 37 | "contrast": 0, 38 | }; 39 | 40 | // window.guiContent = guiContent; 41 | let guiAttributes = null; 42 | let guiScalarMin = null; 43 | let guiScalarMax = null; 44 | 45 | export function initGUI(potree){ 46 | 47 | gui = new dat.GUI(); 48 | window.gui = gui; 49 | window.guiContent = guiContent; 50 | 51 | { 52 | let stats = gui.addFolder("stats"); 53 | stats.open(); 54 | stats.add(guiContent, "#points").listen(); 55 | stats.add(guiContent, "#nodes").listen(); 56 | stats.add(guiContent, "fps").listen(); 57 | stats.add(guiContent, "duration(update)").listen(); 58 | stats.add(guiContent, "cam.pos").listen(); 59 | stats.add(guiContent, "cam.target").listen(); 60 | stats.add(guiContent, "cam.dir").listen(); 61 | } 62 | 63 | { 64 | let input = gui.addFolder("input"); 65 | input.open(); 66 | 67 | // input.add(guiContent, "mode", [ 68 | // "pixels", 69 | // "dilate", 70 | // "HQS", 71 | // ]); 72 | input.add(guiContent, "use compute"); 73 | // input.add(guiContent, "dilate"); 74 | // input.add(guiContent, "Eye-Dome-Lighting"); 75 | // input.add(guiContent, "High-Quality"); 76 | // input.add(guiContent, "show bounding box"); 77 | // input.add(guiContent, "update"); 78 | // guiAttributes = input.add(guiContent, "attribute", ["rgba", "intensity"]).listen(); 79 | // window.guiAttributes = guiAttributes; 80 | 81 | // slider 82 | // input.add(guiContent, 'point budget (M)', 0.01, 5); 83 | // input.add(guiContent, 'point size', 1, 5); 84 | } 85 | 86 | // { 87 | // let input = gui.addFolder("Color Adjustments"); 88 | // input.open(); 89 | 90 | // guiScalarMin = input.add(guiContent, 'scalar min', 0, 2 ** 16).listen(); 91 | // guiScalarMax = input.add(guiContent, 'scalar max', 0, 2 ** 16).listen(); 92 | // input.add(guiContent, 'gamma', 0, 2).listen(); 93 | // input.add(guiContent, 'brightness', -1, 1).listen(); 94 | // input.add(guiContent, 'contrast', -1, 1).listen(); 95 | // } 96 | 97 | 98 | potree.addEventListener("update", () => { 99 | 100 | let state = Potree.state; 101 | 102 | guiContent["fps"] = state.fps.toLocaleString(); 103 | guiContent["#points"] = state.numPoints.toLocaleString(); 104 | guiContent["#nodes"] = state.numNodes.toLocaleString(); 105 | guiContent["cam.pos"] = state.camPos; 106 | guiContent["cam.target"] = state.camTarget; 107 | guiContent["cam.dir"] = state.camDir; 108 | 109 | // Potree.settings.mode = guiContent["mode"]; 110 | Potree.settings.useCompute = guiContent["use compute"]; 111 | Potree.settings.dilateEnabled = guiContent["dilate"]; 112 | Potree.settings.attribute = guiContent["attribute"]; 113 | Potree.settings.pointBudget = guiContent["point budget (M)"] * 1_000_000; 114 | Potree.settings.pointSize = guiContent["point size"]; 115 | Potree.settings.edlEnabled = guiContent["Eye-Dome-Lighting"]; 116 | Potree.settings.hqsEnabled = guiContent["High-Quality"]; 117 | Potree.settings.updateEnabled = guiContent["update"]; 118 | 119 | 120 | }); 121 | 122 | } -------------------------------------------------------------------------------- /src/modules/lines/Lines.js: -------------------------------------------------------------------------------- 1 | 2 | import {SceneNode} from "../../scene/SceneNode.js"; 3 | 4 | export class Lines extends SceneNode{ 5 | 6 | constructor(name, geometry){ 7 | super(name); 8 | this.geometry = geometry; 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/modules/mesh/Mesh.js: -------------------------------------------------------------------------------- 1 | 2 | import {SceneNode} from "../../scene/SceneNode.js"; 3 | import {NormalMaterial} from "./NormalMaterial.js"; 4 | import {renderMeshes} from "potree"; 5 | 6 | export class Mesh extends SceneNode{ 7 | 8 | constructor(name, geometry){ 9 | super(name); 10 | 11 | this.geometry = geometry; 12 | this.material = new NormalMaterial(); 13 | 14 | if(geometry.boundingBox){ 15 | this.boundingBox.copy(geometry.boundingBox); 16 | } 17 | } 18 | 19 | render(drawstate){ 20 | renderMeshes([this], drawstate); 21 | } 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/modules/mesh/renderMesh.js: -------------------------------------------------------------------------------- 1 | 2 | import { render as renderNormal, NormalMaterial } from "./NormalMaterial.js"; 3 | import { render as renderPhong, PhongMaterial } from "./PhongMaterial.js"; 4 | import { render as renderTriangleColor, TriangleColorMaterial } from "./TriangleColorMaterial.js"; 5 | import { render as renderWireframe, WireframeMaterial } from "./WireframeMaterial.js"; 6 | 7 | export function renderMeshes(meshes, drawstate){ 8 | 9 | for(let mesh of meshes){ 10 | if(mesh.material instanceof NormalMaterial){ 11 | renderNormal(mesh, drawstate); 12 | }else if(mesh.material instanceof PhongMaterial){ 13 | renderPhong(mesh, drawstate); 14 | }else if(mesh.material instanceof WireframeMaterial){ 15 | renderWireframe(mesh, drawstate); 16 | }else if(mesh.material instanceof TriangleColorMaterial){ 17 | renderTriangleColor(mesh, drawstate); 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/modules/points/Points.js: -------------------------------------------------------------------------------- 1 | 2 | import {SceneNode, Geometry} from "potree"; 3 | 4 | export class Points extends SceneNode{ 5 | 6 | constructor(name, geometry){ 7 | super(name); 8 | 9 | this.geometry = geometry; 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/modules/progressive_loader/ProgressiveLoader.js: -------------------------------------------------------------------------------- 1 | 2 | import {Vector3, Box3} from "potree"; 3 | import {Geometry, SceneNode, Points} from "potree"; 4 | import {WorkerPool} from "potree"; 5 | 6 | let root = new SceneNode("progressive_root"); 7 | 8 | let progress = { 9 | header: null, 10 | nodes: [], 11 | }; 12 | 13 | let workerPath = `../src/modules/progressive_loader/LasDecoder_worker.js`; 14 | let workers = [ 15 | WorkerPool.getWorker(workerPath, {type: "module"}), 16 | WorkerPool.getWorker(workerPath, {type: "module"}), 17 | WorkerPool.getWorker(workerPath, {type: "module"}), 18 | WorkerPool.getWorker(workerPath, {type: "module"}), 19 | // WorkerPool.getWorker(workerPath, {type: "module"}), 20 | // WorkerPool.getWorker(workerPath, {type: "module"}), 21 | ]; 22 | 23 | 24 | async function loadHeader(file){ 25 | 26 | if(workers.length == 0){ 27 | setTimeout(loadHeader, 1, file); 28 | 29 | return; 30 | } 31 | 32 | let worker = workers.pop(); 33 | 34 | worker.onmessage = (e) => { 35 | let {buffers, numPoints, header, min, max} = e.data; 36 | 37 | let geometry = new Geometry(); 38 | geometry.numElements = numPoints; 39 | 40 | geometry.buffers.push({ 41 | name: "position", 42 | buffer: buffers.position 43 | }); 44 | 45 | geometry.buffers.push({ 46 | name: "rgba", 47 | buffer: buffers.color 48 | }); 49 | 50 | let node = new Points(); 51 | node.geometry = geometry; 52 | 53 | root.children.push(node); 54 | 55 | console.log("node loaded"); 56 | 57 | workers.push(worker); 58 | 59 | console.log("time: ", performance.now()); 60 | }; 61 | 62 | worker.postMessage({file}); 63 | 64 | } 65 | 66 | function load(file){ 67 | setTimeout(loadHeader, 1, file); 68 | } 69 | 70 | 71 | function install(element, args = {}){ 72 | 73 | element.addEventListener('dragover', function(e) { 74 | e.stopPropagation(); 75 | e.preventDefault(); 76 | e.dataTransfer.dropEffect = 'copy'; 77 | }); 78 | 79 | element.addEventListener('drop', async function(e) { 80 | e.stopPropagation(); 81 | e.preventDefault(); 82 | 83 | let files = e.dataTransfer.files; 84 | 85 | console.log("start: ", performance.now()); 86 | 87 | let promises = []; 88 | for (let file of files) { 89 | let blob = file.slice(0, 227); 90 | let promise = blob.arrayBuffer(); 91 | 92 | promises.push(promise); 93 | 94 | // load(file); 95 | } 96 | 97 | 98 | let buffers = await Promise.all(promises); 99 | 100 | let full_aabb = new Box3(); 101 | 102 | let boxes = []; 103 | for(let buffer of buffers){ 104 | let view = new DataView(buffer); 105 | 106 | let min = new Vector3(); 107 | min.x = view.getFloat64(187, true); 108 | min.y = view.getFloat64(203, true); 109 | min.z = view.getFloat64(219, true); 110 | 111 | let max = new Vector3(); 112 | max.x = view.getFloat64(179, true); 113 | max.y = view.getFloat64(195, true); 114 | max.z = view.getFloat64(211, true); 115 | 116 | let box = new Box3(min, max); 117 | boxes.push(box); 118 | 119 | full_aabb.expandByPoint(min); 120 | full_aabb.expandByPoint(max); 121 | } 122 | 123 | progress.boundingBox = full_aabb; 124 | 125 | for (let file of files) { 126 | load(file); 127 | } 128 | 129 | // load(files.item(0)); 130 | // load(files.item(1)); 131 | // load(files.item(2)); 132 | 133 | if(args.onProgress){ 134 | args.onProgress({boxes, progress}); 135 | } 136 | }); 137 | 138 | if(args.onSetup){ 139 | args.onSetup(root); 140 | } 141 | 142 | 143 | } 144 | 145 | 146 | export {load, install}; -------------------------------------------------------------------------------- /src/modules/progressive_loader/ProgressivePointCloud.js: -------------------------------------------------------------------------------- 1 | 2 | import {SceneNode} from "potree"; 3 | import {Vector3, Box3, Matrix4, Ray} from "potree"; 4 | 5 | 6 | class ProgressiveItem{ 7 | 8 | constructor({file, boundingBox, box, numPoints}){ 9 | this.file = file; 10 | this.numPoints = numPoints; 11 | this.boundingBox = boundingBox; 12 | this.stage_0 = box; 13 | } 14 | 15 | } 16 | 17 | export class ProgressivePointCloud extends SceneNode{ 18 | 19 | constructor({files}){ 20 | 21 | super("progressive point cloud"); 22 | 23 | this.files = files; 24 | 25 | this.items = []; 26 | 27 | this.renderables = { 28 | boxes: [], 29 | boundingBoxes: [], 30 | pointclouds: [], 31 | }; 32 | 33 | this.spawnWorker(); 34 | } 35 | 36 | spawnWorker(){ 37 | let workerPath = `${import.meta.url}/../progressive_worker.js`; 38 | let worker = new Worker(workerPath, {type: "module"}); 39 | 40 | worker.onmessage = (e) => { 41 | let stage0 = e.data; 42 | 43 | let boundingBox = new Box3( 44 | new Vector3().copy(stage0.boundingBox.min), 45 | new Vector3().copy(stage0.boundingBox.max), 46 | ); 47 | let color = new Vector3().copy(stage0.color); 48 | let box = {boundingBox, color}; 49 | 50 | this.renderables.boxes.push(box); 51 | 52 | let item = new ProgressiveItem({ 53 | file: stage0.file, 54 | numPoints: stage0.numPoints, 55 | boundingBox, 56 | box 57 | }); 58 | 59 | this.items.push(item); 60 | }; 61 | 62 | worker.postMessage({files: this.files}); 63 | } 64 | 65 | update(renderer, camera){ 66 | 67 | let boundingBoxes = []; 68 | 69 | let ray = new Ray( 70 | camera.getWorldPosition(), 71 | camera.getWorldDirection(), 72 | ); 73 | let view = camera.view; 74 | let aspect = camera.aspect; 75 | 76 | let numPoints_priority0 = 0; 77 | let numPoints_priority1 = 0; 78 | let numPoints_priority2 = 0; 79 | 80 | let stage0 = []; 81 | let stage1 = []; 82 | let stage2 = []; 83 | 84 | for(let item of this.items){ 85 | 86 | let box = item.boundingBox; 87 | let center = box.center(); 88 | let radius = box.min.distanceTo(box.max) / 2; 89 | 90 | let center_view = center.clone().applyMatrix4(view); 91 | let depth = -center_view.z; 92 | let center_distance = Math.sqrt(center_view.x ** 2 + (center_view.y * aspect) ** 2); 93 | let weight = center_distance / depth; 94 | 95 | if(depth + radius < 0){ 96 | 97 | weight = 100; 98 | } 99 | 100 | if(weight < 0.2){ 101 | boundingBoxes.push({ 102 | boundingBox: item.boundingBox, 103 | color: new Vector3(0, 255, 0), 104 | }); 105 | 106 | stage0.push(item); 107 | numPoints_priority0 += item.numPoints; 108 | }else if(weight < 0.5){ 109 | boundingBoxes.push({ 110 | boundingBox: item.boundingBox, 111 | color: new Vector3(255, 255, 0), 112 | }); 113 | 114 | stage1.push(item); 115 | numPoints_priority1 += item.numPoints; 116 | }else{ 117 | boundingBoxes.push({ 118 | boundingBox: item.boundingBox, 119 | color: new Vector3(255, 0, 0), 120 | }); 121 | 122 | stage2.push(item); 123 | numPoints_priority2 += item.numPoints; 124 | } 125 | } 126 | 127 | // this.renderables.boundingBoxes = boundingBoxes; 128 | // this.renderables.boundingBoxes = []; 129 | 130 | { // dbg 131 | let msg = ` 132 | priority 0: ${numPoints_priority0.toLocaleString()} 133 | priority 1: ${numPoints_priority1.toLocaleString()} 134 | priority 2: ${numPoints_priority2.toLocaleString()} 135 | `; 136 | 137 | // document.getElementById("big_message").innerText = msg; 138 | } 139 | 140 | } 141 | 142 | }; -------------------------------------------------------------------------------- /src/modules/progressive_loader/progressive_worker.js: -------------------------------------------------------------------------------- 1 | 2 | import {Vector3, Box3} from "../../math/math.js"; 3 | 4 | 5 | async function loadStage0(file){ 6 | 7 | let blob = file.slice(0, Math.min(file.size, 120_000)); 8 | //let blob = file.slice(0, 227); 9 | 10 | let buffer = await blob.arrayBuffer(); 11 | let view = new DataView(buffer); 12 | 13 | let versionMajor = view.getUint8(24); 14 | let versionMinor = view.getUint8(25); 15 | let offsetToPointData = view.getUint32(96, true); 16 | let formatID = view.getUint8(104); 17 | let recordLength = view.getUint16(105, true); 18 | let compressed = formatID >= 64; 19 | 20 | let numPoints = view.getUint32(107, true); 21 | if(versionMajor >= 1 && versionMinor >= 4){ 22 | numPoints = Number(view.getBigInt64(247, true)); 23 | } 24 | 25 | 26 | let min = new Vector3(); 27 | min.x = view.getFloat64(187, true); 28 | min.y = view.getFloat64(203, true); 29 | min.z = view.getFloat64(219, true); 30 | 31 | let max = new Vector3(); 32 | max.x = view.getFloat64(179, true); 33 | max.y = view.getFloat64(195, true); 34 | max.z = view.getFloat64(211, true); 35 | 36 | 37 | let color; 38 | { 39 | let offsetFirstPoint = offsetToPointData; 40 | 41 | if(compressed){ 42 | offsetFirstPoint += 8; 43 | formatID = formatID & 0b1111; 44 | 45 | } 46 | 47 | let offsetRGB = { 48 | 0: 0, 49 | 1: 0, 50 | 2: 20, 51 | 3: 28, 52 | }[formatID]; 53 | 54 | let [R, G, B] = [0, 0, 0]; 55 | 56 | let n = Math.min(numPoints, 1000); 57 | 58 | if(compressed){ 59 | n = 1; 60 | } 61 | 62 | for(let i = 0; i < n; i++){ 63 | R += view.getUint16(offsetFirstPoint + i * recordLength + offsetRGB + 0, true); 64 | G += view.getUint16(offsetFirstPoint + i * recordLength + offsetRGB + 2, true); 65 | B += view.getUint16(offsetFirstPoint + i * recordLength + offsetRGB + 4, true); 66 | } 67 | 68 | R = R / n; 69 | G = G / n; 70 | B = B / n; 71 | 72 | // console.log(R, G, B); 73 | 74 | color = new Vector3(R, G, B).multiplyScalar(1 / 256); 75 | } 76 | 77 | let boundingBox = new Box3(min, max); 78 | // let box = { 79 | // boundingBox: new Box3(min, max), 80 | // color: color, 81 | // } 82 | 83 | return {numPoints, boundingBox, color, file}; 84 | } 85 | 86 | 87 | onmessage = async function(e){ 88 | 89 | let {files} = e.data; 90 | 91 | let tStart = performance.now(); 92 | 93 | let active = new Set(); 94 | let promises = []; 95 | 96 | for(let file of files){ 97 | 98 | if(active.size >= 8){ 99 | await Promise.any(active); 100 | } 101 | 102 | let promise = loadStage0(file); 103 | active.add(promise); 104 | promises.push(promise); 105 | 106 | promise.then(stage0 => { 107 | active.delete(promise); 108 | 109 | postMessage(stage0); 110 | }); 111 | 112 | } 113 | 114 | await Promise.all(promises); 115 | 116 | let duration = performance.now() - tStart; 117 | let seconds = duration / 1000; 118 | console.log(`[load]: ${seconds.toFixed(1)}`); 119 | 120 | } 121 | 122 | -------------------------------------------------------------------------------- /src/modules/quads/Quads.js: -------------------------------------------------------------------------------- 1 | 2 | import {SceneNode} from "../../scene/SceneNode.js"; 3 | 4 | export class Quads extends SceneNode{ 5 | 6 | constructor(name, positions){ 7 | super(name); 8 | 9 | this.positions = positions; 10 | this.size = 20; 11 | 12 | } 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/modules/sidebar/icons/LICENSE: -------------------------------------------------------------------------------- 1 | { 2 | file: "home.svg", 3 | url: "https://fonts.google.com/icons?selected=Material+Icons&icon.query=house", 4 | misc: "https://google.github.io/material-design-icons/", 5 | license: "Apache License Version 2.0", 6 | licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0.txt", 7 | },{ 8 | file: "measure.svg", 9 | url: "https://fonts.google.com/icons?selected=Material+Icons&icon.query=measure", 10 | misc: "https://google.github.io/material-design-icons/", 11 | license: "Apache License Version 2.0", 12 | licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0.txt", 13 | },{ 14 | file: "material.svg", 15 | url: "https://fonts.google.com/icons?selected=Material+Icons&icon.query=paint", 16 | misc: "https://google.github.io/material-design-icons/", 17 | license: "Apache License Version 2.0", 18 | licenseUrl: "http://www.apache.org/licenses/LICENSE-2.0.txt", 19 | } -------------------------------------------------------------------------------- /src/modules/sidebar/icons/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 39 | 42 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 66 | 73 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/modules/sidebar/icons/distance.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 39 | 42 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 59 | 64 | 74 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/modules/sidebar/icons/dotdotdot.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 21 | 27 | 28 | 29 | 49 | 52 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 69 | 75 | 81 | 87 | ... 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/modules/sidebar/icons/height.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 39 | 42 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 59 | 64 | 69 | 79 | 89 | 99 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/modules/sidebar/icons/help.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/sidebar/icons/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/sidebar/icons/material.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/sidebar/icons/measure.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/sidebar/icons/point.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 42 | 45 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 62 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/modules/sidebar/panel_infos.js: -------------------------------------------------------------------------------- 1 | 2 | import {Gradients, Utils} from "potree"; 3 | let dir = new URL(import.meta.url + "/../").href; 4 | 5 | class Panel{ 6 | 7 | constructor(){ 8 | this.element = document.createElement("div"); 9 | this.elTable = document.createElement("table"); 10 | this.prevStrTable = ""; 11 | 12 | let elTitle = document.createElement("div"); 13 | elTitle.classList.add("subsection"); 14 | elTitle.textContent = "Infos"; 15 | 16 | let elButtons = document.createElement("div"); 17 | 18 | let createButton = (value, onClick) => { 19 | let elButton = document.createElement("input"); 20 | 21 | elButton.type = "button"; 22 | elButton.value = value; 23 | elButton.onclick = onClick; 24 | 25 | 26 | elButtons.append(elButton); 27 | }; 28 | 29 | createButton("📋 camera", () => { 30 | 31 | let {camPos, camTarget} = Potree.state; 32 | 33 | let str = `[${camPos}], \n[${camTarget}]`; 34 | 35 | Utils.clipboardCopy(str); 36 | }); 37 | 38 | this.element.append(elTitle); 39 | this.element.append(this.elTable); 40 | this.element.append(elButtons); 41 | } 42 | 43 | set(pointcloud){ 44 | // connect attributes 45 | } 46 | 47 | update(){ 48 | 49 | let valueRows = ""; 50 | 51 | valueRows += ` 52 | 53 | rendered points 54 | ${Potree.state.numPoints.toLocaleString()} 55 | 56 | rendered voxels 57 | ${Potree.state.numVoxels.toLocaleString()} 58 | 59 | 60 | rendered octree nodes 61 | ${Potree.state.numNodes.toLocaleString()} 62 | 63 | 64 | # 3D Tile Nodes 65 | ${Potree.state.num3DTileNodes.toLocaleString()} 66 | 67 | 68 | 3D Tile Triangles 69 | ${Potree.state.num3DTileTriangles.toLocaleString()} 70 | 71 | FPS 72 | ${Potree.state.fps} 73 | 74 | cam.pos 75 | ${Potree.state.camPos} 76 | 77 | cam.dir 78 | ${Potree.state.camDir} 79 | 80 | cam.target 81 | ${Potree.state.camTarget} 82 | 83 | `; 84 | 85 | let strTable = ` 86 | 87 | ${valueRows} 88 |
89 | `; 90 | 91 | // only update DOM if the code changed 92 | if(strTable !== this.prevStrTable){ 93 | this.elTable.innerHTML = strTable; 94 | this.prevStrTable = strTable; 95 | } 96 | 97 | requestAnimationFrame(this.update.bind(this)); 98 | } 99 | 100 | } 101 | 102 | export function createPanel(){ 103 | let panel = new Panel(); 104 | panel.element.id = "info_panel"; 105 | panel.element.classList.add("subsection_panel"); 106 | 107 | panel.update(); 108 | 109 | return panel; 110 | } -------------------------------------------------------------------------------- /src/modules/sidebar/panel_scene.js: -------------------------------------------------------------------------------- 1 | 2 | import {Gradients, Utils} from "potree"; 3 | let dir = new URL(import.meta.url + "/../").href; 4 | 5 | class Panel{ 6 | 7 | constructor(){ 8 | this.element = document.createElement("div"); 9 | this.elTable = document.createElement("div"); 10 | 11 | let elTitle = document.createElement("div"); 12 | elTitle.classList.add("subsection"); 13 | elTitle.textContent = "Scene"; 14 | 15 | this.element.append(elTitle); 16 | this.element.append(this.elTable); 17 | 18 | this.oldTableString = ""; 19 | } 20 | 21 | set(scene){ 22 | this.scene = scene; 23 | } 24 | 25 | update(){ 26 | 27 | let scene = Potree.instance.scene; 28 | 29 | let tableString = ""; 30 | 31 | let nodes = []; 32 | 33 | // let tableString = ""; 34 | tableString += ` \n`; 35 | tableString += ` \n`; 36 | tableString += ` \n`; 37 | tableString += ` \n`; 38 | tableString += ` \n`; 39 | tableString += ` \n`; 40 | tableString += ` \n`; 41 | 42 | scene.root.traverse(node => { 43 | let i = nodes.length; 44 | 45 | let isValidSceneObject = [ 46 | "PointCloudOctree", 47 | "Images360", 48 | "TDTiles" 49 | ].includes(node.constructor.name); 50 | 51 | if(isValidSceneObject){ 52 | tableString += ` 53 | 54 | 55 | 56 | 57 | 58 | 59 | `; 60 | nodes.push(node); 61 | } 62 | }); 63 | 64 | if(tableString !== this.oldTableString){ 65 | this.elTable.style.display = "grid"; 66 | this.elTable.innerHTML = tableString; 67 | this.oldTableString = tableString; 68 | 69 | for(let i = 0; i < nodes.length; i++){ 70 | let node = nodes[i]; 71 | 72 | let elLabel = this.elTable.querySelector(`td[name=label_${i}]`); 73 | let elCheckbox = this.elTable.querySelector(`input[name=item_${i}]`); 74 | let elZoom = this.elTable.querySelector(`input[name=zoom_${i}]`) 75 | ?? this.elTable.querySelector(`td[name=zoom_${i}]`); 76 | 77 | 78 | elLabel.innerHTML = node.name ?? `<${node.constructor.name} object>`; 79 | 80 | 81 | if(elLabel){ 82 | elLabel.onmouseenter = () => {node.isHighlighted = true;}; 83 | elLabel.onmouseleave = () => {node.isHighlighted = false;}; 84 | } 85 | 86 | if(elZoom){ 87 | elZoom.onmouseenter = () => {node.isHighlighted = true;}; 88 | elZoom.onmouseleave = () => {node.isHighlighted = false;}; 89 | } 90 | 91 | if(elCheckbox){ 92 | elCheckbox.checked = node.visible; 93 | elCheckbox.onclick = () => { 94 | node.visible = elCheckbox.checked; 95 | }; 96 | 97 | elCheckbox.onmouseenter = () => {node.isHighlighted = true;}; 98 | elCheckbox.onmouseleave = () => {node.isHighlighted = false;}; 99 | } 100 | 101 | if(elZoom){ 102 | elZoom.onclick = () => { 103 | potree.controls.zoomTo(node); 104 | }; 105 | } 106 | 107 | 108 | } 109 | } 110 | 111 | requestAnimationFrame(this.update.bind(this)); 112 | } 113 | 114 | } 115 | 116 | export function createPanel(){ 117 | let panel = new Panel(); 118 | panel.element.id = "scene_panel"; 119 | panel.element.classList.add("subsection_panel"); 120 | 121 | panel.update(); 122 | 123 | return panel; 124 | } -------------------------------------------------------------------------------- /src/modules/sidebar/sidebar.css.js: -------------------------------------------------------------------------------- 1 | 2 | export const css = ` 3 | 4 | 5 | tr:hover { 6 | background-color: coral; 7 | } 8 | 9 | 10 | #potree_sidebar{ 11 | background: #333333; 12 | color: #ffffff; 13 | font-family: Calibri; 14 | overflow: hidden; 15 | } 16 | 17 | #potree_sidebar_section_selection{ 18 | background: #333333; 19 | margin: 0 auto; 20 | padding: 5px 0 0 0; 21 | display: grid; 22 | grid-template-rows: repeat(10, 48px); 23 | } 24 | 25 | #potree_sidebar_main{ 26 | background: #252525; 27 | padding: 0.5em; 28 | } 29 | 30 | #potree_sidebar_content{ 31 | 32 | } 33 | 34 | #potree_sidebar_footer{ 35 | opacity: 0.6; 36 | } 37 | 38 | .potree_sidebar_section_button{ 39 | width: 32px; 40 | height: 32px; 41 | background-size: contain; 42 | background-repeat: no-repeat; 43 | background-color: transparent; 44 | border: none; 45 | margin: 4px 0px; 46 | } 47 | 48 | .potree_sidebar_section_button:hover{ 49 | filter: brightness(120%); 50 | filter: drop-shadow(0px 0px 3px white) drop-shadow(0px 0px 3px white); 51 | } 52 | 53 | .potree_sidebar_button{ 54 | width: 3em; 55 | height: 3em; 56 | background-size: contain; 57 | background-repeat: no-repeat; 58 | background-color: transparent; 59 | border: none; 60 | } 61 | 62 | .potree_sidebar_button:hover{ 63 | filter: brightness(120%); 64 | filter: drop-shadow(0px 0px 3px white) drop-shadow(0px 0px 3px white); 65 | } 66 | 67 | select{ 68 | width: 100%; 69 | } 70 | 71 | .subsection_panel{ 72 | margin: 0px 0px 25px 0px; 73 | } 74 | 75 | .subsection{ 76 | text-align: center; 77 | font-family: Calibri; 78 | font-size: 1.1em; 79 | font-weight: bold; 80 | padding: 0.1em 0em 0.5em 0em; 81 | letter-spacing: 1px; 82 | z-index: 1; 83 | overflow: hidden; 84 | width: 100%; 85 | display: flex; 86 | gap: 10px; 87 | text-wrap: nowrap; 88 | } 89 | .subsection:before, .subsection:after { 90 | position: relative; 91 | top: 51%; 92 | overflow: hidden; 93 | width: 100%; 94 | height: 1px; 95 | content: '-'; 96 | background-color: #888888; 97 | align-self: center; 98 | } 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | .potree_gradient_button{ 108 | width: 100%; 109 | height: 3em; 110 | } 111 | 112 | .potree_gradient_button:hover { 113 | filter: brightness(120%); 114 | } 115 | 116 | table{ 117 | color: white; 118 | border: none; 119 | border-collapse: collapse; 120 | } 121 | 122 | tr{ 123 | border: none; 124 | padding: none; 125 | margin: none; 126 | } 127 | 128 | .sidebarlabel{ 129 | white-space: nowrap 130 | } 131 | 132 | sidebarlabel{ 133 | white-space: nowrap 134 | } 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | section.range-slider { 144 | position: relative; 145 | user-select: none; 146 | } 147 | 148 | section.range-slider input { 149 | position: absolute; 150 | pointer-events: none; 151 | outline: none; 152 | -webkit-appearance: none; 153 | background: none; 154 | width: 100%; 155 | } 156 | 157 | section.range-slider input::-webkit-slider-thumb { 158 | pointer-events: all; 159 | position: relative; 160 | z-index: 1; 161 | outline: 0; 162 | } 163 | 164 | .range-slider-background{ 165 | position: absolute; 166 | background: lightgray; 167 | width: 100%; 168 | height: 0.5em; 169 | border-radius: 0.5em; 170 | top: calc(50% - 0.25em); 171 | } 172 | 173 | .range-slider-selected{ 174 | position: absolute; 175 | background: red; 176 | width: 50%; 177 | height: 0.5em; 178 | border-radius: 0.5em; 179 | top: calc(50% - 0.25em); 180 | } 181 | 182 | `; -------------------------------------------------------------------------------- /src/modules/toolbar/icons/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 39 | 42 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 66 | 73 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/modules/toolbar/icons/distance.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 39 | 42 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 59 | 64 | 74 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/modules/toolbar/icons/dotdotdot.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 21 | 27 | 28 | 29 | 49 | 52 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 69 | 75 | 81 | 87 | ... 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/modules/toolbar/icons/point.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 42 | 45 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 62 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/modules/toolbar/toolbar.css.js: -------------------------------------------------------------------------------- 1 | 2 | export const css = ` 3 | #potree_toolbar{ 4 | position: absolute; 5 | z-index: 10000; 6 | left: 100px; 7 | top: 0px; 8 | background: black; 9 | color: white; 10 | padding: 0.3em 0.8em; 11 | font-family: "system-ui"; 12 | border-radius: 0em 0em 0.3em 0.3em; 13 | display: flex; 14 | transform: scale(1.3, 1.3); 15 | transform-origin: top left; 16 | } 17 | 18 | .potree_toolbar_label{ 19 | text-align: center; 20 | font-size: smaller; 21 | opacity: 0.9; 22 | padding: 0.1em 0.5em 0.5em 0.5em; 23 | } 24 | 25 | .potree_toolbar_separator{ 26 | background: white; 27 | padding: 0px; 28 | margin: 5px 10px; 29 | width: 1px; 30 | } 31 | 32 | .potree_toolbar_gradient_button{ 33 | width: 2em; 34 | height: 3em; 35 | } 36 | 37 | .potree_toolbar_gradient_button:hover { 38 | filter: brightness(120%); 39 | } 40 | 41 | .potree_toolbar_button{ 42 | width: 3em; 43 | height: 3em; 44 | background-size: contain; 45 | background-repeat: no-repeat; 46 | background-color: transparent; 47 | border: none; 48 | } 49 | 50 | .potree_toolbar_button:hover{ 51 | filter: brightness(120%); 52 | filter: drop-shadow(0px 0px 6px white) drop-shadow(0px 0px 6px white); 53 | } 54 | 55 | .potree_toolbar_dropdown_button{ 56 | background: none; 57 | border: none; 58 | width: 100%; 59 | color: white; 60 | opacity: 0.5; 61 | font-weight: bold; 62 | background-position: center; 63 | background-repeat: no-repeat; 64 | background-size: 1.6em; 65 | height: 0.8em; 66 | margin-top: 0.2em; 67 | 68 | } 69 | 70 | .potree_toolbar_dropdown_button:hover{ 71 | filter: brightness(120%); 72 | filter: drop-shadow(0px 0px 3px white); 73 | border: 1px solid rgba(255, 255, 255, 0.4); 74 | opacity: 1; 75 | } 76 | 77 | `; -------------------------------------------------------------------------------- /src/potree/ChunkedBuffer.js: -------------------------------------------------------------------------------- 1 | 2 | const {floor} = Math; 3 | 4 | class Chunk{ 5 | 6 | constructor(offset, size){ 7 | this.offset = offset; 8 | this.size = size; 9 | } 10 | 11 | } 12 | 13 | export class ChunkedBuffer{ 14 | 15 | constructor(capacity, chunkSize, renderer){ 16 | 17 | this.renderer = renderer; 18 | this.capacity = capacity; 19 | this.chunkSize = chunkSize; 20 | this.numChunks = floor(capacity / chunkSize); 21 | this.chunks = new Array(this.numChunks); 22 | this.chunkPointer = 0; 23 | this.gpuBuffer = renderer.device.createBuffer({ 24 | size: capacity, 25 | usage: GPUBufferUsage.VERTEX 26 | | GPUBufferUsage.STORAGE 27 | | GPUBufferUsage.COPY_SRC 28 | | GPUBufferUsage.COPY_DST 29 | | GPUBufferUsage.UNIFORM, 30 | }); 31 | 32 | for(let i = 0; i < this.numChunks; i++){ 33 | this.chunks[i] = new Chunk(i * chunkSize, chunkSize); 34 | } 35 | 36 | } 37 | 38 | acquire(numBytes){ 39 | 40 | let numChunks = floor((numBytes + this.chunkSize - 1) / this.chunkSize); 41 | 42 | let chunks = []; 43 | for(let i = 0; i < numChunks; i++){ 44 | 45 | let chunk = this.chunks[this.chunkPointer + i]; 46 | 47 | chunks.push(chunk); 48 | } 49 | 50 | this.chunkPointer += numChunks; 51 | 52 | return chunks; 53 | } 54 | 55 | release(chunks){ 56 | 57 | for(let i = 0; i < chunks.length; i++){ 58 | this.chunkPointer--; 59 | this.chunks[this.chunkPointer] = chunks[i]; 60 | } 61 | 62 | } 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/potree/LRU.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | class LRUItem{ 4 | 5 | constructor(node){ 6 | this.previous = null; 7 | this.next = null; 8 | this.node = node; 9 | this.timestamp = 0; 10 | } 11 | 12 | } 13 | 14 | /** 15 | * 16 | * @class A doubly-linked-list of the least recently used elements. 17 | */ 18 | class LRU{ 19 | 20 | constructor(){ 21 | // the least recently used item 22 | this.oldest = null; 23 | // the most recently used item 24 | this.newest = null; 25 | // a list of all items in the lru list 26 | this.items = new Map(); 27 | this.elements = 0; 28 | } 29 | 30 | size(){ 31 | return this.elements; 32 | } 33 | 34 | contains(node){ 35 | return this.items[node.id] == null; 36 | } 37 | 38 | touch(node, timestamp){ 39 | 40 | let item; 41 | if (!this.items.has(node)) { 42 | // add to list 43 | item = new LRUItem(node); 44 | item.previous = this.newest; 45 | item.timestamp = timestamp; 46 | 47 | this.newest = item; 48 | if (item.previous !== null) { 49 | item.previous.next = item; 50 | } 51 | 52 | this.items.set(node, item); 53 | this.elements++; 54 | 55 | if (this.oldest === null) { 56 | this.oldest = item; 57 | } 58 | } else { 59 | // update in list 60 | item = this.items.get(node); 61 | item.timestamp = timestamp; 62 | 63 | if (item.previous === null) { 64 | // handle touch on oldest element 65 | if (item.next !== null) { 66 | this.oldest = item.next; 67 | this.oldest.previous = null; 68 | item.previous = this.newest; 69 | item.next = null; 70 | this.newest = item; 71 | item.previous.next = item; 72 | } 73 | } else if (item.next === null) { 74 | // handle touch on newest element 75 | } else { 76 | // handle touch on any other element 77 | item.previous.next = item.next; 78 | item.next.previous = item.previous; 79 | item.previous = this.newest; 80 | item.next = null; 81 | this.newest = item; 82 | item.previous.next = item; 83 | } 84 | } 85 | } 86 | 87 | remove(node){ 88 | let lruItem = this.items.get(node); 89 | 90 | if (lruItem) { 91 | if (this.elements === 1) { 92 | this.oldest = null; 93 | this.newest = null; 94 | } else { 95 | if (!lruItem.previous) { 96 | this.oldest = lruItem.next; 97 | this.oldest.previous = null; 98 | } 99 | if (!lruItem.next) { 100 | this.newest = lruItem.previous; 101 | this.newest.next = null; 102 | } 103 | if (lruItem.previous && lruItem.next) { 104 | lruItem.previous.next = lruItem.next; 105 | lruItem.next.previous = lruItem.previous; 106 | } 107 | } 108 | 109 | // delete this.items[node.id]; 110 | this.items.delete(node); 111 | this.elements--; 112 | this.numPoints -= node.numPoints; 113 | } 114 | } 115 | 116 | getLRUItem(){ 117 | if (this.oldest === null) { 118 | return null; 119 | } 120 | let lru = this.oldest; 121 | 122 | return lru.node; 123 | } 124 | 125 | toString(){ 126 | let string = '{ '; 127 | let curr = this.oldest; 128 | while (curr !== null) { 129 | string += curr.node.id; 130 | if (curr.next !== null) { 131 | string += ', '; 132 | } 133 | curr = curr.next; 134 | } 135 | string += '}'; 136 | string += '(' + this.size() + ')'; 137 | return string; 138 | } 139 | } 140 | 141 | export {LRU, LRUItem}; -------------------------------------------------------------------------------- /src/potree/PointAttributes.js: -------------------------------------------------------------------------------- 1 | 2 | const PointAttributeTypes = { 3 | DOUBLE: {ordinal: 0, name: "double", size: 8}, 4 | FLOAT: {ordinal: 1, name: "float", size: 4}, 5 | INT8: {ordinal: 2, name: "int8", size: 1}, 6 | UINT8: {ordinal: 3, name: "uint8", size: 1}, 7 | INT16: {ordinal: 4, name: "int16", size: 2}, 8 | UINT16: {ordinal: 5, name: "uint16", size: 2}, 9 | INT32: {ordinal: 6, name: "int32", size: 4}, 10 | UINT32: {ordinal: 7, name: "uint32", size: 4}, 11 | INT64: {ordinal: 8, name: "int64", size: 8}, 12 | UINT64: {ordinal: 9, name: "uint64", size: 8} 13 | }; 14 | 15 | export {PointAttributeTypes}; 16 | 17 | export class PointAttribute{ 18 | 19 | constructor(name, type, numElements){ 20 | this.name = name; 21 | this.type = type; 22 | this.numElements = numElements; 23 | this.byteOffset = null; 24 | this.byteSize = this.numElements * this.type.size; 25 | this.description = ""; 26 | this.scale = [1.0, 1.0, 1.0]; 27 | this.offset = [0.0, 0.0, 0.0]; 28 | this.range = [Infinity, -Infinity]; 29 | } 30 | 31 | }; 32 | 33 | export class PointAttributes{ 34 | 35 | constructor(pointAttributes){ 36 | this.attributes = []; 37 | this.byteSize = 0; 38 | this.size = 0; 39 | this.vectors = []; 40 | 41 | if (pointAttributes != null) { 42 | for (let i = 0; i < pointAttributes.length; i++) { 43 | let pointAttribute = pointAttributes[i]; 44 | this.attributes.push(pointAttribute); 45 | pointAttribute.byteOffset = this.byteSize; 46 | this.byteSize += pointAttribute.byteSize; 47 | this.size++; 48 | } 49 | } 50 | } 51 | 52 | add(pointAttribute){ 53 | this.attributes.push(pointAttribute); 54 | pointAttribute.byteOffset = this.byteSize; 55 | this.byteSize += pointAttribute.byteSize; 56 | this.size++; 57 | } 58 | 59 | indexOf(name){ 60 | for(let i = 0; i < this.attributes.length; i++){ 61 | if(this.attributes[i].name === name){ 62 | return i; 63 | } 64 | } 65 | 66 | return -1; 67 | } 68 | 69 | get(name){ 70 | return this.attributes.find(a => a.name === name); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/potree/octree/loader_v3/DecoderWorker.js: -------------------------------------------------------------------------------- 1 | import {loadVoxels} from "./loadVoxels.js"; 2 | import {loadPoints, loadPointsBrotli} from "./loadPoints.js"; 3 | 4 | // - voxels are encoded relative to parent. 5 | // - the children of this chunk's root need the parent voxels. 6 | // - stores that 7 | // - note that the root of this chunk also needs parent voxels, 8 | // but these are passed from the main thread via postMessage 9 | let parentVoxelCoords = null; 10 | 11 | function loadNode(octree, node, dataview){ 12 | 13 | if(node.numVoxels > 0){ 14 | return loadVoxels(octree, node, dataview, parentVoxelCoords); 15 | }else if(node.numPoints > 0){ 16 | // debugger; 17 | return loadPointsBrotli(octree, node, dataview); 18 | } 19 | 20 | } 21 | 22 | async function loadNodes(event){ 23 | 24 | let {octree, nodes, url} = event.data; 25 | 26 | let chunkOffset = Infinity; 27 | let chunkSize = 0; 28 | 29 | let byLevel = (a, b) => a.level - b.level; 30 | nodes.sort(byLevel); 31 | 32 | for(let node of nodes){ 33 | chunkOffset = Math.min(chunkOffset, node.byteOffset); 34 | chunkSize += node.byteSize; 35 | } 36 | 37 | chunkOffset += event.data.metadata.pointBuffer.offset; 38 | 39 | let numElements = nodes.reduce( (sum, node) => sum + node.numPoints + node.numVoxels, 0); 40 | let bitsPerElement = Math.ceil(8 * chunkSize / numElements); 41 | 42 | // let strChunkSize = chunkSize.toLocaleString().padStart(10); 43 | // let strNumElements = numElements.toLocaleString().padStart(8); 44 | // let strBpe = bitsPerElement.toLocaleString().padStart(4); 45 | // let strBytes = (bitsPerElement / 8).toFixed(1).padStart(4); 46 | // console.log(`#nodes: ${nodes.length}, chunkSize: ${strChunkSize}, numElements: ${strNumElements}, bpe: ${strBpe} (${strBytes} bytes)`); 47 | 48 | // Add some infos to the url for debugging. 49 | let urlWithInfos = new URL(url); 50 | urlWithInfos.searchParams.set("query", "loadNodes"); 51 | urlWithInfos.searchParams.set("numNodes", nodes.length); 52 | 53 | let has = new Set(); 54 | for(let node of nodes){ 55 | if(node.numPoints > 0) has.add("points"); 56 | if(node.numVoxels > 0) has.add("voxels"); 57 | } 58 | urlWithInfos.searchParams.set("has", [...has].join("_")); 59 | 60 | 61 | 62 | let response = await fetch(urlWithInfos, { 63 | headers: { 64 | 'content-type': 'multipart/byteranges', 65 | 'Range': `bytes=${chunkOffset}-${chunkOffset + chunkSize - 1}`, 66 | }, 67 | }); 68 | 69 | // console.log(`loading chunk offset ${chunkOffset.toLocaleString()}. size ${chunkSize.toLocaleString()}`); 70 | 71 | let buffer = await response.arrayBuffer(); 72 | 73 | parentVoxelCoords = event.data.parentVoxelCoords; 74 | 75 | for(let node of nodes){ 76 | // debugger; 77 | let dataview = new DataView(buffer, 78 | node.byteOffset - chunkOffset + event.data.metadata.pointBuffer.offset, 79 | node.byteSize); 80 | 81 | let buffers = loadNode(octree, node, dataview); 82 | 83 | // clone voxel coords, the child nodes need them to decode their coords 84 | // if(node === nodes[0] && node.numVoxels > 0){ 85 | // let voxelCoords = buffers.voxelCoords; 86 | // parentVoxelCoords = new Uint8Array(voxelCoords.byteLength); 87 | // parentVoxelCoords.set(voxelCoords); 88 | // } 89 | 90 | // done loading node, send results to main thread 91 | let message = { 92 | type: "node parsed", 93 | name: node.name, 94 | buffer: buffers.buffer, 95 | voxelCoords: buffers.voxelCoords 96 | }; 97 | let transferables = [message.buffer]; 98 | 99 | if(buffers.voxelCoords){ 100 | transferables.push(buffers.voxelCoords.buffer); 101 | } 102 | 103 | postMessage(message, transferables); 104 | } 105 | 106 | } 107 | 108 | onmessage = async function (event) { 109 | 110 | let promise = loadNodes(event); 111 | 112 | promise.then(e => { 113 | postMessage("finished"); 114 | }); 115 | 116 | // Chrome frequently fails with range requests. 117 | // Notify main thread that loading failed, so that it can try again. 118 | promise.catch(e => { 119 | console.log(e); 120 | postMessage("failed"); 121 | }); 122 | 123 | 124 | }; 125 | -------------------------------------------------------------------------------- /src/potree/octree/loader_v3/DecoderWorker_points.js: -------------------------------------------------------------------------------- 1 | 2 | // import {BrotliDecode} from "../../../../libs/brotli/decode.js"; 3 | 4 | function round4(number){ 5 | return number + (4 - (number % 4)); 6 | } 7 | 8 | async function loadNode(event){ 9 | let data = event.data; 10 | let n = event.data.numElements; 11 | let {scale, offset} = data; 12 | 13 | let bufferSize = round4(18 * n); 14 | let buffer = new ArrayBuffer(bufferSize); 15 | 16 | let first = event.data.metadata.pointBuffer.offset + data.byteOffset; 17 | let last = first + data.byteSize - 1; 18 | 19 | let response = await fetch(data.url, { 20 | headers: { 21 | 'content-type': 'multipart/byteranges', 22 | 'Range': `bytes=${first}-${last}`, 23 | }, 24 | }); 25 | 26 | { 27 | let nmin = data.nodeMin; 28 | let nmax = data.nodeMax; 29 | let nsize = nmax[0] - nmin[0]; 30 | let cellsize = nsize / 128; 31 | let precision = 0.001; 32 | let states = cellsize / precision; 33 | let bits = (Math.log2(states)).toFixed(1); 34 | 35 | // console.log(`leaf, size: ${nsize}, cellsize: ${cellsize.toFixed(3)}, bits: ${bits}`); 36 | 37 | } 38 | 39 | let s_stride = data.byteSize / n; 40 | 41 | let source = await response.arrayBuffer(); 42 | let sourceView = new DataView(source); 43 | let targetView = new DataView(buffer); 44 | let t_offset_xyz = 0; 45 | let t_offset_rgb = 12 * n; 46 | let s_offset_rgb = 0; 47 | for(let attribute of data.pointAttributes.attributes){ 48 | 49 | if(["rgb", "rgba"].includes(attribute.name)){ 50 | s_offset_rgb = attribute.byteOffset; 51 | break; 52 | } 53 | 54 | } 55 | 56 | for(let i = 0; i < n; i++){ 57 | let X = sourceView.getInt32(s_stride * i + 0, true); 58 | let Y = sourceView.getInt32(s_stride * i + 4, true); 59 | let Z = sourceView.getInt32(s_stride * i + 8, true); 60 | 61 | let x = X * scale[0] + offset[0] - data.min[0]; 62 | let y = Y * scale[1] + offset[1] - data.min[1]; 63 | let z = Z * scale[2] + offset[2] - data.min[2]; 64 | 65 | // let r = sourceView.getUint8(s_stride * i + 12); 66 | // let g = sourceView.getUint8(s_stride * i + 13); 67 | // let b = sourceView.getUint8(s_stride * i + 14); 68 | let R = sourceView.getUint16(s_stride * i + s_offset_rgb + 0, true); 69 | let G = sourceView.getUint16(s_stride * i + s_offset_rgb + 2, true); 70 | let B = sourceView.getUint16(s_stride * i + s_offset_rgb + 4, true); 71 | 72 | let r = R < 256 ? R : R / 256; 73 | let g = G < 256 ? G : G / 256; 74 | let b = B < 256 ? B : B / 256; 75 | // debugger; 76 | 77 | targetView.setFloat32(t_offset_xyz + 12 * i + 0, x, true); 78 | targetView.setFloat32(t_offset_xyz + 12 * i + 4, y, true); 79 | targetView.setFloat32(t_offset_xyz + 12 * i + 8, z, true); 80 | targetView.setUint16(t_offset_rgb + 6 * i + 0, r, true); 81 | targetView.setUint16(t_offset_rgb + 6 * i + 2, g, true); 82 | targetView.setUint16(t_offset_rgb + 6 * i + 4, b, true); 83 | } 84 | 85 | let message = { 86 | type: "points parsed", 87 | buffer 88 | }; 89 | let transferables = [buffer]; 90 | 91 | postMessage(message, transferables); 92 | } 93 | 94 | onmessage = async function (event) { 95 | 96 | let promise = loadNode(event); 97 | 98 | // Chrome frequently fails with range requests. 99 | // Notify main thread that loading failed, so that it can try again. 100 | promise.catch(e => { 101 | console.log(e); 102 | postMessage("failed"); 103 | }); 104 | 105 | }; 106 | -------------------------------------------------------------------------------- /src/potree/octree/mappings/map_rgb.js: -------------------------------------------------------------------------------- 1 | 2 | export const wgsl = ` 3 | 4 | fn mapping_RGB(vertex : VertexInput, attrib : AttributeDescriptor, node : Node) -> vec4{ 5 | 6 | var offset = node.numPoints * attrib.offset + attrib.byteSize * vertex.vertexID; 7 | 8 | var r = 0.0; 9 | var g = 0.0; 10 | var b = 0.0; 11 | 12 | if(attrib.datatype == TYPES_UINT8){ 13 | r = f32(readU8(offset + 0u)); 14 | g = f32(readU8(offset + 1u)); 15 | b = f32(readU8(offset + 2u)); 16 | }else if(attrib.datatype == TYPES_UINT16){ 17 | r = f32(readU16(offset + 0u)); 18 | g = f32(readU16(offset + 2u)); 19 | b = f32(readU16(offset + 4u)); 20 | } 21 | 22 | if(r > 255.0){ 23 | r = r / 256.0; 24 | } 25 | if(g > 255.0){ 26 | g = g / 256.0; 27 | } 28 | if(b > 255.0){ 29 | b = b / 256.0; 30 | } 31 | 32 | color = vec4(r, g, b, 255.0) / 255.0; 33 | 34 | return color; 35 | } 36 | `; 37 | 38 | -------------------------------------------------------------------------------- /src/potree/octree/materials/mat_elevation.js: -------------------------------------------------------------------------------- 1 | 2 | const wgsl = ` 3 | 4 | fn map_rgb(vertex : VertexInput, attrib : AttributeDescriptor, node : Node) -> vec4{ 5 | 6 | var offset = node.numPoints * attrib.offset + attrib.byteSize * vertex.vertexID; 7 | 8 | var r = 0.0; 9 | var g = 0.0; 10 | var b = 0.0; 11 | 12 | if(attrib.datatype == TYPES_UINT8){ 13 | r = f32(readU8(offset + 0u)); 14 | g = f32(readU8(offset + 1u)); 15 | b = f32(readU8(offset + 2u)); 16 | }else if(attrib.datatype == TYPES_UINT16){ 17 | r = f32(readU16(offset + 0u)); 18 | g = f32(readU16(offset + 2u)); 19 | b = f32(readU16(offset + 4u)); 20 | } 21 | 22 | if(r > 255.0){ 23 | r = r / 256.0; 24 | } 25 | if(g > 255.0){ 26 | g = g / 256.0; 27 | } 28 | if(b > 255.0){ 29 | b = b / 256.0; 30 | } 31 | 32 | color = vec4(r, g, b, 255.0) / 255.0; 33 | 34 | return color; 35 | } 36 | 37 | `; 38 | 39 | class RgbMaterial{ 40 | 41 | constructor(){ 42 | this.attributeIndex = 0; 43 | this.wgsl = wgsl; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/potree/octree/materials/mat_rgb.js: -------------------------------------------------------------------------------- 1 | 2 | const wgsl = ` 3 | 4 | fn map_rgb(vertex : VertexInput, attrib : AttributeDescriptor, node : Node) -> vec4{ 5 | 6 | var offset = node.numPoints * attrib.offset + attrib.byteSize * vertex.vertexID; 7 | 8 | var r = 0.0; 9 | var g = 0.0; 10 | var b = 0.0; 11 | 12 | if(attrib.datatype == TYPES_UINT8){ 13 | r = f32(readU8(offset + 0u)); 14 | g = f32(readU8(offset + 1u)); 15 | b = f32(readU8(offset + 2u)); 16 | }else if(attrib.datatype == TYPES_UINT16){ 17 | r = f32(readU16(offset + 0u)); 18 | g = f32(readU16(offset + 2u)); 19 | b = f32(readU16(offset + 4u)); 20 | } 21 | 22 | if(r > 255.0){ 23 | r = r / 256.0; 24 | } 25 | if(g > 255.0){ 26 | g = g / 256.0; 27 | } 28 | if(b > 255.0){ 29 | b = b / 256.0; 30 | } 31 | 32 | color = vec4(r, g, b, 255.0) / 255.0; 33 | 34 | return color; 35 | } 36 | 37 | `; 38 | 39 | class RgbMaterial{ 40 | 41 | constructor(){ 42 | this.attributeIndex = 0; 43 | this.wgsl = wgsl; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/prototyping/LoadWorker.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | onmessage = async function(e) { 4 | 5 | let tStart = performance.now(); 6 | 7 | let files = e.data; 8 | 9 | let printElapsed = (label) => { 10 | let duration = performance.now() - tStart; 11 | console.log(`${label}: ${(duration / 1000).toFixed(3)}s`); 12 | }; 13 | 14 | let blobs = []; 15 | for (let file of files) { 16 | let promise = file.slice(0, 10); 17 | blobs.push(promise); 18 | 19 | if(blobs.length >= 32){ 20 | break; 21 | } 22 | } 23 | 24 | printElapsed("00"); 25 | await Promise.all(blobs); 26 | printElapsed("10"); 27 | 28 | 29 | let buffers = blobs.map(blob => blob.arrayBuffer()); 30 | printElapsed("20"); 31 | await Promise.all(buffers); 32 | printElapsed("30"); 33 | 34 | let duration = performance.now() - tStart; 35 | console.log(`duration: ${(duration / 1000).toFixed(3)}s`); 36 | 37 | } -------------------------------------------------------------------------------- /src/prototyping/generate_voxels_compute/common.js: -------------------------------------------------------------------------------- 1 | 2 | import {Box3} from "potree"; 3 | 4 | export const chunkGridSize = 2; 5 | export const voxelGridSize = 32; 6 | export const maxTrianglesPerNode = 10_000; 7 | 8 | export function toIndex1D(gridSize, voxelPos){ 9 | 10 | return voxelPos.x 11 | + gridSize * voxelPos.y 12 | + gridSize * gridSize * voxelPos.z; 13 | } 14 | 15 | export function toIndex3D(gridSize, voxelIndex){ 16 | let z = Math.floor(voxelIndex / (gridSize * gridSize)); 17 | let y = Math.floor((voxelIndex - gridSize * gridSize * z) / gridSize); 18 | let x = voxelIndex % gridSize; 19 | 20 | return new Vector3(x, y, z); 21 | } 22 | 23 | export function computeChildBox(parentBox, childIndex){ 24 | 25 | let center = parentBox.center(); 26 | let box = new Box3(); 27 | 28 | if((childIndex & 0b001) === 0){ 29 | box.min.x = parentBox.min.x; 30 | box.max.x = center.x; 31 | }else{ 32 | box.min.x = center.x; 33 | box.max.x = parentBox.max.x; 34 | } 35 | 36 | if((childIndex & 0b010) === 0){ 37 | box.min.y = parentBox.min.y; 38 | box.max.y = center.y; 39 | }else{ 40 | box.min.y = center.y; 41 | box.max.y = parentBox.max.y; 42 | } 43 | 44 | if((childIndex & 0b100) === 0){ 45 | box.min.z = parentBox.min.z; 46 | box.max.z = center.z; 47 | }else{ 48 | box.min.z = center.z; 49 | box.max.z = parentBox.max.z; 50 | } 51 | 52 | return box; 53 | } 54 | 55 | export let storage_flags = GPUBufferUsage.STORAGE 56 | | GPUBufferUsage.COPY_SRC 57 | | GPUBufferUsage.COPY_DST 58 | | GPUBufferUsage.VERTEX 59 | | GPUBufferUsage.INDEX; 60 | export let uniform_flags = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; 61 | 62 | export class Chunk{ 63 | 64 | constructor(){ 65 | this.triangleOffset = 0; 66 | this.numTriangles = 0; 67 | this.boundingBox = new Box3(); 68 | this.level = 0; 69 | this.index = 0; 70 | this.parent = null; 71 | this.children = new Array(8).fill(null) 72 | this.visible = true; 73 | this.processing = false; 74 | this.processed = false; 75 | } 76 | 77 | traverse(callback){ 78 | 79 | let shouldContinue = callback(this) ?? true; 80 | 81 | if(!shouldContinue){ 82 | return; 83 | } 84 | 85 | for(let child of this.children){ 86 | if(child){ 87 | child.traverse(callback); 88 | } 89 | } 90 | 91 | } 92 | 93 | }; 94 | -------------------------------------------------------------------------------- /src/prototyping/generate_voxels_compute/generate_voxels_compute.js: -------------------------------------------------------------------------------- 1 | 2 | import {chunkGridSize, voxelGridSize, toIndex1D, toIndex3D} from "./common.js"; 3 | import {doDownsampling} from "./downsampling.js"; 4 | import {doChunking} from "./chunking.js"; 5 | import {Matrix4} from "potree"; 6 | 7 | 8 | export async function generateVoxelsCompute(renderer, node){ 9 | 10 | console.time("generate voxels"); 11 | 12 | 13 | potree.onUpdate( () => { 14 | let positions = node.geometry.buffers.find(b => b.name === "position").buffer; 15 | // let colors = node.geometry.buffers.find(b => b.name === "color").buffer; 16 | let uvs = node.geometry.buffers.find(b => b.name === "uv").buffer; 17 | let indices = node.geometry.indices; 18 | 19 | let s = 1; 20 | let world = new Matrix4().set( 21 | s, 0, 0, 0, 22 | 0, s, 0, 0, 23 | 0, 0, s, 0, 24 | 0, 0, 0, 1, 25 | ); 26 | potree.renderer.drawMesh({positions, uvs, indices, world, image: node.material.image}); 27 | 28 | { 29 | let cube = node.boundingBox.cube(); 30 | let position = cube.center(); 31 | let size = cube.size(); 32 | let color = new Vector3(255, 0, 0); 33 | potree.renderer.drawBoundingBox(position, size, color); 34 | } 35 | }); 36 | 37 | // doDownsampling(renderer, node); 38 | 39 | // let meshes = await doChunking(renderer, node); 40 | // for(let mesh of meshes){ 41 | // doDownsampling(renderer, mesh); 42 | // } 43 | 44 | // for(let mesh of meshes){ 45 | // potree.onUpdate( () => { 46 | // let positions = mesh.geometry.buffers[0].buffer; 47 | // let colors = mesh.geometry.buffers[1].buffer; 48 | // potree.renderer.drawMesh({positions, colors}); 49 | // }); 50 | // } 51 | 52 | 53 | 54 | // doDownsampling(renderer, node); 55 | 56 | 57 | } -------------------------------------------------------------------------------- /src/prototyping/generate_voxels_compute/remove_indices.js: -------------------------------------------------------------------------------- 1 | 2 | import { Vector3 } from "../../math/Vector3.js"; 3 | import {Chunk, voxelGridSize, toIndex1D, toIndex3D, computeChildBox} from "./common.js"; 4 | import {storage_flags, uniform_flags} from "./common.js"; 5 | import { transferTriangles } from "./transferTriangles.js"; 6 | import {Geometry, Mesh, Box3} from "potree"; 7 | 8 | export let csChunking = ` 9 | 10 | struct Uniforms { 11 | chunkGridSize : u32; 12 | pad_1 : u32; 13 | pad_0 : u32; 14 | batchIndex : u32; 15 | boxMin : vec4; // offset(16) 16 | boxMax : vec4; // offset(32) 17 | }; 18 | 19 | struct Batch { 20 | numTriangles : u32; 21 | firstTriangle : u32; 22 | lutCounter : atomic; 23 | }; 24 | 25 | struct F32s { values : array; }; 26 | struct U32s { values : array }; 27 | struct I32s { values : array; }; 28 | struct AU32s { values : array>; }; 29 | struct AI32s { values : array>; }; 30 | struct Batches { values : array; }; 31 | 32 | [[binding( 0), group(0)]] var uniforms : Uniforms; 33 | 34 | [[binding(10), group(0)]] var indices : U32s; 35 | [[binding(11), group(0)]] var positions : F32s; 36 | [[binding(12), group(0)]] var colors : U32s; 37 | 38 | [[binding(50), group(0)]] var sortedTriangles : U32s; 39 | [[binding(50), group(0)]] var sortedTriangles : U32s; 40 | 41 | 42 | [[stage(compute), workgroup_size(128)]] 43 | fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3) { 44 | 45 | doIgnore(); 46 | 47 | let batch = &batches.values[uniforms.batchIndex]; 48 | 49 | if(GlobalInvocationID.x >= (*batch).numTriangles){ 50 | return; 51 | } 52 | 53 | } 54 | 55 | 56 | `; 57 | 58 | const maxBatchSize = 10_000_000; 59 | 60 | export async function doChunking(renderer, mesh){ 61 | 62 | 63 | 64 | 65 | } -------------------------------------------------------------------------------- /src/prototyping/generate_voxels_compute_2021.08.18/common.js: -------------------------------------------------------------------------------- 1 | 2 | import {Box3} from "potree"; 3 | 4 | export let chunkGridSize = 2; 5 | export let voxelGridSize = 32; 6 | 7 | export function toIndex1D(gridSize, voxelPos){ 8 | 9 | return voxelPos.x 10 | + gridSize * voxelPos.y 11 | + gridSize * gridSize * voxelPos.z; 12 | } 13 | 14 | export function toIndex3D(gridSize, voxelIndex){ 15 | let z = Math.floor(voxelIndex / (gridSize * gridSize)); 16 | let y = Math.floor((voxelIndex - gridSize * gridSize * z) / gridSize); 17 | let x = voxelIndex % gridSize; 18 | 19 | return new Vector3(x, y, z); 20 | } 21 | 22 | export let storage_flags = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST; 23 | export let uniform_flags = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; 24 | 25 | export class Chunk{ 26 | 27 | constructor(){ 28 | this.voxelOffset = 0; 29 | this.numVoxels = 0; 30 | this.triangleOffset = 0; 31 | this.numTriangles = 0; 32 | this.boundingBox = new Box3(); 33 | 34 | this.children = []; 35 | } 36 | 37 | }; -------------------------------------------------------------------------------- /src/prototyping/generate_voxels_compute_2021.08.18/generate_voxels_compute.js: -------------------------------------------------------------------------------- 1 | 2 | import {chunkGridSize, voxelGridSize, toIndex1D, toIndex3D} from "./common.js"; 3 | import {storage_flags, uniform_flags} from "./common.js"; 4 | import {doChunking} from "./chunking.js"; 5 | import {doDownsampling} from "./downsampling.js"; 6 | 7 | 8 | export async function generateVoxelsCompute(renderer, node){ 9 | 10 | let {device} = renderer; 11 | 12 | let numTriangles = node.geometry.indices.length / 3; 13 | let box = node.boundingBox.clone(); 14 | let cube = box.cube(); 15 | 16 | console.time("generate voxels"); 17 | 18 | let result_chunking = await doChunking(renderer, node); 19 | let result_downsampling = await doDownsampling(renderer, node, result_chunking); 20 | 21 | 22 | } -------------------------------------------------------------------------------- /src/prototyping/generate_voxels_compute_2021.08.28/common.js: -------------------------------------------------------------------------------- 1 | 2 | import {Box3} from "potree"; 3 | 4 | export let chunkGridSize = 2; 5 | export let voxelGridSize = 32; 6 | 7 | export function toIndex1D(gridSize, voxelPos){ 8 | 9 | return voxelPos.x 10 | + gridSize * voxelPos.y 11 | + gridSize * gridSize * voxelPos.z; 12 | } 13 | 14 | export function toIndex3D(gridSize, voxelIndex){ 15 | let z = Math.floor(voxelIndex / (gridSize * gridSize)); 16 | let y = Math.floor((voxelIndex - gridSize * gridSize * z) / gridSize); 17 | let x = voxelIndex % gridSize; 18 | 19 | return new Vector3(x, y, z); 20 | } 21 | 22 | export function computeChildBox(parentBox, childIndex){ 23 | 24 | let center = parentBox.center(); 25 | let box = new Box3(); 26 | 27 | if((childIndex & 0b001) === 0){ 28 | box.min.x = parentBox.min.x; 29 | box.max.x = center.x; 30 | }else{ 31 | box.min.x = center.x; 32 | box.max.x = parentBox.max.x; 33 | } 34 | 35 | if((childIndex & 0b010) === 0){ 36 | box.min.y = parentBox.min.y; 37 | box.max.y = center.y; 38 | }else{ 39 | box.min.y = center.y; 40 | box.max.y = parentBox.max.y; 41 | } 42 | 43 | if((childIndex & 0b100) === 0){ 44 | box.min.z = parentBox.min.z; 45 | box.max.z = center.z; 46 | }else{ 47 | box.min.z = center.z; 48 | box.max.z = parentBox.max.z; 49 | } 50 | 51 | return box; 52 | } 53 | 54 | export let storage_flags = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST; 55 | export let uniform_flags = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; 56 | 57 | export class Chunk{ 58 | 59 | constructor(){ 60 | this.triangleOffset = 0; 61 | this.numTriangles = 0; 62 | this.boundingBox = new Box3(); 63 | this.level = 0; 64 | this.index = 0; 65 | this.parent = null; 66 | this.children = new Array(8).fill(null) 67 | this.visible = true; 68 | this.processing = false; 69 | this.processed = false; 70 | } 71 | 72 | traverse(callback){ 73 | 74 | let shouldContinue = callback(this) ?? true; 75 | 76 | if(!shouldContinue){ 77 | return; 78 | } 79 | 80 | for(let child of this.children){ 81 | if(child){ 82 | child.traverse(callback); 83 | } 84 | } 85 | 86 | } 87 | 88 | }; 89 | -------------------------------------------------------------------------------- /src/prototyping/generate_voxels_compute_2021.08.28/generate_voxels_compute.js: -------------------------------------------------------------------------------- 1 | 2 | import {chunkGridSize, voxelGridSize, toIndex1D, toIndex3D} from "./common.js"; 3 | import {doDownsampling} from "./downsampling.js"; 4 | 5 | 6 | export async function generateVoxelsCompute(renderer, node){ 7 | 8 | console.time("generate voxels"); 9 | 10 | doDownsampling(renderer, node); 11 | 12 | 13 | } -------------------------------------------------------------------------------- /src/prototyping/generate_voxels_compute_2021.09.03/common.js: -------------------------------------------------------------------------------- 1 | 2 | import {Box3} from "potree"; 3 | 4 | export const chunkGridSize = 2; 5 | export const voxelGridSize = 32; 6 | export const maxTrianglesPerNode = 10_000; 7 | 8 | export function toIndex1D(gridSize, voxelPos){ 9 | 10 | return voxelPos.x 11 | + gridSize * voxelPos.y 12 | + gridSize * gridSize * voxelPos.z; 13 | } 14 | 15 | export function toIndex3D(gridSize, voxelIndex){ 16 | let z = Math.floor(voxelIndex / (gridSize * gridSize)); 17 | let y = Math.floor((voxelIndex - gridSize * gridSize * z) / gridSize); 18 | let x = voxelIndex % gridSize; 19 | 20 | return new Vector3(x, y, z); 21 | } 22 | 23 | export function computeChildBox(parentBox, childIndex){ 24 | 25 | let center = parentBox.center(); 26 | let box = new Box3(); 27 | 28 | if((childIndex & 0b001) === 0){ 29 | box.min.x = parentBox.min.x; 30 | box.max.x = center.x; 31 | }else{ 32 | box.min.x = center.x; 33 | box.max.x = parentBox.max.x; 34 | } 35 | 36 | if((childIndex & 0b010) === 0){ 37 | box.min.y = parentBox.min.y; 38 | box.max.y = center.y; 39 | }else{ 40 | box.min.y = center.y; 41 | box.max.y = parentBox.max.y; 42 | } 43 | 44 | if((childIndex & 0b100) === 0){ 45 | box.min.z = parentBox.min.z; 46 | box.max.z = center.z; 47 | }else{ 48 | box.min.z = center.z; 49 | box.max.z = parentBox.max.z; 50 | } 51 | 52 | return box; 53 | } 54 | 55 | export let storage_flags = GPUBufferUsage.STORAGE 56 | | GPUBufferUsage.COPY_SRC 57 | | GPUBufferUsage.COPY_DST 58 | | GPUBufferUsage.VERTEX 59 | | GPUBufferUsage.INDEX; 60 | export let uniform_flags = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; 61 | 62 | export class Chunk{ 63 | 64 | constructor(){ 65 | this.triangleOffset = 0; 66 | this.numTriangles = 0; 67 | this.boundingBox = new Box3(); 68 | this.level = 0; 69 | this.index = 0; 70 | this.parent = null; 71 | this.children = new Array(8).fill(null) 72 | this.visible = true; 73 | this.processing = false; 74 | this.processed = false; 75 | } 76 | 77 | traverse(callback){ 78 | 79 | let shouldContinue = callback(this) ?? true; 80 | 81 | if(!shouldContinue){ 82 | return; 83 | } 84 | 85 | for(let child of this.children){ 86 | if(child){ 87 | child.traverse(callback); 88 | } 89 | } 90 | 91 | } 92 | 93 | }; 94 | -------------------------------------------------------------------------------- /src/prototyping/generate_voxels_compute_2021.09.03/generate_voxels_compute.js: -------------------------------------------------------------------------------- 1 | 2 | import {chunkGridSize, voxelGridSize, toIndex1D, toIndex3D} from "./common.js"; 3 | import {doDownsampling} from "./downsampling.js"; 4 | import {doChunking} from "./chunking.js"; 5 | 6 | 7 | export async function generateVoxelsCompute(renderer, node){ 8 | 9 | console.time("generate voxels"); 10 | 11 | 12 | // potree.onUpdate( () => { 13 | // let positions = node.geometry.buffers.find(b => b.name === "position").buffer; 14 | // let colors = node.geometry.buffers.find(b => b.name === "color").buffer; 15 | // let indices = node.geometry.indices; 16 | // potree.renderer.drawMesh({positions, colors, indices}); 17 | // }); 18 | 19 | // doDownsampling(renderer, node); 20 | 21 | let meshes = await doChunking(renderer, node); 22 | for(let mesh of meshes){ 23 | doDownsampling(renderer, mesh); 24 | } 25 | 26 | // potree.onUpdate( () => { 27 | // // { 28 | // // let mesh = {positions, colors}; 29 | // // potree.renderer.drawMesh(mesh); 30 | // // } 31 | 32 | // // { 33 | // // let position = cube.center(); 34 | // // let size = cube.size(); 35 | // // let color = new Vector3(255, 0, 0); 36 | // // potree.renderer.drawBoundingBox(position, size, color); 37 | // // } 38 | 39 | // { 40 | // let chunkPos = node.boundingBox.center(); 41 | // let chunkSize = node.boundingBox.size(); 42 | // let color = new Vector3(0, 255, 0); 43 | // potree.renderer.drawBoundingBox(chunkPos, chunkSize, color); 44 | // } 45 | // }); 46 | 47 | // for(let mesh of meshes){ 48 | // potree.onUpdate( () => { 49 | // let positions = mesh.geometry.buffers[0].buffer; 50 | // let colors = mesh.geometry.buffers[1].buffer; 51 | // potree.renderer.drawMesh({positions, colors}); 52 | // }); 53 | // } 54 | 55 | 56 | 57 | // doDownsampling(renderer, node); 58 | 59 | 60 | } -------------------------------------------------------------------------------- /src/prototyping/generate_voxels_compute_2021.09.03/remove_indices.js: -------------------------------------------------------------------------------- 1 | 2 | import { Vector3 } from "../../math/Vector3.js"; 3 | import {Chunk, voxelGridSize, toIndex1D, toIndex3D, computeChildBox} from "./common.js"; 4 | import {storage_flags, uniform_flags} from "./common.js"; 5 | import { transferTriangles } from "./transferTriangles.js"; 6 | import {Geometry, Mesh, Box3} from "potree"; 7 | 8 | export let csChunking = ` 9 | 10 | struct Uniforms { 11 | chunkGridSize : u32; 12 | pad_1 : u32; 13 | pad_0 : u32; 14 | batchIndex : u32; 15 | boxMin : vec4; // offset(16) 16 | boxMax : vec4; // offset(32) 17 | }; 18 | 19 | struct Batch { 20 | numTriangles : u32; 21 | firstTriangle : u32; 22 | lutCounter : atomic; 23 | }; 24 | 25 | struct F32s { values : array; }; 26 | struct U32s { values : array }; 27 | struct I32s { values : array; }; 28 | struct AU32s { values : array>; }; 29 | struct AI32s { values : array>; }; 30 | struct Batches { values : array; }; 31 | 32 | [[binding( 0), group(0)]] var uniforms : Uniforms; 33 | 34 | [[binding(10), group(0)]] var indices : U32s; 35 | [[binding(11), group(0)]] var positions : F32s; 36 | [[binding(12), group(0)]] var colors : U32s; 37 | 38 | [[binding(50), group(0)]] var sortedTriangles : U32s; 39 | [[binding(50), group(0)]] var sortedTriangles : U32s; 40 | 41 | 42 | [[stage(compute), workgroup_size(128)]] 43 | fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3) { 44 | 45 | doIgnore(); 46 | 47 | let batch = &batches.values[uniforms.batchIndex]; 48 | 49 | if(GlobalInvocationID.x >= (*batch).numTriangles){ 50 | return; 51 | } 52 | 53 | } 54 | 55 | 56 | `; 57 | 58 | const maxBatchSize = 10_000_000; 59 | 60 | export async function doChunking(renderer, mesh){ 61 | 62 | 63 | 64 | 65 | } -------------------------------------------------------------------------------- /src/prototyping/glbloader/loadGLB.js: -------------------------------------------------------------------------------- 1 | 2 | export function loadGLB(url){ 3 | 4 | } -------------------------------------------------------------------------------- /src/prototyping/lasinfo.mjs: -------------------------------------------------------------------------------- 1 | 2 | import * as fs from "fs"; 3 | 4 | let files = [ 5 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120C7101A_1.laz", 6 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120C7101C_1.laz", 7 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120C7101B_1.laz", 8 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120C7101D_1.laz", 9 | 10 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120D7322D_1.laz", 11 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120D7322B_1.laz", 12 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120D7322A_1.laz", 13 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120D7322C_1.laz", 14 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120D7323A_1.laz", 15 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120D7323C_1.laz", 16 | 17 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120C7102A_1.laz", 18 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120C7102C_1.laz", 19 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120C7102B_1.laz", 20 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120C7102D_1.laz", 21 | 22 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120C7103A_1.laz", 23 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120C7103C_1.laz", 24 | 25 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120D7321D_1.laz", 26 | "E:/dev/pointclouds/opentopography/CA13_SAN_SIM/ot_35120D7321B_1.laz", 27 | 28 | ]; 29 | 30 | let sum = 0; 31 | for(let file of files){ 32 | let buffer = fs.readFileSync(file); 33 | 34 | let numPoints = buffer.readUInt32LE(107); 35 | sum += numPoints; 36 | } 37 | 38 | console.log(sum.toLocaleString()); -------------------------------------------------------------------------------- /src/prototyping/load.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/renderer/fillBuffer.js: -------------------------------------------------------------------------------- 1 | 2 | let cs = ` 3 | 4 | struct Uniforms { 5 | numElements : u32; 6 | value : u32; 7 | }; 8 | 9 | struct U32s { values : array }; 10 | 11 | @binding(0) @group(0) var uniforms : Uniforms; 12 | @binding(1) @group(0) var target : U32s; 13 | 14 | [[stage(compute), workgroup_size(128)]] 15 | fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3) { 16 | 17 | var index = GlobalInvocationID.x; 18 | 19 | if(index > uniforms.numElements){ 20 | return; 21 | } 22 | 23 | target.values[index] = uniforms.value; 24 | } 25 | `; 26 | 27 | 28 | let pipeline = null; 29 | let uniformBuffer = null; 30 | 31 | function init(renderer){ 32 | 33 | if(pipeline !== null){ 34 | return; 35 | } 36 | 37 | let {device} = renderer; 38 | 39 | pipeline = device.createComputePipeline({ 40 | compute: { 41 | module: device.createShaderModule({code: cs}), 42 | entryPoint: 'main', 43 | }, 44 | }); 45 | 46 | } 47 | 48 | export function fillBuffer(renderer, buffer, value, numU32Elements){ 49 | init(renderer); 50 | 51 | let {device} = renderer; 52 | 53 | let uniform_flags = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; 54 | let uniformBuffer = device.createBuffer({size: 256, usage: uniform_flags}); 55 | 56 | let bindGroup = renderer.device.createBindGroup({ 57 | layout: pipeline.getBindGroupLayout(0), 58 | entries: [ 59 | {binding: 0, resource: {buffer: uniformBuffer}}, 60 | {binding: 1, resource: {buffer: buffer}} 61 | ], 62 | }); 63 | 64 | const commandEncoder = device.createCommandEncoder(); 65 | const passEncoder = commandEncoder.beginComputePass(); 66 | 67 | { // update uniforms 68 | let source = new ArrayBuffer(256); 69 | let view = new DataView(source); 70 | 71 | view.setUint32(0, numU32Elements, true); 72 | view.setUint32(4, value, true); 73 | 74 | renderer.device.queue.writeBuffer( 75 | uniformBuffer, 0, 76 | source, 0, source.byteLength 77 | ); 78 | } 79 | 80 | passEncoder.setPipeline(pipeline); 81 | passEncoder.setBindGroup(0, bindGroup); 82 | 83 | let groups = Math.ceil(numU32Elements / 128); 84 | passEncoder.dispatch(groups); 85 | passEncoder.end(); 86 | 87 | device.queue.submit([commandEncoder.finish()]); 88 | } -------------------------------------------------------------------------------- /src/scene/Camera.js: -------------------------------------------------------------------------------- 1 | 2 | import {Matrix4, Vector3, toRadians, toDegrees} from "potree"; 3 | import {SceneNode} from "./SceneNode.js"; 4 | 5 | export class Camera extends SceneNode{ 6 | 7 | constructor(name){ 8 | super(name ?? "camera"); 9 | 10 | this.fov = 80; 11 | this.near = 0.01; 12 | this.far = 10_000; 13 | this.aspect = 1; 14 | this.proj = new Matrix4(); 15 | this.view = new Matrix4(); 16 | 17 | } 18 | 19 | updateView(){ 20 | this.view.copy(this.world).invert(); 21 | } 22 | 23 | updateProj(){ 24 | 25 | let fovy = toRadians(this.fov); 26 | //let fovy = toRadians(0.5 * this.fov); 27 | 28 | // const near = this.near; 29 | // let top = near * Math.tan(fovy); 30 | // let height = 2 * top; 31 | // let width = this.aspect * height; 32 | // let left = - 0.5 * width; 33 | 34 | // this.proj.makePerspective( left, left + width, top, top - height, near, this.far); 35 | 36 | this.proj.perspectiveZO(fovy, this.aspect, this.near); 37 | 38 | let remap = new Matrix4(); 39 | remap.elements[10] = -1; 40 | remap.elements[14] = 1; 41 | 42 | this.proj = remap.multiply(this.proj); 43 | } 44 | 45 | // u, v in [0, 1] 46 | // origin: bottom left 47 | mouseToDirection(u, v){ 48 | 49 | let fovRad = toRadians(this.fov); 50 | 51 | let top = Math.tan(fovRad / 2); 52 | let height = 2 * top; 53 | let width = this.aspect * height; 54 | 55 | let origin = new Vector3(0, 0, 0).applyMatrix4(this.world); 56 | 57 | let dir = new Vector3( 58 | 0.5 * (2.0 * u - 1.0) * width, 59 | 0.5 * (2.0 * v - 1.0) * height, 60 | -1, 61 | ).applyMatrix4(this.world); 62 | 63 | return dir.sub(origin).normalize(); 64 | } 65 | 66 | mouseToUnormalizedDirection(u, v){ 67 | let fovRad = toRadians(this.fov); 68 | 69 | let top = Math.tan(fovRad / 2); 70 | let height = 2 * top; 71 | let width = this.aspect * height; 72 | 73 | let origin = new Vector3(0, 0, 0).applyMatrix4(this.world); 74 | 75 | let dir = new Vector3( 76 | 0.5 * (2.0 * u - 1.0) * width, 77 | 0.5 * (2.0 * v - 1.0) * height, 78 | -1, 79 | ).applyMatrix4(this.world); 80 | 81 | return dir.sub(origin); 82 | } 83 | 84 | 85 | }; -------------------------------------------------------------------------------- /src/scene/PointLight.js: -------------------------------------------------------------------------------- 1 | 2 | import {SceneNode} from "./SceneNode.js"; 3 | 4 | export class PointLight extends SceneNode{ 5 | 6 | constructor(name){ 7 | super(name); 8 | } 9 | 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/scene/Scene.js: -------------------------------------------------------------------------------- 1 | 2 | import {SceneNode} from "./SceneNode.js"; 3 | 4 | export class Scene{ 5 | 6 | constructor(){ 7 | 8 | this.root = new SceneNode("root"); 9 | this.root.scene = this; 10 | 11 | } 12 | 13 | add(parent, node){ 14 | parent.children.push(node); 15 | } 16 | 17 | }; -------------------------------------------------------------------------------- /src/scene/SceneNode.js: -------------------------------------------------------------------------------- 1 | 2 | import {Vector3, Box3, Matrix4} from "potree"; 3 | 4 | export class SceneNode{ 5 | 6 | constructor(name){ 7 | this.name = name; 8 | 9 | this.position = new Vector3(0, 0, 0); 10 | this.rotation = new Matrix4(); 11 | this.scale = new Vector3(1, 1, 1); 12 | this.boundingBox = new Box3(); 13 | this.renderLayer = 0; 14 | this.visible = true; 15 | this.children = []; 16 | this.world = new Matrix4(); 17 | } 18 | 19 | updateWorld(){ 20 | 21 | let {world} = this; 22 | 23 | world.makeIdentity(); 24 | world.scale(this.scale.x, this.scale.y, this.scale.z); 25 | world.multiplyMatrices(this.rotation, world); 26 | world.translate(this.position.x, this.position.y, this.position.z); 27 | 28 | } 29 | 30 | getWorldPosition(){ 31 | return new Vector3().applyMatrix4(this.world); 32 | } 33 | 34 | getWorldDirection(){ 35 | let p0 = new Vector3(0, 0, 0).applyMatrix4(this.world); 36 | let p1 = new Vector3(0, 0, -1).applyMatrix4(this.world); 37 | 38 | return p1.sub(p0).normalize(); 39 | } 40 | 41 | traverse(callback){ 42 | 43 | callback(this); 44 | 45 | for(let child of this.children){ 46 | child.traverse(callback); 47 | } 48 | 49 | } 50 | 51 | set(args = {}){ 52 | 53 | if(args.position){ 54 | this.position.set(...args.position); 55 | } 56 | 57 | if(args.scale){ 58 | this.scale.set(...args.scale); 59 | } 60 | 61 | this.updateWorld(); 62 | } 63 | 64 | find(name){ 65 | let result = null; 66 | 67 | this.traverse((node) => { 68 | if(node.name === name) result = node; 69 | }); 70 | 71 | return result; 72 | } 73 | 74 | }; -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | 2 | function createSvgGradient(scheme){ 3 | 4 | // this is what we are creating: 5 | // 6 | // 7 | // 8 | // 9 | // 10 | // ... 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // 17 | 18 | const gradientId = `${Math.random()}_${Date.now()}`; 19 | 20 | const svgn = "http://www.w3.org/2000/svg"; 21 | const svg = document.createElementNS(svgn, "svg"); 22 | svg.setAttributeNS(null, "width", "2em"); 23 | svg.setAttributeNS(null, "height", "3em"); 24 | 25 | { // 26 | const defs = document.createElementNS(svgn, "defs"); 27 | 28 | const linearGradient = document.createElementNS(svgn, "linearGradient"); 29 | linearGradient.setAttributeNS(null, "id", gradientId); 30 | linearGradient.setAttributeNS(null, "gradientTransform", "rotate(90)"); 31 | 32 | let n = 32; 33 | for(let i = 0; i <= n; i++){ 34 | let u = i / n; 35 | let stopVal = scheme.get(u); 36 | 37 | const percent = u * 100; 38 | const [r, g, b, a] = stopVal.map(v => parseInt(v)); 39 | 40 | const stop = document.createElementNS(svgn, "stop"); 41 | stop.setAttributeNS(null, "offset", `${percent}%`); 42 | stop.setAttributeNS(null, "stop-color", `rgb(${r}, ${g}, ${b})`); 43 | 44 | linearGradient.appendChild(stop); 45 | } 46 | 47 | defs.appendChild(linearGradient); 48 | svg.appendChild(defs); 49 | } 50 | 51 | const rect = document.createElementNS(svgn, "rect"); 52 | rect.setAttributeNS(null, "width", `100%`); 53 | rect.setAttributeNS(null, "height", `100%`); 54 | rect.setAttributeNS(null, "fill", `url("#${gradientId}")`); 55 | rect.setAttributeNS(null, "stroke", `black`); 56 | rect.setAttributeNS(null, "stroke-width", `0.1em`); 57 | 58 | svg.appendChild(rect); 59 | 60 | return svg; 61 | } 62 | 63 | 64 | function domFindByName(parent, name){ 65 | let result = Array.from(parent.getElementsByTagName("span")).find(node => node.getAttribute("name") === name); 66 | 67 | return result; 68 | } 69 | 70 | // see https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript 71 | function clipboardCopy(text){ 72 | let textArea = document.createElement("textarea"); 73 | 74 | textArea.style.position = 'fixed'; 75 | textArea.style.top = 0; 76 | textArea.style.left = 0; 77 | 78 | textArea.style.width = '2em'; 79 | textArea.style.height = '2em'; 80 | 81 | textArea.style.padding = 0; 82 | 83 | textArea.style.border = 'none'; 84 | textArea.style.outline = 'none'; 85 | textArea.style.boxShadow = 'none'; 86 | 87 | textArea.style.background = 'transparent'; 88 | 89 | textArea.value = text; 90 | 91 | document.body.appendChild(textArea); 92 | 93 | textArea.select(); 94 | 95 | try { 96 | let success = document.execCommand('copy'); 97 | if(success){ 98 | console.log("copied text to clipboard"); 99 | }else{ 100 | console.log("copy to clipboard failed"); 101 | } 102 | } catch (err) { 103 | console.log("error while trying to copy to clipboard"); 104 | } 105 | 106 | document.body.removeChild(textArea); 107 | 108 | } 109 | 110 | export const Utils = { 111 | createSvgGradient, 112 | domFindByName, 113 | clipboardCopy, 114 | }; 115 | 116 | -------------------------------------------------------------------------------- /tools/shuffleLas.mjs: -------------------------------------------------------------------------------- 1 | 2 | import {promises as fsp} from "fs"; 3 | 4 | 5 | let path = "D:/dev/pointclouds/eclepens.las"; 6 | let targetPath = "D:/dev/pointclouds/eclepens_shuffled.las"; 7 | 8 | function randomInt(start, end){ 9 | return Math.floor(Math.random() * (end - start + 1) + start); 10 | } 11 | 12 | function shuffleIndices(n){ 13 | let indices = new Array(n).fill(0).map((v, i) => i); 14 | 15 | for(let i = 0; i < indices.length - 1; i++){ 16 | 17 | let sourceIndex = randomInt(i, indices.length - 1); 18 | 19 | let tmp = indices[i]; 20 | indices[i] = indices[sourceIndex]; 21 | indices[sourceIndex] = tmp; 22 | } 23 | 24 | return indices; 25 | } 26 | 27 | async function run(){ 28 | let buffer = await fsp.readFile(path); 29 | 30 | let versionMajor = buffer.readUInt8(24); 31 | let versionMinor = buffer.readUInt8(25); 32 | let offsetToPointData = buffer.readUInt32LE(96); 33 | let recordLength = buffer.readUInt16LE(105); 34 | let numPoints; 35 | if(versionMajor >= 1 && versionMinor >= 4){ 36 | numPoints = Number(buffer.readBigUInt64LE(247)); 37 | }else{ 38 | numPoints = buffer.readUInt32LE(107); 39 | } 40 | 41 | let order = shuffleIndices(numPoints); 42 | 43 | 44 | let targetBuffer = Buffer.alloc(buffer.byteLength); 45 | let target = await fsp.open(targetPath, "w"); 46 | 47 | buffer.copy(targetBuffer, 0, 0, offsetToPointData); 48 | 49 | for(let i = 0; i < numPoints; i++){ 50 | let index = order[i]; 51 | let sourceStart = offsetToPointData + index * recordLength; 52 | let sourceEnd = sourceStart + recordLength; 53 | let targetStart = offsetToPointData + i * recordLength; 54 | 55 | buffer.copy(targetBuffer, targetStart, sourceStart, sourceEnd); 56 | } 57 | 58 | await target.write(targetBuffer); 59 | 60 | // await target.write(buffer.slice(0, offsetToPointData)); 61 | 62 | // for(let i = 0; i < numPoints; i++){ 63 | // let index = order[i]; 64 | // let start = offsetToPointData + index * recordLength; 65 | // let end = start + recordLength; 66 | // let slice = buffer.slice(start, end); 67 | // await target.write(slice); 68 | // } 69 | 70 | 71 | 72 | 73 | await target.close(); 74 | 75 | } 76 | 77 | run(); 78 | 79 | --------------------------------------------------------------------------------