gltf-parser-plugin 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { Mesh, Vector3, Triangle, BufferGeometry, EventDispatcher, DataTexture, RGBAFormat, UnsignedByteType, SRGBColorSpace, Texture, MeshStandardMaterial, DoubleSide, FrontSide, BufferAttribute, OrthographicCamera, Float32BufferAttribute, Vector2, WebGLRenderer, WebGLRenderTarget, ShaderMaterial, OneFactor, ZeroFactor, CustomBlending, Box2, Matrix4, Matrix3, Matrix2, Vector4, MeshBasicMaterial, Loader, Scene, Group, InstancedMesh, Quaternion, Box3 } from "three";
1
+ import { Mesh, Vector3, Triangle, BufferGeometry, EventDispatcher, DataTexture, RGBAFormat, UnsignedByteType, SRGBColorSpace, Texture, MeshStandardMaterial, DoubleSide, FrontSide, BufferAttribute, Box3, Vector2, OrthographicCamera, Float32BufferAttribute, WebGLRenderer, WebGLRenderTarget, ShaderMaterial, OneFactor, ZeroFactor, CustomBlending, Box2, Matrix4, Matrix3, Matrix2, Vector4, MeshBasicMaterial, Loader, Scene, Group, InstancedMesh, Quaternion, Color } from "three";
2
2
  class FeatureIdUniforms {
3
3
  mesh;
4
4
  plugin;
@@ -554,6 +554,114 @@ function acquireWorker() {
554
554
  currentWorkerIndex = (currentWorkerIndex + 1) % workerPool.length;
555
555
  return worker;
556
556
  }
557
+ function pointInPolygon(px, py, polygon) {
558
+ let inside = false;
559
+ const n = polygon.length;
560
+ for (let i = 0, j = n - 1; i < n; j = i++) {
561
+ const xi = polygon[i].x, yi = polygon[i].y;
562
+ const xj = polygon[j].x, yj = polygon[j].y;
563
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
564
+ inside = !inside;
565
+ }
566
+ }
567
+ return inside;
568
+ }
569
+ function segmentsIntersect(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) {
570
+ const cross = (ox, oy, ax, ay, bx, by) => (ax - ox) * (by - oy) - (ay - oy) * (bx - ox);
571
+ const d1 = cross(bx1, by1, bx2, by2, ax1, ay1);
572
+ const d2 = cross(bx1, by1, bx2, by2, ax2, ay2);
573
+ const d3 = cross(ax1, ay1, ax2, ay2, bx1, by1);
574
+ const d4 = cross(ax1, ay1, ax2, ay2, bx2, by2);
575
+ if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) {
576
+ return true;
577
+ }
578
+ const onSeg = (px, py, qx, qy, rx, ry) => Math.min(px, qx) <= rx && rx <= Math.max(px, qx) && Math.min(py, qy) <= ry && ry <= Math.max(py, qy);
579
+ if (d1 === 0 && onSeg(bx1, by1, bx2, by2, ax1, ay1)) return true;
580
+ if (d2 === 0 && onSeg(bx1, by1, bx2, by2, ax2, ay2)) return true;
581
+ if (d3 === 0 && onSeg(ax1, ay1, ax2, ay2, bx1, by1)) return true;
582
+ if (d4 === 0 && onSeg(ax1, ay1, ax2, ay2, bx2, by2)) return true;
583
+ return false;
584
+ }
585
+ function polygonIntersectsRect(polygon, minX, minY, maxX, maxY) {
586
+ const n = polygon.length;
587
+ for (let i = 0; i < n; i++) {
588
+ const p = polygon[i];
589
+ if (p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY) {
590
+ return true;
591
+ }
592
+ }
593
+ if (pointInPolygon(minX, minY, polygon) || pointInPolygon(maxX, minY, polygon) || pointInPolygon(maxX, maxY, polygon) || pointInPolygon(minX, maxY, polygon)) {
594
+ return true;
595
+ }
596
+ const rx = [minX, maxX, maxX, minX];
597
+ const ry = [minY, minY, maxY, maxY];
598
+ for (let i = 0; i < n; i++) {
599
+ const a = polygon[i];
600
+ const b = polygon[(i + 1) % n];
601
+ for (let j = 0; j < 4; j++) {
602
+ const k = (j + 1) % 4;
603
+ if (segmentsIntersect(a.x, a.y, b.x, b.y, rx[j], ry[j], rx[k], ry[k])) {
604
+ return true;
605
+ }
606
+ }
607
+ }
608
+ return false;
609
+ }
610
+ function selectByBoxFromOidMap(oidNodeMap, box) {
611
+ const result = [];
612
+ const nodeBox = new Box3();
613
+ for (const [oid, node] of oidNodeMap) {
614
+ if (!node.bbox || node.bbox.length < 6) continue;
615
+ nodeBox.min.set(node.bbox[0], node.bbox[1], node.bbox[2]);
616
+ nodeBox.max.set(node.bbox[3], node.bbox[4], node.bbox[5]);
617
+ if (box.intersectsBox(nodeBox)) {
618
+ result.push(oid);
619
+ }
620
+ }
621
+ return result;
622
+ }
623
+ function selectByPolygonFromOidMap(oidNodeMap, polygon, axis = "xz") {
624
+ const result = [];
625
+ const polygon2D = polygon.map((p) => {
626
+ switch (axis) {
627
+ case "xy":
628
+ return new Vector2(p.x, p.y);
629
+ case "yz":
630
+ return new Vector2(p.y, p.z);
631
+ case "xz":
632
+ default:
633
+ return new Vector2(p.x, p.z);
634
+ }
635
+ });
636
+ for (const [oid, node] of oidNodeMap) {
637
+ if (!node.bbox || node.bbox.length < 6) continue;
638
+ let minU, minV, maxU, maxV;
639
+ switch (axis) {
640
+ case "xy":
641
+ minU = node.bbox[0];
642
+ minV = node.bbox[1];
643
+ maxU = node.bbox[3];
644
+ maxV = node.bbox[4];
645
+ break;
646
+ case "xz":
647
+ minU = node.bbox[0];
648
+ minV = node.bbox[2];
649
+ maxU = node.bbox[3];
650
+ maxV = node.bbox[5];
651
+ break;
652
+ case "yz":
653
+ minU = node.bbox[1];
654
+ minV = node.bbox[2];
655
+ maxU = node.bbox[4];
656
+ maxV = node.bbox[5];
657
+ break;
658
+ }
659
+ if (polygonIntersectsRect(polygon2D, minU, minV, maxU, maxV)) {
660
+ result.push(oid);
661
+ }
662
+ }
663
+ return result;
664
+ }
557
665
  const _camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
558
666
  class FullscreenTriangleGeometry extends BufferGeometry {
559
667
  constructor() {
@@ -1646,6 +1754,369 @@ class GLTFWorkerLoader extends Loader {
1646
1754
  });
1647
1755
  }
1648
1756
  }
1757
+ function ensureColor(color) {
1758
+ if (color instanceof Color) return color;
1759
+ return new Color(color);
1760
+ }
1761
+ function getMaterials(mesh) {
1762
+ const mat = mesh.material;
1763
+ if (!mat) return [];
1764
+ return Array.isArray(mat) ? mat : [mat];
1765
+ }
1766
+ function getMaterialForColor(color) {
1767
+ const key = color.getHex();
1768
+ if (!materialCache.has(key)) {
1769
+ materialCache.set(key, new MeshStandardMaterial({
1770
+ color: color.clone(),
1771
+ roughness: 0.5,
1772
+ metalness: 0.1
1773
+ }));
1774
+ }
1775
+ return materialCache.get(key);
1776
+ }
1777
+ const materialCache = /* @__PURE__ */ new Map();
1778
+ class ComponentColorHelper {
1779
+ constructor(context) {
1780
+ this.context = context;
1781
+ }
1782
+ coloredOids = /* @__PURE__ */ new Set();
1783
+ materialByOid = /* @__PURE__ */ new Map();
1784
+ originalMaterialByMesh = /* @__PURE__ */ new Map();
1785
+ opacityModifiedOids = /* @__PURE__ */ new Set();
1786
+ opacityByOid = /* @__PURE__ */ new Map();
1787
+ originalOpacityByMaterial = /* @__PURE__ */ new Map();
1788
+ meshChangeHandlers = /* @__PURE__ */ new Map();
1789
+ getAllModifiedOids() {
1790
+ return Array.from(/* @__PURE__ */ new Set([...this.coloredOids, ...this.opacityModifiedOids]));
1791
+ }
1792
+ applyToMeshes(oid) {
1793
+ const scene = this.context.getScene();
1794
+ if (!scene) return;
1795
+ const collector = this.context.getMeshCollectorByOid(oid);
1796
+ const colorMaterial = this.materialByOid.get(oid);
1797
+ const opacity = this.opacityByOid.get(oid);
1798
+ collector.meshes.forEach((mesh) => {
1799
+ if (!mesh.parent) scene.add(mesh);
1800
+ if (colorMaterial) {
1801
+ mesh.material = colorMaterial;
1802
+ }
1803
+ if (opacity !== void 0) {
1804
+ for (const mat of getMaterials(mesh)) {
1805
+ if (!this.originalOpacityByMaterial.has(mat)) {
1806
+ this.originalOpacityByMaterial.set(mat, mat.opacity);
1807
+ }
1808
+ mat.opacity = opacity;
1809
+ mat.transparent = opacity < 1;
1810
+ }
1811
+ }
1812
+ });
1813
+ }
1814
+ /**
1815
+ * 根据 oid 数组设置构件颜色
1816
+ * 隐藏原 mesh,将 split mesh 替换材质后加入场景
1817
+ */
1818
+ setComponentColorByOids(oids, color) {
1819
+ const scene = this.context.getScene();
1820
+ if (!scene) return;
1821
+ const threeColor = ensureColor(color);
1822
+ const material = getMaterialForColor(threeColor);
1823
+ for (const oid of oids) {
1824
+ this.coloredOids.add(oid);
1825
+ this.materialByOid.set(oid, material);
1826
+ const collector = this.context.getMeshCollectorByOid(oid);
1827
+ collector.meshes.forEach((mesh) => {
1828
+ if (!this.originalMaterialByMesh.has(mesh.uuid)) {
1829
+ this.originalMaterialByMesh.set(mesh.uuid, mesh.material);
1830
+ }
1831
+ mesh.material = material;
1832
+ scene.add(mesh);
1833
+ });
1834
+ if (!this.meshChangeHandlers.has(oid)) {
1835
+ const handler = () => this.applyToMeshes(oid);
1836
+ this.meshChangeHandlers.set(oid, handler);
1837
+ collector.addEventListener("mesh-change", handler);
1838
+ }
1839
+ }
1840
+ this.context.hideByOids(this.getAllModifiedOids());
1841
+ }
1842
+ /**
1843
+ * 恢复指定构件的颜色
1844
+ * 若该 oid 已无颜色且无透明度修改,则从场景移除 split mesh 并 unhide 原 mesh
1845
+ */
1846
+ restoreComponentColorByOids(oids) {
1847
+ const scene = this.context.getScene();
1848
+ if (!scene) return;
1849
+ for (const oid of oids) {
1850
+ this.coloredOids.delete(oid);
1851
+ this.materialByOid.delete(oid);
1852
+ const collector = this.context.getMeshCollectorByOid(oid);
1853
+ const opacity = this.opacityByOid.get(oid);
1854
+ collector.meshes.forEach((mesh) => {
1855
+ const originalMat = this.originalMaterialByMesh.get(mesh.uuid);
1856
+ if (originalMat) {
1857
+ mesh.material = originalMat;
1858
+ this.originalMaterialByMesh.delete(mesh.uuid);
1859
+ if (opacity !== void 0) {
1860
+ for (const mat of getMaterials(mesh)) {
1861
+ if (!this.originalOpacityByMaterial.has(mat)) {
1862
+ this.originalOpacityByMaterial.set(mat, mat.opacity);
1863
+ }
1864
+ mat.opacity = opacity;
1865
+ mat.transparent = opacity < 1;
1866
+ }
1867
+ }
1868
+ }
1869
+ if (!this.coloredOids.has(oid) && !this.opacityModifiedOids.has(oid) && mesh.parent === scene) {
1870
+ scene.remove(mesh);
1871
+ }
1872
+ });
1873
+ if (!this.coloredOids.has(oid) && !this.opacityModifiedOids.has(oid)) {
1874
+ const handler = this.meshChangeHandlers.get(oid);
1875
+ if (handler) {
1876
+ this.meshChangeHandlers.delete(oid);
1877
+ collector.removeEventListener("mesh-change", handler);
1878
+ }
1879
+ }
1880
+ }
1881
+ this.context.unhideByOids(oids);
1882
+ }
1883
+ /**
1884
+ * 根据 oid 数组设置构件透明度
1885
+ * 隐藏原 mesh,将 split mesh 修改材质透明度后加入场景
1886
+ * @param oids 构件 OID 数组
1887
+ * @param opacity 透明度,0-1,0 完全透明,1 完全不透明
1888
+ */
1889
+ setComponentOpacityByOids(oids, opacity) {
1890
+ const scene = this.context.getScene();
1891
+ if (!scene) return;
1892
+ const clampedOpacity = Math.max(0, Math.min(1, opacity));
1893
+ for (const oid of oids) {
1894
+ this.opacityModifiedOids.add(oid);
1895
+ this.opacityByOid.set(oid, clampedOpacity);
1896
+ const colorMat = this.materialByOid.get(oid);
1897
+ if (colorMat && colorMat.opacity !== clampedOpacity) {
1898
+ const clone = colorMat.clone();
1899
+ clone.opacity = clampedOpacity;
1900
+ clone.transparent = clampedOpacity < 1;
1901
+ this.materialByOid.set(oid, clone);
1902
+ this.originalOpacityByMaterial.set(clone, 1);
1903
+ }
1904
+ const collector = this.context.getMeshCollectorByOid(oid);
1905
+ collector.meshes.forEach((mesh) => {
1906
+ scene.add(mesh);
1907
+ const mat = colorMat ? this.materialByOid.get(oid) : mesh.material;
1908
+ if (!this.originalOpacityByMaterial.has(mat)) {
1909
+ this.originalOpacityByMaterial.set(mat, mat.opacity);
1910
+ }
1911
+ mat.opacity = clampedOpacity;
1912
+ mat.transparent = clampedOpacity < 1;
1913
+ if (colorMat) mesh.material = mat;
1914
+ });
1915
+ if (!this.meshChangeHandlers.has(oid)) {
1916
+ const handler = () => this.applyToMeshes(oid);
1917
+ this.meshChangeHandlers.set(oid, handler);
1918
+ collector.addEventListener("mesh-change", handler);
1919
+ }
1920
+ }
1921
+ this.context.hideByOids(this.getAllModifiedOids());
1922
+ }
1923
+ /**
1924
+ * 恢复指定构件的透明度
1925
+ * 若该 oid 已无颜色且无透明度修改,则从场景移除 split mesh 并 unhide 原 mesh
1926
+ */
1927
+ restoreComponentOpacityByOids(oids) {
1928
+ const scene = this.context.getScene();
1929
+ if (!scene) return;
1930
+ for (const oid of oids) {
1931
+ this.opacityModifiedOids.delete(oid);
1932
+ this.opacityByOid.delete(oid);
1933
+ const collector = this.context.getMeshCollectorByOid(oid);
1934
+ collector.meshes.forEach((mesh) => {
1935
+ for (const mat of getMaterials(mesh)) {
1936
+ const original = this.originalOpacityByMaterial.get(mat);
1937
+ if (original !== void 0) {
1938
+ mat.opacity = original;
1939
+ mat.transparent = original < 1;
1940
+ this.originalOpacityByMaterial.delete(mat);
1941
+ }
1942
+ }
1943
+ if (!this.coloredOids.has(oid) && !this.opacityModifiedOids.has(oid) && mesh.parent === scene) {
1944
+ scene.remove(mesh);
1945
+ }
1946
+ });
1947
+ if (!this.coloredOids.has(oid) && !this.opacityModifiedOids.has(oid)) {
1948
+ const handler = this.meshChangeHandlers.get(oid);
1949
+ if (handler) {
1950
+ this.meshChangeHandlers.delete(oid);
1951
+ collector.removeEventListener("mesh-change", handler);
1952
+ }
1953
+ }
1954
+ }
1955
+ this.context.unhideByOids(oids);
1956
+ }
1957
+ }
1958
+ class InteractionFilter {
1959
+ constructor(context) {
1960
+ this.context = context;
1961
+ }
1962
+ frozenOids = /* @__PURE__ */ new Set();
1963
+ isolatedOids = /* @__PURE__ */ new Set();
1964
+ trackedMeshes = /* @__PURE__ */ new Map();
1965
+ meshListeners = /* @__PURE__ */ new Map();
1966
+ isPluginRemoving = false;
1967
+ isOidBlocked(oid) {
1968
+ if (this.frozenOids.has(oid)) return true;
1969
+ if (this.isolatedOids.size > 0 && !this.isolatedOids.has(oid)) return true;
1970
+ return false;
1971
+ }
1972
+ trackMesh(mesh, oid) {
1973
+ if (this.meshListeners.has(mesh)) return;
1974
+ const onAdded = () => {
1975
+ if (this.isPluginRemoving) return;
1976
+ mesh.userData._detachedParent = null;
1977
+ if (this.isOidBlocked(oid) && mesh.parent) {
1978
+ const parent = mesh.parent;
1979
+ this.isPluginRemoving = true;
1980
+ mesh.userData._detachedParent = parent;
1981
+ parent.remove(mesh);
1982
+ this.isPluginRemoving = false;
1983
+ }
1984
+ };
1985
+ const onRemoved = () => {
1986
+ if (this.isPluginRemoving) return;
1987
+ mesh.userData._detachedParent = null;
1988
+ };
1989
+ mesh.addEventListener("added", onAdded);
1990
+ mesh.addEventListener("removed", onRemoved);
1991
+ this.meshListeners.set(mesh, { onAdded, onRemoved });
1992
+ }
1993
+ untrackMesh(mesh) {
1994
+ const listeners = this.meshListeners.get(mesh);
1995
+ if (listeners) {
1996
+ mesh.removeEventListener("added", listeners.onAdded);
1997
+ mesh.removeEventListener("removed", listeners.onRemoved);
1998
+ this.meshListeners.delete(mesh);
1999
+ }
2000
+ mesh.userData._detachedParent = null;
2001
+ }
2002
+ onCollectorMeshChange(oid, newMeshes) {
2003
+ const tracked = this.trackedMeshes.get(oid);
2004
+ const newSet = new Set(newMeshes);
2005
+ if (tracked) {
2006
+ for (const mesh of tracked) {
2007
+ if (!newSet.has(mesh)) {
2008
+ this.untrackMesh(mesh);
2009
+ tracked.delete(mesh);
2010
+ }
2011
+ }
2012
+ }
2013
+ const trackSet = tracked || /* @__PURE__ */ new Set();
2014
+ for (const mesh of newMeshes) {
2015
+ if (!trackSet.has(mesh)) {
2016
+ this.trackMesh(mesh, oid);
2017
+ trackSet.add(mesh);
2018
+ }
2019
+ }
2020
+ this.trackedMeshes.set(oid, trackSet);
2021
+ }
2022
+ syncCollectorMeshes() {
2023
+ this.isPluginRemoving = true;
2024
+ for (const [oid, collector] of this.context.getCollectorCache()) {
2025
+ const blocked = this.isOidBlocked(oid);
2026
+ for (const mesh of collector.meshes) {
2027
+ if (!this.meshListeners.has(mesh)) continue;
2028
+ if (blocked) {
2029
+ if (mesh.parent && !mesh.userData._detachedParent) {
2030
+ const parent = mesh.parent;
2031
+ mesh.userData._detachedParent = parent;
2032
+ parent.remove(mesh);
2033
+ }
2034
+ } else {
2035
+ const storedParent = mesh.userData._detachedParent;
2036
+ if (storedParent && !mesh.parent) {
2037
+ storedParent.add(mesh);
2038
+ mesh.userData._detachedParent = null;
2039
+ }
2040
+ }
2041
+ }
2042
+ }
2043
+ this.isPluginRemoving = false;
2044
+ }
2045
+ onUnregisterCollector(oid) {
2046
+ const tracked = this.trackedMeshes.get(oid);
2047
+ if (tracked) {
2048
+ for (const mesh of tracked) {
2049
+ this.untrackMesh(mesh);
2050
+ }
2051
+ this.trackedMeshes.delete(oid);
2052
+ }
2053
+ }
2054
+ freezeByOids(oids) {
2055
+ for (const oid of oids) {
2056
+ this.frozenOids.add(oid);
2057
+ }
2058
+ this.syncCollectorMeshes();
2059
+ }
2060
+ freezeByOid(oid) {
2061
+ this.frozenOids.add(oid);
2062
+ this.syncCollectorMeshes();
2063
+ }
2064
+ unfreezeByOids(oids) {
2065
+ for (const oid of oids) {
2066
+ this.frozenOids.delete(oid);
2067
+ }
2068
+ this.syncCollectorMeshes();
2069
+ }
2070
+ unfreezeByOid(oid) {
2071
+ this.frozenOids.delete(oid);
2072
+ this.syncCollectorMeshes();
2073
+ }
2074
+ unfreeze() {
2075
+ this.frozenOids.clear();
2076
+ this.syncCollectorMeshes();
2077
+ }
2078
+ getFrozenOids() {
2079
+ return Array.from(this.frozenOids);
2080
+ }
2081
+ isolateByOids(oids) {
2082
+ for (const oid of oids) {
2083
+ this.isolatedOids.add(oid);
2084
+ }
2085
+ this.syncCollectorMeshes();
2086
+ }
2087
+ isolateByOid(oid) {
2088
+ this.isolatedOids.add(oid);
2089
+ this.syncCollectorMeshes();
2090
+ }
2091
+ unisolateByOids(oids) {
2092
+ for (const oid of oids) {
2093
+ this.isolatedOids.delete(oid);
2094
+ }
2095
+ this.syncCollectorMeshes();
2096
+ }
2097
+ unisolateByOid(oid) {
2098
+ this.isolatedOids.delete(oid);
2099
+ this.syncCollectorMeshes();
2100
+ }
2101
+ unisolate() {
2102
+ this.isolatedOids.clear();
2103
+ this.syncCollectorMeshes();
2104
+ }
2105
+ getIsolatedOids() {
2106
+ return Array.from(this.isolatedOids);
2107
+ }
2108
+ dispose() {
2109
+ for (const [, meshSet] of this.trackedMeshes) {
2110
+ for (const mesh of meshSet) {
2111
+ this.untrackMesh(mesh);
2112
+ }
2113
+ }
2114
+ this.trackedMeshes.clear();
2115
+ this.meshListeners.clear();
2116
+ this.frozenOids.clear();
2117
+ this.isolatedOids.clear();
2118
+ }
2119
+ }
1649
2120
  const DB_NAME = "GLTFParserPluginTilesCache";
1650
2121
  const DB_VERSION = 1;
1651
2122
  const STORE_NAME = "tiles";
@@ -1749,12 +2220,8 @@ class GLTFParserPlugin {
1749
2220
  // --- Model info properties ---
1750
2221
  _modelInfo = null;
1751
2222
  _modelInfoPromise = null;
1752
- // --- Interaction filter properties ---
1753
- _frozenOids = /* @__PURE__ */ new Set();
1754
- _isolatedOids = /* @__PURE__ */ new Set();
1755
- _trackedMeshes = /* @__PURE__ */ new Map();
1756
- _meshListeners = /* @__PURE__ */ new Map();
1757
- _isPluginRemoving = false;
2223
+ _interactionFilter;
2224
+ _componentColorHelper = null;
1758
2225
  // --- Mesh helper properties ---
1759
2226
  oids = [];
1760
2227
  renderer = null;
@@ -1777,6 +2244,9 @@ class GLTFParserPlugin {
1777
2244
  if (options?.renderer) {
1778
2245
  this.renderer = options.renderer;
1779
2246
  }
2247
+ this._interactionFilter = new InteractionFilter({
2248
+ getCollectorCache: () => this.collectorCache
2249
+ });
1780
2250
  setMaxWorkers(this._options.maxWorkers);
1781
2251
  }
1782
2252
  /**
@@ -1784,6 +2254,12 @@ class GLTFParserPlugin {
1784
2254
  */
1785
2255
  init(tiles) {
1786
2256
  this.tiles = tiles;
2257
+ this._componentColorHelper = new ComponentColorHelper({
2258
+ hideByOids: (oids) => this.hideByOids(oids),
2259
+ unhideByOids: (oids) => this.unhideByOids(oids),
2260
+ getMeshCollectorByOid: (oid) => this.getMeshCollectorByOid(oid),
2261
+ getScene: () => this.tiles?.group ?? null
2262
+ });
1787
2263
  this._loader = new GLTFWorkerLoader(tiles.manager, {
1788
2264
  metadata: this._options.metadata,
1789
2265
  materialBuilder: this._options.materialBuilder
@@ -1935,71 +2411,6 @@ class GLTFParserPlugin {
1935
2411
  async getStructureData() {
1936
2412
  return this._ensureStructureLoaded();
1937
2413
  }
1938
- // =============================================
1939
- // Spatial Query Methods
1940
- // =============================================
1941
- _pointInPolygon(px, py, polygon) {
1942
- let inside = false;
1943
- const n = polygon.length;
1944
- for (let i = 0, j = n - 1; i < n; j = i++) {
1945
- const xi = polygon[i].x, yi = polygon[i].y;
1946
- const xj = polygon[j].x, yj = polygon[j].y;
1947
- if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
1948
- inside = !inside;
1949
- }
1950
- }
1951
- return inside;
1952
- }
1953
- _segmentsIntersect(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) {
1954
- const cross = (ox, oy, ax, ay, bx, by) => (ax - ox) * (by - oy) - (ay - oy) * (bx - ox);
1955
- const d1 = cross(bx1, by1, bx2, by2, ax1, ay1);
1956
- const d2 = cross(bx1, by1, bx2, by2, ax2, ay2);
1957
- const d3 = cross(ax1, ay1, ax2, ay2, bx1, by1);
1958
- const d4 = cross(ax1, ay1, ax2, ay2, bx2, by2);
1959
- if ((d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0)) {
1960
- return true;
1961
- }
1962
- const onSeg = (px, py, qx, qy, rx, ry) => Math.min(px, qx) <= rx && rx <= Math.max(px, qx) && Math.min(py, qy) <= ry && ry <= Math.max(py, qy);
1963
- if (d1 === 0 && onSeg(bx1, by1, bx2, by2, ax1, ay1)) return true;
1964
- if (d2 === 0 && onSeg(bx1, by1, bx2, by2, ax2, ay2)) return true;
1965
- if (d3 === 0 && onSeg(ax1, ay1, ax2, ay2, bx1, by1)) return true;
1966
- if (d4 === 0 && onSeg(ax1, ay1, ax2, ay2, bx2, by2)) return true;
1967
- return false;
1968
- }
1969
- _polygonIntersectsRect(polygon, minX, minY, maxX, maxY) {
1970
- const n = polygon.length;
1971
- for (let i = 0; i < n; i++) {
1972
- const p = polygon[i];
1973
- if (p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY) {
1974
- return true;
1975
- }
1976
- }
1977
- if (this._pointInPolygon(minX, minY, polygon) || this._pointInPolygon(maxX, minY, polygon) || this._pointInPolygon(maxX, maxY, polygon) || this._pointInPolygon(minX, maxY, polygon)) {
1978
- return true;
1979
- }
1980
- const rx = [minX, maxX, maxX, minX];
1981
- const ry = [minY, minY, maxY, maxY];
1982
- for (let i = 0; i < n; i++) {
1983
- const a = polygon[i];
1984
- const b = polygon[(i + 1) % n];
1985
- for (let j = 0; j < 4; j++) {
1986
- const k = (j + 1) % 4;
1987
- if (this._segmentsIntersect(
1988
- a.x,
1989
- a.y,
1990
- b.x,
1991
- b.y,
1992
- rx[j],
1993
- ry[j],
1994
- rx[k],
1995
- ry[k]
1996
- )) {
1997
- return true;
1998
- }
1999
- }
2000
- }
2001
- return false;
2002
- }
2003
2414
  /**
2004
2415
  * 选择包围盒范围内的构件(包含相交和包含两种情况)
2005
2416
  * @param box 查询用的 Box3 范围,坐标系与 structure.json 中 bbox 一致
@@ -2007,17 +2418,7 @@ class GLTFParserPlugin {
2007
2418
  */
2008
2419
  async selectByBox(box) {
2009
2420
  await this._ensureStructureLoaded();
2010
- const result = [];
2011
- const nodeBox = new Box3();
2012
- for (const [oid, node] of this._oidNodeMap) {
2013
- if (!node.bbox || node.bbox.length < 6) continue;
2014
- nodeBox.min.set(node.bbox[0], node.bbox[1], node.bbox[2]);
2015
- nodeBox.max.set(node.bbox[3], node.bbox[4], node.bbox[5]);
2016
- if (box.intersectsBox(nodeBox)) {
2017
- result.push(oid);
2018
- }
2019
- }
2020
- return result;
2421
+ return selectByBoxFromOidMap(this._oidNodeMap, box);
2021
2422
  }
2022
2423
  /**
2023
2424
  * 选择多边形(平面投影)范围内的构件(包含相交和包含两种情况)
@@ -2030,46 +2431,7 @@ class GLTFParserPlugin {
2030
2431
  */
2031
2432
  async selectByPolygon(polygon, axis = "xz") {
2032
2433
  await this._ensureStructureLoaded();
2033
- const result = [];
2034
- const polygon2D = polygon.map((p) => {
2035
- switch (axis) {
2036
- case "xy":
2037
- return new Vector2(p.x, p.y);
2038
- case "yz":
2039
- return new Vector2(p.y, p.z);
2040
- case "xz":
2041
- default:
2042
- return new Vector2(p.x, p.z);
2043
- }
2044
- });
2045
- for (const [oid, node] of this._oidNodeMap) {
2046
- if (!node.bbox || node.bbox.length < 6) continue;
2047
- let minU, minV, maxU, maxV;
2048
- switch (axis) {
2049
- case "xy":
2050
- minU = node.bbox[0];
2051
- minV = node.bbox[1];
2052
- maxU = node.bbox[3];
2053
- maxV = node.bbox[4];
2054
- break;
2055
- case "xz":
2056
- minU = node.bbox[0];
2057
- minV = node.bbox[2];
2058
- maxU = node.bbox[3];
2059
- maxV = node.bbox[5];
2060
- break;
2061
- case "yz":
2062
- minU = node.bbox[1];
2063
- minV = node.bbox[2];
2064
- maxU = node.bbox[4];
2065
- maxV = node.bbox[5];
2066
- break;
2067
- }
2068
- if (this._polygonIntersectsRect(polygon2D, minU, minV, maxU, maxV)) {
2069
- result.push(oid);
2070
- }
2071
- }
2072
- return result;
2434
+ return selectByPolygonFromOidMap(this._oidNodeMap, polygon, axis);
2073
2435
  }
2074
2436
  // =============================================
2075
2437
  // Model Info Methods
@@ -2154,13 +2516,7 @@ class GLTFParserPlugin {
2154
2516
  const oid = collector.getOid();
2155
2517
  this.collectors.delete(collector);
2156
2518
  this.collectorCache.delete(oid);
2157
- const tracked = this._trackedMeshes.get(oid);
2158
- if (tracked) {
2159
- for (const mesh of tracked) {
2160
- this._untrackMesh(mesh);
2161
- }
2162
- this._trackedMeshes.delete(oid);
2163
- }
2519
+ this._interactionFilter.onUnregisterCollector(oid);
2164
2520
  }
2165
2521
  _updateWebGLLimits() {
2166
2522
  const gl = this.renderer.getContext();
@@ -2259,186 +2615,53 @@ class GLTFParserPlugin {
2259
2615
  queryFeatureFromIntersection(hit) {
2260
2616
  const result = queryFeatureFromIntersection(hit);
2261
2617
  if (result.isValid && result.oid !== void 0) {
2262
- if (this._frozenOids.has(result.oid)) {
2263
- return { isValid: false, error: "Component is frozen" };
2264
- }
2265
- if (this._isolatedOids.size > 0 && !this._isolatedOids.has(result.oid)) {
2266
- return { isValid: false, error: "Component is not in isolated set" };
2618
+ if (this._interactionFilter.isOidBlocked(result.oid)) {
2619
+ return {
2620
+ isValid: false,
2621
+ error: this._interactionFilter.getFrozenOids().includes(result.oid) ? "Component is frozen" : "Component is not in isolated set"
2622
+ };
2267
2623
  }
2268
2624
  }
2269
2625
  return result;
2270
2626
  }
2271
2627
  // =============================================
2272
- // Interaction Filter Methods
2628
+ // Interaction Filter Methods (delegated)
2273
2629
  // =============================================
2274
- _isOidBlocked(oid) {
2275
- if (this._frozenOids.has(oid)) return true;
2276
- if (this._isolatedOids.size > 0 && !this._isolatedOids.has(oid))
2277
- return true;
2278
- return false;
2279
- }
2280
- _trackMesh(mesh, oid) {
2281
- if (this._meshListeners.has(mesh)) return;
2282
- const onAdded = () => {
2283
- if (this._isPluginRemoving) return;
2284
- mesh.userData._detachedParent = null;
2285
- if (this._isOidBlocked(oid) && mesh.parent) {
2286
- const parent = mesh.parent;
2287
- this._isPluginRemoving = true;
2288
- mesh.userData._detachedParent = parent;
2289
- parent.remove(mesh);
2290
- this._isPluginRemoving = false;
2291
- }
2292
- };
2293
- const onRemoved = () => {
2294
- if (this._isPluginRemoving) return;
2295
- mesh.userData._detachedParent = null;
2296
- };
2297
- mesh.addEventListener("added", onAdded);
2298
- mesh.addEventListener("removed", onRemoved);
2299
- this._meshListeners.set(mesh, { onAdded, onRemoved });
2300
- }
2301
- _untrackMesh(mesh) {
2302
- const listeners = this._meshListeners.get(mesh);
2303
- if (listeners) {
2304
- mesh.removeEventListener("added", listeners.onAdded);
2305
- mesh.removeEventListener("removed", listeners.onRemoved);
2306
- this._meshListeners.delete(mesh);
2307
- }
2308
- mesh.userData._detachedParent = null;
2309
- }
2310
- _onCollectorMeshChange(oid, newMeshes) {
2311
- const tracked = this._trackedMeshes.get(oid);
2312
- const newSet = new Set(newMeshes);
2313
- if (tracked) {
2314
- for (const mesh of tracked) {
2315
- if (!newSet.has(mesh)) {
2316
- this._untrackMesh(mesh);
2317
- tracked.delete(mesh);
2318
- }
2319
- }
2320
- }
2321
- const trackSet = tracked || /* @__PURE__ */ new Set();
2322
- for (const mesh of newMeshes) {
2323
- if (!trackSet.has(mesh)) {
2324
- this._trackMesh(mesh, oid);
2325
- trackSet.add(mesh);
2326
- }
2327
- }
2328
- this._trackedMeshes.set(oid, trackSet);
2329
- }
2330
- _syncCollectorMeshes() {
2331
- this._isPluginRemoving = true;
2332
- for (const [oid, collector] of this.collectorCache) {
2333
- const blocked = this._isOidBlocked(oid);
2334
- for (const mesh of collector.meshes) {
2335
- if (!this._meshListeners.has(mesh)) continue;
2336
- if (blocked) {
2337
- if (mesh.parent && !mesh.userData._detachedParent) {
2338
- const parent = mesh.parent;
2339
- mesh.userData._detachedParent = parent;
2340
- this.unhideByOids([oid]);
2341
- }
2342
- } else {
2343
- const storedParent = mesh.userData._detachedParent;
2344
- if (storedParent && !mesh.parent) {
2345
- storedParent.add(mesh);
2346
- mesh.userData._detachedParent = null;
2347
- }
2348
- }
2349
- }
2350
- }
2351
- this._isPluginRemoving = false;
2352
- }
2353
- /**
2354
- * 冻结指定构件,被冻结的构件不再响应任何交互和事件
2355
- */
2356
2630
  freezeByOids(oids) {
2357
- for (const oid of oids) {
2358
- this._frozenOids.add(oid);
2359
- }
2360
- this._syncCollectorMeshes();
2631
+ this._interactionFilter.freezeByOids(oids);
2361
2632
  }
2362
- /**
2363
- * 冻结单个构件
2364
- */
2365
2633
  freezeByOid(oid) {
2366
- this._frozenOids.add(oid);
2367
- this._syncCollectorMeshes();
2634
+ this._interactionFilter.freezeByOid(oid);
2368
2635
  }
2369
- /**
2370
- * 解冻指定构件
2371
- */
2372
2636
  unfreezeByOids(oids) {
2373
- for (const oid of oids) {
2374
- this._frozenOids.delete(oid);
2375
- }
2376
- this._syncCollectorMeshes();
2637
+ this._interactionFilter.unfreezeByOids(oids);
2377
2638
  }
2378
- /**
2379
- * 解冻单个构件
2380
- */
2381
2639
  unfreezeByOid(oid) {
2382
- this._frozenOids.delete(oid);
2383
- this._syncCollectorMeshes();
2640
+ this._interactionFilter.unfreezeByOid(oid);
2384
2641
  }
2385
- /**
2386
- * 解冻全部构件
2387
- */
2388
2642
  unfreeze() {
2389
- this._frozenOids.clear();
2390
- this._syncCollectorMeshes();
2643
+ this._interactionFilter.unfreeze();
2391
2644
  }
2392
- /**
2393
- * 获取当前被冻结的 OID 数组
2394
- */
2395
2645
  getFrozenOids() {
2396
- return Array.from(this._frozenOids);
2646
+ return this._interactionFilter.getFrozenOids();
2397
2647
  }
2398
- /**
2399
- * 隔离指定构件,隔离模式下只有被隔离的构件才能响应交互和事件
2400
- */
2401
2648
  isolateByOids(oids) {
2402
- for (const oid of oids) {
2403
- this._isolatedOids.add(oid);
2404
- }
2405
- this._syncCollectorMeshes();
2649
+ this._interactionFilter.isolateByOids(oids);
2406
2650
  }
2407
- /**
2408
- * 往隔离集合中添加单个构件
2409
- */
2410
2651
  isolateByOid(oid) {
2411
- this._isolatedOids.add(oid);
2412
- this._syncCollectorMeshes();
2652
+ this._interactionFilter.isolateByOid(oid);
2413
2653
  }
2414
- /**
2415
- * 取消隔离指定构件
2416
- */
2417
2654
  unisolateByOids(oids) {
2418
- for (const oid of oids) {
2419
- this._isolatedOids.delete(oid);
2420
- }
2421
- this._syncCollectorMeshes();
2655
+ this._interactionFilter.unisolateByOids(oids);
2422
2656
  }
2423
- /**
2424
- * 从隔离集合中移除单个构件
2425
- */
2426
2657
  unisolateByOid(oid) {
2427
- this._isolatedOids.delete(oid);
2428
- this._syncCollectorMeshes();
2658
+ this._interactionFilter.unisolateByOid(oid);
2429
2659
  }
2430
- /**
2431
- * 取消全部隔离,恢复所有构件的交互能力
2432
- */
2433
2660
  unisolate() {
2434
- this._isolatedOids.clear();
2435
- this._syncCollectorMeshes();
2661
+ this._interactionFilter.unisolate();
2436
2662
  }
2437
- /**
2438
- * 获取当前被隔离的 OID 数组
2439
- */
2440
2663
  getIsolatedOids() {
2441
- return Array.from(this._isolatedOids);
2664
+ return this._interactionFilter.getIsolatedOids();
2442
2665
  }
2443
2666
  /**
2444
2667
  * 内部方法:根据 oid 获取 mesh 数组
@@ -2469,9 +2692,9 @@ class GLTFParserPlugin {
2469
2692
  }
2470
2693
  const collector = new MeshCollector(oid, this);
2471
2694
  this.collectorCache.set(oid, collector);
2472
- this._onCollectorMeshChange(oid, collector.meshes);
2695
+ this._interactionFilter.onCollectorMeshChange(oid, collector.meshes);
2473
2696
  collector.addEventListener("mesh-change", (event) => {
2474
- this._onCollectorMeshChange(oid, event.meshes);
2697
+ this._interactionFilter.onCollectorMeshChange(oid, event.meshes);
2475
2698
  });
2476
2699
  return collector;
2477
2700
  }
@@ -2491,6 +2714,38 @@ class GLTFParserPlugin {
2491
2714
  this.oids = newOids;
2492
2715
  this.featureIdCount = this._calculateFeatureIdCount();
2493
2716
  }
2717
+ /**
2718
+ * 根据 oid 数组设置构件颜色
2719
+ * 隐藏原 mesh,将 split mesh 替换材质后加入场景(使用 tiles.group)
2720
+ * @param oids 构件 OID 数组
2721
+ * @param color 颜色值,支持 hex 数字、颜色字符串(如 "#ff0000")、THREE.Color 对象
2722
+ */
2723
+ setComponentColorByOids(oids, color) {
2724
+ this._componentColorHelper?.setComponentColorByOids(oids, color);
2725
+ }
2726
+ /**
2727
+ * 恢复指定构件的颜色
2728
+ * 从场景移除 split mesh,unhide 原 mesh
2729
+ * @param oids 构件 OID 数组
2730
+ */
2731
+ restoreComponentColorByOids(oids) {
2732
+ this._componentColorHelper?.restoreComponentColorByOids(oids);
2733
+ }
2734
+ /**
2735
+ * 根据 oid 数组设置构件透明度
2736
+ * @param oids 构件 OID 数组
2737
+ * @param opacity 透明度,0-1,0 完全透明,1 完全不透明
2738
+ */
2739
+ setComponentOpacityByOids(oids, opacity) {
2740
+ this._componentColorHelper?.setComponentOpacityByOids(oids, opacity);
2741
+ }
2742
+ /**
2743
+ * 恢复指定构件的透明度
2744
+ * @param oids 构件 OID 数组
2745
+ */
2746
+ restoreComponentOpacityByOids(oids) {
2747
+ this._componentColorHelper?.restoreComponentOpacityByOids(oids);
2748
+ }
2494
2749
  /**
2495
2750
  * Restore the original materials of the mesh
2496
2751
  */
@@ -2527,15 +2782,8 @@ class GLTFParserPlugin {
2527
2782
  this._structurePromise = null;
2528
2783
  this._modelInfo = null;
2529
2784
  this._modelInfoPromise = null;
2530
- for (const [, meshSet] of this._trackedMeshes) {
2531
- for (const mesh of meshSet) {
2532
- this._untrackMesh(mesh);
2533
- }
2534
- }
2535
- this._trackedMeshes.clear();
2536
- this._meshListeners.clear();
2537
- this._frozenOids.clear();
2538
- this._isolatedOids.clear();
2785
+ this._interactionFilter.dispose();
2786
+ this._componentColorHelper = null;
2539
2787
  this._loader = null;
2540
2788
  this.tiles = null;
2541
2789
  }