@sequent-org/ifc-viewer 1.2.4-ci.40.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 +1 -1
- package/src/ifc/IfcService.js +1 -1
- package/src/viewer/Viewer.js +166 -5
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.
|
|
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",
|
package/src/ifc/IfcService.js
CHANGED
|
@@ -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,
|
package/src/viewer/Viewer.js
CHANGED
|
@@ -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 =
|
|
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 });
|
|
@@ -1679,10 +1695,12 @@ export class Viewer {
|
|
|
1679
1695
|
this.activeModel.traverse?.((node) => {
|
|
1680
1696
|
if (!node?.isMesh) return;
|
|
1681
1697
|
node.castShadow = next;
|
|
1682
|
-
//
|
|
1683
|
-
node.receiveShadow =
|
|
1698
|
+
// Самозатенение: в тест-пресете ИЛИ при активном сечении
|
|
1699
|
+
node.receiveShadow = this.#getModelReceiveShadowEnabled();
|
|
1684
1700
|
});
|
|
1685
1701
|
}
|
|
1702
|
+
// Если тени переключили — синхронизируем shadow-pass клиппинга
|
|
1703
|
+
this.#applyClipShadowsToModelMaterials();
|
|
1686
1704
|
}
|
|
1687
1705
|
|
|
1688
1706
|
/**
|
|
@@ -1847,9 +1865,141 @@ export class Viewer {
|
|
|
1847
1865
|
this.activeModel.traverse?.((node) => {
|
|
1848
1866
|
if (!node?.isMesh) return;
|
|
1849
1867
|
node.castShadow = !!this.shadowsEnabled;
|
|
1850
|
-
node.receiveShadow =
|
|
1868
|
+
node.receiveShadow = this.#getModelReceiveShadowEnabled();
|
|
1851
1869
|
});
|
|
1852
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 (_) {}
|
|
1853
2003
|
}
|
|
1854
2004
|
|
|
1855
2005
|
/**
|
|
@@ -2722,6 +2872,14 @@ export class Viewer {
|
|
|
2722
2872
|
this._step4Pass.enabled = !!this._step4?.enabled;
|
|
2723
2873
|
this._composer.addPass(this._step4Pass);
|
|
2724
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
|
+
|
|
2725
2883
|
try { this._composer.setSize(w, h); } catch (_) {}
|
|
2726
2884
|
}
|
|
2727
2885
|
|
|
@@ -3020,6 +3178,9 @@ export class Viewer {
|
|
|
3020
3178
|
plane.constant = Infinity;
|
|
3021
3179
|
this.#setGizmoVisible(axis, false);
|
|
3022
3180
|
}
|
|
3181
|
+
|
|
3182
|
+
// Сечение → тени/освещение
|
|
3183
|
+
this.#syncSectionClippingState();
|
|
3023
3184
|
}
|
|
3024
3185
|
|
|
3025
3186
|
// Устанавливает позицию секущей плоскости по нормализованному значению [0..1] в пределах габаритов модели
|