FlxTilemap.LoadMap fixed #58

FlxTilemap.LoadMap fixed #58

Strict

Import mojo
Import reflection

Import flxextern
Import flxobject
Import flxrect
Import flxg
Import flxcamera
Import flxgroup
Import flxbasic
Import flxpath

Import system.flxtile
Import system.flxtilemapbuffer

Import "data/autotiles_flx.png"
Import "data/autotiles_flx.png"

Class FlxTilemap Extends FlxObject

	Global ClassObject:Object

	Const AUTOTILES:String =  "autotiles" + FlxG.DATA_SUFFIX

	Const AUTOTILES_ALT:String =  "autotiles_alt" + FlxG.DATA_SUFFIX

	Const OFF:Int = 0

	Const AUTO:Int = 1

	Const ALT:Int = 2

	Field auto:Int

	Field widthInTiles:Int

	Field heightInTiles:Int

	Field totalTiles:Int

Private
	Global _TileLoader:FlxTileLoader = New FlxTileLoader()

	Field _tiles:Image

	Field _buffers:IntMap<FlxTilemapBuffer>

	Field _data:Int[]

	Field _rects:FlxRect[]

	Field _tileWidth:Int

	Field _tileHeight:Int

	Field _tileObjects:FlxTile[]

	Field _startingIndex:Int = 0

	Field _camera:FlxCamera

	Field _buffer:FlxTilemapBuffer

	Field _screenXInTiles:Int

	Field _screenYInTiles:Int

	Field _screenRows:Int

	Field _screenColumns:Int

