Эмуляция африканских деревенских фракталов с помощью JavaScript
(Вопрос вдохновлен этим TED Talk - больше информации здесь)
Я разрабатываю плагин jQuery для рисования изображений на холсте таким способом, который предполагает фрактальную компоновку некоторых африканских деревень:
Учитывая список источников изображений, плагин работает так:
- Создайте ортогонально ориентированную, фрактальную структуру, подобную изображению выше.
- Для каждой отдельной области (прямоугольника) в структуре нарисуйте изображение из списка в этой области.
Рисовать каждое изображение достаточно просто, но я застрял на том, как создать структуру. Математика, которая входит в описание фракталов, довольно далеко над моей головой.
В идеале получить фрактальное представление было бы так же просто, как передать размеры холста в функцию, которая возвращала бы список прямоугольников. Каждый прямоугольник будет представлен как:
- Координаты X/Y для верхнего левого угла (относительно холста)
- ширина
- рост
Как я могу реализовать такую функцию?
1 ответ
Существует важный класс фракталов, называемый L-системой. грубо говоря, идея состоит не в том, чтобы думать о всей структуре слишком много, а в том, чтобы думать только об одном крошечном элементе и о том, как прикрепить меньшую версию того же самого к себе.
один замечательный пример - дерево. может быть, до сих пор, когда вы описали бы дерево, вы скажете: "Ну, это дерево. У него есть корень. А из корня растут ветви. Из этого растут листья". но есть другой способ описать дерево по правилам:
сначала определите палку. палка имеет некоторое положение в пространстве, длину и вращается в некотором направлении. Теперь к правилу: возьми палку. прикрепите от 5 до 10 палочек поочередно повернутыми влево и вправо. повторите процедуру для ваших новых палочек. продолжай делать это, пока не устанешь. Невероятно, но в итоге получается что-то похожее на растение.
Различные параметры влияют на внешний вид вашего цифрового оборудования. вращение палки, прикрепленной к другой палочке, может сделать дерево более густым или более узким. положение прикрепленных палочек изменит то, какое растение вы получаете. прикрепление всех "детских палочек" к дальнему концу сделает его похожим на обычное дерево. более равномерное расположение элементов сделает его более похожим на папоротник (как на картинке). и так далее. Самое приятное, что ты не ограничен реальностью.
В любом случае, все это теория. я написал кучу кода и добавил к нему несколько комментариев, но, надеюсь, это довольно простая реализация того, что я объяснил выше, с блоками вместо палочек.
нажмите здесь, чтобы поиграть с jsfiddle этого кода. нажмите Run несколько раз, чтобы увидеть разные результаты.
// 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
является четным числом, и по вертикали, если это нечетное. и так и так и так далее.
Бездонные чудеса возникают из простых правил, повторяющихся без конца. как сказал Бенуа Мандельброт:)
удачи, фракталы классные!
пс. я знаю, что этот код не дает результат, как на вашей картинке. вы, вероятно, потратите часы на то, чтобы настроить условия if, чтобы он выполнял то, что вы хотите, и было бы неплохо создать интерфейс с несколькими ползунками, чтобы экспериментировать с различными настройками. или, может быть, вы хотите разделить на три, а не на одну или две коробки иногда. возможности слишком бесконечны для ответа, который уже получил довольно долго:)