A4 из docs/PARITY.md. Раньше cull_mode был None во всех пайплайнах, и
изнанка брашей рисовалась поверх настоящих поверхностей (на gm_construct
трава была видна сквозь бетонную плиту).
Намотку нельзя задать «как в карте»: в данных она непоследовательна.
Замер на gm_construct показал, что у обычных граней и у displacement'ов
противоположный порядок обхода, а нормаль плоскости у них трактуется
с разным знаком. Поэтому:
- face_normal: нормаль плоскости смотрит внутрь тела, лицевая сторона —
обратная (side == 0 => -normal);
- face_winding: геометрическая нормаль каждой грани приводится к нормали
грани, треугольник разворачивается при несовпадении;
- build_displacement: то же для рельефа, причём «наружу» — это нормаль
БАЗОВОЙ плоскости (на склонах геометрическая нормаль наклонная).
После этого инвариант «геометрическая нормаль = нормаль освещения» держится
везде, и правильная фронт-грань одна: Ccw в NDC, то есть Cw в wgpu/Vulkan
(Source использует левую систему координат).
Проверено рендерами на gm_construct: рельеф виден сверху целиком, плита и
трава вместе, трава корректно перекрыта плитой. Диагностические флаги
SOURCE_RS_NO_CULL и SOURCE_RS_FRONT_FACE=ccw оставлены для сверки.