3
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user