30

Я интересуюсь компьютерной графикой и хотел бы провести своего рода конкурс.

Я хотел бы узнать о новых (для себя) способах генерации различных фракталов или других изображений, полученных по достаточно простой формуле.

То есть критерием соревнования является, - использование простой базовой формулы для получения интересных картинок.


Например есть такая реализация цикла по всем пикселям на картинке:

let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height

let formula = (x, y, cx, cy, m) => { return [x/w+cx/w, y/h+cy/h, 0] }

canvas.onmousemove = e => { var img = c.getImageData(0, 0, w, h) for(var x = 0; x<w; x++) { for(var y = 0; y<h; y++) { let value = formula(x, y, e.x, e.y) let offset = (yw + x)4
img.data[offset] = value[0]255 img.data[offset + 1] = value[1]255 img.data[offset + 2] = value[2]*255 img.data[offset + 3] = 255 } } c.putImageData(img, 0, 0) }

canvas.onmousemove({x: 456, y: 123})

<canvas width="600" height="175" id="canvas"/>

Необходимо реализовать функцию formula для получения "интересного" изображения, дополнительными аргументами выступают координаты мыши

Язык - любой, но желательно js, из-за возможности онлайн визуализации.

PS: рекурсивные методы мне менее интересны, особенно если рекурсия не хвостовая, так какпортировать это на glsl будет сложно если не невозможно.

P.P.S. для привлечения более широкой аудитории мной был выбран cpu и код цикла по картинке я написал для него, однако если Вам угодно, мне больше импонирует webgl, по этому ниже сниппет, где цикл по всем пикселям делает видеокарта, когда я рисую один треугольник, закрывающий весь экран, а функция formula - это фрагментный шейдер :)

let gl = canvas.getContext('webgl');

gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, 3, -1, -1, 3, -1]), gl.STATIC_DRAW);

let pid = gl.createProgram(); shader('vertex', gl.VERTEX_SHADER); shader('fragment', gl.FRAGMENT_SHADER); gl.linkProgram(pid); gl.useProgram(pid);

let coords = gl.getAttribLocation(pid, "coords"); gl.vertexAttribPointer(coords, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(coords);

let mouse = gl.getUniformLocation(pid, 'mouse'); let resolution = gl.getUniformLocation(pid, 'resolution'); gl.uniform2f(resolution, gl.drawingBufferWidth, gl.drawingBufferHeight);

let changeCenter = e => { e = e.touches ? e.touches[0] : e; gl.uniform2f(mouse, e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop); draw(); }

window.addEventListener('mousemove', changeCenter); window.addEventListener('touchmove', changeCenter);

draw();

function draw() { gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); gl.clearColor(0, 0, 0, 0); gl.drawArrays(gl.TRIANGLES, 0, 3); }

function shader(src, type) { let sid = gl.createShader(type); gl.shaderSource(sid, document.querySelector(script[type="glsl/${src}"]).textContent); gl.compileShader(sid); var message = gl.getShaderInfoLog(sid); gl.attachShader(pid, sid); if (message.length > 0) { console.log(src.split('\n').map(function (str, i) { return ("" + (1 + i)).padStart(4, "0") + ": " + str }).join('\n')); throw message; } }

<canvas width="600" height="175" id="canvas"/>

<script type="glsl/vertex">

attribute vec2 coords;

void main(void) {
  gl_Position = vec4(coords.xy, 0.0, 1.0);
}

</script>

<script type="glsl/fragment">

precision highp float;

uniform vec2 mouse;
uniform vec2 resolution;

void main(void) {
    vec2 m = mouse/resolution;
    vec2 p = gl_FragCoord.xy/resolution - 0.5;
    gl_FragColor = vec4(p, m); 
}

</script>

9 Answers9

18

Предлагаю Вашему вниманию Суперформулу

Суперформула является обобщением суперэллипса и впервые была выведена Йоханом Гиелисом в 2003 году. Гиелис предположил использовать формулу для описания сложных форм и кривых, которые встречаются в природе.

В полярной системе координат, с введите сюда описание изображения радиусом, и введите сюда описание изображения углом, суперформула выглядит так:

введите сюда описание изображения

Выбирая различные значения параметров введите сюда описание изображения, получаются различные формы.

введите сюда описание изображения

let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height

// функция возвращает расстояние в полярной системе координат для угла phi, идущего первым аргументом function superformula(phi, m, n1, a, b, n2, n3) { with (Math) { m = m*phi/4; a = pow(abs(cos(m))/a, n2); b = pow(abs(sin(m))/b, n3); return pow(a + b, -1/n1); } }

let formula = (x, y, cx, cy) => { x = (2x-w)/w(3-cy/h); y = (2y-h)/w(3-cy/h); let a = Math.atan2(y, x) - cx/wMath.PI; let d = superformula(a, t.m, t.n1, t.a, t.b, t.n2, t.n3); let l = Math.sqrt(xx + yy); let c = Math.min(d-l)10.; return [c, c, c] }

let types = { asterisk: {m: 12, n1: .3, n2: 0, n3: 10, a: 1, b: 1}, bean: {m: 2, n1: 1, n2: 4, n3: 8, a: 1, b: 1}, butterfly: {m: 3, n1: 1, n2: 6, n3: 2, a: .6, b: 1}, circle: {m: 4, n1: 2, n2: 2, n3: 2, a: 1, b: 1}, clover: {m: 6, n1: .3, n2: 0, n3: 10, a: 1, b: 1}, cloverFour: {m: 8, n1: 10, n2: -1, n3: -8, a: 1, b: 1}, cross: {m: 8, n1: 1.3, n2: .01, n3: 8, a: 1, b: 1}, diamond: {m: 4, n1: 1, n2: 1, n3: 1, a: 1, b: 1}, drop: {m: 1, n1: .5, n2: .5, n3: .5, a: 1, b: 1}, gear: {m: 19, n1: 100, n2: 50, n3: 50, a: 1, b: 1}, heart: {m: 1, n1: .8, n2: 1, n3: -8, a: 1, b: .18}, heptagon: {m: 7, n1: 1000, n2: 400, n3: 400, a: 1, b: 1}, hexagon: {m: 6, n1: 1000, n2: 400, n3: 400, a: 1, b: 1}, malteseCross: {m: 8, n1: .9, n2: .1, n3: 100, a: 1, b: 1}, pentagon: {m: 5, n1: 1000, n2: 600, n3: 600, a: 1, b: 1}, rectangle: {m: 4, n1: 100, n2: 100, n3: 100, a: 2, b: 1}, roundedStar: {m: 5, n1: 2, n2: 7, n3: 7, a: 1, b: 1}, square: {m: 4, n1: 100, n2: 100, n3: 100, a: 1, b: 1}, star: {m: 5, n1: 30, n2: 100, n3: 100, a: 1, b: 1}, triangle: {m: 3, n1: 100, n2: 200, n3: 200, a: 1, b: 1} };

let t = Object.values(types)[0];

let draw = e => { var img = c.getImageData(0, 0, w, h) for(var x = 0; x<w; x++) { for(var y = 0; y<h; y++) { let value = formula(x, y, e.x, e.y) let offset = (yw + x)4
img.data[offset] = value[0]255 img.data[offset + 1] = value[1]255 img.data[offset + 2] = value[2]*255 img.data[offset + 3] = 255 } } c.putImageData(img, 0, 0) }

draw({x:100, y:100})

canvas.onmousemove = e => { draw(e); }

canvas.onclick = e => { let vals = Object.values(types); let i = Math.floor(Math.random()*vals.length); t = vals[i]; document.querySelector('span').textContent = 'click to change: ' + Object.keys(types)[i] draw(e) }

<body style="margin:0;user-select:none"><canvas width="175" height="175" id="canvas"></canvas><br><span>click to change: asterisk</span></body>
15

Самое простое что известно мне, помимо фракталов в комплексной плоскости - это фрактал kali, назван в честь опубликовавшего эту формулу на fractalforums

формула в glsl для него вообще простая:

vec2 q = vec2(x, y);
for(var i=0; i<10; i++)
  q = abs(q)/dot(q,q) - vec2(cx, cy);

на js чуть посложнее из-за отсутствия операций над векторами, но все равно очень простая:

let formula = (x, y, cx, cy, m) => {
    x = (2*x-w)/w;
    y = (2*y-h)/w;
    for (var i=0; i<10; i++) {
        x = Math.abs(x)
        y = Math.abs(y)
        m = x*x + y*y
        x = x/m - cx/w
        y = y/m - cy/h
    }
    return [x, y, Math.sqrt(x*x+y*y)/2.]
}

Вот результат для cpu версии, мышка меняет переменные в формуле для получения другого изображения:

введите сюда описание изображения

let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height

let formula = (x, y, cx, cy, m) => { x = (2x-w)/w; y = (2y-h)/w; for (var i=0; i<10; i++) { x = Math.abs(x) y = Math.abs(y) m = xx + yy x = x/m - cx/w y = y/m - cy/h } return [x, y, Math.sqrt(xx+yy)/2.] }

canvas.onmousemove = e => { var img = c.getImageData(0, 0, w, h) for(var x = 0; x<w; x++) { for(var y = 0; y<h; y++) { let value = formula(x, y, e.x, e.y) let offset = (yw + x)4
img.data[offset] = value[0]255 img.data[offset + 1] = value[1]255 img.data[offset + 2] = value[2]*255 img.data[offset + 3] = 255 } } c.putImageData(img, 0, 0) }

canvas.onmousemove({x: 456, y: 123})

<canvas width="600" height="175" id="canvas"/>

GPU вариация и другая палитра

введите сюда описание изображения

let gl = canvas.getContext('webgl');

gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, 3, -1, -1, 3, -1]), gl.STATIC_DRAW);

let pid = gl.createProgram(); shader('vertex', gl.VERTEX_SHADER); shader('fragment', gl.FRAGMENT_SHADER); gl.linkProgram(pid); gl.useProgram(pid);

let coords = gl.getAttribLocation(pid, "coords"); gl.vertexAttribPointer(coords, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(coords);

let mouse = gl.getUniformLocation(pid, 'mouse'); let resolution = gl.getUniformLocation(pid, 'resolution'); gl.uniform2f(resolution, gl.drawingBufferWidth, gl.drawingBufferHeight);

let changeCenter = e => { e = e.touches ? e.touches[0] : e; gl.uniform2f(mouse, e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop); draw(); }

window.addEventListener('mousemove', changeCenter); window.addEventListener('touchmove', changeCenter);

draw();

function draw() { gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); gl.clearColor(0, 0, 0, 0); gl.drawArrays(gl.TRIANGLES, 0, 3); }

function shader(src, type) { let sid = gl.createShader(type); gl.shaderSource(sid, document.querySelector(script[type="glsl/${src}"]).textContent); gl.compileShader(sid); var message = gl.getShaderInfoLog(sid); gl.attachShader(pid, sid); if (message.length > 0) { console.log(src.split('\n').map(function (str, i) { return ("" + (1 + i)).padStart(4, "0") + ": " + str }).join('\n')); throw message; } }

<canvas width="600" height="175" id="canvas"/>

<script type="glsl/vertex">

attribute vec2 coords;

void main(void) {
  gl_Position = vec4(coords.xy, 0.0, 1.0);
}

</script>

<script type="glsl/fragment">

precision highp float;

uniform vec2 mouse;
uniform vec2 resolution;

void main(void) {

    vec2 m = mouse/resolution;
    vec2 p = gl_FragCoord.xy;

    // вот собственно сама формула реализована в этих 3 строчках
    vec2 q = (p + p - resolution) / resolution.y;
    for(int i = 0; i < 10; i++)
         q = abs(q)/dot(q,q) -  m;


    gl_FragColor = vec4(q, q.x/q.y, 1.0);
}

</script>

Еще одна вариация фрактала

let gl = canvas.getContext('webgl');

gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, 3, -1, -1, 3, -1]), gl.STATIC_DRAW);

let pid = gl.createProgram(); shader('vertex', gl.VERTEX_SHADER); shader('fragment', gl.FRAGMENT_SHADER); gl.linkProgram(pid); gl.useProgram(pid);

let coords = gl.getAttribLocation(pid, "coords"); gl.vertexAttribPointer(coords, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(coords);

let mouse = gl.getUniformLocation(pid, 'mouse'); let resolution = gl.getUniformLocation(pid, 'resolution'); gl.uniform2f(resolution, gl.drawingBufferWidth, gl.drawingBufferHeight);

let changeCenter = e => { e = e.touches ? e.touches[0] : e; gl.uniform2f(mouse, e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop); draw(); }

window.addEventListener('mousemove', changeCenter); window.addEventListener('touchmove', changeCenter);

draw();

function draw() { gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); gl.clearColor(0, 0, 0, 0); gl.drawArrays(gl.TRIANGLES, 0, 3); }

function shader(src, type) { let sid = gl.createShader(type); gl.shaderSource(sid, document.querySelector(script[type="glsl/${src}"]).textContent); gl.compileShader(sid); var message = gl.getShaderInfoLog(sid); gl.attachShader(pid, sid); if (message.length > 0) { console.log(src.split('\n').map(function (str, i) { return ("" + (1 + i)).padStart(4, "0") + ": " + str }).join('\n')); throw message; } }

<canvas width="600" height="175" id="canvas"/>

<script type="glsl/vertex">

attribute vec2 coords;

void main(void) {
  gl_Position = vec4(coords.xy, 0.0, 1.0);
}

</script>

<script type="glsl/fragment">

precision highp float;

uniform vec2 mouse;
uniform vec2 resolution;

void main(void) {

    vec2 m = mouse/resolution;
    vec2 p = gl_FragCoord.xy;

    // вот собственно сама формула реализована в этих 3 строчках
    vec2 q = (p + p - resolution) / resolution.y/m.y;
    for(int i = 0; i < 11; i++)
         q = -abs(q)/dot(q,q) + m;


    gl_FragColor = vec4(q, 1.-dot(q,q), 1.0);
}

</script>
13

Множество Жюлиа (Julia set)

Формула для него выглядит вот так:

z где z это комплексное число:

В этом примере значение c зависит от координат мышки, что позволяет одновременно наблюдать великое множество различных изображений множества Жюлиа

введите сюда описание изображения

введите сюда описание изображения

введите сюда описание изображения

Все эти изображения получены при помощи нижеприведенного сниппета с минимальными изменениями

let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height

let formula = (x, y, cx, cy, m) => { let z = [(2y-h)/w1.5,(2x-w)/w1.5];
for (var i = 0; i < 32; ++i) {
z = [ z[0] * z[0] - z[1] * z[1] + cy/h, 2. * z[0] * z[1] + cx/w]; if (z[0]z[0] + z[1]z[1] > 4.) return i; } return 0 }

canvas.onmousemove = e => { var img = c.getImageData(0, 0, w, h) for(var x = 0; x<w; x++) { for(var y = 0; y<h; y++) { let v = formula(x, y, e.x, e.y) let o = (yw + x)4
img.data[o++] = Math.sin(v/5)255 img.data[o++] = Math.sin(v/6)255 img.data[o++] = Math.sin(v/7)*255 img.data[o++] = 255 } } c.putImageData(img, 0, 0) }

canvas.onmousemove({x: 111, y: 123})

<canvas width="400" height="400" id="canvas"/>

По очень похожей формуле получается обобщение всех множеств Жюлиа - Множество Мандельброта (Mandelbrot set)

введите сюда описание изображения

9

Пример с бассейном Ньютона.

Области подкрашиваются в соответствии с близостью к корням уравнения z3-1 = 0

class Complex {
  constructor({algebraic,trigonometric} = {}) {
    if (algebraic) {
      this.initAlgebraic(algebraic);
    } else if (trigonometric) {
      this.initTrigonometric(trigonometric)
    } else {
      throw new Error('Invalid arguments');
    }
  }
  static fromReal(r) {
    return Complex.fromAlgebraic(r, 0);
  }
  static fromAlgebraic(r, i) {
    return new Complex({algebraic: {r,i}});
  }
  static fromTrigonometric(modulus, arg) {
    return new Complex({trigonometric: {modulus,arg}});
  }

initAlgebraic({r,i}) { this.r = r; this.i = i; this.modulus = Math.sqrt(r * r + i * i); this.arg = Math.atan2(i, r) } initTrigonometric({modulus,arg}) { this.modulus = modulus, this.arg = arg; this.r = this.modulus * Math.cos(arg); this.i = this.modulus * Math.sin(arg); } pow(exp) { return Complex.fromTrigonometric(Math.pow(this.modulus, exp), exp * this.arg); } add(c) { return Complex.fromAlgebraic(this.r + c.r, this.i + c.i); } conjugate(){ return Complex.fromAlgebraic(this.r, -this.i); } sub(c){ return Complex.fromAlgebraic(this.r - c.r, this.i - c.i); } mul(c){ return Complex.fromAlgebraic(this.rc.r-this.ic.i, this.rc.i+this.ic.r); } div(c){ var cConjugate = c.conjugate(); var mulToConjugate = this.mul(cConjugate); var divider = c.mul(cConjugate).r; return Complex.fromAlgebraic(mulToConjugate.r/divider, mulToConjugate.i/divider); } } let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height function formula(x,y, maxIter, max, min){ var z = Complex.fromAlgebraic(x,y); var d = z; for(var i=0;i<maxIter && z.modulus<max && d.modulus>min;i++){ var z1 = z.sub(z.pow(3).add(Complex.fromReal(-1)).div(z.pow(2).mul(Complex.fromReal(3)))); //var z1 = z.sub(z.pow(5).add(Complex.fromReal(-1)).div(z.pow(4).mul(Complex.fromReal(5)))); var z2 = z1.sub(z); d = Complex.fromAlgebraic(Math.abs(z2.r),Math.abs(z2.i)); z = z1; } return [i, z]; } var input = document.getElementById('coef'); input.addEventListener('change',function(){ draw(this.value); }); var roots = [ Complex.fromReal(1), Complex.fromAlgebraic(-1, Math.sqrt(3)/2), Complex.fromAlgebraic(-1, -Math.sqrt(3)/2), ] var maxDistance = input.max; function draw(distance){ var xc = w/2; var yc = h/2; var coef = 0.0015 + distance * ((0.015-0.0015)/maxDistance); var img = c.getImageData(0, 0, w, h); var aaa = 0; for(var y = -h/2; y<h/2; y++) { for(var x = -w/2; x<w/2; x++) { let [value,root] = formula(xcoef, ycoef, 50,1e6, 1e-6);

    let offset = ((y+yc)*w + x+xc)*4 
    img.data[offset]     = (value*roots[0].sub(root).modulus*7)%255;
    img.data[offset + 1] = (value*roots[1].sub(root).modulus*5)%255;
    img.data[offset + 2] = (value*roots[2].sub(root).modulus*11)%255;
    img.data[offset + 3] = 255
  }
}
c.putImageData(img, 0, 0) ;

}; draw(100);

<canvas width="400" height="400" id="canvas"></canvas>
<input type="range" min="0" max="200" id="coef" value="100">

Для урaвнения z5-1 = 0

