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:
' 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
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