All Downloads are FREE. Search and download functionalities are using the official Maven repository.

indigo.shared.formats.TiledMap.scala Maven / Gradle / Ivy

The newest version!
package indigo.shared.formats

import indigo.shared.assets.AssetName
import indigo.shared.collections.Batch
import indigo.shared.collections.NonEmptyList
import indigo.shared.datatypes.Point
import indigo.shared.datatypes.Rectangle
import indigo.shared.datatypes.Size
import indigo.shared.materials.Material
import indigo.shared.scenegraph.Graphic
import indigo.shared.scenegraph.Group

import scala.annotation.tailrec

/*
Full spec is here:
http://doc.mapeditor.org/reference/tmx-map-format/

This is not a full implementation. No doubt I'll be adding and tweaking as I go based on requirements.
 */

final case class TiledMap(
    width: Int,
    height: Int,
    infinite: Boolean,
    layers: List[TiledLayer],
    nextobjectid: Int, // Stores the next available ID for new objects. This number is stored to prevent reuse of the same ID after objects have been removed.
    orientation: String, // orthogonal, isometric, staggered and hexagonal
    renderorder: String, // right-down (the default), right-up, left-down and left-up. In all cases, the map is drawn row-by-row.
    tiledversion: String,
    tilewidth: Int,
    tileheight: Int,
    tilesets: List[TileSet],
    `type`: String, // "map"
    hexsidelength: Option[Int],
    staggeraxis: Option[String], // For staggered and hexagonal maps, determines which axis ("x" or "y") is staggered
    staggerindex: Option[
      String
    ], // For staggered and hexagonal maps, determines whether the "even" or "odd" indexes along the staggered axis are shifted.
    backgroundcolor: Option[String] // #AARRGGBB
) derives CanEqual {

  def toGrid[A](mapper: Int => A): Option[TiledGridMap[A]] = {

    def toGridLayer(tiledLayer: TiledLayer): TiledGridLayer[A] =
      TiledGridLayer(
        rec(tiledLayer.data.map(mapper).zipWithIndex, tiledLayer.width, Nil),
        tiledLayer.width,
        tiledLayer.height
      )

    given CanEqual[List[(A, Int)], List[(A, Int)]] = CanEqual.derived

    @tailrec
    def rec(remaining: List[(A, Int)], columnCount: Int, acc: List[TiledGridCell[A]]): List[TiledGridCell[A]] =
      remaining match {
        case Nil =>
          acc

        case (a, i) :: xs =>
          rec(xs, columnCount, acc ++ List(TiledGridCell(i % columnCount, i / columnCount, a)))
      }

    layers match {
      case Nil =>
        None

      case l :: ls =>
        Option(
          TiledGridMap[A](
            NonEmptyList(toGridLayer(l), ls.map(toGridLayer))
          )
        )

    }
  }

  def toGroup(assetName: AssetName): Option[Group] =
    TiledMap.toGroup(this, assetName)

}

final case class TiledLayer(
    name: String,
    data: List[Int],
    x: Int,
    y: Int,
    width: Int,
    height: Int,
    opacity: Double,
    `type`: String, // tilelayer, objectgroup, or imagelayer
    visible: Boolean
) derives CanEqual

final case class TileSet(
    columns: Option[Int],
    firstgid: Int,
    image: Option[String],
    imageheight: Option[Int],
    imagewidth: Option[Int],
    margin: Option[Int],
    name: Option[String],
    spacing: Option[Int],
    terrains: Option[List[TiledTerrain]],
    tilecount: Option[Int],
    tileheight: Option[Int],
    tiles: Option[Map[String, TiledTerrainCorner]],
    tilewidth: Option[Int],
    source: Option[String]
) derives CanEqual

final case class TiledTerrain(name: String, tile: Int) derives CanEqual
final case class TiledTerrainCorner(terrain: List[Int]) derives CanEqual

object TiledMap {

  private def fromIndex(index: Int, gridWidth: Int): Point =
    Point(
      x = index % gridWidth,
      y = index / gridWidth
    )

  def toGroup(tiledMap: TiledMap, assetName: AssetName): Option[Group] =
    tiledMap.tilesets.headOption.flatMap(_.columns).map { tileSheetColumnCount =>
      val tileSize: Size = Size(tiledMap.tilewidth, tiledMap.tileheight)

      val layers: Batch[Group] =
        Batch.fromList(tiledMap.layers).map { layer =>
          val tilesInUse: Map[Int, Graphic[Material.Bitmap]] =
            layer.data.toSet.foldLeft(Map.empty[Int, Graphic[Material.Bitmap]]) { (tiles, i) =>
              tiles ++ Map(
                i ->
                  Graphic(Rectangle(Point.zero, tileSize), 1, Material.Bitmap(assetName))
                    .withCrop(
                      Rectangle(fromIndex(i - 1, tileSheetColumnCount) * tileSize.toPoint, tileSize)
                    )
              )
            }

          Group(
            Batch.fromList(layer.data).zipWithIndex.flatMap { case (tileIndex, positionIndex) =>
              if (tileIndex == 0) Batch.empty
              else
                tilesInUse
                  .get(tileIndex)
                  .map(g => Batch(g.moveTo(fromIndex(positionIndex, tiledMap.width) * tileSize.toPoint)))
                  .getOrElse(Batch.empty)
            }
          )
        }

      Group(layers)
    }

}

final case class TiledGridMap[A](layers: NonEmptyList[TiledGridLayer[A]]) derives CanEqual {

  lazy val toListPerLayer: NonEmptyList[List[TiledGridCell[A]]] =
    layers.map(_.grid)

  lazy val toList2DPerLayer: NonEmptyList[List[List[TiledGridCell[A]]]] = {
    given CanEqual[List[TiledGridCell[A]], List[TiledGridCell[A]]] = CanEqual.derived

    @tailrec
    def rec(
        remaining: List[TiledGridCell[A]],
        columnCount: Int,
        current: List[TiledGridCell[A]],
        acc: List[List[TiledGridCell[A]]]
    ): List[List[TiledGridCell[A]]] =
      remaining match {
        case Nil =>
          acc.reverse

        case x :: xs if x.column == columnCount - 1 =>
          rec(xs, columnCount, Nil, (current ++ List(x)) :: acc)

        case x :: xs =>
          rec(xs, columnCount, current ++ List(x), acc)
      }

    layers.map { layer =>
      rec(layer.grid, layer.columnCount, Nil, Nil)
    }
  }

}
final case class TiledGridLayer[A](grid: List[TiledGridCell[A]], columnCount: Int, rowCount: Int) derives CanEqual
final case class TiledGridCell[A](column: Int, row: Int, tile: A) derives CanEqual {
  lazy val x: Int = column
  lazy val y: Int = row
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy