ホームページ > 記事 > ウェブフロントエンド > キャンバス内のランダムな点に基づいて多角形を描画する方法
この記事は主に、キャンバス内のランダムな点に基づいてポリゴンを描画する方法に関する関連情報を紹介します。内容は非常に優れているので、参考として共有します。
原因
今日、「HTML5+Javascriptアニメーションの基礎」という本を勉強していたとき、第8章の3番目のセクションで、3本のバネを使って3点を結び、ストレッチ動作を実行する方法について話しました。
例を終えた後、4点や5点だったらどうなるかを考えました。
コードを書き直してポイント数を可変にしました。最終的な効果は、バランスをとるために各ポイントの最終的なストレッチ動作を実現することですが、ポイント間の接続はあまり見栄えがよくなく、いくつかは交差しています。
そこで、この領域を最適化できないか考えました。
接続を回転させます
前の例の点はすべてランダムな位置にあるため、接続は制御できません。ということで、まずはこれから始めたいと思います。
まず、ある点を基準点として使用し、この点に対する他の点の角度を取得します。
次に、これらの点を小さい角度から大きい角度まで角度に応じて接続すると、正多角形を描くことができます。
おおよその実装コードは以下の通りです:
let balls = []; let ballNum = 6; let firstBall = null; while(ballNum--) { let ball = new Ball(20, parseColor(Math.random() * 0xffffff)) ball.x = Math.random() * width; ball.y = Math.random() * height; balls.push(ball) if (!firstBall) { firstBall = ball ball.angle = 0 } else { const dx = ball.x - firstBall.x, dy = ball.y - firstBall.y; ball.angle = Math.atan2(dy, dx); } } // 尝试让球连线是一个正多边形 balls = balls.sort((ballA, ballB) => { return ballA.angle - ballB.angle })
このようにして、最後に接続を描画する際に、配列を横断して小さい角度から大きい角度まで描画することができます。
その効果は次のとおりです:
これにより、一線を越える状況を大幅に減らすことができますが、それでも完全に回避することはできません。
次に、Math.abs を使用して正の角度にするか、各点を結ぶ最小角度の点を見つけるなど、このソリューションの最適化を試みたいと思います。しかし結果はダメ、一線越えは避けられない。
中心点に基づいて回転
後で別のアイデアが思いつきました。多角形の中心点が決定できれば、中心点に対するすべての点の角度を個別に計算できます。ポイントは時計回りまたは反時計回りに接続できます。
しかし、インターネットで長時間検索した結果、すべての点アルゴリズムには特定の時計回りの順序で配置された一連の点が必要であることがわかりました。
でも、これらのポイントがあれば、すでに多角形を描くことができます。諦めざるを得ませんでした
X 軸極セグメンテーション
絶望のあまり、Google で検索しなければならなかったのですが、Zhihu で良い答えを見つけました: How to connect an unordered set of points on the plan into aシンプルなポリゴン?
具体的なアルゴリズムの説明については、答えを見てください。詳細には触れません。
ただし、上のチェーンと下のチェーンを接続する際は、実際には上のチェーンがX軸の降順に接続され、下のチェーンがX軸の昇順に接続されるようにするだけで済みます(反時計回りに描かれます)。 X 軸が同じ点については、Y 軸が大きいか小さいかは関係ありません。
実装される場合は、回答のアルゴリズムに従って厳密に実装されます。
点が上のチェーンに属するか、下のチェーンに属するかを判断するとき、最初に考えるべきことは、2つの点に基づいて直線の関数方程式を決定し、次に計算する点の座標を導入することです。しかし後になって、すべての点が左端の極を使用して斜角を計算し、それを角度の大きさに応じて分割するほうが視覚的に理解しやすいと思いました。
おおよそのコードは次のとおりです:
let balls = []; let tempBalls = []; let ballNum = 6; let isDragingBall = false; while(ballNum--) { let ball = new Ball(10, parseColor(Math.random() * 0xffffff)) ball.x = Math.random() * width; ball.y = Math.random() * height; tempBalls.push(ball) } // 让点按X轴升序排序 tempBalls = tempBalls.sort((ballA, ballB) => { return ballA.x - ballB.x }) // 找X轴左右极点 let firstBall = tempBalls[0], lastBall = tempBalls[tempBalls.length -1]; let smallXBalls = tempBalls.filter(ball => ball.x === firstBall.x), bigXBalls = tempBalls.filter(ball => ball.x === lastBall.x) // 处理左右极点有多个的情况 if (smallXBalls.length > 1) { smallXBalls.sort((ballA, ballB) => { return ballB.y - ballA.y }) } if (bigXBalls.length > 1) { bigXBalls.sort((ballA, ballB) => { return ballB.y - ballA.y }) } firstBall = smallXBalls[0] lastBall = bigXBalls[0] // 获得极点连线的角度 let splitLineAngle = Math.atan2(lastBall.y - firstBall.y, lastBall.x - firstBall.x); let upperBalls = [], lowerBalls = []; // 所有其他点跟firstBall计算角度 // 大于splitLineAngle的都是下链 // 其他是上链 tempBalls.forEach(ball => { if (ball === firstBall || ball === lastBall) { return false } let angle = Math.atan2(ball.y - firstBall.y, ball.x - firstBall.x); if (angle > splitLineAngle) { lowerBalls.push(ball) } else { upperBalls.push(ball) } }) // 处理X轴相同情况的排序 lowerBalls = lowerBalls.sort((ballA, ballB) => { if (ballA.x !== ballB.x) { return ballA.x - ballB.x } return ballB.y - ballA.y }) upperBalls = upperBalls.sort((ballA, ballB) => { if (ballA.x !== ballB.x) { return ballB.x - ballA.x } return ballB.y - ballB.x }) // 逆时针连接所有的点 balls = [firstBall].concat(lowerBalls, [lastBall], upperBalls) balls = balls.map((ball, i) => { ball.text = i + 1; return ball })
最終的に返されるボールは、反時計回りにソートされた多角形の点です。
効果は次のとおりです:
各ボールの内部ステータスは次のとおりです:
以上がこの記事の全内容であり、皆様の学習に役立つことを願っています。関連コンテンツについては、PHP 中国語 Web サイトにご注意ください。
関連する推奨事項:
html5 を使用する
Canvas は、echarts では実装できない円グラフをカプセル化します
HTML5 Canvas は、曲線を描画するメソッドを実装します
以上がキャンバス内のランダムな点に基づいて多角形を描画する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。