@webwriter/geometry-cloze
Version:
Create and view geometry exercises with coloring, styling and labeling options.
229 lines (201 loc) • 6.41 kB
text/typescript
export type MathPoint = { x: number; y: number };
export type MathLine = {
start: MathPoint;
end: MathPoint;
};
export type MathRect = { x1: number; y1: number; x2: number; y2: number };
export enum IsAbove {
Above = 1,
On = 0,
Below = -1
}
export default class Calc {
/**
* Calculate the distance between a line and a point
*/
static distance(line: MathLine, point: MathPoint): number;
/**
* Calculate the distance between two points
*/
static distance(point1: MathPoint, point2: MathPoint): number;
static distance(obj1: MathPoint | MathLine, obj2: MathPoint) {
if ('start' in obj1) return this.distanceLinePoint(obj1, obj2);
else return this.distancePointPoint(obj1, obj2);
}
private static distanceLinePoint(line: MathLine, point: MathPoint) {
const { x: x1, y: y1 } = line.start;
const { x: x2, y: y2 } = line.end;
const { x: x3, y: y3 } = point;
const px = x2 - x1;
const py = y2 - y1;
let u = ((x3 - x1) * px + (y3 - y1) * py) / (px ** 2 + py ** 2);
u = Math.max(0, Math.min(1, u));
const x = x1 + u * px;
const y = y1 + u * py;
const dx = x - x3;
const dy = y - y3;
const dist = Math.sqrt(dx * dx + dy * dy);
return dist;
}
private static distancePointPoint(point1: MathPoint, point2: MathPoint) {
return Math.sqrt(
Math.abs((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2)
);
}
/**
* Get the point with the extreme value of a given axis
*/
static getExtremePoint(
points: MathPoint[],
extreme: 'max' | 'min',
axis: 'x' | 'y'
): MathPoint | null {
const compare = extreme === 'max' ? Math.max : Math.min;
return (
points.reduce((extremePoint, point) =>
compare(extremePoint[axis], point[axis]) === extremePoint[axis]
? extremePoint
: point
) ?? null
);
}
/**
* Calculate if a point is above a line
*/
static isPointAboveLine(line: MathLine, point: MathPoint): IsAbove {
const { x: x1, y: y1 } = line.start;
const { x: x2, y: y2 } = line.end;
const { x: x3, y: y3 } = point;
const d = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);
if (d > 0) return IsAbove.Above;
if (d < 0) return IsAbove.Below;
return IsAbove.On;
}
/**
* Calculate if a point is in a rect
*/
static isInRect(rect: MathRect, point: MathPoint): boolean;
/**
* Calculate if a line touches a rect
*/
static isInRect(rect: MathRect, line: MathLine): boolean;
/**
* Calculate if a point or line touches a rect
*/
static isInRect(rect: MathRect, obj: MathPoint | MathLine) {
if ('start' in obj) return this.isLineInRect(rect, obj);
else return this.isPointInRect(rect, obj);
}
static isPointInRect(rect: MathRect, point: MathPoint) {
return (
point.x >= rect.x1 &&
point.x <= rect.x2 &&
point.y >= rect.y1 &&
point.y <= rect.y2
);
}
/**
* Check if the line touches the rect
*/
private static isLineInRect(rect: MathRect, line: MathLine) {
// if the line touches the rect, it must cross one of the rect's sides or be completely inside the rect
const left = this.getLinePositionAtX(line, rect.x1);
const right = this.getLinePositionAtX(line, rect.x2);
const top = this.getLinePositionAtY(line, rect.y1);
const bottom = this.getLinePositionAtY(line, rect.y2);
const lineMinX = Math.min(line.start.x, line.end.x);
const lineMaxX = Math.max(line.start.x, line.end.x);
const lineMinY = Math.min(line.start.y, line.end.y);
const lineMaxY = Math.max(line.start.y, line.end.y);
if (
left > rect.y1 &&
left < rect.y2 &&
lineMinX < rect.x1 &&
lineMaxX > rect.x1
)
return true;
if (
right > rect.y1 &&
right < rect.y2 &&
lineMinX < rect.x2 &&
lineMaxX > rect.x2
)
return true;
if (
top > rect.x1 &&
top < rect.x2 &&
lineMinY < rect.y1 &&
lineMaxY > rect.y1
)
return true;
if (
bottom > rect.x1 &&
bottom < rect.x2 &&
lineMinY < rect.y2 &&
lineMaxY > rect.y2
)
return true;
if (this.isPointInRect(rect, line.start)) return true;
if (this.isPointInRect(rect, line.end)) return true;
return false;
}
private static getLinePositionAtX(line: MathLine, x: number): number {
const { x: x1, y: y1 } = line.start;
const { x: x2, y: y2 } = line.end;
return ((x - x1) * (y2 - y1)) / (x2 - x1) + y1;
}
private static getLinePositionAtY(line: MathLine, y: number): number {
const { x: x1, y: y1 } = line.start;
const { x: x2, y: y2 } = line.end;
return ((y - y1) * (x2 - x1)) / (y2 - y1) + x1;
}
static isPointInPolygon(point: MathPoint, polygon: MathPoint[]): boolean {
const x = point.x;
const y = point.y;
let inside = false;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i].x;
const yi = polygon[i].y;
const xj = polygon[j].x;
const yj = polygon[j].y;
const intersect =
yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
if (intersect) inside = !inside;
}
return inside;
}
static getAreaOfPolygon(polygon: MathPoint[]): number {
// calculate area of all subtriangles
const rootPoint = polygon.shift();
if (!rootPoint) return 0;
let lastPoint = polygon.shift();
if (!lastPoint) return 0;
let area = 0;
for (const point of polygon) {
area += this.getAreaOfTriangle(rootPoint, lastPoint, point);
lastPoint = point;
}
return area;
}
static getAreaOfTriangle(
p1: MathPoint,
p2: MathPoint,
p3: MathPoint
): number {
return (
Math.abs(
(p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)) / 2
) || 0
);
}
static getPerimeterOfPolygon(polygon: MathPoint[]): number {
let perimeter = 0;
let lastPoint = polygon.shift();
if (!lastPoint) return 0;
for (const point of polygon) {
perimeter += Calc.distance(lastPoint, point);
lastPoint = point;
}
return perimeter;
}
}