簡體   English   中英

如何檢測貝塞爾曲線和圓形物體之間的碰撞?

[英]How to detect collision between object made of bezier curves and a circle?

在此輸入圖像描述

所以我寫了一個微生物動畫。 這一切都很酷,但我認為如果微生物能夠吃硅藻並破壞氣泡會更好。

問題是微生物是由貝塞爾曲線組成的。 我不知道如何以合理的方式檢查由貝塞爾曲線構成的物體與圓形之間的碰撞。 我想到的唯一一件事是繪制微生物形狀並為隱藏的畫布起泡,然后檢查它們是否繪制到相同的像素。 但這會導致哇哇大的性能問題。

代碼: https//codepen.io/michaelKurowski/pen/opWeKY

class Cell是單元格,而class CellWallNodeclass CellWallNode曲線的節點,以防有人需要查找實現。

氣泡和硅藻可以很容易地簡化為圓形。

綁定beziers定義的測試對象的解決方案

下面是一個示例解決方案,用於查找圓是否在由中心點和定義周邊的一組貝塞爾定義的對象內。

該解決方案僅針對非交叉立方貝塞爾曲線進行了測試。 如果被測對象與單元格中心之間有兩個以上的截距,也無法工作。 但是,代碼中需要解決更復雜邊界的問題。

方法

  1. 定義要作為2D點進行測試的中心點
  2. 將測試點定義為2D點
  3. 定義從中心到測試點的line
  4. 對於每個bezier
  5. 翻譯bezier所以第一點是在行的開頭
  6. 旋轉貝塞爾曲線,使線與x軸對齊
  7. 求解貝塞爾多項式以找到根(x軸截距的位置)
  8. 使用根在線截距的貝塞爾曲線上找到位置。
  9. 使用距離點最近的截距來查找從中心到周邊的距離。
  10. 如果周長距離大於測試點距離加半徑則在內部。

筆記

測試是沿着到中心的線的點而不是圓形,該圓形是由三角形定義的區域。 只要圓弧半徑與貝塞爾曲線的尺寸相比較小,則近似效果很好。

不確定您是使用立方或二次貝塞爾曲線,因此解決方案涵蓋了立方和二次貝塞爾曲線。

該片段圍繞中心點創建了一組貝塞爾曲線(立方體)。 theBlob擁有動畫theBlob的對象。 函數testBlob測試鼠標位置,如果在theBlob內,則返回true。 對象bezHelper包含解決問題所需的所有功能。

