Превращение треугольника в снежинку Коха с помощью WebGL

Пытаюсь уложить это в голове. У меня есть следующий код WebGL, который рисует треугольник:

"use strict";

var canvas;
var gl;

var points = [];

window.onload = function init() {
    canvas = document.getElementById("gl-canvas");

    gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) { alert("WebGL isn't available"); }

    var vertices = [
        vec2(-1, -1),
        vec2(0, 1),
        vec2(1, -1)
    ];

    // draw a triangle; this is the current output of the program
    triangle(vertices[0], vertices[1], vertices[2]);

    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(1.0, 1.0, 1.0, 1.0);


    var program = initShaders(gl, "vertex-shader", "fragment-shader");
    gl.useProgram(program);

    var bufferId = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(points), gl.STATIC_DRAW);


    var vPosition = gl.getAttribLocation(program, "vPosition");
    gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(vPosition);

    render();
};

function triangle(a, b, c) {
    points.push(a, b, c);  // (-1, -1), (0, 1), (1, -1) 
}

function render() {
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES, 0, points.length);
}

Начиная с этого [равностороннего] треугольника, я хочу превратить его в снежинку Коха. У меня есть алгоритм благодаря статье из Википедии, но мой неопытный опыт работы с JavaScript делает это немного сложным . Я понимаю, что нужны лишь незначительные изменения в приведенном выше коде, чтобы превратить треугольник в снежинку Коха. Как бы я закодировал требуемый алгоритм?


person TRX    schedule 27.01.2016    source источник


Ответы (1)


Один из способов сделать снежинку Коха — рекурсивно создать группу треугольников, написанную псевдокодом:

/* Return the vertices of a Koch triangle who's left and right sides have
   Koch triangles sticking out of them (recursive).

   pos: The position of the triangle
   dir: The direction triangle should point in
   side: The length of one side of the triangle
   iterations: The number of triangle babies to make
*/
function kochTriangle(pos, dir, side, iterations):
    tri = a big equilateral triangle

    if iterations == 1:
        return tri
    else:
        leftTri = recursively create a little kochTriangle on the left side
                  of tri with iterations - 1
        rightTri = recursively create a little kochTriangle on the right
                   side of tri with iterations - 1

        return concat(tri, leftTri, rightTri)

Обратите внимание, что это создаст только верхнюю половину снежинки Коха, вам нужно будет использовать эту функцию для создания одного большого треугольника Коха, направленного вверх, и одного меньшего треугольника, расположенного внизу и направленного вниз (трудно объяснить). Вы можете использовать либо векторную математику, либо тригонометрию, чтобы выяснить, где именно расположить leftTri и rightTri.

Я подумал, что это крутой вызов, так что я пошел дальше и решил его по-своему. Я поместил свой в исполняемый фрагмент ниже. Вы можете посмотреть код ниже, если вы застряли, но гораздо полезнее разобраться в этом самостоятельно.

JSFiddle

var SNOWFLAKE_ITERATIONS = 5;
var SNOWFLAKE_SIZE = 1.5;

// How much smaller a triangle's child should be. A traditional Kotch
// snowflake should be 1/3. Change this value to get cool shapes.
var SNOWFLAKE_CHILD_SCALE = 1 / 3;

// Canvas element
var canvas;

// WebGL context
var gl;

// Vertices of the snowflake
var snowflakeVerticies;

// Buffer storing the snowflake's vertices
var snowflakeVertexBuffer;

// Vertex shader attribute
var aPositionAttrib;

function main() {
    canvas = document.getElementById("c");
    gl = canvas.getContext("webgl");
    initSnowflakeVertices();
    initShaders();
    initBuffers();
    drawScene();
}

// Initialize the snowflake's vertices
function initSnowflakeVertices() {
    // We have two Koch triangles that make up the snowflake: t1 and t2. t1
    // is the top and sides of the snowflake, t2 gives the bottom.

    var t1Side = SNOWFLAKE_SIZE;
    var t2Side = t1Side * SNOWFLAKE_CHILD_SCALE;

    var t1Height = eqTriHeight(t1Side);
    var t2Height = eqTriHeight(t2Side);

    var snowFlakeHeight = t1Height + t2Height;
    var base = vec2(0.0, t2Height - snowFlakeHeight / 2);

    var t1Dir = vec2(0.0, 1.0);
    var t2Dir = vec2(0.0, -1.0);

    var t1 = kochTriangle(base, t1Dir, t1Side, SNOWFLAKE_ITERATIONS);
    var t2 = kochTriangle(base, t2Dir, t2Side, SNOWFLAKE_ITERATIONS - 1);

    // To clearly see the difference between t1 and t2, you can remove
    // the .concat(t2) to hide t2.
    snowflakeVerticies = t1.concat(t2);
}

// Initialize the shader program
function initShaders() {
    var program = gl.createProgram();
    gl.attachShader(program, compileShader("shader-fs"));
    gl.attachShader(program, compileShader("shader-vs"));
    gl.linkProgram(program);
    gl.useProgram(program);

    aPositionAttrib = gl.getAttribLocation(program, "aPosition");
    gl.enableVertexAttribArray(aPositionAttrib);
}

// Initialize a buffer and put the snowflake's vertices in it
function initBuffers() {
    snowflakeVertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, snowflakeVertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(snowflakeVerticies), gl.STATIC_DRAW);
}

function drawScene() {
    gl.clearColor(1.0, 1.0, 1.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.bindBuffer(gl.ARRAY_BUFFER, snowflakeVertexBuffer);
    gl.vertexAttribPointer(aPositionAttrib, 2, gl.FLOAT, false, 0, 0);
    gl.drawArrays(gl.TRIANGLES, 0, snowflakeVerticies.length / 2);
}

// Create the vertices of a Koch triangle who's left and right sides have
// Koch triangles sticking out of them (recursive).
//
// base: The position vector of the base of the triangle
// dir: The unit vector direction triangle should point in
// side: The length of one side of the triangle
// iterations: The number of triangle babies to make
//
// Note that the bottom of the Koch triangle doesn't have another triangle
// sticking out, this is so we don't create unecessary triangles.
function kochTriangle(base, dir, side, iterations) {
    // tri is the big triangle that has two little triangles sticking out of
    // it's sides.
    var tri = eqTri(base, dir, side);
    var leftVert = vec2(tri[0], tri[1]);
    var rightVert = vec2(tri[2], tri[3]);
    var topVert = vec2(tri[4], tri[5]);

    if (iterations == 1) {
        return tri;
    } else {
        var leftBase = midpoint(leftVert, topVert);
        var leftDir = topVert.minus(leftVert).rotate90DegreesCCW().normalize();
        var leftTri = kochTriangle(leftBase, leftDir, side * SNOWFLAKE_CHILD_SCALE, iterations - 1);

        var rightBase = midpoint(rightVert, topVert);
        var rightDir = topVert.minus(rightVert).rotate90DegreesCW().normalize();
        var rightTri = kochTriangle(rightBase, rightDir, side * SNOWFLAKE_CHILD_SCALE, iterations - 1);

        return tri.concat(leftTri).concat(rightTri);
    }
}

// Create the vertices of an equilateral triangle.
//
// base: The position vector of the base of the triangle
// dir: The unit vector direction triangle should point in
// side: The length of one side of the triangle
function eqTri(base, dir, side) {
    var height = eqTriHeight(side);
    var leftVert = dir.rotate90DegreesCCW().scale(side / 2).plus(base);
    var rightVert = dir.rotate90DegreesCW().scale(side / 2).plus(base);
    var topVert = dir.scale(height).plus(base);
    return [
        leftVert.x, leftVert.y,
        rightVert.x, rightVert.y,
        topVert.x, topVert.y
    ];
}

// Get the height of an equilateral triangle with a given side length
function eqTriHeight(side) {
    return Math.sqrt(3) / 2 * side;
}

// A minimal 2D vector class. Example usage:
//
// x = vec2(1, 2);
// y = x.rotate90DegreesCW();
// z = x.plus(y);
function vec2(x, y) {
    var v = {x: x, y: y};

    v.plus = function(w) {
        return vec2(v.x + w.x, v.y + w.y);
    };

    v.minus = function(w) {
        return vec2(v.x - w.x, v.y - w.y);
    };

    v.scale = function(a) {
        return vec2(v.x * a, v.y * a);
    };

    v.rotate90DegreesCW = function() {
        return vec2(v.y, -v.x);
    };

    v.rotate90DegreesCCW = function() {
        return vec2(-v.y, v.x);
    };

    v.normalize = function() {
        return v.scale(1 / v.length());
    };

    v.length = function() {
        return Math.sqrt(v.x * v.x + v.y * v.y);
    };

    return v;
}

// Return the midpoint of two vectors
function midpoint(v, w) {
    return v.plus(w).scale(1 / 2);
}

// Compile and return the shader in the given element.
function compileShader(id) {
    var script = document.getElementById(id);
    if (!script) {
        return null;
    }

    var str = "";
    var k = script.firstChild;
    while (k) {
        if (k.nodeType == 3) {
            str += k.textContent;
        }
        k = k.nextSibling;
    }

    var shader;
    if (script.type == "x-shader/x-fragment") {
        shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (script.type == "x-shader/x-vertex") {
        shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
        return null;
    }

    gl.shaderSource(shader, str);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        console.error(id, gl.getShaderInfoLog(shader));
        return null;
    }

    return shader;
}

main();
<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec2 aPosition;

    void main(void) {
        gl_Position = vec4(aPosition, 0.0, 1.0);
    }
</script>

<script id="shader-fs" type="x-shader/x-fragment">
    void main(void) {
        gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
    }
</script>

<canvas id="c" width="500" height="500"></canvas>

person John Owen    schedule 27.01.2016
comment
Большое спасибо, Джон! - person TRX; 27.01.2016