Загрузка данных
План стал заметно лучше, но в текущем виде ещё не утверждай. Он правильно отказался от шаров и нашёл реальную потенциальную причину — торцевые крышки раскосов. Однако сейчас Claude слишком рано решил, что именно крышки объясняют весь дефект.
По скриншоту проблема выглядит крупнее обычного шва от крышки: заметны также наложения боковых граней и неаккуратное поведение геометрии у крайних узлов.
Что в плане правильно
Правильно:
удалить крышки с обоих концов раскосов;
оставить крышки только на настоящих свободных концах поясов;
не менять координаты узлов и шаг панелей;
не добавлять сферы, муфты и декоративные накладки;
сначала исследовать увеличенные узлы;
не увеличивать количество геометрии.
Но нужно исправить пять моментов.
1. Отключение крышки не убирает торчащее кольцо стержня
У bar3d торцевая крышка — только набор треугольников, закрывающих отверстие. После capStart=false останется торцевое кольцо, к которому приходят боковые грани призмы.
Поскольку раскос наклонён, плоскость этого кольца имеет разброс по U. В крайнем узле с центром u=0 часть вершин кольца может иметь U<0. На другом конце — U>length.
То есть можно удалить крышку, но синий «шип» за торцом останется.
В плане нельзя оставлять:
если перелёт субвизуален — обосновать
По критериям проекта любой выход за пролёт недопустим. Нужно либо исправить, либо не сдавать.
Добавь:
Отключение capStart/capEnd удаляет только треугольники крышки, но НЕ удаляет и НЕ перемещает торцевое кольцо боковых граней bar3d.
Поэтому после удаления крышек обязательно проверить ВСЕ вершины торцевых колец первого и последнего раскоса.
Если хотя бы одна вершина имеет U < -eps или U > length + eps, это считается реальным нарушением, независимо от того, насколько оно заметно на общем кадре.
Разрешённые способы исправления:
1. геометрически клиповать треугольники раскоса плоскостями U=0 и U=length;
2. либо использовать отдельные render-endpoints, утопленные внутрь объёма пояса настолько, чтобы полное торцевое кольцо оставалось внутри пролёта и было закрыто поясом.
Логические узлы leftBottom/rightBottom/top при этом не меняются. Запрещены ручные поправки для конкретной балки или конкретного торца.
В финале:
minU полной геометрии >= -eps;
maxU полной геометрии <= length + eps.
Объяснение «перелёт почти не виден» не принимается.
2. TIE_EPS=0.15 применять нельзя
Это очень большое значение:
rodRadius = 0.03;
0.15 — пять радиусов;
это около 16% длины панели 0.92.
Такой компаратор начнёт переставлять не только грани одного соединения, но и соседние поверхности. Фраза «на порядок больше rodRadius» также математически неверна: 0.15 больше 0.03 в пять раз, а не в десять.
Глобальный tie-break по одному лишь расстоянию глубины может снова создать хаотичные наложения.
Замени весь пункт 3 на:
Tie-breaker НЕ внедрять одновременно с удалением крышек.
Этап A:
- удалить крышки раскосов;
- отрендерить контрольные узлы;
- проверить результат.
Только если после этапа A останется доказанная ошибка порядка граней, переходить к этапу B.
Этап B:
tie-breaker разрешён только для граней:
- одной балки;
- одного и того же соединения jointId;
- фактически пересекающихся в проекции в области этого узла.
Запрещено использовать глобальный TIE_EPS=0.15.
Каждый узел получает стабильный jointId, например:
beam-2-left-lower-3
beam-2-right-lower-3
beam-2-top-2
Приоритет типа грани применяется только внутри одинакового jointId. Грани разных узлов, разных балок и удалённых участков никогда не переставляются этим правилом.
Если числовой допуск всё же необходим, начать с допуска порядка 1e-6 в единицах depthKey и повышать его только на основании измеренной разницы конкретных граней одного узла.
3. Один длинный пояс плохо совместим с локальной сортировкой
Сейчас каждый пояс — один bar3d длиной 4.6. Значит, его боковые грани представляют собой очень длинные треугольники, а depthKey вычисляется по их центроиду примерно в середине балки.
В районе торца такой длинный треугольник может пересекаться с коротким раскосом, но сортироваться по глубине своего далёкого центроида. Локальный jointId здесь не поможет: одна грань пояса одновременно проходит через много узлов.
Это очень вероятная причина визуальной грязи.
Нужно добавить:
Продольный пояс остаётся ОДНИМ непрерывным конструктивным элементом, но его render mesh необходимо разбить по узлам на несколько последовательно примыкающих участков.
Нижние пояса разделить в точках:
u = 0, pitch, 2*pitch, ..., length.
Верхний пояс разделить в точках:
u = 0, um_0, um_1, ..., um_last, length.
Правила:
- внутренние участки не имеют торцевых крышек;
- крышка существует только на первом внешнем конце первого участка и последнем внешнем конце последнего участка;
- соседние участки используют абсолютно одинаковую ориентацию восьмигранного сечения, чтобы на стыке не было поворота фасок;
- визуально пояс остаётся непрерывным;
- логическая модель одного пояса не меняется.
Цель разделения — получить короткие локальные боковые грани, глубина которых корректно сортируется около соответствующего узла, вместо одной длинной грани с центроидом в середине балки.
Это не возвращает «куски пояса» конструктивно. Это только правильная тесселяция для painter’s algorithm.
4. Проверка количества крышек сформулирована неоднозначно
В плане написано:
крышек поясов — ровно 6
Но если восьмиугольная крышка разбивается на восемь треугольников, в массиве будет не 6 граней, а:
3 пояса × 2 конца × 8 треугольников = 48 треугольников крышек
Нужно считать не полигоны, а группы крышек или уникальные capId.
Замени проверку:
Каждая крышка получает уникальный capId.
Для одной балки должно быть:
- 0 уникальных capId у раскосов;
- ровно 6 уникальных capId у продольных поясов:
3 пояса × 2 свободных конца.
Количество треугольников крышек зависит от sides:
при sides=8 ожидается 6 × 8 = 48 cap-треугольников на балку, если каждая крышка строится веером из 8 треугольников.
Не путать число физических крышек с числом треугольных граней.
5. Сигнатура с двумя позиционными boolean опасна
Вот это:
bar3d(..., kind, capStart, capEnd)
легко вызвать как false, true вместо true, false. Лучше объект параметров:
bar3d(faces, pA, pB, radius, sides, beamId, kind, {
capStart = true,
capEnd = true,
jointStartId = null,
jointEndId = null
})
Для раскоса:
bar3d(..., {
capStart: false,
capEnd: false,
jointStartId,
jointEndId
})
Для сегментов пояса внутренние крышки задаются явно.
Готовый комментарий Claude
Отправь ему это:
План почти принят, но требует корректировки перед реализацией.
1. Удаление capStart/capEnd удаляет только крышку, но не торцевое кольцо боковых граней. У первого/последнего наклонного раскоса кольцо может выходить за U=0..length. «Субвизуальный перелёт» не допускается. Полная геометрия после исправления обязана удовлетворять minU >= -eps и maxU <= length+eps. При нарушении клиповать меш плоскостями пролёта либо использовать единообразные render-endpoints, утопленные внутрь объёма пояса. Логические узлы не менять.
2. Не вводить TIE_EPS=0.15. Это слишком большое значение: 5×rodRadius и около 16% pitch. Оно может переставить грани соседних узлов. Сначала реализовать только управление крышками и проверить рендер. Tie-breaker добавлять лишь при доказанной остаточной ошибке.
3. Если tie-breaker понадобится, он действует только для одинаковых beamId + jointId, а не для всех граней с близким depthKey. Начальный числовой допуск порядка 1e-6, а не 0.15.
4. Один bar3d на всю длину пояса создаёт длинные треугольники, сортируемые по центроиду в середине балки. Для корректной локальной сортировки разбить только render mesh поясов по точкам соединений:
- нижние пояса — по u_k;
- верхний пояс — по um_k.
Внутренних крышек нет, внешние крышки только на двух истинных концах. Ориентация восьмигранного сечения всех соседних участков идентична. Логически это по-прежнему один непрерывный пояс.
5. Считать крышки по уникальному capId:
- диагонали: 0 физических крышек;
- пояса: 6 физических крышек на балку.
При sides=8 это может быть 48 cap-треугольников, а не 6 граней.
6. Вместо позиционных capStart/capEnd использовать options object, чтобы не перепутать boolean:
bar3d(..., {
capStart,
capEnd,
jointStartId,
jointEndId
})
7. Реализацию проводить по воротам:
A. Диагностика.
B. Удаление крышек раскосов и проверочный рендер.
C. Разбиение render mesh длинных поясов и повторный рендер.
D. Только если всё ещё доказана ошибка сортировки — локальный jointId tie-breaker.
Не внедрять все три изменения одновременно: иначе невозможно определить, какое из них исправило или ухудшило результат.
После этих изменений план можно утверждать. В нынешнем варианте его главный риск — огромный глобальный TIE_EPS и надежда, что удаление дисков автоматически уберёт всю грязь узла. Это не гарантировано.