diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 0375257..f379223 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -182,11 +182,12 @@ viewer3d-core/builders/railing — classic scripts с общей глобаль версию поднимать обязательно. Актуальный срез (совпадает с `index.html`): ``` -styles-desktop.css?v=53 styles-mobile.css?v=5 state.js?v=78 -canvas.js?v=66 shared/house-builder.js?v=89 ResourceManager.js?v=14 -viewer3d-core.js?v=161 viewer3d-builders.js?v=67 viewer3d-railing.js?v=18 -viewer3d-entourage.js?v=14 editor3d.js?v=21 nav-desktop.js?v=143 -nav-mobile.js?v=6 project-io.js?v=7 backend_API/Calculator.js?v=3 +color-management.js?v=1 styles-desktop.css?v=53 styles-mobile.css?v=5 +state.js?v=78 canvas.js?v=66 shared/house-builder.js?v=90 +ResourceManager.js?v=14 viewer3d-core.js?v=163 viewer3d-builders.js?v=68 +viewer3d-railing.js?v=18 viewer3d-entourage.js?v=14 editor3d.js?v=22 +nav-desktop.js?v=143 nav-mobile.js?v=6 project-io.js?v=7 +backend_API/Calculator.js?v=3 ``` `viewer3d-entourage.js` автоматически детектит `IS_MOBILE` (UA + `innerWidth<768`) @@ -949,6 +950,33 @@ JSON-контракта `POST /api/calculate` и схемы БД лежит в g - В «Порядок подключения скриптов» добавлен актуальный срез `?v=N`, чтобы версии не искать по журналу; в решения — две строки про снап ограждения. +Ветка **Mobile**: цвета и свет сцены. + +- **Цвета — sRGB на входе, линейные внутри** (`color-management.js`, подключается + сразу после three.min.js). Рендер выводит sRGB, а three r128 принимал + `color: 0x…` / `color.set('#…')` как линейные — цвета образцов выходили заметно + светлее (серый #7e7e7e → #b8bec6 в кадре, тёмно-серый #1d2630 → #596f87). Модуль + делает то же, что ColorManagement новых three: `setHex`/`setStyle` переводят + sRGB → linear, `getHex` отдаёт sRGB (прочитанный и записанный заново цвет не + переводится дважды). `setRGB`, конструктор из трёх чисел и цвета GLB (glTF + хранит их линейными) не трогаются. +- **Свет подобран замером** на стенде (серый куб на серой площадке): + `SUN_INTENSITY = 2.6`, `SCENE_EXPOSURE = 0.95`; полусферическая подсветка только без + HDRI (1.8), с HDRI — 0. Было: солнце 1.5 при physicallyCorrectLights, экспозиция + 0.72 (её занизили, компенсируя высветление цветов, — отсюда тёмные текстуры) и + подсветка 0.3. Итог: #7e7e7e на солнце — #8e8f93 (чуть светлее образца), тень на + земле — 0.57 от освещённого места (было 0.67; промежуточный вариант с солнцем 5 и + тенью 0.43 продукт счёл слишком плотным). +- Цвета, подобранные под старое высветление, приведены к задуманным: подложка + `PAD_COLOR` 0x3c3c3c → 0x6a6a6a (≈130 в кадре, как и было задумано), рама забора + `FENCE_FRAME_COLOR` 0x2a2a2a → 0x4a4a4a. `_texAverageColor` отдаёт sRGB-hex. +- **Разметка редактора без тональной кривой** (`toneMapped: false` у линий, + маркеров, сетки, рамок фасада): оранжевый выбора теперь ровно акцент интерфейса + (раньше выходил #dab05e), белые ручки — белые. Зелёный «прочих объектов» и сетка + заданы цветами, которые раньше были видны на экране (0x73a782, 0xb6b6b6/0xc7c7c7). +- Cache-bust: `color-management.js?v=1`, `shared/house-builder.js?v=90`, + `viewer3d-core.js?v=163`, `viewer3d-builders.js?v=68`, `editor3d.js?v=22`. + Ветка **Mobile**: заглушки каталога убраны совсем. - Несуществующих товаров интерфейс больше не показывает: `STUB_RESULTS` diff --git a/TODO.md b/TODO.md index b7faff5..e7c8250 100644 --- a/TODO.md +++ b/TODO.md @@ -258,6 +258,9 @@ «По выбранным фильтрам товаров нет» и «Сбросить фильтры» вместо заглушек. - [x] Заглушки каталога убраны совсем: пустой раздел — «В этом разделе каталога пока нет товаров», недоступный каталог — «Каталог временно недоступен». +- [x] Цвета и свет: цвета образцов переводятся из sRGB в линейные (раньше на + рендере выходили светлее образцов), экспозиция и солнце подобраны замером — + текстуры не темнее, чем надо, тени плотнее (0.57 от освещённого вместо 0.67). - [x] Дорожки: замкнутое кольцо сшивается на шве (миттер, как на внутренних углах); сшитые точки (шов кольца, стык линий) перетаскиваются вместе. - [x] Смета: остался только расчёт по спецификации; описание проекта и diff --git a/color-management.js b/color-management.js new file mode 100644 index 0000000..9585bfd --- /dev/null +++ b/color-management.js @@ -0,0 +1,43 @@ +// ══════════════════════════════════════════════ +// ЦВЕТА: sRGB на входе, линейные внутри (правка 2026-10-08) +// +// Рендер выводит картинку в sRGB (renderer.outputEncoding = sRGBEncoding), а +// освещение считается в линейном пространстве. Three r128 цвета не переводит: +// `color: 0x7e7e7e` или `color.set('#7e7e7e')` он принимал как ЛИНЕЙНЫЕ, и на +// выходе цвет выходил заметно светлее образца (серый #7e7e7e — #b8bec6 в кадре, +// тёмно-серый #1d2630 — #596f87). Текстуры при этом кодировались правильно, и +// экспозицию когда-то занизили, чтобы «не разбеливало» — так потемнели они. +// +// Здесь — то, что новые версии three делают сами (ColorManagement): цвет, +// заданный шестнадцатеричным числом или CSS-строкой, считается sRGB и +// переводится в линейный; getHex() отдаёт обратно sRGB, поэтому цвет, прочитанный +// и записанный заново (средний цвет текстуры, цвет карниза), не переводится +// дважды. setRGB, конструктор с тремя числами и цвета из GLB (glTF хранит их +// линейными) не трогаются. +// +// Подключать СРАЗУ после three.min.js, до любого кода, создающего цвета. +// ══════════════════════════════════════════════ +(function () { + if (typeof THREE === 'undefined' || !THREE.Color || THREE.Color.prototype._srgbPatched) return; + const P = THREE.Color.prototype; + const setHex = P.setHex, setStyle = P.setStyle, getHex = P.getHex; + + P.setHex = function (hex) { + setHex.call(this, hex); + return this.convertSRGBToLinear(); + }; + + // Имена цветов ('white') setStyle разбирает через setHex — они уже переведены. + // Остальные формы (#hex, rgb(), hsl()) он пишет в r, g, b напрямую. + P.setStyle = function (style) { + setStyle.call(this, style); + if (/^\s*(#|rgb|hsl)/i.test(String(style))) this.convertSRGBToLinear(); + return this; + }; + + P.getHex = function () { + return getHex.call(this.clone().convertLinearToSRGB()); + }; + + P._srgbPatched = true; +})(); diff --git a/editor3d.js b/editor3d.js index 7d3da2e..a2dc756 100644 --- a/editor3d.js +++ b/editor3d.js @@ -419,9 +419,13 @@ function _e3dClearGroup() { return g; } +// Разметка редактора — интерфейс, а не предмет в сцене: тональная кривая рендера +// (ACES) её не трогает, цвета на экране — ровно цвета интерфейса (правка 2026-10-08; +// раньше оранжевый выбора выходил грязно-жёлтым #dab05e, белые ручки — серыми). function _e3dLineMat(color, width) { return new THREE.LineBasicMaterial({ color, linewidth: width || 1, depthTest: false, transparent: true, opacity: 0.95, + toneMapped: false, }); } @@ -445,6 +449,7 @@ function _e3dMarker(np, color, radius) { const geo = new THREE.CircleGeometry(radius || 0.18, 20); const mesh = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({ color, depthTest: false, transparent: true, opacity: 0.9, side: THREE.DoubleSide, + toneMapped: false, })); mesh.rotation.x = -Math.PI / 2; mesh.position.set(w.x, y, w.z); @@ -473,7 +478,9 @@ function _e3dRectPts(r) { } const E3D_COL_SEL = 0xff7a1a; // выбранный объект — оранжевый, как акцент интерфейса -const E3D_COL_IDLE = 0x2f6f3f; // прочие объекты раздела — приглушённый зелёный +// Прочие объекты раздела — приглушённый зелёный. Значение — то, что раньше было +// видно на экране (0x2f6f3f проходил через высветление и ACES и выглядел так). +const E3D_COL_IDLE = 0x73a782; // Сетка 1 м: видна, пока открыт раздел с разметкой на земле. function _e3dSyncGrid(show) { @@ -482,7 +489,9 @@ function _e3dSyncGrid(show) { if (!show) { if (E3D.grid) E3D.grid.visible = false; return; } if (!E3D.grid) { const div = Math.round(GRID / E3D_GRID_STEP); - E3D.grid = new THREE.GridHelper(GRID, div, 0x8a8a8a, 0xb9b9b9); + // Цвета — видимые раньше (0x8a8a8a / 0xb9b9b9 после высветления и ACES). + E3D.grid = new THREE.GridHelper(GRID, div, 0xb6b6b6, 0xc7c7c7); + E3D.grid.material.toneMapped = false; E3D.grid.material.transparent = true; E3D.grid.material.opacity = 0.35; E3D.grid.material.depthWrite = false; @@ -563,6 +572,7 @@ function e3dSync() { const box = new THREE.Box3().setFromObject(seg); const h = new THREE.Box3Helper(box, E3D_COL_SEL); h.material.depthTest = false; + h.material.toneMapped = false; h.renderOrder = 999; g.add(h); } diff --git a/index.html b/index.html index 71353b6..9e2be3e 100644 --- a/index.html +++ b/index.html @@ -279,6 +279,7 @@ + @@ -287,7 +288,7 @@ - + - - + + - +