From a9ac7fe4c392434c0d556bba34f2d8a83e441079 Mon Sep 17 00:00:00 2001 From: BOUILLAGUET Quentin Date: Fri, 30 Jan 2026 14:44:18 +0100 Subject: [PATCH] feat: priority updates --- package-lock.json | 96 ++++----- packages/Debug/src/PointCloudDebug.js | 170 +++++---------- packages/Main/package.json | 3 +- packages/Main/src/Core/LasNodeBase.ts | 2 + packages/Main/src/Core/PointCloudNode.ts | 5 + packages/Main/src/Core/PotreeNodeBase.ts | 1 + packages/Main/src/Layer/PointCloudLayer.ts | 235 ++++++++++++--------- packages/Main/src/Renderer/OBB.ts | 75 ++++++- 8 files changed, 312 insertions(+), 275 deletions(-) diff --git a/package-lock.json b/package-lock.json index 507032499d..febc553394 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,8 +72,7 @@ "version": "0.9.29", "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.29.tgz", "integrity": "sha512-G90x0VW+9nW4dFajtjCoT+NM0scAfH9Mb08IcjgFHYbfiL/lU04dTF9JuVOi3/OH+DJCQdcIseSXkdCB9Ky6JA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@ampproject/remapping": { "version": "2.3.0", @@ -94,7 +93,6 @@ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", "license": "MIT", - "peer": true, "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-color-parser": "^3.1.0", @@ -108,7 +106,6 @@ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz", "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==", "license": "MIT", - "peer": true, "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", @@ -121,8 +118,7 @@ "version": "2.3.9", "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@babel/cli": { "version": "7.26.4", @@ -185,6 +181,7 @@ "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -1862,7 +1859,6 @@ } ], "license": "MIT-0", - "peer": true, "engines": { "node": ">=18" } @@ -1882,7 +1878,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1906,7 +1901,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" @@ -1957,7 +1951,6 @@ } ], "license": "MIT-0", - "peer": true, "engines": { "node": ">=18" } @@ -2708,6 +2701,7 @@ "version": "8.17.10", "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.17.10.tgz", "integrity": "sha512-S6bqa4DqUooEkInYv/W+Jklv2zjSYCXAhm6qKpAQyOXhTEt5gBXnA7W6aoJ0bjmp9pAeaSj/AZUoz1HCSof/uA==", + "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/debounce": "^1.2.1", @@ -2758,6 +2752,7 @@ "resolved": "https://registry.npmjs.org/@react-three/postprocessing/-/postprocessing-2.19.1.tgz", "integrity": "sha512-7P25LOSToH/I6b3UipNK17IIFlX4FDUmWcaomfwu82+CzhXTOz8Fcc1ZXEZ7vFA/5Fr/2peNlXgXZJvoa+aCdA==", "license": "MIT", + "peer": true, "dependencies": { "buffer": "^6.0.3", "maath": "^0.6.0", @@ -3202,6 +3197,7 @@ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -3273,6 +3269,7 @@ "version": "18.3.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -3347,6 +3344,7 @@ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.182.0.tgz", "integrity": "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==", "license": "MIT", + "peer": true, "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", @@ -3442,6 +3440,7 @@ "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.26.0", "@typescript-eslint/types": "8.26.0", @@ -3977,6 +3976,7 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4634,7 +4634,6 @@ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", "license": "MIT", - "peer": true, "dependencies": { "require-from-string": "^2.0.2" } @@ -4792,6 +4791,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -5807,7 +5807,6 @@ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "license": "MIT", - "peer": true, "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" @@ -5821,7 +5820,6 @@ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.5.tgz", "integrity": "sha512-GlsEptulso7Jg0VaOZ8BXQi3AkYM5BOJKEO/rjMidSCq70FkIC5y0eawrCXeYzxgt3OCf4Ls+eoxN+/05vN0Ag==", "license": "MIT", - "peer": true, "dependencies": { "@asamuzakjp/css-color": "^4.1.1", "@csstools/css-syntax-patches-for-csstree": "^1.0.21", @@ -5869,7 +5867,6 @@ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", "license": "MIT", - "peer": true, "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^15.0.0" @@ -5883,7 +5880,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "license": "MIT", - "peer": true, "dependencies": { "punycode": "^2.3.1" }, @@ -5896,7 +5892,6 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=20" } @@ -5906,7 +5901,6 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", "license": "MIT", - "peer": true, "dependencies": { "tr46": "^6.0.0", "webidl-conversions": "^8.0.0" @@ -5991,8 +5985,7 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/deep-is": { "version": "0.1.4", @@ -6128,7 +6121,8 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1413902.tgz", "integrity": "sha512-yRtvFD8Oyk7C9Os3GmnFZLu53yAfsnyw1s+mLmHHUK0GQEc9zthHWvS1r67Zqzm5t7v56PILHIVZ7kmFMaL2yQ==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/diff": { "version": "5.2.0", @@ -6529,6 +6523,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -6673,6 +6668,7 @@ "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -8046,7 +8042,6 @@ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "license": "MIT", - "peer": true, "dependencies": { "whatwg-encoding": "^3.1.1" }, @@ -8206,7 +8201,6 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8728,8 +8722,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-regex": { "version": "1.1.4", @@ -9159,7 +9152,6 @@ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.3.0.tgz", "integrity": "sha512-GtldT42B8+jefDUC4yUKAvsaOrH7PDHmZxZXNgF2xMmymjUbRYJvpAybZAKEmXDGTM0mCsz8duOa4vTm5AY2Kg==", "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", @@ -9199,7 +9191,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "license": "MIT", - "peer": true, "dependencies": { "punycode": "^2.3.1" }, @@ -9212,7 +9203,6 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=20" } @@ -9222,7 +9212,6 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", "license": "MIT", - "peer": true, "dependencies": { "tr46": "^6.0.0", "webidl-conversions": "^8.0.0" @@ -9563,6 +9552,7 @@ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -9613,8 +9603,7 @@ "version": "2.12.2", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", - "license": "CC0-1.0", - "peer": true + "license": "CC0-1.0" }, "node_modules/mdurl": { "version": "2.0.0", @@ -10498,7 +10487,6 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", "license": "MIT", - "peer": true, "dependencies": { "entities": "^6.0.0" }, @@ -10511,7 +10499,6 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.12" }, @@ -10878,6 +10865,7 @@ "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.38.1.tgz", "integrity": "sha512-CrdQfziZqyJ0TFvncYL8/RgOWjxCxQqEICIn7TLJv5cqhqg4xy2EZG979u09YKJSh/RQmCgEVY7j+iMLsPIR4w==", "license": "Zlib", + "peer": true, "peerDependencies": { "three": ">= 0.157.0 < 0.183.0" } @@ -10920,6 +10908,7 @@ "resolved": "https://registry.npmjs.org/proj4/-/proj4-2.19.10.tgz", "integrity": "sha512-uL6/C6kA8+ncJAEDmUeV8PmNJcTlRLDZZa4/87CzRpb8My4p+Ame4LhC4G3H/77z2icVqcu3nNL9h5buSdnY+g==", "license": "MIT", + "peer": true, "dependencies": { "mgrs": "1.0.0", "wkt-parser": "^1.5.1" @@ -11229,6 +11218,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -11860,7 +11850,6 @@ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "license": "ISC", - "peer": true, "dependencies": { "xmlchars": "^2.2.0" }, @@ -11902,6 +11891,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -12440,7 +12430,6 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12802,8 +12791,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.2.1", @@ -13113,7 +13101,8 @@ "version": "0.182.0", "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz", "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/three-stdlib": { "version": "2.35.14", @@ -13222,14 +13211,14 @@ "node_modules/tinyqueue": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", - "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==" + "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", + "license": "ISC" }, "node_modules/tldts": { "version": "7.0.19", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.19.tgz", "integrity": "sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==", "license": "MIT", - "peer": true, "dependencies": { "tldts-core": "^7.0.19" }, @@ -13241,8 +13230,7 @@ "version": "7.0.19", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -13272,7 +13260,6 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "tldts": "^7.0.5" }, @@ -13388,7 +13375,8 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/type-check": { "version": "0.4.0", @@ -13529,6 +13517,7 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13783,7 +13772,6 @@ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "license": "MIT", - "peer": true, "dependencies": { "xml-name-validator": "^5.0.0" }, @@ -13839,6 +13827,7 @@ "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -13886,6 +13875,7 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -14123,7 +14113,6 @@ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "license": "MIT", - "peer": true, "dependencies": { "iconv-lite": "0.6.3" }, @@ -14136,7 +14125,6 @@ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -14320,7 +14308,6 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18" } @@ -14334,8 +14321,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/xmlcreate": { "version": "2.0.4", @@ -14524,7 +14510,8 @@ "pbf": "^4.0.1", "postprocessing": "^6.37.3", "shpjs": "^6.1.0", - "threads": "^1.7.0" + "threads": "^1.7.0", + "tinyqueue": "^3.0.0" }, "devDependencies": { "chalk": "^5.4.1", @@ -14643,7 +14630,6 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.32.3.tgz", "integrity": "sha512-cMi5ZrLG7UtbL7LTK6hq9w/EZIRk4Mf1Z5qHoI+qBh7/WkYkFXQ7gOto2yfUvPzF5ERMAhaXS5eTQ2SAnHjLzA==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "*" } @@ -14659,7 +14645,6 @@ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", "license": "MIT", - "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.9" }, @@ -14672,7 +14657,6 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "*" } @@ -14710,7 +14694,6 @@ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz", "integrity": "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.25.0" }, @@ -14726,7 +14709,6 @@ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", "license": "MIT", - "peer": true, "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" @@ -14741,8 +14723,7 @@ "version": "0.25.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "packages/Main/node_modules/three-stdlib": { "version": "2.36.0", @@ -14766,7 +14747,6 @@ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz", "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==", "license": "MIT", - "peer": true, "engines": { "node": ">=12.20.0" }, diff --git a/packages/Debug/src/PointCloudDebug.js b/packages/Debug/src/PointCloudDebug.js index 44004fbac8..9adb2db357 100644 --- a/packages/Debug/src/PointCloudDebug.js +++ b/packages/Debug/src/PointCloudDebug.js @@ -1,4 +1,4 @@ -import { View, PNTS_MODE, PNTS_SHAPE, PNTS_SIZE_MODE, GeometryLayer } from 'itowns'; +import { PNTS_MODE, PNTS_SHAPE, PNTS_SIZE_MODE } from 'itowns'; import * as THREE from 'three'; import OBBHelper from './OBBHelper'; @@ -49,119 +49,62 @@ function setupControllerVisibily(gui, displayMode, sizeMode) { } } -/** - * Generate the position array of the bbox corner form the bbox - * Adapted from THREE.BoxHelper.js - * https://github.com/mrdoob/three.js/blob/master/src/helpers/BoxHelper.js - * - * @param {THREE.box3} bbox - Box3 of the node - * @returns {array} - */ -function getCornerPosition(bbox) { - const array = new Float32Array(8 * 3); +const red = new THREE.Color(0xff0000); - const min = bbox.min; - const max = bbox.max; - - /* - 5____4 - 1/___0/| - | 6__|_7 - 2/___3/ - - 0: max.x, max.y, max.z - 1: min.x, max.y, max.z - 2: min.x, min.y, max.z - 3: max.x, min.y, max.z - 4: max.x, max.y, min.z - 5: min.x, max.y, min.z - 6: min.x, min.y, min.z - 7: max.x, min.y, min.z - */ - array[0] = max.x; array[1] = max.y; array[2] = max.z; - array[3] = min.x; array[4] = max.y; array[5] = max.z; - array[6] = min.x; array[7] = min.y; array[8] = max.z; - array[9] = max.x; array[10] = min.y; array[11] = max.z; - array[12] = max.x; array[13] = max.y; array[14] = min.z; - array[15] = min.x; array[16] = max.y; array[17] = min.z; - array[18] = min.x; array[19] = min.y; array[20] = min.z; - array[21] = max.x; array[22] = min.y; array[23] = min.z; - return array; +function isVisibleLeaf(node) { + if (!node.visible) { return false; } + return !node.children.some(c => c.visible); } -const red = new THREE.Color(0xff0000); -function debugIdUpdate(context, layer, node) { - // filtering helper attached to node with the current debug layer - if (!node.link) { - node.link = {}; +function setupOBBDebugPlugin(layer, boxGroup, debugState) { + function createOBBHelper(node) { + if (node._obbHelper) { return; } + const helper = new OBBHelper(node.voxelOBB, null, red); + helper._debugNode = node; + node._obbHelper = helper; + boxGroup.add(helper); + helper.updateMatrixWorld(true); + + if (!debugState.displayParentBounds) { + helper.visible = isVisibleLeaf(node); + if (node.parent?._obbHelper) { + node.parent._obbHelper.visible = isVisibleLeaf(node.parent); + } + } } - let helper = node.link[layer.id]; - if (node.visible) { - if (!helper) { - helper = new THREE.Group(); - // node obbes - const obbHelper = new OBBHelper(node.clampOBB, node.voxelKey, red); - obbHelper.layer = layer; - helper.add(obbHelper); + function removeOBBHelper(node) { + if (!node._obbHelper) { return; } + boxGroup.remove(node._obbHelper); + node._obbHelper = undefined; - // point data boxes (in local ref) - const tightboxHelper = new THREE.BoxHelper(undefined, 0x0000ff); - if (node.obj) { - tightboxHelper.geometry.attributes.position.array = getCornerPosition(node.obj.geometry.boundingBox); - tightboxHelper.applyMatrix4(node.obj.matrixWorld); - node.obj.tightboxHelper = tightboxHelper; - helper.add(tightboxHelper); - tightboxHelper.updateMatrixWorld(true); - } else if (node.promise) { - // TODO rethink architecture of node.obj/node.promise ? - node.promise.then(() => { - if (node.obj) { - tightboxHelper.geometry.attributes.position.array = getCornerPosition(node.obj.geometry.boundingBox); - tightboxHelper.applyMatrix4(node.obj.matrixWorld); - node.obj.tightboxHelper = tightboxHelper; - helper.add(tightboxHelper); - tightboxHelper.updateMatrixWorld(true); - } - }); - } + if (!debugState.displayParentBounds && node.parent?.visible && node.parent?._obbHelper) { + node.parent._obbHelper.visible = isVisibleLeaf(node.parent); + } + } - node.link[layer.id] = helper; + layer.addEventListener('tile-visibility-change', (event) => { + if (event.visible) { + createOBBHelper(event.tile); } else { - node.link[layer.id].visible = true; + removeOBBHelper(event.tile); } + }); - layer.object3d.add(helper); + layer.addEventListener('dispose-model', (event) => { + removeOBBHelper(event.tile); + }); +} - if (node.children && node.children.length) { - if (node.sse >= 1) { - return node.children; +function refreshHelperVisibility(boxGroup, debugState) { + for (const helper of boxGroup.children) { + if (helper._debugNode) { + if (debugState.displayParentBounds) { + helper.visible = true; } else { - for (const child of node.children) { - if (child.link?.[layer.id]) { - child.link[layer.id].visible = false; - } - } + helper.visible = isVisibleLeaf(helper._debugNode); } } - } else if (helper) { - layer.object3d.remove(helper); - } -} - -class DebugLayer extends GeometryLayer { - constructor(id, options = {}) { - super(id, options.object3d || new THREE.Group(), options); - this.update = debugIdUpdate; - this.isDebugLayer = true; - this.layer = options.layer; - } - - preUpdate(context, sources) { - if (sources.has(this.parent)) { - this.object3d.clear(); - } - return this.layer.preUpdate(context, sources); } } @@ -303,22 +246,23 @@ export default { // UI const debugUI = layer.debugUI.addFolder('Debug').close(); - const obb_layer_id = `${layer.id}_obb_debug`; - const obbLayer = new DebugLayer(obb_layer_id, { - visible: false, - cacheLifeTime: Infinity, - source: false, - layer, - }); + // OBB debug plugin: event-driven, shows voxelOBB helpers + const boxGroup = new THREE.Group(); + boxGroup.name = 'debug-obb-group'; + boxGroup.visible = false; + layer.object3d.add(boxGroup); - if (view.getLayerById(obbLayer.id)) { - view.removeLayer(obbLayer.id); - } - View.prototype.addLayer.call(view, obbLayer); + const debugState = { displayParentBounds: false }; + setupOBBDebugPlugin(layer, boxGroup, debugState); - debugUI.add(obbLayer, 'visible').name('Display Bounding Boxes') + debugUI.add(boxGroup, 'visible').name('Display Bounding Boxes') + .onChange(() => { + view.notifyChange(layer); + }); + debugUI.add(debugState, 'displayParentBounds').name('Display Parent Bounds') .onChange(() => { - view.notifyChange(obbLayer); + refreshHelperVisibility(boxGroup, debugState); + view.notifyChange(layer); }); debugUI.add(layer, 'dbgStickyNode').name('Sticky node name').onChange(update); diff --git a/packages/Main/package.json b/packages/Main/package.json index da8d62ea39..6765a97cd9 100644 --- a/packages/Main/package.json +++ b/packages/Main/package.json @@ -67,7 +67,8 @@ "pbf": "^4.0.1", "postprocessing": "^6.37.3", "shpjs": "^6.1.0", - "threads": "^1.7.0" + "threads": "^1.7.0", + "tinyqueue": "^3.0.0" }, "peerDependencies": { "proj4": "^2.19.10", diff --git a/packages/Main/src/Core/LasNodeBase.ts b/packages/Main/src/Core/LasNodeBase.ts index b22276c322..529995125f 100644 --- a/packages/Main/src/Core/LasNodeBase.ts +++ b/packages/Main/src/Core/LasNodeBase.ts @@ -99,6 +99,8 @@ abstract class LasNodeBase extends PointCloudNode { childNode.clampOBB.copy(childNode.voxelOBB).clampZ(this.source.zmin, this.source.zmax); (this.clampOBB.parent as Group).add(childNode.clampOBB); + + childNode.voxelOBB.updateMatrixWorld(true); childNode.clampOBB.updateMatrixWorld(true); } } diff --git a/packages/Main/src/Core/PointCloudNode.ts b/packages/Main/src/Core/PointCloudNode.ts index a7a11b2bc5..db081ef9c0 100644 --- a/packages/Main/src/Core/PointCloudNode.ts +++ b/packages/Main/src/Core/PointCloudNode.ts @@ -130,6 +130,11 @@ abstract class PointCloudNode extends THREE.EventDispatcher { return this.findCommonAncestor(node.parent as this); } } + + traverse(callback: (node: this) => void): void { + callback(this); + this.children.forEach(child => child.traverse(callback)); + } } export default PointCloudNode; diff --git a/packages/Main/src/Core/PotreeNodeBase.ts b/packages/Main/src/Core/PotreeNodeBase.ts index df175c3b92..e849f1b96d 100644 --- a/packages/Main/src/Core/PotreeNodeBase.ts +++ b/packages/Main/src/Core/PotreeNodeBase.ts @@ -106,6 +106,7 @@ export abstract class PotreeNodeBase extends PointCloudNode { (this.clampOBB.parent as Group).add(childNode.clampOBB); childNode.clampOBB.updateMatrixWorld(true); + childNode.voxelOBB.updateMatrixWorld(true); } } diff --git a/packages/Main/src/Layer/PointCloudLayer.ts b/packages/Main/src/Layer/PointCloudLayer.ts index c4867cd4fa..f58ad72e8a 100644 --- a/packages/Main/src/Layer/PointCloudLayer.ts +++ b/packages/Main/src/Layer/PointCloudLayer.ts @@ -1,5 +1,7 @@ import * as THREE from 'three'; +import TinyQueue from 'tinyqueue'; import GeometryLayer from 'Layer/GeometryLayer'; +import OBB from 'Renderer/OBB'; import PointsMaterial, { PNTS_MODE } from 'Renderer/PointsMaterial'; import Picking from 'Core/Picking'; @@ -71,6 +73,51 @@ interface Context { }; } +function computeObjectScreenSpace(context: Context, object: OBB) { + // Update camera matrices + context.camera.camera3D.updateMatrixWorld(); + + // Get bounding sphere from the OBB's box3D + const box3 = object.box3D; + const sphere = new THREE.Sphere(); + box3.getBoundingSphere(sphere); + + // Transform sphere center to world space using the OBB's matrixWorld + const sphereCenterWorld = new THREE.Vector3(); + sphereCenterWorld.copy(sphere.center); + sphereCenterWorld.applyMatrix4(object.matrixWorld); + + // Get camera position in world space + const cameraPos = context.camera.camera3D.position; + + // Calculate distance from camera to sphere center + const dx = cameraPos.x - sphereCenterWorld.x; + const dy = cameraPos.y - sphereCenterWorld.y; + const dz = cameraPos.z - sphereCenterWorld.z; + const distance = Math.sqrt(dx * dx + dy * dy + dz * dz); + + // If camera is inside or very close to the sphere, return maximum weight + if (distance - sphere.radius < 0) { + return Infinity; + } + + // Handle perspective camera (similar to Potree) + if (context.camera.camera3D instanceof THREE.PerspectiveCamera) { + const fov = (context.camera.camera3D.fov * Math.PI) / 180; + const slope = Math.tan(fov / 2); + const projFactor = (0.5 * context.camera.height) / (slope * distance); + const screenPixelRadius = sphere.radius * projFactor; + + return screenPixelRadius; + } else { + // Handle orthographic camera + // For orthographic, we can use the diagonal of the box projected + // to screen + const diagonal = box3.max.clone().sub(box3.min).length(); + return diagonal; + } +} + function computeSSEPerspective( context: Context, pointSize: number, @@ -120,21 +167,6 @@ function computeScreenSpaceError( return computeSSEPerspective(context, pointSize, pointSpacing, distance); } -function markForDeletion(elt: PointCloudNode) { - if (elt.obj) { - elt.obj.visible = false; - } - - if (!elt.notVisibleSince) { - elt.notVisibleSince = Date.now(); - // Set .sse to an invalid value - elt.sse = -1; - } - for (const child of elt.children) { - markForDeletion(child); - } -} - function changeIntensityRange(layer: PointCloudLayer) { // @ts-expect-error PointsMaterial is not typed yet layer.material.intensityRange?.set(layer.minIntensityRange, layer.maxIntensityRange); @@ -227,6 +259,9 @@ abstract class PointCloudLayer * Be default it is a new `PointsMaterial`. */ material: THREE.PointsMaterial; + private _visibleNodes: Set; + private _loadedNodes: Set; + /** * Constructs a new instance of point cloud layer. * Constructs a new instance of a Point Cloud Layer. This should not be used @@ -309,6 +344,9 @@ abstract class PointCloudLayer this.root = undefined; this.displayedCount = 0; + + this._visibleNodes = new Set(); + this._loadedNodes = new Set(); } setElevationRange() { @@ -356,14 +394,10 @@ abstract class PointCloudLayer */ loadData( elt: PointCloudNode, context: Context, layer: this, distanceToCamera: number, - ): [] | PointCloudNode[] { - elt.notVisibleSince = undefined; - + ) { // only load geometry if this elements has points if (elt.numPoints !== 0) { - if (elt.obj) { - elt.obj.visible = true; - } else if (!elt.promise) { + if (!elt.obj && !elt.promise) { const distance = Math.max(0.001, distanceToCamera); // Increase priority of nearest node const priority = computeScreenSpaceError( @@ -378,14 +412,10 @@ abstract class PointCloudLayer view: context.view, priority, redraw: true, - earlyDropFunction: cmd => !cmd.requester.visible || !this.visible, + earlyDropFunction: cmd => cmd.requester.notVisibleSince !== undefined || !this.visible, }).then((pts: THREE.Points) => { + this._loadedNodes.add(elt); elt.obj = pts; - elt.obj.visible = false; - // make sure to add it here, otherwise it might never - // be added nor cleaned - this.group.add(elt.obj); - elt.obj.updateMatrixWorld(true); context.view.notifyChange(this); this.dispatchEvent({ type: 'load-model', scene: pts, tile: elt }); }).catch((err: { isCancelledCommandException: boolean }) => { @@ -398,24 +428,27 @@ abstract class PointCloudLayer }); } } + } - if (elt.children && elt.children.length) { - elt.sse = computeScreenSpaceError( - context, - layer.pointSize, - elt.pointSpacing, - distanceToCamera, - ) / this.sseThreshold; - if (elt.sse >= 1) { - return elt.children; - } else { - for (const child of elt.children) { - markForDeletion(child); - } - return []; - } + setTileVisibility(node: PointCloudNode, visible: boolean) { + const { obj } = node; + + if (!obj) { + return; + } + + if (visible) { + this._visibleNodes.add(node); + this.group.add(obj); + obj.updateMatrixWorld(true); + node.notVisibleSince = undefined; + } else { + this._visibleNodes.delete(node); + this.group.remove(obj); + node.notVisibleSince = Date.now(); } - return []; + node.visible = visible; + this.dispatchEvent({ type: 'tile-visibility-change', tile: node, visible }); } /** @@ -428,84 +461,82 @@ abstract class PointCloudLayer * * @returns The child nodes to update or [] if there is none. */ - update(context: Context, layer: this, elt: PointCloudNode): PointCloudNode[] { - elt.visible = false; - - if (this.octreeDepthLimit >= 0 && this.octreeDepthLimit < elt.depth) { - markForDeletion(elt); - return []; - } + update(context: Context, layer: this, root: PointCloudNode) { + const rootWithWeight = { node: root, weight: Infinity }; + const stack = new TinyQueue([rootWithWeight], (a, b) => { + if (b.weight < a.weight) { + return -1; + } + if (b.weight > a.weight) { + return 1; + } + return 0; + }); + let numVisiblePoints = 0; + while (stack.length > 0) { + const { node } = stack.pop() as { node: PointCloudNode }; - // get object on which to measure distance - let bbox; - let object3d; - if (elt.obj) { - object3d = elt.obj; - bbox = object3d.geometry.boundingBox as THREE.Box3; - } else { - object3d = elt.clampOBB; - bbox = object3d.box3D; - } + const bbox = node.voxelOBB.box3D; + const object3d = node.voxelOBB; - elt.visible = context.camera.isBox3Visible(bbox, object3d.matrixWorld); + let visible = this.octreeDepthLimit < 0 || this.octreeDepthLimit >= node.depth; + visible = visible && context.camera.isBox3Visible(bbox, object3d.matrixWorld); + visible = visible && numVisiblePoints + node.numPoints <= this.pointBudget; - if (!elt.visible) { - markForDeletion(elt); - return []; - } + this.setTileVisibility(node, visible); - // TODO: See if we can limit the calcul of the matrixWorlInverse. - point.copy(context.camera.camera3D.position) - .applyMatrix4(object3d.matrixWorld.clone().invert()); + if (!visible) { + continue; + } - const distanceToCamera = bbox.distanceToPoint(point); + numVisiblePoints += node.numPoints; - return this.loadData(elt, context, layer, distanceToCamera); - } + point.copy(context.camera.camera3D.position).applyMatrix4(object3d.matrixWorldInverse); + const distanceToCamera = bbox.distanceToPoint(point); - postUpdate() { - this.displayedCount = 0; - for (const pts of this.group.children as THREE.Points[]) { - if (pts.visible) { - const count = pts.geometry.attributes.position.count; - pts.geometry.setDrawRange(0, count); - this.displayedCount += count; - } - } + this.loadData(node, context, layer, distanceToCamera); - if (this.displayedCount > this.pointBudget) { - // This format doesn't require points to be evenly distributed, - // so we're going to sort the nodes by "importance" - // (= on screen size) and display only the first N nodes - this.group.children.sort((p1, p2) => p2.userData.node.sse - p1.userData.node.sse); - - let limitHit = false; - this.displayedCount = 0; - for (const pts of this.group.children as THREE.Points[]) { - const count = pts.geometry.attributes.position.count; - if (limitHit || (this.displayedCount + count) > this.pointBudget) { - pts.visible = false; - limitHit = true; - } else { - this.displayedCount += count; + node.sse = computeScreenSpaceError( + context, + layer.pointSize, + node.pointSpacing, + distanceToCamera, + ); + for (const child of node.children) { + if (node.sse >= this.sseThreshold) { + const childWeight = computeObjectScreenSpace(context, child.voxelOBB); + stack.push({ node: child, weight: childWeight }); + } else if (child.visible !== false) { + this.setTileVisibility(child, false); } } } + } + postUpdate() { const now = Date.now(); - for (let i = this.group.children.length - 1; i >= 0; i--) { - const obj = this.group.children[i] as THREE.Points; - if (!obj.visible && (now - obj.userData.node.notVisibleSince) > 10000) { - // remove from group - this.group.children.splice(i, 1); + this.displayedCount = 0; + for (const node of this._visibleNodes) { + this.displayedCount += node.numPoints; + } + const disposedNodes = new Set(); + for (const node of this._loadedNodes) { + const obj = node.obj as THREE.Points; + if (obj && node.notVisibleSince !== undefined && (now - node.notVisibleSince) > 10000) { + disposedNodes.add(node); // no need to dispose obj.material, as it is shared by all // objects of this layer obj.geometry.dispose(); - obj.userData.node.obj = null; - this.dispatchEvent({ type: 'dispose-model', scene: obj, tile: obj.userData.node }); + node.obj = undefined; + node.notVisibleSince = undefined; + this.dispatchEvent({ type: 'dispose-model', scene: obj, tile: node }); } } + + for (const node of disposedNodes) { + this._loadedNodes.delete(node); + } } // @ts-expect-error Layer and Picking are not typed yet diff --git a/packages/Main/src/Renderer/OBB.ts b/packages/Main/src/Renderer/OBB.ts index 5bd91081b6..fa02d8cc63 100644 --- a/packages/Main/src/Renderer/OBB.ts +++ b/packages/Main/src/Renderer/OBB.ts @@ -40,7 +40,7 @@ class OBB extends THREE.Object3D { z: { min: number, max: number, scale: number, delta: number }; private _center: undefined | THREE.Vector3; - private matrixWorldInverse: THREE.Matrix4; + matrixWorldInverse: THREE.Matrix4; /** * @param min - (optional) A {@link THREE.Vector3} representing the lower @@ -114,6 +114,79 @@ class OBB extends THREE.Object3D { this.box3D.max.z = this.natBox.max.z + this.z.max * this.z.scale + geoidHeight; } + intersectsFrustum(frustum: THREE.Frustum): boolean { + this.updateMatrixWorld(true); + + const invMatrix = this.matrixWorldInverse; + + const min = this.box3D.min; + const max = this.box3D.max; + + const center = new THREE.Vector3() + .addVectors(min, max) + .multiplyScalar(0.5); + + const extents = new THREE.Vector3() + .subVectors(max, min) + .multiplyScalar(0.5); + + const localPlane = new THREE.Plane(); + const localNormal = new THREE.Vector3(); + + for (let i = 0; i < 6; i++) { + localPlane.copy(frustum.planes[i]).applyMatrix4(invMatrix); + + localNormal.copy(localPlane.normal); + localNormal.set( + Math.abs(localNormal.x), + Math.abs(localNormal.y), + Math.abs(localNormal.z), + ); + + const r = + extents.x * localNormal.x + + extents.y * localNormal.y + + extents.z * localNormal.z; + + const d = localPlane.distanceToPoint(center); + + if (d + r < 0) { + return false; + } + } + + return true; + } + + getBoundingSphere(sphere: THREE.Sphere = new THREE.Sphere()): THREE.Sphere { + const min = this.box3D.min; + const max = this.box3D.max; + + // 8 corners of the box in local space + const points = [ + new THREE.Vector3(min.x, min.y, min.z), + new THREE.Vector3(min.x, min.y, max.z), + new THREE.Vector3(min.x, max.y, min.z), + new THREE.Vector3(min.x, max.y, max.z), + new THREE.Vector3(max.x, min.y, min.z), + new THREE.Vector3(max.x, min.y, max.z), + new THREE.Vector3(max.x, max.y, min.z), + new THREE.Vector3(max.x, max.y, max.z), + ]; + + // Temporary world-space AABB + const worldBox = new THREE.Box3(); + worldBox.makeEmpty(); + + for (const p of points) { + p.applyMatrix4(this.matrixWorld); + worldBox.expandByPoint(p); + } + + worldBox.getBoundingSphere(sphere); + return sphere; + } + /** * Determines if the sphere is above the XY space of the box. *