Tag Archives: Animation

Basic4Android: A first look at ABPlay, a game engine library

ABPlay Screenshot

ABPlay Screenshot


It has been a while since I’ve written a new article on this blog because it have been busy months both at work and in my personal life. One of the things I wanted to do was writing an easy to use Game Engine for B4A. I have written the ABPhysics engine in the past and recently Informatix pointed out I had started another engine (ABGameEngine) before that. Development on ABGameEngine was stopped early for several reasons: time was one of them, but also because several other developers were working on an engine themselves. It seemed a little bit pointless to continue.

However, the other engines were not further developed either. Until recently Erel came up with the GameView. This is an excellent View that will cover the needs of a lot of beginning programmers. I definitely would like to urge starting game developers to take a look at this. Registered users of B4A can download the GameView lib from here.

But still, I wanted something more. I looked at ABgameEngine again, as it had some great ideas like layers, animated sprites, gamepads etc. But it was also very old code and not written very well. And it crashed all the time :-)

I decided to restart from scratch a couple of weeks ago. Some weekends and evenings later ABPlay was born! And with a lot of goodies!

Here is a small demo video of what I got so far. it demonstrates the following:

1. Layers 
     (Layer 1) the moving background with the Odies as animated sprites
     (Layer 2) the black foreground layer with a weird dancing creature that passes now and then
2. Animated sprites, not bound to a layer 
     (our hero Garfield is back!)
3. A sprite can have different animations. 
     (like one for standing, one for walking, one for fighting, etc)
4. A sprite can have different predefined 'Walks'
     (A walk can be build like you would build a path. It's a sequence of lines, bezier curves, wait periods etc. It's like a simple flash movie)
5. Gamepad controls
     (the 'joystick' pad on the left)
     (the 'action' pad on the right)
     (the 'direction' pad as an alternative to the joystick, not shown in this video)
6. In the demo I cannot demonstrate it with a mouse, but it is completely multi touch
     (You can control Garfield AND press the Action button X AND do a swipe anywhere at the same time)

But let’s have a look (beware this is running on the Emulator. On a real device it is much smoother):

I’m actually very pleased with the result. The graphics and handling is very smooth.
Above all, it’s still very easy to program. Here is the whole code for the demo app:

#Region  Project Attributes 
	#ApplicationLabel: ABPlayTest
	#VersionCode: 1
	#VersionName: 
	'SupportedOrientations possible values: unspecified, landscape or portrait.
	#SupportedOrientations: landscape
	#CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes 
	#FullScreen: true
	#IncludeTitle: false
#End Region

Sub Process_Globals

End Sub

Sub Globals
	Dim myPlay As ABPlay
	' the panel that will hold the ABPlay
	Dim myPanel As Panel
	
	' a background and foregroud layer
	Dim bgLayer As ABLayer
	Dim fgLayer As ABLayer
	
	' some colors to show the multi touch points in the demo
	Dim myColors() As Int = Array As Int(Colors.Red, Colors.Green, Colors.Blue, Colors.Cyan, Colors.Yellow, Colors.Gray, Colors.White, Colors.Magenta, Colors.LightGray, Colors.DarkGray)
	
	' our hero
	Dim Hero As ABSprite
	' our enemies
	Dim Enemies As List
	
	' param to set the set the speed
	Dim Speed As Float = 0.2
	
	' some variables to hold the current state of the hero
	Dim currentAction As String
	Dim currentDirection As String
	Dim currentIsStanding As Boolean
End Sub

Sub Activity_Create(FirstTime As Boolean)
	' initialize ABPlay with myPanel
	myPanel.Initialize("")
	Activity.AddView(myPanel, 0,0,100%x,100%y)
	myPlay.Initialize(myPanel, "myPlay")
	
	'////////////// BEGIN loading sprite sequences
	' Load Sprite Sequences
	myPlay.LoadSpriteSequence("GarStandingLeft",LoadBitmap(File.DirAssets, "garleftstill.png"),6, 1, 1000)
	myPlay.LoadSpriteSequence("GarStandingLeft",LoadBitmap(File.DirAssets, "garleftstill.png"),6, 1, 1000)
	myPlay.LoadSpriteSequence("GarStandingRight",LoadBitmap(File.DirAssets, "garrightstill.png"),6, 1, 1000)
	myPlay.LoadSpriteSequence("GarWalkingLeft",LoadBitmap(File.DirAssets, "garleft.png"),8, 1, 1000)
	myPlay.LoadSpriteSequence("GarWalkingRight",LoadBitmap(File.DirAssets, "garright.png"),8, 1, 1000)
	myPlay.LoadSpriteSequence("GarStandingLeftFight",LoadBitmap(File.DirAssets, "garleftstillfight.png"),2, 1, 250)
	myPlay.LoadSpriteSequence("GarStandingRightFight",LoadBitmap(File.DirAssets, "garrightstillfight.png"),2, 1, 250)
	myPlay.LoadSpriteSequence("GarWalkingLeftFight",LoadBitmap(File.DirAssets, "garleftfight.png"),7, 1, 1000)
	myPlay.LoadSpriteSequence("GarWalkingRightFight",LoadBitmap(File.DirAssets, "garrightfight.png"),7, 1, 1000)
	' and the ones for Odie
	myPlay.LoadSpriteSequence("OdieStandingLeft", LoadBitmap(File.DirAssets, "odieleftstill.png"),7,1,Rnd(800,1200))
	myPlay.LoadSpriteSequence("OdieStandingRight", LoadBitmap(File.DirAssets, "odierightstill.png"),7,1,Rnd(800,1200))
	myPlay.LoadSpriteSequence("OdieWalkingLeft", LoadBitmap(File.DirAssets, "odieleft.png"),5,1,Rnd(800,1200))
	myPlay.LoadSpriteSequence("OdieWalkingRight", LoadBitmap(File.DirAssets, "odieright.png"),5,1,Rnd(800,1200))
	' and the one for the creature
	myPlay.LoadSpriteSequence("CreaturePassToRight", LoadBitmap(File.DirAssets, "creature.png"), 3, 4, 1200)
	'////////////// END loading sprite sequences
	
	'////////////// BEGIN building the Hero Garfield
	' initialize the hero
	Hero.Initialize("Hero", 50%x, 50%y)
	' add sprite sequences
	Hero.AddSpriteSequence("GarStandingLeft")
	Hero.AddSpriteSequence("GarStandingRight")
	Hero.AddSpriteSequence("GarWalkingLeft")
	Hero.AddSpriteSequence("GarWalkingRight")
	Hero.AddSpriteSequence("GarStandingLeftFight")
	Hero.AddSpriteSequence("GarStandingRightFight")
	Hero.AddSpriteSequence("GarWalkingLeftFight")
	Hero.AddSpriteSequence("GarWalkingRightFight")
	Hero.StartSpriteSequence("GarStandingLeft", True)
	currentDirection="LEFT"
	'//////////////  END building the Hero Garfield
	
	'////////////// BEGIN building the background layer with Odies
	' initalize a background layer
	bgLayer.Initialize("background", 0,0)
	bgLayer.SetBackground(LoadBitmap(File.DirAssets, "bga.jpg"), 1.0)
	myPlay.AddLayer(bgLayer)
	
	' initialize some enemies
	Enemies.Initialize
	Dim a As Int
	For a = 1 To 10
		Dim Odie As ABSprite
		
		' initialize an Odie with some animation sequences
		Odie.Initialize("Odie" & a, Rnd(10%x,90%x), Rnd(10%y, 90%y))
		' add multiple sprite sequences
		Odie.AddSpriteSequences(Array As String("OdieStandingLeft","OdieStandingRight","OdieWalkingLeft","OdieWalkingRight"))
		
		' create a random walk to the right
		Dim WalkRight As ABSpriteWalk
		WalkRight.Initialize("ToTheRight", True)
		Dim newX As Int = Odie.x+Rnd(20%x, 80%x)
		WalkRight.AddLine("OdieWalkingRight",Odie.x, Odie.y, newX, Odie.y, Rnd(90,100))
		WalkRight.AddWait("OdieStandingRight",newX, Odie.y, Rnd(10,50))
		WalkRight.AddLine("OdieWalkingLeft",newX, Odie.y, Odie.x, Odie.y, Rnd(90,100))
		WalkRight.AddWait("OdieStandingLeft",Odie.x, Odie.y, Rnd(10,50))
		Odie.AddWalk(WalkRight)
		
		' create a random walk to the left
		Dim WalkLeft As ABSpriteWalk
		WalkLeft.Initialize("ToTheLeft", True)
		Dim newX As Int = Odie.x-Rnd(20%x, 80%x)
		WalkLeft.AddLine("OdieWalkingLeft",Odie.x, Odie.y, newX, Odie.y , Rnd(90,100))
		WalkLeft.AddWait("OdieStandingLeft",newX, Odie.y, Rnd(10,50))
		WalkLeft.AddLine("OdieWalkingRight",newX, Odie.y, Odie.x, Odie.y, Rnd(90,100))
		WalkLeft.AddWait("OdieStandingRight",Odie.x, Odie.y, Rnd(10,50))
		Odie.AddWalk(WalkLeft)
		
		' pick random a walk, left or right
		Dim GoLeft As Int = Rnd(0,2)
		If GoLeft = 0 Then			
			Odie.StartWalk("ToTheRight")
		Else
			Odie.StartWalk("ToTheLeft")
		End If
		bgLayer.AddSprite(Odie)		
	Next
	'////////////// END building the background layer with Odies
	
	'////////////// BEGIN building the foreground layer with creature
	' initalize a foreground layer
	Dim fgLayer As ABLayer
	fgLayer.Initialize("foreground", 0,0)
	fgLayer.SetBackground(LoadBitmap(File.DirAssets, "fga.png"), 100%y/400)
	myPlay.AddLayer(fgLayer)
	
	' and a weird creature...
	Dim creature As ABSprite
	creature.Initialize("creature", -100%x, 100%y-240)
	creature.AddSpriteSequence("CreaturePassToRight")
	' with a walk
	Dim pass As ABSpriteWalk
	pass.Initialize("DanseToTheRight", True)
	pass.AddLine("DansingRight",creature.x, creature.y, 200%x, creature.y, 300)
	creature.AddWalk(pass)
	' and start the walk
	creature.StartWalk("DanseToTheRight")
	' add the creature to the foreground layer
	fgLayer.AddSprite(creature)
	'////////////// END building the foreground layer with creature
	
	'////////////// BEGIN Add game pads
	' initialize and start a Joystick Gamepad
	myPlay.InitializeJoystickPad(18, 100%y-210, 192, 192 , LoadBitmap(File.DirAssets, "joystick_bg.png"),LoadBitmap(File.DirAssets, "joystick.png"))
	myPlay.ShowJoystickPad(True)	
	
	' initialize and start a Action Gamepad
	myPlay.InitializeActionPad(100%x-210, 100%y-210, 192, 192 , LoadBitmap(File.DirAssets, "action_active.png"),LoadBitmap(File.DirAssets, "action_inactive.png"),LoadBitmap(File.DirAssets, "action_mask.png"),True, False, True, False)
	myPlay.ShowActionPad(True)		
	'////////////// END Add game pads
