日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

多边形裁剪(Polygon Clipping) 1

發布時間:2023/12/8 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 多边形裁剪(Polygon Clipping) 1 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文地址:?https://sean.cm/a/polygon-clipping-pt1

Greiner-Hormann裁剪算法無法處理重合線。?所以我研究并寫了另一篇適用于所有多邊形的文章。

在此處閱讀后續內容:多邊形裁剪(第 2 部分)

問題

首先, 讓我們定義問題,

假設您有兩個多邊形,每個多邊形都以 2D 形式存在

var poly1 = [ // red[ 181, 270 ],[ 85, 418 ],[ 171, 477 ],[ 491, 365 ],[ 218, 381 ],[ 458, 260 ] ]; var poly2 = [ // blue[ 474, 488 ],[ 659, 363 ],[ 255, 283 ],[ 56, 340 ],[ 284, 488 ],[ 371, 342 ] ];

?奇偶規則

多邊形遵循奇偶規則來確定一個點是否被視為區域“內部”。

基本規則是想象您正在用一條水平線上從左到右掃描。每次越過邊緣時,都會在外部和內部之間切換。

?那么:給定這兩個多邊形,我們如何計算不同的布爾運算?

基本理念

?首先,讓我們定義一些基本的規則.

順時針vs逆時針(Forward vs. Backward Movement)

如果我們坐在多邊形的任何一點上,我們總是可以向前一個點或者后一個點移動

順時針只是意味著沿箭頭方向移動,逆時針則相反

?插入點

?在處理過程中,我們需要在多邊形中插入點。只要我們對如何插入它們很聰明,它就不會改變多邊形的形狀:

交叉點

識別和分類交叉點是算法中的魔法.

如果您考慮一下,我們將執行的每個操作(交集、聯合、差異)都會產生一個包含多邊形之間所有交點的多邊形

?我們不關心同一多邊形內的交叉點.

另外:如果你想象我們正沿著一個多邊形行走并遇到一個十字路口,我們有3個選擇.

1.?保持在同一個多邊形上(這是毫無意義的)

2.?切換多邊形,開始順時針移動

3.?切換多邊形,并開始逆時針移動

?

因此,如果我們能夠智能地選擇在每個交叉路口的方向,我們就可以追蹤到正確的結果形狀.

交叉點示例

想象一下, 我們想象一下,我們正在追蹤兩個多邊形合并union的結果.

在每個交叉點,我們都希望朝著最終形狀繼續增長的方向移動.

我們可以這樣做:

我們也可以在相反的方向得到相同的結果:

關于所有四個決定,我們可以說哪些是正確的?

在每個交叉點,我們總是朝著遠離我們離開的多邊形的方向前進.

因此,例如,如果我們沿著 Blue 行駛,然后遇到一個十字路口,我們應該繼續沿著 Red 向遠離Blue的方向行使.

會有什么不同?這是Red-Blue(從Red中減去Blue區域):

?而在另一個方向:

對此我們能說什么?

當從紅色切換到藍色時,我們進入紅色。當從藍色切換到紅色時,我們遠離紅色.

所以我們有兩個基本的決定:

1. 當從紅色切換到藍色時,我們是進入還是遠離紅色?

2.當從藍色切換到紅色時,我們是進入還是遠離藍色?

對于聯合(union)來說, 答案總是離開.?但是對于Red-Blue(Red減Blue),我們想要進入紅色,?遠離藍色。如果你玩玩,你會注意到交叉(intersection?)意味著總是進入你要離開的

這給了我們下邊的表

OperatorInto Red?Into Blue?
聯合(Union)FalseFalse
Red減Blue(Red - Blue)TrueFalse
Blue減Red(Blue - Red)FalseTrue
交叉(Intersection)TrueTrue

交叉入口/ 交叉出口

我們不知道如何進入或離開——我們只知道沿著多邊形順時針,逆時針移動。我們如何把兩者同意起來.

如果我們在一個交點的兩邊取兩個點,并測試它們是否在另一個多邊形內,我們可以保證一個點在外面,一個點在里面:

如果第一個點在外面,那么我們可以認為這條線是通過交點進入多邊形的。如果第一個點在內,則該線通過交點離開多邊形.

所以,我們真的只需要將每個交叉點標記為交叉入口/ 交叉出口

當我們沿著一條路徑行駛時,每個路口都會切換我們是在里面還是外面。它必須.

因此,我們只需要計算第一個點是否在另一個多邊形內部。如果是,那么第一個交叉點是一個交叉出口——否則第一個交叉點是一個交叉入口.

而且由于路徑上的每個交叉點都在entry和exit之間切換,我們不必繼續測試點是在內部還是外部(這很昂貴).

表現

最后,重要的是要認識到交叉點是相對于多邊形的交叉入口或交叉出口.

這意味著每個交叉點有四種可能性.

?白色代表進入,黑色代表退出。左半球為紅色,右半球為藍色

?實際上,對于每個交點,我們將在每個多邊形中插入一個點。所以每個交點會有兩個點,一個存儲在每個多邊形中。每個點都會跟蹤它是進入還是退出.

現在我們準備好代碼啦.

步驟1. 將多邊形轉換為鏈表

雙鏈表對于這個算法來說是一個有用的多邊形表示,因為我們將同時插入點和遍歷。通過使用雙鏈表,我們不必擔心插入會破壞遍歷.

我們還需要跟蹤一個點是否是一個交點,所以我們可以從false這里初始化它開始:

function UpgradePolygon(p){// converts a list of points into a double linked listvar root = null;for (var i = 0; i < p.length; i++){var node = {point: p[i],intersection: false,next: null,prev: null};if (root === null){// root just points to itself:// +-> (root) <-+// | |// +------------+node.next = node;node.prev = node;root = node;}else{// change this:// ...-- (prev) <--------------> (root) --...// to this:// ...-- (prev) <--> (node) <--> (root) --...var prev = root.prev;prev.next = node;node.prev = prev;node.next = root;root.prev = node;}}return root; }

步驟2. 計算并插入交叉點

接下來,我們需要遍歷每個邊組合,看看它們是否相交。如果它們確實彼此相交,那么我們需要在多邊形中插入交點.

線交點

首先,我們需要一個輔助函數來計算兩條線的交點:

function LinesIntersect(a0, a1, b0, b1){var adx = a1[0] - a0[0];var ady = a1[1] - a0[1];var bdx = b1[0] - b0[0];var bdy = b1[1] - b0[1];var axb = adx * bdy - ady * bdx;var ret = {cross: axb,alongA: Infinity,alongB: Infinity,point: [Infinity, Infinity]};if (axb === 0)return ret;var dx = a0[0] - b0[0];var dy = a0[1] - b0[1];ret.alongA = (bdx * dy - bdy * dx) / axb;ret.alongB = (adx * dy - ady * dx) / axb;ret.point = [a0[0] + ret.alongA * adx,a0[1] + ret.alongA * ady];return ret; }

它計算兩條線的交點,并返回每條線上的交點“沿”多遠。因此,例如,如果alongA是0.75,那么交集發生在從a0到 的75% 處a1.?

這些值是重要的,因為他們可能是負數或大于1,因此,如果兩條線實際相交,我們需要測試alongA和alongB0和1(不含)之間.

下一個非交點

由于我們將在我們的鏈表中插入交點,所以有一個幫助函數來查找下一個非交點.

function NextNonIntersection(node){do{node = node.next;} while (node.intersection);return node; }

每個邊組合(Edge Pair)

現在我們可以編寫迭代每個邊組合的代碼:

var root1 = UpgradePolygon(poly1); var root2 = UpgradePolygon(poly2);var here1 = root1; var here2 = root2; do{do{//// TODO: test intersection between:// here1 -> NextNonIntersection(here1) and// here2 -> NextNonIntersection(here2)//here2 = NextNonIntersection(here2);} while (here2 !== root2);here1 = NextNonIntersection(here1); } while (here1 !== root1);

交叉點測試

給定兩個節點,我們可以測試交集:

var next1 = NextNonIntersection(here1); var next2 = NextNonIntersection(here2);var i = LinesIntersect(here1.point, next1.point,here2.point, next2.point );if (i.alongA > 0 && i.alongA < 1 &&i.alongB > 0 && i.alongB < 1){//// TODO: insert intersection points in both polygons at// the correct location, referencing each other// }

插入交叉點

最后,如果兩條邊相交,那么我們要在兩個非交點之間插入我們的交叉點.

為了將它插入正確的位置,我們必須跟蹤alongA和alongB值以確保如果兩個交點在同一條邊上,它們以正確的順序插入.

我們將要創建兩個節點,一個用于每個多邊形——但這些節點應該相互指向,以便我們稍后在遇到交叉點時可以在多邊形之間“跳躍”

var node1 = {point: i.point,intersection: true,next: null,prev: null,dist: i.alongA,friend: null }; var node2 = {point: i.point,intersection: true,next: null,prev: null,dist: i.alongB,friend: null };// point the nodes at each other node1.friend = node2; node2.friend = node1;var inext, iprev;// find insertion between here1 and next1, based on dist inext = here1.next; while (inext !== next1 && inext.dist < node1.dist)inext = inext.next; iprev = inext.prev;// insert node1 between iprev and inext inext.prev = node1; node1.next = inext; node1.prev = iprev; iprev.next = node1;// find insertion between here2 and next2, based on dist inext = here2.next; while (inext !== next2 && inext.dist < node2.dist)inext = inext.next; iprev = inext.prev;// insert node2 between iprev and inext inext.prev = node2; node2.next = inext; node2.prev = iprev; iprev.next = node2;

步驟3. 計算交叉入口/交叉出口

我們知道交叉口在進入和退出之間交替。但是第一個交叉點是什么?是入口還是出口.

簡單:如果多邊形的第一個點在另一個多邊形內,那么第一個交點必須是出口.

但是,計算一個點是否在多邊形內部實際上有點復雜.

點在多邊形內

function PointInPolygon(point, root){var odd = false;var x = point[0];var y = point[1];var here = root;do {var next = here.next;var hx = here.point[0];var hy = here.point[1];var nx = next.point[0];var ny = next.point[1];if (((hy < y && ny >= y) || (hy >= y && ny < y)) &&(hx <= x || nx <= x) &&(hx + (y - hy) / (ny - hy) * (nx - hx) < x)){odd = !odd;}here = next;} while (here !== root);return odd; }

PointInPolygon通過計算水平線相交的邊數來工作。水平線從(-Infinity, y)到(x, y)。它只關心交叉點的數量是奇數還是偶數。它基于光線投射。

交替進入/退出

現在我們可以輕松計算出一個交叉點是入口還是出口:

function CalculateEntryExit(root, isEntry){var here = root;do{if (here.intersection){here.isEntry = isEntry;isEntry = !isEntry;}here = here.next;} while (here !== root); }var is1in2 = PointInPolygon(root1.point, root2); var is2in1 = PointInPolygon(root2.point, root1);CalculateEntryExit(root1, !is1in2); CalculateEntryExit(root2, !is2in1);

步驟4. 生成結果

我們已經走了很長一段路!這是我們到目前為止所擁有的.

我們已經計算并插入了交點,并將它們標記為每個多邊形的入口或出口.

現在是有趣的部分!

從哪里開始

?我們從哪里開始追蹤結果?我們不能只選擇一個隨機點,因為有些點實際上可以從結果中完全刪除.

由于所有操作都包括每個交集,我們應該從尋找未處理的交集開始.

我們添加到最終結果中的每個交點,我們都標記為已處理.

然后,我們只是繼續跟蹤,直到我們不再有任何交集需要處理.

var result = []; var isect = root1; var into = [intoBlue, intoRed]; // explained below while (true){do{if (isect.intersection && !isect.processed)break;isect = isect.next;} while (isect !== root1);if (isect === root1)break;//// TODO: process isect// }

?轉向哪個方向

最后,我們來到了癥結所在:

當我們遇到十字路口時,我們怎么知道該往哪個方向轉彎?

讓我們來推理一下:

Is Entry?Move Into?Move Forward?
TrueTrueTrue
TrueFalseFalse
FalseTrueFalse
FalseFalseTrue

因此,如果 ,我們應該繼續前進isEntry === intoPoly

由于我們所在的多邊形來回切換,我們只需通過將intoBlue和存儲intoRed在into列表中來使我們的決策動態化,并將?其curpoly用作索引.

var curpoly = 0; var clipped = [];var here = isect; do{// mark intersection as processedhere.processed = true;here.friend.processed = true;var moveForward = here.isEntry === into[curpoly];do{clipped.push(here.point);if (moveForward)here = here.next;elsehere = here.prev;} while (!here.intersection);// we've hit the next intersection so switch polygonshere = here.friend;curpoly = 1 - curpoly; } while (!here.processed);result.push(clipped);

沒有交叉點

如果沒有交叉點?

我們的結果集將是空的……這可能是正確的,也可能是錯誤的——這取決于操作.

一個簡單的檢查就足以修復它:

if (result.length <= 0){if (is1in2 === intoBlue)result.push(poly1);if (is2in1 === intoRed)result.push(poly2); }

演示

單擊此處啟動演示!

?您可以拖動每個多邊形的點,并通過單擊按鈕切換操作。

?附錄:限制

抱歉,這個算法有一個嚴重的局限性:

您不能擁有完美重疊的點或邊.

如果你仔細想想,這是有道理的:整個算法都是基于交叉點的思想.

如果點或邊直接重疊,那么您就不會得到那種好的跳躍效果.

最初的論文建議稍微“擾亂”點,這樣線條就不會完全重疊。我最初認為這是一個小調整,不會有有問題.

但是,我錯了.

擾動點會破壞數據——因此可能很重要的源數據的屬性(例如,平滑邊緣)變得無效.

幸運的是我研究了另一種處理一切的算法,并寫了一篇后續文章

此處閱讀后續內容:多邊形裁剪(第 2 部分)

總結

以上是生活随笔為你收集整理的多边形裁剪(Polygon Clipping) 1的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。