Code Sketch


dr babasaheb 3d yoi
By: Mhalsakant School
Category: Programming
import java.awt._
import java.awt.event._
import java.awt.image.BufferedImage
import java.awt.image.DataBufferInt
import java.io.File
import javax.imageio.ImageIO
import javax.swing._
import scala.collection.mutable.ArrayBuffer

// ================================================================
// BABASAHEB 3D STATUE STUDIO
// A self-contained Scala + Swing procedural 3D statue viewer.
// No external libraries or copyrighted model files are required.
// It creates a stylized, colored 3D representation inspired by
// the widely used blue-suit, glasses, pointing-hand, Constitution
// book pose of Dr. B. R. Ambedkar.
// ================================================================

object Babasaheb3DStatue {

  case class V(x: Double, y: Double, z: Double) {
    def +(b: V): V = V(x + b.x, y + b.y, z + b.z)
    def -(b: V): V = V(x - b.x, y - b.y, z - b.z)
    def *(k: Double): V = V(x * k, y * k, z * k)
  }
  case class Tri(a: V, b: V, c: V, color: Color)
  case class TV(x: Double, y: Double, z: Double)
  case class SV(x: Double, y: Double, invZ: Double)

  val triangles = ArrayBuffer[Tri]()

  // Camera / viewer state.
  var yaw = 0.0
  var pitch = 0.02
  var zoom = 9.4
  val targetY = 2.65
  var themeMode = 0
  var showHalo = true
  var showBook = true
  var showStars = true

  val backgroundTop = new Color(8, 14, 35)
  val backgroundBottom = new Color(45, 27, 57)
  val gold = new Color(255, 200, 72)
  val classicBlue = new Color(25, 78, 190)
  val skinClassic = new Color(190, 137, 105)
  val darkClassic = new Color(22, 22, 27)

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

  def dot(a: V, b: V): Double = a.x * b.x + a.y * b.y + a.z * b.z
  def cross(a: V, b: V): V = V(
    a.y * b.z - a.z * b.y,
    a.z * b.x - a.x * b.z,
    a.x * b.y - a.y * b.x
  )
  def length(v: V): Double = math.sqrt(dot(v, v))
  def norm(v: V): V = {
    val n = length(v)
    if (n < 0.000001) V(0, 1, 0) else V(v.x / n, v.y / n, v.z / n)
  }

  def recolor(role: String, c: Color): Color = {
    if (themeMode == 0) c
    else if (themeMode == 1) { // bronze sculpture
      role match {
        case "glasses" | "eye" | "dark" => new Color(45, 31, 25)
        case "gold" => new Color(222, 167, 69)
        case "base" => new Color(59, 47, 42)
        case "background" => c
        case _ => new Color(151, 91, 53)
      }
    } else if (themeMode == 2) { // golden tribute statue
      role match {
        case "glasses" | "eye" | "dark" => new Color(58, 39, 19)
        case "base" => new Color(22, 39, 83)
        case "background" => c
        case _ => new Color(236, 177, 43)
      }
    } else { // marble
      role match {
        case "glasses" | "eye" | "dark" => new Color(51, 55, 66)
        case "gold" => new Color(170, 139, 76)
        case "base" => new Color(62, 72, 91)
        case "background" => c
        case _ => new Color(220, 222, 224)
      }
    }
  }

  def scaleColor(c: Color, factor: Double): Color = new Color(
    clamp(c.getRed * factor, 0, 255).toInt,
    clamp(c.getGreen * factor, 0, 255).toInt,
    clamp(c.getBlue * factor, 0, 255).toInt
  )

  def addTri(a: V, b: V, c: V, base: Color): Unit = {
    val n = norm(cross(b - a, c - a))
    val light = norm(V(-0.38, 0.82, 0.58))
    val diffuse = math.abs(dot(n, light))
    val factor = 0.47 + 0.53 * diffuse
    triangles += Tri(a, b, c, scaleColor(base, factor))
  }

  def addBox(cx: Double, cy: Double, cz: Double,
             sx: Double, sy: Double, sz: Double, color: Color): Unit = {
    val x0 = cx - sx / 2; val x1 = cx + sx / 2
    val y0 = cy - sy / 2; val y1 = cy + sy / 2
    val z0 = cz - sz / 2; val z1 = cz + sz / 2
    val v = Array(
      V(x0,y0,z0), V(x1,y0,z0), V(x1,y1,z0), V(x0,y1,z0),
      V(x0,y0,z1), V(x1,y0,z1), V(x1,y1,z1), V(x0,y1,z1)
    )
    val f = Array(
      Array(0,1,2), Array(0,2,3), Array(4,7,6), Array(4,6,5),
      Array(0,4,5), Array(0,5,1), Array(3,2,6), Array(3,6,7),
      Array(0,3,7), Array(0,7,4), Array(1,5,6), Array(1,6,2)
    )
    f.foreach(k => addTri(v(k(0)), v(k(1)), v(k(2)), color))
  }

