blitz-foundation / monkey2

zlib License
3 stars 0 forks source link

Scene.UpdateRate -- is it working correctly? #53

Open Pharmhaus-2 opened 5 years ago

Pharmhaus-2 commented 5 years ago

Original Author: DruggedBunny

I've run into an odd problem today, which has only brought itself to light as somehow my VR setup has disabled its own vsync, and I can't figure out how to get it back!

However, it appears not to be a VR-specific problem, as using the (non-VR) code below, and disabling vsync, the app, and its physics processing, appears not to be restricted by Scene.UpdateRate, which is at the default 60, and instead appears to be running at the un-synced refresh rate, 300-odd fps.

I assumed it would run at Scene.UpdateRate while refreshing the display as often as possible (MX1-style).

I notice world.monkey2 does this:

_btworld.stepSimulation( 1.0/_scene.UpdateRate )

... which seems to suggest that physics, at least, should be restricted to 60 FPS.

Here's a video showing the effect, with vsync first enabled, then disabled:

mojo3d - Scene.UpdateRate

' Wall of boxes...

#Import "<std>"
#Import "<mojo>"
#Import "<mojo3d>"
#Import "<bullet>"

Using std..
Using mojo..
Using mojo3d..

Const WALL_WIDTH:Int    = 15
Const WALL_HEIGHT:Int   = 10

Const BULLET_FORCE:Float = 250.0 ' Too high will go through without colliding!

Class PhysBox

    Private

        ' PhysParams is used internally to allow creation of box prior to manual placement.
        ' Entities should not be manually positioned after being attached to a RigidBody.

        ' Create PhysBox, manually position PhysBox.model, then call PhysBox.Start ().

        Class InitParams
            Field mass:Float
            Field group:Short
            Field mask:Short
            Field box:Boxf
        End

        ' InitParams are only needed for setup, and freed afterwards, hence not added as separate fields.

        Field init:InitParams

    Public

    ' Public references...

    Field model:Model           ' mojo3d entity
    Field collider:BoxCollider  ' Bullet physics collider
    Field body:RigidBody        ' Bullet physics body

    Method New (width:Float = 1, height:Float = 1, depth:Float = 1, mass:Float = 1, material:Material = Null, parent:Entity = Null, group:Int = 1, mask:Int = 1)

        ' Store setup params for PhysBox.Start ()

        init = New InitParams

        init.mass       = mass
        init.group      = group
        init.mask       = mask
        init.box        = New Boxf (-width * 0.5, -height * 0.5, -depth * 0.5, width * 0.5, height * 0.5, depth * 0.5)

        If Not material
            material    = New PbrMaterial (Color.Red)
        Endif

        model           = Model.CreateBox (init.box, 1, 1, 1, material, parent)

    End

    Method Start (sleeps:Bool = True)

        collider            = New BoxCollider (model)
        body                = New RigidBody (model)

        collider.Box        = init.box

        body.Mass           = init.mass
        body.CollisionGroup = init.group
        body.CollisionMask  = init.mask

        body.Restitution    = 0.1 ' Slight bounciness

        ' Disables sleeping -- NOT generally desirable outside of demos, as most bodies should settle...

        ' Recommended only for player-controlled bodies in general!

        If Not sleeps
            body.btBody.setActivationState (bullet.DISABLE_DEACTIVATION)
        Endif

        init                = Null

    End

    Method Move (x:Float, y:Float, z:Float)
        model.Move (x, y, z)
    End

    Method Rotate (pitch:Float, roll:Float, yaw:Float, localspace:Bool = False)
        model.Rotate (pitch, roll, yaw, localspace)
    End

End

Class Entity Extension

    ' Temp workaround for child-entity.PointAt by DoctorWhoof!

    ' https://github.com/blitz-research/monkey2/issues/324

    Method WorldPointAt( target:Vec3f,up:Vec3f=New Vec3f( 0,1,0 ) )
        Local k:=(target-Position).Normalize()
        Local i:=up.Cross( k ).Normalize()
        Local j:=k.Cross( i )
        Basis=New Mat3f( i,j,k )
    End

    ' Temp workaround for removing parent entities, by Mark:

    ' http://monkeycoder.co.nz/forums/topic/mojo3d-set-to-default-parent/#post-12911

    Method RemoveParent ()
        Local matrix:AffineMat4f = Matrix
        Parent = Null
        Matrix = matrix
    End

End

' Main Game class...