End Sub

Sub Activity_Resume
	
End Sub

Sub Activity_Pause (UserClosed As Boolean)
	' NEEDED FOR THE MOMENT TO CATCH THE HOME KEY, RESUME/PAUSE NOT YET SUPPORTED!
	myPlay.StopAndRecycle	
	Activity.Finish
End Sub

Sub Activity_KeyPress (KeyCode As Int) As Boolean 'Return True to consume the event
	Select Case KeyCode
		Case KeyCodes.KEYCODE_BACK
			' NEEDED: stop the drawing thread and recycle stuff
			myPlay.StopAndRecycle	
			Activity.Finish
			Return True
		Case KeyCodes.KEYCODE_HOME
			Return True
	End Select
End Sub

Sub myPlay_Draw(c As Canvas, State As ABState)
	' the actual drawing, do NOT set a debug stop in here!
	
	'////////////// BEGIN Calculation stuff
	' calculate hero stuff depending on what buttons we pressed on the gamepad
	currentAction = ""
	If State.UsingActionPad Then
		' go into fight state
		If State.ACTION_X Then
			currentAction = "X"
		End If
		' go back to center
		If State.ACTION_Y Then
			Hero.SetPostition(50%x,50%y)
		End If
	End If
	
	Dim XMovement As Int 
	Dim YMovement As Int
	If State.UsingJoystickPad Then
		XMovement = State.JOYSTICK_X*Speed
		YMovement = State.JOYSTICK_Y*Speed
		' update the hero's position and direction
		If XMovement<0 Then
			currentDirection = "LEFT"
		Else
			currentDirection = "RIGHT"
		End If
		Hero.SetPostition(Hero.x+XMovement, Hero.y+YMovement)		
		currentIsStanding = False
	Else
		currentIsStanding = True
	End If
	
	' set the animation type
	Select Case currentDirection
		Case "LEFT"
			Select Case currentAction
				Case "X"
					If currentIsStanding Then
						Hero.StartSpriteSequence("GarStandingLeftFight", False)
					Else
						Hero.StartSpriteSequence("GarWalkingLeftFight", False)
					End If				
				Case Else
					If currentIsStanding Then
						Hero.StartSpriteSequence("GarStandingLeft", False)
					Else
						Hero.StartSpriteSequence("GarWalkingLeft", False)
					End If
			End Select
		Case "RIGHT"
			Select Case currentAction
				Case "X"
					If currentIsStanding Then
						Hero.StartSpriteSequence("GarStandingRightFight", False)
					Else
						Hero.StartSpriteSequence("GarWalkingRightFight", False)
					End If				
				Case Else
					If currentIsStanding Then
						Hero.StartSpriteSequence("GarStandingRight", False)
					Else
						Hero.StartSpriteSequence("GarWalkingRight", False)
					End If				
			End Select
	End Select	
	
	' update the hero animation
	Hero.Update
	
	' move our backgrounds, does not make sense but shows the possibilities
	Dim newX, newY As Int
	Dim newMovementX, newMovementY As Int
	newMovementX=Min(Abs(XMovement),1)
	newMovementY=Min(Abs(YMovement),1)
	If currentIsStanding = False Then
		If currentDirection = "LEFT" Then		
			newX = bgLayer.ViewX
			newY = bgLayer.ViewY
			If newX-newMovementX >= 0 Then
				newX = newX-newMovementX
			End If
			If newY-newMovementY >= 0 Then
				newY = newY-newMovementY
			End If
			bgLayer.SetLayerPostion(newX, newY)			
		Else
			newX = bgLayer.ViewX
			newY = bgLayer.ViewY
			If newX+newMovementX <= bgLayer.OuterWidth - myPlay.Width Then
				newX = newX+newMovementX
			End If
			If newY+newMovementY <= bgLayer.OuterHeight - myPlay.Height Then
				newY = newY+newMovementY
			End If
			bgLayer.SetLayerPostion(newX, newY)			
		End If
	End If
	
	'////////////// END Calculation stuff
	
	'////////////// BEGIN Drawing stuff
	'ok, all the calulations are done, let's draw!
	
	' draw the background layer with all its sprites on it and advance all sprite animations and walks
	myPlay.DrawLayer("background", c)
	
	Dim a As Int
	' draw the multitouch points that are not on the gamepad, no action here but just to show the possibilities
	For a = 0 To State.touchPoints.Size - 1
		Dim tmpP As ABTouchPoint		
		tmpP = State.touchPoints.GetValueAt(a)		
		c.DrawCircle(tmpP.X, tmpP.Y, 50dip, myColors(tmpP.id), True, 1dip)		
	Next
	
	' draw the hero
	Hero.Draw(c)
	
	' draw the foreground layer with all its sprites on it and advance all sprite animations and walks
	myPlay.DrawLayer("foreground", c)
	'////////////// END Drawing stuff
	
	
	' NEEDED: very last line of the Draw event. Let ABPlay know it may process touches again!
	myPlay.DrawDone()
End Sub

I’m not there yet, but it’s a good start. It needs a lot more testing and a lot of new features.

Until next time!
Alwaysbusy

Click here to Donation if you like my work


Let’s shrink the world: A Tilt Shift Effect

Everybody has seen the cute pictures floating around showing a mini world! Some examples:

But how is it done? And can it be done fast?

Answers: Easy and Yes!

How does it work?
For a digital Tilt-Shift we use some kind of gradient blur. What I mean is a blur that is very light where the focus lens is and gets blurrier the further something is in the picture. Some ‘faster’ systems only use one blur and then put the focused part back into the picture but this creates an ugly edge between the sharp and blurred part of the picture. And we do not want this!

To get his effect we could do several blurs where we enlarge the focus box in the loop so we get some gradience. Eg. If we do it 8 times we get a gradient of 8 layers. Not bad but…we want more!

Doing a blur in languages like Realstudio and Basic4Android (or Java) is not that fast. This is the first issue we’ll have to tackle because we want to be close to doing it real-time.

Second, a lot of focusses can only be a rectangle or a circle. This has a side effect: some objects in the picture will be in focus while we do not want that because it spoils the illusion. So our focus needs to be able to be irregular. In this tutorial the example uses also a rectangle but I’ll show you how to change this to anything you want.

In some cases changing the saturation of the picture can even more give the illusion of a miniature.

So in short we want:
1. few blurs, but still have 255 gradient levels
2. the blur must be fast
3. an irregular focus
4. some kind of saturation
5. did I mention fast?

How will Basic4Android (or java) and Realbasic be able to do this?

Well, we’re gonna cheat! :D

Instead of creating a gradient blur, we are going to write one heavy blur and then resharpen it. In fact you’ll see we can do the 255 levels of sharpness with only two blurs!

In Realbasic we can take advantage of the great mask property in a picture we’ve used a lot in the past. In Basic4Android I’m going to simulate this so the code can be very similar.

For our example let’s say we want to make this:

Let me show you in a schema what the steps are we’re going to use in our Tilt-Shift (click the picture to see full size):

1. our orignal picture. Make sure it is a sharp one
2. a blurred version of (1)
3. our focus area.
4. a blurred version of (3)
5. we use our blurred (4) as a mask on our original (1). As you can see in the schema, (5) fades out to its background (the alpha channel increases), in this case white.
6. so if we draw (5) on our blurred (2) the blurred pixels will re-sharpen! Do you see the magic happening here :-)
7. the saturation is optional but may make the effect more real.

And the clever part is step 3: you can draw any shape you want here! Some examples for Realbasic:

And remember, in Basic4Android (java) it needs to be inverted:

And now for the code.
(For B4A you’ll need my ABExtDrawing library version 1.7 here)

First we’ll need a fast blur. Again we’ll cheat as we’re not doing a real gaussian blur. We are ging to exploit our mask feature again and do a diagonal blur.

We’ll need some help functions:
Resize()

Realbasic:

Function Resize(pic as picture, Percentage as integer) As Picture
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim p as Picture
  dim w,h as Integer
  dim by as Double = Percentage / 100
  
  w = pic.Width * by
  h = pic.Height * by
  
  p = NewPicture(w,h,32)
  p.Graphics.UseOldRenderer=true
  p.Graphics.DrawPicture pic,0,0,w,h, 0,0,pic.Width, pic.Height
  
  Return p
End Function

Java (B4A library):

public Bitmap Resize(Bitmap bmp, float Percentage)
{
	float by = (float) (Percentage/100.0);
	
	int w = (int) (bmp.getWidth()*by);
	int h = (int) (bmp.getHeight()*by);
	
	if (Percentage>100) {
		return Bitmap.createScaledBitmap(bmp, w, h, true);
	} else {
		return Bitmap.createScaledBitmap(bmp, w, h, false);
	}
	
}

Innerblur()
We create a mask on our picture and draw it a number of times on each other, just shifting bits to the left, top, right and bottom.

Realbasic:

Function InnerBlur(Pic as picture, Level as double) As Picture
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim p,t as Picture
  dim L,w,h as Integer
  
  w=pic.Width
  h=pic.Height
  p=NewPicture(w,h,32)
  p.Graphics.UseOldRenderer=true
  p.Graphics.DrawPicture pic,0,0
  
  t=NewPicture(w,h,32)
  t.Graphics.UseOldRenderer=true
  t.Mask.Graphics.UseOldRenderer=true
  t.Mask.Graphics.ForeColor=&c7F7F7F
  t.Mask.Graphics.FillRect 0,0,t.Width,t.Height
  
  for L=abs(Level) DownTo 1
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,-L,-L   'upper left
    
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,-L,L    'lower left
    
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,L,L     'lower right
    
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,L,-L    'upper right
  next
  
  Return p
End Function

Java (B4A library):

private Bitmap InnerBlur(Bitmap bmp, float Level) {
	int w = bmp.getWidth();
	int h = bmp.getHeight();
	
	Paint paint = new Paint();
	paint.setAlpha(0x7F);
	
	Bitmap t = Bitmap.createBitmap(w, h, Config.ARGB_8888);
	Canvas tc = new Canvas(t);
	
	Bitmap p = Bitmap.createBitmap(w, h, Config.ARGB_8888);
	Canvas pc = new Canvas(p);
	pc.drawBitmap(bmp, 0, 0, null);
	
	int Lev = (int) Level;
	
	for (int L=Lev;L>0;L--) {
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, -L, -L, null);
		
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, -L, L, null);
		
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, L, L, null);
		
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, L, -L, null);
	}
	
	t.recycle();
	return p;
}

Blur()
The actual blur function is a mix of Resizes and Innerblurs.
1. Resize to smaller
2. Innerblur to level
3. Resize back to original size
4. Innerblur with level 1 to make it smooth

Realbasic:

Function Blur(Pic as picture, Level as double, Speed as Integer) As Picture
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim p as Picture
  dim L,w,h as Integer
  
  w=pic.Width
  h=pic.Height
  p=NewPicture(w,h,32)
  p.Graphics.UseOldRenderer=true
  p.Graphics.DrawPicture pic,0,0
  
  L=Level-round(Level*.4)
  
  select case speed
  case 0
    // shrink the image, blur it
    p=Resize(p,50)
    p=InnerBlur(p, L )
    // back to normal, smooth it
    p=Resize(p, 200)
    p=InnerBlur(p, 1)
  case 1
    // shrink the image, blur it
    p=Resize(p,25)
    p=InnerBlur(p, L )
    // back to normal, smooth it
    p=Resize(p, 400)
    p=InnerBlur(p, 1)
    
  end select
  
  Return p
End Function

Java (B4A library):

/**
 * blur and image. 
 * Speed: 0 = normal, 1 = fast
 */
public Bitmap Blur(Bitmap bmp, float Level, int Speed)
{
	int L = (int) (Level - Math.round(Level*.4));
	Bitmap p = null;	 
	
	switch (Speed) {
	case 0:
		p = Resize(bmp, 50);
		p = InnerBlur(p, L);
		p = Resize(p,200);
		p = InnerBlur(p, 1);
		break;
	case 1:
		p = Resize(bmp, 25);
		p = InnerBlur(p, L);
		p = Resize(p,400);
		p = InnerBlur(p, 1);
		break;		
	}		
	return p;
}

This is, as far as I know, the fastest way to do a blur in Realbasic and Java. If someone knows of a faster one, please contact me, I’ll be very interested. :-)

Before we continue, I’m going to make a function in B4A to merge a Mask layer with a picture so we can do the tilt Shift in a way similar to Realbasic.
As mentioned in a previous article, you have to make sure your images are in a certain format on Android otherwise you can strange colors or resizes.
Here is a function that makes sure the picture is in ARGB_8888 format:

Basic4Android:

' exDraw was dimmed before as Dim ExDraw As ABExtDrawing '(version 1.70 or higher required)
Sub Convert_RGB565_To_ARGB8888(iFolder As String, iFile As String) As Bitmap	
	Dim iBmp As Bitmap
	iBmp.Initialize(iFolder, iFile)

	' only convert it if it is a RGB565
	If ExDraw.GetConfig(iBmp) = ExDraw.RGB_565 Then
		Dim w, h As Int
		
		w = iBmp.Width
		h = iBmp.Height
	
		Dim tmpBmp As Bitmap
		tmpBmp.InitializeMutable(w,h)
		Dim cnvRect As Rect
		cnvRect.Initialize(0,0,w, h)
		Dim cnvCanv As Canvas
		cnvCanv.Initialize2(tmpBmp)
		
		Dim aRect As Rect
		aRect.Initialize(0,0,w,h)
		
		cnvCanv.DrawBitmap(iBmp,Null, aRect)		
		Return tmpBmp
	Else
		Return iBmp
	End If
End Sub

And here is our merge function:

Basic4Android:

Sub MergeWithAlphaLayer(iBmp As Bitmap, iLayerBmp As Bitmap) As Bitmap
	Dim MergedBmp As Bitmap
	Dim MergedBmpCanv As Canvas
	
	Dim w, h As Int
	
	w = iBmp.Width
	h = iBmp.Height
	
	Dim aRect As Rect
	aRect.Initialize(0,0,w,h)
	
	MergedBmp.InitializeMutable(w,h)
	MergedBmpCanv.Initialize2(MergedBmp)
	
	Dim Alpha As Bitmap
	Alpha.InitializeMutable(w,h)
	
	' pic
	MergedBmpCanv.DrawBitmap(iBmp, Null, aRect)
	
	' mask
	Dim Pixels(w*h) As Int
	ExDraw.getPixels(iLayerBmp,Pixels , 0, w, 0, 0, w, h)
	Dim count As Int
	count = (w*h) - 1
	For i = 0 To count
    	    Pixels(i) = Bit.ShiftLeft(Pixels(i),8) 'move the red pixel value to the alpha channel
	Next
	ExDraw.setPixels(Alpha, Pixels, 0, w, 0, 0, w, h)
	
	Dim AlphaP As ABPaint
	AlphaP.Initialize
	AlphaP.SetAntiAlias(True)
	AlphaP.SetPorterDuffXfermode(ExDraw.PorterDuffMode_DST_IN) 'DST_IN only takes over the alpha values
	
	ExDraw.drawBitmap2(MergedBmpCanv, Alpha, 0, 0, AlphaP)
	
	ExDraw.Recycle(Alpha)
	
	Return MergedBmp
End Sub

Ok, back on track! The saturation function. I admit the one in Java has some room for improvement.

Realbasic:

Sub Saturation(Pic as picture, Ammount as integer)
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim c as color
  dim d as Double
  dim a,x,y,w,h,m() as Integer
  dim s as RGBSurface
  dim p as Picture
  dim ir,ig,ib as Integer
  
  s=pic.RGBSurface
  d=Ammount/100
  w=pic.Width-1
  h=pic.Height-1
  
  Redim m(510)
  for x=0 to 510
    m(x)=(x-255)*d
  Next
  
  for y=0 to h
    for x=0 to w
      c=s.Pixel(x,y)
      ir=c.red
      ig=c.green
      ib=c.blue
      a=( ir + ig + ib ) \ 3
      s.Pixel(x,y)=rgb( ir+m(ir-a+255) , ig+m(ig-a+255) , ib+m(ib-a+255) )
    next
  next