  def addEllipsoid(center: V, rx: Double, ry: Double, rz: Double,
                   color: Color, lat: Int = 14, lon: Int = 22): Unit = {
    def point(i: Int, j: Int): V = {
      val phi = math.Pi * i.toDouble / lat
      val theta = 2.0 * math.Pi * j.toDouble / lon
      V(center.x + rx * math.sin(phi) * math.cos(theta),
        center.y + ry * math.cos(phi),
        center.z + rz * math.sin(phi) * math.sin(theta))
    }
    for (i <- 0 until lat; j <- 0 until lon) {
      val a = point(i, j)
      val b = point(i + 1, j)
      val c = point(i + 1, (j + 1) % lon)
      val d = point(i, (j + 1) % lon)
      addTri(a, b, c, color)
      addTri(a, c, d, color)
    }
  }

  // Cylinder / capsule-like limb between two 3D points.
  def addCylinder(a: V, b: V, rA: Double, rB: Double,
                  color: Color, seg: Int = 12, caps: Boolean = true): Unit = {
    val axis = norm(b - a)
    val helper = if (math.abs(axis.y) < 0.88) V(0,1,0) else V(1,0,0)
    val u = norm(cross(axis, helper))
    val v = norm(cross(axis, u))
    val ringA = (0 until seg).map { i =>
      val t = 2.0 * math.Pi * i / seg
      a + u * (math.cos(t) * rA) + v * (math.sin(t) * rA)
    }.toVector
    val ringB = (0 until seg).map { i =>
      val t = 2.0 * math.Pi * i / seg
      b + u * (math.cos(t) * rB) + v * (math.sin(t) * rB)
    }.toVector
    for (i <- 0 until seg) {
      val j = (i + 1) % seg
      addTri(ringA(i), ringA(j), ringB(j), color)
      addTri(ringA(i), ringB(j), ringB(i), color)
      if (caps) {
        addTri(a, ringA(j), ringA(i), color)
        addTri(b, ringB(i), ringB(j), color)
      }
    }
  }

  def addCircle(cx: Double, cy: Double, z: Double, radius: Double,
                tube: Double, color: Color, seg: Int = 40): Unit = {
    for (i <- 0 until seg) {
      val t0 = 2.0 * math.Pi * i / seg
      val t1 = 2.0 * math.Pi * (i + 1) / seg
      val a = V(cx + radius * math.cos(t0), cy + radius * math.sin(t0), z)
      val b = V(cx + radius * math.cos(t1), cy + radius * math.sin(t1), z)
      addCylinder(a, b, tube, tube, color, 6, false)
    }
  }

  def addTorusXY(cx: Double, cy: Double, cz: Double, major: Double,
                 tube: Double, color: Color, sides: Int = 72,
                 rings: Int = 8): Unit = {
    def p(i: Int, j: Int): V = {
      val t = 2.0 * math.Pi * i / sides
      val u = 2.0 * math.Pi * j / rings
      V(cx + (major + tube * math.cos(u)) * math.cos(t),
        cy + (major + tube * math.cos(u)) * math.sin(t),
        cz + tube * math.sin(u))
    }
    for (i <- 0 until sides; j <- 0 until rings) {
      val a = p(i,j); val b = p((i+1)%sides,j)
      val c = p((i+1)%sides,(j+1)%rings); val d = p(i,(j+1)%rings)
      addTri(a,b,c,color); addTri(a,c,d,color)
    }
  }

  def addGround(): Unit = {
    val c = new Color(28, 34, 54)
    val a = V(-24, -0.035, -22); val b = V(24, -0.035, -22)
    val d = V(-24, -0.035, 22); val e = V(24, -0.035, 22)
    addTri(a,d,e,c); addTri(a,e,b,c)
  }

  def C(role: String, color: Color): Color = recolor(role, color)

  // --------------------------------------------------------------
  // BUILD THE STATUE: face, glasses, pointing arm, book and suit.
  // --------------------------------------------------------------
  def buildScene(): Unit = {
    triangles.clear()
    addGround()

    // Decorative halo stays behind the statue.
    if (showHalo) {
      addTorusXY(0, 2.82, -0.88, 2.23, 0.065, C("gold", gold), 72, 8)
      addTorusXY(0, 2.82, -0.90, 2.05, 0.018, C("gold", new Color(220, 169, 60)), 72, 6)
      for (i <- 0 until 28) {
        val a = i * 2.0 * math.Pi / 28.0
        val p1 = V(2.38 * math.cos(a), 2.82 + 2.38 * math.sin(a), -0.86)
        val p2 = V(2.58 * math.cos(a), 2.82 + 2.58 * math.sin(a), -0.86)
        addCylinder(p1, p2, 0.018, 0.012, C("gold", gold), 6, false)
      }
    }

    // Gentle distant decorative lights.
    if (showStars) {
      val stars = Array(
        (-3.0,4.45),(-2.8,2.1),(-2.45,3.7),(2.8,4.3),(3.0,2.8),
        (2.6,1.25),(-2.5,1.25),(1.9,4.75),(-1.8,4.8),(3.25,3.65)
      )
      stars.foreach { s =>
        addEllipsoid(V(s._1,s._2,-1.15),0.035,0.035,0.025,
          C("gold",new Color(255, 219, 127)),8,10)
      }
    }

    // Pedestal: layered circular 3D plinth.
    addCylinder(V(0,0.02,0), V(0,0.24,0), 1.02, 1.02,
      C("base",new Color(20,46,103)), 48, true)
    addCylinder(V(0,0.24,0), V(0,0.53,0), 0.84, 0.84,
      C("base",new Color(25,65,139)), 48, true)
    addCylinder(V(0,0.53,0), V(0,0.80,0), 0.73, 0.68,
      C("base",new Color(24,48,96)), 48, true)
    addCylinder(V(0,0.79,0), V(0,0.84,0), 0.70, 0.70,
      C("gold",new Color(218,174,72)), 48, true)
    addBox(0,0.41,0.70,1.48,0.36,0.12,C("base",new Color(15,36,83)))
    addBox(0,0.41,0.768,1.34,0.25,0.025,C("gold",new Color(197,151,58)))
    addBox(0,0.41,0.786,1.28,0.20,0.018,C("base",new Color(19,41,89)))

    // Shoes, trousers and polished black shoes.
    addEllipsoid(V(-0.23,0.91,0.10),0.22,0.12,0.34,C("dark",new Color(20,22,27)),12,20)
    addEllipsoid(V(0.23,0.91,0.10),0.22,0.12,0.34,C("dark",new Color(20,22,27)),12,20)
    addBox(-0.23,0.99,0.07,0.26,0.08,0.35,C("dark",new Color(38,39,43)))
    addBox(0.23,0.99,0.07,0.26,0.08,0.35,C("dark",new Color(38,39,43)))

    val pants = C("suit",new Color(24,70,163))
    addCylinder(V(-0.23,0.98,0.00),V(-0.29,2.04,-0.01),0.19,0.23,pants,18,true)
    addCylinder(V(0.23,0.98,0.00),V(0.29,2.04,-0.01),0.19,0.23,pants,18,true)
    addEllipsoid(V(0,2.00,-0.01),0.43,0.30,0.29,pants,14,22)
    addBox(-0.24,1.55,0.00,0.28,0.78,0.34,pants)
    addBox(0.24,1.55,0.00,0.28,0.78,0.34,pants)

    // Shirt and main suit jacket.
    val suit = C("suit",classicBlue)
    val shirt = C("shirt",new Color(241,238,226))
    val skin = C("skin",skinClassic)
    val tie = C("tie",new Color(188,27,47))
    val hair = C("dark",new Color(25,24,27))
    val jacketDark = scaleColor(suit,0.78)

    addEllipsoid(V(0,2.72,-0.015),0.65,0.88,0.36,suit,20,28)
    addEllipsoid(V(-0.48,3.36,-0.015),0.29,0.29,0.31,suit,14,20)
    addEllipsoid(V(0.48,3.36,-0.015),0.29,0.29,0.31,suit,14,20)
    addEllipsoid(V(0,3.46,-0.025),0.38,0.24,0.30,suit,14,20)

    // White shirt panel on the front of the suit.
    addEllipsoid(V(0,2.91,0.285),0.255,0.66,0.105,shirt,18,24)
    addTri(V(-0.24,3.41,0.365),V(-0.04,3.12,0.405),V(-0.13,3.47,0.36),
      C("shirt",new Color(247,245,236)))
    addTri(V(0.24,3.41,0.365),V(0.04,3.12,0.405),V(0.13,3.47,0.36),
      C("shirt",new Color(247,245,236)))

    // Lapels, jacket seams and buttons.
    addTri(V(-0.42,3.43,0.27),V(-0.12,3.02,0.40),V(-0.27,2.72,0.38),jacketDark)
    addTri(V(0.42,3.43,0.27),V(0.12,3.02,0.40),V(0.27,2.72,0.38),jacketDark)
    addTri(V(-0.10,3.42,0.39),V(-0.01,3.10,0.415),V(0.04,3.41,0.39),
      C("shirt",new Color(245,241,230)))
    addTri(V(-0.015,3.10,0.416),V(0.04,3.41,0.39),V(0.13,3.10,0.405),
      C("shirt",new Color(245,241,230)))
    addTri(V(-0.09,3.22,0.43),V(0,3.11,0.445),V(0.09,3.22,0.43),tie)
    addTri(V(-0.085,3.17,0.44),V(0.085,3.17,0.44),V(0.025,2.43,0.44),tie)
    addTri(V(-0.085,3.17,0.44),V(0.025,2.43,0.44),V(-0.025,2.43,0.44),tie)

    for (yy <- Seq(2.55,2.78,3.00)) {
      addEllipsoid(V(0.28,yy,0.345),0.028,0.028,0.022,C("gold",new Color(205,167,72)),8,10)
    }
    addBox(0.39,2.96,0.34,0.20,0.24,0.035,jacketDark)
    addBox(0.39,2.96,0.364,0.15,0.18,0.018,suit)

    // Right arm: extended to point outward (on the viewer's left).
    addCylinder(V(-0.53,3.37,0.00),V(-0.97,3.68,0.08),0.20,0.175,suit,18,true)
    addEllipsoid(V(-0.77,3.55,0.05),0.21,0.22,0.20,suit,14,18)
    addCylinder(V(-0.97,3.68,0.08),V(-1.47,4.01,0.13),0.16,0.125,suit,18,true)
    addCylinder(V(-1.45,4.00,0.13),V(-1.61,4.055,0.15),0.13,0.105,
      C("shirt",new Color(238,234,222)),12,true)
    addEllipsoid(V(-1.72,4.085,0.18),0.19,0.12,0.13,skin,14,18)
    // Pointing index finger and curled remaining fingers.
    addCylinder(V(-1.78,4.13,0.21),V(-2.22,4.32,0.22),0.047,0.027,skin,10,true)
    addEllipsoid(V(-2.235,4.326,0.22),0.038,0.032,0.032,skin,8,10)
    addCylinder(V(-1.82,4.07,0.22),V(-2.04,4.04,0.24),0.039,0.029,skin,10,true)
    addCylinder(V(-1.80,4.04,0.22),V(-1.98,3.985,0.23),0.035,0.026,skin,10,true)
    addCylinder(V(-1.75,4.02,0.22),V(-1.90,3.965,0.22),0.032,0.024,skin,10,true)
    addCylinder(V(-1.67,4.04,0.22),V(-1.76,3.98,0.25),0.034,0.027,skin,10,true)

    // Left arm: bent in toward the Constitution book.
    addCylinder(V(0.51,3.34,0.00),V(0.78,2.96,0.22),0.19,0.16,suit,18,true)
    addEllipsoid(V(0.68,3.13,0.12),0.19,0.22,0.19,suit,14,18)
    addCylinder(V(0.78,2.96,0.22),V(0.59,2.64,0.42),0.15,0.12,suit,18,true)
    addCylinder(V(0.59,2.66,0.42),V(0.54,2.58,0.51),0.12,0.09,
      C("shirt",new Color(240,237,225)),12,true)

    // Constitution book with cover, pages, gold border and page lines.
    if (showBook) {
      addBox(0.43,2.72,0.53,0.48,0.67,0.18,C("book",new Color(100,51,28)))
      addBox(0.43,2.72,0.635,0.425,0.61,0.025,C("book",new Color(115,61,31)))
      addBox(0.43,2.72,0.653,0.38,0.56,0.012,C("gold",new Color(212,174,83)))
      addBox(0.43,2.72,0.661,0.35,0.53,0.01,C("book",new Color(99,47,25)))
      for (yy <- Seq(2.57,2.62,2.67,2.72,2.77,2.82,2.87)) {
        addBox(0.43,yy,0.67,0.25,0.008,0.006,C("gold",new Color(225,194,117)))
      }
      addBox(0.43,3.015,0.663,0.34,0.012,0.008,C("gold",new Color(231,202,132)))
    }

    // Neck, collar, head and ears.
    addCylinder(V(0,3.38,0.00),V(0,3.78,0.00),0.19,0.17,skin,18,true)
    addEllipsoid(V(0,3.70,0.06),0.27,0.14,0.23,shirt,14,20)
    addEllipsoid(V(0,4.22,0.025),0.365,0.49,0.315,skin,24,32)
    addEllipsoid(V(-0.355,4.19,0.015),0.075,0.12,0.07,skin,12,16)
    addEllipsoid(V(0.355,4.19,0.015),0.075,0.12,0.07,skin,12,16)

    // Neatly combed dark hair cap / side sections.
    addEllipsoid(V(0,4.585,-0.005),0.355,0.145,0.30,hair,16,26)
    addEllipsoid(V(-0.30,4.43,0.005),0.09,0.19,0.26,hair,12,18)
    addEllipsoid(V(0.30,4.43,0.005),0.09,0.19,0.26,hair,12,18)
    addEllipsoid(V(0,4.50,0.275),0.26,0.045,0.055,hair,10,18)

    // Eyes, pupils, brows, nose, mouth and iconic round glasses.
    addEllipsoid(V(-0.135,4.265,0.308),0.061,0.043,0.027,
      C("shirt",new Color(247,238,221)),10,14)
    addEllipsoid(V(0.135,4.265,0.308),0.061,0.043,0.027,
      C("shirt",new Color(247,238,221)),10,14)
    addEllipsoid(V(-0.132,4.265,0.337),0.022,0.027,0.014,C("eye",new Color(28,25,24)),8,10)
    addEllipsoid(V(0.138,4.265,0.337),0.022,0.027,0.014,C("eye",new Color(28,25,24)),8,10)
    addCylinder(V(-0.20,4.333,0.32),V(-0.07,4.342,0.326),0.018,0.016,hair,8,true)
    addCylinder(V(0.07,4.342,0.326),V(0.20,4.333,0.32),0.016,0.018,hair,8,true)

    val glasses = C("glasses",new Color(28,29,34))
    addCircle(-0.135,4.265,0.36,0.105,0.014,glasses,32)
    addCircle(0.135,4.265,0.36,0.105,0.014,glasses,32)
    addCylinder(V(-0.03,4.27,0.36),V(0.03,4.27,0.36),0.014,0.014,glasses,8,false)
    addCylinder(V(-0.24,4.28,0.34),V(-0.34,4.24,0.17),0.012,0.009,glasses,8,false)
    addCylinder(V(0.24,4.28,0.34),V(0.34,4.24,0.17),0.012,0.009,glasses,8,false)

    addEllipsoid(V(0,4.13,0.345),0.065,0.105,0.075,skin,12,16)
    addEllipsoid(V(0,4.075,0.395),0.038,0.028,0.027,
      C("skin",new Color(181,121,91)),10,12)
    addCylinder(V(-0.085,3.995,0.316),V(0.085,3.995,0.316),0.013,0.013,
      C("dark",new Color(102,54,47)),8,false)
    addEllipsoid(V(0,3.945,0.302),0.085,0.035,0.035,
      C("skin",new Color(177,117,91)),10,14)

    // Small decorative lapel pin.
    addEllipsoid(V(-0.34,3.18,0.367),0.045,0.045,0.02,
      C("gold",new Color(248,203,76)),10,14)
  }

