diff --git a/src/components/LayoutStep.tsx b/src/components/LayoutStep.tsx index bde04f1..affd96a 100644 --- a/src/components/LayoutStep.tsx +++ b/src/components/LayoutStep.tsx @@ -59,17 +59,17 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { }; const selectedData = getSelectedPartition(); - // --- RAYCASTING (Надежный поиск коробки) --- - // Находит ближайшие стенки во всех 4 направлениях + // --- RAYCASTING (Поиск коробки для НОВОЙ стенки) --- + // Мы ищем ближайшие препятствия, чтобы определить границы новой стенки const getCursorBox = (lx: number, ly: number, parts: Partition[]) => { let minX = 0, maxX = 1; let minY = 0, maxY = 1; parts.forEach(p => { - // Используем сохраненные границы (они теперь достоверны) + // Используем сохраненные границы (честные) const pMin = p.min ?? 0; const pMax = p.max ?? 1; - const EPS = 0.005; // Допуск на попадание + const EPS = 0.005; // Допуск if (p.axis === 'x') { // Вертикальная стенка. Перекрывает ли она наш Y? @@ -90,7 +90,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { return { minX, maxX, minY, maxY }; }; - // Поиск соседей для размеров (Та же логика, что Raycasting) + // Поиск соседей для размеров (Используем сохраненные данные) const getNeighborOffsets = (offset: number, crossPos: number, axis: Axis, parts: Partition[]) => { let min = 0; let max = 1; @@ -232,7 +232,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { if (found) { setHoveredPartition({ id: found.id, cellKey: key }); } else { - // --- FIND BOX --- + // --- FIND BOX USING RAYCASTING --- const box = getCursorBox(lx, ly, parts); const boxW = (box.maxX - box.minX) * realCellW; @@ -244,11 +244,12 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { // Override if near edges const relL = (lx - box.minX) / (box.maxX - box.minX); const relT = (ly - box.minY) / (box.maxY - box.minY); - const THRESHOLD = 0.2; + const THRESHOLD = 0.2; // 20% zone near edges - if (relL < THRESHOLD || relL > 1 - THRESHOLD) newAxis = 'x'; // Near vertical edge -> vertical wall - else if (relT < THRESHOLD || relT > 1 - THRESHOLD) newAxis = 'y'; // Near horiz edge -> horizontal wall + if (relL < THRESHOLD || relL > 1 - THRESHOLD) newAxis = 'x'; + else if (relT < THRESHOLD || relT > 1 - THRESHOLD) newAxis = 'y'; + // Validate space const valid = (newAxis === 'x' && (box.maxX - box.minX) > 0.05) || (newAxis === 'y' && (box.maxY - box.minY) > 0.05); if (valid) { @@ -298,6 +299,16 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { } }; + // --- HANDLER: Clear Phantom on Leave --- + const handleMouseLeave = () => { + setDragging(null); + setPhantomMainAxis(null); + setHoveredMainSplit(null); + setHoveredCell(null); + setHoveredPartition(null); + setPhantomPartition(null); + }; + // --- RENDER --- const renderCellsAndPartitions = () => { const elements = []; @@ -430,7 +441,13 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => {
1 ? 'auto' : '100%', height: aspectRatio > 1 ? '100%' : 'auto', aspectRatio: `${1/aspectRatio}`, maxHeight: '100%', maxWidth: '100%', cursor: mode === 'lines' ? (dragging ? 'grabbing' : hoveredMainSplit ? 'col-resize' : 'crosshair') : (dragging ? 'grabbing' : hoveredPartition ? 'grab' : hoveredCell ? 'crosshair' : 'default') }}> - setDragging(null)} onMouseLeave={() => setDragging(null)} onContextMenu={(e) => e.preventDefault()}> + setDragging(null)} + onMouseLeave={handleMouseLeave} // ДОБАВЛЕНО СЮДА + onContextMenu={(e) => e.preventDefault()} + > {renderCellsAndPartitions()} diff --git a/src/services/geometryGenerator.ts b/src/services/geometryGenerator.ts index 58d7263..8633c7d 100644 --- a/src/services/geometryGenerator.ts +++ b/src/services/geometryGenerator.ts @@ -110,9 +110,9 @@ export const createBinGeometry = ( wallGeo.translate(0, thickness, 0); geometries.push(wallGeo); - // ВНУТРЕННИЕ ПЕРЕГОРОДКИ - // Мы просто верим сохраненным данным (p.min/p.max). Они должны быть корректны при создании. + // ВНУТРЕННИЕ ПЕРЕГОРОДКИ (СТРОГО ПО ДАННЫМ, БЕЗ SOLVER) partitions.forEach(p => { + // Берем данные напрямую. Если в 2D нарисовано от 0.2 до 0.8, тут будет 0.2 до 0.8. const pMin = p.min ?? 0; const pMax = p.max ?? 1; @@ -141,38 +141,57 @@ 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) => { + if (pos < 0.001 || pos > 0.999) return height; // Край ящика + + const neighbor = partitions.find(n => { + if (n.axis === p.axis) return false; // Перпендикуляр + const nMin = n.min ?? 0; + const nMax = n.max ?? 1; + // Совпадает ли позиция? + if (Math.abs(n.offset - pos) > 0.002) return false; + // Перекрывает ли? + return p.offset > nMin && p.offset < nMax; + }); + return neighbor ? neighbor.height : 0; + }; + + const hStart = Math.min(p.height, getNeighborHeight(pMin)); + const hEnd = Math.min(p.height, getNeighborHeight(pMax)); + + 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') { 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); + + addFillet(pX - t, topY, Math.PI, hStart); + addFillet(pX + t, topY, -Math.PI / 2, hStart); + addFillet(pX - t, botY, Math.PI / 2, hEnd); + addFillet(pX + t, botY, 0, hEnd); } else { const leftX = (-innerWidth / 2) + (innerWidth * pMin); - addFillet(leftX, pY - h, 0); - addFillet(leftX, pY + h, -Math.PI / 2); - const rightX = (-innerWidth / 2) + (innerWidth * pMax); - addFillet(rightX, pY - h, Math.PI / 2); - addFillet(rightX, pY + h, Math.PI); + + addFillet(leftX, pY - t, 0, hStart); + addFillet(leftX, pY + t, -Math.PI / 2, hStart); + addFillet(rightX, pY - t, Math.PI / 2, hEnd); + addFillet(rightX, pY + t, Math.PI, hEnd); } } });