import React , { useEffect , useState } from "react" ; import useResizeObserver from "use-resize-observer" ; import { Stats } from "../stats" ; import { IAppState , IBubble } from "../types" ; import { StageView } from "./stage" ; const formatFloat = ( n ) => Math . round ( n * 10 ) / 10 ; interface IAppProps { app : IAppState ; } function move ( bubble , width , height ) { let { x , y } = bubble ; const { vx , vy , r } = bubble ; bubble . x = x += vx ; bubble . y = y += vy ; if ( vx < 0 && x < r ) { bubble . vx = - vx ; } else if ( vx > 0 && x > ( width - r ) ) { bubble . vx = - vx ; } if ( vy < 0 && y < r ) { bubble . vy = - vy ; } else if ( vy > 0 && y > ( height - r ) ) { bubble . vy = - vy ; } } function collide ( left , right ) { const dx = left . x - right . x ; const dy = left . y - right . y ; const distance2 = dx * dx + dy * dy ; const threshold = left . r + right . r ; const threshold2 = threshold * threshold ; if ( distance2 > threshold2 ) { return ; } const { vx : lvx , vy : lvy } = left ; const { vx : rvx , vy : rvy } = right ; const dvx = lvx - rvx ; const dvy = lvy - rvy ; let impulse = dvx * dx + dvy * dy ; if ( impulse > 0 ) { return ; } impulse /= distance2 ; left . vx = lvx - dx * impulse ; left . vy = lvy - dy * impulse ; right . vx = rvx + dx * impulse ; right . vy = rvy + dy * impulse ; } export const AppView = ( { app } ) => { const [ stats ] = useState < Stats > ( new Stats ( ) ) ; const [ , setFrame ] = useState < number > ( 0 ) ; useEffect ( ( ) => { const localBubbles = app . localClient . bubbles ; for ( const bubble of localBubbles ) { move ( bubble , app . width , app . height ) ; } for ( let i = 0 ; i < localBubbles . length ; i ++ ) { const left = localBubbles [ i ] ; for ( let j = i + 1 ; j < localBubbles . length ; j ++ ) { const right = localBubbles [ j ] ; collide ( left , right ) ; } } for ( const client of app . clients ) { if ( client . clientId === app . localClient . clientId ) { continue ; } for ( const right of client . bubbles ) { for ( const left of localBubbles ) { collide ( left , right ) ; } } } if ( ! ( stats . smoothFps > 22 ) ) { app . decreaseBubbles ( ) ; } else if ( stats . smoothFps > 23 ) { app . increaseBubbles ( ) ; } app . applyEdits ( ) ; } ) ; requestAnimationFrame ( ( ) => { setFrame ( ( currentFrame ) => currentFrame + 1 ) ; } ) ; stats . endFrame ( ) ; const { ref } = useResizeObserver < HTMLDivElement > ( { onResize : ( { width , height } ) => { app . setSize ( width , height ) ; } } ) ; let bubbleCount = 0 ; for ( const client of app . clients ) { bubbleCount += client . bubbles . length ; } return ( < div ref = { ref } style = { { position : "absolute" , inset : "0px" } } > < div > { `${ app . localClient . bubbles . length }/${ bubbleCount } bubbles @${ formatFloat ( stats . smoothFps ) } fps (${ stats . lastFrameElapsed } ms)` } < /div> < div > { `Total FPS: ${ formatFloat ( stats . totalFps ) } (Glitches: ${ stats . glitchCount })` } < /div> < StageView app = { app } > < /StageView> < /div>); } ;