diff --git a/assets/hex.tmx b/assets/hex.tmx new file mode 100644 index 0000000..d474e03 --- /dev/null +++ b/assets/hex.tmx @@ -0,0 +1,18 @@ + + + + + +1,2,4,6,6,6,2,6,6,6, +4,5,4,6,6,2,6,6,6,6, +5,5,4,6,6,2,6,6,6,6, +4,4,6,6,2,6,6,6,6,6, +6,6,6,6,2,6,6,6,6,6, +6,6,6,2,6,6,6,6,6,6, +6,6,6,2,6,6,6,6,6,6, +6,6,2,6,6,6,6,6,6,6, +6,6,2,6,6,6,6,6,6,6, +6,2,6,6,6,6,6,6,6,6 + + + diff --git a/assets/tilesets/hex-tiles.png b/assets/tilesets/hex-tiles.png new file mode 100644 index 0000000..92d9701 Binary files /dev/null and b/assets/tilesets/hex-tiles.png differ diff --git a/assets/tilesets/hex-tiles.tsx b/assets/tilesets/hex-tiles.tsx new file mode 100644 index 0000000..02d5f06 --- /dev/null +++ b/assets/tilesets/hex-tiles.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/render/hexagonal.go b/render/hexagonal.go new file mode 100644 index 0000000..c1e4cd8 --- /dev/null +++ b/render/hexagonal.go @@ -0,0 +1,96 @@ +/* +Copyright (c) 2022 Andre Renaud + +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. +*/ + +package render + +import ( + "image" + + "github.com/disintegration/imaging" + tiled "github.com/lafriks/go-tiled" +) + +// HexagonalRendererEngine represents hexangonal rendering engine. +type HexagonalRendererEngine struct { + m *tiled.Map +} + +// Init initializes rendering engine with provided map options. +func (e *HexagonalRendererEngine) Init(m *tiled.Map) { + e.m = m +} + +// GetFinalImageSize returns final image size based on map data. +func (e *HexagonalRendererEngine) GetFinalImageSize() image.Rectangle { + switch e.m.StaggerAxis { + case tiled.AxisX: + return image.Rect(0, 0, e.m.Width*e.m.TileWidth, e.m.Height*e.m.TileHeight+e.m.TileHeight/2) + case tiled.AxisY: + return image.Rect(0, 0, e.m.Width*e.m.TileWidth+e.m.TileWidth/2, (e.m.Height+1)*e.m.TileHeight*3/4) + } + return image.Rectangle{} +} + +// RotateTileImage rotates provided tile layer. +func (e *HexagonalRendererEngine) RotateTileImage(tile *tiled.LayerTile, img image.Image) image.Image { + timg := img + if tile.HorizontalFlip { + timg = imaging.FlipH(timg) + } + if tile.VerticalFlip { + timg = imaging.FlipV(timg) + } + if tile.DiagonalFlip { + timg = imaging.FlipH(imaging.Rotate90(timg)) + } + + return timg +} + +// GetTilePosition returns tile position in image. +func (e *HexagonalRendererEngine) GetTilePosition(x, y int) image.Rectangle { + switch e.m.StaggerAxis { + case tiled.AxisX: + oddColumn := (x % 2) == 1 + offsetWidth := e.m.TileWidth * 3 / 4 + yBump := 0 + if oddColumn { + yBump = e.m.TileHeight / 2 + } + return image.Rect(x*offsetWidth, + y*e.m.TileHeight+yBump, + x*offsetWidth+e.m.TileWidth, + (y+2)*e.m.TileHeight+yBump) + case tiled.AxisY: + oddRow := (y % 2) == 1 + offsetHeight := e.m.TileHeight * 3 / 4 + xBump := 0 + if oddRow { + xBump = e.m.TileWidth / 2 + } + return image.Rect(x*e.m.TileHeight+xBump, + y*offsetHeight, + (x+2)*e.m.TileWidth+xBump, + (y+1)*offsetHeight+e.m.TileHeight) + } + return image.Rectangle{} +} diff --git a/render/renderer.go b/render/renderer.go index 17f3a99..52a3d53 100644 --- a/render/renderer.go +++ b/render/renderer.go @@ -75,6 +75,8 @@ func NewRendererWithFileSystem(m *tiled.Map, fs fs.FS) (*Renderer, error) { r := &Renderer{m: m, tileCache: make(map[uint32]image.Image), fs: fs} if r.m.Orientation == "orthogonal" { r.engine = &OrthogonalRendererEngine{} + } else if r.m.Orientation == "hexagonal" { + r.engine = &HexagonalRendererEngine{} } else { return nil, ErrUnsupportedOrientation } @@ -137,10 +139,42 @@ func (r *Renderer) getTileImage(tile *tiled.LayerTile) (image.Image, error) { return r.engine.RotateTileImage(tile, timg), nil } +func (r *Renderer) _renderTile(layer *tiled.Layer, i int, x int, y int) error { + if layer.Tiles[i].IsNil() { + return nil + } + + img, err := r.getTileImage(layer.Tiles[i]) + if err != nil { + return err + } + + pos := r.engine.GetTilePosition(x, y) + + if layer.Opacity < 1 { + mask := image.NewUniform(color.Alpha{uint8(layer.Opacity * 255)}) + + draw.DrawMask(r.Result, pos, img, img.Bounds().Min, mask, mask.Bounds().Min, draw.Over) + } else { + draw.Draw(r.Result, pos, img, img.Bounds().Min, draw.Over) + } + + return nil +} + func (r *Renderer) _renderLayer(layer *tiled.Layer) error { var xs, xe, xi, ys, ye, yi int - if r.m.RenderOrder == "" || r.m.RenderOrder == "right-down" { + var odd bool + if r.m.Orientation == "hexagonal" { + xs = 0 + xe = r.m.Width + xi = 2 + ys = 0 + ye = r.m.Height + yi = 1 + odd = true + } else if r.m.RenderOrder == "" || r.m.RenderOrder == "right-down" { xs = 0 xe = r.m.Width xi = 1 @@ -151,30 +185,22 @@ func (r *Renderer) _renderLayer(layer *tiled.Layer) error { return ErrUnsupportedRenderOrder } - i := 0 for y := ys; y*yi < ye; y = y + yi { - for x := xs; x*xi < xe; x = x + xi { - if layer.Tiles[i].IsNil() { - i++ - continue - } - - img, err := r.getTileImage(layer.Tiles[i]) - if err != nil { + i := (y - ys) * xe + for x := xs; x < xe; x = x + xi { + if err := r._renderTile(layer, i, x, y); err != nil { return err } - - pos := r.engine.GetTilePosition(x, y) - - if layer.Opacity < 1 { - mask := image.NewUniform(color.Alpha{uint8(layer.Opacity * 255)}) - - draw.DrawMask(r.Result, pos, img, img.Bounds().Min, mask, mask.Bounds().Min, draw.Over) - } else { - draw.Draw(r.Result, pos, img, img.Bounds().Min, draw.Over) + i += xi + } + if odd { + i = (y-ys)*xe + 1 + for x := xs + 1; x < xe; x = x + xi { + if err := r._renderTile(layer, i, x, y); err != nil { + return err + } + i += xi } - - i++ } }