class Complex {
  constructor({algebraic,trigonometric} = {}) {
    if (algebraic) {
      this.initAlgebraic(algebraic);
    } else if (trigonometric) {
      this.initTrigonometric(trigonometric)
    } else {
      throw new Error('Invalid arguments');
    }
  }
  static fromReal(r) {
    return Complex.fromAlgebraic(r, 0);
  }
  static fromAlgebraic(r, i) {
    return new Complex({algebraic: {r,i}});
  }
  static fromTrigonometric(modulus, arg) {
    return new Complex({trigonometric: {modulus,arg}});
  }

initAlgebraic({r,i}) { this.r = r; this.i = i; this.modulus = Math.sqrt(r * r + i * i); this.arg = Math.atan2(i, r) } initTrigonometric({modulus,arg}) { this.modulus = modulus, this.arg = arg; this.r = this.modulus * Math.cos(arg); this.i = this.modulus * Math.sin(arg); } pow(exp) { return Complex.fromTrigonometric(Math.pow(this.modulus, exp), exp * this.arg); } add(c) { return Complex.fromAlgebraic(this.r + c.r, this.i + c.i); } conjugate(){ return Complex.fromAlgebraic(this.r, -this.i); } sub(c){ return Complex.fromAlgebraic(this.r - c.r, this.i - c.i); } mul(c){ return Complex.fromAlgebraic(this.rc.r-this.ic.i, this.rc.i+this.ic.r); } div(c){ var cConjugate = c.conjugate(); var mulToConjugate = this.mul(cConjugate); var divider = c.mul(cConjugate).r; return Complex.fromAlgebraic(mulToConjugate.r/divider, mulToConjugate.i/divider); } } let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height function formula(x,y, maxIter, max, min){ var z = Complex.fromAlgebraic(x,y); var d = z; for(var i=0;i<maxIter && z.modulus<max && d.modulus>min;i++){ //var z1 = z.sub(z.pow(3).add(Complex.fromReal(-1)).div(z.pow(2).mul(Complex.fromReal(3)))); var z1 = z.sub(z.pow(5).add(Complex.fromReal(-1)).div(z.pow(4).mul(Complex.fromReal(5)))); var z2 = z1.sub(z); d = Complex.fromAlgebraic(Math.abs(z2.r),Math.abs(z2.i)); z = z1; } return [i, z]; } var input = document.getElementById('coef'); input.addEventListener('change',function(){ draw(this.value); });

var maxDistance = input.max; function draw(distance){ var xc = w/2; var yc = h/2; var coef = 0.0015 + distance * ((0.015-0.0015)/maxDistance); var img = c.getImageData(0, 0, w, h); var aaa = 0; for(var y = -h/2; y<h/2; y++) { for(var x = -w/2; x<w/2; x++) { let [value,root] = formula(xcoef, ycoef, 50,1e6, 1e-6);

    let offset = ((y+yc)*w + x+xc)*4 
    img.data[offset]     = (value*7)%255;
    img.data[offset + 1] = (value*5)%255;
    img.data[offset + 2] = (value*11)%255;
    img.data[offset + 3] = 255
  }
}
c.putImageData(img, 0, 0) ;

}; draw(100);

<canvas width="400" height="400" id="canvas"></canvas>
<input type="range" min="0" max="400" id="coef" value="100">
Grundy
  • 81,538
  • 3
    я выбрал этот ответ, потому что этот фрактал еще не реализовывал и пока не думал даже об этом =) – Stranger in the Q Jun 04 '19 at 11:19
7

Сетка Аполлона.

введите сюда описание изображения

let c = canvas.getContext('2d'),
  w = canvas.width,
  h = canvas.height;

var x = 0.2, y = 0.3, a = 0, b = 0, r = Math.sqrt(3), points = Array(1000), counter = 0;

(function starter() { draw(); requestAnimationFrame(starter); }())

function sqr(x) { return x * x; }

function putPixels(x, y) { var img = c.getImageData(0, 0, w, h) let offset = (y * w + x) * 4; img.data[offset] = 255; img.data[offset + 1] = 0; img.data[offset + 2] = 0; img.data[offset + 3] = 255; c.putImageData(img, 0, 0) }

function draw() { a = Math.random(); var div = sqr(1 + r - x) + sqr(y); var a0 = 3 * (1 + r - x) / (div) - (1 + r) / (2 + r); var b0 = 3 * y / (div); if (a <= 1 / 3 && a >= 0) { x1 = a0; y1 = b0; } else { var a1 = -1 / 2; var b1 = r / 2; var a2 = -1 / 2; var b2 = -r / 2; var d =sqr(a0) + sqr(b0); var f1x = a0 / (d); var f1y = -b0 / (d);

if (a &lt;= 2 / 3 &amp;&amp; a &gt; 1 / 3) {
  x1 = f1x * a1 - f1y * b1;
  y1 = f1x * b1 + f1y * a1;
}
if (a &lt;= 3 / 3 &amp;&amp; a &gt; 2 / 3) {
  x1 = f1x * a2 - f1y * b2;
  y1 = f1x * b2 + f1y * a2;
}

} x = x1; y = y1; putPixels(320 + x * 50 | 0, 240 + y * 50 | 0); }

<canvas width="650" height="500" id="canvas" />
Grundy
  • 81,538
  • Круто, спасибо, за объяснениями в гугл или у Вас есть какая-то ссылочка? – Stranger in the Q May 29 '19 at 08:21
  • @StrangerintheQ. добавил ссылку на википедию. Код в принципе переписан с pascal отсюда. Но очень долго рисуется вероятно из-за того что random иногда в себя попадает. А через построение кругов, а не поточечно не осилил :-) – Grundy May 29 '19 at 08:22
  • визуально похоже на метод хаоса для построения треугольника серпинского : https://elementy.ru/posters/fractals/Sierpinski – Stranger in the Q May 29 '19 at 08:28
  • На плоскости зафиксирован правильный треугольник A1A2A3. Отмечают любую начальную точку B0. Затем случайным образом выбирают одну из трех вершин треугольника и отмечают точку B1 — середину отрезка с концами в этой вершине и в B0 (на рисунке справа случайно выбралась вершина A1). То же самое повторяют с точкой B1, чтобы получить B2. Потом получают точки B3, B4, и т. д. Важно, чтобы точка «прыгала» случайным образом, независимо от того, что было выбрано в предыдущие шаги. Удивительно, что если отмечать точки из последовательности Bi, то вскоре начнет проступать треугольник Серпинского. – Stranger in the Q May 29 '19 at 08:28
  • @StrangerintheQ, да, я так понял они по одному принципу просто у Серпинского треугольники, а тут окружности – Grundy May 29 '19 at 08:31
  • Метод конечно странный, так получилось что тоже совсем недавно о нем узнал, не о фрактале а о методе. – Stranger in the Q May 29 '19 at 08:33
  • попробовал исключать повторы - не сильно помогло – Stranger in the Q May 29 '19 at 08:47
  • @StrangerintheQ, я тоже пробовал, мне кажется там еще дольше получается :-) – Grundy May 29 '19 at 09:04
  • удаленный ответ - я не понял в чем отличие но оно есть, это множество жюлиа, только степень четвератя а не вторая на каждой итерации https://tinyurl.com/y3pewru6 – Stranger in the Q May 29 '19 at 10:20
