From 4b4ebb68d18e5417e5845627481facd9af31b6d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A5=D0=B0=D0=BB=D0=B8=D0=BC=D0=BE=D0=B2=20=D0=A0=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BC?= Date: Sat, 10 Jan 2026 23:42:23 +0300 Subject: [PATCH] Radius --- src/services/geometryGenerator.ts | 91 ++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/src/services/geometryGenerator.ts b/src/services/geometryGenerator.ts index 31dcc5e..354166f 100644 --- a/src/services/geometryGenerator.ts +++ b/src/services/geometryGenerator.ts @@ -46,6 +46,9 @@ export const calculateParts = (config: AppConfig, splits: LayoutSplits): Generat return parts; }; +// --- ГЕОМЕТРИЯ --- + +// 1. Форма скругленного прямоугольника (для дна и внешних стенок) const createRoundedRectShape = (width: number, height: number, radius: number): THREE.Shape => { const shape = new THREE.Shape(); const x = -width / 2; @@ -72,16 +75,35 @@ const createRoundedRectShape = (width: number, height: number, radius: number): return shape; }; +// 2. Форма галтели (вогнутого треугольника) для углов +const createFilletShape = (radius: number): THREE.Shape => { + const shape = new THREE.Shape(); + // Начинаем из угла (0,0) + shape.moveTo(0, 0); + // Линия вдоль одной стенки + shape.lineTo(radius, 0); + // Вогнутая дуга к другой стенке + // Центр окружности (radius, radius), радиус radius. + // Рисуем дугу от 270 (-PI/2) до 180 (PI) градусов по часовой стрелке + shape.absarc(radius, radius, radius, 1.5 * Math.PI, Math.PI, true); + // Замыкаем в угол + shape.lineTo(0, 0); + + return shape; +}; + export const createBinGeometry = ( width: number, depth: number, height: number, thickness: number, radius: number = 0, partitions: Partition[] = [] ): THREE.BufferGeometry => { const geometries: THREE.BufferGeometry[] = []; + // ДНО const floorShape = createRoundedRectShape(width, depth, radius); const floorGeo = new THREE.ExtrudeGeometry(floorShape, { depth: thickness, bevelEnabled: false, curveSegments: 12 }); floorGeo.rotateX(-Math.PI / 2); geometries.push(floorGeo); + // ВНЕШНИЕ СТЕНКИ const outerShape = createRoundedRectShape(width, depth, radius); const innerRadius = Math.max(0, radius - thickness); const innerWidth = width - (2 * thickness); @@ -98,6 +120,7 @@ export const createBinGeometry = ( wallGeo.translate(0, thickness, 0); geometries.push(wallGeo); + // ВНУТРЕННИЕ ПЕРЕГОРОДКИ И СКРУГЛЕНИЯ partitions.forEach(p => { const pMin = p.min ?? 0; const pMax = p.max ?? 1; @@ -106,6 +129,7 @@ export const createBinGeometry = ( let pWidth = 0, pDepth = 0, pX = 0, pY = 0; + // Размеры самой стенки if (p.axis === 'x') { pWidth = thickness; pDepth = lengthRatio * innerDepth; @@ -118,12 +142,73 @@ export const createBinGeometry = ( pY = (-innerDepth / 2) + (innerDepth * p.offset); } - const pRadius = p.rounded ? Math.min(radius, thickness / 1.5) : 0; - const partShape = createRoundedRectShape(pWidth, pDepth, pRadius); - const partGeo = new THREE.ExtrudeGeometry(partShape, { depth: p.height, bevelEnabled: false, curveSegments: 8 }); + // Создаем стенку + const partShape = createRoundedRectShape(pWidth, pDepth, 0.1); // Чуть-чуть скругляем саму стенку, чтобы не была острой + const partGeo = new THREE.ExtrudeGeometry(partShape, { depth: p.height, bevelEnabled: false, curveSegments: 2 }); partGeo.rotateX(-Math.PI / 2); partGeo.translate(pX, thickness, pY); geometries.push(partGeo); + + // --- ДОБАВЛЕНИЕ ГАЛТЕЛЕЙ (FILLETS) --- + if (p.rounded && radius > 0.5) { + // Радиус скругления такой же, как у углов ящика, но не больше разумного предела + const filletR = Math.min(radius, 6); + const filletShape = createFilletShape(filletR); + const filletExtrudeSettings = { depth: p.height, bevelEnabled: false, curveSegments: 8 }; + + // Функция для создания и позиционирования одной галтели + const addFillet = (x: number, y: number, rotation: number) => { + const geo = new THREE.ExtrudeGeometry(filletShape, filletExtrudeSettings); + geo.rotateX(-Math.PI / 2); // Положить на пол + geo.rotateY(rotation); // Повернуть в нужный угол + + // Корректировка позиции после вращения вокруг (0,0) + // Нам нужно сместить так, чтобы угол (0,0) галтели совпал с углом стыка + geo.translate(x, thickness, y); + geometries.push(geo); + }; + + const halfThick = thickness / 2; + + if (p.axis === 'x') { + // Вертикальная стенка. Концы: Top (pMin) и Bottom (pMax) (в 2D координатах Y) + // Y координата начала: -innerDepth/2 + innerDepth * pMin + // Y координата конца: -innerDepth/2 + innerDepth * pMax + // X координата центра: pX + + const startY = (-innerDepth / 2) + (innerDepth * pMin); + const endY = (-innerDepth / 2) + (innerDepth * pMax); + + // 4 Угла: + // 1. Start Left: X = pX - halfThick, Y = startY. Rotation: 0 (смотрит вправо-вверх? нет) + // Галтель рисуется в +X, +Y квадранте от 0,0. + // Нам нужно заполнить угол между стенкой (идет вниз) и перпендикуляром. + + // Start (Top in 2D view, actually Min Y in 3D logic here usually means "Back") + // Let's assume standard plan view: + // Min Y is "Top" edge visually in SVG usually 0. In 3D Z is Y. + + // Min End (Start of wall): + addFillet(pX - halfThick, startY, 0); // Left side, pointing towards +Z (down in visual) + addFillet(pX + halfThick, startY, -Math.PI/2); // Right side + + // Max End (End of wall): + addFillet(pX - halfThick, endY, Math.PI/2); // Left side + addFillet(pX + halfThick, endY, Math.PI); // Right side + } else { + // Горизонтальная стенка + const startX = (-innerWidth / 2) + (innerWidth * pMin); + const endX = (-innerWidth / 2) + (innerWidth * pMax); + + // Min End (Left side): + addFillet(startX, pY + halfThick, -Math.PI/2); + addFillet(startX, pY - halfThick, 0); + + // Max End (Right side): + addFillet(endX, pY + halfThick, Math.PI); + addFillet(endX, pY - halfThick, Math.PI/2); + } + } }); const merged = mergeBufferGeometries(geometries);