Code Sketch


siddhi sabale
By: Mhalsakant School
Category: Programming
// ============================================================
// ? TITAN X-1 // ULTRA ROBOT
// CINEMATIC MOUSE CONTROLLED WALKING ANIMATION
// KOJO / SCALA
// ============================================================

cleari()
originBottomLeft()

class Sketch {

  // ==========================================================
  // ROBOT POSITION
  // ==========================================================

  var rx = cwidth / 2.0
  var ry = 170.0

  var targetX = cwidth / 2.0
  var targetY = 170.0

  var walkPhase = 0.0
  var idlePhase = 0.0
  var glowPhase = 0.0
  var blinkTimer = 0.0

  var lastX = cwidth / 2.0
  var speedX = 0.0

  var dustTimer = 0.0

  // ==========================================================
  // PARTICLE DATA
  // ==========================================================

  case class Dust(
    var x: Double,
    var y: Double,
    var vx: Double,
    var vy: Double,
    var life: Double,
    var size: Double
  )

  val dust = scala.collection.mutable.ArrayBuffer.empty[Dust]

  // ==========================================================
  // SAFE HELPERS
  // ==========================================================

  def clamp(v: Double, lo: Double, hi: Double): Double = {
    math.max(lo, math.min(hi, v))
  }

  def alphaValue(v: Double): Int = {
    clamp(v, 0.0, 255.0).toInt
  }

  // ==========================================================
  // DISTANCE
  // ==========================================================

  def distance(
      x1: Double,
      y1: Double,
      x2: Double,
      y2: Double
  ): Double = {

    math.sqrt(
      (x2 - x1) * (x2 - x1) +
      (y2 - y1) * (y2 - y1)
    )
  }

  // ==========================================================
  // GLOW DOT
  // ==========================================================

  def glowDot(
      c: CanvasDraw,
      x: Double,
      y: Double,
      size: Double,
      phase: Double
  ): Unit = {

    val a = alphaValue(
      70.0 + math.sin(phase) * 45.0
    )

    c.noStroke()
    c.fill(0, 180, 255, a)
    c.ellipse(x, y, size * 2.5, size * 2.5)

    c.fill(120, 240, 255, 220)
    c.ellipse(x, y, size, size)
  }

  // ==========================================================
  // METAL JOINT
  // ==========================================================

  def joint(
      c: CanvasDraw,
      x: Double,
      y: Double,
      r: Double
  ): Unit = {

    c.fill(115, 125, 138)
    c.stroke(55, 65, 75)
    c.strokeWeight(1.4)

    c.ellipse(x, y, r * 2.0, r * 2.0)

    c.fill(205, 215, 225)
    c.noStroke()

    c.ellipse(
      x - r * 0.22,
      y + r * 0.22,
      r * 0.95,
      r * 0.95
    )

    c.fill(35, 150, 235)
    c.ellipse(
      x,
      y,
      r * 0.42,
      r * 0.42
    )
  }

  // ==========================================================
  // LIMB SEGMENT
  // ==========================================================

  def limb(
      c: CanvasDraw,
      x1: Double,
      y1: Double,
      x2: Double,
      y2: Double,
      thickness: Double
  ): Unit = {

    c.stroke(0, 0, 0, 70)
    c.strokeWeight(thickness + 4.0)
    c.line(x1 + 2, y1 - 2, x2 + 2, y2 - 2)

    c.stroke(238, 241, 246)
    c.strokeWeight(thickness)
    c.line(x1, y1, x2, y2)

    c.stroke(185, 192, 204)
    c.strokeWeight(2.0)

    val mx = (x1 + x2) / 2.0
    val my = (y1 + y2) / 2.0

    c.line(
      x1,
      y1,
      mx,
      my
    )
  }

  // ==========================================================
  // BLUE ARMOR PANEL
  // ==========================================================

