├── docs
├── js
│ ├── dom-main.js
│ └── webgl-util.js
├── laz-perf.wasm
├── bundle.js.LICENSE.txt
├── index.html
└── WorkerConsole.js
├── .gitignore
├── src
├── utils
│ ├── kd-tree.js
│ ├── utils.js
│ └── loader.js
├── lru-cache
│ └── index.js
├── worker
│ ├── createWorker.js
│ ├── privateOpenWorker.js
│ └── fetcher.worker.js
├── webgpu
│ ├── material.js
│ └── renderer.js
├── styles
│ └── main.css
├── private_origin
│ ├── cache_manager.js
│ └── file_manager.js
├── helper.js
├── shaders
│ └── renderShader.js
├── passiveloader.js
├── octree.js
└── index.js
├── README.md
├── .env
├── webpack.config.js
└── package.json
/docs/js/dom-main.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | package-lock.json
3 |
--------------------------------------------------------------------------------
/src/utils/kd-tree.js:
--------------------------------------------------------------------------------
1 | // class Node {
2 |
3 | // }
4 |
--------------------------------------------------------------------------------
/docs/laz-perf.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ComputingElevatedLab/WGPU-COPC-Viewer/HEAD/docs/laz-perf.wasm
--------------------------------------------------------------------------------
/docs/bundle.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright 2010-2022 Three.js Authors
4 | * SPDX-License-Identifier: MIT
5 | */
6 |
--------------------------------------------------------------------------------
/src/utils/utils.js:
--------------------------------------------------------------------------------
1 | function deepCopy(obj) {
2 | const newObj = {};
3 |
4 | for (let key in obj) {
5 | const value = obj[key];
6 |
7 | if (typeof value === "object" && value !== null) {
8 | newObj[key] = deepCopy(value); // Recursively copy nested objects
9 | } else {
10 | newObj[key] = value; // Copy non-object values as-is
11 | }
12 | }
13 |
14 | return newObj;
15 | }
16 |
17 | export { deepCopy };
18 |
--------------------------------------------------------------------------------
/src/lru-cache/index.js:
--------------------------------------------------------------------------------
1 | import LRUCache from "lru-cache";
2 | const options = {
3 | max: 500,
4 | // ttl: 100 * 60 * 10, i dont think i need this as the node value wont be time dependent
5 | allowStale: false,
6 | updateAgeOnGet: true,
7 | updateAgeOnHas: true,
8 | };
9 |
10 | const cache = new LRUCache(options);
11 | // -------------------------------------------------------
12 | // since LRU Cache is not persistant on reload by default and is in-memory cache we dont need to be worried about clearing
13 |
14 | export { cache };
15 |
--------------------------------------------------------------------------------
/src/utils/loader.js:
--------------------------------------------------------------------------------
1 | class Loader {
2 | constructor(url) {
3 | this.url = url;
4 | }
5 | async loadHeader() {
6 | // loadheader
7 | let loaderByteSize = 549;
8 | }
9 | }
10 |
11 | function computeFocalLength(angle) {
12 | let canvas = document.getElementById("screen-canvas");
13 | let angleRadian = (angle * Math.PI) / 180;
14 | return canvas.clientHeight * 0.5 * (1 / Math.tan(angleRadian / 2));
15 | }
16 |
17 | function computeSSE(width, distance, focalLength) {
18 | return (width / distance) * focalLength;
19 | }
20 |
21 | export { computeFocalLength, computeSSE };
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | -----------------------------------------------------------------------------------------------
2 |
3 | # WebGPU COPC Viewer
4 |
5 | to install and run:
6 |
7 | ```
8 | 1. clone the repo
9 | ```
10 | ```
11 | 2. npm install
12 | ```
13 |
14 | ### Note:
15 |
16 | ```Renderer is made using WebGPU and WGSL and does out of core rendering so check for support of WebGPU to run this code in your browser```
17 |
18 |
19 | ```
20 | 3. npm run dev
21 | ```
22 | Current render output:
23 |
24 | 
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 |
2 | tree= {
3 | leafCapacity: 16,
4 | bufferCapacity: 16,
5 | }
6 |
7 | # https://media.githubusercontent.com/media/pravinpoudel/LAZ-COPC-Converter/main/morro_20m-reprojected.copc.laz
8 |
9 | # https://media.githubusercontent.com/media/pravinpoudel/LAZ-COPC-Converter/main/u_campus_30m.copc.laz
10 | # https://media.githubusercontent.com/media/sceneserver/copc/main/naarden-vesting.copc.laz
11 | # https://hobu-lidar.s3.amazonaws.com/sofi.copc.laz
12 | filename= "https://media.githubusercontent.com/media/sceneserver/copc/main/naarden-vesting.copc.laz"
13 | p_cache = "cache-holder"
14 | p_cache_capacity = 150
15 |
16 |
--------------------------------------------------------------------------------
/src/worker/createWorker.js:
--------------------------------------------------------------------------------
1 | import Worker from "./fetcher.worker.js";
2 | import { loadCOPC } from "../index";
3 |
4 | const workers = new Array(5).fill(null);
5 | for (let i = 0; i < workers.length; i++) {
6 | const fetchWorker = new Worker();
7 | fetchWorker.onmessage = (event) => {
8 | postMessageRes = event.data;
9 | if (postMessageRes == 200) {
10 | loadCOPC();
11 | } else {
12 | console.log("received");
13 | let position = postMessageRes[0];
14 | let color = postMessageRes[1];
15 | for (let i = 0; i < position.length; i++) {
16 | positions.push(position[i]);
17 | colors.push(colors[i]);
18 | }
19 | }
20 | };
21 | workers.push(fetchWorker);
22 | }
23 |
--------------------------------------------------------------------------------
/src/worker/privateOpenWorker.js:
--------------------------------------------------------------------------------
1 | let write = async () => {
2 | const root = await navigator.storage.getDirectory();
3 | console.log(root);
4 | const draftHandle = await root.getFileHandle("draft.txt", { create: true });
5 | console.log(draftHandle);
6 | const accessHandle = await draftHandle.createSyncAccessHandle();
7 | const encoder = new TextEncoder();
8 | const encodedMessage = encoder.encode("hi there");
9 | const writeBuffer = accessHandle.write(encodedMessage, { at: 0 });
10 | const writeSize = accessHandle.write(writeBuffer, { "at" : 0 });
11 | accessHandle.flush();
12 | accessHandle.close();
13 | console.log("done");
14 | };
15 | let read = async()=>{
16 |
17 | }
18 |
19 | write();
20 |
21 | onmessage = function (message) {
22 | console.log("hello");
23 | };
24 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require("webpack");
2 | const path = require("path");
3 | const lazPerf = require("laz-perf");
4 | const Dotenv = require("dotenv-webpack");
5 | module.exports = {
6 | entry: "./src/index.js",
7 | output: {
8 | filename: "bundle.js",
9 | path: path.resolve(__dirname, "docs"),
10 | },
11 | resolve: {
12 | fallback: {
13 | fs: false,
14 | },
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.css$/,
20 | use: ["style-loader", "css-loader"],
21 | },
22 | {
23 | test: /\.worker\.js$/,
24 | exclude: /node_modules/,
25 | use: "worker-loader",
26 | },
27 | ],
28 | },
29 | devServer: {
30 | port: 8080,
31 | static: path.resolve(__dirname, "docs"),
32 | hot: true,
33 | },
34 | mode: "development",
35 | devtool: "cheap-module-source-map",
36 | plugins: [
37 | new Dotenv(),
38 | // commonjs({ include: /node_modules\/laz-perf/ }),
39 | ],
40 | };
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "octree",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "webpack server",
8 | "build": "webpack"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/pravinpoudel/octree.git"
13 | },
14 | "keywords": [],
15 | "author": "",
16 | "license": "ISC",
17 | "bugs": {
18 | "url": "https://github.com/pravinpoudel/octree/issues"
19 | },
20 | "homepage": "https://github.com/pravinpoudel/octree#readme",
21 | "dependencies": {
22 | "copc": "^0.0.4",
23 | "cross-fetch": "^3.1.5",
24 | "dotenv-webpack": "^8.0.1",
25 | "json-fn": "^1.1.1",
26 | "laz-perf": "^0.0.5",
27 | "lru-cache": "^8.0.4",
28 | "three": "^0.148.0",
29 | "webpack": "^5.75.0",
30 | "webpack-cli": "^5.0.1",
31 | "webpack-dev-server": "^4.11.1"
32 | },
33 | "devDependencies": {
34 | "css-loader": "^6.7.3",
35 | "style-loader": "^3.3.1",
36 | "worker-loader": "^3.0.8"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/webgpu/material.js:
--------------------------------------------------------------------------------
1 | async function loadImageBitMap(device, url) {
2 | const response = await fetch(url);
3 | const image = response.blob();
4 | // const imageData = await
5 | const textureDescriptor = {
6 | size: {
7 | width: image.width,
8 | height: image.height,
9 | },
10 | format: "rgba8unorm",
11 | usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
12 | };
13 |
14 | let texture = device.createTexture(textureDescriptor);
15 |
16 | device.queue.copyExternalImageToTexture(
17 | image,
18 | texture,
19 | textureDescriptor.size
20 | );
21 |
22 | let viewDescriptor = {
23 | label: "heatmap texture view",
24 | format: "rgba8unorm",
25 | dimension: textureDescriptor.size,
26 | };
27 |
28 | let view = texture.createView(viewDescriptor);
29 |
30 | let sampler = device.createSampler({
31 | label: "heatmap texture sampler",
32 | addressModeU: "repeat",
33 | addressModeV: "repeat",
34 | });
35 |
36 | return [view, sampler];
37 | }
38 |
39 | export { loadImageBitMap };
40 |
--------------------------------------------------------------------------------
/src/styles/main.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | margin: 0;
4 | padding: 0;
5 | border: none;
6 | font-family: "Poppins", sans-serif;
7 | }
8 |
9 | body {
10 | overflow: hidden;
11 | }
12 |
13 | .note {
14 | position: absolute;
15 | color: rgb(32, 31, 30);
16 | top: 15px;
17 | left: 20px;
18 | }
19 |
20 | .note title {
21 | font-size: 20px;
22 | }
23 |
24 | .bottom-note {
25 | position: absolute;
26 | bottom: 55px;
27 | left: 50px;
28 | font-weight: 600;
29 | }
30 | .selection-map {
31 | position: absolute;
32 | top: 50px;
33 | right: 50px;
34 | }
35 |
36 | .option-title {
37 | border-bottom: 1px solid #aaa;
38 | margin-bottom: 20px;
39 | }
40 |
41 | select {
42 | padding: 20px;
43 | border: 1px solid #ccc;
44 | }
45 |
46 | option {
47 | text-indent: 15px;
48 | }
49 |
50 | .abort-div {
51 | position: absolute;
52 | top: 50px;
53 | left: 10px;
54 | padding: 20px 30px;
55 | border: 2px solid;
56 | }
57 |
58 | #abort_btn {
59 | background: none;
60 | font-size: 14px;
61 | }
62 |
63 | #stats-div{
64 | position: absolute;
65 | bottom: 100px;
66 | right: 50px;
67 | background-color: aliceblue;
68 | padding: 20px;
69 | }
--------------------------------------------------------------------------------
/src/private_origin/cache_manager.js:
--------------------------------------------------------------------------------
1 | import { doesExist } from "./file_manager";
2 |
3 | function sortObjectIntoMap(object1) {
4 | let resultMap = new Map();
5 | if (!object1) return resultMap;
6 | const sortedArray1 = Object.entries(object1).sort((a, b) => a.date - b.date);
7 | sortedArray1.forEach(([key, value]) => resultMap.set(key, value));
8 | return resultMap;
9 | }
10 |
11 | function mapIntoJSON(map) {
12 | return JSON.stringify(Object.fromEntries(map));
13 | }
14 |
15 | const p_cache = async (capacity) => {
16 | let [permission, content] = await doesExist(process.env.p_cache);
17 | let cache = sortObjectIntoMap(content);
18 | return cache;
19 | };
20 |
21 | const get_inCache = (cache, key) => {
22 | if (!cache.has(key)) return cache;
23 | let val = cache.get(key);
24 | cache.delete(key);
25 | cache.set(key, { date: Date.now(), count: val.count + 1 });
26 | return cache;
27 | };
28 |
29 | const put_inCache = (cache, key, value) => {
30 | cache.delete(key);
31 | if (cache.size == process.env.p_cache_capacity) {
32 | cache.delete(cache.keys().next().value);
33 | } else {
34 | cache.set(key, value);
35 | }
36 | return cache;
37 | };
38 |
39 | const getLRU_inCache = (cache) => {
40 | return Array.from(cache)[0];
41 | };
42 |
43 | const getMRU_inCache = (cache) => {
44 | return Array.from(cache)[cache.size - 1];
45 | };
46 |
47 | export {
48 | p_cache,
49 | get_inCache,
50 | getLRU_inCache,
51 | getMRU_inCache,
52 | sortObjectIntoMap,
53 | mapIntoJSON,
54 | put_inCache,
55 | };
56 |
--------------------------------------------------------------------------------
/src/helper.js:
--------------------------------------------------------------------------------
1 | import * as Octree from "./octree";
2 |
3 | function fillArray(points, count, WIDTH, HEIGHT, DEPTH) {
4 | for (let i = 0; i < count; i++) {
5 | let point = new Octree.Point(
6 | i,
7 | Math.floor(Math.random() * WIDTH) - WIDTH / 2,
8 | Math.floor(Math.random() * HEIGHT) - HEIGHT / 2,
9 | Math.floor(Math.random() * DEPTH) - DEPTH / 2
10 | );
11 | points.push(point);
12 | }
13 | }
14 |
15 | function fillMidNodes(tree) {
16 | if (!tree.isDivided) {
17 | tree.points.splice(0, 1);
18 | return tree.points[0];
19 | }
20 |
21 | let children = [
22 | tree.minNE,
23 | tree.minNW,
24 | tree.minSW,
25 | tree.minSE,
26 | tree.maxNE,
27 | tree.maxNW,
28 | tree.maxSW,
29 | tree.maxSE,
30 | ];
31 | let result = [];
32 | for (let i = 0, _length = children.length; i < _length; i++) {
33 | let result1 = fillMidNodes(children[i]);
34 | if (result1 != null) {
35 | result.push(result1);
36 | }
37 | }
38 | let passIndex = Math.ceil(result.length / 2);
39 | let passingValue = result[passIndex];
40 | if (tree.level > 0) {
41 | result.splice(passIndex, 1);
42 | }
43 | tree.representativeNodes = [...result];
44 | return passingValue;
45 | }
46 |
47 | function updateHtmlUI(nodeNotFoundInBuffer, nodeFoundInBuffer, nodeFoundInLRU, nodeFoundInPersistent, nodeToFetch){
48 | let stats_text = `Among total nodes needed ${nodeFoundInBuffer + nodeNotFoundInBuffer}\b
49 | nodes found in GPU Buffer: ${nodeFoundInBuffer} \b
50 | ----------------------------------------------------
51 | nodes need to be loaded ${nodeNotFoundInBuffer}\b
52 | \b
53 | nodes found in LRU Cache: ${nodeFoundInLRU} \b
54 | nodes found in Persistent memory: ${nodeFoundInPersistent} \b
55 | nodes that were fetched from host: ${nodeToFetch} `
56 | document.getElementById("stats-div").innerText = stats_text
57 | }
58 |
59 | export { fillArray, fillMidNodes, updateHtmlUI };
60 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
14 |
15 |
16 |
31 |
32 |
33 |
34 |
Color-map axis
35 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/src/worker/fetcher.worker.js:
--------------------------------------------------------------------------------
1 | import { Copc, Key } from "copc";
2 | import * as THREE from "three";
3 | const color = new THREE.Color();
4 | const colors = [];
5 | let maxZ = -999;
6 | let minZ = 1000;
7 | let maxIntensity = -100;
8 | let firstTime = true;
9 | var nodePages, pages, receivedData, copc;
10 | let x_min,
11 | y_min,
12 | z_min,
13 | x_max,
14 | y_max,
15 | z_max,
16 | widthx,
17 | widthy,
18 | widthz,
19 | scaleX,
20 | scaleY,
21 | scaleZ,
22 | level;
23 | let positions = [];
24 | // const filename = process.env.filename;
25 | const filename = process.env.filename;
26 |
27 | const readPoints = (id, getters) => {
28 | let returnPoint = getXyzi(id, getters);
29 | // console.log(returnPoint)
30 | if (returnPoint[2] > maxZ) {
31 | maxZ = returnPoint[2];
32 | }
33 | if (returnPoint[2] < minZ) {
34 | minZ = returnPoint[2];
35 | }
36 |
37 | positions.push(returnPoint[0], returnPoint[1], returnPoint[2]);
38 | // console.log("intensity is", returnPoint[3]);
39 | const vx = returnPoint[3];
40 | if (vx > maxIntensity) {
41 | maxIntensity = vx;
42 | }
43 | color.setRGB(returnPoint[3], returnPoint[4], returnPoint[5]);
44 | colors.push(color.r, color.g, color.b);
45 | firstTime = false;
46 | };
47 |
48 | function getXyzi(index, getters) {
49 | return getters.map((get) => get(index));
50 | }
51 |
52 | async function load() {
53 | // copc = await Copc.create(filename);
54 | // let scale = copc.header.scale[0];
55 | // [x_min, y_min, z_min, x_max, y_max, z_max] = copc.info.cube;
56 | // width = Math.abs(x_max - x_min);
57 | // // let center_x = (x_min + x_max) / 2;
58 | // // let center_y = (y_min + y_max) / 2;
59 | // // let center_z = (z_min + z_max) / 2;
60 | // receivedData = await Copc.loadHierarchyPage(
61 | // filename,
62 | // copc.info.rootHierarchyPage
63 | // );
64 | // nodePages = receivedData.nodes;
65 | // pages = receivedData.pages;
66 | postMessage(200);
67 | }
68 |
69 | async function loadData(nodes, pages, copc, myRoot, pointCount) {
70 | // console.log(copc, myRoot);
71 | const view = await Copc.loadPointDataView(filename, copc, myRoot);
72 | // let getters = ["X", "Y", "Z", "Intensity"].map(view.getter);
73 | let getters = ["X", "Y", "Z", "Red", "Green", "Blue"].map(view.getter);
74 | for (let j = 0; j < pointCount; j += 1) {
75 | readPoints(j, getters);
76 | }
77 | postMessage([positions, colors, [minZ, maxZ, maxIntensity, level]]);
78 | }
79 |
80 | load();
81 |
82 | onmessage = function (message) {
83 | let nodePages = message.data[0];
84 | let nodes = JSON.parse(nodePages);
85 | let pagesStr = message.data[1];
86 | let pages = JSON.parse(pagesStr);
87 | let copcStr = message.data[2];
88 | let copc = JSON.parse(copcStr);
89 |
90 | let mapIndex = message.data[3];
91 | let pointCount = message.data[4];
92 | let myRoot = nodes[mapIndex];
93 | x_min = message.data[5][0];
94 | y_min = message.data[5][1];
95 | z_min = message.data[5][2];
96 | widthx = message.data[5][3];
97 | widthy = message.data[5][4];
98 | widthz = message.data[5][5];
99 | scaleX = message.data[5][6];
100 | scaleY = message.data[5][7];
101 | scaleZ = message.data[5][8];
102 | level = message.data[5][9];
103 | loadData(nodes, pages, copc, myRoot, pointCount);
104 | };
105 |
--------------------------------------------------------------------------------
/src/shaders/renderShader.js:
--------------------------------------------------------------------------------
1 | let vs = `
2 | struct VertexInput {
3 | @location(0) position: vec4,
4 | @location(1) color: vec3
5 | };
6 |
7 | struct VertexOut {
8 | @builtin(position) position: vec4,
9 | @location(0) color: vec4,
10 | };
11 |
12 | struct paramsUniform {
13 | width_x:f32,
14 | width_y:f32,
15 | width_z:f32,
16 | x_min: f32,
17 | y_min: f32,
18 | z_min: f32,
19 | current_Axis: f32,
20 | max_Intensity: f32
21 | };
22 |
23 | struct cmapUniform {
24 | colors: array, 20>
25 | };
26 |
27 | @group(0) @binding(0) var MVP_Matrix: mat4x4;
28 | @group(0) @binding(1) var cMap: cmapUniform;
29 | @group(0) @binding(2) var params: paramsUniform;
30 |
31 | const direction = array, 4>(
32 | vec2(-1, -1),
33 | vec2(1, -1),
34 | vec2(-1, 1),
35 | vec2(1, 1)
36 | );
37 |
38 | const PI: f32 = 3.1415926535897932384626433832795;
39 |
40 | fn getCmapped(cMapIndex: i32)->vec4{
41 | var cmapped = cMap.colors[cMapIndex];
42 | if(cMapIndex>19){
43 | cmapped = cMap.colors[19];
44 | }
45 | return cmapped;
46 | }
47 |
48 | @vertex
49 | fn main(in: VertexInput, @builtin(instance_index) inst_index:u32, @builtin(vertex_index) vertexIndex : u32)->VertexOut{
50 | var out:VertexOut;
51 | var cMapIndex:i32;
52 | var level:f32 = in.position.w;
53 | var radius:f32 = 3.0* pow(0.6, level);
54 | radius = max(radius, 1.0);
55 | var position:vec3 = in.position.xyz - vec3(params.x_min, params.y_min, params.z_min) - 0.5*vec3(params.width_x, params.width_y, params.width_z);
56 | var factor = in.color.x/params.max_Intensity;
57 | if(params.current_Axis == 2.0){
58 | cMapIndex = i32((abs(in.position.z - params.z_min)/params.width_z) *19);
59 | let mappedColor = getCmapped(cMapIndex);
60 | out.color = vec4(mappedColor.xyz, 1.0);
61 | if(cMapIndex < 0){
62 | out.color = vec4(1.0, 0.0, 0.0, 1.0);
63 | }
64 | out.color = vec4(out.color.x, out.color.y, out.color.z, 1.0)*factor;
65 |
66 | }
67 | else if(params.current_Axis == 1.0){
68 | cMapIndex = i32(1.25*(abs(in.position.y - params.y_min)/params.width_y) *19);
69 | out.color = getCmapped(cMapIndex);
70 | out.color = vec4(out.color.x, out.color.y, out.color.z, 1.0)*factor;
71 |
72 | }
73 | else if(params.current_Axis == 0.0){
74 | cMapIndex = i32(1.25*(abs(in.position.x - params.x_min)/params.width_x) *19);
75 | out.color = getCmapped(cMapIndex);
76 | out.color = vec4(out.color.x, out.color.y, out.color.z, 1.0)*factor;
77 | }
78 | else{
79 | out.color = vec4(in.color.x/255.0, in.color.y/225.0, in.color.z/255.0, 1.0);
80 | }
81 |
82 | if(factor < 0.1){
83 | factor = 0.35;
84 | }
85 | // if(level <= 1.0){
86 | // out.color = vec4(0.0, 1.0, 0.0, 1.0);
87 | // }
88 | position = position + vec3(radius*direction[vertexIndex], 0.0);
89 | out.position = MVP_Matrix* vec4(position, 1.0);
90 | return out;
91 | }
92 | `;
93 |
94 | let fs = `
95 | struct VertexOut {
96 | @builtin(position) position: vec4,
97 | @location(0) color: vec4
98 | };
99 |
100 | @fragment
101 | fn main(in:VertexOut)->@location(0) vec4{
102 | return in.color;
103 | }
104 | `;
105 |
106 | export { fs, vs };
107 |
--------------------------------------------------------------------------------
/docs/WorkerConsole.js:
--------------------------------------------------------------------------------
1 | /*
2 | * WorkerConsole.js:
3 | *
4 | * Include this script in your web pages in order to give your worker threads
5 | * a working console.log() function. This file is also loaded by all
6 | * workers you create in order to define the log() function. It is one
7 | * file used in two distinct ways.
8 | *
9 | * This does not work in Firefox, since FF4 does not support MessageChannel.
10 | *
11 | * It appears to work in Chrome, but has not been tested in other browsers.
12 | * Note that Workers don't work in Chrome if you're using the file://
13 | * protocol, so in order to try this out you have to be running a server.
14 | *
15 | * It does not work for workers nested within other workers, but it could
16 | * probably be made to work in that case.
17 | *
18 | * It has only been tested with very simple directory structures.
19 | * WorkerConsole.js probably needs to be in the same directory as the
20 | * HTML file that includes it. There are likely to be path issues
21 | * for more complicated directory structures.
22 | *
23 | * Copyright 2011 by David Flanagan
24 | * http://creativecommons.org/licenses/by-nc-sa/3.0/
25 | */
26 | if (this.console && this.console.log) {
27 | /*
28 | * If there is already a console.log() function defined, then wrap the
29 | * Worker() constructor so that workers get console.log(), too.
30 | */
31 | // Remember the original Worker() constructor
32 | this._Worker = Worker;
33 |
34 | // Make this.Worker writable, so we can replace it.
35 | Object.defineProperty(this, "Worker", {writable: true});
36 |
37 | // Replace the Worker() constructor with this augmented version
38 | this.Worker = function Worker(url) {
39 | // Create a real Worker object that first loads this file to define
40 | // console.log() and then loads the requested URL
41 | var w = new _Worker("WorkerConsole.js#" + url);
42 |
43 | // Create a side channel for the worker to send log messages on
44 | var channel = new MessageChannel();
45 |
46 | // Send one end of the channel to the worker
47 | w.postMessage("console", [channel.port2]);
48 |
49 | // And listen for log messages on the other end of the channel
50 | channel.port1.onmessage = function(e) {
51 | var args = e.data; // Array of args to console.log()
52 | args.unshift(url + ": "); // Add an arg to id the worker
53 | console.log.apply(console, args); // Pass the args to the real log
54 | }
55 |
56 | // Return the real Worker object from this fake constructor
57 | return w;
58 | }
59 | }
60 | else {
61 | /*
62 | * If there wasn't a console.log() function defined, then we're in a
63 | * Worker created with the wrapped Worker() constructor above, and
64 | * we need to define the console.
65 | *
66 | * Wait until we get the event that delivers the MessagePort sent by the
67 | * main thread. Once we get it, we define the console.log() function
68 | * and load and run the original file that was passed to the constructor.
69 | */
70 | self.onmessage = function(e) {
71 | if (e.data === "console") {
72 | // Define the console object
73 | self.console = {
74 | _port: e.ports[0], // Remember the port we log to
75 | log: function log() { // Define console.log()
76 | // Copy the arguments into a real array
77 | var args = Array.prototype.slice.call(arguments);
78 | // Send the arguments as a message, over our side channel
79 | console._port.postMessage(args);
80 | }
81 | };
82 |
83 | // Get rid of this event handler
84 | onmessage = null;
85 |
86 | // Now run the script that was originally passed to Worker()
87 | var url = location.hash.substring(1); // Get the real URL to run
88 | importScripts(url); // Load and run it now
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/passiveloader.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { computeFocalLength, computeSSE } from "./utils/loader";
3 |
4 | let direction = [
5 | [0, 0, 0],
6 | [0, 0, 1],
7 | [0, 1, 0],
8 | [0, 1, 1],
9 | [1, 0, 0],
10 | [1, 0, 1],
11 | [1, 1, 0],
12 | [1, 1, 1],
13 | ];
14 |
15 | let cameraFocalLength = computeFocalLength(90);
16 | let nodeToPrefetch = [];
17 |
18 | async function* lazyLoad(offsetMap, url) {
19 | while (offsetMap.length > 0) {
20 | let fetchStart = offsetMap.pop();
21 | let bytesofPointData = offsetMap.pop();
22 | let fetchEnd = fetchStart + bytesofPointData;
23 | let response = await fetch(url, {
24 | headers: {
25 | "content-type": "multipart/byteranges",
26 | Range: `bytes=${fetchStart}-${fetchEnd}`,
27 | },
28 | });
29 | let buffer = await response.arrayBuffer();
30 | let view = new DataView(buffer);
31 | }
32 | }
33 |
34 | function isLeadfNode(root, nodePages) {
35 | let [level, x, y, z] = root;
36 | for (let i = 0; i < direction.length; i++) {
37 | let [dx, dy, dz] = direction[i];
38 | let newLevel = level + 1;
39 | let key = `${newLevel}-${2 * x + dx}-${2 * y + dy}-${2 * z + dz}`;
40 | if (key in nodePages) {
41 | return false;
42 | }
43 | }
44 | return true;
45 | }
46 |
47 | let canvas = document.getElementById("screen-canvas");
48 | canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
49 | canvas.height = window.innerHeight * (window.devicePixelRatio || 1);
50 | let screenWidth = canvas.width;
51 | let screenHeight = canvas.height;
52 | let fovRADIAN = Math.PI / 2;
53 |
54 | function isRendered(
55 | center,
56 | radius,
57 | distance,
58 | projViewMatrix,
59 | level,
60 | key,
61 | nodePages
62 | ) {
63 | let minPoint = [center[0] - radius, center[1] - radius, center[2] - radius];
64 | let maxPoint = [center[0] + radius, center[1] + radius, center[2] + radius];
65 | let frustum = new Frustum(projViewMatrix);
66 | if (!frustum.containsBox([...minPoint, ...maxPoint])) {
67 | if (level <= 2) {
68 | nodeToPrefetch.push(key, nodePages[key].pointCount);
69 | }
70 | return false;
71 | }
72 |
73 | // let pixel_size = (2 * Math.tan(fovRADIAN / 2.0) * distance) / screenHeight;
74 | let projectedRadius =
75 | (radius * screenHeight) / (distance * (2 * Math.tan(fovRADIAN / 2.0)));
76 | return Math.abs(projectedRadius) > 90;
77 | }
78 |
79 | function traverseTreeWrapper(
80 | nodePages,
81 | root,
82 | center_x,
83 | center_y,
84 | center_z,
85 | width,
86 | scale,
87 | controls,
88 | projViewMatrix
89 | ) {
90 | let cameraPosition = controls.object.position.toArray();
91 | let width_x_world = width[0];
92 | nodeToPrefetch = [];
93 | function traverseTree(root, center_x, center_y, center_z, width) {
94 | let [level, x, y, z] = root;
95 | let newLevel = level + 1;
96 | let key = level + "-" + x + "-" + y + "-" + z;
97 | let distance = Math.sqrt(
98 | Math.pow(Math.abs(cameraPosition[0] - center_x), 2) +
99 | Math.pow(Math.abs(cameraPosition[1] - center_y), 2) +
100 | Math.pow(Math.abs(cameraPosition[2] - center_z), 2)
101 | );
102 | if (
103 | !isRendered(
104 | [center_x, center_y, center_z],
105 | Math.max(...width),
106 | distance,
107 | projViewMatrix,
108 | level,
109 | key,
110 | nodePages
111 | )
112 | ) {
113 | return [];
114 | }
115 |
116 | let center_x_left = center_x - width[0] / 2;
117 | let center_x_right = center_x + width[0] / 2;
118 | let center_y_top = center_y + width[1] / 2;
119 | let center_y_bottom = center_y - width[1] / 2;
120 | let center_z_near = center_z + width[2] / 2;
121 | let center_z_far = center_z - width[2] / 2;
122 |
123 | let result = [key, nodePages[key].pointCount];
124 | direction.forEach((element, index) => {
125 | let [dx, dy, dz] = element;
126 | let key1 = `${newLevel}-${2 * x + dx}-${2 * y + dy}-${2 * z + dz}`;
127 | if (!(key1 in nodePages && nodePages[key].pointCount > 0)) {
128 | return [];
129 | }
130 | center_x = center_x_left;
131 | center_y = center_y_bottom;
132 | center_z = center_z_far;
133 | if (dx == 1) {
134 | center_x = center_x_right;
135 | }
136 | if (dy == 1) {
137 | center_y = center_y_top;
138 | }
139 | if (dz == 1) {
140 | center_z = center_z_near;
141 | }
142 | let result1 = traverseTree(
143 | [newLevel, 2 * x + dx, 2 * y + dy, 2 * z + dz],
144 | center_x,
145 | center_y,
146 | center_z,
147 | [width[0] / 2, width[1] / 2, width[2] / 2]
148 | );
149 | result.push(...result1);
150 | });
151 | return result;
152 | }
153 | let finalPoints = traverseTree(root, center_x, center_y, center_z, [
154 | width[0],
155 | width[1],
156 | width[2],
157 | ]);
158 | return [finalPoints, nodeToPrefetch];
159 | }
160 |
161 | export { traverseTreeWrapper };
162 |
--------------------------------------------------------------------------------
/src/private_origin/file_manager.js:
--------------------------------------------------------------------------------
1 | import { throttle } from "../webgpu/renderer";
2 |
3 | let total_ops = 0;
4 | let used_ops = 0;
5 | navigator.webkitPersistentStorage.queryUsageAndQuota(
6 | function (used, total) {
7 | total_ops = total;
8 | used_ops = used;
9 | },
10 | function (error) {
11 | console.error("Error getting origin-private file system size:", error);
12 | }
13 | );
14 |
15 | let available_ops = total_ops - used_ops;
16 | //------------------------------------------------ for persistent cache capacity is number of files for now --------------------------------------------
17 | let create_P_Meta_Cache = async () => {
18 | const root = await navigator.storage.getDirectory();
19 | let fileToCheck = `${process.env.p_cache}.json`;
20 | let [already_exist, content] = await doesExist(fileToCheck);
21 | if (!already_exist) {
22 | const root = await navigator.storage.getDirectory();
23 | const fileHandle = await root.getFileHandle(fileToCheck, {
24 | create: true,
25 | });
26 | } else {
27 | console.log("meta cache file already exist");
28 | }
29 | };
30 |
31 | let updatePersCache = async (updatD_data) => {
32 | let fileToCheck = `${process.env.p_cache}.json`;
33 | const root = await navigator.storage.getDirectory();
34 | const fileHandle = await root.getFileHandle(fileToCheck, {
35 | create: true,
36 | });
37 | const writableStream = await fileHandle.createWritable();
38 | await writableStream.write(updatD_data);
39 | await writableStream.close();
40 | console.log("cache updating is done");
41 | };
42 |
43 | let throttled_Update_Pers_Cache = throttle(updatePersCache, 30000);
44 |
45 | // let update_cache = async (method, data) => {
46 | // const cache_name = "cache-holder";
47 | // let [isAvailable, content] = doesExist(cache_name);
48 | // // if (!isAvailable) {
49 | // // console.error("cache meta ile does not exist");
50 | // // return;
51 | // // }
52 | // // content = JSON.parse(content);
53 | // // switch (method) {
54 | // // case "life_update":
55 | // // content[data.fileName].useCounter = content[data.fileName].useCounter + 1;
56 |
57 | // // break;
58 | // // case "put":
59 | // // }
60 | // };
61 |
62 | let clear = async () => {
63 | const root = await navigator.storage.getDirectory();
64 | const fileNames = await root.keys();
65 | let x = await fileNames.next();
66 | while (!x.done) {
67 | let fileName = x.value;
68 | const fileHandle = await root.getFileHandle(fileName);
69 | await fileHandle.remove();
70 | x = await fileNames.next();
71 | }
72 | };
73 |
74 | // clear()
75 | // --------------------------- indepedent readBinary file code wrote to test before putting this part inside doesExist --------------------------
76 | let readBin = async (fileName) => {
77 | let fileToCheck = `${fileName}.bin`;
78 | const root = await navigator.storage.getDirectory();
79 | const fileHandle = await root.getFileHandle(fileToCheck);
80 | let retrived_blob = await fileHandle.getFile();
81 | var reader = new FileReader();
82 | return await new Promise((resolve, reject) => {
83 | reader.onload = function () {
84 | resolve(JSON.parse(reader.result));
85 | };
86 | reader.readAsText(retrived_blob);
87 | });
88 | };
89 |
90 | //--------------------------------------------------------------------------------
91 |
92 | let write = async (fileName, data1) => {
93 | let fileToCheck = `${fileName}.bin`;
94 | const blob = new Blob([data1], { type: "application/octet-stream" });
95 | const root = await navigator.storage.getDirectory();
96 | const fileHandle = await root.getFileHandle(fileToCheck, {
97 | create: true,
98 | });
99 | const writableStream = await fileHandle.createWritable();
100 | await writableStream.write(blob);
101 | await writableStream.close();
102 | };
103 |
104 | let read = async (fileName) => {
105 | const root = await navigator.storage.getDirectory();
106 | const fileHandle = await root.getFileHandle(`${fileName}.bin`, {
107 | create: false,
108 | });
109 | let file = await fileHandle.getFile();
110 | let content = await file.text();
111 | if (content) {
112 | return JSON.parse(content);
113 | }
114 | return null;
115 | };
116 |
117 | let doesExist = async (fileName) => {
118 | try {
119 | let fileToCheck = `${fileName}.bin`;
120 | const root = await navigator.storage.getDirectory();
121 | const fileHandle = await root.getFileHandle(fileToCheck);
122 | const permissionStatus = await fileHandle.queryPermission();
123 | let found = permissionStatus == "granted" ? true : false;
124 | let retrived_blob = await fileHandle.getFile();
125 | if(retrived_blob.size>0){
126 | var reader = new FileReader();
127 | return await new Promise((resolve, reject) => {
128 | reader.onload = function () {
129 | resolve([true, JSON.parse(reader.result)]);
130 | };
131 | reader.readAsText(retrived_blob);
132 | });
133 |
134 | }
135 | else{
136 | return [ true, {position:[], color: []}]
137 | }
138 | } catch (error) {
139 | if (error.name === "NotFoundError") {
140 | return [false, null];
141 | } else {
142 | console.error("Error checking if file exists:", error);
143 | return [false, null];
144 | }
145 | }
146 | };
147 |
148 | export {
149 | write,
150 | read,
151 | doesExist,
152 | clear,
153 | create_P_Meta_Cache,
154 | throttled_Update_Pers_Cache,
155 | };
156 |
--------------------------------------------------------------------------------
/src/octree.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | let MAX_BOUNDARY_X = 8;
3 | let MAX_BOUNDARY_Y = 8;
4 | let MAX_BOUNDARY_Z = 8;
5 | // color
6 | const white = new THREE.Color(0xffffff);
7 | const red = new THREE.Color(0xff0000);
8 | const green = new THREE.Color(0xc5e908);
9 | const blue = new THREE.Color(0x0000ff);
10 | const yellow = new THREE.Color(0xe69b00);
11 | const grey = new THREE.Color(0xe0a387);
12 | const illusion = new THREE.Color(0xf1a784);
13 |
14 | const colors = [yellow, red, illusion, blue, green, grey, illusion];
15 | // -------------------------------------
16 |
17 | class Point {
18 | constructor(index, x, y, z) {
19 | this.index = index;
20 | this.x = x;
21 | this.y = y;
22 | this.z = z;
23 | let mesh = new THREE.Mesh(
24 | new THREE.BoxGeometry(5, 5, 5),
25 | new THREE.MeshBasicMaterial({ color: 0xff0000 })
26 | );
27 | mesh.position.set(x, y, z);
28 | mesh.updateMatrix();
29 | mesh.matrixAutoUpdate = false;
30 | this.mesh = mesh;
31 | }
32 | }
33 |
34 | class Box {
35 | constructor(label, x, y, z, width, level) {
36 | this.label = label;
37 | this.x = x;
38 | this.y = y;
39 | this.z = z;
40 | this.width = width;
41 | let mesh = new THREE.Mesh(
42 | new THREE.BoxGeometry(width, width, width),
43 | new THREE.MeshBasicMaterial({ color: colors[level % 7], wireframe: true })
44 | );
45 | mesh.position.set(x, y, z);
46 | mesh.updateMatrix();
47 | mesh.matrixAutoUpdate = false;
48 | this.mesh = mesh;
49 | // scene.add(mesh);
50 | }
51 |
52 | bound(point) {
53 | if (this.x + this.width * 0.5 == 0.5 * MAX_BOUNDARY_X) {
54 | return (
55 | point.x >= this.x - this.width * 0.5 &&
56 | point.x <= this.x + this.width * 0.5 &&
57 | point.y < this.y + this.width * 0.5 &&
58 | point.y >= this.y - this.width * 0.5 &&
59 | point.z >= this.z - this.width * 0.5 &&
60 | point.z < this.z + this.width * 0.5
61 | );
62 | }
63 | if (this.y + this.width * 0.5 == 0.5 * MAX_BOUNDARY_Y) {
64 | return (
65 | point.x >= this.x - this.width * 0.5 &&
66 | point.x < this.x + this.width * 0.5 &&
67 | point.y <= this.y + this.width * 0.5 &&
68 | point.y >= this.y - this.width * 0.5 &&
69 | point.z >= this.z - this.width * 0.5 &&
70 | point.z < this.z + this.width * 0.5
71 | );
72 | }
73 | if (this.z + this.width * 0.5 == 0.5 * MAX_BOUNDARY_Z) {
74 | return (
75 | point.x >= this.x - this.width * 0.5 &&
76 | point.x < this.x + this.width * 0.5 &&
77 | point.y < this.y + this.width * 0.5 &&
78 | point.y >= this.y - this.width * 0.5 &&
79 | point.z >= this.z - this.width * 0.5 &&
80 | point.z <= this.z + this.width * 0.5
81 | );
82 | } else {
83 | return (
84 | point.x >= this.x - this.width * 0.5 &&
85 | point.x < this.x + this.width * 0.5 &&
86 | point.y < this.y + this.width * 0.5 &&
87 | point.y >= this.y - this.width * 0.5 &&
88 | point.z >= this.z - this.width * 0.5 &&
89 | point.z < this.z + this.width * 0.5
90 | );
91 | }
92 | }
93 | }
94 |
95 | class Octree {
96 | constructor(box, level = 0) {
97 | this.box = box;
98 | this.minNE = null;
99 | this.minNW = null;
100 | this.minSW = null;
101 | this.minSE = null;
102 | this.maxNE = null;
103 | this.maxNW = null;
104 | this.maxSW = null;
105 | this.maxSE = null;
106 | this.isDivided = false;
107 | // this.representativeNodes = [];
108 | this.points = [];
109 | this.buffer = [];
110 | this.level = level;
111 | this.parent = null;
112 | }
113 |
114 | // function findRepresentiveNode(){
115 | // let children = [this.minNE, this.minNW, this.minSW, this.minSE, this.maxNE, this.maxNW, this.maxSW, this.maxSE]
116 | // children.forEach((element, index)=>{
117 | // if(element != null && element.length>0){
118 | // this.representativeNodes.push(element.nodes[0])
119 | // }
120 | // })
121 | // }
122 |
123 | partition() {
124 | let x = this.box.x;
125 | let y = this.box.y;
126 | let z = this.box.z;
127 | let newWidth = this.box.width * 0.5;
128 | let level = this.level + 1;
129 | let maxNE_Box = new Box(
130 | "maxNE",
131 | x + newWidth * 0.5,
132 | y + 0.5 * newWidth,
133 | z - 0.5 * newWidth,
134 | newWidth,
135 | level
136 | );
137 | let maxNW_Box = new Box(
138 | "maxNW",
139 | x - newWidth * 0.5,
140 | y + 0.5 * newWidth,
141 | z - 0.5 * newWidth,
142 | newWidth,
143 | level
144 | );
145 | let maxSW_Box = new Box(
146 | "maxSW",
147 | x - newWidth * 0.5,
148 | y - 0.5 * newWidth,
149 | z - 0.5 * newWidth,
150 | newWidth,
151 | level
152 | );
153 | let maxSE_Box = new Box(
154 | "maxSE",
155 | x + newWidth * 0.5,
156 | y - 0.5 * newWidth,
157 | z - 0.5 * newWidth,
158 | newWidth,
159 | level
160 | );
161 |
162 | let minNE_Box = new Box(
163 | "minNE",
164 | x + newWidth * 0.5,
165 | y + 0.5 * newWidth,
166 | z + 0.5 * newWidth,
167 | newWidth,
168 | level
169 | );
170 | let minNW_Box = new Box(
171 | "minNW",
172 | x - newWidth * 0.5,
173 | y + 0.5 * newWidth,
174 | z + 0.5 * newWidth,
175 | newWidth,
176 | level
177 | );
178 | let minSW_Box = new Box(
179 | "minSW",
180 | x - newWidth * 0.5,
181 | y - 0.5 * newWidth,
182 | z + 0.5 * newWidth,
183 | newWidth,
184 | level
185 | );
186 | let minSE_Box = new Box(
187 | "minSE",
188 | x + newWidth * 0.5,
189 | y - 0.5 * newWidth,
190 | z + 0.5 * newWidth,
191 | newWidth,
192 | level
193 | );
194 |
195 | this.minNE = new Octree(minNE_Box, level);
196 | this.minNW = new Octree(minNW_Box, level);
197 | this.minSW = new Octree(minSW_Box, level);
198 | this.minSE = new Octree(minSE_Box, level);
199 | this.maxNE = new Octree(maxNE_Box, level);
200 | this.maxNW = new Octree(maxNW_Box, level);
201 | this.maxSW = new Octree(maxSW_Box, level);
202 | this.maxSE = new Octree(maxSE_Box, level);
203 | this.isDivided = true;
204 | }
205 |
206 | insert(point) {
207 | if (!this.box.bound(point)) {
208 | // console.log(
209 | // "out of boundary",
210 | // "for node",
211 | // point.x,
212 | // point.y,
213 | // point.z,
214 | // "for box",
215 | // this.box.x,
216 | // this.box.y,
217 | // this.box.z,
218 | // this.box.width * 0.5,
219 | // this.box.label
220 | // );
221 | return false;
222 | }
223 | if (this.points.length < tree.leafCapacity && !this.isDivided) {
224 | // this.updateRepresentativeNode();
225 | this.points.push(point.index);
226 | // this.sortNode();
227 | return true;
228 | } else if (this.buffer.length < tree.bufferCapacity && !this.isDivided) {
229 | this.buffer.push(point.index);
230 | return true;
231 | } else {
232 | if (!this.isDivided) {
233 | this.partition();
234 | this.buffer.forEach((existingPoint, index) => {
235 | if (
236 | existingPoint.x == point.x &&
237 | existingPoint.y == point.y &&
238 | existingPoint.z == point.z
239 | ) {
240 | console.log("repetitive node not allowed");
241 | return false;
242 | }
243 | this.minNE.insert(existingPoint) ||
244 | this.minNW.insert(existingPoint) ||
245 | this.minSE.insert(existingPoint) ||
246 | this.minSW.insert(existingPoint) ||
247 | this.maxNE.insert(existingPoint) ||
248 | this.maxNW.insert(existingPoint) ||
249 | this.maxSW.insert(existingPoint) ||
250 | this.maxSE.insert(existingPoint);
251 | });
252 | this.buffer = [];
253 | }
254 | return (
255 | this.minNE.insert(point) ||
256 | this.minNW.insert(point) ||
257 | this.minSE.insert(point) ||
258 | this.minSW.insert(point) ||
259 | this.maxNE.insert(point) ||
260 | this.maxNW.insert(point) ||
261 | this.maxSW.insert(point) ||
262 | this.maxSE.insert(point)
263 | );
264 | }
265 | }
266 | }
267 |
268 | export { Point, Box, Octree };
269 |
--------------------------------------------------------------------------------
/src/webgpu/renderer.js:
--------------------------------------------------------------------------------
1 | import { vs, fs } from "../shaders/renderShader.js";
2 | import {
3 | bufferMap,
4 | retrivePoints,
5 | toDeleteMap,
6 | wait,
7 | controls,
8 | global_max_intensity,
9 | } from "../index.js";
10 | import Stats from "three/examples/jsm/libs/stats.module";
11 |
12 | let adapter = null;
13 | let device = null;
14 | let worldViewProj = mat4.create();
15 | var projView = mat4.create();
16 | let proj;
17 | let camera;
18 | let context = null;
19 | let swapChainFormat = "bgra8unorm";
20 | let renderPipeline;
21 | let mvp_BG;
22 | let positionBuffer;
23 | let colorBuffer;
24 | let MVP_Buffer;
25 | var lasInfoBuffer;
26 | let maxIntensityBuffer;
27 | let commandEncoder;
28 | let renderPassDescriptor;
29 | let renderDepthTexture;
30 | let canvas;
31 | let numPoints;
32 | let positions, colors;
33 | let keyMap = {
34 | isDown: false,
35 | dragging: false,
36 | };
37 | let debounceTimeOutId = null;
38 | let colorMapBuffer;
39 | let paramsBuffer;
40 | let currentAxis = 3;
41 | let param;
42 | let abortController = null;
43 | let levelBuffer;
44 |
45 | const stats = Stats();
46 | document.body.appendChild(stats.dom);
47 |
48 | function throttle(callback, interval) {
49 | let enableCall = true;
50 | return function (...args) {
51 | if (!enableCall) return;
52 | enableCall = false;
53 | callback.apply(this, args);
54 | setTimeout(() => (enableCall = true), interval);
55 | };
56 | }
57 |
58 | let throttleTreeTravel = throttle(retrivePoints, 2000);
59 |
60 | // ------------------------------- camera itenary
61 |
62 | const iternary = [
63 | { x: -100, y: 1000, z: 700 },
64 | { x: 0, y: 1000, z: 700 },
65 | { x: 0, y: 500, z: 300 },
66 | { x: 100, y: 200, z: 300 },
67 | { x: 0, y: 100, z: 100 },
68 | ];
69 |
70 | function moveCamera() {
71 | return new Promise((resolve, reject) => {
72 | controls.object.position.set(
73 | iternary[count].x,
74 | iternary[count].y,
75 | iternary[count].z
76 | );
77 | render();
78 | throttleTreeTravel(projView);
79 | resolve("done");
80 | });
81 | }
82 |
83 | let count = 0;
84 | function itenaryStart(fn) {
85 | fn().then((response) => {
86 | count++;
87 | if (count < 5) setTimeout(() => itenaryStart(fn), 10000);
88 | });
89 | }
90 |
91 | // -----------------------------------------------------------------
92 |
93 | function configureSwapChain(device) {
94 | context.configure({
95 | device: device,
96 | format: swapChainFormat,
97 | usage: GPUTextureUsage.RenderAttachment,
98 | alphaMode: "premultiplied",
99 | });
100 | }
101 |
102 | function goToFallback() {
103 | console.error("unable to start webgpu");
104 | return;
105 | }
106 |
107 | function recoverFromDeviceLoss(data) {
108 | console.log("device is lost");
109 | }
110 |
111 | (() => {
112 | const selectColormap = document.getElementById("colormap-axis");
113 | selectColormap.addEventListener("change", (event) => {
114 | const axis = parseInt(event.target.value);
115 | if (axis != currentAxis) {
116 | currentAxis = axis;
117 | updateAxis();
118 | }
119 | });
120 | })();
121 |
122 | async function updateMaxIntensity() {
123 | param[param.length - 1] = global_max_intensity;
124 | const stagingBuffer = device.createBuffer({
125 | usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC,
126 | size: 32,
127 | mappedAtCreation: true,
128 | });
129 |
130 | const stagingData = new Float32Array(stagingBuffer.getMappedRange());
131 | stagingData.set(param);
132 | stagingBuffer.unmap();
133 | const copyEncoder = device.createCommandEncoder();
134 | copyEncoder.copyBufferToBuffer(stagingBuffer, 28, paramsBuffer, 28, 4);
135 | device.queue.submit([copyEncoder.finish()]);
136 | }
137 |
138 | async function updateAxis() {
139 | param[param.length - 2] = currentAxis;
140 | const stagingBuffer = device.createBuffer({
141 | usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC,
142 | size: 32,
143 | mappedAtCreation: true,
144 | });
145 |
146 | const stagingData = new Float32Array(stagingBuffer.getMappedRange());
147 | stagingData.set(param);
148 | stagingBuffer.unmap();
149 | const copyEncoder = device.createCommandEncoder();
150 | copyEncoder.copyBufferToBuffer(stagingBuffer, 24, paramsBuffer, 24, 8);
151 | device.queue.submit([copyEncoder.finish()]);
152 | }
153 |
154 | async function init() {
155 | adapter = await navigator.gpu.requestAdapter();
156 | if (!adapter) return goToFallback();
157 | device = await adapter.requestDevice();
158 | if (!device) return goToFallback();
159 | // device.lost.then(recoverFromDeviceLoss);
160 |
161 | canvas = document.getElementById("screen-canvas");
162 | canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
163 | canvas.height = window.innerHeight * (window.devicePixelRatio || 1);
164 |
165 | context = canvas.getContext("webgpu");
166 | if (!context) {
167 | console.error("coould not get context from the canvas");
168 | return;
169 | }
170 |
171 | swapChainFormat = navigator.gpu.getPreferredCanvasFormat();
172 | configureSwapChain(device);
173 | canvas.addEventListener("mousedown", (e) => {
174 | if (e.buttons == 1 || e.buttons == 2) {
175 | keyMap["isDown"] = true;
176 | }
177 | });
178 |
179 | window.addEventListener("mouseup", (e) => {
180 | keyMap["isDown"] = false;
181 | });
182 |
183 | canvas.addEventListener("mousemove", () => {
184 | if (keyMap["isDown"] == true) {
185 | throttleTreeTravel(projView);
186 | }
187 | });
188 |
189 | window.addEventListener("wheel", (event) => {
190 | // console.log(camera.eyePos());
191 | if (abortController) {
192 | abortController.abort();
193 | }
194 | abortController = new AbortController();
195 | throttleTreeTravel(projView, abortController.signal);
196 | });
197 | }
198 |
199 | async function intRenderPipeline() {
200 | let Vertex_Buffer_Descriptor = [{}];
201 | let vs_module = device.createShaderModule({
202 | label: "vertex shader",
203 | code: vs,
204 | });
205 |
206 | let fs_module = device.createShaderModule({
207 | label: "fragment shader",
208 | code: fs,
209 | });
210 |
211 | let positionAttribute_Desc = {
212 | shaderLocation: 0,
213 | offset: 0,
214 | format: "float32x4",
215 | };
216 |
217 | let colorAttribute_Desc = {
218 | shaderLocation: 1,
219 | offset: 0,
220 | format: "float32x3",
221 | };
222 |
223 | let Vertex_Shader_Descriptor = {
224 | module: vs_module,
225 | entryPoint: "main",
226 | buffers: [
227 | {
228 | arrayStride: 16,
229 | stepMode: "instance",
230 | attributes: [positionAttribute_Desc],
231 | },
232 | {
233 | arrayStride: 12,
234 | stepMode: "instance",
235 | attributes: [colorAttribute_Desc],
236 | },
237 | ],
238 | };
239 |
240 | let Fragment_Shader_Descriptor = {
241 | module: fs_module,
242 | entryPoint: "main",
243 | targets: [{ format: swapChainFormat }],
244 | };
245 |
246 | let Depth_Stencil_Descriptor = {
247 | format: "depth24plus-stencil8",
248 | depthWriteEnabled: true,
249 | depthCompare: "less",
250 | };
251 |
252 | let Primitive_Descriptor = {
253 | topology: "triangle-strip",
254 | cullMode: "none",
255 | };
256 |
257 | renderPipeline = await device.createRenderPipeline({
258 | label: "render pipeline",
259 | layout: "auto",
260 | vertex: Vertex_Shader_Descriptor,
261 | fragment: Fragment_Shader_Descriptor,
262 | depthStencil: Depth_Stencil_Descriptor,
263 | primitive: Primitive_Descriptor,
264 | });
265 | }
266 |
267 | async function initVertexBuffer() {
268 | let totalNumberOfPoints = numPoints;
269 | positionBuffer = device.createBuffer({
270 | label: "vertex position buffer",
271 | size: totalNumberOfPoints * 16,
272 | usage: GPUBufferUsage.VERTEX,
273 | mappedAtCreation: true,
274 | });
275 |
276 | let mapArrayPosition = new Float32Array(positionBuffer.getMappedRange());
277 | mapArrayPosition.set(positions);
278 | positionBuffer.unmap();
279 |
280 | colorBuffer = device.createBuffer({
281 | label: "vertex color buffer",
282 | size: totalNumberOfPoints * 12,
283 | usage: GPUBufferUsage.VERTEX,
284 | mappedAtCreation: true,
285 | });
286 |
287 | let mapArrayColor = new Float32Array(colorBuffer.getMappedRange());
288 | mapArrayColor.set(colors);
289 | colorBuffer.unmap();
290 | }
291 |
292 | function initUniform(cam, projMatrix, params) {
293 | camera = cam;
294 | proj = projMatrix;
295 | param = params;
296 | params.push(currentAxis);
297 | params.push(global_max_intensity);
298 | // params
299 | paramsBuffer = device.createBuffer({
300 | size: 8 * 4,
301 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
302 | mappedAtCreation: true,
303 | });
304 | let mapArray_params = new Float32Array(paramsBuffer.getMappedRange());
305 | mapArray_params.set(params);
306 | paramsBuffer.unmap();
307 |
308 | levelBuffer = device.createBuffer({
309 | size: 4,
310 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
311 | mappedAtCreation: true,
312 | });
313 | let mapArray_level = new Float32Array(levelBuffer.getMappedRange());
314 | mapArray_level.set([0]);
315 | levelBuffer.unmap();
316 |
317 | function get1DArray(arr) {
318 | return +arr.join().split(",");
319 | }
320 | // create colormap
321 | let hsv_colors = [
322 | [0.0, 0.0, 0.5],
323 | [0.0, 0.2, 0.7],
324 | [0.0, 0.4, 0.9],
325 | [0.0, 0.6, 1.0],
326 | [0.0, 0.8, 1.0],
327 | [0.2, 0.9, 0.8],
328 | [0.4, 1.0, 0.6],
329 | [0.6, 1.0, 0.4],
330 | [0.8, 1.0, 0.2],
331 | [1.0, 1.0, 0.0],
332 | [1.0, 0.9, 0.0],
333 | [1.0, 0.8, 0.0],
334 | [1.0, 0.6, 0.0],
335 | [1.0, 0.4, 0.0],
336 | [1.0, 0.2, 0.0],
337 | [0.9, 0.0, 0.0],
338 | [0.7, 0.0, 0.0],
339 | [0.5, 0.0, 0.0],
340 | [0.3, 0.0, 0.0],
341 | [0.1, 0.5, 0.0],
342 | ];
343 |
344 | hsv_colors = hsv_colors.flat();
345 | colorMapBuffer = device.createBuffer({
346 | size: hsv_colors.length * 3 * 4,
347 | usage: GPUBufferUsage.UNIFORM,
348 | mappedAtCreation: true,
349 | });
350 |
351 | let mapArray = new Float32Array(colorMapBuffer.getMappedRange());
352 |
353 | mapArray.set(hsv_colors);
354 | colorMapBuffer.unmap();
355 |
356 | MVP_Buffer = device.createBuffer({
357 | size: 16 * 4,
358 | usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
359 | });
360 |
361 | let viewMatrix = camera.matrixWorldInverse.elements
362 | projView = mat4.mul(projView, viewMatrix, proj);
363 | return projView;
364 | }
365 |
366 | async function createBindGroups() {
367 | mvp_BG = device.createBindGroup({
368 | label: "uniform bindgroup - rendering",
369 | layout: renderPipeline.getBindGroupLayout(0),
370 | entries: [
371 | {
372 | binding: 0,
373 | resource: {
374 | buffer: MVP_Buffer,
375 | },
376 | },
377 | {
378 | binding: 1,
379 | resource: {
380 | buffer: colorMapBuffer,
381 | },
382 | },
383 | {
384 | binding: 2,
385 | resource: {
386 | buffer: paramsBuffer,
387 | },
388 | },
389 | ],
390 | });
391 | }
392 |
393 | async function createDepthBuffer() {
394 | renderDepthTexture = device.createTexture({
395 | size: [canvas.width, canvas.height, 1],
396 | format: "depth24plus-stencil8",
397 | usage: GPUTextureUsage.RENDER_ATTACHMENT,
398 | });
399 | }
400 |
401 | async function encodedCommand() {
402 | // create render pass descriptor
403 | // console.log(currentAxis);
404 |
405 | let colorAttachment = {
406 | view: context.getCurrentTexture().createView(),
407 | clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 },
408 | loadOp: "clear",
409 | storeOp: "store",
410 | };
411 |
412 | let depthAttachment = {
413 | view: renderDepthTexture.createView(),
414 | depthLoadOp: "clear",
415 | depthClearValue: 1.0,
416 | depthStoreOp: "store",
417 | stencilLoadOp: "clear",
418 | stencilClearValue: 0,
419 | stencilStoreOp: "store",
420 | };
421 |
422 | renderPassDescriptor = {
423 | colorAttachments: [colorAttachment],
424 | depthStencilAttachment: depthAttachment,
425 | };
426 | }
427 |
428 | async function update(timestamp) {
429 | {
430 | // update worldViewProj
431 | let proj = mat4.create();
432 | let view = mat4.create();
433 |
434 | {
435 | // proj
436 | const aspect = Math.abs(canvas.width / canvas.height);
437 | mat4.perspective(proj, 90, aspect, 0.1, 1000.0);
438 | }
439 |
440 | {
441 | // view
442 | let target = vec3.fromValues(2, 5, 0);
443 | // let r = 50;
444 | // let x = r * Math.sin(timestamp / 10) + target[0];
445 | // let y = r * Math.cos(timestamp / 10) + target[1];
446 | // let z = 10;
447 |
448 | let position = vec3.fromValues(5, 100, 100);
449 | let up = vec3.fromValues(0, 0, 1);
450 | mat4.lookAt(view, position, target, up);
451 | }
452 | const view_matrix = camera.matrixWorldInverse;
453 | mat4.multiply(worldViewProj, view_matrix, proj);
454 | }
455 | }
456 |
457 | async function stages(camera, proj, params) {
458 | await init();
459 | await intRenderPipeline();
460 | let projectionViewMatrix = await initUniform(camera, proj, params);
461 | return projectionViewMatrix;
462 | }
463 |
464 | // ---------------------------------------------------------------------------
465 | // i guess i am not using this
466 |
467 | async function renderStages(position, color) {
468 | numPoints = position.length / 3;
469 | positions = position;
470 | colors = color;
471 | await initVertexBuffer();
472 | await createBindGroups();
473 | await createDepthBuffer();
474 | requestAnimationFrame(render2);
475 | }
476 | // -----------------------------------------------------------------------------
477 | async function renderWrapper() {
478 | await createBindGroups();
479 | await createDepthBuffer();
480 | await updateMaxIntensity();
481 | render();
482 | // itenaryStart(moveCamera);
483 | }
484 |
485 | function render(timestamp) {
486 | stats.update();
487 | var startTime = performance.now();
488 | commandEncoder = device.createCommandEncoder();
489 | // this is not helpful for tree traversal so model matrix rotation is removed for now
490 | let viewMatrix = camera.matrixWorldInverse.elements;
491 | // console.log(controls.object.position, controls.object.rotation, controls.target )
492 | projView = mat4.mul(projView, proj, viewMatrix);
493 | controls.update();
494 | // update(timestamp);
495 | encodedCommand();
496 |
497 | // device.queue.writeBuffer(MVP_Buffer, 0, worldViewProj, 16);
498 |
499 | let wvStagingBuffer = device.createBuffer({
500 | size: 4 * 16,
501 | usage: GPUBufferUsage.COPY_SRC,
502 | mappedAtCreation: true,
503 | });
504 | const stagingUniformData = new Float32Array(wvStagingBuffer.getMappedRange());
505 | stagingUniformData.set(projView);
506 | wvStagingBuffer.unmap();
507 | commandEncoder.copyBufferToBuffer(wvStagingBuffer, 0, MVP_Buffer, 0, 64);
508 | let renderPass = commandEncoder.beginRenderPass(renderPassDescriptor);
509 | renderPass.setPipeline(renderPipeline);
510 | renderPass.setViewport(0, 0, canvas.width, canvas.height, 0.0, 1.0);
511 | renderPass.setBindGroup(0, mvp_BG);
512 | for (let key in bufferMap) {
513 | // console.log(bufferMap[key].position);
514 | renderPass.setVertexBuffer(0, bufferMap[key].position);
515 | renderPass.setVertexBuffer(1, bufferMap[key].color);
516 | numPoints = +bufferMap[key].position.label / 4;
517 | renderPass.draw(4, numPoints, 0, 0);
518 | }
519 | renderPass.end();
520 | device.queue.submit([commandEncoder.finish()]);
521 | var endTime = performance.now();
522 | requestAnimationFrame(render);
523 | }
524 |
525 | export { renderStages, device, stages, renderWrapper, throttle };
526 |
--------------------------------------------------------------------------------
/docs/js/webgl-util.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // Compute the view frustum in world space from the provided
3 | // column major projection * view matrix
4 | var Frustum = function (projView) {
5 | var rows = [vec4.create(), vec4.create(), vec4.create(), vec4.create()];
6 | for (var i = 0; i < rows.length; ++i) {
7 | rows[i] = vec4.set(
8 | rows[i],
9 | projView[i],
10 | projView[4 + i],
11 | projView[8 + i],
12 | projView[12 + i]
13 | );
14 | }
15 |
16 | this.planes = [
17 | // -x plane
18 | vec4.add(vec4.create(), rows[3], rows[0]),
19 | // +x plane
20 | vec4.sub(vec4.create(), rows[3], rows[0]),
21 | // -y plane
22 | vec4.add(vec4.create(), rows[3], rows[1]),
23 | // +y plane
24 | vec4.sub(vec4.create(), rows[3], rows[1]),
25 | // -z plane
26 | vec4.add(vec4.create(), rows[3], rows[2]),
27 | // +z plane
28 | vec4.sub(vec4.create(), rows[3], rows[2]),
29 | ];
30 |
31 | // Normalize the planes
32 | for (var i = 0; i < this.planes.length; ++i) {
33 | var s =
34 | 1.0 /
35 | Math.sqrt(
36 | this.planes[i][0] * this.planes[i][0] +
37 | this.planes[i][1] * this.planes[i][1] +
38 | this.planes[i][2] * this.planes[i][2]
39 | );
40 | this.planes[i][0] *= s;
41 | this.planes[i][1] *= s;
42 | this.planes[i][2] *= s;
43 | this.planes[i][3] *= s;
44 | }
45 |
46 | // Compute the frustum points as well
47 | var invProjView = mat4.invert(mat4.create(), projView);
48 | this.points = [
49 | // x_l, y_l, z_l
50 | vec4.set(vec4.create(), -1, -1, -1, 1),
51 | // x_h, y_l, z_l
52 | vec4.set(vec4.create(), 1, -1, -1, 1),
53 | // x_l, y_h, z_l
54 | vec4.set(vec4.create(), -1, 1, -1, 1),
55 | // x_h, y_h, z_l
56 | vec4.set(vec4.create(), 1, 1, -1, 1),
57 | // x_l, y_l, z_h
58 | vec4.set(vec4.create(), -1, -1, 1, 1),
59 | // x_h, y_l, z_h
60 | vec4.set(vec4.create(), 1, -1, 1, 1),
61 | // x_l, y_h, z_h
62 | vec4.set(vec4.create(), -1, 1, 1, 1),
63 | // x_h, y_h, z_h
64 | vec4.set(vec4.create(), 1, 1, 1, 1),
65 | ];
66 | for (var i = 0; i < 8; ++i) {
67 | this.points[i] = vec4.transformMat4(
68 | this.points[i],
69 | this.points[i],
70 | invProjView
71 | );
72 | this.points[i][0] /= this.points[i][3];
73 | this.points[i][1] /= this.points[i][3];
74 | this.points[i][2] /= this.points[i][3];
75 | this.points[i][3] = 1.0;
76 | }
77 | };
78 |
79 | // Check if the box is contained in the Frustum
80 | // The box should be [x_lower, y_lower, z_lower, x_upper, y_upper, z_upper]
81 | // This is done using Inigo Quilez's approach to help with large
82 | // bounds: https://www.iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm
83 | Frustum.prototype.containsBox = function (box) {
84 | // Test the box against each plane
85 | var vec = vec4.create();
86 | var out = 0;
87 | for (var i = 0; i < this.planes.length; ++i) {
88 | out = 0;
89 | // x_l, y_l, z_l
90 | vec4.set(vec, box[0], box[1], box[2], 1.0);
91 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0;
92 | // x_h, y_l, z_l
93 | vec4.set(vec, box[3], box[1], box[2], 1.0);
94 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0;
95 | // x_l, y_h, z_l
96 | vec4.set(vec, box[0], box[4], box[2], 1.0);
97 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0;
98 | // x_h, y_h, z_l
99 | vec4.set(vec, box[3], box[4], box[2], 1.0);
100 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0;
101 | // x_l, y_l, z_h
102 | vec4.set(vec, box[0], box[1], box[5], 1.0);
103 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0;
104 | // x_h, y_l, z_h
105 | vec4.set(vec, box[3], box[1], box[5], 1.0);
106 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0;
107 | // x_l, y_h, z_h
108 | vec4.set(vec, box[0], box[4], box[5], 1.0);
109 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0;
110 | // x_h, y_h, z_h
111 | vec4.set(vec, box[3], box[4], box[5], 1.0);
112 | out += vec4.dot(this.planes[i], vec) < 0.0 ? 1 : 0;
113 |
114 | if (out == 8) {
115 | return false;
116 | }
117 | }
118 |
119 | // Test the frustum against the box
120 | out = 0;
121 | for (var i = 0; i < 8; ++i) {
122 | out += this.points[i][0] > box[3] ? 1 : 0;
123 | }
124 | if (out == 8) {
125 | return false;
126 | }
127 |
128 | out = 0;
129 | for (var i = 0; i < 8; ++i) {
130 | out += this.points[i][0] < box[0] ? 1 : 0;
131 | }
132 | if (out == 8) {
133 | return false;
134 | }
135 |
136 | out = 0;
137 | for (var i = 0; i < 8; ++i) {
138 | out += this.points[i][1] > box[4] ? 1 : 0;
139 | }
140 | if (out == 8) {
141 | return false;
142 | }
143 |
144 | out = 0;
145 | for (var i = 0; i < 8; ++i) {
146 | out += this.points[i][1] < box[1] ? 1 : 0;
147 | }
148 | if (out == 8) {
149 | return false;
150 | }
151 |
152 | out = 0;
153 | for (var i = 0; i < 8; ++i) {
154 | out += this.points[i][2] > box[5] ? 1 : 0;
155 | }
156 | if (out == 8) {
157 | return false;
158 | }
159 |
160 | out = 0;
161 | for (var i = 0; i < 8; ++i) {
162 | out += this.points[i][2] < box[2] ? 1 : 0;
163 | }
164 | if (out == 8) {
165 | return false;
166 | }
167 | return true;
168 | };
169 |
170 | var Shader = function (gl, vertexSrc, fragmentSrc) {
171 | var self = this;
172 | this.program = compileShader(gl, vertexSrc, fragmentSrc);
173 |
174 | var regexUniform = /uniform[^;]+[ ](\w+);/g;
175 | var matchUniformName = /uniform[^;]+[ ](\w+);/;
176 |
177 | this.uniforms = {};
178 |
179 | var vertexUnifs = vertexSrc.match(regexUniform);
180 | var fragUnifs = fragmentSrc.match(regexUniform);
181 |
182 | if (vertexUnifs) {
183 | vertexUnifs.forEach(function (unif) {
184 | var m = unif.match(matchUniformName);
185 | self.uniforms[m[1]] = -1;
186 | });
187 | }
188 | if (fragUnifs) {
189 | fragUnifs.forEach(function (unif) {
190 | var m = unif.match(matchUniformName);
191 | self.uniforms[m[1]] = -1;
192 | });
193 | }
194 |
195 | for (var unif in this.uniforms) {
196 | this.uniforms[unif] = gl.getUniformLocation(this.program, unif);
197 | }
198 | };
199 |
200 | Shader.prototype.use = function (gl) {
201 | gl.useProgram(this.program);
202 | };
203 |
204 | // Compile and link the shaders vert and frag. vert and frag should contain
205 | // the shader source code for the vertex and fragment shaders respectively
206 | // Returns the compiled and linked program, or null if compilation or linking failed
207 | var compileShader = function (gl, vert, frag) {
208 | var vs = gl.createShader(gl.VERTEX_SHADER);
209 | gl.shaderSource(vs, vert);
210 | gl.compileShader(vs);
211 | if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
212 | alert("Vertex shader failed to compile, see console for log");
213 | console.log(gl.getShaderInfoLog(vs));
214 | return null;
215 | }
216 |
217 | var fs = gl.createShader(gl.FRAGMENT_SHADER);
218 | gl.shaderSource(fs, frag);
219 | gl.compileShader(fs);
220 | if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
221 | alert("Fragment shader failed to compile, see console for log");
222 | console.log(gl.getShaderInfoLog(fs));
223 | return null;
224 | }
225 |
226 | var program = gl.createProgram();
227 | gl.attachShader(program, vs);
228 | gl.attachShader(program, fs);
229 | gl.linkProgram(program);
230 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
231 | alert("Shader failed to link, see console for log");
232 | console.log(gl.getProgramInfoLog(program));
233 | return null;
234 | }
235 | return program;
236 | };
237 |
238 | var getGLExtension = function (gl, ext) {
239 | if (!gl.getExtension(ext)) {
240 | alert("Missing " + ext + " WebGL extension");
241 | return false;
242 | }
243 | return true;
244 | };
245 |
246 | /* The arcball camera will be placed at the position 'eye', rotating
247 | * around the point 'center', with the up vector 'up'. 'screenDims'
248 | * should be the dimensions of the canvas or region taking mouse input
249 | * so the mouse positions can be normalized into [-1, 1] from the pixel
250 | * coordinates.
251 | */
252 | var ArcballCamera = function (eye, center, up, zoomSpeed, screenDims) {
253 | var veye = vec3.set(vec3.create(), eye[0], eye[1], eye[2]);
254 | var vcenter = vec3.set(vec3.create(), center[0], center[1], center[2]);
255 | var vup = vec3.set(vec3.create(), up[0], up[1], up[2]);
256 | vec3.normalize(vup, vup);
257 |
258 | var zAxis = vec3.sub(vec3.create(), vcenter, veye);
259 | var viewDist = vec3.len(zAxis);
260 | vec3.normalize(zAxis, zAxis);
261 |
262 | var xAxis = vec3.cross(vec3.create(), zAxis, vup);
263 | vec3.normalize(xAxis, xAxis);
264 |
265 | var yAxis = vec3.cross(vec3.create(), xAxis, zAxis);
266 | vec3.normalize(yAxis, yAxis);
267 |
268 | vec3.cross(xAxis, zAxis, yAxis);
269 | vec3.normalize(xAxis, xAxis);
270 |
271 | this.zoomSpeed = zoomSpeed;
272 | this.invScreen = [1.0 / screenDims[0], 1.0 / screenDims[1]];
273 |
274 | this.centerTranslation = mat4.fromTranslation(mat4.create(), center);
275 | mat4.invert(this.centerTranslation, this.centerTranslation);
276 |
277 | var vt = vec3.set(vec3.create(), 0, 0, -1.0 * viewDist);
278 | this.translation = mat4.fromTranslation(mat4.create(), vt);
279 |
280 | var rotMat = mat3.fromValues(
281 | xAxis[0],
282 | xAxis[1],
283 | xAxis[2],
284 | yAxis[0],
285 | yAxis[1],
286 | yAxis[2],
287 | -zAxis[0],
288 | -zAxis[1],
289 | -zAxis[2]
290 | );
291 | mat3.transpose(rotMat, rotMat);
292 | this.rotation = quat.fromMat3(quat.create(), rotMat);
293 | quat.normalize(this.rotation, this.rotation);
294 |
295 | this.camera = mat4.create();
296 | this.invCamera = mat4.create();
297 | this.updateCameraMatrix();
298 | };
299 |
300 | ArcballCamera.prototype.rotate = function (prevMouse, curMouse) {
301 | var mPrev = vec2.set(
302 | vec2.create(),
303 | clamp(prevMouse[0] * 2.0 * this.invScreen[0] - 1.0, -1.0, 1.0),
304 | clamp(1.0 - prevMouse[1] * 2.0 * this.invScreen[1], -1.0, 1.0)
305 | );
306 |
307 | var mCur = vec2.set(
308 | vec2.create(),
309 | clamp(curMouse[0] * 2.0 * this.invScreen[0] - 1.0, -1.0, 1.0),
310 | clamp(1.0 - curMouse[1] * 2.0 * this.invScreen[1], -1.0, 1.0)
311 | );
312 |
313 | var mPrevBall = screenToArcball(mPrev);
314 | var mCurBall = screenToArcball(mCur);
315 | // rotation = curBall * prevBall * rotation
316 | this.rotation = quat.mul(this.rotation, mPrevBall, this.rotation);
317 | this.rotation = quat.mul(this.rotation, mCurBall, this.rotation);
318 |
319 | this.updateCameraMatrix();
320 | };
321 |
322 | ArcballCamera.prototype.zoom = function (amount) {
323 | var vt = vec3.set(
324 | vec3.create(),
325 | 0.0,
326 | 0.0,
327 | amount * this.invScreen[1] * this.zoomSpeed
328 | );
329 | var t = mat4.fromTranslation(mat4.create(), vt);
330 | this.translation = mat4.mul(this.translation, t, this.translation);
331 | if (this.translation[14] >= -0.2) {
332 | this.translation[14] = -0.2;
333 | }
334 | this.updateCameraMatrix();
335 | };
336 |
337 | ArcballCamera.prototype.pan = function (mouseDelta) {
338 | var delta = vec4.set(
339 | vec4.create(),
340 | mouseDelta[0] * this.invScreen[0] * Math.abs(this.translation[14]),
341 | mouseDelta[1] * this.invScreen[1] * Math.abs(this.translation[14]),
342 | 0,
343 | 0
344 | );
345 | var worldDelta = vec4.transformMat4(vec4.create(), delta, this.invCamera);
346 | var translation = mat4.fromTranslation(mat4.create(), worldDelta);
347 | this.centerTranslation = mat4.mul(
348 | this.centerTranslation,
349 | translation,
350 | this.centerTranslation
351 | );
352 | this.updateCameraMatrix();
353 | };
354 |
355 | ArcballCamera.prototype.updateCameraMatrix = function () {
356 | // camera = translation * rotation * centerTranslation
357 | var rotMat = mat4.fromQuat(mat4.create(), this.rotation);
358 | this.camera = mat4.mul(this.camera, rotMat, this.centerTranslation);
359 | this.camera = mat4.mul(this.camera, this.translation, this.camera);
360 | this.invCamera = mat4.invert(this.invCamera, this.camera);
361 | };
362 |
363 | ArcballCamera.prototype.eyePos = function () {
364 | return [this.invCamera[12], this.invCamera[13], this.invCamera[14]];
365 | };
366 |
367 | ArcballCamera.prototype.eyeDir = function () {
368 | var dir = vec4.set(vec4.create(), 0.0, 0.0, -1.0, 0.0);
369 | dir = vec4.transformMat4(dir, dir, this.invCamera);
370 | dir = vec4.normalize(dir, dir);
371 | return [dir[0], dir[1], dir[2]];
372 | };
373 |
374 | ArcballCamera.prototype.upDir = function () {
375 | var dir = vec4.set(vec4.create(), 0.0, 1.0, 0.0, 0.0);
376 | dir = vec4.transformMat4(dir, dir, this.invCamera);
377 | dir = vec4.normalize(dir, dir);
378 | return [dir[0], dir[1], dir[2]];
379 | };
380 |
381 | var screenToArcball = function (p) {
382 | var dist = vec2.dot(p, p);
383 | if (dist <= 1.0) {
384 | return quat.set(quat.create(), p[0], p[1], Math.sqrt(1.0 - dist), 0);
385 | } else {
386 | var unitP = vec2.normalize(vec2.create(), p);
387 | // cgmath is w, x, y, z
388 | // glmatrix is x, y, z, w
389 | return quat.set(quat.create(), unitP[0], unitP[1], 0, 0);
390 | }
391 | };
392 | var clamp = function (a, min, max) {
393 | return a < min ? min : a > max ? max : a;
394 | };
395 |
396 | var pointDist = function (a, b) {
397 | var v = [b[0] - a[0], b[1] - a[1]];
398 | return Math.sqrt(Math.pow(v[0], 2.0) + Math.pow(v[1], 2.0));
399 | };
400 |
401 | var Buffer = function (capacity, dtype) {
402 | this.len = 0;
403 | this.capacity = capacity;
404 | if (dtype == "uint8") {
405 | this.buffer = new Uint8Array(capacity);
406 | } else if (dtype == "int8") {
407 | this.buffer = new Int8Array(capacity);
408 | } else if (dtype == "uint16") {
409 | this.buffer = new Uint16Array(capacity);
410 | } else if (dtype == "int16") {
411 | this.buffer = new Int16Array(capacity);
412 | } else if (dtype == "uint32") {
413 | this.buffer = new Uint32Array(capacity);
414 | } else if (dtype == "int32") {
415 | this.buffer = new Int32Array(capacity);
416 | } else if (dtype == "float32") {
417 | this.buffer = new Float32Array(capacity);
418 | } else if (dtype == "float64") {
419 | this.buffer = new Float64Array(capacity);
420 | } else {
421 | console.log("ERROR: unsupported type " + dtype);
422 | }
423 | };
424 |
425 | Buffer.prototype.append = function (buf) {
426 | if (this.len + buf.length >= this.capacity) {
427 | var newCap = Math.floor(
428 | Math.max(this.capacity * 1.5),
429 | this.len + buf.length
430 | );
431 | var tmp = new this.buffer.constructor(newCap);
432 | tmp.set(this.buffer);
433 |
434 | this.capacity = newCap;
435 | this.buffer = tmp;
436 | }
437 | this.buffer.set(buf, this.len);
438 | this.len += buf.length;
439 | };
440 |
441 | Buffer.prototype.clear = function () {
442 | this.len = 0;
443 | };
444 |
445 | Buffer.prototype.stride = function () {
446 | return this.buffer.BYTES_PER_ELEMENT;
447 | };
448 |
449 | Buffer.prototype.view = function (offset, length) {
450 | return new this.buffer.constructor(this.buffer.buffer, offset, length);
451 | };
452 |
453 | // Various utilities that don't really fit anywhere else
454 |
455 | // Parse the hex string to RGB values in [0, 255]
456 | var hexToRGB = function (hex) {
457 | var val = parseInt(hex.substr(1), 16);
458 | var r = (val >> 16) & 255;
459 | var g = (val >> 8) & 255;
460 | var b = val & 255;
461 | return [r, g, b];
462 | };
463 |
464 | // Parse the hex string to RGB values in [0, 1]
465 | var hexToRGBf = function (hex) {
466 | var c = hexToRGB(hex);
467 | return [c[0] / 255.0, c[1] / 255.0, c[2] / 255.0];
468 | };
469 |
470 | /* The controller can register callbacks for various events on a canvas:
471 | *
472 | * mousemove: function(prevMouse, curMouse, evt)
473 | * receives both regular mouse events, and single-finger drags (sent as a left-click),
474 | *
475 | * press: function(curMouse, evt)
476 | * receives mouse click and touch start events
477 | *
478 | * wheel: function(amount)
479 | * mouse wheel scrolling
480 | *
481 | * pinch: function(amount)
482 | * two finger pinch, receives the distance change between the fingers
483 | *
484 | * twoFingerDrag: function(dragVector)
485 | * two finger drag, receives the drag movement amount
486 | */
487 | var Controller = function () {
488 | this.mousemove = null;
489 | this.press = null;
490 | this.wheel = null;
491 | this.twoFingerDrag = null;
492 | this.pinch = null;
493 | };
494 |
495 | Controller.prototype.registerForCanvas = function (canvas) {
496 | var prevMouse = null;
497 | var mouseState = [false, false];
498 | var self = this;
499 | canvas.addEventListener("mousemove", function (evt) {
500 | evt.preventDefault();
501 | var rect = canvas.getBoundingClientRect();
502 | var curMouse = [evt.clientX - rect.left, evt.clientY - rect.top];
503 | if (!prevMouse) {
504 | prevMouse = [evt.clientX - rect.left, evt.clientY - rect.top];
505 | } else if (self.mousemove) {
506 | self.mousemove(prevMouse, curMouse, evt);
507 | }
508 | prevMouse = curMouse;
509 | });
510 |
511 | canvas.addEventListener("mousedown", function (evt) {
512 | evt.preventDefault();
513 | var rect = canvas.getBoundingClientRect();
514 | var curMouse = [evt.clientX - rect.left, evt.clientY - rect.top];
515 | if (self.press) {
516 | self.press(curMouse, evt);
517 | }
518 | });
519 |
520 | canvas.addEventListener("wheel", function (evt) {
521 | evt.preventDefault();
522 | if (self.wheel) {
523 | self.wheel(-evt.deltaY);
524 | }
525 | });
526 |
527 | canvas.oncontextmenu = function (evt) {
528 | evt.preventDefault();
529 | };
530 |
531 | var touches = {};
532 | canvas.addEventListener("touchstart", function (evt) {
533 | var rect = canvas.getBoundingClientRect();
534 | evt.preventDefault();
535 | for (var i = 0; i < evt.changedTouches.length; ++i) {
536 | var t = evt.changedTouches[i];
537 | touches[t.identifier] = [t.clientX - rect.left, t.clientY - rect.top];
538 | if (evt.changedTouches.length == 1 && self.press) {
539 | self.press(touches[t.identifier], evt);
540 | }
541 | }
542 | });
543 |
544 | canvas.addEventListener("touchmove", function (evt) {
545 | evt.preventDefault();
546 | var rect = canvas.getBoundingClientRect();
547 | var numTouches = Object.keys(touches).length;
548 | // Single finger to rotate the camera
549 | if (numTouches == 1) {
550 | if (self.mousemove) {
551 | var t = evt.changedTouches[0];
552 | var prevTouch = touches[t.identifier];
553 | var curTouch = [t.clientX - rect.left, t.clientY - rect.top];
554 | evt.buttons = 1;
555 | self.mousemove(prevTouch, curTouch, evt);
556 | }
557 | } else {
558 | var curTouches = {};
559 | for (var i = 0; i < evt.changedTouches.length; ++i) {
560 | var t = evt.changedTouches[i];
561 | curTouches[t.identifier] = [
562 | t.clientX - rect.left,
563 | t.clientY - rect.top,
564 | ];
565 | }
566 |
567 | // If some touches didn't change make sure we have them in
568 | // our curTouches list to compute the pinch distance
569 | // Also get the old touch points to compute the distance here
570 | var oldTouches = [];
571 | for (t in touches) {
572 | if (!(t in curTouches)) {
573 | curTouches[t] = touches[t];
574 | }
575 | oldTouches.push(touches[t]);
576 | }
577 |
578 | var newTouches = [];
579 | for (t in curTouches) {
580 | newTouches.push(curTouches[t]);
581 | }
582 |
583 | // Determine if the user is pinching or panning
584 | var motionVectors = [
585 | vec2.set(
586 | vec2.create(),
587 | newTouches[0][0] - oldTouches[0][0],
588 | newTouches[0][1] - oldTouches[0][1]
589 | ),
590 | vec2.set(
591 | vec2.create(),
592 | newTouches[1][0] - oldTouches[1][0],
593 | newTouches[1][1] - oldTouches[1][1]
594 | ),
595 | ];
596 | var motionDirs = [vec2.create(), vec2.create()];
597 | vec2.normalize(motionDirs[0], motionVectors[0]);
598 | vec2.normalize(motionDirs[1], motionVectors[1]);
599 |
600 | var pinchAxis = vec2.set(
601 | vec2.create(),
602 | oldTouches[1][0] - oldTouches[0][0],
603 | oldTouches[1][1] - oldTouches[0][1]
604 | );
605 | vec2.normalize(pinchAxis, pinchAxis);
606 |
607 | var panAxis = vec2.lerp(
608 | vec2.create(),
609 | motionVectors[0],
610 | motionVectors[1],
611 | 0.5
612 | );
613 | vec2.normalize(panAxis, panAxis);
614 |
615 | var pinchMotion = [
616 | vec2.dot(pinchAxis, motionDirs[0]),
617 | vec2.dot(pinchAxis, motionDirs[1]),
618 | ];
619 | var panMotion = [
620 | vec2.dot(panAxis, motionDirs[0]),
621 | vec2.dot(panAxis, motionDirs[1]),
622 | ];
623 |
624 | // If we're primarily moving along the pinching axis and in the opposite direction with
625 | // the fingers, then the user is zooming.
626 | // Otherwise, if the fingers are moving along the same direction they're panning
627 | if (
628 | self.pinch &&
629 | Math.abs(pinchMotion[0]) > 0.5 &&
630 | Math.abs(pinchMotion[1]) > 0.5 &&
631 | Math.sign(pinchMotion[0]) != Math.sign(pinchMotion[1])
632 | ) {
633 | // Pinch distance change for zooming
634 | var oldDist = pointDist(oldTouches[0], oldTouches[1]);
635 | var newDist = pointDist(newTouches[0], newTouches[1]);
636 | self.pinch(newDist - oldDist);
637 | } else if (
638 | self.twoFingerDrag &&
639 | Math.abs(panMotion[0]) > 0.5 &&
640 | Math.abs(panMotion[1]) > 0.5 &&
641 | Math.sign(panMotion[0]) == Math.sign(panMotion[1])
642 | ) {
643 | // Pan by the average motion of the two fingers
644 | var panAmount = vec2.lerp(
645 | vec2.create(),
646 | motionVectors[0],
647 | motionVectors[1],
648 | 0.5
649 | );
650 | panAmount[1] = -panAmount[1];
651 | self.twoFingerDrag(panAmount);
652 | }
653 | }
654 |
655 | // Update the existing list of touches with the current positions
656 | for (var i = 0; i < evt.changedTouches.length; ++i) {
657 | var t = evt.changedTouches[i];
658 | touches[t.identifier] = [t.clientX - rect.left, t.clientY - rect.top];
659 | }
660 | });
661 |
662 | var touchEnd = function (evt) {
663 | evt.preventDefault();
664 | for (var i = 0; i < evt.changedTouches.length; ++i) {
665 | var t = evt.changedTouches[i];
666 | delete touches[t.identifier];
667 | }
668 | };
669 | canvas.addEventListener("touchcancel", touchEnd);
670 | canvas.addEventListener("touchend", touchEnd);
671 | };
672 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from "three";
2 | import { Octree, Box, Point } from "./octree";
3 | import { OrbitControls } from "three/addons/controls/OrbitControls.js";
4 |
5 | import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
6 | import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
7 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass";
8 | import { CopyShader } from "three/examples/jsm/shaders/CopyShader";
9 | import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader";
10 | import Stats from "three/examples/jsm/libs/stats.module";
11 | import { Copc, Key } from "copc";
12 | import Worker from "./worker/fetcher.worker.js";
13 | import { renderStages, device, stages, renderWrapper } from "./webgpu/renderer";
14 | import { traverseTreeWrapper } from "./passiveloader";
15 | import {
16 | write,
17 | read,
18 | doesExist,
19 | clear,
20 | create_P_Meta_Cache,
21 | throttled_Update_Pers_Cache,
22 | } from "./private_origin/file_manager";
23 | import {
24 | p_cache,
25 | get_inCache,
26 | getLRU_inCache,
27 | getMRU_inCache,
28 | sortObjectIntoMap,
29 | mapIntoJSON,
30 | put_inCache,
31 | } from "./private_origin/cache_manager";
32 |
33 | import "./styles/main.css";
34 | import { fillArray, fillMidNodes, updateHtmlUI } from "./helper";
35 | import { cache } from "./lru-cache/index";
36 |
37 | let loadedFromCache = 0;
38 | let fetchedFromsource = 0;
39 | const bytes = new Float32Array(59);
40 | const source_file_name = process.env.filename.split("/").pop();
41 | let state_meta = {};
42 |
43 | // ------------------- used to clear intital POFS that got created while writing code ---------------
44 | // (async () => {
45 | // const root = await navigator.storage.getDirectory();
46 | // const fileHandle2 = await root.getFileHandle("1-1-0-0.txt");
47 | // await fileHandle2.remove();
48 | // const fileHandle = await root.getFileHandle("0-0-0-0.txt");
49 | // await fileHandle.remove();
50 | // const fileHandle1 = await root.getFileHandle("1-0-0-0.txt");
51 | // await fileHandle1.remove();
52 |
53 | // // console.log(await doesExist("0-1-0-0"));
54 | // })();
55 | // ---------------------------------------------------------------------------------------------------
56 |
57 | let bufferMap = {};
58 | let wait = false;
59 | let toDeleteMap = {};
60 | let toDelete = false;
61 | let postMessageRes = 100;
62 | let positions = [];
63 | let colors = [];
64 | let workerCount = 0;
65 | const clock = new THREE.Clock();
66 | const workers = new Array(1).fill(null);
67 | let TotalCount = 0;
68 | const MAX_WORKERS = navigator.hardwareConcurrency - 1;
69 | let promises = [];
70 | let nodePages, nodePagesString;
71 | let pagesString;
72 | let camera, proj;
73 | let copcString;
74 | let x_min,
75 | y_min,
76 | z_min,
77 | x_max,
78 | y_max,
79 | z_max,
80 | widthx,
81 | widthy,
82 | widthz,
83 | center_x,
84 | center_y,
85 | center_z,
86 | scaleFactor,
87 | params,
88 | controls;
89 | let pers_cache;
90 | let global_max_intensity = 0;
91 | let prefetch_keyCountMap;
92 |
93 | const canvas = document.getElementById("screen-canvas");
94 |
95 | canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
96 | canvas.height = window.innerHeight * (window.devicePixelRatio || 1);
97 |
98 | function isTerminated(worker) {
99 | try {
100 | worker.postMessage(() => {});
101 | return true;
102 | } catch (e) {
103 | return false;
104 | }
105 | }
106 |
107 | function createWorker(data1, data2) {
108 | let myNode = data1.split("-").map(Number);
109 | let myLevel = myNode[0];
110 | return new Promise((resolve) => {
111 | let worker = new Worker();
112 | worker.onmessage = (event) => {
113 | let postMessageRes = event.data;
114 | if (postMessageRes == 200) {
115 | worker.postMessage([
116 | nodePagesString,
117 | pagesString,
118 | copcString,
119 | data1,
120 | data2,
121 | [
122 | x_min,
123 | y_min,
124 | z_min,
125 | widthx,
126 | widthy,
127 | widthz,
128 | scaleFactor[0],
129 | scaleFactor[1],
130 | scaleFactor[2],
131 | myLevel,
132 | ],
133 | ]);
134 | } else {
135 | workerCount += 1;
136 | let position = postMessageRes[0];
137 | let color = postMessageRes[1];
138 | let [minZ, maxZ, maxIntensity, dataLevel] = postMessageRes[2];
139 | if (maxIntensity > global_max_intensity) {
140 | global_max_intensity = maxIntensity;
141 | }
142 | let localPosition = [];
143 | let localColor = [];
144 | for (let i = 0; i < position.length; i++) {
145 | positions.push(position[i]);
146 | if (i > 0 && i % 3 == 0) {
147 | localPosition.push(dataLevel);
148 | }
149 | localPosition.push(position[i]);
150 | colors.push(color[i]);
151 | localColor.push(color[i]);
152 | }
153 | localPosition.push(dataLevel);
154 |
155 | if (workerCount == MAX_WORKERS) {
156 | workerCount = 0;
157 | promises = [];
158 | }
159 | worker.terminate();
160 | resolve([localPosition, localColor, data1, maxIntensity]);
161 | }
162 | };
163 | });
164 | }
165 |
166 | let boxGroup = new THREE.Group();
167 | let scene_width = 1000;
168 | let scene_height = 1000;
169 | let scene_depth = 1000;
170 | let scale = 1;
171 | let qt;
172 | let mapCamera,
173 | mapSizeX = 128,
174 | mapSizeY = 64;
175 | let _width = window.innerWidth;
176 | let _height = window.innerHeight;
177 | let right = 1024,
178 | left = -1024,
179 | top = 1024,
180 | bottom = -1024;
181 |
182 | let isIntensityPresent;
183 |
184 | function findLevel(qt) {
185 | // traverse octre
186 | let threshold = 100;
187 | let cameraPosition = controls.object.position;
188 | // remove all bounding box 3d object after disposing before every check
189 | for (let i = 0, _length = boxGroup.children.length; i < _length; i++) {
190 | boxGroup.children[i].material.dispose();
191 | boxGroup.children[i].geometry.dispose();
192 | }
193 |
194 | boxGroup.remove(...boxGroup.children);
195 |
196 | function traverseTree(node = qt) {
197 | if (node == null) {
198 | return null;
199 | }
200 | boxGroup.add(node.box.mesh);
201 | if (!node.isDivided) {
202 | return [...node.points, ...node.buffer];
203 | }
204 | let myDistanceFromCamera = cameraPosition.distanceTo(
205 | new THREE.Vector3(node.box.x, node.box.y, node.box.z)
206 | );
207 | if (myDistanceFromCamera > threshold) {
208 | return [...node.points, ...node.buffer];
209 | }
210 | let children = [
211 | node.minNE,
212 | node.minNW,
213 | node.minSW,
214 | node.minSE,
215 | node.maxNE,
216 | node.maxNW,
217 | node.maxSW,
218 | node.maxSE,
219 | ];
220 | let results = [...node.points, ...node.buffer];
221 | for (let i = 0, _length = children.length; i < _length; i++) {
222 | let points = traverseTree(children[i]);
223 | results.push(...points);
224 | }
225 | return results;
226 | }
227 | return traverseTree();
228 | }
229 | function onWindowResize() {
230 | camera.aspect = window.innerWidth / window.innerHeight;
231 | camera.updateProjectionMatrix();
232 | renderer.setSize(window.innerWidth, window.innerHeight);
233 | }
234 |
235 | function sleep(ms) {
236 | return new Promise((resolve) => setTimeout(resolve, ms));
237 | }
238 |
239 | let keyCountMap;
240 |
241 | const createBuffer = (positions, colors) => {
242 | let size = positions.length;
243 | // console.log("size is ", size);
244 | let positionBuffer = device.createBuffer({
245 | label: `${size}`,
246 | size: size * 4,
247 | usage: GPUBufferUsage.VERTEX,
248 | mappedAtCreation: true,
249 | });
250 |
251 | let positionMappedArray = new Float32Array(positionBuffer.getMappedRange());
252 | positionMappedArray.set(positions);
253 | positionBuffer.unmap();
254 |
255 | let colorBuffer = device.createBuffer({
256 | label: `${size}`,
257 | size: size * 4,
258 | usage: GPUBufferUsage.VERTEX,
259 | mappedAtCreation: true,
260 | });
261 |
262 | let colorMappedArray = new Float32Array(colorBuffer.getMappedRange());
263 | colorMappedArray.set(colors);
264 | colorBuffer.unmap();
265 | return [positionBuffer, colorBuffer];
266 | };
267 |
268 | const syncThread = async () => {
269 | await Promise.all(promises).then(async (response) => {
270 | for (let i = 0, _length = response.length; i < _length; i++) {
271 | let data = response[i];
272 | let fileName = data[2];
273 |
274 | let data_json = {
275 | position: data[0],
276 | color: data[1],
277 | maxIntensity: data[3]
278 | };
279 |
280 | let data_json_stringify = JSON.stringify(data_json);
281 | await write(`${source_file_name}-${fileName}`, data_json_stringify);
282 | state_meta[fileName] = {
283 | count: 1,
284 | date: new Date(),
285 | };
286 | // console.log("data sent from worker is", data[0].length);
287 | let [positionBuffer, colorBuffer] = createBuffer(data[0], data[1]);
288 |
289 | bufferMap[data[2]] = {
290 | position: positionBuffer,
291 | color: colorBuffer,
292 | maxIntensity: data[3]
293 | };
294 | }
295 | // console.log(bufferMap);
296 | // console.log("one chunk finish");
297 | });
298 | };
299 |
300 | const syncThread_Prefetch = async () => {
301 | await Promise.all(promises).then(async (response) => {
302 | for (let i = 0, _length = response.length; i < _length; i++) {
303 | let data = response[i];
304 | let fileName = data[2];
305 |
306 | let data_json = {
307 | position: data[0],
308 | color: data[1],
309 | maxIntensity: data[3]
310 | };
311 | let data_json_stringify = JSON.stringify(data_json);
312 | await write(`${source_file_name}-${fileName}`, data_json_stringify);
313 | state_meta[fileName] = {
314 | count: 1,
315 | date: new Date(),
316 | };
317 | cache.set(fileName, data_json_stringify);
318 | }
319 | });
320 | };
321 |
322 |
323 |
324 | async function filterkeyCountMap_Prefetch(keyMap) {
325 |
326 |
327 | let afterCheckingCache = [];
328 |
329 | for (let i = 0; i < keyMap.length; i += 2) {
330 | let cachedResult = cache.get(keyMap[i]);
331 | if (!cachedResult) {
332 | afterCheckingCache.push(keyMap[i], keyMap[i + 1]);
333 | }
334 | }
335 |
336 | let filteredElements = [];
337 | for (let i = 0; i < afterCheckingCache.length; i += 2) {
338 | let [Exist, data] = await doesExist(
339 | `${source_file_name}-${afterCheckingCache[i]}`
340 | );
341 | if (Exist) {
342 | cache.set(afterCheckingCache[i], JSON.stringify(data));
343 | pers_cache = get_inCache(pers_cache, afterCheckingCache[i]);
344 | } else {
345 | filteredElements.push(afterCheckingCache[i], afterCheckingCache[i + 1]);
346 | pers_cache = put_inCache(pers_cache, afterCheckingCache[i], {
347 | count: 1,
348 | date: Date.now(),
349 | });
350 | }
351 | }
352 | await throttled_Update_Pers_Cache(mapIntoJSON(cache));
353 | return filteredElements;
354 | }
355 |
356 | async function filterkeyCountMap(keyMap) {
357 |
358 | // computing node fetch counts
359 | let nodeNotFoundInBuffer = 0;
360 | let nodeFoundInBuffer = 0;
361 | let nodeFoundInLRU = 0;
362 | let nodeFoundInPersistent = 0;
363 | let nodeToFetch = 0;
364 |
365 | let newKeyMap = [];
366 | let newBufferMap = {};
367 | for (const key in toDeleteMap) {
368 | toDeleteMap[key].position.destroy();
369 | toDeleteMap[key].color.destroy();
370 | delete toDeleteMap[key];
371 | }
372 |
373 | let existingBuffers = Object.keys(bufferMap);
374 | let toDeleteArray = existingBuffers.reduce((acc, val) => {
375 | acc[val] = true;
376 | return acc;
377 | }, {});
378 |
379 | const startTime1 = performance.now();
380 | for (let i = 0; i < keyMap.length; i += 2) {
381 | if (!(keyMap[i] in bufferMap)) {
382 | newKeyMap.push(keyMap[i], keyMap[i + 1]);
383 | nodeNotFoundInBuffer++
384 | } else {
385 | // console.log(`found ${keyMap[i]} in existing buffer`);
386 | nodeFoundInBuffer++;
387 | let maxIntensity = bufferMap[keyMap[i]].maxIntensity;
388 | newBufferMap[keyMap[i]] = {
389 | position: bufferMap[keyMap[i]].position,
390 | color: bufferMap[keyMap[i]].color,
391 | maxIntensity: maxIntensity
392 | };
393 | if (maxIntensity > global_max_intensity) {
394 | global_max_intensity = maxIntensity;
395 | }
396 | pers_cache = get_inCache(pers_cache, keyMap[i]);
397 | delete toDeleteArray[keyMap[i]];
398 | }
399 | }
400 | const endTime1 = performance.now();
401 | // console.log(`Time taken: ${endTime1 - startTime1}ms`);
402 |
403 | // --------------------- these are new ones
404 | // are they in non-persistence cache?
405 | const startTime2 = performance.now();
406 |
407 | let afterCheckingCache = [];
408 | for (let i = 0; i < newKeyMap.length; i += 2) {
409 | let cachedResult = cache.get(newKeyMap[i]);
410 | if (cachedResult) {
411 | nodeFoundInLRU++
412 | // console.log(`found ${newKeyMap[i]} in cache`);
413 | cachedResult = JSON.parse(cachedResult);
414 | pers_cache = get_inCache(pers_cache, newKeyMap[i]);
415 | let [positionBuffer, colorBuffer] = createBuffer(
416 | cachedResult.position,
417 | cachedResult.color
418 | );
419 | const maxIntensity = cachedResult.maxIntensity
420 | newBufferMap[newKeyMap[i]] = {
421 | position: positionBuffer,
422 | color: colorBuffer,
423 | maxIntensity: maxIntensity
424 | };
425 | if(maxIntensity > global_max_intensity){
426 | global_max_intensity = maxIntensity;
427 | }
428 | } else {
429 | afterCheckingCache.push(newKeyMap[i], newKeyMap[i + 1]);
430 | }
431 | }
432 | const endTime2 = performance.now();
433 | // console.log(`Time taken: ${endTime2 - startTime2}ms`);
434 |
435 | const startTime3 = performance.now();
436 | let filteredElements = [];
437 | for (let i = 0; i < afterCheckingCache.length; i += 2) {
438 | let [Exist, data] = await doesExist(
439 | `${source_file_name}-${afterCheckingCache[i]}`
440 | );
441 | if (Exist) {
442 | console.log("found in POFS")
443 | // console.log(`found ${afterCheckingCache[i]} in POFS`);
444 | // let data = await read(afterCheckingCache[i]);
445 | nodeFoundInPersistent++;
446 | let [positionBuffer, colorBuffer] = createBuffer(
447 | data.position,
448 | data.color
449 | );
450 | newBufferMap[afterCheckingCache[i]] = {
451 | position: positionBuffer,
452 | color: colorBuffer,
453 | maxIntensity: data.maxIntensity
454 | };
455 | if(data.maxIntensity > global_max_intensity){
456 | global_max_intensity = data.maxIntensity;
457 | }
458 | cache.set(afterCheckingCache[i], JSON.stringify(data));
459 | pers_cache = get_inCache(pers_cache, afterCheckingCache[i]);
460 | } else {
461 | filteredElements.push(afterCheckingCache[i], afterCheckingCache[i + 1]);
462 | pers_cache = put_inCache(pers_cache, afterCheckingCache[i], {
463 | count: 1,
464 | date: Date.now(),
465 | });
466 | nodeToFetch++
467 | }
468 | }
469 | const endTime3 = performance.now();
470 | // console.log(`Time taken: ${endTime3 - startTime3}ms`);
471 |
472 | await throttled_Update_Pers_Cache(mapIntoJSON(cache));
473 | //-------------------------------------------------------------------------
474 |
475 | for (let key in toDeleteArray) {
476 | toDeleteMap[key] = {
477 | position: bufferMap[key].position,
478 | color: bufferMap[key].position,
479 | };
480 | }
481 | bufferMap = newBufferMap;
482 | // console.log(`to fetch this ${filteredElements}`);
483 |
484 | updateHtmlUI(nodeNotFoundInBuffer, nodeFoundInBuffer, nodeFoundInLRU, nodeFoundInPersistent, nodeToFetch)
485 | return filteredElements;
486 | //filter and delete unwanted bufferMap
487 | }
488 |
489 | let direction = [
490 | [0, 0, 0],
491 | [0, 0, 1],
492 | [0, 1, 0],
493 | [0, 1, 1],
494 | [1, 0, 0],
495 | [1, 0, 1],
496 | [1, 1, 0],
497 | [1, 1, 1],
498 | ];
499 |
500 | function findSiblings(keyCountMap) {
501 | let siblings = {};
502 | let nodeKeyCount = [];
503 | for (let i = 0; i < keyCountMap.length; i += 2) {
504 | let myNode = keyCountMap[i];
505 | myNode = myNode.split("-").map(Number);
506 | if (myNode.every((c) => c == 0)) {
507 | continue;
508 | }
509 | let myParent = [];
510 | myParent[0] = myNode[0] - 1;
511 | for (let i = 1; i <= 3; i++) {
512 | myParent[i] = Math.floor(myNode[i] / 2);
513 | }
514 |
515 | // for (let k = 0; k < 8; k++) {
516 | // let sibling = [
517 | // myNode[0],
518 | // 2 * myParent[1] + direction[k][0],
519 | // 2 * myParent[2] + direction[k][1],
520 | // 2 * myParent[3] + direction[k][2],
521 | // ];
522 | // sibling = sibling.join("-");
523 | // if (sibling in siblings || sibling in bufferMap) {
524 | // continue;
525 | // } else if (sibling in nodePages && nodePages[sibling].pointCount > 0) {
526 | // nodeKeyCount.push(sibling, nodePages[sibling].pointCount);
527 | // siblings[sibling] = true;
528 | // }
529 | // }
530 | }
531 | return nodeKeyCount;
532 | }
533 |
534 | async function retrivePoints(projectionViewMatrix, controllerSignal = null) {
535 | const startTime4 = performance.now();
536 | let [keyCountMap, nodeToPrefetch] = traverseTreeWrapper(
537 | nodePages,
538 | [0, 0, 0, 0],
539 | center_x,
540 | center_y,
541 | center_z,
542 | [0.5 * widthx, 0.5 * widthy, 0.5 * widthz],
543 | scaleFactor,
544 | controls,
545 | projectionViewMatrix
546 | );
547 | const endTime4 = performance.now();
548 | // console.log(`Time taken to traverse tree: ${endTime4 - startTime4}ms`);
549 |
550 | keyCountMap = await filterkeyCountMap(keyCountMap);
551 | prefetch_keyCountMap = await filterkeyCountMap_Prefetch(nodeToPrefetch);
552 |
553 | clock.getDelta();
554 | let totalNodes = keyCountMap.length / 2;
555 | let doneCount = 0;
556 |
557 | for (let m = 0; m < keyCountMap.length; ) {
558 | let remaining = totalNodes - doneCount;
559 | let numbWorker = Math.min(MAX_WORKERS, remaining);
560 | for (let i = 0; i < numbWorker; i++) {
561 | // console.log("i am entering first time");
562 | promises.push(createWorker(keyCountMap[m], keyCountMap[m + 1]));
563 | doneCount++;
564 | m += 2;
565 | if (doneCount % MAX_WORKERS == 0 || doneCount == totalNodes) {
566 | await syncThread();
567 | // console.log(controllerSignal)
568 | if (controllerSignal && controllerSignal.aborted) {
569 | // console.log("i am aborted now from fetching thread");
570 | return;
571 | }
572 | }
573 | }
574 | }
575 |
576 | // ------------------------------------------------------------------
577 | totalNodes = prefetch_keyCountMap.length / 2;
578 | doneCount = 0;
579 | promises = [];
580 |
581 | // for (let m = 0; m < prefetch_keyCountMap.length; ) {
582 | // let remaining = totalNodes - doneCount;
583 | // let numbWorker = Math.min(MAX_WORKERS, remaining);
584 | // for (let i = 0; i < numbWorker; i++) {
585 | // // console.log("i am entering first time");
586 | // promises.push(
587 | // createWorker(prefetch_keyCountMap[m], prefetch_keyCountMap[m + 1])
588 | // );
589 | // doneCount++;
590 | // m += 2;
591 | // if (doneCount % MAX_WORKERS == 0 || doneCount == totalNodes) {
592 | // await syncThread_Prefetch();
593 | // if (controllerSignal && controllerSignal.aborted) {
594 | // console.log("i am aborted now from prefetcher");
595 | // return;
596 | // }
597 | // }
598 | // }
599 | // }
600 |
601 | // console.log("it finished at", clock.getDelta());
602 |
603 | // find sibling
604 | // let siblings = findSiblings(keyCountMap);
605 | // console.log(siblings);
606 | }
607 |
608 | async function createCameraProj() {
609 | camera = new THREE.PerspectiveCamera(
610 | 50,
611 | canvas.width / canvas.height,
612 | 0.1,
613 | 4000
614 | );
615 | camera.up.set(0, 0, 1 );
616 | camera.position.set(0, 1000, 1000)
617 | // camera.position.set( 1446.6944661802368, -1721.55051389683 , 444.2788);
618 | // camera.rotation.set(0.6403667747403355, 0.34, 0.42);
619 | camera.updateProjectionMatrix();
620 |
621 | controls = new OrbitControls(camera, canvas);
622 | // controls.target.set(900, -665, -710);
623 |
624 | controls.enableDamping = true;
625 | controls.dampingFactor = 0.5;
626 | // controls.minAzimuthAngle = 0;
627 | // controls.maxAzimuthAngle = 0.25 * Math.PI;
628 | // controls.minPolarAngle = 0;
629 | // controls.maxPolarAngle = Math.PI;
630 | // controls.rotateSpeed = 2;
631 | controls.zoomSpeed = 1;
632 | controls.panSpeed = 2;
633 | // controls.autoRotate = true;
634 | // controls.autoRotateSpeed = 0.5;
635 | controls.update();
636 |
637 | // camera = new ArcballCamera([100, -100, 100], [0, 0, 0], [0, 1, 0], 300, [
638 | // window.innerWidth,
639 | // window.innerHeight,
640 | // ]);
641 |
642 | proj = mat4.perspective(
643 | mat4.create(),
644 | (50 * Math.PI) / 180.0,
645 | canvas.width / canvas.height,
646 | 0.1,
647 | 8000
648 | );
649 | }
650 |
651 | async function loadCOPC() {
652 | clock.getDelta();
653 | // https://viewer.copc.io/?copc=https://s3.amazonaws.com/data.entwine.io/millsite.copc.laz
654 | // https://github.com/PDAL/data/blob/master/autzen/autzen-classified.copc.laz
655 | // let filename = "https://s3.amazonaws.com/data.entwine.io/millsite.copc.laz";
656 | const filename = process.env.filename;
657 | const copc = await Copc.create(filename);
658 | console.log("file is", copc)
659 | scaleFactor = copc.header.scale;
660 | scaleFactor = [1.0, 1.0, 1.0]
661 | copcString = JSON.stringify(copc);
662 | // scale = copc.header.scale[0];
663 | [x_min, y_min, z_min, x_max, y_max, z_max] = [...copc.header.min, ...copc.header.max];
664 | x_min *= scaleFactor[0];
665 | x_max *= scaleFactor[0];
666 | y_min *= scaleFactor[1];
667 | y_max *= scaleFactor[1];
668 | z_min *= scaleFactor[2];
669 | z_max *= scaleFactor[2];
670 | widthx = Math.abs(x_max - x_min);
671 | widthy = Math.abs(y_max - y_min);
672 | widthz = Math.abs(z_max - z_min);
673 | // console.log(widthx, widthy, widthz);
674 | // console.log(z_max, z_min, widthz);
675 | // for new COPC file widthz is 50, z_min is fine but width is wrong
676 |
677 | // console.log("minimum z is", z_min, "z-width is", widthz, copc);
678 |
679 | params = [widthx, widthy, widthz, x_min, y_min, z_min];
680 | center_x = ((x_min + x_max) / 2 - x_min - 0.5 * widthx) ;
681 | center_y = ((y_min + y_max) / 2 - y_min - 0.5 * widthy);
682 | center_z = ((z_min + z_max) / 2 - z_min - 0.5 * widthz);
683 | // center is moved to origin and we dont need to do this but for sake for checking the cordinate system, i am doing this
684 | const { nodes: nodePages1, pages: pages } = await Copc.loadHierarchyPage(
685 | filename,
686 | copc.info.rootHierarchyPage
687 | );
688 | nodePages = nodePages1;
689 | nodePagesString = JSON.stringify(nodePages);
690 | pagesString = JSON.stringify(pages);
691 | }
692 |
693 | (async () => {
694 | // await clear();
695 | // const start6 = performance.now();
696 | await create_P_Meta_Cache();
697 | // const end6 = performance.now();
698 | // console.log(`Time taken to create meta cache: ${end6 - start6}ms`);
699 |
700 | // const start7 = performance.now();
701 | pers_cache = await p_cache();
702 | // const end7 = performance.now();
703 | // console.log(`Time taken to make LRU meta cache: ${end7 - start7}ms`);
704 | // console.log("cache created successfully");
705 |
706 | // const start8 = performance.now();
707 | await createCameraProj();
708 | // const end8 = performance.now();
709 | // console.log(`Time taken to create camera: ${end8 - start8}ms`);
710 | // console.log("file reading start");
711 |
712 | // const start9 = performance.now();
713 | await loadCOPC();
714 | // const end9 = performance.now();
715 | // console.log(`Time taken to load COPC file: ${end9 - start9}ms`);
716 | // console.log("initialize the uniform buffers");
717 |
718 | // const start10 = performance.now();
719 | let projViewMatrix = await stages(camera, proj, params);
720 | // const end10 = performance.now();
721 | // console.log(`Time taken to do initial rendering setup: ${end10 - start10}ms`);
722 | // console.log("data loading start");
723 |
724 | // const start11 = performance.now();
725 | await retrivePoints(projViewMatrix);
726 | // const end11 = performance.now();
727 | // console.log(
728 | // `Time taken to retrive needed nodes from tree: ${end11 - start11}ms`
729 | // );
730 | // console.log("data loaded");
731 |
732 | // const start12 = performance.now();
733 | await renderWrapper();
734 | // const end12 = performance.now();
735 | // console.log(`Time taken to render: ${end12 - start12}ms`);
736 | // console.log("render done");
737 | })();
738 |
739 | // ------------------------------------------------------------------------------------
740 | // -------------make iternary of camera ----------------------------------------------
741 |
742 | // render by WebGPU
743 | // console.log(colors);
744 | // renderStages(positions, colors);
745 | // ----------------------------------------------------------------------------
746 | // geometry.setAttribute(
747 | // "position",
748 | // new THREE.Float32BufferAttribute(positions, 3)
749 | // );
750 | // geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
751 | // var material = new THREE.PointsMaterial({ size: 2, vertexColors: true });
752 | // let p = new THREE.Points(geometry, material);
753 | // scene.add(p);
754 | // -----------------------------------------------------------------------------
755 |
756 | export {
757 | scene_width,
758 | scene_height,
759 | scene_depth,
760 | loadCOPC,
761 | bufferMap,
762 | retrivePoints,
763 | toDeleteMap,
764 | wait,
765 | controls,
766 | global_max_intensity,
767 | };
768 |
--------------------------------------------------------------------------------