Home About Contact
SVG , Groovy , Mathematics , Fractal

Groovy で SVG を出力して、六角形のフラクタル図形を書いた

六角形のフラクタル図形 kotlin 版 を書きました。2023-02-04 更新

コードが少し長いのですが、ここにメモしておきます。

fractal-hexagons

class SvgUtils {
    def createXYWH = { bounds->
        def sb = ''<<''
        sb.append("x=\"").append( bounds.x ).append("\" ")
        sb.append("y=\"").append( bounds.y ).append("\" ")
        sb.append("width=\"").append( bounds.width ).append("\" ")
        sb.append("height=\"").append( bounds.height ).append("\" ")
        sb.toString()
    }
    
    def createViewBox = { bounds->
        def sb = ''<<''
        sb.append("viewBox=\"").append( bounds.x ).append(" ").append( bounds.y ).append(" ").append( bounds.width ).append(" ").append( bounds.height ).append("\"");
        sb.toString()
    }
    
    def createSVGHeader = { viewBoxBounds1, viewBoxBounds2->
        def br = System.getProperty('line.separator')
    
        def sb = ''<<''
        sb << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
        sb << br
        sb << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">"
        sb << br
        sb << "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" "
        sb << br
        sb << createXYWH(viewBoxBounds1)
        sb << " "
        sb << createViewBox(viewBoxBounds2)
        sb << ">"
        sb << "<g style=\"stroke:rgb(0,0,0)\" stroke-width=\"1\" fill=\"black\">"
        sb << "<rect x=\"${viewBoxBounds2.x}\" y=\"${viewBoxBounds2.y}\" width=\"${viewBoxBounds2.width}\" height=\"${viewBoxBounds2.height}\"/>"
        sb << "</g>"
        sb.toString()
    }
    
    def createSVGFooter = {
        '</svg>'
    }
}

class AwtUtils {
    private def random = new Random()

    def createRandomValue(){
        int minimumValue = 64
        int max = 255 - minimumValue
        Math.abs(random.nextInt() % max) + minimumValue
    }

    def createRandomColor(){
        int r = createRandomValue()
        int g = createRandomValue()
        int b = createRandomValue()
        [red:r,green:g,blue:b]
    }
    
    def createPoint = {x,y-> [x:x,y:y] }
    def createRectangle = { x,y,width,height-> [x:x,y:y,width:width,height:height] }
    
    def centerPoint = { rectangle->
        def x = rectangle.x + rectangle.width/2f
        def y = rectangle.y + rectangle.height/2f
        createPoint(x,y)
    }
    
    // calc each vertex of a honeycomb 
    def createPoints = { rectangle->
        def centerPt = centerPoint(rectangle) 
    
        def vertexes = 6
        def radius=Math.min(rectangle.width,rectangle.height)/2.0f
        def angle = 2 * Math.PI/vertexes
    
        def pointList = []
        (0..(vertexes-1)).each{ 
            def x = radius*Math.sin(angle*it)
            def y = radius*Math.cos(angle*it)*(-1)
    
            // translate
            x = x + centerPt.x
            y = y + centerPt.y
    
            pointList.add( createPoint(x,y) ) 
        }
        pointList
    }
}


class HoneycombsMaker {
    def sb
    def awtUtils
    HoneycombsMaker(){
        sb = ''<<''
        awtUtils=new AwtUtils()
    }

    // 指定した点を中心に半径r の(円に入る)六角形を描写 色は c で指定
    void createHoneycomb(Map centerPt, float r, Map c, float scale ){
        def myCreatePoints = { left,top,right,bottom->
            def x = left
            def y = top
            def w = right -left
            def h = bottom -top
            def points = awtUtils.createPoints( awtUtils.createRectangle( x,y,w,h ) )
            points
        }

        def myR = r * scale
    
        def left   = centerPt.x -myR
        def top    = centerPt.y -myR
        def right  = centerPt.x +myR
        def bottom = centerPt.y +myR
    
        sb << "<g style=\"stroke:rgb(${c.red},${c.green},${c.blue})\" stroke-width=\"1\" fill=\"none\">"
        sb << "<path d=\""
        
        myCreatePoints(left,top,right,bottom).eachWithIndex{ pt,index->
            if( index==0 ) sb << "M${pt.x},${pt.y}"
            else sb << "L${pt.x},${pt.y}"
        }
        sb << ' z"/>' // z ... close path
        sb << '</g>'
    
    
        if( r>5f ){
            def left2   = centerPt.x -r
            def top2    = centerPt.y -r
            def right2  = centerPt.x +r
            def bottom2 = centerPt.y +r

            def c2 = awtUtils.createRandomColor()
            myCreatePoints(left2,top2,right2,bottom2).each { pt->
                sb << createHoneycomb( [x:pt.x,y:pt.y],(r*scale as float),c2, scale)
            }
        }
    }    
}


def canvasW = 640
def canvasH = 640
def scale = 0.32f

def svgUtils = new SvgUtils()
def awtUtils = new AwtUtils()

def paperRect = awtUtils.createRectangle( 0,0,canvasW,canvasH )
def viewBoxRect = awtUtils.createRectangle( 0,0,canvasW,canvasH )

def br = System.getProperty('line.separator')
def sb = ''<<''
sb << svgUtils.createSVGHeader( paperRect,viewBoxRect )
sb << br

def c = awtUtils.createRandomColor()

def maker = new HoneycombsMaker()
maker.createHoneycomb(
    [x:canvasW/2f,y:canvasH/2f],
    (Math.min(canvasW, canvasH)*scale) as Integer,
    c,
    scale )
sb << maker.sb.toString() 
sb << br
sb << svgUtils.createSVGFooter()
new File('result.svg').text = sb.toString()