import { Point } from "./Point" ; import { $int } from "./util" ; import { Fraction } from "./Fraction" ; import { Matrix } from "./Matrix" ; import { Vector } from "./Vector" ; import type { Sign } from "./types" ; import type { IPoint } from "./Point" ; /**
 *  Line 是由兩個  Point 構成的。
  export class Line { public readonly p1 : Point ; public readonly p2 : Point ; constructor ( p , v ) ; constructor ( p1 , p2 ) ; constructor ( p , c ) { if ( c instanceof Vector ) c = p . add ( c ) ; this . p1 = p ; this . p2 = c ; } /** 把直線以 `(x1, y1), (x2, y2)` 的格式輸出成字串，頂點會排序，因此可以作為識別簽章使用 */ public toString ( ) { return [ this . p1 , this . p2 ] . sort ( ) . toString ( ) ; } public get $isDegenerated ( ) : boolean { return this . p1 . eq ( this . p2 ) ; } /**Check if two line segments are identical */ public eq ( l ) { return this . p1 . eq ( l . p1 ) && this . p2 . eq ( l . p2 ) || this . p1 . eq ( l . p2 ) && this . p2 . eq ( l . p1 ) ; } /** 傳回一個點是否落在這個線段的內部（預設不含端點） */ public $contains ( point , includeEndpoints = false ) { let p = point instanceof Point ? point : new Point ( point ) ; if ( includeEndpoints && ( p . eq ( this . p1 ) || p . eq ( this . p2 ) ) ) return true ; let v1 = p . sub ( this . p1 ) , v2 = p . sub ( this . p2 ) ; return v1 . _x . mul ( v2 . _y ) . eq ( v2 . _x . mul ( v1 . _y ) ) && v1 . dot ( v2 ) < 0 ; } public $lineContains ( p ) { return this . $vector . $parallel ( p . sub ( this . p1 ) ) ; } /** 取得這條線段（含端點）與給定動向（視為直線，除非指定 isRay）的交點 */ public $intersection ( l ) ; public $intersection ( p , v , isRay ? ) ; public $intersection ( ... t ) { if ( t . length == 1 ) return this . $intersection ( t [ 0 ] . p1 , t [ 0 ] . p2 . sub ( t [ 0 ] . p1 ) ) ; let [ p , v , isRay ] = t ; let v1 = this . p2 . sub ( this . p1 ) ; let m = new Matrix ( v1 . _x , v . _x , v1 . _y , v . _y ) . $inverse ; if ( m == null ) return null ; let r = m . $multiply ( new Point ( p . sub ( this . p1 ) ) ) ; let a = r . _x , b = r . _y . neg ; if ( a . lt ( Fraction . ZERO ) || a . gt ( Fraction . ONE ) ) return null ; if ( isRay && b . lt ( Fraction . ZERO ) ) return null ; return p . add ( v . $scale ( b ) ) ; } /** 根據指定的相位變換並傳回一條新的直線 */ public $transform ( fx , fy ) { return new Line ( this . p1 . $transform ( fx , fy ) , this . p2 . $transform ( fx , fy ) ) ; } /** 根據指定的向量移動並傳回一條新的直線 */ public $shift ( v ) { return new Line ( this . p1 . add ( v ) , this . p2 . add ( v ) ) ; } /** 過濾掉直線陣列裡面重複的、並且傳回新的陣列 */ public static $distinct ( lines ) { let signatures = new Set < string > ( ) ; return lines . filter ( l => { let signature = l . toString ( ) , ok = ! signatures . has ( signature ) ; if ( ok ) signatures . add ( signature ) ; return ok ; } ) ; } /** 把線段集合 l1 扣除掉任何和線段集 l2 重疊到的部份 */ public static $subtract ( l1 , l2 ) { let result = [ ] ; let slopeMap = new Map < string , Line [ ] > ( ) ; for ( let l of l2 ) { let slope = l . $slope . toString ( ) ; let arr = slopeMap . get ( slope ) ; if ( ! arr ) slopeMap . set ( slope , arr = [ ] ) ; arr . push ( l ) ; } for ( let l of l1 ) { let slope = l . $slope . toString ( ) ; if ( ! slopeMap . has ( slope ) ) result . push ( l ) ; else result . push ( ... l . _cancel ( slopeMap . get ( slope ) ! ) ) ; } return result ; } /** 把當前的線段扣除掉傳入的線段集中任何重疊到的部份，並傳回剩下的各部份線段。 */ private _cancel ( set ) { let result = [ this ] ; for ( let l2 of set ) { let next = [ ] ; for ( let l1 of result ) next . push ( ... l1 . _cancelCore ( l2 ) ) ; result = next ; } return result ; } private * _cancelCore ( l ) { let a = this . $contains ( l . p1 , true ) , b = this . $contains ( l . p2 , true ) ; let c = l . $contains ( this . p1 , true ) , d = l . $contains ( this . p2 , true ) ; if ( c && d ) return ; if ( ! a && ! b ) { yield this ; } else if ( a && b ) { let l11 = new Line ( this . p1 , l . p1 ) , l12 = new Line ( this . p1 , l . p2 ) ; let l21 = new Line ( this . p2 , l . p1 ) , l22 = new Line ( this . p2 , l . p2 ) ; if ( l11 . $isDegenerated ) { yield l22 ; } else if ( l12 . $isDegenerated ) { yield l21 ; } else if ( l21 . $isDegenerated ) { yield l12 ; } else if ( l22 . $isDegenerated ) { yield l11 ; } else if ( l11 . $contains ( l . p2 ) ) { yield l12 ; yield l21 ; } else { yield l11 ; yield l22 ; } } else { let p1 = a ? l . p1 : l . p2 ; let p2 = d ? this . p1 : this . p2 ; if ( ! p1 . eq ( p2 ) ) yield new Line ( p1 , p2 ) ; } } /** 傳回分數斜率（有可能為 1/0 無限大） */ public get $slope ( ) : Fraction { return this . p1 . _y . sub ( this . p2 . _y ) . d ( this . p1 . _x . sub ( this . p2 . _x ) ) ; } /** 把自身的頂點依 x 座標排序並且傳回 */ public $xOrient ( ) { if ( this . p1 . _x . gt ( this . p2 . _x ) ) return [ this . p2 , this . p1 ] ; return [ this . p1 , this . p2 ] ; } /** 傳回位於這個線段上的所有格子點 */ public * $gridPoints ( ) { let { p1 , p2 } = this ; let dx = p2 . x - p1 . x , dy = p2 . y - p1 . y ; if ( Math . abs ( dx ) < Math . abs ( dy ) ) { let f = Math . sign ( dx ) ; for ( let x = $int ( p1 . x , f ) ; x * f <= p2 . x * f ; x += f ) { let p = this . $xIntersection ( x ) ; if ( p . $isIntegral ) yield p ; } } else { let f = Math . sign ( dy ) ; for ( let y = $int ( p1 . y , f ) ; y * f <= p2 . y * f ; y += f ) { let p = this . $yIntersection ( y ) ; if ( p . $isIntegral ) yield p ; } } } public $xIntersection ( x ) { let v = this . p2 . sub ( this . p1 ) ; let f = new Fraction ( x ) ; return new Point ( f , this . p1 . _y . sub ( v . $slope . mul ( this . p1 . _x . sub ( f ) ) ) ) ; } public $yIntersection ( y ) { let v = this . p2 . sub ( this . p1 ) ; let f = new Fraction ( y ) ; return new Point ( this . p1 . _x . sub ( this . p1 . _y . sub ( f ) . div ( v . $slope ) ) , f ) ; } /** 把給定的向量對於這條線作鏡射 */ public $reflect ( v ) { v = v . neg ; let m = new Matrix ( v . _x , v . _y . neg , v . _y , v . _x ) ; let mi = m . $inverse ! ; v = mi . $multiply ( this . p2 . sub ( this . p1 ) ) ; v = v . $doubleAngle ( ) ; return m . $multiply ( v ) . reduce ( ) ; } /** 直線是否和給定的向量垂直 */ public $perpendicular ( v ) { return this . $vector . dot ( v ) == 0 ; } public get $vector ( ) : Vector { return this . p1 . sub ( this . p2 ) ; } }