  // --------------------------------------------------------------
  // SOFTWARE 3D RENDERER: near clipping + perspective + Z-buffer.
  // --------------------------------------------------------------
  val nearPlane = 0.18

  def transform(p: V): TV = {
    val cy = math.cos(yaw); val sy = math.sin(yaw)
    val x1 = p.x * cy - p.z * sy
    val z1 = p.x * sy + p.z * cy
    val y1 = p.y - targetY
    val cp = math.cos(pitch); val sp = math.sin(pitch)
    val y2 = y1 * cp - z1 * sp
    val z2 = y1 * sp + z1 * cp
    TV(x1, y2, zoom - z2)
  }

  def clipNear(poly: scala.collection.immutable.List[TV]): scala.collection.immutable.List[TV] = {
    if (poly.isEmpty) return poly
    val out = ArrayBuffer[TV]()
    var previous = poly.last
    var previousInside = previous.z > nearPlane
    poly.foreach { current =>
      val currentInside = current.z > nearPlane
      if (currentInside != previousInside) {
        val den = current.z - previous.z
        if (math.abs(den) > 0.0000001) {
          val t = (nearPlane - previous.z) / den
          out += TV(
            previous.x + (current.x - previous.x) * t,
            previous.y + (current.y - previous.y) * t,
            nearPlane
          )
        }
      }
      if (currentInside) out += current
      previous = current
      previousInside = currentInside
    }
    out.toList
  }

  def project(p: TV, w: Int, h: Int): SV = {
    val focal = math.min(w, h) * 1.03
    SV(w / 2.0 + p.x * focal / p.z,
       h / 2.0 - p.y * focal / p.z,
       1.0 / p.z)
  }

  def rasterTriangle(imgPixels: Array[Int], zbuf: Array[Double],
                     w: Int, h: Int, a: SV, b: SV, c: SV, color: Color): Unit = {
    val area = (b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y)
    if (math.abs(area) < 0.00001) return

    val minX = math.max(0, math.floor(math.min(a.x, math.min(b.x,c.x))).toInt)
    val maxX = math.min(w - 1, math.ceil(math.max(a.x, math.max(b.x,c.x))).toInt)
    val minY = math.max(0, math.floor(math.min(a.y, math.min(b.y,c.y))).toInt)
    val maxY = math.min(h - 1, math.ceil(math.max(a.y, math.max(b.y,c.y))).toInt)
    if (minX > maxX || minY > maxY) return

    val rgb = 0xff000000 | (color.getRed << 16) | (color.getGreen << 8) | color.getBlue
    var y = minY
    while (y <= maxY) {
      val py = y + 0.5
      var x = minX
      while (x <= maxX) {
        val px = x + 0.5
        val wa = ((b.y-c.y)*(px-c.x) + (c.x-b.x)*(py-c.y)) / area
        val wb = ((c.y-a.y)*(px-c.x) + (a.x-c.x)*(py-c.y)) / area
        val wc = 1.0 - wa - wb
        if (wa >= -0.00001 && wb >= -0.00001 && wc >= -0.00001) {
          val inv = wa*a.invZ + wb*b.invZ + wc*c.invZ
          val idx = y*w + x
          if (inv > zbuf(idx)) {
            zbuf(idx) = inv
            imgPixels(idx) = rgb
          }
        }
        x += 1
      }
      y += 1
    }
  }