End Sub

Java (B4A library):

public Bitmap Saturate(Bitmap bmp,float Ammount) {
	//Initialize the ColorMatrix object  
	ColorMatrix colorMatrix = new ColorMatrix();  
	//Initialize the ColorMatrixColorFilter object  
	ColorMatrixColorFilter cmFilter = new ColorMatrixColorFilter(colorMatrix);  

	//Initialize the cmPaint  
	Paint cmPaint = new Paint();  
	//Set 'cmFilter' as the color filter of this paint  
	cmPaint.setColorFilter(cmFilter);  
	
	colorMatrix.setSaturation(Ammount/(float)100);  
	//Create a new ColorMatrixColorFilter with the recently altered colorMatrix  
	cmFilter = new ColorMatrixColorFilter(colorMatrix);  

	//Assign the ColorMatrix to the paint object again  
	cmPaint.setColorFilter(cmFilter);  

	//Draw the Bitmap into the mutable Bitmap using the canvas. Don't forget to pass the Paint as the last parameter
	Bitmap alteredBitmap = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());
	Canvas canvas = new Canvas(alteredBitmap);
	canvas.drawBitmap(bmp, 0, 0, cmPaint);
	
	return alteredBitmap;
}

The actual Tilt Shift. This is nothing more that going over the steps I’ve shown in the schema.

Realbasic:

Sub DoTiltShiftRect(bmp as Picture, BlurRadius as integer, focusX as integer, focusY as integer, focusW as integer, focusH as integer, DoSaturtion as boolean)
  mBuffer = new Picture(640,480, 32)
  tmpPic = NewPicture(640,480, 32)
  tmpMask = NewPicture(640,480, 32)
  
  mBuffer = Blur(bmp,blurRadius,0)
  
  tmpPic.Graphics.DrawPicture bmp,0,0
  
  tmpMask.Graphics.FillRect(focusX,focusY - focusH/2, focusW,focusH)
  tmpMask = Blur(tmpMask, 7,1)
  
  tmpPic.Mask.Graphics.DrawPicture tmpMask,0,0
  
  mBuffer.Graphics.DrawPicture tmpPic,0,0
  
  if DoSaturtion then
    Saturation mBuffer,80
  end if
  
  Canvas1.Refresh(false)
End Sub

Basic4Android:

Sub DoTiltShiftRect(iBmp As Bitmap, BlurRadius As Int, focusR As Rect, DoSaturation As Boolean) As Bitmap
	Dim tgtCanv As Canvas
	
	Dim w, h As Int
		
	w = iBmp.Width
	h = iBmp.Height

	Dim tgtBmp As Bitmap
	tgtBmp.InitializeMutable(w, h)
	tgtCanv.Initialize2(tgtBmp)
	
	' blur the picture
	Dim srcBlurred As Bitmap
	srcBlurred = ExDraw.Blur(iBmp, BlurRadius, 0)	
	
	' create the sharp box
	Dim Alpha As Bitmap
	Alpha.InitializeMutable(w, h)
	Dim AlphaCanv As Canvas
	AlphaCanv.Initialize2(Alpha)
	' draw a full white rect on a black background
	Dim tmpR As Rect
	tmpR.Initialize(0,0,w, h)
	AlphaCanv.DrawRect(tmpR, Colors.black, True, 0dip)
	AlphaCanv.DrawRect(focusR, Colors.white, True, 0dip)
	' blur the white box to emulate a gradient blur, may be fast and ugly
	Dim AlphaBlurred As Bitmap
	AlphaBlurred = ExDraw.Blur(Alpha, 7, 1)
	
	' merge the blurred alpha layer with the bitmap into the overlay
	Dim Overlay As Bitmap
	Overlay = MergeWithAlphaLayer(iBmp, AlphaBlurred)		
	
	' saturate the picture to make it more life like
	Overlay = ExDraw.Saturate(Overlay, 80)
		
	Dim aRect As Rect
	aRect.Initialize(0,0,w,h)
	
	' draw the blurred image
	tgtCanv.DrawBitmap(srcBlurred, aRect, aRect)
	' draw the overlay
	tgtCanv.DrawBitmap(Overlay, aRect, aRect)	
	
	' saturate
	If DoSaturation Then
		tgtBmp = ExDraw.Saturate(tgtBmp, 90)
	End If
	
	ExDraw.Recycle(srcBlurred)
	ExDraw.Recycle(Alpha)
	ExDraw.Recycle(AlphaBlurred)
	ExDraw.Recycle(Overlay)
	
	Return tgtBmp
End Sub

And that’s it! Remember I’ll only did it for a rectangle here in the tutorial, but you can use anything.

Notes about the programs:

1. you can point anywhere on the y-axe in the picture to re-position the focusbox.
2. in the Realbasic version you can change the picture in the main.open() event: CurrentPicture = beach2
3. for the compiled RB version you can use the command line:

	Syntax: TiltShift.exe /PIC=picturename.jpg;focusleft;focustop;focuswidth;focusheight;dosaturation

		focus variables = the focus box
		DoSaturation: 0 = false, 1 = true

		Example:

		TiltShift.exe /PIC=beach2.jpg;30;300;580;240;1

4. in the Basic4Android version you can change the picture in the Activity_Create() sub: srcBmp = ABBitmapTools.Convert_RGB565_To_ARGB8888(File.DirAssets, "beach2.jpg")
5. the TiltShift.apk can be found in the B4A version in the folder \Objects\.

It would be nice if you give me credit if you use this code in your own program. ;-)

The full source code can be downloaded from:
Realbasic: download here
Basic4Android: download here

And some more eye candy!

Click here to Donation if you like my work


RealBasic: Robot A.I. – Part 3 – Pathfinding with obstacles


Let’s learn Robby to react to an obstacle on its way to the target!

Just like your GPS has to recalculate the route to your destination if a road is blocked, Robby must adept and recalculate a new path to his target if a door is closed in front of him. You can close or open a doorway by rightclicking on the grey parts of the map.

When Robby starts a walk, his belief state is that all doors are open. If he encounters a closed door, he changes his belief state and recalculates an alternative route. Once the robot arrives at his destination, the belief state is reset:

Example:

Robby wants to go from point A to point B. The robot calculates its route and starts the walk (Dark blue path). Suddenly it finds a closed door in its path. Robby recalculates an alternative route to B and continues its walk (Light blue path). Pretty nifty hè! :-)

Let’s look at it at work on our familiar floor plan:

As you can see I’ve also added the possibility to set Robby at any start position within the map and you can choose between a number of sensors.
Less sensors is faster but also less accurate and the algorithm may fail!

Note:
I’ve also been asked to extend the pathfinding example for the people that do not have RealBasic so they can use their own maps. To use your own maps with the ABExplorer.exe file you can give a map at the command line:

Systax: ABExplorer.exe /M=mapfile.png where mapfile is a picture file.

The map file specs:
1. must a picture size 500×500 pixels.
2. only contain 3 colors:

black RGB(0,0,0) for a wall
white RGB(255,255,255) for empty space
grey RGB(192,192,192) for a doorway that can be opened and closed

3. doorways that can be closed must be either horizontal or vertical, not diagonal.
4. make sure the map is ‘closed’: there is no way Robby can escape from the map.

The code and program can be downloaded from here

Try it out with you own maps!

In the next part of this series we’re going to learn Robby to find his location within the map. As for now, when we put Robby somewhere on the map, we tell him in which room and at what x,y coordinates he is. But what if he has to find this information for himself?

Until next time!

Click here to Donation if you like my work


RealBasic: isolated project 3 -> Liquid physics


This is a very rudimentary physics simulation of liquid water. It cannot show al lot of particles in plain RealBasic. I suspect if you rewrite it for GDI+ or even OpenGL you could use thousands of particles.

It was based on the work of Grant Kot (who made several demos for HTML5) and jgittins/quinbd’s version for Android.

It’s Grant Kots implementation of the Material Point Method (MPM). Wiki: http://en.wikipedia.org/wiki/Material_Point_Method

Grant Kot:

For interpolation, I use the quadratic B-spline presented here: Analysis and Reduction of Quadrature Errors in the Material Point Method. Please note that there is an error in equation 17, for the cubic B-spline. The middle two equations should end with 2/3. I don’t use the cubic spline though, because I’m interested in real-time simulation and I feel that the quadratic spline is the best balance of speed and quality.

Instead of integrating the density over time (which is what most of the MPM papers do), I do a density summation every frame. Because this is not dependent on previously calculated values of density, there is no accumulated error. To minimize grid artifacts, I use the cubic interpolation method presented here: A Semi-Lagrangian CIP Fluid Solver without Dimensional Splitting.

I had to read it also several times before it made any sense. :-)

If you want to get deeper into the mechanism read:
Analysis and Reduction of Quadrature Errors in the Material Point Method: http://www.gorgeousapps.com/steffen_08_IJNME_mpm_preprint.pdf

and
A Semi-Lagrangian CIP Fluid Solver without Dimensional Splitting: http://www.gorgeousapps.com/PA-08-09-19.pdf

Anyhow, the result of it was this piece of code I translated to RealBasic:

  Dim drag As Boolean = False
  Dim mdx As Single
  Dim mdy As Single
  
  Dim i, j as Integer
  Dim cxi As Integer
  Dim cyj As Integer
  Dim cyi as Integer
  Dim n As ABNode
  
  Dim phi As Single
  Dim dx, dy As Single
  Dim x, y As Single
  
  Dim cx As Integer
  Dim cy As Integer
  
  Dim p00 As Single
  Dim x00 As Single
  Dim y00 As Single
  Dim p01 As Single
  Dim x01 As Single 
  Dim y01 As Single 
  Dim p10 As Single 
  Dim x10 As Single 
  Dim y10 As Single 
  Dim p11 As Single 
  Dim x11 As Single 
  Dim y11 As Single 
  
  Dim pdx As Single 
  Dim pdy As Single 
  Dim C20 As Single 
  Dim C02 As Single 
  Dim C30 As Single 
  Dim C03 As Single 
  Dim csum1 As Single 
  Dim csum2 As Single 
  Dim C21 As Single 
  Dim C31 As Single 
  Dim C12 As Single 
  Dim C13 As Single 
  Dim C11 As Single 
  
  Dim u As Single 
  Dim u2 As Single 
  Dim u3 As Single 
  Dim v As Single 
  Dim v2 As Single 
  Dim v3 As Single 
  Dim density As Single 
  
  Dim gx,gy as Single
  Dim mu, mv As Single 
  Dim gu, gv As Single 
  
  Dim pressure As Single 
  
  Dim fx As Single
  Dim fy As Single
  
  Dim p as ABParticle
  
  Dim vx As Single
  Dim vy As Single
  Dim weight As Single
  
  If (pressed) And (pressedprev) Then
    drag = True
    mdx = 0.25 * (mx - mxprev)
    mdy = 0.25 * (my - myprev)
  End If
  
  pressedprev = pressed
  mxprev = mx
  myprev = my
  
  For Each n  In active
    n.m = 0
    n.d = 0
    n.gx = 0
    n.gy = 0
    n.u = 0
    n.v = 0
    n.ax = 0
    n.ay = 0
    n.active = False
  Next
  Redim active(-1)
  
  For Each p  In Particles
    p.cx = p.x - 0.5
    p.cy = p.y - 0.5
    
    x = p.cx - p.x
    p.px(0) = (0.5 * x * x + 1.5 * x + 1.125)
    p.gx(0) = (x + 1.5)
    x = x + 1.0
    p.px(1) = (-x * x + 0.75)
    p.gx(1) = (-2.0 * x)
    x = x + 1.0
    p.px(2) = (0.5 * x * x - 1.5 * x + 1.125)
    p.gx(2) = (x - 1.5)
    
    y = p.cy - p.y
    p.py(0) = (0.5 * y * y + 1.5 * y + 1.125)
    p.gy(0) = (y + 1.5)
    y = y + 1.0
    p.py(1) = (-y * y + 0.75)
    p.gy(1) = (-2.0 * y)
    y = y + 1.0
    p.py(2) = (0.5 * y * y - 1.5 * y + 1.125)
    p.gy(2) = (y - 1.5)
    
    For i  = 0 To 2
      For j  = 0 To 2
        cxi = p.cx + i
        cyj = p.cy + j
        n = grid(cxi,cyj)
        If Not n.active Then
          n.active = True
          active.Append n
        End If
        phi = p.px(i) * p.py(j)
        n.m = n.m + phi * p.mat.m
        n.d = n.d + phi
        dx = p.gx(i) * p.py(j)
        dy = p.px(i) * p.gy(j)
        n.gx = n.gx + dx
        n.gy = n.gy + dy
      Next
    Next
  Next
  
  For Each p In Particles
    cx = p.x
    cy = p.y
    cxi  = cx + 1
    cyi  = cy + 1
    
    p00 =grid(cx,cy).d
    x00 =grid(cx,cy).gx
    y00 =grid(cx,cy).gy
    p01 =grid(cx,cyi).d
    x01 =grid(cx,cyi).gx
    y01 =grid(cx,cyi).gy
    p10 =grid(cxi,cy).d
    x10 =grid(cxi,cy).gx
    y10 =grid(cxi,cy).gy
    p11 =grid(cxi,cyi).d
    x11 =grid(cxi,cyi).gx
    y11 =grid(cxi,cyi).gy
    
    pdx = p10 - p00
    pdy = p01 - p00
    C20 = 3.0 * pdx - x10 - 2.0 * x00
    C02 = 3.0 * pdy - y01 - 2.0 * y00
    C30 = -2.0 * pdx + x10 + x00
    C03 = -2.0 * pdy + y01 + y00
    csum1 = p00 + y00 + C02 + C03
    csum2 = p00 + x00 + C20 + C30
    C21 = 3.0 * p11 - 2.0 * x01 - x11 - 3.0 * csum1 - C20
    C31 = -2.0 * p11 + x01 + x11 + 2.0 * csum1 - C30
    C12 = 3.0 * p11 - 2.0 * y10 - y11 - 3.0 * csum2 - C02
    C13 = -2.0 * p11 + y10 + y11 + 2.0 * csum2 - C03
    C11 = x01 - C13 - C12 - x00
    
    u = p.x - cx
    u2 = u * u
    u3 = u * u2
    v = p.y - cy
    v2 = v * v
    v3 = v * v2
    density = p00 + x00 * u + y00 * v + C20 * u2 + C02 * v2 + C30 * u3 + C03 * v3 + C21 * u2 * v + C31 * u3 * v + C12 * u * v2 + C13 * u * v3 + C11 * u * v
    
    pressure  = density - 1.0
    If pressure > 2.0 Then
      pressure = 2.0
    End If
    
    fx = 0.0
    fy = 0.0
    
    If p.x < 4.0 Then
      fx = fx + p.mat.m * (4.0 - p.x)
    ElseIf p.x > gsizeX - 5 Then
      fx = fx + p.mat.m * (gsizeX - 5 - p.x)
    End If
    If p.y < 4.0 Then
      fy = fy + p.mat.m * (4.0 - p.y)
    ElseIf p.y > gsizeY - 5 Then
      fy = fy + p.mat.m * (gsizeY - 5 - p.y)
    End If
    
    If drag Then
      vx = Abs(p.x - 0.25 * mx)
      vy = Abs(p.y - 0.25 * my)
      If (vx < 10.0) And (vy < 10.0) Then
        weight = p.mat.m * (1.0 - vx / 10.0) * (1.0 - vy / 10.0)
        fx = fx + weight * (mdx - p.u)
        fy = fy + weight * (mdy - p.v)
      End If
    End If
    
    For i  = 0 To 2
      For j  = 0 To 2
        n = grid((p.cx + i),(p.cy + j))
        phi  = p.px(i) * p.py(j)
        gx  = p.gx(i) * p.py(j)
        gy  = p.px(i) * p.gy(j)
        
        n.ax = n.ax + -(gx * pressure) + fx * phi
        n.ay = n.ay + -(gy * pressure) + fy * phi
      Next
    Next
  Next
  
  For Each n  In Active
    If n.m > 0.0 Then
      n.ax = n.ax / n.m
      n.ay = n.ay / n.m
      n.ay = n.ay + 0.03
    End If
  Next
  For Each p in Particles
    For i  = 0 To 2
      For j  = 0 To 2
        n = grid((p.cx + i),(p.cy + j))
        phi = p.px(i) * p.py(j)
        p.u = p.u + phi * n.ax
        p.v = p.v + phi * n.ay
      Next
    Next
    mu = p.mat.m * p.u
    mv = p.mat.m * p.v
    For i  = 0 To 2
      For j  = 0 To 2
        n = grid((p.cx + i),(p.cy + j))
        phi  = p.px(i) * p.py(j)
        n.u = n.u + phi * mu
        n.v = n.v + phi * mv
      Next
    Next
  Next
  For Each n  In Active
    If n.m > 0.0 Then
      n.u = n.u / n.m
      n.v = n.v / n.m
    End If
  Next
  For Each p  In Particles
    gu = 0.0
    gv = 0.0
    For i  = 0 To 2
      For j  = 0 To 2
        n = grid((p.cx + i),(p.cy + j))
        phi = p.px(i) * p.py(j)
        gu = gu + phi * n.u
        gv = gv + phi * n.v
      Next
    Next
    p.x = p.x + gu
    p.y = p.y + gv
    p.u = p.u + 1.0 * (gu - p.u)
    p.v = p.v + 1.0 * (gv - p.v)
    If p.x < 1.0 Then
      p.x = (1.0 + rnd() * 0.01)
      p.u = 0.0
    ElseIf p.x > gsizeX - 2 Then
      p.x = (gsizeX - 2 - rnd() * 0.01)
      p.u = 0.0
    End If
    If p.y < 1.0 Then
      p.y = (1.0 + rnd() * 0.01)
      p.v = 0.0
    ElseIf p.y > gsizeY - 2 Then
      p.y = (gsizeY - 2 - rnd() * 0.01)
      p.v = 0.0
    End If
  Next 

The full source code and demo can be downloaded from: http://www.gorgeousapps.com/ABLiquid.zip

Until next time!


RealBasic: isolated project 2 -> Fire!

The second one is a little ‘old school’ demo effect of moving fire. I know it’s not the most convincing version out there but it is a quick and dirty one. Maybe if you play around with the params you’ll get it better. I’m not sure who made the original code. It may even be a mix of several projects.

First we need to setup the fire color palette. There are some formulas out there to generate the colors. A typical one is this (pseudo):

HSLtoRGB is used to generate colors:
Hue goes from 0 to 85: red to yellow
Saturation is always the maximum: 255
Lightness is 0..255 for x=0..128, and 255 for x=128..255

But I was lazy and just filled a table of 256 items with the colors I would need ;-)

The next part is the main loop that animates the fire.

  'start the loop (one frame per loop)
  While Not StopFire
    'make the background black
    gBuffer.ForeColor = &c000000
    gBuffer.FillRect(0,0,w,h)
    
    'randomize the bottom row of the fire buffer
    For x = 0 To w - 1
      fire(x, h - 1) = Abs(32768 + rnd()*256) Mod 256 + 128
      if fire(x, h - 1) > 255 then
        fire(x, h - 1) = 255
      end if
    Next
    'do the fire calculations for every pixel, from top to bottom
    'I remember there are other formulas out there that have different effects. Just Google it.
    For y  = 0 To h - 2
      For x  = 0 To w - 1
        fire(x, y) = ((fire((x - 1 + w) Mod w, (y + 1) Mod h) + fire((x) Mod w, (y + 1) Mod h) + fire((x + 1) Mod w, (y + 1) Mod h) + fire((x) Mod w, (y + 2) Mod h)) * 32) / 130 + rnd()
      Next
    Next
    
    'set the drawing buffer to the fire buffer, using the palette colors
    For x  = 0 To w - 1
      For y  = 0 To h - 1
        rgbBuffer.pixel(x, y) = palette(fire(x, y))
      Next
    Next
    
    'redraw the screen
    me.Refresh(false)
    app.DoEvents
   wend

That’s it! Or as Tom Hanks would have said in Cast Away: “I have made fire!”

The code and demo can be downloaded from: http://www.gorgeousapps.com/ABFire.zip

Happy coding!


Basic4Android: Using the new 3D Camera in ABExtDrawing 1.1

I’ve added the Camera object to the ABExtDrawing library for Basic4Android. This object must not be confused with the hardware camera. There are other libraries available for that. The new version 1.1 of the library can be downloaded from http://www.basic4ppc.com.

The camera I’m talking about is a nice feature you can use to do 3D effects on the canvas without using OpenGL. The picture above is just a noral B4A canvas where we did some rotations and translations on. It simulates a scrolling list where the listitems rotate around their axe.

Additional, I shows other features of the ABExtDrawing library to do some lighting effects.

Let me show you how this is done in B4A.

We create a new type item3D. This will hold one item in the list.

Sub Process_Globals
	Type item3D (bmp As Bitmap, Top As Int, Left As Int, Width As Int, Height As Int)
End Sub

We also have to declare some variables. Note our mCamera variable which will do the 3D conversions and some constants for our lighting effects. On a Paint object we can set color filters which will affect the color values of what we draw with that Paint object. SetLightingColorFilter takes care of that. A LightingColorFilter takes two colors that are used to modify the colors that we are drawing. The first color will be multiplied with the colors we draw, while the second one will be added to the colors we draw. The multiplication will darken the color and adding will make it brighter so we can use this class to model both shadows and highlights. It would have been even better if instead of adding it would have implemented the screen blend mode, but add works OK.

To actually calculate the light we’ll use a simplified version of Phong lighting.

Sub Globals
	'These global variables will be redeclared each time the activity is created.
	'These variables can only be accessed from this module.
	Dim items As List
	Dim HalfHeight As Float
	
	Dim SCALE_DOWN_FACTOR As Float: SCALE_DOWN_FACTOR = 0.15
	Dim DEGREES_PER_SCREEN As Int: DEGREES_PER_SCREEN = 270
	
	' Ambient light intensity
    Dim AMBIENT_LIGHT As Int: AMBIENT_LIGHT = 55
    ' Diffuse light intensity
    Dim DIFFUSE_LIGHT As Int: DIFFUSE_LIGHT = 200
    ' Specular light intensity
    Dim SPECULAR_LIGHT As Float: SPECULAR_LIGHT = 70
    ' Shininess constant
    Dim SHININESS As Float: SHININESS = 200
    ' The Max intensity of the light
    Dim MAX_INTENSITY As Int: MAX_INTENSITY = 0xFF

	Dim CurrentRotation As Int
	Dim CurrentTop As Int 
	Dim MyCanvas As Canvas
	
	Dim ScreenTop As Int: ScreenTop = 1
	Dim Panel1 As Panel
	
	Dim ExDraw As ABExtDrawing
	Dim mCamera As ABCamera
	Dim mMatrix As ABMatrix
	Dim mPaint As ABPaint
	
	Dim PI As Double: PI= 3.141592653589793238462643383279502884197
	
	Dim ClearRect As Rect
	
	Dim CurrY As Int	
End Sub

In the Activity_Create sub we initialize mCamera and the other variables like mPaint which we will also need for our lighting. Also our pictures are preloaded.

Sub Activity_Create(FirstTime As Boolean)
	Activity.LoadLayout("1")
	
	MyCanvas.Initialize(Panel1)

	HalfHeight = Activity.Height / 2
	
	Dim backbmp As Bitmap
	backbmp.Initialize(File.DirAssets, "background.png")
	Dim backbmp2 As Bitmap
	backbmp2.Initialize(File.DirAssets, "background2.png")
	Dim backbmp3 As Bitmap
	backbmp3.Initialize(File.DirAssets, "background3.png")
	
	Dim conbmp As Bitmap
	conbmp.Initialize(File.DirAssets, "contact_image.png")
	Dim conbmp2 As Bitmap
	conbmp2.Initialize(File.DirAssets, "contact_image2.png")
	Dim conbmp3 As Bitmap
	conbmp3.Initialize(File.DirAssets, "contact_image3.png")

	items.Initialize
	Dim i As Int
	Dim random As Int
	For i = 0 To 19
		random = Rnd(0,3)
		If random = 0 Then
			items.Add(CreateNewItem(backbmp, conbmp, "Colleague " & i, "Name of colleague " & i, 25, i*175, Activity.Width - 50, 120))
		Else
			If random = 1 Then
				items.Add(CreateNewItem(backbmp2, conbmp2, "Friend " & i, "Name of friend " & i, 25, i*175, Activity.Width - 50, 120))
			Else
				items.Add(CreateNewItem(backbmp3, conbmp3, "Client " & i, "Name of the client " & i, 25, i*175, Activity.Width - 50, 120))
			End If
		End If
	Next
	
	CurrentRotation = -(DEGREES_PER_SCREEN * ScreenTop) / Activity.Height
    
	ClearRect.Initialize(0,0,Activity.Width, Activity.Height)
	
	mCamera.Initialize
	mMatrix.Initialize
	mPaint.Initialize
	mPaint.SetAntiAlias(True)
    mPaint.SetFilterBitmap(True)
	
	DrawMe
End Sub

The sub CreateNewItem() is used to make one list item. Default B4A canvas drawing functions are used to show how well they work together with the ABExtDrawing functions.

Sub CreateNewItem(Background As Bitmap, icon As Bitmap, Subj As String, desc As String, Left As Int, Top As Int, Width As Int, Height As Int) As item3D
	Dim item As item3D
	Dim c As Canvas
	item.Initialize
	item.bmp.InitializeMutable(Width, Height)
	
	c.Initialize2(item.bmp)
	
	' background
	Dim dstR As Rect	
	dstR.Initialize(0,0,Width,Height)
	c.DrawBitmap(Background,Null, dstR)
		
	' draw Icon
	dstR.Initialize(15,15, icon.Width, icon.Height)
	c.DrawBitmap(icon, Null, dstR)
	
	c.DrawText(Subj, 100, 30,Typeface.DEFAULT_BOLD,16, Colors.White,"LEFT") 
	c.DrawText(desc, 100, 60, Typeface.DEFAULT,16, Colors.White,"LEFT")
	
	item.Top = Top
	item.Left = Left
	item.Width = Width
	item.Height = Height
	Return item
End Sub

In the drawItem() sub all calculations are done for one item in the list. Each item will be a block that will rotate around its X-axis and look like it is rolling on the ground when the list stars to scroll. Each block will be as wide as the item normally is and the depth will be the same as the height. We’ll use the same bitmap for all the sides.

