diff --git a/src/services/geometryGenerator.ts b/src/services/geometryGenerator.ts index 58d7263..05a1e2c 100644 --- a/src/services/geometryGenerator.ts +++ b/src/services/geometryGenerator.ts @@ -2,6 +2,37 @@ import * as THREE from 'three'; import { STLExporter, mergeBufferGeometries } from 'three-stdlib'; import { AppConfig, LayoutSplits, GeneratedPart, Partition } from '../types'; +type Limits = { min: number; max: number }; +type LimitMap = Record; + +// --- SOLVER --- +const solveWallLimits = (partitions: Partition[]): LimitMap => { + const limits: LimitMap = {}; + partitions.forEach(p => { limits[p.id] = { min: 0, max: 1 }; }); + + for (let pass = 0; pass < 3; pass++) { + partitions.forEach(target => { + let newMin = 0; + let newMax = 1; + const center = target.offset; + + partitions.forEach(obstacle => { + if (target.id === obstacle.id || target.axis === obstacle.axis) return; + + const obsMin = limits[obstacle.id].min; + const obsMax = limits[obstacle.id].max; + + if (target.offset > obsMin && target.offset < obsMax) { + if (obstacle.offset < center) newMin = Math.max(newMin, obstacle.offset); + else if (obstacle.offset > center) newMax = Math.min(newMax, obstacle.offset); + } + }); + limits[target.id] = { min: newMin, max: newMax }; + }); + } + return limits; +}; + export const calculateParts = (config: AppConfig, splits: LayoutSplits): GeneratedPart[] => { const parts: GeneratedPart[] = []; const safeX = Array.isArray(splits?.x) ? splits.x : []; @@ -111,11 +142,10 @@ export const createBinGeometry = ( geometries.push(wallGeo); // ВНУТРЕННИЕ ПЕРЕГОРОДКИ - // Мы просто верим сохраненным данным (p.min/p.max). Они должны быть корректны при создании. - partitions.forEach(p => { - const pMin = p.min ?? 0; - const pMax = p.max ?? 1; + const limitMap = solveWallLimits(partitions); + partitions.forEach(p => { + const { min: pMin, max: pMax } = limitMap[p.id]; if (pMax - pMin < 0.01) return; const lengthRatio = pMax - pMin; @@ -141,38 +171,77 @@ export const createBinGeometry = ( partGeo.translate(pX, thickness, pY); geometries.push(partGeo); - // СКРУГЛЕНИЯ + // --- ДОБАВЛЕНИЕ ГАЛТЕЛЕЙ (FILLETS) --- if (p.rounded && radius > 1) { const filletR = Math.min(radius, 5); const filletShape = createConcaveFilletShape(filletR); - const filletExtrude = { depth: p.height, bevelEnabled: false }; - const addFillet = (x: number, y: number, rotY: number) => { - const geo = new THREE.ExtrudeGeometry(filletShape, filletExtrude); + // ФУНКЦИЯ ДЛЯ ПОИСКА ВЫСОТЫ СОСЕДА + const getNeighborHeight = (pos: number) => { + // Если это край ящика (0 или 1), то высота равна высоте ящика + if (pos < 0.001 || pos > 0.999) return height; + + // Ищем внутреннюю стенку в этой точке + const neighbor = partitions.find(n => { + if (n.axis === p.axis) return false; // Должна быть перпендикулярна + + // Соседка должна находиться в точке pos (по своей оси смещения) + if (Math.abs(n.offset - pos) > 0.001) return false; + + // И соседка должна перекрывать нашу стенку (по своей длине) + const nLims = limitMap[n.id]; + return p.offset >= nLims.min && p.offset <= nLims.max; + }); + + // Если нашли соседа - возвращаем его высоту, иначе 0 (не должно случаться при корректном Solver) + return neighbor ? neighbor.height : 0; + }; + + // Вычисляем высоту скругления для начала и конца стенки + // Высота не может быть больше самой стенки (p.height) и больше соседа + 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) => { + if (h <= 1) return; // Если высота слишком мала, не рисуем + const geo = new THREE.ExtrudeGeometry(filletShape, { depth: h, bevelEnabled: false }); geo.rotateX(-Math.PI / 2); geo.rotateY(rotY); geo.translate(x, thickness, y); geometries.push(geo); }; - const h = thickness / 2; + const t = thickness / 2; if (p.axis === 'x') { + // VERTICAL WALL const topY = (-innerDepth / 2) + (innerDepth * pMin); - addFillet(pX - h, topY, Math.PI); - addFillet(pX + h, topY, -Math.PI / 2); - const botY = (-innerDepth / 2) + (innerDepth * pMax); - addFillet(pX - h, botY, Math.PI / 2); - addFillet(pX + h, botY, 0); - } else { - const leftX = (-innerWidth / 2) + (innerWidth * pMin); - addFillet(leftX, pY - h, 0); - addFillet(leftX, pY + h, -Math.PI / 2); + // Top End (pMin) -> hStart + addFillet(pX - t, topY, Math.PI, 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, 0, hEnd); + + } else { + // HORIZONTAL WALL + const leftX = (-innerWidth / 2) + (innerWidth * pMin); const rightX = (-innerWidth / 2) + (innerWidth * pMax); - addFillet(rightX, pY - h, Math.PI / 2); - addFillet(rightX, pY + h, Math.PI); + + // Left End (pMin) -> hStart + addFillet(leftX, pY - t, 0, 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, hEnd); } } });