diff --git a/.gitignore b/.gitignore index 4502932..f3f73ff 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ debug /tmx2img vendor/ map.png +assets/test_output/ diff --git a/assets/test_isometric.tmx b/assets/test_isometric.tmx new file mode 100644 index 0000000..80e2f6c --- /dev/null +++ b/assets/test_isometric.tmx @@ -0,0 +1,76 @@ + + + + + +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,9,9,9,9,9,9,9,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,9,9,9,9,9,9,9,9,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,9,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,12,12,12,12,12,12,12,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,12,12,12,12,12,12,12,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1073741835,0,2147483659,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1610612751,0,2684354572,3221225486,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,9,11,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,9,11,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,9,11,0,0,0,0,0,0,0, +0,0,0,12,12,0,12,12,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,13,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + diff --git a/assets/tilesets/isometric.tsx b/assets/tilesets/isometric.tsx new file mode 100755 index 0000000..19ab86a --- /dev/null +++ b/assets/tilesets/isometric.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/assets/tilesets/isometric_tiles.png b/assets/tilesets/isometric_tiles.png new file mode 100755 index 0000000..40f5e47 Binary files /dev/null and b/assets/tilesets/isometric_tiles.png differ diff --git a/render/isometric.go b/render/isometric.go new file mode 100644 index 0000000..96153f4 --- /dev/null +++ b/render/isometric.go @@ -0,0 +1,58 @@ +package render + +import ( + "image" + + "github.com/disintegration/imaging" + "github.com/lafriks/go-tiled" +) + +type IsometricRendererEngine struct { + m *tiled.Map +} + +func (e *IsometricRendererEngine) Init(m *tiled.Map) { + e.m = m +} + +func (e *IsometricRendererEngine) GetFinalImageSize() image.Rectangle { + side := e.m.Height + e.m.Width + hx := side * e.m.TileWidth/2 + hy := side * e.m.TileHeight/2 + + return image.Rect(0, 0, hx, hy) +} + +func (e *IsometricRendererEngine) RotateTileImage(tile *tiled.LayerTile, img image.Image) image.Image { + timg := img + if tile.DiagonalFlip { + timg = imaging.FlipH(imaging.Rotate270(timg)) + } + if tile.HorizontalFlip { + timg = imaging.FlipH(timg) + } + if tile.VerticalFlip { + timg = imaging.FlipV(timg) + } + + return timg +} + +func (e *IsometricRendererEngine) GetTilePosition(x, y int) image.Point { + tw, th := e.m.TileWidth, e.m.TileHeight + + stepX := tw / 2 + stepY := th / 2 + + offsetX := e.m.Height * e.m.TileWidth/2 + + offsetY := 0 + if tw > th { + offsetY = tw - th + } + + sx := (x - y) * stepX + offsetX - stepX + sy := (x + y) * stepY - offsetY + + return image.Pt(sx, sy) +} \ No newline at end of file diff --git a/render/orthogonal.go b/render/orthogonal.go index cfe762d..0f2de33 100644 --- a/render/orthogonal.go +++ b/render/orthogonal.go @@ -61,9 +61,7 @@ func (e *OrthogonalRendererEngine) RotateTileImage(tile *tiled.LayerTile, img im } // GetTilePosition returns tile position in image. -func (e *OrthogonalRendererEngine) GetTilePosition(x, y int) image.Rectangle { - return image.Rect(x*e.m.TileWidth, - y*e.m.TileHeight, - (x+1)*e.m.TileWidth, - (y+1)*e.m.TileHeight) +func (e *OrthogonalRendererEngine) GetTilePosition(x, y int) image.Point { + return image.Pt(x*e.m.TileWidth, + y*e.m.TileHeight) } diff --git a/render/render_objects_test.go b/render/render_objects_test.go index 20820cb..71ad447 100644 --- a/render/render_objects_test.go +++ b/render/render_objects_test.go @@ -41,7 +41,7 @@ func TestRenderer_RenderObjectGroup(t *testing.T) { renderer.RenderObjectGroup(0) - w, _ := os.Create("../assets/test_render_objects.png") + w, _ := os.Create("../assets/test_output/test_render_objects.png") defer w.Close() if err = renderer.SaveAsPng(w); err != nil { diff --git a/render/renderer.go b/render/renderer.go index 56d4c48..e0eebff 100644 --- a/render/renderer.go +++ b/render/renderer.go @@ -54,7 +54,7 @@ type RendererEngine interface { Init(m *tiled.Map) GetFinalImageSize() image.Rectangle RotateTileImage(tile *tiled.LayerTile, img image.Image) image.Image - GetTilePosition(x, y int) image.Rectangle + GetTilePosition(x, y int) image.Point } // Renderer represents an rendering engine. @@ -74,10 +74,14 @@ func NewRenderer(m *tiled.Map) (*Renderer, error) { // NewRendererWithFileSystem creates new rendering engine instance with a custom file system. 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" { + switch r.m.Orientation { + case "orthogonal": r.engine = &OrthogonalRendererEngine{} - } else { + case "isometric": + r.engine = &IsometricRendererEngine{} + default: return nil, ErrUnsupportedOrientation + } r.engine.Init(r.m) @@ -165,13 +169,14 @@ func (r *Renderer) _renderLayer(layer *tiled.Layer) error { } pos := r.engine.GetTilePosition(x, y) + renderRect := image.Rect(pos.X, pos.Y, pos.X + img.Bounds().Dx(), pos.Y + img.Bounds().Dy()) 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) + draw.DrawMask(r.Result, renderRect, img, img.Bounds().Min, mask, mask.Bounds().Min, draw.Over) } else { - draw.Draw(r.Result, pos, img, img.Bounds().Min, draw.Over) + draw.Draw(r.Result, renderRect, img, img.Bounds().Min, draw.Over) } i++ diff --git a/render/renderer_test.go b/render/renderer_test.go new file mode 100644 index 0000000..7a841b3 --- /dev/null +++ b/render/renderer_test.go @@ -0,0 +1,81 @@ +package render + +import ( + "image" + "os" + "testing" + "path/filepath" + + "github.com/lafriks/go-tiled" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMain(m *testing.M) { + dir := filepath.Join("..", "assets", "test_output") + if err := os.MkdirAll(dir, 0755); err != nil { + os.Exit(1) + } + + exitCode := m.Run() + os.Exit(exitCode) +} + +func TestRenderer_RenderOrthogonalMap(t *testing.T) { + tiledMap, err := tiled.LoadFile("../assets/test_wangsets_map.tmx") + + if err != nil { + t.Error(err) + return + } + + renderer, err := NewRenderer(tiledMap) + if err != nil { + t.Error(err) + return + } + + renderer.RenderVisibleLayers() + + w, _ := os.Create("../assets/test_output/test_render_orthogonal.png") + defer w.Close() + + if err = renderer.SaveAsPng(w); err != nil { + t.Error(err) + } +} + +func TestRenderer_RenderIsometricMap(t *testing.T) { + tiledMap, err := tiled.LoadFile("../assets/test_isometric.tmx") + if err != nil { + t.Error(err) + return + } + + renderer, err := NewRenderer(tiledMap) + if err != nil { + t.Error(err) + return + } + + renderer.RenderVisibleLayers() + + outputPath := "../assets/test_output/test_render_isomap.png" + + w, _ := os.Create(outputPath) + defer w.Close() + + if err = renderer.SaveAsPng(w); err != nil { + t.Error(err) + } + + file, err := os.Open(outputPath) + require.NoError(t, err) + defer file.Close() + + img, _, err := image.Decode(file) + require.NoError(t, err) + + assert.Equal(t, 800, img.Bounds().Dx(), "image width should be 800 pixels") + assert.Equal(t, 400, img.Bounds().Dy(), "image height should be 400 pixels") +} \ No newline at end of file