Code Sketch
bhavesh
Category: Art
// 3D Vector & Projection Setup
case class Vector3D(x: Double, y: Double, z: Double)
val size = canvasBounds.width * 0.4
val viewAngleHalf = Math.toRadians(60.0 / 2.0)
def rotateX(p: Vector3D, angle: Double): Vector3D = {
val rad = Math.toRadians(angle)
Vector3D(p.x, p.y * Math.cos(rad) - p.z * Math.sin(rad), p.y * Math.sin(rad) + p.z * Math.cos(rad))
}
def rotateY(p: Vector3D, angle: Double): Vector3D = {
val rad = Math.toRadians(angle)
Vector3D(p.x * Math.cos(rad) + p.z * Math.sin(rad), p.y, -p.x * Math.sin(rad) + p.z * Math.cos(rad))
}
def rotateZ(p: Vector3D, angle: Double): Vector3D = {
val rad = Math.toRadians(angle)
Vector3D(p.x * Math.cos(rad) - p.y * Math.sin(rad), p.x * Math.sin(rad) + p.y * Math.cos(rad), p.z)
}
def project(p: Vector3D): (Double, Double, Double) = {
val distance = size + 350
val pz = p.z + distance
val factor = size / (pz * Math.tan(viewAngleHalf))
(p.x * factor, p.y * factor, pz)
}
// Colors including Navy Blue for Ashoka Chakra
val flagColors = Map(
"SAFFRON" -> Color(255, 153, 51),
"WHITE" -> Color(255, 255, 255),
"GREEN" -> Color(19, 136, 8),
"POLE" -> Color(128, 128, 128),
"CHAKRA" -> Color(0, 0, 128) // Navy Blue
)
case class MiniBlock(cx: Double, cy: Double, cz: Double, s: Double, colorType: String) {
val h = s / 2.0
val vertices = Seq(
Vector3D(cx - h, cy - h, cz - h), // 0
Vector3D(cx + h, cy - h, cz - h), // 1
Vector3D(cx + h, cy + h, cz - h), // 2
Vector3D(cx - h, cy + h, cz - h), // 3
Vector3D(cx - h, cy - h, cz + h), // 4
Vector3D(cx + h, cy - h, cz + h), // 5
Vector3D(cx + h, cy + h, cz + h), // 6
Vector3D(cx - h, cy + h, cz + h) // 7
)
val faces = Seq(
("FRONT", Seq(4, 5, 6, 7), Vector3D(0, 0, 1)),
("BACK", Seq(1, 0, 3, 2), Vector3D(0, 0, -1)),
("TOP", Seq(3, 2, 6, 7), Vector3D(0, 1, 0)),
("BOTTOM", Seq(4, 5, 1, 0), Vector3D(0, -1, 0)),
("RIGHT", Seq(5, 1, 2, 6), Vector3D(1, 0, 0)),
("LEFT", Seq(0, 4, 7, 3), Vector3D(-1, 0, 0))
)
}
val blockSpacing = 30.0
val blockSize = 28.0
// Construct Flag with Ashoka Chakra in the center white band
val flagBlocks = {
val flagPart = for {
x <- -1 to 2
y <- -1 to 1
z <- 0 to 0
} yield {
// Place Ashoka Chakra block right in the center of the white band
val colorType = if (x == 0 && y == 0 && z == 0) {
"CHAKRA"
} else {
y match {
case 1 => "SAFFRON"
case 0 => "WHITE"
case -1 => "GREEN"
}
}
MiniBlock(x * blockSpacing, y * blockSpacing, z * blockSpacing, blockSize, colorType)
}
val polePart = for {
y <- -3 to 3
} yield {
MiniBlock(-2.0 * blockSpacing, y * blockSpacing, 0.0, blockSize, "POLE")
}
flagPart ++ polePart
}
// Animation Loop
animateWithState(0.0) { angle =>
erasePictures()
val rotX = angle * 0.3
val rotY = angle * 0.5
val rotZ = angle * 0.1
var allRenderFaces = Seq.empty[(Seq[(Double, Double)], Color, Double)]
flagBlocks.foreach { mini =>
mini.faces.foreach { case (faceName, idxs, normal) =>
val rotatedVerts = idxs.map { i =>
val v = mini.vertices(i)
val r = rotateZ(rotateY(rotateX(v, rotX), rotY), rotZ)
project(r)
}
val rNormal = rotateZ(rotateY(rotateX(normal, rotX), rotY), rotZ)
if (rNormal.z < 0) {
val points2D = rotatedVerts.map { case (px, py, _) => (px, py) }
val avgZ = rotatedVerts.map(_._3).sum / 4.0
val color = flagColors(mini.colorType)
allRenderFaces = allRenderFaces :+ (points2D, color, avgZ)
}
}
}
val sortedFaces = allRenderFaces.sortBy(_._3).reverse
sortedFaces.foreach { case (pts, color, _) =>
val facePic = Picture.fromVertexShape { p =>
p.beginShape()
pts.foreach { case (x, y) => p.vertex(x, y) }
p.endShape()
}
facePic.setFillColor(color)
facePic.setPenColor(Color(30, 30, 30))
facePic.setPenThickness(1)
draw(facePic)
}
angle + 1.0
}