6

При возведении комплексного аргумента в 4 степень можно получить фрактал по форме напоминающий живой организм, такие фракталы называют биоморфами.

class Complex {
  constructor({algebraic,trigonometric} = {}) {
    if (algebraic) {
      this.initAlgebraic(algebraic);
    } else if (trigonometric) {
      this.initTrigonometric(trigonometric)
    } else {
      throw new Error('Invalid arguments');
    }
  }
  static fromReal(r) {
    return Complex.fromAlgebraic(r, 0);
  }
  static fromAlgebraic(r, i) {
    return new Complex({algebraic: {r,i}});
  }
  static fromTrigonometric(modulus, arg) {
    return new Complex({trigonometric: {modulus,arg}});
  }

initAlgebraic({r,i}) { this.r = r; this.i = i; this.modulus = Math.sqrt(r * r + i * i); this.arg = Math.atan2(i, r) } initTrigonometric({modulus,arg}) { this.modulus = modulus, this.arg = arg; this.r = this.modulus * Math.cos(arg); this.i = this.modulus * Math.sin(arg); } pow(exp) { return Complex.fromTrigonometric(Math.pow(this.modulus, exp), exp * this.arg); } add(c) { return Complex.fromAlgebraic(this.r + c.r, this.i + c.i); } } let c = canvas.getContext('2d'), w = canvas.width, h = canvas.height function formula(x,y,cx,cy, maxIter){ var z = Complex.fromAlgebraic(x,y); var c = Complex.fromAlgebraic(cx,cy); for(var i=0;i<maxIter || z.modulus<50;i++){ z = z.pow(4).add(c); if(Math.abs(z.r) > 10 || Math.abs(z.i) > 1000) { return i; } } return 0; } canvas.onmousemove = e => { var xc = w/2; var yc = h/2; var img = c.getImageData(0, 0, w, h) for(var y = -h/2; y<h/2; y++) { for(var x = -w/2; x<w/2; x++) { let value = formula(x0.01, y0.01, e.x/399, 1+e.y/398, 70) let offset = ((y+yc)w + x+xc)4 img.data[offset] = Math.sin(value)255; img.data[offset + 1] = Math.cos(value)255; img.data[offset + 2] = Math.random()value255; img.data[offset + 3] = 255 } } c.putImageData(img, 0, 0) } console.log(1); canvas.onmousemove({x: 111, y: 123})

<canvas width="400" height="400" id="canvas"/>
Grundy
  • 81,538
  • спасибо, это вариация множества Жюлиа, попробую попозже подправить палитру – Stranger in the Q May 29 '19 at 11:59
  • @StrangerintheQ, на сколько я понял Жюлиа - это только вторая степень, но могу ошибаться – Grundy May 29 '19 at 12:00
  • множество Жюлиа образуется когда к базовой формуле на каждой итерации прибавляется какая-то константа, а не значение текущего пикселя. – Stranger in the Q May 29 '19 at 12:02
  • 2 степерь это множество жюлиа для множества мандельброта, для любого фрактала в комплексном пространстве, даже трехмерного есть соответствующее бесконечное множество множеств жюлиа – Stranger in the Q May 29 '19 at 12:03
  • https://strangerintheq.github.io/mandelbulb-julia.html – Stranger in the Q May 29 '19 at 12:04
  • по ссылке - множество жюлиа для mandelbulb, сама бульба почему-то не работает, сломал – Stranger in the Q May 29 '19 at 12:06
  • https://codepen.io/strangerintheq/pen/yWxqqB вот Ваш фрактал, я там немного подправил код выхода из цикла и добавил c1, попробуйте его добавить в итерации а не c и появится обобщение всех этих множеств жюлиа - множество мандельброта только не с 1 бульбой а с 3мя =) – Stranger in the Q May 29 '19 at 12:40
  • дальнейшее возведение в степень добавляет еще малые пузыри – Stranger in the Q May 29 '19 at 12:41
  • выход из цикла можно проверять по условию что точка отлетела от центра больше чем пороговое расстояние 2, по теореме пифагора z.r*z*r+z.i*z.i > 2*2 – Stranger in the Q May 29 '19 at 12:44
  • @StrangerintheQ, z.r*z*r+z.i*z.i - Это модуль комплексного числа :-) он в отдельном поле у меня хранится :-) – Grundy May 29 '19 at 13:20
  • Добавил ответ с горящим кораблём, потом сделаю интерактивную версию чтобы можно было передвигаться вглубь – Stranger in the Q May 29 '19 at 13:27
4

В продолжение темы фракталов в комплексной плоскости - вариация множества Мандельброта, но в каждой итерации компоненты комплексного z берутся по модулю - имеет название Burning Ship, за визуальную похожесть деталей фрактала на корабль. Интересен очень высокой вариативностью геометрических узоров при большом увеличении.

   ...
z[0] = Math.abs(z[0]);
z[1] = Math.abs(z[1]);
   ...

введите сюда описание изображения

введите сюда описание изображения

введите сюда описание изображения

Если приближаться в район мачт кораблей можно найти интересные участки

введите сюда описание изображения

введите сюда описание изображения

let c = canvas.getContext('2d'), w=canvas.width, h=canvas.height, cx=0, cy=0, z=0.25;

let formula = (x, y, cx, cy, it) => { let z = [0,0];
for (var i = 0; i < it; ++i) {
z[0] = Math.abs(z[0]); z[1] = Math.abs(z[1]); z = [ z[0] * z[0] - z[1] * z[1] - x+cx, 2. * z[0] * z[1] + y-cy]; if (z[0]z[0] + z[1]z[1] > 4.1) return i; } return 0 }

let p, ct; canvas.addEventListener('mousedown',function(e){ p = {x:e.pageX, y:e.pageY}; ct = {x:cx, y:cy}; })

canvas.addEventListener('mousemove',function(e){ if (!p) return; cx = ct.x + (e.pageX - p.x)/w/z; cy = ct.y + (e.pageY - p.y)/h/z; render() })

canvas.addEventListener('mouseup',function(e){ p = null })

canvas.addEventListener('wheel',function(e){ z = 1 + Math.sign(e.wheelDeltaY)0.1 e.preventDefault(); render() }) render()

function render() { let red = r.value; let green = g.value; let blue = b.value; let it = iter.value; var img = c.getImageData(0, 0, w, h) for(var x = 0; x<w; x++) { for(var y = 0; y<h; y++) { let v = formula((x/w-0.5)/z, (y/h-0.5)/z,cx,cy,it) let o = (yw + x)4
img.data[o++] = Math.sin(v/red)255 img.data[o++] = Math.sin(v/green)255 img.data[o++] = Math.sin(v/blue)*255 img.data[o++] = 255 } } c.putImageData(img, 0, 0) }

<input type="range" id="iter" min=16 max=512 value=32 onchange="render()"/>
<input type="range" id="r" min=1 max=33 value=11 onchange="render()"/>
<input type="range" id="g" min=1 max=33 value=7 onchange="render()" />
<input type="range" id="b" min=1 max=33 value=22 onchange="render()" />
<br>
<canvas width="400" height="400" id="canvas"/>

PS: В сниппете работает колесо мышки и драг, при помощи слайдеров можно поменять кол-во итерация и палитру

  • посмотрел я все примеры и так и не понял куда это всё применять ? какие то без форменные объекты ... – Резидент Казахстана May 29 '19 at 14:11
  • 1
    @MaximLensky не во всем прекрасном есть практический смысл... – Stranger in the Q May 29 '19 at 14:14
  • @MaximLensky посмотрите "Доктора Стренджа" – ThisMan May 29 '19 at 14:14
  • 1
    @MaximLensky если Вам действительно интересно - фракталы и фрактальные алгоритмы нашли много применения в компьютерной графике и геймдеве, - многие природные объекты (облака, деревья, горы, реки) - фракталы, и для их программной генерации хорошо подходят фрактальные алгоримты. но это мало соотносится конкретно с этими изображеними. эти изображения - прекрасны с точки зрения математики т.к. имеют очень простые базовые формулы и генерируют бесконечное множество интересных узоров - своего рода арт объекты – Stranger in the Q May 29 '19 at 14:29
  • @MaximLensky так же существуют например фрактальные алгоритмы сжатия изображений https://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D1%84%D1%80%D0%B0%D0%BA%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE_%D1%81%D0%B6%D0%B0%D1%82%D0%B8%D1%8F – Stranger in the Q May 29 '19 at 14:35
  • 1
    я это понял ...но так же я понял что я далёк от Квадрата Малевича ... и импресионистов ...:)) – Резидент Казахстана May 29 '19 at 14:39
3

Фрактал Ляпунова

Фракталы Ляпунова строятся отображением областей стабильного и хаотического поведения, измеряемых экспонентой Ляпунова, в плоскости a-b для данной периодической последовательности a и b.

Форма зависит от введенной строки включающей символы A и B, а так же от выбранного квадрата в диапазоне [0x4, 0x4]

Приведенный пример с конкретными параметрами также известен как Zircon City.

let c = canvas.getContext('2d'),
  w = canvas.width,
  h = canvas.height
var i = 0;

function formula(a, b, N, S) { var xn = 0.5; var sum = 0; for (var n = 1; n < N; n++) { var rn = S[n % S.length] == 'A' ? a : b; xn = rn * xn * (1 - xn); sum += Math.log2(Math.abs(rn * (1 - 2 * xn)));

if (sum &gt; 10000) return 50;
if (sum &lt; -10000) return -50;

} return sum / N; }

function getRect() { var xmin = +document.getElementById('xmin').value; var xmax = +document.getElementById('xmax').value; var ymin = +document.getElementById('ymin').value; var ymax = +document.getElementById('ymax').value;

return [{ start: Math.min(xmin, xmax), range: Math.abs(xmax - xmin) / w }, { start: Math.min(ymin, ymax), range: Math.abs(ymax - ymin) / h }]; }

function draw() { var img = c.getImageData(0, 0, w, h) var S = document.getElementById('S').value; var [p1, p2] = getRect(); for (var x = 0; x < w; x++) { for (var y = 0; y < h; y++) { let La = formula(p1.start + x * p1.range, p2.start + y * p2.range, 1000, S); let offset = ((h - y) * w + x) * 4 if (La > 0) { img.data[offset] = (La * 25 + 10) % 256; img.data[offset + 1] = (La * 25 + 10) % 216; } else { img.data[offset] = 255 - (-La * 20 + 50) % 256 img.data[offset + 1] = 215 - (-La * 20 + 50) % 216 } img.data[offset + 2] = 0 img.data[offset + 3] = 255

}

} c.putImageData(img, 0, 0) }

draw();

#c {
  display: flex;
}
#settings{
  margin-left:10px;
}
<div id="c">
  <div>
    <canvas width="400" height="400" id="canvas"></canvas>
  </div>
  <div id="settings">
    <label>xmin <input type="number" id="xmin" min="0" max="4" step="0.1" value="2.5"/></label>
    <label>xmax <input type="number" id="xmax" min="0" max="4" step="0.1" value="3.4"/></label><br>
    <label>ymin <input type="number" id="ymin" min="0" max="4" step="0.1" value="3.4"/></label>
    <label>ymax <input type="number" id="ymax" min="0" max="4" step="0.1" value="4"/></label><br>
    <label>Seq <input type="text" id="S" value="BBBBBBAAAAAA"/></label>
    <input type="button" value="apply" onclick="draw()" />
  </div>
</div>
Grundy
  • 81,538
  • спасибо, это очень интересная штука, но её колоризация похоже будет болью, попробую на досуге – Stranger in the Q Jun 04 '19 at 11:20
  • @StrangerintheQ, во всех описаниях: цвет в зависимости от значения экспоненты Ляпунова, но саму зависимость нигде не указывают :) – Grundy Jun 04 '19 at 11:53
1

1)Модификация кривой Леви.

Посмотрите тут новые фракталы, модификация кривой Леви, есть онлайн построение. Формула одна меняется только кол-во углов. Скорей это уже больше L системы. https://habr.com/ru/post/328568/

2).Странные аттракторы с симметрией.

Обычно странные аттракторы бесформенная каша пикселей или траекторий, тут фотки как из хаоса можно получить симметрию. Вроде в той проге и коды есть…да и pdf можно нагуглить при желании. http://www.fractalsciencekit.com/tutorial/examples/symicon.htm

3). Снежинки из параметрических кривых Безье.

Формы и сложность можно создать бесконечную. Суть формула одна и также только разное количество кривых и координат опорных точек. Вот тут делал давно на GeoGebra 5. https://www.cyberforum.ru/mathcad/thread1271478.html#post6735492

Там еще где-то в теме была ссылка люди скидывали параметрические уравнения рисующие сложные цветки, форма менялась от параметров. Вот эти делал. https://www.cyberforum.ru/mathcad/thread1271478-page2.html#post6766654

4)Вообще по фракталам

Есть прога Fractal Science Kit, не видел больше нигде такого количества всяких таких штук, несколько сотен наверно с настройками.

5)Новый алгоритм генерации типа снежинок или лабиринтов.

Вот тут случайно открыл новый алгоритм генерации типа снежинок или лабиринтов, алгоритм чем-то похоже на игру жизнь. Делал реакцию диффузии и алгоритмически ошибся =). Получилось интересно. Можно генерировать всякие узоры типа вышиванки крестиком, алгоритм останавливает граница в 1 пиксель. Рост начинается с 1 пикселя. Вот тут делал 5 анимаций, а код там есть в теме чего-то вылетает после компиляции =(. Делал вроде на старом glut. https://www.cyberforum.ru/opengl/thread2749102-page3.html#post15131849

Вспомнил еще два новых для меня метода, насколько помню почти не упомянуты в литературе по фракталам.

6)Chaos_game.

Недавно для себя открыл метод, еще ничего с ним не пробовал. По идее потенциал огромен. https://en.wikipedia.org/wiki/Chaos_game

7)Cyclic Symmetric Multi-Scale Turing Patterns Новый для меня алгоритм основан на реакции диффузии. Собственно для нее я и хотел подойти сделав базовую диффузию из 5 пункта. Пока энтузиазм подугас. Но рисует там очень необычно, как фотки с электронного микроскопа.

barsik34566
  • 146
  • 2