Home About
SVG , Kotlin , Fractal

六角形のフラクタル図形 kotlin 版

随分前にGroovy で SVG を出力して、六角形のフラクタル図形を書いたというエントリーを書いたのですが、 それを Kotlin に移植しました。 あとで Haskell にも移植するつもりなので、そのとき移植しやすいように Haskell に寄せて記述したつもりです。

Groovyのコードでは、描画色をカラフルにしていましたが、これは白黒です。

fractal-hexagons

// hexagon.main.kts

import java.io.File

data class Color(val red:Int, val green:Int, val blue:Int)
data class Point(val x:Float, val y:Float) 
data class Circle(val centerPt: Point, val r: Float)
data class Rect(val left: Float, val top: Float, val right: Float, val bottom: Float)

typealias OneHexagonSVG = String
typealias Hexagon = List<Point>
typealias Radius = Float


val createSVG: (Rect, String)->String = { rect, svgContents->
    val list = mutableListOf<String>()

    list.add("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
    list.add("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">")
    list.add("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" x=\"${rect.left.toInt()}\" y=\"${rect.top.toInt()}\" width=\"${rect.right.toInt()}\" height=\"${rect.bottom.toInt()}\" viewBox=\"${rect.left.toInt()} ${rect.top.toInt()} ${rect.right.toInt()} ${rect.bottom.toInt()}\">")

    list.add("<g style=\"stroke:rgb(0,0,0)\" stroke-width=\"1\" fill=\"black\">")
    list.add("<rect x=\"${rect.left.toInt()}\" y=\"${rect.top.toInt()}\" width=\"${rect.right.toInt()}\" height=\"${rect.bottom.toInt()}\"/>")
    list.add("</g>")

    list.add(svgContents)
    list.add("</svg>")

    list.joinToString(System.getProperty("line.separator"))
}

val toHexagon: (Rect)->Hexagon = { rect->
    val centerPt = Point((rect.left + rect.right)/2f, (rect.top + rect.bottom)/2f)

    val vertexCount = 6
    val radius = Math.min((rect.right-rect.left), (rect.bottom-rect.top))/2f
    val angle  = 2 * Math.PI/vertexCount

    0.until(vertexCount).map {
        val x = radius*Math.sin(angle*it)
        val y = radius*Math.cos(angle*it)*(-1)

        Point(
            x.toFloat() +centerPt.x,
            y.toFloat() +centerPt.y)
    }
}

val toRect: (Circle)->Rect = { c->
    val left   = c.centerPt.x - c.r
    val top    = c.centerPt.y - c.r
    val right  = c.centerPt.x + c.r
    val bottom = c.centerPt.y + c.r
    Rect(left,top,right,bottom)
}

val toOneHexagonSVG: (Circle, Color) -> OneHexagonSVG = {c, strokeColor->
    val list = mutableListOf<String>()

    list.add("<g style=\"stroke:rgb(${strokeColor.red},${strokeColor.green},${strokeColor.blue})\" stroke-width=\"1\" fill=\"none\">")
    list.add("<path d=\"")

    val commandList = listOf("M", "L", "L", "L", "L", "L")
    list.add(commandList.zip(toHexagon(toRect(c))).map { pair->
        val cmd = pair.first
        val pt  = pair.second
        "${cmd} ${pt.x},${pt.y}"
    }.joinToString(" "))

    list.add(" z\"/>")
    list.add("</g>")

    list.joinToString("")
}

// この関数は再帰するので fun で記述.
fun toHexagonSVGList(c: Circle, strokeColor: Color, downScale: Float): List<OneHexagonSVG>{
    return if( c.r<2f ){
        listOf<OneHexagonSVG>()
    } else {
        val oneHexagonSVG = toOneHexagonSVG(
            Circle(c.centerPt, c.r*downScale),
            strokeColor)

        val childHexagonSVGList: List<OneHexagonSVG> = toHexagon(toRect(c)).map { centerPt1->
            val newCircle = Circle(centerPt1, c.r*downScale)
            toHexagonSVGList(newCircle, strokeColor, downScale)
        }.flatten()

        listOf(listOf(oneHexagonSVG), childHexagonSVGList).flatten()
    }
}


val canvasWidth  = 640f
val canvasHeight = 640f
val downScale = 0.32f
val circle = Circle(
    Point(canvasWidth/2f, canvasHeight/2f),
    Math.min(canvasWidth, canvasHeight)*downScale)

val hexagonSVGList = toHexagonSVGList(
    circle,
    Color(255,255,255),
    downScale)

val svg = createSVG(Rect(0f,0f,canvasWidth,canvasHeight), hexagonSVGList.joinToString(""))
File("result.svg").printWriter(Charsets.UTF_8).use { pw ->
    pw.println(svg)
}

実行する kotlinc バージョン:

$ kotlinc -version
info: kotlinc-jvm 1.8.10 (JRE 11.0.17+8-post-Ubuntu-1ubuntu220.04)

実行:

$ kotlinc -script hexagon.main.kts

結果は result.svg に生成されます。

Liked some of this entry? Buy me a coffee, please.