- •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. Руководство пользователя
- •Список использованных источников
- •Заключение
- •Приложение а Реализация математического ядра
- •Реализация матричных преобразований
- •Парсинг и сохранение данных
- •Логика отрисовки и взаимодействия
Реализация матричных преобразований
src/core/math/Matrix.ts
import type { Point } from "../types";
/**
* Класс для работы с матрицами преобразований 3x3.
* Используется для аффинных преобразований в 2D (сдвиг, поворот).
* Хранит данные в плоском массиве: [m00, m01, m02, m10, m11, m12, m20, m21, m22]
*/
export class Matrix {
values: number[];
constructor(values?: number[]) {
if (values) {
if (values.length !== 9) {
throw new Error("Matrix must have exactly 9 values!");
}
this.values = values;
} else {
this.values = [1, 0, 0, 0, 1, 0, 0, 0, 1];
}
}
/**
* Умножение матриц: A * B
* Результат - новая матрица, объединяющая эффекты (сначала B, потом A)
*/
multiply(other: Matrix): Matrix {
const a = this.values;
const b = other.values;
const result: number[] = [];
// Строка 0
result[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
result[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
result[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
// Строка 1
result[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
result[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
result[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
// Строка 2
result[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
result[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
result[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
return new Matrix(result);
}
transformPoint(p: Point): Point {
const m = this.values;
const x = m[0] * p.x + m[1] * p.y + m[2];
const y = m[3] * p.x + m[4] * p.y + m[5];
return { x, y };
}
/**
* Матрица переноса (Translation)
*/
static translation(dx: number, dy: number): Matrix {
return new Matrix([1, 0, dx, 0, 1, dy, 0, 0, 1]);
}
/**
* Матрица поворота (Rotation) вокруг начала координат (0,0)
* @param angleDegrees угол в градусах
*/
static rotation(angleDegrees: number): Matrix {
const rad = (angleDegrees * Math.PI) / 180;
const c = Math.cos(rad);
const s = Math.sin(rad);
return new Matrix([c, -s, 0, s, c, 0, 0, 0, 1]);
}
/**
* Единичная матрица
*/
static identity(): Matrix {
return new Matrix();
}
}
Парсинг и сохранение данных
src/core/parsers/FileParser.ts
import { Polygon, Line, type Point, type SceneData } from "../types";
import { COLORS } from "../theme";
export class FileParser {
/**
* Превращает данные текстового формата в данные сцены
*/
static parse(text: string): SceneData {
const polygons: Polygon[] = [];
let cuttingLine: Line | null = null;
const lines = text
.split("\n")
.map((l) => l.trim())
.filter((l) => l.length > 0);
let currentPolygonPoints: Point[] = [];
let isReadingPolygon = false;
let isReadingLine = false;
let linePoints: Point[] = [];
for (const line of lines) {
if (line === "POLYGON") {
isReadingPolygon = true;
currentPolygonPoints = [];
continue;
}
if (line === "END" && isReadingPolygon) {
if (currentPolygonPoints.length >= 3) {
polygons.push(new Polygon(currentPolygonPoints, COLORS.POLYGON.DEFAULT));
}
isReadingPolygon = false;
continue;
}
if (line === "LINE") {
isReadingLine = true;
linePoints = [];
continue;
}
const parts = line.split(/\s+/);
if (parts.length >= 2) {
const x = parseFloat(parts[0]);
const y = parseFloat(parts[1]);
if (!isNaN(x) && !isNaN(y)) {
const point: Point = { x, y };
if (isReadingPolygon) {
currentPolygonPoints.push(point);
} else if (isReadingLine) {
linePoints.push(point);
}
}
}
}
if (linePoints.length >= 2) {
cuttingLine = new Line(linePoints[0], linePoints[1]);
}
return { polygons, cuttingLine };
}
/**
* Превращает данные сцены обратно в текстовый формат
*/
static save(data: SceneData): string {
const lines: string[] = [];
data.polygons.forEach((poly) => {
lines.push("POLYGON");
poly.vertices.forEach((v) => {
lines.push(`${v.x.toFixed(2)} ${v.y.toFixed(2)}`);
});
lines.push("END");
lines.push("");
});
if (data.cuttingLine) {
lines.push("LINE");
const start = data.cuttingLine.start;
const end = data.cuttingLine.end;
lines.push(`${start.x.toFixed(2)} ${start.y.toFixed(2)}`);
lines.push(`${end.x.toFixed(2)} ${end.y.toFixed(2)}`);
}
return lines.join("\n");
}
}
