Tilemap Scrolling (2D in 3D) - who the what now?

Blitz3D Forums/Blitz3D Beginners Area/Tilemap Scrolling (2D in 3D) - who the what now?

I'm about to head off to bed, but this is something that's been floating about my head for a while so I thought I'd ask and maybe the answer will be here when I awake:

I've read a lot of stuff in my time about scrolling 2D tilemaps nicely and efficiently (only redrawing tiles that change, stuff about the edge tiles etc). That's all well and good, but the bit that's got me bejiggered is how to translate that to a 2D-in-3D system (eg SpriteCandy), in a general sense.

<begin ignorance>
Is it a case of doing all the same stuff, but then pasting the resultant tilemap onto a quad or something? And if so, then what? Scroll that quad around a bit?
<end>

I'm not looking for code, or any preboxed solutions - just a general conceptual idea of how this sorta thing works. I've Googled (granted, not like my life depends on it), but it doesn't seem to be something that people discuss much. Pseudocode me if you like.

It's just niggling me that I don't know where to begin with the idea behind it. Please, deniggle if you can.

Just do your standard 2D stuff, then do a renderworld before the flip making sure the cameraclsmode is off.

This way you'll get the 3D over the top of the 2D.

I did this with the Alien Breed community project, had a scrolling 2D tile map with 3D models and sprites running around on it.

Ah... no. Thankyou anyway, but...

I'm not doing a mix of 2D and 3D per se. I'm doing a 2D thing (think....err... Paradroid), but instead of doing it with your standard DrawImage/DrawBlock or whatever, I want to be rendering it all with a 2D-in-3D single-surface sorta system (again, like SpriteCandy. In fact, exactly like SpriteCandy) so it's all being done using the black magic of 3D.

2D-in-3D. Not "2D and 3D".

Ah I see where you're coming from now...

Well personally to do that I'd create a mesh of quads (or however many you want on screen at one time), make each quad a different surface then paint the surfaces with the correct tile.

This mesh will be slightly bigger than the screen so you've move it until a tile was completely off screen, jump it back on screen and repaint the surfaces... No idea how fast this would be though.

Couldn't you just have your quads on a single surface and plonk all your tiles to a single texture?

That would work well actually, then just adjust the UVs of the quads.

