- •1. Постановка задачи
- •Математический аппарат:
- •2. Математическое моделирование
- •2.1. Математические модели геометрических объектов
- •2.7. Определение вхождения точки в многоугольник (Ray Casting)
- •2.8. Сортировка точек на прямой (Скалярное произведение)
- •2.9. Расчет угла поворота (Арктангенс)
- •2.10. Вычисление центроида группы
- •3. Структура и Архитектура приложения
- •3.1. Слой Математического Ядра
- •3.2. Взаимодействие приложения с математикой (Interaction Pipeline)
- •3.3. Абстракция Системы Координат
- •4. Организация данных
- •5. Руководство пользователя
- •Список использованных источников
- •Заключение
- •Приложение а Реализация математического ядра
- •Реализация матричных преобразований
- •Парсинг и сохранение данных
- •Логика отрисовки и взаимодействия
Логика отрисовки и взаимодействия
drawScene из файла src/components/PixiCanvas.vue
const drawScene = () => {
if (!app || !polygonsLayer || !lineLayer || !uiGraphics) return;
polygonsLayer.removeChildren();
lineLayer.removeChildren();
uiGraphics.clear();
props.data.polygons.forEach((polyData) => {
let status = PolygonPosition.POSITIVE_SIDE;
if (props.data.cuttingLine) {
status = Geometry.checkPolygonPosition(polyData, props.data.cuttingLine);
}
if (status === PolygonPosition.INTERSECTED && props.data.cuttingLine) {
const { positive, negative } = Geometry.cutPolygon(polyData, props.data.cuttingLine);
positive.forEach((polyPoints) => {
if (polyPoints.length > 2) {
const gPos = new Graphics();
drawPolyShape(gPos, polyPoints);
gPos.fill(COLORS.POLYGON.POSITIVE);
gPos.stroke({ width: 1, color: "red", alpha: 0.5 });
setupInteractive(gPos, polyData);
polygonsLayer!.addChild(gPos);
}
});
negative.forEach((polyPoints) => {
if (polyPoints.length > 2) {
const gNeg = new Graphics();
drawPolyShape(gNeg, polyPoints);
gNeg.fill(COLORS.POLYGON.NEGATIVE);
gNeg.stroke({ width: 1, color: "red", alpha: 0.5 });
setupInteractive(gNeg, polyData);
polygonsLayer!.addChild(gNeg);
}
});
if (polyData.isSelected) {
const gSel = new Graphics();
drawPolyShape(gSel, polyData.vertices);
gSel.stroke({ width: 3, color: COLORS.POLYGON.SELECTED });
polygonsLayer!.addChild(gSel);
}
} else {
const g = new Graphics();
if (polyData.vertices.length > 2) {
drawPolyShape(g, polyData.vertices);
let fillColor = COLORS.POLYGON.DEFAULT;
if (status === PolygonPosition.POSITIVE_SIDE && props.data.cuttingLine)
fillColor = COLORS.POLYGON.POSITIVE;
else if (status === PolygonPosition.NEGATIVE_SIDE && props.data.cuttingLine)
fillColor = COLORS.POLYGON.NEGATIVE;
g.fill(fillColor);
if (polyData.isSelected) g.stroke({ width: 3, color: COLORS.POLYGON.SELECTED });
else g.stroke({ width: 1, color: "red" });
}
setupInteractive(g, polyData);
polygonsLayer!.addChild(g);
}
});
if (props.data.cuttingLine) {
const line = props.data.cuttingLine;
const gLine = new Graphics();
gLine.eventMode = "static";
gLine.cursor = "pointer";
const dx = line.end.x - line.start.x;
const dy = line.end.y - line.start.y;
const infStart = { x: line.start.x - dx * 100, y: line.start.y - dy * 100 };
const infEnd = { x: line.end.x + dx * 100, y: line.end.y + dy * 100 };
const sStart = CoordinateSystem.toScreen(infStart);
const sEnd = CoordinateSystem.toScreen(infEnd);
gLine.moveTo(sStart.x, sStart.y);
gLine.lineTo(sEnd.x, sEnd.y);
gLine.stroke({ width: 2, color: COLORS.LINE.CUTTING, alpha: 0.3 });
gLine.stroke({ width: 20, alpha: 0.001, color: 0xffffff });
const p1 = CoordinateSystem.toScreen(line.start);
const p2 = CoordinateSystem.toScreen(line.end);
const color = line.isSelected ? COLORS.POLYGON.SELECTED : COLORS.LINE.CUTTING;
gLine.moveTo(p1.x, p1.y);
gLine.lineTo(p2.x, p2.y);
gLine.stroke({ width: 4, color: color });
gLine.circle(p1.x, p1.y, 4);
gLine.circle(p2.x, p2.y, 4);
gLine.fill(color);
gLine.on("pointerdown", (e) => onObjectDown(e, line));
lineLayer.addChild(gLine);
}
if (creationPoints.length > 0) {
creationPoints.forEach((pWorld, index) => {
const pScreen = CoordinateSystem.toScreen(pWorld);
uiGraphics.circle(pScreen.x, pScreen.y, 4);
if (props.activeTool === "create-poly" && index === 0) {
uiGraphics.fill(isHoveringStart ? "#00ff00" : "#ffffff");
if (isHoveringStart) uiGraphics.stroke({ width: 2, color: "#00ff00" });
} else {
uiGraphics.fill("#ffffff");
}
});
if (props.activeTool === "create-poly" && creationPoints.length > 1) {
const start = CoordinateSystem.toScreen(creationPoints[0]);
uiGraphics.moveTo(start.x, start.y);
for (let i = 1; i < creationPoints.length; i++) {
const p = CoordinateSystem.toScreen(creationPoints[i]);
uiGraphics.lineTo(p.x, p.y);
}
uiGraphics.stroke({ width: 2, color: "#00ffff" });
}
const lastWorld = creationPoints[creationPoints.length - 1];
const lastScreen = CoordinateSystem.toScreen(lastWorld);
const mouseScreen = CoordinateSystem.toScreen(tempMousePos);
uiGraphics.moveTo(lastScreen.x, lastScreen.y);
uiGraphics.lineTo(mouseScreen.x, mouseScreen.y);
const rubberColor = props.activeTool === "create-line" ? "#ff00ff" : "#00ffff";
uiGraphics.stroke({ width: 1, color: rubberColor, alpha: 0.7 });
}
const selected = getAllSelected();
if (props.activeTool === "rotate" && selected.length > 0) {
const centerWorld = getGroupCentroid(selected);
const centerScreen = CoordinateSystem.toScreen(centerWorld);
uiGraphics.circle(centerScreen.x, centerScreen.y, 6);
uiGraphics.fill("white");
uiGraphics.stroke({ width: 1, color: "black" });
}
};
