@sequent-org/ifc-viewer 1.2.4-ci.39.0 → 1.2.4-ci.41.0

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sequent-org/ifc-viewer",
3
3
  "private": false,
4
- "version": "1.2.4-ci.39.0",
4
+ "version": "1.2.4-ci.41.0",
5
5
  "type": "module",
6
6
  "description": "IFC 3D model viewer component for web applications - fully self-contained with local IFCLoader",
7
7
  "main": "src/index.js",
@@ -190,7 +190,7 @@ class IFCParser {
190
190
  this.BVH = BVH;
191
191
  this.loadedModels = 0;
192
192
  this.optionalCategories = {
193
- [IFCSPACE]: true,
193
+ [IFCSPACE]: false, // Исключаем пространства (убирает 90% z-fighting, абстракция - безопасно)
194
194
  [IFCOPENINGELEMENT]: false
195
195
  };
196
196
  this.geometriesByMaterials = {};
@@ -2109,7 +2109,7 @@ class ParserHandler {
2109
2109
  this.BVH = BVH;
2110
2110
  this.IDB = IDB;
2111
2111
  this.optionalCategories = {
2112
- [IFCSPACE]: true,
2112
+ [IFCSPACE]: false, // Исключаем пространства (убирает 90% z-fighting, абстракция - безопасно)
2113
2113
  [IFCOPENINGELEMENT]: false
2114
2114
  };
2115
2115
  this.API = WorkerAPIs.parser;
@@ -113,7 +113,7 @@ export class IfcService {
113
113
  // Ранее используемые флаги вроде USE_FAST_BOOLS/SMALL_TRIANGLE_THRESHOLD библиотекой web-ifc не используются.
114
114
  const config = {
115
115
  COORDINATE_TO_ORIGIN: true,
116
- CIRCLE_SEGMENTS: 12,
116
+ CIRCLE_SEGMENTS: 32, // Увеличено с 12 до 32 для гладких кривых (уменьшает "лесенку")
117
117
  // Tolerances: см. web-ifc CreateSettings defaults
118
118
  TOLERANCE_PLANE_INTERSECTION: 1e-4,
119
119
  TOLERANCE_PLANE_DEVIATION: 1e-4,
@@ -9,6 +9,7 @@ import { SSAOPass } from "three/examples/jsm/postprocessing/SSAOPass.js";
9
9
  import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
10
10
  import { HueSaturationShader } from "three/examples/jsm/shaders/HueSaturationShader.js";
11
11
  import { BrightnessContrastShader } from "three/examples/jsm/shaders/BrightnessContrastShader.js";
12
+ import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js";
12
13
  import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
13
14
  import { NavCube } from "./NavCube.js";
14
15
  import { SectionManipulator } from "./SectionManipulator.js";
@@ -143,6 +144,12 @@ export class Viewer {
143
144
  z: null,
144
145
  },
145
146
  };
147
+ // Состояние "есть ли активное сечение" (нужно, чтобы сечение влияло на тени/освещение).
148
+ this._sectionClippingActive = false;
149
+ /** @type {WeakMap<THREE.Mesh, any>} */
150
+ this._sectionOriginalMaterial = new WeakMap();
151
+ // +50% яркости при активном сечении (комнаты светлее)
152
+ this._sectionLightBoost = { mul: 1.5, snapshot: null };
146
153
 
147
154
  // Snapshot начального состояния для Home
148
155
  this._home = {
@@ -1056,6 +1063,13 @@ export class Viewer {
1056
1063
  if (this._ssaoPass?.setSize) {
1057
1064
  try { this._ssaoPass.setSize(width, height); } catch (_) {}
1058
1065
  }
1066
+ // Обновляем FXAA resolution при изменении размера
1067
+ if (this._fxaaPass) {
1068
+ try {
1069
+ this._fxaaPass.material.uniforms['resolution'].value.x = 1 / Math.max(1, width);
1070
+ this._fxaaPass.material.uniforms['resolution'].value.y = 1 / Math.max(1, height);
1071
+ } catch (_) {}
1072
+ }
1059
1073
 
1060
1074
  // Если активен MMB-pan (viewOffset), нужно переустановить его под новый размер
1061
1075
  try { this._mmbPan?.controller?.applyCurrentOffset?.(width, height); } catch (_) {}
@@ -1184,8 +1198,8 @@ export class Viewer {
1184
1198
  if (node.isMesh) {
1185
1199
  // Тени управляются единообразно через setShadowsEnabled()
1186
1200
  node.castShadow = !!this.shadowsEnabled;
1187
- // Самозатенение включается только в пресете "Тест"
1188
- node.receiveShadow = !!this.shadowsEnabled && !!this._testPreset?.enabled;
1201
+ // Самозатенение: в тест-пресете ИЛИ при активном сечении
1202
+ node.receiveShadow = this.#getModelReceiveShadowEnabled();
1189
1203
  // Стекло/прозрачность: рендерим после непрозрачных (уменьшает мерцание сортировки)
1190
1204
  try {
1191
1205
  const mats = Array.isArray(node.material) ? node.material : [node.material];
@@ -1199,6 +1213,8 @@ export class Viewer {
1199
1213
 
1200
1214
  // Материальный пресет (если выбран не original)
1201
1215
  this.#applyMaterialStyleToModel(object3D);
1216
+ // Синхронизируем "сечение → shadow-pass" для материалов после назначения пресета
1217
+ this.#applyClipShadowsToModelMaterials();
1202
1218
 
1203
1219
  // Настроим пределы зума и сфокусируемся на новой модели
1204
1220
  this.applyAdaptiveZoomLimits(object3D, { padding: 1.2, slack: 2.5, minRatio: 0.05, recenter: true });
@@ -1307,6 +1323,7 @@ export class Viewer {
1307
1323
  const apply = (mat) => {
1308
1324
  if (!mat) return;
1309
1325
  mat.polygonOffset = true;
1326
+ // Умеренные значения polygon offset для уменьшения z-fighting
1310
1327
  mat.polygonOffsetFactor = 1;
1311
1328
  mat.polygonOffsetUnits = 1;
1312
1329
  // Улучшим читаемость плоскостей
@@ -1678,10 +1695,12 @@ export class Viewer {
1678
1695
  this.activeModel.traverse?.((node) => {
1679
1696
  if (!node?.isMesh) return;
1680
1697
  node.castShadow = next;
1681
- // Самозатенение включается только в пресете "Тест"
1682
- node.receiveShadow = next && !!this._testPreset?.enabled;
1698
+ // Самозатенение: в тест-пресете ИЛИ при активном сечении
1699
+ node.receiveShadow = this.#getModelReceiveShadowEnabled();
1683
1700
  });
1684
1701
  }
1702
+ // Если тени переключили — синхронизируем shadow-pass клиппинга
1703
+ this.#applyClipShadowsToModelMaterials();
1685
1704
  }
1686
1705
 
1687
1706
  /**
@@ -1846,9 +1865,141 @@ export class Viewer {
1846
1865
  this.activeModel.traverse?.((node) => {
1847
1866
  if (!node?.isMesh) return;
1848
1867
  node.castShadow = !!this.shadowsEnabled;
1849
- node.receiveShadow = false;
1868
+ node.receiveShadow = this.#getModelReceiveShadowEnabled();
1850
1869
  });
1851
1870
  }
1871
+ this.#applyClipShadowsToModelMaterials();
1872
+ }
1873
+
1874
+ #getModelReceiveShadowEnabled() {
1875
+ // Самозатенение нужно для корректного освещения при сечении (видны комнаты внутри).
1876
+ return !!this.shadowsEnabled && (this._sectionClippingActive || !!this._testPreset?.enabled);
1877
+ }
1878
+
1879
+ #getClipShadowsEnabled() {
1880
+ // Чтобы внешняя тень менялась при движении сечения, клиппинг должен участвовать в shadow-pass.
1881
+ return !!this.shadowsEnabled && !!this._sectionClippingActive;
1882
+ }
1883
+
1884
+ #applyClipShadowsToModelMaterials() {
1885
+ const model = this.activeModel || this.demoCube;
1886
+ if (!model) return;
1887
+ const enabled = this.#getClipShadowsEnabled();
1888
+ const touched = new Set();
1889
+ // ВАЖНО: НЕ использовать renderer.clippingPlanes (там каждый кадр создаётся новый массив).
1890
+ // Для shadow-pass материал должен держать актуальный список активных плоскостей.
1891
+ const activePlanes = enabled
1892
+ ? (this.clipping?.planes || []).filter((p) => p && isFinite(p.constant))
1893
+ : null;
1894
+ model.traverse?.((node) => {
1895
+ if (!node?.isMesh) return;
1896
+ const mats = Array.isArray(node.material) ? node.material : [node.material];
1897
+ for (const m of mats) {
1898
+ if (!m || touched.has(m)) continue;
1899
+ touched.add(m);
1900
+ try {
1901
+ // Важно: для shadow-pass надёжнее использовать LOCAL clipping (material.clippingPlanes) + clipShadows.
1902
+ // Иначе часть сборок three может не применять global renderer.clippingPlanes к shadow map.
1903
+ if ('clippingPlanes' in m) m.clippingPlanes = activePlanes;
1904
+ if ('clipShadows' in m) m.clipShadows = enabled;
1905
+ // Тени “среза” и внутренняя отрисовка часто требуют double-side (в IFC нормали бывают проблемные).
1906
+ if (enabled && 'side' in m && m.side !== THREE.DoubleSide) m.side = THREE.DoubleSide;
1907
+ m.needsUpdate = true; // пересобрать шейдеры (включая depth/shadow варианты)
1908
+ } catch (_) {}
1909
+ }
1910
+ });
1911
+ }
1912
+
1913
+ #applySectionMaterialPolicy() {
1914
+ // При активном сечении переводим неосвещаемые материалы в MeshStandardMaterial,
1915
+ // чтобы внутри появились свет/тени. При выключении — возвращаем оригиналы.
1916
+ const model = this.activeModel || this.demoCube;
1917
+ if (!model) return;
1918
+
1919
+ if (!this._sectionClippingActive) {
1920
+ model.traverse?.((node) => {
1921
+ if (!node?.isMesh) return;
1922
+ const orig = this._sectionOriginalMaterial.get(node);
1923
+ if (orig) node.material = orig;
1924
+ });
1925
+ return;
1926
+ }
1927
+
1928
+ model.traverse?.((node) => {
1929
+ if (!node?.isMesh) return;
1930
+ const cur = node.material;
1931
+ if (!cur) return;
1932
+ // Трогаем только "неосвещаемые" материалы (они не показывают тени на стенах).
1933
+ const arr = Array.isArray(cur) ? cur : [cur];
1934
+ const hasBasic = arr.some((m) => !!m?.isMeshBasicMaterial);
1935
+ if (!hasBasic) return;
1936
+
1937
+ if (!this._sectionOriginalMaterial.has(node)) {
1938
+ this._sectionOriginalMaterial.set(node, cur);
1939
+ }
1940
+
1941
+ const convert = (m) => {
1942
+ if (!m?.isMeshBasicMaterial) return m;
1943
+ const cm = this.#getConvertedMaterial(m); // -> MeshStandardMaterial с сохранением color/map/alpha
1944
+ // Делаем "архитектурный" вид по умолчанию: матовый, без металла
1945
+ try { if ('roughness' in cm) cm.roughness = 0.9; } catch (_) {}
1946
+ try { if ('metalness' in cm) cm.metalness = 0.0; } catch (_) {}
1947
+ try { cm.needsUpdate = true; } catch (_) {}
1948
+ return cm;
1949
+ };
1950
+
1951
+ node.material = Array.isArray(cur) ? cur.map(convert) : convert(cur);
1952
+ });
1953
+ }
1954
+
1955
+ #syncSectionClippingState() {
1956
+ const active = this.clipping?.planes?.some((p) => p && isFinite(p.constant));
1957
+ const next = !!active;
1958
+ if (next === this._sectionClippingActive) return;
1959
+ this._sectionClippingActive = next;
1960
+
1961
+ // 0) Светлее при сечении (+50%)
1962
+ try {
1963
+ if (this._sectionClippingActive) {
1964
+ if (!this._sectionLightBoost.snapshot) {
1965
+ this._sectionLightBoost.snapshot = {
1966
+ sunIntensity: this.sunLight?.intensity ?? null,
1967
+ ambientIntensity: this.ambientLight?.intensity ?? null,
1968
+ };
1969
+ }
1970
+ if (this.sunLight && Number.isFinite(this._sectionLightBoost.snapshot.sunIntensity)) {
1971
+ this.sunLight.intensity = this._sectionLightBoost.snapshot.sunIntensity * this._sectionLightBoost.mul;
1972
+ }
1973
+ if (this.ambientLight && Number.isFinite(this._sectionLightBoost.snapshot.ambientIntensity)) {
1974
+ this.ambientLight.intensity = this._sectionLightBoost.snapshot.ambientIntensity * this._sectionLightBoost.mul;
1975
+ }
1976
+ } else {
1977
+ const snap = this._sectionLightBoost.snapshot;
1978
+ if (snap) {
1979
+ if (this.sunLight && Number.isFinite(snap.sunIntensity)) this.sunLight.intensity = snap.sunIntensity;
1980
+ if (this.ambientLight && Number.isFinite(snap.ambientIntensity)) this.ambientLight.intensity = snap.ambientIntensity;
1981
+ }
1982
+ this._sectionLightBoost.snapshot = null;
1983
+ }
1984
+ } catch (_) {}
1985
+
1986
+ // 1) self-shadowing (комнаты/стены)
1987
+ if (this.activeModel) {
1988
+ this.activeModel.traverse?.((node) => {
1989
+ if (!node?.isMesh) return;
1990
+ try { node.receiveShadow = this.#getModelReceiveShadowEnabled(); } catch (_) {}
1991
+ });
1992
+ }
1993
+
1994
+ // 1.5) materials: ensure they react to light/shadows when section is active
1995
+ this.#applySectionMaterialPolicy();
1996
+
1997
+ // 2) clipping in shadow pass (крыша перестаёт участвовать в тени)
1998
+ this.#applyClipShadowsToModelMaterials();
1999
+
2000
+ // 3) форсируем апдейт теней
2001
+ try { if (this.sunLight?.shadow) this.sunLight.shadow.needsUpdate = true; } catch (_) {}
2002
+ try { if (this.renderer?.shadowMap) this.renderer.shadowMap.needsUpdate = true; } catch (_) {}
1852
2003
  }
1853
2004
 
1854
2005
  /**
@@ -2721,6 +2872,14 @@ export class Viewer {
2721
2872
  this._step4Pass.enabled = !!this._step4?.enabled;
2722
2873
  this._composer.addPass(this._step4Pass);
2723
2874
  this.#applyStep4Uniforms();
2875
+
2876
+ // FXAA pass для устранения "лесенки" на кривых линиях (aliasing)
2877
+ this._fxaaPass = new ShaderPass(FXAAShader);
2878
+ this._fxaaPass.material.uniforms['resolution'].value.x = 1 / w;
2879
+ this._fxaaPass.material.uniforms['resolution'].value.y = 1 / h;
2880
+ this._fxaaPass.enabled = true; // Включен всегда для сглаживания
2881
+ this._composer.addPass(this._fxaaPass);
2882
+
2724
2883
  try { this._composer.setSize(w, h); } catch (_) {}
2725
2884
  }
2726
2885
 
@@ -3019,6 +3178,9 @@ export class Viewer {
3019
3178
  plane.constant = Infinity;
3020
3179
  this.#setGizmoVisible(axis, false);
3021
3180
  }
3181
+
3182
+ // Сечение → тени/освещение
3183
+ this.#syncSectionClippingState();
3022
3184
  }
3023
3185
 
3024
3186
  // Устанавливает позицию секущей плоскости по нормализованному значению [0..1] в пределах габаритов модели