最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端JS可視化學習利用向量判斷多邊形邊界

 更新時間:2023年12月01日 09:04:40   作者:beckyyyy  
這篇文章主要為大家介紹了前端JS可視化學習利用向量判斷多邊形邊界示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

繼續(xù)鞏固我的可視化學習,向量運算是計算機圖形學的基礎,本例依舊是向量的一種應用,利用向量判斷多邊形邊界,但是多邊形的邊界判斷稍微有點復雜,所以除了應用向量之外,還需要借助三角剖分的相關工具。這個例子中可視化的展示采用Canvas2D來實現。

問題

假設Canvas畫布上存在一個如下多邊形:

我們移動鼠標的時候,想要實現一個效果,就是當鼠標移動到多邊形內部的時候,將多邊形內部的填充顏色更新成其他顏色;所以此時我們需要判斷鼠標是否在多邊形內部,這就涉及到多邊形邊界的判斷。

思路

首先我們先將這個多邊形繪制到Canvas畫布上。

<canvas width="512" height="512"></canvas>
canvas {
  width: 512px;
  height: 512px;
  border: 1px solid #eee;
}
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.scale(1, -1);
const vertices = [
    [ -179.2, 128 ],
    [ -102.4, 76.8 ],
    [ -64, 181.76 ],
    [ -25.6, 143.36 ],
    [ -25.6, 33.28 ],
    [ 102.4, 53.76 ],
    [ 0, -153.6 ],
    [ -76.8, -76.8 ],
    [ -153.6, -76.8 ],
    [ -115.2, 0 ]
];
drawPolygon(vertices);
function drawPolygon(vertices, fillStyle = "red") {
  ctx.beginPath();
  ctx.moveTo(...vertices[0]);
  for (let i = 1; i < vertices.length; i ++) {
      ctx.lineTo(...vertices[i]);
  }
  ctx.closePath();
  ctx.fillStyle = fillStyle;
  ctx.fill();
}

1. 調用API

對于Canvas2D而言,有一個API自帶的方法,就是CanvasRenderingContext2D的isPointInPath方法。

這個方法使用起來非常簡單,我們在這個時候直接增加一個鼠標移動事件的監(jiān)聽就可以。

const {left, top} = canvas.getBoundingClientRect();
canvas.addEventListener('mousemove', e =&gt; {
  const {x: pageX, y: pageY} = e;
  // 坐標轉化
  const offsetX = x - left;
  const offsetY = y - top;
  // 清除畫布
  ctx.clearRect(-256, -256, 512, 512);
  if (ctx.isPointInPath(offsetX, offsetY)) {
    drawPolygon(vetices, "green");
  } else {
    drawPolygon(vetices);
  }
});

但是這個API的使用存在很大的局限性,就是它只能針對當前繪制的圖形生效。

就比如說,如果在完成這個多邊形的繪制之后,又繪制了一個小三角形。

const triangle = [
  [100, 100], 
  [100, 200], 
  [150, 200]
];
drawPolygon(triangle, "blue");

為了保持這個小三角形,我們還需要修改鼠標監(jiān)聽事件,以達到更新畫布時,三角形依舊被繪制。

canvas.addEventListener('mousemove', e => {
  const {pageX: x, pageY: y} = e;
  // 坐標轉化
  const offsetX = x - left;
  const offsetY = y - top;
  // 清除畫布
  ctx.clearRect(-256, -256, 512, 512);
  if (ctx.isPointInPath(offsetX, offsetY)) {
    drawPolygon(vertices, "green");
    drawPolygon(triangle, "blue");
  } else {
    drawPolygon(vertices);
    drawPolygon(triangle, "blue");
  }
});

此時我們再移動鼠標,就會發(fā)現,在鼠標移動到多邊形內部時,多邊形的填充顏色并不會變,但是當鼠標移動到小三角形內部時,多邊形的填充色發(fā)生了變化;這就是Canvas2D Context的isPointInPath方法所存在的局限性。

2. 自定義isPointInPath

為了突破Canvas2D API中自帶方法的局限性,最簡單的方法就是,我們手動自定義一個自己的isPointInPath方法。

具體實現如下:

function isPointInPath(x, y) {
  // 根據ctx重新clone一個新的Canvas對象
  const cloned =  ctx.canvas.cloneNode().getContext('2d');
  cloned.translate(canvas.width / 2, canvas.height / 2);
  cloned.scale(1, -1);
  let ret = false;
  // 繪制多邊形,判斷點是否在圖形內部
  drawPolygon(cloned, vertices, "red");
  ret |= cloned.isPointInPath(x, y);
  if (!ret) {
    // 如果不在,繼續(xù)繪制小三角形,判斷點是否在圖形內部
    drawPolygon(cloned, triangle, "blue");
    ret |= cloned.isPointInPath(x, y);
  }
  return ret;
}
  • 首先,根據原畫布的Context創(chuàng)建一個新的Canvas對象并獲取它的上下文
  • 然后繪制多邊形,并判斷鼠標是否在多邊形內部
  • 如果不在多邊形內部,繼續(xù)判斷是否在三角形內部
  • 最后將結果返回

可以看到,在這個自定義的方法內部,我們依然是調用了Canvas2D Context的isPointInPath方法。

接著我們還需要修改鼠標的監(jiān)聽事件,把判斷方法改為我們自定義的isPointInPath。

此時移動鼠標,可以看到,當鼠標移動到多邊形或者三角形內部,都可以使多邊形的填充色發(fā)生變化;這就是因為我們在自定義的isPointInPath中做的兩次判斷。

但是我們也能發(fā)現,雖然這種方式解決了我們在第一種方式中所碰到的問題,卻也存在其他問題,第一,是增加了很多無謂的Canvas繪圖操作;第二,是通用性差,如果圖形有修改,那么isPointInPath方法就要跟著修改,并且這個方法依賴于Canvas2D的API,如果哪天修改了繪圖方式,比如改為使用WebGL,就不能使用了。

3. 通用型isPointInPath

所以我們需要實現一個更具通用性的isPointInPath方法:直接通過點與幾何圖形的數學關系來判斷點是否在圖形內,也就是我們標題中所說的利用向量來判斷。

但是直接判斷點與幾何圖形的關系,還是比較困難的。這個時候,我們可以先對多邊形進行三角剖分,三角剖分可以簡單地理解為是把多邊形表示成由多個三角形組合而成的形式;接著將點和對應的多個三角形的關系進行逐一判斷;最后得出結果。

對于三角剖分,涉及的算法稍復雜,這里我們直接使用一個成熟的、使用起來比較簡單的庫——earcut;然后就剩下最關鍵的一步,就是點和三角形的位置判斷。

判斷點是否在三角形內部,就相對比較簡單了:

假設三角形的三個點是A、B、C,把三角形的三條邊分別使用向量表示,再將平面上一個點D連接三角形三個頂點得到三個向量,那么點D在三角形內部的充分必要條件就是:

AB x AD、BC x BD、CA x CD三組向量的叉乘結果符號相同。就如下圖所示。

  • 如果點在三角形內部,就如圖上的點D,可以看出AB 到 AD、BC 到 BD、CA 到 CD的旋轉方向都是逆時針,旋轉方向相同,所以最后的叉乘結果符號都是相同的;
  • 而如果點在三角形外部,就如圖上的點D',可以看出AB到AD'和CA到CD'的旋轉方向是逆時針,但BC到BD'的旋轉方向是順時針,所以三組向量叉乘的結果符號并不相同

因此根據上述條件,就可以定義一個簡單的判定函數:

function inTriangle(p1, p2, p3, point) {
    const a = p2.copy().minus(p1);
    const b = p3.copy().minus(p2);
    const c = p1.copy().minus(p3);

    const u1 = point.copy().minus(p1);
    const u2 = point.copy().minus(p2);
    const u3 = point.copy().minus(p3);

    const s1 = Math.sign(a.cross(u1));
    const s2 = Math.sign(b.cross(u2));
    const s3 = Math.sign(c.cross(u3));

    return s1 === s2 && s2 === s3;
}

這個函數的前三個參數是三角形的三個頂點,最后一個參數是待判斷的點;這樣就能判斷點是否在三角形內部了。

但是這個函數中還缺少一種特殊情況的判斷,就是點恰好在三角形某條邊上的情況。

如果一個點在三角形的一條邊上,那它需要滿足以下2個條件:

第一,它和所在邊某個頂點形成的向量與這個頂點所在邊的向量,這兩個向量的叉乘結果為0,即這兩個向量的夾角為180度或0度。比如點D在邊AB上,則AB x AD為0

第二,它和這個頂點形成的向量與這個頂點所在邊的向量,這兩個向量的點乘結果除以邊長的平方,結果大于等于0且小于等于1。比如點D在邊AB上,則0<= AB·AD/AB² <=1

這個值也就是AD在AB上的投影的長度,與AB長度的比值,大于零,說明兩個向量的夾角是0度,為同一方向,小于等于1,也就說明點D在線段AB上。

根據這兩個條件,我們可以對上面的判定函數進行優(yōu)化:

function inTriangle(p1, p2, p3, point) {
    const a = p2.copy().minus(p1);
    const b = p3.copy().minus(p2);
    const c = p1.copy().minus(p3);
    const u1 = point.copy().minus(p1);
    const u2 = point.copy().minus(p2);
    const u3 = point.copy().minus(p3);
    const s1 = Math.sign(a.cross(u1));
    let p = a.dot(u1) / a.length ** 2;
    if (s1 === 0 && p >= 0 && p <= 1) return true;
    const s2 = Math.sign(b.cross(u2));
    p = b.dot(u2) / b.length ** 2;
    if (s2 === 0 && p >= 0 && p <= 1) return true;
    const s3 = Math.sign(c.cross(u3));
    p = c.dot(u3) / c.length ** 2;
    if(s3 === 0 && p >= 0 && p <= 1) return true;
    return s1 === s2 && s2 === s3;
}

這樣我們就可以使用inTriangle函數對某個點是否在三角形內部進行判斷了。

現在我們來繼續(xù)完成對點在多邊形內部的判斷:

首先使用earcut庫對多邊形進行三角剖分處理

引入earcut庫

<script src="https://unpkg.com/earcut@2.2.4/dist/earcut.dev.js"></script>

因為earcut庫只接受扁平化的頂點數據,我們需要先用數組的flat方法將頂點扁平化

const points = vertices.flat();

然后我們就可以把扁平化后的數據傳給earcut進行處理了

const triangles = earcut(points);
console.log(triangles);

根據打印結果,可以看到earcut的處理結果是一個數組,這個triangles數組的元素是頂點數據在vertices數組中的下標;在這個數組中,每三個元素所對應的頂點就構成一個三角形。

這樣我們就完成了多邊形的三角剖分。

接著逐個判斷點是否在每個三角形內部。

// 判斷點是否在多邊形內部
// 將多邊形進行三角剖分,然后判斷點是否在其中某個三角形內部
function isPointInPolygon({vertices, cells}, point) {
    let ret = false;
    for(let i = 0; i < cells.length; i += 3) {
        const p1 = new Vector2D(...vertices[cells[i]]);
        const p2 = new Vector2D(...vertices[cells[i + 1]]);
        const p3 = new Vector2D(...vertices[cells[i + 2]]);
        if (inTriangle(p1, p2, p3, point)) {
            ret = true;
            break;
        }
    }
    return ret;
}

根據返回的布爾值就可以知道點是否在多邊形內部。

最后就是修改鼠標監(jiān)聽事件的處理程序。

const {left, top} = canvas.getBoundingClientRect();
canvas.addEventListener('mousemove', e => {
  const {pageX: x, pageY: y} = e;
  // 坐標轉化
  const offsetX = x - left;
  const offsetY = y - top;
  ctx.clearRect(-256, -256, 512, 512);

  const point = new Vector2D((offsetX - canvas.width / 2), (canvas.height / 2 - offsetY)); // 因為Canvas經過坐標轉換,所以這里需要把頁面上點的坐標也轉換一遍,才能正常判斷
  if (isPointInPolygon({
        vertices,
        cells: triangles
      }, point)
  ) {
    drawPolygon(vertices, "green");
    drawPolygon(triangle, "blue");
  } else {
    drawPolygon(vertices);
    drawPolygon(triangle, "blue");
  }
});

這里需要注意,Canvas2D自帶的API在進行判斷時,應該是自動對鼠標對應的點的坐標進行了轉換,所以我們使用自定義的方法時,不能直接使用offsetX和offsetY,需要自己去將點的坐標根據坐標系的轉換計算出對應在畫布上的坐標。

此時,我們再去移動鼠標,就可以看到,當鼠標移動到多邊形內部或者多邊形的邊時,多邊形的填充色發(fā)生了改變,也就說明我們的判斷生效了;這就成功應用了向量來判斷多邊形邊界。

以上就是前端JS可視化學習利用向量判斷多邊形邊界的詳細內容,更多關于前端JS可視化學的資料請關注腳本之家其它相關文章!

相關文章

  • js使用對象直接量創(chuàng)建對象的代碼

    js使用對象直接量創(chuàng)建對象的代碼

    js使用對象直接量創(chuàng)建對象的代碼...
    2007-09-09
  • TypeScript中never類型的妙用詳解

    TypeScript中never類型的妙用詳解

    TypeScript?是一種靜態(tài)類型的編程語言,它可以讓我們在編寫?JavaScript?代碼時,提前發(fā)現并避免一些潛在的錯誤,本文主要為大家介紹了TypeScript中never類型的妙用,感興趣的可以了解下
    2023-11-11
  • JS使用JSON.parse(),JSON.stringify()實現對對象的深拷貝功能分析

    JS使用JSON.parse(),JSON.stringify()實現對對象的深拷貝功能分析

    這篇文章主要介紹了JS使用JSON.parse(),JSON.stringify()實現對對象的深拷貝功能,結合實例形式分析了JSON.parse()與JSON.stringify()方法實現深拷貝的相關實現技巧與操作注意事項,需要的朋友可以參考下
    2019-03-03
  • Web端選擇本地文件的幾種方式匯總

    Web端選擇本地文件的幾種方式匯總

    在開發(fā)中經常需要實現本地文件選擇功能,無論是上傳圖片、視頻,還是處理批量文件,Web端提供了多種方式來實現這一需求,每種方式都有其獨特的優(yōu)缺點和適用場景,本文將詳細總結Web端選擇本地文件的幾種方式,需要的朋友可以參考下
    2025-04-04
  • JavaScript實現模態(tài)對話框實例

    JavaScript實現模態(tài)對話框實例

    這篇文章主要介紹了JavaScript實現模態(tài)對話框實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-01-01
  • JS實現的自定義map方法示例

    JS實現的自定義map方法示例

    這篇文章主要介紹了JS實現的自定義map方法,結合實例形式分析了javascript自定義map相關的json數組定義、遍歷、添加、刪除、讀取等相關操作技巧,需要的朋友可以參考下
    2019-05-05
  • 使用javascript實現頁面定時跳轉總結篇

    使用javascript實現頁面定時跳轉總結篇

    下面對使用JavaScript實現頁面定時跳轉做一下總結,各種定時跳轉代碼記錄如下,希望對大家有所幫助
    2013-09-09
  • 基于JS實現的隨機數字抽簽實例

    基于JS實現的隨機數字抽簽實例

    本文分享了基于JS實現的隨機數字抽簽的實例代碼。小編認為具很好的參考價值,感興趣的朋友可以看下
    2016-12-12
  • JavaScript基本語法_動力節(jié)點Java學院整理

    JavaScript基本語法_動力節(jié)點Java學院整理

    這篇文章主要介紹了JavaScript基本語法,適合剛入門的同學,有興趣的可以了解下。
    2017-06-06
  • 原生Javascript插件開發(fā)實踐

    原生Javascript插件開發(fā)實踐

    本文主要介紹了原生Javascript插件開發(fā)實踐思路與代碼,具有一定的參考價值,下面跟著小編一起來看下吧
    2017-01-01

最新評論

林口县| 廉江市| 民丰县| 周宁县| 阳东县| 岳池县| 水城县| 麦盖提县| 恩施市| 巧家县| 巍山| 格尔木市| 青岛市| 东源县| 渑池县| 南城县| 梅州市| 绍兴市| 泗洪县| 海安县| 石景山区| 朝阳区| 塘沽区| 恩平市| 旌德县| 库车县| 齐河县| 新绛县| 余庆县| 德州市| 沙田区| 云龙县| 浮山县| 青神县| 偏关县| 竹山县| 赞皇县| 汝城县| 交口县| 亳州市| 阆中市|