通用有机污渍/污垢形状

时间:2015-03-04 22:26:53

标签: javascript algorithm

我正在寻找一种算法,该算法实质上返回一系列点,这些点定义了由液体形成的污点的形状。

我不需要任何方向性影响,它可能是垂直落下的水滴产生的污渍。

我开始从一个中心以两对的方式传播点,一个靠近中心一点,这给了我一个星球。但后来我的想法离开了我。我怎样才能获得交叉点等但主要是:对于没有任何花哨侧面的单一形状解决方案,应该有一个我不知道的算法?那么任何想法/解决方案?

染色的例子我的意思是(我只需要中央/主要形状的课程): related stains on Google-Images

1 个答案:

答案 0 :(得分:3)

您可以通过制作Bézier曲线来优化星形逼近。如果你看一下搜索的例子,你基本上会看到两种飞溅模式:细小的尖刺和更大的水滴形状。

我们可以在圆圈上随机分散飞溅并确定飞溅长度。然后我们根据该长度决定要绘制哪种形状。我们需要的控制点是:

Bézier control points

下面的代码尝试对其进行建模。功能splash返回以原点为中心的飞溅坐标列表。对于3*n + 1Bézier段的闭合曲线,该列表具有n个点。 (n是随机确定的。)

代码远非完美,也有太多辅助内容,可以改进,但可能会给你一个想法:

var rnd = {
    uniform: function(n) {
            return Math.floor(n * Math.random());
        },

    range: function(from, to) {
            return from + this.uniform(to - from);
        },

    float: function(from, to) {
            return from + (to - from) * Math.random();
        }
}

var coord = {
    radiants: function(x) {
            return Math.PI * x / 180.0;
        },

    degrees: function(x) {
            return 180.0 * x / Math.PI;
        },

    cartesian: function(P) {
            return {
                x: P.r * Math.cos(P.phi),
                y: P.r * Math.sin(P.phi)
            }
        },

    mid: function(P, Q, a) {
            if (!a) a = 0.5;

            return {
                x: (1 - a) * P.x + a * Q.x,
                y: (1 - a) * P.y + a * Q.y
            };
        },

    normal: function(P, len) {
            if (!len) len = 1;
            var l = Math.sqrt(P.x*P.x + P.y*P.y);

            return {
                x: len * P.y / l, 
                y: -len * P.x / l
            };
        },

    add: function(P, Q) {
            return {
                x: P.x + Q.x,
                y: P.y + Q.y
            };
        },

    mul: function(P, a) {
            return {
                x: a * P.x,
                y: a * P.y
            };
        },

    dist: function(P, Q) {
            var dx = P.x - Q.x;
            var dy = P.y - Q.y;

            var l = Math.sqrt(dx*dx + dy*dy);
        },

    normalize: function(P, len) {
            if (!len) len = 1;
            var l = Math.sqrt(P.x*P.x + P.y*P.y);

            return {
                x: len * P.x / l, 
                y: len * P.y / l
            };
        }
}

function get(param, value, dflt) {
    if (value in param) return param[value];
    return dflt;
}

function splash(param) {
    var r = get(param, "r", 10);
    var minangle = get(param, "minangle", 5);
    var maxangle = get(param, "maxangle", 30);
    var ratio = get(param, "ratio", 2.4);
    var n = get(param, "n", 2);

    var radial = [];
    var phi = 0;

    while (phi < 2 * Math.PI) {
        radial.push({
            phi: phi, 
            r: r * (1 + (ratio - 1) * Math.pow(Math.random(), n))
        });
        phi += coord.radiants(rnd.float(minangle, maxangle, 30));
    }

    var phi0 = coord.radiants(rnd.float(0, 10));
    for (var i = 0; i < radial.length; i++) {
        var rr = radial[i];

        rr.phi =  2 * rr.phi * Math.PI / phi + phi0;
    }

    var res = [];

    var prev = radial[radial.length - 1];
    var curr = radial[0];
    var C = {x: 0, y: 0};

    for (var i = 0; i < radial.length; i++) {
        var next = radial[(i + 1) % radial.length];

        var ML = coord.cartesian(prev);
        var MR = coord.cartesian(next);
        var M = coord.cartesian(curr);

        var L = coord.mid(C, coord.mid(ML, M));
        var R = coord.mid(C, coord.mid(MR, M));
        if (i == 0) res.push(L);

        var dphi = (next.phi - prev.phi);
        if (dphi < 0) dphi += 2 * Math.PI;
        var dr = 0.5 * r * dphi;

        var NL = coord.normal(L, -dr * rnd.float(0.3, 0.45));
        res.push(coord.add(L, NL));

        console.log((curr.r - r) / (ratio - 1));
        if (Math.random() > (curr.r - r) / r / (ratio - 1)) {
            // little splash
            var MM = coord.mid(C, M, rnd.float(0.75, 0.95));

            res.push(MM);
            res.push(M);
            res.push(MM);                
        } else {
            // drop-shaped splash
            var s = dr * rnd.float(0.2, 0.5);
            var t = dr * rnd.float(0.02, 0.2);

            var MM = coord.mid(coord.mid(L, M), coord.mid(R, M));
            var Mpos = coord.normalize(M, s);
            var Mneg = coord.normalize(M, -s);

            var MT = coord.add(M, Mpos);

            var NML = coord.normal(M, s);
            var NLL = coord.normal(M, t);
            var MML = coord.add(MM, NLL);
            var ML = coord.add(M, NML);

            var NMR = coord.normal(M, -s);
            var NRR = coord.normal(M, -t);
            var MMR = coord.add(MM, NRR);
            var MR = coord.add(M, NMR);

            res.push(coord.mid(C, MML, 0.8));
            res.push(MML);
            res.push(coord.mid(C, MML, 1.25));

            res.push(coord.add(ML, coord.mul(Mneg, 0.55)));
            res.push(ML);
            res.push(coord.add(ML, coord.mul(Mpos, 0.55)));

            res.push(coord.add(MT, coord.mul(NML, 0.55)));
            res.push(MT);
            res.push(coord.add(MT, coord.mul(NMR, 0.55)));

            res.push(coord.add(MR, coord.mul(Mpos, 0.55)));
            res.push(MR);
            res.push(coord.add(MR, coord.mul(Mneg, 0.55)));

            res.push(coord.mid(C, MMR, 1.25));
            res.push(MMR);
            res.push(coord.mid(C, MMR, 0.8));
        }

        var NR = coord.normal(R, dr * rnd.float(0.3, 0.45));
        res.push(coord.add(R, NR));
        res.push(R);

        prev = curr;
        curr = next;
    }

    return res;
}

以及如何使用该代码的示例:

window.onload = function() {
    var cv = document.getElementById("plot");
    var cx = cv.getContext("2d");

    var p = splash({
        r: 100,
        ratio: 1.6,
        n: 1
    });

    cx.fillStyle = "tomato";
    cx.translate(300, 300);

    cx.beginPath();
    cx.moveTo(p[0].x, p[0].y);
    for (var i = 1; i < p.length; i++) {
        var p1 = p[i++];
        var p2 = p[i++];
        var p3 = p[i];

        cx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
    }

    cx.closePath();
    cx.fill();
}