  def armor(
      c: CanvasDraw,
      x: Double,
      y: Double,
      w: Double,
      h: Double
  ): Unit = {

    c.fill(4, 36, 88)
    c.stroke(15, 145, 235)
    c.strokeWeight(1.8)

    c.ellipse(x, y, w, h)

    c.fill(35, 145, 235, 130)
    c.noStroke()

    c.ellipse(
      x - w * 0.18,
      y + h * 0.14,
      w * 0.42,
      h * 0.24
    )

    c.fill(110, 220, 255, 120)

    c.ellipse(
      x - w * 0.22,
      y + h * 0.12,
      w * 0.18,
      h * 0.12
    )
  }

  // ==========================================================
  // FOOT
  // ==========================================================

  def drawFoot(
      c: CanvasDraw,
      x: Double,
      y: Double,
      flip: Double
  ): Unit = {

    c.fill(243, 245, 248)
    c.stroke(70, 80, 90)
    c.strokeWeight(2)

    c.ellipse(
      x + flip * 5.0,
      y,
      30,
      14
    )

    c.fill(4, 48, 110)
    c.noStroke()

    c.ellipse(
      x + flip * 5.0,
      y - 5,
      23,
      6
    )

    c.fill(50, 175, 245, 130)
    c.ellipse(
      x + flip * 7.0,
      y - 4,
      10,
      3
    )
  }

  // ==========================================================
  // HAND
  // ==========================================================

  def drawHand(
      c: CanvasDraw,
      x: Double,
      y: Double,
      side: Double
  ): Unit = {

    c.fill(242, 244, 248)
    c.stroke(90, 100, 110)
    c.strokeWeight(1.3)

    c.ellipse(x, y, 14, 18)

    // Palm blue light
    c.fill(15, 95, 175)
    c.noStroke()

    c.ellipse(
      x,
      y + 1,
      8,
      7
    )

    // Fingers
    c.stroke(120, 130, 140)
    c.strokeWeight(1.3)

    for (i <- 0 until 3) {

      val fy = y - 5.0 + i * 4.0

      c.line(
        x,
        fy,
        x + side * 7.0,
        fy - 1.5
      )
    }
  }

  // ==========================================================
  // PARTICLE UPDATE
  // ==========================================================

  def updateDust(): Unit = {

    dust.foreach { p =>

      p.x += p.vx
      p.y += p.vy

      p.vy += 0.025

      p.life -= 0.025
      p.size += 0.05
    }

    dust.filterInPlace(
      _.life > 0.0
    )
  }

  // ==========================================================
  // CREATE DUST
  // ==========================================================

  def addDust(
      x: Double,
      y: Double
  ): Unit = {

    for (_ <- 0 until 2) {

      dust.append(
        Dust(
          x + ((math.random * 20.0) - 10.0),
          y + math.random * 3.0,
          ((math.random * 1.4) - 0.7),
          -(math.random * 0.9),
          1.0,
          2.0 + math.random * 3.0
        )
      )
    }
  }

  // ==========================================================
  // DRAW DUST
  // ==========================================================

  def drawDust(c: CanvasDraw): Unit = {

    dust.foreach { p =>

      val a =
        alphaValue(
          p.life * 120.0
        )

      c.fill(205, 215, 225, a)
      c.noStroke()

      c.ellipse(
        p.x,
        p.y,
        p.size,
        p.size
      )
    }
  }

  // ==========================================================
  // FLOOR
  // ==========================================================

  def drawFloor(c: CanvasDraw): Unit = {

    c.fill(11, 17, 28)
    c.noStroke()

    c.rect(
      0,
      0,
      cwidth,
      64
    )

    c.stroke(24, 86, 130, 160)
    c.strokeWeight(1.5)

    c.line(
      0,
      64,
      cwidth,
      64
    )

    // Horizontal futuristic lines
    for (i <- 0 until 7) {

      val y =
        64.0 + i * 28.0

      c.stroke(
        20,
        70,
        110,
        70
      )

      c.line(
        0,
        y,
        cwidth,
        y
      )
    }

    // Perspective lines
    val center = cwidth / 2.0

    for (i <- -10 to 10) {

      val bottomX =
        center + i * 65.0

      c.stroke(
        20,
        90,
        135,
        65
      )

      c.line(
        center,
        64,
        bottomX,
        0
      )
    }
  }

  // ==========================================================
  // BACKGROUND
  // ==========================================================

