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

indigo.shared.scenegraph.Clip.scala Maven / Gradle / Ivy

The newest version!
package indigo.shared.scenegraph

import indigo.shared.BoundaryLocator
import indigo.shared.collections.Batch
import indigo.shared.datatypes.Depth
import indigo.shared.datatypes.Flip
import indigo.shared.datatypes.Point
import indigo.shared.datatypes.Radians
import indigo.shared.datatypes.Rectangle
import indigo.shared.datatypes.Size
import indigo.shared.datatypes.Vector2
import indigo.shared.events.GlobalEvent
import indigo.shared.materials.Material
import indigo.shared.materials.ShaderData
import indigo.shared.shader.ShaderPrimitive.float
import indigo.shared.shader.StandardShaders
import indigo.shared.shader.Uniform
import indigo.shared.shader.UniformBlock
import indigo.shared.shader.UniformBlockName
import indigo.shared.time.FPS
import indigo.shared.time.Seconds

final case class Clip[M <: Material](
    size: Size,
    sheet: ClipSheet,
    playMode: ClipPlayMode,
    material: M,
    eventHandlerEnabled: Boolean,
    eventHandler: ((Clip[?], GlobalEvent)) => Option[GlobalEvent],
    position: Point,
    rotation: Radians,
    scale: Vector2,
    depth: Depth,
    ref: Point,
    flip: Flip
) extends EntityNode[Clip[M]]
    with Cloneable
    with SpatialModifiers[Clip[M]]
    derives CanEqual:

  export sheet.frameCount
  export sheet.frameDuration

  lazy val length: Seconds =
    sheet.frameDuration * sheet.frameCount

  def loop: Clip[M] =
    this.copy(playMode = ClipPlayMode.Loop(playMode.direction))

  def playOnce: Clip[M] =
    this.copy(playMode = ClipPlayMode.PlayOnce(playMode.direction, Seconds.zero))
  def playOnce(startTime: Seconds): Clip[M] =
    this.copy(playMode = ClipPlayMode.PlayOnce(playMode.direction, startTime))

  def play: Clip[M] =
    loop
  def play(startTime: Seconds): Clip[M] =
    this.copy(playMode = ClipPlayMode.PlayOnce(playMode.direction, startTime))
  def play(startTime: Seconds, numOfTimes: Int): Clip[M] =
    this.copy(playMode = ClipPlayMode.PlayCount(playMode.direction, startTime, numOfTimes))

  def forwards: Clip[M]       = this.copy(playMode = playMode.forwards)
  def backwards: Clip[M]      = this.copy(playMode = playMode.backwards)
  def reverse: Clip[M]        = this.copy(playMode = playMode.reverse)
  def pingPong: Clip[M]       = this.copy(playMode = playMode.pingPong)
  def smoothPingPong: Clip[M] = this.copy(playMode = playMode.smoothPingPong)

  def withFrameCount(newFrameCount: Int): Clip[M] =
    this.copy(sheet = sheet.withFrameCount(newFrameCount))

  def withFrameDuration(newFrameDuration: Seconds): Clip[M] =
    this.copy(sheet = sheet.withFrameDuration(newFrameDuration))

  def withWrapAt(newWrapAt: Int): Clip[M] =
    this.copy(sheet = sheet.withWrapAt(newWrapAt))

  def withArrangement(newArrangement: ClipSheetArrangement): Clip[M] =
    this.copy(sheet = sheet.withArrangement(newArrangement))

  def withStartOffset(newStartOffset: Int): Clip[M] =
    this.copy(sheet = sheet.withStartOffset(newStartOffset))

  def withFPS(fps: FPS): Clip[M] =
    this.copy(sheet = sheet.withFPS(fps))

  lazy val x: Int = position.x
  lazy val y: Int = position.y

  def withMaterial[MB <: Material](newMaterial: MB): Clip[MB] =
    this.copy(material = newMaterial)
  def modifyMaterial[MB <: Material](alter: M => MB): Clip[MB] =
    this.copy(material = alter(material))

  def withSize(newSize: Size): Clip[M] =
    this.copy(size = newSize)
  def withSize(width: Int, height: Int): Clip[M] =
    withSize(Size(width, height))

  def withSheet(newSheet: ClipSheet): Clip[M] =
    this.copy(sheet = newSheet)
  def modifySheet(alter: ClipSheet => ClipSheet): Clip[M] =
    this.copy(sheet = alter(sheet))

  def withPlayMode(newPlayMode: ClipPlayMode): Clip[M] =
    this.copy(playMode = newPlayMode)
  def modifyPlayMode(alter: ClipPlayMode => ClipPlayMode): Clip[M] =
    this.copy(playMode = alter(playMode))

  def moveTo(pt: Point): Clip[M] =
    this.copy(position = pt)
  def moveTo(x: Int, y: Int): Clip[M] =
    moveTo(Point(x, y))
  def withPosition(newPosition: Point): Clip[M] =
    moveTo(newPosition)

  def moveBy(pt: Point): Clip[M] =
    this.copy(position = position + pt)
  def moveBy(x: Int, y: Int): Clip[M] =
    moveBy(Point(x, y))

  def rotateTo(angle: Radians): Clip[M] =
    this.copy(rotation = angle)
  def rotateBy(angle: Radians): Clip[M] =
    rotateTo(rotation + angle)
  def withRotation(newRotation: Radians): Clip[M] =
    rotateTo(newRotation)

  def scaleBy(amount: Vector2): Clip[M] =
    this.copy(scale = scale * amount)
  def scaleBy(x: Double, y: Double): Clip[M] =
    scaleBy(Vector2(x, y))
  def withScale(newScale: Vector2): Clip[M] =
    this.copy(scale = newScale)

  def transformTo(newPosition: Point, newRotation: Radians, newScale: Vector2): Clip[M] =
    this.copy(position = newPosition, rotation = newRotation, scale = newScale)

  def transformBy(positionDiff: Point, rotationDiff: Radians, scaleDiff: Vector2): Clip[M] =
    transformTo(position + positionDiff, rotation + rotationDiff, scale * scaleDiff)

  def withDepth(newDepth: Depth): Clip[M] =
    this.copy(depth = newDepth)

  def flipHorizontal(isFlipped: Boolean): Clip[M] =
    this.copy(flip = flip.withHorizontalFlip(isFlipped))
  def flipVertical(isFlipped: Boolean): Clip[M] =
    this.copy(flip = flip.withVerticalFlip(isFlipped))
  def withFlip(newFlip: Flip): Clip[M] =
    this.copy(flip = newFlip)

  def withRef(newRef: Point): Clip[M] =
    this.copy(ref = newRef)
  def withRef(x: Int, y: Int): Clip[M] =
    withRef(Point(x, y))

  lazy val toShaderData: ShaderData =
    val data = material.toShaderData
    data
      .withShaderId(StandardShaders.shaderIdToClipShaderId(data.shaderId))
      .addUniformBlock(
        UniformBlock(
          UniformBlockName("IndigoClipData"),
          Batch(
            Uniform("CLIP_SHEET_FRAME_COUNT")    -> float(sheet.frameCount),
            Uniform("CLIP_SHEET_FRAME_DURATION") -> float.fromSeconds(sheet.frameDuration),
            Uniform("CLIP_SHEET_WRAP_AT")        -> float(sheet.wrapAt),
            Uniform("CLIP_SHEET_ARRANGEMENT")    -> float(sheet.arrangement.toInt),
            Uniform("CLIP_SHEET_START_OFFSET")   -> float(sheet.startOffset),
            Uniform("CLIP_PLAY_DIRECTION")       -> float(playMode.direction.toInt),
            Uniform("CLIP_PLAYMODE_START_TIME")  -> float.fromSeconds(playMode.giveStartTime),
            Uniform("CLIP_PLAYMODE_TIMES")       -> float(playMode.giveTimes)
          )
        )
      )

  def withEventHandler(f: ((Clip[?], GlobalEvent)) => Option[GlobalEvent]): Clip[M] =
    this.copy(eventHandler = f, eventHandlerEnabled = true)
  def onEvent(f: PartialFunction[(Clip[?], GlobalEvent), GlobalEvent]): Clip[M] =
    withEventHandler(f.lift)
  def enableEvents: Clip[M] =
    this.copy(eventHandlerEnabled = true)
  def disableEvents: Clip[M] =
    this.copy(eventHandlerEnabled = false)

  def toGraphic: Graphic[M] =
    toGraphic(0)

  def toGraphic(frameNumber: Int): Graphic[M] =
    val num   = Math.min(sheet.frameCount - 1, Math.max(0, frameNumber))
    val frame = num + sheet.startOffset
    def framePositon: Point =
      sheet.arrangement match
        case ClipSheetArrangement.Horizontal =>
          Point(
            x = frame % sheet.wrapAt,
            y = frame / sheet.wrapAt
          )

        case ClipSheetArrangement.Vertical =>
          Point(
            x = frame / sheet.wrapAt,
            y = frame % sheet.wrapAt
          )

    Graphic(
      eventHandlerEnabled = false,
      eventHandler = Function.const(None),
      position = position,
      rotation = rotation,
      scale = scale,
      depth = depth,
      ref = ref,
      flip = flip,
      crop = Rectangle(framePositon * size.toPoint, size),
      material = material
    )

  def scrubTo(position: Double): Clip[M] =
    val p = Math.min(1.0d, Math.max(0.0d, position))
    toFrame((sheet.frameCount * p).toInt)

  def toFrame(frameNumber: Int): Clip[M] =
    this.copy(
      sheet = sheet
        .withStartOffset(frameNumber)
        .withFrameCount(1)
    )

object Clip:

  def apply[M <: Material](
      width: Int,
      height: Int,
      sheet: ClipSheet,
      playMode: ClipPlayMode,
      material: M
  ): Clip[M] =
    Clip(
      size = Size(width, height),
      sheet = sheet,
      playMode = playMode,
      material = material,
      eventHandlerEnabled = false,
      eventHandler = Function.const(None),
      position = Point.zero,
      rotation = Radians.zero,
      scale = Vector2.one,
      depth = Depth.zero,
      ref = Point.zero,
      flip = Flip.default
    )

  def apply[M <: Material](
      width: Int,
      height: Int,
      sheet: ClipSheet,
      material: M
  ): Clip[M] =
    Clip(
      size = Size(width, height),
      sheet = sheet,
      playMode = ClipPlayMode.default,
      material = material,
      eventHandlerEnabled = false,
      eventHandler = Function.const(None),
      position = Point.zero,
      rotation = Radians.zero,
      scale = Vector2.one,
      depth = Depth.zero,
      ref = Point.zero,
      flip = Flip.default
    )

  def apply[M <: Material](
      x: Int,
      y: Int,
      width: Int,
      height: Int,
      sheet: ClipSheet,
      playMode: ClipPlayMode,
      material: M
  ): Clip[M] =
    Clip(
      size = Size(width, height),
      sheet = sheet,
      playMode = playMode,
      material = material,
      eventHandlerEnabled = false,
      eventHandler = Function.const(None),
      position = Point(x, y),
      rotation = Radians.zero,
      scale = Vector2.one,
      depth = Depth.zero,
      ref = Point.zero,
      flip = Flip.default
    )

  def apply[M <: Material](
      x: Int,
      y: Int,
      width: Int,
      height: Int,
      sheet: ClipSheet,
      material: M
  ): Clip[M] =
    Clip(
      size = Size(width, height),
      sheet = sheet,
      playMode = ClipPlayMode.default,
      material = material,
      eventHandlerEnabled = false,
      eventHandler = Function.const(None),
      position = Point(x, y),
      rotation = Radians.zero,
      scale = Vector2.one,
      depth = Depth.zero,
      ref = Point.zero,
      flip = Flip.default
    )

  def apply[M <: Material](
      size: Size,
      sheet: ClipSheet,
      playMode: ClipPlayMode,
      material: M
  ): Clip[M] =
    Clip(
      size = size,
      sheet = sheet,
      playMode = playMode,
      material = material,
      eventHandlerEnabled = false,
      eventHandler = Function.const(None),
      position = Point.zero,
      rotation = Radians.zero,
      scale = Vector2.one,
      depth = Depth.zero,
      ref = Point.zero,
      flip = Flip.default
    )

  def apply[M <: Material](
      size: Size,
      sheet: ClipSheet,
      material: M
  ): Clip[M] =
    Clip(
      size = size,
      sheet = sheet,
      playMode = ClipPlayMode.default,
      material = material,
      eventHandlerEnabled = false,
      eventHandler = Function.const(None),
      position = Point.zero,
      rotation = Radians.zero,
      scale = Vector2.one,
      depth = Depth.zero,
      ref = Point.zero,
      flip = Flip.default
    )

  def apply[M <: Material](
      position: Point,
      size: Size,
      sheet: ClipSheet,
      playMode: ClipPlayMode,
      material: M
  ): Clip[M] =
    Clip(
      size = size,
      sheet = sheet,
      playMode = playMode,
      material = material,
      eventHandlerEnabled = false,
      eventHandler = Function.const(None),
      position = position,
      rotation = Radians.zero,
      scale = Vector2.one,
      depth = Depth.zero,
      ref = Point.zero,
      flip = Flip.default
    )

  def apply[M <: Material](
      position: Point,
      size: Size,
      sheet: ClipSheet,
      material: M
  ): Clip[M] =
    Clip(
      size = size,
      sheet = sheet,
      playMode = ClipPlayMode.default,
      material = material,
      eventHandlerEnabled = false,
      eventHandler = Function.const(None),
      position = position,
      rotation = Radians.zero,
      scale = Vector2.one,
      depth = Depth.zero,
      ref = Point.zero,
      flip = Flip.default
    )

enum ClipSheetArrangement derives CanEqual:
  case Horizontal, Vertical

  // Using this instead of `ordinal` so that order shouldn't break implementation...
  def toInt: Int =
    this match
      case ClipSheetArrangement.Horizontal => 0
      case ClipSheetArrangement.Vertical   => 1

object ClipSheetArrangement:
  val default: ClipSheetArrangement =
    ClipSheetArrangement.Horizontal

final case class ClipSheet(
    frameCount: Int,
    frameDuration: Seconds,
    wrapAt: Int,
    arrangement: ClipSheetArrangement,
    startOffset: Int
):
  def withFrameCount(newFrameCount: Int): ClipSheet =
    this.copy(frameCount = newFrameCount)

  def withFrameDuration(newFrameDuration: Seconds): ClipSheet =
    this.copy(frameDuration = newFrameDuration)

  def withWrapAt(newWrapAt: Int): ClipSheet =
    this.copy(wrapAt = newWrapAt)

  def withArrangement(newArrangement: ClipSheetArrangement): ClipSheet =
    this.copy(arrangement = newArrangement)

  def withStartOffset(newStartOffset: Int): ClipSheet =
    this.copy(startOffset = newStartOffset)

  def withFPS(fps: FPS): ClipSheet =
    withFrameDuration(fps.toSeconds)

object ClipSheet:

  def apply(frameCount: Int, frameDuration: Seconds): ClipSheet =
    ClipSheet(frameCount, frameDuration, frameCount, ClipSheetArrangement.default, 0)

  def apply(frameCount: Int, fps: FPS): ClipSheet =
    ClipSheet(frameCount, fps.toSeconds, frameCount, ClipSheetArrangement.default, 0)

  def apply(frameCount: Int, frameDuration: Seconds, wrapAt: Int): ClipSheet =
    ClipSheet(frameCount, frameDuration, wrapAt, ClipSheetArrangement.default, 0)

  def apply(frameCount: Int, fps: FPS, wrapAt: Int): ClipSheet =
    ClipSheet(frameCount, fps.toSeconds, wrapAt, ClipSheetArrangement.default, 0)

  def apply(frameCount: Int, frameDuration: Seconds, wrapAt: Int, arrangement: ClipSheetArrangement): ClipSheet =
    ClipSheet(frameCount, frameDuration, wrapAt, arrangement, 0)

  def apply(frameCount: Int, fps: FPS, wrapAt: Int, arrangement: ClipSheetArrangement): ClipSheet =
    ClipSheet(frameCount, fps.toSeconds, wrapAt, arrangement, 0)

enum ClipPlayDirection derives CanEqual:
  case Forward, Backward, PingPong, SmoothPingPong

  // Using this instead of `ordinal` so that order shouldn't break implementation...
  def toInt: Int =
    this match
      case ClipPlayDirection.Forward        => 0
      case ClipPlayDirection.Backward       => 1
      case ClipPlayDirection.PingPong       => 2
      case ClipPlayDirection.SmoothPingPong => 3

  def reverse: ClipPlayDirection =
    this match
      case ClipPlayDirection.Forward  => ClipPlayDirection.Backward
      case ClipPlayDirection.Backward => ClipPlayDirection.Forward
      case d                          => d

object ClipPlayDirection:
  val default: ClipPlayDirection =
    ClipPlayDirection.Forward

enum ClipPlayMode derives CanEqual:
  val direction: ClipPlayDirection

  case Loop(direction: ClipPlayDirection)                                      extends ClipPlayMode
  case PlayOnce(direction: ClipPlayDirection, startTime: Seconds)              extends ClipPlayMode
  case PlayCount(direction: ClipPlayDirection, startTime: Seconds, times: Int) extends ClipPlayMode

  def giveStartTime: Seconds =
    this match
      case _: ClipPlayMode.Loop      => Seconds.zero
      case x: ClipPlayMode.PlayOnce  => x.startTime
      case x: ClipPlayMode.PlayCount => x.startTime

  def giveTimes: Int =
    this match
      case _: ClipPlayMode.Loop      => -1
      case _: ClipPlayMode.PlayOnce  => 1
      case x: ClipPlayMode.PlayCount => x.times

  def forwards: ClipPlayMode =
    this match
      case x: ClipPlayMode.Loop      => ClipPlayMode.Loop(ClipPlayDirection.Forward)
      case x: ClipPlayMode.PlayOnce  => ClipPlayMode.PlayOnce(ClipPlayDirection.Forward, x.startTime)
      case x: ClipPlayMode.PlayCount => ClipPlayMode.PlayCount(ClipPlayDirection.Forward, x.startTime, x.times)

  def backwards: ClipPlayMode =
    this match
      case x: ClipPlayMode.Loop      => ClipPlayMode.Loop(ClipPlayDirection.Backward)
      case x: ClipPlayMode.PlayOnce  => ClipPlayMode.PlayOnce(ClipPlayDirection.Backward, x.startTime)
      case x: ClipPlayMode.PlayCount => ClipPlayMode.PlayCount(ClipPlayDirection.Backward, x.startTime, x.times)

  def reverse: ClipPlayMode =
    this match
      case x: ClipPlayMode.Loop      => ClipPlayMode.Loop(x.direction.reverse)
      case x: ClipPlayMode.PlayOnce  => ClipPlayMode.PlayOnce(x.direction.reverse, x.startTime)
      case x: ClipPlayMode.PlayCount => ClipPlayMode.PlayCount(x.direction.reverse, x.startTime, x.times)

  def pingPong: ClipPlayMode =
    this match
      case x: ClipPlayMode.Loop      => ClipPlayMode.Loop(ClipPlayDirection.PingPong)
      case x: ClipPlayMode.PlayOnce  => ClipPlayMode.PlayOnce(ClipPlayDirection.PingPong, x.startTime)
      case x: ClipPlayMode.PlayCount => ClipPlayMode.PlayCount(ClipPlayDirection.PingPong, x.startTime, x.times)

  def smoothPingPong: ClipPlayMode =
    this match
      case x: ClipPlayMode.Loop      => ClipPlayMode.Loop(ClipPlayDirection.SmoothPingPong)
      case x: ClipPlayMode.PlayOnce  => ClipPlayMode.PlayOnce(ClipPlayDirection.SmoothPingPong, x.startTime)
      case x: ClipPlayMode.PlayCount => ClipPlayMode.PlayCount(ClipPlayDirection.SmoothPingPong, x.startTime, x.times)

object ClipPlayMode:
  val default: ClipPlayMode =
    ClipPlayMode.Loop(ClipPlayDirection.default)

  val loop: ClipPlayMode =
    ClipPlayMode.Loop(ClipPlayDirection.default)

  def playOnce(startTime: Seconds): ClipPlayMode =
    ClipPlayMode.PlayOnce(ClipPlayDirection.default, startTime)

  def playCount(startTime: Seconds, times: Int): ClipPlayMode =
    ClipPlayMode.PlayCount(ClipPlayDirection.default, startTime, times)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy