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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > windows >内容正文

windows

可视化学习:利用向量计算点到线段的距离并展示

發(fā)布時(shí)間:2023/11/23 windows 43 coder
生活随笔 收集整理的這篇文章主要介紹了 可视化学习:利用向量计算点到线段的距离并展示 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文可配合本人錄制的視頻一起食用。

引言

最近我在學(xué)可視化的東西,借此來(lái)鞏固一下學(xué)習(xí)的內(nèi)容,向量運(yùn)算是計(jì)算機(jī)圖形學(xué)的基礎(chǔ),這個(gè)例子就是向量的一種應(yīng)用,是利用向量來(lái)計(jì)算點(diǎn)到線段的距離,這個(gè)例子中可視化的展示采用Canvas2D來(lái)實(shí)現(xiàn)。

說(shuō)起向量,當(dāng)時(shí)一看到這個(gè)詞,我是一種很模糊的記憶;這些是中學(xué)學(xué)的東西,感覺(jué)好像都還給老師了。然后又說(shuō)起了向量的乘法,當(dāng)看到點(diǎn)積、叉積這兩個(gè)詞,我才猛然想起點(diǎn)乘和叉乘;但整體上還是模模糊糊的,不太記得兩者具體的定義了;就找資料快速過(guò)了一遍吧。

因?yàn)楸疚闹胁簧婕跋蛄康幕A(chǔ)知識(shí);如果有跟我一樣遺忘的小伙伴,可以找點(diǎn)視頻回憶一下,或者是找點(diǎn)資料看下。

題面

首先本次的例子中要獲取兩個(gè)值,一個(gè)是點(diǎn)到線段的距離,另一個(gè)是點(diǎn)到線段所在直線的距離。

假設(shè)存在一個(gè)線段AB,以及一個(gè)點(diǎn)C;則他們之前的位置可能有三種情況:

  • 點(diǎn)C在線段AB左側(cè)

  • 點(diǎn)C在線段AB的上方或下方

  • 點(diǎn)C在線段AB的右側(cè)

在第一種和第三種情況下,點(diǎn)C到線段AB的距離為點(diǎn)C到點(diǎn)A或點(diǎn)B的距離,即向量AC或向量BC的長(zhǎng)度。

在第二種情況下,點(diǎn)C到線段AB和到線段AB所在直線的距離是一樣的,這個(gè)時(shí)候,我們就可以利用向量的乘法來(lái)解決這個(gè)距離的計(jì)算。

這個(gè)例子給的思路是利用向量的乘法,因?yàn)橄蛄坎娉说膸缀我饬x就是平行四邊形的面積,已知底邊長(zhǎng)度,也就是線段AB的長(zhǎng)度,然后就可以得出點(diǎn)C到直線的距離;但因?yàn)橐陧?yè)面上展示出來(lái),所以我們需要求得點(diǎn)D的坐標(biāo)。

思路

一開(kāi)始我想的有點(diǎn)復(fù)雜,想要去求AB所在直線的函數(shù)方程,從而計(jì)算出點(diǎn)C是在直線的上方還是下方,雖然向量的叉乘我記得不太多了,但我依舊還記得,如果向量AB旋轉(zhuǎn)到向量CD為順時(shí)針,則向量AB叉乘向量CD的值就為正,如果是逆時(shí)針,就為負(fù)。

接著再利用叉乘和點(diǎn)乘,去計(jì)算點(diǎn)D的x坐標(biāo)和y坐標(biāo);這其實(shí)有點(diǎn)把事情搞復(fù)雜了,另外還需要去特殊處理CD和X軸平行以及Y軸平行的特殊情況。

然后我看了別人的提示才反應(yīng)過(guò)來(lái),我們只要充分地利用向量的乘法就可以了,而不需要去求什么直線的函數(shù)方程,當(dāng)然這也就不用考慮什么特殊情況。

由上圖可知AD是AC在AB上的投影,然后我們知道投影可以通過(guò)點(diǎn)乘來(lái)求得,要求兩個(gè)向量的點(diǎn)乘,有兩種計(jì)算方式,一種是通過(guò)坐標(biāo)來(lái)計(jì)算,另一種是通過(guò)向量的模和夾角來(lái)計(jì)算;分別對(duì)應(yīng)以下兩個(gè)公式:

  • AC · AB = AC.x * AB.x + AC.y * AB.y
  • AC · AB = |AC| * |AB| * cosθ

因?yàn)橐阎c(diǎn)A、點(diǎn)B和點(diǎn)C的坐標(biāo),所以我們可以利用以上兩個(gè)公式計(jì)算點(diǎn)D的坐標(biāo)。

具體實(shí)現(xiàn)

現(xiàn)在我們就來(lái)通過(guò)Canvas來(lái)實(shí)現(xiàn)以上效果。

HTML

首先我們?cè)贖TML中先放一個(gè)Canvas標(biāo)簽。

<canvas width="512" height="512"></canvas>

CSS

然后寫(xiě)一點(diǎn)簡(jiǎn)單的CSS樣式。

canvas {
  margin: 0;
  width: 512px;
  height: 512px;
  border: 1px solid #eee;
}

JavaScript

最后我們來(lái)編寫(xiě)最重要的JavaScript代碼。

這里預(yù)先定義了一個(gè)Vector2D的類用于表示二維向量。

/*
* 定義二維向量
* */
export default class Vector2D extends Array {
    constructor(x = 1, y = 0) {
        super(x, y);
    }
    get x() {
        return this[0];
    }
    set x(value) {
        this[0] = value;
    }
    get y() {
        return this[1];
    }
    set y(value) {
        this[1] = value;
    }
    // 獲取向量的長(zhǎng)度
    get len() {
        // x、y的平方和的平方根
        return Math.hypot(this.x, this.y);
    }
    // 獲取向量與X軸的夾角
    get dir() {
        // 向量與X軸的夾角
        return Math.atan2(this.y, this.x);
    }
    // 復(fù)制向量
    copy() {
        return new Vector2D(this.x, this.y);
    }
    // 向量的加法
    add(v) {
        this.x += v.x;
        this.y += v.y;
        return this;
    }
    // 向量旋轉(zhuǎn)
    rotate(rad) {
        const c = Math.cos(rad),
            s = Math.sin(rad);
        const [x, y] = this;

        this.x = x * c - y * s;
        this.y = x * s + y * c;

        return this;
    }
    scale(length) {
        this.x *= length;
        this.y *= length;

        return this;
    }
    // 向量的點(diǎn)乘
    dot(v) {
        return this.x * v.x + this.y * v.y;
    }
    // 向量的叉乘
    cross(v) {
        return this.x * v.y - v.x * this.y;
    }
    reverse() {
        return this.copy().scale(-1);
    }
    // 向量的減法
    minus(v) {
        return this.copy().add(v.reverse());
    }
    // 向量歸一化
    normalize() {
        return this.copy().scale(1 / this.len);
    }
}

x和y分別是向量的坐標(biāo),len獲取的是向量的長(zhǎng)度、利用了Math對(duì)象上的方法,dot和cross方法分別對(duì)應(yīng)的就是向量的點(diǎn)乘和叉乘。