  def drawBackground(c: CanvasDraw): Unit = {

    c.background(
      5,
      9,
      19
    )

    // Stars
    for (i <- 0 until 70) {

      val sx =
        (i * 97) % cwidth

      val sy =
        90.0 +
        ((i * 59) % (cheight - 160))

      val pulse =
        1.0 +
        math.sin(glowPhase + i * 0.37) * 0.5

      val alpha =
        alphaValue(
          90.0 +
          math.sin(glowPhase + i) * 45.0
        )

      c.fill(
        180,
        225,
        255,
        alpha
      )

      c.noStroke()

      c.ellipse(
        sx,
        sy,
        pulse * 2.0,
        pulse * 2.0
      )
    }

    // Horizon glow
    c.fill(
      20,
      100,
      155,
      35
    )

    c.noStroke()

    c.ellipse(
      cwidth / 2.0,
      90,
      cwidth * 0.75,
      120
    )

    drawFloor(c)
  }

  // ==========================================================
  // ROBOT BODY
  // ==========================================================

  def drawRobot(c: CanvasDraw): Unit = {

    val moving =
      distance(
        rx,
        ry,
        targetX,
        targetY
      ) > 7.0

    val walkAmount =
      if (moving) {
        math.sin(walkPhase)
      } else {
        0.0
      }

    // --------------------------------------------------------
    // BODY BOB
    // --------------------------------------------------------

    val bob =
      if (moving) {
        math.abs(math.sin(walkPhase * 2.0)) * 2.8
      } else {
        math.sin(idlePhase) * 1.2
      }

    val baseX = rx
    val baseY = ry + bob

    // --------------------------------------------------------
    // LARGE SHADOW
    // --------------------------------------------------------

    val shadowScale =
      clamp(
        1.0 -
        math.abs(bob) * 0.05,
        0.7,
        1.0
      )

    c.fill(
      0,
      0,
      0,
      115
    )

    c.noStroke()

    c.ellipse(
      baseX,
      ry - 60,
      90 * shadowScale,
      17
    )

    // ========================================================
    // LEGS
    // ========================================================

    val leftLegSwing =
      walkAmount * 0.38

    val rightLegSwing =
      -walkAmount * 0.38

    // LEFT LEG
    val leftHipX =
      baseX - 18

    val leftHipY =
      baseY - 8

    val leftKneeX =
      leftHipX +
      math.sin(leftLegSwing) * 18

    val leftKneeY =
      leftHipY - 32 +
      math.abs(walkAmount) * 5

    val leftFootX =
      leftKneeX +
      math.sin(leftLegSwing * 0.7) * 10

    val leftFootY =
      leftKneeY - 30

    limb(
      c,
      leftHipX,
      leftHipY,
      leftKneeX,
      leftKneeY,
      11
    )

    joint(
      c,
      leftKneeX,
      leftKneeY,
      7
    )

    limb(
      c,
      leftKneeX,
      leftKneeY,
      leftFootX,
      leftFootY,
      10
    )

    drawFoot(
      c,
      leftFootX,
      leftFootY,
      -1
    )

    // RIGHT LEG
    val rightHipX =
      baseX + 18

    val rightHipY =
      baseY - 8

    val rightKneeX =
      rightHipX +
      math.sin(rightLegSwing) * 18

    val rightKneeY =
      rightHipY - 32 +
      math.abs(walkAmount) * 5

    val rightFootX =
      rightKneeX +
      math.sin(rightLegSwing * 0.7) * 10

    val rightFootY =
      rightKneeY - 30

    limb(
      c,
      rightHipX,
      rightHipY,
      rightKneeX,
      rightKneeY,
      11
    )

    joint(
      c,
      rightKneeX,
      rightKneeY,
      7
    )

    limb(
      c,
      rightKneeX,
      rightKneeY,
      rightFootX,
      rightFootY,
      10
    )

    drawFoot(
      c,
      rightFootX,
      rightFootY,
      1
    )

    // Knee armor
    armor(
      c,
      leftKneeX,
      leftKneeY,
      13,
      15
    )

    armor(
      c,
      rightKneeX,
      rightKneeY,
      13,
      15
    )

    // ========================================================
    // TORSO
    // ========================================================

    c.fill(
      232,
      235,
      241
    )

    c.stroke(
      95,
      105,
      118
    )

    c.strokeWeight(
      2.0
    )

    c.ellipse(
      baseX,
      baseY + 18,
      55,
      64
    )

    // Torso side plates
    c.fill(
      192,
      198,
      208
    )

    c.noStroke()

    c.ellipse(
      baseX - 24,
      baseY + 13,
      11,
      30
    )

    c.ellipse(
      baseX + 24,
      baseY + 13,
      11,
      30
    )

    // ========================================================
    // CHEST ARMOR
    // ========================================================

    c.fill(
      3,
      40,
      95
    )

    c.stroke(
      20,
      165,
      240
    )

    c.strokeWeight(
      2
    )

    c.ellipse(
      baseX,
      baseY + 25,
      34,
      31
    )

    c.fill(
      35,
      150,
      240,
      150
    )

    c.noStroke()

    c.ellipse(
      baseX - 7,
      baseY + 31,
      13,
      7
    )

    // Reactor
    val reactorGlow =
      alphaValue(
        160.0 +
        math.sin(glowPhase * 2.0) *
        70.0
      )

    c.fill(
      0,
      180,
      255,
      reactorGlow
    )

    c.ellipse(
      baseX,
      baseY + 25,
      12,
      12
    )

    c.fill(
      205,
      245,
      255,
      230
    )

    c.ellipse(
      baseX,
      baseY + 25,
      5,
      5
    )

    // ========================================================
    // NECK
    // ========================================================

    c.fill(
      170,
      178,
      190
    )

    c.stroke(
      80,
      90,
      100
    )

    c.strokeWeight(
      1.5
    )

    c.rect(
      baseX - 9,
      baseY + 45,
      18,
      10
    )

    // ========================================================
    // ARMS
    // ========================================================

    val armWave =
      walkAmount * 0.42

    // LEFT ARM
    val lShoulderX =
      baseX - 27

    val lShoulderY =
      baseY + 35

    val lElbowX =
      lShoulderX -
      16 -
      armWave * 8

    val lElbowY =
      lShoulderY -
      19

    val lHandX =
      lElbowX -
      8 -
      armWave * 5

    val lHandY =
      lElbowY -
      25

    armor(
      c,
      lShoulderX,
      lShoulderY,
      19,
      20
    )

    limb(
      c,
      lShoulderX,
      lShoulderY,
      lElbowX,
      lElbowY,
      10
    )

    joint(
      c,
      lElbowX,
      lElbowY,
      6
    )

    limb(
      c,
      lElbowX,
      lElbowY,
      lHandX,
      lHandY,
      9
    )

    armor(
      c,
      lElbowX,
      lElbowY,
      13,
      18
    )

    drawHand(
      c,
      lHandX,
      lHandY,
      -1
    )

    // RIGHT ARM
    val rShoulderX =
      baseX + 27

    val rShoulderY =
      baseY + 35

    val rElbowX =
      rShoulderX +
      16 +
      armWave * 8

    val rElbowY =
      rShoulderY -
      19

    val rHandX =
      rElbowX +
      8 +
      armWave * 5

    val rHandY =
      rElbowY -
      25

    armor(
      c,
      rShoulderX,
      rShoulderY,
      19,
      20
    )

    limb(
      c,
      rShoulderX,
      rShoulderY,
      rElbowX,
      rElbowY,
      10
    )

    joint(
      c,
      rElbowX,
      rElbowY,
      6
    )

    limb(
      c,
      rElbowX,
      rElbowY,
      rHandX,
      rHandY,
      9
    )

    armor(
      c,
      rElbowX,
      rElbowY,
      13,
      18
    )

    drawHand(
      c,
      rHandX,
      rHandY,
      1
    )

    // Shoulder lights
    glowDot(
      c,
      lShoulderX,
      lShoulderY,
      3.0,
      glowPhase
    )

    glowDot(
      c,
      rShoulderX,
      rShoulderY,
      3.0,
      glowPhase + 1.4
    )

    // ========================================================
    // HEAD
    // ========================================================

    val headY =
      baseY + 72

    // Head shadow
    c.fill(
      0,
      0,
      0,
      65
    )

    c.noStroke()

    c.ellipse(
      baseX + 2,
      headY - 2,
      48,
      42
    )

    // Head
    c.fill(
      241,
      243,
      247
    )

    c.stroke(
      95,
      105,
      116
    )

    c.strokeWeight(
      2
    )

    c.ellipse(
      baseX,
      headY,
      47,
      41
    )

    // Blue helmet cap
    c.fill(
      4,
      46,
      105
    )

    c.stroke(
      20,
      165,
      240
    )

    c.strokeWeight(
      2
    )

    c.ellipse(
      baseX,
      headY + 18,
      32,
      10
    )

    // Cap reflection
    c.fill(
      100,
      225,
      255,
      160
    )

    c.noStroke()

    c.ellipse(
      baseX - 6,
      headY + 20,
      12,
      4
    )

    // ========================================================
    // FACE TRACKING
    // ========================================================

    val lookX =
      clamp(
        (mouseX.toDouble - baseX) * 0.05,
        -3.0,
        3.0
      )

    val lookY =
      clamp(
        (mouseY.toDouble - headY) * 0.035,
        -2.0,
        2.0
      )

    // Eyes
    val eyeOpen =
      if (math.sin(blinkTimer) > 0.985) {
        1.5
      } else {
        6.0
      }

    c.fill(
      15,
      20,
      27
    )

    c.ellipse(
      baseX - 9,
      headY + 3,
      7,
      eyeOpen
    )

    c.ellipse(
      baseX + 9,
      headY + 3,
      7,
      eyeOpen
    )

    // Eye lights
    c.fill(
      80,
      220,
      255
    )

    c.ellipse(
      baseX - 8 + lookX,
      headY + 4 + lookY,
      2.5,
      2.5
    )

    c.ellipse(
      baseX + 10 + lookX,
      headY + 4 + lookY,
      2.5,
      2.5
    )

    // Mouth
    c.stroke(
      75,
      85,
      95
    )

    c.strokeWeight(
      1.5
    )

    c.line(
      baseX - 3,
      headY - 8,
      baseX + 3,
      headY - 8
    )

    // ========================================================
    // SIDE HEAD PANELS
    // ========================================================

    c.fill(
      205,
      210,
      218
    )

    c.stroke(
      90,
      100,
      110
    )

    c.strokeWeight(
      1.2
    )

    c.ellipse(
      baseX - 23,
      headY + 1,
      7,
      12
    )

    c.ellipse(
      baseX + 23,
      headY + 1,
      7,
      12
    )

    // ========================================================
    // ANTENNA
    // ========================================================

    c.stroke(
      140,
      150,
      165
    )

    c.strokeWeight(
      2
    )

    c.line(
      baseX,
      headY + 24,
      baseX,
      headY + 31
    )

    glowDot(
      c,
      baseX,
      headY + 34,
      2.7,
      glowPhase * 2.0
    )

    // ========================================================
    // CHEST SIDE LIGHTS
    // ========================================================

    glowDot(
      c,
      baseX - 18,
      baseY + 17,
      2,
      glowPhase + 0.7
    )

    glowDot(
      c,
      baseX + 18,
      baseY + 17,
      2,
      glowPhase + 2.0
    )
  }

  // ==========================================================
  // HUD
  // ==========================================================

  def drawHUD(c: CanvasDraw): Unit = {

    // Top title
    c.fill(
      0,
      15,
      35,
      190
    )

    c.stroke(
      0,
      170,
      240,
      150
    )

    c.strokeWeight(
      1.5
    )

    c.rect(
      18,
      cheight - 60,
      290,
      38
    )

    c.fill(
      125,
      230,
      255
    )

    c.noStroke()

    c.text(
      "TITAN X-1 // ACTIVE",
      30,
      cheight - 37
    )

    // Bottom instructions
    c.fill(
      175,
      210,
      235
    )

    c.text(
      "MOVE MOUSE  ?  ROBOT FOLLOWS",
      28,
      cheight - 85
    )

    // Speed
    val robotSpeed =
      math.abs(speedX) * 10.0

    c.fill(
      70,
      200,
      255
    )

    c.text(
      "MOTION: " +
      robotSpeed.toInt.toString +
      "%",
      28,
      cheight - 108
    )

    // System lights
    c.fill(
      20,
      35,
      52
    )

    c.stroke(
      0,
      150,
      220,
      140
    )

    c.strokeWeight(
      1
    )

    c.rect(
      cwidth - 175,
      cheight - 60,
      145,
      38
    )

    val systemAlpha =
      alphaValue(
        120.0 +
        math.sin(glowPhase) * 80.0
      )

    c.fill(
      0,
      220,
      255,
      systemAlpha
    )

    c.ellipse(
      cwidth - 155,
      cheight - 41,
      7,
      7
    )

    c.fill(
      175,
      225,
      240
    )

    c.text(
      "SYSTEM ONLINE",
      cwidth - 140,
      cheight - 38
    )
  }