Class Game Extends Window

    ' Er, camera speed logic is a bit weird/unnecessary...

    Const CAMERA_SPEED_NORMAL:Float = 0.05  ' Normal camera movement
    Const CAMERA_SPEED_BOOST:Float = 5.0    ' Camera boost with Shift key

    Field camera_speed:Float = 1.0              ' Camera forward speed

    ' 3D scene references...

    Field scene:Scene
    Field camera:Camera
    Field light:Light

    Field cam_piv:Model
    Field camera_collider:SphereCollider
    Field camera_body:RigidBody

    ' Physics box representing ground...

    Field ground:PhysBox

    ' List of PhysBoxes...

    Field boxes:List <PhysBox>

    ' App init/scene creation...

    Method New (title:String, width:Int, height:Int, flags:WindowFlags)

        Super.New (title, width, height, flags) ' Create window

        SeedRnd (Millisecs ())                  ' Init random seed

        SwapInterval = 1                        ' V-sync on

        CreateScene (50)                        ' WAAAAAAAHHH! (Parameter = ground size.)

    End

    Method CreateScene:Void (ground_size:Float = 100)

        ' Set up 3D scene stuff...

        scene                   = Scene.GetCurrent ()

        ground                  = New PhysBox (ground_size, 1, ground_size, 0, New PbrMaterial (Color.Green * 0.25))

        ' Camera pivot...

        cam_piv                 = New Model

        camera                  = New Camera (cam_piv)
        light                   = New Light

        scene.AmbientLight      = Color.White * 0.75
        scene.ClearColor        = Color.Sky * 0.75

        ' Camera setup...

        camera.FOV              = 100

        cam_piv.Move (0, 5, -10)

        camera.Near             = 0.01
        camera.Far              = 1000

        ' Camera collision with boxes. Not with ground? Both masses = 0, possibly relevant...

        ' NB. camera radius is half width of default block (1.0), minus a little to allow passing between columns of blocks...

        camera_collider         = New SphereCollider (cam_piv)
        camera_collider.Radius  = 0.45

        camera_body             = New RigidBody (cam_piv)
        camera_body.Kinematic   = True
        camera_body.Mass        = 0

        ' Light setup...

        light.CastsShadow       = True
        light.Range             = 1000

        light.Move (-10, 50, -10)

        ' Point things at ground centre...

'       camera.WorldPointAt (ground.model.Position)
        light.PointAt (ground.model)

        ' Create list of PhysBoxes...

        boxes   = New List <PhysBox>

        ' Build wall of PhysBoxes...

        BuildWall (WALL_WIDTH, WALL_HEIGHT)

        ' Start ground physics...

        ground.Start ()

    End

    Method BuildWall (width:Int, height:Int, block_mass:Float = 5.0)

        Local pb:PhysBox

        ' Creates bunch of PhysBoxes and adds to internal list (boxes)...

        For Local y:Int = 0 Until height
            For Local x:Int = 0 Until width

                Local color:Color = New Color (Rnd (0.4, 1.0), Rnd (0.4, 1.0), Rnd (0.4, 1.0))

                ' Create new PhysBox...

                pb = New PhysBox (1, 1, 1, block_mass, New PbrMaterial (color))

                ' Position PhysBox...

                pb.Move (x - (width * 0.5) + 0.5, y + 1, 0)

                ' Add to list...

                boxes.Add (pb)

                ' Start its physics...

                pb.Start () ' False = RigidBody doesn't sleep at rest

            Next
        Next

        ' Lintel, or "top bit"...

        pb = New PhysBox (width, 1, 1, width * height * 1.0, New PbrMaterial (Color.Chromium))

        pb.Move (0, height + 1, 0)

        boxes.Add (pb)

        pb.Start ()

    End

    Method DropBox ()

        ' Drops pink boxes into scene...

        Local pb:PhysBox = New PhysBox (1, 1, 1, 1, New PbrMaterial (Color.HotPink))

        pb.Move (0, 30, 0)

        boxes.Add (pb)

        pb.Start ()

    End

    ' Fire bullets!

    Method ShootBox (force:Float = BULLET_FORCE)

        ' Create bullet -- mass 5.0 -- as child of
        ' camera, to pick up location/rotation...

        Local pb:PhysBox = New PhysBox (0.75, 0.75, 0.75, 5, New PbrMaterial (Color.Red), camera)

        ' Move forward in local (child) space by 2 units...

        pb.model.Move (0.0, 0.0, 2.0)

        ' Get rid of parent/child relationship so bullet isn't
        ' attached to camera any more...

        pb.model.RemoveParent ()

        ' Add to list of PhysBoxes...

        boxes.Add (pb)

        ' Start physics...

        pb.Start ()

        ' Apply force (as impulse, ie. instantly)...

        ' Method carefully derived from typing properties and methods until it worked!

        ' World vector multiplied by 1 in all axes other than z, which is multiplied by force...

        pb.body.ApplyImpulse (pb.model.Basis * New Vec3f (1.0, 1.0, force))

    End

    Method UpdateBoxes ()

        For Local pb:PhysBox = Eachin boxes

            If pb.model.Y < -20

                pb.model.Scale = pb.model.Scale * 0.75

                ' Remove when too small...

                If pb.model.Scale.x < 0.01
                    pb.model.Destroy ()
                    boxes.Remove (pb)
                Endif

            Endif

        Next

    End

    Method UpdateGame ()

        ' Check box position/remove if too far down...

        UpdateBoxes ()

        If Keyboard.KeyHit (Key.Space)
            ShootBox ()
        Endif

        ' Drop boxes...

        If Keyboard.KeyDown (Key.D)
            DropBox ()
        Endif

        ' Quit...

        If Keyboard.KeyHit (Key.Escape)
            App.Terminate ()
        Endif

        ' Shadows on/off...

        If Keyboard.KeyHit (Key.S)
            light.CastsShadow = Not light.CastsShadow
        Endif

        ' Rebuild wall...

        If Keyboard.KeyHit (Key.R)

            For Local pb:PhysBox = Eachin boxes
                pb.model.Destroy ()
                boxes.Remove (pb)
            Next

            BuildWall (WALL_WIDTH, WALL_HEIGHT)

        Endif

        ' Speed booster...

        If Keyboard.KeyDown (Key.LeftShift)
            camera_speed = CAMERA_SPEED_BOOST
        Else
            camera_speed = 1.0
        Endif

        ' Forward...

        If Keyboard.KeyDown (Key.A)
            cam_piv.Move (0.0, 0.0, CAMERA_SPEED_NORMAL * camera_speed)
        Endif

        ' Back...

        If Keyboard.KeyDown (Key.Z)
            cam_piv.Move (0.0, 0.0, -CAMERA_SPEED_NORMAL * camera_speed)
        Endif

        ' Rotation...

        If Keyboard.KeyDown (Key.Left)
            cam_piv.Rotate (0.0, 1.0, 0.0)
        Endif

        If Keyboard.KeyDown (Key.Right)
            cam_piv.Rotate (0.0, -1.0, 0.0)
        Endif

        If Keyboard.KeyDown (Key.Up)
            cam_piv.Rotate (1.0, 0.0, 0.0, True)
        Endif

        If Keyboard.KeyDown (Key.Down)
            cam_piv.Rotate (-1.0, 0.0, 0.0, True)
        Endif

    End

    ' Helper...

    Method ShadowText:Void (canvas:Canvas, s:String, x:Float, y:Float, warning:Bool = False)
        canvas.Color = Color.Black
        canvas.DrawText (s, x + 1, y + 1)
        If warning
            canvas.Color = Color.Red
        Else
            canvas.Color = Color.White
        Endif
        canvas.DrawText (s, x, y)
    End

    Method RenderText (canvas:Canvas)

        ShadowText (canvas, "FPS: " + App.FPS + " / Scene refresh rate: " + scene.UpdateRate, 20.0, 20.0)
        ShadowText (canvas, "A/Z + Cursors to move camera", 20.0, 40.0)
        ShadowText (canvas, "SHIFT to boost", 20.0, 60.0)
        ShadowText (canvas, "S to toggle shadows", 20.0, 80.0)
        ShadowText (canvas, "R to rebuild wall", 20.0, 120.0)
        ShadowText (canvas, "D to drop more boxes", 20.0, 140.0)
        ShadowText (canvas, "SPACE to fire", 20.0, 160.0)
        ShadowText (canvas, "Boxes: " + boxes.Count (), 20.0, 200.0)

    End

    Method OnRender (canvas:Canvas) Override

        UpdateGame ()                   ' Process controls/game world

        RequestRender ()                ' Ask window to render itself

        scene.Update ()                 ' Update scene AND PHYSICS; IMPORTANT!

        scene.Render (canvas, camera)       ' Render scene

        RenderText (canvas)             ' Add text

    End

End

Function Run3D (title:String, width:Int, height:Int, flags:WindowFlags = WindowFlags.Center)

    New AppInstance
    New Game (title, width, height, flags)

    App.Run ()

End

Function Main ()
    Run3D ("3D Scene", 960, 540, WindowFlags.Center)        ' 1/4 HD!
'   Run3D ("3D Scene", 1920, 1080, WindowFlags.Fullscreen) 
End