From c29c48dac6e89d9458771b08fb4b025cec939c68 Mon Sep 17 00:00:00 2001 From: luoyifan Date: Fri, 13 Jun 2025 21:24:24 +0800 Subject: [PATCH] =?UTF-8?q?OBB=20=E9=80=89=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - pnpm-lock.yaml | 149 ++++++++++------------- src/core/ItemObbManager.bak | 100 ++++++++++++++++ src/core/ItemObbManager.ts | 88 -------------- src/core/ModelUtils.ts | 99 +++++++-------- src/core/QuadTree.ts | 120 +++++++++++++++++++ src/core/controls/DragControl.ts | 10 +- src/core/controls/SelectInspect.ts | 9 +- src/core/engine/Viewport.ts | 8 ++ src/core/manager/EntityManager.ts | 41 ++++--- src/core/manager/InstancePointManager.ts | 11 ++ src/core/manager/ItemFindManager.ts | 200 +++++++++++++++++++++++++++++++ src/core/manager/LineSegmentManager.ts | 7 +- src/example/example1.js | 12 +- src/modules/gstore/GstoreRenderer.ts | 77 +++++++----- src/modules/way/WayRenderer.ts | 11 +- src/types/model.d.ts | 55 +++++---- 17 files changed, 671 insertions(+), 327 deletions(-) create mode 100644 src/core/ItemObbManager.bak delete mode 100644 src/core/ItemObbManager.ts create mode 100644 src/core/QuadTree.ts create mode 100644 src/core/manager/ItemFindManager.ts diff --git a/package.json b/package.json index b6dd66c..3e88c00 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "sortablejs": "1.15.6", "split.js": "^1.6.4", "three": "^0.176.0", - "three-mesh-bvh": "^0.9.0", "troika-three-text": "^0.52.4", "tslib": "2.8.1", "typescript": "~5.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68c5ac4..ffb118b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,9 +126,6 @@ importers: three-dxf-viewer: specifier: ^1.0.36 version: 1.0.36 - three-mesh-bvh: - specifier: ^0.9.0 - version: 0.9.0(three@0.176.0) troika-three-text: specifier: ^0.52.4 version: 0.52.4(three@0.176.0) @@ -324,151 +321,151 @@ packages: vue: ^3.2.0 '@esbuild/aix-ppc64@0.25.4': - resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==, tarball: https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz} + resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/android-arm64@0.25.4': - resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==, tarball: https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz} + resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm@0.25.4': - resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==, tarball: https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.4.tgz} + resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-x64@0.25.4': - resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==, tarball: https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.4.tgz} + resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/darwin-arm64@0.25.4': - resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==, tarball: https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz} + resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.25.4': - resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==, tarball: https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz} + resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/freebsd-arm64@0.25.4': - resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz} + resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.25.4': - resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz} + resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/linux-arm64@0.25.4': - resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz} + resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.25.4': - resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz} + resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.25.4': - resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==, tarball: https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz} + resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.25.4': - resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==, tarball: https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz} + resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.25.4': - resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==, tarball: https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz} + resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.25.4': - resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==, tarball: https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz} + resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.25.4': - resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==, tarball: https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz} + resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.25.4': - resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==, tarball: https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz} + resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.25.4': - resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==, tarball: https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz} + resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/netbsd-arm64@0.25.4': - resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==, tarball: https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz} + resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.25.4': - resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==, tarball: https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz} + resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/openbsd-arm64@0.25.4': - resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==, tarball: https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz} + resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.25.4': - resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==, tarball: https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz} + resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/sunos-x64@0.25.4': - resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==, tarball: https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz} + resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/win32-arm64@0.25.4': - resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==, tarball: https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz} + resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.25.4': - resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==, tarball: https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz} + resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.25.4': - resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==, tarball: https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz} + resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -540,113 +537,102 @@ packages: optional: true '@rollup/rollup-android-arm-eabi@4.41.0': - resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==, tarball: https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz} + resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==} cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.41.0': - resolution: {integrity: sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz} + resolution: {integrity: sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==} cpu: [arm64] os: [android] '@rollup/rollup-darwin-arm64@4.41.0': - resolution: {integrity: sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==, tarball: https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz} + resolution: {integrity: sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==} cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.41.0': - resolution: {integrity: sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz} + resolution: {integrity: sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==} cpu: [x64] os: [darwin] '@rollup/rollup-freebsd-arm64@4.41.0': - resolution: {integrity: sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==, tarball: https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz} + resolution: {integrity: sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==} cpu: [arm64] os: [freebsd] '@rollup/rollup-freebsd-x64@4.41.0': - resolution: {integrity: sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==, tarball: https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz} + resolution: {integrity: sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==} cpu: [x64] os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.41.0': - resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz} + resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.41.0': - resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz} + resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.41.0': - resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz} + resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.41.0': - resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz} + resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.41.0': - resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz} + resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': - resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz} + resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.41.0': - resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz} + resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.41.0': - resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz} + resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.41.0': - resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz} + resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.41.0': - resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz} + resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.41.0': - resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz} + resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.41.0': - resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz} + resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==} cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.41.0': - resolution: {integrity: sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz} + resolution: {integrity: sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==} cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-msvc@4.41.0': - resolution: {integrity: sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz} + resolution: {integrity: sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==} cpu: [x64] os: [win32] @@ -658,7 +644,7 @@ packages: engines: {node: '>=18'} '@sxzz/popperjs-es@2.11.7': - resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==, tarball: https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz} + resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==} '@tsconfig/node22@22.0.2': resolution: {integrity: sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==} @@ -879,7 +865,7 @@ packages: resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, tarball: https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz} + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} ansi-regex@6.1.0: @@ -887,7 +873,7 @@ packages: engines: {node: '>=12'} ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, tarball: https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz} + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} ansi-styles@6.2.1: @@ -944,11 +930,11 @@ packages: resolution: {integrity: sha512-+aFkvqhaAVr1gferNMuN8vkTSrWIFvzlMV9I2KBLCWS2WpZ2+UAkZjlMZmEuT+gcXTi6RrGQCkWq1/bDtGqhIA==} color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, tarball: https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz} + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, tarball: https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz} + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} @@ -1032,7 +1018,7 @@ packages: vue: ^3.2.0 emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, tarball: https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz} + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1042,7 +1028,7 @@ packages: engines: {node: '>=0.12'} errno@0.1.8: - resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==, tarball: https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz} + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} hasBin: true error-stack-parser-es@0.1.5: @@ -1120,7 +1106,7 @@ packages: engines: {node: '>=14.14'} fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz} + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -1157,7 +1143,7 @@ packages: engines: {node: '>= 0.4'} graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, tarball: https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz} + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} @@ -1190,7 +1176,7 @@ packages: engines: {node: '>=0.10.0'} image-size@0.5.5: - resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==, tarball: https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz} + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} engines: {node: '>=0.10.0'} hasBin: true @@ -1203,7 +1189,7 @@ packages: hasBin: true is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, tarball: https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz} + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} is-inside-container@1.0.0: @@ -1306,7 +1292,7 @@ packages: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} make-dir@2.1.0: - resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==, tarball: https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz} + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} math-intrinsics@1.1.0: @@ -1332,7 +1318,7 @@ packages: engines: {node: '>= 0.6'} mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==, tarball: https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz} + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} hasBin: true @@ -1372,7 +1358,7 @@ packages: hasBin: true needle@3.3.1: - resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==, tarball: https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz} + resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} engines: {node: '>= 4.4.x'} hasBin: true @@ -1569,7 +1555,7 @@ packages: engines: {node: '>=0.10.0'} source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz} + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} speakingurl@14.0.1: @@ -1580,7 +1566,7 @@ packages: resolution: {integrity: sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==} string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, tarball: https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz} + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} string-width@5.1.2: @@ -1588,7 +1574,7 @@ packages: engines: {node: '>=12'} strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, tarball: https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz} + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} strip-ansi@7.1.0: @@ -1606,11 +1592,6 @@ packages: three-dxf-viewer@1.0.36: resolution: {integrity: sha512-Tu7k+/yCyovpMJTeQ8fpGHdjtynq66vMMXYzDCwI9prJo2dRDxig4S+ixQVuMIy3I99VZa51z5KwlkeWyCnFzQ==, tarball: https://registry.npmmirror.com/three-dxf-viewer/-/three-dxf-viewer-1.0.36.tgz} - three-mesh-bvh@0.9.0: - resolution: {integrity: sha512-xAwZj0hZknpwVsdK5BBJTIAZDjDPZCRzURY1o+z/JHBON/jc2UetK1CzPeQZiiOVSfI4jV2z7sXnnGtgsgnjaA==} - peerDependencies: - three: '>= 0.159.0' - three@0.171.0: resolution: {integrity: sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==, tarball: https://registry.npmmirror.com/three/-/three-0.171.0.tgz} @@ -1796,7 +1777,7 @@ packages: hasBin: true wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, tarball: https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz} + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} wrap-ansi@8.1.0: @@ -3268,10 +3249,6 @@ snapshots: dxf: 5.2.0 three: 0.171.0 - three-mesh-bvh@0.9.0(three@0.176.0): - dependencies: - three: 0.176.0 - three@0.171.0: {} three@0.176.0: {} diff --git a/src/core/ItemObbManager.bak b/src/core/ItemObbManager.bak new file mode 100644 index 0000000..05d8bbf --- /dev/null +++ b/src/core/ItemObbManager.bak @@ -0,0 +1,100 @@ +我想用 JS 开发一个浏览器 3D 场景下的物品查询工具 ItemFindManager, 借助 octree 算法结构来实现 +物品数超过 100,000 个, 基于物品的 OBB 包围盒进行各种查询操作, 并可能存在频繁且少量的点位更新/删除操作 +查询方法包括: +1.根据位置,获取命中到物品OBB包围盒的所有物品ID集合 +2.根据位置和距离,获取给定范围内,OBB包围盒碰到的所有物品ID集合 +3.给定一个矩形区域 (x1,z1)->(x2,z2),获取物品OBB包围盒与矩形有交集的所有物品ID集合 + +不要创建 Mesh 不要创建 Group +物品不用管渲染(不用去到 Scene / 不用管纹理和材质),但期望他最大程度的使用 BufferGeometry 用显卡来运算 +OBB 不用刻意追求,可以先用 AABB 进行检索,再通过 OBB 进行进一步筛选达到目的 + +物品的结构是 + +export interface ItemMetrix { + /** + * 物体ID, 唯一标识 + */ + id: string + + /** + * 变换矩阵, 3x3矩阵, X轴正增长向右, Y轴正增长向屏幕外, Z轴正增长向下。右手坐标系 + */ + tf: [ + /** + * 平移向量 position, 三维坐标 + * [0]=x轴向右, [1]=y轴高度向屏幕外, [2]=z轴向下 + */ + [number, number, number], + + /** + * 旋转向量 rotation, 单位为度 + * [0]=X轴逆向旋转角度, [1]=Y轴逆向旋转角度, [2]=Z轴逆向旋转角度 + * 对应 three.js 应进行"角度"转"弧度"的换算 + */ + [number, number, number], + + /** + * 缩放向量 scale, 三维缩放比例, [0]=X宽度, [1]=Y高度, [2]=Z长度 + */ + [number, number, number], + ] +} + +参考: + +import * as THREE from 'three' + +/** + * 物品 OBB 包围盒管理器 + * 物品数超过 100,000 个, 基于物品的 OBB 包围盒进行各种查询操作 + * 并可能存在频繁且少量的点位更新/删除操作 + */ +export default class ItemObbManager { + /** + * 添加或更新物品 (根据ID) + */ + addOrUpdate(...items: ItemMetrix[]): void { + } + + /** + * 根据 ID 移除物品 + */ + remove(...ids: string[]): void { + } + + /** + * 根据位置,获取命中到物品OBB包围盒的所有物品ID集合 + * @param x 位置X坐标 + * @param z 位置Z坐标 + */ + getItemsByPosition(x: number, z: number): string[] { + return [] + } + + /** + * 根据位置和距离,获取给定范围内,OBB包围盒碰到的所有物品ID集合 + * @param x 位置X坐标 + * @param z 位置Z坐标 + * @param distance 范围距离 + */ + getItemsByDistance(x: number, z: number, distance: number): string[] { + // Implementation for getting items by distance + return [] + } + + /** + * 给定一个矩形区域 (x1,z1)->(x2,z2),获取物品OBB包围盒与矩形有交集的所有物品ID集合 + */ + getItemsByRect(x1: number, z1: number, x2: number, z2: number): string[] { + return [] + } + + /** + * 给定一个矩形区域 (x1,z1)->(x2,z2),获取物品OBB包围盒完全在矩形内的物品ID集合 + */ + getItemsByRectInclude(): string[] { + return [] + } +} + diff --git a/src/core/ItemObbManager.ts b/src/core/ItemObbManager.ts deleted file mode 100644 index a8e9044..0000000 --- a/src/core/ItemObbManager.ts +++ /dev/null @@ -1,88 +0,0 @@ -// 开发一个物品 OBB 包围盒管理器 -// 借助 three-mesh-bvh 库来实现 -// 里面所有的物品不用管渲染(不用去到 Scene / 不用管纹理和材质),但期望他最大程度的使用 BufferGeometry 用显卡来运算 - -import * as THREE from 'three' - -/** - * 物品 OBB 包围盒管理器 - * 点位数超过 100,000 个, 基于物品的 OBB 包围盒进行各种查询操作 - * 并可能存在频繁且少量的点位更新/删除操作 - */ -export default class ItemObbManager { - /** - * 添加或更新物品 (根据ID) - */ - addOrUpdate(...items: ItemMetrix[]): void { - } - - /** - * 根据 ID 移除物品 - */ - remove(...ids: string[]): void { - } - - /** - * 根据位置,获取命中到物品OBB包围盒的所有物品ID集合 - * @param x 位置X坐标 - * @param z 位置Z坐标 - */ - getItemsByPosition(x: number, z: number): string[] { - return [] - } - - /** - * 根据位置和距离,获取给定范围内,OBB包围盒碰到的所有物品ID集合 - * @param x 位置X坐标 - * @param z 位置Z坐标 - * @param distance 范围距离 - */ - getItemsByDistance(x: number, z: number, distance: number): string[] { - // Implementation for getting items by distance - return [] - } - - /** - * 给定一个矩形区域 (x1,z1)->(x2,z2),获取物品OBB包围盒与矩形有交集的所有物品ID集合 - */ - getItemsByRect(x1: number, z1: number, x2: number, z2: number): string[] { - return [] - } - - /** - * 给定一个矩形区域 (x1,z1)->(x2,z2),获取物品OBB包围盒完全在矩形内的物品ID集合 - */ - getItemsByRectInclude(): string[] { - return [] - } -} - -export interface ItemMetrix { - /** - * 物体ID, 唯一标识 - */ - id: string - - /** - * 变换矩阵, 3x3矩阵, X轴正增长向右, Y轴正增长向屏幕外, Z轴正增长向下。右手坐标系 - */ - tf: [ - /** - * 平移向量 position, 三维坐标 - * [0]=x轴向右, [1]=y轴高度向屏幕外, [2]=z轴向下 - */ - [number, number, number], - - /** - * 旋转向量 rotation, 单位为度 - * [0]=X轴逆向旋转角度, [1]=Y轴逆向旋转角度, [2]=Z轴逆向旋转角度 - * 对应 three.js 应进行"角度"转"弧度"的换算 - */ - [number, number, number], - - /** - * 缩放向量 scale, 三维缩放比例, [0]=X宽度, [1]=Y高度, [2]=Z长度 - */ - [number, number, number], - ] -} diff --git a/src/core/ModelUtils.ts b/src/core/ModelUtils.ts index 4a6eab4..00be723 100644 --- a/src/core/ModelUtils.ts +++ b/src/core/ModelUtils.ts @@ -11,6 +11,46 @@ import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' /** + * 计算点集凸包(2D) + */ +export function convexHull(points: { x: number, z: number }[]): { x: number, z: number }[] { + if (points.length <= 3) return [...points] + + // 找到最左下角的点 + let start = points[0] + for (const p of points) { + if (p.z < start.z || (p.z === start.z && p.x < start.x)) { + start = p + } + } + + // 按极角排序 + const sorted = points.filter(p => p !== start) + sorted.sort((a, b) => { + const angleA = Math.atan2(a.z - start.z, a.x - start.x) + const angleB = Math.atan2(b.z - start.z, b.x - start.x) + return angleA === angleB + ? (Math.abs(a.x - start.x) - Math.abs(b.x - start.x)) + : angleA - angleB + }) + + // 使用栈计算凸包 + const stack: { x: number, z: number }[] = [start, sorted[0]] + for (let i = 1; i < sorted.length; i++) { + while (stack.length >= 2) { + const top = stack[stack.length - 1] + const nextTop = stack[stack.length - 2] + const cross = (top.x - nextTop.x) * (sorted[i].z - nextTop.z) - + (top.z - nextTop.z) * (sorted[i].x - nextTop.x) + if (cross <= 0) stack.pop() + else break + } + stack.push(sorted[i]) + } + return stack +} + +/** * 无序线条的id */ export function getCargoLineId(linkName: string, startId: string, endId: string): string { @@ -458,63 +498,6 @@ export function findObject3DByCondition(scene: THREE.Object3D, condition: (objec return foundObjects } -// export function loadSceneFromJson(viewport: Viewport, scene: THREE.Scene, items: ItemJson[]) { -// console.time('loadSceneFromJson') -// -// const object3ds: THREE.Object3D[] = [] -// -// // beforeLoad 通知所有加载的对象, 模型加载开始 -// getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => { -// const ret = itemType.clazz.beforeLoad() -// Array.isArray(ret) && object3ds.push(...ret) -// }) -// -// const loads = loadObject3DFromJson(items) -// Array.isArray(loads) && object3ds.push(...loads) -// -// // afterLoadComplete 通知所有加载的对象, 模型加载完成 -// getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => { -// const ret = itemType.clazz.afterLoadComplete(object3ds) -// Array.isArray(ret) && object3ds.push(...ret) -// }) -// -// scene.add(...object3ds) -// -// // afterAddScene 通知所有加载的对象, 模型加载完成 -// getAllItemTypes().forEach(itemType => { -// itemType.clazz.afterAddScene(viewport, scene, object3ds) -// }) -// -// console.log('loadSceneFromJson:', items.length, 'items,', object3ds.length, 'objects') -// console.timeEnd('loadSceneFromJson') -// } -// -// function loadObject3DFromJson(items: ItemJson[]): THREE.Object3D[] { -// const result: THREE.Object3D[] = [] -// -// for (const item of items) { -// if (!item || !item.t) { -// console.error('unkown item:', item) -// continue -// } -// -// const object3D: THREE.Object3D | undefined = getItemTypeByName(item.t)?.clazz.loadFromJson(item) -// if (object3D === undefined) { -// continue -// } -// -// if (_.isArray(item.items)) { -// // 如果有子元素,递归处理 -// const children = loadObject3DFromJson(item.items) -// children.forEach(child => object3D.add(child)) -// } -// -// result.push(object3D) -// } -// -// return result -// } - /** * 十进制求和 * @param collection @@ -547,7 +530,7 @@ export async function loadByUrl(url): Promise { * @param width 平面宽度 * */ -export function createLinkPlaneMatrix4(startPosition: THREE.Vector3, endPosition: THREE.Vector3, width: number, direction: LinkDirection): THREE.Matrix4 { +export function createLinkPlaneMatrix4(startPosition: THREE.Vector3, endPosition: THREE.Vector3, width: number): THREE.Matrix4 { const dir = new THREE.Vector3().subVectors(endPosition, startPosition) const length = dir.length() // 平面长度 = 两点距离 dir.normalize() diff --git a/src/core/QuadTree.ts b/src/core/QuadTree.ts new file mode 100644 index 0000000..2571520 --- /dev/null +++ b/src/core/QuadTree.ts @@ -0,0 +1,120 @@ +/** + * 四叉树节点实现 + */ +export class QuadTreeNode { + static MAX_OBJECTS = 10 + static MAX_LEVELS = 6 + + level: number + bounds: { minX: number, minZ: number, maxX: number, maxZ: number } + objects: { id: string, aabb: { minX: number, minZ: number, maxX: number, maxZ: number } }[] + nodes: QuadTreeNode[] + + constructor(level: number, bounds: { minX: number, minZ: number, maxX: number, maxZ: number }) { + this.level = level + this.bounds = bounds + this.objects = [] + this.nodes = [] + } + + clear(): void { + this.objects = [] + for (const node of this.nodes) { + node.clear() + } + this.nodes = [] + } + + // 获取物体所属的子节点索引 + getIndex(aabb: { minX: number, minZ: number, maxX: number, maxZ: number }): number[] { + const indices: number[] = [] + const midX = (this.bounds.minX + this.bounds.maxX) / 2 + const midZ = (this.bounds.minZ + this.bounds.maxZ) / 2 + + const top = aabb.minZ < midZ && aabb.maxZ > midZ + const bottom = aabb.minZ < this.bounds.maxZ && aabb.maxZ > midZ + const left = aabb.minX < midX && aabb.maxX > midX + const right = aabb.minX < this.bounds.maxX && aabb.maxX > midX + + if (top && left) indices.push(0) + if (top && right) indices.push(1) + if (bottom && left) indices.push(2) + if (bottom && right) indices.push(3) + + return indices + } + + // 分裂节点 + split(): void { + const { minX, minZ, maxX, maxZ } = this.bounds + const midX = (minX + maxX) / 2 + const midZ = (minZ + maxZ) / 2 + + this.nodes[0] = new QuadTreeNode(this.level + 1, { minX, minZ, maxX: midX, maxZ: midZ }) + this.nodes[1] = new QuadTreeNode(this.level + 1, { minX: midX, minZ, maxX, maxZ: midZ }) + this.nodes[2] = new QuadTreeNode(this.level + 1, { minX, minZ: midZ, maxX: midX, maxZ }) + this.nodes[3] = new QuadTreeNode(this.level + 1, { minX: midX, minZ: midZ, maxX, maxZ }) + } + + // 插入物体 + insert(id: string, aabb: { minX: number, minZ: number, maxX: number, maxZ: number }): void { + if (this.nodes.length) { + const indices = this.getIndex(aabb) + for (const index of indices) { + this.nodes[index].insert(id, aabb) + } + return + } + + this.objects.push({ id, aabb }) + + if (this.objects.length > QuadTreeNode.MAX_OBJECTS && + this.level < QuadTreeNode.MAX_LEVELS) { + if (!this.nodes.length) { + this.split() + } + + for (const obj of this.objects) { + const indices = this.getIndex(obj.aabb) + for (const index of indices) { + this.nodes[index].insert(obj.id, obj.aabb) + } + } + this.objects = [] + } + } + + // 查询区域 + query(region: { minX: number, minZ: number, maxX: number, maxZ: number }, result: Set): void { + if (!this.intersects(region)) return + + if (this.nodes.length) { + for (const node of this.nodes) { + node.query(region, result) + } + } else { + for (const obj of this.objects) { + if (this.intersectsAABB(region, obj.aabb)) { + result.add(obj.id) + } + } + } + } + + // 区域相交检测 + private intersects(region: { minX: number, minZ: number, maxX: number, maxZ: number }): boolean { + return !(region.maxX < this.bounds.minX || + region.minX > this.bounds.maxX || + region.maxZ < this.bounds.minZ || + region.minZ > this.bounds.maxZ) + } + + // AABB相交检测 + private intersectsAABB(a: { minX: number, minZ: number, maxX: number, maxZ: number }, + b: { minX: number, minZ: number, maxX: number, maxZ: number }): boolean { + return !(a.maxX < b.minX || + a.minX > b.maxX || + a.maxZ < b.minZ || + a.minZ > b.maxZ) + } +} diff --git a/src/core/controls/DragControl.ts b/src/core/controls/DragControl.ts index 902b0c6..7f4f855 100644 --- a/src/core/controls/DragControl.ts +++ b/src/core/controls/DragControl.ts @@ -108,10 +108,12 @@ export default class DragControl implements IControls { this.updateShadows(new THREE.Vector2(CurrentMouseInfo.x, CurrentMouseInfo.z)) } else { - - const mouse = this.getMousePosition(event.clientX, event.clientY) - const intersected = this.getIntersectedDraggableObject(mouse) - this.domElement.style.cursor = intersected ? 'grab' : 'auto' + // 射线方法修改 ========================== + // const mouse = this.getMousePosition(event.clientX, event.clientY) + // const intersected = this.getIntersectedDraggableObject(mouse) + // ===================================== + const ids = this.viewport.itemFindManager.getItemsByPosition(CurrentMouseInfo.x, CurrentMouseInfo.z) + this.domElement.style.cursor = ids.length > 0 ? 'grab' : 'auto' } } diff --git a/src/core/controls/SelectInspect.ts b/src/core/controls/SelectInspect.ts index 74a3af2..f075275 100644 --- a/src/core/controls/SelectInspect.ts +++ b/src/core/controls/SelectInspect.ts @@ -406,7 +406,7 @@ export default class SelectInspect implements IControls { const endZ = box.max.z // 查找所有在矩形内的对象 - const objects = this.viewport.entityManager.getObjectsInBox(startX, startZ, endX, endZ) + const ids = this.viewport.itemFindManager.getItemsByRect(startX, startZ, endX, endZ) // 清空之前的多选对象 this.viewport.state.multiSelectedObjects = [] @@ -415,19 +415,20 @@ export default class SelectInspect implements IControls { const multiSelectedObjects = [] const multiSelectedItems = [] const multiSelectedEntityIds = [] - for (const object of objects) { + for (const id of ids) { + const object = this.viewport.entityManager.findObjectById(id) if (object.userData.entityId && object.userData.t) { const item = this.viewport.entityManager.findItemById(object.userData.entityId) if (item && item.dt.protected !== true) { multiSelectedObjects.push(object) multiSelectedItems.push(item) - multiSelectedEntityIds.push(object.userData.entityId) + multiSelectedEntityIds.push(id) } } } // 触发多选对象更新事件 - this.viewport.state.multiSelectedObjects = markRaw(objects) + this.viewport.state.multiSelectedObjects = markRaw(multiSelectedObjects) this.viewport.state.multiSelectedItems = markRaw(multiSelectedItems) this.viewport.state.multiSelectedEntityIds = multiSelectedEntityIds EventBus.dispatch('multiSelectedObjectsChanged', { diff --git a/src/core/engine/Viewport.ts b/src/core/engine/Viewport.ts index 3c0db4a..3bebf71 100644 --- a/src/core/engine/Viewport.ts +++ b/src/core/engine/Viewport.ts @@ -25,6 +25,7 @@ import type InstancePointManager from '@/core/manager/InstancePointManager.ts' import type LineSegmentManager from '@/core/manager/LineSegmentManager.ts' import type { Object3DLike } from '@/types/ModelTypes.ts' import type InstanceMeshManager from '@/core/manager/InstanceMeshManager.ts' +import ItemFindManager from '@/core/manager/ItemFindManager.ts' /** * 视窗对象 @@ -58,6 +59,8 @@ export default class Viewport { // 实体管理器 entityManager = new EntityManager() + itemFindManager = new ItemFindManager() + // 交互管理器 interactionManager = new InteractionManager() @@ -599,6 +602,11 @@ export default class Viewport { this.dragControl.dispose() this.dragControl = null } + + if (this.itemFindManager) { + this.itemFindManager.dispose() + this.itemFindManager = null + } } getIntersects(point: THREE.Vector2) { diff --git a/src/core/manager/EntityManager.ts b/src/core/manager/EntityManager.ts index fd5408c..0a9d1fa 100644 --- a/src/core/manager/EntityManager.ts +++ b/src/core/manager/EntityManager.ts @@ -101,6 +101,9 @@ export default class EntityManager { if (!entity?.id) { throw new Error('Entity must have an id') } + + this.viewport.itemFindManager.addOrUpdate(entity) + // 改成非深拷贝, 直接使用原始数据, 因为 renderer 可能会修改这个数据, 后续也要能生效 // const entity = _.cloneDeep(entityRaw) as ItemJson const originEntity = this.___entityMap.get(entity.id) @@ -157,6 +160,8 @@ export default class EntityManager { const entity = this.___entityMap.get(id) if (!entity) return + this.viewport.itemFindManager.remove(id) + option.originEntity = _.cloneDeep(entity) this.writeBackEntities.add(id) @@ -510,24 +515,24 @@ export default class EntityManager { ).filter((obj: Object3DLike) => obj?.userData && obj.userData.selectable !== false) } - /** - * 获取指定范围内的所有对象 - */ - getObjectsInBox(startX: number, startZ: number, endX: number, endZ: number) { - const box = new THREE.Box2( - new THREE.Vector2(startX, startZ), - new THREE.Vector2(endX, endZ) - ) - const objectsInBox: Object3DLike[] = [] - - for (const [id, obj] of this.__objectMap.entries()) { - if (box.containsPoint(new Vector2(obj.position.x, obj.position.z))) { - objectsInBox.push(obj) - } - } - - return objectsInBox - } + // /** + // * 获取指定范围内的所有对象 + // */ + // getObjectsInBox(startX: number, startZ: number, endX: number, endZ: number) { + // const box = new THREE.Box2( + // new THREE.Vector2(startX, startZ), + // new THREE.Vector2(endX, endZ) + // ) + // const objectsInBox: Object3DLike[] = [] + // + // for (const [id, obj] of this.__objectMap.entries()) { + // if (box.containsPoint(new Vector2(obj.position.x, obj.position.z))) { + // objectsInBox.push(obj) + // } + // } + // + // return objectsInBox + // } } interface LineDiffItem { diff --git a/src/core/manager/InstancePointManager.ts b/src/core/manager/InstancePointManager.ts index b5e441b..3c0cf8a 100644 --- a/src/core/manager/InstancePointManager.ts +++ b/src/core/manager/InstancePointManager.ts @@ -305,6 +305,17 @@ export class PointManageWrap { } createBox3() { + const instancedMesh = this.manager.blocks[this.blockIndex]?.instancedMesh + const matrix = new THREE.Matrix4() + instancedMesh.getMatrixAt(this.meshIndex, matrix) + // 创建包围盒并应用矩阵 + const geometry = instancedMesh.geometry + //@ts-ignore + const localBox = new THREE.Box3().setFromBufferAttribute(geometry.attributes.position) + return localBox.clone().applyMatrix4(matrix) + } + + createBox3Points() { // 原始代码 ================================= // const instancedMesh = this.manager.blocks[this.blockIndex]?.instancedMesh // diff --git a/src/core/manager/ItemFindManager.ts b/src/core/manager/ItemFindManager.ts new file mode 100644 index 0000000..1ff27f8 --- /dev/null +++ b/src/core/manager/ItemFindManager.ts @@ -0,0 +1,200 @@ +import * as THREE from 'three' +import rbush from 'rbush' +import { OBB } from 'three/examples/jsm/math/OBB' +// import { Octree } from 'three/examples/jsm/math/Octree.js' +// import { QuadTreeNode } from '@/core/QuadTree.ts' +// import { convexHull } from '@/core/ModelUtils.ts' + +interface ItemEntry extends rbush.BBox { + id: string + obb: OBB +} + +// 主管理器类 +export default class ItemFindManager { + private spatialIndex = new rbush() + private items = new Map() + + dispose() { + this.spatialIndex.clear() + this.items.clear() + } + + constructor() { + } + + // 添加或更新物品 + addOrUpdate(...items: ItemMetrix[]): void { + for (const item of items) { + const aabb = itemToAABB(item) + const obb = itemToOBB(item) + + if (this.items.has(item.id)) { + this.remove(item.id) + } + + const entry: ItemEntry = { + id: item.id, + obb, + ...aabb + } + + this.items.set(item.id, entry) + this.spatialIndex.insert(entry) + } + } + + // 移除物品 + remove(...ids: string[]): void { + for (const id of ids) { + const entry = this.items.get(id) + if (entry) { + this.spatialIndex.remove(entry) + this.items.delete(id) + } + } + } + + // 位置查询 + getItemsByPosition(x: number, z: number): string[] { + const candidates = this.spatialIndex.search({ + minX: x, + minY: z, + maxX: x, + maxY: z + }) + + const point = new THREE.Vector3(x, 0, z) + return candidates.filter((item) => pointIntersectsOBB(point, item.obb)).map((item) => item.id) + } + + // 距离查询 + getItemsByDistance(x: number, z: number, distance: number): string[] { + const sphere = new THREE.Sphere(new THREE.Vector3(x, 0, z), distance) + + const candidates = this.spatialIndex.search({ + minX: x - distance, + minY: z - distance, + maxX: x + distance, + maxY: z + distance + }) + + return candidates.filter((item) => sphereIntersectsOBB(sphere, item.obb)).map((item) => item.id) + } + + // 矩形区域查询(有交集) + getItemsByRect(x1: number, z1: number, x2: number, z2: number): string[] { + const rect = { + minX: Math.min(x1, x2), + maxX: Math.max(x1, x2), + minY: Math.min(z1, z2), + maxY: Math.max(z1, z2) + } + + const candidates = this.spatialIndex.search(rect) + return candidates.filter((item) => rectIntersectsOBB(rect, item.obb)).map((item) => item.id) + } +} + +function pointIntersectsOBB(point: THREE.Vector3, obb: OBB): boolean { + const box = new THREE.Box3() + setBox3FromOBB(box, obb) + return box.containsPoint(point) +} + +function sphereIntersectsOBB(sphere: THREE.Sphere, obb: OBB): boolean { + const box = new THREE.Box3() + setBox3FromOBB(box, obb) + return box.intersectsSphere(sphere) +} + +function rectIntersectsOBB(rect: { minX: number; minY: number; maxX: number; maxY: number }, obb: OBB): boolean { + // 简化判断:用 AABB 投影到 XZ 平面做矩形交叉检测 + const aabb = new THREE.Box3() + setBox3FromOBB(aabb, obb) + const aabb2D = { + minX: aabb.min.x, + minY: aabb.min.z, + maxX: aabb.max.x, + maxY: aabb.max.z + } + + return !( + rect.maxX < aabb2D.minX || + rect.minX > aabb2D.maxX || + rect.maxY < aabb2D.minY || + rect.minY > aabb2D.maxY + ) +} + +export function itemToAABB(item: ItemMetrix): { minX: number; minY: number; maxX: number; maxY: number } { + // 假设所有物品都是立方体,尺寸为 scale.x × scale.z + const x = item.tf[0][0] + const z = item.tf[0][2] + const halfWidth = item.tf[2][0] / 2 + const halfDepth = item.tf[2][2] / 2 + + return { + minX: x - halfWidth, + maxX: x + halfWidth, + minY: z - halfDepth, + maxY: z + halfDepth + } +} + +export function itemToOBB(item: ItemMetrix): OBB { + const position = new THREE.Vector3(...item.tf[0]) + const rotation = new THREE.Euler( + THREE.MathUtils.degToRad(item.tf[1][0]), + THREE.MathUtils.degToRad(item.tf[1][1]), + THREE.MathUtils.degToRad(item.tf[1][2]), + 'XYZ' + ) + const scale = new THREE.Vector3(...item.tf[2]) + + const matrix = new THREE.Matrix4() + .makeRotationFromEuler(rotation) + .premultiply(new THREE.Matrix4().makeTranslation(position.x, position.y, position.z)) + .premultiply(new THREE.Matrix4().makeScale(scale.x, scale.y, scale.z)) + + const obb = new OBB( + new THREE.Vector3(), + new THREE.Vector3(0.5, 0.5, 0.5), + new THREE.Matrix3().setFromMatrix4(matrix) + ) + + return obb +} + +function setBox3FromOBB(box: THREE.Box3, obb: OBB): THREE.Box3 { + const center = obb.center + const halfSize = new THREE.Vector3().copy(obb.halfSize) + const rotation = obb.rotation + + // 8 个局部顶点 + const vertices = [ + new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, 1, 1)), + new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, 1, 1)), + new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, -1, 1)), + new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, -1, 1)), + new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, 1, -1)), + new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, 1, -1)), + new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, -1, -1)), + new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, -1, -1)) + ] + + // 应用旋转和平移到每个顶点 + const worldVertices = vertices.map((v) => { + return v.applyMatrix3(rotation).add(center) + }) + + // 构造包围盒 + box.min.set(Infinity, Infinity, Infinity) + box.max.set(-Infinity, -Infinity, -Infinity) + + for (const v of worldVertices) { + box.expandByPoint(v) + } + + return box +} diff --git a/src/core/manager/LineSegmentManager.ts b/src/core/manager/LineSegmentManager.ts index c17e740..99fa480 100644 --- a/src/core/manager/LineSegmentManager.ts +++ b/src/core/manager/LineSegmentManager.ts @@ -5,7 +5,6 @@ import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2' import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' import type { Object3DLike, Vector3Like } from '@/types/ModelTypes.ts' import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry' -import { Line2 } from 'three/examples/jsm/lines/Line2' /** * 线段管理器 @@ -262,7 +261,7 @@ export default class LineSegmentManager { this.lineGeometry = new LineGeometry() // new LineSegmentsGeometry() // 创建线段的渲染对象 - this.lineSegments = new Line2(this.lineGeometry, this.lineMaterial) + this.lineSegments = new LineSegments2(this.lineGeometry, this.lineMaterial) this.lineSegments.name = name this.lineSegments.frustumCulled = false this.viewport.scene.add(this.lineSegments) @@ -284,6 +283,7 @@ export default class LineSegmentManager { this.positionArray = null this.colorArray = null } + } @@ -312,7 +312,8 @@ export class LineManageWrap { return false } - updateWorldMatrix(){} + updateWorldMatrix() { + } constructor(lineManager: LineSegmentManager, data: any, start: THREE.Vector3, end: THREE.Vector3, color: THREE.Color) { this.manager = lineManager diff --git a/src/example/example1.js b/src/example/example1.js index dceb895..c87483c 100644 --- a/src/example/example1.js +++ b/src/example/example1.js @@ -94,22 +94,22 @@ export default { tf: [[5, 0.1, 2], [90, 0, 0], [0.25, 0.25, 0.1]], dt: { in: [], out: [], center: ['39zML1rnSOOQGQYQ2YUMGy'] } }, { - id: '6UhIIw9QPYh6acwyW8OSGs', + id: 'gs1', t: 'gstore', v: true, - tf: [[-1, 0.1, 0.55], [0, 0, 0], [1.5, 0.1, 1.5]], + tf: [[-1, 0.1, -5.55], [0, 0, 0], [1, 0.1, 1]], dt: { in: [], out: [], center: [] } }, { - id: '1D0WSRPj8JJJwIcmA0UMqG', + id: 'gs2', t: 'gstore', v: true, - tf: [[0.75, 0.1, 0.55], [0, 0, 0], [1.5, 0.1, 1.5]], + tf: [[0.75, 0.1, -5.55], [0, 0, 0], [1, 0.1, 1]], dt: { in: [], out: [], center: [] } }, { - id: 'gstore333', + id: 'gs3', t: 'gstore', v: true, - tf: [[3, 0.1, 0.55], [0, 0, 0], [1.5, 0.1, 1.5]], + tf: [[3, 0.1, -5.55], [0, 0, 0], [1, 0.1, 1]], dt: { in: [], out: [], center: [] } }, { id: 'pallet1', diff --git a/src/modules/gstore/GstoreRenderer.ts b/src/modules/gstore/GstoreRenderer.ts index f68e100..fe907a6 100644 --- a/src/modules/gstore/GstoreRenderer.ts +++ b/src/modules/gstore/GstoreRenderer.ts @@ -5,6 +5,8 @@ import { type Object3DLike } from '@/types/ModelTypes.ts' import InstancePointManager from '@/core/manager/InstancePointManager.ts' import LineSegmentManager from '@/core/manager/LineSegmentManager.ts' import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' +import { itemToOBB } from '@/core/manager/ItemFindManager.ts' +import { getOBBox } from '@/core/ModelUtils.ts' /** * 地堆货位渲染器 @@ -59,17 +61,6 @@ export default class GstoreRenderer extends BaseRenderer { ) } - get lineSegmentManager(): LineSegmentManager { - if (!this.tempViewport) { - throw new Error('tempViewport is not set.') - } - return this.tempViewport.getOrCreateLineManager(this.itemTypeName, () => - // 构建 LineSegment.points 代理对象 - LineSegmentManager.create(this.itemTypeName, - this.tempViewport, - this.strokeMaterial) - ) - } createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike { return this.pointManager.createPoint(item) @@ -78,25 +69,35 @@ export default class GstoreRenderer extends BaseRenderer { afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, object: Object3DLike) { super.afterCreateOrUpdatePoint(item, option, object) - // 画边线 - const center = [item.tf[0][0], item.tf[0][2]] - const h = (item.tf[0][1] || this.defulePositionY) + 0.01 - const widthHalf = item.tf[2][0] / 2 - const depthHalf = item.tf[2][2] / 2 - const lwHalf = (item.dt.strokeWidth || this.defaultPointOption.strokeWidth) / 2 - // 左上角 - const p1 = [center[0] - widthHalf + lwHalf, h, center[1] - depthHalf + lwHalf] - // 右上角 - const p2 = [center[0] + widthHalf - lwHalf, h, center[1] - depthHalf + lwHalf] - // 右下角 - const p3 = [center[0] + widthHalf - lwHalf, h, center[1] + depthHalf - lwHalf] - // 左下角 - const p4 = [center[0] - widthHalf + lwHalf, h, center[1] + depthHalf - lwHalf] - - this.lineSegmentManager.createLine(item.id + '_l1', p1, p2, this.strokeMaterial.color) - this.lineSegmentManager.createLine(item.id + '_l2', p2, p3, this.strokeMaterial.color) - this.lineSegmentManager.createLine(item.id + '_l3', p3, p4, this.strokeMaterial.color) - this.lineSegmentManager.createLine(item.id + '_l4', p4, p1, this.strokeMaterial.color) + const edgePositions = getOBBox(item) + // const positions = [] + for (let i = 0; i < edgePositions.length; i += 2) { + const p1 = edgePositions[i] + const p2 = edgePositions[i + 1] + // positions.push(p1.x, p1.y, p1.z) + // positions.push(p2.x, p2.y, p2.z) + this.lineSegmentManager.createLine(item.id + '_l' + i, p1, p2, this.strokeMaterial.color) + } + + // // 画边线 + // const center = [item.tf[0][0], item.tf[0][2]] + // const h = (item.tf[0][1] || this.defulePositionY) + 0.01 + // const widthHalf = item.tf[2][0] / 2 + // const depthHalf = item.tf[2][2] / 2 + // const lwHalf = (item.dt.strokeWidth || this.defaultPointOption.strokeWidth) / 2 + // // 左上角 + // const p1 = [center[0] - widthHalf + lwHalf, h, center[1] - depthHalf + lwHalf] + // // 右上角 + // const p2 = [center[0] + widthHalf - lwHalf, h, center[1] - depthHalf + lwHalf] + // // 右下角 + // const p3 = [center[0] + widthHalf - lwHalf, h, center[1] + depthHalf - lwHalf] + // // 左下角 + // const p4 = [center[0] - widthHalf + lwHalf, h, center[1] + depthHalf - lwHalf] + // + // this.lineSegmentManager.createLine(item.id + '_l1', p1, p2, this.strokeMaterial.color) + // this.lineSegmentManager.createLine(item.id + '_l2', p2, p3, this.strokeMaterial.color) + // this.lineSegmentManager.createLine(item.id + '_l3', p3, p4, this.strokeMaterial.color) + // this.lineSegmentManager.createLine(item.id + '_l4', p4, p1, this.strokeMaterial.color) } createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D { @@ -113,4 +114,20 @@ export default class GstoreRenderer extends BaseRenderer { this.pointMaterial.dispose() this.strokeMaterial.dispose() } + + + get lineSegmentManager(): LineSegmentManager { + if (!this.tempViewport) { + throw new Error('tempViewport is not set.') + } + + const name = this.itemTypeName + '_bj' + + return this.tempViewport.getOrCreateLineManager(name, () => + // 构建 LineSegment.points 代理对象 + LineSegmentManager.create(name, + this.tempViewport, + this.strokeMaterial) + ) + } } diff --git a/src/modules/way/WayRenderer.ts b/src/modules/way/WayRenderer.ts index a240aad..f11ad2b 100644 --- a/src/modules/way/WayRenderer.ts +++ b/src/modules/way/WayRenderer.ts @@ -6,7 +6,7 @@ import Constract from '@/core/Constract.ts' import InstancePointManager from '@/core/manager/InstancePointManager.ts' import type { Object3DLike } from '@/types/ModelTypes.ts' import TriangleUrl from '@/assets/images/conveyor/shapes/triangle.png' -import Triangle2Url from '@/assets/images/conveyor/shapes/triangle2.png' +import Triangle2Url from '@/assets/images/conveyor/shapes/triangle-double.png' import InstanceMeshManager from '@/core/manager/InstanceMeshManager.ts' /** @@ -132,7 +132,7 @@ export default class WayRenderer extends BaseRenderer { const endPosition = new THREE.Vector3(end.tf[0][0], this.defulePositionY, end.tf[0][2]) const wrap = this.guidewayManager.create(lineId, {}) wrap.uuid = lineId - const matrix = createLinkPlaneMatrix4(startPosition, endPosition, this.rendererOption.lineWidth, direction) + const matrix = createLinkPlaneMatrix4(startPosition, endPosition, this.rendererOption.lineWidth) wrap.setMatrix4(matrix) @@ -153,8 +153,10 @@ export default class WayRenderer extends BaseRenderer { const dummy = new THREE.Object3D() dummy.position.setFromMatrixPosition(matrix) // dummy.rotation.setFromRotationMatrix(matrix) + dummy.scale.set(0.4, 0.01, 0.2) if (direction === '<->') { + dummy.scale.set(0.4, 0.01, 0.4) dummy.lookAt(endPosition) } else if (direction === '->') { dummy.lookAt(endPosition) @@ -162,7 +164,6 @@ export default class WayRenderer extends BaseRenderer { dummy.lookAt(startPosition) } - dummy.scale.set(0.4, 0.01, 0.2) dummy.updateMatrix() dirWrap.setMatrix4(dummy.matrix) wrap.userData.dirWraps = [dirWrap.uuid] @@ -180,8 +181,10 @@ export default class WayRenderer extends BaseRenderer { const position = startPosition.clone().lerp(endPosition, i / length) const dummy = new THREE.Object3D() dummy.position.copy(position) + dummy.scale.set(0.4, 0.01, 0.2) if (direction === '<->') { + dummy.scale.set(0.4, 0.01, 0.4) dummy.lookAt(endPosition) } else if (direction === '->') { dummy.lookAt(endPosition) @@ -189,7 +192,7 @@ export default class WayRenderer extends BaseRenderer { dummy.lookAt(startPosition) } - dummy.scale.set(0.4, 0.01, 0.2) + dummy.updateMatrix() dirWrap.setMatrix4(dummy.matrix) diff --git a/src/types/model.d.ts b/src/types/model.d.ts index 1a54c4d..ce1a90a 100644 --- a/src/types/model.d.ts +++ b/src/types/model.d.ts @@ -106,6 +106,35 @@ interface IGridHelper { snapDistance: number; } +interface ItemMetrix { + /** + * 物体ID, 唯一标识 + */ + id: string + + /** + * 变换矩阵, 3x3矩阵, X轴正增长向右, Y轴正增长向屏幕外, Z轴正增长向下。右手坐标系 + */ + tf: [ + /** + * 平移向量 position, 三维坐标 + * [0]=x轴向右, [1]=y轴高度向屏幕外, [2]=z轴向下 + */ + [number, number, number], + + /** + * 旋转向量 rotation, 单位为度 + * [0]=X轴逆向旋转角度, [1]=Y轴逆向旋转角度, [2]=Z轴逆向旋转角度 + * 对应 three.js 应进行"角度"转"弧度"的换算 + */ + [number, number, number], + + /** + * 缩放向量 scale, 三维缩放比例, [0]=X宽度, [1]=Y高度, [2]=Z长度 + */ + [number, number, number], + ] +} /** * 物体单元(点) @@ -128,12 +157,7 @@ interface IGridHelper { * } * } */ -interface ItemJson { - /** - * 对应 three.js 中的 uuid, 物体ID, 唯一标识, 需保证唯一, 有方法可以进行快速的 O(1) 查找 - */ - id?: string - +interface ItemJson extends ItemMetrix { /** * 物体名称, 显示用, 最后初始化到 three.js 的 name 中, 可以不设置, 可以不唯一, 但他的查找速度是 O(N) */ @@ -150,24 +174,6 @@ interface ItemJson { v: boolean /** - * 变换矩阵, 3x3矩阵, 采用Y轴向上为正, X轴向右, Z轴向前的右手坐标系 - */ - tf: [ - /** - * 平移向量 position, 三维坐标 - */ - [number, number, number], - /** - * 旋转向量 rotation, 表示绕Y轴旋转的角度, 单位为度。对应 three.js 应进行"角度"转"弧度"的换算 - */ - [number, number, number], - /** - * 缩放向量 scale, 三维缩放比例 - */ - [number, number, number], - ] - - /** * 用户数据, 可自定义, 一般用在 three.js 的 userData 中 */ dt: { @@ -214,7 +220,6 @@ interface ItemJson { } - /** * Object3D 与 Mesh / Geometry 都能存储的用户相关数据 */