So what do we need to do to achieve this effect? In order to draw the blocks we need to draw the bitmap two times (since we will almost always see two sides of the block). We also need to have some kind of rotation variable to keep track of the main rotation. Since the blocks should rotate when the user scrolls the list and the blocks should have the same rotation (so that they all face up at the same time, see further).

Sub DrawItem(item As item3D)
	Dim CenterX As Float
	Dim CenterY As Float

	' get centerX AND centerY
	CenterX = item.Width / 2
	CenterY = item.Height / 2

    ' get scale
    Dim distFromCenter As Float 
	distFromCenter = (item.Top + CenterY - HalfHeight) / HalfHeight
	Dim scale As Float
	scale = (1 - SCALE_DOWN_FACTOR * (1 - Cos(distFromCenter)))

    ' get rotation
    Dim RotationX As Float
	RotationX = CurrentRotation - 20 * distFromCenter
    RotationX = RotationX Mod 90
    If (RotationX < 0) Then
        RotationX = RotationX + 90
    End If	

    ' draw it
    If (RotationX < 45) Then
		drawFace(item, CenterX, CenterY, scale, RotationX - 90)
        drawFace(item, CenterX, CenterY, scale, RotationX)
		       
    Else
        drawFace(item, CenterX, CenterY, scale, RotationX)
        drawFace(item, CenterX, CenterY, scale, RotationX - 90)
    End If
End Sub

Finally, DrawFace is called and this is where the magic happens. Worth noting is that the code that will draw one face of the block is the same, it just depends on the rotation, so it’s extracted to a method. To draw a complete block we then simply draw two faces 90 degrees apart at the same place.

To draw a face we first translate the camera so that the face will be drawn closer to us. Then we rotate it and after that we translate it back so we don’t scale it. Keep in mind that the calls to the camera, just like the rotate, translate and scale methods on Canvas, needs to be written in reversed order, so to speak. In the code below, it is the last line that translates the face towards us, then we rotate it, and finally, with the first line, we translate it back.

mCamera.translate(0, 0, centerY);
mCamera.rotateX(rotation);
mCamera.translate(0, 0, -centerY);

The rest of drawFace is not that hard. It gets the matrix from the camera, pre and post translates the matrix and then draws the bitmap with the matrix.

This code will draw each item as if placed in the origin in 3D space and then we move the items to the correct place on the screen using pre and post translate on the matrix. This moves what we draw in 2D space without changing the perspective. We could apply the translation in X and Y on the camera instead, then the translation would be in 3D space and it would affect the perspective. We’re not doing that here because I want the appearance of a larger field of view than the fixed field of view of the camera. Instead, we fake it by slightly rotating and scaling the items depending on the distance from center of the screen.

We calculate the light and create a LightingColorFilter that we can set to our Paint object.

Sub drawFace(item As item3D, CenterX As Float, CenterY As Float, scale As Float, RotationX As Float) 
	' save the camera state
    mCamera.save

    ' translate AND Then rotate the camera
    mCamera.translate(0, 0, CenterY)
    mCamera.rotateX(RotationX)
	mCamera.translate(0, 0, -CenterY)

    
    ' get the matrix from the camera AND Then restore the camera
    mCamera.getMatrix(mMatrix)
    mCamera.restore()

    ' translate AND scale the matrix
    mMatrix.preTranslate(-CenterX, -CenterY)
    mMatrix.postScale(scale, scale)
    mMatrix.postTranslate(item.left + CenterX, item.top + CenterY)

    ' set the light
	Dim cosRotation As Double
	cosRotation = Cos(PI * RotationX / 180)
    Dim intensity As Int
	intensity = AMBIENT_LIGHT + (DIFFUSE_LIGHT * cosRotation)
    Dim highlightIntensity As Int
	highlightIntensity = (SPECULAR_LIGHT * Power(cosRotation,SHININESS))	
    If (intensity > MAX_INTENSITY) Then
        intensity = MAX_INTENSITY
    End If
    If (highlightIntensity > MAX_INTENSITY) Then
        highlightIntensity = MAX_INTENSITY
    End If
    Dim light As Int
	light = Colors.rgb(intensity, intensity, intensity)
    Dim highlight As Int
	highlight = Colors.rgb(highlightIntensity, highlightIntensity, highlightIntensity)
    mPaint.SetLightingColorFilter(light, highlight)   
    
    ' draw the Bitmap
    ExDraw.drawBitmap4(MyCanvas, item.bmp,  mMatrix, mPaint)
End Sub

The DrawMe() sub is the overall function to draw all the items.

Sub DrawMe()
	Dim i As Int
	ExDraw.save2(MyCanvas, ExDraw.MATRIX_SAVE_FLAG)
	MyCanvas.DrawRect(ClearRect, Colors.Black, True, 1dip)
	'MyCanvas.DrawBitmap(FormBack, Null, formR)
	For i = 0 To items.Size - 1
		DrawItem(items.Get(i))
	Next
	ExDraw.restore(MyCanvas)
	
	Panel1.Invalidate
End Sub

And in the Panel1_Touch sub we’ll animate our list. Note that it is here that we make sure all boxes face the same. This is done by the lines:

ScreenTop = ScreenTop + DeltaY
CurrentRotation = -(DEGREES_PER_SCREEN * ScreenTop) / Activity.Height

Doing like this will make the blocks rotate DEGREES_PER_SCREEN degrees when the user scrolls the list an entire screen no matter the pixel-height of the screen.

Here is the full sub:

Sub Panel1_Touch (Action As Int, X As Float, Y As Float) As Boolean 'Return True to consume the event
	Dim DeltaY As Int
	Select Action
        Case Activity.ACTION_DOWN
			CurrY = Y
            'Log("down")
        Case Activity.ACTION_MOVE
			DeltaY = Y - CurrY
			CurrY = Y
			Dim i As Int
			Dim it As item3D
			For i = 0 To items.Size - 1
				it = items.Get(i)
				it.Top = it.Top + DeltaY
				items.Set(i, it)
			Next
			
			ScreenTop = ScreenTop + DeltaY
			CurrentRotation = -(DEGREES_PER_SCREEN * ScreenTop) / Activity.Height
						
			DrawMe
            'Log("move")
        Case Activity.ACTION_UP
            'Log("up")
    End Select
    Return True
End Sub

So this concludes this tutorial. You can download the project from http://www.gorgeousapps.com/AB3DCamera.zip

Note that at the first run, it may not work very smooth on some devices. But after some time it goes very well.

Have fun programming and until next time!

Click here to Donation if you like my work


Realbasic: Canvas Tutorial Lesson 6

Animation! Now that we have all our Comics, CDs and DVDs on the screen, it looks a little bit chaotic. What if we could make it so that when the user right clicks on e.g. a CD, all CDs group together in one fluent motion? Same for the Comics and DVDs?

Within our framework, this is possible and we’re going to build it now!

This is how we are going to do it. We will give every ABElement a WalkPath. A Walkpath is a path our Element has to follow from its original position to its destination. Every ‘Walk’ should take the same time, no matter the starting position of the ABElement. Let me show you in this illustration:

Note: To explain it each walkpath is only four steps (pink segments). We will of course program it with a lot more steps.

As you can see WalkPath A is much longer than WalkPath C. However, as it needs to take exactly the same time for both Elements to reach their destination, the ‘steps’ to take are different. So for every ABElement from type CD we will calculate its own step distance.

Don’t worry, It will get clearer once we dive into the code. You can skip to the video at the end of this article if you want to see it working.

We need a new class: ABVector. This class will hold a vector x,y to mark one step in the WalkPath.

X As Integer
Y as Integer

Sub Constructor(X as integer, Y as integer)
  me.X = X
  me.Y = Y
End Sub

In our ABElement, we’ll define our WalkPath, a table of ABVectors. We also need a function to add a Walk step and one to reset the complete walk. We make sure we clean up when our object is destroyed:

WalkPath(-1) As ABVector

Sub AddWalkPosition(X as integer, Y as integer)
  Dim tmpVector as new ABVector(X,Y)
  WalkPath.Append tmpVector
End Sub

Sub ResetWalkPath()
  redim WalkPath(-1)
End Sub

Sub CleanUp()
  gBuffer = nil
  pBuffer = nil
  Image = nil
  MyABCanvas = nil
  MyGlow = nil
  MyMask = nil
  ResetWalkPath ' new
End Sub

In the MouseDown of ABCanvas we’re going to catch our right mouse button. We bring the Element to the front and start the grouping animation in the GroupMyType() function:

Sub MouseDown(X as integer, Y as integer) as Boolean
  Dim tmpElem as ABElement
  tmpElem = ElementHit(X,Y)
  if tmpElem <> nil then
    if IsContextualClick then 'new
      ' right mouse button
      
      ' bring the found ABelement to the front
      BringToFront tmpElem
      
      ' group them together
      GroupMyType tmpElem.type, X, Y, 500
      
      return false
    else
      ' left mouse button
      
      mLastX = X
      mLastY = Y
      
      BringToFront tmpElem
      
      RefreshElement tmpElem
      
      DragElem = tmpElem
      Return true
    end if
  end if
  
  return MouseDown(X,Y)
