歯車について勉強する2 の履歴(No.7)

更新


工作/歯車について勉強する

歯車について勉強するシリーズ

ラックを使って歯車を切り出す

歯車について勉強する でやったようにインボリュート曲線やトロコイド曲線、歯先や歯底やフィレット用の円弧を繋げて歯車形状を作ろうとすると、特に歯元の形状をきれいに作るのがとても難しい。

実際の歯車が製作される手順と同様に、仮想的な切削用のラックを想定して、ラックの歯形により切り出される歯車形状を計算すると実際の歯車と同様の自然な歯形を生成できるようだ。

参考: https://involutegearsoft.hatenablog.com/entry/involute-trochoid-draw-tool2

下図では緑色が切削用のラックであり、赤色の歯車はこのラックの上を転がることによりラックと重なる部分が切削されて形状が定まる。下図ではラックと接している点に小丸が描かれるので、ラックのどの部分で切削されるかを感じ取れる。

ラックは通常の歯車歯形と比べて歯先に太い点線で示した曲線の分だけ高くなっていて、その部分で相手の歯元に必要な隙間を生じる。またこの部分にはフィレットを付けることが多い(そのため曲線になっている)。

LANG: p5js_live
var t;let e;const a=[];function s(t,e,s,h){let r=20*s+20,n=t.createSpan(e).position(20,r),i=t.createSpan(String(h.value));i.position(100,r);let l=t.createSlider(h.min,h.max,h.value,h.step);return l.position(150,r),l.size(300),l.elt.oninput=()=>i.html(String(l.value())),a.push(n,i,l),l}function h(t,e,s,r=0,n=!1){let i=100*r+20,l=20*s+20,o=t.createCheckbox();o.position(i,l),o.checked(n);let u=t.createSpan(e).position(i+20,l);return u.mouseClicked(()=>{o.checked(!o.checked()),o.elt.onchange&&o.elt.onchange()}),u.style("cursor","pointer "),a.push(u,o),o}class r{constructor(t,e){this.x=t,this.y=e}static polar(t,e){return new r(t*Math.cos(e),t*Math.sin(e))}angle(){return Math.atan2(this.y,this.x)}equal(t){return 1e-6>Math.abs(this.x-t.x)&&1e-6>Math.abs(this.y-t.y)}add(t){return new r(this.x+t.x,this.y+t.y)}sub(t){return new r(this.x-t.x,this.y-t.y)}mul(t){return new r(this.x*t,this.y*t)}rotate(t){let e=Math.cos(t),a=Math.sin(t);return new r(this.x*e-this.y*a,this.x*a+this.y*e)}addRotated(t,e){return this.add(e.rotate(t))}flipY(){return new r(this.x,-this.y)}flipX(){return new r(-this.x,this.y)}norm(){return Math.sqrt(this.x*this.x+this.y*this.y)}normalize(t=1){let e=this.norm()/t;return new r(this.x/e,this.y/e)}polar(){return[this.norm(),this.angle()]}inner(t){return this.x*t.x+this.y*t.y}outer(t){return this.x*t.y-this.y*t.x}}function n(t,e){return new r(t,e)}class i extends Array{constructor(t,...e){t instanceof r?(super(t,...e),this.option={}):(super(...e),this.option=t??{})}static circle(t,e=n(0,0),a={},s=100){let h=new i(a);for(let a=0;a<=s;a++){let r=2*Math.PI*a/s;h.push(n(t*Math.cos(r),t*Math.sin(r)).add(e))}return h}draw(t){if(void 0===this.option?.stroke?t.stroke("black"):t.stroke(this.option.stroke),void 0===this.option?.weight?t.strokeWeight(1):t.strokeWeight(this.option.weight),void 0===this.option?.dash?t.drawingContext.setLineDash([]):t.drawingContext.setLineDash(this.option.dash),void 0===this.option?.fill){t.noFill(),t.beginShape();for(let e=0;e<this.length;e++)t.vertex(this[e].x,this[e].y);t.endShape()}else{t.fill(this.option.fill),t.beginShape();for(let e=0;e<this.length;e++)t.vertex(this[e].x,this[e].y);t.endShape(t.CLOSE)}}apply(t,e=this.option){return new i(e,...this.filter(t=>t).map(t))}crossPoint(t){return l(this[0],this[1],t[0],t[1])}direction(t=0){return this[t].sub(this[t+1]).normalize()}}function l(t,e,a,s){let h=e.sub(t),r=s.sub(a),n=a.sub(t),i=h.outer(r);if(0==i)return;let l=n.outer(h)/i,o=n.outer(r)/i;if(!(l<0)&&!(l>1)&&!(o<0)&&!(o>1))return t.add(h.mul(o))}function o(t,e,a){let s=a.sub(e),h=t.sub(e);return Math.abs(s.outer(h)/s.norm())}function u(t,e,a,s=1e-6){for(;t+s<e;){let s=t+(e-t)/3,h=e-(e-t)/3;a(s)<a(h)?e=h:t=s}return(t+e)/2}const c={width:600,height:600,ox:0,oy:0};(t=p).setup=()=>{let{width:r,height:n}=c;c.ox=r/2,c.oy=n/2,t.createCanvas(r,n).style("position","relative"),e=function(t){let e=h(t,"ツールを非表示",27.5,0),r={m:s(t,"モジュール",0,{min:50,max:800,value:75,step:1}),z:s(t,"歯数",1,{min:4,max:100,value:6,step:1}),alpha:s(t,"圧力角",2,{min:10,max:32,value:20,step:1}),shift:s(t,"転位",3,{min:-.5,max:2,value:0,step:.05}),fillet:s(t,"フィレット",4,{min:0,max:.4,value:.4,step:.01}),backlash:s(t,"バックラッシュ",5,{min:0,max:2,value:0,step:.01}),theta:s(t,"回転角",6,{min:-90,max:90,value:0,step:.05}),play:h(t,"自動更新",7.5,0,!0),stop:!1};return a.forEach((t,e)=>{t.style("opacity","0.2"),t.mouseOver(()=>a.forEach(t=>t.style("opacity","1"))),t.mouseOut(()=>a.forEach(t=>t.style("opacity","0.2")))}),r.theta.mousePressed(()=>{r.stop=!0}),r.theta.mouseReleased(()=>{r.stop=!1}),e.elt.onchange=()=>{a.forEach((t,a)=>{a<2||(e.checked()?t.hide():t.show())})},{get m(){return Number(r.m.value())},get z(){return Number(r.z.value())},get alpha(){return Number(r.alpha.value())/180*Math.PI},get shift(){return Number(r.shift.value())*this.m},get theta(){return-(Number(r.theta.value())/180)*Math.PI},set theta(value){r.theta.value(-(180*value)/Math.PI),r.theta.elt.oninput()},get fillet(){return Number(r.fillet.value())*this.m},get mk(){return+this.m},get mf(){return 1.25*this.m},get rp(){return this.m*this.z/2},get rf(){return this.m*this.z/2-this.mf},get rk(){return this.m*this.z/2+this.mk},get backlash(){return Number(r.backlash.value())/100*2*Math.PI/this.z},get play(){return r.play.checked()&&!r.stop}}}(t)},t.draw=()=>{let a;let{ox:s,oy:h}=c;t.background(220);let{m:d,z:m,alpha:f,shift:y,theta:g,rp:w,rk:M,rf:k,backlash:x,play:b}=e,{sin:v,max:P,PI:I,cos:z,abs:G,min:S,ceil:R}=Math;if(b){let t=e.theta-.1*I/180;t<=-I/2&&(t=I/2),e.theta=t}let[E,N,C,B,q]=function(t){let e,{m:a,z:s,alpha:h,shift:r,theta:l,fillet:o,mf:u,mk:c,rp:d,rk:m}=t,f=[];o=function(t){let{m:e,alpha:a,fillet:s,mf:h,mk:r}=t;return Math.max(s=Math.min(s,(h-r)/(1-Math.sin(a)),(e*Math.PI/4-(e+h-r)*Math.tan(a))/Math.tan(Math.PI/4-a/2)),0)}(t);let y=Math.PI*a,g=new i({stroke:"Green",dash:[5,5],weight:1},n(-y-u*Math.tan(h),d+u+r),n(-y+c*Math.tan(h),d-c+r),n(-y/2-c*Math.tan(h),d-c+r),n(-y/2+u*Math.tan(h),d+u+r),n(-u*Math.tan(h),d+u+r),n(c*Math.tan(h),d-c+r),n(y/2-c*Math.tan(h),d-c+r),n(y/2+u*Math.tan(h),d+u+r),n(y-u*Math.tan(h),d+u+r),n(y+c*Math.tan(h),d-c+r),n(3*y/2-c*Math.tan(h),d-c+r),n(3*y/2+u*Math.tan(h),d+u+r));f.push(g);let w=new i({stroke:"Green",dash:[3,3],weight:2},n(-u*Math.tan(h),d+u+r),n(u*Math.tan(h),d-u+r));f.push(w);let M=new i({stroke:"Green",dash:[3,3],weight:2},n(u*Math.tan(h),d-u+r),n(a*Math.PI/4,d-u+r));f.push(M);let k=new i({stroke:"Green",dash:[10,6,4,6]},n(a*Math.PI/4,0),n(a*Math.PI/4,m+a+r));if(f.push(k),o>0){let t=w.direction().rotate(-Math.PI/2),a=w.apply(e=>e.add(t.mul(o))),s=M.apply(t=>t.add(n(0,o)));(e=a.crossPoint(s))||(e=a.crossPoint(k));let r=new i({stroke:"Green",dash:[3,3],weight:2});if(e){let t=h+Math.PI,a=3*Math.PI/2;for(let s=0;s<=20;s++){let h=t+(a-t)*s/20;r.push(n(e.x+o*Math.cos(h),e.y+o*Math.sin(h)))}f.push(r),w[1]=r[0],M[0]=r[r.length-1]}else e=w[1]}else{let t=w.crossPoint(k);t?(w[1]=t,M.splice(0),e=t):e=w[1]}f.push(i.circle(a/50,e,{stroke:"Green",fill:"Green"}));let x=new i({stroke:"Green",weight:2},n(c*Math.tan(h),d-c+r),n(a*Math.PI/4,d-c+r)),b=new i({stroke:"Green",weight:2},w[0],x[0]);return f.push(x,b),w[1].equal(x[0])?w.splice(0):w[0]=x[0],f.push(new i({stroke:"Green",dash:[10,10]},n(-a*Math.PI,d+r),n(a*Math.PI,d+r))),[f,b[0],b[1],e,o]}(e),L=[],O=(t,e)=>e.add(n(w*t,0)).rotate(t),Y=(t,e)=>e.rotate(t+I/2).add(n(w*(z(t)-t*v(t)),w*(v(t)+t*z(t))));E=E.map(t=>t.apply(t=>O(g,t)));let D=t=>{let e=O(t,N),a=O(t,C).sub(e),s=Y(t,N),h=Y(t,C).sub(s),r=s.outer(a)/h.outer(a);return[e.sub(a.mul(r)),r]},W,X,A=[];W=u(0,90-f,t=>G(D(t)[0].norm()-M-y)),D(W)[0].angle()>I*(1+1/m)/2-x&&(W=u(0,W,t=>G(D(t)[0].angle()-(I*(1+1/m)/2-x)))),X=u(0,-2*f,t=>D(t)[0].norm());let F=new i({stroke:"Red",weight:2});for(let t=0;t<100;t++){let e=W+(X-W)*t/100;F.push(D(e)[0]),A.push(e)}let j=new i({stroke:"Red",weight:2});if(F[0].angle()-I*(1+1/m)/2+x<-.001){let t=F[0].angle(),e=I*(1+1/m)/2-x;for(let a=0;a<=20;a++){let s=t+(e-t)*a/20;j.push(n((M+y)*z(s),(M+y)*v(s)))}}L.push(j);let H=new i({stroke:"Green",dash:[2,2]}),J,K,Q=[];for(J=0;;J-=.01){let t=B.add(n(w*J,0)).rotate(J);if(H.unshift(t),t.norm()>w+y+d/4)break}for(K=.01;;K+=.01){let t=B.add(n(w*K,0)).rotate(K);if(H.push(t),t.norm()>w+y+d/4)break}E.push(H);let T=new i({stroke:"Red",weight:2}),U=t=>{let e=O(t,B),a=Y(t,B).normalize(q),s=e.addRotated(-I/2,a),h=e.addRotated(I/2,a);return s.x+s.y<h.x+h.y?s:h},V=u(J,K,t=>U(t).norm());O(V,B).norm()>w&&([J,K]=[K,J]),K=V;let Z=!1,$=1,_=J;for(let t=0;t<=100;t+=$){let e=J+(K-J)*t/100,s=U(e);Z||s.norm()>C.norm()&&s.sub(C).angle()<I/2+f||(Z=!0,_=e,T.push(a),Q.push(e-(K-J)*$/100)),Z&&(T.push(s),Q.push(e));let h=O(e,B);if(t>0&&h.sub(O(V,B)).norm()<d/1e3){let t=-G(s.sub(O(V,B)).angle()),e=-I*(1+1/m)/2+x,a=R(G(e-t)/I*180);for(let s=1;s<=a;s++){let h=t+(e-t)*s/a;T.push(O(V,B).add(r.polar(q,h)))}break}t>0&&s.sub(a).norm()>d/100&&($/=2),a=s}J=_,(()=>{F=F.apply(t=>t),T=T.apply(t=>t);let[t,e,a]=function(t,e){let a=0,s=0;for(;a<t.length-1&&s<e.length-1;){if(t[a+1].y>e[s].y){a++;continue}if(t[a].y<e[s+1].y){s++;continue}let h=l(t[a],t[a+1],e[s],e[s+1]);if(h)return[h,a,s];t[a+1].y>e[s+1].y?a++:s++}return[]}(F,T);if(t)F.splice(e+1,1/0,t)
T.splice(0a+1t)X=A[e]J=Q[a];else{let[teas]=function(te){let a=0s=0h=[1/0];for(;a<t.length-1&&s<e.length-1;){if(t[a+1].y>e[s].y){a++;continue}if(t[a].y<e[s+1].y){s++;continue}if(t[a].y<e[s].y){let r=o(t[a]e[s]e[s+1]);r<h[0]&&(h=[ras0])}else{let r=o(e[s]t[a]t[a+1]);r<h[0]&&(h=[ras1])}t[a+1].y>e[s+1].y?a++:s++}return h}(FT);s?(F.splice(e+11/0T[a])T.splice(0a)):(F.splice(e+11/0)T.splice(0a+1F[e]))X=A[e]J=Q[a]}})()L.push(F)L.push(T);let tt=D(g)[0];X<=g&&g<=W&&E.push(i.circle(5tt));let te=U(g);S(JK)<=g&&g<=P(JK)&&E.push(i.circle(5te{stroke:"Green"}));let ta=new i({stroke:"Red"weight:2});if(T.length>0){let t=T[T.length-1].angle()e=T[T.length-1].norm();if(t-I*(1-1/m)/2+x>.001){let a=I*(1-1/m)/2-x;for(let s=0;s<20;s++){let h=t+(a-t)*s/20;ta.push(r.polar(eh))}}}L.push(ta)E.push(new i({stroke:"Blue"dash:[10636]}n(00)n(0M+d+y)));let ts=new i({stroke:"Blue"dash:[1010]})th=new i({stroke:"Blue"dash:[1010]})tr=new i({stroke:"Blue"dash:[1010]});for(let t=0;t<=100;t++){let e=I*t/100;tr.push(n(k*z(e)k*v(e)))ts.push(n(w*z(e)w*v(e)))y&&th.push(n((w+y)*z(e)(w+y)*v(e)))}E.push(tstrth)E.push(new i({stroke:"Red"weight:1dash:[10636]}n(00)n((M+d+y)*z(I*(1-1/m)/2)(M+d+y)*v(I*(1-1/m)/2))))E.push(new i({stroke:"Red"weight:1dash:[10636]}n(00)n((M+d+y)*z(I*(1+1/m)/2)(M+d+y)*v(I*(1+1/m)/2))))(E=E.map(t=>t.apply(t=>t.flipY().add(n(sh+w))))).forEach(e=>e.draw(t))L.push(...L.map(t=>t.apply(t=>t.rotate(-I/m/2+x).flipX().rotate(I/m/2-x){...t.optionweight:1dash:[55]})))L.push(...L.map(t=>t.apply(t=>t.rotate(2*I/m){...t.optionweight:1dash:[55]})))(L=(L=(L=[...L...L.map(t=>t.apply(t=>t.rotate(-4*I/m){...t.optionweight:1dash:[55]}))]).map(t=>t.apply(t=>t.rotate(x)))).map(t=>t.apply(t=>t.flipY().add(n(sh+w))))).forEach(e=>e.draw(t))};

