UNPKG

@webwriter/geometry-cloze

Version:

Create and view geometry exercises with coloring, styling and labeling options.

229 lines (201 loc) 6.41 kB
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; } }