End Sub

Now for the hard bit :-)

I’ve added some comments to the code to explain what actually happens, but I’ll give you a little walk-through.

To get a smooth animation, we want to have 25 frames per second. Calculating the path while the animation runs will take time so we precalculate this path first. We calculate the number of Elements that are of the Type we clicked. In a real app, you should save this value and change it when elements are added or removed.

for a = UBound(MyElements) to 0 step -1
    tmpElem = MyElements(a)
    if tmpElem.Type = Type then
      Total = Total + 1
    end if
next

Next, we calculate the radius of the circle where we will group the elements. The size of the circle will depend on the number of elements. (fewer elements are grouped closes together).

radius = (Total - 1) * 15 + 1

We want our elements to be spread evenly around the center so we calculate the angle between two elements on our circle:

Dim PlusAngle as integer
PlusAngle = 360 / (Total + 1) ' +1 makes it more irrigular in some cases

Also, we calculate the number of steps needed for the given time. This way, all elements arrive at their destination at the same time. Note that the time is the time of the actual animation. The time to do the pre-calculation is not included.

Dim Frames as integer
Frames = FramesPerSec / 1000 * MilliSecs

Now, we are ready to calculate the Walkpath for each Element:

for a = UBound(MyElements) to 0 step -1
    tmpElem = MyElements(a)
    if tmpElem.Type = Type then
      ' reset our walk
      tmpElem.ResetWalkPath
      
      ' calculate our end point
      EndX = CenterX + radius * cos(angle*PI/180)
      EndY = CenterY + radius * sin(angle*PI/180)
      
      ' add our steps
      for b = 1 to Frames
        newX = tmpElem.x + (EndX - tmpElem.x)/Frames*b
        newY = tmpElem.y + (EndY - tmpElem.y)/Frames*b
        tmpElem.AddWalkPosition(newX, newY)
      next
      
      Angle = Angle + PlusAngle
      
      TempListOfElems.Append tmpElem
      
    end if
next

We can now draw all the frames in our animation. If we are working on a slower computer, we’ll skip frames:
c = Frames * ((NowTime-BeginTime)/EndTime)
The rest is almost the same as we’ve done in a previous tutorial, except now we get our position from our WalkPath. To make it as fast as possible, we don’t redraw every frame, but remember what needs to be redrawn with the Totalfull… variables.

While c < Frames 
    NowTime = Microseconds 
    
    ' get my frame
    c = Frames * ((NowTime-BeginTime)/EndTime) 
    
    if c <> oldc and c < Frames then
      ' initialize
      TotalfullLeft = me.Width
      TotalfullTop = me.Height
      TotalfullRight = 0
      TotalfullBottom = 0
      
      for a = 0 to UBound(TempListOfElems)
        tmpElem = TempListOfElems(a)
        
        ' we remember the old position
        oldX = tmpElem.X
        oldY = tmpElem.Y
        
        ' get the new position from the walkpath
        newX = tmpElem.WalkPath(c).x
        newY = tmpElem.WalkPath(c).y
        
        ' Find the union between the old and the new position
        fullLeft = Min(oldX,newX) - tmpElem.w \ 2 - Extra
        fullTop = Min(oldY,newY) - tmpElem.h \ 2 - Extra
        fullRight = Max(oldX,newX) - tmpElem.w \ 2 + tmpElem.w  + Extra * 2
        fullBottom = Max(oldY,newY) - tmpElem.h \ 2 + tmpElem.h  + Extra * 2
        
        TotalfullLeft = min(TotalfullLeft, fullLeft)
        Totalfulltop = min(TotalfullTop, fullTop)
        TotalfullRight = max(TotalfullRight, fullRight)
        TotalfullBottom = max(TotalfullBottom, fullBottom)
        
        ' set our new position
        tmpElem.X = newX
        tmpElem.Y = newY
      next
      
      ' redraw only what is needed
      DrawMe TotalfullLeft, TotalfullTop, TotalfullRight - TotalFullLeft, totalfullBottom - TotalfullTop
      oldc = c
    end if
Wend

A lot happended here so take your time to take it all in.

Here is the full listing of this function:

Sub GroupMyType(Type as integer, CenterX as integer, CenterY as integer, MilliSecs as integer)
  ' make the mouse cursor invisible
  self.MouseCursor = System.Cursors.InvisibleCursor
  
  Const PI=3.14159
  
  Dim tmpElem as ABElement
  Dim a as integer
  Dim b as integer
  
  Dim Angle as integer
  Dim newX as integer
  Dim newY as integer
  Dim Radius as integer
  Dim slope as integer
  
  Dim EndX as integer
  Dim EndY as integer
  
  Dim Total as integer
  Dim FramesPerSec as integer
  ' if your pc cannot handle 25 frames per second, lower this
  FramesPerSec = 25
  
  Dim TempListOfElems(-1) as ABElement
  
  ' some pre-calculations, in a real app you could save this value in a variable
  for a = UBound(MyElements) to 0 step -1
    tmpElem = MyElements(a)
    if tmpElem.Type = Type then
      Total = Total + 1
    end if
  next
  
  ' we make the radius dependant of the number of element in the circle. The more, the bigger
  radius = (Total - 1) * 15 + 1
  
  Dim PlusAngle as integer
  PlusAngle = 360 / (Total + 1) ' +1 makes it more irrigular in some cases
  
  ' Calcualte the number of steps needed for the given time so all elements arrive at their destination at the same time
  Dim Frames as integer
  Frames = FramesPerSec / 1000 * MilliSecs 
  
  ' build the walkpaths for all elements of this type
  for a = UBound(MyElements) to 0 step -1
    tmpElem = MyElements(a)
    if tmpElem.Type = Type then
      ' reset our walk
      tmpElem.ResetWalkPath
      
      ' calculate our end point
      EndX = CenterX + radius * cos(angle*PI/180)
      EndY = CenterY + radius * sin(angle*PI/180)
      
      ' add our steps
      for b = 1 to Frames
        newX = tmpElem.x + (EndX - tmpElem.x)/Frames*b
        newY = tmpElem.y + (EndY - tmpElem.y)/Frames*b
        tmpElem.AddWalkPosition(newX, newY)
      next
      
      Angle = Angle + PlusAngle
      
      TempListOfElems.Append tmpElem
      
    end if
  next
  
  Dim oldX, oldY, fullLeft, fullTop, fullRight, fullBottom as integer
  Dim TotalfullLeft, TotalfullTop, TotalfullRight, TotalfullBottom as integer
  Dim Extra as integer
    
  ' we add some extra pixels around the object when we redraw because otherwise we sometimes get a 'trail'
  Extra = 5
  
  ' start the animation
  Dim NowTime as Double
  Dim BeginTime as Double
  Dim EndTime as Double
  
  Dim c as integer
  Dim oldc as integer
  
  BeginTime = Microseconds
  EndTime = MilliSecs * 1000
  
  While c < Frames 
    NowTime = Microseconds 
    
    ' get my frame
    c = Frames * ((NowTime-BeginTime)/EndTime) 
    
    if c <> oldc and c < Frames then
      ' initialize
      TotalfullLeft = me.Width
      TotalfullTop = me.Height
      TotalfullRight = 0
      TotalfullBottom = 0
      
      for a = 0 to UBound(TempListOfElems)
        tmpElem = TempListOfElems(a)
        
        ' we remember the old position
        oldX = tmpElem.X
        oldY = tmpElem.Y
        
        ' get the new position
        newX = tmpElem.WalkPath(c).x
        newY = tmpElem.WalkPath(c).y
        
        ' Find the union between the old and the new position
        fullLeft = Min(oldX,newX) - tmpElem.w \ 2 - Extra
        fullTop = Min(oldY,newY) - tmpElem.h \ 2 - Extra
        fullRight = Max(oldX,newX) - tmpElem.w \ 2 + tmpElem.w  + Extra * 2
        fullBottom = Max(oldY,newY) - tmpElem.h \ 2 + tmpElem.h  + Extra * 2
        
        TotalfullLeft = min(TotalfullLeft, fullLeft)
        Totalfulltop = min(TotalfullTop, fullTop)
        TotalfullRight = max(TotalfullRight, fullRight)
        TotalfullBottom = max(TotalfullBottom, fullBottom)
        
        ' set our new position
        tmpElem.X = newX
        tmpElem.Y = newY
      next
      
      ' redraw only what is needed
      DrawMe TotalfullLeft, TotalfullTop, TotalfullRight - TotalFullLeft, totalfullBottom - TotalfullTop
      oldc = c
    end if
  Wend
  
  ' show the cursor
  self.MouseCursor = System.Cursors.StandardPointer
End Sub

And we’re done. Let’s run our program and enjoy our animated canvas!

The source code for this tutorial: http://www.gorgeousapps.com/Tut6.zip

In our next lesson, we’ll add a menu to each Element so we can play the CD/DVD or open the Comic.

Until next Time!

Click here to Donation if you like my work


Follow

Get every new post delivered to your Inbox.

Join 84 other followers