This commit is contained in:
Халимов Рустам
2026-01-11 13:54:10 +03:00
parent 438c7b6615
commit 6cd7a5194d
2 changed files with 174 additions and 235 deletions

View File

@@ -48,7 +48,6 @@ export const calculateParts = (config: AppConfig, splits: LayoutSplits): Generat
// --- ГЕОМЕТРИЯ ---
// 1. Форма скругленного прямоугольника (для дна и внешних стенок)
const createRoundedRectShape = (width: number, height: number, radius: number): THREE.Shape => {
const shape = new THREE.Shape();
const x = -width / 2;
@@ -75,35 +74,60 @@ 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;
};
// --- УМНЫЙ РАСЧЕТ ГРАНИЦ (Fix overlapping walls) ---
const calculateDynamicLimits = (target: Partition, allParts: Partition[]) => {
let min = 0;
let max = 1;
// Центр текущей стенки (чтобы понять, в каком мы сегменте)
const mid = ((target.min ?? 0) + (target.max ?? 1)) / 2;
allParts.forEach(p => {
// Нас интересуют только ПЕРПЕНДИКУЛЯРНЫЕ стенки
if (p.axis === target.axis) return;
// Определяем, пересекает ли соседка путь нашей стенки
// Для этого соседка должна "покрывать" нашу координату offset
const pMin = p.min ?? 0;
const pMax = p.max ?? 1;
// p.offset - это позиция соседки по нашей оси движения
// target.offset - это наша позиция по оси соседки
if (target.offset > pMin && target.offset < pMax) {
// Соседка стоит на пути. Где она? Сверху или снизу (слева или справа)?
if (p.offset < mid) {
// Соседка "перед" нами, это новая нижняя граница
min = Math.max(min, p.offset);
} else if (p.offset > mid) {
// Соседка "после" нас, это новая верхняя граница
max = Math.min(max, p.offset);
}
}
});
return { min, max };
};
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);
@@ -120,16 +144,15 @@ export const createBinGeometry = (
wallGeo.translate(0, thickness, 0);
geometries.push(wallGeo);
// ВНУТРЕННИЕ ПЕРЕГОРОДКИ И СКРУГЛЕНИЯ
partitions.forEach(p => {
const pMin = p.min ?? 0;
const pMax = p.max ?? 1;
// ИСПОЛЬЗУЕМ ДИНАМИЧЕСКИЙ РАСЧЕТ ВМЕСТО p.min/p.max
const { min: pMin, max: pMax } = calculateDynamicLimits(p, partitions);
const lengthRatio = pMax - pMin;
const midRatio = pMin + (lengthRatio / 2);
let pWidth = 0, pDepth = 0, pX = 0, pY = 0;
// Размеры самой стенки
if (p.axis === 'x') {
pWidth = thickness;
pDepth = lengthRatio * innerDepth;
@@ -142,28 +165,21 @@ export const createBinGeometry = (
pY = (-innerDepth / 2) + (innerDepth * p.offset);
}
// Создаем стенку
const partShape = createRoundedRectShape(pWidth, pDepth, 0.1); // Чуть-чуть скругляем саму стенку, чтобы не была острой
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.rotateX(-Math.PI / 2);
geo.rotateY(rotation);
geo.translate(x, thickness, y);
geometries.push(geo);
};
@@ -171,40 +187,17 @@ export const createBinGeometry = (
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
addFillet(pX - halfThick, startY, 0);
addFillet(pX + halfThick, startY, -Math.PI/2);
addFillet(pX - halfThick, endY, Math.PI/2);
addFillet(pX + halfThick, endY, Math.PI);
} 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);
}