立方根求解器來自github交叉點立方根求解器。

 const bezHelper = (()=>{ // creates a 2D point const P2 = (x=0, y= x === 0 ? 0 : xy + (x = xx, 0)) => ({x, y}); const setP2As = (p,pFrom) => (px = pFrom.x, py = pFrom.y, p); // To prevent heap thrashing close over some pre defined 2D points const v1 = P2(); const v2 = P2(); const v3 = P2(); const v4 = P2(); var u,u1,u2; // solves quadratic for bezier 2 returns first root function solveBezier2(A, B, C){ // solve the 2nd order bezier equation. // There can be 2 roots, u,u1 hold the results; // 2nd order function a+2(-a+b)x+(a-2b+c)x^2 a = (A - 2 * B + C); b = 2 * ( - A + B); c = A; a1 = 2 * a; c = b * b - 4 * a * c; if(c < 0){ u = Infinity; u1 = Infinity; return u; }else{ b1 = Math.sqrt(c); } u = (-b + b1) / a1; u1 = (-b - b1) / a1; return u; } // solves cubic for bezier 3 returns first root function solveBezier3(A, B, C, D){ // There can be 3 roots, u,u1,u2 hold the results; // Solves 3rd order a+(-2a+3b)t+(2a-6b+3c)t^2+(-a+3b-3c+d)t^3 Cardano method for finding roots // this function was derived from http://pomax.github.io/bezierinfo/#intersections cube root solver // Also see https://en.wikipedia.org/wiki/Cubic_function#Cardano.27s_method function crt(v) { if(v<0) return -Math.pow(-v,1/3); return Math.pow(v,1/3); } function sqrt(v) { if(v<0) return -Math.sqrt(-v); return Math.sqrt(v); } var a, b, c, d, p, p3, q, q2, discriminant, U, v1, r, t, mp3, cosphi,phi, t1, sd; u2 = u1 = u = -Infinity; d = (-A + 3 * B - 3 * C + D); a = (3 * A - 6 * B + 3 * C) / d; b = (-3 * A + 3 * B) / d; c = A / d; p = (3 * b - a * a) / 3; p3 = p / 3; q = (2 * a * a * a - 9 * a * b + 27 * c) / 27; q2 = q / 2; a /= 3; discriminant = q2 * q2 + p3 * p3 * p3; if (discriminant < 0) { mp3 = -p / 3; r = sqrt(mp3 * mp3 * mp3); t = -q / (2 * r); cosphi = t < -1 ? -1 : t > 1 ? 1 : t; phi = Math.acos(cosphi); t1 = 2 * crt(r); u = t1 * Math.cos(phi / 3) - a; u1 = t1 * Math.cos((phi + 2 * Math.PI) / 3) - a; u2 = t1 * Math.cos((phi + 4 * Math.PI) / 3) - a; return u; } if(discriminant === 0) { U = q2 < 0 ? crt(-q2) : -crt(q2); u = 2 * U - a; u1 = -U - a; return u; } sd = sqrt(discriminant); u = crt(sd - q2) - crt(sd + q2) - a; return u; } // get a point on the bezier at pos ( from 0 to 1 values outside this range will be outside the bezier) // p1, p2 are end points and cp1, cp2 are control points. // ret is the resulting point. If given it is set to the result, if not given a new point is created function getPositionOnBez(pos,p1,p2,cp1,cp2,ret = P2()){ if(pos === 0){ ret.x = p1.x; ret.y = p1.y; return ret; }else if(pos === 1){ ret.x = p2.x; ret.y = p2.y; return ret; } v1.x = p1.x; v1.y = p1.y; var c = pos; if(cp2 === undefined){ v2.x = cp1.x; v2.y = cp1.y; v1.x += (v2.x - v1.x) * c; v1.y += (v2.y - v1.y) * c; v2.x += (p2.x - v2.x) * c; v2.y += (p2.y - v2.y) * c; ret.x = v1.x + (v2.x - v1.x) * c; ret.y = v1.y + (v2.y - v1.y) * c; return ret; } v2.x = cp1.x; v2.y = cp1.y; v3.x = cp2.x; v3.y = cp2.y; v1.x += (v2.x - v1.x) * c; v1.y += (v2.y - v1.y) * c; v2.x += (v3.x - v2.x) * c; v2.y += (v3.y - v2.y) * c; v3.x += (p2.x - v3.x) * c; v3.y += (p2.y - v3.y) * c; v1.x += (v2.x - v1.x) * c; v1.y += (v2.y - v1.y) * c; v2.x += (v3.x - v2.x) * c; v2.y += (v3.y - v2.y) * c; ret.x = v1.x + (v2.x - v1.x) * c; ret.y = v1.y + (v2.y - v1.y) * c; return ret; } const cubicBez = 0; const quadraticBez = 1; const none = 2; var type = none; // working bezier const p1 = P2(); const p2 = P2(); const cp1 = P2(); const cp2 = P2(); // rotated bezier const rp1 = P2(); const rp2 = P2(); const rcp1 = P2(); const rcp2 = P2(); // translate and rotate bezier function transformBez(pos,rot){ const ax = Math.cos(rot); const ay = Math.sin(rot); var x = p1.x - pos.x; var y = p1.y - pos.y; rp1.x = x * ax - y * ay; rp1.y = x * ay + y * ax; x = p2.x - pos.x; y = p2.y - pos.y; rp2.x = x * ax - y * ay; rp2.y = x * ay + y * ax; x = cp1.x - pos.x; y = cp1.y - pos.y; rcp1.x = x * ax - y * ay; rcp1.y = x * ay + y * ax; if(type === cubicBez){ x = cp2.x - pos.x; y = cp2.y - pos.y; rcp2.x = x * ax - y * ay; rcp2.y = x * ay + y * ax; } } function getPosition2(pos,ret){ return getPositionOnBez(pos,p1,p2,cp1,undefined,ret); } function getPosition3(pos,ret){ return getPositionOnBez(pos,p1,p2,cp1,cp2,ret); } const API = { getPosOnQBez(pos,p1,cp1,p2,ret){ return getPositionOnBez(pos,p1,p2,cp1,undefined,ret); }, getPosOnCBez(pos,p1,cp1,cp2,p2,ret){ return getPositionOnBez(pos,p1,p2,cp1,cp2,ret); }, set bezQ(points){ setP2As(p1, points[0]); setP2As(cp1, points[1]); setP2As(p2, points[2]); type = quadraticBez; }, set bezC(points){ setP2As(p1, points[0]); setP2As(cp1, points[1]); setP2As(cp2, points[2]); setP2As(p2, points[3]); type = cubicBez; }, isInside(center, testPoint, pointRadius){ drawLine(testPoint , center); v1.x = (testPoint.x - center.x); v1.y = (testPoint.y - center.y); const pointDist = Math.sqrt(v1.x * v1.x + v1.y * v1.y) const dir = -Math.atan2(v1.y,v1.x); transformBez(center,dir); if(type === cubicBez){ solveBezier3(rp1.y, rcp1.y, rcp2.y, rp2.y); if (u < 0 || u > 1) { u = u1 } if (u < 0 || u > 1) { u = u2 } if (u < 0 || u > 1) { return } getPosition3(u, v4); }else{ solveBezier2(rp1.y, rcp1.y, rp2.y); if (u < 0 || u > 1) { u = u1 } if (u < 0 || u > 1) { return } getPosition2(u, v4); } drawCircle(v4); const dist = Math.sqrt((v4.x - center.x) ** 2 + (v4.y - center.y) ** 2); const dist1 = Math.sqrt((v4.x - testPoint.x) ** 2 + (v4.y - testPoint.y) ** 2); return dist1 < dist && dist > pointDist - pointRadius; } } return API; })(); const ctx = canvas.getContext("2d"); const m = {x : 0, y : 0}; document.addEventListener("mousemove",e=>{ var b = canvas.getBoundingClientRect(); mx = e.pageX - b.left - scrollX - 2; my = e.pageY - b.top - scrollY - 2; }); function drawCircle(p,r = 5,col = "black"){ ctx.beginPath(); ctx.strokeStyle = col; ctx.arc(px,py,r,0,Math.PI*2) ctx.stroke(); } function drawLine(p1,p2,r = 5,col = "black"){ ctx.beginPath(); ctx.strokeStyle = col; ctx.lineTo(p1.x,p1.y); ctx.lineTo(p2.x,p2.y); ctx.stroke(); } const w = 400; const h = 400; const diag = Math.sqrt(w * w + h * h); // creates a 2D point const P2 = (x=0, y= x === 0 ? 0 : xy + (x = xx, 0)) => ({x, y}); const setP2As = (p,pFrom) => (px = pFrom.x, py = pFrom.y, p); // random int and double const randI = (min, max = min + (min = 0)) => (Math.random()*(max - min) + min) | 0; const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min; const theBlobSet = []; const theBlob = []; function createCubicBlob(segs){ const step = Math.PI / segs; for(var i = 0; i < Math.PI * 2; i += step){ const dist = rand(diag * (1/6), diag * (1/5)); const ang = i + rand(-step * 0.2,step * 0.2); const p = P2( w / 2 + Math.cos(ang) * dist, h / 2 + Math.sin(ang) * dist ); theBlobSet.push(p); theBlob.push(P2(p)); } theBlobSet[theBlobSet.length -1] = theBlobSet[0]; theBlob[theBlobSet.length -1] = theBlob[0]; } createCubicBlob(8); function animateTheBlob(time){ for(var i = 0; i < theBlobSet.length-1; i++){ const ang = Math.sin(time + i) * 6; theBlob[i].x = theBlobSet[i].x + Math.cos(ang) * diag * 0.04; theBlob[i].y = theBlobSet[i].y + Math.sin(ang) * diag * 0.04; } } function drawTheBlob(){ ctx.strokeStyle = "black"; ctx.lineWidth = 3; ctx.beginPath(); var i = 0; ctx.moveTo(theBlob[i].x,theBlob[i++].y); while(i < theBlob.length){ ctx.bezierCurveTo( theBlob[i].x,theBlob[i++].y, theBlob[i].x,theBlob[i++].y, theBlob[i].x,theBlob[i++].y ); } ctx.stroke(); } var center = P2(w/2,h/2); function testBlob(){ var i = 0; while(i < theBlob.length-3){ bezHelper.bezC = [theBlob[i++], theBlob[i++], theBlob[i++], theBlob[i]]; if(bezHelper.isInside(center,m,6)){ return true; } } return false; } // main update function function update(timer){ ctx.clearRect(0,0,w,h); animateTheBlob(timer/1000) drawTheBlob(); if(testBlob()){ ctx.strokeStyle = "red"; }else{ ctx.strokeStyle = "black"; } ctx.beginPath(); ctx.arc(mx,my,5,0,Math.PI*2) ctx.stroke(); requestAnimationFrame(update); } requestAnimationFrame(update); 
 canvas { border : 2px solid black; } 
 <canvas id="canvas" width = "400" height = "400"></canvas> 

我已經創建了一個氣泡動畫,其中圓圈將擴展為小於50px的鼠標。 所以這就是訣竅。 你可以簡單地用microbe's X and Y coordinates改變mouseXmouseYmicrobe's X and Y coordinates radius of your microbe 50 當我的氣泡變大時,你就可以消滅氣泡。 這是我的動畫的鏈接。 https://ankittorenzo.github.io/canvasAnimations/Elements/Bubbles/

這是我的GitHub代碼的鏈接。 https://github.com/AnkitTorenzo/canvasAnimations/blob/master/Elements/Bubbles/js/main.js

讓我知道你是否有任何問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM