質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
canvas

HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

解決済

2回答

1820閲覧

canvasで、2直線に接する円弧を描画したい

nekorisu8150

総合スコア11

canvas

HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

0グッド

2クリップ

投稿2018/08/05 06:44

canvasで、2直線に接する円弧を描画したい

HTMLのCANVAS要素を使って、ある点で交わる線分が2つあり、その2直線に接する円弧を描画し、AからBへカーブを描いて描画できるようにしたいです。
下記の画像のようなイメージです。
円の半径rを与えると、赤部分の弧を描画できるようにしたいです。
bezierCurveTo() かarc()を利用すればできるのかもしれないですが、そもそもどの値を引数に与えたらわからない状況です。
数学的な知識に乏しい為、ご助言いただけないでしょうか。
![イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

2つの直線に接する円弧を描画するのは提示されている通り、arc または bezierCurveTo で描画することが可能です。ただし、bezierCurveTo で描画する三次ベジェ曲線は完璧な円弧にはならず近似値となります。この辺については以下の解説サイトをご参照ください。
https://cat-in-136.github.io/2014/03/bezier-1-kappa.html

記号の説明

アルゴリズム的には高校数学レベルで解ける問題かと思いますが、プログラムに応用する場合、方向や座標系を意識せずに解くためにベクトルで計算して解くのがおすすめです。以下に示す手順となります。

1.ベクトルA、ベクトルBの単位ベクトルをそれぞれ求める。

2.ベクトルAとベクトルBの単位ベクトルの和から角度を2等分するベクトルMを求める。

3.ベクトルMの単位ベクトルを求める。

4.ベクトルAとベクトルMからcosθを求める。

5.cosθからsinθとtanθを求める。

6.半径rとtanθから隣辺の長さを求める。

7.ベクトルAとベクトルBの単位ベクトルを掛け算して図上のCAとCBの座標を求める。
ここまでの手順で bezierCurveTo によって疑似円弧を描画する情報は集まっています。

8.ベクトルMの単位ベクトル、半径rおよびsinθから円の中心を求める。

9.円の中心とCAおよびCBの座標から円弧の角度を求める。

10.8で求めた円の中心、9で求めた円弧の角度から円弧を描画する。

おつかれさまでした。

円弧を赤、二次ベジェ曲線を緑で描画しています。
arcで描画bezierCurveToで描画

コードにする以下のような感じです。

HTML

1<!DOCTYPE html> 2<html> 3 4<head> 5 <meta charset="UTF-8"> 6</head> 7 8<body> 9 A:<input id="input_xA" value="57" /> , <input id="input_yA" value="83" /><br /> 10 P:<input id="input_xP" value="336" /> , <input id="input_yP" value="123" /><br /> 11 B:<input id="input_xB" value="346" /> , <input id="input_yB" value="300" /><br /> 12 半径:<input id="input_r" value="40" /><br /> 13 <input type="checkbox" id="arc" checked /><label for="arc">arc</label><input type="checkbox" id="bezierCurveTo" checked /><label for="bezierCurveTo">bezierCurveTo</label><input type="checkbox" id="quadraticCurveTo" /><label for="quadraticCurveTo">quadraticCurveTo</label><br /> 14 <canvas id="canvas" width="500" height="500"></canvas> 15</body> 16 17<script> 18 19const canvas = document.getElementById('canvas'); 20 21function onInput() { 22 let xA = input_xA.value; 23 let yA = input_yA.value; 24 const xP = input_xP.value; 25 const yP = input_yP.value; 26 let xB = input_xB.value; 27 let yB = input_yB.value; 28 const r = input_r.value; 29 30 const ctx = canvas.getContext('2d'); 31 ctx.save(); 32 ctx.fillStyle = 'white'; 33 ctx.fillRect(0, 0, 500, 500); 34 35 ctx.beginPath(); 36 ctx.strokeStyle = 'black'; 37 ctx.moveTo(xA, yA); 38 ctx.lineTo(xP, yP); 39 ctx.lineTo(xB, yB); 40 ctx.stroke(); 41 42 // Pを原点とした座標(ベクトル)を求める。 43 xA -= xP; 44 yA -= yP; 45 xB -= xP; 46 yB -= yP; 47 48 // 1.Aの単位ベクトルを求める。 49 const xUA = xA / Math.sqrt(xA ** 2 + yA ** 2); 50 const yUA = yA / Math.sqrt(xA ** 2 + yA ** 2); 51 52 // 1.Bの単位ベクトルを求める。 53 const xUB = xB / Math.sqrt(xB ** 2 + yB ** 2); 54 const yUB = yB / Math.sqrt(xB ** 2 + yB ** 2); 55 56 // 2.Aの単位ベクトルとBの単位ベクトルから角度を2等分するベクトルMを求める。 57 const xM = xUA + xUB; 58 const yM = yUA + yUB; 59 60 // 3.ベクトルMの単位ベクトルを求める。 61 const xUM = xM / Math.sqrt(xM ** 2 + yM ** 2); 62 const yUM = yM / Math.sqrt(xM ** 2 + yM ** 2); 63 64 // ベクトルMを描画してみる。 65 ctx.translate(xP, yP); // canvasの原点を中点Pに移動する。 66 ctx.beginPath(); 67 ctx.moveTo(0, 0); 68 ctx.lineTo(xUM * 1000, yUM * 1000); // 単位ベクトルは小さすぎるので1000倍しておく 69 ctx.stroke(); 70 71 // 4.ベクトルAとベクトルMからcosθを求める。 72 cos = (xA * xM + yA * yM) / (Math.sqrt(xA ** 2 + yA ** 2) * Math.sqrt(xM ** 2 + yM ** 2)); 73 console.log('cosθ: ' + cos); 74 // 検算用にベクトルBでも計算してみる。 75 cos2 = (xB * xM + yB * yM) / (Math.sqrt(xB ** 2 + yB ** 2) * Math.sqrt(xM ** 2 + yM ** 2)); 76 console.log('cosθ: ' + cos2); 77 78 // 5/6.半径とcosθから隣辺の長さを求める。 79 sin = Math.sqrt(1 - cos ** 2); // 後で使うのでsinθを求める。 80 tan = sin / cos; // tanθを求める。 81 s = r / tan; 82 console.log('隣辺: ' + s); 83 84 // 7.単位ベクトルと隣辺の長さから接点の座標を求める。 85 const xCA = xUA * s; 86 const yCA = yUA * s; 87 const xCB = xUB * s; 88 const yCB = yUB * s; 89 90 // 制御点をPとした二次ベジェを描画してみる。 91 if (quadraticCurveTo.checked) { 92 ctx.beginPath(); 93 ctx.strokeStyle = 'green'; 94 ctx.moveTo(xCA, yCA); 95 ctx.quadraticCurveTo(0, 0, xCB, yCB); 96 ctx.stroke(); 97 } 98 99 // 8.円の中心を求める。 100 // 円の中心ベクトルはベクトルMの単位ベクトルに対辺の長さを掛けたものになる。 101 const xE = xUM * r / sin; 102 const yE = yUM * r / sin; 103 104 // 参考のために円を描画する。 105 ctx.beginPath(); 106 ctx.strokeStyle = 'black'; 107 ctx.arc(xE, yE, r, 0, 2 * Math.PI); 108 ctx.stroke(); 109 110 // 9.A接点の角度を求める。 111 const thetaCA = Math.atan2(yCA - yE, xCA - xE); 112 113 // 9.B接点の角度を求める。 114 const thetaCB = Math.atan2(yCB - yE, xCB - xE); 115 116 // 10.三次ベジェを描画してみる。 117 if (bezierCurveTo.checked) { 118 // κ値を計算する。 119 // https://cat-in-136.github.io/2014/03/bezier-3-general-theta.html 120 const k = 4 * Math.tan(Math.abs(thetaCA - thetaCB) / 4) / 3; 121 122 // A側制御点 123 const xCPA = xCA - xUA * r * k; 124 const yCPA = yCA - yUA * r * k; 125 ctx.beginPath(); 126 ctx.fillStyle = 'blue'; 127 ctx.arc(xCPA, yCPA, 2, 0, 2 * Math.PI); 128 ctx.fill(); 129 130 // B側制御点 131 const xCPB = xCB - xUB * r * k; 132 const yCPB = yCB - yUB * r * k; 133 ctx.beginPath(); 134 ctx.fillStyle = 'blue'; 135 ctx.arc(xCPB, yCPB, 2, 0, 2 * Math.PI); 136 ctx.fill(); 137 138 ctx.beginPath(); 139 ctx.strokeStyle = 'blue'; 140 ctx.moveTo(xCA, yCA); 141 ctx.bezierCurveTo(xCPA, yCPA, xCPB, yCPB, xCB, yCB); 142 ctx.stroke(); 143 } 144 145 // 10.円弧を描画する。 146 if (arc.checked) { 147 ctx.beginPath(); 148 ctx.strokeStyle = 'red'; 149 ctx.arc(xE, yE, r, Math.min(thetaCA, thetaCB), Math.max(thetaCA, thetaCB)); 150 ctx.stroke(); 151 } 152 153 ctx.restore(); 154} 155 156input_xA.addEventListener('input', onInput, false); 157input_yA.addEventListener('input', onInput, false); 158input_xP.addEventListener('input', onInput, false); 159input_yP.addEventListener('input', onInput, false); 160input_xB.addEventListener('input', onInput, false); 161input_yB.addEventListener('input', onInput, false); 162input_r.addEventListener('input', onInput, false); 163arc.addEventListener('change', onInput, false); 164bezierCurveTo.addEventListener('change', onInput, false); 165quadraticCurveTo.addEventListener('change', onInput, false); 166 167onInput(); 168 169</script> 170 171</html>

Chrome と Edge で確認しています。
単一のHTMLですが github にも上げてみました。
https://github.com/atata0319/teratail139697

なお、偉そうにいってますが、上記のコードでは三次ベジェが正しく描画されていないように思えます。私が作った数式は間違っている以外に考えられないのですが、問題を見つけることができませんでした。


問題点が分かりました。最初のプログラムではκ値を固定値としていましたが、2つの線分の角度によってκ値を計算する必要があったようです。
https://cat-in-136.github.io/2014/03/bezier-3-general-theta.html
文中のコードも修正してあります。結果として2点の角度を求める必要があるため、arc で処理するのも bezierCurveTo も計算量は同じになりました。cosθから計算することもできますが、数式を考えるのが面倒なのでやってません。

投稿2018/08/06 16:07

編集2018/08/07 14:00
atata0319

総合スコア881

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nekorisu8150

2018/08/15 16:22

ご回答ありがとうございます。 返信が遅くなり申し訳ありません。 詳しい説明とわかりやすいコードの提示に感謝致します。 ご教授いただいた内容を参考に、考えていたことを再現させることができました。 ありがとうございました。
guest

0

ABの角度の半分の角度で線引いて、そこから直角にAの線におろしてrの距離になる点を求める。
#円弧に接する直線は必ず中心から直角
そこを中心にしてABの直角三角形どおしで弧を書けばよろしい

投稿2018/08/05 07:20

y_waiwai

総合スコア87784

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問