That`s how Sprite Candy does it I think. Keeping the amount of surfaces down reduces slowdown too if I remember correctly.

Jason.

Okay, now we're getting somewhere. :)

Let's get more specific: I'm looking to adapt the following library of functions (that I'm rather fond of) to work with Sprite Candy (that I'm also rather fond of), and from studying it, I'm sure really the only functions I need to deal with (mainly) are the Simplscroller_Draw function and the SimpleScroller_DrawRegions one.

From what I understand, the various functions use the dark arts to split the screen up into regions and determine what tiles need drawing to which region (based on what tiles are animating etc). It draws (using good ole DrawImage) the appropriate tiles to the image in a particular buffer, then uses DrawImageRect to draw the four sections of that buffer to the BackBuffer() before Flipping and livin' la vida loca.

Now, being the 3D mega-ultra-n00b that I am, I'm having trouble conceptually working out how to exchange out the DrawImage and DrawImageRect commands to replace them with Sprite Candy equivalents. If you know Sprite Candy, then sweet (feel free to tell me!), but otherwise I'm just looking for some guidance on the concept so I can adapt it to Sprite Candy myself.

See? :)

Here's the library I made reference to, if it adds detail to what I've said:
; ******************************************************************************
; SimpleScroller
; by TheKLF (TheKLF on BlitzCoder.com)
; ******************************************************************************
; This file exposes functionality to maintain a 8-way scrolling tilemap using
; any tile sizes (multiple of 16) and any window sizes (multiple of 16).
; The special thing about this scroller is that it uses a seperate 'buffer'
; screen to draw the content on, that wraps around. This way, only the border
; regions will need to be drawn when scrolling, which is FAST! Ofcourse there's
; a performance hit when the total area scrolled is bigger than half the screen
; because every pixel is written twice then (first to the 'buffer', then the
; 'buffer' to the backbuffer itself). But with most games, this should not
; cause problems.
; ******************************************************************************
; Versions:
; 0.0	2003-01-23 - First version
; 0.1   2003-01-25 - More optimized version with friendly functions added
; ******************************************************************************
; Credits:
; Graham N Goring for his Scroller 2002 source code. It told me the tricks :)
; ******************************************************************************

Type SimpleScrollerRegion
	Field simplescroller.SimpleScroller
	Field screenx		;The pixel position and size on screen of the region
	Field screeny
	Field screenw
	Field screenh
	Field mapx			;The absolute pixel position in the map where the region starts
	Field mapy
End Type

Type SimpleScroller
	;private fields
	Field buffer				;The bitmap the buffered scroll graphics are drawn on
	Field dividerx			;position of splitpoint on buffer (buffer is wrapped around by this)
	Field dividery
	Field tilegraphics	;The bitmap that contains the tile grpahics
	Field mapbank				;The bank that will contain the tile numbers
	Field tilesizebits	;The number of bits to shift for the tilesize
	;public fields
	Field viewportx			;The position of the viewport that will display the scrolled area
	Field viewporty
	Field viewportw
	Field viewporth
	Field tilesize			;The size of the tiles
	Field offsetx				;The position of the topleft pixel of the viewport in the map
	Field offsety
	Field mapw					;The width of the map in number of tiles
	Field maph
End Type

;Intialize SimpleScroller
Function SimpleScroller_Create(ss.SimpleScroller, viewportx, viewporty, viewportw, viewporth, tilesize, tilegraphics, mapw, maph)
	If (tilesize Mod 4 <> 0) Or (tilesize / (tilesize Shr 1) <> 2) Then RuntimeError "SimpleScroller: tilesize should be 8, 16, 32 or 64"
	If (viewportw Mod tilesize <> 0) Or (viewporth Mod tilesize <> 0) Then RuntimeError "SimpleScroller: viewportsize should be exact multiple of tilesize"
	ss\viewportx = viewportx
	ss\viewporty = viewporty
	ss\viewportw = viewportw
	ss\viewporth = viewporth
	ss\tilesize = tilesize
	ss\tilesizebits = -1
	;determine the number of bits to shift for this tilesize (for optimization later)
	While tilesize > 0
		tilesize = tilesize Shr 1
		ss\tilesizebits = ss\tilesizebits + 1
	Wend	
	ss\tilegraphics = tilegraphics
	ss\offsetx = 0
	ss\offsety = 0
	ss\dividerx = 0
	ss\dividery = 0
	ss\buffer = CreateImage(viewportw, viewporth)
	ss\mapw = mapw
	ss\maph = maph
	ss\mapbank = CreateBank(mapw * maph Shl 2)
End Function

;Free SimpleScroller
Function SimpleScroller_Delete(ss.SimpleScroller)
	If (ss\buffer) Then FreeImage(ss\buffer)
	If (ss\mapbank) Then FreeBank(ss\mapbank)
	Delete ss
End Function

;Shortcut functions to get and set a tile from the map
Function GetTile(ss.SimpleScroller, x, y)
	Return PeekInt(ss\mapbank, (y * ss\mapw + x) Shl 2)
End Function

Function SetTile(ss.SimpleScroller, x, y, tilenr)
	PokeInt ss\mapbank, (y * ss\mapw + x) Shl 2, tilenr
End Function

;Invalidate a tile in the map
Function SimpleScroller_InvalidateTile(ss.SimpleScroller, tilex, tiley)
	SimpleScroller_InvalidateRegion(ss, tilex Shl ss\tilesizebits, tiley Shl ss\tilesizebits, ss\tilesize, ss\tilesize)
End Function

;Invalidate a rectangle of tiles in the map
Function SimpleScroller_InvalidateTiles(ss.SimpleScroller, tilex, tiley, tilew, tileh)
	SimpleScroller_InvalidateRegion(ss, tilex Shl ss\tilesizebits, tiley Shl ss\tilesizebits, tilew Shl ss\tilesizebits, tileh Shl ss\tilesizebits)
End Function

;Invalidate a region in map pixel coordinates
Function SimpleScroller_InvalidateRegion(ss.SimpleScroller, x, y, w, h)
	;If the box starts off the left of the screen we need to chop that edge off of it.
	If (x < ss\offsetx) Then 
		w = w - (ss\offsetx - x)
		x = ss\offsetx
	EndIf

	;If the box starts off the top of the screen, chop!
	If (y < ss\offsety) Then 
		h = h - (ss\offsety - y)
		y = ss\offsety
	EndIf

	;If it trails off the right of the screen...
	If (x + w >= ss\viewportw + ss\offsetx) Then 
		w = (ss\viewportw + ss\offsetx) - x
	EndIf
	
	;If it trails off the bottom of the screen...
	If (y + h >= ss\viewporth + ss\offsety) 
		h = (ss\viewporth + ss\offsety) - y
	EndIf
	
	;If the box is actually anywhere on the screen
	If (w>0) And (h>0) And (x < ss\viewportw + ss\offsetx) And (y < ss\viewporth + ss\offsety) 

		r.SimpleScrollerRegion = New SimpleScrollerRegion
	
		r\simplescroller = ss
		r\mapx = x
		r\mapy = y
		r\screenx = (x - ss\offsetx) + ss\dividerx
		r\screeny = (y - ss\offsety) + ss\dividery
		r\screenw = w
		r\screenh = h
	
	EndIf

End Function

;Split regions into buffer-compatible regions where they overlap the limits
Function SimpleScroller_SplitRegions(ss.SimpleScroller) 

	Repeat
	
		flag=0

		For r.SimpleScrollerRegion = Each SimpleScrollerRegion
		
			If r\simplescroller = ss Then 
	
; --- code below removed because regions outside screen should be ignored ---
;				If ( (r\screenx < 0) And (r\screenx + r\screenw-1 < 0) ) Or ( (r\screenx > ss\viewportw-1) And (r\screenx + r\screenw-1 > ss\viewportw-1) )
;					r\screenx = (r\screenx + ss\viewportw) Mod ss\viewportw
;					flag=1
;				EndIf
;				If ( (r\screeny < 0) And (r\screeny + r\screenh-1 < 0) ) Or ( (r\screeny > ss\viewporth-1) And (r\screeny + r\screenh-1 > ss\viewporth-1) )
;					r\screeny = (r\screeny + ss\viewporth) Mod ss\viewporth
;					flag=1
;				EndIf

				If (r\screenx < 0) ; box starts off the left edge of screen
					r\screenx = r\screenx + ss\viewportw ; bumps it forward so the next line catches it.
				EndIf
		
				If (r\screenx + r\screenw > ss\viewportw) ; box goes off right edge of screen
					rr.SimpleScrollerRegion = New SimpleScrollerRegion
					rr\simplescroller = ss
					rr\screenx = 0
					rr\screeny = r\screeny
					rr\screenw = (r\screenx + r\screenw) - ss\viewportw
					rr\screenh = r\screenh
					r\screenw = r\screenw - rr\screenw
					rr\mapy = r\mapy
					rr\mapx = r\mapx + r\screenw
					flag = 1
				EndIf
		
				If (r\screeny < 0) ; box starts off the top edge of screen
					r\screeny = r\screeny + ss\viewporth ; bumps it forward so the next line catches it.
				EndIf				
		
				If (r\screeny + r\screenh > ss\viewporth) ; box goes off bottom edge of screen
					rr.SimpleScrollerRegion = New SimpleScrollerRegion
					rr\screeny = 0
					rr\simplescroller = ss
					rr\screenx = r\screenx
					rr\screenh = (r\screeny + r\screenh) - ss\viewporth
					rr\screenw = r\screenw
					r\screenh = r\screenh - rr\screenh
					rr\mapx = r\mapx
					rr\mapy = r\mapy + r\screenh
					flag = 1
				EndIf
			
			EndIf
		
		Next
	
	Until (flag=0)
		
End Function

;Fill present regions on Buffer with tile graphics
Function SimpleScroller_DrawRegions(ss.SimpleScroller)
	SetBuffer ImageBuffer(ss\buffer)
	For r.SimpleScrollerRegion = Each SimpleScrollerRegion
		If r\simplescroller = ss Then 
			Viewport r\screenx, r\screeny, r\screenw, r\screenh
			Cls
			For xx = (r\screenx Shr ss\tilesizebits) To ((r\screenx + r\screenw-1) Shr ss\tilesizebits)	
				For yy = (r\screeny Shr ss\tilesizebits) To ((r\screeny + r\screenh-1) Shr ss\tilesizebits)
					tx = (r\mapx Shr ss\tilesizebits) + (xx - (r\screenx Shr ss\tilesizebits))
					ty = (r\mapy Shr ss\tilesizebits) + (yy - (r\screeny Shr ss\tilesizebits))
					DrawImage ss\tilegraphics, xx Shl ss\tilesizebits, yy Shl ss\tilesizebits, PeekInt(ss\mapbank, (ty * ss\mapw + tx) Shl 2)
				Next
			Next
			Delete r
		EndIf
	Next
	SetBuffer BackBuffer()
End Function

;Draw buffer to current buffer
Function SimpleScroller_Draw(ss.SimpleScroller)
	SimpleScroller_SplitRegions(ss)
	SimpleScroller_DrawRegions(ss)
	;Bottom-right chunk of the screen
	DrawImageRect ss\buffer, ss\viewportx, ss\viewporty, ss\dividerx, ss\dividery, ss\viewportw - ss\dividerx, ss\viewporth - ss\dividery 
	;Top-left chunk of the screen	
	DrawImageRect ss\buffer, ss\viewportx + ss\viewportw - ss\dividerx, ss\viewporty + ss\viewporth - ss\dividery, 0,0, ss\dividerx, ss\dividery 
	;Bottom-left chunk of the screen
	DrawImageRect ss\buffer, ss\viewportx, ss\viewporty + ss\viewporth - ss\dividery, ss\dividerx, 0, ss\viewportw - ss\dividerx, ss\dividery
	;Top-right chunk of the screen
	DrawImageRect ss\buffer, ss\viewportx + ss\viewportw - ss\dividerx, ss\viewporty, 0, ss\dividery, ss\dividerx, ss\viewporth - ss\dividery
End Function

;Scroll the SimpleScroller by a certain amount of pixels in x and y
Function SimpleScroller_Scroll(ss.SimpleScroller, deltax, deltay)
	If (deltax > 0)
		r.SimpleScrollerRegion = New SimpleScrollerRegion
		r\simplescroller = ss
		r\screenx = ss\dividerx
		r\screeny = ss\dividery + deltay
		r\screenw = deltax
		r\screenh = ss\viewporth
		r\mapx = ss\offsetx + ss\viewportw
		r\mapy = ss\offsety + deltay
	ElseIf (deltax < 0)
		r.SimpleScrollerRegion = New SimpleScrollerRegion
		r\simplescroller = ss
		r\screenx = ss\dividerx + deltax
		r\screeny = ss\dividery + deltay
		r\screenw = -deltax
		r\screenh = ss\viewporth
		r\mapx = ss\offsetx + deltax
		r\mapy = ss\offsety + deltay
	EndIf	

	If (deltay>0)	
		r.SimpleScrollerRegion = New SimpleScrollerRegion
		r\simplescroller = ss
		r\screenx = ss\dividerx + deltax
		r\screeny = ss\dividery
		r\screenw = ss\viewportw
		r\screenh = deltay
		r\mapx = ss\offsetx + deltax
		r\mapy = ss\offsety + ss\viewporth
	ElseIf (deltay<0)
		r.SimpleScrollerRegion = New SimpleScrollerRegion
		r\simplescroller = ss
		r\screenx = ss\dividerx + deltax
		r\screeny = ss\dividery + deltay
		r\screenw = ss\viewportw
		r\screenh = -deltay
		r\mapx = ss\offsetx + deltax
		r\mapy = ss\offsety + deltay
	EndIf

	ss\offsetx = ss\offsetx + deltax
	ss\offsety = ss\offsety + deltay
	ss\dividerx = (ss\dividerx + deltax + ss\viewportw) Mod ss\viewportw
	ss\dividery = (ss\dividery + deltay + ss\viewporth) Mod ss\viewporth
End Function


btw, it's entirely possible in my current drug-addled state (wisdom tooth extraction) that I'm just not looking at this closely enough and if I did it's rather straightforward.

If that's the case, feel free to call me on it. :)