ラックの形状

ラックは歯数を無限大にした歯車と考えられる。

  • 歯の角度 = 圧力角 $\alpha$
  • 基準高さで歯と隙間とが1:1になる
  • 歯末の高さ $m$ = 基準高さより上の長さ
  • 歯元の高さ $1.25m$ = 基準高さより下の長さ

歯車切削用のラックの形状

歯車切削用のラックの形状は通常のラックと書きの点で異なる:

  • 歯末の高さは $m$ ではなく相手の歯末の高さと等しく $1.25m$ になっている
  • 必要に応じて歯末にフィレットを付ける
    • フィレットは本来の歯末の高さまでの部分に影響しないようその上の部分に付ける

計算上での切削

歯車とラックとの相対運動

切削用のラックは歯車が回転すると歯車の基準円の円周と同じ速度で平行移動する。

本来はラックが直線運動、歯車が回転運動するのだけれど、 歯車を基準に考えるとラックが「回転運動しながら直線運動する」

すなわち、相対的な回転角が $\theta$ であるときのラックの位置は、

  • ラックを $r_p\theta$ だけ直線移動する
  • それを歯車原点を中心に $\theta$ 回転する

として求まる。

$\theta$ を連続的に変えたときのラック外形の包絡線が歯車形状を切り出すことになる。

