您草圖中的雲只是沿着每個多邊形邊緣繪製的一系列圓,並具有一定的重疊。
繪製填充的基本雲形狀的簡單方法是首先填充多邊形,然後在填充的多邊形上繪製圓。
當您想用部分透明的顏色填充雲時,該方法會變得平坦,因爲圓與彼此以及與基礎多邊形的重疊將被繪製兩次。它也會錯過雲曲線上的小卡通風格的過沖。
繪製雲的更好方法是首先創建所有圓,然後確定每個圓與其下一個相鄰圓的相交角。然後,您可以創建一個包含圓形段的路徑,您可以填寫該路徑。輪廓由獨立的弧組成,對於終點角度具有較小的偏移量。
在您的示例中,雲弧之間的距離是靜態的。通過使該距離變化並通過強制多邊形邊緣可以被該距離均勻分割,使得多邊形頂點上的弧很容易變化。
下面是JavaScript中的示例實現(沒有多邊形拖動)。我不熟悉C#,但我認爲基本算法很清晰。該代碼是一個完整的網頁,您可以保存並顯示在支持畫布的瀏覽器中;我已經在Firefox中測試過它。
繪製雲的功能需要選項的對象,例如半徑,弧度距離和以度爲單位的過沖。我沒有測試像小多邊形這樣的退化情況,但是在極端情況下,算法應該爲每個多邊形頂點繪製一個弧。
多邊形必須順時針定義。否則,雲將更像雲層中的洞。如果角落附近沒有任何人造物,那將是一個很好的特點。
編輯:我爲下面的雲算法提供了simple online test page。該頁面允許您使用各種參數進行播放。它也很好地顯示了該算法的缺點。 (在FF和Chrome中測試)
當起始角度和結束角度未正確確定時出現人爲現象。以非常鈍的角度,角落旁邊的弧線之間也可能有交點。我還沒有解決這個問題,但我也沒有給出太muczh認爲。)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Cumulunimbus</title>
<script type="text/javascript">
function Point(x, y) {
this.x = x;
this.y = y;
}
function get(obj, prop, fallback) {
if (obj.hasOwnProperty(prop)) return obj[prop];
return fallback;
}
/*
* Global intersection angles of two circles of the same radius
*/
function intersect(p, q, r) {
var dx = q.x - p.x;
var dy = q.y - p.y;
var len = Math.sqrt(dx*dx + dy*dy);
var a = 0.5 * len/r;
if (a < -1) a = -1;
if (a > 1) a = 1;
var phi = Math.atan2(dy, dx);
var gamma = Math.acos(a);
return [phi - gamma, Math.PI + phi + gamma];
}
/*
* Draw a cloud with the given options to the given context
*/
function cloud(cx, poly, opt) {
var radius = get(opt, "radius", 20);
var overlap = get(opt, "overlap", 5/6);
var stretch = get(opt, "stretch", true);
// Create a list of circles
var circle = [];
var delta = 2 * radius * overlap;
var prev = poly[poly.length - 1];
for (var i = 0; i < poly.length; i++) {
var curr = poly[i];
var dx = curr.x - prev.x;
var dy = curr.y - prev.y;
var len = Math.sqrt(dx*dx + dy*dy);
dx = dx/len;
dy = dy/len;
var d = delta;
if (stretch) {
var n = (len/delta + 0.5) | 0;
if (n < 1) n = 1;
d = len/n;
}
for (var a = 0; a + 0.1 * d < len; a += d) {
circle.push({
x: prev.x + a * dx,
y: prev.y + a * dy,
});
}
prev = curr;
}
// Determine intersection angles of circles
var prev = circle[circle.length - 1];
for (var i = 0; i < circle.length; i++) {
var curr = circle[i];
var angle = intersect(prev, curr, radius);
prev.end = angle[0];
curr.begin = angle[1];
prev = curr;
}
// Draw the cloud
cx.save();
if (get(opt, "fill", false)) {
cx.fillStyle = opt.fill;
cx.beginPath();
for (var i = 0; i < circle.length; i++) {
var curr = circle[i];
cx.arc(curr.x, curr.y, radius, curr.begin, curr.end);
}
cx.fill();
}
if (get(opt, "outline", false)) {
cx.strokeStyle = opt.outline;
cx.lineWidth = get(opt, "width", 1.0);
var incise = Math.PI * get(opt, "incise", 15)/180;
for (var i = 0; i < circle.length; i++) {
var curr = circle[i];
cx.beginPath();
cx.arc(curr.x, curr.y, radius,
curr.begin, curr.end + incise);
cx.stroke();
}
}
cx.restore();
}
var poly = [
new Point(250, 50),
new Point(450, 150),
new Point(350, 450),
new Point(50, 300),
];
window.onload = function() {
cv = document.getElementById("cv");
cx = cv.getContext("2d");
cloud(cx, poly, {
fill: "lightblue", // fill colour
outline: "black", // outline colour
incise: 15, // overshoot in degrees
radius: 20, // arc radius
overlap: 0.8333, // arc distance relative to radius
stretch: false, // should corner arcs coincide?
});
}
</script>
</head>
<body>
<canvas width="500" height="500" id="cv"></canvas>
</body>
</html>
這是迄今爲止我收到的最好的迴應StackOverflow。我希望我能爲您提供更多的積分,讓您獲得難以置信的努力。您的代碼結構合理,易於理解,並且無可否認令人印象深刻!謝謝,姆姆。 – Laith
雖然有一個小問題。如果點(形成一個正方形)從右下,右上,左上和左下開始,則繪製的雲相反。 – chitgoks
@chitgoks:錯誤發生在帖子中,而不是在代碼中。必須定義多邊形的方向,因爲它用於找出什麼是雲的「外部」。這篇文章說的是順時針方向,但是在HTML畫布中,從上到下y是逆時針的。所以交換你的第二和第四個版本,你應該找到。 –