Browse Source

Work on support for tile maps.

Beoran 6 years ago
parent
commit
063e2bdd2b
8 changed files with 1548 additions and 281 deletions
  1. 8 0
      ebs.go
  2. 77 0
      engine/fifi/fifi.go
  3. 1 1
      engine/geometry/point/point.go
  4. 23 0
      engine/geometry/rectangle/rectangle.go
  5. 420 0
      engine/tile/io.go
  6. 193 0
      engine/tile/map.go
  7. 655 0
      engine/tile/pane.go
  8. 171 280
      engine/tile/tile.go

+ 8 - 0
ebs.go

@@ -3,6 +3,8 @@ package main
 import "fmt"
 import "flag"
 import "gitlab.com/beoran/ebsgo/engine/global"
+import "gitlab.com/beoran/ebsgo/engine/fif"
+
 // import "path/filepath"
 // import "math/rand"
 
@@ -16,6 +18,12 @@ var fullscreen_flag = flag.Bool("fullscreen", false, "Run fullscreen or not")
 
 func main() {
     state := global.State{}
+    if ! fifi.Initialize() {        
+        fmt.Printf("Could not find data file directory.")
+        return
+    }
+
+    
     if ! state.InstallAllegro() {
         fmt.Printf("Could not initialize allegro.")
         return

+ 77 - 0
engine/fifi/fifi.go

@@ -0,0 +1,77 @@
+package fifi
+
+import "os"
+import "path/filepath"
+
+import al "gitlab.com/beoran/al5go/al"
+
+
+// Find files 
+
+
+var DataPath string = ""
+
+func isDataPathOK(path string) bool {
+    fn := filepath.Join(path, "eruta.cfg")
+    file, err := os.Open(fn) 
+    file.Close()
+    return err == nil
+}
+
+
+func Initialize() bool {
+    DataPath = os.Getenv("EBS_DATA")
+    if isDataPathOK(DataPath) {
+        return true
+    }
+
+    wd, err := os.Getwd()
+    if err == nil { 
+        DataPath = filepath.Join(wd, "data")
+        if isDataPathOK(DataPath) {
+            return true
+        }
+    }
+    return false
+}
+
+func Map(name string) string {
+    return filepath.Join(DataPath, name)
+}
+
+
+/* Fifi contain functionality that helps finding back the file resouces,
+such as images, music, etc that theengine needs.
+
+An important concept here is the "virtual path", that is the path under the 
+location of the data directory. So, for example, if the data of the 
+app is installed on Linux in /usr/share/app/data,
+then a vpath of 
+font/my_nice_font.ttf 
+will be resolved as 
+/usr/share/app/data/font/my_nice_font.ttf.  
+Or, if, on another OS, the data of the app is installed in 
+C:\Program Files\App\data
+then the same vpath will refer to
+C:\Program Files\App\data\font\my_nice_font.ttf.
+
+So Fifi is a way to get OS-independence and location-independence of 
+the data files all at once. 
+
+For save files or scores, the vpath is similar, but relative to the
+"writeable" directory of the application.
+
+*/
+
+func LoadBitmap(filename string) * al.Bitmap {
+    return al.LoadBitmap(Map(filename))
+}
+
+
+
+
+
+
+
+
+

+ 1 - 1
engine/geometry/point/point.go

@@ -6,7 +6,7 @@ type P struct {
 }
 
 
-func New(x, y Float64) P {
+func New(x, y float64) P {
     p := P{ x, y } 
     return p
 }

+ 23 - 0
engine/geometry/rectangle/rectangle.go

@@ -0,0 +1,23 @@
+package rectangle
+
+type Rectangle struct {
+    X   float64
+    Y   float64
+    W   float64
+    H   float64
+}
+
+
+func New(x, y, w, h float64) Rectangle {
+    r := Rectangle{ x, y, w, h } 
+    return r
+}
+
+func (rect Rectangle) X2() float64 {
+    return rect.X + rect.W
+}
+
+func (rect Rectangle) Y2() float64 {
+    return rect.Y + rect.H
+}
+

+ 420 - 0
engine/tile/io.go

@@ -0,0 +1,420 @@
+package tile
+
+import "fmt"
+import "strconv"
+import "io/ioutil"
+import "compress/gzip"
+import "compress/zlib"
+import "encoding/csv"
+import "encoding/binary"
+
+
+import "gitlab.com/beoran/al5go/al"
+// import "gitlab.com/beoran/ebsgo/engine/geometry/point"
+import "gitlab.com/beoran/ebsgo/engine/physics/camera"
+import "gitlab.com/beoran/ebsgo/engine/fifi"
+
+import "github.com/beevik/etree"
+
+
+// Tile flipping constants
+const (
+    TMX_FLIPPED_HORIZONTALLY = 0x80000000
+    TMX_FLIPPED_VERTICALLY   = 0x40000000
+    TMX_FLIPPED_DIAGONALLY   = 0x20000000
+    TMX_FLIPPED_FILTER       = (~0xE0000000)
+)
+
+
+func AttrInt(e * etree.Element, string key) (int, error) {
+    return strconv.Atoi(e.SelectAttrValue(key, ""))
+}
+
+func AttrFloat64(e * etree.Element, string key) (float64, error) {
+    return strconv.ParseFloat(e.SelectAttrValue(key, ""), 64)
+}
+
+
+func Property(e * etree.Element, propname string) string, bool {
+  properties := e.FindElements('//properties/property')
+  
+  for property := range properties {
+        name := e.SelectAttrValue("name", "")
+        if name == propname {
+            return e.SelectAttrValue("value", ""), true
+        }
+  }
+  
+  return "", false
+}
+
+func PropertyInt(e * etree.Element, propname string) int, error {
+    value , ok := Property(e, propname)
+    if (!ok) {
+        return 0, fmt.Errorf("no such property")
+    }
+    
+    return strconv.Atoi(e.SelectAttrValue(key, ""))
+} 
+
+
+func LoadFrame(xml_frame * etree.Element, tile * Tile, set * Set) * Tile {
+    tile_id, err := AttrInt(xml_frame, "tileid")
+    if err != nil {
+        return tile;
+    }
+    
+    // the tile id obtained here is correct and doesn't need to be 
+    // lessened by firstgid
+    
+    duration, err := AttrFloat64(xml_frame, "duration")
+    
+    if err != nil {
+        return tile;
+    }
+    
+    /* Convert ms to s. */ 
+    duration /= 1000.0
+    
+    tile.AddAnimationFrame(tile_id, duration)
+    
+    return tile
+}
+
+
+func LoadAnimation(xml_tile * etree.Element, tile * Tile, set * Set) * Tile {
+    xml_anim := xml_tile.FindElement("//animation")
+    if xml_anim == nil {
+        return tile;
+    }
+    
+    xml_frames := xml_anim.FindElements("//frame")
+    for xml_frame := range xml_frames {
+        LoadFrame(xml_frame, tile, set)
+    }
+    return tile
+}
+
+func XmlToTileType(xml_tile * etree.Element) int, error {
+    
+    value , ok := Property(e, "type")
+    if (!ok) {
+        return 0, fmt.Errorf("no type property")
+    }
+    
+    result, ok  := FlagNames[value]
+    
+    if ! ok {
+        return 0, fmt.Errorf("unknown type property")
+    }
+    
+    return result, nil
+} 
+
+
+func LoadTile(xml_tile * etree.Element, index int, set * Set, tm * Map) (*Tile, error) {
+     
+    if id, err := AttrInt(xml_tile, "id") ; err != nil { 
+        return nil, fmr.Errorf("Tile id not found: %s", err)
+    }
+  
+    tile := set.Tile(id)
+    if tile == nil { 
+        return nil, fmr.Errorf("Tile id not found in tile set: %d", id)
+    }
+    
+    if tiletype, err := XmlToTileType(xml_tile) ; err == nil {
+        tile.Type = tiletype
+    }
+    
+    // ianim       , err := PropertyInt(xml_tile, "anim"       )
+    // iwait       , err := PropertyInt(xml_tile, "wait"       )
+    ilight      , err := PropertyInt(xml_tile, "light"      )
+    iblend      , err := PropertyInt(xml_tile, "blend"      )
+    ishadow     , err := PropertyInt(xml_tile, "shadow"     )
+    ilightmask  , err := PropertyInt(xml_tile, "lightmask"  )
+    iblendmask  , err := PropertyInt(xml_tile, "blendmask"  )
+    ishadowmask , err := PropertyInt(xml_tile, "shadowmask" )
+    
+    // No support anymore for "classic" animations since TMX now 
+    // supports animations.
+    tile.Light      = ilight
+    tile.Blend      = iblend
+    tile.Shadow     = ishadow
+    tile.LightMask  = ilightmask
+    tile.BlendMask  = iblendmask
+    tile.ShadowMask = ishadowmask
+
+    /* Support tiled-style animations. */
+    return LoadAnimation(xml_tile, tile, set)
+}
+
+
+/** Calculates index and the Allegro draw flags and rotation for a tile. */
+
+func TmxTileIndexFlagsAndRotation(tmx_index int32) int, int, float32 { 
+    index := int(tmx_index & TMX_FLIPPED_FILTER) 
+    flags := 0
+    rotate := 0.0
+
+    if (tmx_index & TMX_FLIPPED_HORIZONTALLY) != 0 {
+        flags |= al.FLIP_HORIZONTAL
+    }
+   
+    if (tmx_index & TMX_FLIPPED_VERTICALLY) != 0 {
+        flags |= al.FLIP_VERTICAL
+    } 
+  
+    if (tmx_index & TMX_FLIPPED_DIAGONALLY) {
+        rotate := al.PI / 2.0
+    }
+
+  return index, flags, rotate
+}
+
+
+func LoadSet(xml_set * etree.Element, index int, tm * Map) * Set, error { 
+    firstgid, err := AttrInt(xml_set, "firstgid")
+    if err != nil {
+        return nil, fmt.Errorf("Could not find firstgid for tile set %d: %s", index, err)
+    }
+    
+    xml_image := xml_set.FindElement("//image")
+    
+    if xml_image == nil {
+        return nil, fmt.Errorf("Could not find image data for tile set %d", index)
+    }
+    
+    tile_w, err := AttrInt("tilewidth")
+    tile_h, err := AttrInt("tilewidth")
+    
+    if (tile_w < 1) || (tile_h < 1) {
+        return nil, fmt.Errorf("Tile size too small for tile set %d: %d x %d", index, tile_w, tile_h)
+    }
+    
+    iname   := xml_image.SelectAttrValue("source", "tile_set_default.png")    
+    bmp     := fifi.LoadBitmap("map/" + iname)
+    set     := NewSet(bmp, tile_w, tile_h, firstgid)
+    
+    xml_tiles := xml_set.FindElements("//tile")
+    
+    for xml_tile, tile_index := range xml_tiles {
+        LoadTile(xml_tile, tile_index, set, tm)
+    }
+    
+    return set, nil
+}
+
+type Expander(in []byte) []byte, error 
+
+type Decoder(data string, w, h int, expand Expander) [][]uint32, error 
+
+
+func CsvDecoder(data string, w, h int, expand Expander) [][]uint32, error {
+    
+    r := csv.NewReader(strings.NewReader(data))
+    records, err := r.ReadAll()
+    if err != nil {
+        return err
+    }
+    
+    result := make([][]uint32, h) 
+    for i := 0 ; i < h; i++ { 
+        result[i] := make([]uint32, w) 
+        for j := 0 ; i < w ; j++ { 
+            res, err := strconv.ParseUint(records[i][j], 10, 32) 
+            if err != nil {
+            result[i][j] = 0
+            } else {
+                result[i][j] = uint32(res)
+            }
+        }
+    }
+    return result
+}
+
+func GzipExpander(in []byte) []byte, error {
+    zr, err := gzip.NewReader(&in)
+    if err != nil {
+        return nil, err
+    }
+    return ioutil.ReadAll(zr)
+}
+
+func ZlibExpander(in []byte) []byte, error {
+    zr, err := lib.NewReader(&in)
+    if err != nil {
+        return nil, err
+    }
+    return ioutil.ReadAll(zr)
+}
+
+
+func Base64Decoder(data string, w, h int, expand Expander) [][]uint32, error {
+    bytes, err := base64.StdEncoding.DecodeString()
+    if err != nil {
+        return nil, err
+    }
+    
+    if expand != nil {
+        bytes, err = expand(bytes)
+        if err != nil {
+            return nil, err
+        }
+    }
+    
+    result := make([][]uint32, h) 
+    for i := 0 ; i < h; i++ { 
+        result[i] := make([]uint32, w) 
+        for j := 0 ; i < w ; j++ { 
+            index := (i * w + j) * 4
+            value := binary.LittleEndian.Uint32(bytes[index:4])
+            result[i][j] = value
+        }
+    }
+
+    return result, nil
+}
+
+var Decoders map[string]Decoder = map[string]Decoder {
+    "csv"   : CsvDecoder,
+    "base64": Base64Decoder,
+}
+
+var Expanders map[string]Expander = map[string]Expander {
+    "gzip"   : GzipExpander,
+    "zlib": ZlibExpander,
+}
+
+
+
+// Loads a single tile pane of the tile map from xml (tmx). 
+func LoadPane(xml_pane * etree.Element, int index, tm * Map) (* Pane, error) {  
+    err, width := AttrInt(xml_pane, "width")
+    err, height := AttrInt(xml_pane, "height")
+  
+    if (width < 1) || (height < 1) {
+        return nil, 
+        fmr.Errorf("Layer %i size too small: %d x %d, ", index, width, height)
+    }
+    
+    name := xml_pane.SelectAttrValue("name", "")
+    
+    iset, err := PropertyInt(xml_pane, "tileset")
+    
+    if err != nil {
+        iset = 0
+    }
+    
+    set := tm.Set(iset)
+    
+    if set == nil {
+      return nil, 
+      fmt.Errorf("No tile set found for layer %d: %d", index, iset)
+    }
+    
+    pane := NewPane(set, width, height, name)
+    
+    xml_data := xml_pane.FindElement('//data')
+    
+    if xml_data == nil {
+        return nil, fmr.Errorf("Cannot find layer data for layer %d", index)
+    }
+    
+    encoding    := xml_data.SelectAttrValue("encoding"   , "csv")
+    compression := xml_data.SelectAttrValue("compression", "")
+    
+    decoder     := Decoders[encoding]
+    
+    if decoder == nil {
+        return nil, fmt.Errorf("Unknown encoding %s", encoding)
+    }
+    
+    expander    := Expanders[compression]
+    
+    grid, err   := decoder(xml_data.Text(), width, height, expander) 
+
+    if err != nil {
+        return nil, fmt.Errorf("Could not decode data for layer %d: %s %s: %s",
+         index, decoder, expander, err )
+    }
+    
+    for y := 0 : y < height, y ++ {
+        for x := 0 ; x < width ; x++ {
+            tmx_index = grid[y][x]
+            if tmx_index == 0 { 
+                pane.SetTile(x, y, nil)
+            } else {
+                index, flags, rotation := TmxTileIndexFlagsAndRotation(tmx_index)
+                if pane.Set == nil { 
+                    pane.Set = tm.LookupTmxTileset(index)
+                }
+                ebs_index   := index - pane.FirstGID + 1 
+                cell        := pane.Cell(x, y)
+                cell.Tile       = pane.Set.Tile(ebs_index)
+                cell.Tile.Flags = flags
+                cell.Tile.Rotate= rotation 
+            }
+        }
+    }
+    return pane, nil
+}
+
+
+// Load a TMX map file from the given XML document.
+func LoadMapXml(doc * etree.Document) (* Map, error) {
+    root ::= doc.Root()
+    if root.Tag != "map" {
+        return nil, fmt.Errorf("File is not a TMX map file: %s", root.Tag)
+    }
+    
+    if wide, err := AttrInt(root, "width"); err != nil {
+        return nil, fmt.Errorf("Width not a number: %s", err)
+    }
+    
+    if high, err := AttrInt(root, "height"); err != nil {
+        return nil, fmt.Errorf("Height not a number: %s", err)
+    }
+    
+    if (wide < 1)  || (high < 1) {
+        return nil, fmt.Errorf("Map size too smalll: %d x %d",wide, high)
+    }
+    
+    tm := NewMap(wide, high)
+    
+    xml_sets := root.FindElements('//tileset')
+    
+    for xml_set, i := range xml_sets {
+        set, err := LoadSet(xml_set, i)
+        if err != nil {
+            return nil, fmt.Errorf("Could not load tile set: %s", err)
+        } else {
+            set := tm.AddSet(set)
+        }
+    }
+    
+    xml_panes := root.FindElements('//layer')
+    
+    for xml_pane, i := range xml_panes {
+        pane, err := LoadLayer(xml_pane, i, tm.Sets)
+        if err != nil {
+            return nil, fmt.Errorf("Could not load tile layer: %s", err)
+        } else {
+            tm.AddPane(pane)
+        }
+    }
+      
+  return tm, nil;          
+}
+
+// Loads a TMX tile map from the named file.
+// Return nil on error.
+func LoadMap(filename string) (* Map, error) {
+    doc := etree.NewDocument()
+    err = doc.ReadFromFile(filename)
+    if err != nil {
+        return nil, err
+    }
+    return LoadMapXml(doc)
+}
+

+ 193 - 0
engine/tile/map.go

@@ -0,0 +1,193 @@
+package tile
+
+// import "fmt"
+
+import "gitlab.com/beoran/al5go/al"
+// import "gitlab.com/beoran/ebsgo/engine/geometry/point"
+import "gitlab.com/beoran/ebsgo/engine/physics/camera"
+// import "gitlab.com/beoran/ebsgo/engine/fifi"
+
+
+/** A tile map is a tile based  map, roughly equivalent to a 
+ * "level" that uses tiled panes for it's display. 
+ */
+type Map struct {
+    Background    *al.Bitmap
+    Panes       []*Pane
+    Sets        []*Set
+    GridW          int
+    GridH          int
+}
+
+const ( 
+    TEXTURE_TILE    = "tile"
+    TEXTURE_PLAYER  = "play"
+    TEXTURE_GEAR    = "gear"
+    TEXTURE_WEAPON  = "arms"
+    TEXTURE_FOE     = "foes"
+    TEXTURE_ITEM    = "item"
+)
+
+/** Initializes a tile map */
+func (tm * Map) Init(tw, th int) {
+    tm.GridW = tw
+    tm.GridH = th
+    tm.Panes = nil
+}
+
+func NewMap(tw, th int) * Map{
+    tm := &Map{}
+    tm.Init(tw, th)
+    return tm
+}
+
+
+/** Returns a pointer to the pane at index or NULL if out of range. */
+func (tm * Map) Pane(index int) * Pane {
+    if index < 0                { return nil; }
+    if index >= len(tm.Panes)   { return nil; }
+    return tm.Panes[index]
+}
+
+
+func (tm * Map) AddPane(pane * Pane) * Pane {
+    tm.Panes = append(tm.Panes, pane)
+    return pane
+}
+
+func (tm * Map) NewPane(set * Set, tw, th int, name string) * Pane {
+    pane := NewPane(set, tw, th, name)
+    return tm.AddPane(pane)
+}
+
+/** Returns a pointer to the pane at index or NULL if out of range. */
+func (tm * Map) Set(index int) * Set {
+    if index < 0                { return nil; }
+    if index >= len(tm.Sets)   { return nil; }
+    return tm.Sets[index]
+}
+
+func (tm * Map) AddSet(set * Set) * Set {
+    tm.Panes = append(tm.Panes, set)
+    return set
+}
+
+func (tm * Map) NewSet(sheet * al.Bitmap, tile_w, tile_h, firstgid int) {
+    set := NewSet(sheet, tile_w, tile_h, firstgid)
+    return tm.AddSet(set)
+}
+
+
+/* Looks up the tile set to use for the tile, based on it's 
+ * TMX index. this is done based on the firstgid varables of the tile set.
+ * In case there are several matches the first mathing tile set is returned
+ */
+func (tm * Map) LookupTmxTileset(tmx_index int) * Set{
+    for set := range tm.Sets {
+        if  tmx_index >= set.FirstGID {
+            return set
+        }
+    }
+    return nil
+}
+
+
+/** Returns the tile in the tile map in the given layer at the given tile coords. */
+func (tm * Map) Tile(l, x, y int) * Tile {
+    pane   := tm.Pane(l)
+    if pane == nil { return nil; }
+    return pane.Tile(x, y)
+}
+
+/** Sets a tile in the tile map to the given tile. */
+func (tm * Map) SetTile(l, x, y int, tile * Tile) * Tile {
+    pane   := tm.Pane(l)
+    if pane == nil { return nil }
+    return pane.SetTile(x, y, tile)
+}
+/** Sets a tile in the tile map to the tile with the given index. */
+func (tm * Map) SetTileIndex(l, x, y, index int) * Tile {
+    pane   := tm.Pane(l)
+    if pane == nil { return nil }
+    return pane.SetTileIndex(x, y, index)
+}
+
+
+/** Draws a tile map. */
+func (tm * Map) Draw(camera * camera.Camera) { 
+    var floor * Pane
+    for i := 0 ; i < len(tm.Panes) ; i ++ {
+        pane := tm.Panes[i]
+        if pane != nil {
+            pane.DrawTiles(camera)
+            if ( i % 2 ) == 0 {
+                pane.DrawBlends(camera)
+                floor =  pane
+            } else if (i % 2) == 1 {
+                pane.DrawShadowsOn(floor, camera)
+            }
+        }
+    }
+}
+
+/** Updates the tile map. Currently this animates the tiles. */
+func (tm * Map) Update(dt float64) {
+    for i := 0 ; i < len(tm.Panes) ; i ++ {
+        pane := tm.Panes[i]
+        if pane != nil {
+            pane.Update(dt)
+        }
+    }
+}    
+
+
+/** Sets up the camera so it will stay locked in to the 
+given layer of the tile map */
+
+/*
+Lockin * tilepane_lockin(Tilepane * pane, Camera * camera) {
+  float x, y, w, h;
+  if(!pane) return NULL;
+  x = 0.0;
+  y = 0.0;
+  w = tilepane_wide(pane);  
+  h = tilepane_high(pane);  
+  return camera_newlockin(camera, x, y, w, h);
+} 
+
+
+Lockin * tilemap_layer_lockin(Tilemap * map, 
+                              int layer, 
+                              Camera * camera) {
+  Tilepane * pane;
+  Lockin * result;
+  float x, y, w, h;
+  if (!map) return NULL;
+  pane = tilemap_pane(map, layer);
+  return tilepane_lockin(pane, camera);  
+}
+
+
+
+Area * tilemap_area(Tilemap * self) {
+  if(!self) return NULL;
+  return self->area;
+}
+
+Thing * tilemap_thing(Tilemap * self, int index) {
+  Area * area = tilemap_area(self);
+  return area_thing(area, index);
+}
+*/
+
+
+func (tm * Map) SetupAfterLoad() {
+    for i := 0 ; i < len(tm.Panes) ; i ++ {
+        pane := tm.Panes[i]
+        if pane != nil {
+            pane.InitBlend(i)
+        }
+    }    
+}
+
+

+ 655 - 0
engine/tile/pane.go

@@ -0,0 +1,655 @@
+package tile
+
+import "fmt"
+
+import "gitlab.com/beoran/al5go/al"
+// import "gitlab.com/beoran/ebsgo/engine/geometry/point"
+import "gitlab.com/beoran/ebsgo/engine/physics/camera"
+import "gitlab.com/beoran/ebsgo/engine/fifi"
+
+
+
+/* Tile blending direction constants. */
+
+const (
+    BLEND_NORTHWEST            = 0 
+    BLEND_NORTH                = 1
+    BLEND_NORTHEAST            = 2
+    BLEND_WEST                 = 3
+    BLEND_EAST                 = 4
+    BLEND_SOUTHWEST            = 5
+    BLEND_SOUTH                = 6
+    BLEND_SOUTHEAST            = 7
+    BLEND_DIRECTION_MAX        = 8
+)
+
+/* Tile blending shapes */
+const (
+    BLEND_CORNER               = 0
+    BLEND_SIDE                 = 1
+    BLEND_SHAPE_MAX            = 2
+)
+
+/* Tile blending types. */
+const (
+    BLEND_SHARP                = 0
+    BLEND_GRADUAL              = 1
+    BLEND_FUZZY                = 2
+    BLEND_FUZZY_GRADUAL        = 3
+    BLEND_TYPE_MAX             = 4
+)
+
+const BLEND_BITMAPS_MAX = 255 
+
+/* A cache of generated generic blending masks. */
+var BlendMasks [BLEND_TYPE_MAX][BLEND_SHAPE_MAX]*al.Bitmap 
+
+
+/* Lookup array with info on how the mask should be applied, i. e. flipped. */
+var BlendFlags [BLEND_DIRECTION_MAX]int = [BLEND_DIRECTION_MAX] int{
+  0,
+  0,
+  al.FLIP_HORIZONTAL,
+  0,
+  al.FLIP_HORIZONTAL,
+  al.FLIP_VERTICAL,
+  al.FLIP_VERTICAL,
+  al.FLIP_HORIZONTAL | al.FLIP_VERTICAL,
+}
+
+/* Lookup array with info on which "side" mask should be used. */
+var BlendSides [BLEND_DIRECTION_MAX]int = [BLEND_DIRECTION_MAX]int {
+  BLEND_CORNER,
+  BLEND_SIDE  ,
+  BLEND_CORNER,
+  BLEND_SIDE  ,
+  BLEND_SIDE  ,
+  BLEND_CORNER,
+  BLEND_SIDE  ,
+  BLEND_CORNER,
+}
+
+/* Lookup array with info on how the mask should be turned, i. e. rotated. */
+var BlendAngles [BLEND_DIRECTION_MAX]float32 = [BLEND_DIRECTION_MAX]float32 {
+  0.0,
+  0.0,
+  0.0,
+  3 * al.PI / 2.0,
+  al.PI / 2.0,
+  0.0,
+  0.0,
+  0.0,
+}
+
+/* 
+ 
+ ## Implementation of automatic overlapping ##
+ 
+ In the simple case where there are only 2 different kinds of tiles, 
+ then for a tile somewhere in the map, that tile has 8 neighbours, and thus 
+ there are 8**2 or 256 different possible layouts of these neighbours around that 
+ single tile, and 256 different blends would be needed.
+ 
+ Of course, this is a staggering amount for hand drawn graphics, and even for 
+ automatically generated mask images, this takes up a hefty chunk of memory,
+ so some kind of simplification is needed. 
+ 
+ The first simplification is the use of the tile's blend value as a blend *priority*
+ value. The tile with the lower blend priority will be blended with the 
+ tile with the higher blend priority blending over it. Tiles with equal blend 
+ priority will simply not blend. This reduces the possibilities because in 
+ cases where 3 or more tyle types must blend, without priorities, 
+ the number of possible blends would become even larger. 
+ 
+ Let's consider the possibilities takng symmetry in account. If only 2 tiles 
+ that will blend are adjacent, there are 8 possibilities. However there are 4 
+ symmetric rotations of the 2 cases of the 2 tiles being either side-to-side 
+ or corner to corner. In case of side-to side, the blend should go rougmly like 
+ this: (scaled down to 8x8
+ 
+ ..........OOOOOOOO     ................OO
+ ..........OOOOOOOO     ..............OOOO
+ ..........OOOOOOOO     ............OOOOOO
+ ..........OOOOOOOO     ............OOOOOO
+ ..........OOOOOOOO =>  ............OOOOOO
+ ..........OOOOOOOO     ............OOOOOO
+ ..........OOOOOOOO     ...........OOOOOOO
+ ..........OOOOOOOO     ..........OOOOOOOO
+ 
+ And corner to corner:
+ 
+           OOOOOOOO               OOOOOOOO
+           OOOOOOOO               OOOOOOOO
+           OOOOOOOO               OOOOOOOO
+           OOOOOOOO               OOOOOOOO
+           OOOOOOOO =>            OOOOOOOO
+           OOOOOOOO               OOOOOOOO
+           OOOOOOOO               .OOOOOOO
+           OOOOOOOO               ..OOOOOO
+ ..........             ..........        
+ ..........             ..........        
+ ..........             ..........        
+ ..........             ..........        
+ ..........         =>  ..........        
+ ..........             ..........        
+ ..........             ..........        
+ ..........             ..........        
+ 
+ If the masks are judiciouly chosen, and all align correctly, 
+ then it will suffice to have just 2 mask images which get rotated as needed,
+ one mask for the side to side, and one for corner to corner. 
+ Each of the masks follows a strict pattern, that is, the side by side 
+ has a \__/ - like shape were the depth of the overlap is at most 1/4th of the 
+ tile height. (Or is it 1/3, let's check it out). 
+ 
+ Then, for every overlapping tile, an overlap bitmap can be generated 
+ when loading the tile layer it is in, and that bitmap can then be used to 
+ draw the tile in stead of the original bitmap. 
+
+
+## Implementation of automatic shadows ##
+
+Every cell in the tile pane contains a slice of shadow polygons,
+that is set up before rendering. This should in the future be used in stead of 
+the current live polygons generated at draw time but this will require 
+an approach where a camera transform is applied whilst drawing in stead of
+the current situation where we use the camera info directly to calculate
+the draw positions. 
+
+*/
+
+func PrepareBlend(blend * al.Bitmap, tile * Tile, blentile * Tile, direction int ) * al.Bitmap {
+    side  := BlendSides[direction]
+    angle := BlendAngles[direction]
+    flags := BlendFlags[direction]
+    maskid:= tile.BlendMask
+    mask  := BlendMasks[maskid][side]
+    if mask == nil {
+        return nil
+    } else {
+        tile.DrawMaskedTo(blend, mask, angle, flags)
+        return blend
+    }
+}
+
+/** 
+ * A Cell is an individual part of a pane. It contains a tile,
+ * a blend bitmap, two shadow polygons, and rendering flags.
+ */
+type Cell struct {
+    * Tile
+    Blend * al.Bitmap
+    Flags   int
+    Shadows [][8]float32 // Slice of shadow polygons.
+}
+
+
+/**
+* A Pane is a layer of tiles for use in a tile map or tiled
+* background. A pane consists of individual tiles from the same
+* tile set. Different panes may have different tile sets.
+*/
+type Pane struct  {
+  Tileset   *    Set
+  Cells      [][]Cell
+  GridW          int
+  GridH          int
+  PixelW         int
+  PixelH         int
+  // This bitmap is the atlas used for blends. XXX: Not implemented yet.
+  Blends         * al.Bitmap
+  Name           string  
+};
+
+
+/** Initializes a tile pane.
+* The pane will not clean up the tile set itself!
+*/
+func (pane * Pane) Init(set * Set, gridw, gridh int, name string) (* Pane) {
+    pane.Tileset = set
+    pane.GridW   = gridw
+    pane.GridH   = gridh
+    pane.PixelW  = pane.GridW * set.TileW
+    pane.PixelH  = pane.GridW * set.TileH
+    pane.Name    = name
+    pane.Cells   = make([][]Cell, pane.GridH)
+    for i := range (pane.Cells) {
+        pane.Cells[i]  = make([]Cell, pane.GridW)
+    }
+    return pane
+}
+
+func NewPane(set * Set, gridw, gridh int, name string) (pane * Pane) {
+    pane = &Pane{}
+    return pane.Init(set, gridw, gridh, name)
+}
+ 
+
+func (pane * Pane) Outside(gridx, gridy int) bool {
+    return ((gridx < 0) || (gridy < 0)) || 
+    ((gridx >= pane.GridW) || (gridy >= pane.GridH))
+}
+
+func (pane * Pane) Cell(gridx, gridy int) * Cell {
+    if pane.Outside(gridx, gridy) {
+        return nil
+    }
+    return & pane.Cells[gridy][gridx]
+}
+
+func (pane * Pane) Tile(gridx, gridy int) * Tile {
+    if pane.Outside(gridx, gridy) {
+        return nil
+    }
+    return pane.Cells[gridy][gridx].Tile
+}
+
+func (pane * Pane) Blend(gridx, gridy int) * al.Bitmap {
+    if pane.Outside(gridx, gridy) {
+        return nil
+    }
+    return pane.Cells[gridy][gridx].Blend
+}
+
+
+func (pane * Pane) SetTile(gridx, gridy int, tile * Tile) * Tile {
+    if pane.Outside(gridx, gridy) {
+        return nil
+    }
+    pane.Cells[gridy][gridx].Tile = tile
+    return tile
+}
+
+func (pane * Pane) SetTileIndex(gridx, gridy, index int) * Tile {
+    return pane.SetTile(gridx, gridy, pane.Tileset.Tile(index))
+}
+
+
+func (pane * Pane) SetBlend(gridx, gridy int, blend * al.Bitmap) * al.Bitmap {
+    if pane.Outside(gridx, gridy) {
+        return nil
+    }
+    if nil != pane.Cells[gridy][gridx].Blend {
+        pane.Cells[gridy][gridx].Blend.Destroy()
+    }
+    pane.Cells[gridy][gridx].Blend = blend
+    return blend
+}
+
+
+func (pane * Pane) SetFlags(gridx, gridy, flags int) int {
+    if pane.Outside(gridx, gridy) {
+        return -1
+    }
+    pane.Cells[gridy][gridx].Flags = flags
+    return flags
+}
+
+
+func (pane * Pane) SetTileRect(gridx, gridy, gridw, gridh int, tile * Tile) * Tile {
+    for jj := gridy ; jj < (gridy + gridh) ; jj++ {
+        for ii := gridx ; ii < (gridx + gridh) ; ii++ {
+            pane.SetTile(ii, jj, tile)
+        }
+    }
+    return tile
+}
+
+func (pane * Pane) Fill(tile * Tile) * Tile {
+    return pane.SetTileRect(0, 0, pane.GridW, pane.GridH, nil)
+}
+
+
+func (cell * Cell) AddShadowPolygon(polygon [8]float32) {
+    cell.Shadows = append(cell.Shadows, polygon)
+}
+
+func (cell * Cell) DeleteShadowPolygons() {
+    cell.Shadows = nil
+}
+
+type DrawIterator func (cell * Cell, x, y float32, tx, ty int)  
+
+/* Draws the tiles of the pane using an iterator function. 
+ * This must be done in one call because changing the source bitmap is less optimal. 
+ * You must hold bitmap drawing yourself if applicable.
+ */
+func (pane * Pane) DrawIterate(camera * camera.Camera, iter DrawIterator) { 
+    gridwide := pane.GridW
+    gridhigh := pane.GridH
+    tilewide := pane.Tileset.TileW
+    tilehigh := pane.Tileset.TileH
+    x        := int(camera.X)
+    y        := int(camera.Y)
+    tx_start := x / tilewide
+    ty_start := y / tilehigh
+    tx_delta := 1 + (int(camera.W) / tilewide)
+    ty_delta := 1 + (int(camera.H) / tilehigh)
+    tx_stop  := tx_start + tx_delta
+    ty_stop  := ty_start + ty_delta
+    tx_index := 0
+    ty_index := 0
+    // realwide := pane.PixelW
+    // realhigh := pane.PixelH
+    // drawflag := 0
+    if tx_start < 0 { tx_start = 0; }
+    if ty_start < 0 { ty_start = 0; }
+    if tx_stop  > gridwide { tx_stop = gridwide; }
+    if ty_stop  > gridhigh { ty_stop = gridhigh; }
+    y_draw    :=  float32(-y + (ty_start * tilehigh))
+    for ty_index = ty_start; ty_index < ty_stop; ty_index++ { 
+        y_draw   += float32(tilehigh)
+        x_draw   := float32(-x + (tx_start * tilewide))
+        row      := pane.Cells[ty_index]
+        for tx_index = tx_start ; tx_index < tx_stop; tx_index++ {
+            x_draw += float32(tilewide)
+            cell := &row[tx_index]
+            iter(cell, x_draw, y_draw, tx_index, ty_index)
+        }
+    }
+}
+
+
+
+/* Draws the tiles of the pane. This must be done in one call because
+ * changing the source bitmap is less optimal. */
+func (pane * Pane) DrawTiles(camera * camera.Camera) { 
+    gridwide := pane.GridW
+    gridhigh := pane.GridH
+    tilewide := pane.Tileset.TileW
+    tilehigh := pane.Tileset.TileH
+    x        := int(camera.X)
+    y        := int(camera.Y)
+    tx_start := x / tilewide
+    ty_start := y / tilehigh
+    tx_delta := 1 + (int(camera.W) / tilewide)
+    ty_delta := 1 + (int(camera.H) / tilehigh)
+    tx_stop  := tx_start + tx_delta
+    ty_stop  := ty_start + ty_delta
+    tx_index := 0
+    ty_index := 0
+    // realwide := pane.PixelW
+    // realhigh := pane.PixelH
+    // drawflag := 0
+    al.HoldBitmapDrawing(true)
+    if tx_start < 0 { tx_start = 0; }
+    if ty_start < 0 { ty_start = 0; }
+    if tx_stop  > gridwide { tx_stop = gridwide; }
+    if ty_stop  > gridhigh { ty_stop = gridhigh; }
+    y_draw    :=  float32(-y + (ty_start * tilehigh))
+    for ty_index = ty_start; ty_index < ty_stop; ty_index++ { 
+        y_draw   += float32(tilehigh)
+        x_draw   := float32(-x + (tx_start * tilewide))
+        row      := pane.Cells[ty_index]
+        for tx_index = tx_start ; tx_index < tx_stop; tx_index++ {
+            x_draw += float32(tilewide)
+            cell := row[tx_index]
+            if (cell.Tile != nil) {
+                cell.Tile.Draw(x_draw, y_draw, cell.Flags)
+            }
+        }
+    }
+    // Let go of hold 
+    al.HoldBitmapDrawing(false)  
+}
+
+
+/* Draws the blends of the pane. This must be done in one call because
+ * changing the source bitmap is less optimal. */
+func (pane * Pane) DrawBlends(camera * camera.Camera) { 
+    gridwide := pane.GridW
+    gridhigh := pane.GridH
+    tilewide := pane.Tileset.TileW
+    tilehigh := pane.Tileset.TileH
+    x        := int(camera.X)
+    y        := int(camera.Y)
+    tx_start := x / tilewide
+    ty_start := y / tilehigh
+    tx_delta := 1 + (int(camera.W) / tilewide)
+    ty_delta := 1 + (int(camera.H) / tilehigh)
+    tx_stop  := tx_start + tx_delta
+    ty_stop  := ty_start + ty_delta
+    tx_index := 0
+    ty_index := 0
+    // realwide := pane.PixelW
+    // realhigh := pane.PixelH
+    // drawflag := 0
+    // al.HoldBitmapDrawing(true)
+    if tx_start < 0 { tx_start = 0; }
+    if ty_start < 0 { ty_start = 0; }
+    if tx_stop  > gridwide { tx_stop = gridwide; }
+    if ty_stop  > gridhigh { ty_stop = gridhigh; }
+    y_draw    :=  float32(-y + (ty_start * tilehigh))
+    for ty_index = ty_start; ty_index < ty_stop; ty_index++ { 
+        y_draw   += float32(tilehigh)
+        x_draw   := float32(-x + (tx_start * tilewide))
+        row      := pane.Cells[ty_index]
+        for tx_index = tx_start ; tx_index < tx_stop; tx_index++ {
+            x_draw += float32(tilewide)
+            cell := row[tx_index]
+            if (cell.Blend != nil) {
+                cell.Blend.Draw(x_draw, y_draw, cell.Flags)
+            }
+        }
+    }
+    // Let go of hold 
+    // al.HoldBitmapDrawing(false)  
+}
+
+
+func (pane * Pane) DrawOneShadowOn(cell * Cell, pane_below * Pane, 
+                                   tx, ty int, x, y float32) {
+
+    tile      :=  cell.Tile
+    if (tile == nil) { return; }
+    if (tile.Shadow == 0) { return; }
+    shadow_color := al.MapRGBA(0, 0, 0, 128)
+
+    edge_tile :=  pane.Tile(tx + 1, ty - 1)
+    aid_tile  :=  pane.Tile(tx + 1, ty)
+    shadow_trapezium := false
+    if (edge_tile != nil) && edge_tile.IsWall() {
+        shadow_trapezium = true
+        // here the shadow is at trapezium, not a parallelogram.
+    }
+    
+    /* Tile sizes... */
+    tw := float32(pane.Tileset.TileW)
+    th := float32(pane.Tileset.TileH)
+    
+      
+    /* Only cast a shadow to the east if no solid tile next to self.
+     * Shadow is a parallelogram to simplify overlaps.
+     */
+     if (aid_tile == nil) || !aid_tile.IsWall() { 
+        low_tile  := pane_below.Tile(tx + 1, ty)
+        if (low_tile != nil) && (low_tile.ShadowMask != 1) {
+            polygon := [8]float32 {
+                x + tw          , y             , 
+                x + tw          , y + th        ,
+                x + tw * 1.5    , y + th * 0.5  ,
+                x + tw * 1.5    , y - th * 0.5  ,
+            }
+            
+            if (shadow_trapezium) {
+                polygon[7] = y
+            }
+            al.DrawFilledPolygon(polygon[0:8], 0, shadow_color)
+        }
+      }
+      
+      aid_tile = pane.Tile(tx, ty-1)
+
+      /* Only cast a shadow to the north if no solid tile above to self.
+       * Shadow is a parallelogram to simplify overlaps.
+       */
+       
+      if (aid_tile == nil) || !aid_tile.IsWall() { 
+        low_tile  := pane_below.Tile(tx, ty - 1)
+        if (low_tile != nil) && (low_tile.ShadowMask != 1) {
+            polygon := [8]float32 {
+                x + tw          , y             , 
+                x + tw          , y             ,
+                x + tw * 1.5    , y - th * 0.5  ,
+                x + tw * 0.5    , y - th * 0.5  ,
+            }
+            
+            if (shadow_trapezium) {
+                polygon[4] = x + tw
+            }
+            al.DrawFilledPolygon(polygon[0:8], 0, shadow_color)
+        }
+      }
+}
+
+/** Draws the shadows that tile pane pane casts onto the pane pane_below 
+ * with the camera delimiting the view.
+ * On a classic tile map the bottom is to the south, so this function draws
+ * "classic" shadows cast as if the sun were in the south-west,
+ * with the shadows pointing north-east.
+*/
+func (pane * Pane) DrawShadowsOn(pane_below * Pane, camera * camera.Camera) { 
+    gridwide := pane.GridW
+    gridhigh := pane.GridH
+    tilewide := pane.Tileset.TileW
+    tilehigh := pane.Tileset.TileH
+    x        := int(camera.X)
+    y        := int(camera.Y)
+    tx_start := x / tilewide
+    ty_start := y / tilehigh
+    tx_delta := 1 + (int(camera.W) / tilewide)
+    ty_delta := 1 + (int(camera.H) / tilehigh)
+    tx_stop  := tx_start + tx_delta
+    ty_stop  := ty_start + ty_delta
+    tx_index := 0
+    ty_index := 0
+    // realwide := pane.PixelW
+    // realhigh := pane.PixelH
+    // drawflag := 0
+    // al.HoldBitmapDrawing(true)
+    if tx_start < 0 { tx_start = 0; }
+    if ty_start < 0 { ty_start = 0; }
+    if tx_stop  > gridwide { tx_stop = gridwide; }
+    if ty_stop  > gridhigh { ty_stop = gridhigh; }
+    y_draw    :=  float32(-y + (ty_start * tilehigh))
+    for ty_index = ty_start; ty_index < ty_stop; ty_index++ { 
+        y_draw   += float32(tilehigh)
+        x_draw   := float32(-x + (tx_start * tilewide))
+        row      := pane.Cells[ty_index]
+        for tx_index = tx_start ; tx_index < tx_stop; tx_index++ {
+            x_draw += float32(tilewide)
+            cell := &row[tx_index]
+            pane.DrawOneShadowOn(cell, pane_below, tx_index, ty_index, x_draw, y_draw) 
+        }
+    }
+    // Let go of hold 
+    // al.HoldBitmapDrawing(false)  
+}
+
+
+
+/** Updates the tile pane. Curently does nothing, but this may change. */
+func (pane * Pane) Update(dt float64) {
+    if pane.Tileset != nil { 
+        pane.Tileset.Update(dt)
+    }
+}
+
+/** Gets a tile from a the tilepane's tile set by it's tile id. **/
+func (pane * Pane)  TileFromSet(index int) * Tile {
+    set := pane.Tileset 
+    if nil == set { 
+        return nil;
+    }
+    return set.Tile(index)
+}
+
+
+type BlendOffset struct {
+  tx int
+  ty int
+}
+
+var BlendOffsets [8]BlendOffset = [8]BlendOffset{
+  {-1, -1}, /* TILE_BLEND_NORTHWEST 0 */ 
+  { 0, -1}, /* TILE_BLEND_NORTH     1 */
+  { 1, -1}, /* TILE_BLEND_NORTHEAST 2 */
+  {-1,  0}, /* TILE_BLEND_WEST      3 */
+  { 1,  0}, /* TILE_BLEND_EAST      4 */
+  {-1,  1}, /* TILE_BLEND_SOUTHWEST 5 */
+  { 0,  1}, /* TILE_BLEND_SOUTH     6 */
+  { 1,  1}, /* TILE_BLEND_SOUTHEAST 7 */
+}
+
+func (pane * Pane) InitBlendTile(index, tx, ty int, tile * Tile) bool {
+    blend := pane.Blend(tx, ty)
+    if blend != nil {
+        blend.Destroy()
+        pane.SetBlend(tx, ty, nil)
+    }
+    
+    target          := al.TargetBitmap()
+    tile_prio       := tile.Blend
+    for i:= 0; i < len(BlendOffsets) ; i++ {
+        offset      := BlendOffsets[i]
+        txn         := tx + offset.tx
+        tyn         := ty + offset.ty
+        aid_tile    := pane.Tile(txn, tyn)
+        if aid_tile == nil { continue; }
+        aid_prio    := aid_tile.Blend
+        if aid_prio <= tile_prio { continue; }
+        blend_bmp   := al.CreateBitmap(pane.Tileset.TileW, pane.Tileset.TileH /* , al.CONVERT_BITMAP */)
+        if blend_bmp == nil { return false; }
+        al.SetTargetBitmap(blend_bmp)
+        al.ClearToColor(al.MapRGBA(0,0,0,0))
+        PrepareBlend(blend_bmp, tile, aid_tile, i);
+    }
+    
+    al.SetTargetBitmap(target)
+    return true
+}
+
+
+
+
+const (
+    MASK_SIDE_W     = 16
+    MASK_SIDE_H     = 16
+    MASK_CORNER_W   = 16
+    MASK_CORNER_H   = 16
+    MASK_W          = 8
+    MASK_H          = 8
+)
+
+func (pane * Pane) InitMasks() {
+    // load the masks into the pane
+    for blend_type := 0; blend_type < BLEND_TYPE_MAX ; blend_type++ {
+        fin1 := fmt.Sprintf("/image/masks/corner_mask_%d.png", blend_type)
+        fin2 := fmt.Sprintf("/image/masks/side_mask_%d.png", blend_type)
+ 
+        bmp1 := fifi.LoadBitmap(fin1)
+        bmp2 := fifi.LoadBitmap(fin2)
+        BlendMasks[blend_type][BLEND_CORNER]    = bmp1
+        BlendMasks[blend_type][BLEND_SIDE]      = bmp2
+    }
+}
+
+func (pane * Pane) InitBlend(index int) bool {
+    if pane == nil        { return false }
+    if pane.Blends == nil { return false }
+    if index > 4          { return false }
+    tx := 0
+    ty := 0
+    w  := pane.GridW
+    h  := pane.GridH
+    for ty = 0 ; ty < h; ty++ { 
+        for tx = 0; tx < w; tx++ {
+            tile := pane.Tile(tx, ty)
+            if tile == nil { continue; }
+            if tile.Blend < 1 { continue; }
+            pane.InitBlendTile(index, tx, ty, tile)
+        }
+    }
+    return true
+}
+

+ 171 - 280
engine/tile/tile.go

@@ -1,11 +1,16 @@
 package tile
 
-import import "log"
+// import "log"
 // import "os"
 // import "math"
 
-import al "gitlab.com/beoran/al5go/al"
-import al "gitlab.com/beoran/ebsgo/engine/geometry/point"
+import "gitlab.com/beoran/al5go/al"
+import "gitlab.com/beoran/ebsgo/engine/geometry/point"
+
+const TILE_W = 32
+const TILE_H = 32
+
+var showSolid = false;
 
 
 /** A tile set */
@@ -33,7 +38,7 @@ type Frame struct {
 * pointer and offset which points to the next tile to be drawn in the tileset.
 */
 type Tile struct {
-  Tileset         Set   /* Tileset this tile belongs to */
+  Tileset       * Set   /* Tileset this tile belongs to */
   Index           int   /* Index in the tile set. */  
   Flags           int   /* Information about the tile's properties. */
   Kind            int
@@ -58,7 +63,7 @@ type Tile struct {
   /* Automatic blending activation and priority. */
   Blend         int
   /* Mask number to use for automatic blending, if any. */
-  BlendMAsk     int
+  BlendMask     int
   /* Automatic lighting activation flag. */
   Light         int
   LightMAsk     int
@@ -79,20 +84,20 @@ type Tile struct {
 */
 
 
-func NewTileset(sheet * al.Bitmap, tile_w, tile_h, firstgid int) Set {
+func NewSet(sheet * al.Bitmap, tile_w, tile_h, firstgid int) * Set {
     set := &Set{}
     set.Sheet       = sheet
     set.TileW       = tile_w
     set.TileH       = tile_h
     set.FirstGID    = firstgid
     set.W           = sheet.Width() / set.TileW
-    set.H           = shet.Height() / set.TileH
+    set.H           = sheet.Height()/ set.TileH
     size           := set.W * set.H
     set.Tiles       = make([]Tile, size)
-    set.Last        = 0
-    for := 0 ; i < size; i ++ {
+    for i := 0 ; i < size; i ++ {
         set.Tiles[i].Init(set, i)
     }
+    return set
 }
 
 func (tile * Tile) Init(set * Set, index int) {
@@ -102,31 +107,31 @@ func (tile * Tile) Init(set * Set, index int) {
     tile.Recalculate()
 } 
 
-func (tile Tile) SheetY(set Tileset) int {
-    (tile->Active * set->TileW) / (set->W * set->TileH)
+func (tile Tile) SheetY(set * Set) int {
+    return (tile.Active * set.TileW) / (set.W * set.TileH)
 }
 
-func (tile Tile) SheetX(set Tileset) int {
-    (tile->Active * set->TileW) % (set->W)
+func (tile Tile) SheetX(set * Set) int {
+    return (tile.Active * set.TileW) % (set.W)
 }
 
 
 /** Recalculates the tile's position (now) in it's tile set. */
 func (tile * Tile) Recalculate() {
-    if !tile.Tileset {
+    if nil == tile.Tileset {
         return
     }
     
     x := float64(tile.SheetX(tile.Tileset))
     y := float64(tile.SheetY(tile.Tileset))
         
-    tile.now = point.New(x, y)
+    tile.Position = point.New(x, y)
 }
 
 
-func (set * Set) Get(index int) * Tile {
+func (set * Set) Tile(index int) * Tile {
     if nil != set && index >= 0 && index <= len(set.Tiles) {
-        return set.Tiles[index]
+        return &set.Tiles[index]
     } 
     return nil
 }
@@ -144,6 +149,8 @@ const (
     TILE_SOUTH  = iota
     TILE_EAST   = iota
     TILE_WEST   = iota
+    TILE_UP     = iota
+    TILE_DOWN   = iota
     TILE_ICE    = iota
  )
  
@@ -151,17 +158,19 @@ const (
 
 
 /* Helper lookup table for the tile flag names */
-var FlagNames := map[string]int {
-  "wall" , TILE_WALL  ,
-  "water", TILE_WATER ,
-  "ledge", TILE_LEDGE ,
-  "stair", TILE_STAIR ,
-  "push" , TILE_PUSH  ,
-  "north", TILE_NORTH ,
-  "south", TILE_SOUTH ,
-  "east" , TILE_EAST  ,
-  "west" , TILE_WEST  ,
-  "ice"  , TILE_ICE   ,
+var FlagNames map[string]uint = map[string]uint {
+  "wall" : TILE_WALL  ,
+  "water": TILE_WATER ,
+  "ledge": TILE_LEDGE ,
+  "stair": TILE_STAIR ,
+  "push" : TILE_PUSH  ,
+  "north": TILE_NORTH ,
+  "south": TILE_SOUTH ,
+  "east" : TILE_EAST  ,
+  "west" : TILE_WEST  ,
+  "up"   : TILE_UP    ,
+  "down" : TILE_DOWN  ,
+  "ice"  : TILE_ICE   ,
 }
 
 
@@ -175,6 +184,14 @@ func (tile * Tile) SetProperty(property string) {
     }
 }
 
+func (tile * Tile) HasFlag(flag uint) bool {
+    return (tile.Flags & (1 << flag)) == (1 << flag)
+}
+
+func (tile * Tile) IsWall() bool {
+    return tile.HasFlag(TILE_WALL)
+}
+
 /** Initializes a tile's frame of animation. */
 func (frame * Frame) Init(index int, duration float64) {
     frame.Index     = index
@@ -188,162 +205,130 @@ func (tile Tile) Frame(index int)  * Frame {
     if nil == tile.Frames{ 
        return nil
     } else { 
-        return tiles.Frames[index]
+        return &tile.Frames[index]
     }
 } 
 
 /** Gets the amount of Tiled style animations for this tile, or 0 if none. */
-int tile_frame_count(Tile * tile) {
+func (tile Tile) FrameCount() int {
   if nil == tile.Frames { 
-       return nil
+       return 0
     } else { 
-        return len(tiles.Frames)
+        return len(tile.Frames)
     }
 }
 
 /** Adds a Tiled-style animation frame to the tile. */
-Tile * tile_add_animation_frame(Tile * tile, int index, double duration) {
-  if (tile->frames) {
-    int size;
-    size = dynar_size(tile->frames);
-    if (!dynar_size_(tile->frames, size + 1)) return NULL;
-    tileframe_init(dynar_getdata(tile->frames, size), index, duration);
-  } else {
-    tile->frames = dynar_new(1, sizeof(struct TileFrame_));
-    if (!tile->frames) return NULL;
-    tileframe_init(dynar_getdata(tile->frames, 0), index, duration);
-  }
-  return tile;
-} 
-
+func (tile * Tile) AddAnimationFrame(index int, duration float64) (frame * Frame) {
+    if (nil == tile.Frames) {
+        tile.Frames = make([]Frame, 1)
+        frame       = &tile.Frames[0] 
+    } else {
+        frame       = &Frame{}
+        tile.Frames = append(tile.Frames, *frame)
+    }
+    
+    frame.Init(index, duration)
+    return frame
+}
+  
 
 /** Rewinds a tile's animations. */
-void tile_rewindanime(Tile * tile) {
-  if (!tile) return;
-  tile->active        = tile->index;
-  tile->active_frame  = 0;
+func (tile * Tile) RewindAnimations() {
+  tile.Active       = tile.Index
+  tile.ActiveFrame  = 0
   // Finally recalculate tile position.
-  tile_recalculate(tile);
+  tile.Recalculate()
 }
 
-/** Updates a tile to animate it using classic style animation.*/
-void tile_update_classic(Tile * tile, double dt) {
-  int active = 0;
-  Tile * aidtile = NULL;
-  Tile * nowtile = tileset_get(tile->set, tile->active);
-  // nowtile is the tile that is currently active, that is shown.
-  // in stead of ourself, but it also may be ourself.
-  if(!nowtile) return;
-  tile->time    += dt; // advance animation time of tile. 
-  // Don't animate if not enough time has passed
-  if(tile->time < tile->wait) return;
-  // if we get here, reset animation time.
-  tile->time     = 0.0;
-  // take the animation parameter and add it to the active  
-  active  = tile->active + nowtile->anim;
-  aidtile = tileset_get(tile->set, active);
-  // Check if there is such a tile.
-  if(!aidtile) return;
-  // If there is no such tile, don't change the active tile of this tile.
-  tile->active = active;  
-  // Finally recalculate tile position.
-  tile_recalculate(tile);
-}
 
-/**  Updates a tile to anmate it using TMX style animation. */
-void tile_update_tmx(Tile * tile, double dt) {
-  int active = 0;
-  TileFrame * frame = NULL;
-  Tile * aidtile = NULL;
-  
-  frame = tile_frame(tile, tile->active_frame);
-  if (!frame) { /* Animation overshoit itself somehow??? */
-    tile->active_frame = 0;
-    frame = tile_frame(tile, tile->active_frame);
-    if (!frame) return;
+/**  Updates a tile to animate it using TMX style animation. */
+func (tile * Tile) UpdateAnimation(dt float64) {
+  active := 0;
+
+  frame := tile.Frame(tile.ActiveFrame)
+  if nil == frame { /* Animation overshot itself */
+    tile.ActiveFrame = 0
+    frame = tile.Frame(tile.ActiveFrame)
+    if nil == frame {
+         return
+    }
   }
   
-  tile->time    += dt; // advance animation time of tile. 
+  tile.Time += dt // advance animation time of tile. 
   // Don't animate if not enough time has passed
-  if(tile->time < frame->duration) return;
+  if tile.Time < frame.Duration {
+      return
+  }
   // advance the animation frame, loop it around if needed. 
-  tile->active_frame++;
-  if (tile->active_frame >= tile_frame_count(tile)) {
-    tile->active_frame = 0;
+  tile.ActiveFrame++
+  if tile.ActiveFrame >= tile.FrameCount() {
+    tile.ActiveFrame = 0
   }
   // Get new tile frame
-  frame = tile_frame(tile, tile->active_frame);
+  frame = tile.Frame(tile.ActiveFrame);
   // If we get here, reset animation time.
-  tile->time     = 0.0;
-  if (!frame) return;
-  
-  // Get the active tile t use from the animation frame
-  active  = frame->index;
-  aidtile = tileset_get(tile->set, active);
+  tile.Time     = 0.0
+  if nil == frame {
+       return
+  }
+  // Get the active tile to use from the animation frame
+  active   = frame.Index
+  aidtile := tile.Tileset.Tile(active);
   // Check if there is such a tile.
-  if(!aidtile) return;
+  if nil == aidtile {
+      return
+  }
   // If there is no such tile, don't change the active tile of this tile.
-  tile->active = active; 
+  tile.Active = active 
   // Finally recalculate tile position.
-  tile_recalculate(tile);
-  // tile->now = aidtile->now;
-  // LOG_NOTE("TMX Anim: %d (%d: set(%d, %d)): (x,y)=(%lf, %lf)\n", tile->index, tile->active, tile->set->w, tile->set->h, tile->now.x, tile->now.y);
+  tile.Recalculate();
 }
 
-/* Animates the tile. If it has a TMX style animation, that takes 
- * precedence, otherwise, use the classic style animation. */
-void tile_update(Tile * tile, double dt) {
-  if (tile->frames) {
-    tile_update_tmx(tile, dt);
-  } else {
-    tile_update_classic(tile, dt);
-  }
+/* Animates the tile. Animates the tile if it has animation frames. */
+func (tile * Tile) Update(dt float64) {
+  if nil != tile.Frames {
+      tile.UpdateAnimation(dt)
+  } 
 } 
 
 
 /** Updates all tiles in a tile set so they all get animated. */
-void tileset_update(Tileset * set, double dt) {
-  int index, size;
-  if (!set) return;
-  if (!set->tiles) return;
-  size = tileset_size(set);
-  for (index = 0; index <  size; index++) {
-    Tile * tile = tileset_get(set, index); 
-    tile_update(tile, dt);
-  }  
+func (set * Set) Update(dt float64) {
+  if nil == set.Tiles {
+       return
+  }
+  
+  for i := 0 ; i < len(set.Tiles); i ++ {
+        tile := &set.Tiles[i]
+        tile.Update(dt)
+  }
 } 
 
 
 /** Draw a tile to the current active drawing target at the
 given coordinates. Does nothing if tile is NULL.  */
-void tile_draw(Tile * tile, int x, int y, int drawflags) {  
-  Tileset * set;
-  Image * sheet;
-  Color dcolor = al_map_rgb(0xee, 0xee, 0x00);
-  if (!tile) return;
-  set   =  tile->set;
-  sheet = set->sheet;
-  float dx      = (float) x;
-  float dy      = (float) y; 
-  float sx      = (float) tile->now.x;
-  float sy      = (float) tile->now.y;
-  float sw      = (float) TILE_W;
-  float sh      = (float) TILE_H;
-  al_draw_bitmap_region(sheet, sx, sy, sw, sh, dx, dy, drawflags);
+func (tile Tile) Draw(x, y float32, drawflags int) {  
+  set   := tile.Tileset
+  sheet := set.Sheet
+  dx    := float32(x)
+  dy    := float32(y) 
+  sx    := float32(tile.Position.X)
+  sy    := float32(tile.Position.Y)
+  sw    := float32(set.TileW)
+  sh    := float32(set.TileH)
+  sheet.DrawRegion(sx, sy, sw, sh, dx, dy, drawflags);
   // debugging solid tiles
-#ifdef TILE_SHOW_SOLID  
-  if (tile_isflag(tile, TILE_WALL)) {
-    al_draw_rectangle(dx, dy, dx+TILE_W, dy+TILE_H, dcolor, 2);
-  } 
-#endif // TILE_SHOW_SOLID
-  
-  // al_draw_bitmap(sheet, dx, dy, 0);
+  if showSolid { 
+    if (tile.Flags & (1<<TILE_WALL)) == (1<<TILE_WALL) {
+        dcolor := al.MapRGB(0xee, 0xee, 0x00)
+        al.DrawRectangle(dx, dy, dx+sw, dy+sh, dcolor, 2);
+    }
+  }
 }
 
-
 /* Used for drawing masked tiles. */
-static Image  * tile_mask_buffer = NULL;   
-
+var tileMaskBuffer * al.Bitmap
 
 /** Draw a tile into the given bitmap, which should be of size TILE_W, TILE_H 
  * applying the given mask bitmap, where the mask will 
@@ -352,162 +337,68 @@ should be white, but with different alpha levels on the white
 which will be applied as the mask. Does nothing if tile is NULL.  
 This requires al_hold_bitmap_drawing to be turned off!
 */
-void tile_draw_masked_to
-(Image * result, Tile * tile, Image * mask, float angle, int mask_flags) {  
+func (tile * Tile) DrawMaskedTo(result * al.Bitmap, mask * al.Bitmap, angle float32, mask_flags int) {
   /* This function need a mask buffer. */
-  Tileset * set;
-  Image * sheet;
-  ALLEGRO_BITMAP * target;
-  Color dcolor = al_map_rgb(0xee, 0x00, 0xee);
-  float dx, dy, sx, sy, sw, sh;
-  int bmpflags;
-  
-  if (!tile) return;
   
   /* Create a 32x32 tile bitmap that will be reused thanks to 
    it being static. And leaked at program shutdown, but I don't care :p. */
-  if (!tile_mask_buffer) { 
-    bmpflags         = al_get_new_bitmap_flags();
-    al_set_new_bitmap_flags(ALLEGRO_CONVERT_BITMAP);
-    tile_mask_buffer = al_create_bitmap(TILE_W, TILE_H);
-    al_set_new_bitmap_flags(bmpflags);
+  if nil == tileMaskBuffer { 
+    bmpflags      := al.NewBitmapFlags()
+    al.SetNewBitmapFlags(al.CONVERT_BITMAP)    
+    tileMaskBuffer = al.CreateBitmap(TILE_W, TILE_H)
+    al.SetNewBitmapFlags(bmpflags)
   } 
   
   /* Keep the target bitmap. */
-  target = al_get_target_bitmap();
+  target := al.TargetBitmap()
   
   /* Copy the tile into the buffer.  */
-  al_set_target_bitmap(tile_mask_buffer);
-  set     = tile->set;
-  sheet   = set->sheet;
-  dx      = 0.0;
-  dy      = 0.0; 
-  sx      = (float) tile->now.x;
-  sy      = (float) tile->now.y;
-  sw      = (float) TILE_W;
-  sh      = (float) TILE_H;
+  al.SetTargetBitmap(tileMaskBuffer)
+  set    := tile.Tileset
+  sheet  := set.Sheet
+  dx     := float32(0.0)
+  dy     := float32(0.0)
+  sx     := float32(tile.Position.X)
+  sy     := float32(tile.Position.Y)
+  sw     := float32(set.TileW )
+  sh     := float32(set.TileH )
   /* Set blender to copy mode. */
-  // al_clear_to_color(al_map_rgba_f(0,0,0,0));
-
-  al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);  
-  al_draw_bitmap_region(sheet, sx, sy, sw, sh, 0, 0, 0);
+  al.SetBlender(al.ADD, al.ONE, al.ZERO) 
+  sheet.DrawRegion(sx, sy, sw, sh, 0, 0, 0);
   
   /* Draw the mask over the tile, taking the alpha of the mask  */
-  al_set_blender(ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ALPHA);
-  al_draw_bitmap(mask, 0, 0, mask_flags);
-
-  /* Restore normal Allegro blending. */
-  al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
+  al.SetBlender(al.ADD, al.ZERO, al.ALPHA) 
+  mask.Draw(0, 0, mask_flags)
   
-  sx = 0.0;
-  sy = 0.0;
+  /* Restore normal Allegro blending. */
+  al.SetBlender(al.ADD, al.ONE, al.INVERSE_ALPHA) 
+
+  sx = 0.0
+  sy = 0.0
   if (angle != 0.0) {
-    sx = TILE_H / 2.0;
-    sy = TILE_W / 2.0;
-    dx += sx;
-    dy += sy;
-  } 
+    sx = float32(set.TileW) / 2.0
+    sy = float32(set.TileH) / 2.0
+    dx += sx
+    dy += sy
+  }
   
   /* Draw the tile mask buffer to the result bitmap */
-  al_set_target_bitmap(result);
-  al_draw_rotated_bitmap(tile_mask_buffer, sx, sy, dx, dy, angle, 0);
+  al.SetTargetBitmap(result)
+  tileMaskBuffer.DrawRotated(sx, sy, dx, dy, angle, 0)
   /* And restore the target bitmap. */ 
-  al_set_target_bitmap(target);
-}
-
-/** Tile's index. Returns -1 if tile is NULL; */
-int tile_index(Tile * tile) { 
-  if (!tile) return -1;
-  return tile->index;
+  al.SetTargetBitmap(target)
 }
 
-/**  Information about the tile's properties. Return -1 if tile is NULL. */
-int tile_kind(Tile * tile) { 
-  if (!tile) return -1;
-  return tile->kind;
-}
-
-/** Information about tile's blending properties and priority.
- * Zero means no blending, positive is a blend priority.
- */
-int tile_blend(Tile * tile) {
-  if (!tile) return 0;
-  return tile->blend;
-}
-
-/** Set the tile's blending and priority */
-int tile_blend_(Tile * tile, int priority) {
-  if (!tile) return 0;
-  return tile->blend = priority;
-}
-
-/** Information about tile's blending mask
- * Returns 0 for the default mask if not set.
- */
-int tile_blend_mask(Tile * tile) {
-  if (!tile) return 0;
-  return tile->blend_mask;
-}
-
-/** Set the tile's blending mask */
-int tile_blend_mask_(Tile * tile, int mask) {
-  if (!tile) return 0;
-  if (mask < 0) mask = 0;
-  if (mask > 2) mask = 0;
-  return tile->blend_mask = mask;
-}
 
-/** Get the tile's light flag. Zero means no lighting. */
-int tile_light(Tile * tile) {
-  if (!tile) return 0;
-  return tile->light;
-}
-
-/** Set the tile's light flag */
-int tile_light_(Tile * tile, int value) {
-  if (!tile) return 0;
-  return tile->light = value;
-}
-
-/** Information about tile's lighting mask
- * Returns 0 for the default mask if not set.
- */
-int tile_light_mask(Tile * tile) {
-  if (!tile) return 0;
-  return tile->light_mask;
-}
-
-/** Set the tile's light mask */
-int tile_light_mask_(Tile * tile, int mask) {
-  if (!tile) return 0;
-  if (mask < 0) mask = 0;
-  return tile->light_mask = mask;
-}
-
-/** Get the tile's shadow flag. Zero means no autoshadow. */
-int tile_shadow(Tile * tile) {
-  if (!tile) return 0;
-  return tile->shadow;
-}
-
-/** Set the tile's light flag */
-int tile_shadow_(Tile * tile, int value) {
-  if (!tile) return 0;
-  return tile->shadow = value;
-}
-
-/** Information about tile's shadow mask
- * Returns 0 for the default mask if not set.
- */
-int tile_shadow_mask(Tile * tile) {
-  if (!tile) return 0;
-  return tile->shadow_mask;
-}
-
-/** Set the tile's shadow mask */
-int tile_shadow_mask_(Tile * tile, int mask) {
-  if (!tile) return 0;
-  if (mask < 0) mask = 0;
-  return tile->shadow_mask = mask;
+func (set Set) Tile(index int) * Tile {
+    if set.Tiles == nil {
+        return nil
+    }
+    
+    if (index < 0) || (index > len(set.Tiles)) {
+        return nil
+    }
+    
+    return &set.Tiles[index]
 }