ラックの先端部分の包絡線

ラックの先端に半径 $r$ のフィレットが付いているとする($r=0$ とすればフィレットなしを表す)。

フィレットの中心の軌跡は上記の通り直線運動+回転運動で求まる(上図の緑の破線で示された曲線)。

この曲線の上を円の中心が通った時の円周の軌跡は、中心の軌跡に垂直にフィレット半径だけ離れた点を結ぶことで得られる。

回転角 $\theta=0$ における中心座標を $\bm p(\theta=0)=\begin{pmatrix}x\\y\end{pmatrix}$ とすると、歯車から見たこの点の軌跡は $x$ 方向へ $r_p\theta$ 移動し $\begin{pmatrix}x+r_p\theta\\y\end{pmatrix}$、それを $\theta$ 回転すれば求まるので、

$$ \bm p(\theta)=\begin{pmatrix} (x+r_p\theta)\cos\theta-y\sin\theta\\ (x+r_p\theta)\sin\theta+y\cos\theta\\ \end{pmatrix} $$

となる。

この曲線に垂直にフィレット半径を取るには曲線の方向ベクトルが必要になるが、方向ベクトルは点の座標を $\theta$ で微分すれば求まる。

$$ \begin{aligned} \frac{\partial\bm p}{\partial\theta}=\partial_\theta\bm p= &\begin{pmatrix} r_p\cos\theta-(x+r_p\theta)\sin\theta-y\cos\theta\\ r_p\sin\theta+(x+r_p\theta)\cos\theta-y\sin\theta\\ \end{pmatrix}\\ &=\begin{pmatrix} r_p(\cos\theta-\theta\sin\theta)-x\sin\theta-y\cos\theta\\ r_p(\sin\theta+\theta\cos\theta)+x\cos\theta-y\sin\theta\\ \end{pmatrix}\\ &=\begin{pmatrix} r_p(\cos\theta-\theta\sin\theta)+x\cos(\theta-\pi/2)-y\sin(\theta-\pi/2)\\ r_p(\sin\theta+\theta\cos\theta)+x\sin(\theta-\pi/2)+y\cos(\theta-\pi/2)\\ \end{pmatrix}\\ \end{aligned} $$

上図で転位のパラメータを変更してみるとわかるように、「フィレット中心の軌跡の先端」が $r_p$ と一致するとき、軌跡には微分不可能な点が生じるため、微分により方向ベクトルを求めることができなくなる。

フィレット中心が軌跡の先端に十分に近づいたら、あとはその点を中心に円を描くようにするとこの問題を回避できるようだった。

$y$ 方向へ $-r_p\theta$ 移動し $\begin{pmatrix}x_c\\y_c-r_p\theta\end{pmatrix}$ それを $\theta$ 回転するなら、 $$ \begin{pmatrix} x_c\cos\theta-(y_c-r_p\theta)\sin\theta\\ x_c\sin\theta+(y_c-r_p\theta)\cos\theta\\ \end{pmatrix} $$ で微分すると、 $$ \begin{pmatrix} r_p(\sin\theta+\theta\cos\theta)+x_c\cos(\theta-\pi/2)-y_c\sin(\theta-\pi/2)\\ r_p(-\cos\theta+\theta\sin\theta)+x_c\sin(\theta-\pi/2)+y_c\cos(\theta-\pi/2)\\ \end{pmatrix} $$ となる。

LANG:js
   // 直進&回転
   const involute= (t: number, p: Vector) =>
     p.add(vec(0, -rp * t)).rotate(t);

   // 直進&回転の微分
   const dInvolute = (t: number, p: Vector) =>
     p.rotate(t + PI / 2).add(
       vec(
         rp * (sin(t) + t * cos(t)), //
         rp * (-cos(t) + t * sin(t))
       )
     );

