import log from "loglevel" ; import { ArgumentOutOfRangeException } from "../Exceptions" ; import { PointF2D } from "../../Common/DataObjects/PointF2D" ; import { SizeF2D } from "../../Common/DataObjects/SizeF2D" ; import { RectangleF2D } from "../../Common/DataObjects/RectangleF2D" ; import { StaffLineActivitySymbol } from "./StaffLineActivitySymbol" ; import { EngravingRules } from "./EngravingRules" ; import { GraphicalObject } from "./GraphicalObject" ; param dataObject param parent param connectChildToParent export class BoundingBox { protected isSymbol : boolean = false ; protected relativePositionHasBeenSet : boolean = false ; protected xBordersHaveBeenSet : boolean = false ; protected yBordersHaveBeenSet : boolean = false ; protected absolutePosition : PointF2D = new PointF2D ( ) ; protected relativePosition : PointF2D = new PointF2D ( ) ; protected size : SizeF2D = new SizeF2D ( ) ; protected marginSize : SizeF2D = new SizeF2D ( ) ; protected upperLeftCorner : PointF2D = new PointF2D ( ) ; protected upperLeftMarginCorner : PointF2D = new PointF2D ( ) ; protected borderLeft : number = 0 ; protected borderRight : number = 0 ; protected borderTop : number = 0 ; protected borderBottom : number = 0 ; protected borderMarginLeft : number = 0 ; protected borderMarginRight : number = 0 ; protected borderMarginTop : number = 0 ; protected borderMarginBottom : number = 0 ; protected boundingRectangle : RectangleF2D ; protected boundingMarginRectangle : RectangleF2D ; protected childElements : BoundingBox [ ] = [ ] ; protected parent : BoundingBox ; protected dataObject : Object ; param dataObject param parent param isSymbol constructor ( dataObject = undefined , parent = undefined , isSymbol = false ) { this . parent = parent ; this . dataObject = dataObject ; this . isSymbol = isSymbol ; this . xBordersHaveBeenSet = false ; this . yBordersHaveBeenSet = false ; if ( parent ) { this . Parent = parent ; } } public get RelativePositionHasBeenSet ( ) : boolean { return this . relativePositionHasBeenSet ; } public get XBordersHaveBeenSet ( ) : boolean { return this . xBordersHaveBeenSet ; } public set XBordersHaveBeenSet ( value ) { this . xBordersHaveBeenSet = value ; } public get YBordersHaveBeenSet ( ) : boolean { return this . yBordersHaveBeenSet ; } public set YBordersHaveBeenSet ( value ) { this . yBordersHaveBeenSet = value ; } public get AbsolutePosition ( ) : PointF2D { return this . absolutePosition ; } public set AbsolutePosition ( value ) { this . absolutePosition = value ; } public get RelativePosition ( ) : PointF2D { return this . relativePosition ; } public set RelativePosition ( value ) { this . relativePosition = value ; this . relativePositionHasBeenSet = true ; } public get Size ( ) : SizeF2D { return this . size ; } public set Size ( value ) { this . size = value ; } public get MarginSize ( ) : SizeF2D { return this . marginSize ; } public get UpperLeftCorner ( ) : PointF2D { return this . upperLeftCorner ; } public get UpperLeftMarginCorner ( ) : PointF2D { return this . upperLeftMarginCorner ; } public get BorderLeft ( ) : number { return this . borderLeft ; } public set BorderLeft ( value ) { this . borderLeft = value ; this . calculateRectangle ( ) ; } public get BorderRight ( ) : number { return this . borderRight ; } public set BorderRight ( value ) { this . borderRight = value ; this . calculateRectangle ( ) ; } public get BorderTop ( ) : number { return this . borderTop ; } public set BorderTop ( value ) { this . borderTop = value ; this . calculateRectangle ( ) ; } public get BorderBottom ( ) : number { return this . borderBottom ; } public set BorderBottom ( value ) { this . borderBottom = value ; this . calculateRectangle ( ) ; } public get BorderMarginLeft ( ) : number { return this . borderMarginLeft > this . borderLeft ? this . borderLeft : this . borderMarginLeft ; } public set BorderMarginLeft ( value ) { this . borderMarginLeft = value ; this . calculateMarginRectangle ( ) ; } public get BorderMarginRight ( ) : number { return this . borderMarginRight < this . borderRight ? this . borderRight : this . borderMarginRight ; } public set BorderMarginRight ( value ) { this . borderMarginRight = value ; this . calculateMarginRectangle ( ) ; } public get BorderMarginTop ( ) : number { return this . borderMarginTop > this . borderTop ? this . borderTop : this . borderMarginTop ; } public set BorderMarginTop ( value ) { this . borderMarginTop = value ; this . calculateMarginRectangle ( ) ; } public get BorderMarginBottom ( ) : number { return this . borderMarginBottom < this . borderBottom ? this . borderBottom : this . borderMarginBottom ; } public set BorderMarginBottom ( value ) { this . borderMarginBottom = value ; this . calculateMarginRectangle ( ) ; } public get BoundingRectangle ( ) : RectangleF2D { return this . boundingRectangle ; } public get BoundingMarginRectangle ( ) : RectangleF2D { return this . boundingMarginRectangle ; } public get ChildElements ( ) : BoundingBox [ ] { return this . childElements ; } public set ChildElements ( value ) { this . childElements = value ; } public get Parent ( ) : BoundingBox { return this . parent ; } public set Parent ( value ) { if ( this . parent ) { const index = this . parent . ChildElements . indexOf ( this , 0 ) ; if ( index > - 1 ) { this . parent . ChildElements . splice ( index , 1 ) ; } } this . parent = value ; if ( this . parent . ChildElements . indexOf ( this ) > - 1 ) { log . error ( "BoundingBox of " + ( this . dataObject . constructor as any ) . name + " already in children list of " + ( this . parent . dataObject . constructor as any ) . name + "'s BoundingBox" ) ; } else { this . parent . ChildElements . push ( this ) ; } } public get DataObject ( ) : Object { return this . dataObject ; } param boundingBox public get Center ( ) : PointF2D { return new PointF2D ( this . RelativePosition . x + ( this . BorderMarginRight + this . BorderMarginLeft ) , this . RelativePosition . y + ( this . BorderMarginBottom + this . BorderMarginTop ) ) ; } public setAbsolutePositionFromParent ( ) { if ( this . parent ) { this . absolutePosition . x = this . parent . AbsolutePosition . x + this . relativePosition . x ; this . absolutePosition . y = this . parent . AbsolutePosition . y + this . relativePosition . y ; } else { this . absolutePosition = this . relativePosition ; } } /**
     * Calculate the the absolute position by adding up all relative positions of all parents (including the own rel. pos.)
     */ public calculateAbsolutePosition ( ) { this . absolutePosition . x = this . relativePosition . x ; this . absolutePosition . y = this . relativePosition . y ; let parent = this . parent ; while ( parent ) { this . absolutePosition . x += parent . relativePosition . x ; this . absolutePosition . y += parent . relativePosition . y ; parent = parent . parent ; } } /**
     * This method calculates the Absolute Positions recursively
     */ public calculateAbsolutePositionsRecursiveWithoutTopelement ( ) { this . absolutePosition . x = 0.0 ; this . absolutePosition . y = 0.0 ; for ( let idx = 0 , len = this . ChildElements . length ; idx < len ; ++ idx ) { const child = this . ChildElements [ idx ] ; child . calculateAbsolutePositionsRecursive ( this . absolutePosition . x , this . absolutePosition . y ) ; } } param x param y public calculateAbsolutePositionsRecursive ( x , y ) { this . absolutePosition . x = this . relativePosition . x + x ; this . absolutePosition . y = this . relativePosition . y + y ; for ( let idx = 0 , len = this . ChildElements . length ; idx < len ; ++ idx ) { const child = this . ChildElements [ idx ] ; child . calculateAbsolutePositionsRecursive ( this . absolutePosition . x , this . absolutePosition . y ) ; } } /**
     * calculates the absolute positions of all children of this boundingBox
     */ public calculateAbsolutePositionsOfChildren ( ) { for ( let idx = 0 , len = this . ChildElements . length ; idx < len ; ++ idx ) { const child = this . ChildElements [ idx ] ; child . calculateAbsolutePositionsRecursive ( this . absolutePosition . x , this . absolutePosition . y ) ; } } /**
     * This method calculates the BoundingBoxes
     */ public calculateBoundingBox ( ) { if ( this . childElements . length === 0 ) { return ; } for ( let idx = 0 , len = this . ChildElements . length ; idx < len ; ++ idx ) { const childElement = this . ChildElements [ idx ] ; childElement . calculateBoundingBox ( ) ; } let minLeft = Number . MAX_VALUE ; let maxRight = Number . MIN_VALUE ; let minTop = Number . MAX_VALUE ; let maxBottom = Number . MIN_VALUE ; let minMarginLeft = Number . MAX_VALUE ; let maxMarginRight = Number . MIN_VALUE ; let minMarginTop = Number . MAX_VALUE ; let maxMarginBottom = Number . MIN_VALUE ; if ( this . isSymbol ) { minLeft = this . borderLeft ; maxRight = this . borderRight ; minTop = this . borderTop ; maxBottom = this . borderBottom ; minMarginLeft = this . borderMarginLeft ; maxMarginRight = this . borderMarginRight ; minMarginTop = this . borderMarginTop ; maxMarginBottom = this . borderMarginBottom ; } for ( let idx = 0 , len = this . ChildElements . length ; idx < len ; ++ idx ) { const childElement = this . ChildElements [ idx ] ; minLeft = Math . min ( minLeft , childElement . relativePosition . x + childElement . borderLeft ) ; maxRight = Math . max ( maxRight , childElement . relativePosition . x + childElement . borderRight ) ; minTop = Math . min ( minTop , childElement . relativePosition . y + childElement . borderTop ) ; maxBottom = Math . max ( maxBottom , childElement . relativePosition . y + childElement . borderBottom ) ; minMarginLeft = Math . min ( minMarginLeft , childElement . relativePosition . x + childElement . borderMarginLeft ) ; maxMarginRight = Math . max ( maxMarginRight , childElement . relativePosition . x + childElement . borderMarginRight ) ; minMarginTop = Math . min ( minMarginTop , childElement . relativePosition . y + childElement . borderMarginTop ) ; maxMarginBottom = Math . max ( maxMarginBottom , childElement . relativePosition . y + childElement . borderMarginBottom ) ; } this . borderLeft = minLeft ; this . borderRight = maxRight ; this . borderTop = minTop ; this . borderBottom = maxBottom ; this . borderMarginLeft = minMarginLeft ; this . borderMarginRight = maxMarginRight ; this . borderMarginTop = minMarginTop ; this . borderMarginBottom = maxMarginBottom ; this . calculateRectangle ( ) ; this . calculateMarginRectangle ( ) ; this . xBordersHaveBeenSet = true ; this . yBordersHaveBeenSet = true ; } public calculateTopBottomBorders ( ) { if ( this . childElements . length === 0 ) { return ; } for ( let idx = 0 , len = this . ChildElements . length ; idx < len ; ++ idx ) { const childElement = this . ChildElements [ idx ] ; childElement . calculateTopBottomBorders ( ) ; } let minTop = Number . MAX_VALUE ; let maxBottom = Number . MIN_VALUE ; let minMarginTop = Number . MAX_VALUE ; let maxMarginBottom = Number . MIN_VALUE ; if ( this . yBordersHaveBeenSet ) { minTop = this . borderTop ; maxBottom = this . borderBottom ; minMarginTop = this . borderMarginTop ; maxMarginBottom = this . borderMarginBottom ; } for ( let idx = 0 , len = this . ChildElements . length ; idx < len ; ++ idx ) { const childElement = this . ChildElements [ idx ] ; minTop = Math . min ( minTop , childElement . relativePosition . y + childElement . borderTop ) ; if ( ! EngravingRules . FixStafflineBoundingBox || ! ( childElement . dataObject instanceof StaffLineActivitySymbol ) ) { maxBottom = Math . max ( maxBottom , childElement . relativePosition . y + childElement . borderBottom ) ; } minMarginTop = Math . min ( minMarginTop , childElement . relativePosition . y + childElement . borderMarginTop ) ; maxMarginBottom = Math . max ( maxMarginBottom , childElement . relativePosition . y + childElement . borderMarginBottom ) ; } this . borderTop = minTop ; this . borderBottom = maxBottom ; this . borderMarginTop = minMarginTop ; this . borderMarginBottom = maxMarginBottom ; this . calculateRectangle ( ) ; this . calculateMarginRectangle ( ) ; } param placementPsi param direction param position public computeNonOverlappingPositionWithMargin ( placementPsi , direction , position ) { this . RelativePosition = new PointF2D ( position . x , position . y ) ; this . setAbsolutePositionFromParent ( ) ; let currentPosition = 0.0 ; let hasBeenMoved = false ; do { switch ( direction ) { case ColDirEnum . Left : case ColDirEnum . Right : currentPosition = this . relativePosition . x ; placementPsi . calculateMarginPositionAlongDirection ( this , direction ) ; hasBeenMoved = Math . abs ( currentPosition - this . relativePosition . x ) > 0.001 ; break ; case ColDirEnum . Up : case ColDirEnum . Down : currentPosition = this . relativePosition . y ; placementPsi . calculateMarginPositionAlongDirection ( this , direction ) ; hasBeenMoved = Math . abs ( currentPosition - this . relativePosition . y ) > 0.001 ; break ; default : throw new ArgumentOutOfRangeException ( "direction" ) ; } } while ( hasBeenMoved ) ; } param psi returns { boolean } public collisionDetection ( psi ) { const overlapWidth = Math . min ( this . AbsolutePosition . x + this . borderRight , psi . absolutePosition . x + psi . borderRight ) - Math . max ( this . AbsolutePosition . x + this . borderLeft , psi . absolutePosition . x + psi . borderLeft ) ; const overlapHeight = Math . min ( this . AbsolutePosition . y + this . borderBottom , psi . absolutePosition . y + psi . borderBottom ) - Math . max ( this . AbsolutePosition . y + this . borderTop , psi . absolutePosition . y + psi . borderTop ) ; if ( overlapWidth > 0 && overlapHeight > 0 ) { return true ; } return false ; } param psi returns { boolean } public liesInsideBorders ( psi ) { const leftBorderInside = ( this . AbsolutePosition . x + this . borderLeft ) <= ( psi . absolutePosition . x + psi . borderLeft ) && ( psi . absolutePosition . x + psi . borderLeft ) <= ( this . AbsolutePosition . x + this . borderRight ) ; const rightBorderInside = ( this . AbsolutePosition . x + this . borderLeft ) <= ( psi . absolutePosition . x + psi . borderRight ) && ( psi . absolutePosition . x + psi . borderRight ) <= ( this . AbsolutePosition . x + this . borderRight ) ; if ( leftBorderInside && rightBorderInside ) { const topBorderInside = ( this . AbsolutePosition . y + this . borderTop ) <= ( psi . absolutePosition . y + psi . borderTop ) && ( psi . absolutePosition . y + psi . borderTop ) <= ( this . AbsolutePosition . y + this . borderBottom ) ; const bottomBorderInside = ( this . AbsolutePosition . y + this . borderTop ) <= ( psi . absolutePosition . y + psi . borderBottom ) && ( psi . absolutePosition . y + psi . borderBottom ) <= ( this . AbsolutePosition . y + this . borderBottom ) ; if ( topBorderInside && bottomBorderInside ) { return true ; } } return false ; } public pointLiesInsideBorders ( position ) { const xInside = ( this . AbsolutePosition . x + this . borderLeft ) <= position . x && position . x <= ( this . AbsolutePosition . x + this . borderRight ) ; if ( xInside ) { const yInside = ( this . AbsolutePosition . y + this . borderTop ) <= position . y && position . y <= ( this . AbsolutePosition . y + this . borderBottom ) ; if ( yInside ) { return true ; } } return false ; } param psi returns { boolean } public marginCollisionDetection ( psi ) { const overlapWidth = Math . min ( this . AbsolutePosition . x + this . borderMarginRight , psi . absolutePosition . x + psi . borderMarginRight ) - Math . max ( this . AbsolutePosition . x + this . borderMarginLeft , psi . absolutePosition . x + psi . borderMarginLeft ) ; const overlapHeight = Math . min ( this . AbsolutePosition . y + this . borderMarginBottom , psi . absolutePosition . y + psi . borderMarginBottom ) - Math . max ( this . AbsolutePosition . y + this . borderMarginTop , psi . absolutePosition . y + psi . borderMarginTop ) ; if ( overlapWidth > 0 && overlapHeight > 0 ) { return true ; } return false ; } param psi returns { boolean } public liesInsideMargins ( psi ) { const leftMarginInside = ( this . AbsolutePosition . x + this . borderMarginLeft ) <= ( psi . absolutePosition . x + psi . borderMarginLeft ) && ( psi . absolutePosition . x + psi . borderMarginLeft ) <= ( this . AbsolutePosition . x + this . borderMarginRight ) ; const rightMarginInside = ( this . AbsolutePosition . x + this . borderMarginLeft ) <= ( psi . absolutePosition . x + psi . borderMarginRight ) && ( psi . absolutePosition . x + psi . borderMarginRight ) <= ( this . AbsolutePosition . x + this . borderMarginRight ) ; if ( leftMarginInside && rightMarginInside ) { const topMarginInside = ( this . AbsolutePosition . y + this . borderMarginTop ) <= ( psi . absolutePosition . y + psi . borderMarginTop ) && ( psi . absolutePosition . y + psi . borderMarginTop ) <= ( this . AbsolutePosition . y + this . borderMarginBottom ) ; const bottomMarginInside = ( this . AbsolutePosition . y + this . borderMarginTop ) <= ( psi . absolutePosition . y + psi . borderMarginBottom ) && ( psi . absolutePosition . y + psi . borderMarginBottom ) <= ( this . AbsolutePosition . y + this . borderMarginBottom ) ; if ( topMarginInside && bottomMarginInside ) { return true ; } } return false ; } public pointLiesInsideMargins ( position ) { const xInside = ( this . AbsolutePosition . x + this . borderMarginLeft ) <= position . x && position . x <= ( this . AbsolutePosition . x + this . borderMarginRight ) ; if ( xInside ) { const yInside = ( this . AbsolutePosition . y + this . borderMarginTop ) <= position . y && position . y <= ( this . AbsolutePosition . y + this . borderMarginBottom ) ; if ( yInside ) { return true ; } } return false ; } param placementPsi param direction param position public computeNonOverlappingPosition ( placementPsi , direction , position ) { this . RelativePosition = new PointF2D ( position . x , position . y ) ; this . setAbsolutePositionFromParent ( ) ; let currentPosition = 0.0 ; let hasBeenMoved = false ; do { switch ( direction ) { case ColDirEnum . Left : case ColDirEnum . Right : currentPosition = this . relativePosition . x ; placementPsi . calculatePositionAlongDirection ( this , direction ) ; hasBeenMoved = Math . abs ( currentPosition - this . relativePosition . x ) > 0.0001 ; break ; case ColDirEnum . Up : case ColDirEnum . Down : currentPosition = this . relativePosition . y ; placementPsi . calculatePositionAlongDirection ( this , direction ) ; hasBeenMoved = Math . abs ( currentPosition - this . relativePosition . y ) > 0.0001 ; break ; default : throw new ArgumentOutOfRangeException ( "direction" ) ; } } while ( hasBeenMoved ) ; } public getClickedObjectOfType < T > ( clickPosition ) { const obj = this . dataObject ; if ( this . pointLiesInsideBorders ( clickPosition ) && ( < T > obj ) ) { return ( obj as T ) ; } for ( let idx = 0 , len = this . childElements . length ; idx < len ; ++ idx ) { const psi = this . childElements [ idx ] ; const innerObject = psi . getClickedObjectOfType < T > ( clickPosition ) ; if ( innerObject ) { return ( innerObject as T ) ; } } return undefined ; } public getObjectsInRegion < T extends GraphicalObject > ( region , liesInside = true , className = GraphicalObject . name ) { let result = [ ] ; for ( const child of this . childElements ) { result = result . concat ( child . getObjectsInRegion < T > ( region , liesInside , className ) ) ; } if ( this . dataObject && ( this . dataObject as T ) . isInstanceOfClass ( className ) ) { if ( liesInside ) { if ( region . liesInsideBorders ( this ) ) { result . push ( this . dataObject as T ) ; } } else { if ( region . collisionDetection ( this ) ) { result . push ( this . dataObject as T ) ; } } } return result ; } protected calculateRectangle ( ) { this . upperLeftCorner = new PointF2D ( this . BorderLeft , this . BorderTop ) ; this . size = new SizeF2D ( this . BorderRight - this . BorderLeft , this . BorderBottom - this . BorderTop ) ; this . boundingRectangle = RectangleF2D . createFromLocationAndSize ( this . upperLeftCorner , this . size ) ; } protected calculateMarginRectangle ( ) { this . upperLeftMarginCorner = new PointF2D ( this . BorderMarginLeft , this . BorderMarginTop ) ; this . marginSize = new SizeF2D ( this . BorderMarginRight - this . BorderMarginLeft , this . BorderMarginBottom - this . BorderMarginTop ) ; this . boundingMarginRectangle = RectangleF2D . createFromLocationAndSize ( this . upperLeftMarginCorner , this . marginSize ) ; } param toBePlaced param direction private calculateMarginPositionAlongDirection ( toBePlaced , direction ) { if ( this === toBePlaced ) { return ; } if ( this . isSymbol && this . marginCollisionDetection ( toBePlaced ) ) { let shiftDistance = 0 ; switch ( direction ) { case ColDirEnum . Left : shiftDistance = ( this . absolutePosition . x + this . borderMarginLeft ) - ( toBePlaced . absolutePosition . x + toBePlaced . borderMarginRight ) ; toBePlaced . relativePosition . x += shiftDistance ; toBePlaced . absolutePosition . x += shiftDistance ; return ; case ColDirEnum . Right : shiftDistance = ( this . absolutePosition . x + this . borderMarginRight ) - ( toBePlaced . absolutePosition . x + toBePlaced . borderMarginLeft ) ; toBePlaced . relativePosition . x += shiftDistance ; toBePlaced . absolutePosition . x += shiftDistance ; return ; case ColDirEnum . Up : shiftDistance = ( this . absolutePosition . y + this . borderMarginTop ) - ( toBePlaced . absolutePosition . y + toBePlaced . borderMarginBottom ) ; toBePlaced . relativePosition . y += shiftDistance ; toBePlaced . absolutePosition . y += shiftDistance ; return ; case ColDirEnum . Down : shiftDistance = ( this . absolutePosition . y + this . borderMarginBottom ) - ( toBePlaced . absolutePosition . y + toBePlaced . borderMarginTop ) ; toBePlaced . relativePosition . y += shiftDistance ; toBePlaced . absolutePosition . y += shiftDistance ; return ; default : throw new ArgumentOutOfRangeException ( "direction" ) ; } } for ( let idx = 0 , len = this . ChildElements . length ; idx < len ; ++ idx ) { const childElement = this . ChildElements [ idx ] ; childElement . calculateMarginPositionAlongDirection ( toBePlaced , direction ) ; } } param toBePlaced param direction private calculatePositionAlongDirection ( toBePlaced , direction ) { if ( this === toBePlaced ) { return ; } if ( this . isSymbol && this . collisionDetection ( toBePlaced ) ) { let shiftDistance ; switch ( direction ) { case ColDirEnum . Left : shiftDistance = ( this . absolutePosition . x + this . borderLeft ) - ( toBePlaced . absolutePosition . x + toBePlaced . borderRight ) ; toBePlaced . relativePosition . x += shiftDistance ; toBePlaced . absolutePosition . x += shiftDistance ; return ; case ColDirEnum . Right : shiftDistance = ( this . absolutePosition . x + this . borderRight ) - ( toBePlaced . absolutePosition . x + toBePlaced . borderLeft ) ; toBePlaced . relativePosition . x += shiftDistance ; toBePlaced . absolutePosition . x += shiftDistance ; return ; case ColDirEnum . Up : shiftDistance = ( this . absolutePosition . y + this . borderTop ) - ( toBePlaced . absolutePosition . y + toBePlaced . borderBottom ) ; toBePlaced . relativePosition . y += shiftDistance ; toBePlaced . absolutePosition . y += shiftDistance ; return ; case ColDirEnum . Down : shiftDistance = ( this . absolutePosition . y + this . borderBottom ) - ( toBePlaced . absolutePosition . y + toBePlaced . borderTop ) ; toBePlaced . relativePosition . y += shiftDistance ; toBePlaced . absolutePosition . y += shiftDistance ; return ; default : throw new ArgumentOutOfRangeException ( "direction" ) ; } } for ( let idx = 0 , len = this . ChildElements . length ; idx < len ; ++ idx ) { const childElement = this . ChildElements [ idx ] ; childElement . calculatePositionAlongDirection ( toBePlaced , direction ) ; } } } export enum ColDirEnum { Left = 0 , Right = 1 , Up = 2 , Down = 3 }