  // ==========================================================
  // TARGET RETICLE
  // ==========================================================

  def drawTarget(c: CanvasDraw): Unit = {

    val pulse =
      22.0 +
      math.sin(glowPhase * 2.0) * 5.0

    c.noFill()

    c.stroke(
      60,
      205,
      255,
      135
    )

    c.strokeWeight(
      1.5
    )

    c.ellipse(
      targetX,
      targetY,
      pulse,
      pulse
    )

    c.line(
      targetX - 14,
      targetY,
      targetX - 5,
      targetY
    )

    c.line(
      targetX + 5,
      targetY,
      targetX + 14,
      targetY
    )

    c.line(
      targetX,
      targetY - 14,
      targetX,
      targetY - 5
    )

    c.line(
      targetX,
      targetY + 5,
      targetX,
      targetY + 14
    )
  }

  // ==========================================================
  // SETUP
  // ==========================================================

  def setup(surface: CanvasDraw): Unit = {

    import surface._

    background(
      5,
      9,
      19
    )
  }

  // ==========================================================
  // MAIN LOOP
  // ==========================================================

  def drawLoop(surface: CanvasDraw): Unit = {

    import surface._

    // --------------------------------------------------------
    // BACKGROUND
    // --------------------------------------------------------

    drawBackground(surface)

    // --------------------------------------------------------
    // MOUSE TARGET
    // --------------------------------------------------------

    targetX =
      clamp(
        mouseX.toDouble,
        80.0,
        cwidth.toDouble - 80.0
      )

    targetY =
      clamp(
        mouseY.toDouble,
        100.0,
        cheight.toDouble - 130.0
      )

    // --------------------------------------------------------
    // SMOOTH MOTION
    // --------------------------------------------------------

    val dx =
      targetX - rx

    val dy =
      targetY - ry

    rx += dx * 0.075
    ry += dy * 0.075

    // --------------------------------------------------------
    // SPEED
    // --------------------------------------------------------

    speedX =
      rx - lastX

    lastX =
      rx

    val moving =
      math.abs(dx) > 7.0 ||
      math.abs(dy) > 7.0

    // --------------------------------------------------------
    // WALK ANIMATION
    // --------------------------------------------------------

    if (moving) {

      walkPhase += 0.22

      dustTimer += 1.0

      if (dustTimer > 5.0) {

        addDust(
          rx - speedX * 2.0,
          ry - 55
        )

        dustTimer = 0.0
      }

    } else {

      idlePhase += 0.05

      walkPhase *= 0.96
    }

    // --------------------------------------------------------
    // GLOBAL ANIMATION
    // --------------------------------------------------------

    glowPhase += 0.08
    blinkTimer += 0.025

    // --------------------------------------------------------
    // PARTICLES
    // --------------------------------------------------------

    updateDust()
    drawDust(surface)

    // --------------------------------------------------------
    // ROBOT
    // --------------------------------------------------------

    drawRobot(surface)

    // --------------------------------------------------------
    // TARGET
    // --------------------------------------------------------

    drawTarget(surface)

    // --------------------------------------------------------
    // HUD
    // --------------------------------------------------------

    drawHUD(surface)
  }
}

// ============================================================
// START
// ============================================================

val sketch = new Sketch

canvasSketch(sketch)