Code archives/Algorithms/Position to Iso Index
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
| Hey, this is a solution I had a hard time to find myself, so I thought I'd share it with you. It basically allows you to convert from a 2d position to the x/y index of the underlying isometric tile. With a horrible amount of border cases to consider, you really won't wanna reinvent the wheel there. Feel free to use, modify, sell, marry my code :) Theres propably a dozen approaches, what I can claim about mine is that it is fast [O(1)], numerically stable, and last but not least working perfectly accurate. The parameters processed can be observed in the demo. Here is a screenshot of the testing application I wrote for the algorithm: Here is the coresponding code. Check out the <getTileIndizesAtPosition> Method to see the algorithm. Eventually part of the code originates from an upcoming engine of mine, so don't wonder about identifiers like "isoWorld" :) | |||||
SuperStrict
AppTitle = "Isometric Index Algorithm Demo"
Global TILESIZE:Int = 50
Global DEBUGINFO:Double[7] '[n,y,m,x,h,m_,n_]
Type isoWorld
Field _terrain:isoTerritory[][] '[y][x]
Field _scale:Double = TILESIZE
Field _tile_w:Int = 2
Field _tile_h:Int = 1
Field _width:Double = 500.0
Field _height:Double = 500.0
Method _initTerritory( divx_:Int, divy_:Int, load_instant_:Int )
Local w_:Double = _width/ Double divx_
Local h_:Double = _width/ Double divx_
Local m_:Int = w_/ _scale
Local n_:Int = h_/ _scale
_terrain = _terrain[..divy_ ]
For Local j_:Int = 0 Until divy_
_terrain[ j_ ] = _terrain[ j_][..divx_ ]
For Local i_:Int = 0 Until divx_
_terrain[ j_][i_ ] = isoTerritory.Create(Self, i_, j_, m_, n_, w_, h_)
Next
Next
End Method
Method getTileScale:Double()
Return _scale
End Method
Method getTileHWratio:Double()
Return (Double _tile_h)/(Double _tile_w)
End Method
Method getTerritory:isoTerritory( i_:Int, j_:Int )
If (j_< _terrain.Length) And (j_>= 0)
If (i_< _terrain[ j_ ].Length) And (i_>= 0)
Return _terrain[ j_][i_ ]
End If
End If
End Method
End Type
Type isoTerritory
Field _world:isoWorld
Field _w:Double
Field _h:Double
Field _i:Int
Field _j:Int
Field _objects:Object[][] '[x][y] TList|isoObject
Function Create:isoTerritory( world_:isoWorld, i_:Int, j_:Int, m_:Int, n_:Int, w_:Double, h_:Double )
Local ret_:isoTerritory = New isoTerritory
ret_._world = world_
ret_._w = w_
ret_._h = h_
ret_._i = i_
ret_._j = j_
'init object list arrays
ret_._objects = ret_._objects[..n_ ]
For Local k_:Int = 0 Until n_
ret_._objects[ k_ ] = ret_._objects[ k_ ][..m_ ]
Next
Return ret_
End Function
Method getNumTilesW:Int()
Return _objects[0].Length
End Method
Method getNumTilesH:Int()
Return _objects.Length
End Method
Function DoublesAreEqual:Int( d1_:Double, d2_:Double )
Return Abs(d1_- d2_)< .00001!
End Function
Method getTileIndizesAtPosition:Int( pos_:Double[], out_i_:Int Ptr, out_j_:Int Ptr ) 'returns false if no tile at that position. pos must be relative to the territory.
If pos_.Length> 1 'just to make it sure
Local halfsize_:Double = _world.getTileScale()/ 2
Local y_:Double = pos_[1] 'y pos
Local n_:Int = y_/ halfsize_ 'tile line index (presumption)
If n_ Then n_:- 1
'################
DEBUGINFO[2] = n_
'################
y_ = (n_+ 1)* halfsize_ - y_ 'make y relative to tile
'################
DEBUGINFO[1] = y_
'################
Local off_:Double = ( n_ Mod 2 )* halfsize_ 'tile border function offset
Local m_:Int = ( pos_[0]- off_ )/ _world.GetTileScale() 'tile row index (presumption)
'################
DEBUGINFO[4] = m_
'################
Local x_:Double = ( ( pos_[0] - off_ ) Mod _world.getTileScale() )+ off_
'################
DEBUGINFO[3] = x_
'################
out_i_[0] = m_
out_j_[0] = n_
If Not DoublesAreEqual(x_, halfsize_+ off_) 'calculate h
Local h_:Double
If x_< ( halfsize_+ off_ )
h_ = x_- off_
If Abs(y_)> h_ 'find neighbouring tile..
If m_ And Not off_
out_i_[0]:- 1
ElseIf ..
( off_ And ( Abs(y_) < (off_- x_) ) And Not m_ ) .. 'this covers a special case where <n> is indented (off > 0) and <m> is zero.
Or Not ( off_ Or m_ )
Return False
End If
Else
Return True
End If
Else
h_ = halfsize_- ( x_- off_- halfsize_ )
If Abs(y_)> h_ 'find neighbouring tile..
If off_ And ( m_ < ( getNumTilesW()- 1 ) )
out_i_[0]:+ 1
ElseIf off_
Return False
End If
Else
Return True
End If
End If
'################
DEBUGINFO[0] = h_
'################
If ( y_< 0 ) And ( n_< getNumTilesH()- 1 )
out_j_[0]:+ 1
ElseIf ( y_> 0 ) And n_
out_j_[0]:- 1
Else
Return False
End If
'################
Else
DEBUGINFO[4] = -1
'################
End If
End If
Return True
End Method
End Type
Global world:isoWorld = New isoWorld
world._initTerritory(1, 1, 0)
Global territory:isoTerritory = world.getTerritory(0, 0)
Function drawTerritory()
Local mpos_:Double[] = [Double MouseX(), Double MouseY()]
Local reti_:Int
Local retj_:Int
If Not territory.getTileIndizesAtPosition(mpos_, VarPtr reti_, VarPtr retj_)
reti_ = -1
retj_ = -1
End If
DEBUGINFO[5] = retj_
DEBUGINFO[6] = reti_
For Local j_:Int = 0 Until territory.getNumTilesH()
For Local i_:Int = 0 Until territory.getNumTilesW()
If ( reti_ = i_ ) And ( retj_ = j_ )
SetColor 255, 0, 0
Else
SetColor 255, 255, 255
End If
Local x_:Int = i_* TILESIZE + ( j_ Mod 2 )* TILESIZE/ 2
Local y_:Int = j_* TILESIZE/ 2
Local swh_:Int[] = [TextWidth(String(i_)), TextHeight(String(i_))]
DrawLine x_+ 1, y_+ TILESIZE/ 2, x_+ TILESIZE/ 2, y_+ 1
DrawLine x_+ TILESIZE/ 2, y_+ 1, x_+ TILESIZE - 1, y_+ TILESIZE/ 2
DrawLine x_+ TILESIZE - 1, y_+ TILESIZE/ 2, x_+ TILESIZE/ 2, y_+ TILESIZE - 1
DrawLine x_+ 1, y_+ TILESIZE/ 2, x_+ TILESIZE/ 2, y_+ TILESIZE - 1
DrawText i_, x_ + TILESIZE/ 2 - swh_[0]/ 2, y_ + TILESIZE/ 2 - swh_[1]/ 2
Next
Next
End Function
Function DrawDebugInfo:Int( x_:Int, y_:Int, boxw_:Int, caption_:String, value_:String )
caption_:+ ":"
Local w_:Int = TextWidth(caption_)
Local h_:Int = TextHeight(caption_)
SetColor 128, 128, 128
DrawRect x_- 5, y_- 5, boxw_, h_+ 10
SetColor 255, 255, 255
DrawText caption_, x_, y_
SetColor 0, 0, 0
DrawText value_, x_+ boxw_- TextWidth(value_)- 10, y_
Return h_+ 10
End Function
Graphics 800, 600
Repeat
Cls
drawTerritory
Local y_:Int = 600- (TextHeight("X") + 10)
For Local i_:Int = 0 To 6
Local caption_:String
Select i_
Case 6
caption_ = "Final horizontal index "
Case 5
caption_ = "Final vertical index"
Case 2 'n
caption_ = "Vertical index guess"
Case 1 'y
caption_ = "Y relative to y index guess"
Case 4 'm
caption_ = "Horizontal index guess"
Case 3 'x
caption_ = "X relative to tile"
Case 0 'h
caption_ = "Calculated h(x)"
End Select
DrawDebugInfo 10, y_, 400, caption_, String(DEBUGINFO[i_])[..5]
y_:- 30
Next
SetColor 0, 255, 0
DrawLine MouseX(), MouseY()- 10, MouseX(), MouseY()+ 10
DrawLine MouseX()- 10, MouseY(), MouseX()+ 10, MouseY()
Flip False
Until AppTerminate() Or KeyHit(KEY_ESCAPE) |
Comments
None.
Code Archives Forum