Public
	Method New()
		Super.New()
		auto = OFF
		widthInTiles = 0
		heightInTiles = 0
		totalTiles = 0
		_buffers = New IntMap<FlxTilemapBuffer>()
		_tileWidth = 0
		_tileHeight = 0
		_tiles = Null
		immovable = True
		moves = False
		_cameras = Null
		_startingIndex = 0
		_camera = Null
		_buffer = Null
	End Method

	Method Destroy:Void()
		_tiles = Null
		_camera = Null
		_buffer = Null

		Local i:Int = 0
		Local l:Int = _tileObjects.Length()

		While (i < l)
			If (_tileObjects[i] <> Null) Then
				_tileObjects[i].Destroy()
				_tileObjects[i] = Null
			End If

			i += 1
		Wend

		_buffers.Clear()
		_buffers = Null

		Super.Destroy()
	End Method

	Method LoadMap:FlxTilemap(mapData:String, tileGraphic:String, tileWidth:Int = 0, tileHeight:Int = 0, autoTile:Int = OFF, startIndex:Int = 0, drawIndex:Int = 1, collideIndex:Int = 1)
		auto = autoTile
		_startingIndex = startIndex

		Local columns:String[]
		Local rows:String[] = mapData.Split("~n")
		Local row:Int = 0
		Local column:Int
		Local data:Stack<Int> = New Stack<Int>()

		heightInTiles = rows.Length()

		While (row < heightInTiles)
			columns = rows[row].Split(",")

			If (columns.Length() <= 1) Then
				heightInTiles = heightInTiles - 1
				Continue
			End If

			If (widthInTiles = 0) Then
				widthInTiles = columns.Length()
			End If

			column = 0

			While (column < widthInTiles)
				data.Push(Int(columns[column]))
				column += 1
			Wend

			row += 1
		Wend

		_data = data.ToArray()

		Local i:Int

		totalTiles = widthInTiles * heightInTiles

		If (auto > OFF) Then
			_startingIndex = 1
			drawIndex = 1
			collideIndex = 1
			i = 0

			While (i < totalTiles)
				_AutoTile(i)
				i += 1
			Wend
		End If

		_tiles = FlxG.AddBitmap(tileGraphic, _TileLoader)

		_tileWidth = tileWidth
		If (_tileWidth = 0) _tileWidth = _tiles.Height()

		_tileHeight = tileHeight
		If (_tileHeight = 0) _tileHeight = _tileWidth

		i = 0
		Local l:Int = (_tiles.Width() / _tileWidth) * (_tiles.Height() / _tileHeight) + _startingIndex

		_tileObjects = _tileObjects.Resize(l)

		While (i < l)
			If (i >= collideIndex) Then
				_tileObjects[i] = New FlxTile(Self, i, _tileWidth, _tileHeight, (i >= drawIndex), allowCollisions)
			Else
				_tileObjects[i] = New FlxTile(Self, i, _tileWidth, _tileHeight, (i >= drawIndex), NONE)
			End If

			i += 1
		Wend

		width = widthInTiles * _tileWidth
		height = heightInTiles * _tileHeight
		_rects = _rects.Resize(totalTiles)
		i = 0

		While (i < totalTiles)
			_UpdateTile(i)
			i += 1
		Wend

		_buffers.Clear()

		Return Self
	End Method

	Method Draw:Void()
		If (_flickerTimer <> 0) Then
			_flicker = Not _flicker
			If (_flicker) Return
		End If

		_camera = FlxG._CurrentCamera

		If (_cameras <> Null And Not _cameras.Contains(_camera.ID)) Return

		_buffer = _buffers.Get(_camera.ID)

		If (_buffer = Null) Then
			_buffer = New FlxTilemapBuffer(_tileWidth, _tileHeight, widthInTiles, heightInTiles, _camera)
			_buffers.Set(_camera.ID, _buffer)
		End If

		_screenRows = _buffer.rows
		_screenColumns = _buffer.columns

		If (Not _buffer.dirty) Then
			_point.x = x - Int(_camera.scroll.x * scrollFactor.x) + _buffer.x
			_point.y = y - Int(_camera.scroll.y * scrollFactor.y) + _buffer.y

			_buffer.dirty = _point.x > 0 Or _point.y > 0 Or _point.x + _buffer.width < _camera.Width Or _point.y + _buffer.height < _camera.Height
		End If

		If (_buffer.dirty) Then
			_point.x = Int(_camera.scroll.x * scrollFactor.x) - x
			_point.y = Int(_camera.scroll.y * scrollFactor.y) - y

			If (_point.x > 0) Then
				_screenXInTiles = (_point.x + 0.0000001) / _tileWidth
			Else
				_screenXInTiles = (_point.x - 0.0000001) / _tileWidth
			End If

			If (_point.y > 0) Then
				_screenYInTiles = (_point.y + 0.0000001) / _tileHeight
			Else
				_screenYInTiles = (_point.y - 0.0000001) / _tileHeight
			End If

			If (_screenXInTiles < 0) _screenXInTiles = 0
			If (_screenXInTiles > widthInTiles - _screenColumns) _screenXInTiles = widthInTiles - _screenColumns
			If (_screenYInTiles < 0) _screenYInTiles = 0
			If (_screenYInTiles > heightInTiles - _screenRows) _screenYInTiles = heightInTiles - _screenRows

			_buffer.x = _screenXInTiles * _tileWidth
			_buffer.y = _screenYInTiles * _tileHeight
		End If

		_point.x = x - Int(_camera.scroll.x * scrollFactor.x) + _buffer.x
		_point.y = y - Int(_camera.scroll.y * scrollFactor.y) + _buffer.y

		If (FlxG._LastDrawingBlend <> AlphaBlend) Then
			SetBlend(AlphaBlend)
			FlxG._LastDrawingBlend = AlphaBlend
		End If

		If (FlxG._LastDrawingColor <> _camera.Color) Then
			SetColor(_camera._color.r, _camera._color.g, _camera._color.b)
			FlxG._LastDrawingColor = _camera.Color
		End If

		Local alpha:Float = _camera._color.a * _camera.Alpha

		If (FlxG._LastDrawingAlpha <> alpha) Then
			SetAlpha(alpha)
			FlxG._LastDrawingAlpha = alpha
		End If

		PushMatrix()
			Translate( _point.x, _point.y)

			Local rowIndex:Int = _screenYInTiles * widthInTiles + _screenXInTiles
			Local row:Int = 0
			Local column:Int
			Local columnIndex:Int

			_point.y = 0

			If (_buffer.scaleFixX = 1 Or _buffer.scaleFixY = 1) Then
				While (row < _screenRows)
					columnIndex = rowIndex
					column = 0
					_point.x = 0

					While (column < _screenColumns)
						_rect = _rects[columnIndex]

						If (_rect <> Null) Then
							DrawImageRect(_tiles, _point.x, _point.y, _rect.x, _rect.y, _rect.width, _rect.height)

							If (FlxG.VisualDebug And Not ignoreDrawDebug) Then
								'TODO
							End If
						End If

						_point.x += _tileWidth
						column += 1
						columnIndex += 1
					Wend

					rowIndex += widthInTiles
					_point.y += _tileHeight
					row += 1
				Wend
			Else
				row = _screenRows - 1
				_point.y = row * _tileHeight
				rowIndex += widthInTiles * row

				While (row >= 0)
					column = _screenColumns - 1
					_point.x = column * _tileWidth
					columnIndex = rowIndex + column

					While (column >= 0)
						_rect = _rects[columnIndex]

						If (_rect <> Null) Then
							DrawImageRect(_tiles, _point.x, _point.y, _rect.x, _rect.y, _rect.width, _rect.height, 0, _buffer.scaleFixX, _buffer.scaleFixY)

							If (FlxG.VisualDebug And Not ignoreDrawDebug) Then
								'TODO
							End If
						End If

						_point.x -= _tileWidth
						column -= 1
						columnIndex -= 1
					Wend

					rowIndex -= widthInTiles
					_point.y -= _tileHeight
					row -= 1
				Wend
			End If
		PopMatrix()

		_VisibleCount += 1
	End Method

	Method GetData:Int[](simple:Bool = False)
		If (Not simple) Return _data

		Local i:Int = 0
		Local l:Int = _data.Length()
		Local data:Int[l]

		While (i < l)
			If (_tileObjects[_data[i]].allowCollisions > 0) Then
				data[i] = 1
			Else
				data[i] = 0
			End If

			i += 1
		Wend

		Return data
	End Method

	Method SetDirty:Void(dirty:Bool = True)
		For Local buffer:FlxTilemapBuffer = EachIn _buffers.Values()
			buffer.dirty = dirty
		Next
	End Method

	Method FindPath:FlxPath(start:FlxPoint, endPoint:FlxPoint, simplify:Bool = True, raySimplify:Bool = False)
		Local startIndex:Int = Int((start.y - y) / _tileHeight) * widthInTiles + Int((start.x - x) / _tileWidth)
		Local endIndex:Int = Int((endPoint.y - y) / _tileHeight) * widthInTiles + Int((endPoint.x - x) / _tileWidth)

		If (_tileObjects[_data[startIndex]].allowCollisions > 0 Or _tileObjects[_data[endIndex]].allowCollisions > 0) Then
			Return Null
		End If

		Local distances:Int[] = _ComputePathDistance(startIndex, endIndex)
		If (distances.Length() = 0) Return Null

		Local points:Stack<FlxPoint> = New Stack<FlxPoint>()
		_WalkPath(distances, endIndex, points)

		Local node:FlxPoint

		node = points.Top()
		node.x = start.x
		node.y = start.y

		node = points.Get(0)
		node.x = endPoint.x
		node.y = endPoint.y

		If (simplify) _SimplifyPath(points)
		If (raySimplify) _RaySimplifyPath(points)

		Local path:FlxPath = New FlxPath()
		Local i:Int = points.Length() - 1

		While (i >= 0)
			node = points.Get(i)

			If (node <> Null) Then
				path.AddPoint(node, true)
			End If

			i -= 1
		Wend

		Return path
	End Method

	Method Overlaps:Bool(objectOrGroup:FlxBasic, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (FlxGroup(objectOrGroup) <> Null) Then
			Local results:Bool = False
			Local basic:FlxBasic
			Local i:Int = 0
			Local members:FlxBasic[] = FlxGroup(objectOrGroup).Members
			Local l:Int = FlxGroup(objectOrGroup).Length

			While (i < l)
				basic = members[i]

				If (FlxObject(basic) <> Null) Then
					If (OverlapsWithCallback(FlxObject(basic))) Then
						results = True
					End If

				Else
					If (Overlaps(basic, inScreenSpace, camera)) Then
						results = True
					End If
				End If

				i += 1
			Wend

			Return results

		ElseIf (FlxObject(objectOrGroup) <> Null) Then
			Return OverlapsWithCallback(FlxObject(objectOrGroup))
		End If

		Return False
	End Method

	Method OverlapsAt:Bool(x:Float, y:Float, objectOrGroup:FlxBasic, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (FlxGroup(objectOrGroup) <> Null) Then
			Local results:Bool = False
			Local basic:FlxBasic
			Local i:Int = 0
			Local members:FlxBasic[] = FlxGroup(objectOrGroup).Members
			Local l:Int = FlxGroup(objectOrGroup).Length

			While (i < l)
				basic = members[i]

				If (FlxObject(basic) <> Null) Then
					_point.x = x
					_point.y = y

					If (OverlapsWithCallback(FlxObject(basic), Null, False, _point)) Then
						results = True
					End If

				Else
					If (OverlapsAt(x, y, basic, inScreenSpace, camera)) Then
						results = True
					End If
				End If

				i += 1
			Wend

			Return results

		ElseIf (FlxObject(objectOrGroup) <> Null) Then
			_point.x = x
			_point.y = y

			Return OverlapsWithCallback(FlxObject(objectOrGroup), Null, False, _point)
		End If

		Return False
	End Method

	Method OverlapsWithCallback:Bool(object:FlxObject, callback:FlxTileOverlapChecker = Null, flipCallbackParams:Bool = False, position:FlxPoint = Null)
		Local results:Bool = False

		Local lx:Float = x
		Local ly:Float = y

		If (position <> Null) Then
			lx = position.x
			ly = position.y
		End If

		Local selectionX:Int = Floor((object.x - lx) / _tileWidth)
		Local selectionY:Int = Floor((object.y - ly) / _tileHeight)
		Local selectionWidth:Int = selectionX + Ceil(object.width / _tileWidth) + 1
		Local selectionHeight:Int = selectionY + Ceil(object.height / _tileHeight) + 1

		If (selectionX < 0) selectionX = 0
		If (selectionY < 0) selectionY = 0
		If (selectionWidth > widthInTiles) selectionWidth = widthInTiles
		If (selectionHeight > heightInTiles) selectionHeight = heightInTiles

		Local rowStart:Int = selectionY * widthInTiles
		Local row:Int = selectionY
		Local column:Int
		Local tile:FlxTile
		Local overlapFound:Bool
		Local deltaX:Float = lx - last.x
		Local deltaY:Float = ly - last.y

		While (row < selectionHeight)
			column = selectionX

			While (column < selectionWidth)
				overlapFound = False
				tile = _tileObjects[_data[rowStart + column]]

				If (tile.allowCollisions) Then
					tile.x = lx + column * _tileWidth
					tile.y = ly + row * _tileHeight
					tile.last.x = tile.x - deltaX
					tile.last.y = tile.y - deltaY

					If (callback <> Null) Then
						If (flipCallbackParams) Then
							overlapFound = callback.IsTileOverlap(object, tile)
						Else
							overlapFound = callback.IsTileOverlap(tile, object)
						End If
					Else
						overlapFound = (object.x + object.width > tile.x And object.x < tile.x + tile.width And object.y + object.height > tile.y And object.y < tile.y + tile.height)
					End If

					If (overlapFound) Then
						If (tile.callback <> Null And (tile.filter = Null Or object.GetClass().ExtendsClass(tile.filter))) Then
							tile.mapIndex = rowStart + column
							tile.callback.OnTileHit(tile, object)
						End If

						results = True
					End If

				ElseIf (tile.callback <> Null And (tile.filter = Null Or object.GetClass().ExtendsClass(tile.filter))) Then
					tile.mapIndex = rowStart + column
					tile.callback.OnTileHit(tile, object)
				End If

				column += 1
			Wend

			rowStart += widthInTiles
			row += 1
		Wend

		Return results
	End Method

	Method OverlapsPoint:Bool(point:FlxPoint, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (Not inScreenSpace) Then
			Return _tileObjects[_data[Int(Int((point.y - y) / _tileHeight) * widthInTiles + (point.x - x) / _tileWidth)]].allowCollisions > 0
		End If

		If (camera = Null) camera = FlxG.Camera

		point.x -= camera.scroll.x
		point.y -= camera.scroll.y

		GetScreenXY(_point, camera)

		Return _tileObjects[_data[Int(Int((point.y - _point.y) / _tileHeight) * widthInTiles + (point.x - _point.x) / _tileWidth)]].allowCollisions > 0
	End Method

	Method GetTile:Int(x:Int, y:Int)
		Return _data[y * widthInTiles + x]
	End Method

	Method GetTileByIndex:Int(index:Int)
		Return _data[index]
	End Method

	Method GetTileInstances:Stack<Int>(index:Int)
		Local elements:Stack<Int> = Null

		Local i:Int = 0
		Local l:Int = widthInTiles * heightInTiles

		While (i < l)
			If (_data[i] = index) Then
				If (elements = Null) elements = New Stack<Int>()
				elements.Push(i)
			End If

			i += 1
		Wend

		Return elements
	End Method

	Method GetTileCoords:Stack<FlxPoint>(index:Int, midpoint:Bool = True)
		Local elements:Stack<FlxPoint> = Null

		Local point:FlxPoint
		Local i:Int = 0
		Local l:Int = widthInTiles * heightInTiles

		While (i < l)
			If (_data[i] = index) Then
				point = New FlxPoint(x + Int(i Mod widthInTiles) * _tileWidth, y + Int(i / widthInTiles) * _tileHeight)

				If (midpoint) Then
					point.x += _tileWidth * .5
					point.y += _tileHeight * .5
				End If

				If (elements = Null) elements = New Stack<FlxPoint>()
				elements.Push(point)
			End If

			i += 1
		Wend

		Return elements
	End Method

	Method SetTile:Bool(x:Int, y:Int, tile:Int, updateGraphics:Bool = True)
		If (x >= widthInTiles Or y >= heightInTiles) Then
			Return False
		End If

		Return SetTileByIndex(y * widthInTiles + x, tile, updateGraphics)
	End Method

	Method SetTileByIndex:Bool(index:Int, tile:Int, updateGraphics:Bool = True)
		If (index >= _data.Length()) Return False

		_data[index] = tile

		If (Not updateGraphics) Return True

		If (auto = OFF) Then
			_UpdateTile(index)
			Return True
		End If

		Local i:Int = 0
		Local row:Int = Int(index / widthInTiles) - 1
		Local rowLength:Int = row + 3
		Local column:Int = index Mod widthInTiles - 1
		Local columnHeight:Int = column + 3

		While (row < rowLength)
			column = columnHeight - 3

			While (column < columnHeight)
				If (row >= 0 And row < heightInTiles And column >= 0 And column < widthInTiles) Then
					i = row * widthInTiles + column
					_AutoTile(i)
					_UpdateTile(i)
				End If

				column += 1
			Wend

			row += 1
		Wend

		Return True
	End Method

	Method SetTileProperties:Void(tile:Int, allowCollisions:Int = $1111, callback:FlxTileHitListener = Null, callbackFilter:ClassInfo = Null, range:Int = 1)
		If (range <= 0) range = 1

		Local tileObject:FlxTile
		Local i:Int = tile
		Local l:Int = tile + range

		While (i < l)
			tileObject = _tileObjects[i]

			tileObject.allowCollisions = allowCollisions
			tileObject.callback = callback
			tileObject.filter = callbackFilter

			i += 1
		Wend
	End Method

	Method Follow:Void(camera:FlxCamera = Null, border:Int = 0, updateWorld:Bool = True)
		If (camera = Null) camera = FlxG.Camera
		camera.SetBounds(x + border * _tileWidth, y + border * _tileHeight, width - border * _tileWidth * 2, height - border * _tileHeight * 2, updateWorld)
	End Method

	Method GetBounds:FlxRect(bounds:FlxRect = Null)
		If (bounds = Null) bounds = New FlxRect()
		Return bounds.Make(x, y, width, height)
	End Method

	Method Ray:Bool(startPoint:FlxPoint, endPoint:FlxPoint, result:FlxPoint = Null, resolution:Float = 1)
		Local _step:Float = _tileWidth

		If (_tileHeight < _tileWidth) Then
			_step = _tileHeight
		End If

		_step /= resolution

		Local deltaX:Float = endPoint.x - startPoint.x
		Local deltaY:Float = endPoint.y - startPoint.y
		Local distance:Float = Sqrt(deltaX * deltaX + deltaY * deltaY)
		Local steps:Int = Ceil(distance / _step)
		Local stepX:Float = deltaX / steps
		Local stepY:Float = deltaY / steps
		Local curX:Float = startPoint.x - stepX - x
		Local curY:Float = startPoint.y - stepY - y
		Local tileX:Int
		Local tileY:Int
		Local i:Int = 0

		While (i < steps)
			curX += stepX
			curY += stepY

			If (curX < 0 Or curX > width Or curY < 0 Or curY > height) Then
				i += 1
				Continue
			End If

			tileX = curX / _tileWidth
			tileY = curY / _tileHeight

			If (_tileObjects[_data[tileY * widthInTiles + tileX]].allowCollisions) Then
				tileX *= _tileWidth
				tileY *= _tileHeight

				Local rx:Float = 0
				Local ry:Float = 0
				Local q:Float
				Local lx:Float = curX - stepX
				Local ly:Float = curY - stepY

				q = tileX
				If (deltaX < 0) q += _tileWidth

				rx = q
				ry = ly + stepY * ((q - lx) / stepX)

				If (ry > tileY And ry < tileY + _tileHeight) Then
					If (result = Null) Then
						result = New FlxPoint()
					End If

					result.x = rx
					result.y = ry

					Return False
				End If

				q = tileY
				If (deltaY < 0) q += _tileHeight

				rx = lx + stepX * ((q - ly) / stepY)
				ry = q

				If (rx > tileX And rx < tileX + _tileWidth) Then
					If (result = Null) Then
						result = New FlxPoint()
					End If

					result.x = rx
					result.y = ry

					Return False
				End If

				Return True
			End If

			i += 1
		Wend

		Return True
	End Method

	Function ArrayToCSV:String(data:Int[], width:Int, invert:Bool = False)
		Local row:Int = 0
		Local column:Int
		Local csv:StringStack = New StringStack()
		Local height:Int = data.Length() / width
		Local index:Int

		While (row < height)
			column = 0

			While (column < width)
				index = data[row * width + column]

				If (invert) Then
					If (index = 0) Then
						index = 1
					Else
						index = 0
					End If
				End If

				If (column = 0) Then
					If (row = 0) Then
						csv.Push(index)
					Else
						csv.Push("~n" + index)
					End If
				Else
					csv.Push(", " + index)
				End If

				column += 1
			Wend

			row += 1
		Wend

		Return csv.Join("")
	End Function

Private
	Method _SimplifyPath:Void(points:Stack<FlxPoint>)
		Local deltaPrevious:Float
		Local deltaNext:Float
		Local last:FlxPoint = points.Get(0)
		Local node:FlxPoint
		Local nextPoint:FlxPoint
		Local i:Int = 1
		Local l:Int = points.Length() - 1

		While (i < l)
			node = points.Get(i)
			nextPoint = points.Get(i + 1)

			deltaPrevious = (node.x - last.x) / (node.y - last.y)
			deltaNext = (node.x - nextPoint.x) / (node.y - nextPoint.y)

			If (last.x = nextPoint.x Or last.y = nextPoint.y Or deltaPrevious = deltaNext) Then
				points.Set(i, Null)
			Else
				last = node
			End If

			i += 1
		Wend
	End Method

	Method _RaySimplifyPath:Void(points:Stack<FlxPoint>)
		Local source:FlxPoint = points.Get(0)
		Local lastIndex:Int = -1
		Local node:FlxPoint
		Local i:Int = 1
		Local l:Int = points.Length()

		While (i < l)
			node = points.Get(i)
			i += 1

			If (node = Null) Continue

			If (Ray(source, node, _point)) Then
				If (lastIndex >= 0) points.Set(lastIndex, Null)
			Else
				source = points.Get(lastIndex)
			End If

			lastIndex = i - 1
		Wend
	End Method

	Method _ComputePathDistance:Int[](startIndex:Int, endIndex:Int)
		Local mapSize:Int = widthInTiles * heightInTiles
		Local distances:Int[mapSize]
		Local i:Int = 0

		While (i < mapSize)
			If (_tileObjects[_data[i]].allowCollisions) Then
				distances[i] = -2
			Else
				distances[i] = -1
			End If

			i += 1
		Wend

		distances[startIndex] = 0

		Local distance:Int = 1
		Local neighbors:Stack<Int> = New Stack<Int>([startIndex])
		Local current:Stack<Int>
		Local currentIndex:Int
		Local left:Bool
		Local right:Bool
		Local up:Bool
		Local down:Bool
		Local currentLength:Int
		Local foundEnd:Bool = False

		While (neighbors.Length() > 0)
			current = neighbors
			neighbors = New Stack<Int>()

			i = 0
			currentLength = current.Length()

			While (i < currentLength)
				currentIndex = current.Get(i)

				If (currentIndex = endIndex) Then
					foundEnd = True
					neighbors.Clear()
					Exit
				End If

				left = currentIndex Mod widthInTiles > 0
				right = currentIndex Mod widthInTiles < widthInTiles - 1
				up = currentIndex / widthInTiles > 0
				down = currentIndex / widthInTiles < heightInTiles - 1

				Local index:Int

				If (up) Then
					index = currentIndex - widthInTiles

					If (distances[index] = -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (right) Then
					index = currentIndex + 1

					If (distances[index] = -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (down) Then
					index = currentIndex + widthInTiles

					If (distances[index] = -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (left) Then
					index = currentIndex - 1

					If (distances[index] = -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (up And right) Then
					index = currentIndex - widthInTiles + 1

					If (distances[index] = -1 And distances[currentIndex - widthInTiles] >= -1 And distances[currentIndex + 1] >= -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (right And down) Then
					index = currentIndex + widthInTiles + 1

					If (distances[index] = -1 And distances[currentIndex + widthInTiles] >= -1 And distances[currentIndex + 1] >= -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (left And down) Then
					index = currentIndex + widthInTiles - 1

					If (distances[index] = -1 And distances[currentIndex + widthInTiles] >= -1 And distances[currentIndex - 1] >= -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (up And left) Then
					index = currentIndex - widthInTiles - 1

					If (distances[index] = -1 And distances[currentIndex - widthInTiles] >= -1 And distances[currentIndex - 1] >= -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				i += 1
			Wend

			distance += 1
		Wend

		If (Not foundEnd) distances = []

		Return distances
	End Method

	Method _WalkPath:Void(data:Int[], start:Int, points:Stack<FlxPoint>)
		points.Push(New FlxPoint(x + Int(start Mod widthInTiles) * _tileWidth + _tileWidth * .5, y + Int(start / widthInTiles) * _tileHeight + _tileHeight * .5))
		If (data[start] = 0) Return

		Local left:Bool = start Mod widthInTiles > 0
		Local right:Bool = start Mod widthInTiles < widthInTiles - 1
		Local up:Bool = start / widthInTiles > 0
		Local down:Bool = start / widthInTiles < heightInTiles - 1

		Local current:Int = data[start]
		Local i:Int

		If (up) Then
			i = start - widthInTiles

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (right) Then
			i = start + 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (down) Then
			i = start + widthInTiles

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (left) Then
			i = start - 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (up And right) Then
			i = start - widthInTiles + 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (right And down) Then
			i = start + widthInTiles + 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (left And down) Then
			i = start + widthInTiles - 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (up And left) Then
			i = start - widthInTiles - 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If
	End Method

	Method _AutoTile:Void(index:Int)
		If (_data[index] = 0) Return

		_data[index] = 0

		If ( (index - widthInTiles < 0) Or (_data[index - widthInTiles] > 0)) Then
			_data[index] += 1
		End If

		If ( (index Mod widthInTiles >= widthInTiles - 1) Or (_data[index + 1] > 0)) Then
			_data[index] += 2
		End If

		If ( (index + widthInTiles >= totalTiles) Or (_data[index + widthInTiles] > 0)) Then
			_data[index] += 4
		End If

		If ( (index Mod widthInTiles <= 0) Or (_data[index - 1] > 0)) Then
			_data[index] += 8
		End If

		If (auto = ALT And _data[index] = 15) Then
			If ( (index Mod widthInTiles > 0) And (index + widthInTiles < totalTiles) And (_data[index + widthInTiles - 1] <= 0)) Then
				_data[index] = 1
			End If

			If ( (index Mod widthInTiles > 0) And (index - widthInTiles >= 0) And (_data[index - widthInTiles - 1] <= 0)) Then
				_data[index] = 2
			End If

			If ( (index Mod widthInTiles < widthInTiles - 1) And (index - widthInTiles >= 0) And (_data[index - widthInTiles + 1] <= 0)) Then
				_data[index] = 4
			End If

			If ( (index Mod widthInTiles < widthInTiles - 1) And (index + widthInTiles < totalTiles) And (_data[index + widthInTiles + 1] <= 0)) Then
				_data[index] = 8
			End If
		End If

		_data[index] += 1
	End Method

	Method _UpdateTile:Void(index:Int)
		Local tile:FlxTile = _tileObjects[_data[index]]

		If (tile = Null Or Not tile.visible) Then
			_rects[index] = Null
			Return
		End If

		Local rx:Int = (_data[index] - _startingIndex) * _tileWidth
		Local ry:Int = 0

		If (rx >= _tiles.Width()) Then
			ry = Int(rx / _tiles.Width()) * _tileHeight
			rx Mod= _tiles.Width()
		End If

		_rects[index] = New FlxRect(rx, ry, _tileWidth, _tileHeight)
	End Method

End Class

Private
Class FlxTileLoader Extends FlxResourceLoader<Image>

	Method Load:Image(name:String)
		Return LoadImage(FlxAssetsManager.GetImagePath(name))
	End Method

End Class

Strict

Import mojo
Import reflection

Import flxextern
Import flxobject
Import flxrect
Import flxg
Import flxcamera
Import flxgroup
Import flxbasic
Import flxpath

Import system.flxtile
Import system.flxtilemapbuffer

Import "data/autotiles_flx.png"
Import "data/autotiles_flx.png"

Class FlxTilemap Extends FlxObject

	Global ClassObject:Object

	Const AUTOTILES:String =  "autotiles" + FlxG.DATA_SUFFIX

	Const AUTOTILES_ALT:String =  "autotiles_alt" + FlxG.DATA_SUFFIX

	Const OFF:Int = 0

	Const AUTO:Int = 1

	Const ALT:Int = 2

	Field auto:Int

	Field widthInTiles:Int

	Field heightInTiles:Int

	Field totalTiles:Int

Private
	Global _TileLoader:FlxTileLoader = New FlxTileLoader()

	Field _tiles:Image

	Field _buffers:IntMap<FlxTilemapBuffer>

	Field _data:Int[]

	Field _rects:FlxRect[]

	Field _tileWidth:Int

	Field _tileHeight:Int

	Field _tileObjects:FlxTile[]

	Field _startingIndex:Int = 0

	Field _camera:FlxCamera

	Field _buffer:FlxTilemapBuffer

	Field _screenXInTiles:Int

	Field _screenYInTiles:Int

	Field _screenRows:Int

	Field _screenColumns:Int

Public
	Method New()
		Super.New()
		auto = OFF
		widthInTiles = 0
		heightInTiles = 0
		totalTiles = 0
		_buffers = New IntMap<FlxTilemapBuffer>()
		_tileWidth = 0
		_tileHeight = 0
		_tiles = Null
		immovable = True
		moves = False
		_cameras = Null
		_startingIndex = 0
		_camera = Null
		_buffer = Null
	End Method

	Method Destroy:Void()
		_tiles = Null
		_camera = Null
		_buffer = Null

		Local i:Int = 0
		Local l:Int = _tileObjects.Length()

		While (i < l)
			If (_tileObjects[i] <> Null) Then
				_tileObjects[i].Destroy()
				_tileObjects[i] = Null
			End If

			i += 1
		Wend

		_buffers.Clear()
		_buffers = Null

		Super.Destroy()
	End Method

	Method LoadMap:FlxTilemap(mapData:String, tileGraphic:String, tileWidth:Int = 0, tileHeight:Int = 0, autoTile:Int = OFF, startIndex:Int = 0, drawIndex:Int = 1, collideIndex:Int = 1)
		auto = autoTile
		_startingIndex = startIndex

		Local columns:String[]
		Local rows:String[] = mapData.Split("~n")
		Local row:Int = 0
		Local column:Int
		Local data:Stack<Int> = New Stack<Int>()

		heightInTiles = rows.Length()

		While (row < heightInTiles)
			columns = rows[row].Split(",")

			If (columns.Length() <= 1) Then
				heightInTiles = heightInTiles - 1
				Continue
			End If

			If (widthInTiles = 0) Then
				widthInTiles = columns.Length()
			End If

			column = 0

			While (column < widthInTiles)
				data.Push(Int(columns[column]))
				column += 1
			Wend

			row += 1
		Wend

		_data = data.ToArray()

		Local i:Int

		totalTiles = widthInTiles * heightInTiles

		If (auto > OFF) Then
			_startingIndex = 1
			drawIndex = 1
			collideIndex = 1
			i = 0

			While (i < totalTiles)
				_AutoTile(i)
				i += 1
			Wend
		End If

		_tiles = FlxG.AddBitmap(tileGraphic, _TileLoader)

		_tileWidth = tileWidth
		If (_tileWidth = 0) _tileWidth = _tiles.Height()

		_tileHeight = tileHeight
		If (_tileHeight = 0) _tileHeight = _tileWidth

		i = 0
		Local l:Int = (_tiles.Width() / _tileWidth) * (_tiles.Height() / _tileHeight)

		If (auto > OFF) l += 1
		_tileObjects = _tileObjects.Resize(l)

		While (i < l)
			If (i >= collideIndex) Then
				_tileObjects[i] = New FlxTile(Self, i, _tileWidth, _tileHeight, (i >= drawIndex), allowCollisions)
			Else
				_tileObjects[i] = New FlxTile(Self, i, _tileWidth, _tileHeight, (i >= drawIndex), NONE)
			End If

			i += 1
		Wend

		width = widthInTiles * _tileWidth
		height = heightInTiles * _tileHeight
		_rects = _rects.Resize(totalTiles)
		i = 0

		While (i < totalTiles)
			_UpdateTile(i)
			i += 1
		Wend

		_buffers.Clear()

		Return Self
	End Method

	Method Draw:Void()
		If (_flickerTimer <> 0) Then
			_flicker = Not _flicker
			If (_flicker) Return
		End If

		_camera = FlxG._CurrentCamera

		If (_cameras <> Null And Not _cameras.Contains(_camera.ID)) Return

		_buffer = _buffers.Get(_camera.ID)

		If (_buffer = Null) Then
			_buffer = New FlxTilemapBuffer(_tileWidth, _tileHeight, widthInTiles, heightInTiles, _camera)
			_buffers.Set(_camera.ID, _buffer)
		End If

		_screenRows = _buffer.rows
		_screenColumns = _buffer.columns

		If (Not _buffer.dirty) Then
			_point.x = x - Int(_camera.scroll.x * scrollFactor.x) + _buffer.x
			_point.y = y - Int(_camera.scroll.y * scrollFactor.y) + _buffer.y

			_buffer.dirty = _point.x > 0 Or _point.y > 0 Or _point.x + _buffer.width < _camera.Width Or _point.y + _buffer.height < _camera.Height
		End If

		If (_buffer.dirty) Then
			_point.x = Int(_camera.scroll.x * scrollFactor.x) - x
			_point.y = Int(_camera.scroll.y * scrollFactor.y) - y

			If (_point.x > 0) Then
				_screenXInTiles = (_point.x + 0.0000001) / _tileWidth
			Else
				_screenXInTiles = (_point.x - 0.0000001) / _tileWidth
			End If

			If (_point.y > 0) Then
				_screenYInTiles = (_point.y + 0.0000001) / _tileHeight
			Else
				_screenYInTiles = (_point.y - 0.0000001) / _tileHeight
			End If

			If (_screenXInTiles < 0) _screenXInTiles = 0
			If (_screenXInTiles > widthInTiles - _screenColumns) _screenXInTiles = widthInTiles - _screenColumns
			If (_screenYInTiles < 0) _screenYInTiles = 0
			If (_screenYInTiles > heightInTiles - _screenRows) _screenYInTiles = heightInTiles - _screenRows

			_buffer.x = _screenXInTiles * _tileWidth
			_buffer.y = _screenYInTiles * _tileHeight
		End If

		_point.x = x - Int(_camera.scroll.x * scrollFactor.x) + _buffer.x
		_point.y = y - Int(_camera.scroll.y * scrollFactor.y) + _buffer.y

		If (FlxG._LastDrawingBlend <> AlphaBlend) Then
			SetBlend(AlphaBlend)
			FlxG._LastDrawingBlend = AlphaBlend
		End If

		If (FlxG._LastDrawingColor <> _camera.Color) Then
			SetColor(_camera._color.r, _camera._color.g, _camera._color.b)
			FlxG._LastDrawingColor = _camera.Color
		End If

		Local alpha:Float = _camera._color.a * _camera.Alpha

		If (FlxG._LastDrawingAlpha <> alpha) Then
			SetAlpha(alpha)
			FlxG._LastDrawingAlpha = alpha
		End If

		PushMatrix()
			Translate( _point.x, _point.y)

			Local rowIndex:Int = _screenYInTiles * widthInTiles + _screenXInTiles
			Local row:Int = 0
			Local column:Int
			Local columnIndex:Int

			_point.y = 0

			If (_buffer.scaleFixX = 1 Or _buffer.scaleFixY = 1) Then
				While (row < _screenRows)
					columnIndex = rowIndex
					column = 0
					_point.x = 0

					While (column < _screenColumns)
						_rect = _rects[columnIndex]

						If (_rect <> Null) Then
							DrawImageRect(_tiles, _point.x, _point.y, _rect.x, _rect.y, _rect.width, _rect.height)

							If (FlxG.VisualDebug And Not ignoreDrawDebug) Then
								'TODO
							End If
						End If

						_point.x += _tileWidth
						column += 1
						columnIndex += 1
					Wend

					rowIndex += widthInTiles
					_point.y += _tileHeight
					row += 1
				Wend
			Else
				row = _screenRows - 1
				_point.y = row * _tileHeight
				rowIndex += widthInTiles * row

				While (row >= 0)
					column = _screenColumns - 1
					_point.x = column * _tileWidth
					columnIndex = rowIndex + column

					While (column >= 0)
						_rect = _rects[columnIndex]

						If (_rect <> Null) Then
							DrawImageRect(_tiles, _point.x, _point.y, _rect.x, _rect.y, _rect.width, _rect.height, 0, _buffer.scaleFixX, _buffer.scaleFixY)

							If (FlxG.VisualDebug And Not ignoreDrawDebug) Then
								'TODO
							End If
						End If

						_point.x -= _tileWidth
						column -= 1
						columnIndex -= 1
					Wend

					rowIndex -= widthInTiles
					_point.y -= _tileHeight
					row -= 1
				Wend
			End If
		PopMatrix()

		_VisibleCount += 1
	End Method

	Method GetData:Int[](simple:Bool = False)
		If (Not simple) Return _data

		Local i:Int = 0
		Local l:Int = _data.Length()
		Local data:Int[l]

		While (i < l)
			If (_tileObjects[_data[i]].allowCollisions > 0) Then
				data[i] = 1
			Else
				data[i] = 0
			End If

			i += 1
		Wend

		Return data
	End Method

	Method SetDirty:Void(dirty:Bool = True)
		For Local buffer:FlxTilemapBuffer = EachIn _buffers.Values()
			buffer.dirty = dirty
		Next
	End Method

	Method FindPath:FlxPath(start:FlxPoint, endPoint:FlxPoint, simplify:Bool = True, raySimplify:Bool = False)
		Local startIndex:Int = Int((start.y - y) / _tileHeight) * widthInTiles + Int((start.x - x) / _tileWidth)
		Local endIndex:Int = Int((endPoint.y - y) / _tileHeight) * widthInTiles + Int((endPoint.x - x) / _tileWidth)

		If (_tileObjects[_data[startIndex]].allowCollisions > 0 Or _tileObjects[_data[endIndex]].allowCollisions > 0) Then
			Return Null
		End If

		Local distances:Int[] = _ComputePathDistance(startIndex, endIndex)
		If (distances.Length() = 0) Return Null

		Local points:Stack<FlxPoint> = New Stack<FlxPoint>()
		_WalkPath(distances, endIndex, points)

		Local node:FlxPoint

		node = points.Top()
		node.x = start.x
		node.y = start.y

		node = points.Get(0)
		node.x = endPoint.x
		node.y = endPoint.y

		If (simplify) _SimplifyPath(points)
		If (raySimplify) _RaySimplifyPath(points)

		Local path:FlxPath = New FlxPath()
		Local i:Int = points.Length() - 1

		While (i >= 0)
			node = points.Get(i)

			If (node <> Null) Then
				path.AddPoint(node, true)
			End If

			i -= 1
		Wend

		Return path
	End Method

	Method Overlaps:Bool(objectOrGroup:FlxBasic, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (FlxGroup(objectOrGroup) <> Null) Then
			Local results:Bool = False
			Local basic:FlxBasic
			Local i:Int = 0
			Local members:FlxBasic[] = FlxGroup(objectOrGroup).Members
			Local l:Int = FlxGroup(objectOrGroup).Length

			While (i < l)
				basic = members[i]

				If (FlxObject(basic) <> Null) Then
					If (OverlapsWithCallback(FlxObject(basic))) Then
						results = True
					End If

				Else
					If (Overlaps(basic, inScreenSpace, camera)) Then
						results = True
					End If
				End If

				i += 1
			Wend

			Return results

		ElseIf (FlxObject(objectOrGroup) <> Null) Then
			Return OverlapsWithCallback(FlxObject(objectOrGroup))
		End If

		Return False
	End Method

	Method OverlapsAt:Bool(x:Float, y:Float, objectOrGroup:FlxBasic, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (FlxGroup(objectOrGroup) <> Null) Then
			Local results:Bool = False
			Local basic:FlxBasic
			Local i:Int = 0
			Local members:FlxBasic[] = FlxGroup(objectOrGroup).Members
			Local l:Int = FlxGroup(objectOrGroup).Length

			While (i < l)
				basic = members[i]

				If (FlxObject(basic) <> Null) Then
					_point.x = x
					_point.y = y

					If (OverlapsWithCallback(FlxObject(basic), Null, False, _point)) Then
						results = True
					End If

				Else
					If (OverlapsAt(x, y, basic, inScreenSpace, camera)) Then
						results = True
					End If
				End If

				i += 1
			Wend

			Return results

		ElseIf (FlxObject(objectOrGroup) <> Null) Then
			_point.x = x
			_point.y = y

			Return OverlapsWithCallback(FlxObject(objectOrGroup), Null, False, _point)
		End If

		Return False
	End Method

	Method OverlapsWithCallback:Bool(object:FlxObject, callback:FlxTileOverlapChecker = Null, flipCallbackParams:Bool = False, position:FlxPoint = Null)
		Local results:Bool = False

		Local lx:Float = x
		Local ly:Float = y

		If (position <> Null) Then
			lx = position.x
			ly = position.y
		End If

		Local selectionX:Int = Floor((object.x - lx) / _tileWidth)
		Local selectionY:Int = Floor((object.y - ly) / _tileHeight)
		Local selectionWidth:Int = selectionX + Ceil(object.width / _tileWidth) + 1
		Local selectionHeight:Int = selectionY + Ceil(object.height / _tileHeight) + 1

		If (selectionX < 0) selectionX = 0
		If (selectionY < 0) selectionY = 0
		If (selectionWidth > widthInTiles) selectionWidth = widthInTiles
		If (selectionHeight > heightInTiles) selectionHeight = heightInTiles

		Local rowStart:Int = selectionY * widthInTiles
		Local row:Int = selectionY
		Local column:Int
		Local tile:FlxTile
		Local overlapFound:Bool
		Local deltaX:Float = lx - last.x
		Local deltaY:Float = ly - last.y

		While (row < selectionHeight)
			column = selectionX

			While (column < selectionWidth)
				overlapFound = False
				tile = _tileObjects[_data[rowStart + column]]

				If (tile.allowCollisions) Then
					tile.x = lx + column * _tileWidth
					tile.y = ly + row * _tileHeight
					tile.last.x = tile.x - deltaX
					tile.last.y = tile.y - deltaY

					If (callback <> Null) Then
						If (flipCallbackParams) Then
							overlapFound = callback.IsTileOverlap(object, tile)
						Else
							overlapFound = callback.IsTileOverlap(tile, object)
						End If
					Else
						overlapFound = (object.x + object.width > tile.x And object.x < tile.x + tile.width And object.y + object.height > tile.y And object.y < tile.y + tile.height)
					End If

					If (overlapFound) Then
						If (tile.callback <> Null And (tile.filter = Null Or object.GetClass().ExtendsClass(tile.filter))) Then
							tile.mapIndex = rowStart + column
							tile.callback.OnTileHit(tile, object)
						End If

						results = True
					End If

				ElseIf (tile.callback <> Null And (tile.filter = Null Or object.GetClass().ExtendsClass(tile.filter))) Then
					tile.mapIndex = rowStart + column
					tile.callback.OnTileHit(tile, object)
				End If

				column += 1
			Wend

			rowStart += widthInTiles
			row += 1
		Wend

		Return results
	End Method

	Method OverlapsPoint:Bool(point:FlxPoint, inScreenSpace:Bool = False, camera:FlxCamera = Null)
		If (Not inScreenSpace) Then
			Return _tileObjects[_data[Int(Int((point.y - y) / _tileHeight) * widthInTiles + (point.x - x) / _tileWidth)]].allowCollisions > 0
		End If

		If (camera = Null) camera = FlxG.Camera

		point.x -= camera.scroll.x
		point.y -= camera.scroll.y

		GetScreenXY(_point, camera)

		Return _tileObjects[_data[Int(Int((point.y - _point.y) / _tileHeight) * widthInTiles + (point.x - _point.x) / _tileWidth)]].allowCollisions > 0
	End Method

	Method GetTile:Int(x:Int, y:Int)
		Return _data[y * widthInTiles + x]
	End Method

	Method GetTileByIndex:Int(index:Int)
		Return _data[index]
	End Method

	Method GetTileInstances:Stack<Int>(index:Int)
		Local elements:Stack<Int> = Null

		Local i:Int = 0
		Local l:Int = widthInTiles * heightInTiles

		While (i < l)
			If (_data[i] = index) Then
				If (elements = Null) elements = New Stack<Int>()
				elements.Push(i)
			End If

			i += 1
		Wend

		Return elements
	End Method

	Method GetTileCoords:Stack<FlxPoint>(index:Int, midpoint:Bool = True)
		Local elements:Stack<FlxPoint> = Null

		Local point:FlxPoint
		Local i:Int = 0
		Local l:Int = widthInTiles * heightInTiles

		While (i < l)
			If (_data[i] = index) Then
				point = New FlxPoint(x + Int(i Mod widthInTiles) * _tileWidth, y + Int(i / widthInTiles) * _tileHeight)

				If (midpoint) Then
					point.x += _tileWidth * .5
					point.y += _tileHeight * .5
				End If

				If (elements = Null) elements = New Stack<FlxPoint>()
				elements.Push(point)
			End If

			i += 1
		Wend

		Return elements
	End Method

	Method SetTile:Bool(x:Int, y:Int, tile:Int, updateGraphics:Bool = True)
		If (x >= widthInTiles Or y >= heightInTiles) Then
			Return False
		End If

		Return SetTileByIndex(y * widthInTiles + x, tile, updateGraphics)
	End Method

	Method SetTileByIndex:Bool(index:Int, tile:Int, updateGraphics:Bool = True)
		If (index >= _data.Length()) Return False

		_data[index] = tile

		If (Not updateGraphics) Return True

		If (auto = OFF) Then
			_UpdateTile(index)
			Return True
		End If

		Local i:Int = 0
		Local row:Int = Int(index / widthInTiles) - 1
		Local rowLength:Int = row + 3
		Local column:Int = index Mod widthInTiles - 1
		Local columnHeight:Int = column + 3

		While (row < rowLength)
			column = columnHeight - 3

			While (column < columnHeight)
				If (row >= 0 And row < heightInTiles And column >= 0 And column < widthInTiles) Then
					i = row * widthInTiles + column
					_AutoTile(i)
					_UpdateTile(i)
				End If

				column += 1
			Wend

			row += 1
		Wend

		Return True
	End Method

	Method SetTileProperties:Void(tile:Int, allowCollisions:Int = $1111, callback:FlxTileHitListener = Null, callbackFilter:ClassInfo = Null, range:Int = 1)
		If (range <= 0) range = 1

		Local tileObject:FlxTile
		Local i:Int = tile
		Local l:Int = tile + range

		While (i < l)
			tileObject = _tileObjects[i]

			tileObject.allowCollisions = allowCollisions
			tileObject.callback = callback
			tileObject.filter = callbackFilter

			i += 1
		Wend
	End Method

	Method Follow:Void(camera:FlxCamera = Null, border:Int = 0, updateWorld:Bool = True)
		If (camera = Null) camera = FlxG.Camera
		camera.SetBounds(x + border * _tileWidth, y + border * _tileHeight, width - border * _tileWidth * 2, height - border * _tileHeight * 2, updateWorld)
	End Method

	Method GetBounds:FlxRect(bounds:FlxRect = Null)
		If (bounds = Null) bounds = New FlxRect()
		Return bounds.Make(x, y, width, height)
	End Method

	Method Ray:Bool(startPoint:FlxPoint, endPoint:FlxPoint, result:FlxPoint = Null, resolution:Float = 1)
		Local _step:Float = _tileWidth

		If (_tileHeight < _tileWidth) Then
			_step = _tileHeight
		End If

		_step /= resolution

		Local deltaX:Float = endPoint.x - startPoint.x
		Local deltaY:Float = endPoint.y - startPoint.y
		Local distance:Float = Sqrt(deltaX * deltaX + deltaY * deltaY)
		Local steps:Int = Ceil(distance / _step)
		Local stepX:Float = deltaX / steps
		Local stepY:Float = deltaY / steps
		Local curX:Float = startPoint.x - stepX - x
		Local curY:Float = startPoint.y - stepY - y
		Local tileX:Int
		Local tileY:Int
		Local i:Int = 0

		While (i < steps)
			curX += stepX
			curY += stepY

			If (curX < 0 Or curX > width Or curY < 0 Or curY > height) Then
				i += 1
				Continue
			End If

			tileX = curX / _tileWidth
			tileY = curY / _tileHeight

			If (_tileObjects[_data[tileY * widthInTiles + tileX]].allowCollisions) Then
				tileX *= _tileWidth
				tileY *= _tileHeight

				Local rx:Float = 0
				Local ry:Float = 0
				Local q:Float
				Local lx:Float = curX - stepX
				Local ly:Float = curY - stepY

				q = tileX
				If (deltaX < 0) q += _tileWidth

				rx = q
				ry = ly + stepY * ((q - lx) / stepX)

				If (ry > tileY And ry < tileY + _tileHeight) Then
					If (result = Null) Then
						result = New FlxPoint()
					End If

					result.x = rx
					result.y = ry

					Return False
				End If

				q = tileY
				If (deltaY < 0) q += _tileHeight

				rx = lx + stepX * ((q - ly) / stepY)
				ry = q

				If (rx > tileX And rx < tileX + _tileWidth) Then
					If (result = Null) Then
						result = New FlxPoint()
					End If

					result.x = rx
					result.y = ry

					Return False
				End If

				Return True
			End If

			i += 1
		Wend

		Return True
	End Method

	Function ArrayToCSV:String(data:Int[], width:Int, invert:Bool = False)
		Local row:Int = 0
		Local column:Int
		Local csv:StringStack = New StringStack()
		Local height:Int = data.Length() / width
		Local index:Int

		While (row < height)
			column = 0

			While (column < width)
				index = data[row * width + column]

				If (invert) Then
					If (index = 0) Then
						index = 1
					Else
						index = 0
					End If
				End If

				If (column = 0) Then
					If (row = 0) Then
						csv.Push(index)
					Else
						csv.Push("~n" + index)
					End If
				Else
					csv.Push(", " + index)
				End If

				column += 1
			Wend

			row += 1
		Wend

		Return csv.Join("")
	End Function

Private
	Method _SimplifyPath:Void(points:Stack<FlxPoint>)
		Local deltaPrevious:Float
		Local deltaNext:Float
		Local last:FlxPoint = points.Get(0)
		Local node:FlxPoint
		Local nextPoint:FlxPoint
		Local i:Int = 1
		Local l:Int = points.Length() - 1

		While (i < l)
			node = points.Get(i)
			nextPoint = points.Get(i + 1)

			deltaPrevious = (node.x - last.x) / (node.y - last.y)
			deltaNext = (node.x - nextPoint.x) / (node.y - nextPoint.y)

			If (last.x = nextPoint.x Or last.y = nextPoint.y Or deltaPrevious = deltaNext) Then
				points.Set(i, Null)
			Else
				last = node
			End If

			i += 1
		Wend
	End Method

	Method _RaySimplifyPath:Void(points:Stack<FlxPoint>)
		Local source:FlxPoint = points.Get(0)
		Local lastIndex:Int = -1
		Local node:FlxPoint
		Local i:Int = 1
		Local l:Int = points.Length()

		While (i < l)
			node = points.Get(i)
			i += 1

			If (node = Null) Continue

			If (Ray(source, node, _point)) Then
				If (lastIndex >= 0) points.Set(lastIndex, Null)
			Else
				source = points.Get(lastIndex)
			End If

			lastIndex = i - 1
		Wend
	End Method

	Method _ComputePathDistance:Int[](startIndex:Int, endIndex:Int)
		Local mapSize:Int = widthInTiles * heightInTiles
		Local distances:Int[mapSize]
		Local i:Int = 0

		While (i < mapSize)
			If (_tileObjects[_data[i]].allowCollisions) Then
				distances[i] = -2
			Else
				distances[i] = -1
			End If

			i += 1
		Wend

		distances[startIndex] = 0

		Local distance:Int = 1
		Local neighbors:Stack<Int> = New Stack<Int>([startIndex])
		Local current:Stack<Int>
		Local currentIndex:Int
		Local left:Bool
		Local right:Bool
		Local up:Bool
		Local down:Bool
		Local currentLength:Int
		Local foundEnd:Bool = False

		While (neighbors.Length() > 0)
			current = neighbors
			neighbors = New Stack<Int>()

			i = 0
			currentLength = current.Length()

			While (i < currentLength)
				currentIndex = current.Get(i)

				If (currentIndex = endIndex) Then
					foundEnd = True
					neighbors.Clear()
					Exit
				End If

				left = currentIndex Mod widthInTiles > 0
				right = currentIndex Mod widthInTiles < widthInTiles - 1
				up = currentIndex / widthInTiles > 0
				down = currentIndex / widthInTiles < heightInTiles - 1

				Local index:Int

				If (up) Then
					index = currentIndex - widthInTiles

					If (distances[index] = -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (right) Then
					index = currentIndex + 1

					If (distances[index] = -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (down) Then
					index = currentIndex + widthInTiles

					If (distances[index] = -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (left) Then
					index = currentIndex - 1

					If (distances[index] = -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (up And right) Then
					index = currentIndex - widthInTiles + 1

					If (distances[index] = -1 And distances[currentIndex - widthInTiles] >= -1 And distances[currentIndex + 1] >= -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (right And down) Then
					index = currentIndex + widthInTiles + 1

					If (distances[index] = -1 And distances[currentIndex + widthInTiles] >= -1 And distances[currentIndex + 1] >= -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (left And down) Then
					index = currentIndex + widthInTiles - 1

					If (distances[index] = -1 And distances[currentIndex + widthInTiles] >= -1 And distances[currentIndex - 1] >= -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				If (up And left) Then
					index = currentIndex - widthInTiles - 1

					If (distances[index] = -1 And distances[currentIndex - widthInTiles] >= -1 And distances[currentIndex - 1] >= -1) Then
						distances[index] = distance
						neighbors.Push(index)
					End If
				End If

				i += 1
			Wend

			distance += 1
		Wend

		If (Not foundEnd) distances = []

		Return distances
	End Method

	Method _WalkPath:Void(data:Int[], start:Int, points:Stack<FlxPoint>)
		points.Push(New FlxPoint(x + Int(start Mod widthInTiles) * _tileWidth + _tileWidth * .5, y + Int(start / widthInTiles) * _tileHeight + _tileHeight * .5))
		If (data[start] = 0) Return

		Local left:Bool = start Mod widthInTiles > 0
		Local right:Bool = start Mod widthInTiles < widthInTiles - 1
		Local up:Bool = start / widthInTiles > 0
		Local down:Bool = start / widthInTiles < heightInTiles - 1

		Local current:Int = data[start]
		Local i:Int

		If (up) Then
			i = start - widthInTiles

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (right) Then
			i = start + 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (down) Then
			i = start + widthInTiles

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (left) Then
			i = start - 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (up And right) Then
			i = start - widthInTiles + 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (right And down) Then
			i = start + widthInTiles + 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (left And down) Then
			i = start + widthInTiles - 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If

		If (up And left) Then
			i = start - widthInTiles - 1

			If (data[i] >= 0 And data[i] < current) Then
				_WalkPath(data, i, points)
				Return
			End If
		End If
	End Method

	Method _AutoTile:Void(index:Int)
		If (_data[index] = 0) Return

		_data[index] = 0

		If ( (index - widthInTiles < 0) Or (_data[index - widthInTiles] > 0)) Then
			_data[index] += 1
		End If

		If ( (index Mod widthInTiles >= widthInTiles - 1) Or (_data[index + 1] > 0)) Then
			_data[index] += 2
		End If

		If ( (index + widthInTiles >= totalTiles) Or (_data[index + widthInTiles] > 0)) Then
			_data[index] += 4
		End If

		If ( (index Mod widthInTiles <= 0) Or (_data[index - 1] > 0)) Then
			_data[index] += 8
		End If

		If (auto = ALT And _data[index] = 15) Then
			If ( (index Mod widthInTiles > 0) And (index + widthInTiles < totalTiles) And (_data[index + widthInTiles - 1] <= 0)) Then
				_data[index] = 1
			End If

			If ( (index Mod widthInTiles > 0) And (index - widthInTiles >= 0) And (_data[index - widthInTiles - 1] <= 0)) Then
				_data[index] = 2
			End If

			If ( (index Mod widthInTiles < widthInTiles - 1) And (index - widthInTiles >= 0) And (_data[index - widthInTiles + 1] <= 0)) Then
				_data[index] = 4
			End If

			If ( (index Mod widthInTiles < widthInTiles - 1) And (index + widthInTiles < totalTiles) And (_data[index + widthInTiles + 1] <= 0)) Then
				_data[index] = 8
			End If
		End If

		_data[index] += 1
	End Method

	Method _UpdateTile:Void(index:Int)
		Local tile:FlxTile = _tileObjects[_data[index]]

		If (tile = Null Or Not tile.visible) Then
			_rects[index] = Null
			Return
		End If

		Local rx:Int = (_data[index] - _startingIndex) * _tileWidth
		Local ry:Int = 0

		If (rx >= _tiles.Width()) Then
			ry = Int(rx / _tiles.Width()) * _tileHeight
			rx Mod= _tiles.Width()
		End If

		_rects[index] = New FlxRect(rx, ry, _tileWidth, _tileHeight)
	End Method

End Class

Private
Class FlxTileLoader Extends FlxResourceLoader<Image>

	Method Load:Image(name:String)
		Return LoadImage(FlxAssetsManager.GetImagePath(name))
	End Method

End Class


Added CircleOverlap and CircleRectsOverlap

#Rem
Copyright (c) 2011 Steve Revill and Shane Woolcock
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#End

Import mojo
Import framework
Import assert
Import vector2d
Import externfunctions
Import constants

#If TARGET<>"html5" Then
	Function GetBrowserName:String()
		Return ""
	End
	Function GetBrowserVersion:String()
		Return ""
	End
	Function GetBrowserOS:String()
		Return ""
	End
	#If TARGET<>"flash" Then
		Function GetCurrentURL:String()
			Return ""
		End
	#End
#End

#If LANG <> "java" Then
	Function BuildString:String(arr:Int[], offset:Int, length:Int)
		If offset<0 Or length<=0 Or offset+length > arr.Length Then Return ""
		Local rv:String = String.FromChar(arr[offset])
		For Local i:Int = offset+1 Until offset+length
			rv += String.FromChar(arr[i])
		Next
		Return rv
	End
#End

#If TARGET="xna" Or TARGET="psm" Then
	Private
	Global seekMusicCalled:Bool = False
	Public
	Function SeekMusic:Int(timeMillis:Int)
		If Not seekMusicCalled Then
			seekMusicCalled = True
			Print "Warning: SeekMusic is not implemented in XNA and PSM!"
		End
		Return 1
	End
#End

Function FlushKeys:Void()
	ResetInput()
End

Function GetUpdateRate:Int()
	Return UpdateRate()
End

Function LaunchBrowser(address:String, openNewWindow:Bool = True)
	Local windowName:String = "_self"
	If openNewWindow
		windowName = "_blank"
	End
	LaunchNativeBrowser(address, windowName)
End

Function SetGraphics:Void(w:Int, h:Int)
	SetNativeGraphicsSize(w, h)
	DEVICE_WIDTH = w
	DEVICE_HEIGHT = h
	SCREEN_HEIGHT = h
	SCREEN_WIDTH = w
	SCREEN_WIDTH2 = SCREEN_WIDTH / 2
	SCREEN_HEIGHT2 = SCREEN_HEIGHT / 2
End

Function ExitApp:Void()
	#If TARGET="win8"
		Print "Cant exit a Win8 application"
	#Else
		EndApp()
	#End
End

Function GetDayOfMonth:Int()
	 Local date:=GetDate()
	 Local day:=date[2]
	 Return day
End

Function GetDayOfWeek:Int()
	Print "GetDayOfWeek is not supported returning -1"
	Print "GetDayOfWeek will be removed in future versions!!!"
	Return -1
End

Function GetMonth:Int()
	Local date:=GetDate()
	Local month:=date[1]
	Return month
End

Function GetYear:Int()
	Local date:=GetDate()
	Local year:=date[0]
	Return year
End

Function GetHours:Int()
	Local date:=GetDate()
	Local hour:=date[3]
	Return hour
End

Function GetMinutes:Int()
	Local date:=GetDate()
	Local min:=date[4]
	Return min
End

Function GetSeconds:Int()
	Local date:=GetDate()
	Local sec:=date[5]
	Return sec
End

Function GetMilliSeconds:Int()
	Local date:=GetDate()
	Local msec:=date[6]
	Return msec
End

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End

Function DrawRectOutline:Void(x:Int, y:Int, w:Int, h:Int)
	w -= 1
	h -= 1
	DrawLine(x, y, x + w, y)
	DrawLine(x + w, y, x + w, y + h)
	DrawLine(x + w, y + h, x, y + h)
	DrawLine(x, y + h, x, y)
End

Function CircleOverlap:Bool(x1:Float, y1:Float, r1:Float, x2:Float, y2:Float, r2:Float)
	dx = x1 - x2
	dy = y1 - y2
	r = r1 + r2
	If dx * dx + dy * dy <= r * r Then ' collided
		Return True
	End
	Return False
End

Function CircleRectsOverlap:Bool(x1:Float, y1:Float, w1:Float, h1:Float, cx:Float, cy:Float, r:Float)
	Local testX:Float = cx
	Local testY:Float = cy
	If testX < x1 Then testX = x1
	If testX > (x1 + w1) Then testX = (x1 + w1)
	If testY < y1 Then testY = y1
	If testY > (y1 + h1) Then testY = (y1 + h1)
	Return ( (cx - testX) * (cx - testX) + (cy - testY) * (cy - testY)) < r * r
End

Function LoadBitmap:Image(path$, flags%=0)
	Local pointer:Image = LoadImage(path, 1, flags)

	AssertNotNull(pointer, "Error loading bitmap "+path)

   	Return pointer
End

Function LoadAnimBitmap:Image(path$, w%, h%, count%, tmpImage:Image)
	'tmpImage = loadBitmap(path) <-- This creates another image, decided to just copy the code here
	tmpImage = LoadImage(path)

	AssertNotNull(tmpImage, "Error loading bitmap "+path)

	Local pointer:Image = tmpImage.GrabImage( 0, 0, w, h, count, Image.MidHandle)

   	Return pointer
End

Function LoadSoundSample:Sound(path$)
	Local pointer:Sound = LoadSound(path)
	AssertNotNull(pointer, "Error loading sound "+path)
	Return pointer
End

Function FormatNumeric:String(value:Float)
	Local i:Int,s:String,ns:String,k:Int
	Local os:String
	s=String(value)
	os=s
	Local pos:Int=s.Length()
	If s.Find(".")>0 pos=s.Find(".") Else os=""
	For i=pos To 1 Step -1
		If k>2 ns+="." k=0
		k+=1
		ns=ns+Mid(s,i,1)
	Next
	s=""
	For i= ns.Length() To 1 Step -1
		s+=Mid(ns,i,1)
	Next
	If os<>"" s=s+","+os[pos+1..]
	Return s
End

Function Left$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[..n]
End

Function Right$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[str.Length()-n..]
End

Function LSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=str+rep
	Return str[..n]
End

Function RSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=rep+str
	Return str[str.Length()-n..]
End

Function Mid$( str$,pos:Int,size:Int=-1 )
	If pos>str.Length() Return ""
	pos-=1
	If( size<0 ) Return str[pos..]
	If pos<0 size=size+pos pos=0
	If pos+size>str.Length() size=str.Length()-pos
	Return str[pos..pos+size]
End

Function StripDir$( path$ )
	Local i:=path.FindLast( "/" )
	If i<>-1 Return path[i+1..]
	Return path
End

Function StripExt$( path$ )
	Local i:=path.FindLast( "." )
	If i<>-1 And path.Find( "/",i+1 )=-1 Return path[..i]
	Return path
End

Function StripAll$( path$ )
	Return StripDir( StripExt( path ) )
End

Function Round%(flot#)
	Return Floor(flot+0.5)
End

'summary: returns an angle between two points
Function CalcAngle:Float(x1:Float, y1:Float, x2:Float, y2:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return (ATan2(dy, dx) + 360) Mod 360
End

'summary: Calculate distance between two points - overload using x,y
Function CalcDistance:Float(x1:Float, y1:Float, x2:Float, y2:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return Sqrt(dx * dx + dy * dy)
End

Function PointInSpot:Int(x1:Float, y1:Float, x2:Float, y2:Float, radius:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return dx * dx + dy * dy <= radius * radius
End

Function AnyInputPressed:Bool()
	For Local i:Int = 0 To 511
		If KeyHit(i) Then Return True
	Next
	Return False
End

Function FormatNumber:String(number:Float, decimal:Int=4, comma:Int=0, padleft:Int=0 )
	Assert(decimal > -1 And comma > -1 And padleft > -1, "Negative numbers not allowed in FormatNumber()")

	Local str:String = number
	Local dl:Int = str.Find(".")
	If decimal = 0 Then decimal = -1
	str = str[..dl+decimal+1]

	If comma
		While dl>comma
			str = str[..dl-comma] + "," + str[dl-comma..]
			dl -= comma
		Wend
	End

	If padleft
		Local paddedLength:Int = padleft+decimal+1
		If paddedLength < str.Length Then str = "Error"
		str = RSet(str,paddedLength)
	End
	Return str
End

Function IsWhitespace:Bool(str:String)
	Return str = "~t" Or str = "~n" Or str = "~r" Or str = " "
End

Function IsWhitespace:Bool(val:Int)
	Return val = ASC_TAB Or val = ASC_LF Or val = ASC_CR Or val = ASC_SPACE
End

Function Interpolate:Float(type:Int, startValue:Float, endValue:Float, alpha:Float)
	Local range:Float = endValue-startValue
	Local rv:Float = 0
	Select type
		Case INTERPOLATION_LINEAR
			rv = startValue + range*alpha

		Case INTERPOLATION_INVERSE_LINEAR
			rv = startValue + range - range*alpha

		Case INTERPOLATION_HALF_SINE
			rv = startValue + range * Sinr(alpha*PI)

		Case INTERPOLATION_HALF_COSINE
			rv = startValue + range * Cosr(alpha*PI)
	End
	' clip to start/end
	If startValue < endValue And rv < startValue Or startValue > endValue And rv > startValue Then
		rv = startValue
	Elseif startValue < endValue And rv > endValue Or startValue > endValue And rv < endValue Then
		rv = endValue
	End
	Return rv
End

' arghhhh monkey needs enums!!!
Function InterpolationFromString:Int(interp:String)
	If interp = "" Or interp = "none" Then Return INTERPOLATION_NONE
	If interp = "linear" Then Return INTERPOLATION_LINEAR
	If interp = "inverselinear" Then Return INTERPOLATION_INVERSE_LINEAR
	If interp = "halfsine" Then Return INTERPOLATION_HALF_SINE
	If interp = "halfcosine" Then Return INTERPOLATION_HALF_COSINE
End

' colour conversions (hsl is range 0-1, return is RGB as a single int)
' Monkey conversion of http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
Function HSLtoRGB:Int(hue:Float, saturation:Float, luminance:Float, rgbArray:Int[] = [])
	Local r:Float = luminance, g:Float = luminance, b:Float = luminance
	Local v:Float = 0
	If luminance <= 0.5 Then
		v = luminance * (1.0 + saturation)
	Else
		v = luminance + saturation - luminance * saturation
	End
	If v > 0 Then
		Local m:Float = luminance + luminance - v
		Local sv:Float = (v - m) / v
		hue *= 6
		Local sextant:Int = Int(hue)
		Local fract:Float = hue - sextant
		Local vsf:Float = v * sv * fract
		Local mid1:Float = m + vsf
		Local mid2:Float = v - vsf

		Select sextant
			Case 0
				r = v
				g = mid1
				b = m

			Case 1
				r = mid2
				g = v
				b = m

			Case 2
				r = m
				g = v
				b = mid1

			Case 3
				r = m
				g = mid2
				b = v

			Case 4
				r = mid1
				g = m
				b = v

			Case 5
				r = v
				g = m
				b = mid2
		End
	End
	If rgbArray.Length = 3 Then
		rgbArray[0] = Int(r*255)
		rgbArray[1] = Int(g*255)
		rgbArray[2] = Int(b*255)
	End
	Return $ff000000 | (Int(r*255) Shl 16) | (Int(g*255) Shl 8) | (Int(b*255) Shl 0)
End

' colour conversions (rgb is 0-255, return is a float array, reusing the hslvals array if it was big enough)
' Monkey conversion of http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
Function RGBtoHSL:Float[](red:Int, green:Int, blue:Int, hslvals:Float[] = [])
	If hslvals.Length <> 3 Then hslvals = New Float[3]
	Local r:Float = red/255.0, g:Float = green/255.0, b:Float = blue/255.0
	hslvals[0] = 0
	hslvals[1] = 0
	hslvals[2] = 0

	' calculate luminance
	Local v:Float = Max(Max(r,g),b)
	Local m:Float = Min(Min(r,g),b)
	hslvals[2] = (m + v) / 2.0
	' die if it's black
	If hslvals[2] <= 0 Then Return hslvals

	' precalculate saturation
	Local vm:Float = v - m
	hslvals[1] = vm
	' die if it's grey
	If hslvals[1] <= 0 Then Return hslvals

	' finish saturation
	If hslvals[2] <= 0.5 Then
		hslvals[1] /= v + m
	Else
		hslvals[1] /= 2 - v - m
	End

	Local r2:Float = (v - r) / vm
	Local g2:Float = (v - g) / vm
	Local b2:Float = (v - b) / vm
	If r = v Then
		If g = m Then hslvals[0] = 5 + b2 Else hslvals[0] = 1 - g2
	Elseif g = v Then
		If b = m Then hslvals[0] = 1 + r2 Else hslvals[0] = 3 - b2
	Else
		If r = m Then hslvals[0] = 3 + g2 Else hslvals[0] = 5 - r2
	End
	hslvals[0] /= 6.0

	Return hslvals
End

Function DecToHex:String(dec:Int)
	Local rv:String = ""
	If dec <= 0 Then Return "0"
	While dec > 0
		If dec Mod 16 < 10 Then
			rv += dec Mod 16
		Else
			rv += String.FromChar(ASC_LOWER_A+(dec Mod 16)-10)
		End
		dec = dec / 16
	End
	Return rv
End

Function HexToDec:Int(hx:String)
	Local rv:Int = 0
	Local lookup:String = "0123456789abcdef"
	hx = hx.ToLower()
	For Local i:Int = 0 Until hx.Length()
		rv *= 16
		Local idx:Int = lookup.Find(hx[i..i+1])
		If idx < 0 Then Error("Error parsing Hex string!")
		rv += idx
	Next
	Return rv
End

Function DrawLineThick(x1:Int, y1:Int, x2:Int, y2:Int, thinkness:Int = 2)
	Local steep:Bool = Abs(y2 - y1) > Abs(x2 - x1)
	Local swapped:Bool = False

	If steep
		Local tmp:Int = x1
		x1 = y1
		y1 = tmp

		tmp = x2
		x2 = y2
		y2 = tmp
	End

	If x1 > x2
		Local t:Float = x1
		x1 = x2
		x2 = t
		t = y1
		y1 = y2
		y2 = t
		swapped = True
	End

	Local deltax:Float = x2 - x1
	Local deltay:Float = Abs(y2 - y1)
	Local error:Float = deltax / 2
	Local ystep:Float
	Local y:Float = y1

	If y1 < y2 Then
		ystep = 1
	Else
		ystep = -1
	End
	Local offset:Int = thinkness / 2
	For Local x:Int = x1 Until x2
		If steep
			DrawRect(y - offset, x - offset, thinkness, thinkness)
		Else
			DrawRect(x - offset, y - offset, thinkness, thinkness)
		End

		error -= deltay
		If error < 0
			y += ystep
			error += deltax
		End
	Next
End

Function BresenhamLine:Vector2D[](startPos:Vector2D, endPos:Vector2D)
	Local points:Stack<Vector2D> = New Stack<Vector2D>
	Local steep:Bool = Abs(endPos.y - startPos.y) > Abs(endPos.x - startPos.x)
	Local swapped:Bool = False

	If steep
		startPos.SwapXY(startPos.x, startPos.y)
		endPos.SwapXY(endPos.x, endPos.y)
	End

	If startPos.x > endPos.x
		Local t:Float = startPos.x
		startPos.x = endPos.x
		endPos.x = t
		t = startPos.y
		startPos.y = endPos.y
		endPos.y = t
		swapped = True
	End

	Local deltax:Float = endPos.x - startPos.x
	Local deltay:Float = Abs(endPos.y - startPos.y)
	Local error:Float = deltax / 2
	Local ystep:Float
	Local y:Float = startPos.y

	If startPos.y < endPos.y Then
		ystep = 1
	Else
		ystep = -1
	End

	For Local x:Int = startPos.x Until endPos.x
		If steep
			points.Push(New Vector2D(y, x))
		Else
			points.Push(New Vector2D(x, y))
		End

		error -= deltay
		If error < 0
			y += ystep
			error += deltax
		End
	Next
	Local arr:Vector2D[] = points.ToArray()
	If swapped
		Arrays < Vector2D >.Reverse(arr)
	End
	Return arr
End

Private
Global screenshotPixels:Int[] ' this is globally cached for performance
Public
' summary: Takes a screenshot of a certain rectangle on the screen.  Defaults to the entire screen.
Function CreateScreenshot:Image(x%=0, y%=0, width%=0, height%=0)
	' if no width passed in, assume the whole width
	If width <= 0 Then
		width = DeviceWidth()
		x = 0
	End

	' if no height passed in, assume the whole height
	If height <= 0 Then
		height = DeviceHeight()
		y = 0
	End

	' check that the rectangle is entirely within the screen bounds
	If x < 0 Or y < 0 Or x + width > DeviceWidth() Or y + height > DeviceHeight() Then Return Null

	' create an image
	Local image:Image = CreateImage(width, height)

	' read the screen
	If screenshotPixels.Length <> width*height Then screenshotPixels = New Int[width*height]
	ReadPixels(screenshotPixels, x, y, width, height)
	image.WritePixels(screenshotPixels, 0, 0, width, height)

	Return image
End

'summary: Returns a random int in the range low (inclusive) to high (exclusive).
Function Rand:Int (low:Int, high:Int)
	Local v:Float = Rnd(low, high)
	if (v < 0) Then v -= 1.0
	Local vi:Int = Int(v)
	if vi = (low - 1) Then vi = Min (-1, high)
	Return vi
End

'summary: Converts Mask Pixel Color to Transparent Pixel
Function PixelArrayMask:Void(pixels:Int[], maskRed:Int = 0, maskGreen:Int = 0, maskBlue:Int = 0)
	For Local i:Int = 0 Until pixels.Length
		Local argb:Int = pixels[i]
		Local a:Int = (argb Shr 24) & $ff
		Local r:Int = (argb Shr 16) & $ff
		Local g:Int = (argb Shr 8) & $ff
		Local b:Int = argb & $ff

		If a = 255 And r = maskRed And g = maskGreen And b = maskBlue
			a = 0
			argb = (a Shl 24) | (r Shl 16) | (g Shl 8) | b
			pixels[i] = argb
		End
	Next
End

Function Lerp:Float(startValue:Float, endValue:Float, progress:Float)
	Return startValue + (endValue-startValue) * progress
End

Function InterpolateWithEase:Float(startValue:Float, endValue:Float, progress:Float, ease:Int)
	If progress <= 0 Then Return startValue
	If progress >= 1 Then Return endValue
	Select ease
		Case EASE_IN_DOUBLE
			Return Lerp(endValue, startValue, (1-progress)*(1-progress)*(1-progress)*(1-progress))
		Case EASE_IN
			Return Lerp(endValue, startValue, (1-progress)*(1-progress))
		Case EASE_IN_HALF
			Return Lerp(endValue, startValue, Pow(1-progress, 1.5))
		Case EASE_OUT
			Return Lerp(startValue, endValue, progress * progress)
		Case EASE_OUT_HALF
			Return Lerp(startValue, endValue, Pow(progress, 1.5))
		Case EASE_OUT_DOUBLE
			Return Lerp(startValue, endValue, progress*progress*progress*progress)
		Case EASE_IN_OUT
			Return startValue + (-2*(progress*progress*progress) + 3*(progress*progress)) * (endValue - startValue)
		Default
			Return Lerp(startValue, endValue, progress);
	End
End

#Rem
Copyright (c) 2011 Steve Revill and Shane Woolcock
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#End

Import mojo
Import framework
Import assert
Import vector2d
Import externfunctions
Import constants

#If TARGET<>"html5" Then
	Function GetBrowserName:String()
		Return ""
	End
	Function GetBrowserVersion:String()
		Return ""
	End
	Function GetBrowserOS:String()
		Return ""
	End
	#If TARGET<>"flash" Then
		Function GetCurrentURL:String()
			Return ""
		End
	#End
#End

#If LANG <> "java" Then
	Function BuildString:String(arr:Int[], offset:Int, length:Int)
		If offset<0 Or length<=0 Or offset+length > arr.Length Then Return ""
		Local rv:String = String.FromChar(arr[offset])
		For Local i:Int = offset+1 Until offset+length
			rv += String.FromChar(arr[i])
		Next
		Return rv
	End
#End

#If TARGET="xna" Or TARGET="psm" Then
	Private
	Global seekMusicCalled:Bool = False
	Public
	Function SeekMusic:Int(timeMillis:Int)
		If Not seekMusicCalled Then
			seekMusicCalled = True
			Print "Warning: SeekMusic is not implemented in XNA and PSM!"
		End
		Return 1
	End
#End

Function FlushKeys:Void()
	ResetInput()
End

Function GetUpdateRate:Int()
	Return UpdateRate()
End

Function LaunchBrowser(address:String, openNewWindow:Bool = True)
	Local windowName:String = "_self"
	If openNewWindow
		windowName = "_blank"
	End
	LaunchNativeBrowser(address, windowName)
End

Function SetGraphics:Void(w:Int, h:Int)
	SetNativeGraphicsSize(w, h)
	DEVICE_WIDTH = w
	DEVICE_HEIGHT = h
	SCREEN_HEIGHT = h
	SCREEN_WIDTH = w
	SCREEN_WIDTH2 = SCREEN_WIDTH / 2
	SCREEN_HEIGHT2 = SCREEN_HEIGHT / 2
End

Function ExitApp:Void()
	#If TARGET="win8"
		Print "Cant exit a Win8 application"
	#Else
		EndApp()
	#End
End

Function GetDayOfMonth:Int()
	 Local date:=GetDate()
	 Local day:=date[2]
	 Return day
End

Function GetDayOfWeek:Int()
	Print "GetDayOfWeek is not supported returning -1"
	Print "GetDayOfWeek will be removed in future versions!!!"
	Return -1
End

Function GetMonth:Int()
	Local date:=GetDate()
	Local month:=date[1]
	Return month
End

Function GetYear:Int()
	Local date:=GetDate()
	Local year:=date[0]
	Return year
End

Function GetHours:Int()
	Local date:=GetDate()
	Local hour:=date[3]
	Return hour
End

Function GetMinutes:Int()
	Local date:=GetDate()
	Local min:=date[4]
	Return min
End

Function GetSeconds:Int()
	Local date:=GetDate()
	Local sec:=date[5]
	Return sec
End

Function GetMilliSeconds:Int()
	Local date:=GetDate()
	Local msec:=date[6]
	Return msec
End

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End

Function DrawRectOutline:Void(x:Int, y:Int, w:Int, h:Int)
	w -= 1
	h -= 1
	DrawLine(x, y, x + w, y)
	DrawLine(x + w, y, x + w, y + h)
	DrawLine(x + w, y + h, x, y + h)
	DrawLine(x, y + h, x, y)
End

Function LoadBitmap:Image(path$, flags%=0)
	Local pointer:Image = LoadImage(path, 1, flags)

	AssertNotNull(pointer, "Error loading bitmap "+path)

   	Return pointer
End

Function LoadAnimBitmap:Image(path$, w%, h%, count%, tmpImage:Image)
	'tmpImage = loadBitmap(path) <-- This creates another image, decided to just copy the code here
	tmpImage = LoadImage(path)

	AssertNotNull(tmpImage, "Error loading bitmap "+path)

	Local pointer:Image = tmpImage.GrabImage( 0, 0, w, h, count, Image.MidHandle)

   	Return pointer
End

Function LoadSoundSample:Sound(path$)
	Local pointer:Sound = LoadSound(path)
	AssertNotNull(pointer, "Error loading sound "+path)
	Return pointer
End

Function FormatNumeric:String(value:Float)
	Local i:Int,s:String,ns:String,k:Int
	Local os:String
	s=String(value)
	os=s
	Local pos:Int=s.Length()
	If s.Find(".")>0 pos=s.Find(".") Else os=""
	For i=pos To 1 Step -1
		If k>2 ns+="." k=0
		k+=1
		ns=ns+Mid(s,i,1)
	Next
	s=""
	For i= ns.Length() To 1 Step -1
		s+=Mid(ns,i,1)
	Next
	If os<>"" s=s+","+os[pos+1..]
	Return s
End

Function Left$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[..n]
End

Function Right$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[str.Length()-n..]
End

Function LSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=str+rep
	Return str[..n]
End

Function RSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=rep+str
	Return str[str.Length()-n..]
End

Function Mid$( str$,pos:Int,size:Int=-1 )
	If pos>str.Length() Return ""
	pos-=1
	If( size<0 ) Return str[pos..]
	If pos<0 size=size+pos pos=0
	If pos+size>str.Length() size=str.Length()-pos
	Return str[pos..pos+size]
End

Function StripDir$( path$ )
	Local i:=path.FindLast( "/" )
	If i<>-1 Return path[i+1..]
	Return path
End

Function StripExt$( path$ )
	Local i:=path.FindLast( "." )
	If i<>-1 And path.Find( "/",i+1 )=-1 Return path[..i]
	Return path
End

Function StripAll$( path$ )
	Return StripDir( StripExt( path ) )
End

Function Round%(flot#)
	Return Floor(flot+0.5)
End

'summary: returns an angle between two points
Function CalcAngle:Float(x1:Float, y1:Float, x2:Float, y2:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return (ATan2(dy, dx) + 360) Mod 360
End

'summary: Calculate distance between two points - overload using x,y
Function CalcDistance:Float(x1:Float, y1:Float, x2:Float, y2:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return Sqrt(dx * dx + dy * dy)
End

Function PointInSpot:Int(x1:Float, y1:Float, x2:Float, y2:Float, radius:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return dx * dx + dy * dy <= radius * radius
End

Function AnyInputPressed:Bool()
	For Local i:Int = 0 To 511
		If KeyHit(i) Then Return True
	Next
	Return False
End

Function FormatNumber:String(number:Float, decimal:Int=4, comma:Int=0, padleft:Int=0 )
	Assert(decimal > -1 And comma > -1 And padleft > -1, "Negative numbers not allowed in FormatNumber()")

	Local str:String = number
	Local dl:Int = str.Find(".")
	If decimal = 0 Then decimal = -1
	str = str[..dl+decimal+1]

	If comma
		While dl>comma
			str = str[..dl-comma] + "," + str[dl-comma..]
			dl -= comma
		Wend
	End

	If padleft
		Local paddedLength:Int = padleft+decimal+1
		If paddedLength < str.Length Then str = "Error"
		str = RSet(str,paddedLength)
	End
	Return str
End

Function IsWhitespace:Bool(str:String)
	Return str = "~t" Or str = "~n" Or str = "~r" Or str = " "
End

Function IsWhitespace:Bool(val:Int)
	Return val = ASC_TAB Or val = ASC_LF Or val = ASC_CR Or val = ASC_SPACE
End

Function Interpolate:Float(type:Int, startValue:Float, endValue:Float, alpha:Float)
	Local range:Float = endValue-startValue
	Local rv:Float = 0
	Select type
		Case INTERPOLATION_LINEAR
			rv = startValue + range*alpha

		Case INTERPOLATION_INVERSE_LINEAR
			rv = startValue + range - range*alpha

		Case INTERPOLATION_HALF_SINE
			rv = startValue + range * Sinr(alpha*PI)

		Case INTERPOLATION_HALF_COSINE
			rv = startValue + range * Cosr(alpha*PI)
	End
	' clip to start/end
	If startValue < endValue And rv < startValue Or startValue > endValue And rv > startValue Then
		rv = startValue
	Elseif startValue < endValue And rv > endValue Or startValue > endValue And rv < endValue Then
		rv = endValue
	End
	Return rv
End

' arghhhh monkey needs enums!!!
Function InterpolationFromString:Int(interp:String)
	If interp = "" Or interp = "none" Then Return INTERPOLATION_NONE
	If interp = "linear" Then Return INTERPOLATION_LINEAR
	If interp = "inverselinear" Then Return INTERPOLATION_INVERSE_LINEAR
	If interp = "halfsine" Then Return INTERPOLATION_HALF_SINE
	If interp = "halfcosine" Then Return INTERPOLATION_HALF_COSINE
End

' colour conversions (hsl is range 0-1, return is RGB as a single int)
' Monkey conversion of http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
Function HSLtoRGB:Int(hue:Float, saturation:Float, luminance:Float, rgbArray:Int[] = [])
	Local r:Float = luminance, g:Float = luminance, b:Float = luminance
	Local v:Float = 0
	If luminance <= 0.5 Then
		v = luminance * (1.0 + saturation)
	Else
		v = luminance + saturation - luminance * saturation
	End
	If v > 0 Then
		Local m:Float = luminance + luminance - v
		Local sv:Float = (v - m) / v
		hue *= 6
		Local sextant:Int = Int(hue)
		Local fract:Float = hue - sextant
		Local vsf:Float = v * sv * fract
		Local mid1:Float = m + vsf
		Local mid2:Float = v - vsf

		Select sextant
			Case 0
				r = v
				g = mid1
				b = m

			Case 1
				r = mid2
				g = v
				b = m

			Case 2
				r = m
				g = v
				b = mid1

			Case 3
				r = m
				g = mid2
				b = v

			Case 4
				r = mid1
				g = m
				b = v

			Case 5
				r = v
				g = m
				b = mid2
		End
	End
	If rgbArray.Length = 3 Then
		rgbArray[0] = Int(r*255)
		rgbArray[1] = Int(g*255)
		rgbArray[2] = Int(b*255)
	End
	Return $ff000000 | (Int(r*255) Shl 16) | (Int(g*255) Shl 8) | (Int(b*255) Shl 0)
End

' colour conversions (rgb is 0-255, return is a float array, reusing the hslvals array if it was big enough)
' Monkey conversion of http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
Function RGBtoHSL:Float[](red:Int, green:Int, blue:Int, hslvals:Float[] = [])
	If hslvals.Length <> 3 Then hslvals = New Float[3]
	Local r:Float = red/255.0, g:Float = green/255.0, b:Float = blue/255.0
	hslvals[0] = 0
	hslvals[1] = 0
	hslvals[2] = 0

	' calculate luminance
	Local v:Float = Max(Max(r,g),b)
	Local m:Float = Min(Min(r,g),b)
	hslvals[2] = (m + v) / 2.0
	' die if it's black
	If hslvals[2] <= 0 Then Return hslvals

	' precalculate saturation
	Local vm:Float = v - m
	hslvals[1] = vm
	' die if it's grey
	If hslvals[1] <= 0 Then Return hslvals

	' finish saturation
	If hslvals[2] <= 0.5 Then
		hslvals[1] /= v + m
	Else
		hslvals[1] /= 2 - v - m
	End

	Local r2:Float = (v - r) / vm
	Local g2:Float = (v - g) / vm
	Local b2:Float = (v - b) / vm
	If r = v Then
		If g = m Then hslvals[0] = 5 + b2 Else hslvals[0] = 1 - g2
	Elseif g = v Then
		If b = m Then hslvals[0] = 1 + r2 Else hslvals[0] = 3 - b2
	Else
		If r = m Then hslvals[0] = 3 + g2 Else hslvals[0] = 5 - r2
	End
	hslvals[0] /= 6.0

	Return hslvals
End

Function DecToHex:String(dec:Int)
	Local rv:String = ""
	If dec <= 0 Then Return "0"
	While dec > 0
		If dec Mod 16 < 10 Then
			rv += dec Mod 16
		Else
			rv += String.FromChar(ASC_LOWER_A+(dec Mod 16)-10)
		End
		dec = dec / 16
	End
	Return rv
End

Function HexToDec:Int(hx:String)
	Local rv:Int = 0
	Local lookup:String = "0123456789abcdef"
	hx = hx.ToLower()
	For Local i:Int = 0 Until hx.Length()
		rv *= 16
		Local idx:Int = lookup.Find(hx[i..i+1])
		If idx < 0 Then Error("Error parsing Hex string!")
		rv += idx
	Next
	Return rv
End

Function DrawLineThick(x1:Int, y1:Int, x2:Int, y2:Int, thinkness:Int = 2)
	Local steep:Bool = Abs(y2 - y1) > Abs(x2 - x1)
	Local swapped:Bool = False

	If steep
		Local tmp:Int = x1
		x1 = y1
		y1 = tmp

		tmp = x2
		x2 = y2
		y2 = tmp
	End

	If x1 > x2
		Local t:Float = x1
		x1 = x2
		x2 = t
		t = y1
		y1 = y2
		y2 = t
		swapped = True
	End

	Local deltax:Float = x2 - x1
	Local deltay:Float = Abs(y2 - y1)
	Local error:Float = deltax / 2
	Local ystep:Float
	Local y:Float = y1

	If y1 < y2 Then
		ystep = 1
	Else
		ystep = -1
	End
	Local offset:Int = thinkness / 2
	For Local x:Int = x1 Until x2
		If steep
			DrawRect(y - offset, x - offset, thinkness, thinkness)
		Else
			DrawRect(x - offset, y - offset, thinkness, thinkness)
		End

		error -= deltay
		If error < 0
			y += ystep
			error += deltax
		End
	Next
End

Function BresenhamLine:Vector2D[](startPos:Vector2D, endPos:Vector2D)
	Local points:Stack<Vector2D> = New Stack<Vector2D>
	Local steep:Bool = Abs(endPos.y - startPos.y) > Abs(endPos.x - startPos.x)
	Local swapped:Bool = False

	If steep
		startPos.SwapXY(startPos.x, startPos.y)
		endPos.SwapXY(endPos.x, endPos.y)
	End

	If startPos.x > endPos.x
		Local t:Float = startPos.x
		startPos.x = endPos.x
		endPos.x = t
		t = startPos.y
		startPos.y = endPos.y
		endPos.y = t
		swapped = True
	End

	Local deltax:Float = endPos.x - startPos.x
	Local deltay:Float = Abs(endPos.y - startPos.y)
	Local error:Float = deltax / 2
	Local ystep:Float
	Local y:Float = startPos.y

	If startPos.y < endPos.y Then
		ystep = 1
	Else
		ystep = -1
	End

	For Local x:Int = startPos.x Until endPos.x
		If steep
			points.Push(New Vector2D(y, x))
		Else
			points.Push(New Vector2D(x, y))
		End

		error -= deltay
		If error < 0
			y += ystep
			error += deltax
		End
	Next
	Local arr:Vector2D[] = points.ToArray()
	If swapped
		Arrays < Vector2D >.Reverse(arr)
	End
	Return arr
End

Private
Global screenshotPixels:Int[] ' this is globally cached for performance
Public
' summary: Takes a screenshot of a certain rectangle on the screen.  Defaults to the entire screen.
Function CreateScreenshot:Image(x%=0, y%=0, width%=0, height%=0)
	' if no width passed in, assume the whole width
	If width <= 0 Then
		width = DeviceWidth()
		x = 0
	End

	' if no height passed in, assume the whole height
	If height <= 0 Then
		height = DeviceHeight()
		y = 0
	End

	' check that the rectangle is entirely within the screen bounds
	If x < 0 Or y < 0 Or x + width > DeviceWidth() Or y + height > DeviceHeight() Then Return Null

	' create an image
	Local image:Image = CreateImage(width, height)

	' read the screen
	If screenshotPixels.Length <> width*height Then screenshotPixels = New Int[width*height]
	ReadPixels(screenshotPixels, x, y, width, height)
	image.WritePixels(screenshotPixels, 0, 0, width, height)

	Return image
End

'summary: Returns a random int in the range low (inclusive) to high (exclusive).
Function Rand:Int (low:Int, high:Int)
	Local v:Float = Rnd(low, high)
	if (v < 0) Then v -= 1.0
	Local vi:Int = Int(v)
	if vi = (low - 1) Then vi = Min (-1, high)
	Return vi
End

'summary: Converts Mask Pixel Color to Transparent Pixel
Function PixelArrayMask:Void(pixels:Int[], maskRed:Int = 0, maskGreen:Int = 0, maskBlue:Int = 0)
	For Local i:Int = 0 Until pixels.Length
		Local argb:Int = pixels[i]
		Local a:Int = (argb Shr 24) & $ff
		Local r:Int = (argb Shr 16) & $ff
		Local g:Int = (argb Shr 8) & $ff
		Local b:Int = argb & $ff

		If a = 255 And r = maskRed And g = maskGreen And b = maskBlue
			a = 0
			argb = (a Shl 24) | (r Shl 16) | (g Shl 8) | b
			pixels[i] = argb
		End
	Next
End

Function Lerp:Float(startValue:Float, endValue:Float, progress:Float)
	Return startValue + (endValue-startValue) * progress
End

Function InterpolateWithEase:Float(startValue:Float, endValue:Float, progress:Float, ease:Int)
	If progress <= 0 Then Return startValue
	If progress >= 1 Then Return endValue
	Select ease
		Case EASE_IN_DOUBLE
			Return Lerp(endValue, startValue, (1-progress)*(1-progress)*(1-progress)*(1-progress))
		Case EASE_IN
			Return Lerp(endValue, startValue, (1-progress)*(1-progress))
		Case EASE_IN_HALF
			Return Lerp(endValue, startValue, Pow(1-progress, 1.5))
		Case EASE_OUT
			Return Lerp(startValue, endValue, progress * progress)
		Case EASE_OUT_HALF
			Return Lerp(startValue, endValue, Pow(progress, 1.5))
		Case EASE_OUT_DOUBLE
			Return Lerp(startValue, endValue, progress*progress*progress*progress)
		Case EASE_IN_OUT
			Return startValue + (-2*(progress*progress*progress) + 3*(progress*progress)) * (endValue - startValue)
		Default
			Return Lerp(startValue, endValue, progress);
	End
End