@sequent-org/ifc-viewer 1.2.4-ci.30.0 → 1.2.4-ci.32.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/IfcViewer.js +33 -15
- package/src/main.js +261 -471
- package/src/viewer/Viewer.js +309 -3
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.32.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/IfcViewer.js
CHANGED
|
@@ -121,6 +121,15 @@ export class IfcViewer {
|
|
|
121
121
|
// Важно: пресет должен примениться ДО загрузки модели, чтобы настройки подхватились при replaceWithModel()
|
|
122
122
|
if (this.options.useTestPreset && this.viewer?.setTestPresetEnabled) {
|
|
123
123
|
this.viewer.setTestPresetEnabled(true);
|
|
124
|
+
// Дефолты пакета (подобранные значения для Autodesk-like вида)
|
|
125
|
+
// В пакете применяются всегда при включённом useTestPreset (по умолчанию true).
|
|
126
|
+
try { this.viewer.setExposure?.(1.19); } catch (_) {}
|
|
127
|
+
try { this.viewer.setCoolLightingEnabled?.(true); } catch (_) {}
|
|
128
|
+
try { this.viewer.setCoolLightingHue?.(240); } catch (_) {}
|
|
129
|
+
try { this.viewer.setCoolLightingAmount?.(1.00); } catch (_) {}
|
|
130
|
+
try { this.viewer.setStep4Enabled?.(true); } catch (_) {}
|
|
131
|
+
try { this.viewer.setStep4Contrast?.(1.35); } catch (_) {}
|
|
132
|
+
try { this.viewer.setStep4Saturation?.(1.60); } catch (_) {}
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
// Настраиваем обработчики событий
|
|
@@ -317,17 +326,17 @@ export class IfcViewer {
|
|
|
317
326
|
</button>
|
|
318
327
|
<button class="btn btn-sm join-item" id="ifcToggleProjection" title="Перспектива / Ортогонально (переключение)">
|
|
319
328
|
<!-- По умолчанию Ortho, поэтому показываем действие: включить Perspective -->
|
|
320
|
-
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
|
321
|
-
<path
|
|
322
|
-
<path
|
|
323
|
-
<path
|
|
324
|
-
<path
|
|
325
|
-
<path
|
|
326
|
-
<path
|
|
327
|
-
<path
|
|
328
|
-
<path
|
|
329
|
-
<path
|
|
330
|
-
<path
|
|
329
|
+
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="currentColor" style="color:#252A3F">
|
|
330
|
+
<path d="M 365.50 333.29 A 0.30 0.30 0.0 0 0 365.95 333.55 L 492.36 259.80 A 0.47 0.47 0.0 0 0 492.51 259.12 Q 489.74 255.31 492.90 252.78 A 0.30 0.30 0.0 0 0 492.83 252.27 C 489.14 250.57 490.13 245.43 493.90 244.50 C 496.33 243.90 501.93 247.88 504.97 249.79 A 1.50 1.48 -85.3 0 1 505.54 250.47 L 505.97 251.53 A 0.72 0.71 76.6 0 0 506.67 251.97 C 509.70 251.84 512.28 254.84 511.15 257.67 Q 510.77 258.62 508.18 260.14 C 355.38 349.68 251.70 410.06 149.28 469.74 A 3.94 3.93 -44.9 0 1 145.31 469.74 Q 7.70 389.45 2.96 386.69 C 0.09 385.02 0.50 382.93 0.50 379.49 Q 0.50 259.79 0.50 128.77 C 0.50 127.21 1.85 125.96 3.27 125.13 Q 68.02 87.24 145.61 41.87 C 146.90 41.11 148.92 41.81 150.33 42.63 Q 219.34 82.64 289.83 124.16 C 291.25 125.00 292.80 126.11 294.76 127.15 Q 299.89 129.89 301.84 131.37 C 305.49 134.15 301.99 140.40 297.26 138.18 Q 295.67 137.42 294.41 136.58 A 0.26 0.26 0.0 0 0 294.00 136.80 L 294.00 209.83 A 0.44 0.44 0.0 0 0 294.36 210.26 Q 340.50 219.23 361.26 223.22 C 366.12 224.15 365.53 227.44 365.51 232.03 Q 365.50 234.52 365.49 251.11 A 0.73 0.73 0.0 0 0 366.22 251.84 L 370.02 251.84 A 3.64 3.64 0.0 0 1 373.66 255.48 L 373.66 256.72 A 3.45 3.44 0.0 0 1 370.21 260.16 L 366.15 260.16 A 0.65 0.65 0.0 0 0 365.50 260.81 L 365.50 333.29 Z M 9.05 131.40 A 0.30 0.30 0.0 0 0 8.90 131.66 L 8.90 380.18 A 0.30 0.30 0.0 0 0 9.05 380.44 L 142.74 458.43 A 0.30 0.30 0.0 0 0 143.19 458.17 L 143.19 53.67 A 0.30 0.30 0.0 0 0 142.74 53.41 L 9.05 131.40 Z M 285.68 380.52 A 0.32 0.32 0.0 0 0 285.84 380.25 L 285.84 131.66 A 0.32 0.32 0.0 0 0 285.68 131.39 L 151.98 53.39 A 0.32 0.32 0.0 0 0 151.50 53.67 L 151.50 458.24 A 0.32 0.32 0.0 0 0 151.98 458.52 L 285.68 380.52 Z M 294.62 218.77 A 0.36 0.36 0.0 0 0 294.19 219.13 L 294.19 374.90 A 0.36 0.36 0.0 0 0 294.73 375.21 L 357.13 338.81 A 0.36 0.36 0.0 0 0 357.31 338.50 L 357.31 231.30 A 0.36 0.36 0.0 0 0 357.02 230.94 L 294.62 218.77 Z"/>
|
|
331
|
+
<path d="M 331.8028 153.6467 A 4.00 4.00 0.0 0 1 326.3286 155.0726 L 318.9110 150.7207 A 4.00 4.00 0.0 0 1 317.4851 145.2465 L 317.6572 144.9533 A 4.00 4.00 0.0 0 1 323.1314 143.5274 L 330.5490 147.8793 A 4.00 4.00 0.0 0 1 331.9749 153.3535 L 331.8028 153.6467 Z"/>
|
|
332
|
+
<path d="M 360.6890 170.5463 A 4.00 4.00 0.0 0 1 355.2099 171.9531 L 347.8247 167.5855 A 4.00 4.00 0.0 0 1 346.4179 162.1064 L 346.5910 161.8137 A 4.00 4.00 0.0 0 1 352.0701 160.4069 L 359.4553 164.7745 A 4.00 4.00 0.0 0 1 360.8621 170.2536 L 360.6890 170.5463 Z"/>
|
|
333
|
+
<path d="M 389.5811 187.4643 A 3.99 3.99 0.0 0 1 384.1181 188.8771 L 376.8287 184.5833 A 3.99 3.99 0.0 0 1 375.4159 179.1204 L 375.6189 178.7757 A 3.99 3.99 0.0 0 1 381.0819 177.3629 L 388.3713 181.6567 A 3.99 3.99 0.0 0 1 389.7841 187.1196 L 389.5811 187.4643 Z"/>
|
|
334
|
+
<path d="M 418.5914 204.3586 A 3.99 3.99 0.0 0 1 413.1235 205.7523 L 405.7288 201.3617 A 3.99 3.99 0.0 0 1 404.3350 195.8938 L 404.5086 195.6014 A 3.99 3.99 0.0 0 1 409.9765 194.2077 L 417.3712 198.5983 A 3.99 3.99 0.0 0 1 418.7650 204.0662 L 418.5914 204.3586 Z"/>
|
|
335
|
+
<path d="M 447.6480 221.1624 A 3.99 3.99 0.0 0 1 442.2027 222.6419 L 434.7225 218.3579 A 3.99 3.99 0.0 0 1 433.2431 212.9126 L 433.4120 212.6176 A 3.99 3.99 0.0 0 1 438.8573 211.1381 L 446.3375 215.4221 A 3.99 3.99 0.0 0 1 447.8169 220.8674 L 447.6480 221.1624 Z"/>
|
|
336
|
+
<path d="M 476.5002 238.1477 A 3.99 3.99 0.0 0 1 471.0372 239.5605 L 463.6099 235.1855 A 3.99 3.99 0.0 0 1 462.1971 229.7225 L 462.3798 229.4123 A 3.99 3.99 0.0 0 1 467.8428 227.9995 L 475.2701 232.3745 A 3.99 3.99 0.0 0 1 476.6829 237.8375 L 476.5002 238.1477 Z"/>
|
|
337
|
+
<path d="M 407.4604 256.3255 A 3.98 3.98 0.0 0 1 403.4873 260.3125 L 394.8874 260.3275 A 3.98 3.98 0.0 0 1 390.9004 256.3545 L 390.8996 255.8945 A 3.98 3.98 0.0 0 1 394.8727 251.9075 L 403.4726 251.8925 A 3.98 3.98 0.0 0 1 407.4596 255.8655 L 407.4604 256.3255 Z"/>
|
|
338
|
+
<path d="M 440.9596 256.3545 A 3.98 3.98 0.0 0 1 436.9726 260.3275 L 428.3727 260.3125 A 3.98 3.98 0.0 0 1 424.3996 256.3255 L 424.4004 255.8655 A 3.98 3.98 0.0 0 1 428.3874 251.8925 L 436.9873 251.9075 A 3.98 3.98 0.0 0 1 440.9604 255.8945 L 440.9596 256.3545 Z"/>
|
|
339
|
+
<path d="M 474.4604 256.3255 A 3.98 3.98 0.0 0 1 470.4873 260.3125 L 461.8874 260.3275 A 3.98 3.98 0.0 0 1 457.9004 256.3545 L 457.8996 255.8945 A 3.98 3.98 0.0 0 1 461.8727 251.9075 L 470.4726 251.8925 A 3.98 3.98 0.0 0 1 474.4596 255.8655 L 474.4604 256.3255 Z"/>
|
|
331
340
|
</svg>
|
|
332
341
|
</button>
|
|
333
342
|
</div>
|
|
@@ -730,13 +739,22 @@ export class IfcViewer {
|
|
|
730
739
|
|
|
731
740
|
// Иконки: показываем альтернативный режим
|
|
732
741
|
const ICON_PERSPECTIVE = `
|
|
733
|
-
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
|
734
|
-
<path
|
|
742
|
+
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="currentColor" style="color:#252A3F">
|
|
743
|
+
<path d="M 365.50 333.29 A 0.30 0.30 0.0 0 0 365.95 333.55 L 492.36 259.80 A 0.47 0.47 0.0 0 0 492.51 259.12 Q 489.74 255.31 492.90 252.78 A 0.30 0.30 0.0 0 0 492.83 252.27 C 489.14 250.57 490.13 245.43 493.90 244.50 C 496.33 243.90 501.93 247.88 504.97 249.79 A 1.50 1.48 -85.3 0 1 505.54 250.47 L 505.97 251.53 A 0.72 0.71 76.6 0 0 506.67 251.97 C 509.70 251.84 512.28 254.84 511.15 257.67 Q 510.77 258.62 508.18 260.14 C 355.38 349.68 251.70 410.06 149.28 469.74 A 3.94 3.93 -44.9 0 1 145.31 469.74 Q 7.70 389.45 2.96 386.69 C 0.09 385.02 0.50 382.93 0.50 379.49 Q 0.50 259.79 0.50 128.77 C 0.50 127.21 1.85 125.96 3.27 125.13 Q 68.02 87.24 145.61 41.87 C 146.90 41.11 148.92 41.81 150.33 42.63 Q 219.34 82.64 289.83 124.16 C 291.25 125.00 292.80 126.11 294.76 127.15 Q 299.89 129.89 301.84 131.37 C 305.49 134.15 301.99 140.40 297.26 138.18 Q 295.67 137.42 294.41 136.58 A 0.26 0.26 0.0 0 0 294.00 136.80 L 294.00 209.83 A 0.44 0.44 0.0 0 0 294.36 210.26 Q 340.50 219.23 361.26 223.22 C 366.12 224.15 365.53 227.44 365.51 232.03 Q 365.50 234.52 365.49 251.11 A 0.73 0.73 0.0 0 0 366.22 251.84 L 370.02 251.84 A 3.64 3.64 0.0 0 1 373.66 255.48 L 373.66 256.72 A 3.45 3.44 0.0 0 1 370.21 260.16 L 366.15 260.16 A 0.65 0.65 0.0 0 0 365.50 260.81 L 365.50 333.29 Z M 9.05 131.40 A 0.30 0.30 0.0 0 0 8.90 131.66 L 8.90 380.18 A 0.30 0.30 0.0 0 0 9.05 380.44 L 142.74 458.43 A 0.30 0.30 0.0 0 0 143.19 458.17 L 143.19 53.67 A 0.30 0.30 0.0 0 0 142.74 53.41 L 9.05 131.40 Z M 285.68 380.52 A 0.32 0.32 0.0 0 0 285.84 380.25 L 285.84 131.66 A 0.32 0.32 0.0 0 0 285.68 131.39 L 151.98 53.39 A 0.32 0.32 0.0 0 0 151.50 53.67 L 151.50 458.24 A 0.32 0.32 0.0 0 0 151.98 458.52 L 285.68 380.52 Z M 294.62 218.77 A 0.36 0.36 0.0 0 0 294.19 219.13 L 294.19 374.90 A 0.36 0.36 0.0 0 0 294.73 375.21 L 357.13 338.81 A 0.36 0.36 0.0 0 0 357.31 338.50 L 357.31 231.30 A 0.36 0.36 0.0 0 0 357.02 230.94 L 294.62 218.77 Z"/>
|
|
744
|
+
<path d="M 331.8028 153.6467 A 4.00 4.00 0.0 0 1 326.3286 155.0726 L 318.9110 150.7207 A 4.00 4.00 0.0 0 1 317.4851 145.2465 L 317.6572 144.9533 A 4.00 4.00 0.0 0 1 323.1314 143.5274 L 330.5490 147.8793 A 4.00 4.00 0.0 0 1 331.9749 153.3535 L 331.8028 153.6467 Z"/>
|
|
745
|
+
<path d="M 360.6890 170.5463 A 4.00 4.00 0.0 0 1 355.2099 171.9531 L 347.8247 167.5855 A 4.00 4.00 0.0 0 1 346.4179 162.1064 L 346.5910 161.8137 A 4.00 4.00 0.0 0 1 352.0701 160.4069 L 359.4553 164.7745 A 4.00 4.00 0.0 0 1 360.8621 170.2536 L 360.6890 170.5463 Z"/>
|
|
746
|
+
<path d="M 389.5811 187.4643 A 3.99 3.99 0.0 0 1 384.1181 188.8771 L 376.8287 184.5833 A 3.99 3.99 0.0 0 1 375.4159 179.1204 L 375.6189 178.7757 A 3.99 3.99 0.0 0 1 381.0819 177.3629 L 388.3713 181.6567 A 3.99 3.99 0.0 0 1 389.7841 187.1196 L 389.5811 187.4643 Z"/>
|
|
747
|
+
<path d="M 418.5914 204.3586 A 3.99 3.99 0.0 0 1 413.1235 205.7523 L 405.7288 201.3617 A 3.99 3.99 0.0 0 1 404.3350 195.8938 L 404.5086 195.6014 A 3.99 3.99 0.0 0 1 409.9765 194.2077 L 417.3712 198.5983 A 3.99 3.99 0.0 0 1 418.7650 204.0662 L 418.5914 204.3586 Z"/>
|
|
748
|
+
<path d="M 447.6480 221.1624 A 3.99 3.99 0.0 0 1 442.2027 222.6419 L 434.7225 218.3579 A 3.99 3.99 0.0 0 1 433.2431 212.9126 L 433.4120 212.6176 A 3.99 3.99 0.0 0 1 438.8573 211.1381 L 446.3375 215.4221 A 3.99 3.99 0.0 0 1 447.8169 220.8674 L 447.6480 221.1624 Z"/>
|
|
749
|
+
<path d="M 476.5002 238.1477 A 3.99 3.99 0.0 0 1 471.0372 239.5605 L 463.6099 235.1855 A 3.99 3.99 0.0 0 1 462.1971 229.7225 L 462.3798 229.4123 A 3.99 3.99 0.0 0 1 467.8428 227.9995 L 475.2701 232.3745 A 3.99 3.99 0.0 0 1 476.6829 237.8375 L 476.5002 238.1477 Z"/>
|
|
750
|
+
<path d="M 407.4604 256.3255 A 3.98 3.98 0.0 0 1 403.4873 260.3125 L 394.8874 260.3275 A 3.98 3.98 0.0 0 1 390.9004 256.3545 L 390.8996 255.8945 A 3.98 3.98 0.0 0 1 394.8727 251.9075 L 403.4726 251.8925 A 3.98 3.98 0.0 0 1 407.4596 255.8655 L 407.4604 256.3255 Z"/>
|
|
751
|
+
<path d="M 440.9596 256.3545 A 3.98 3.98 0.0 0 1 436.9726 260.3275 L 428.3727 260.3125 A 3.98 3.98 0.0 0 1 424.3996 256.3255 L 424.4004 255.8655 A 3.98 3.98 0.0 0 1 428.3874 251.8925 L 436.9873 251.9075 A 3.98 3.98 0.0 0 1 440.9604 255.8945 L 440.9596 256.3545 Z"/>
|
|
752
|
+
<path d="M 474.4604 256.3255 A 3.98 3.98 0.0 0 1 470.4873 260.3125 L 461.8874 260.3275 A 3.98 3.98 0.0 0 1 457.9004 256.3545 L 457.8996 255.8945 A 3.98 3.98 0.0 0 1 461.8727 251.9075 L 470.4726 251.8925 A 3.98 3.98 0.0 0 1 474.4596 255.8655 L 474.4604 256.3255 Z"/>
|
|
735
753
|
</svg>
|
|
736
754
|
`;
|
|
737
755
|
const ICON_ORTHO = `
|
|
738
|
-
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
|
739
|
-
<path
|
|
756
|
+
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="currentColor" style="color:#252A3F">
|
|
757
|
+
<path d="M 256.02 48.55 Q 257.33 48.55 258.06 48.94 Q 381.49 115.11 442.91 148.14 Q 445.24 149.39 445.26 152.25 Q 445.52 184.71 445.52 256.00 Q 445.52 327.29 445.26 359.75 Q 445.24 362.61 442.91 363.86 Q 381.49 396.89 258.06 463.06 Q 257.33 463.45 256.02 463.45 Q 254.71 463.45 253.98 463.06 Q 130.55 396.89 69.13 363.86 Q 66.80 362.61 66.78 359.75 Q 66.52 327.29 66.52 256.00 Q 66.52 184.71 66.78 152.25 Q 66.80 149.39 69.13 148.14 Q 130.55 115.11 253.98 48.94 Q 254.71 48.55 256.02 48.55 Z M 256.03 147.56 Q 257.36 147.56 258.05 147.94 Q 295.68 168.96 347.89 198.33 A 0.77 0.75 44.3 0 0 348.62 198.33 L 429.62 152.89 A 0.32 0.32 0.0 0 0 429.62 152.33 Q 332.33 100.05 256.30 59.37 Q 256.25 59.35 256.15 59.34 Q 256.09 59.33 256.02 59.33 Q 255.97 59.33 255.90 59.34 Q 255.81 59.35 255.76 59.37 Q 179.73 100.05 82.44 152.34 A 0.32 0.32 0.0 0 0 82.44 152.90 L 163.44 198.34 A 0.77 0.75 -44.3 0 0 164.17 198.34 Q 216.38 168.96 254.01 147.94 Q 254.70 147.56 256.03 147.56 Z M 255.82 250.17 A 0.38 0.38 0.0 0 0 256.20 250.17 L 337.45 204.58 A 0.38 0.38 0.0 0 0 337.45 203.92 L 256.20 158.33 A 0.38 0.38 0.0 0 0 255.82 158.33 L 174.57 203.92 A 0.38 0.38 0.0 0 0 174.57 204.58 L 255.82 250.17 Z M 76.99 161.29 A 0.33 0.33 0.0 0 0 76.50 161.58 L 76.50 246.92 A 0.33 0.33 0.0 0 0 76.99 247.21 L 153.06 204.54 A 0.33 0.33 0.0 0 0 153.06 203.96 L 76.99 161.29 Z M 434.97 247.14 A 0.35 0.35 0.0 0 0 435.49 246.83 L 435.49 161.67 A 0.35 0.35 0.0 0 0 434.97 161.36 L 359.05 203.94 A 0.35 0.35 0.0 0 0 359.05 204.56 L 434.97 247.14 Z M 245.33 256.28 A 0.32 0.32 0.0 0 0 245.33 255.72 L 163.96 210.07 A 0.32 0.32 0.0 0 0 163.64 210.07 L 82.27 255.72 A 0.32 0.32 0.0 0 0 82.27 256.28 L 163.64 301.93 A 0.32 0.32 0.0 0 0 163.96 301.93 L 245.33 256.28 Z M 429.83 256.28 A 0.32 0.32 0.0 0 0 429.83 255.72 L 348.46 210.07 A 0.32 0.32 0.0 0 0 348.14 210.07 L 266.77 255.72 A 0.32 0.32 0.0 0 0 266.77 256.28 L 348.14 301.93 A 0.32 0.32 0.0 0 0 348.46 301.93 L 429.83 256.28 Z M 337.56 308.04 A 0.33 0.33 0.0 0 0 337.56 307.46 L 256.20 261.82 A 0.33 0.33 0.0 0 0 255.88 261.82 L 174.51 307.46 A 0.33 0.33 0.0 0 0 174.51 308.04 L 255.87 353.68 A 0.33 0.33 0.0 0 0 256.19 353.68 L 337.56 308.04 Z M 76.96 264.77 A 0.31 0.31 0.0 0 0 76.50 265.04 L 76.50 350.46 A 0.31 0.31 0.0 0 0 76.96 350.73 L 153.09 308.02 A 0.31 0.31 0.0 0 0 153.09 307.48 L 76.96 264.77 Z M 434.97 350.63 A 0.35 0.35 0.0 0 0 435.49 350.33 L 435.49 265.17 A 0.35 0.35 0.0 0 0 434.97 264.87 L 359.05 307.44 A 0.35 0.35 0.0 0 0 359.05 308.06 L 434.97 350.63 Z M 256.02 364.45 Q 254.69 364.45 254.00 364.06 Q 216.37 343.04 164.17 313.67 A 0.77 0.75 44.3 0 0 163.44 313.67 L 82.44 359.10 A 0.32 0.32 0.0 0 0 82.44 359.66 Q 179.72 411.94 255.74 452.63 Q 255.79 452.65 255.89 452.66 Q 255.96 452.67 256.00 452.67 Q 256.07 452.67 256.14 452.66 Q 256.24 452.65 256.29 452.63 Q 332.31 411.95 429.60 359.67 A 0.32 0.32 0.0 0 0 429.60 359.11 L 348.60 313.68 A 0.77 0.75 -44.3 0 0 347.87 313.68 Q 295.66 343.04 258.03 364.06 Q 257.35 364.45 256.02 364.45 Z"/>
|
|
740
758
|
</svg>
|
|
741
759
|
`;
|
|
742
760
|
|
package/src/main.js
CHANGED
|
@@ -9,496 +9,277 @@ if (app) {
|
|
|
9
9
|
const viewer = new Viewer(app);
|
|
10
10
|
viewer.init();
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const shadowGradLen = document.getElementById("shadowGradLen");
|
|
16
|
-
const shadowGradLenValue = document.getElementById("shadowGradLenValue");
|
|
17
|
-
const shadowGradStr = document.getElementById("shadowGradStr");
|
|
18
|
-
const shadowGradStrValue = document.getElementById("shadowGradStrValue");
|
|
19
|
-
const shadowGradCurve = document.getElementById("shadowGradCurve");
|
|
20
|
-
const shadowGradCurveValue = document.getElementById("shadowGradCurveValue");
|
|
21
|
-
const shadowOpacity = document.getElementById("shadowOpacity");
|
|
22
|
-
const shadowOpacityValue = document.getElementById("shadowOpacityValue");
|
|
23
|
-
const shadowSoft = document.getElementById("shadowSoft");
|
|
24
|
-
const shadowSoftValue = document.getElementById("shadowSoftValue");
|
|
25
|
-
// Материалы
|
|
26
|
-
const matPreset = document.getElementById("matPreset");
|
|
27
|
-
const matRough = document.getElementById("matRough");
|
|
28
|
-
const matRoughValue = document.getElementById("matRoughValue");
|
|
29
|
-
const matMetal = document.getElementById("matMetal");
|
|
30
|
-
const matMetalValue = document.getElementById("matMetalValue");
|
|
31
|
-
// Визуал (диагностика)
|
|
32
|
-
const testPresetToggle = document.getElementById("testPresetToggle");
|
|
33
|
-
const rtQualityToggle = document.getElementById("rtQualityToggle");
|
|
34
|
-
const envToggle = document.getElementById("envToggle");
|
|
35
|
-
const envInt = document.getElementById("envInt");
|
|
36
|
-
const envIntValue = document.getElementById("envIntValue");
|
|
37
|
-
const toneToggle = document.getElementById("toneToggle");
|
|
38
|
-
const exposure = document.getElementById("exposure");
|
|
39
|
-
const exposureValue = document.getElementById("exposureValue");
|
|
40
|
-
const aoToggle = document.getElementById("aoToggle");
|
|
41
|
-
const aoInt = document.getElementById("aoInt");
|
|
42
|
-
const aoIntValue = document.getElementById("aoIntValue");
|
|
43
|
-
const aoRad = document.getElementById("aoRad");
|
|
44
|
-
const aoRadValue = document.getElementById("aoRadValue");
|
|
45
|
-
const dumpVisual = document.getElementById("dumpVisual");
|
|
46
|
-
// Цветокор
|
|
47
|
-
const ccToggle = document.getElementById("ccToggle");
|
|
48
|
-
const ccHue = document.getElementById("ccHue");
|
|
49
|
-
const ccHueValue = document.getElementById("ccHueValue");
|
|
50
|
-
const ccSat = document.getElementById("ccSat");
|
|
51
|
-
const ccSatValue = document.getElementById("ccSatValue");
|
|
52
|
-
const ccBri = document.getElementById("ccBri");
|
|
53
|
-
const ccBriValue = document.getElementById("ccBriValue");
|
|
54
|
-
const ccCon = document.getElementById("ccCon");
|
|
55
|
-
const ccConValue = document.getElementById("ccConValue");
|
|
56
|
-
|
|
57
|
-
// ===== Test preset ("Тест") - полностью изолированная настройка =====
|
|
58
|
-
const _testSnapshot = new Map();
|
|
59
|
-
const testSnapshotEl = (el) => {
|
|
60
|
-
if (!el) return;
|
|
61
|
-
_testSnapshot.set(el, {
|
|
62
|
-
checked: "checked" in el ? el.checked : undefined,
|
|
63
|
-
value: "value" in el ? el.value : undefined,
|
|
64
|
-
disabled: "disabled" in el ? el.disabled : undefined,
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
const testRestoreEl = (el) => {
|
|
68
|
-
if (!el) return;
|
|
69
|
-
const s = _testSnapshot.get(el);
|
|
70
|
-
if (!s) return;
|
|
71
|
-
if ("checked" in el && typeof s.checked === "boolean") el.checked = s.checked;
|
|
72
|
-
if ("value" in el && typeof s.value === "string") el.value = s.value;
|
|
73
|
-
if ("disabled" in el && typeof s.disabled === "boolean") el.disabled = s.disabled;
|
|
74
|
-
};
|
|
12
|
+
// ===== Левая панель: временно оставляем только "Тест" =====
|
|
13
|
+
// Остальные контролы (тени/солнце/материалы/визуал/цветокор) будем добавлять пошагово позже.
|
|
14
|
+
// (Старый код управления панелью закомментирован ниже для последующего восстановления при необходимости.)
|
|
75
15
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
16
|
+
const testPresetToggle = document.getElementById("testPresetToggle");
|
|
17
|
+
// Шаг 1 (Tone mapping): в текущей версии он входит в пресет "Тест", но exposure можно подстроить.
|
|
18
|
+
const step1ToneToggle = document.getElementById("step1ToneToggle");
|
|
19
|
+
const step1Exposure = document.getElementById("step1Exposure");
|
|
20
|
+
const step1ExposureValue = document.getElementById("step1ExposureValue");
|
|
21
|
+
const step1Dump = document.getElementById("step1Dump");
|
|
22
|
+
// Шаг 2 (свет): холодный оттенок
|
|
23
|
+
const step2CoolLighting = document.getElementById("step2CoolLighting");
|
|
24
|
+
const step2Hue = document.getElementById("step2Hue");
|
|
25
|
+
const step2HueValue = document.getElementById("step2HueValue");
|
|
26
|
+
const step2Blue = document.getElementById("step2Blue");
|
|
27
|
+
const step2BlueValue = document.getElementById("step2BlueValue");
|
|
28
|
+
const step2Dump = document.getElementById("step2Dump");
|
|
29
|
+
// Шаг 3 (фон)
|
|
30
|
+
const step3Background = document.getElementById("step3Background");
|
|
31
|
+
const step3Dump = document.getElementById("step3Dump");
|
|
32
|
+
// Шаг 4 (пост-обработка: контраст + насыщенность)
|
|
33
|
+
const step4Post = document.getElementById("step4Post");
|
|
34
|
+
const step4Contrast = document.getElementById("step4Contrast");
|
|
35
|
+
const step4ContrastValue = document.getElementById("step4ContrastValue");
|
|
36
|
+
const step4Saturation = document.getElementById("step4Saturation");
|
|
37
|
+
const step4SaturationValue = document.getElementById("step4SaturationValue");
|
|
38
|
+
const step4Dump = document.getElementById("step4Dump");
|
|
39
|
+
|
|
40
|
+
const setStep1UiEnabled = (enabled) => {
|
|
41
|
+
const on = !!enabled;
|
|
42
|
+
if (step1ToneToggle) step1ToneToggle.checked = on;
|
|
43
|
+
if (step1Exposure) step1Exposure.disabled = !on;
|
|
44
|
+
if (step1Dump) step1Dump.disabled = !on;
|
|
96
45
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// снимем снапшот со всех контролов, включая сам test (чтобы вернуть checked), но блокировать его не будем
|
|
105
|
-
[testPresetToggle, ...getAllNonTestControls()].forEach(testSnapshotEl);
|
|
106
|
-
viewer.setTestPresetEnabled?.(true);
|
|
107
|
-
applyTestUiLock(true);
|
|
108
|
-
} else {
|
|
109
|
-
viewer.setTestPresetEnabled?.(false);
|
|
110
|
-
// вернём UI
|
|
111
|
-
[testPresetToggle, ...getAllNonTestControls()].forEach(testRestoreEl);
|
|
112
|
-
applyTestUiLock(false);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ===== Realtime-quality preset (UI master toggle) =====
|
|
118
|
-
const _rtSnapshot = new Map();
|
|
119
|
-
const snapshotEl = (el) => {
|
|
120
|
-
if (!el) return;
|
|
121
|
-
_rtSnapshot.set(el, {
|
|
122
|
-
checked: "checked" in el ? el.checked : undefined,
|
|
123
|
-
value: "value" in el ? el.value : undefined,
|
|
124
|
-
disabled: "disabled" in el ? el.disabled : undefined,
|
|
125
|
-
});
|
|
46
|
+
const setStep2UiEnabled = (enabled) => {
|
|
47
|
+
const on = !!enabled;
|
|
48
|
+
if (step2CoolLighting) step2CoolLighting.disabled = !on;
|
|
49
|
+
if (step2Hue) step2Hue.disabled = !on || !(step2CoolLighting && step2CoolLighting.checked);
|
|
50
|
+
if (step2Blue) step2Blue.disabled = !on || !(step2CoolLighting && step2CoolLighting.checked);
|
|
51
|
+
if (step2Dump) step2Dump.disabled = !on;
|
|
52
|
+
if (!on && step2CoolLighting) step2CoolLighting.checked = false;
|
|
126
53
|
};
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
if (
|
|
132
|
-
if ("value" in el && typeof s.value === "string") el.value = s.value;
|
|
133
|
-
if ("disabled" in el && typeof s.disabled === "boolean") el.disabled = s.disabled;
|
|
54
|
+
const setStep3UiEnabled = (enabled) => {
|
|
55
|
+
const on = !!enabled;
|
|
56
|
+
if (step3Background) step3Background.disabled = !on;
|
|
57
|
+
if (step3Dump) step3Dump.disabled = !on;
|
|
58
|
+
if (!on && step3Background) step3Background.checked = false;
|
|
134
59
|
};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
].filter(Boolean));
|
|
148
|
-
|
|
149
|
-
const applyRtQualityUiLock = (enabled) => {
|
|
150
|
-
// Блокируем все ручные контролы, кроме самого переключателя и кнопки dump
|
|
151
|
-
getRtManagedControls().forEach((el) => setDisabled(el, enabled));
|
|
60
|
+
const setStep4UiEnabled = (enabled) => {
|
|
61
|
+
const on = !!enabled;
|
|
62
|
+
if (step4Post) step4Post.disabled = !on;
|
|
63
|
+
const step4On = !!(step4Post && step4Post.checked);
|
|
64
|
+
if (step4Contrast) step4Contrast.disabled = !on || !step4On;
|
|
65
|
+
if (step4Saturation) step4Saturation.disabled = !on || !step4On;
|
|
66
|
+
if (step4Dump) step4Dump.disabled = !on;
|
|
67
|
+
if (!on) {
|
|
68
|
+
if (step4Post) step4Post.checked = false;
|
|
69
|
+
if (step4Contrast) step4Contrast.disabled = true;
|
|
70
|
+
if (step4Saturation) step4Saturation.disabled = true;
|
|
71
|
+
}
|
|
152
72
|
};
|
|
153
73
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// Снимем UI-снапшот, чтобы вернуть всё как было (включая disabled-состояния)
|
|
160
|
-
_rtSnapshot.clear();
|
|
161
|
-
getRtManagedControls().forEach(snapshotEl);
|
|
162
|
-
snapshotEl(dumpVisual); // dump не блокируем, но состояние тоже сохраним на всякий
|
|
163
|
-
|
|
164
|
-
viewer.setRealtimeQualityEnabled(true);
|
|
165
|
-
applyRtQualityUiLock(true);
|
|
166
|
-
} else {
|
|
167
|
-
viewer.setRealtimeQualityEnabled(false);
|
|
168
|
-
// Вернём UI
|
|
169
|
-
getRtManagedControls().forEach(restoreEl);
|
|
170
|
-
restoreEl(dumpVisual);
|
|
171
|
-
applyRtQualityUiLock(false);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
if (shadowToggle) {
|
|
176
|
-
// Дефолт (из текущих подобранных значений)
|
|
177
|
-
shadowToggle.checked = true;
|
|
178
|
-
viewer.setShadowsEnabled(true);
|
|
179
|
-
// синхронизируем тулбар-кнопку, если есть
|
|
180
|
-
const _btn = document.getElementById("ifcToggleShadows");
|
|
181
|
-
if (_btn) _btn.classList.toggle('btn-active', true);
|
|
182
|
-
shadowToggle.addEventListener("change", (e) => {
|
|
183
|
-
const on = !!e.target.checked;
|
|
184
|
-
viewer.setShadowsEnabled(on);
|
|
185
|
-
const btn = document.getElementById("ifcToggleShadows");
|
|
186
|
-
if (btn) btn.classList.toggle('btn-active', on);
|
|
187
|
-
// UI градиента имеет смысл только когда тени включены
|
|
188
|
-
if (shadowGradToggle) shadowGradToggle.disabled = !on;
|
|
189
|
-
if (shadowGradLen) shadowGradLen.disabled = !on;
|
|
190
|
-
if (shadowGradStr) shadowGradStr.disabled = !on;
|
|
191
|
-
if (shadowGradCurve) shadowGradCurve.disabled = !on;
|
|
192
|
-
if (shadowOpacity) shadowOpacity.disabled = !on;
|
|
193
|
-
if (shadowSoft) shadowSoft.disabled = !on;
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
// Градиент тени: по умолчанию включён, но элементы блокируем пока тени выключены
|
|
197
|
-
const syncGradUiEnabled = (enabled) => {
|
|
198
|
-
if (shadowGradToggle) shadowGradToggle.disabled = !enabled;
|
|
199
|
-
if (shadowGradLen) shadowGradLen.disabled = !enabled;
|
|
200
|
-
if (shadowGradStr) shadowGradStr.disabled = !enabled;
|
|
201
|
-
if (shadowGradCurve) shadowGradCurve.disabled = !enabled;
|
|
202
|
-
if (shadowOpacity) shadowOpacity.disabled = !enabled;
|
|
203
|
-
if (shadowSoft) shadowSoft.disabled = !enabled;
|
|
74
|
+
const applyStep2Hue = (deg) => {
|
|
75
|
+
const v = Math.round(Number(deg));
|
|
76
|
+
if (!Number.isFinite(v)) return;
|
|
77
|
+
if (step2HueValue) step2HueValue.textContent = String(v);
|
|
78
|
+
try { viewer.setCoolLightingHue?.(v); } catch (_) {}
|
|
204
79
|
};
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
viewer.
|
|
210
|
-
shadowGradToggle.addEventListener("change", (e) => {
|
|
211
|
-
viewer.setShadowGradientEnabled(!!e.target.checked);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
if (shadowGradLen) {
|
|
215
|
-
shadowGradLen.value = "14.4";
|
|
216
|
-
if (shadowGradLenValue) shadowGradLenValue.textContent = "14.4";
|
|
217
|
-
viewer.setShadowGradientLength(14.4);
|
|
218
|
-
shadowGradLen.addEventListener("input", (e) => {
|
|
219
|
-
const v = Number(e.target.value);
|
|
220
|
-
if (shadowGradLenValue) shadowGradLenValue.textContent = v.toFixed(1);
|
|
221
|
-
viewer.setShadowGradientLength(v);
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
if (shadowGradStr) {
|
|
225
|
-
shadowGradStr.value = "1.00";
|
|
226
|
-
if (shadowGradStrValue) shadowGradStrValue.textContent = "1.00";
|
|
227
|
-
viewer.setShadowGradientStrength(1.0);
|
|
228
|
-
shadowGradStr.addEventListener("input", (e) => {
|
|
229
|
-
const v = Number(e.target.value);
|
|
230
|
-
if (shadowGradStrValue) shadowGradStrValue.textContent = v.toFixed(2);
|
|
231
|
-
viewer.setShadowGradientStrength(v);
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (shadowGradCurve) {
|
|
236
|
-
shadowGradCurve.value = "0.50";
|
|
237
|
-
if (shadowGradCurveValue) shadowGradCurveValue.textContent = "0.50";
|
|
238
|
-
viewer.setShadowGradientCurve(0.5);
|
|
239
|
-
shadowGradCurve.addEventListener("input", (e) => {
|
|
240
|
-
const v = Number(e.target.value);
|
|
241
|
-
if (shadowGradCurveValue) shadowGradCurveValue.textContent = v.toFixed(2);
|
|
242
|
-
viewer.setShadowGradientCurve(v);
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Полупрозрачность тени на земле
|
|
247
|
-
if (shadowOpacity) {
|
|
248
|
-
shadowOpacity.value = "0.14";
|
|
249
|
-
if (shadowOpacityValue) shadowOpacityValue.textContent = "0.14";
|
|
250
|
-
viewer.setShadowOpacity(0.14);
|
|
251
|
-
shadowOpacity.addEventListener("input", (e) => {
|
|
252
|
-
const v = Number(e.target.value);
|
|
253
|
-
if (shadowOpacityValue) shadowOpacityValue.textContent = v.toFixed(2);
|
|
254
|
-
viewer.setShadowOpacity(v);
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Мягкость края тени
|
|
259
|
-
if (shadowSoft) {
|
|
260
|
-
shadowSoft.value = "0.0";
|
|
261
|
-
if (shadowSoftValue) shadowSoftValue.textContent = "0.0";
|
|
262
|
-
viewer.setShadowSoftness(0.0);
|
|
263
|
-
shadowSoft.addEventListener("input", (e) => {
|
|
264
|
-
const v = Number(e.target.value);
|
|
265
|
-
if (shadowSoftValue) shadowSoftValue.textContent = v.toFixed(1);
|
|
266
|
-
viewer.setShadowSoftness(v);
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Панель свойств: солнце (глобальное освещение)
|
|
271
|
-
const sunToggle = document.getElementById("sunToggle");
|
|
272
|
-
const sunHeight = document.getElementById("sunHeight");
|
|
273
|
-
const sunHeightValue = document.getElementById("sunHeightValue");
|
|
274
|
-
if (sunToggle) {
|
|
275
|
-
// По умолчанию включено
|
|
276
|
-
sunToggle.checked = true;
|
|
277
|
-
viewer.setSunEnabled(true);
|
|
278
|
-
sunToggle.addEventListener("change", (e) => {
|
|
279
|
-
const on = !!e.target.checked;
|
|
280
|
-
viewer.setSunEnabled(on);
|
|
281
|
-
if (sunHeight) sunHeight.disabled = !on;
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
if (sunHeight) {
|
|
285
|
-
// Дефолт (из текущих подобранных значений)
|
|
286
|
-
sunHeight.value = "5.9";
|
|
287
|
-
if (sunHeightValue) sunHeightValue.textContent = "5.9";
|
|
288
|
-
viewer.setSunHeight(5.9);
|
|
289
|
-
sunHeight.disabled = !(sunToggle ? !!sunToggle.checked : true);
|
|
290
|
-
sunHeight.addEventListener("input", (e) => {
|
|
291
|
-
const v = Number(e.target.value);
|
|
292
|
-
if (sunHeightValue) sunHeightValue.textContent = v.toFixed(1);
|
|
293
|
-
viewer.setSunHeight(v);
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// ===== Материалы =====
|
|
298
|
-
const MAT_DEFAULTS = {
|
|
299
|
-
original: { roughness: 0.90, metalness: 0.00, slidersEnabled: false },
|
|
300
|
-
matte: { roughness: 0.90, metalness: 0.00, slidersEnabled: true },
|
|
301
|
-
glossy: { roughness: 0.05, metalness: 0.00, slidersEnabled: true },
|
|
302
|
-
// Важно: "пластик" не должен быть металлом, иначе появятся резкие блики и "дёрганая" картинка при вращении
|
|
303
|
-
plastic: { roughness: 0.65, metalness: 0.00, slidersEnabled: true },
|
|
304
|
-
concrete: { roughness: 0.95, metalness: 0.00, slidersEnabled: true },
|
|
80
|
+
const applyStep2Blue = (amount) => {
|
|
81
|
+
const v = Number(amount);
|
|
82
|
+
if (!Number.isFinite(v)) return;
|
|
83
|
+
if (step2BlueValue) step2BlueValue.textContent = v.toFixed(2);
|
|
84
|
+
try { viewer.setCoolLightingAmount?.(v); } catch (_) {}
|
|
305
85
|
};
|
|
306
86
|
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
if (
|
|
87
|
+
const applyStep1Exposure = (v) => {
|
|
88
|
+
const value = Number(v);
|
|
89
|
+
if (!Number.isFinite(value)) return;
|
|
90
|
+
if (step1ExposureValue) step1ExposureValue.textContent = value.toFixed(2);
|
|
91
|
+
try { viewer.setExposure(value); } catch (_) {}
|
|
310
92
|
};
|
|
311
93
|
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
if (
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
if (matMetalValue) matMetalValue.textContent = d.metalness.toFixed(2);
|
|
318
|
-
setMatUiEnabled(d.slidersEnabled);
|
|
94
|
+
const applyStep4Contrast = (v) => {
|
|
95
|
+
const value = Number(v);
|
|
96
|
+
if (!Number.isFinite(value)) return;
|
|
97
|
+
if (step4ContrastValue) step4ContrastValue.textContent = value.toFixed(2);
|
|
98
|
+
try { viewer.setStep4Contrast?.(value); } catch (_) {}
|
|
319
99
|
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
viewer.setMaterialPreset("plastic");
|
|
326
|
-
viewer.setMaterialRoughness(MAT_DEFAULTS.plastic.roughness);
|
|
327
|
-
viewer.setMaterialMetalness(MAT_DEFAULTS.plastic.metalness);
|
|
328
|
-
matPreset.addEventListener("change", (e) => {
|
|
329
|
-
const preset = e.target.value;
|
|
330
|
-
viewer.setMaterialPreset(preset);
|
|
331
|
-
applyMatPresetUi(preset);
|
|
332
|
-
// применяем дефолтные параметры пресета как override
|
|
333
|
-
const d = MAT_DEFAULTS[preset] || MAT_DEFAULTS.original;
|
|
334
|
-
if (d.slidersEnabled) {
|
|
335
|
-
viewer.setMaterialRoughness(d.roughness);
|
|
336
|
-
viewer.setMaterialMetalness(d.metalness);
|
|
337
|
-
} else {
|
|
338
|
-
viewer.setMaterialRoughness(null);
|
|
339
|
-
viewer.setMaterialMetalness(null);
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (matRough) {
|
|
345
|
-
matRough.addEventListener("input", (e) => {
|
|
346
|
-
const v = Number(e.target.value);
|
|
347
|
-
if (matRoughValue) matRoughValue.textContent = v.toFixed(2);
|
|
348
|
-
viewer.setMaterialRoughness(v);
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
if (matMetal) {
|
|
352
|
-
matMetal.addEventListener("input", (e) => {
|
|
353
|
-
const v = Number(e.target.value);
|
|
354
|
-
if (matMetalValue) matMetalValue.textContent = v.toFixed(2);
|
|
355
|
-
viewer.setMaterialMetalness(v);
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// ===== Визуал (диагностика) =====
|
|
360
|
-
const syncVisualUiEnabled = () => {
|
|
361
|
-
const envOn = !!(envToggle && envToggle.checked);
|
|
362
|
-
const toneOn = !!(toneToggle && toneToggle.checked);
|
|
363
|
-
const aoOn = !!(aoToggle && aoToggle.checked);
|
|
364
|
-
if (envInt) envInt.disabled = !envOn;
|
|
365
|
-
if (exposure) exposure.disabled = !toneOn;
|
|
366
|
-
if (aoInt) aoInt.disabled = !aoOn;
|
|
367
|
-
if (aoRad) aoRad.disabled = !aoOn;
|
|
100
|
+
const applyStep4Saturation = (v) => {
|
|
101
|
+
const value = Number(v);
|
|
102
|
+
if (!Number.isFinite(value)) return;
|
|
103
|
+
if (step4SaturationValue) step4SaturationValue.textContent = value.toFixed(2);
|
|
104
|
+
try { viewer.setStep4Saturation?.(value); } catch (_) {}
|
|
368
105
|
};
|
|
369
106
|
|
|
370
|
-
const
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
107
|
+
const setTestPresetEnabled = (on) => {
|
|
108
|
+
const enabled = !!on;
|
|
109
|
+
try { viewer.setTestPresetEnabled?.(enabled); } catch (_) {}
|
|
110
|
+
if (testPresetToggle) testPresetToggle.checked = enabled;
|
|
111
|
+
// Шаг 1 сейчас привязан к "Тест"
|
|
112
|
+
setStep1UiEnabled(enabled);
|
|
113
|
+
if (enabled && step1Exposure) applyStep1Exposure(step1Exposure.value);
|
|
114
|
+
// Шаг 2 сейчас тоже привязан к "Тест"
|
|
115
|
+
setStep2UiEnabled(enabled);
|
|
116
|
+
if (!enabled) {
|
|
117
|
+
try { viewer.setCoolLightingEnabled?.(false); } catch (_) {}
|
|
118
|
+
}
|
|
119
|
+
// Шаг 3 сейчас тоже привязан к "Тест"
|
|
120
|
+
setStep3UiEnabled(enabled);
|
|
121
|
+
if (!enabled) {
|
|
122
|
+
try { viewer.setStep3BackgroundEnabled?.(false); } catch (_) {}
|
|
123
|
+
}
|
|
124
|
+
// Шаг 4 сейчас тоже привязан к "Тест"
|
|
125
|
+
setStep4UiEnabled(enabled);
|
|
126
|
+
if (!enabled) {
|
|
127
|
+
try { viewer.setStep4Enabled?.(false); } catch (_) {}
|
|
128
|
+
}
|
|
376
129
|
};
|
|
377
130
|
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
131
|
+
// По умолчанию "Тест" включён и применяется сразу (до загрузки IFC)
|
|
132
|
+
setTestPresetEnabled(true);
|
|
133
|
+
testPresetToggle?.addEventListener("change", (e) => setTestPresetEnabled(!!e.target.checked));
|
|
134
|
+
|
|
135
|
+
// Шаг 1: exposure
|
|
136
|
+
if (step1Exposure) {
|
|
137
|
+
// дефолтное положение для подбора
|
|
138
|
+
step1Exposure.value = "1.19";
|
|
139
|
+
applyStep1Exposure(step1Exposure.value);
|
|
140
|
+
step1Exposure.addEventListener("input", (e) => applyStep1Exposure(e.target.value));
|
|
141
|
+
}
|
|
142
|
+
// Шаг 1: dump
|
|
143
|
+
step1Dump?.addEventListener("click", () => {
|
|
144
|
+
const r = viewer?.renderer;
|
|
145
|
+
// eslint-disable-next-line no-console
|
|
146
|
+
console.log('[Step1] tone', {
|
|
147
|
+
visualTone: viewer?.visual?.tone,
|
|
148
|
+
renderer: {
|
|
149
|
+
outputColorSpace: r?.outputColorSpace,
|
|
150
|
+
outputEncoding: r?.outputEncoding,
|
|
151
|
+
toneMapping: r?.toneMapping,
|
|
152
|
+
toneMappingExposure: r?.toneMappingExposure,
|
|
153
|
+
},
|
|
385
154
|
});
|
|
386
|
-
}
|
|
387
|
-
if (envInt) {
|
|
388
|
-
envInt.value = "0.65";
|
|
389
|
-
if (envIntValue) envIntValue.textContent = "0.65";
|
|
390
|
-
viewer.setEnvironmentIntensity(0.65);
|
|
391
|
-
envInt.addEventListener("input", (e) => {
|
|
392
|
-
const v = Number(e.target.value);
|
|
393
|
-
if (envIntValue) envIntValue.textContent = v.toFixed(2);
|
|
394
|
-
viewer.setEnvironmentIntensity(v);
|
|
395
|
-
});
|
|
396
|
-
}
|
|
155
|
+
});
|
|
397
156
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
157
|
+
// Шаг 2: холодный свет
|
|
158
|
+
if (step2CoolLighting) {
|
|
159
|
+
step2CoolLighting.checked = false;
|
|
160
|
+
step2CoolLighting.addEventListener("change", (e) => {
|
|
161
|
+
const on = !!e.target.checked;
|
|
162
|
+
try { viewer.setCoolLightingEnabled?.(on); } catch (_) {}
|
|
163
|
+
// при включении применим текущие значения ручек
|
|
164
|
+
if (on) {
|
|
165
|
+
if (step2Hue) applyStep2Hue(step2Hue.value);
|
|
166
|
+
if (step2Blue) applyStep2Blue(step2Blue.value);
|
|
167
|
+
}
|
|
168
|
+
// синхронизируем доступность ручек
|
|
169
|
+
if (step2Hue) step2Hue.disabled = !on;
|
|
170
|
+
if (step2Blue) step2Blue.disabled = !on;
|
|
404
171
|
});
|
|
405
172
|
}
|
|
406
|
-
if (
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
173
|
+
if (step2Hue) {
|
|
174
|
+
// дефолтное положение для подбора
|
|
175
|
+
step2Hue.value = "240";
|
|
176
|
+
applyStep2Hue(step2Hue.value);
|
|
177
|
+
step2Hue.disabled = true; // пока step2 выключен
|
|
178
|
+
step2Hue.addEventListener("input", (e) => applyStep2Hue(e.target.value));
|
|
179
|
+
}
|
|
180
|
+
if (step2Blue) {
|
|
181
|
+
// дефолтное положение для подбора
|
|
182
|
+
step2Blue.value = "1.00";
|
|
183
|
+
applyStep2Blue(step2Blue.value);
|
|
184
|
+
step2Blue.disabled = true; // пока step2 выключен
|
|
185
|
+
step2Blue.addEventListener("input", (e) => applyStep2Blue(e.target.value));
|
|
186
|
+
}
|
|
187
|
+
step2Dump?.addEventListener("click", () => {
|
|
188
|
+
const amb = viewer?.ambientLight;
|
|
189
|
+
const sun = viewer?.sunLight;
|
|
190
|
+
const hemi = viewer?.hemiLight;
|
|
191
|
+
const recv = viewer?.shadowReceiver;
|
|
192
|
+
const recvMat = recv?.material;
|
|
193
|
+
// eslint-disable-next-line no-console
|
|
194
|
+
console.log('[Step2] cool lighting', {
|
|
195
|
+
coolEnabled: viewer?._coolLighting?.enabled,
|
|
196
|
+
params: viewer?._coolLighting?.params,
|
|
197
|
+
ambient: amb ? { visible: amb.visible, intensity: amb.intensity, color: amb.color?.getHexString?.() } : null,
|
|
198
|
+
hemi: hemi ? { visible: hemi.visible, intensity: hemi.intensity, color: hemi.color?.getHexString?.(), ground: hemi.groundColor?.getHexString?.() } : null,
|
|
199
|
+
sun: sun ? { visible: sun.visible, intensity: sun.intensity, color: sun.color?.getHexString?.() } : null,
|
|
200
|
+
shadowReceiver: recv ? {
|
|
201
|
+
visible: !!recv.visible,
|
|
202
|
+
receiveShadow: !!recv.receiveShadow,
|
|
203
|
+
opacity: ('opacity' in (recvMat || {})) ? recvMat.opacity : undefined,
|
|
204
|
+
color: recvMat?.color?.getHexString?.(),
|
|
205
|
+
} : null,
|
|
414
206
|
});
|
|
415
|
-
}
|
|
207
|
+
});
|
|
416
208
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
if (aoInt) {
|
|
426
|
-
aoInt.value = "0.52";
|
|
427
|
-
if (aoIntValue) aoIntValue.textContent = "0.52";
|
|
428
|
-
viewer.setAOIntensity(0.52);
|
|
429
|
-
aoInt.addEventListener("input", (e) => {
|
|
430
|
-
const v = Number(e.target.value);
|
|
431
|
-
if (aoIntValue) aoIntValue.textContent = v.toFixed(2);
|
|
432
|
-
viewer.setAOIntensity(v);
|
|
209
|
+
// Шаг 3: фон сцены
|
|
210
|
+
if (step3Background) {
|
|
211
|
+
step3Background.checked = false;
|
|
212
|
+
step3Background.addEventListener("change", (e) => {
|
|
213
|
+
const on = !!e.target.checked;
|
|
214
|
+
try { viewer.setStep3BackgroundEnabled?.(on); } catch (_) {}
|
|
433
215
|
});
|
|
434
216
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
viewer.setAORadius(v);
|
|
217
|
+
step3Dump?.addEventListener("click", () => {
|
|
218
|
+
const bg = viewer?.scene?.background;
|
|
219
|
+
// eslint-disable-next-line no-console
|
|
220
|
+
console.log('[Step3] background', {
|
|
221
|
+
enabled: viewer?._step3Background?.enabled,
|
|
222
|
+
type: bg ? (bg.isColor ? 'Color' : bg.isTexture ? 'Texture' : typeof bg) : 'null',
|
|
223
|
+
value: bg?.isColor ? bg.getHexString?.() : null,
|
|
443
224
|
});
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
syncVisualUiEnabled();
|
|
447
|
-
|
|
448
|
-
if (dumpVisual) {
|
|
449
|
-
dumpVisual.addEventListener("click", () => viewer.dumpVisualDebug());
|
|
450
|
-
}
|
|
225
|
+
});
|
|
451
226
|
|
|
452
|
-
//
|
|
453
|
-
if (
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
viewer.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
ccHue.addEventListener("input", (e) => {
|
|
466
|
-
const v = Number(e.target.value);
|
|
467
|
-
if (ccHueValue) ccHueValue.textContent = v.toFixed(2);
|
|
468
|
-
viewer.setColorHue(v);
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
if (ccSat) {
|
|
472
|
-
ccSat.value = "0.00";
|
|
473
|
-
if (ccSatValue) ccSatValue.textContent = "0.00";
|
|
474
|
-
viewer.setColorSaturation(0.0);
|
|
475
|
-
ccSat.addEventListener("input", (e) => {
|
|
476
|
-
const v = Number(e.target.value);
|
|
477
|
-
if (ccSatValue) ccSatValue.textContent = v.toFixed(2);
|
|
478
|
-
viewer.setColorSaturation(v);
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
if (ccBri) {
|
|
482
|
-
ccBri.value = "0.00";
|
|
483
|
-
if (ccBriValue) ccBriValue.textContent = "0.00";
|
|
484
|
-
viewer.setColorBrightness(0.0);
|
|
485
|
-
ccBri.addEventListener("input", (e) => {
|
|
486
|
-
const v = Number(e.target.value);
|
|
487
|
-
if (ccBriValue) ccBriValue.textContent = v.toFixed(2);
|
|
488
|
-
viewer.setColorBrightness(v);
|
|
227
|
+
// Шаг 4: пост-обработка (финальный pass)
|
|
228
|
+
if (step4Post) {
|
|
229
|
+
step4Post.checked = false;
|
|
230
|
+
step4Post.addEventListener("change", (e) => {
|
|
231
|
+
const on = !!e.target.checked;
|
|
232
|
+
try { viewer.setStep4Enabled?.(on); } catch (_) {}
|
|
233
|
+
// синхронизируем доступность ручек
|
|
234
|
+
if (step4Contrast) step4Contrast.disabled = !on;
|
|
235
|
+
if (step4Saturation) step4Saturation.disabled = !on;
|
|
236
|
+
if (on) {
|
|
237
|
+
if (step4Contrast) applyStep4Contrast(step4Contrast.value);
|
|
238
|
+
if (step4Saturation) applyStep4Saturation(step4Saturation.value);
|
|
239
|
+
}
|
|
489
240
|
});
|
|
490
241
|
}
|
|
491
|
-
if (
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
242
|
+
if (step4Contrast) {
|
|
243
|
+
// дефолтное положение для подбора
|
|
244
|
+
step4Contrast.value = step4Contrast.value || "1.35";
|
|
245
|
+
applyStep4Contrast(step4Contrast.value);
|
|
246
|
+
step4Contrast.disabled = true; // пока step4 выключен
|
|
247
|
+
step4Contrast.addEventListener("input", (e) => applyStep4Contrast(e.target.value));
|
|
248
|
+
}
|
|
249
|
+
if (step4Saturation) {
|
|
250
|
+
// дефолтное положение для подбора
|
|
251
|
+
step4Saturation.value = step4Saturation.value || "1.60";
|
|
252
|
+
applyStep4Saturation(step4Saturation.value);
|
|
253
|
+
step4Saturation.disabled = true; // пока step4 выключен
|
|
254
|
+
step4Saturation.addEventListener("input", (e) => applyStep4Saturation(e.target.value));
|
|
255
|
+
}
|
|
256
|
+
step4Dump?.addEventListener("click", () => {
|
|
257
|
+
const recv = viewer?.shadowReceiver;
|
|
258
|
+
const recvMat = recv?.material;
|
|
259
|
+
// eslint-disable-next-line no-console
|
|
260
|
+
console.log('[Step4] post', {
|
|
261
|
+
enabled: viewer?._step4?.enabled,
|
|
262
|
+
params: viewer?._step4 ? { contrast: viewer._step4.contrast, saturation: viewer._step4.saturation } : null,
|
|
263
|
+
composer: viewer?._composer ? { passes: viewer._composer.passes?.length } : null,
|
|
264
|
+
shadowReceiver: recv ? {
|
|
265
|
+
visible: !!recv.visible,
|
|
266
|
+
receiveShadow: !!recv.receiveShadow,
|
|
267
|
+
opacity: ('opacity' in (recvMat || {})) ? recvMat.opacity : undefined,
|
|
268
|
+
color: recvMat?.color?.getHexString?.(),
|
|
269
|
+
} : null,
|
|
499
270
|
});
|
|
500
|
-
}
|
|
501
|
-
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
/*
|
|
274
|
+
* ===== LEGACY: старые настройки левой панели (временно не используем) =====
|
|
275
|
+
* Здесь был код управления:
|
|
276
|
+
* - shadowToggle/shadowGrad/shadowOpacity/shadowSoft
|
|
277
|
+
* - sunToggle/sunHeight
|
|
278
|
+
* - material preset + rough/metal
|
|
279
|
+
* - env/tone/AO + dumpVisual
|
|
280
|
+
* - color correction
|
|
281
|
+
* - realtime-quality UI lock + test UI lock snapshots
|
|
282
|
+
*/
|
|
502
283
|
// IFC загрузка
|
|
503
284
|
const ifc = new IfcService(viewer);
|
|
504
285
|
ifc.init();
|
|
@@ -574,13 +355,22 @@ if (app) {
|
|
|
574
355
|
// Переключение вида: "без перспективы" (Ortho) ↔ Perspective
|
|
575
356
|
// Вариант 2: на кнопке показываем действие (альтернативный режим)
|
|
576
357
|
const PROJ_ICON_PERSPECTIVE = `
|
|
577
|
-
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
|
578
|
-
<path
|
|
358
|
+
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="currentColor" style="color:#252A3F">
|
|
359
|
+
<path d="M 365.50 333.29 A 0.30 0.30 0.0 0 0 365.95 333.55 L 492.36 259.80 A 0.47 0.47 0.0 0 0 492.51 259.12 Q 489.74 255.31 492.90 252.78 A 0.30 0.30 0.0 0 0 492.83 252.27 C 489.14 250.57 490.13 245.43 493.90 244.50 C 496.33 243.90 501.93 247.88 504.97 249.79 A 1.50 1.48 -85.3 0 1 505.54 250.47 L 505.97 251.53 A 0.72 0.71 76.6 0 0 506.67 251.97 C 509.70 251.84 512.28 254.84 511.15 257.67 Q 510.77 258.62 508.18 260.14 C 355.38 349.68 251.70 410.06 149.28 469.74 A 3.94 3.93 -44.9 0 1 145.31 469.74 Q 7.70 389.45 2.96 386.69 C 0.09 385.02 0.50 382.93 0.50 379.49 Q 0.50 259.79 0.50 128.77 C 0.50 127.21 1.85 125.96 3.27 125.13 Q 68.02 87.24 145.61 41.87 C 146.90 41.11 148.92 41.81 150.33 42.63 Q 219.34 82.64 289.83 124.16 C 291.25 125.00 292.80 126.11 294.76 127.15 Q 299.89 129.89 301.84 131.37 C 305.49 134.15 301.99 140.40 297.26 138.18 Q 295.67 137.42 294.41 136.58 A 0.26 0.26 0.0 0 0 294.00 136.80 L 294.00 209.83 A 0.44 0.44 0.0 0 0 294.36 210.26 Q 340.50 219.23 361.26 223.22 C 366.12 224.15 365.53 227.44 365.51 232.03 Q 365.50 234.52 365.49 251.11 A 0.73 0.73 0.0 0 0 366.22 251.84 L 370.02 251.84 A 3.64 3.64 0.0 0 1 373.66 255.48 L 373.66 256.72 A 3.45 3.44 0.0 0 1 370.21 260.16 L 366.15 260.16 A 0.65 0.65 0.0 0 0 365.50 260.81 L 365.50 333.29 Z M 9.05 131.40 A 0.30 0.30 0.0 0 0 8.90 131.66 L 8.90 380.18 A 0.30 0.30 0.0 0 0 9.05 380.44 L 142.74 458.43 A 0.30 0.30 0.0 0 0 143.19 458.17 L 143.19 53.67 A 0.30 0.30 0.0 0 0 142.74 53.41 L 9.05 131.40 Z M 285.68 380.52 A 0.32 0.32 0.0 0 0 285.84 380.25 L 285.84 131.66 A 0.32 0.32 0.0 0 0 285.68 131.39 L 151.98 53.39 A 0.32 0.32 0.0 0 0 151.50 53.67 L 151.50 458.24 A 0.32 0.32 0.0 0 0 151.98 458.52 L 285.68 380.52 Z M 294.62 218.77 A 0.36 0.36 0.0 0 0 294.19 219.13 L 294.19 374.90 A 0.36 0.36 0.0 0 0 294.73 375.21 L 357.13 338.81 A 0.36 0.36 0.0 0 0 357.31 338.50 L 357.31 231.30 A 0.36 0.36 0.0 0 0 357.02 230.94 L 294.62 218.77 Z"/>
|
|
360
|
+
<path d="M 331.8028 153.6467 A 4.00 4.00 0.0 0 1 326.3286 155.0726 L 318.9110 150.7207 A 4.00 4.00 0.0 0 1 317.4851 145.2465 L 317.6572 144.9533 A 4.00 4.00 0.0 0 1 323.1314 143.5274 L 330.5490 147.8793 A 4.00 4.00 0.0 0 1 331.9749 153.3535 L 331.8028 153.6467 Z"/>
|
|
361
|
+
<path d="M 360.6890 170.5463 A 4.00 4.00 0.0 0 1 355.2099 171.9531 L 347.8247 167.5855 A 4.00 4.00 0.0 0 1 346.4179 162.1064 L 346.5910 161.8137 A 4.00 4.00 0.0 0 1 352.0701 160.4069 L 359.4553 164.7745 A 4.00 4.00 0.0 0 1 360.8621 170.2536 L 360.6890 170.5463 Z"/>
|
|
362
|
+
<path d="M 389.5811 187.4643 A 3.99 3.99 0.0 0 1 384.1181 188.8771 L 376.8287 184.5833 A 3.99 3.99 0.0 0 1 375.4159 179.1204 L 375.6189 178.7757 A 3.99 3.99 0.0 0 1 381.0819 177.3629 L 388.3713 181.6567 A 3.99 3.99 0.0 0 1 389.7841 187.1196 L 389.5811 187.4643 Z"/>
|
|
363
|
+
<path d="M 418.5914 204.3586 A 3.99 3.99 0.0 0 1 413.1235 205.7523 L 405.7288 201.3617 A 3.99 3.99 0.0 0 1 404.3350 195.8938 L 404.5086 195.6014 A 3.99 3.99 0.0 0 1 409.9765 194.2077 L 417.3712 198.5983 A 3.99 3.99 0.0 0 1 418.7650 204.0662 L 418.5914 204.3586 Z"/>
|
|
364
|
+
<path d="M 447.6480 221.1624 A 3.99 3.99 0.0 0 1 442.2027 222.6419 L 434.7225 218.3579 A 3.99 3.99 0.0 0 1 433.2431 212.9126 L 433.4120 212.6176 A 3.99 3.99 0.0 0 1 438.8573 211.1381 L 446.3375 215.4221 A 3.99 3.99 0.0 0 1 447.8169 220.8674 L 447.6480 221.1624 Z"/>
|
|
365
|
+
<path d="M 476.5002 238.1477 A 3.99 3.99 0.0 0 1 471.0372 239.5605 L 463.6099 235.1855 A 3.99 3.99 0.0 0 1 462.1971 229.7225 L 462.3798 229.4123 A 3.99 3.99 0.0 0 1 467.8428 227.9995 L 475.2701 232.3745 A 3.99 3.99 0.0 0 1 476.6829 237.8375 L 476.5002 238.1477 Z"/>
|
|
366
|
+
<path d="M 407.4604 256.3255 A 3.98 3.98 0.0 0 1 403.4873 260.3125 L 394.8874 260.3275 A 3.98 3.98 0.0 0 1 390.9004 256.3545 L 390.8996 255.8945 A 3.98 3.98 0.0 0 1 394.8727 251.9075 L 403.4726 251.8925 A 3.98 3.98 0.0 0 1 407.4596 255.8655 L 407.4604 256.3255 Z"/>
|
|
367
|
+
<path d="M 440.9596 256.3545 A 3.98 3.98 0.0 0 1 436.9726 260.3275 L 428.3727 260.3125 A 3.98 3.98 0.0 0 1 424.3996 256.3255 L 424.4004 255.8655 A 3.98 3.98 0.0 0 1 428.3874 251.8925 L 436.9873 251.9075 A 3.98 3.98 0.0 0 1 440.9604 255.8945 L 440.9596 256.3545 Z"/>
|
|
368
|
+
<path d="M 474.4604 256.3255 A 3.98 3.98 0.0 0 1 470.4873 260.3125 L 461.8874 260.3275 A 3.98 3.98 0.0 0 1 457.9004 256.3545 L 457.8996 255.8945 A 3.98 3.98 0.0 0 1 461.8727 251.9075 L 470.4726 251.8925 A 3.98 3.98 0.0 0 1 474.4596 255.8655 L 474.4604 256.3255 Z"/>
|
|
579
369
|
</svg>
|
|
580
370
|
`;
|
|
581
371
|
const PROJ_ICON_ORTHO = `
|
|
582
|
-
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
|
583
|
-
<path
|
|
372
|
+
<svg width="24" height="24" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="currentColor" style="color:#252A3F">
|
|
373
|
+
<path d="M 256.02 48.55 Q 257.33 48.55 258.06 48.94 Q 381.49 115.11 442.91 148.14 Q 445.24 149.39 445.26 152.25 Q 445.52 184.71 445.52 256.00 Q 445.52 327.29 445.26 359.75 Q 445.24 362.61 442.91 363.86 Q 381.49 396.89 258.06 463.06 Q 257.33 463.45 256.02 463.45 Q 254.71 463.45 253.98 463.06 Q 130.55 396.89 69.13 363.86 Q 66.80 362.61 66.78 359.75 Q 66.52 327.29 66.52 256.00 Q 66.52 184.71 66.78 152.25 Q 66.80 149.39 69.13 148.14 Q 130.55 115.11 253.98 48.94 Q 254.71 48.55 256.02 48.55 Z M 256.03 147.56 Q 257.36 147.56 258.05 147.94 Q 295.68 168.96 347.89 198.33 A 0.77 0.75 44.3 0 0 348.62 198.33 L 429.62 152.89 A 0.32 0.32 0.0 0 0 429.62 152.33 Q 332.33 100.05 256.30 59.37 Q 256.25 59.35 256.15 59.34 Q 256.09 59.33 256.02 59.33 Q 255.97 59.33 255.90 59.34 Q 255.81 59.35 255.76 59.37 Q 179.73 100.05 82.44 152.34 A 0.32 0.32 0.0 0 0 82.44 152.90 L 163.44 198.34 A 0.77 0.75 -44.3 0 0 164.17 198.34 Q 216.38 168.96 254.01 147.94 Q 254.70 147.56 256.03 147.56 Z M 255.82 250.17 A 0.38 0.38 0.0 0 0 256.20 250.17 L 337.45 204.58 A 0.38 0.38 0.0 0 0 337.45 203.92 L 256.20 158.33 A 0.38 0.38 0.0 0 0 255.82 158.33 L 174.57 203.92 A 0.38 0.38 0.0 0 0 174.57 204.58 L 255.82 250.17 Z M 76.99 161.29 A 0.33 0.33 0.0 0 0 76.50 161.58 L 76.50 246.92 A 0.33 0.33 0.0 0 0 76.99 247.21 L 153.06 204.54 A 0.33 0.33 0.0 0 0 153.06 203.96 L 76.99 161.29 Z M 434.97 247.14 A 0.35 0.35 0.0 0 0 435.49 246.83 L 435.49 161.67 A 0.35 0.35 0.0 0 0 434.97 161.36 L 359.05 203.94 A 0.35 0.35 0.0 0 0 359.05 204.56 L 434.97 247.14 Z M 245.33 256.28 A 0.32 0.32 0.0 0 0 245.33 255.72 L 163.96 210.07 A 0.32 0.32 0.0 0 0 163.64 210.07 L 82.27 255.72 A 0.32 0.32 0.0 0 0 82.27 256.28 L 163.64 301.93 A 0.32 0.32 0.0 0 0 163.96 301.93 L 245.33 256.28 Z M 429.83 256.28 A 0.32 0.32 0.0 0 0 429.83 255.72 L 348.46 210.07 A 0.32 0.32 0.0 0 0 348.14 210.07 L 266.77 255.72 A 0.32 0.32 0.0 0 0 266.77 256.28 L 348.14 301.93 A 0.32 0.32 0.0 0 0 348.46 301.93 L 429.83 256.28 Z M 337.56 308.04 A 0.33 0.33 0.0 0 0 337.56 307.46 L 256.20 261.82 A 0.33 0.33 0.0 0 0 255.88 261.82 L 174.51 307.46 A 0.33 0.33 0.0 0 0 174.51 308.04 L 255.87 353.68 A 0.33 0.33 0.0 0 0 256.19 353.68 L 337.56 308.04 Z M 76.96 264.77 A 0.31 0.31 0.0 0 0 76.50 265.04 L 76.50 350.46 A 0.31 0.31 0.0 0 0 76.96 350.73 L 153.09 308.02 A 0.31 0.31 0.0 0 0 153.09 307.48 L 76.96 264.77 Z M 434.97 350.63 A 0.35 0.35 0.0 0 0 435.49 350.33 L 435.49 265.17 A 0.35 0.35 0.0 0 0 434.97 264.87 L 359.05 307.44 A 0.35 0.35 0.0 0 0 359.05 308.06 L 434.97 350.63 Z M 256.02 364.45 Q 254.69 364.45 254.00 364.06 Q 216.37 343.04 164.17 313.67 A 0.77 0.75 44.3 0 0 163.44 313.67 L 82.44 359.10 A 0.32 0.32 0.0 0 0 82.44 359.66 Q 179.72 411.94 255.74 452.63 Q 255.79 452.65 255.89 452.66 Q 255.96 452.67 256.00 452.67 Q 256.07 452.67 256.14 452.66 Q 256.24 452.65 256.29 452.63 Q 332.31 411.95 429.60 359.67 A 0.32 0.32 0.0 0 0 429.60 359.11 L 348.60 313.68 A 0.77 0.75 -44.3 0 0 347.87 313.68 Q 295.66 343.04 258.03 364.06 Q 257.35 364.45 256.02 364.45 Z"/>
|
|
584
374
|
</svg>
|
|
585
375
|
`;
|
|
586
376
|
const syncProjectionIcon = () => {
|
|
@@ -598,14 +388,14 @@ if (app) {
|
|
|
598
388
|
const setToolbarShadowsActive = (on) => {
|
|
599
389
|
if (toggleShadowsBtn) toggleShadowsBtn.classList.toggle('btn-active', !!on);
|
|
600
390
|
};
|
|
601
|
-
|
|
391
|
+
let toolbarShadowsOn = true;
|
|
392
|
+
try { viewer.setShadowsEnabled(toolbarShadowsOn); } catch (_) {}
|
|
393
|
+
setToolbarShadowsActive(toolbarShadowsOn);
|
|
602
394
|
toggleShadowsBtn?.addEventListener("click", () => {
|
|
603
|
-
|
|
395
|
+
toolbarShadowsOn = !toolbarShadowsOn;
|
|
604
396
|
// Меняем состояние у Viewer
|
|
605
|
-
viewer.setShadowsEnabled(
|
|
606
|
-
|
|
607
|
-
if (shadowToggle) shadowToggle.checked = next;
|
|
608
|
-
setToolbarShadowsActive(next);
|
|
397
|
+
try { viewer.setShadowsEnabled(toolbarShadowsOn); } catch (_) {}
|
|
398
|
+
setToolbarShadowsActive(toolbarShadowsOn);
|
|
609
399
|
});
|
|
610
400
|
|
|
611
401
|
let flatOn = true;
|
package/src/viewer/Viewer.js
CHANGED
|
@@ -56,6 +56,8 @@ export class Viewer {
|
|
|
56
56
|
this.sunLight = null;
|
|
57
57
|
/** @type {THREE.AmbientLight|null} */
|
|
58
58
|
this.ambientLight = null;
|
|
59
|
+
/** @type {THREE.HemisphereLight|null} */
|
|
60
|
+
this.hemiLight = null;
|
|
59
61
|
// Базовые координаты солнца (чтобы менять только высоту по Y)
|
|
60
62
|
this._sunBaseXZ = { x: 5, z: 5 };
|
|
61
63
|
// Параметры градиента тени на земле (модифицирует только ShadowMaterial приёмника)
|
|
@@ -78,6 +80,8 @@ export class Viewer {
|
|
|
78
80
|
opacity: 0.14, // прозрачность тени на земле (ShadowMaterial.opacity)
|
|
79
81
|
softness: 0.0, // мягкость края (DirectionalLight.shadow.radius)
|
|
80
82
|
};
|
|
83
|
+
// Базовый цвет тени на земле (по умолчанию нейтрально-серый, чтобы можно было сравнивать с "синей" тенью в Шаге 2)
|
|
84
|
+
this._shadowReceiverBaseColor = new THREE.Color(0x2a2a2a);
|
|
81
85
|
|
|
82
86
|
// Материалы (пресеты)
|
|
83
87
|
this.materialStyle = {
|
|
@@ -101,6 +105,12 @@ export class Viewer {
|
|
|
101
105
|
this._rtQuality = { enabled: false, snapshot: null };
|
|
102
106
|
// Пресет "Тест": полностью изолированная настройка (тени+самозатенение+визуал из рекомендаций)
|
|
103
107
|
this._testPreset = { enabled: false, snapshot: null };
|
|
108
|
+
|
|
109
|
+
// Шаг 2: холодное освещение (отдельно от пресета "Тест", но обычно используется вместе с ним)
|
|
110
|
+
this._coolLighting = { enabled: false, snapshot: null, params: { hueDeg: 210, amount: 1.0 } };
|
|
111
|
+
|
|
112
|
+
// Шаг 3: фон сцены (как в Autodesk)
|
|
113
|
+
this._step3Background = { enabled: false, snapshot: null, colorHex: 0xe8eef4 };
|
|
104
114
|
this._baselineRenderer = null;
|
|
105
115
|
this._pmrem = null;
|
|
106
116
|
this._roomEnvTex = null;
|
|
@@ -109,6 +119,9 @@ export class Viewer {
|
|
|
109
119
|
this._ssaoPass = null;
|
|
110
120
|
this._hueSatPass = null;
|
|
111
121
|
this._bcPass = null;
|
|
122
|
+
// Шаг 4: финальная постобработка (контраст/насыщенность) — должна быть последним pass'ом
|
|
123
|
+
this._step4Pass = null;
|
|
124
|
+
this._step4 = { enabled: false, saturation: 1.0, contrast: 1.0 };
|
|
112
125
|
this.clipping = {
|
|
113
126
|
enabled: false,
|
|
114
127
|
planes: [
|
|
@@ -467,7 +480,7 @@ export class Viewer {
|
|
|
467
480
|
this.#updateClippingGizmos();
|
|
468
481
|
// Рендер основной сцены
|
|
469
482
|
this.renderer.clear(true, true, true);
|
|
470
|
-
const useComposer = !!(this._composer && (this.visual?.ao?.enabled || this.visual?.color?.enabled));
|
|
483
|
+
const useComposer = !!(this._composer && (this.visual?.ao?.enabled || this.visual?.color?.enabled || this._step4?.enabled));
|
|
471
484
|
if (useComposer) {
|
|
472
485
|
this._composer.render();
|
|
473
486
|
} else {
|
|
@@ -534,6 +547,7 @@ export class Viewer {
|
|
|
534
547
|
this._ssaoPass = null;
|
|
535
548
|
this._hueSatPass = null;
|
|
536
549
|
this._bcPass = null;
|
|
550
|
+
this._step4Pass = null;
|
|
537
551
|
}
|
|
538
552
|
if (this.resizeObserver) {
|
|
539
553
|
this.resizeObserver.disconnect();
|
|
@@ -992,7 +1006,7 @@ export class Viewer {
|
|
|
992
1006
|
if (!this.scene || this.shadowReceiver) return;
|
|
993
1007
|
// ShadowMaterial рисует только тени, сама плоскость прозрачная.
|
|
994
1008
|
// Если тени отключены — визуально ничего не изменится.
|
|
995
|
-
const mat = new THREE.ShadowMaterial({ opacity: this.shadowStyle.opacity });
|
|
1009
|
+
const mat = new THREE.ShadowMaterial({ opacity: this.shadowStyle.opacity, color: this._shadowReceiverBaseColor.clone() });
|
|
996
1010
|
// Градиент тени: модифицируем шейдер только приёмника (не влияет на остальные материалы)
|
|
997
1011
|
mat.onBeforeCompile = (shader) => {
|
|
998
1012
|
// uniforms
|
|
@@ -1083,6 +1097,8 @@ export class Viewer {
|
|
|
1083
1097
|
plane.position.set(0, -9999, 0); // спрячем до первого апдейта по модели
|
|
1084
1098
|
this.scene.add(plane);
|
|
1085
1099
|
this.shadowReceiver = plane;
|
|
1100
|
+
// Если "холодный свет" (Шаг 2) уже включён — сразу подкрасим тень тем же оттенком
|
|
1101
|
+
try { this.#applyShadowTintFromCoolLighting(); } catch (_) {}
|
|
1086
1102
|
}
|
|
1087
1103
|
|
|
1088
1104
|
#updateShadowReceiverFromModel(model) {
|
|
@@ -1890,10 +1906,212 @@ export class Viewer {
|
|
|
1890
1906
|
setExposure(exposure) {
|
|
1891
1907
|
const v = Number(exposure);
|
|
1892
1908
|
if (!Number.isFinite(v)) return;
|
|
1893
|
-
this.visual.tone.exposure = Math.min(
|
|
1909
|
+
this.visual.tone.exposure = Math.min(3.0, Math.max(0.1, v));
|
|
1894
1910
|
this.#applyToneSettings();
|
|
1895
1911
|
}
|
|
1896
1912
|
|
|
1913
|
+
/**
|
|
1914
|
+
* Шаг 2: включает/выключает холодное освещение (HemisphereLight + холодный AmbientLight),
|
|
1915
|
+
* сохраняя и восстанавливая исходные параметры.
|
|
1916
|
+
* @param {boolean} enabled
|
|
1917
|
+
*/
|
|
1918
|
+
setCoolLightingEnabled(enabled) {
|
|
1919
|
+
const next = !!enabled;
|
|
1920
|
+
if (next === this._coolLighting.enabled) return;
|
|
1921
|
+
|
|
1922
|
+
if (next) {
|
|
1923
|
+
// Снимем снапшот, чтобы можно было восстановить
|
|
1924
|
+
this._coolLighting.snapshot = {
|
|
1925
|
+
ambient: this.ambientLight ? {
|
|
1926
|
+
visible: this.ambientLight.visible,
|
|
1927
|
+
intensity: this.ambientLight.intensity,
|
|
1928
|
+
color: this.ambientLight.color?.clone?.() || null,
|
|
1929
|
+
} : null,
|
|
1930
|
+
hemi: this.hemiLight ? {
|
|
1931
|
+
existed: true,
|
|
1932
|
+
visible: this.hemiLight.visible,
|
|
1933
|
+
intensity: this.hemiLight.intensity,
|
|
1934
|
+
color: this.hemiLight.color?.clone?.() || null,
|
|
1935
|
+
groundColor: this.hemiLight.groundColor?.clone?.() || null,
|
|
1936
|
+
} : { existed: false },
|
|
1937
|
+
shadowReceiverColor: (this.shadowReceiver?.material?.color?.clone?.() || null),
|
|
1938
|
+
};
|
|
1939
|
+
|
|
1940
|
+
// 1) Ambient: холодный общий
|
|
1941
|
+
if (this.ambientLight) {
|
|
1942
|
+
try { this.ambientLight.visible = true; } catch (_) {}
|
|
1943
|
+
try { this.ambientLight.color?.setHex?.(0xd0e0f0); } catch (_) {}
|
|
1944
|
+
try { this.ambientLight.intensity = 0.4; } catch (_) {}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// 2) Hemisphere: добавляем/включаем
|
|
1948
|
+
if (!this.hemiLight && this.scene) {
|
|
1949
|
+
try {
|
|
1950
|
+
const hemi = new THREE.HemisphereLight(0xc0d8f0, 0x444444, 0.6);
|
|
1951
|
+
this.scene.add(hemi);
|
|
1952
|
+
this.hemiLight = hemi;
|
|
1953
|
+
} catch (_) {}
|
|
1954
|
+
}
|
|
1955
|
+
if (this.hemiLight) {
|
|
1956
|
+
try { this.hemiLight.visible = true; } catch (_) {}
|
|
1957
|
+
try { this.hemiLight.color?.setHex?.(0xc0d8f0); } catch (_) {}
|
|
1958
|
+
try { this.hemiLight.groundColor?.setHex?.(0x444444); } catch (_) {}
|
|
1959
|
+
try { this.hemiLight.intensity = 0.6; } catch (_) {}
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
this._coolLighting.enabled = true;
|
|
1963
|
+
// Применим текущие параметры (hue/amount)
|
|
1964
|
+
try { this.#applyCoolLightingParams(); } catch (_) {}
|
|
1965
|
+
// Подкрасим тень на земле тем же холодным оттенком
|
|
1966
|
+
try { this.#applyShadowTintFromCoolLighting(); } catch (_) {}
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// Выключение: восстановление
|
|
1971
|
+
const snap = this._coolLighting.snapshot;
|
|
1972
|
+
this._coolLighting.enabled = false;
|
|
1973
|
+
this._coolLighting.snapshot = null;
|
|
1974
|
+
if (!snap) return;
|
|
1975
|
+
|
|
1976
|
+
// Ambient restore
|
|
1977
|
+
if (this.ambientLight && snap.ambient) {
|
|
1978
|
+
try { this.ambientLight.visible = !!snap.ambient.visible; } catch (_) {}
|
|
1979
|
+
try { if (snap.ambient.color) this.ambientLight.color.copy(snap.ambient.color); } catch (_) {}
|
|
1980
|
+
try { this.ambientLight.intensity = snap.ambient.intensity; } catch (_) {}
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// Hemisphere restore / dispose if created by us
|
|
1984
|
+
if (snap.hemi?.existed) {
|
|
1985
|
+
// Был — вернём параметры
|
|
1986
|
+
if (this.hemiLight) {
|
|
1987
|
+
try { this.hemiLight.visible = !!snap.hemi.visible; } catch (_) {}
|
|
1988
|
+
try { if (snap.hemi.color) this.hemiLight.color.copy(snap.hemi.color); } catch (_) {}
|
|
1989
|
+
try { if (snap.hemi.groundColor) this.hemiLight.groundColor.copy(snap.hemi.groundColor); } catch (_) {}
|
|
1990
|
+
try { this.hemiLight.intensity = snap.hemi.intensity; } catch (_) {}
|
|
1991
|
+
}
|
|
1992
|
+
} else {
|
|
1993
|
+
// Не было — удалим созданный
|
|
1994
|
+
if (this.hemiLight && this.scene) {
|
|
1995
|
+
try { this.scene.remove(this.hemiLight); } catch (_) {}
|
|
1996
|
+
try { this.hemiLight.dispose?.(); } catch (_) {}
|
|
1997
|
+
}
|
|
1998
|
+
this.hemiLight = null;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
// Shadow receiver color restore
|
|
2002
|
+
if (this.shadowReceiver?.material?.color) {
|
|
2003
|
+
try {
|
|
2004
|
+
if (snap.shadowReceiverColor) this.shadowReceiver.material.color.copy(snap.shadowReceiverColor);
|
|
2005
|
+
else this.shadowReceiver.material.color.copy(this._shadowReceiverBaseColor);
|
|
2006
|
+
this.shadowReceiver.material.needsUpdate = true;
|
|
2007
|
+
} catch (_) {}
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
/**
|
|
2012
|
+
* Оттенок "холодного" цвета (hue в градусах). Рекомендуемый диапазон: 190..240.
|
|
2013
|
+
* @param {number} hueDeg
|
|
2014
|
+
*/
|
|
2015
|
+
setCoolLightingHue(hueDeg) {
|
|
2016
|
+
const v = Number(hueDeg);
|
|
2017
|
+
if (!Number.isFinite(v)) return;
|
|
2018
|
+
// Разрешим шире, но UI ограничивает "холодным" диапазоном
|
|
2019
|
+
this._coolLighting.params.hueDeg = Math.round(Math.min(360, Math.max(0, v)));
|
|
2020
|
+
this.#applyCoolLightingParams();
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/**
|
|
2024
|
+
* "Сколько синего добавить" (0..1). Это не экспозиция — влияет только на оттенок (смешивание базового и холодного).
|
|
2025
|
+
* @param {number} amount
|
|
2026
|
+
*/
|
|
2027
|
+
setCoolLightingAmount(amount) {
|
|
2028
|
+
const v = Number(amount);
|
|
2029
|
+
if (!Number.isFinite(v)) return;
|
|
2030
|
+
this._coolLighting.params.amount = Math.min(1, Math.max(0, v));
|
|
2031
|
+
this.#applyCoolLightingParams();
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
#applyCoolLightingParams() {
|
|
2035
|
+
if (!this._coolLighting?.enabled) return;
|
|
2036
|
+
const snap = this._coolLighting.snapshot;
|
|
2037
|
+
if (!snap) return;
|
|
2038
|
+
|
|
2039
|
+
const hue = (this._coolLighting.params?.hueDeg ?? 210) / 360;
|
|
2040
|
+
const amount = this._coolLighting.params?.amount ?? 1.0;
|
|
2041
|
+
|
|
2042
|
+
// Целевой "холодный" цвет: светлый, с небольшим насыщением, но с регулируемым hue
|
|
2043
|
+
const target = new THREE.Color().setHSL(hue, 0.22, 0.88);
|
|
2044
|
+
|
|
2045
|
+
// Ambient: смешиваем исходный цвет с target
|
|
2046
|
+
if (this.ambientLight && snap.ambient?.color) {
|
|
2047
|
+
try {
|
|
2048
|
+
const base = snap.ambient.color.clone();
|
|
2049
|
+
this.ambientLight.color.copy(base.lerp(target, amount));
|
|
2050
|
+
} catch (_) {}
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
// Hemisphere sky: смешиваем исходный цвет с target, ground оставляем как есть
|
|
2054
|
+
if (this.hemiLight && snap.hemi?.color) {
|
|
2055
|
+
try {
|
|
2056
|
+
const base = snap.hemi.color.clone();
|
|
2057
|
+
this.hemiLight.color.copy(base.lerp(target, amount));
|
|
2058
|
+
} catch (_) {}
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
// Тень на земле: подмешиваем тот же холодный оттенок
|
|
2062
|
+
try { this.#applyShadowTintFromCoolLighting(); } catch (_) {}
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
/**
|
|
2066
|
+
* Подкрашивает тень на "земле" (ShadowMaterial приёмника) в холодный оттенок по параметрам Шага 2.
|
|
2067
|
+
* Это НЕ меняет самозатенение на модели — только цвет плоскости-приёмника.
|
|
2068
|
+
*/
|
|
2069
|
+
#applyShadowTintFromCoolLighting() {
|
|
2070
|
+
if (!this._coolLighting?.enabled) return;
|
|
2071
|
+
const receiver = this.shadowReceiver;
|
|
2072
|
+
const mat = receiver?.material;
|
|
2073
|
+
if (!receiver || !mat || !mat.color) return;
|
|
2074
|
+
|
|
2075
|
+
const amount = this._coolLighting.params?.amount ?? 1.0;
|
|
2076
|
+
|
|
2077
|
+
// Базовый цвет: нейтрально-серый (или снапшот, если приёмник существовал до включения шага)
|
|
2078
|
+
const base = (this._coolLighting.snapshot?.shadowReceiverColor?.clone?.() || this._shadowReceiverBaseColor.clone());
|
|
2079
|
+
// Целевой "холодный" цвет для тени (фиксируем для визуального подбора): #386fa4
|
|
2080
|
+
const target = new THREE.Color('#386fa4');
|
|
2081
|
+
// Amount из Шага 2 напрямую управляет подмешиванием (0..1)
|
|
2082
|
+
const t = Math.min(1, Math.max(0, amount));
|
|
2083
|
+
|
|
2084
|
+
try {
|
|
2085
|
+
mat.color.copy(base.lerp(target, t));
|
|
2086
|
+
mat.needsUpdate = true;
|
|
2087
|
+
} catch (_) {}
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
/**
|
|
2091
|
+
* Шаг 3: включает/выключает фон сцены (scene.background) и восстанавливает предыдущее значение.
|
|
2092
|
+
* @param {boolean} enabled
|
|
2093
|
+
*/
|
|
2094
|
+
setStep3BackgroundEnabled(enabled) {
|
|
2095
|
+
const next = !!enabled;
|
|
2096
|
+
if (next === this._step3Background.enabled) return;
|
|
2097
|
+
if (!this.scene) return;
|
|
2098
|
+
|
|
2099
|
+
if (next) {
|
|
2100
|
+
// Снимем, что было до (Color|Texture|null)
|
|
2101
|
+
const prev = this.scene.background ?? null;
|
|
2102
|
+
this._step3Background.snapshot = { background: prev };
|
|
2103
|
+
try { this.scene.background = new THREE.Color(this._step3Background.colorHex); } catch (_) {}
|
|
2104
|
+
this._step3Background.enabled = true;
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
const snap = this._step3Background.snapshot;
|
|
2109
|
+
this._step3Background.enabled = false;
|
|
2110
|
+
this._step3Background.snapshot = null;
|
|
2111
|
+
if (!snap) return;
|
|
2112
|
+
try { this.scene.background = snap.background ?? null; } catch (_) {}
|
|
2113
|
+
}
|
|
2114
|
+
|
|
1897
2115
|
setAOEnabled(enabled) {
|
|
1898
2116
|
const next = !!enabled;
|
|
1899
2117
|
this.visual.ao.enabled = next;
|
|
@@ -1939,6 +2157,37 @@ export class Viewer {
|
|
|
1939
2157
|
this.#applyColorCorrectionUniforms();
|
|
1940
2158
|
}
|
|
1941
2159
|
|
|
2160
|
+
// ===== Шаг 4: финальная постобработка (контраст/насыщенность) =====
|
|
2161
|
+
setStep4Enabled(enabled) {
|
|
2162
|
+
const next = !!enabled;
|
|
2163
|
+
this._step4.enabled = next;
|
|
2164
|
+
if (next) this.#ensureComposer();
|
|
2165
|
+
if (this._step4Pass) this._step4Pass.enabled = next;
|
|
2166
|
+
this.#applyStep4Uniforms();
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
setStep4Saturation(value) {
|
|
2170
|
+
const v = Number(value);
|
|
2171
|
+
if (!Number.isFinite(v)) return;
|
|
2172
|
+
// 1.0 = без изменений, <1.0 = менее насыщенно, >1.0 = более насыщенно
|
|
2173
|
+
this._step4.saturation = Math.min(3.0, Math.max(0.0, v));
|
|
2174
|
+
this.#applyStep4Uniforms();
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
setStep4Contrast(value) {
|
|
2178
|
+
const v = Number(value);
|
|
2179
|
+
if (!Number.isFinite(v)) return;
|
|
2180
|
+
// 1.0 = без изменений, <1.0 = ниже контраст, >1.0 = выше контраст
|
|
2181
|
+
this._step4.contrast = Math.min(3.0, Math.max(0.0, v));
|
|
2182
|
+
this.#applyStep4Uniforms();
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
#applyStep4Uniforms() {
|
|
2186
|
+
if (!this._step4Pass?.uniforms) return;
|
|
2187
|
+
if (this._step4Pass.uniforms.saturation) this._step4Pass.uniforms.saturation.value = this._step4.saturation ?? 1.0;
|
|
2188
|
+
if (this._step4Pass.uniforms.contrast) this._step4Pass.uniforms.contrast.value = this._step4.contrast ?? 1.0;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
1942
2191
|
#applyColorCorrectionUniforms() {
|
|
1943
2192
|
if (this._hueSatPass?.uniforms) {
|
|
1944
2193
|
this._hueSatPass.uniforms.hue.value = this.visual.color.hue ?? 0.0;
|
|
@@ -2098,6 +2347,63 @@ export class Viewer {
|
|
|
2098
2347
|
this._composer.addPass(this._bcPass);
|
|
2099
2348
|
|
|
2100
2349
|
this.#applyColorCorrectionUniforms();
|
|
2350
|
+
|
|
2351
|
+
// Шаг 4: финальный цветовой pass (должен применяться в самом конце конвейера)
|
|
2352
|
+
const step4Shader = {
|
|
2353
|
+
uniforms: {
|
|
2354
|
+
tDiffuse: { value: null },
|
|
2355
|
+
saturation: { value: this._step4?.saturation ?? 1.0 },
|
|
2356
|
+
contrast: { value: this._step4?.contrast ?? 1.0 },
|
|
2357
|
+
},
|
|
2358
|
+
vertexShader: `
|
|
2359
|
+
varying vec2 vUv;
|
|
2360
|
+
void main() {
|
|
2361
|
+
vUv = uv;
|
|
2362
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
2363
|
+
}
|
|
2364
|
+
`,
|
|
2365
|
+
fragmentShader: `
|
|
2366
|
+
uniform sampler2D tDiffuse;
|
|
2367
|
+
uniform float saturation; // 1.0 = no change
|
|
2368
|
+
uniform float contrast; // 1.0 = no change
|
|
2369
|
+
varying vec2 vUv;
|
|
2370
|
+
|
|
2371
|
+
vec3 rgb2hsv(vec3 c) {
|
|
2372
|
+
vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
|
|
2373
|
+
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
|
2374
|
+
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
|
|
2375
|
+
float d = q.x - min(q.w, q.y);
|
|
2376
|
+
float e = 1.0e-10;
|
|
2377
|
+
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
vec3 hsv2rgb(vec3 c) {
|
|
2381
|
+
vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
|
|
2382
|
+
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
|
2383
|
+
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
void main() {
|
|
2387
|
+
vec4 color = texture2D(tDiffuse, vUv);
|
|
2388
|
+
|
|
2389
|
+
// Contrast first (pivot at 0.5), then clamp to keep values stable
|
|
2390
|
+
vec3 rgb = (color.rgb - 0.5) * contrast + 0.5;
|
|
2391
|
+
rgb = clamp(rgb, 0.0, 1.0);
|
|
2392
|
+
|
|
2393
|
+
// Saturation in HSV: лучше сохраняет оттенок в тёмных тонах (например, у синеватой тени)
|
|
2394
|
+
vec3 hsv = rgb2hsv(rgb);
|
|
2395
|
+
hsv.y = clamp(hsv.y * saturation, 0.0, 1.0);
|
|
2396
|
+
rgb = hsv2rgb(hsv);
|
|
2397
|
+
|
|
2398
|
+
color.rgb = clamp(rgb, 0.0, 1.0);
|
|
2399
|
+
gl_FragColor = color;
|
|
2400
|
+
}
|
|
2401
|
+
`,
|
|
2402
|
+
};
|
|
2403
|
+
this._step4Pass = new ShaderPass(step4Shader);
|
|
2404
|
+
this._step4Pass.enabled = !!this._step4?.enabled;
|
|
2405
|
+
this._composer.addPass(this._step4Pass);
|
|
2406
|
+
this.#applyStep4Uniforms();
|
|
2101
2407
|
try { this._composer.setSize(w, h); } catch (_) {}
|
|
2102
2408
|
}
|
|
2103
2409
|
|