自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

圖形編輯器開(kāi)發(fā):基于相交策略選中圖形

開(kāi)發(fā) 前端
我想要選中一個(gè)大矩形,就比較費(fèi)勁,要畫(huà)一個(gè)比它還大的選區(qū),可能還會(huì)把其他的矩形不小心放進(jìn)去(那你別用選區(qū),直接點(diǎn)選?。?。 不管怎樣,我選擇同時(shí)提供 “包含(Contain)” 和 "相交(Intersect)" 兩種模式,默認(rèn)使用相交策略。

大家好,我是前端西瓜哥。

我開(kāi)發(fā)的圖形編輯器,原本選中圖形是基于選區(qū)是否完全包含對(duì)應(yīng)圖形來(lái)判斷其是否被選中,使用的是矩形包含判斷。

編輯器 github 地址:

https://github.com/F-star/suika

線上體驗(yàn):

https://blog.fstars.wang/app/suika/

但用著用著,我發(fā)現(xiàn)包含可能并不是一個(gè)好策略。

我想要選中一個(gè)大矩形,就比較費(fèi)勁,要畫(huà)一個(gè)比它還大的選區(qū),可能還會(huì)把其他的矩形不小心放進(jìn)去(那你別用選區(qū),直接點(diǎn)選?。?/p>

不管怎樣,我選擇同時(shí)提供 “包含(contain)” 和 "相交(intersect)" 兩種模式,默認(rèn)使用相交策略。

包含選擇

包含策略很簡(jiǎn)單,遍歷圖形,對(duì)比 selection 選區(qū)矩形和圖形的包圍盒,判斷是否為前者包含后者的關(guān)系。

如果是,就放到選中圖形集合中。

相比相交的實(shí)現(xiàn),算法不復(fù)雜。

// 矩形 1 是否包含矩形 2
function isRectContain(rect1: IRect, rect2: IRect) {
  return (
    rect1.x <= rect2.x &&
    rect1.y <= rect2.y &&
    rect1.x + rect1.width >= rect2.x + rect2.width &&
    rect1.y + rect1.height >= rect2.y + rect2.height
  );
}

// 使用
for (const el of elements) {
  // getBBox 拿到的是 AABB 包圍盒
  if(isRectContain(selection, el.getBBox())) {
    selectSet.add(el);
  }
}

相交選擇

相交(也叫碰撞)的實(shí)現(xiàn)類似。

// 兩矩形是否相交
function isRectIntersect(rect1: IRect, rect2: IRect) {
  return (
    rect1.x <= rect2.x + rect2.width &&
    rect1.x + rect1.width >= rect2.x &&
    rect1.y <= rect2.y + rect2.height &&
    rect1.y + rect1.height >= rect2.y
  );
}

// 使用
for (const el of elements) {
  // getBBox 拿到的是 AABB 包圍盒
  if(isRectIntersect(selection, el.getBBox())) {
    selectSet.add(el);
  }
}

效果:

看起來(lái)不錯(cuò),但它這個(gè)相交檢測(cè),很 “粗糙”。

因?yàn)樯厦鎸?shí)現(xiàn),只做了大的 AABB 包圍盒的相交檢測(cè),沒(méi)有做小的 OBB 包圍盒的相交檢測(cè)。

對(duì)于發(fā)生旋轉(zhuǎn)的圖形,selection 如果和包裹圖形的空白區(qū)域相交了,圖形也被選中。

這種事情,不要啊。

OBB 相交檢測(cè)

我們來(lái)實(shí)現(xiàn)更精準(zhǔn)的 OBB 的相交檢測(cè)。

為此西瓜哥我調(diào)研(其實(shí)是瞎想)了幾個(gè)方案,并研究了算法實(shí)現(xiàn)。

方案 1:線段相交判斷

直接一點(diǎn),判斷 selection 的邊和圖形的邊是否有相交的。

核心算法實(shí)現(xiàn)為:

type Point = [number, number];

function crossProduct(p1: Point, p2: Point, p3: Point): number {
  const x1 = p2[0] - p1[0];
  const y1 = p2[1] - p1[1];
  const x2 = p3[0] - p1[0];
  const y2 = p3[1] - p1[1];
  return x1 * y2 - x2 * y1;
}

function isSegmentIntersect(
  seg1: [Point, Point],
  seg2: [Point, Point],
): boolean {
  const [a, b] = seg1;
  const [c, d] = seg2;

  const d1 = crossProduct(a, b, c);
  const d2 = crossProduct(a, b, d);
  const d3 = crossProduct(c, d, a);
  const d4 = crossProduct(c, d, b);

  // 突然發(fā)現(xiàn)這里可以做一個(gè)短路計(jì)算優(yōu)化
  return d1 * d2 < 0 && d3 * d4 < 0;
}

光是比較 1 對(duì)線段,就要進(jìn)行如此多的計(jì)算。而我們要對(duì)比 4 * 4,共 16 組(當(dāng)然這是最壞情況)。

感覺(jué) 8 太行,最后沒(méi)選擇該方案。

(理論上應(yīng)該做性能測(cè)試對(duì)比各種實(shí)現(xiàn)的,還要考慮用戶使用選區(qū)的場(chǎng)景,是否會(huì)經(jīng)常出現(xiàn)特定算法的最壞時(shí)間復(fù)雜度的情形,有空再做吧)

方案2:分離軸定理算法

這個(gè)算法挺有意思。

分離軸(Separating Axis Theorem,簡(jiǎn)稱SAT),它的思想是:

如果能找到一條直線將兩個(gè)圖形分開(kāi),那說(shuō)明這兩個(gè)圖形不相交。

如圖:

具體做法是做投影。(通過(guò)降維,將大問(wèn)題拆分成小問(wèn)題)

我們會(huì)對(duì)兩個(gè)凸多邊形做投影,投影到的線稱為 “分離軸”。

分離軸基本選擇的是兩個(gè)圖形的每條邊對(duì)應(yīng)的法向量。當(dāng)發(fā)現(xiàn)投影產(chǎn)生的兩條線段沒(méi)有相交,那找到了那條那條分割兩圖形的直線,證明兩個(gè)凸多邊形不相交。

否則繼續(xù),如果都沒(méi)找到,說(shuō)明相交。

下圖是以一個(gè)圖形的藍(lán)邊的法向量作為分離軸,進(jìn)行投影的示意圖。

求投影會(huì)用到向量點(diǎn)乘的運(yùn)算。

因?yàn)椴皇潜疚闹攸c(diǎn),具體實(shí)現(xiàn)細(xì)節(jié)就不講解了,可以考慮以后專門(mén)寫(xiě)一篇文章。

矩形碰撞,特殊的分離軸定理碰撞

不知道你發(fā)現(xiàn)沒(méi)有,從分離軸線的角度去看,兩個(gè)沒(méi)有旋轉(zhuǎn)矩形的相交判斷,其實(shí)是一個(gè)特例。

我們?cè)谂袛噙x區(qū)矩形和圖形的 AABB 包圍盒是否相交時(shí),其實(shí)就已經(jīng)完成了 基于選區(qū)矩形對(duì)應(yīng)的所有分離軸 的投影上是否相交的比較。

接下來(lái)我們只要再對(duì)圖形的邊對(duì)應(yīng)的分離軸線投影,去對(duì)比就好了。

怎么做呢?

我們 “旋轉(zhuǎn)回來(lái)”,將圖形掰正,選區(qū)矩形產(chǎn)生了旋轉(zhuǎn)角度,計(jì)算選區(qū)矩形的 AABB 包圍盒,再進(jìn)行矩形對(duì)比就好了。

這樣,圖形的分離軸的投影也對(duì)比完了,所有的分離軸都對(duì)比了,就能判斷出選區(qū)和圖形的 OBB 包圍盒是否碰撞了。

甚至都不用向量點(diǎn)乘。

OBB 相交判斷代碼實(shí)現(xiàn)

下面給出代碼實(shí)現(xiàn)。

