blitz-research/monkey2

Weird jumping/flickering sprites problem

Opened this issue · 13 comments

Hi Mark,

Bit of an odd one I've run into while trying to convert my rocket smoke from cubes to sprites, sort of narrowed it down to the sample below.

In short, I'm firing a stream of physics-controlled particles that cycles through white, yellow, orange, red, black, then fade out. It should look like this (extreme debug version using cubes):

particles

However, when using sprites, they start out correctly, then once a few of them are active at once, they start to flicker and jump.

You should be able to see it in the code below. Note that the class is duplicated, just that one uses cubes, other uses sprites.

Hold Space for a constant stream and note the cubes are fine, but the sprites start jumping and flickering. With short taps, the sprites are also OK, but held for a little longer, they start to mess up. (Tap Enter for single particles and note they only screw up if tapped rapidly.)

Thought it was some sort of alpha problem, but replacing alpha with a dummy variable (leaving entity alpha at 1.0 throughout) does the same thing.


Namespace myapp3d

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

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

' ---------------------- SPRITE version ----------------------

Class RocketParticle_Sprite Extends Behaviour

	Public
	
		Function Create:RocketParticle_Sprite (parent:Entity, thrust:Vec3f, size:Float = 0.5, fadeout:Float = 0.95)

'Game.MainCamera.CameraDistance = 4.0
			'RocketParticle.SpriteInit ()
			
			Local sprite:Sprite = New Sprite (New SpriteMaterial (), parent)
		
				Cast <SpriteMaterial> (sprite.Material).ColorFactor = Color.White
				
				'sprite.Move (Rnd (-0.1, 0.1), Rnd (-2.1, -2.5), Rnd (-0.1, 0.1))
				
				sprite.Move (0.0, -2.1, 0.0)
				
				sprite.Parent				= Null
				sprite.Scale				= New Vec3f (size, size, 0.0)
				sprite.Alpha				= 1.0
				
			Local sp:RocketParticle_Sprite			= New RocketParticle_Sprite (sprite)
			
				sp.thrust					= thrust
				sp.update_fader				= fadeout
			
				sp.TMP_fake_alpha = 1.0
				
			Return sp
			
		End

	Private
	
		Method New (entity:Entity)
		
			Super.New (entity)

			AddInstance ()
			
		End

		Method OnStart () Override

			Local collider:BoxCollider	= Entity.AddComponent <BoxCollider> () ' Unexpected: Collider needs to be added BEFORE applying impulse!

			Local body:RigidBody		= Entity.AddComponent <RigidBody> ()
' No Boxf!
				body.Mass				= 0.01
				body.Restitution		= 0.5
				body.Friction			= 0.1
	
				body.CollisionMask		= 0'COLL_NOTHING
			
				body.ApplyImpulse (thrust)
			
				thrust					= Null ' Don't need to keep temp Vec3f object
				
		End
		
		Method OnUpdate (elapsed:Float) Override
			
		'		Print Int (color_change * 5.0)
				
'				Cast <Sprite> (Entity).Material = SpriteMat [Int (Entity.Alpha * 5.0)] ' There are 5 sprite materials

'				Local sm:SpriteMaterial = Cast <SpriteMaterial> (Cast <Sprite> (Entity).Material)

				Local cs:Sprite	= Cast <Sprite> (Entity)
				Local sm:SpriteMaterial	= Cast <SpriteMaterial> (cs.Material)

'				Local cs:Model	= Cast <Model> (Entity)
'				Local sm:PbrMaterial	= Cast <PbrMaterial> (cs.Material)
				
				Select Int (color_change * 5.0) ' There are 5 sprite materials
					Case 0
						sm.ColorFactor = Color.Black
					Case 1
						sm.ColorFactor = Color.Red
					Case 2
						sm.ColorFactor = Color.Orange
					Case 3
						sm.ColorFactor = Color.Yellow
					Case 4
						sm.ColorFactor = Color.White
				End
				
				If sm.ColorFactor = Color.Black
					Entity.Alpha = Entity.Alpha * 0.975 ' TODO: Needs adjusting for framerate!
					'TMP_fake_alpha = TMP_fake_alpha * 0.975
				Else
					color_change = color_change * update_fader ' TODO: Needs adjusting for framerate!
				End
				
				' Slow particle down (air resistance)... very dependent on start speed and alpha fade amount...
				
				Entity.GetComponent <RigidBody> ().LinearDamping = (1.0 - color_change)' * 0.95 ' Trial and error!
				
				'If TMP_fake_alpha < 0.075'
				If Entity.Alpha < 0.075
					Entity.Destroy ()
				Endif
			
		End
	
		' Rocket thrust level -- need to temp-store here as OnStart can't be passed custom params!
		
		Field thrust:Vec3f
		Field update_fader:Float
		Field color_change:Float = 0.99
		
		Field TMP_fake_alpha:Float
End

' ---------------------- MESH version ----------------------

Class RocketParticle_Mesh Extends Behaviour

	Public
	
		Function Create:RocketParticle_Mesh (parent:Entity, thrust:Vec3f, size:Float = 0.5, fadeout:Float = 0.95)

'Game.MainCamera.CameraDistance = 4.0
			'RocketParticle.SpriteInit ()
			
'			Local sprite:Sprite = New Sprite (New SpriteMaterial (), rocket.RocketModel)
			Local sprite:Model = Model.CreateBox (New Boxf (-0.5, -0.5, -0.5, 0.5, 0.5, 0.5), 1, 1, 1, New PbrMaterial (Color.White), parent)'New Sprite (New SpriteMaterial (), rocket.RocketModel)
		
'				Cast <SpriteMaterial> (sprite.Material).ColorFactor = Color.White
				
				'sprite.Move (Rnd (-0.1, 0.1), Rnd (-2.1, -2.5), Rnd (-0.1, 0.1))
				
				sprite.Move (0.0, -2.1, 0.0)
				
				sprite.Parent				= Null
				sprite.Scale				= New Vec3f (size, size, size)
				sprite.Alpha				= 1.0
				
			Local sp:RocketParticle_Mesh			= New RocketParticle_Mesh (sprite)
			
				sp.thrust					= thrust
				sp.update_fader				= fadeout

				sp.TMP_fake_alpha = 1.0
			
			Return sp
			
		End

	Private
	
		Method New (entity:Entity)
		
			Super.New (entity)

			AddInstance ()
			
		End

		Method OnStart () Override

			Local collider:BoxCollider	= Entity.AddComponent <BoxCollider> () ' Unexpected: Collider needs to be added BEFORE applying impulse!

			Local body:RigidBody		= Entity.AddComponent <RigidBody> ()
' No Boxf!
				body.Mass				= 0.01
				body.Restitution		= 0.5
				body.Friction			= 0.1
	
				body.CollisionMask		= 0'COLL_NOTHING
			
				body.ApplyImpulse (thrust)
			
				thrust					= Null ' Don't need to keep temp Vec3f object
				
		End
		
		Method OnUpdate (elapsed:Float) Override
			
		'		Print Int (color_change * 5.0)
				
'				Cast <Sprite> (Entity).Material = SpriteMat [Int (Entity.Alpha * 5.0)] ' There are 5 sprite materials

'				Local sm:SpriteMaterial = Cast <SpriteMaterial> (Cast <Sprite> (Entity).Material)

'				Local cs:Sprite	= Cast <Sprite> (Entity)
'				Local sm:SpriteMaterial	= Cast <SpriteMaterial> (cs.Material)

				Local cs:Model	= Cast <Model> (Entity)
				Local sm:PbrMaterial	= Cast <PbrMaterial> (cs.Material)
				
				Select Int (color_change * 5.0) ' There are 5 sprite materials
					Case 0
						sm.ColorFactor = Color.Black
					Case 1
						sm.ColorFactor = Color.Red
					Case 2
						sm.ColorFactor = Color.Orange
					Case 3
						sm.ColorFactor = Color.Yellow
					Case 4
						sm.ColorFactor = Color.White
				End
				
				If sm.ColorFactor = Color.Black
					Entity.Alpha = Entity.Alpha * 0.975 ' TODO: Needs adjusting for framerate!
				'	TMP_fake_alpha = TMP_fake_alpha * 0.975
				Else
					color_change = color_change * update_fader ' TODO: Needs adjusting for framerate!
				End
				
				' Slow particle down (air resistance)... very dependent on start speed and alpha fade amount...
				
				Entity.GetComponent <RigidBody> ().LinearDamping = (1.0 - color_change)' * 0.95 ' Trial and error!
				
				'If TMP_fake_alpha < 0.075'
				If Entity.Alpha < 0.075
					Entity.Destroy ()
				Endif
			
		End
	
		' Rocket thrust level -- need to temp-store here as OnStart can't be passed custom params!
		
		Field thrust:Vec3f
		Field update_fader:Float
		Field color_change:Float = 0.99
		
		Field TMP_fake_alpha:Float
End

Class MyWindow Extends Window
	
	Field _scene:Scene
	Field _camera:Camera
	Field _light:Light
	Field _ground:Model
	
	Field pivot0:Pivot
	Field pivot1:Pivot
	
	Method New( title:String="Simple mojo3d app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
		
		Super.New( title,width,height,flags )
	End
	
	Method OnCreateWindow() Override
		
		'create (current) scene
		_scene=New Scene
		_scene.ClearColor = New Color( 0.2, 0.6, 1.0 )
		_scene.AmbientLight = _scene.ClearColor * 0.25
		_scene.FogColor = _scene.ClearColor
		_scene.FogNear = 1.0
		_scene.FogFar = 200.0
		
		'create camera
		_camera=New Camera( Self )
		_camera.AddComponent<FlyBehaviour>()
		_camera.Move( 0,2.5,-5 )
		_camera.Rotate (-25, 0, 0)
			
		'create light
		_light=New Light
		_light.CastsShadow=True
		_light.Rotate( 45, 45, 0 )
		
		'create ground
		Local groundBox:=New Boxf( -100,-1,-100,100,0,100 )
		Local groundMaterial:=New PbrMaterial( Color.Lime )
		_ground=Model.CreateBox( groundBox,1,1,1,groundMaterial )
		_ground.CastsShadow=False
		
		pivot0 = New Pivot
		pivot0.Move (-3, 7.5, 0)
		
		pivot1 = New Pivot
		pivot1.Move (3, 7.5, 0)
		
	End
	
	Method OnRender( canvas:Canvas ) Override

		Local spread:Float = 0.025

		If Keyboard.KeyHit (Key.Enter)
			RocketParticle_Mesh.Create (pivot0, New Vec3f (Rnd (-spread, spread), 0.1, Rnd (-spread, spread)))
			RocketParticle_Sprite.Create (pivot1, New Vec3f (Rnd (-spread, spread), 0.1, Rnd (-spread, spread)))
		Endif
	
		If Keyboard.KeyDown (Key.Space)
			For Local loop:Int = 1 To 1
				RocketParticle_Mesh.Create (pivot0, New Vec3f (Rnd (-spread, spread), 0.1, Rnd (-spread, spread)))
				RocketParticle_Sprite.Create (pivot1, New Vec3f (Rnd (-spread, spread), 0.1, Rnd (-spread, spread)))
			Next
		Endif

		If Keyboard.KeyHit (Key.Escape) Then App.Terminate ()
		RequestRender()
		_scene.Update()
		_camera.Render( canvas )
		canvas.DrawText( "FPS="+App.FPS,0,0 )
		canvas.DrawText( "SPACE: Stream of particles",0,20 )
		canvas.DrawText( "ENTER: Single particles",0,40 )
		canvas.DrawText( "RocketParticle_Mesh on left",0,80 )
		canvas.DrawText( "RocketParticle_Sprite on right",0,100 )
	End
	
End

Function Main()

	New AppInstance
	
	New MyWindow
	
	App.Run()
End

Tried a non-physics version, and this doesn't suffer from the same problem, though it does (possibly related?) have a weird colour-changing effect on cycling the alpha, which may be another bug:

Namespace myapp3d

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

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

Const RadDivider:Float = Pi / 180.0

Function Degrees:Float (radian:Float)
	Return radian * RadDivider
End

Class MovingSprite
	Field sprite:Sprite
End

Class MyWindow Extends Window
	
	Field _scene:Scene
	Field _camera:Camera
	Field _light:Light
	Field _ground:Model

	Field list:List <MovingSprite>
	
	Method New( title:String="Simple mojo3d app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
		
		Super.New( title,width,height,flags )
	End
	
	Method OnCreateWindow() Override
		
		'create (current) scene
		_scene=New Scene
		_scene.ClearColor = New Color( 0.2, 0.6, 1.0 )
		_scene.AmbientLight = _scene.ClearColor * 0.25
		_scene.FogColor = _scene.ClearColor
		_scene.FogNear = 1.0
		_scene.FogFar = 200.0
		
		'create camera
		_camera=New Camera( Self )
		_camera.AddComponent<FlyBehaviour>()
		_camera.Move( 0,2.5,-5 )
		
		'create light
		_light=New Light
		_light.CastsShadow=True
		_light.Rotate( 45, 45, 0 )
		
		'create ground
		Local groundBox:=New Boxf( -100,-1,-100,100,0,100 )
		Local groundMaterial:=New PbrMaterial( Color.Lime )
		_ground=Model.CreateBox( groundBox,1,1,1,groundMaterial )
		_ground.CastsShadow=False
		
		list = New List <MovingSprite>
		
		For Local loop:Int = 1 To 1000
			Local ms:MovingSprite = New MovingSprite
			ms.sprite = New Sprite (New SpriteMaterial)
			Cast <SpriteMaterial> (ms.sprite.Material).ColorFactor = Color.Rnd ()
			ms.sprite.Move (Rnd (-10, 10), Rnd (-10, 10), Rnd (-10, 10))
			ms.sprite.Rotate (Rnd (-180, 180), Rnd (-180, 180), Rnd (-180, 180))
			list.Add (ms)
		Next
		
	End
	
	Method OnRender( canvas:Canvas ) Override
	
		For Local ms:MovingSprite = Eachin list
			ms.sprite.Move (Sin (Degrees (Millisecs ())) * 0.1, Cos (Degrees (Millisecs ())) * 0.1, Sin (Degrees (Millisecs ())) * 0.1)
			ms.sprite.Alpha = Sin (Degrees (Millisecs () * 0.1))
		Next
	
		If Keyboard.KeyHit (Key.Escape) Then App.Terminate ()
		RequestRender()
		_scene.Update()
		_camera.Render( canvas )
		canvas.DrawText( "FPS="+App.FPS,0,0 )
	End
	
End

Function Main()

	New AppInstance
	
	New MyWindow
	
	App.Run()
End

Working great, thanks, Mark. Just updated my project if you want to see it. Particle effect needs some work still, but the sprites look to be working perfect.

I'll mess about and try getting global sprite materials working -- the ExplosionParticle uses a fixed array of 5 materials, but I think I ended up with one material per sprite in RocketParticle while fiddling with workarounds!

(I can't remember if I could change the colour of individual particles using just one material but think I'm doing some weird stuff at the moment anyway... )

Thanks again!

Remembered why I was creating a material per-sprite (on trying to re-implement reassigning each sprite's material, as it 'cools', from an array of five flame-colour materials, ie. White, Yellow, Orange, Red, Black):

ie. sprite.Material = MatArray [heat_level] ' MatArray:SpriteMaterial

(I think this may be the "related material bug" you were referring to?)

I'll delve in again tomorrow (and will try the quad fix, though I don't really need it now), but from memory, using a single SpriteMaterial and trying to change the colour affected all sprites at once -- I assumed Entity.Color was changing the colour of the single material. Will try again tomorrow...

Many thanks!

OK, I'd assumed sprites'/entities' colours/alphas were controlled via a default material that I had to create.

However, there seem to be problems relating to creating sprites with no material, possibly relating to having a parent, too -- the example below crashes for me at _camera.Render. (Switch to the "FAILS" New Sprite line using no material -- left working here just for reference.)

Interestingly, in my game, I've updated my explosion particle code like this, and it doesn't crash, despite having no material!

			Local sprite:Sprite = New Sprite (rocket.RocketModel) ' Just a Model
		
				' sprite.Parent				= Null

... but if I enable sprite.Parent = Null it crashes. The sprites are created with the rocket Model as parent for positioning, then 'freed' via parent = Null to operate independently under physics control.

(Interestingly, I get 'attempt to invoke method on null instance' in debug mode, but it doesn't highlight the line in Ted2Go.)

I can't reproduce this parenting part in my sample below!

Will upload the current rocket source if needed, but perhaps getting the sample below to work will just fix it.

Namespace myapp3d

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

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


Class MyWindow Extends Window
	
	Field _scene:Scene
	Field _camera:Camera
	Field _light:Light
	Field _ground:Model
	Field _sprite:Sprite
	
	
	Method New( title:String="Simple mojo3d app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
		
		Super.New( title,width,height,flags )
	End
	
	Method OnCreateWindow() Override
		
		'create (current) scene
		_scene=New Scene
		_scene.ClearColor = New Color( 0.2, 0.6, 1.0 )
		_scene.AmbientLight = _scene.ClearColor * 0.25
		_scene.FogColor = _scene.ClearColor
		_scene.FogNear = 1.0
		_scene.FogFar = 200.0
		
		'create camera
		_camera=New Camera( Self )
		_camera.AddComponent<FlyBehaviour>()
		_camera.Move( 0,2.5,-5 )
		
		'create light
		_light=New Light
		_light.CastsShadow=True
		_light.Rotate( 45, 45, 0 )
		
		'create ground
		Local groundBox:=New Boxf( -100,-1,-100,100,0,100 )
		Local groundMaterial:=New PbrMaterial( Color.Lime )
		_ground=Model.CreateBox( groundBox,1,1,1,groundMaterial )
		_ground.CastsShadow=False
		
		Local spmat:SpriteMaterial = New SpriteMaterial ()

		' FAILS:
		
'		_sprite = New Sprite (_camera)
		
		' WORKS:
		
		_sprite = New Sprite (spmat, _camera)

		_sprite.Move (0, 0, 5)

		If _sprite = Null Then Print "Null" ' Fine

	End
	
	Method OnRender( canvas:Canvas ) Override
		
		Print "Requesting render..."
		RequestRender()
		Print "Updating scene..."
		_scene.Update()
		Print "Rendering scene..."				' Boom AFTER this!
		_camera.Render( canvas )
		Print "Drawing text..."
		canvas.DrawText( "FPS="+App.FPS,0,0 )
		Print "OnRender complete"
	End
	
End

Function Main()

	New AppInstance
	
	New MyWindow
	
	App.Run()
End

Thanks, Mark... but there's something wrong with sprite colours now, when using default material!

It's like only colours with an element of red get shown (I think), and of those, the colours are weirdly stripped of something... here, for example, Color.Orange sprites are shown in magenta, under:

	Method OnRender( canvas:Canvas ) Override
		
		Local copy:Sprite = sprite.Copy ()
		
			copy.Color = Color.Orange' White

Color.Blue and Color.Green simply don't appear.

Demo below...

Namespace myapp3d

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

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


Class MyWindow Extends Window
	
	Field _scene:Scene
	Field _camera:Camera
	Field _light:Light
	
	Field sprite:Sprite
	
	Method New( title:String="Simple mojo3d app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable )
		
		Super.New( title,width,height,flags )
	End
	
	Method OnCreateWindow() Override
		
		'create (current) scene
		_scene=New Scene
		_scene.ClearColor = Color.Black
		_scene.AmbientLight = _scene.ClearColor * 0.25
		_scene.FogColor = _scene.ClearColor
		_scene.FogNear = 1.0
		_scene.FogFar = 200.0
		
		'create camera
		_camera=New Camera( Self )
		_camera.AddComponent<FlyBehaviour>()
		_camera.Move( 0,2.5,-5 )
		
		'create light
		_light=New Light
		_light.CastsShadow=True
		_light.Rotate( 45, 45, 0 )
		
		sprite = New Sprite ()
		
	End
	
	Method OnRender( canvas:Canvas ) Override
		
		Local copy:Sprite = sprite.Copy ()
		
			copy.Color = Color.Orange' White
'			Print copy.Color
			Local size:Float = Rnd (0.1, 2.0)
			copy.Scale = New Vec3f (size, size, 1.0)
			copy.Move (Rnd (-100.0, 100.0), Rnd (-100.0, 100.0), Rnd (50.0, 150.0))
		
		RequestRender()
		_scene.Update()
		_camera.Render( canvas )
		canvas.DrawText( "FPS="+App.FPS,0,0 )
	End
	
End

Function Main()

	New AppInstance
	
	New MyWindow
	
	App.Run()
End

Try Color.Rnd (), too -- it produces random RGBs (shown by Print), but only the sprites in a range best described as Barratts Fruit Salad gets shown!

That's looking spot-on now with my rocket particles using default material -- thanks!

I did a little experiment, leaving a plain sprite (Copy'd from a master sprite) when deleting each particle, and with default material I can now hit 10,000 sprites leaving a cool 3D trail through the sky at 60 FPS. (You'll notice 52 fps here -- it's pretty much dead-on 10k when it starts to drop.)

EDIT: Latest update includes this stuff.

10ktrail

Couple of mostly-unrelated queries if you get chance...

  1. While trying to make things framerate-independent, I think I've got the hang of OnUpdate's elapsed parameter, but for non-physics entities that I also want to move/rotate/fade out framerate-independently, I've thought that I could really use Scene's elapsed value -- could that be made accessible, or would it make no sense to use that outside of Behaviour's OnUpdate methods?

  2. Totally unrelated: I noticed btCompoundShape had been started at some point, and I believe this is what I would need to stop my rocket rotating awkwardly about its (conical) base when landing -- the idea being to attach a cuboid shape (below the cone) whose corners fit the four fin positions, hence making it only tip in the four resulting directions. Just one for the wishlist! (It would also allow bolt-on rocket engines a-la Kerbal Space Program, quadcopter-drone simulation, etc.)

Can be closed.