import { Piece } from "./Piece" ; import { Trace } from "../calculation/trace" ; import { Fraction , Line , PathUtil , Point , Vector } from "bp/math" ; import { makePerQuadrant , onDemand } from "bp/global" ; import { clone } from "bp/util" ; import type { JAnchor , JGadget } from "bp/content/json" ; import type { PerQuadrant , QuadrantDirection } from "bp/global" ; import type { IPoint , Path } from "bp/math" ; /** 第一個值傳回點，第二個值傳回來自的 Piece（若有的話） */ export type AnchorMap = [ Point , number | null ] ; /**
 *  Gadget 是一個對應於一個  Overlap 的  Device 部件。
 * 它總是具有 4 個  Anchor 。
 *
 * 這個東西只是抽象的計算類別，且沒有任何反應成份，因此不需要繼承任何東西。
 *  Gadget 本身實作了  JGadget 介面，並且在執行  setup 方法之前可以跟普通的  JGadget 一樣操作內容。
  export class Gadget implements JGadget , ISerializable < JGadget > { exports public pieces : Piece [ ] ; exports public offset ? : IPoint ; exports public anchors ? : JAnchor [ ] ; constructor ( gadget ) { this . pieces = gadget . pieces . map ( p => Piece . $instantiate ( p ) ) ; this . offset = gadget . offset ; this . pieces . forEach ( p => p . $offset ( this . offset ) ) ; this . anchors = gadget . anchors ; } public toJSON ( ) { return { pieces : this . pieces , offset : this . offset , anchors : this . anchors , } ; } /** 傳回 Gadget 的 AnchorMap 陣列（點的位置是相位變換之前） */ @ onDemand public get $anchorMap ( ) : PerQuadrant < AnchorMap > { return makePerQuadrant < AnchorMap > ( q => { if ( this . anchors ?. [ q ] ?. location ) { let p = new Point ( this . anchors [ q ] . location ! ) ; if ( this . offset ) p . addBy ( new Vector ( this . offset ) ) ; return [ p , null ] ; } else { if ( this . pieces . length == 1 ) return [ this . pieces [ 0 ] . $anchors [ q ] ! , 0 ] ; for ( let [ i , p ] of this . pieces . entries ( ) ) { if ( p . $anchors [ q ] ) return [ p . $anchors [ q ] ! , i ] ; } debugger ; throw new Error ( ) ; } } ) ; } private _getSlack ( q ) { return this . anchors ?. [ q ] ?. slack ?? 0 ; } @ onDemand public get $slacks ( ) : PerQuadrant < number > { return makePerQuadrant ( q => this . _getSlack ( q ) ) ; } /** 取得這個 Gadget 的 sx 整數全長 */ @ onDemand public get sx ( ) : number { return Math . ceil ( this . $anchorMap [ 2 ] [ 0 ] . x - this . $anchorMap [ 0 ] [ 0 ] . x ) ; } /** 取得這個 Gadget 的 sy 整數全長 */ @ onDemand public get sy ( ) : number { return Math . ceil ( this . $anchorMap [ 2 ] [ 0 ] . y - this . $anchorMap [ 0 ] [ 0 ] . y ) ; } public $reverseGPS ( ) { let g = Gadget . $instantiate ( clone ( this ) ) ; let [ p1 , p2 ] = g . pieces ; let sx = Math . ceil ( Math . max ( p1 . sx , p2 . sx ) ) ; let sy = Math . ceil ( Math . max ( p1 . sy , p2 . sy ) ) ; p1 . $reverse ( sx , sy ) ; p2 . $reverse ( sx , sy ) ; return g ; } public $addSlack ( q , slack ) { this . anchors = this . anchors || [ ] ; this . anchors [ q ] = this . anchors [ q ] || { } ; this . anchors [ q ] . slack = ( this . anchors [ q ] . slack ?? 0 ) + slack ; return this ; } param g param q1 param q2 public $setupConnectionSlack ( g , q1 , q2 ) { let c1 = this . $contour , c2 = g . $contour ; let f = q1 == 0 ? 1 : - 1 ; let step = new Vector ( f , f ) ; let slack = new Fraction ( this . _getSlack ( q1 ) ) ; let v = g . $anchorMap [ q2 ] [ 0 ] . sub ( Point . ZERO ) . addBy ( step . $scale ( slack ) ) ; c1 = PathUtil . $shift ( c1 , q1 == 0 ? v : v . add ( Point . ZERO . sub ( this . $anchorMap [ 2 ] [ 0 ] ) ) ) ; let s = 0 ; while ( PathUtil . $polygonIntersect ( c1 , c2 ) ) { c1 = PathUtil . $shift ( c1 , step ) ; s ++ ; } this . $addSlack ( q1 , s ) ; return s ; } @ onDemand public get $contour ( ) : Path { let p = this . pieces , contour = p [ 0 ] . $shape . contour ; for ( let i = 1 ; i < p . length ; i ++ ) contour = PathUtil . $join ( contour , p [ i ] . $shape . contour ) ; return contour ; } param q1 param q2 public rx ( q1 , q2 ) { return Math . abs ( this . $anchorMap [ q1 ] [ 0 ] . x - this . $anchorMap [ q2 ] [ 0 ] . x ) ; } param q1 param q2 public ry ( q1 , q2 ) { return Math . abs ( this . $anchorMap [ q1 ] [ 0 ] . y - this . $anchorMap [ q2 ] [ 0 ] . y ) ; } /** 自身是否包含指定的點（座標為 offset 之前的座標） */ public $intersects ( p , v ) { let test = this . $contour . map ( ( c , i , a ) => new Line ( c , a [ ( i + 1 ) % a . length ] ) ) ; return test . some ( l => Trace . $getIntersection ( l , p , v ) ) ; } /** 把 JGadget 中的 JPiece 全部實體化 */ public static $instantiate ( g ) { if ( g instanceof Gadget ) return g ; else return new Gadget ( g ) ; } /** 簡化 JSON 資料的呈現 */ public static $simplify ( g ) { if ( g . offset && g . offset . x == 0 && g . offset . y == 0 ) delete g . offset ; if ( g . anchors ) { for ( let [ i , a ] of g . anchors . entries ( ) ) { if ( ! a ) continue ; if ( a . slack === 0 ) delete a . slack ; if ( Object . keys ( a ) . length == 0 ) delete g . anchors [ i ] ; } if ( ! g . anchors . some ( a => Boolean ( a ) ) ) delete g . anchors ; } return g ; } }