// 使用相交策略,遍歷圖形是否和選區(qū)矩形相交。
for (const el of elements) {
  let isSelected = false; // 是否被選中到
 
  // 首先做 AABB 碰撞檢測(cè)
  // 絕大多數(shù)場(chǎng)景下,只有少數(shù)圖形和選區(qū)有相交
  if (!isRectIntersect(selection, el.getBBox())) {
    // 其實(shí)這里用 break; 在意圖上更明顯
    isSelected = false;
  } else {
    // 如果旋轉(zhuǎn)角度為 90 的倍數(shù),
    // 則 OBB 等價(jià)于 AABB,前面已經(jīng)判斷了,沒(méi)必要繼續(xù)算了
    if (el.rotation % HALF_PI == 0) {
      isSelected = true;
    } else {
      // OBB 碰撞檢測(cè)
      // 使用分離軸定理的特殊寫(xiě)法
      const { x: cx, y: cy } = el.getCenter();
      const r = -el.rotation;
      const s1 = transformRotate(selection.x, selection.y, r, cx, cy);
      
      // 下面一大段代碼都是求選區(qū)旋轉(zhuǎn)后的 AABB
      const s2 = transformRotate(
        selection.x + selection.width,
        selection.y + selection.height,
        r,
        cx,
        cy,
      );
      const s3 = transformRotate(
        selection.x + selection.width,
        selection.y,
        r,
        cx,
        cy,
      );
      const s4 = transformRotate(
        selection.x,
        selection.y + selection.height,
        r,
        cx,
        cy,
      );

      const rotatedSelectionX = Math.min(s1.x, s2.x, s3.x, s4.x);
      const rotatedSelectionY = Math.min(s1.y, s2.y, s3.y, s4.y);
      const rotatedSelectionWidth =
        Math.max(s1.x, s2.x, s3.x, s4.x) - rotatedSelectionX;
      const rotatedSelectionHeight =
        Math.max(s1.y, s2.y, s3.y, s4.y) - rotatedSelectionY;

      // 這個(gè)就是選區(qū)矩形旋轉(zhuǎn)后的 AABB 包圍盒
      const rotatedSelection = {
        x: rotatedSelectionX,
        y: rotatedSelectionY,
        width: rotatedSelectionWidth,
        height: rotatedSelectionHeight,
      };

      // 對(duì)比它們(注意圖形不要再用 AABB 了)
      isSelected = isRectIntersect(rotatedSelection, {
        x: el.x,
        y: el.y,
        width: el.width,
        height: el.height,
      });
    }
  }

  // 更新選中圖形集合
  if (isSelected) {
    selectSet.add(el);
  }
}

看看效果,很完美。

結(jié)尾

矩形相交是分離軸定理相交算法的特殊情況。

責(zé)任編輯:姜華 來(lái)源: 前端西瓜哥
相關(guān)推薦

2023-10-19 10:12:34

圖形編輯器開(kāi)發(fā)縮放圖形

2023-01-18 08:30:40

圖形編輯器元素

2023-09-07 08:24:35

圖形編輯器開(kāi)發(fā)繪制圖形工具

2023-09-26 07:39:21

2024-01-08 08:30:05

光標(biāo)圖形編輯器開(kāi)發(fā)游標(biāo)

2023-09-11 09:02:31

圖形編輯器模塊間的通信

2023-07-31 08:46:07

圖形編輯器圖形自動(dòng)對(duì)齊

2023-08-28 08:10:50

Hex圖形編輯器

2023-10-08 08:11:40

圖形編輯器快捷鍵操作

2023-10-10 16:04:30

圖形編輯器格式轉(zhuǎn)換

2023-02-09 07:02:30

圖形編輯器修改圖形

2023-02-06 16:59:57

Canvas編輯器

2023-04-07 08:02:30

圖形編輯器對(duì)齊功能

2023-02-01 09:21:59

圖形編輯器標(biāo)尺

2023-07-07 13:56:01

圖形編輯器畫(huà)布縮放

2024-01-03 08:43:17

圖形編輯器旋轉(zhuǎn)控制點(diǎn)縮放控制點(diǎn)

2023-02-02 14:07:00

圖形編輯器Canvas

2023-04-10 08:45:44

圖形編輯器排列移動(dòng)功能

2023-05-09 08:15:32

圖形編輯器撤銷重做功能

2024-11-27 09:02:01

文本編輯canvas圖形編輯器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)