用JavaScript模拟非洲村庄分形

时间:2014-07-24 22:07:02

标签: javascript jquery math fractals

(受TED Talk启发的问题 - 更多信息here

我正在开发一个jQuery插件,用于在画布上绘制图像,这样可以显示某些非洲村庄的分形布局:

African village

给定一个图像源列表,该插件的工作方式如下:

  1. 生成类似于上图的正交取向的分形结构。
  2. 对于结构中的每个离散区域(矩形),从该区域的列表中绘制图像。
  3. 绘制每个图像很容易,但我仍然坚持如何生成结构。描述分形的数学知识远远超出我的想象。

    理想情况下,获取分形表示就像将画布尺寸传递给函数一样简单,该函数将返回矩形列表。每个矩形表示为:

    • 左上角的X / Y坐标(相对于画布)
    • 宽度
    • 高度

    我该如何实现这样的功能?

1 个答案:

答案 0 :(得分:1)

有一类重要的分形称为L-system。大致的想法是不要过多地考虑整个结构,而是只考虑其中的一小部分,以及如何将同一事物的较小版本附加到自身。

一个很好的例子是一棵树。也许直到现在,当你描述一棵树时,你会说“好吧,它是一棵树。它有一根树根。从根部开始,树枝长出来。然后树叶长出来”。但是还有另一种按规则描述树的方法:

首先定义一根棍子。一根棍子在空间中有一些位置,一个长度并在某个方向上旋转。现在规则:拿一根棍子。连接5到10根木棍交替向左和向右旋转。重复你的新棍棒的程序。继续这样做,直到你累了。令人难以置信的是,你最终得到的东西看起来像某种植物。

Fractal weeds example from wikipedia

不同的参数会影响数字工厂的视觉外观。附着在另一根棍子上的棍子的旋转可能使树看起来更浓密或更窄。附着的枝条的位置将改变你正在获得的植物种类。将所有“儿童棍子”连接到远端将使它看起来像一棵普通的树。将东西间隔得更均匀会使它看起来更像蕨类植物(如图所示)。等等。好的是你不受现实的限制。

无论如何,所有这些都是理论。我已经写了一堆代码并添加了一些注释,但希望我用上面用盒子而不是棒子来解释我所说的内容。

click here to play with a jsfiddle of this code。命中运行几次,看看它产生的不同结果。

// a ton of colors. always handy! 
var colors = ["AliceBlue","AntiqueWhite","Aqua","Aquamarine","Azure","Beige","Bisque","Black","BlanchedAlmond","Blue","BlueViolet","Brown","BurlyWood","CadetBlue","Chartreuse","Chocolate","Coral","CornflowerBlue","Cornsilk","Crimson","Cyan","DarkBlue","DarkCyan","DarkGoldenRod","DarkGray","DarkGreen","DarkKhaki","DarkMagenta","DarkOliveGreen","DarkOrange","DarkOrchid","DarkRed","DarkSalmon","DarkSeaGreen","DarkSlateBlue","DarkSlateGray","DarkTurquoise","DarkViolet"]; 

// store base structure here
// in the end this will contain a nested representation of your village
base = {
    x: 0, 
    y: 0, 
    width: 400, 
    height: 400, 
    children: []
}; 

// and a flat structure here, that's always handy too 
var boxes = [base];  

// add some children to the base recursively. 
addChildren( base, 0 ); 

// now create a div for each
for( var i in boxes ){
    var box = boxes[i]; 
    var el = document.createElement("div"); 
    el.className = "box"; 
    el.style.left = box.x + "px"; 
    el.style.top = box.y + "px"; 
    el.style.width = box.width + "px"; 
    el.style.height = box.height + "px"; 
    el.style.backgroundColor = colors[i%colors.length]; 
    document.body.appendChild( el ); 
}


// randomly add children to a box recursively
function addChildren( box, level ){
    // maybe... split vertically? (two next to each other)
    if( Math.random() < 0.5 ){
        // maybe... nest further in the top? 
        if( Math.random() < 1-level/10.0 ){
            box.children.push( {
                x: box.x, 
                y: box.y, 
                width: box.width/2, 
                height: box.height, 
                children: []
            } ); 
        }
        // maybe bottom too? 
        if( Math.random() < 1-level/10.0 ){
            box.children.push( {
                x: box.x + box.width/2, 
                y: box.y, 
                width: box.width/2, 
                height: box.height, 
                children: []
            } ); 
        }
    }
    // ah. maybe we split horizontally instead
    else{
        // maybe... nest further in the top? 
        if( Math.random() < 1-level/10.0 ){
            box.children.push( {
                x: box.x, 
                y: box.y, 
                width: box.width, 
                height: box.height/2, 
                children: []
            } ); 
        }
        // maybe bottom too? 
        if( Math.random() < 1-level/10.0 ){
            box.children.push( {
                x: box.x, 
                y: box.y + box.height/2, 
                width: box.width, 
                height: box.height/2, 
                children: []
            } ); 
        }
    }

    // also add all the children to our 
    // flat list of boxes
    for( var i in box.children ){
        boxes.push( box.children[i] ); 
    }

    // unless we reach level 5 subdivide further! 
    if( level < 5 ){

        for( var i in box.children ){
            // nest deeper! 
            addChildren( box.children[i], level+1 ); 
        }
    } 
}

addChildren函数是神奇发生的地方。它随机决定是否将盒子水平或垂直分成两部分。然后它随机决定是向顶部/左侧和/或底部/右侧添加一个框。这是您需要进行一些修改并使用不同标准的地方。例如,如果在level为偶数时始终水平分割,则可能会获得有趣的结果,如果是奇数则垂直分割。等等等等。

无底的奇迹源于简单的规则,不断重复。 正如benoit mandelbrot所说:)

祝你好运,分形很棒!


PS。我知道这段代码不会产生你图片中的结果。你可能花了几个小时来调整if条件,让它做你想做的事情,并且让自己成为一个带有几个滑块的接口来试验不同的设置可能是一个好主意。或者你有时想分成三个而不是一个或两个盒子。对于已经有很长时间的答案,可能性太无穷无尽了:)