  def renderImage(w0: Int, h0: Int): BufferedImage = {
    val w = math.max(300,w0); val h = math.max(300,h0)
    val img = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB)
    val bg = img.createGraphics()
    bg.setPaint(new GradientPaint(0,0,backgroundTop,0,h,backgroundBottom))
    bg.fillRect(0,0,w,h)
    // A subtle floor horizon glow.
    bg.setColor(new Color(174,113,43,22))
    bg.fillOval(w/2 - 340,h/2 + 100,680,300)
    bg.dispose()

    val pixels = img.getRaster.getDataBuffer.asInstanceOf[DataBufferInt].getData
    val zbuf = Array.fill(w*h)(0.0)

    triangles.foreach { t =>
      val av = transform(t.a); val bv = transform(t.b); val cv = transform(t.c)
      val clipped = clipNear(scala.collection.immutable.List(av,bv,cv))
      if (clipped.length >= 3) {
        val first = project(clipped.head,w,h)
        var i = 1
        while (i < clipped.length - 1) {
          rasterTriangle(pixels,zbuf,w,h,first,
            project(clipped(i),w,h),project(clipped(i+1),w,h),t.color)
          i += 1
        }
      }
    }

    val g = img.createGraphics()
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON)
    drawOverlay(g,w,h)
    g.dispose()
    img
  }

  def projectWorld(x: Double,y: Double,z: Double,w: Int,h: Int): Point = {
    val p = transform(V(x,y,z))
    if (p.z <= nearPlane) new Point(w/2,h/2)
    else {
      val s = project(p,w,h)
      new Point(s.x.toInt,s.y.toInt)
    }
  }

  def drawOverlay(g: Graphics2D,w: Int,h: Int): Unit = {
    // Header and soft dark band.
    g.setColor(new Color(4,8,22,168))
    g.fillRoundRect(16,14,w-32,66,22,22)
    g.setFont(new Font("Serif",Font.BOLD,26))
    g.setColor(new Color(255,218,140))
    val title = "DR. BABASAHEB AMBEDKAR"
    g.drawString(title,(w-g.getFontMetrics.stringWidth(title))/2,43)
    g.setFont(new Font("SansSerif",Font.PLAIN,13))
    g.setColor(new Color(235,238,250))
    val subtitle = "3D STATUE STUDIO  |  DRAG TO ROTATE  |  SCROLL TO ZOOM"
    g.drawString(subtitle,(w-g.getFontMetrics.stringWidth(subtitle))/2,64)

    // Label over the front face of the pedestal when viewed near-front-on.
    if (math.abs(yaw) < 0.55 || math.abs(yaw - 2*math.Pi) < 0.55) {
      val p = projectWorld(0,0.42,0.81,w,h)
      g.setFont(new Font("Serif",Font.BOLD,10))
      g.setColor(new Color(255,221,142))
      val txt = "DR. B. R. AMBEDKAR"
      g.drawString(txt,p.x-g.getFontMetrics.stringWidth(txt)/2,p.y+4)
    }

    // Bottom help strip.
    g.setColor(new Color(3,6,17,185))
    g.fillRoundRect(18,h-47,w-36,30,14,14)
    g.setFont(new Font("SansSerif",Font.PLAIN,12))
    g.setColor(new Color(237,240,250))
    val help = "Mouse drag: rotate   |   Wheel / +/-: zoom   |   A/D: rotate   |   Arrows: tilt   |   1-4: themes   |   H: halo   |   B: book   |   R: reset"
    g.drawString(help,30,h-28)
  }

  // --------------------------------------------------------------
  // UI, themes and screenshot export.
  // --------------------------------------------------------------
  var canvas: JPanel = _
  var statusLabel: JLabel = _
  var frame: JFrame = _

  def resetCamera(): Unit = {
    yaw = 0.0
    pitch = 0.02
    zoom = 9.4
    if (canvas != null) canvas.repaint()
  }

  def saveScreenshot(): Unit = {
    val chooser = new JFileChooser()
    chooser.setDialogTitle("Save statue screenshot as PNG")
    chooser.setSelectedFile(new File("Babasaheb_3D_Statue.png"))
    if (chooser.showSaveDialog(frame) == JFileChooser.APPROVE_OPTION) {
      try {
        val image = renderImage(canvas.getWidth,canvas.getHeight)
        val target = chooser.getSelectedFile
        val file = if (target.getName.toLowerCase.endsWith(".png")) target
                   else new File(target.getParentFile,target.getName + ".png")
        ImageIO.write(image,"png",file)
        statusLabel.setText("Saved: " + file.getAbsolutePath)
      } catch {
        case ex: Exception =>
          JOptionPane.showMessageDialog(frame,"Could not save screenshot: " + ex.getMessage,
            "Save Error",JOptionPane.ERROR_MESSAGE)
      }
    }
  }

  def makeButton(text: String): JButton = {
    val b = new JButton(text)
    b.setFont(new Font("SansSerif",Font.BOLD,13))
    b.setFocusPainted(false)
    b.setBackground(new Color(28,43,81))
    b.setForeground(new Color(245,240,225))
    b.setBorder(BorderFactory.createCompoundBorder(
      BorderFactory.createLineBorder(new Color(207,164,75),1),
      BorderFactory.createEmptyBorder(7,10,7,10)))
    b.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR))
    b
  }

  def start(): Unit = {
    buildScene()
    SwingUtilities.invokeLater(new Runnable {
      override def run(): Unit = {
        frame = new JFrame("Babasaheb 3D Statue Studio - Scala")
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
        frame.setMinimumSize(new Dimension(800,650))

        val root = new JPanel(new BorderLayout(0,0))
        root.setBackground(new Color(8,12,28))

        val tools = new JPanel(new FlowLayout(FlowLayout.CENTER,8,8))
        tools.setBackground(new Color(13,20,43))

        val themeBox = new JComboBox[String](Array(
          "Classic Blue Suit", "Bronze Sculpture", "Golden Tribute", "White Marble"
        ))
        themeBox.setFont(new Font("SansSerif",Font.BOLD,13))
        themeBox.setPreferredSize(new Dimension(175,34))
        themeBox.addActionListener(new ActionListener {
          override def actionPerformed(e: ActionEvent): Unit = {
            themeMode = themeBox.getSelectedIndex
            buildScene()
            canvas.repaint()
          }
        })

        val front = makeButton("FRONT VIEW")
        front.addActionListener(new ActionListener {
          override def actionPerformed(e: ActionEvent): Unit = resetCamera()
        })
        val halo = makeButton("TOGGLE HALO (H)")
        halo.addActionListener(new ActionListener {
          override def actionPerformed(e: ActionEvent): Unit = {
            showHalo = !showHalo; buildScene(); canvas.repaint()
            statusLabel.setText("Halo: " + (if (showHalo) "ON" else "OFF"))
          }
        })
        val book = makeButton("TOGGLE BOOK (B)")
        book.addActionListener(new ActionListener {
          override def actionPerformed(e: ActionEvent): Unit = {
            showBook = !showBook; buildScene(); canvas.repaint()
            statusLabel.setText("Constitution book: " + (if (showBook) "ON" else "OFF"))
          }
        })
        val shot = makeButton("SAVE PNG")
        shot.addActionListener(new ActionListener {
          override def actionPerformed(e: ActionEvent): Unit = saveScreenshot()
        })

        tools.add(new JLabel("STATUE STYLE:"))
        tools.getComponent(0).asInstanceOf[JLabel].setForeground(new Color(242,222,173))
        tools.add(themeBox); tools.add(front); tools.add(halo); tools.add(book); tools.add(shot)
        root.add(tools,BorderLayout.NORTH)

        canvas = new JPanel() {
          setOpaque(true)
          setBackground(Color.BLACK)
          setFocusable(true)
          override def paintComponent(g: Graphics): Unit = {
            super.paintComponent(g)
            val image = renderImage(getWidth,getHeight)
            g.drawImage(image,0,0,null)
          }
        }
        root.add(canvas,BorderLayout.CENTER)

        statusLabel = new JLabel(" Ready ? drag the model to inspect it from every side.")
        statusLabel.setFont(new Font("SansSerif",Font.PLAIN,12))
        statusLabel.setForeground(new Color(230,230,245))
        statusLabel.setBorder(BorderFactory.createEmptyBorder(7,12,7,12))
        statusLabel.setOpaque(true)
        statusLabel.setBackground(new Color(10,15,31))
        root.add(statusLabel,BorderLayout.SOUTH)

        var lastX = 0
        var lastY = 0
        canvas.addMouseListener(new MouseAdapter {
          override def mousePressed(e: MouseEvent): Unit = {
            lastX = e.getX; lastY = e.getY
            canvas.requestFocusInWindow()
          }
        })
        canvas.addMouseMotionListener(new MouseMotionAdapter {
          override def mouseDragged(e: MouseEvent): Unit = {
            val dx = e.getX - lastX
            val dy = e.getY - lastY
            yaw += dx * 0.009
            pitch = clamp(pitch + dy * 0.006,-0.72,0.72)
            lastX = e.getX; lastY = e.getY
            canvas.repaint()
          }
        })
        canvas.addMouseWheelListener(new MouseWheelListener {
          override def mouseWheelMoved(e: MouseWheelEvent): Unit = {
            zoom = clamp(zoom + e.getWheelRotation * 0.45,6.3,15.5)
            canvas.repaint()
          }
        })
        canvas.addKeyListener(new KeyAdapter {
          override def keyPressed(e: KeyEvent): Unit = {
            e.getKeyCode match {
              case KeyEvent.VK_A | KeyEvent.VK_LEFT => yaw -= 0.12
              case KeyEvent.VK_D | KeyEvent.VK_RIGHT => yaw += 0.12
              case KeyEvent.VK_UP => pitch = clamp(pitch - 0.06,-0.72,0.72)
              case KeyEvent.VK_DOWN => pitch = clamp(pitch + 0.06,-0.72,0.72)
              case KeyEvent.VK_ADD | KeyEvent.VK_EQUALS => zoom = clamp(zoom - 0.4,6.3,15.5)
              case KeyEvent.VK_SUBTRACT | KeyEvent.VK_MINUS => zoom = clamp(zoom + 0.4,6.3,15.5)
              case KeyEvent.VK_H => showHalo = !showHalo; buildScene()
              case KeyEvent.VK_B => showBook = !showBook; buildScene()
              case KeyEvent.VK_R => resetCamera()
              case KeyEvent.VK_1 => themeMode = 0; themeBox.setSelectedIndex(0); buildScene()
              case KeyEvent.VK_2 => themeMode = 1; themeBox.setSelectedIndex(1); buildScene()
              case KeyEvent.VK_3 => themeMode = 2; themeBox.setSelectedIndex(2); buildScene()
              case KeyEvent.VK_4 => themeMode = 3; themeBox.setSelectedIndex(3); buildScene()
              case _ => ()
            }
            canvas.repaint()
          }
        })

        frame.setContentPane(root)
        frame.setSize(1120,850)
        frame.setLocationRelativeTo(null)
        frame.setVisible(true)
        canvas.requestFocusInWindow()
      }
    })
  }
}

Babasaheb3DStatue.start()