接著就來(lái)編寫(xiě)功能代碼。

  • 首先是獲取canvas2d的上下文,并完成坐標(biāo)的轉(zhuǎn)換

    let canvas = document.querySelector('canvas'),
       ctx = canvas.getContext('2d');
    
    ctx.translate(canvas.width / 2, canvas.height / 2);
    ctx.scale(1, -1);
    

    因?yàn)楫?huà)布原始的坐標(biāo)系是以左上角為原點(diǎn),X軸向左,Y軸向下,這不符合我們?cè)跀?shù)學(xué)中常用的配置。

    這里我們先通過(guò)translate方法把坐標(biāo)挪到畫(huà)布中心,再通過(guò)scale方法將坐標(biāo)系繞X軸翻轉(zhuǎn);通過(guò)這樣的轉(zhuǎn)換,就可以按照我們?cè)跀?shù)學(xué)中常見(jiàn)的坐標(biāo)系來(lái)操作了。

  • 然后我們來(lái)初始化三個(gè)點(diǎn),也就是之前說(shuō)的點(diǎn)A、點(diǎn)B和點(diǎn)C。

    坐標(biāo)可以隨便寫(xiě),只要范圍在-256到256之間就可以。

    我這里就簡(jiǎn)單定義三個(gè)在X軸上的點(diǎn),并維護(hù)在一個(gè)Map中,方便后續(xù)在canvas上顯示三個(gè)點(diǎn)的標(biāo)識(shí);后面會(huì)加一個(gè)事件監(jiān)聽(tīng)來(lái)更新點(diǎn)C的坐標(biāo)。

    let map = new Map();
    let v0 = new Vector2D(0, 0),
        v1 = new Vector2D(100, 0),
        v2 = new Vector2D(-100, 0);
    map.set('C', v0);
    map.set('A', v1);
    map.set('B', v2);
    
  • 然后就可以開(kāi)始繪制

    這里我們定義一個(gè)draw函數(shù),然后調(diào)用它。

    draw();
    
    function draw() {}
    
    • 首先,為了看上去更清晰,我們可以把坐標(biāo)系繪制出來(lái)。

      因?yàn)榻酉氯ダL制的直線比較多,這里我簡(jiǎn)單封裝一個(gè)繪制直線的方法。

      function drawLine(start, end, color) {
        ctx.beginPath();
        ctx.save();
        ctx.lineWidth = '4px';
        ctx.strokeStyle = color;
        ctx.moveTo(...start);
        ctx.lineTo(...end);
        ctx.stroke();
        ctx.restore();
        ctx.closePath();
      }
      

      然后我們來(lái)繪制坐標(biāo)系。

      drawAxis();
      
      function drawAxis() {
        drawLine([-canvas.width / 2, 0], [canvas.width / 2, 0], "#333");
        drawLine([0, canvas.height / 2], [0, -canvas.height / 2], "#333");
      }
      
    • 接著我們把點(diǎn)繪制到畫(huà)布上

      for(const p of map) {
        drawPoint(p[1], p[0]);
      }
      
      function drawPoint(v, name, color='#333') {
        ctx.beginPath();
        ctx.save();
        ctx.fillStyle = color;
        ctx.arc(v.x, v.y, 2, 0, Math.PI * 2);
        ctx.scale(1, -1);
        ctx.fillText(`${name}`, v.x, 16 - v.y);
        ctx.restore();
        ctx.fill();
      }
      

      這里我們想把點(diǎn)的標(biāo)識(shí)通過(guò)fillText也繪制到畫(huà)布上,但由于之前坐標(biāo)被繞X軸翻轉(zhuǎn)過(guò)一次,所以直接繪制表示會(huì)導(dǎo)致文本是倒過(guò)來(lái)的,所以我們這里臨時(shí)把坐標(biāo)系翻轉(zhuǎn)回來(lái),完成文本繪制后,再通過(guò)restore恢復(fù)回去。

    • 現(xiàn)在我們把線段AB也繪制出來(lái)

      drawBaseline();
      
      function drawBaseline() {
        drawLine(map.get('A'), map.get('B'), "blue");
      }
      
    • 最后就是最關(guān)鍵的一步,把點(diǎn)C到線段AB和直線的距離求出來(lái)并展示在canvas畫(huà)布上

      d為點(diǎn)C到線段AB的距離,dLine為點(diǎn)C到直線的距離;

      result存儲(chǔ)的是AC和AB的點(diǎn)乘結(jié)果;crossProduct存儲(chǔ)的是AC和AB的叉乘結(jié)果。

      根據(jù)叉乘結(jié)果,我們就可以計(jì)算出dLine的值,也就是點(diǎn)C到直線的距離。

      drawLines();
      
      function drawLines() {
        let AC = map.get('C').minus(map.get('A'));
        let AB = map.get('B').minus(map.get('A'));
        let BC = map.get('C').minus(map.get('B'));
        let result = AC.dot(AB);
        let d, dLine; // distance
      
        let crossProduct = AC.cross(AB);
        dLine = Math.abs(crossProduct) / AB.len;
        let pd = getD();
        map.set('D', pd);
        if (result < 0) {
          // 角CAB為鈍角
          drawLine(map.get('A'), map.get('C'), 'red');
          drawLine(map.get('C'), pd, 'green');
          d = AC.len;
        } else if (result > Math.pow(AB.len, 2)) {
          // 角CBA為鈍角
          drawLine(map.get('B'), map.get('C'), 'red');
          drawLine(map.get('C'), pd, 'green');
          d = BC.len;
        } else {
          d = dLine;
          drawLine(map.get('C'), pd, 'red');
        }
      
        let text = `點(diǎn)C到線段AB的距離:${Math.floor(d)}, 點(diǎn)C到AB所在直線的距離為${Math.floor(dLine)}`;
        drawText(text);
      }
      
      function getD() {
        let AC = map.get('C').minus(map.get('A'));
        let AB = map.get('B').minus(map.get('A'));
        let A = map.get('A'); // 即:向量OA
        // 已知:AD為AC在AB上的投影
        // AD = (AB / |AB|) * (AC·AB / |AB|)
        //    = AB * (AC·AB / |AB|2) 
        // D.x - A.x = AD.x, D.y - A.y = AD.y
        let AD = AB.scale(AC.dot(AB) / AB.len**2);
        let D = new Vector2D(
          AD.x + A.x,
          AD.y + A.y
        );
        return D;
      }
      

      然后我們來(lái)計(jì)算點(diǎn)D的坐標(biāo):

      已知:AD是AC在AB上的投影。

      所以AD可以表示為這樣:(AB / |AB|) * (AC·AB / |AB|)

      向量AB除以AB的模即代表和向量AB同一方向夾角的單位向量,單位向量可以簡(jiǎn)單理解為長(zhǎng)度為1的向量;

      AC和AB的點(diǎn)積除以AB的模結(jié)果等于AC的模乘以兩個(gè)向量夾角的余弦值

      所以這兩個(gè)值相乘,就等于是向量AD。

      通過(guò)調(diào)整上面的公式,我們可以得到AD = AB * (AC·AB / |AB|2) ,因?yàn)锳、B、C的坐標(biāo)都已知,也就可以得到向量AD的坐標(biāo)。

      然后我們又知道向量AD的坐標(biāo)可以直接通過(guò)向量的減法得到,也就是:

      • AD.x = D.x - A.x
      • AD.y = D.y - A.y

      所以我們就可以得到點(diǎn)D的坐標(biāo),即(AD.x + A.x, AD.y + A.y)

      接著我們根據(jù)AC和AB的點(diǎn)乘結(jié)果result,來(lái)繪制相應(yīng)的直線。

      • 當(dāng)result為負(fù)數(shù)時(shí),說(shuō)明AC和AB夾角的余弦值大于90度

        即∠CAB為鈍角,說(shuō)明點(diǎn)C到線段AB的距離就是點(diǎn)C到點(diǎn)A的距離。

      • 而當(dāng)result大于AC長(zhǎng)度的平方,也就是AC的模乘以余弦值大于AB的模,也就是說(shuō),AC在向量AB上的投影大于AB的長(zhǎng)度

        那么此時(shí)∠CBA是鈍角,點(diǎn)C到線段AB的距離就是點(diǎn)C到點(diǎn)B的距離。

      • 當(dāng)result為0時(shí),說(shuō)明兩個(gè)向量互相垂直

        此時(shí),點(diǎn)C在線段AB的上方或下方,點(diǎn)C到線段AB的距離就是點(diǎn)C到直線的距離。也就是我們前面求到的dLine的值。

      最后我們將結(jié)果通過(guò)fillText方法繪制到屏幕上。

      function drawText(distance) {
        ctx.beginPath();
        ctx.save();
        ctx.font = "16px serif";
        ctx.scale(1, -1);
        ctx.fillText(`${distance}`, -250, 240);
        ctx.restore();
      }
      
    • 最后我們加一個(gè)鼠標(biāo)移動(dòng)事件,動(dòng)態(tài)地更新點(diǎn)C的坐標(biāo),以及點(diǎn)C到線段AB和直線的距離。

      initEvents();
      
      function initEvents() {
      	canvas.addEventListener('mousemove', e => {
          const rect = canvas.getBoundingClientRect();
          ctx.clearRect(-canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height);
          let x = e.pageX - rect.left - canvas.width / 2;
          let y = -(e.pageY - rect.top - canvas.height / 2);
          v0 = new Vector2D(x, y);
          map.set('C', v0);
          draw();
      	});
      }
      

    好啦,到這里為止一個(gè)簡(jiǎn)單的距離展示就完成了;我們可以通過(guò)移動(dòng)鼠標(biāo)來(lái)查看最后的效果。

總結(jié)

以上是生活随笔為你收集整理的可视化学习:利用向量计算点到线段的距离并展示的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 激情小说中文字幕 | 操欧美老逼 | xxx69美国| 97小视频| 久久久久人妻一道无码AV | 青青草99| 在线观看视频免费 | 337p日本大胆噜噜噜鲁 | 亚洲欧美在线一区二区 | 爱情岛论坛成人 | 日本中文字幕有码 | 男生插女生视频在线观看 | 男人女人拔萝卜视频 | 五月激情婷婷丁香 | 天堂av中文字幕 | 在线观看日韩视频 | 在线亚洲天堂 | 69av在线播放 | 欧美xxxx精品 | 国内自拍视频在线观看 | 亚洲人成小说 | 青草视频免费看 | 1级黄色大片 | 亚洲欧美一 | 美美女高清毛片视频免费观看 | 亚洲av无码精品色午夜果冻不卡 | 欧美一区二区三区公司 | 91成人在线观看喷潮动漫 | 国产成人精品123区免费视频 | 欧美毛片视频 | 亚洲国产一区在线观看 | 午夜写真片福利电影网 | 日本黄色短片 | 国产午夜亚洲精品午夜鲁丝片 | 日本视频不卡 | 日本91在线 | 成人涩涩网站 | 亚洲午夜精品一区二区三区他趣 | 波多野结衣视频在线播放 | 日本少妇作爱视频 | 亚洲 激情 小说 另类 欧美 | 中国一级大黄大黄大色毛片 | 玖玖色在线 | 香蕉在线观看视频 | 国产无套在线观看 | 69国产在线 | 国产精品天天av精麻传媒 | 欧美激情国产日韩精品一区18 | 91传媒入口 | 久久精品国产亚洲av成人 | 先锋影音在线 | 亚洲精品国产精品乱码桃花 | 国产精品乱码久久久久久 | 免费看成人片 | 欧美美女爱爱视频 | 丁香亚洲 | 97国产在线观看 | 国产野外作爱视频播放 | 69久久夜色精品国产69 | 97视频在线观看免费 | 欧美嫩交| 日韩成年视频 | 懂色视频在线观看 | 91在线免费网站 | 青青草久久| 婷婷综合一区 | 免费看av网 | 99久久久无码国产精品免费麻豆 | 日韩精品一区二区亚洲av性色 | 日本一区视频 | 黄色片视频免费 | 精品视频成人 | 色小说在线 | 日本做爰全过程免费看 | 亚洲人在线观看视频 | 国产精品第三页 | 91红桃视频 | 99久久国产热无码精品免费 | 一级裸体片 | 日本91在线 | 午夜理伦三级做爰电影 | 久久wwww | 成人国产在线观看 | 免费av地址| 天天操天天爽天天干 | 欧美 日韩 中文字幕 | 男人天堂成人网 | 在线三级av| 成人免费高清在线播放 | 五月色婷| 国产精品一二三区 | 久久精品中文 | www.亚洲激情 | 午夜激情成人 | aaa大片十八岁禁止 中文字幕亚洲在线观看 | 久久国产免费看 | 在线播放精品 | 欧美日日 | 日本japanese丰满白浆 |