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

eu.joaocosta.minart.graphics.Plane.scala Maven / Gradle / Ivy

package eu.joaocosta.minart.graphics

/** A procedurally generated infinite surface.
  *
  * Can be clipped to create a surface.
  */
trait Plane extends Function2[Int, Int, Color] { outer =>

  /** Returns the color at position (x, y). */
  def apply(x: Int, y: Int): Color = getPixel(x, y)

  /** Returns the color at position (x, y). */
  def getPixel(x: Int, y: Int): Color

  /** Maps the colors from this plane. */
  final def map(f: Color => Color): Plane = new Plane {
    def getPixel(x: Int, y: Int): Color = f(outer.getPixel(x, y))
  }

  /** Flatmaps this plane */
  final def flatMap(f: Color => Plane): Plane = new Plane {
    def getPixel(x: Int, y: Int): Color = f(outer.getPixel(x, y)).getPixel(x, y)
  }

  /** Contramaps the positions from this plane. */
  final def contramap(f: (Int, Int) => (Int, Int)): Plane = new Plane {
    def getPixel(x: Int, y: Int): Color = {
      val res = f(x, y)
      outer.getPixel(res._1, res._2)
    }
  }

  /** Combines this plane with another by combining their colors with the given function. */
  final def zipWith(that: Plane, f: (Color, Color) => Color): Plane = new Plane {
    def getPixel(x: Int, y: Int): Color = {
      val c1 = outer.getPixel(x, y)
      val c2 = that.getPixel(x, y)
      f(c1, c2)
    }
  }

  /** Combines this plane with a surface by combining their colors with the given function. */
  final def zipWith(that: Surface, f: (Color, Color) => Color): SurfaceView = SurfaceView(
    new Plane {
      def getPixel(x: Int, y: Int): Color = {
        val c1 = outer.getPixel(x, y)
        that.getPixel(x, y).fold(c1)(c2 => f(c1, c2))
      }
    },
    width = that.width,
    height = that.height
  )

  /** Coflatmaps this plane with a Plane => Color function.
    * Effectively, each pixel of the new plane is computed from a translated plane, which can be used to
    * implement convolutions.
    */
  final def coflatMap(f: Plane => Color): Plane =
    new Plane {
      def getPixel(x: Int, y: Int): Color =
        f((dx: Int, dy: Int) => outer.getPixel(x + dx, y + dy))
    }

  /** Clips this plane to a chosen rectangle
    *
    * @param cx leftmost pixel on the surface
    * @param cy topmost pixel on the surface
    * @param cw clip width
    * @param ch clip height
    */
  final def clip(cx: Int, cy: Int, cw: Int, ch: Int): SurfaceView =
    if (cx == 0 && cy == 0) toSurfaceView(cw, ch)
    else
      new Plane {
        def getPixel(x: Int, y: Int): Color = {
          outer.getPixel(x + cx, y + cy)
        }
      }.toSurfaceView(cw, ch)

  /** Inverts a plane color. */
  def invertColor: Plane = map(_.invert)

  /** Contramaps this plane using a matrix instead of a function.
    *
    *  This method can be chained multiple times efficiently.
    *
    * Note that this is *contramaping*. The operation is applied as
    * [a b c] [dx] = [sx]
    * [d e f] [dy]   [sy]
    * [0 0 1] [ 1]   [ 1]
    *
    * Where (sx,sy) are the positions in the original plane and (dx, dy) are the positions in the new plane.
    *
    * This means that you need to invert the transformations to use the common transformation matrices.
    *
    * For example, the matrix:
    *
    * [2 0 0] [dx] = [sx]
    * [0 2 0] [dy]   [sy]
    * [0 0 1] [ 1]   [ 1]
    *
    * Will *scale down* the image, not scale up.
    */
  def contramapMatrix(matrix: Matrix) =
    Plane.MatrixPlane(matrix, this)

  /** Translates a plane. */
  def translate(dx: Double, dy: Double): Plane = contramapMatrix(Matrix(1, 0, -dx, 0, 1, -dy))

  /** Flips a plane horizontally. */
  def flipH: Plane = contramapMatrix(Matrix(-1, 0, 0, 0, 1, 0))

  /** Flips a plane vertically. */
  def flipV: Plane = contramapMatrix(Matrix(1, 0, 0, 0, -1, 0))

  /** Scales a plane. */
  def scale(sx: Double, sy: Double): Plane = contramapMatrix(Matrix(1.0 / sx, 0, 0, 0, 1.0 / sy, 0))

  /** Scales a plane. */
  def scale(s: Double): Plane = scale(s, s)

  /** Rotates a plane by a certain angle (clockwise). */
  def rotate(theta: Double): Plane = {
    val ct = math.cos(-theta)
    val st = math.sin(-theta)
    contramapMatrix(Matrix(ct, -st, 0, st, ct, 0))
  }

  /** Shears a plane. */
  def shear(sx: Double, sy: Double): Plane = contramapMatrix(Matrix(1.0, -sx, 0, -sy, 1.0, 0))

  /** Transposes a plane (switches the x and y coordinates). */
  def transpose: Plane = contramapMatrix(Matrix(0, 1, 0, 1, 0, 0))

  /** Converts this plane to a surface view, assuming (0, 0) as the top-left corner.
    *
    * @param width surface view width
    * @param height surface view height
    */
  final def toSurfaceView(width: Int, height: Int): SurfaceView =
    SurfaceView(this, width, height)

  /** Converts this plane to a RAM surface, assuming (0, 0) as the top-left corner.
    *
    * @param width surface width
    * @param height surface height
    */
  final def toRamSurface(width: Int, height: Int): RamSurface =
    toSurfaceView(width, height).toRamSurface()
}

object Plane {
  private[Plane] final case class MatrixPlane(matrix: Matrix, plane: Plane) extends Plane {
    def getPixel(x: Int, y: Int): Color = {
      val (xx, yy) = matrix(x, y)
      plane.getPixel(xx, yy)
    }

    override def contramapMatrix(matrix: Matrix) =
      MatrixPlane(this.matrix.multiply(matrix), plane)
  }

  /** Creates a plane from a constant color.
    *
    * @param constant constant color
    */
  def fromConstant(color: Color): Plane = new Plane {
    def getPixel(x: Int, y: Int): Color = color
  }

  /** Creates a plane from a generator function.
    *
    * @param generator generator function from (x, y) to a color
    */
  def fromFunction(generator: (Int, Int) => Color): Plane = new Plane {
    def getPixel(x: Int, y: Int): Color = {
      generator(x, y)
    }
  }

  /** Creates a plane from a surface, filling the remaining pixels with a fallback color.
    * @param surface reference surface
    * @param fallback fallback color
    */
  def fromSurfaceWithFallback(surface: Surface, fallback: Color): Plane = new Plane {
    def getPixel(x: Int, y: Int): Color = {
      surface.getPixelOrElse(x, y, fallback)
    }
  }

  /** Creates a plane from a surface, by repeating that surface accross both axis.
    * @param surface reference surface
    */
  @deprecated("Use surface.view.repeating")
  def fromSurfaceWithRepetition(surface: Surface): Plane =
    surface.view.repeating
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy