1
This commit is contained in:
@@ -5,12 +5,15 @@ import { AppConfig, LayoutSplits, GeneratedPart, Partition } from '../types';
|
|||||||
type Limits = { min: number; max: number };
|
type Limits = { min: number; max: number };
|
||||||
type LimitMap = Record<string, Limits>;
|
type LimitMap = Record<string, Limits>;
|
||||||
|
|
||||||
// --- SOLVER ---
|
// --- SOLVER: Идентичен тому, что в LayoutStep.tsx ---
|
||||||
|
// Гарантирует, что 3D модель будет выглядеть точно так же, как 2D макет
|
||||||
const solveWallLimits = (partitions: Partition[]): LimitMap => {
|
const solveWallLimits = (partitions: Partition[]): LimitMap => {
|
||||||
const limits: LimitMap = {};
|
const limits: LimitMap = {};
|
||||||
|
// 1. Сброс границ
|
||||||
partitions.forEach(p => { limits[p.id] = { min: 0, max: 1 }; });
|
partitions.forEach(p => { limits[p.id] = { min: 0, max: 1 }; });
|
||||||
|
|
||||||
for (let pass = 0; pass < 3; pass++) {
|
// 2. Итеративное решение коллизий (4 прохода для надежности)
|
||||||
|
for (let pass = 0; pass < 4; pass++) {
|
||||||
partitions.forEach(target => {
|
partitions.forEach(target => {
|
||||||
let newMin = 0;
|
let newMin = 0;
|
||||||
let newMax = 1;
|
let newMax = 1;
|
||||||
@@ -19,10 +22,15 @@ const solveWallLimits = (partitions: Partition[]): LimitMap => {
|
|||||||
partitions.forEach(obstacle => {
|
partitions.forEach(obstacle => {
|
||||||
if (target.id === obstacle.id || target.axis === obstacle.axis) return;
|
if (target.id === obstacle.id || target.axis === obstacle.axis) return;
|
||||||
|
|
||||||
|
// Берем актуальные границы препятствия
|
||||||
const obsMin = limits[obstacle.id].min;
|
const obsMin = limits[obstacle.id].min;
|
||||||
const obsMax = limits[obstacle.id].max;
|
const obsMax = limits[obstacle.id].max;
|
||||||
|
|
||||||
if (target.offset > obsMin && target.offset < obsMax) {
|
// ВАЖНО: Используем тот же допуск (EPSILON), что и в 2D редакторе
|
||||||
|
const EPS = 0.002;
|
||||||
|
|
||||||
|
// Проверяем пересечение
|
||||||
|
if (target.offset >= obsMin - EPS && target.offset <= obsMax + EPS) {
|
||||||
if (obstacle.offset < center) newMin = Math.max(newMin, obstacle.offset);
|
if (obstacle.offset < center) newMin = Math.max(newMin, obstacle.offset);
|
||||||
else if (obstacle.offset > center) newMax = Math.min(newMax, obstacle.offset);
|
else if (obstacle.offset > center) newMax = Math.min(newMax, obstacle.offset);
|
||||||
}
|
}
|
||||||
@@ -53,6 +61,7 @@ export const calculateParts = (config: AppConfig, splits: LayoutSplits): Generat
|
|||||||
|
|
||||||
const rawX = xPoints[i] * config.drawer.width;
|
const rawX = xPoints[i] * config.drawer.width;
|
||||||
const rawY = yPoints[j] * config.drawer.depth;
|
const rawY = yPoints[j] * config.drawer.depth;
|
||||||
|
|
||||||
const internalPartitions = safeParts[`${i}-${j}`] || [];
|
const internalPartitions = safeParts[`${i}-${j}`] || [];
|
||||||
|
|
||||||
const realWidth = rawW - config.printerTolerance;
|
const realWidth = rawW - config.printerTolerance;
|
||||||
@@ -141,11 +150,13 @@ export const createBinGeometry = (
|
|||||||
wallGeo.translate(0, thickness, 0);
|
wallGeo.translate(0, thickness, 0);
|
||||||
geometries.push(wallGeo);
|
geometries.push(wallGeo);
|
||||||
|
|
||||||
// ВНУТРЕННИЕ ПЕРЕГОРОДКИ
|
// ВНУТРЕННИЕ ПЕРЕГОРОДКИ (С SOLVER'ом)
|
||||||
const limitMap = solveWallLimits(partitions);
|
const limitMap = solveWallLimits(partitions);
|
||||||
|
|
||||||
partitions.forEach(p => {
|
partitions.forEach(p => {
|
||||||
|
// Используем рассчитанные границы, а не старые сохраненные
|
||||||
const { min: pMin, max: pMax } = limitMap[p.id];
|
const { min: pMin, max: pMax } = limitMap[p.id];
|
||||||
|
|
||||||
if (pMax - pMin < 0.01) return;
|
if (pMax - pMin < 0.01) return;
|
||||||
|
|
||||||
const lengthRatio = pMax - pMin;
|
const lengthRatio = pMax - pMin;
|
||||||
@@ -171,43 +182,29 @@ export const createBinGeometry = (
|
|||||||
partGeo.translate(pX, thickness, pY);
|
partGeo.translate(pX, thickness, pY);
|
||||||
geometries.push(partGeo);
|
geometries.push(partGeo);
|
||||||
|
|
||||||
// --- ДОБАВЛЕНИЕ ГАЛТЕЛЕЙ (FILLETS) ---
|
// СКРУГЛЕНИЯ
|
||||||
if (p.rounded && radius > 1) {
|
if (p.rounded && radius > 1) {
|
||||||
const filletR = Math.min(radius, 5);
|
const filletR = Math.min(radius, 5);
|
||||||
const filletShape = createConcaveFilletShape(filletR);
|
const filletShape = createConcaveFilletShape(filletR);
|
||||||
|
|
||||||
// ФУНКЦИЯ ДЛЯ ПОИСКА ВЫСОТЫ СОСЕДА
|
// Функция высоты соседа (чтобы скругление не висело в воздухе)
|
||||||
const getNeighborHeight = (pos: number) => {
|
const getNeighborHeight = (pos: number) => {
|
||||||
// Если это край ящика (0 или 1), то высота равна высоте ящика
|
if (pos < 0.001 || pos > 0.999) return height; // Край ящика = полная высота
|
||||||
if (pos < 0.001 || pos > 0.999) return height;
|
|
||||||
|
|
||||||
// Ищем внутреннюю стенку в этой точке
|
|
||||||
const neighbor = partitions.find(n => {
|
const neighbor = partitions.find(n => {
|
||||||
if (n.axis === p.axis) return false; // Должна быть перпендикулярна
|
if (n.axis === p.axis) return false;
|
||||||
|
// Проверка на пересечение координат
|
||||||
// Соседка должна находиться в точке pos (по своей оси смещения)
|
const nLims = limitMap[n.id]; // Берем актуальные границы соседа
|
||||||
if (Math.abs(n.offset - pos) > 0.001) return false;
|
return Math.abs(n.offset - pos) < 0.002 && p.offset >= nLims.min && p.offset <= nLims.max;
|
||||||
|
|
||||||
// И соседка должна перекрывать нашу стенку (по своей длине)
|
|
||||||
const nLims = limitMap[n.id];
|
|
||||||
return p.offset >= nLims.min && p.offset <= nLims.max;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Если нашли соседа - возвращаем его высоту, иначе 0 (не должно случаться при корректном Solver)
|
|
||||||
return neighbor ? neighbor.height : 0;
|
return neighbor ? neighbor.height : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Вычисляем высоту скругления для начала и конца стенки
|
const hStart = Math.min(p.height, getNeighborHeight(pMin));
|
||||||
// Высота не может быть больше самой стенки (p.height) и больше соседа
|
const hEnd = Math.min(p.height, getNeighborHeight(pMax));
|
||||||
const startNeighborH = getNeighborHeight(pMin);
|
|
||||||
const endNeighborH = getNeighborHeight(pMax);
|
|
||||||
|
|
||||||
const hStart = Math.min(p.height, startNeighborH);
|
|
||||||
const hEnd = Math.min(p.height, endNeighborH);
|
|
||||||
|
|
||||||
// Функция добавления с конкретной высотой
|
|
||||||
const addFillet = (x: number, y: number, rotY: number, h: number) => {
|
const addFillet = (x: number, y: number, rotY: number, h: number) => {
|
||||||
if (h <= 1) return; // Если высота слишком мала, не рисуем
|
if (h <= 1) return;
|
||||||
const geo = new THREE.ExtrudeGeometry(filletShape, { depth: h, bevelEnabled: false });
|
const geo = new THREE.ExtrudeGeometry(filletShape, { depth: h, bevelEnabled: false });
|
||||||
geo.rotateX(-Math.PI / 2);
|
geo.rotateX(-Math.PI / 2);
|
||||||
geo.rotateY(rotY);
|
geo.rotateY(rotY);
|
||||||
@@ -218,28 +215,21 @@ export const createBinGeometry = (
|
|||||||
const t = thickness / 2;
|
const t = thickness / 2;
|
||||||
|
|
||||||
if (p.axis === 'x') {
|
if (p.axis === 'x') {
|
||||||
// VERTICAL WALL
|
|
||||||
const topY = (-innerDepth / 2) + (innerDepth * pMin);
|
const topY = (-innerDepth / 2) + (innerDepth * pMin);
|
||||||
const botY = (-innerDepth / 2) + (innerDepth * pMax);
|
const botY = (-innerDepth / 2) + (innerDepth * pMax);
|
||||||
|
|
||||||
// Top End (pMin) -> hStart
|
|
||||||
addFillet(pX - t, topY, Math.PI, hStart);
|
addFillet(pX - t, topY, Math.PI, hStart);
|
||||||
addFillet(pX + t, topY, -Math.PI / 2, hStart);
|
addFillet(pX + t, topY, -Math.PI / 2, hStart);
|
||||||
|
|
||||||
// Bottom End (pMax) -> hEnd
|
|
||||||
addFillet(pX - t, botY, Math.PI / 2, hEnd);
|
addFillet(pX - t, botY, Math.PI / 2, hEnd);
|
||||||
addFillet(pX + t, botY, 0, hEnd);
|
addFillet(pX + t, botY, 0, hEnd);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// HORIZONTAL WALL
|
|
||||||
const leftX = (-innerWidth / 2) + (innerWidth * pMin);
|
const leftX = (-innerWidth / 2) + (innerWidth * pMin);
|
||||||
const rightX = (-innerWidth / 2) + (innerWidth * pMax);
|
const rightX = (-innerWidth / 2) + (innerWidth * pMax);
|
||||||
|
|
||||||
// Left End (pMin) -> hStart
|
|
||||||
addFillet(leftX, pY - t, 0, hStart);
|
addFillet(leftX, pY - t, 0, hStart);
|
||||||
addFillet(leftX, pY + t, -Math.PI / 2, hStart);
|
addFillet(leftX, pY + t, -Math.PI / 2, hStart);
|
||||||
|
|
||||||
// Right End (pMax) -> hEnd
|
|
||||||
addFillet(rightX, pY - t, Math.PI / 2, hEnd);
|
addFillet(rightX, pY - t, Math.PI / 2, hEnd);
|
||||||
addFillet(rightX, pY + t, Math.PI, hEnd);
|
addFillet(rightX, pY + t, Math.PI, hEnd);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user