ラックの直線部分の包絡線

ラックの直線部分が歯車の歯先の形状を切り出すことになる。

ラックの直線部分の端点の座標を $\bm p_1(\theta),\bm p_2(\theta)$ とする。 この2点を結ぶ直線が生成する包絡線を曲線として得たい。

この包絡線は $\bm p_1(\theta),\bm p_2(\theta)$ を結ぶ直線と $\bm p_1'=\bm p_1(\theta+\Delta\theta),\bm p_2'=\bm p_2(\theta+\Delta\theta)$ を結ぶ直線との交点を通る。

2つの線分の交点は例えば

https://qiita.com/zu_rin/items/09876d2c7ec12974bc0f

などで解説されている通り、

$$ \bm p=\bm p_1+\frac {(\bm p_1'-\bm p_1)\times(\bm p_2'-\bm p_1')} {(\bm p_2-\bm p_1)\times(\bm p_2'-\bm p_1')} (\bm p_2-\bm p_1) $$

と表せる。

$\partial \bm p_i/\partial \theta = \partial_\theta\bm p_i$ と書くことにすると、

$$ \begin{aligned} &\bm p_1'-\bm p_1=\partial_\theta\bm p_1\Delta\theta\\ &\Delta \bm p=\bm p_2-\bm p_1\\ &\Delta \bm p'=\bm p_2'-\bm p_1'=\Delta \bm p+(\partial_\theta\bm p_2-\partial_\theta\bm p_1)\Delta\theta\\ \end{aligned} $$

であるから、

$$ \begin{aligned} \bm p(\theta) &=\bm p_1+\frac {\partial_\theta\bm p_1\Delta\theta\times (\bm p_2'-\bm p_1')} {\Delta\bm p\times\{\cancel{\Delta\bm p}+(\partial_\theta\bm p_2-\partial_\theta\bm p_1)\Delta\theta\}}\Delta \bm p\\ &=\bm p_1+\frac {\partial_\theta\bm p_1\cancel{\Delta\theta}\times (\bm p_2'-\bm p_1')} {\Delta\bm p\times(\partial_\theta\bm p_2-\partial_\theta\bm p_1)\cancel{\Delta\theta}}\Delta \bm p\\ &=\bm p_1+\frac {\partial_\theta\bm p_1\times (\bm p_2'-\bm p_1')} {\Delta\bm p\times(\partial_\theta\bm p_2-\partial_\theta\bm p_1)}\Delta \bm p\\ &\to\bm p_1+\frac {\partial_\theta\bm p_1\times (\bm p_2-\bm p_1)} {\Delta\bm p\times(\partial_\theta\bm p_2-\partial_\theta\bm p_1)}\Delta \bm p\\ &=\bm p_1-\frac {\partial_\theta\bm p_1\times \Delta\bm p} {(\partial_\theta\bm p_2-\partial_\theta\bm p_1)\times \Delta\bm p}\Delta \bm p\\ \end{aligned} $$

として回転角 $\theta$ をパラメータとしてラックと歯車との接点の座標 $\bm p(\theta)$ を求められる。

そのようにして求めた曲線は 歯車について勉強する で説明したインボリュート曲線に重なるのであるが、転位を与えた場合などはインボリュート曲線と歯元のトロコイド曲線との間の部分にもラックの直線部分の軌跡が現れるようだった。

LANG:js
   // ラックの直線部分 (点 r1 と点 r2 を通る) の包絡線
   const lineTrace = (t: number) => {
     const p1 = shiftRotate(t, r1);
     const Dp = shiftRotate(t, r2).sub(p1);
     const dp1 = dShiftRotate(t, r1);
     const dDp = dShiftRotate(t, r2).sub(dp1);
     const s = dp1.outer(Dp) / dDp.outer(Dp);
     return [p1.sub(Dp.mul(s)), -s] as [Vector, number];
   };

-s が 0 から 1 の間にあれば接点は2点の間にあることになる。範囲外なら延長線上にあることになる。

コメント・質問





Counter: 150 (from 2010/06/03), today: 2, yesterday: 2