Code Sketch


yoii lord 3d
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]()

  // Choose a figure from the dropdown. These are original, procedural,
  // low-poly interpretations made only from geometry in this Scala file.
  val figureNames = Array(
    "Dr. Babasaheb Ambedkar",
    "Gautam Buddha",
    "Ganesh Ji",
    "Lord Shiva",
    "Shri Krishna",
    "Lord Rama",
    "Hanuman Ji",
    "Maa Durga",
    "Maa Saraswati",
    "Maa Lakshmi",
    "Lord Vishnu",
    "Mahavir Swami",
    "Sai Baba",
    "Jesus Christ",
    "Guru Nanak Dev Ji"
  )
  var selectedFigure = figureNames(0)

  // 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)))

    if (selectedFigure == "Dr. Babasaheb Ambedkar") {
    // 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)
    } else {
      buildOtherFigure()
    }
  }

  // --------------------------------------------------------------
  // OTHER ORIGINAL PROCEDURAL STATUES
  // These are stylized 3D interpretations, not photo-realistic assets.
  // --------------------------------------------------------------

  def addFace(skin0: Color, hair0: Color, cy: Double,
              hasHair: Boolean = true, closedEyes: Boolean = false,
              foreheadMark: Option[Color] = None): Unit = {
    val skin = C("skin", skin0)
    val hair = C("dark", hair0)
    addCylinder(V(0,cy-0.48,0.00), V(0,cy-0.28,0.00), 0.15,0.14,skin,14,true)
    addEllipsoid(V(0,cy,0.05),0.325,0.425,0.285,skin,20,28)
    addEllipsoid(V(-0.323,cy-0.01,0.02),0.065,0.12,0.07,skin,10,14)
    addEllipsoid(V(0.323,cy-0.01,0.02),0.065,0.12,0.07,skin,10,14)
    if (hasHair) {
      addEllipsoid(V(0,cy+0.30,-0.015),0.325,0.15,0.275,hair,14,24)
      addEllipsoid(V(-0.275,cy+0.19,0.0),0.075,0.18,0.24,hair,10,14)
      addEllipsoid(V(0.275,cy+0.19,0.0),0.075,0.18,0.24,hair,10,14)
    }
    if (closedEyes) {
      addCylinder(V(-0.19,cy+0.015,0.307),V(-0.075,cy+0.005,0.322),0.012,0.012,C("dark",new Color(65,40,32)),8,false)
      addCylinder(V(0.075,cy+0.005,0.322),V(0.19,cy+0.015,0.307),0.012,0.012,C("dark",new Color(65,40,32)),8,false)
    } else {
      addEllipsoid(V(-0.125,cy+0.015,0.315),0.047,0.029,0.018,new Color(247,239,222),8,12)
      addEllipsoid(V(0.125,cy+0.015,0.315),0.047,0.029,0.018,new Color(247,239,222),8,12)
      addEllipsoid(V(-0.125,cy+0.015,0.334),0.018,0.022,0.012,new Color(32,25,22),8,10)
      addEllipsoid(V(0.125,cy+0.015,0.334),0.018,0.022,0.012,new Color(32,25,22),8,10)
      addCylinder(V(-0.19,cy+0.075,0.303),V(-0.07,cy+0.083,0.31),0.013,0.013,hair,8,false)
      addCylinder(V(0.07,cy+0.083,0.31),V(0.19,cy+0.075,0.303),0.013,0.013,hair,8,false)
    }
    addEllipsoid(V(0,cy-0.085,0.335),0.042,0.075,0.04,skin,10,12)
    addCylinder(V(-0.075,cy-0.19,0.305),V(0.075,cy-0.19,0.305),0.012,0.012,C("dark",new Color(110,62,52)),8,false)
    foreheadMark.foreach { mark =>
      addEllipsoid(V(0,cy+0.17,0.333),0.035,0.07,0.02,mark,10,12)
    }
  }

  def addCrown(col0: Color, baseY: Double = 4.34): Unit = {
    val col = C("gold", col0)
    addCylinder(V(0,baseY,0.01),V(0,baseY+0.17,0.01),0.31,0.27,col,20,true)
    addCylinder(V(0,baseY+0.17,0.01),V(0,baseY+0.40,0.01),0.27,0.12,col,18,true)
    addEllipsoid(V(0,baseY+0.42,0.01),0.10,0.08,0.10,col,10,14)
    addEllipsoid(V(0,baseY+0.10,0.285),0.06,0.07,0.035,new Color(45,170,190),8,10)
  }

  def addStandingLegs(legColor0: Color): Unit = {
    val leg = C("robe", legColor0)
    addEllipsoid(V(-0.22,1.15,0.07),0.20,0.25,0.31,leg,12,18)
    addEllipsoid(V(0.22,1.15,0.07),0.20,0.25,0.31,leg,12,18)
    addEllipsoid(V(-0.23,0.92,0.18),0.22,0.10,0.32,C("dark",new Color(62,43,31)),12,18)
    addEllipsoid(V(0.23,0.92,0.18),0.22,0.10,0.32,C("dark",new Color(62,43,31)),12,18)
  }

  def addStandingTorso(skin0: Color, cloth0: Color, sash0: Color): Unit = {
    val skin = C("skin",skin0)
    val cloth = C("robe",cloth0)
    val sash = C("gold",sash0)
    addStandingLegs(cloth)
    addEllipsoid(V(0,1.72,0.00),0.48,0.40,0.34,cloth,16,22)
    addEllipsoid(V(0,2.58,0.00),0.52,0.76,0.34,cloth,18,24)
    addCylinder(V(0,3.13,0.00),V(0,3.48,0.00),0.15,0.14,skin,14,true)
    addCylinder(V(-0.42,2.88,0.00),V(-0.70,2.47,0.16),0.15,0.12,cloth,14,true)
    addCylinder(V(-0.70,2.47,0.16),V(-0.72,2.21,0.34),0.12,0.095,skin,12,true)
    addEllipsoid(V(-0.72,2.18,0.36),0.13,0.12,0.12,skin,10,14)
    addCylinder(V(0.42,2.88,0.00),V(0.70,2.47,0.16),0.15,0.12,cloth,14,true)
    addCylinder(V(0.70,2.47,0.16),V(0.72,2.21,0.34),0.12,0.095,skin,12,true)
    addEllipsoid(V(0.72,2.18,0.36),0.13,0.12,0.12,skin,10,14)
    addTri(V(-0.27,3.0,0.30),V(0.26,2.28,0.37),V(0.05,2.05,0.38),sash)
    addCylinder(V(-0.39,1.98,0.27),V(0.39,1.98,0.27),0.035,0.035,sash,8,false)
  }

  def buildBuddha(): Unit = {
    val skin = new Color(198,145,109)
    val robe = C("robe",new Color(208,116,35))
    val fold = C("gold",new Color(245,183,68))
    // Seated lotus posture and draped robe.
    addEllipsoid(V(0,1.31,0.06),0.76,0.30,0.47,robe,18,26)
    addEllipsoid(V(-0.39,1.26,0.18),0.43,0.22,0.40,robe,14,22)
    addEllipsoid(V(0.39,1.26,0.18),0.43,0.22,0.40,robe,14,22)
    addEllipsoid(V(0,1.82,0.01),0.54,0.46,0.39,robe,18,24)
    addEllipsoid(V(0,2.39,0.00),0.52,0.62,0.35,robe,18,24)
    addEllipsoid(V(-0.43,2.57,0.00),0.25,0.27,0.27,robe,12,18)
    addEllipsoid(V(0.43,2.57,0.00),0.25,0.27,0.27,robe,12,18)
    addCylinder(V(-0.43,2.55,0.02),V(-0.28,2.02,0.35),0.16,0.12,robe,14,true)
    addCylinder(V(0.43,2.55,0.02),V(0.28,2.02,0.35),0.16,0.12,robe,14,true)
    addEllipsoid(V(-0.16,1.96,0.40),0.24,0.10,0.12,skin,12,18)
    addEllipsoid(V(0.16,1.98,0.42),0.24,0.10,0.12,skin,12,18)
    addTri(V(-0.36,2.79,0.28),V(0.14,2.15,0.40),V(0.30,2.78,0.27),fold)
    addCylinder(V(0,2.72,0.00),V(0,3.05,0.00),0.14,0.14,skin,14,true)
    addFace(skin,new Color(65,45,31),3.45,false,true,None)
    addEllipsoid(V(0,3.86,0.04),0.19,0.15,0.18,skin,12,18) // ushnisha
    // Small curled hair dots, intentionally understated.
    for (row <- 0 until 3; col <- -4 to 4 if math.abs(col)+row < 7) {
      addEllipsoid(V(col*0.064,3.67+row*0.062,0.245-row*0.018),0.026,0.026,0.022,
        new Color(48,44,39),6,8)
    }
    addTorusXY(0,1.72,0.25,0.42,0.025,fold,36,6)
  }

  def buildGanesh(): Unit = {
    val skin = new Color(218,151,133)
    val robe = C("robe",new Color(191,85,40))
    val goldC = C("gold",new Color(245,190,55))
    // Seated body and round belly.
    addEllipsoid(V(0,1.25,0.08),0.65,0.28,0.43,C("robe",new Color(226,157,54)),16,22)
    addEllipsoid(V(0,1.90,0.00),0.50,0.44,0.37,robe,16,22)
    addEllipsoid(V(0,2.34,0.00),0.48,0.58,0.34,robe,16,22)
    addCylinder(V(-0.43,2.52,0.00),V(-0.82,2.16,0.25),0.14,0.10,robe,12,true)
    addCylinder(V(-0.82,2.16,0.25),V(-0.73,2.03,0.36),0.10,0.08,skin,10,true)
    addCylinder(V(0.43,2.52,0.00),V(0.82,2.16,0.25),0.14,0.10,robe,12,true)
    addCylinder(V(0.82,2.16,0.25),V(0.73,2.03,0.36),0.10,0.08,skin,10,true)
    // Elephant head, broad ears, trunk and tusks.
    addCylinder(V(0,2.67,0.00),V(0,3.00,0.00),0.15,0.15,skin,12,true)
    addEllipsoid(V(0,3.50,0.03),0.40,0.46,0.34,skin,18,24)
    addEllipsoid(V(-0.48,3.56,-0.01),0.30,0.19,0.10,skin,12,18)
    addEllipsoid(V(0.48,3.56,-0.01),0.30,0.19,0.10,skin,12,18)
    addEllipsoid(V(-0.14,3.59,0.342),0.055,0.035,0.022,new Color(35,29,27),8,10)
    addEllipsoid(V(0.14,3.59,0.342),0.055,0.035,0.022,new Color(35,29,27),8,10)
    addCylinder(V(-0.20,3.39,0.32),V(-0.16,3.26,0.37),0.045,0.03,new Color(248,231,193),8,true)
    addCylinder(V(0.20,3.39,0.32),V(0.16,3.26,0.37),0.045,0.03,new Color(248,231,193),8,true)
    addCylinder(V(0.02,3.49,0.36),V(0.05,3.19,0.48),0.13,0.10,skin,12,true)
    addCylinder(V(0.05,3.19,0.48),V(0.19,3.06,0.45),0.10,0.075,skin,12,true)
    addCrown(goldC,3.88)
    // Four symbolic arms.
    addCylinder(V(-0.39,2.55,0.00),V(-0.86,2.85,0.05),0.12,0.09,robe,12,true)
    addEllipsoid(V(-0.91,2.88,0.08),0.13,0.12,0.11,skin,10,12)
    addCylinder(V(0.39,2.55,0.00),V(0.86,2.85,0.05),0.12,0.09,robe,12,true)
    addEllipsoid(V(0.91,2.88,0.08),0.13,0.12,0.11,skin,10,12)
    addEllipsoid(V(-0.95,2.90,0.08),0.09,0.10,0.08,goldC,10,12)
    addEllipsoid(V(0.95,2.90,0.08),0.09,0.10,0.08,goldC,10,12)
    // Small mouse companion on the pedestal.
    addEllipsoid(V(0.65,0.94,0.40),0.17,0.09,0.13,new Color(105,100,91),10,14)
    addEllipsoid(V(0.76,1.00,0.40),0.07,0.07,0.07,new Color(105,100,91),8,10)
  }

  def buildShiva(): Unit = {
    val blueSkin = new Color(85,153,205)
    addStandingTorso(blueSkin,new Color(224,182,81),new Color(239,203,103))
    addFace(blueSkin,new Color(50,43,37),4.00,true,false,Some(new Color(195,35,40)))
    addEllipsoid(V(0,4.45,-0.02),0.22,0.26,0.21,new Color(51,47,40),12,18)
    addEllipsoid(V(0.16,4.58,0.00),0.095,0.18,0.08,new Color(60,54,45),10,14)
    // Crescent ornament above the matted hair.
    addCylinder(V(-0.15,4.60,0.05),V(-0.06,4.76,0.05),0.025,0.025,new Color(228,229,235),8,false)
    addCylinder(V(-0.06,4.76,0.05),V(0.08,4.78,0.05),0.025,0.025,new Color(228,229,235),8,false)
    // Rudraksha beads and snake coil.
    addTorusXY(0,3.43,0.22,0.19,0.035,new Color(92,62,36),40,6)
    for (i <- 0 until 18) {
      val a = i*2*math.Pi/18.0
      addEllipsoid(V(0.22*math.cos(a),3.43+0.22*math.sin(a),0.245),0.025,0.025,0.022,
        new Color(126,84,49),6,8)
    }
    // Trident.
    val metal = C("gold",new Color(219,194,129))
    addCylinder(V(1.02,0.94,-0.02),V(1.02,4.48,-0.02),0.035,0.028,WOODEN,10,true)
    addCylinder(V(1.02,4.08,-0.02),V(0.88,4.45,-0.02),0.045,0.012,metal,8,true)
    addCylinder(V(1.02,4.08,-0.02),V(1.16,4.45,-0.02),0.045,0.012,metal,8,true)
    addCylinder(V(1.02,4.08,-0.02),V(1.02,4.57,-0.02),0.045,0.015,metal,8,true)
  }

  // Shared colour for sacred wooden props.
  val WOODEN = new Color(105,68,42)

  def buildKrishna(): Unit = {
    val skin = new Color(76,133,199)
    addStandingTorso(skin,new Color(240,190,48),new Color(54,156,96))
    addFace(skin,new Color(28,25,23),4.00,true,false,Some(new Color(220,43,48)))
    addCrown(new Color(224,174,49),4.37)
    // Peacock feather.
    addCylinder(V(0.13,4.68,0.02),V(0.29,4.96,0.00),0.025,0.016,new Color(41,114,66),8,true)
    addEllipsoid(V(0.31,4.98,0.01),0.10,0.15,0.035,new Color(35,144,73),10,16)
    addEllipsoid(V(0.32,4.98,0.045),0.055,0.08,0.02,new Color(40,112,193),8,12)
    addEllipsoid(V(0.32,4.98,0.063),0.026,0.045,0.012,new Color(239,191,48),8,10)
    // Flute across the chest.
    addCylinder(V(-0.67,2.63,0.38),V(0.68,2.68,0.38),0.035,0.035,new Color(214,168,72),12,true)
    for (x <- Seq(-0.34,-0.12,0.12,0.34)) {
      addEllipsoid(V(x,2.64,0.415),0.018,0.018,0.01,new Color(54,46,33),6,8)
    }
  }

  def buildRama(): Unit = {
    val skin = new Color(93,151,212)
    addStandingTorso(skin,new Color(230,175,49),new Color(194,48,42))
    addFace(skin,new Color(43,34,26),4.00,true,false,Some(new Color(190,35,37)))
    addCrown(new Color(224,174,49),4.37)
    // Tall curved bow at the figure's left.
    val wood = new Color(118,70,37)
    val pts = Vector(V(-1.05,1.20,0.04),V(-1.20,1.80,0.04),V(-1.26,2.55,0.04),V(-1.16,3.30,0.04),V(-0.94,4.00,0.04))
    pts.sliding(2).foreach(pair => if(pair.length==2) addCylinder(pair(0),pair(1),0.035,0.028,wood,8,true))
    addCylinder(pts.head,pts.last,0.008,0.008,new Color(224,217,194),6,false)
    addCylinder(V(-0.69,2.78,0.10),V(-0.89,2.98,0.10),0.06,0.035,wood,8,true)
  }

  def buildHanuman(): Unit = {
    val skin = new Color(199,121,67)
    val cloth = new Color(207,82,42)
    addStandingTorso(skin,cloth,new Color(243,185,52))
    addFace(skin,new Color(117,66,36),4.00,true,false,Some(new Color(211,35,35)))
    // Broad muzzle and divine crown.
    addEllipsoid(V(0,3.88,0.34),0.18,0.12,0.11,new Color(220,159,113),12,16)
    addEllipsoid(V(-0.10,3.90,0.43),0.025,0.025,0.018,new Color(36,25,20),8,10)
    addEllipsoid(V(0.10,3.90,0.43),0.025,0.025,0.018,new Color(36,25,20),8,10)
    addCrown(new Color(227,173,48),4.38)
    // Curled tail behind the body.
    val tail = Vector(V(0.36,1.55,-0.16),V(0.74,1.90,-0.27),V(0.80,2.32,-0.27),V(0.64,2.55,-0.25))
    tail.sliding(2).foreach(pair => if(pair.length==2) addCylinder(pair(0),pair(1),0.05,0.04,skin,8,true))
    // Gada (mace).
    addCylinder(V(1.03,1.0,-0.02),V(0.93,3.38,-0.02),0.055,0.035,new Color(155,99,42),10,true)
    addEllipsoid(V(0.92,3.48,-0.02),0.20,0.24,0.18,new Color(211,161,63),12,16)
  }

  def buildDurga(): Unit = {
    val skin = new Color(215,153,123)
    val red = C("robe",new Color(174,28,53))
    addStandingLegs(new Color(186,37,56))
    addEllipsoid(V(0,1.80,0),0.53,0.43,0.37,new Color(196,36,57),16,22)
    addEllipsoid(V(0,2.58,0),0.57,0.78,0.37,red,18,24)
    addTri(V(-0.40,3.20,0.31),V(0.36,2.14,0.39),V(0.10,1.90,0.40),new Color(239,186,65))
    addCylinder(V(0,3.10,0),V(0,3.47,0),0.15,0.14,skin,14,true)
    addFace(skin,new Color(55,35,25),4.00,true,false,Some(new Color(203,27,45)))
    addCrown(new Color(236,182,56),4.38)
    // Six arms, symbolically holding divine tools.
    val armEnds = Seq((-1.05,3.18),(1.05,3.18),(-1.18,2.65),(1.18,2.65),(-0.95,2.14),(0.95,2.14))
    armEnds.zipWithIndex.foreach { case ((ex,ey),i) =>
      val sy = if (i<2) 3.05 else if (i<4) 2.70 else 2.35
      val sx = if (ex<0) -0.43 else 0.43
      addCylinder(V(sx,sy,0.0),V(ex,ey,0.12),0.12,0.08,red,10,true)
      addEllipsoid(V(ex,ey,0.13),0.10,0.11,0.10,skin,10,12)
    }
    // A lion companion sits beside the pedestal.
    addEllipsoid(V(0.83,1.06,0.34),0.26,0.18,0.25,new Color(197,139,49),12,16)
    addEllipsoid(V(1.02,1.18,0.37),0.15,0.14,0.15,new Color(221,167,61),10,14)
    addEllipsoid(V(1.08,1.20,0.48),0.035,0.03,0.022,new Color(25,23,22),8,10)
  }

  def buildSaraswati(): Unit = {
    val skin = new Color(218,169,139)
    val white = C("robe",new Color(236,229,207))
    addStandingTorso(skin,white,new Color(211,173,76))
    addFace(skin,new Color(57,40,30),4.00,true,false,None)
    addCrown(new Color(220,175,66),4.37)
    // Veena diagonally across body, built from simple 3D parts.
    addCylinder(V(-0.90,2.15,0.38),V(0.72,3.10,0.35),0.045,0.035,new Color(143,80,37),12,true)
    addEllipsoid(V(-0.84,2.12,0.39),0.24,0.21,0.15,new Color(174,103,50),12,16)
    addEllipsoid(V(0.69,3.08,0.36),0.12,0.16,0.10,new Color(196,132,63),10,14)
    for (i <- 0 until 5) {
      addCylinder(V(-0.55,2.35+i*0.12,0.425),V(0.48,2.95+i*0.03,0.40),0.008,0.008,new Color(236,214,153),6,false)
    }
    // Swan at the side.
    addEllipsoid(V(0.88,0.98,0.33),0.20,0.11,0.26,new Color(243,239,220),10,14)
    addCylinder(V(0.96,1.02,0.35),V(1.03,1.22,0.35),0.05,0.035,new Color(243,239,220),8,true)
    addEllipsoid(V(1.04,1.23,0.35),0.09,0.08,0.08,new Color(243,239,220),8,10)
    addCylinder(V(1.09,1.22,0.42),V(1.18,1.20,0.42),0.025,0.01,new Color(223,137,45),6,true)
  }

  def buildLakshmi(): Unit = {
    val skin = new Color(219,161,132)
    val pink = C("robe",new Color(176,33,76))
    addStandingTorso(skin,pink,new Color(238,188,57))
    addFace(skin,new Color(55,37,29),4.00,true,false,Some(new Color(207,32,45)))
    addCrown(new Color(238,187,58),4.37)
    // Lotus petals around the base.
    for (i <- 0 until 12) {
      val a = i * 2.0 * math.Pi / 12.0
      addEllipsoid(V(0.67*math.cos(a),0.99,0.67*math.sin(a)),0.17,0.08,0.23,
        new Color(232,109,153),10,14)
    }
    // Gold coins symbolising abundance.
    for (i <- 0 until 5) {
      addEllipsoid(V(0.60+i*0.08,2.04-i*0.12,0.44),0.035,0.035,0.025,new Color(255,209,66),8,10)
    }
    addEllipsoid(V(-0.78,2.25,0.36),0.14,0.18,0.12,new Color(237,173,56),10,14)
  }

  def buildVishnu(): Unit = {
    val skin = new Color(82,139,206)
    addStandingTorso(skin,new Color(236,189,54),new Color(52,126,77))
    addFace(skin,new Color(48,35,26),4.00,true,false,Some(new Color(210,40,45)))
    addCrown(new Color(228,177,51),4.37)
    // Four arms and the conch/disc symbols.
    addCylinder(V(-0.42,2.94,0),V(-0.90,3.26,0.02),0.12,0.08,new Color(235,184,50),10,true)
    addEllipsoid(V(-0.94,3.29,0.04),0.13,0.13,0.11,new Color(246,226,184),10,12)
    addCylinder(V(0.42,2.94,0),V(0.90,3.26,0.02),0.12,0.08,new Color(235,184,50),10,true)
    addTorusXY(0.94,3.29,0.16,0.12,0.025,new Color(228,182,61),24,6)
    addCylinder(V(-0.42,2.55,0),V(-0.96,2.30,0.02),0.12,0.08,new Color(235,184,50),10,true)
    addEllipsoid(V(-1.01,2.28,0.03),0.14,0.14,0.10,new Color(231,224,204),10,12)
    addCylinder(V(0.42,2.55,0),V(0.96,2.30,0.02),0.12,0.08,new Color(235,184,50),10,true)
    addEllipsoid(V(1.01,2.28,0.04),0.11,0.16,0.09,new Color(224,180,53),10,12)
  }

  def buildMahavir(): Unit = {
    val skin = new Color(206,158,124)
    val robe = C("robe",new Color(226,220,202))
    addEllipsoid(V(0,1.31,0.06),0.73,0.28,0.45,robe,16,22)
    addEllipsoid(V(-0.38,1.28,0.18),0.40,0.20,0.38,robe,12,18)
    addEllipsoid(V(0.38,1.28,0.18),0.40,0.20,0.38,robe,12,18)
    addEllipsoid(V(0,1.91,0.01),0.50,0.44,0.35,robe,16,22)
    addEllipsoid(V(0,2.44,0.00),0.51,0.60,0.34,robe,16,22)
    addCylinder(V(-0.42,2.58,0),V(-0.26,2.03,0.35),0.14,0.11,robe,12,true)
    addCylinder(V(0.42,2.58,0),V(0.26,2.03,0.35),0.14,0.11,robe,12,true)
    addEllipsoid(V(-0.12,1.99,0.39),0.20,0.08,0.10,skin,10,14)
    addEllipsoid(V(0.12,1.99,0.39),0.20,0.08,0.10,skin,10,14)
    addFace(skin,new Color(64,47,35),3.48,false,true,None)
    addEllipsoid(V(0,3.89,0.04),0.18,0.14,0.17,skin,10,14)
  }

  def buildSaiBaba(): Unit = {
    val skin = new Color(196,145,112)
    val robe = C("robe",new Color(231,227,212))
    addEllipsoid(V(0,1.48,0.05),0.55,0.48,0.39,robe,16,20)
    addEllipsoid(V(0,2.08,0.00),0.51,0.62,0.35,robe,16,22)
    addCylinder(V(-0.42,2.42,0),V(-0.30,1.92,0.36),0.14,0.10,robe,12,true)
    addCylinder(V(0.42,2.42,0),V(0.30,1.92,0.36),0.14,0.10,robe,12,true)
    addEllipsoid(V(0,1.88,0.39),0.26,0.10,0.10,skin,12,16)
    addFace(skin,new Color(114,78,53),3.34,false,false,None)
    // Head cloth, moustache and beard.
    addEllipsoid(V(0,3.70,0.00),0.35,0.16,0.31,robe,14,18)
    addEllipsoid(V(0,3.08,0.28),0.20,0.23,0.12,new Color(166,155,141),12,16)
    addCylinder(V(-0.15,3.23,0.30),V(0.0,3.18,0.33),0.03,0.02,new Color(91,66,49),8,true)
    addCylinder(V(0.15,3.23,0.30),V(0.0,3.18,0.33),0.03,0.02,new Color(91,66,49),8,true)
  }

  def buildJesus(): Unit = {
    val skin = new Color(195,146,118)
    addStandingTorso(skin,new Color(228,218,190),new Color(153,35,46))
    addFace(skin,new Color(88,48,31),4.00,true,false,None)
    addEllipsoid(V(0,3.72,0.08),0.12,0.24,0.10,new Color(94,54,35),10,14)
    addEllipsoid(V(-0.15,3.82,0.05),0.08,0.25,0.08,new Color(94,54,35),10,14)
    addEllipsoid(V(0.15,3.82,0.05),0.08,0.25,0.08,new Color(94,54,35),10,14)
    // Simple wooden cross behind the figure.
    addBox(0.93,2.70,-0.42,0.11,2.35,0.10,new Color(104,66,40))
    addBox(0.93,3.25,-0.40,0.95,0.12,0.11,new Color(104,66,40))
  }

  def buildGuruNanak(): Unit = {
    val skin = new Color(197,151,121)
    val robe = C("robe",new Color(224,195,132))
    addEllipsoid(V(0,1.47,0.03),0.58,0.44,0.37,robe,16,20)
    addEllipsoid(V(0,2.12,0),0.53,0.66,0.36,robe,16,22)
    addCylinder(V(-0.42,2.48,0),V(-0.27,1.95,0.35),0.14,0.11,robe,12,true)
    addCylinder(V(0.42,2.48,0),V(0.27,1.95,0.35),0.14,0.11,robe,12,true)
    addEllipsoid(V(0,1.92,0.39),0.25,0.10,0.10,skin,12,16)
    addFace(skin,new Color(91,60,39),3.39,false,false,None)
    addEllipsoid(V(0,3.72,0.00),0.36,0.20,0.32,new Color(233,225,203),14,18) // turban
    addEllipsoid(V(0,3.20,0.29),0.19,0.22,0.12,new Color(158,139,112),12,16) // beard
    addCylinder(V(-0.16,3.26,0.31),V(0,3.20,0.34),0.025,0.02,new Color(90,67,48),8,true)
    addCylinder(V(0.16,3.26,0.31),V(0,3.20,0.34),0.025,0.02,new Color(90,67,48),8,true)
  }

  def buildOtherFigure(): Unit = {
    selectedFigure match {
      case "Gautam Buddha" => buildBuddha()
      case "Ganesh Ji" => buildGanesh()
      case "Lord Shiva" => buildShiva()
      case "Shri Krishna" => buildKrishna()
      case "Lord Rama" => buildRama()
      case "Hanuman Ji" => buildHanuman()
      case "Maa Durga" => buildDurga()
      case "Maa Saraswati" => buildSaraswati()
      case "Maa Lakshmi" => buildLakshmi()
      case "Lord Vishnu" => buildVishnu()
      case "Mahavir Swami" => buildMahavir()
      case "Sai Baba" => buildSaiBaba()
      case "Jesus Christ" => buildJesus()
      case "Guru Nanak Dev Ji" => buildGuruNanak()
      case _ => buildBuddha()
    }
  }

  // --------------------------------------------------------------
  // 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 = selectedFigure.toUpperCase + " 3D STATUE"
    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 = if (selectedFigure == "Dr. Babasaheb Ambedkar") "DR. B. R. AMBEDKAR" else selectedFigure.toUpperCase
      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: finishes  |  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(selectedFigure.replaceAll("[^A-Za-z0-9]+", "_") + "_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(selectedFigure + " - 3D Figure 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 GridLayout(2,1,2,2))
        tools.setBackground(new Color(13,20,43))
        val figureBar = new JPanel(new FlowLayout(FlowLayout.CENTER,8,4))
        figureBar.setBackground(new Color(13,20,43))
        val buttonBar = new JPanel(new FlowLayout(FlowLayout.CENTER,8,4))
        buttonBar.setBackground(new Color(13,20,43))

        val figureBox = new JComboBox[String](figureNames)
        figureBox.setSelectedItem(selectedFigure)
        figureBox.setFont(new Font("SansSerif",Font.BOLD,13))
        figureBox.setPreferredSize(new Dimension(245,32))
        figureBox.addActionListener(new ActionListener {
          override def actionPerformed(e: ActionEvent): Unit = {
            selectedFigure = figureBox.getSelectedItem.toString
            buildScene()
            resetCamera()
            canvas.repaint()
            frame.setTitle(selectedFigure + " - 3D Figure Studio")
            statusLabel.setText(" Selected: " + selectedFigure + " ? use the mouse to rotate and inspect the model.")
          }
        })

        val themeBox = new JComboBox[String](Array(
          "Classic Colours", "Bronze Sculpture", "Golden Tribute", "White Marble"
        ))
        themeBox.setFont(new Font("SansSerif",Font.BOLD,13))
        themeBox.setPreferredSize(new Dimension(160,32))
        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("Book toggle applies to Dr. Babasaheb Ambedkar's Constitution prop.")
          }
        })
        val shot = makeButton("SAVE PNG")
        shot.addActionListener(new ActionListener {
          override def actionPerformed(e: ActionEvent): Unit = saveScreenshot()
        })

        val figureLabel = new JLabel("CLICK TO CHOOSE FIGURE:")
        figureLabel.setForeground(new Color(242,222,173))
        val themeLabel = new JLabel("COLOUR FINISH:")
        themeLabel.setForeground(new Color(242,222,173))
        figureBar.add(figureLabel)
        figureBar.add(figureBox)
        figureBar.add(themeLabel)
        figureBar.add(themeBox)
        buttonBar.add(front); buttonBar.add(halo); buttonBar.add(book); buttonBar.add(shot)
        tools.add(figureBar)
        tools.add(buttonBar)
        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 ? choose a figure above, then 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(1220,880)
        frame.setLocationRelativeTo(null)
        frame.setVisible(true)
        canvas.requestFocusInWindow()
      }
    })
  }
}

Babasaheb3DStatue.start()