import { MapProps as BP } from '@components/utils' ; import { makeStyles } from '@material-ui/core/styles' ; import { PARTICIPANT_SIZE } from '@models/Participant' ; import { crossProduct , extractRotation , extractScaleX , radian2Degree , rotate90ClockWise , rotateVector2D , transformPoint2D , transfromAt , vectorLength , } from '@models/utils' ; import { addV2 , mulV2 , normV , subV2 } from '@models/utils/coordinates' ; import { SCALE_LIMIT } from '@stores/Map' ; import { useObserver } from 'mobx-react-lite' ; import React , { useEffect , useRef } from 'react' ; import ResizeObserver from 'react-resize-observer' ; import { useGesture } from 'react-use-gesture' ; function limitScale ( currentScale , scale ) { const targetScale = currentScale * scale ; if ( targetScale > SCALE_LIMIT . maxScale ) { return SCALE_LIMIT . maxScale / currentScale ; } if ( targetScale < SCALE_LIMIT . minScale ) { return SCALE_LIMIT . minScale / currentScale ; } return scale ; } interface StyleProps { matrix : DOMMatrixReadOnly ; } const useStyles = makeStyles ( { root : { position : 'absolute' , width : '100%' , height : '100%' , top : 0 , left : 0 , userDrag : 'none' , userSelect : 'none' , overflow : 'hidden' , } , center : { position : 'absolute' , margin : 'auto' , left : 0 , right : 0 , top : 0 , bottom : 0 , width : 0 , height : 0 , } , transform : ( props ) => ( { position : 'absolute' , width : 0 , height : 0 , transform : props . matrix . toString ( ) , } ) , } ) ; type MapProps = React . PropsWithChildren < BP > ; class BaseMember { prebThirdPersonView = false ; mouseDown = false ; dragging = false ; } export const Base = ( props ) => { const { map , participants } = props . stores ; const matrix = useObserver ( ( ) => map . matrix ) ; const container = useRef < HTMLDivElement > ( null ) ; const outer = useRef < HTMLDivElement > ( null ) ; function offset ( ) { return map . offset ; } const thirdPersonView = useObserver ( ( ) => participants . local . thirdPersonView ) ; const memRef = useRef < BaseMember > ( new BaseMember ( ) ) ; const mem = memRef . current ; const center = transformPoint2D ( matrix , participants . local . pose . position ) ; if ( thirdPersonView !== mem . prebThirdPersonView ) { mem . prebThirdPersonView = thirdPersonView ; if ( thirdPersonView ) { const mapRot = radian2Degree ( extractRotation ( matrix ) ) ; if ( mapRot ) { const newMatrix = rotateMap ( - mapRot , center ) ; map . setCommittedMatrix ( newMatrix ) ; } } else { const avatarRot = participants . local . pose . orientation ; const mapRot = radian2Degree ( extractRotation ( matrix ) ) ; if ( avatarRot + mapRot ) { const newMatrix = rotateMap ( - ( avatarRot + mapRot ) , center ) ; map . setCommittedMatrix ( newMatrix ) ; } } } function rotateMap ( degree , center ) { const changeMatrix = ( new DOMMatrix ( ) ) . rotateSelf ( 0 , 0 , degree ) ; const newMatrix = transfromAt ( center , changeMatrix , matrix ) ; map . setMatrix ( newMatrix ) ; return newMatrix ; } const MOUSE_LEFT = 1 ; const MOUSE_RIGHT = 2 ; function wheelHandler ( event ) { if ( ! event . ctrlKey ) { let scale = Math . pow ( 1.2 , event . deltaY / 100 ) ; scale = limitScale ( extractScaleX ( map . matrix ) , scale ) ; if ( scale === 1 ) { return ; } const newMatrix = map . matrix . scale ( scale , scale , 1 , ... transformPoint2D ( map . matrix . inverse ( ) , map . mouse ) ) ; map . setMatrix ( newMatrix ) ; map . setCommittedMatrix ( newMatrix ) ; } } function moveParticipant ( move , givenTarget ? ) { const local = participants . local ; let target = givenTarget ; if ( ! target ) { target = map . mouseOnMap ; } const diff = subV2 ( target , local . pose . position ) ; if ( normV ( diff ) > ( givenTarget ? PARTICIPANT_SIZE * 2 : PARTICIPANT_SIZE / 2 ) ) { const dir = mulV2 ( 20 / normV ( diff ) , diff ) ; local . pose . orientation = Math . atan2 ( dir [ 0 ] , - dir [ 1 ] ) * 180 / Math . PI ; if ( move ) { local . pose . position = addV2 ( local . pose . position , dir ) ; } local . savePhysicsToStorage ( false ) ; } } function moveParticipantPeriodically ( move , target ? ) { moveParticipant ( move , target ) ; const TIMER_INTERVAL = move ? 33 : 300 ; setTimeout ( ( ) => { if ( mem . mouseDown ) { moveParticipantPeriodically ( true ) ; } } , TIMER_INTERVAL ) ; } const bind = useGesture ( { onDragStart : ( { buttons } ) => { document . body . focus ( ) ; mem . dragging = true ; mem . mouseDown = true ; if ( buttons === MOUSE_LEFT ) { moveParticipantPeriodically ( false ) ; } } , onDrag : ( { down , delta , xy , buttons } ) => { if ( delta [ 0 ] || delta [ 1 ] ) { mem . mouseDown = false ; } if ( mem . dragging && down && outer . current ) { if ( ! thirdPersonView && buttons === MOUSE_RIGHT ) { const center = transformPoint2D ( matrix , participants . local . pose . position ) ; const target = addV2 ( xy , offset ( ) ) ; const radius1 = subV2 ( target , center ) ; const radius2 = subV2 ( radius1 , delta ) ; const cosAngle = crossProduct ( radius1 , radius2 ) / ( vectorLength ( radius1 ) * vectorLength ( radius2 ) ) ; const flag = crossProduct ( rotate90ClockWise ( radius1 ) , delta ) > 0 ? - 1 : 1 ; const angle = Math . acos ( cosAngle ) * flag ; if ( isNaN ( angle ) ) { return ; } const newMatrix = rotateMap ( radian2Degree ( angle ) , center ) ; participants . local . pose . orientation = - radian2Degree ( extractRotation ( newMatrix ) ) ; } else { const diff = rotateVector2D ( matrix . inverse ( ) , delta ) ; const newMatrix = matrix . translate ( ... diff ) ; map . setMatrix ( newMatrix ) ; if ( delta [ 0 ] || delta [ 1 ] ) { moveParticipant ( false , map . centerOnMap ) ; } } } } , onDragEnd : ( ) => { if ( matrix . toString ( ) !== map . committedMatrix . toString ( ) ) { map . setCommittedMatrix ( matrix ) ; moveParticipant ( false , map . centerOnMap ) ; } mem . dragging = false ; mem . mouseDown = false ; } , onPinch : ( { da : [ d , a ] , origin , event , memo } ) => { if ( memo === undefined ) { return [ d , a ] ; } const [ md , ma ] = memo ; const center = addV2 ( origin as [ number , number ] , offset ( ) ) ; const MIN_D = 10 ; let scale = d > MIN_D ? d / md : d < - MIN_D ? md / d : ( 1 + ( d - md ) / MIN_D ) ; scale = limitScale ( extractScaleX ( matrix ) , scale ) ; const changeMatrix = thirdPersonView ? ( new DOMMatrix ( ) ) . scaleSelf ( scale , scale , 1 ) : ( new DOMMatrix ( ) ) . scaleSelf ( scale , scale , 1 ) . rotateSelf ( 0 , 0 , a - ma ) ; const newMatrix = transfromAt ( center , changeMatrix , matrix ) ; map . setMatrix ( newMatrix ) ; if ( ! thirdPersonView ) { participants . local . pose . orientation = - radian2Degree ( extractRotation ( newMatrix ) ) ; } return [ d , a ] ; } , onPinchEnd : ( ) => map . setCommittedMatrix ( matrix ) , onMove : ( { xy } ) => { map . setMouse ( xy ) ; participants . local . mouse . position = Object . assign ( { } , map . mouseOnMap ) ; } , onTouchStart : ( ev ) => { map . setMouse ( [ ev . touches [ 0 ] . clientX , ev . touches [ 0 ] . clientY ] ) ; participants . local . mouse . position = Object . assign ( { } , map . mouseOnMap ) ; } , } , { eventOptions : { passive : false } , } ) ; useEffect ( ( ) => { onResizeOuter ( ) ; } , [ ] ) ; useEffect ( ( ) => { function topWindowHandler ( event ) { if ( event . ctrlKey ) { if ( window . visualViewport && window . visualViewport . scale > 1 ) { if ( event . deltaY < 0 ) { event . preventDefault ( ) ; } else { } } else { event . preventDefault ( ) ; } } } window . document . body . addEventListener ( 'wheel' , topWindowHandler , { passive : false } ) ; return ( ) => { window . document . body . removeEventListener ( 'wheel' , topWindowHandler ) ; } ; } , [ ] ) ; const onResizeOuter = useRef ( ( ) => { if ( outer . current ) { let cur = outer . current as HTMLElement ; let offsetLeft = 0 ; while ( cur ) { offsetLeft += cur . offsetLeft ; cur = cur . offsetParent as HTMLElement ; } map . setScreenSize ( [ outer . current . clientWidth , outer . current . clientHeight ] ) ; map . setLeft ( offsetLeft ) ; } } ) . current ; const styleProps = { matrix , } ; const classes = useStyles ( styleProps ) ; return ( < div className = { classes . root } ref = { outer } { ... bind ( ) } > < ResizeObserver onResize = { onResizeOuter } /> < div className = { classes . center } onWheel = { wheelHandler } > < div id = "map-transform" className = { classes . transform } ref = { container } > { props . children } < /div> < /div> < /div>); } ; Base . displayName = 'MapBase' ;